Merge pull request #7175 from neilLasrado/develop
Student Attendance Tool
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 2d1a24d..97ab934 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -2,7 +2,7 @@
from __future__ import unicode_literals
import frappe
-__version__ = '7.1.21'
+__version__ = '7.1.23'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index fc6a1b4..8c7925a 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -30,8 +30,8 @@
options: ['Asset', 'Liability', 'Equity', 'Income', 'Expense'].join('\n'),
depends_on: 'eval:doc.is_group && !doc.parent_account'},
{fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
- options: ['', 'Accumulated Depreciation', 'Bank', 'Cash', 'Chargeable', 'Cost of Goods Sold', 'Depreciation',
- 'Equity', 'Expense Account', 'Expenses Included In Valuation', 'Fixed Asset', 'Income Account', 'Payable', 'Receivable',
+ options: ['', 'Accumulated Depreciation', 'Bank', 'Cash', 'Chargeable', 'Cost of Goods Sold', 'Depreciation',
+ 'Equity', 'Expense Account', 'Expenses Included In Valuation', 'Fixed Asset', 'Income Account', 'Payable', 'Receivable',
'Round Off', 'Stock', 'Stock Adjustment', 'Stock Received But Not Billed', 'Tax', 'Temporary'].join('\n'),
description: __("Optional. This setting will be used to filter in various transactions.")
},
diff --git a/erpnext/accounts/doctype/budget/budget.js b/erpnext/accounts/doctype/budget/budget.js
index f6a2c88..40e929a 100644
--- a/erpnext/accounts/doctype/budget/budget.js
+++ b/erpnext/accounts/doctype/budget/budget.js
@@ -10,6 +10,14 @@
}
}
})
+
+ frm.set_query("project", function() {
+ return {
+ filters: {
+ company: frm.doc.company
+ }
+ }
+ })
frm.set_query("account", "accounts", function() {
return {
diff --git a/erpnext/accounts/doctype/budget/budget.json b/erpnext/accounts/doctype/budget/budget.json
index d88f374..d081364 100644
--- a/erpnext/accounts/doctype/budget/budget.json
+++ b/erpnext/accounts/doctype/budget/budget.json
@@ -15,47 +15,19 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "cost_center",
- "fieldtype": "Link",
+ "default": "Cost Center",
+ "fieldname": "budget_against",
+ "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 1,
- "label": "Cost Center",
+ "label": "Budget Against",
"length": 0,
"no_copy": 0,
- "options": "Cost Center",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "fiscal_year",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Fiscal Year",
- "length": 0,
- "no_copy": 0,
- "options": "Fiscal Year",
+ "options": "\nCost Center\nProject",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -90,7 +62,96 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
- "remember_last_selected_value": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "eval:doc.budget_against == 'Cost Center'",
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 1,
+ "label": "Cost Center",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cost Center",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "eval:doc.budget_against == 'Project'",
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 1,
+ "label": "Project",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Project",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "fiscal_year",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Fiscal Year",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Fiscal Year",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -310,7 +371,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-11-07 05:50:57.064961",
+ "modified": "2016-11-30 08:51:10.453935",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget",
diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py
index 2488dd6..5fc4fbf 100644
--- a/erpnext/accounts/doctype/budget/budget.py
+++ b/erpnext/accounts/doctype/budget/budget.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+ # -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
@@ -14,20 +14,25 @@
class Budget(Document):
def autoname(self):
- self.name = make_autoname(self.cost_center + "/" + self.fiscal_year + "/.###")
+ self.name = make_autoname(self.get(frappe.scrub(self.budget_against))
+ + "/" + self.fiscal_year + "/.###")
def validate(self):
+ if not self.get(frappe.scrub(self.budget_against)):
+ frappe.throw(_("{0} is mandatory").format(self.budget_against))
self.validate_duplicate()
self.validate_accounts()
def validate_duplicate(self):
- existing_budget = frappe.db.get_value("Budget", {"cost_center": self.cost_center,
+ budget_against_field = frappe.scrub(self.budget_against)
+ budget_against = self.get(budget_against_field)
+ existing_budget = frappe.db.get_value("Budget", {budget_against_field: budget_against,
"fiscal_year": self.fiscal_year, "company": self.company,
"name": ["!=", self.name], "docstatus": ["!=", 2]})
- if existing_budget:
- frappe.throw(_("Another Budget record {0} already exists against {1} for fiscal year {2}")
- .format(existing_budget, self.cost_center, self.fiscal_year), DuplicateBudgetError)
-
+ if existing_budget:
+ frappe.throw(_("Another Budget record '{0}' already exists against {1} '{2}' for fiscal year {3}")
+ .format(existing_budget, self.budget_against, budget_against, self.fiscal_year), DuplicateBudgetError)
+
def validate_accounts(self):
account_list = []
for d in self.get('accounts'):
@@ -51,56 +56,104 @@
def validate_expense_against_budget(args):
args = frappe._dict(args)
- if not args.cost_center:
+ if not args.cost_center and not args.project:
return
-
- if frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}):
- cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"])
+ for budget_against in [args.project, args.cost_center]:
+ if budget_against \
+ and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}):
- budget_records = frappe.db.sql("""
- select ba.budget_amount, b.monthly_distribution, b.cost_center,
- b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded
- from `tabBudget` b, `tabBudget Account` ba
- where
- b.name=ba.parent and b.fiscal_year=%s and ba.account=%s and b.docstatus=1
- and exists(select name from `tabCost Center` where lft<=%s and rgt>=%s and name=b.cost_center)
- """, (args.fiscal_year, args.account, cc_lft, cc_rgt), as_dict=True)
+ if args.project:
+ condition = "and exists(select name from `tabProject` where name=b.project)"
+ args.budget_against_field = "Project"
+
+ elif args.cost_center:
+ cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"])
+ condition = """and exists(select name from `tabCost Center`
+ where lft<=%s and rgt>=%s and name=b.cost_center)""" % (cc_lft, cc_rgt)
+ args.budget_against_field = "Cost Center"
+
+ args.budget_against = budget_against
- for budget in budget_records:
- if budget.budget_amount:
- yearly_action = budget.action_if_annual_budget_exceeded
- monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
+ budget_records = frappe.db.sql("""
+ select
+ b.{budget_against_field}, ba.budget_amount, b.monthly_distribution,
+ b.action_if_annual_budget_exceeded,
+ b.action_if_accumulated_monthly_budget_exceeded
+ from
+ `tabBudget` b, `tabBudget Account` ba
+ where
+ b.name=ba.parent and b.fiscal_year=%s
+ and ba.account=%s and b.docstatus=1
+ {condition}
+ """.format(condition=condition,
+ budget_against_field=frappe.scrub(args.get("budget_against_field"))),
+ (args.fiscal_year, args.account), as_dict=True)
- if monthly_action in ["Stop", "Warn"]:
- budget_amount = get_accumulated_monthly_budget(budget.monthly_distribution,
- args.posting_date, args.fiscal_year, budget.budget_amount)
+ validate_budget_records(args, budget_records)
- args["month_end_date"] = get_last_day(args.posting_date)
-
- compare_expense_with_budget(args, budget.cost_center,
- budget_amount, _("Accumulated Monthly"), monthly_action)
+def validate_budget_records(args, budget_records):
+ for budget in budget_records:
+ if budget.budget_amount:
+ yearly_action = budget.action_if_annual_budget_exceeded
+ monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
- if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \
- and yearly_action != monthly_action:
- compare_expense_with_budget(args, budget.cost_center,
- flt(budget.budget_amount), _("Annual"), yearly_action)
+ if monthly_action in ["Stop", "Warn"]:
+ budget_amount = get_accumulated_monthly_budget(budget.monthly_distribution,
+ args.posting_date, args.fiscal_year, budget.budget_amount)
+ args["month_end_date"] = get_last_day(args.posting_date)
-def compare_expense_with_budget(args, cost_center, budget_amount, action_for, action):
- actual_expense = get_actual_expense(args, cost_center)
+ compare_expense_with_budget(args, budget_amount,
+ _("Accumulated Monthly"), monthly_action)
+
+ if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \
+ and yearly_action != monthly_action:
+ compare_expense_with_budget(args, flt(budget.budget_amount),
+ _("Annual"), yearly_action)
+
+
+def compare_expense_with_budget(args, budget_amount, action_for, action):
+ actual_expense = get_actual_expense(args)
if actual_expense > budget_amount:
diff = actual_expense - budget_amount
- currency = frappe.db.get_value('Company', frappe.db.get_value('Cost Center',
- cost_center, 'company'), 'default_currency')
+ currency = frappe.db.get_value('Company', args.company, 'default_currency')
- msg = _("{0} Budget for Account {1} against Cost Center {2} is {3}. It will exceed by {4}").format(_(action_for),
- frappe.bold(args.account), frappe.bold(cost_center),
- frappe.bold(fmt_money(budget_amount, currency=currency)), frappe.bold(fmt_money(diff, currency=currency)))
+ msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
+ _(action_for), frappe.bold(args.account), args.budget_against_field,
+ frappe.bold(args.budget_against),
+ frappe.bold(fmt_money(budget_amount, currency=currency)),
+ frappe.bold(fmt_money(diff, currency=currency)))
if action=="Stop":
frappe.throw(msg, BudgetError)
else:
frappe.msgprint(msg, indicator='orange')
+
+def get_actual_expense(args):
+ condition1 = " and gle.posting_date <= %(month_end_date)s" \
+ if args.get("month_end_date") else ""
+ if args.budget_against_field == "Cost Center":
+ lft_rgt = frappe.db.get_value(args.budget_against_field,
+ args.budget_against, ["lft", "rgt"], as_dict=1)
+ args.update(lft_rgt)
+ condition2 = """and exists(select name from `tabCost Center`
+ where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)"""
+
+ elif args.budget_against_field == "Project":
+ condition2 = "and exists(select name from `tabProject` where name=gle.project)"
+
+ return flt(frappe.db.sql("""
+ select sum(gle.debit) - sum(gle.credit)
+ from `tabGL Entry` gle
+ where gle.account=%(account)s
+ {condition1}
+ and gle.fiscal_year=%(fiscal_year)s
+ and gle.company=%(company)s
+ and gle.docstatus=1
+ {condition2}
+ """.format(condition1=condition1, condition2=condition2), (args))[0][0])
+
+
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {}
if monthly_distribution:
@@ -121,21 +174,3 @@
dt = add_months(dt, 1)
return annual_budget * accumulated_percentage / 100
-
-def get_actual_expense(args, cost_center):
- lft_rgt = frappe.db.get_value("Cost Center", cost_center, ["lft", "rgt"], as_dict=1)
- args.update(lft_rgt)
-
- condition = " and gle.posting_date <= %(month_end_date)s" if args.get("month_end_date") else ""
-
- return flt(frappe.db.sql("""
- select sum(gle.debit) - sum(gle.credit)
- from `tabGL Entry` gle
- where gle.account=%(account)s
- and exists(select name from `tabCost Center`
- where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)
- and gle.fiscal_year=%(fiscal_year)s
- and gle.company=%(company)s
- and gle.docstatus=1
- {condition}
- """.format(condition=condition), (args))[0][0])
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index ef6a1c8..15895dc 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -10,9 +10,9 @@
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
- set_total_expense_zero("2013-02-28")
+ set_total_expense_zero("2013-02-28", "Cost Center")
- budget = make_budget()
+ budget = make_budget("Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", submit=True)
@@ -22,10 +22,10 @@
budget.cancel()
- def test_monthly_budget_crossed_stop(self):
- set_total_expense_zero("2013-02-28")
+ def test_monthly_budget_crossed_stop1(self):
+ set_total_expense_zero("2013-02-28", "Cost Center")
- budget = make_budget()
+ budget = make_budget("Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
@@ -37,10 +37,25 @@
budget.load_from_db()
budget.cancel()
- def test_yearly_budget_crossed_stop(self):
- set_total_expense_zero("2013-02-28")
+ def test_monthly_budget_crossed_stop2(self):
+ set_total_expense_zero("2013-02-28", "Project")
- budget = make_budget()
+ budget = make_budget("Project")
+
+ frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+
+ jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project")
+
+ self.assertRaises(BudgetError, jv.submit)
+
+ budget.load_from_db()
+ budget.cancel()
+
+ def test_yearly_budget_crossed_stop1(self):
+ set_total_expense_zero("2013-02-28", "Cost Center")
+
+ budget = make_budget("Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC")
@@ -49,10 +64,22 @@
budget.cancel()
- def test_monthly_budget_on_cancellation(self):
- set_total_expense_zero("2013-02-28")
+ def test_yearly_budget_crossed_stop2(self):
+ set_total_expense_zero("2013-02-28", "Project")
- budget = make_budget()
+ budget = make_budget("Project")
+
+ jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC", 150000, "_Test Cost Center - _TC", project="_Test Project")
+
+ self.assertRaises(BudgetError, jv.submit)
+
+ budget.cancel()
+
+ def test_monthly_budget_on_cancellation1(self):
+ set_total_expense_zero("2013-02-28", "Cost Center")
+
+ budget = make_budget("Cost Center")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True)
@@ -72,12 +99,37 @@
budget.load_from_db()
budget.cancel()
+
+ def test_monthly_budget_on_cancellation2(self):
+ set_total_expense_zero("2013-02-28", "Project")
+
+ budget = make_budget("Project")
+
+ jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True, project="_Test Project")
+
+ self.assertTrue(frappe.db.get_value("GL Entry",
+ {"voucher_type": "Journal Entry", "voucher_no": jv1.name}))
+
+ jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True, project="_Test Project")
+
+ self.assertTrue(frappe.db.get_value("GL Entry",
+ {"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
+
+ frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
+
+ self.assertRaises(BudgetError, jv1.cancel)
+
+ budget.load_from_db()
+ budget.cancel()
+
def test_monthly_budget_against_group_cost_center(self):
- set_total_expense_zero("2013-02-28")
- set_total_expense_zero("2013-02-28", "_Test Cost Center 2 - _TC")
+ set_total_expense_zero("2013-02-28", "Cost Center")
+ set_total_expense_zero("2013-02-28", "Cost Center", "_Test Cost Center 2 - _TC")
- budget = make_budget("_Test Company - _TC")
+ budget = make_budget("Cost Center", "_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
@@ -88,28 +140,52 @@
budget.load_from_db()
budget.cancel()
-def set_total_expense_zero(posting_date, cost_center=None):
- existing_expense = get_actual_expense({
+def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
+ if budget_against_field == "Project":
+ budget_against = "_Test Project"
+ else:
+ budget_against = budget_against_CC or "_Test Cost Center - _TC"
+ existing_expense = get_actual_expense(frappe._dict({
"account": "_Test Account Cost for Goods Sold - _TC",
- "cost_center": cost_center or "_Test Cost Center - _TC",
+ "cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date,
"company": "_Test Company",
- "fiscal_year": "_Test Fiscal Year 2013"
- }, cost_center or "_Test Cost Center - _TC")
+ "fiscal_year": "_Test Fiscal Year 2013",
+ "budget_against_field": budget_against_field,
+ "budget_against": budget_against
+ }))
if existing_expense:
- make_journal_entry("_Test Account Cost for Goods Sold - _TC",
+ if budget_against_field == "Cost Center":
+ make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True)
+ elif budget_against_field == "Project":
+ make_journal_entry("_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project")
-def make_budget(cost_center=None):
+def make_budget(budget_against=None, cost_center=None):
+ if budget_against == "Project":
+ budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Project/_Test Fiscal Year 2013%")})
+ else:
+ budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Cost Center - _TC/_Test Fiscal Year 2013%")})
+ for d in budget_list:
+ frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
+ frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
+
budget = frappe.new_doc("Budget")
- budget.cost_center = cost_center or "_Test Cost Center - _TC"
+
+ if budget_against == "Project":
+ budget.project = "_Test Project"
+ else:
+ budget.cost_center =cost_center or "_Test Cost Center - _TC"
+
+
budget.fiscal_year = "_Test Fiscal Year 2013"
budget.monthly_distribution = "_Test Distribution"
budget.company = "_Test Company"
budget.action_if_annual_budget_exceeded = "Stop"
budget.action_if_accumulated_monthly_budget_exceeded = "Ignore"
-
+ budget.budget_against = budget_against
budget.append("accounts", {
"account": "_Test Account Cost for Goods Sold - _TC",
"budget_amount": 100000
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 06ba5df..c98e77f 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -35,6 +35,14 @@
multi_currency: function(frm) {
erpnext.journal_entry.toggle_fields_based_on_currency(frm);
+ },
+
+ posting_date: function(frm) {
+ if(!frm.doc.multi_currency) return;
+
+ $.each(frm.doc.accounts || [], function(i, row) {
+ erpnext.journal_entry.set_exchange_rate(frm, row.doctype, row.name);
+ })
}
})
@@ -345,7 +353,7 @@
});
}
},
-
+
debit_in_account_currency: function(frm, cdt, cdn) {
erpnext.journal_entry.set_exchange_rate(frm, cdt, cdn);
},
@@ -420,6 +428,7 @@
frappe.call({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate",
args: {
+ posting_date: frm.doc.posting_date,
account: row.account,
account_currency: row.account_currency,
company: frm.doc.company,
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 2f3b101..cd79363 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -326,8 +326,9 @@
if d.account_currency == self.company_currency:
d.exchange_rate = 1
elif not d.exchange_rate or d.exchange_rate == 1 or \
- (d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name):
- d.exchange_rate = get_exchange_rate(d.account, d.account_currency, self.company,
+ (d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name and d.posting_date):
+ # Modified to include the posting date for which to retreive the exchange rate
+ d.exchange_rate = get_exchange_rate(self.posting_date, d.account, d.account_currency, self.company,
d.reference_type, d.reference_name, d.debit, d.credit, d.exchange_rate)
if not d.exchange_rate:
@@ -648,7 +649,9 @@
cost_center = frappe.db.get_value("Company", ref_doc.company, "cost_center")
exchange_rate = 1
if args.get("party_account"):
- exchange_rate = get_exchange_rate(args.get("party_account"), args.get("party_account_currency"),
+ # Modified to include the posting date for which the exchange rate is required.
+ # Assumed to be the posting date in the reference document
+ exchange_rate = get_exchange_rate(ref_doc.posting_date, args.get("party_account"), args.get("party_account_currency"),
ref_doc.company, ref_doc.doctype, ref_doc.name)
je = frappe.new_doc("Journal Entry")
@@ -681,7 +684,9 @@
bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account"))
if bank_account:
bank_row.update(bank_account)
- bank_row.exchange_rate = get_exchange_rate(bank_account["account"],
+ # Modified to include the posting date for which the exchange rate is required.
+ # Assumed to be the posting date of the reference date
+ bank_row.exchange_rate = get_exchange_rate(ref_doc.posting_date, bank_account["account"],
bank_account["account_currency"], ref_doc.company)
bank_row.cost_center = cost_center
@@ -805,7 +810,10 @@
"party_type": party_type,
"account_type": account_details.account_type,
"account_currency": account_details.account_currency or company_currency,
- "exchange_rate": get_exchange_rate(account, account_details.account_currency,
+
+ # The date used to retreive the exchange rate here is the date passed in
+ # as an argument to this function. It is assumed to be the date on which the balance is sought
+ "exchange_rate": get_exchange_rate(date, account, account_details.account_currency,
company, debit=debit, credit=credit, exchange_rate=exchange_rate)
}
@@ -815,8 +823,9 @@
return grid_values
+# Added posting_date as one of the parameters of get_exchange_rate
@frappe.whitelist()
-def get_exchange_rate(account, account_currency=None, company=None,
+def get_exchange_rate(posting_date, account, account_currency=None, company=None,
reference_type=None, reference_name=None, debit=None, credit=None, exchange_rate=None):
from erpnext.setup.utils import get_exchange_rate
account_details = frappe.db.get_value("Account", account,
@@ -842,9 +851,10 @@
(account_details.root_type == "Liability" and debit)):
exchange_rate = get_average_exchange_rate(account)
- if not exchange_rate and account_currency:
- exchange_rate = get_exchange_rate(account_currency, company_currency)
-
+ # The date used to retreive the exchange rate here is the date passed
+ # in as an argument to this function.
+ if not exchange_rate and account_currency and posting_date:
+ exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
else:
exchange_rate = 1
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 4740ad9..b4bb542 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -186,10 +186,10 @@
self.assertEqual(len(je.get("accounts")), 2)
-def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False):
+def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None):
if not cost_center:
cost_center = "_Test Cost Center - _TC"
-
+
jv = frappe.new_doc("Journal Entry")
jv.posting_date = posting_date or "2013-02-14"
jv.company = "_Test Company"
@@ -199,12 +199,14 @@
{
"account": account1,
"cost_center": cost_center,
+ "project": project,
"debit_in_account_currency": amount if amount > 0 else 0,
"credit_in_account_currency": abs(amount) if amount < 0 else 0,
"exchange_rate": exchange_rate
}, {
"account": account2,
"cost_center": cost_center,
+ "project": project,
"credit_in_account_currency": amount if amount > 0 else 0,
"debit_in_account_currency": abs(amount) if amount < 0 else 0,
"exchange_rate": exchange_rate
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 1f6199e..d3dbd31 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -327,6 +327,7 @@
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
+ transaction_date: frm.doc.posting_date,
from_currency: from_currency,
to_currency: to_currency
},
@@ -335,6 +336,10 @@
}
})
},
+
+ posting_date: function(frm) {
+ frm.events.paid_from_account_currency(frm);
+ },
source_exchange_rate: function(frm) {
if (frm.doc.paid_amount) {
@@ -425,6 +430,7 @@
method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_outstanding_reference_documents',
args: {
args: {
+ "posting_date": frm.doc.posting_date,
"company": frm.doc.company,
"party_type": frm.doc.party_type,
"payment_type": frm.doc.payment_type,
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index d4f8edb..02d2b6b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -153,11 +153,11 @@
self.source_exchange_rate = get_average_exchange_rate(self.paid_from)
else:
self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
- self.company_currency)
+ self.company_currency, self.posting_date)
if self.paid_to and not self.target_exchange_rate:
self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
- self.company_currency)
+ self.company_currency, self.posting_date)
def validate_mandatory(self):
for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
@@ -482,12 +482,12 @@
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
# Get all SO / PO which are not fully billed or aginst which full advance not paid
- orders_to_be_billed = get_orders_to_be_billed(args.get("party_type"), args.get("party"),
+ orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"),
party_account_currency, company_currency)
return negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
-def get_orders_to_be_billed(party_type, party, party_account_currency, company_currency):
+def get_orders_to_be_billed(posting_date, party_type, party, party_account_currency, company_currency):
voucher_type = 'Sales Order' if party_type == "Customer" else 'Purchase Order'
ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
@@ -517,7 +517,9 @@
order_list = []
for d in orders:
d["voucher_type"] = voucher_type
- d["exchange_rate"] = get_exchange_rate(party_account_currency, company_currency)
+ # This assumes that the exchange rate required is the one in the SO
+ d["exchange_rate"] = get_exchange_rate(party_account_currency,
+ company_currency, posting_date)
order_list.append(d)
return order_list
@@ -592,14 +594,19 @@
exchange_rate = 1
else:
total_amount = ref_doc.grand_total
+
+ # Get the exchange rate from the original ref doc
+ # or get it based on the posting date of the ref doc
exchange_rate = ref_doc.get("conversion_rate") or \
- get_exchange_rate(party_account_currency, ref_doc.company_currency)
+ get_exchange_rate(party_account_currency, ref_doc.company_currency, ref_doc.posting_date)
outstanding_amount = ref_doc.get("outstanding_amount") \
if reference_doctype in ("Sales Invoice", "Purchase Invoice") \
else flt(total_amount) - flt(ref_doc.advance_paid)
else:
- exchange_rate = get_exchange_rate(party_account_currency, ref_doc.company_currency)
+ # Get the exchange rate based on the posting date of the ref doc
+ exchange_rate = get_exchange_rate(party_account_currency,
+ ref_doc.company_currency, ref_doc.posting_date)
return frappe._dict({
"due_date": ref_doc.get("due_date"),
@@ -639,7 +646,7 @@
if party_amount:
grand_total = outstanding_amount = party_amount
elif dt in ("Sales Invoice", "Purchase Invoice"):
- grand_total = doc.grand_total
+ grand_total = doc.base_grand_total if party_account_currency == doc.company_currency else doc.grand_total
outstanding_amount = doc.outstanding_amount
else:
total_field = "base_grand_total" if party_account_currency == doc.company_currency else "grand_total"
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 73c412f..bf3e24f 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -9,7 +9,6 @@
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.setup.utils import get_exchange_rate
-# test_records = frappe.get_test_records('Payment Request')
test_dependencies = ["Currency Exchange", "Journal Entry", "Contact", "Address"]
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index d6cf120..9fb11f2 100644
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -3,7 +3,6 @@
from __future__ import unicode_literals
import frappe, json
-from frappe import _
from frappe.utils import nowdate
from erpnext.setup.utils import get_exchange_rate
from erpnext.stock.get_item_details import get_pos_profile
@@ -63,8 +62,10 @@
doc.currency = pos_profile.get('currency') or company_data.default_currency
doc.conversion_rate = 1.0
+
if doc.currency != company_data.default_currency:
- doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency)
+ doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency, doc.posting_date)
+
doc.selling_price_list = pos_profile.get('selling_price_list') or \
frappe.db.get_value('Selling Settings', None, 'selling_price_list')
doc.naming_series = pos_profile.get('naming_series') or 'SINV-'
@@ -296,4 +297,3 @@
si_doc.docstatus = 0
si_doc.flags.ignore_mandatory = True
si_doc.insert()
- frappe.log_error(frappe.get_traceback())
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index e66d20e..bc4a220 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -42,9 +42,6 @@
if to_date == get_first_day(to_date):
# if to_date is the first day, get the last day of previous month
to_date = add_days(to_date, -1)
- else:
- # to_date should be the last day of the new to_date's month
- to_date = get_last_day(to_date)
if to_date <= year_end_date:
# the normal case
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py
index 4bba066..47f79f1 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.py
+++ b/erpnext/accounts/report/purchase_register/purchase_register.py
@@ -185,7 +185,7 @@
pr_list = [d.purchase_receipt]
elif d.po_detail:
pr_list = frappe.db.sql_list("""select distinct parent from `tabPurchase Receipt Item`
- where docstatus=1 and prevdoc_detail_docname=%s""", d.po_detail)
+ where docstatus=1 and purchase_order_item=%s""", d.po_detail)
if pr_list:
invoice_po_pr_map.setdefault(d.parent, frappe._dict()).setdefault("purchase_receipt", pr_list)
diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
index 1793fc3..f627b4a 100644
--- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
+++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
@@ -3,11 +3,9 @@
from __future__ import unicode_literals
from erpnext.setup.utils import get_exchange_rate
-
import frappe
def execute(filters=None):
-
qty_list = get_quantity_list(filters.item)
data = get_quote_list(filters.item, qty_list)
@@ -15,12 +13,9 @@
columns = get_columns(qty_list)
return columns, data
-
def get_quote_list(item, qty_list):
-
out = []
-
if item:
price_data = []
suppliers = []
@@ -38,8 +33,11 @@
#Add a row for each supplier
for root in set(suppliers):
- supplier_currency = frappe.db.get_value("Supplier",root,"default_currency")
- exg = get_exchange_rate(supplier_currency,company_currency)
+ supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
+ if supplier_currency:
+ exchange_rate = get_exchange_rate(supplier_currency, company_currency)
+ else:
+ exchange_rate = 1
row = frappe._dict({
"supplier_name": root
@@ -48,7 +46,7 @@
# Get the quantity for this row
for item_price in price_data:
if str(item_price.qty) == col.key and item_price.supplier == root:
- row[col.key] = item_price.rate * exg
+ row[col.key] = item_price.rate * exchange_rate
row[col.key + "QUOTE"] = item_price.parent
break
else:
@@ -56,15 +54,11 @@
row[col.key + "QUOTE"] = ""
out.append(row)
-
-
return out
def get_quantity_list(item):
-
out = []
-
if item:
qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item` where ifnull(item_code,'')=%s and docstatus < 2""", item, as_dict=1)
qty_list.sort(reverse=False)
@@ -102,5 +96,4 @@
"width": 90
})
-
- return columns
\ No newline at end of file
+ return columns
diff --git a/erpnext/config/docs.py b/erpnext/config/docs.py
index 7b6915a..2d2cc0a 100644
--- a/erpnext/config/docs.py
+++ b/erpnext/config/docs.py
@@ -1,3 +1,7 @@
+from __future__ import unicode_literals
+
+docs_version = "7.x.x"
+
source_link = "https://github.com/frappe/erpnext"
docs_base_url = "https://frappe.github.io/erpnext"
headline = "ERPNext Documentation"
@@ -15,8 +19,8 @@
listed as one of the Best Open Source Softwares in the world by many online
blogs."""
-docs_version = "7.x.x"
splash_light_background = True
+google_analytics_id = 'UA-8911157-22'
def get_context(context):
context.brand_html = ('<img class="brand-logo" src="'+context.docs_base_url
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 5796a4d..554529c 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -127,6 +127,11 @@
validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company)
def set_price_list_currency(self, buying_or_selling):
+ if self.meta.get_field("posting_date"):
+ transaction_date = self.posting_date
+ else:
+ transaction_date = self.transaction_date
+
if self.meta.get_field("currency"):
# price list part
fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \
@@ -139,8 +144,8 @@
self.plc_conversion_rate = 1.0
elif not self.plc_conversion_rate:
- self.plc_conversion_rate = get_exchange_rate(
- self.price_list_currency, self.company_currency)
+ self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
+ self.company_currency, transaction_date)
# currency
if not self.currency:
@@ -150,7 +155,7 @@
self.conversion_rate = 1.0
elif not self.conversion_rate:
self.conversion_rate = get_exchange_rate(self.currency,
- self.company_currency)
+ self.company_currency, transaction_date)
def set_missing_item_details(self, for_validate=False):
"""set missing item values"""
@@ -602,7 +607,6 @@
for item in duplicate_list:
self.remove(item)
-
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 6be0768..6ee9003 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -205,7 +205,8 @@
if company_currency == quotation.currency:
exchange_rate = 1
else:
- exchange_rate = get_exchange_rate(quotation.currency, company_currency)
+ exchange_rate = get_exchange_rate(quotation.currency, company_currency,
+ quotation.transaction_date)
quotation.conversion_rate = exchange_rate
diff --git a/erpnext/docs/assets/img/human-resources/payroll-frequency.png b/erpnext/docs/assets/img/human-resources/payroll-frequency.png
new file mode 100644
index 0000000..e639903
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/payroll-frequency.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/process-payroll.png b/erpnext/docs/assets/img/human-resources/process-payroll.png
index 2e2fbed..0901cbe 100644
--- a/erpnext/docs/assets/img/human-resources/process-payroll.png
+++ b/erpnext/docs/assets/img/human-resources/process-payroll.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/salary-structure.png b/erpnext/docs/assets/img/human-resources/salary-structure.png
index 71f6013..83e4820 100644
--- a/erpnext/docs/assets/img/human-resources/salary-structure.png
+++ b/erpnext/docs/assets/img/human-resources/salary-structure.png
Binary files differ
diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.js b/erpnext/hr/doctype/process_payroll/process_payroll.js
index 35c097a..3265f88 100644
--- a/erpnext/hr/doctype/process_payroll/process_payroll.js
+++ b/erpnext/hr/doctype/process_payroll/process_payroll.js
@@ -2,43 +2,43 @@
// License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Process Payroll", {
- refresh: function(frm) {
- frm.disable_save();
- frm.trigger("toggle_fields");
- frm.trigger("set_month_dates");
- },
-
- month: function(frm) {
- frm.trigger("set_month_dates");
- },
-
- fiscal_year: function(frm) {
- frm.trigger("set_month_dates");
- },
-
- salary_slip_based_on_timesheet: function(frm) {
- frm.trigger("toggle_fields")
+ onload: function(frm) {
+ frm.doc.posting_date = frm.doc.start_date = frm.doc.end_date = frappe.datetime.nowdate()
},
- toggle_fields: function(frm) {
- frm.toggle_display(['from_date','to_date'],
- cint(frm.doc.salary_slip_based_on_timesheet)==1);
- frm.toggle_display(['fiscal_year', 'month'],
- cint(frm.doc.salary_slip_based_on_timesheet)==0);
+ refresh: function(frm) {
+ frm.disable_save();
},
- set_month_dates: function(frm) {
+ payroll_frequency: function(frm) {
+ frm.trigger("set_start_end_dates");
+ },
+
+ start_date: function(frm) {
+ frm.trigger("set_start_end_dates");
+ },
+
+ end_date: function(frm) {
+ frm.trigger("set_start_end_dates");
+ },
+
+ payment_account: function(frm) {
+ frm.toggle_display(['make_bank_entry'], (frm.doc.payment_account!="" && frm.doc.payment_account!="undefined"));
+ },
+
+ set_start_end_dates: function(frm) {
if (!frm.doc.salary_slip_based_on_timesheet){
frappe.call({
- method:'erpnext.hr.doctype.process_payroll.process_payroll.get_month_details',
+ method:'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates',
args:{
- year: frm.doc.fiscal_year,
- month: frm.doc.month
+ payroll_frequency: frm.doc.payroll_frequency,
+ start_date: frm.doc.start_date,
+ end_date: frm.doc.end_date
},
callback: function(r){
if (r.message){
- frm.set_value('from_date', r.message.month_start_date);
- frm.set_value('to_date', r.message.month_end_date);
+ frm.set_value('start_date', r.message.start_date);
+ frm.set_value('end_date', r.message.end_date);
}
}
})
@@ -56,17 +56,6 @@
}
})
-cur_frm.cscript.onload = function(doc,cdt,cdn){
- if(!doc.month) {
- var today=new Date();
- month = (today.getMonth()+01).toString();
- if(month.length>1) doc.month = month;
- else doc.month = '0'+month;
- }
- if(!doc.fiscal_year) doc.fiscal_year = sys_defaults['fiscal_year'];
- refresh_many(['month', 'fiscal_year']);
-}
-
cur_frm.cscript.display_activity_log = function(msg) {
if(!cur_frm.ss_html)
cur_frm.ss_html = $a(cur_frm.fields_dict['activity_log'].wrapper,'div');
@@ -92,7 +81,7 @@
cur_frm.cscript.submit_salary_slip = function(doc, cdt, cdn) {
cur_frm.cscript.display_activity_log("");
- frappe.confirm(__("Do you really want to Submit all Salary Slip from {0} to {1}", [doc.from_date, doc.to_date]), function() {
+ frappe.confirm(__("Do you really want to Submit all Salary Slip from {0} to {1}", [doc.start_date, doc.end_date]), function() {
// clear all in locals
if(locals["Salary Slip"]) {
$.each(locals["Salary Slip"], function(name, d) {
@@ -110,7 +99,7 @@
}
cur_frm.cscript.make_bank_entry = function(doc,cdt,cdn){
- if(doc.company && doc.from_date && doc.to_date){
+ if(doc.company && doc.start_date && doc.end_date){
return cur_frm.cscript.reference_entry(doc,cdt,cdn);
} else {
msgprint(__("Company, From Date and To Date is mandatory"));
diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.json b/erpnext/hr/doctype/process_payroll/process_payroll.json
index ae86c2d..8e0d395 100644
--- a/erpnext/hr/doctype/process_payroll/process_payroll.json
+++ b/erpnext/hr/doctype/process_payroll/process_payroll.json
@@ -22,6 +22,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Select Employees",
"length": 0,
"no_copy": 0,
@@ -48,6 +49,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -67,6 +69,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "",
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -74,6 +77,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
@@ -102,6 +106,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Posting Date",
"length": 0,
"no_copy": 0,
@@ -122,6 +127,37 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "Monthly",
+ "depends_on": "eval:doc.salary_slip_based_on_timesheet == 0",
+ "fieldname": "payroll_frequency",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Payroll Frequency",
+ "length": 0,
+ "no_copy": 0,
+ "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -129,6 +165,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -155,6 +192,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Branch",
"length": 0,
"no_copy": 0,
@@ -182,6 +220,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
@@ -209,6 +248,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Designation",
"length": 0,
"no_copy": 0,
@@ -236,6 +276,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -262,6 +303,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Salary Slip Based on Timesheet",
"length": 0,
"no_copy": 0,
@@ -289,6 +331,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Select Payroll Period",
"length": 0,
"no_copy": 0,
@@ -309,14 +352,16 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "from_date",
- "fieldtype": "Data",
+ "default": "Today",
+ "fieldname": "start_date",
+ "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "From",
+ "in_standard_filter": 0,
+ "label": "Start Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -336,33 +381,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "fiscal_year",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Fiscal Year",
- "length": 0,
- "no_copy": 0,
- "options": "Fiscal Year",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_11",
"fieldtype": "Column Break",
"hidden": 0,
@@ -370,6 +388,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -389,14 +408,16 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "to_date",
- "fieldtype": "Data",
+ "default": "Today",
+ "fieldname": "end_date",
+ "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "To",
+ "in_standard_filter": 0,
+ "label": "End Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -416,33 +437,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "month",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Month",
- "length": 0,
- "no_copy": 0,
- "options": "\n01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "process_payroll",
"fieldtype": "Section Break",
"hidden": 0,
@@ -450,6 +444,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Process Payroll",
"length": 0,
"no_copy": 0,
@@ -477,6 +472,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -504,6 +500,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Create Salary Slip",
"length": 0,
"no_copy": 0,
@@ -530,6 +527,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -557,6 +555,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Submit Salary Slip",
"length": 0,
"no_copy": 0,
@@ -583,6 +582,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Account",
"length": 0,
"no_copy": 0,
@@ -611,6 +611,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Payment Account",
"length": 0,
"no_copy": 0,
@@ -632,15 +633,16 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "depends_on": "eval:doc.payment_account",
+ "depends_on": "",
"description": "Create Bank Entry for the total salary paid for the above selected criteria",
"fieldname": "make_bank_entry",
"fieldtype": "Button",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Make Bank Entry",
"length": 0,
"no_copy": 0,
@@ -667,6 +669,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -692,6 +695,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Activity Log",
"length": 0,
"no_copy": 0,
@@ -718,7 +722,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-11-03 16:02:34.040851",
+ "modified": "2016-11-26 01:14:51.691057",
"modified_by": "Administrator",
"module": "HR",
"name": "Process Payroll",
diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.py b/erpnext/hr/doctype/process_payroll/process_payroll.py
index 2077982..97e3876 100644
--- a/erpnext/hr/doctype/process_payroll/process_payroll.py
+++ b/erpnext/hr/doctype/process_payroll/process_payroll.py
@@ -3,10 +3,12 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import cint, flt, nowdate
+from frappe.utils import cint, flt, nowdate, add_days, getdate
from frappe import _
import collections
from collections import defaultdict
+from calendar import monthrange
+from erpnext.accounts.utils import get_fiscal_year
from frappe.model.document import Document
@@ -19,7 +21,12 @@
"""
cond = self.get_filter_condition()
cond += self.get_joining_releiving_condition()
-
+
+
+ struct_cond = ''
+ if self.payroll_frequency:
+ struct_cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency}
+
sal_struct = frappe.db.sql("""
select name from `tabSalary Structure`
where docstatus != 2 and is_active = 'Yes' and company = %(company)s and
@@ -28,13 +35,11 @@
if sal_struct:
cond += "and t2.parent IN %(sal_struct)s "
-
emp_list = frappe.db.sql("""
select t1.name
from `tabEmployee` t1, `tabSalary Structure Employee` t2
where t1.docstatus!=2 and t1.name = t2.employee
%s """% cond, {"sal_struct": sal_struct})
-
return emp_list
@@ -51,16 +56,16 @@
def get_joining_releiving_condition(self):
cond = """
- and ifnull(t1.date_of_joining, '0000-00-00') <= '%(to_date)s'
- and ifnull(t1.relieving_date, '2199-12-31') >= '%(from_date)s'
- """ % {"from_date": self.from_date, "to_date": self.to_date}
+ and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
+ and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
+ """ % {"start_date": self.start_date, "end_date": self.end_date}
return cond
def check_mandatory(self):
- for f in ['company', 'from_date', 'to_date']:
- if not self.get(f):
- frappe.throw(_("Please set {0}").format(f))
+ for fieldname in ['company', 'payroll_frequency', 'start_date', 'end_date']:
+ if not self.get(fieldname):
+ frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname)))
def create_sal_slip(self):
"""
@@ -74,28 +79,28 @@
for emp in emp_list:
if not frappe.db.sql("""select name from `tabSalary Slip`
where docstatus!= 2 and employee = %s and start_date >= %s and end_date <= %s and company = %s
- """, (emp[0], self.from_date, self.to_date, self.company)):
- if self.salary_slip_based_on_timesheet:
+ """, (emp[0], self.start_date, self.end_date, self.company)):
+ if self.payroll_frequency == "Monthly" or self.payroll_frequency == '':
ss = frappe.get_doc({
"doctype": "Salary Slip",
"salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
- "start_date": self.from_date,
- "end_date": self.to_date,
"employee": emp[0],
"employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"),
"company": self.company,
- "posting_date": self.posting_date
+ "posting_date": self.posting_date,
+ "payroll_frequency": self.payroll_frequency
})
else:
ss = frappe.get_doc({
"doctype": "Salary Slip",
"salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
- "fiscal_year": self.fiscal_year,
- "month": self.month,
+ "start_date": self.start_date,
+ "end_date": self.end_date,
"employee": emp[0],
"employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"),
"company": self.company,
- "posting_date": self.posting_date
+ "posting_date": self.posting_date,
+ "payroll_frequency": self.payroll_frequency
})
ss.insert()
ss_list.append(ss.name)
@@ -120,7 +125,7 @@
select t1.name, t1.salary_structure from `tabSalary Slip` t1
where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
- """ % ('%s', '%s', '%s','%s', cond), (ss_status, self.from_date, self.to_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
+ """ % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
return ss_list
@@ -183,7 +188,7 @@
tot = frappe.db.sql("""
select sum(rounded_total) from `tabSalary Slip` t1
where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s
- """ % ('%s', '%s', cond), (self.from_date, self.to_date))
+ """ % ('%s', '%s', cond), (self.start_date, self.end_date))
return flt(tot[0][0])
@@ -231,8 +236,8 @@
if earnings or deductions:
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Bank Entry'
- journal_entry.user_remark = _('Payment of salary from {0} to {1}').format(self.from_date,
- self.to_date)
+ journal_entry.user_remark = _('Payment of salary from {0} to {1}').format(self.start_date,
+ self.end_date)
journal_entry.company = self.company
journal_entry.posting_date = nowdate()
@@ -257,6 +262,7 @@
journal_entry.set("accounts", account_amt_list)
journal_entry.cheque_no = reference_number
journal_entry.cheque_date = reference_date
+ journal_entry.multi_currency = 1
journal_entry.save()
try:
journal_entry.submit()
@@ -279,7 +285,7 @@
for ss in ss_list:
ss_obj = frappe.get_doc("Salary Slip",ss[0])
frappe.db.set_value("Salary Slip", ss_obj.name, "status", "Paid")
- frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name)
+ frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name)
@frappe.whitelist()
@@ -293,12 +299,45 @@
diff_mnt = 12-int(ysd.month)+cint(month)
msd = ysd + relativedelta(months=diff_mnt) # month start date
month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month
+ mid_start = datetime.date(msd.year, cint(month), 16) # month mid start date
+ mid_end = datetime.date(msd.year, cint(month), 15) # month mid end date
med = datetime.date(msd.year, cint(month), month_days) # month end date
return frappe._dict({
'year': msd.year,
'month_start_date': msd,
'month_end_date': med,
+ 'month_mid_start_date': mid_start,
+ 'month_mid_end_date': mid_end,
'month_days': month_days
})
else:
- frappe.throw(_("Fiscal Year {0} not found").format(year))
\ No newline at end of file
+ frappe.throw(_("Fiscal Year {0} not found").format(year))
+
+@frappe.whitelist()
+def get_start_end_dates(payroll_frequency, start_date, end_date):
+ if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly":
+ fiscal_year = get_fiscal_year(start_date)[0] or get_fiscal_year(end_date)[0]
+ month = "%02d" % getdate(start_date).month or "%02d" % getdate(end_date).month
+ m = get_month_details(fiscal_year, month)
+ if payroll_frequency == "Bimonthly":
+ if getdate(start_date).day <= 15:
+ start_date = m['month_start_date']
+ end_date = m['month_mid_end_date']
+ else:
+ start_date = m['month_mid_start_date']
+ end_date = m['month_end_date']
+ else:
+ start_date = m['month_start_date']
+ end_date = m['month_end_date']
+
+ if payroll_frequency == "Weekly":
+ end_date = add_days(start_date, 6)
+
+ if payroll_frequency == "Fortnightly":
+ end_date = add_days(start_date, 13)
+
+ if payroll_frequency == "Daily":
+ end_date = start_date
+ return frappe._dict({
+ 'start_date': start_date, 'end_date': end_date
+ })
\ No newline at end of file
diff --git a/erpnext/hr/doctype/process_payroll/test_process_payroll.py b/erpnext/hr/doctype/process_payroll/test_process_payroll.py
index 3be4b4c..5167365 100644
--- a/erpnext/hr/doctype/process_payroll/test_process_payroll.py
+++ b/erpnext/hr/doctype/process_payroll/test_process_payroll.py
@@ -18,15 +18,14 @@
get_salary_component_account(data.name)
payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
- if not frappe.db.get_value("Salary Slip", {"fiscal_year": fiscal_year, "month": month}):
+ if not frappe.db.get_value("Salary Slip", {"start_date": "2016-11-01", "end_date": "2016-11-30"}):
process_payroll = frappe.get_doc("Process Payroll", "Process Payroll")
process_payroll.company = erpnext.get_default_company()
- process_payroll.month = month
- process_payroll.fiscal_year = fiscal_year
- process_payroll.from_date = "2016-11-01"
- process_payroll.to_date = "2016-11-30"
+ process_payroll.start_date = "2016-11-01"
+ process_payroll.end_date = "2016-11-30"
process_payroll.payment_account = payment_account
process_payroll.posting_date = nowdate()
+ process_payroll.payroll_frequency = "Monthly"
process_payroll.create_sal_slip()
process_payroll.submit_salary_slip()
if process_payroll.get_sal_slip_list(ss_status = 1):
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.js b/erpnext/hr/doctype/salary_slip/salary_slip.js
index e1120e8..8b0dd16 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.js
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.js
@@ -46,59 +46,44 @@
salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields")
},
+
+ payroll_frequency: function(frm) {
+ frm.trigger("toggle_fields")
+ },
toggle_fields: function(frm) {
- frm.toggle_display(['start_date', 'end_date', 'hourly_wages', 'timesheets'],
+ frm.toggle_display(['hourly_wages', 'timesheets'],
cint(frm.doc.salary_slip_based_on_timesheet)==1);
- frm.toggle_display(['fiscal_year', 'month', 'total_days_in_month', 'leave_without_pay', 'payment_days'],
- cint(frm.doc.salary_slip_based_on_timesheet)==0);
+
+ frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'],
+ frm.doc.payroll_frequency!="");
}
})
-frappe.ui.form.on("Salary Slip Timesheet", {
- time_sheet: function(frm, cdt, cdn) {
- doc = frm.doc;
- cur_frm.cscript.fiscal_year(doc, cdt, cdn)
- }
-})
-
-
-// On load
-// -------------------------------------------------------------------
-cur_frm.cscript.onload = function(doc,dt,dn){
- if((cint(doc.__islocal) == 1) && !doc.amended_from){
- if(!doc.month) {
- var today=new Date();
- month = (today.getMonth()+01).toString();
- if(month.length>1) doc.month = month;
- else doc.month = '0'+month;
- }
- if(!doc.fiscal_year) doc.fiscal_year = sys_defaults['fiscal_year'];
- refresh_many(['month', 'fiscal_year']);
- }
-}
-
// Get leave details
//---------------------------------------------------------------------
-cur_frm.cscript.fiscal_year = function(doc,dt,dn){
- return $c_obj(doc, 'get_emp_and_leave_details','',function(r, rt) {
- var doc = locals[dt][dn];
+cur_frm.cscript.start_date = function(doc, dt, dn){
+ return frappe.call({
+ method: 'get_emp_and_leave_details',
+ doc: locals[dt][dn],
+ callback: function(r, rt) {
cur_frm.refresh();
calculate_all(doc, dt, dn);
- });
+ }
+ });
}
-cur_frm.cscript.month = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.fiscal_year;
-cur_frm.cscript.start_date = cur_frm.cscript.end_date = cur_frm.cscript.fiscal_year;
+cur_frm.cscript.payroll_frequency = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.start_date;
+cur_frm.cscript.end_date = cur_frm.cscript.start_date;
cur_frm.cscript.employee = function(doc,dt,dn){
doc.salary_structure = ''
- cur_frm.cscript.fiscal_year(doc, dt, dn)
+ cur_frm.cscript.start_date(doc, dt, dn)
}
cur_frm.cscript.leave_without_pay = function(doc,dt,dn){
- if (doc.employee && doc.fiscal_year && doc.month) {
+ if (doc.employee && doc.start_date && doc.end_date) {
return $c_obj(doc, 'get_leave_details', {"lwp": doc.leave_without_pay}, function(r, rt) {
var doc = locals[dt][dn];
cur_frm.refresh();
@@ -135,7 +120,7 @@
for(var i = 0; i < tbl.length; i++){
if(cint(tbl[i].depends_on_lwp) == 1) {
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days) /
- cint(doc.total_days_in_month)*100)/100;
+ cint(doc.total_working_days)*100)/100;
refresh_field('amount', tbl[i].name, 'earnings');
} else if(reset_amount) {
tbl[i].amount = tbl[i].default_amount;
@@ -155,7 +140,7 @@
var total_ded = 0;
for(var i = 0; i < tbl.length; i++){
if(cint(tbl[i].depends_on_lwp) == 1) {
- tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_days_in_month)*100)/100;
+ tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_working_days)*100)/100;
refresh_field('amount', tbl[i].name, 'deductions');
} else if(reset_amount) {
tbl[i].amount = tbl[i].default_amount;
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json
index 4d69b01..062f41f 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.json
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.json
@@ -421,69 +421,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "depends_on": "",
- "fieldname": "fiscal_year",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Fiscal Year",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "fiscal_year",
- "oldfieldtype": "Data",
- "options": "Fiscal Year",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "month",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Month",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "month",
- "oldfieldtype": "Select",
- "options": "\n01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "unique": 0,
- "width": "37%"
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "Today",
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -599,8 +537,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "",
+ "depends_on": "eval:(!doc.salary_slip_based_on_timesheet)",
+ "fieldname": "payroll_frequency",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Payroll Frequency",
+ "length": 0,
+ "no_copy": 0,
+ "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"depends_on": "",
- "fieldname": "total_days_in_month",
+ "fieldname": "total_working_days",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -1392,7 +1361,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-11-07 05:58:37.151587",
+ "modified": "2016-12-08 12:03:31.602913",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Slip",
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 34022bd..95bea7a 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -10,10 +10,12 @@
from frappe import msgprint, _
from erpnext.accounts.utils import get_fiscal_year
from erpnext.setup.utils import get_company_currency
-from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details
+from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details, get_start_end_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.utilities.transaction_base import TransactionBase
+from datetime import timedelta
+
class SalarySlip(TransactionBase):
def autoname(self):
self.name = make_autoname('Sal Slip/' +self.employee + '/.#####')
@@ -22,8 +24,7 @@
self.status = self.get_status()
self.validate_dates()
self.check_existing()
- self.set_month_dates()
-
+ self.get_date_details()
if not (len(self.get("earnings")) or len(self.get("deductions"))):
# get details from salary structure
self.get_emp_and_leave_details()
@@ -120,7 +121,7 @@
self.set("earnings", [])
self.set("deductions", [])
- self.set_month_dates()
+ self.get_date_details()
self.validate_dates()
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
@@ -146,20 +147,24 @@
'working_hours': data.total_hours
})
- def set_month_dates(self):
- if self.month and not self.salary_slip_based_on_timesheet:
- m = get_month_details(self.fiscal_year, self.month)
- self.start_date = m['month_start_date']
- self.end_date = m['month_end_date']
+ def get_date_details(self):
+ date_details = get_start_end_dates(self.payroll_frequency, self.start_date, self.end_date)
+ self.start_date = date_details.start_date
+ self.end_date = date_details.end_date
+
def check_sal_struct(self, joining_date, relieving_date):
+ cond = ''
+ if self.payroll_frequency:
+ cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency}
+
st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee`
where employee=%s
and parent in (select name from `tabSalary Structure`
where is_active = 'Yes'
and (from_date <= %s or from_date <= %s)
- and (to_date is null or to_date >= %s or to_date >= %s))
- """,(self.employee, self.start_date, joining_date, self.end_date, relieving_date))
+ and (to_date is null or to_date >= %s or to_date >= %s) %s)
+ """% ('%s', '%s', '%s','%s','%s', cond),(self.employee, self.start_date, joining_date, self.end_date, relieving_date))
if st_name:
if len(st_name) > 1:
@@ -185,9 +190,11 @@
def process_salary_structure(self):
'''Calculate salary after salary structure details have been updated'''
+ self.get_date_details()
self.pull_emp_details()
self.get_leave_details()
self.calculate_net_pay()
+
def add_earning_for_hourly_wages(self, salary_component):
default_type = False
@@ -210,14 +217,6 @@
def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None):
- if not self.fiscal_year:
- # if default fiscal year is not set, get from nowdate
- self.fiscal_year = get_fiscal_year(nowdate())[0]
-
- if not self.month:
- self.month = "%02d" % getdate(nowdate()).month
- self.set_month_dates()
-
if not joining_date:
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
@@ -236,7 +235,7 @@
elif lwp != actual_lwp:
frappe.msgprint(_("Leave Without Pay does not match with approved Leave Application records"))
- self.total_days_in_month = working_days
+ self.total_working_days = working_days
self.leave_without_pay = lwp
payment_days = flt(self.get_payment_days(joining_date, relieving_date)) - flt(lwp)
@@ -244,16 +243,15 @@
def get_payment_days(self, joining_date, relieving_date):
start_date = getdate(self.start_date)
-
if joining_date:
- if joining_date > getdate(self.start_date):
+ if getdate(self.start_date) <= joining_date <= getdate(self.end_date):
start_date = joining_date
elif joining_date > getdate(self.end_date):
return
end_date = getdate(self.end_date)
if relieving_date:
- if relieving_date > start_date and relieving_date < getdate(self.end_date):
+ if getdate(self.start_date) <= relieving_date <= getdate(self.end_date):
end_date = relieving_date
elif relieving_date < getdate(self.start_date):
frappe.throw(_("Employee relieved on {0} must be set as 'Left'")
@@ -264,7 +262,6 @@
if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")):
holidays = self.get_holidays_for_employee(start_date, end_date)
payment_days -= len(holidays)
-
return payment_days
def get_holidays_for_employee(self, start_date, end_date):
@@ -306,9 +303,9 @@
def check_existing(self):
if not self.salary_slip_based_on_timesheet:
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
- where month = %s and fiscal_year = %s and docstatus != 2
+ where start_date = %s and end_date = %s and docstatus != 2
and employee = %s and name != %s""",
- (self.month, self.fiscal_year, self.employee, self.name))
+ (self.start_date, self.end_date, self.employee, self.name))
if ret_exist:
self.employee = ''
frappe.throw(_("Salary Slip of employee {0} already created for this period").format(self.employee))
@@ -321,7 +318,7 @@
for d in self.get(component_type):
if cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet:
d.amount = rounded((flt(d.default_amount) * flt(self.payment_days)
- / cint(self.total_days_in_month)), self.precision("amount", component_type))
+ / cint(self.total_working_days)), self.precision("amount", component_type))
elif not self.payment_days and not self.salary_slip_based_on_timesheet:
d.amount = 0
elif not d.amount:
@@ -361,7 +358,7 @@
receiver = frappe.db.get_value("Employee", self.employee, "prefered_email")
if receiver:
- subj = 'Salary Slip - from {0} to {1}, fiscal year {2}'.format(self.start_date, self.end_date, self.fiscal_year)
+ subj = 'Salary Slip - from {0} to {1}'.format(self.start_date, self.end_date)
frappe.sendmail([receiver], subject=subj, message = _("Please see attachment"),
attachments=[frappe.attach_print(self.doctype, self.name, file_name=self.name)], reference_doctype= self.doctype, reference_name= self.name)
else:
diff --git a/erpnext/hr/doctype/salary_slip/test_records.json b/erpnext/hr/doctype/salary_slip/test_records.json
deleted file mode 100644
index 20cef3d..0000000
--- a/erpnext/hr/doctype/salary_slip/test_records.json
+++ /dev/null
@@ -1,45 +0,0 @@
-[
- {
- "company": "_Test Company",
- "doctype": "Salary Slip",
- "deductions": [
- {
- "doctype": "Salary Detail",
- "amount": 100,
- "depends_on_lwp": 0,
- "salary_component": "_Test Professional Tax",
- "parentfield": "deductions"
- },
- {
- "doctype": "Salary Detail",
- "amount": 48.39,
- "depends_on_lwp": 0,
- "salary_component": "_Test TDS",
- "parentfield": "deductions"
- }
- ],
- "earnings": [
- {
- "doctype": "Salary Detail",
- "amount": 14516.13,
- "depends_on_lwp": 0,
- "salary_component": "_Test Basic Salary",
- "parentfield": "earnings"
- },
- {
- "doctype": "Salary Detail",
- "amount": 500,
- "depends_on_lwp": 0,
- "salary_component": "_Test Allowance",
- "parentfield": "earnings"
- }
- ],
- "employee": "_T-Employee-0001",
- "employee_name": "_Test Employee",
- "posting_date": "2013-02-01",
- "fiscal_year": "_Test Fiscal Year 2013",
- "month": "01",
- "payment_days": 31,
- "total_days_in_month": 31
- }
-]
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
index 57a3711..4025db7 100644
--- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
@@ -5,41 +5,41 @@
import unittest
import frappe
import erpnext
-from frappe.utils.make_random import get_random
-from frappe.utils import today, now_datetime, getdate, cstr, add_years, nowdate
+import calendar
+from erpnext.accounts.utils import get_fiscal_year
+from frappe.utils import getdate, nowdate, add_days
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.hr.doctype.process_payroll.test_process_payroll import get_salary_component_account
+from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details
class TestSalarySlip(unittest.TestCase):
def setUp(self):
- make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"])
+ make_earning_salary_component(["Basic Salary", "Allowance", "HRA"])
+ make_deduction_salary_component(["Professional Tax", "TDS"])
for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]:
frappe.db.sql("delete from `tab%s`" % dt)
self.make_holiday_list()
+
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
- from erpnext.hr.doctype.leave_application.test_leave_application import _test_records as leave_applications
- la = frappe.copy_doc(leave_applications[2])
- la.insert()
- la.status = "Approved"
- la.submit()
def tearDown(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
frappe.set_user("Administrator")
def test_salary_slip_with_holidays_included(self):
+ no_of_days = self.get_no_of_days()
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
self.make_employee("test_employee@salary.com")
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
ss = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee@salary.com"))
+ self.make_employee_salary_slip("test_employee@salary.com", "Monthly"))
- self.assertEquals(ss.total_days_in_month, 31)
- self.assertEquals(ss.payment_days, 31)
+ self.assertEquals(ss.total_working_days, no_of_days[0])
+ self.assertEquals(ss.payment_days, no_of_days[0])
self.assertEquals(ss.earnings[0].amount, 5000)
self.assertEquals(ss.earnings[1].amount, 3000)
self.assertEquals(ss.deductions[0].amount, 5000)
@@ -48,15 +48,16 @@
self.assertEquals(ss.net_pay, 3000)
def test_salary_slip_with_holidays_excluded(self):
+ no_of_days = self.get_no_of_days()
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
self.make_employee("test_employee@salary.com")
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
ss = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee@salary.com"))
+ self.make_employee_salary_slip("test_employee@salary.com", "Monthly"))
- self.assertEquals(ss.total_days_in_month, 28)
- self.assertEquals(ss.payment_days, 28)
+ self.assertEquals(ss.total_working_days, no_of_days[0] - no_of_days[1])
+ self.assertEquals(ss.payment_days, no_of_days[0] - no_of_days[1])
self.assertEquals(ss.earnings[0].amount, 5000)
self.assertEquals(ss.earnings[0].default_amount, 5000)
self.assertEquals(ss.earnings[1].amount, 3000)
@@ -66,43 +67,52 @@
self.assertEquals(ss.net_pay, 3000)
def test_payment_days(self):
+ no_of_days = self.get_no_of_days()
# Holidays not included in working days
- frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
+ frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
# set joinng date in the same month
self.make_employee("test_employee@salary.com")
- frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", "2013-01-11")
-
- ss = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee@salary.com"))
+ if getdate(nowdate()).day > 15:
+ date_of_joining = getdate(add_days(nowdate(),-10))
+ relieving_date = getdate(add_days(nowdate(),-10))
+ elif getdate(nowdate()).day < 15 and getdate(nowdate()).day > 5:
+ date_of_joining = getdate(add_days(nowdate(),-3))
+ relieving_date = getdate(add_days(nowdate(),-3))
+ elif getdate(nowdate()).day < 5 and not getdate(nowdate()).day == 1:
+ date_of_joining = getdate(add_days(nowdate(),-1))
+ relieving_date = getdate(add_days(nowdate(),-1))
+ elif getdate(nowdate()).day == 1:
+ date_of_joining = getdate(nowdate())
+ relieving_date = getdate(nowdate())
- self.assertEquals(ss.total_days_in_month, 28)
- self.assertEquals(ss.payment_days, 28)
-
- # set relieving date in the same month
- frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", "12-12-2016")
- frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
-
- self.assertEquals(ss.total_days_in_month, 28)
- self.assertEquals(ss.payment_days, 28)
- ss.save()
-
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", date_of_joining)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
- # Holidays included in working days
- frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
- self.assertEquals(ss.total_days_in_month, 28)
- self.assertEquals(ss.payment_days, 28)
+
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com", "Monthly"))
+
+ self.assertEquals(ss.total_working_days, no_of_days[0])
+ self.assertEquals(ss.payment_days, (no_of_days[0] - getdate(date_of_joining).day + 1))
+
+ # set relieving date in the same month
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", relieving_date)
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
ss.save()
- #
- # frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", "2001-01-11")
- # frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+
+ self.assertEquals(ss.total_working_days, no_of_days[0])
+ self.assertEquals(ss.payment_days, getdate(relieving_date).day)
+
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
def test_employee_salary_slip_read_permission(self):
self.make_employee("test_employee@salary.com")
salary_slip_test_employee = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee@salary.com"))
+ self.make_employee_salary_slip("test_employee@salary.com", "Monthly"))
frappe.set_user("test_employee@salary.com")
self.assertTrue(salary_slip_test_employee.has_permission("read"))
@@ -115,11 +125,33 @@
self.make_employee("test_employee@salary.com")
ss = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee@salary.com"))
+ self.make_employee_salary_slip("test_employee@salary.com", "Monthly"))
ss.submit()
email_queue = frappe.db.sql("""select name from `tabEmail Queue`""")
self.assertTrue(email_queue)
+ def test_payroll_frequency(self):
+ fiscal_year = get_fiscal_year(nowdate())[0]
+ month = "%02d" % getdate(nowdate()).month
+ m = get_month_details(fiscal_year, month)
+
+ for payroll_frequncy in ["Monthly", "Bimonthly", "Fortnightly", "Weekly", "Daily"]:
+ self.make_employee(payroll_frequncy + "_test_employee@salary.com")
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip(payroll_frequncy + "_test_employee@salary.com", payroll_frequncy))
+ if payroll_frequncy == "Monthly":
+ self.assertEqual(ss.end_date, m['month_end_date'])
+ elif payroll_frequncy == "Bimonthly":
+ if getdate(ss.start_date).day <= 15:
+ self.assertEqual(ss.end_date, m['month_mid_end_date'])
+ else:
+ self.assertEqual(ss.end_date, m['month_end_date'])
+ elif payroll_frequncy == "Fortnightly":
+ self.assertEqual(ss.end_date, getdate(add_days(nowdate(),13)))
+ elif payroll_frequncy == "Weekly":
+ self.assertEqual(ss.end_date, getdate(add_days(nowdate(),6)))
+ elif payroll_frequncy == "Daily":
+ self.assertEqual(ss.end_date, getdate(nowdate()))
def make_employee(self, user):
if not frappe.db.get_value("User", user):
@@ -148,29 +180,30 @@
"status": "Active",
"employment_type": "Intern"
}).insert()
-
+
def make_holiday_list(self):
+ fiscal_year = get_fiscal_year(nowdate())
if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):
holiday_list = frappe.get_doc({
"doctype": "Holiday List",
"holiday_list_name": "Salary Slip Test Holiday List",
- "from_date": nowdate(),
- "to_date": add_years(nowdate(), 1),
+ "from_date": fiscal_year[1],
+ "to_date": fiscal_year[2],
"weekly_off": "Sunday"
}).insert()
holiday_list.get_weekly_off_dates()
holiday_list.save()
-
- def make_employee_salary_slip(self, user):
+
+ def make_employee_salary_slip(self, user, payroll_frequency):
employee = frappe.db.get_value("Employee", {"user_id": user})
- salary_structure = make_salary_structure("Salary Structure Test for Salary Slip")
+ salary_structure = make_salary_structure(payroll_frequency + " Salary Structure Test for Salary Slip", payroll_frequency, employee)
salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
if not salary_slip:
salary_slip = make_salary_slip(salary_structure, employee = employee)
salary_slip.employee_name = frappe.get_value("Employee", {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
- salary_slip.month = "12"
- salary_slip.fiscal_year = "_Test Fiscal Year 2016"
+ salary_slip.payroll_frequency = payroll_frequency
+ salary_slip.start_date = nowdate()
salary_slip.posting_date = nowdate()
salary_slip.insert()
# salary_slip.submit()
@@ -185,38 +218,68 @@
activity_type.wage_rate = 25
activity_type.save()
-def make_salary_component(salary_components):
+ def get_no_of_days(self):
+ no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year,
+ getdate(nowdate()).month)
+ no_of_holidays_in_month = len([1 for i in calendar.monthcalendar(getdate(nowdate()).year,
+ getdate(nowdate()).month) if i[6] != 0])
+ return [no_of_days_in_month[1], no_of_holidays_in_month]
+
+
+def make_earning_salary_component(salary_components):
for salary_component in salary_components:
if not frappe.db.exists('Salary Component', salary_component):
sal_comp = frappe.get_doc({
"doctype": "Salary Component",
- "salary_component": salary_component
+ "salary_component": salary_component,
+ "type": "Earning"
})
sal_comp.insert()
get_salary_component_account(salary_component)
-def make_salary_structure(sal_struct):
+def make_deduction_salary_component(salary_components):
+ for salary_component in salary_components:
+ if not frappe.db.exists('Salary Component', salary_component):
+ sal_comp = frappe.get_doc({
+ "doctype": "Salary Component",
+ "salary_component": salary_component,
+ "type": "Deduction"
+ })
+ sal_comp.insert()
+ get_salary_component_account(salary_component)
+
+def make_salary_structure(sal_struct, payroll_frequency, employee):
if not frappe.db.exists('Salary Structure', sal_struct):
frappe.get_doc({
"doctype": "Salary Structure",
"name": sal_struct,
"company": erpnext.get_default_company(),
"from_date": nowdate(),
- "employees": get_employee_details(),
+ "employees": get_employee_details(employee),
"earnings": get_earnings_component(),
"deductions": get_deductions_component(),
+ "payroll_frequency": payroll_frequency,
"payment_account": frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
}).insert()
+
+ elif not frappe.db.get_value("Salary Structure Employee",{'parent':sal_struct, 'employee':employee},'name'):
+ sal_struct = frappe.get_doc("Salary Structure", sal_struct)
+ sal_struct.append("employees", {"employee": employee,
+ "employee_name": employee,
+ "base": 32000,
+ "variable": 3200
+ })
+ sal_struct.save()
+ sal_struct = sal_struct.name
return sal_struct
-
-
-def get_employee_details():
- return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"),
+
+def get_employee_details(employee):
+ return [{"employee": employee,
"base": 25000,
"variable": 5000
}
]
-
+
def get_earnings_component():
return [
{
@@ -270,7 +333,4 @@
"formula": 'base*.1',
"idx": 3
}
- ]
-
-test_dependencies = ["Leave Application", "Holiday List"]
-
\ No newline at end of file
+ ]
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js
index d3ab809..0bc67a7 100755
--- a/erpnext/hr/doctype/salary_structure/salary_structure.js
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.js
@@ -15,6 +15,8 @@
frappe.ui.form.on('Salary Structure', {
onload: function(frm) {
+ frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet),
+
frm.set_query("salary_component", "earnings", function() {
return {
filters: {
@@ -142,6 +144,7 @@
toggle_fields: function(frm) {
frm.toggle_display(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet);
frm.toggle_reqd(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet);
+ frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet);
}
});
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.json b/erpnext/hr/doctype/salary_structure/salary_structure.json
index 8a9068d..5c0a635 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.json
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.json
@@ -100,6 +100,37 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "",
+ "depends_on": "eval:(!doc.salary_slip_based_on_timesheet)",
+ "fieldname": "payroll_frequency",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Payroll Frequency",
+ "length": 0,
+ "no_copy": 0,
+ "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -863,7 +894,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-11-07 05:13:46.584365",
+ "modified": "2016-12-07 14:57:22.083825",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Structure",
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
index 81f6743..9217c64 100644
--- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
@@ -8,25 +8,16 @@
from frappe.utils.make_random import get_random
from frappe.utils import nowdate, add_days, add_years
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
-from erpnext.hr.doctype.salary_slip.test_salary_slip import make_salary_component
-# test_records = frappe.get_test_records('Salary Structure')
+from erpnext.hr.doctype.salary_slip.test_salary_slip import make_earning_salary_component, make_deduction_salary_component
class TestSalaryStructure(unittest.TestCase):
def test_setup(self):
- if not frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2016"):
- fy = frappe.get_doc({
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2016",
- "year_end_date": "2016-12-31",
- "year_start_date": "2016-01-01"
- })
- fy.insert()
-
self.make_holiday_list()
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List")
- make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"])
- employee1 = self.make_employee("test_employee@salary.com")
- employee2 = self.make_employee("test_employee_2@salary.com")
+ make_earning_salary_component(["Basic Salary", "Allowance", "HRA"])
+ make_deduction_salary_component(["Professional Tax", "TDS"])
+ self.make_employee("test_employee@salary.com")
+ self.make_employee("test_employee_2@salary.com")
def make_holiday_list(self):
if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"):
@@ -87,9 +78,9 @@
sal_struct = make_salary_structure('Salary Structure Sample')
sal_slip = make_salary_slip(sal_struct, employee = employee)
sal_slip.employee_name = frappe.get_value("Employee", {"name":employee}, "employee_name")
- sal_slip.month = "11"
- sal_slip.fiscal_year = "_Test Fiscal Year 2016"
+ sal_slip.start_date = nowdate()
sal_slip.posting_date = nowdate()
+ sal_slip.payroll_frequency = "Monthly"
sal_slip.insert()
sal_slip.submit()
return sal_slip
@@ -104,6 +95,7 @@
"employees": get_employee_details(),
"earnings": get_earnings_component(),
"deductions": get_deductions_component(),
+ "payroll_frequency": "Monthly",
"payment_account": frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
}).insert()
return sal_struct
diff --git a/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json b/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json
index 1789c75..0c38d45 100644
--- a/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json
+++ b/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json
@@ -6,7 +6,7 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
- "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"HTML\", \"options\": \" <h3 style=\\\"text-align: right;\\\"><span style=\\\"line-height: 1.42857;\\\">{{doc.name}}</span></h3>\\n<div>\\n <hr style=\\\"text-align: center;\\\">\\n</div> \"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"employee\"}, {\"print_hide\": 0, \"fieldname\": \"company\"}, {\"print_hide\": 0, \"fieldname\": \"employee_name\"}, {\"print_hide\": 0, \"fieldname\": \"department\"}, {\"print_hide\": 0, \"fieldname\": \"designation\"}, {\"print_hide\": 0, \"fieldname\": \"branch\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"start_date\"}, {\"print_hide\": 0, \"fieldname\": \"end_date\"}, {\"print_hide\": 0, \"fieldname\": \"total_days_in_month\"}, {\"print_hide\": 0, \"fieldname\": \"leave_without_pay\"}, {\"print_hide\": 0, \"fieldname\": \"payment_days\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"earnings\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"deductions\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"arrear_amount\"}, {\"print_hide\": 0, \"fieldname\": \"leave_encashment_amount\"}, {\"print_hide\": 0, \"fieldname\": \"gross_pay\"}, {\"print_hide\": 0, \"fieldname\": \"total_deduction\"}, {\"print_hide\": 0, \"fieldname\": \"net_pay\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\"}, {\"print_hide\": 0, \"fieldname\": \"total_in_words\"}]",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"HTML\", \"options\": \" <h3 style=\\\"text-align: right;\\\"><span style=\\\"line-height: 1.42857;\\\">{{doc.name}}</span></h3>\\n<div>\\n <hr style=\\\"text-align: center;\\\">\\n</div> \"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"employee\"}, {\"print_hide\": 0, \"fieldname\": \"company\"}, {\"print_hide\": 0, \"fieldname\": \"employee_name\"}, {\"print_hide\": 0, \"fieldname\": \"department\"}, {\"print_hide\": 0, \"fieldname\": \"designation\"}, {\"print_hide\": 0, \"fieldname\": \"branch\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"start_date\"}, {\"print_hide\": 0, \"fieldname\": \"end_date\"}, {\"print_hide\": 0, \"fieldname\": \"total_working_days\"}, {\"print_hide\": 0, \"fieldname\": \"leave_without_pay\"}, {\"print_hide\": 0, \"fieldname\": \"payment_days\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"earnings\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"deductions\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"arrear_amount\"}, {\"print_hide\": 0, \"fieldname\": \"leave_encashment_amount\"}, {\"print_hide\": 0, \"fieldname\": \"gross_pay\"}, {\"print_hide\": 0, \"fieldname\": \"total_deduction\"}, {\"print_hide\": 0, \"fieldname\": \"net_pay\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\"}, {\"print_hide\": 0, \"fieldname\": \"total_in_words\"}]",
"idx": 0,
"modified": "2016-08-22 00:21:42.600548",
"modified_by": "Administrator",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 6069a4d..94212ec 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -354,3 +354,5 @@
erpnext.patches.v7_0.update_autoname_field
erpnext.patches.v7_1.update_bom_base_currency
erpnext.patches.v7_0.update_status_of_po_so
+erpnext.patches.v7_1.set_budget_against_as_cost_center
+erpnext.patches.v7_1.set_currency_exchange_date
\ No newline at end of file
diff --git a/erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py b/erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py
index b660d39..481f130 100644
--- a/erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py
+++ b/erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py
@@ -22,7 +22,7 @@
# Update billed_amt in DN and PR which are not against any order
for d in frappe.db.sql("""select name from `tabPurchase Receipt Item` item
- where (prevdoc_detail_docname is null or prevdoc_detail_docname = '') and docstatus=1""", as_dict=1):
+ where (purchase_order_item is null or purchase_order_item = '') and docstatus=1""", as_dict=1):
billed_amt = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item`
where pr_detail=%s and docstatus=1""", d.name)
diff --git a/erpnext/patches/v7_0/update_status_of_po_so.py b/erpnext/patches/v7_0/update_status_of_po_so.py
index 0e2dd74..c0b6f59 100644
--- a/erpnext/patches/v7_0/update_status_of_po_so.py
+++ b/erpnext/patches/v7_0/update_status_of_po_so.py
@@ -18,9 +18,9 @@
`tabPurchase Order`.per_received = round((select sum(if(qty > ifnull(received_qty, 0),
ifnull(received_qty, 0), qty)) / sum(qty) *100 from `tabPurchase Order Item`
where parent = `tabPurchase Order`.name), 2),
- `tabPurchase Order`.per_billed = round((select sum( if(amount > ifnull(billed_amt, 0),
+ `tabPurchase Order`.per_billed = ifnull(round((select sum( if(amount > ifnull(billed_amt, 0),
ifnull(billed_amt, 0), amount)) / sum(amount) *100 from `tabPurchase Order Item`
- where parent = `tabPurchase Order`.name), 2)""")
+ where parent = `tabPurchase Order`.name), 2), 0)""")
def update_so_per_delivered_per_billed():
frappe.db.sql("""
@@ -30,9 +30,9 @@
`tabSales Order`.per_delivered = round((select sum( if(qty > ifnull(delivered_qty, 0),
ifnull(delivered_qty, 0), qty)) / sum(qty) *100 from `tabSales Order Item`
where parent = `tabSales Order`.name), 2),
- `tabSales Order`.per_billed = round((select sum( if(amount > ifnull(billed_amt, 0),
+ `tabSales Order`.per_billed = ifnull(round((select sum( if(amount > ifnull(billed_amt, 0),
ifnull(billed_amt, 0), amount)) / sum(amount) *100 from `tabSales Order Item`
- where parent = `tabSales Order`.name), 2)""")
+ where parent = `tabSales Order`.name), 2), 0)""")
def update_status():
frappe.db.sql("""
diff --git a/erpnext/patches/v7_1/set_budget_against_as_cost_center.py b/erpnext/patches/v7_1/set_budget_against_as_cost_center.py
new file mode 100644
index 0000000..1d334a5
--- /dev/null
+++ b/erpnext/patches/v7_1/set_budget_against_as_cost_center.py
@@ -0,0 +1,10 @@
+import frappe
+
+def execute():
+ frappe.reload_doc("accounts", "doctype", "budget")
+ frappe.db.sql("""
+ update
+ `tabBudget`
+ set
+ budget_against = 'Cost Center'
+ """)
diff --git a/erpnext/patches/v7_1/set_currency_exchange_date.py b/erpnext/patches/v7_1/set_currency_exchange_date.py
new file mode 100644
index 0000000..7d8e4f0
--- /dev/null
+++ b/erpnext/patches/v7_1/set_currency_exchange_date.py
@@ -0,0 +1,9 @@
+import frappe
+
+def execute():
+ frappe.reload_doctype("Currency Exchange")
+ frappe.db.sql("""
+ update `tabCurrency Exchange`
+ set `date` = '2010-01-01'
+ where date is null or date = '' or date = '0000-00-00'
+ """)
\ No newline at end of file
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 98866ab..11d0b1c 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -7,7 +7,7 @@
import unittest
import datetime
from frappe.utils.make_random import get_random
-from frappe.utils import now_datetime, nowdate
+from frappe.utils import now_datetime, nowdate, add_days
from erpnext.projects.doctype.timesheet.timesheet import OverlapError
from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -76,7 +76,7 @@
salary_structure = frappe.new_doc("Salary Structure")
salary_structure.name = "Timesheet Salary Structure Test"
salary_structure.salary_slip_based_on_timesheet = 1
- salary_structure.from_date = nowdate()
+ salary_structure.from_date = add_days(nowdate(), -30)
salary_structure.salary_component = "Basic"
salary_structure.hour_rate = 50.0
salary_structure.company = "_Test Company"
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index bb19581..48d3e00 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -79,9 +79,9 @@
self.status = "Completed"
def set_dates(self):
- if self.docstatus < 2:
- start_date = min([d.from_time for d in self.time_logs])
- end_date = max([d.to_time for d in self.time_logs])
+ if self.docstatus < 2 and self.time_logs:
+ start_date = min([getdate(d.from_time) for d in self.time_logs])
+ end_date = max([getdate(d.to_time) for d in self.time_logs])
if start_date and end_date:
self.start_date = getdate(start_date)
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 954b03b..c31b0c8 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -413,6 +413,7 @@
transaction_date: function() {
if (this.frm.doc.transaction_date) {
this.frm.transaction_date = this.frm.doc.transaction_date;
+ frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
}
},
@@ -434,9 +435,12 @@
callback: function(r, rt) {
if(r.message) {
me.frm.set_value("due_date", r.message);
+ frappe.ui.form.trigger(me.frm.doc.doctype, "currency");
}
}
})
+ } else {
+ frappe.ui.form.trigger(me.frm.doc.doctype, "currency");
}
}
},
@@ -450,6 +454,10 @@
},
currency: function() {
+ /* manqala 19/09/2016: let the translation date be whichever of the transaction_date or posting_date is available */
+ var transaction_date = this.frm.doc.transaction_date || this.frm.doc.posting_date;
+ /* end manqala */
+
var me = this;
this.set_dynamic_labels();
@@ -457,7 +465,7 @@
// Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
if(this.frm.doc.currency && this.frm.doc.currency !== company_currency
&& !this.frm.doc.ignore_pricing_rule) {
- this.get_exchange_rate(this.frm.doc.currency, company_currency,
+ this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency,
function(exchange_rate) {
me.frm.set_value("conversion_rate", exchange_rate);
});
@@ -485,10 +493,11 @@
}
},
- get_exchange_rate: function(from_currency, to_currency, callback) {
+ get_exchange_rate: function(transaction_date, from_currency, to_currency, callback) {
return frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
+ transaction_date: transaction_date,
from_currency: from_currency,
to_currency: to_currency
},
@@ -505,7 +514,7 @@
var company_currency = this.get_company_currency();
// Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
if(this.frm.doc.price_list_currency !== company_currency && !this.frm.doc.ignore_pricing_rule) {
- this.get_exchange_rate(this.frm.doc.price_list_currency, company_currency,
+ this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency,
function(exchange_rate) {
me.frm.set_value("plc_conversion_rate", exchange_rate);
});
diff --git a/erpnext/setup/doctype/company/fixtures/india/__init__.py b/erpnext/setup/doctype/company/fixtures/india/__init__.py
index 2aeec99..0f4bd4f 100644
--- a/erpnext/setup/doctype/company/fixtures/india/__init__.py
+++ b/erpnext/setup/doctype/company/fixtures/india/__init__.py
@@ -7,9 +7,10 @@
def install(company):
docs = [
- {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', 'description': 'Professional Tax'},
- {'doctype': 'Salary Component', 'salary_component': 'Provident Fund', 'description': 'Provident fund'},
- {'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance', 'description': 'House Rent Allowance'}
+ {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', 'description': 'Professional Tax', 'type': 'Deduction'},
+ {'doctype': 'Salary Component', 'salary_component': 'Provident Fund', 'description': 'Provident fund', 'type': 'Deduction'},
+ {'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance', 'description': 'House Rent Allowance', 'type': 'Earning'},
+ {'doctype': 'Salary Component', 'salary_component': 'Basic', 'description': 'Basic', 'type': 'Earning'}
]
for d in docs:
diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.json b/erpnext/setup/doctype/currency_exchange/currency_exchange.json
index 44cea20..76e1a6b 100644
--- a/erpnext/setup/doctype/currency_exchange/currency_exchange.json
+++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.json
@@ -17,6 +17,33 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0,
+ "width": "5"
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "from_currency",
"fieldtype": "Link",
"hidden": 0,
@@ -38,7 +65,8 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "unique": 0
+ "unique": 0,
+ "width": "3"
},
{
"allow_on_submit": 0,
@@ -66,7 +94,8 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "unique": 0
+ "unique": 0,
+ "width": "3"
},
{
"allow_on_submit": 0,
@@ -94,7 +123,8 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "unique": 0
+ "unique": 0,
+ "width": "3"
}
],
"hide_heading": 0,
@@ -108,7 +138,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-11-07 05:28:09.772560",
+ "modified": "2016-11-08 05:28:09.772560",
"modified_by": "Administrator",
"module": "Setup",
"name": "Currency Exchange",
@@ -202,5 +232,8 @@
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
+ "sort_field": "name",
+ "sort_order": "DESC",
+ "title_field": "",
"track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.py b/erpnext/setup/doctype/currency_exchange/currency_exchange.py
index 6022812..7f1a43c 100644
--- a/erpnext/setup/doctype/currency_exchange/currency_exchange.py
+++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.py
@@ -7,13 +7,15 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from frappe.utils import get_datetime, get_datetime_str, formatdate
class CurrencyExchange(Document):
- def autoname(self):
- self.name = self.from_currency + "-" + self.to_currency
+ def autoname(self):
+ self.name = formatdate(get_datetime_str(self.date),"yyyy-MM-dd") + "-" + self.from_currency + "-" + self.to_currency
+ #self.name = self.date + "-" + self.from_currency + "-" + self.to_currency
- def validate(self):
- self.validate_value("exchange_rate", ">", 0)
+ def validate(self):
+ self.validate_value("exchange_rate", ">", 0)
- if self.from_currency == self.to_currency:
- frappe.throw(_("From Currency and To Currency cannot be same"))
+ if self.from_currency == self.to_currency:
+ frappe.throw(_("From Currency and To Currency cannot be same"))
diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
index 0077630..181f072 100644
--- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
+++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
@@ -3,5 +3,18 @@
from __future__ import unicode_literals
-import frappe
-test_records = frappe.get_test_records('Currency Exchange')
\ No newline at end of file
+import frappe, unittest
+test_records = frappe.get_test_records('Currency Exchange')
+
+class TestCurrencyExchange(unittest.TestCase):
+ def test_exchnage_rate(self):
+ from erpnext.setup.utils import get_exchange_rate
+
+ # Exchange rate as on 15th Jan, 2016, should be fetched from Currency Exchange record
+ exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15")
+ self.assertEqual(exchange_rate, 60.0)
+
+ # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
+ exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15")
+ self.assertFalse(exchange_rate==60)
+
\ No newline at end of file
diff --git a/erpnext/setup/doctype/currency_exchange/test_records.json b/erpnext/setup/doctype/currency_exchange/test_records.json
index 784bf26..23edd8a 100644
--- a/erpnext/setup/doctype/currency_exchange/test_records.json
+++ b/erpnext/setup/doctype/currency_exchange/test_records.json
@@ -1,18 +1,21 @@
[
{
"doctype": "Currency Exchange",
+ "date": "2016-01-01",
"exchange_rate": 60.0,
"from_currency": "USD",
"to_currency": "INR"
},
{
"doctype": "Currency Exchange",
+ "date": "2016-01-01",
"exchange_rate": 0.773,
"from_currency": "USD",
"to_currency": "EUR"
},
{
"doctype": "Currency Exchange",
+ "date": "2016-01-01",
"exchange_rate": 0.0167,
"from_currency": "INR",
"to_currency": "USD"
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index eda2042..0c214e4 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -5,7 +5,8 @@
import frappe
from frappe import _, throw
from frappe.utils import flt
-
+from frappe.utils import get_datetime_str, nowdate
+
def get_company_currency(company):
currency = frappe.db.get_value("Company", company, "default_currency", cache=True)
if not currency:
@@ -64,36 +65,44 @@
frappe.db.commit()
@frappe.whitelist()
-def get_exchange_rate(from_currency, to_currency):
+def get_exchange_rate(from_currency, to_currency, transaction_date=None):
+ if not transaction_date:
+ transaction_date = nowdate()
if not (from_currency and to_currency):
+ # manqala 19/09/2016: Should this be an empty return or should it throw and exception?
return
if from_currency == to_currency:
return 1
- exchange = "%s-%s" % (from_currency, to_currency)
- value = flt(frappe.db.get_value("Currency Exchange", exchange, "exchange_rate"))
+ # cksgb 19/09/2016: get last entry in Currency Exchange with from_currency and to_currency.
+ entries = frappe.get_all("Currency Exchange", fields = ["exchange_rate"],
+ filters=[
+ ["date", "<=", get_datetime_str(transaction_date)],
+ ["from_currency", "=", from_currency],
+ ["to_currency", "=", to_currency]
+ ], order_by="date desc", limit=1)
+
+ if entries:
+ return flt(entries[0].exchange_rate)
- if not value:
- try:
- cache = frappe.cache()
- key = "currency_exchange_rate:{0}:{1}".format(from_currency, to_currency)
- value = cache.get(key)
+ try:
+ cache = frappe.cache()
+ key = "currency_exchange_rate:{0}:{1}".format(from_currency, to_currency)
+ value = cache.get(key)
- if not value:
- import requests
- response = requests.get("http://api.fixer.io/latest", params={
- "base": from_currency,
- "symbols": to_currency
- })
- # expire in 6 hours
- response.raise_for_status()
- value = response.json()["rates"][to_currency]
- cache.setex(key, value, 6 * 60 * 60)
+ if not value:
+ import requests
+ response = requests.get("http://api.fixer.io/latest", params={
+ "base": from_currency,
+ "symbols": to_currency
+ })
+ # expire in 6 hours
+ response.raise_for_status()
+ value = response.json()["rates"][to_currency]
+ cache.setex(key, value, 6 * 60 * 60)
- return flt(value)
- except:
- frappe.msgprint(_("Unable to find exchange rate for {0} to {1}").format(from_currency, to_currency))
- return 0.0
- else:
- return value
+ return flt(value)
+ except:
+ frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}").format(from_currency, to_currency, transaction_date))
+ return 0.0
\ No newline at end of file
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
index d8d00ef..6915ef5 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
+++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
@@ -8,6 +8,7 @@
from frappe import _, msgprint
from frappe.utils import comma_and
from frappe.model.document import Document
+from frappe.utils import get_datetime, get_datetime_str, now_datetime
class ShoppingCartSetupError(frappe.ValidationError): pass
@@ -38,11 +39,18 @@
expected_to_exist = [currency + "-" + company_currency
for currency in price_list_currency_map.values()
if currency != company_currency]
+
+ # manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange
+ from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency]
+ to_currency = company_currency
+ # manqala end
if expected_to_exist:
- exists = frappe.db.sql_list("""select name from `tabCurrency Exchange`
- where name in (%s)""" % (", ".join(["%s"]*len(expected_to_exist)),),
- tuple(expected_to_exist))
+ # manqala 20/09/2016: modify query so that it uses date in the selection from Currency Exchange.
+ # exchange rates defined with date less than the date on which this document is being saved will be selected
+ exists = frappe.db.sql_list("""select CONCAT(from_currency,'-',to_currency) from `tabCurrency Exchange`
+ where from_currency in (%s) and to_currency = "%s" and date <= curdate()""" % (", ".join(["%s"]*len(from_currency)), to_currency), tuple(from_currency))
+ # manqala end
missing = list(set(expected_to_exist).difference(exists))
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index f7ca7d9..b1d6ec0 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -23,6 +23,7 @@
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Item Code",
"length": 0,
"no_copy": 0,
@@ -34,6 +35,7 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
@@ -53,12 +55,14 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -77,6 +81,7 @@
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Item Name",
"length": 0,
"no_copy": 0,
@@ -87,6 +92,7 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
@@ -106,6 +112,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
@@ -114,6 +121,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -132,6 +140,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
@@ -142,6 +151,7 @@
"print_hide_if_no_value": 0,
"print_width": "250px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -161,6 +171,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -168,6 +179,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -186,6 +198,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 0,
@@ -194,6 +207,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -212,6 +226,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Quantity and Warehouse",
"length": 0,
"no_copy": 0,
@@ -219,6 +234,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -237,6 +253,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Quantity",
"length": 0,
"no_copy": 0,
@@ -247,6 +264,7 @@
"print_hide_if_no_value": 0,
"print_width": "80px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -266,6 +284,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Stock UOM",
"length": 0,
"no_copy": 0,
@@ -277,6 +296,7 @@
"print_hide_if_no_value": 0,
"print_width": "70px",
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -296,6 +316,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "For Warehouse",
"length": 0,
"no_copy": 0,
@@ -307,6 +328,7 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -326,12 +348,14 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -351,6 +375,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Required Date",
"length": 0,
"no_copy": 0,
@@ -361,6 +386,7 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -380,6 +406,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "More Information",
"length": 0,
"no_copy": 0,
@@ -387,6 +414,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -406,6 +434,7 @@
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Item Group",
"length": 0,
"no_copy": 0,
@@ -416,6 +445,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
@@ -434,6 +464,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Brand",
"length": 0,
"no_copy": 0,
@@ -445,6 +476,7 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -464,6 +496,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Lead Time Date",
"length": 0,
"no_copy": 1,
@@ -473,6 +506,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -491,6 +525,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Sales Order",
"length": 0,
"no_copy": 0,
@@ -499,6 +534,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -517,6 +553,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Project",
"length": 0,
"no_copy": 0,
@@ -526,6 +563,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -544,12 +582,14 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -568,6 +608,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Min Order Qty",
"length": 0,
"no_copy": 1,
@@ -578,6 +619,7 @@
"print_hide_if_no_value": 0,
"print_width": "70px",
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -597,6 +639,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Projected Qty",
"length": 0,
"no_copy": 1,
@@ -607,6 +650,7 @@
"print_hide_if_no_value": 0,
"print_width": "70px",
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -626,6 +670,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Actual Qty",
"length": 0,
"no_copy": 1,
@@ -634,6 +679,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 1,
"reqd": 0,
"search_index": 0,
@@ -652,6 +698,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Completed Qty",
"length": 0,
"no_copy": 1,
@@ -661,6 +708,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -679,6 +727,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Page Break",
"length": 0,
"no_copy": 1,
@@ -688,6 +737,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -705,7 +755,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-10-17 04:58:33.317145",
+ "modified": "2016-12-08 14:49:48.397015",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 3ba3056..c3808f7 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -33,12 +33,11 @@
def on_cancel(self):
if self.reference_type and self.reference_name:
- frappe.db.sql("""update `tab{doctype} Item` t1, `tab{doctype}` t2
- set t1.quality_inspection = null, t2.modified = %s
- where t1.parent = %s and t1.item_code = %s and t1.parent = t2.name"""
- .format(doctype=self.reference_type),
- (self.modified, self.reference_name, self.item_code))
-
+ frappe.db.sql("""update `tab{doctype} Item`
+ set quality_inspection = null, modified=%s
+ where quality_inspection = %s"""
+ .format(doctype=self.reference_type), (self.modified, self.name))
+
def item_query(doctype, txt, searchfield, start, page_len, filters):
if filters.get("from"):
from frappe.desk.reportview import get_match_cond
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 57e2c06..16ea58e 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -43,7 +43,7 @@
get_party_item_code(args, item_doc, out)
if out.get("warehouse"):
- out.update(get_bin_details_and_serial_nos(args.item_code, out.warehouse, args.qty, args.serial_no))
+ out.update(get_bin_details(args.item_code, out.warehouse))
if frappe.db.exists("Product Bundle", args.item_code):
valuation_rate = 0.0
@@ -481,7 +481,9 @@
if (not plc_conversion_rate) or (price_list_currency and args.price_list_currency \
and price_list_currency != args.price_list_currency):
- plc_conversion_rate = get_exchange_rate(price_list_currency, args.currency) or plc_conversion_rate
+ # cksgb 19/09/2016: added args.transaction_date as posting_date argument for get_exchange_rate
+ plc_conversion_rate = get_exchange_rate(price_list_currency, args.currency,
+ args.transaction_date) or plc_conversion_rate
return frappe._dict({
"price_list_currency": price_list_currency,
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index 21ef5b8..2bee422 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -15,5 +15,21 @@
frm.save();
});
}
+ },
+
+ timeline_refresh: function(frm) {
+ // create button for "Add to Knowledge Base"
+ if(frappe.model.can_create('Help Article')) {
+ $('<button class="btn btn-xs btn-default btn-add-to-kb pull-right" style="margin-top: -2px">'+
+ __('Add to Knowledge Base') + '</button>')
+ .appendTo(frm.timeline.wrapper.find('.comment-header'))
+ .on('click', function() {
+ var content = $(this).parents('.timeline-item:first').find('.timeline-item-content').html();
+ var doc = frappe.model.get_new_doc('Help Article');
+ doc.title = frm.doc.subject;
+ doc.content = content;
+ frappe.set_route('Form', 'Help Article', doc.name);
+ });
+ }
}
});
diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json
index 4b7c70c..6474799 100644
--- a/erpnext/support/web_form/issues/issues.json
+++ b/erpnext/support/web_form/issues/issues.json
@@ -1,8 +1,12 @@
{
+ "accept_payment": 0,
"allow_comments": 1,
"allow_delete": 1,
"allow_edit": 1,
+ "allow_incomplete": 0,
"allow_multiple": 1,
+ "allow_print": 0,
+ "amount": 0.0,
"breadcrumbs": "[{\"title\":\"Issues\", \"name\":\"issues\"}]",
"creation": "2016-06-24 15:50:33.186483",
"doc_type": "Issue",
@@ -11,13 +15,16 @@
"idx": 0,
"is_standard": 1,
"login_required": 1,
- "modified": "2016-06-24 15:52:24.768558",
+ "max_attachment_size": 0,
+ "modified": "2016-12-07 04:26:13.917693",
"modified_by": "Administrator",
"module": "Support",
"name": "issues",
"owner": "Administrator",
"published": 1,
"route": "issues",
+ "show_sidebar": 1,
+ "sidebar_items": [],
"success_message": "",
"success_url": "/issues",
"title": "Issue",
@@ -27,6 +34,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "Subject",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 1
},
@@ -36,15 +45,30 @@
"fieldtype": "Select",
"hidden": 0,
"label": "Status",
+ "max_length": 0,
+ "max_value": 0,
"options": "Open\nReplied\nHold\nClosed",
"read_only": 1,
"reqd": 0
},
{
+ "fieldname": "customer",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Customer",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Customer",
+ "read_only": 1,
+ "reqd": 0
+ },
+ {
"fieldname": "description",
"fieldtype": "Text",
"hidden": 0,
"label": "Description",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
},
@@ -53,6 +77,8 @@
"fieldtype": "Attach",
"hidden": 0,
"label": "Attachment",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
}
diff --git a/erpnext/utilities/web_form/addresses/addresses.json b/erpnext/utilities/web_form/addresses/addresses.json
index 50fe6e9..6d3fefc 100644
--- a/erpnext/utilities/web_form/addresses/addresses.json
+++ b/erpnext/utilities/web_form/addresses/addresses.json
@@ -1,8 +1,12 @@
{
+ "accept_payment": 0,
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 1,
+ "allow_incomplete": 0,
"allow_multiple": 1,
+ "allow_print": 0,
+ "amount": 0.0,
"creation": "2016-06-24 15:50:33.196990",
"doc_type": "Address",
"docstatus": 0,
@@ -10,13 +14,16 @@
"idx": 0,
"is_standard": 1,
"login_required": 1,
- "modified": "2016-06-24 16:11:28.802353",
+ "max_attachment_size": 0,
+ "modified": "2016-12-07 04:17:02.020768",
"modified_by": "Administrator",
"module": "Utilities",
"name": "addresses",
"owner": "Administrator",
"published": 1,
"route": "address",
+ "show_sidebar": 0,
+ "sidebar_items": [],
"success_url": "/addresses",
"title": "Address",
"web_form_fields": [
@@ -26,6 +33,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "Address Title",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
},
@@ -34,6 +43,8 @@
"fieldtype": "Select",
"hidden": 0,
"label": "Address Type",
+ "max_length": 0,
+ "max_value": 0,
"options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nOther",
"read_only": 0,
"reqd": 1
@@ -43,6 +54,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "Address Line 1",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 1
},
@@ -51,6 +64,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "Address Line 2",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
},
@@ -59,6 +74,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "City/Town",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 1
},
@@ -67,6 +84,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "State",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
},
@@ -75,6 +94,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "Postal Code",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
},
@@ -83,6 +104,8 @@
"fieldtype": "Link",
"hidden": 0,
"label": "Country",
+ "max_length": 0,
+ "max_value": 0,
"options": "Country",
"read_only": 0,
"reqd": 1
@@ -90,6 +113,8 @@
{
"fieldtype": "Column Break",
"hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
},
@@ -98,6 +123,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "Email Id",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
},
@@ -106,6 +133,8 @@
"fieldtype": "Data",
"hidden": 0,
"label": "Phone",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 1
},
@@ -116,6 +145,8 @@
"fieldtype": "Check",
"hidden": 0,
"label": "Preferred Billing Address",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
},
@@ -126,6 +157,8 @@
"fieldtype": "Check",
"hidden": 0,
"label": "Preferred Shipping Address",
+ "max_length": 0,
+ "max_value": 0,
"read_only": 0,
"reqd": 0
}