From 83afaf48df80d47126450f9436557e5cd221aa1b Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 25 Apr 2023 14:58:37 +0530 Subject: [PATCH 01/17] chore: set correct currency symbol in salary register for multi-currency salary slip --- .../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 31bda37970d4d4e4c1a83cb3d288d49d164afa63 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 26 Apr 2023 15:44:34 +0530 Subject: [PATCH 02/17] chore: fix linter --- 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) From 54388e8d925e06031738c23d4590efd9025f4cc7 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Thu, 27 Apr 2023 17:04:39 +0530 Subject: [PATCH 03/17] fix: Hyperlink in Quality Inspection Summary (cherry picked from commit 72dd7884a89b7dcce342410c8bc3f3da56bf99e8) --- .../quality_inspection_summary/quality_inspection_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py index de96a6c03232..38e05852ee8d 100644 --- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py @@ -69,7 +69,7 @@ def get_columns(filters): "label": _("Id"), "fieldname": "name", "fieldtype": "Link", - "options": "Work Order", + "options": "Quality Inspection", "width": 100, }, {"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150}, From 563e5c0b6947063745cc2103d3d0e14e2473ba7e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 10:51:23 +0530 Subject: [PATCH 04/17] fix: per_billed condition for Payment Entry (#34969) fix: per_billed condition for Payment Entry (#34969) fix: per_billed condition for Payment Entry (#34969) (cherry picked from commit d6bc8bba8b7ed748483bf61b03c8c87eb54f8ab0) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> (cherry picked from commit f9f42c7e988426bc688ad2db3e3a804f13b88edc) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f959a4508d42..82f993910453 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1855,7 +1855,10 @@ def get_payment_entry( ): reference_doc = None doc = frappe.get_doc(dt, dn) - if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= 99.99: + over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance") + if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= ( + 100.0 + over_billing_allowance + ): frappe.throw(_("Can only make payment against unbilled {0}").format(dt)) party_type = set_party_type(dt) From d3c769c183beba4864b4da113082a7c49b051933 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 13:02:28 +0530 Subject: [PATCH 05/17] fix: Naming series error in Journal Entry template (#35084) fix: Naming series error in Journal Entry template (#35084) (cherry picked from commit f3b3dabb9a6e5f8cd34c4a07ed54738b28f2b69a) Co-authored-by: Deepesh Garg --- .../journal_entry_template.js | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js index 88f1c9069c82..5ebdf61db2ce 100644 --- a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js @@ -2,6 +2,21 @@ // For license information, please see license.txt frappe.ui.form.on("Journal Entry Template", { + onload: function(frm) { + if(frm.is_new()) { + frappe.call({ + type: "GET", + method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series", + callback: function(r){ + if(r.message) { + frm.set_df_property("naming_series", "options", r.message.split("\n")); + frm.set_value("naming_series", r.message.split("\n")[0]); + frm.refresh_field("naming_series"); + } + } + }); + } + }, refresh: function(frm) { frappe.model.set_default_values(frm.doc); @@ -19,18 +34,6 @@ frappe.ui.form.on("Journal Entry Template", { return { filters: filters }; }); - - frappe.call({ - type: "GET", - method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series", - callback: function(r){ - if(r.message){ - frm.set_df_property("naming_series", "options", r.message.split("\n")); - frm.set_value("naming_series", r.message.split("\n")[0]); - frm.refresh_field("naming_series"); - } - } - }); }, voucher_type: function(frm) { var add_accounts = function(doc, r) { From c5261cde9c08c0ba78ce192f015d5e7fbefe7177 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 1 May 2023 13:33:26 +0530 Subject: [PATCH 06/17] fix: allow user to set standard deductions in income tax slab without allowing other exemptions --- erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json | 3 +-- erpnext/payroll/doctype/salary_slip/salary_slip.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json index 5a7de37becbb..66070d02e5e9 100644 --- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json +++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json @@ -67,7 +67,6 @@ "label": "Disabled" }, { - "depends_on": "allow_tax_exemption", "fieldname": "standard_tax_exemption_amount", "fieldtype": "Currency", "label": "Standard Tax Exemption Amount", @@ -104,7 +103,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:42:08.139520", + "modified": "2023-05-01 13:42:08.139520", "modified_by": "Administrator", "module": "Payroll", "name": "Income Tax Slab", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index a3ec0386ad59..d141ac68e21f 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1331,6 +1331,7 @@ def get_total_exemption_amount(self, payroll_period, tax_slab): if declaration: total_exemption_amount = declaration + if tax_slab.standard_tax_exemption_amount: total_exemption_amount += flt(tax_slab.standard_tax_exemption_amount) return total_exemption_amount From a852dc1f11db26bf744f1657a2fc0dfdc7c8a137 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Thu, 17 Nov 2022 11:00:01 +0100 Subject: [PATCH 07/17] feat: validate repost item valuation against accounts freeze date (cherry picked from commit 61f05132dbd0cd3d0ed76e04f2d45a8a6ebf1c66) # Conflicts: # erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py --- .../repost_item_valuation.py | 19 ++++- .../test_repost_item_valuation.py | 80 +++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 14f5e548eccf..97027082843f 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from frappe import _ from frappe.exceptions import QueryDeadlockError, QueryTimeoutError from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime +from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException @@ -25,6 +25,21 @@ def validate(self): self.set_status(write=False) self.reset_field_values() self.set_company() + self.validate_accounts_freeze() + + def validate_accounts_freeze(self): + acc_settings = frappe.db.get_value( + 'Accounts Settings', + 'Accounts Settings', + ['acc_frozen_upto', 'frozen_accounts_modifier'], + as_dict=1 + ) + if not acc_settings.acc_frozen_upto: + return + if acc_settings.frozen_accounts_modifier and self.owner in get_users_with_role(acc_settings.frozen_accounts_modifier): + return + if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto): + frappe.throw(_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto)) def reset_field_values(self): if self.based_on == "Transaction": @@ -235,7 +250,7 @@ def _get_directly_dependent_vouchers(doc): def notify_error_to_stock_managers(doc, traceback): recipients = get_users_with_role("Stock Manager") if not recipients: - get_users_with_role("System Manager") + recipients = get_users_with_role("System Manager") subject = _("Error while reposting item valuation") message = ( diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index edd2553d5d14..62ed035df953 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -272,3 +272,83 @@ def test_gl_complete_gl_reposting(self): [{"credit": 50, "debit": 0}], gle_filters={"account": "Stock In Hand - TCP1"}, ) +<<<<<<< HEAD +======= + + def test_duplicate_ple_on_repost(self): + from erpnext.accounts import utils + + # lower numbers to simplify test + orig_chunk_size = utils.GL_REPOSTING_CHUNK + utils.GL_REPOSTING_CHUNK = 2 + self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size) + + rate = 100 + item = self.make_item() + item.valuation_rate = 90 + item.allow_negative_stock = 1 + item.save() + + company = "_Test Company with perpetual inventory" + + # consume non-existing stock + sinv = create_sales_invoice( + company=company, + posting_date=today(), + debit_to="Debtors - TCP1", + income_account="Sales - TCP1", + expense_account="Cost of Goods Sold - TCP1", + warehouse="Stores - TCP1", + update_stock=1, + currency="INR", + item_code=item.name, + cost_center="Main - TCP1", + qty=1, + rate=rate, + ) + + # backdated receipt triggers repost + make_stock_entry( + item=item.name, + company=company, + qty=5, + rate=rate, + target="Stores - TCP1", + posting_date=add_to_date(today(), days=-1), + ) + + ple_entries = frappe.db.get_list( + "Payment Ledger Entry", + filters={"voucher_type": sinv.doctype, "voucher_no": sinv.name, "delinked": 0}, + ) + + # assert successful deduplication on PLE + self.assertEqual(len(ple_entries), 1) + + # outstanding should not be affected + sinv.reload() + self.assertEqual(sinv.outstanding_amount, 100) + + def test_account_freeze_validation(self): + today = nowdate() + + riv = frappe.get_doc( + doctype="Repost Item Valuation", + item_code="_Test Item", + warehouse="_Test Warehouse - _TC", + based_on="Item and Warehouse", + posting_date=today, + posting_time="00:01:00", + ) + riv.flags.dont_run_in_test = True # keep it queued + + accounts_settings = frappe.get_doc("Accounts Settings") + accounts_settings.acc_frozen_upto = today + accounts_settings.frozen_accounts_modifier = '' + accounts_settings.save() + + self.assertRaises(frappe.ValidationError, riv.save) + + accounts_settings.acc_frozen_upto = '' + accounts_settings.save() +>>>>>>> 61f05132db (feat: validate repost item valuation against accounts freeze date) From 6992e727cf283fa1e55934e21b08de033742cc34 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Thu, 17 Nov 2022 11:00:34 +0100 Subject: [PATCH 08/17] chore: pre-commit (cherry picked from commit be15419bd5d3481dd83c7c4fedbb010d3e0e13d9) # Conflicts: # erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py --- .../repost_item_valuation.py | 16 ++++++++++------ .../test_repost_item_valuation.py | 7 ++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 97027082843f..62d0d94a92e6 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -29,17 +29,21 @@ def validate(self): def validate_accounts_freeze(self): acc_settings = frappe.db.get_value( - 'Accounts Settings', - 'Accounts Settings', - ['acc_frozen_upto', 'frozen_accounts_modifier'], - as_dict=1 + "Accounts Settings", + "Accounts Settings", + ["acc_frozen_upto", "frozen_accounts_modifier"], + as_dict=1, ) if not acc_settings.acc_frozen_upto: return - if acc_settings.frozen_accounts_modifier and self.owner in get_users_with_role(acc_settings.frozen_accounts_modifier): + if acc_settings.frozen_accounts_modifier and self.owner in get_users_with_role( + acc_settings.frozen_accounts_modifier + ): return if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto): - frappe.throw(_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto)) + frappe.throw( + _("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto) + ) def reset_field_values(self): if self.based_on == "Transaction": diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index 62ed035df953..7758b32d98cb 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -344,11 +344,16 @@ def test_account_freeze_validation(self): accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings.acc_frozen_upto = today - accounts_settings.frozen_accounts_modifier = '' + accounts_settings.frozen_accounts_modifier = "" accounts_settings.save() self.assertRaises(frappe.ValidationError, riv.save) +<<<<<<< HEAD accounts_settings.acc_frozen_upto = '' accounts_settings.save() >>>>>>> 61f05132db (feat: validate repost item valuation against accounts freeze date) +======= + accounts_settings.acc_frozen_upto = "" + accounts_settings.save() +>>>>>>> be15419bd5 (chore: pre-commit) From 7d6e2f979fe4575e9efda97e67f49ba1619d90a1 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 23 Nov 2022 18:59:15 +0530 Subject: [PATCH 09/17] fix: check for session user rather than owner (cherry picked from commit b482e3876dccb2547330b95c1c8d4f2cf7039918) --- .../repost_item_valuation/repost_item_valuation.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 62d0d94a92e6..9e0ff07a6427 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -36,11 +36,12 @@ def validate_accounts_freeze(self): ) if not acc_settings.acc_frozen_upto: return - if acc_settings.frozen_accounts_modifier and self.owner in get_users_with_role( - acc_settings.frozen_accounts_modifier - ): - return if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto): + if acc_settings.frozen_accounts_modifier and frappe.session.user in get_users_with_role( + acc_settings.frozen_accounts_modifier + ): + frappe.msgprint(_("Caution: This might alter frozen accounts.")) + return frappe.throw( _("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto) ) From 198a64d5746d0a6911002cfe6b61cf147fa258ee Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 23 Nov 2022 19:04:11 +0530 Subject: [PATCH 10/17] chore: pre-commit (cherry picked from commit 88a0aa4077b6cd96800a813e5ba163b573d01f47) --- .../doctype/repost_item_valuation/repost_item_valuation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 9e0ff07a6427..10e4ba055a1c 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -37,8 +37,9 @@ def validate_accounts_freeze(self): if not acc_settings.acc_frozen_upto: return if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto): - if acc_settings.frozen_accounts_modifier and frappe.session.user in get_users_with_role( + if ( acc_settings.frozen_accounts_modifier + and frappe.session.user in get_users_with_role(acc_settings.frozen_accounts_modifier) ): frappe.msgprint(_("Caution: This might alter frozen accounts.")) return From 6bdf143084ce02eb2e08b7c61922541fd48ca5b1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 May 2023 18:26:51 +0530 Subject: [PATCH 11/17] fix: conflicts --- .../test_repost_item_valuation.py | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index 7758b32d98cb..b9e2ed1beb96 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -272,62 +272,6 @@ def test_gl_complete_gl_reposting(self): [{"credit": 50, "debit": 0}], gle_filters={"account": "Stock In Hand - TCP1"}, ) -<<<<<<< HEAD -======= - - def test_duplicate_ple_on_repost(self): - from erpnext.accounts import utils - - # lower numbers to simplify test - orig_chunk_size = utils.GL_REPOSTING_CHUNK - utils.GL_REPOSTING_CHUNK = 2 - self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size) - - rate = 100 - item = self.make_item() - item.valuation_rate = 90 - item.allow_negative_stock = 1 - item.save() - - company = "_Test Company with perpetual inventory" - - # consume non-existing stock - sinv = create_sales_invoice( - company=company, - posting_date=today(), - debit_to="Debtors - TCP1", - income_account="Sales - TCP1", - expense_account="Cost of Goods Sold - TCP1", - warehouse="Stores - TCP1", - update_stock=1, - currency="INR", - item_code=item.name, - cost_center="Main - TCP1", - qty=1, - rate=rate, - ) - - # backdated receipt triggers repost - make_stock_entry( - item=item.name, - company=company, - qty=5, - rate=rate, - target="Stores - TCP1", - posting_date=add_to_date(today(), days=-1), - ) - - ple_entries = frappe.db.get_list( - "Payment Ledger Entry", - filters={"voucher_type": sinv.doctype, "voucher_no": sinv.name, "delinked": 0}, - ) - - # assert successful deduplication on PLE - self.assertEqual(len(ple_entries), 1) - - # outstanding should not be affected - sinv.reload() - self.assertEqual(sinv.outstanding_amount, 100) def test_account_freeze_validation(self): today = nowdate() @@ -348,12 +292,5 @@ def test_account_freeze_validation(self): accounts_settings.save() self.assertRaises(frappe.ValidationError, riv.save) - -<<<<<<< HEAD - accounts_settings.acc_frozen_upto = '' - accounts_settings.save() ->>>>>>> 61f05132db (feat: validate repost item valuation against accounts freeze date) -======= accounts_settings.acc_frozen_upto = "" accounts_settings.save() ->>>>>>> be15419bd5 (chore: pre-commit) From 635559d9052b9c65a16e7ddf3ec92fc9fbfb5334 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 20:00:04 +0530 Subject: [PATCH 12/17] fix: handle expected_value_after_useful_life properly in asset value adjustment (backport #35117) (#35120) fix: handle expected_value_after_useful_life properly in asset value adjustment (#35117) (cherry picked from commit 80230fec3ef079b75825447a61836f8a9ce64f57) Co-authored-by: Anand Baburajan --- .../doctype/asset_value_adjustment/asset_value_adjustment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index 710c6cfacf32..f63773a8b7f0 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -119,7 +119,9 @@ def reschedule_depreciations(self, asset_value): if d.depreciation_method in ("Straight Line", "Manual"): end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx) total_days = date_diff(end_date, self.date) - rate_per_day = flt(d.value_after_depreciation) / flt(total_days) + rate_per_day = flt(d.value_after_depreciation - d.expected_value_after_useful_life) / flt( + total_days + ) from_date = self.date else: no_of_depreciations = len( From b31d8eec057f3c34b4139c427b8d07ffb0054e59 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 1 May 2023 18:47:25 +0530 Subject: [PATCH 13/17] fix: don't allow to make reposting for the closed period (cherry picked from commit f751727149392cfa304e20b2d50a7cbeef17d388) # Conflicts: # erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py # erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py --- .../test_period_closing_voucher.py | 138 ++++++++++++++++++ .../repost_item_valuation.py | 32 ++++ 2 files changed, 170 insertions(+) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 93869ed6c042..07fadc0fac66 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -178,7 +178,145 @@ def test_period_closing_with_finance_book_entries(self): self.assertEqual(pcv_gle, expected_gle) +<<<<<<< HEAD def make_period_closing_voucher(self, submit=True): +======= + def test_gl_entries_restrictions(self): + frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") + frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") + + company = create_company() + cost_center = create_cost_center("Test Cost Center 1") + + self.make_period_closing_voucher(posting_date="2021-03-31") + + jv1 = make_journal_entry( + posting_date="2021-03-15", + amount=400, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center, + save=False, + ) + jv1.company = company + jv1.save() + + self.assertRaises(frappe.ValidationError, jv1.submit) + + def test_closing_balance_with_dimensions_and_test_reposting_entry(self): + frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") + frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") + frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'") + + company = create_company() + cost_center1 = create_cost_center("Test Cost Center 1") + cost_center2 = create_cost_center("Test Cost Center 2") + + jv1 = make_journal_entry( + posting_date="2021-03-15", + amount=400, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center1, + save=False, + ) + jv1.company = company + jv1.save() + jv1.submit() + + jv2 = make_journal_entry( + posting_date="2021-03-15", + amount=200, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center2, + save=False, + ) + jv2.company = company + jv2.save() + jv2.submit() + + pcv1 = self.make_period_closing_voucher(posting_date="2021-03-31") + + closing_balance = frappe.db.get_value( + "Account Closing Balance", + { + "account": "Sales - TPC", + "cost_center": cost_center1, + "period_closing_voucher": pcv1.name, + "is_period_closing_voucher_entry": 0, + }, + ["credit", "credit_in_account_currency"], + as_dict=1, + ) + + self.assertEqual(closing_balance.credit, 400) + self.assertEqual(closing_balance.credit_in_account_currency, 400) + + jv3 = make_journal_entry( + posting_date="2022-03-15", + amount=300, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center2, + save=False, + ) + + jv3.company = company + jv3.save() + jv3.submit() + + pcv2 = self.make_period_closing_voucher(posting_date="2022-03-31") + + cc1_closing_balance = frappe.db.get_value( + "Account Closing Balance", + { + "account": "Sales - TPC", + "cost_center": cost_center1, + "period_closing_voucher": pcv2.name, + "is_period_closing_voucher_entry": 0, + }, + ["credit", "credit_in_account_currency"], + as_dict=1, + ) + + cc2_closing_balance = frappe.db.get_value( + "Account Closing Balance", + { + "account": "Sales - TPC", + "cost_center": cost_center2, + "period_closing_voucher": pcv2.name, + "is_period_closing_voucher_entry": 0, + }, + ["credit", "credit_in_account_currency"], + as_dict=1, + ) + + self.assertEqual(cc1_closing_balance.credit, 400) + self.assertEqual(cc1_closing_balance.credit_in_account_currency, 400) + self.assertEqual(cc2_closing_balance.credit, 500) + self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500) + + warehouse = frappe.db.get_value("Warehouse", {"company": company}, "name") + + repost_doc = frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "company": company, + "posting_date": "2020-03-15", + "based_on": "Item and Warehouse", + "item_code": "Test Item 1", + "warehouse": warehouse, + } + ) + + self.assertRaises(frappe.ValidationError, repost_doc.save) + + repost_doc.posting_date = today() + repost_doc.save() + + def make_period_closing_voucher(self, posting_date=None, submit=True): +>>>>>>> f751727149 (fix: don't allow to make reposting for the closed period) surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") pcv = frappe.get_doc( diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 10e4ba055a1c..5cb2ce2eb323 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,6 +5,11 @@ from frappe import _ from frappe.exceptions import QueryDeadlockError, QueryTimeoutError from frappe.model.document import Document +<<<<<<< HEAD +======= +from frappe.query_builder import DocType, Interval +from frappe.query_builder.functions import Max, Now +>>>>>>> f751727149 (fix: don't allow to make reposting for the closed period) from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException @@ -22,11 +27,38 @@ class RepostItemValuation(Document): def validate(self): + self.validate_period_closing_voucher() self.set_status(write=False) self.reset_field_values() self.set_company() self.validate_accounts_freeze() + def validate_period_closing_voucher(self): + year_end_date = self.get_max_year_end_date(self.company) + if year_end_date and getdate(self.posting_date) <= getdate(year_end_date): + msg = f"Due to period closing, you cannot repost item valuation before {year_end_date}" + frappe.throw(_(msg)) + + @staticmethod + def get_max_year_end_date(company): + data = frappe.get_all( + "Period Closing Voucher", fields=["fiscal_year"], filters={"docstatus": 1, "company": company} + ) + + if not data: + return + + fiscal_years = [d.fiscal_year for d in data] + table = frappe.qb.DocType("Fiscal Year") + + query = ( + frappe.qb.from_(table) + .select(Max(table.year_end_date)) + .where((table.name.isin(fiscal_years)) & (table.disabled == 0)) + ).run() + + return query[0][0] if query else None + def validate_accounts_freeze(self): acc_settings = frappe.db.get_value( "Accounts Settings", From b19b0a4a98b1f74785f18d0d754827257ac8c2b0 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 May 2023 20:54:32 +0530 Subject: [PATCH 14/17] fix: conflicts --- .../doctype/repost_item_valuation/repost_item_valuation.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 5cb2ce2eb323..e197f302901d 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,11 +5,7 @@ from frappe import _ from frappe.exceptions import QueryDeadlockError, QueryTimeoutError from frappe.model.document import Document -<<<<<<< HEAD -======= -from frappe.query_builder import DocType, Interval -from frappe.query_builder.functions import Max, Now ->>>>>>> f751727149 (fix: don't allow to make reposting for the closed period) +from frappe.query_builder.functions import Max from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException From 778ba6956cb92148aa0e4b495658957cb6e06ca0 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 May 2023 20:55:52 +0530 Subject: [PATCH 15/17] fix: conflicts --- .../test_period_closing_voucher.py | 101 +----------------- 1 file changed, 1 insertion(+), 100 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 07fadc0fac66..a03c308a2480 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -178,9 +178,6 @@ def test_period_closing_with_finance_book_entries(self): self.assertEqual(pcv_gle, expected_gle) -<<<<<<< HEAD - def make_period_closing_voucher(self, submit=True): -======= def test_gl_entries_restrictions(self): frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") @@ -202,101 +199,6 @@ def test_gl_entries_restrictions(self): jv1.save() self.assertRaises(frappe.ValidationError, jv1.submit) - - def test_closing_balance_with_dimensions_and_test_reposting_entry(self): - frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") - frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") - frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'") - - company = create_company() - cost_center1 = create_cost_center("Test Cost Center 1") - cost_center2 = create_cost_center("Test Cost Center 2") - - jv1 = make_journal_entry( - posting_date="2021-03-15", - amount=400, - account1="Cash - TPC", - account2="Sales - TPC", - cost_center=cost_center1, - save=False, - ) - jv1.company = company - jv1.save() - jv1.submit() - - jv2 = make_journal_entry( - posting_date="2021-03-15", - amount=200, - account1="Cash - TPC", - account2="Sales - TPC", - cost_center=cost_center2, - save=False, - ) - jv2.company = company - jv2.save() - jv2.submit() - - pcv1 = self.make_period_closing_voucher(posting_date="2021-03-31") - - closing_balance = frappe.db.get_value( - "Account Closing Balance", - { - "account": "Sales - TPC", - "cost_center": cost_center1, - "period_closing_voucher": pcv1.name, - "is_period_closing_voucher_entry": 0, - }, - ["credit", "credit_in_account_currency"], - as_dict=1, - ) - - self.assertEqual(closing_balance.credit, 400) - self.assertEqual(closing_balance.credit_in_account_currency, 400) - - jv3 = make_journal_entry( - posting_date="2022-03-15", - amount=300, - account1="Cash - TPC", - account2="Sales - TPC", - cost_center=cost_center2, - save=False, - ) - - jv3.company = company - jv3.save() - jv3.submit() - - pcv2 = self.make_period_closing_voucher(posting_date="2022-03-31") - - cc1_closing_balance = frappe.db.get_value( - "Account Closing Balance", - { - "account": "Sales - TPC", - "cost_center": cost_center1, - "period_closing_voucher": pcv2.name, - "is_period_closing_voucher_entry": 0, - }, - ["credit", "credit_in_account_currency"], - as_dict=1, - ) - - cc2_closing_balance = frappe.db.get_value( - "Account Closing Balance", - { - "account": "Sales - TPC", - "cost_center": cost_center2, - "period_closing_voucher": pcv2.name, - "is_period_closing_voucher_entry": 0, - }, - ["credit", "credit_in_account_currency"], - as_dict=1, - ) - - self.assertEqual(cc1_closing_balance.credit, 400) - self.assertEqual(cc1_closing_balance.credit_in_account_currency, 400) - self.assertEqual(cc2_closing_balance.credit, 500) - self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500) - warehouse = frappe.db.get_value("Warehouse", {"company": company}, "name") repost_doc = frappe.get_doc( @@ -315,8 +217,7 @@ def test_closing_balance_with_dimensions_and_test_reposting_entry(self): repost_doc.posting_date = today() repost_doc.save() - def make_period_closing_voucher(self, posting_date=None, submit=True): ->>>>>>> f751727149 (fix: don't allow to make reposting for the closed period) + def make_period_closing_voucher(self, submit=True): surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") pcv = frappe.get_doc( From db6d0e03f5b2a5db4f121856d42828dbbb4b5bdc Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 May 2023 23:34:31 +0530 Subject: [PATCH 16/17] fix: test case --- .../test_period_closing_voucher.py | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index a03c308a2480..93869ed6c042 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -178,45 +178,6 @@ def test_period_closing_with_finance_book_entries(self): self.assertEqual(pcv_gle, expected_gle) - def test_gl_entries_restrictions(self): - frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") - frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") - - company = create_company() - cost_center = create_cost_center("Test Cost Center 1") - - self.make_period_closing_voucher(posting_date="2021-03-31") - - jv1 = make_journal_entry( - posting_date="2021-03-15", - amount=400, - account1="Cash - TPC", - account2="Sales - TPC", - cost_center=cost_center, - save=False, - ) - jv1.company = company - jv1.save() - - self.assertRaises(frappe.ValidationError, jv1.submit) - warehouse = frappe.db.get_value("Warehouse", {"company": company}, "name") - - repost_doc = frappe.get_doc( - { - "doctype": "Repost Item Valuation", - "company": company, - "posting_date": "2020-03-15", - "based_on": "Item and Warehouse", - "item_code": "Test Item 1", - "warehouse": warehouse, - } - ) - - self.assertRaises(frappe.ValidationError, repost_doc.save) - - repost_doc.posting_date = today() - repost_doc.save() - def make_period_closing_voucher(self, submit=True): surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") From 9a376039aae56e4632faab8a4406ef8818082e67 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 08:25:01 +0530 Subject: [PATCH 17/17] fix: handle finance book properly in trial balance and general ledger (#35136) fix: handle finance book properly in trial balance and general ledger [v14] (#35136) fix: handle FBs properly in general ledger and trial balance (cherry picked from commit 344c33948400ad51ed769309ed52c7cf1c72e937) Co-authored-by: Anand Baburajan --- .../accounts/report/financial_statements.py | 19 ++++++++++++---- .../report/general_ledger/general_ledger.js | 3 ++- .../report/general_ledger/general_ledger.py | 22 ++++++++++++++----- .../report/trial_balance/trial_balance.py | 21 +++++++++++++----- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 2a3fbc9bdd7f..d61330e0cfb6 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -524,11 +524,22 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions.append("cost_center in %(cost_center)s") if filters.get("include_default_book_entries"): - additional_conditions.append( - "(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)" - ) + if filters.get("finance_book"): + if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr( + filters.get("company_fb") + ): + frappe.throw( + _("To use a different finance book, please uncheck 'Include Default Book Entries'") + ) + else: + additional_conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)") + else: + additional_conditions.append("(finance_book in (%(company_fb)s) OR finance_book IS NULL)") else: - additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)") + if filters.get("finance_book"): + additional_conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)") + else: + additional_conditions.append("(finance_book IS NULL)") if accounting_dimensions: for dimension in accounting_dimensions: diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 2100f26c1ec8..57a9091cf9ba 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -176,7 +176,8 @@ frappe.query_reports["General Ledger"] = { { "fieldname": "include_default_book_entries", "label": __("Include Default Book Entries"), - "fieldtype": "Check" + "fieldtype": "Check", + "default": 1 }, { "fieldname": "show_cancelled_entries", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 33d46bc901da..34534f883ed0 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -287,13 +287,23 @@ def get_conditions(filters): if filters.get("project"): conditions.append("project in %(project)s") - if filters.get("finance_book"): - if filters.get("include_default_book_entries"): - conditions.append( - "(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)" - ) + if filters.get("include_default_book_entries"): + if filters.get("finance_book"): + if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr( + filters.get("company_fb") + ): + frappe.throw( + _("To use a different finance book, please uncheck 'Include Default Book Entries'") + ) + else: + conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)") + else: + conditions.append("(finance_book in (%(company_fb)s) OR finance_book IS NULL)") + else: + if filters.get("finance_book"): + conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)") else: - conditions.append("finance_book in (%(finance_book)s)") + conditions.append("(finance_book IS NULL)") if not filters.get("show_cancelled_entries"): conditions.append("is_cancelled = 0") diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 61bc58009a6f..d2300cd12389 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -157,12 +157,23 @@ def get_rootwise_opening_balances(filters, report_type): if filters.project: additional_conditions += " and project = %(project)s" + company_fb = frappe.db.get_value("Company", filters.company, "default_finance_book") + if filters.get("include_default_book_entries"): - additional_conditions += ( - " AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)" - ) + if filters.get("finance_book"): + if company_fb and cstr(filters.get("finance_book")) != cstr(company_fb): + frappe.throw( + _("To use a different finance book, please uncheck 'Include Default Book Entries'") + ) + else: + additional_conditions += " AND (finance_book in (%(finance_book)s) OR finance_book IS NULL)" + else: + additional_conditions += " AND (finance_book in (%(company_fb)s) OR finance_book IS NULL)" else: - additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)" + if filters.get("finance_book"): + additional_conditions += " AND (finance_book in (%(finance_book)s) OR finance_book IS NULL)" + else: + additional_conditions += " AND (finance_book IS NULL)" accounting_dimensions = get_accounting_dimensions(as_list=False) @@ -174,7 +185,7 @@ def get_rootwise_opening_balances(filters, report_type): "year_start_date": filters.year_start_date, "project": filters.project, "finance_book": filters.finance_book, - "company_fb": frappe.db.get_value("Company", filters.company, "default_finance_book"), + "company_fb": company_fb, } if accounting_dimensions: