Merge branch 'develop' of https://github.com/frappe/erpnext into loan_patch_and_fixes
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 87fe371..4d43919 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -162,7 +162,8 @@
debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date)
supplier_credit_amount -= debit_note_amount
- if supplier_credit_amount >= tax_details.get('threshold', 0) or supplier_credit_amount >= tax_details.get('cumulative_threshold', 0):
+ if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
+ or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total,
ldc.certificate_limit):
@@ -224,4 +225,4 @@
certificate_limit > deducted_amount):
valid = True
- return valid
+ return valid
\ No newline at end of file
diff --git a/erpnext/hr/doctype/department/department.json b/erpnext/hr/doctype/department/department.json
index 6469f4c..a54c1d1 100644
--- a/erpnext/hr/doctype/department/department.json
+++ b/erpnext/hr/doctype/department/department.json
@@ -14,6 +14,8 @@
"is_group",
"disabled",
"section_break_4",
+ "payroll_cost_center",
+ "column_break_9",
"leave_block_list",
"leave_section",
"leave_approvers",
@@ -125,13 +127,23 @@
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "payroll_cost_center",
+ "fieldtype": "Link",
+ "label": "Payroll Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-sitemap",
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2020-03-18 18:03:27.784362",
+ "modified": "2020-05-05 18:49:28.503931",
"modified_by": "Administrator",
"module": "HR",
"name": "Department",
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 13c202c..f575765 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -60,6 +60,8 @@
"default_shift",
"salary_information",
"salary_mode",
+ "payroll_cost_center",
+ "column_break_52",
"bank_name",
"bank_ac_no",
"health_insurance_section",
@@ -783,13 +785,25 @@
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "department.payroll_cost_center",
+ "fetch_if_empty": 1,
+ "fieldname": "payroll_cost_center",
+ "fieldtype": "Link",
+ "label": "Payroll Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "column_break_52",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2020-04-08 12:25:34.306695",
+ "modified": "2020-05-05 18:51:03.152503",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index d3410de..f4b214a 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -45,7 +45,7 @@
employee1_doc.status = 'Left'
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
-def make_employee(user, company=None):
+def make_employee(user, company=None, **kwargs):
if not frappe.db.get_value("User", user):
frappe.get_doc({
"doctype": "User",
@@ -55,7 +55,7 @@
"roles": [{"doctype": "Has Role", "role": "Employee"}]
}).insert()
- if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() }):
+ if not frappe.db.get_value("Employee", {"user_id": user}):
employee = frappe.get_doc({
"doctype": "Employee",
"naming_series": "EMP-",
@@ -71,7 +71,10 @@
"prefered_email": user,
"status": "Active",
"employment_type": "Intern"
- }).insert()
+ })
+ if kwargs:
+ employee.update(kwargs)
+ employee.insert()
return employee.name
else:
return frappe.get_value("Employee", {"employee_name":user}, "name")
diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py
index 9ef3a99..656de01 100644
--- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py
@@ -55,6 +55,7 @@
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
{condition}""".format(condition=condition),
{"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
+
if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s "
cond += "and %(from_date)s >= t2.from_date"
@@ -138,7 +139,7 @@
cond = self.get_filter_condition()
ss_list = frappe.db.sql("""
- select t1.name, t1.salary_structure from `tabSalary Slip` t1
+ select t1.name, t1.salary_structure, t1.payroll_cost_center 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.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
@@ -169,10 +170,14 @@
def get_salary_components(self, component_type):
salary_slips = self.get_sal_slip_list(ss_status = 1, as_dict = True)
- if salary_slips:
- salary_components = frappe.db.sql("""select salary_component, amount, parentfield
- from `tabSalary Detail` where parentfield = '%s' and parent in (%s)""" %
- (component_type, ', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=True)
+ if salary_slips:
+ salary_components = frappe.db.sql("""
+ select ssd.salary_component, ssd.amount, ssd.parentfield, ss.payroll_cost_center
+ from `tabSalary Slip` ss, `tabSalary Detail` ssd
+ where ss.name = ssd.parent and ssd.parentfield = '%s' and ss.name in (%s)
+ """ % (component_type, ', '.join(['%s']*len(salary_slips))),
+ tuple([d.name for d in salary_slips]), as_dict=True)
+
return salary_components
def get_salary_component_total(self, component_type = None):
@@ -186,15 +191,16 @@
if is_flexible_benefit == 1 and only_tax_impact ==1:
add_component_to_accrual_jv_entry = False
if add_component_to_accrual_jv_entry:
- component_dict[item['salary_component']] = component_dict.get(item['salary_component'], 0) + item['amount']
+ component_dict[(item.salary_component, item.payroll_cost_center)] \
+ = component_dict.get((item.salary_component, item.payroll_cost_center), 0) + flt(item.amount)
account_details = self.get_account(component_dict = component_dict)
return account_details
def get_account(self, component_dict = None):
- account_dict = {}
- for s, a in component_dict.items():
- account = self.get_salary_component_account(s)
- account_dict[account] = account_dict.get(account, 0) + a
+ account_dict = {}
+ for key, amount in component_dict.items():
+ account = self.get_salary_component_account(key[0])
+ account_dict[(account, key[1])] = account_dict.get((account, key[1]), 0) + amount
return account_dict
def get_default_payroll_payable_account(self):
@@ -227,23 +233,23 @@
payable_amount = 0
# Earnings
- for acc, amount in earnings.items():
+ for acc_cc, amount in earnings.items():
payable_amount += flt(amount, precision)
accounts.append({
- "account": acc,
+ "account": acc_cc[0],
"debit_in_account_currency": flt(amount, precision),
"party_type": '',
- "cost_center": self.cost_center,
+ "cost_center": acc_cc[1] or self.cost_center,
"project": self.project
})
# Deductions
- for acc, amount in deductions.items():
+ for acc_cc, amount in deductions.items():
payable_amount -= flt(amount, precision)
accounts.append({
- "account": acc,
+ "account": acc_cc[0],
"credit_in_account_currency": flt(amount, precision),
- "cost_center": self.cost_center,
+ "cost_center": acc_cc[1] or self.cost_center,
"party_type": '',
"project": self.project
})
@@ -253,6 +259,7 @@
"account": default_payroll_payable_account,
"credit_in_account_currency": flt(payable_amount, precision),
"party_type": '',
+ "cost_center": self.cost_center
})
journal_entry.set("accounts", accounts)
diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
index e43f744..3c318e7 100644
--- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
@@ -10,15 +10,16 @@
from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
- make_earning_salary_component, make_deduction_salary_component
+ make_earning_salary_component, make_deduction_salary_component, create_account
from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure
from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
class TestPayrollEntry(unittest.TestCase):
def setUp(self):
- for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure"]:
- frappe.db.sql("delete from `tab%s`" % dt)
+ for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
+ "Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]:
+ frappe.db.sql("delete from `tab%s`" % dt)
make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
@@ -33,11 +34,59 @@
get_salary_component_account(data.name)
employee = frappe.db.get_value("Employee", {'company': company})
- make_salary_structure("_Test Salary Structure", "Monthly", employee)
+ make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company)
dates = get_start_end_dates('Monthly', nowdate())
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date)
+ def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use
+ for data in frappe.get_all('Salary Component', fields = ["name"]):
+ if not frappe.db.get_value('Salary Component Account',
+ {'parent': data.name, 'company': "_Test Company"}, 'name'):
+ get_salary_component_account(data.name)
+
+ if not frappe.db.exists('Department', "cc - _TC"):
+ frappe.get_doc({
+ 'doctype': 'Department',
+ 'department_name': "cc",
+ "company": "_Test Company"
+ }).insert()
+
+ employee1 = make_employee("test_employee1@example.com", payroll_cost_center="_Test Cost Center - _TC",
+ department="cc - _TC", company="_Test Company")
+ employee2 = make_employee("test_employee2@example.com", payroll_cost_center="_Test Cost Center 2 - _TC",
+ department="cc - _TC", company="_Test Company")
+
+ make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company")
+ make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company")
+
+ if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
+ create_account(account_name="_Test Payroll Payable",
+ company="_Test Company", parent_account="Current Liabilities - _TC")
+ frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
+ "_Test Payroll Payable - _TC")
+
+ dates = get_start_end_dates('Monthly', nowdate())
+ if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
+ pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
+ department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
+ je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
+ je_entries = frappe.db.sql("""
+ select account, cost_center, debit, credit
+ from `tabJournal Entry Account`
+ where parent=%s
+ order by account, cost_center
+ """, je)
+ expected_je = (
+ ('_Test Payroll Payable - _TC', 'Main - _TC', 0.0, 155600.0),
+ ('Salary - _TC', '_Test Cost Center - _TC', 78000.0, 0.0),
+ ('Salary - _TC', '_Test Cost Center 2 - _TC', 78000.0, 0.0),
+ ('Salary Deductions - _TC', '_Test Cost Center - _TC', 0.0, 200.0),
+ ('Salary Deductions - _TC', '_Test Cost Center 2 - _TC', 0.0, 200.0)
+ )
+
+ self.assertEqual(je_entries, expected_je)
+
def test_get_end_date(self):
self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'})
self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'})
@@ -49,7 +98,6 @@
self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
def test_loan(self):
-
branch = "Test Employee Branch"
applicant = make_employee("test_employee@loan.com", company="_Test Company")
company = "_Test Company"
@@ -116,6 +164,7 @@
payroll_entry.posting_date = nowdate()
payroll_entry.payroll_frequency = "Monthly"
payroll_entry.branch = args.branch or None
+ payroll_entry.department = args.department or None
if args.cost_center:
payroll_entry.cost_center = args.cost_center
@@ -123,6 +172,7 @@
if args.payment_account:
payroll_entry.payment_account = args.payment_account
+ payroll_entry.fill_employee_details()
payroll_entry.save()
payroll_entry.create_salary_slips()
payroll_entry.submit_salary_slips()
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json
index 54a8164..cfd4d89 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.json
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.json
@@ -12,6 +12,7 @@
"department",
"designation",
"branch",
+ "payroll_cost_center",
"column_break1",
"status",
"journal_entry",
@@ -459,13 +460,22 @@
"options": "Salary Slip",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fetch_from": "employee.payroll_cost_center",
+ "fetch_if_empty": 1,
+ "fieldname": "payroll_cost_center",
+ "fieldtype": "Link",
+ "label": "Payroll Cost Center",
+ "options": "Cost Center",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-14 20:02:53.159827",
+ "modified": "2020-05-05 18:55:26.173629",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Slip",
diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
index a7dcb94..3eff738 100644
--- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
@@ -422,22 +422,32 @@
sal_comp = frappe.get_doc("Salary Component", sal_comp)
if not sal_comp.get("accounts"):
for d in company_list:
+ company_abbr = frappe.get_cached_value('Company', d, 'abbr')
+
+ if sal_comp.type == "Earning":
+ account_name = "Salary"
+ parent_account = "Indirect Expenses - " + company_abbr
+ else:
+ account_name = "Salary Deductions"
+ parent_account = "Current Liabilities - " + company_abbr
+
sal_comp.append("accounts", {
"company": d,
- "default_account": create_account(d)
+ "default_account": create_account(account_name, d, parent_account)
})
sal_comp.save()
-def create_account(company):
- salary_account = frappe.db.get_value("Account", "Salary - " + frappe.get_cached_value('Company', company, 'abbr'))
- if not salary_account:
+def create_account(account_name, company, parent_account):
+ company_abbr = frappe.get_cached_value('Company', company, 'abbr')
+ account = frappe.db.get_value("Account", account_name + " - " + company_abbr)
+ if not account:
frappe.get_doc({
"doctype": "Account",
- "account_name": "Salary",
- "parent_account": "Indirect Expenses - " + frappe.get_cached_value('Company', company, 'abbr'),
+ "account_name": account_name,
+ "parent_account": parent_account,
"company": company
}).insert()
- return salary_account
+ return account
def make_earning_salary_component(setup=False, test_tax=False, company_list=None):
data = [
@@ -683,7 +693,7 @@
make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
- for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance"]:
+ for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance", "Additional Salary"]:
frappe.db.sql("delete from `tab%s`" % dt)
make_holiday_list()
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py
index 5ba7f1c..ffc16d7 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.py
@@ -153,12 +153,16 @@
def postprocess(source, target):
if employee:
employee_details = frappe.db.get_value("Employee", employee,
- ["employee_name", "branch", "designation", "department"], as_dict=1)
+ ["employee_name", "branch", "designation", "department", "payroll_cost_center"], as_dict=1)
target.employee = employee
target.employee_name = employee_details.employee_name
target.branch = employee_details.branch
target.designation = employee_details.designation
target.department = employee_details.department
+ target.payroll_cost_center = employee_details.payroll_cost_center
+ if not target.payroll_cost_center and target.department:
+ target.payroll_cost_center = frappe.db.get_value("Department", target.department, "payroll_cost_center")
+
target.run_method('process_salary_structure', for_preview=for_preview)
doc = get_mapped_doc("Salary Structure", source_name, {
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
index c1869f0..eb5311e 100644
--- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
@@ -128,6 +128,7 @@
salary_structure_doc.insert()
if not dont_submit:
salary_structure_doc.submit()
+
else:
salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index eb31ab7..38ed919 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -685,3 +685,4 @@
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation
erpnext.patches.v13_0.update_old_loans
+erpnext.patches.v12_0.set_serial_no_status
diff --git a/erpnext/patches/v12_0/set_serial_no_status.py b/erpnext/patches/v12_0/set_serial_no_status.py
new file mode 100644
index 0000000..4ec84ef
--- /dev/null
+++ b/erpnext/patches/v12_0/set_serial_no_status.py
@@ -0,0 +1,17 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import getdate, nowdate
+
+def execute():
+ frappe.reload_doc('stock', 'doctype', 'serial_no')
+
+ for serial_no in frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date from `tabSerial No`
+ where (status is NULL OR status='')""", as_dict = 1):
+ if serial_no.get("delivery_document_type"):
+ status = "Delivered"
+ elif serial_no.get("warranty_expiry_date") and getdate(serial_no.get("warranty_expiry_date")) <= getdate(nowdate()):
+ status = "Expired"
+ else:
+ status = "Active"
+
+ frappe.db.set_value("Serial No", serial_no.get("name"), "status", status)
\ No newline at end of file