From cef7126a355dff37afbd28dc34d5bacbbfe78b80 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 25 Apr 2023 14:58:37 +0530 Subject: [PATCH 1/2] chore: set correct currency symbol in salary register for multi-currency salary slip (cherry picked from commit 83afaf48df80d47126450f9436557e5cd221aa1b) --- .../report/salary_register/salary_register.py | 411 +++++++++++------- 1 file changed, 256 insertions(+), 155 deletions(-) diff --git a/erpnext/payroll/report/salary_register/salary_register.py b/erpnext/payroll/report/salary_register/salary_register.py index 0a62b43a8ea1..d9d682126654 100644 --- a/erpnext/payroll/report/salary_register/salary_register.py +++ b/erpnext/payroll/report/salary_register/salary_register.py @@ -8,222 +8,323 @@ import erpnext +salary_slip = frappe.qb.DocType("Salary Slip") +salary_detail = frappe.qb.DocType("Salary Detail") +salary_component = frappe.qb.DocType("Salary Component") def execute(filters=None): if not filters: filters = {} + currency = None if filters.get("currency"): currency = filters.get("currency") company_currency = erpnext.get_company_currency(filters.get("company")) + salary_slips = get_salary_slips(filters, company_currency) if not salary_slips: return [], [] - columns, earning_types, ded_types = get_columns(salary_slips) - ss_earning_map = get_ss_earning_map(salary_slips, currency, company_currency) - ss_ded_map = get_ss_ded_map(salary_slips, currency, company_currency) + earning_types, ded_types = get_earning_and_deduction_types(salary_slips) + columns = get_columns(earning_types, ded_types) + + ss_earning_map = get_salary_slip_details(salary_slips, currency, company_currency, "earnings") + ss_ded_map = get_salary_slip_details(salary_slips, currency, company_currency, "deductions") + doj_map = get_employee_doj_map() data = [] for ss in salary_slips: - row = [ - ss.name, - ss.employee, - ss.employee_name, - doj_map.get(ss.employee), - ss.branch, - ss.department, - ss.designation, - ss.company, - ss.start_date, - ss.end_date, - ss.leave_without_pay, - ss.payment_days, - ] - - if ss.branch is not None: - columns[3] = columns[3].replace("-1", "120") - if ss.department is not None: - columns[4] = columns[4].replace("-1", "120") - if ss.designation is not None: - columns[5] = columns[5].replace("-1", "120") - if ss.leave_without_pay is not None: - columns[9] = columns[9].replace("-1", "130") + row = { + "salary_slip_id": ss.name, + "employee": ss.employee, + "employee_name": ss.employee_name, + "data_of_joining": doj_map.get(ss.employee), + "branch": ss.branch, + "department": ss.department, + "designation": ss.designation, + "company": ss.company, + "start_date": ss.start_date, + "end_date": ss.end_date, + "leave_without_pay": ss.leave_without_pay, + "payment_days": ss.payment_days, + "currency": currency or company_currency, + "total_loan_repayment": ss.total_loan_repayment, + } + + update_column_width(ss, columns) for e in earning_types: - row.append(ss_earning_map.get(ss.name, {}).get(e)) - - if currency == company_currency: - row += [flt(ss.gross_pay) * flt(ss.exchange_rate)] - else: - row += [ss.gross_pay] + row.update({frappe.scrub(e): ss_earning_map.get(ss.name, {}).get(e)}) for d in ded_types: - row.append(ss_ded_map.get(ss.name, {}).get(d)) - - row.append(ss.total_loan_repayment) + row.update({frappe.scrub(d): ss_ded_map.get(ss.name, {}).get(d)}) if currency == company_currency: - row += [ - flt(ss.total_deduction) * flt(ss.exchange_rate), - flt(ss.net_pay) * flt(ss.exchange_rate), - ] + row.update( + { + "gross_pay": flt(ss.gross_pay) * flt(ss.exchange_rate), + "total_deduction": flt(ss.total_deduction) * flt(ss.exchange_rate), + "net_pay": flt(ss.net_pay) * flt(ss.exchange_rate), + } + ) + else: - row += [ss.total_deduction, ss.net_pay] - row.append(currency or company_currency) + row.update( + {"gross_pay": ss.gross_pay, "total_deduction": ss.total_deduction, "net_pay": ss.net_pay} + ) + data.append(row) return columns, data -def get_columns(salary_slips): - """ - columns = [ - _("Salary Slip ID") + ":Link/Salary Slip:150", - _("Employee") + ":Link/Employee:120", - _("Employee Name") + "::140", - _("Date of Joining") + "::80", - _("Branch") + ":Link/Branch:120", - _("Department") + ":Link/Department:120", - _("Designation") + ":Link/Designation:120", - _("Company") + ":Link/Company:120", - _("Start Date") + "::80", - _("End Date") + "::80", - _("Leave Without Pay") + ":Float:130", - _("Payment Days") + ":Float:120", - _("Currency") + ":Link/Currency:80" - ] - """ +def get_earning_and_deduction_types(salary_slips): + salary_component_and_type = {_("Earning"): [], _("Deduction"): []} + + for salary_compoent in get_salary_components(salary_slips): + component_type = get_salary_component_type(salary_compoent[0]) + salary_component_and_type[_(component_type)].append(salary_compoent[0]) + + return sorted(salary_component_and_type[_("Earning")]), sorted( + salary_component_and_type[_("Deduction")] + ) + + +def update_column_width(ss, columns): + if ss.branch is not None: + columns[3].update({"width": 120}) + if ss.department is not None: + columns[4].update({"width": 120}) + if ss.designation is not None: + columns[5].update({"width": 120}) + if ss.leave_without_pay is not None: + columns[9].update({"width": 120}) + + +def get_columns(earning_types, ded_types): columns = [ - _("Salary Slip ID") + ":Link/Salary Slip:150", - _("Employee") + ":Link/Employee:120", - _("Employee Name") + "::140", - _("Date of Joining") + "::80", - _("Branch") + ":Link/Branch:-1", - _("Department") + ":Link/Department:-1", - _("Designation") + ":Link/Designation:120", - _("Company") + ":Link/Company:120", - _("Start Date") + "::80", - _("End Date") + "::80", - _("Leave Without Pay") + ":Float:50", - _("Payment Days") + ":Float:120", + { + "label": _("Salary Slip ID"), + "fieldname": "salary_slip_id", + "fieldtype": "Link", + "options": "Salary Slip", + "width": 150, + }, + { + "label": _("Employee"), + "fieldname": "employee", + "fieldtype": "Link", + "options": "Employee", + "width": 120, + }, + { + "label": _("Employee Name"), + "fieldname": "employee_name", + "fieldtype": "Data", + "width": 140, + }, + { + "label": _("Date of Joining"), + "fieldname": "data_of_joining", + "fieldtype": "Date", + "width": 80, + }, + { + "label": _("Branch"), + "fieldname": "branch", + "fieldtype": "Link", + "options": "Branch", + "width": -1, + }, + { + "label": _("Department"), + "fieldname": "department", + "fieldtype": "Link", + "options": "Department", + "width": -1, + }, + { + "label": _("Designation"), + "fieldname": "designation", + "fieldtype": "Link", + "options": "Designation", + "width": 120, + }, + { + "label": _("Company"), + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "width": 120, + }, + { + "label": _("Start Date"), + "fieldname": "start_date", + "fieldtype": "Data", + "width": 80, + }, + { + "label": _("End Date"), + "fieldname": "end_date", + "fieldtype": "Data", + "width": 80, + }, + { + "label": _("Leave Without Pay"), + "fieldname": "leave_without_pay", + "fieldtype": "Float", + "width": 50, + }, + { + "label": _("Payment Days"), + "fieldname": "payment_days", + "fieldtype": "Float", + "width": 120, + }, + { + "label": _("Currency"), + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "hidden": 1, + }, ] - salary_components = {_("Earning"): [], _("Deduction"): []} - - for component in frappe.db.sql( - """select distinct sd.salary_component, sc.type - from `tabSalary Detail` sd, `tabSalary Component` sc - where sc.name=sd.salary_component and sd.amount != 0 and sd.parent in (%s)""" - % (", ".join(["%s"] * len(salary_slips))), - tuple([d.name for d in salary_slips]), - as_dict=1, - ): - salary_components[_(component.type)].append(component.salary_component) - - columns = ( - columns - + [(e + ":Currency:120") for e in salary_components[_("Earning")]] - + [_("Gross Pay") + ":Currency:120"] - + [(d + ":Currency:120") for d in salary_components[_("Deduction")]] - + [ - _("Loan Repayment") + ":Currency:120", - _("Total Deduction") + ":Currency:120", - _("Net Pay") + ":Currency:120", + for earning in earning_types: + columns.append( + { + "label": earning, + "fieldname": frappe.scrub(earning), + "fieldtype": "Currency", + "options": "currency", + "width": 120, + } + ) + + columns.append( + { + "label": _("Gross Pay"), + "fieldname": "gross_pay", + "fieldtype": "Currency", + "options": "currency", + "width": 120, + } + ) + + for deduction in ded_types: + columns.append( + { + "label": deduction, + "fieldname": frappe.scrub(deduction), + "fieldtype": "Currency", + "options": "currency", + "width": 120, + } + ) + + columns.extend( + [ + { + "label": _("Loan Repayment"), + "fieldname": "total_loan_repayment", + "fieldtype": "Currency", + "options": "currency", + "width": 120, + }, + { + "label": _("Total Deduction"), + "fieldname": "total_deduction", + "fieldtype": "Currency", + "options": "currency", + "width": 120, + }, + { + "label": _("Net Pay"), + "fieldname": "net_pay", + "fieldtype": "Currency", + "options": "currency", + "width": 120, + }, ] ) - return columns, salary_components[_("Earning")], salary_components[_("Deduction")] + return columns -def get_salary_slips(filters, company_currency): - filters.update({"from_date": filters.get("from_date"), "to_date": filters.get("to_date")}) - conditions, filters = get_conditions(filters, company_currency) - salary_slips = frappe.db.sql( - """select * from `tabSalary Slip` where %s - order by employee""" - % conditions, - filters, - as_dict=1, - ) +def get_salary_components(salary_slips): + return ( + frappe.qb.from_(salary_detail) + .where((salary_detail.amount != 0) & (salary_detail.parent.isin([d.name for d in salary_slips]))) + .select(salary_detail.salary_component) + .distinct() + ).run(as_list=True) - return salary_slips or [] +def get_salary_component_type(salary_component): + return frappe.db.get_value("Salary Component", salary_component, "type", cache=True) -def get_conditions(filters, company_currency): - conditions = "" +def get_salary_slips(filters, company_currency): doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2} + query = frappe.qb.from_(salary_slip).select(salary_slip.star) + if filters.get("docstatus"): - conditions += "docstatus = {0}".format(doc_status[filters.get("docstatus")]) + query = query.where(salary_slip.docstatus == doc_status[filters.get("docstatus")]) if filters.get("from_date"): - conditions += " and start_date >= %(from_date)s" + query = query.where(salary_slip.start_date >= filters.get("from_date")) + if filters.get("to_date"): - conditions += " and end_date <= %(to_date)s" + query = query.where(salary_slip.end_date <= filters.get("to_date")) + if filters.get("company"): - conditions += " and company = %(company)s" + query = query.where(salary_slip.company == filters.get("company")) + if filters.get("employee"): - conditions += " and employee = %(employee)s" + query = query.where(salary_slip.employee == filters.get("employee")) + if filters.get("currency") and filters.get("currency") != company_currency: - conditions += " and currency = %(currency)s" + query = query.where(salary_slip.currency == filters.get("currency")) + + salary_slips = query.run(as_dict=1) - return conditions, filters + return salary_slips or [] def get_employee_doj_map(): - return frappe._dict( - frappe.db.sql( - """ - SELECT - employee, - date_of_joining - FROM `tabEmployee` - """ - ) - ) + employee = frappe.qb.DocType("Employee") + result = (frappe.qb.from_(employee).select(employee.name, employee.date_of_joining)).run() -def get_ss_earning_map(salary_slips, currency, company_currency): - ss_earnings = frappe.db.sql( - """select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name - from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)""" - % (", ".join(["%s"] * len(salary_slips))), - tuple([d.name for d in salary_slips]), - as_dict=1, - ) + return frappe._dict(result) - ss_earning_map = {} - for d in ss_earnings: - ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0) - if currency == company_currency: - ss_earning_map[d.parent][d.salary_component] += flt(d.amount) * flt( - d.exchange_rate if d.exchange_rate else 1 - ) - else: - ss_earning_map[d.parent][d.salary_component] += flt(d.amount) - return ss_earning_map +def get_salary_slip_details(salary_slips, currency, company_currency, component_type): + salary_slips = [ss.name for ss in salary_slips] + result = ( + frappe.qb.from_(salary_slip) + .join(salary_detail) + .on(salary_slip.name == salary_detail.parent) + .where((salary_detail.parent.isin(salary_slips)) & (salary_detail.parentfield == component_type)) + .select( + salary_detail.parent, + salary_detail.salary_component, + salary_detail.amount, + salary_slip.exchange_rate, + ) + ).run(as_dict=1) -def get_ss_ded_map(salary_slips, currency, company_currency): - ss_deductions = frappe.db.sql( - """select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name - from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)""" - % (", ".join(["%s"] * len(salary_slips))), - tuple([d.name for d in salary_slips]), - as_dict=1, - ) + ss_map = {} - ss_ded_map = {} - for d in ss_deductions: - ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0) + for d in result: + ss_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0) if currency == company_currency: - ss_ded_map[d.parent][d.salary_component] += flt(d.amount) * flt( + ss_map[d.parent][d.salary_component] += flt(d.amount) * flt( d.exchange_rate if d.exchange_rate else 1 ) else: - ss_ded_map[d.parent][d.salary_component] += flt(d.amount) + ss_map[d.parent][d.salary_component] += flt(d.amount) - return ss_ded_map + return ss_map From fc42e026ab09f657dbcfaffd37c6a2f2ab858673 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 26 Apr 2023 15:44:34 +0530 Subject: [PATCH 2/2] chore: fix linter (cherry picked from commit 31bda37970d4d4e4c1a83cb3d288d49d164afa63) --- erpnext/payroll/report/salary_register/salary_register.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/payroll/report/salary_register/salary_register.py b/erpnext/payroll/report/salary_register/salary_register.py index d9d682126654..94369e4a4688 100644 --- a/erpnext/payroll/report/salary_register/salary_register.py +++ b/erpnext/payroll/report/salary_register/salary_register.py @@ -12,6 +12,7 @@ salary_detail = frappe.qb.DocType("Salary Detail") salary_component = frappe.qb.DocType("Salary Component") + def execute(filters=None): if not filters: filters = {} @@ -260,6 +261,7 @@ def get_salary_components(salary_slips): .distinct() ).run(as_list=True) + def get_salary_component_type(salary_component): return frappe.db.get_value("Salary Component", salary_component, "type", cache=True)