From 05de8994b09cf7e5c1364631316d6b5d8bd2cf2e Mon Sep 17 00:00:00 2001 From: Sanket322 <113279972+Sanket322@users.noreply.github.com> Date: Mon, 9 Sep 2024 06:40:17 +0530 Subject: [PATCH] refactor: age range in one field (#42736) * fix: age range in one field * fix: patch for custom reports * refactor: stock ageing and account payable report * fix: fixing the test cases * fix: common patch for reports with ageing * refactor: rename variable and minor refactor * fix: fixing the test case --- .../accounts_payable/accounts_payable.js | 30 +----- .../accounts_payable/test_accounts_payable.py | 5 +- .../accounts_payable_summary.js | 30 +----- .../accounts_receivable.js | 30 +----- .../accounts_receivable.py | 65 +++++-------- .../test_accounts_receivable.py | 95 ++++--------------- .../accounts_receivable_summary.js | 30 +----- .../accounts_receivable_summary.py | 54 ++++------- .../test_accounts_receivable_summary.py | 10 +- erpnext/accounts/test/test_reports.py | 4 +- erpnext/patches.txt | 1 + .../v14_0/update_reports_with_range.py | 36 +++++++ .../stock/report/stock_ageing/stock_ageing.js | 23 +---- .../stock/report/stock_ageing/stock_ageing.py | 41 ++++---- .../report/stock_ageing/test_stock_ageing.py | 4 +- erpnext/stock/report/test_reports.py | 2 +- 16 files changed, 142 insertions(+), 318 deletions(-) create mode 100644 erpnext/patches/v14_0/update_reports_with_range.py diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index d4ad43b78e7..97870a3fef7 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -61,32 +61,10 @@ frappe.query_reports["Accounts Payable"] = { default: "Due Date", }, { - fieldname: "range1", - label: __("Ageing Range 1"), - fieldtype: "Int", - default: "30", - reqd: 1, - }, - { - fieldname: "range2", - label: __("Ageing Range 2"), - fieldtype: "Int", - default: "60", - reqd: 1, - }, - { - fieldname: "range3", - label: __("Ageing Range 3"), - fieldtype: "Int", - default: "90", - reqd: 1, - }, - { - fieldname: "range4", - label: __("Ageing Range 4"), - fieldtype: "Int", - default: "120", - reqd: 1, + fieldname: "range", + label: __("Ageing Range"), + fieldtype: "Data", + default: "30, 60, 90, 120", }, { fieldname: "payment_terms_template", diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index 43856bf569f..8971dc3d37b 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -30,10 +30,7 @@ class TestAccountsPayable(AccountsTestMixin, FrappeTestCase): "party_type": "Supplier", "party": [self.supplier], "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "in_party_currency": 1, } diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 92ea9e8f598..cf7a62c6b69 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -24,32 +24,10 @@ frappe.query_reports["Accounts Payable Summary"] = { default: "Due Date", }, { - fieldname: "range1", - label: __("Ageing Range 1"), - fieldtype: "Int", - default: "30", - reqd: 1, - }, - { - fieldname: "range2", - label: __("Ageing Range 2"), - fieldtype: "Int", - default: "60", - reqd: 1, - }, - { - fieldname: "range3", - label: __("Ageing Range 3"), - fieldtype: "Int", - default: "90", - reqd: 1, - }, - { - fieldname: "range4", - label: __("Ageing Range 4"), - fieldtype: "Int", - default: "120", - reqd: 1, + fieldname: "range", + label: __("Ageing Range"), + fieldtype: "Data", + default: "30, 60, 90, 120", }, { fieldname: "finance_book", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 0daf79e09c7..30195e7c743 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -89,32 +89,10 @@ frappe.query_reports["Accounts Receivable"] = { default: "Due Date", }, { - fieldname: "range1", - label: __("Ageing Range 1"), - fieldtype: "Int", - default: "30", - reqd: 1, - }, - { - fieldname: "range2", - label: __("Ageing Range 2"), - fieldtype: "Int", - default: "60", - reqd: 1, - }, - { - fieldname: "range3", - label: __("Ageing Range 3"), - fieldtype: "Int", - default: "90", - reqd: 1, - }, - { - fieldname: "range4", - label: __("Ageing Range 4"), - fieldtype: "Int", - default: "120", - reqd: 1, + fieldname: "range", + label: __("Ageing Range"), + fieldtype: "Data", + default: "30, 60, 90, 120", }, { fieldname: "customer_group", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 19c6df3e702..b1a17c94df6 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -50,6 +50,11 @@ class ReceivablePayableReport: getdate(nowdate()) if self.filters.report_date > getdate(nowdate()) else self.filters.report_date ) + if not self.filters.range: + self.filters.range = "30, 60, 90, 120" + self.ranges = [num.strip() for num in self.filters.range.split(",") if num.strip().isdigit()] + self.range_numbers = [num for num in range(1, len(self.ranges) + 2)] + def run(self, args): self.filters.update(args) self.set_defaults() @@ -733,37 +738,22 @@ class ReceivablePayableReport: # ageing buckets should not have amounts if due date is not reached if getdate(entry_date) > getdate(self.filters.report_date): - row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0 + [setattr(row, f"range{i}", 0.0) for i in self.range_numbers] - row.total_due = row.range1 + row.range2 + row.range3 + row.range4 + row.range5 + row.total_due = sum(row[f"range{i}"] for i in self.range_numbers) def get_ageing_data(self, entry_date, row): # [0-30, 30-60, 60-90, 90-120, 120-above] - row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0 + [setattr(row, f"range{i}", 0.0) for i in self.range_numbers] if not (self.age_as_on and entry_date): return row.age = (getdate(self.age_as_on) - getdate(entry_date)).days or 0 - index = None - if not (self.filters.range1 and self.filters.range2 and self.filters.range3 and self.filters.range4): - self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4 = ( - 30, - 60, - 90, - 120, - ) - - for i, days in enumerate( - [self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4] - ): - if cint(row.age) <= cint(days): - index = i - break - - if index is None: - index = 4 + index = next( + (i for i, days in enumerate(self.ranges) if cint(row.age) <= cint(days)), len(self.ranges) + ) row["range" + str(index + 1)] = row.outstanding def get_ple_entries(self): @@ -1075,6 +1065,7 @@ class ReceivablePayableReport: self.add_column(_("Debit Note"), fieldname="credit_note") self.add_column(_("Outstanding Amount"), fieldname="outstanding") + self.add_column(label=_("Age (Days)"), fieldname="age", fieldtype="Int", width=80) self.setup_ageing_columns() self.add_column( @@ -1133,34 +1124,26 @@ class ReceivablePayableReport: def setup_ageing_columns(self): # for charts self.ageing_column_labels = [] - self.add_column(label=_("Age (Days)"), fieldname="age", fieldtype="Int", width=80) + ranges = [*self.ranges, "Above"] + + prev_range_value = 0 + for idx, curr_range_value in enumerate(ranges): + label = f"{prev_range_value}-{curr_range_value}" + self.add_column(label=label, fieldname="range" + str(idx + 1)) - for i, label in enumerate( - [ - "0-{range1}".format(range1=self.filters["range1"]), - "{range1}-{range2}".format( - range1=cint(self.filters["range1"]) + 1, range2=self.filters["range2"] - ), - "{range2}-{range3}".format( - range2=cint(self.filters["range2"]) + 1, range3=self.filters["range3"] - ), - "{range3}-{range4}".format( - range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"] - ), - _("{range4}-Above").format(range4=cint(self.filters["range4"]) + 1), - ] - ): - self.add_column(label=label, fieldname="range" + str(i + 1)) self.ageing_column_labels.append(label) + if curr_range_value.isdigit(): + prev_range_value = cint(curr_range_value) + 1 + def get_chart_data(self): + precision = cint(frappe.db.get_default("float_precision")) or 2 rows = [] for row in self.data: row = frappe._dict(row) if not cint(row.bold): - values = [row.range1, row.range2, row.range3, row.range4, row.range5] - precision = cint(frappe.db.get_default("float_precision")) or 2 - rows.append({"values": [flt(val, precision) for val in values]}) + values = [flt(row.get(f"range{i}", None), precision) for i in self.range_numbers] + rows.append({"values": values}) self.chart = { "data": {"labels": self.ageing_column_labels, "datasets": rows}, diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index c4baa4e4842..39ca78153c3 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -83,10 +83,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "party": [self.customer], "report_date": add_days(today(), 2), "based_on_payment_terms": 0, - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "show_remarks": False, } @@ -116,10 +113,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "company": self.company, "based_on_payment_terms": 1, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "show_remarks": True, } @@ -172,10 +166,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "show_remarks": True, } @@ -266,10 +257,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "company": self.company, "based_on_payment_terms": 0, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } report = execute(filters) @@ -328,10 +316,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } report = execute(filters) @@ -397,10 +382,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } report = execute(filters) self.assertEqual(report[1], []) @@ -416,10 +398,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "group_by_party": True, } report = execute(filters)[1] @@ -493,10 +472,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "show_future_payments": True, } report = execute(filters)[1] @@ -555,10 +531,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "sales_person": sales_person.name, "show_sales_person": True, } @@ -575,10 +548,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "cost_center": self.cost_center, } report = execute(filters)[1] @@ -593,10 +563,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "customer_group": cus_group, } report = execute(filters)[1] @@ -618,10 +585,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "customer_group": cus_groups_list, # Use the list of customer groups } report = execute(filters)[1] @@ -660,10 +624,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "party_account": self.debit_to, } report = execute(filters)[1] @@ -711,10 +672,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "party_type": "Customer", "party": [self.customer], "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "in_party_currency": 1, } @@ -754,10 +712,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "party_type": "Customer", "party": [self.customer1, self.customer3], "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) @@ -837,10 +792,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } report_ouput = execute(filters)[1] @@ -903,10 +855,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", "show_future_payments": True, "in_party_currency": False, } @@ -965,10 +914,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } # check invoice grand total and invoiced column's value for 3 payment terms @@ -991,10 +937,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "report_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } # check invoice grand total and invoiced column's value for 3 payment terms diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 964abc23747..e36f40169b3 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -24,32 +24,10 @@ frappe.query_reports["Accounts Receivable Summary"] = { default: "Due Date", }, { - fieldname: "range1", - label: __("Ageing Range 1"), - fieldtype: "Int", - default: "30", - reqd: 1, - }, - { - fieldname: "range2", - label: __("Ageing Range 2"), - fieldtype: "Int", - default: "60", - reqd: 1, - }, - { - fieldname: "range3", - label: __("Ageing Range 3"), - fieldtype: "Int", - default: "90", - reqd: 1, - }, - { - fieldname: "range4", - label: __("Ageing Range 4"), - fieldtype: "Int", - default: "120", - reqd: 1, + fieldname: "range", + label: __("Ageing Range"), + fieldtype: "Data", + default: "30, 60, 90, 120", }, { fieldname: "finance_book", diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 1aab7f752a8..921a73f59cb 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -104,25 +104,23 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.set_party_details(d) def init_party_total(self, row): + default_dict = { + "invoiced": 0.0, + "paid": 0.0, + "credit_note": 0.0, + "outstanding": 0.0, + "total_due": 0.0, + "future_amount": 0.0, + "sales_person": [], + "party_type": row.party_type, + } + for i in self.range_numbers: + range_key = f"range{i}" + default_dict[range_key] = 0.0 + self.party_total.setdefault( row.party, - frappe._dict( - { - "invoiced": 0.0, - "paid": 0.0, - "credit_note": 0.0, - "outstanding": 0.0, - "range1": 0.0, - "range2": 0.0, - "range3": 0.0, - "range4": 0.0, - "range5": 0.0, - "total_due": 0.0, - "future_amount": 0.0, - "sales_person": [], - "party_type": row.party_type, - } - ), + frappe._dict(default_dict), ) def set_party_details(self, row): @@ -173,6 +171,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.add_column(_("Difference"), fieldname="diff") self.setup_ageing_columns() + self.add_column(label="Total Amount Due", fieldname="total_due") if self.filters.show_future_payments: self.add_column(label=_("Future Payment Amount"), fieldname="future_amount") @@ -206,27 +205,6 @@ class AccountsReceivableSummary(ReceivablePayableReport): label=_("Currency"), fieldname="currency", fieldtype="Link", options="Currency", width=80 ) - def setup_ageing_columns(self): - for i, label in enumerate( - [ - "0-{range1}".format(range1=self.filters["range1"]), - "{range1}-{range2}".format( - range1=cint(self.filters["range1"]) + 1, range2=self.filters["range2"] - ), - "{range2}-{range3}".format( - range2=cint(self.filters["range2"]) + 1, range3=self.filters["range3"] - ), - "{range3}-{range4}".format( - range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"] - ), - "{range4}-{above}".format(range4=cint(self.filters["range4"]) + 1, above=_("Above")), - ] - ): - self.add_column(label=label, fieldname="range" + str(i + 1)) - - # Add column for total due amount - self.add_column(label="Total Amount Due", fieldname="total_due") - def get_gl_balance(report_date, company): return frappe._dict( diff --git a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py index 4ef607bab28..a98cc6af7a3 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py @@ -27,10 +27,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "company": self.company, "customer": self.customer, "posting_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } si = create_sales_invoice( @@ -121,10 +118,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "company": self.company, "customer": self.customer, "posting_date": today(), - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, + "range": "30, 60, 90, 120", } si = create_sales_invoice( diff --git a/erpnext/accounts/test/test_reports.py b/erpnext/accounts/test/test_reports.py index c2e10f8fd47..56b7832a32e 100644 --- a/erpnext/accounts/test/test_reports.py +++ b/erpnext/accounts/test/test_reports.py @@ -14,8 +14,8 @@ DEFAULT_FILTERS = { REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [ ("General Ledger", {"group_by": "Group by Voucher (Consolidated)"}), ("General Ledger", {"group_by": "Group by Voucher (Consolidated)", "include_dimensions": 1}), - ("Accounts Payable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}), - ("Accounts Receivable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}), + ("Accounts Payable", {"range": "30, 60, 90, 120"}), + ("Accounts Receivable", {"range": "30, 60, 90, 120"}), ("Consolidated Financial Statement", {"report": "Balance Sheet"}), ("Consolidated Financial Statement", {"report": "Profit and Loss Statement"}), ("Consolidated Financial Statement", {"report": "Cash Flow"}), diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8b155d70c6f..d769448ff4f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -375,6 +375,7 @@ erpnext.patches.v15_0.update_warehouse_field_in_asset_repair_consumed_item_docty erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry erpnext.patches.v15_0.update_total_number_of_booked_depreciations erpnext.patches.v15_0.do_not_use_batchwise_valuation +erpnext.patches.v14_0.update_reports_with_range erpnext.patches.v15_0.drop_index_posting_datetime_from_sle erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1 erpnext.patches.v15_0.set_standard_stock_entry_type diff --git a/erpnext/patches/v14_0/update_reports_with_range.py b/erpnext/patches/v14_0/update_reports_with_range.py new file mode 100644 index 00000000000..2bda265ca66 --- /dev/null +++ b/erpnext/patches/v14_0/update_reports_with_range.py @@ -0,0 +1,36 @@ +import json + +import frappe + +REFERENCE_REPORTS = [ + "Accounts Receivable", + "Accounts Receivable Summary", + "Accounts Payable", + "Accounts Payable Summary", + "Stock Ageing", +] + + +def execute(): + for report in REFERENCE_REPORTS: + update_reference_reports(report) + + +def update_reference_reports(reference_report): + reports = frappe.get_all( + "Report", filters={"reference_report": reference_report}, fields={"json", "name"} + ) + + for report in reports: + update_report_json(report) + update_reference_reports(report.name) + + +def update_report_json(report): + report_json = json.loads(report.json) + report_filter = report_json.get("filters") + + keys_to_pop = [key for key in report_filter if key.startswith("range")] + report_filter["range"] = ", ".join(str(report_filter.pop(key)) for key in keys_to_pop) + + frappe.db.set_value("Report", report.name, "json", json.dumps(report_json)) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.js b/erpnext/stock/report/stock_ageing/stock_ageing.js index 578869b6e93..a3ebf14571d 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.js +++ b/erpnext/stock/report/stock_ageing/stock_ageing.js @@ -54,25 +54,10 @@ frappe.query_reports["Stock Ageing"] = { options: "Brand", }, { - fieldname: "range1", - label: __("Ageing Range 1"), - fieldtype: "Int", - default: "30", - reqd: 1, - }, - { - fieldname: "range2", - label: __("Ageing Range 2"), - fieldtype: "Int", - default: "60", - reqd: 1, - }, - { - fieldname: "range3", - label: __("Ageing Range 3"), - fieldtype: "Int", - default: "90", - reqd: 1, + fieldname: "range", + label: __("Ageing Range"), + fieldtype: "Data", + default: "30, 60, 90", }, { fieldname: "show_warehouse_wise_stock", diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 09af3f224a3..c7e0af6cd38 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -16,6 +16,7 @@ Filters = frappe._dict def execute(filters: Filters = None) -> tuple: to_date = filters["to_date"] + filters.ranges = [num.strip() for num in filters.range.split(",") if num.strip().isdigit()] columns = get_columns(filters) item_details = FIFOSlots(filters).generate() @@ -48,7 +49,7 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li average_age = get_average_age(fifo_queue, to_date) earliest_age = date_diff(to_date, fifo_queue[0][1]) latest_age = date_diff(to_date, fifo_queue[-1][1]) - range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict) + range_values = get_range_age(filters, fifo_queue, to_date, item_dict) row = [details.name, details.item_name, details.description, details.item_group, details.brand] @@ -59,10 +60,7 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li [ flt(item_dict.get("total_qty"), precision), average_age, - range1, - range2, - range3, - above_range3, + *range_values, earliest_age, latest_age, details.stock_uom, @@ -89,25 +87,22 @@ def get_average_age(fifo_queue: list, to_date: str) -> float: return flt(age_qty / total_qty, 2) if total_qty else 0.0 -def get_range_age(filters: Filters, fifo_queue: list, to_date: str, item_dict: dict) -> tuple: +def get_range_age(filters: Filters, fifo_queue: list, to_date: str, item_dict: dict) -> list: precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True)) - - range1 = range2 = range3 = above_range3 = 0.0 + range_values = [0.0] * (len(filters.ranges) + 1) for item in fifo_queue: age = flt(date_diff(to_date, item[1])) qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0 - if age <= flt(filters.range1): - range1 = flt(range1 + qty, precision) - elif age <= flt(filters.range2): - range2 = flt(range2 + qty, precision) - elif age <= flt(filters.range3): - range3 = flt(range3 + qty, precision) + for i, age_limit in enumerate(filters.ranges): + if age <= flt(age_limit): + range_values[i] = flt(range_values[i] + qty, precision) + break else: - above_range3 = flt(above_range3 + qty, precision) + range_values[-1] = flt(range_values[-1] + qty, precision) - return range1, range2, range3, above_range3 + return range_values def get_columns(filters: Filters) -> list[dict]: @@ -193,12 +188,14 @@ def get_chart_data(data: list, filters: Filters) -> dict: def setup_ageing_columns(filters: Filters, range_columns: list): - ranges = [ - f"0 - {filters['range1']}", - f"{cint(filters['range1']) + 1} - {cint(filters['range2'])}", - f"{cint(filters['range2']) + 1} - {cint(filters['range3'])}", - _("{0} - Above").format(cint(filters["range3"]) + 1), - ] + prev_range_value = 0 + ranges = [] + for range in filters.ranges: + ranges.append(f"{prev_range_value} - {range}") + prev_range_value = cint(range) + 1 + + ranges.append(f"{prev_range_value} - Above") + for i, label in enumerate(ranges): fieldname = "range" + str(i + 1) add_column(range_columns, label=_("Age ({0})").format(label), fieldname=fieldname) diff --git a/erpnext/stock/report/stock_ageing/test_stock_ageing.py b/erpnext/stock/report/stock_ageing/test_stock_ageing.py index fb363606233..c0c007e5015 100644 --- a/erpnext/stock/report/stock_ageing/test_stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/test_stock_ageing.py @@ -9,9 +9,7 @@ from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots, format_rep class TestStockAgeing(FrappeTestCase): def setUp(self) -> None: - self.filters = frappe._dict( - company="_Test Company", to_date="2021-12-10", range1=30, range2=60, range3=90 - ) + self.filters = frappe._dict(company="_Test Company", to_date="2021-12-10", ranges=["30", "60", "90"]) def test_normal_inward_outward_queue(self): "Reference: Case 1 in stock_ageing_fifo_logic.md (same wh)" diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index 74c6afa204b..85337a3bf54 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -62,7 +62,7 @@ REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [ ("Item Prices", {"items": "Enabled Items only"}), ("Delayed Item Report", {"based_on": "Sales Invoice"}), ("Delayed Item Report", {"based_on": "Delivery Note"}), - ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), + ("Stock Ageing", {"range": "30, 60, 90", "_optional": True}), ("Stock Ledger Invariant Check", {"warehouse": "_Test Warehouse - _TC", "item": "_Test Item"}), ("FIFO Queue vs Qty After Transaction Comparison", {"warehouse": "_Test Warehouse - _TC"}), ("FIFO Queue vs Qty After Transaction Comparison", {"item_group": "All Item Groups"}),