From 99828d945fe13540b1ccbfb68b86c623c9832940 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 5 Sep 2024 14:38:22 +0530 Subject: [PATCH 01/39] refactor: Handle Emp Advance as separate row in AP report (cherry picked from commit eedf22b07a86c0c8ff6763e94f7f541482714fa5) --- .../accounts_receivable.py | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index f8511d2f497..59087a7c2d0 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -112,6 +112,26 @@ class ReceivablePayableReport: self.build_data() + def build_voucher_dict(self, ple): + return frappe._dict( + voucher_type=ple.voucher_type, + voucher_no=ple.voucher_no, + party=ple.party, + party_account=ple.account, + posting_date=ple.posting_date, + account_currency=ple.account_currency, + remarks=ple.remarks, + invoiced=0.0, + paid=0.0, + credit_note=0.0, + outstanding=0.0, + invoiced_in_account_currency=0.0, + paid_in_account_currency=0.0, + credit_note_in_account_currency=0.0, + outstanding_in_account_currency=0.0, + cost_center=ple.cost_center, + ) + def init_voucher_balance(self): # build all keys, since we want to exclude vouchers beyond the report date for ple in self.ple_entries: @@ -123,24 +143,8 @@ class ReceivablePayableReport: key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) if key not in self.voucher_balance: - self.voucher_balance[key] = frappe._dict( - voucher_type=ple.voucher_type, - voucher_no=ple.voucher_no, - party=ple.party, - party_account=ple.account, - posting_date=ple.posting_date, - account_currency=ple.account_currency, - remarks=ple.remarks, - invoiced=0.0, - paid=0.0, - credit_note=0.0, - outstanding=0.0, - invoiced_in_account_currency=0.0, - paid_in_account_currency=0.0, - credit_note_in_account_currency=0.0, - outstanding_in_account_currency=0.0, - cost_center=ple.cost_center, - ) + self.voucher_balance[key] = self.build_voucher_dict(ple) + self.get_invoices(ple) if self.filters.get("group_by_party"): @@ -208,6 +212,14 @@ class ReceivablePayableReport: row = self.voucher_balance.get(key) + # Build and use a separate row for Employee Advances. + # This allows Payments or Journals made against Emp Advance to be processed. + if not row and ple.against_voucher_type == "Employee Advance": + _d = self.build_voucher_dict(ple) + _d.voucher_type = ple.against_voucher_type + _d.voucher_no = ple.against_voucher_no + row = self.voucher_balance[key] = _d + if not row: # no invoice, this is an invoice / stand-alone payment / credit note if self.filters.get("ignore_accounts"): From efdc2173b233261d99fb8e7134158bd4805359a2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 5 Sep 2024 16:12:52 +0530 Subject: [PATCH 02/39] refactor: filter to toggle employee advance scenario in AP (cherry picked from commit 257e13c2997a0d2fc7682ae9b7b5feca837a63dc) # Conflicts: # erpnext/accounts/report/accounts_payable/accounts_payable.js --- .../accounts/report/accounts_payable/accounts_payable.js | 5 +++++ .../report/accounts_receivable/accounts_receivable.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 86463f1f1f2..ee37c1a2153 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -158,8 +158,13 @@ frappe.query_reports["Accounts Payable"] = { fieldtype: "Check", }, { +<<<<<<< HEAD fieldname: "in_party_currency", label: __("In Party Currency"), +======= + fieldname: "handle_employee_advances", + label: __("Handle Employee Advances"), +>>>>>>> 257e13c299 (refactor: filter to toggle employee advance scenario in AP) fieldtype: "Check", }, ], diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 59087a7c2d0..6303b317e78 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -214,7 +214,11 @@ class ReceivablePayableReport: # Build and use a separate row for Employee Advances. # This allows Payments or Journals made against Emp Advance to be processed. - if not row and ple.against_voucher_type == "Employee Advance": + if ( + not row + and ple.against_voucher_type == "Employee Advance" + and self.filters.handle_employee_advances + ): _d = self.build_voucher_dict(ple) _d.voucher_type = ple.against_voucher_type _d.voucher_no = ple.against_voucher_no From cb64f90d7d3a9e350b74773c9841b5dad39f1afe Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 5 Sep 2024 17:22:47 +0530 Subject: [PATCH 03/39] chore: resolve conflict --- .../accounts/report/accounts_payable/accounts_payable.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index ee37c1a2153..4f3ffd6b839 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -158,13 +158,13 @@ frappe.query_reports["Accounts Payable"] = { fieldtype: "Check", }, { -<<<<<<< HEAD fieldname: "in_party_currency", label: __("In Party Currency"), -======= + fieldtype: "Check", + }, + { fieldname: "handle_employee_advances", label: __("Handle Employee Advances"), ->>>>>>> 257e13c299 (refactor: filter to toggle employee advance scenario in AP) fieldtype: "Check", }, ], From 75cb29890d5f275dc172c4528fb1ecf41e02d8ea Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 27 Aug 2024 16:53:34 +0530 Subject: [PATCH 04/39] fix: `default_advance_account` field in Process Payment Reconciliation (cherry picked from commit 143209f91a201c58004408cd04812aefa49e9f94) # Conflicts: # erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json --- .../process_payment_reconciliation.js | 20 ++++++++++++++++++- .../process_payment_reconciliation.json | 18 ++++++++++++++++- .../process_payment_reconciliation.py | 2 ++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js index 0f52a4d998e..9c0a14bd177 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js @@ -20,6 +20,17 @@ frappe.ui.form.on("Process Payment Reconciliation", { }, }; }); + + frm.set_query("default_advance_account", function (doc) { + return { + filters: { + company: doc.company, + is_group: 0, + account_type: doc.party_type == "Customer" ? "Receivable" : "Payable", + root_type: doc.party_type == "Customer" ? "Liability" : "Asset", + }, + }; + }); frm.set_query("cost_center", function (doc) { return { filters: { @@ -102,6 +113,7 @@ frappe.ui.form.on("Process Payment Reconciliation", { company(frm) { frm.set_value("party", ""); frm.set_value("receivable_payable_account", ""); + frm.set_value("default_advance_account", ""); }, party_type(frm) { frm.set_value("party", ""); @@ -109,6 +121,7 @@ frappe.ui.form.on("Process Payment Reconciliation", { party(frm) { frm.set_value("receivable_payable_account", ""); + frm.set_value("default_advance_account", ""); if (!frm.doc.receivable_payable_account && frm.doc.party_type && frm.doc.party) { return frappe.call({ method: "erpnext.accounts.party.get_party_account", @@ -119,7 +132,12 @@ frappe.ui.form.on("Process Payment Reconciliation", { }, callback: (r) => { if (!r.exc && r.message) { - frm.set_value("receivable_payable_account", r.message); + if (typeof r.message === "string") { + frm.set_value("receivable_payable_account", r.message); + } else if (Array.isArray(r.message)) { + frm.set_value("receivable_payable_account", r.message[0]); + frm.set_value("default_advance_account", r.message[1]); + } } frm.refresh(); }, diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json index 1a1ab4d800e..b0584dd4c4d 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json @@ -13,6 +13,7 @@ "column_break_io6c", "party", "receivable_payable_account", + "default_advance_account", "filter_section", "from_invoice_date", "to_invoice_date", @@ -141,12 +142,27 @@ { "fieldname": "section_break_a8yx", "fieldtype": "Section Break" + }, + { + "depends_on": "eval:doc.party", + "description": "Only 'Payment Entries' made against this advance account are supported.", + "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/advance-in-separate-party-account", + "fieldname": "default_advance_account", + "fieldtype": "Link", + "label": "Default Advance Account", + "mandatory_depends_on": "doc.party_type", + "options": "Account", + "reqd": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-08-11 10:56:51.699137", +======= + "modified": "2024-08-27 14:48:56.715320", +>>>>>>> 143209f91a (fix: `default_advance_account` field in Process Payment Reconciliation) "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation", @@ -180,4 +196,4 @@ "sort_order": "DESC", "states": [], "title_field": "company" -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py index 5048fc5e25e..882a2c4ad1c 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -23,6 +23,7 @@ class ProcessPaymentReconciliation(Document): bank_cash_account: DF.Link | None company: DF.Link cost_center: DF.Link | None + default_advance_account: DF.Link error_log: DF.LongText | None from_invoice_date: DF.Date | None from_payment_date: DF.Date | None @@ -101,6 +102,7 @@ def get_pr_instance(doc: str): "party_type", "party", "receivable_payable_account", + "default_advance_account", "from_invoice_date", "to_invoice_date", "from_payment_date", From 84b0fa38d5b99b6c2b646852e0081dd8fee4f3d1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 3 Sep 2024 17:27:50 +0530 Subject: [PATCH 05/39] refactor: fetch advance account on party seleection (cherry picked from commit c4ed04cb31a80e694a3c27ec92e4e574f2035d39) --- .../process_payment_reconciliation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js index 9c0a14bd177..d72c4724690 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js @@ -129,6 +129,7 @@ frappe.ui.form.on("Process Payment Reconciliation", { company: frm.doc.company, party_type: frm.doc.party_type, party: frm.doc.party, + include_advance: 1, }, callback: (r) => { if (!r.exc && r.message) { From 9bd3d7a020ad8ce4e90da1ffdfddfc71510aab74 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Thu, 5 Sep 2024 19:13:31 +0530 Subject: [PATCH 06/39] fix: cancel common party advance jv while canceling the invoice (cherry picked from commit 6a928b92dfa90de8290a3e5829deb69a80c6ccdb) --- erpnext/accounts/utils.py | 40 ++++++++++++++++++++++ erpnext/controllers/accounts_controller.py | 3 ++ 2 files changed, 43 insertions(+) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 0450221222d..e462f749b54 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -781,6 +781,46 @@ def cancel_exchange_gain_loss_journal( gain_loss_je.cancel() +def cancel_common_party_journal(self): + if self.doctype not in ["Sales Invoice", "Purchase Invoice"]: + return + + if not frappe.db.get_single_value("Accounts Settings", "enable_common_party_accounting"): + return + + party_link = self.get_common_party_link() + if not party_link: + return + + journal_entry = frappe.db.get_value( + "Journal Entry Account", + filters={ + "reference_type": self.doctype, + "reference_name": self.name, + "docstatus": 1, + }, + fieldname="parent", + ) + + if not journal_entry: + return + + common_party_journal = frappe.db.get_value( + "Journal Entry", + filters={ + "name": journal_entry, + "is_system_generated": True, + "docstatus": 1, + }, + ) + + if not common_party_journal: + return + + common_party_je = frappe.get_doc("Journal Entry", common_party_journal) + common_party_je.cancel() + + def update_accounting_ledgers_after_reference_removal( ref_type: str | None = None, ref_no: str | None = None, payment_name: str | None = None ): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ce5d813b801..fbd717db153 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1580,6 +1580,7 @@ class AccountsController(TransactionBase): remove_from_bank_transaction, ) from erpnext.accounts.utils import ( + cancel_common_party_journal, cancel_exchange_gain_loss_journal, unlink_ref_doc_from_payment_entries, ) @@ -1591,6 +1592,7 @@ class AccountsController(TransactionBase): # Cancel Exchange Gain/Loss Journal before unlinking cancel_exchange_gain_loss_journal(self) + cancel_common_party_journal(self) if frappe.db.get_single_value("Accounts Settings", "unlink_payment_on_cancellation_of_invoice"): unlink_ref_doc_from_payment_entries(self) @@ -2424,6 +2426,7 @@ class AccountsController(TransactionBase): jv.posting_date = self.posting_date jv.company = self.company jv.remark = f"Adjustment for {self.doctype} {self.name}" + jv.is_system_generated = True reconcilation_entry = frappe._dict() advance_entry = frappe._dict() From 6c74180e1c2fae184ae3300d0bfb9b932e8b9a13 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Thu, 5 Sep 2024 23:30:35 +0530 Subject: [PATCH 07/39] test: add unit test for canceling the common party advance jv created from sales invoice (cherry picked from commit 8c6e3f3c12164f30f044b3bf1fa0b71b66ff837d) --- .../sales_invoice/test_sales_invoice.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index fef30bdfecd..ce8627be27f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3162,6 +3162,50 @@ class TestSalesInvoice(FrappeTestCase): party_link.delete() frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0) + def test_sales_invoice_cancel_with_common_party_advance_jv(self): + from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( + make_customer, + ) + from erpnext.accounts.doctype.party_link.party_link import create_party_link + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + + # create a customer + customer = make_customer(customer="_Test Common Supplier") + # create a supplier + supplier = create_supplier(supplier_name="_Test Common Supplier").name + + # create a party link between customer & supplier + party_link = create_party_link("Supplier", supplier, customer) + + # enable common party accounting + frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1) + + # create a sales invoice + si = create_sales_invoice(customer=customer) + + # check creation of journal entry + jv = frappe.db.get_value( + "Journal Entry Account", + filters={ + "reference_type": si.doctype, + "reference_name": si.name, + "docstatus": 1, + }, + fieldname="parent", + ) + + self.assertTrue(jv) + + # cancel sales invoice + si.cancel() + + # check cancellation of journal entry + jv_status = frappe.db.get_value("Journal Entry", jv, "docstatus") + self.assertEqual(jv_status, 2) + + party_link.delete() + frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0) + def test_payment_statuses(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry From 6fde07da0e5a477f375429ace53067f1ab8eb0c8 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 6 Sep 2024 23:46:29 +0530 Subject: [PATCH 08/39] fix(minor): reorder expected value validation (cherry picked from commit 0a6bf1559bd21f94b903b8304d90fd727382a91a) --- erpnext/assets/doctype/asset/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f6846ea3cee..abb615ff277 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -125,7 +125,6 @@ class Asset(AccountsController): self.validate_cost_center() self.set_missing_values() self.validate_gross_and_purchase_amount() - self.validate_expected_value_after_useful_life() self.validate_finance_books() if not self.split_from: @@ -146,6 +145,7 @@ class Asset(AccountsController): "Asset Depreciation Schedules created:
{0}

Please check, edit if needed, and submit the Asset." ).format(asset_depr_schedules_links) ) + self.validate_expected_value_after_useful_life() self.set_total_booked_depreciations() self.total_asset_cost = self.gross_purchase_amount self.status = self.get_status() From 8447bf34f07c230f3084a2fe66e0295db10a566f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 17:32:42 +0530 Subject: [PATCH 09/39] fix: incorrect qty after transaction in SLE (backport #43103) (#43105) fix: incorrect qty after transaction in SLE (#43103) (cherry picked from commit 5ff87edc85b6bf1a355a44650440055abbb75ef5) Co-authored-by: rohitwaghchaure --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 556aac09abd..49a3a3335d1 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1527,7 +1527,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc voucher_no = args.get("voucher_no") voucher_condition = f"and voucher_no != '{voucher_no}'" - elif args.get("creation"): + elif args.get("creation") and args.get("sle_id"): creation = args.get("creation") operator = "<=" voucher_condition = f"and creation < '{creation}'" From b832b60b28e9cd015f061b65f56e220551e42b08 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 10/39] 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 (cherry picked from commit 05de8994b09cf7e5c1364631316d6b5d8bd2cf2e) --- .../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 4f3ffd6b839..445e532183b 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 7e795fbe3c1..9f15bbc333d 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 6303b317e78..1a7638aaae4 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 6a1b1057724..87fc7ea68be 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 2a97061a6a2..7350fa77e1c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -371,6 +371,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"}), From 39150184008bcf7accdbddae2d6b9276ed3fdf5b Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 9 Sep 2024 07:00:32 +0530 Subject: [PATCH 11/39] chore: resolve conflicts with backport --- .../process_payment_reconciliation.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json index b0584dd4c4d..0511571d754 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json @@ -158,11 +158,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-08-11 10:56:51.699137", -======= "modified": "2024-08-27 14:48:56.715320", ->>>>>>> 143209f91a (fix: `default_advance_account` field in Process Payment Reconciliation) "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation", From 2c1f72e44cac422f5f2ab94b7b2934a5b0979c83 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 19 Aug 2024 08:16:11 +0530 Subject: [PATCH 12/39] fix: ensure `SellingController.onload` gets called for SO & DN (cherry picked from commit 8431e3c275cc8cad8406f8a27436ecf31140106d) --- erpnext/selling/doctype/sales_order/sales_order.py | 2 ++ erpnext/stock/doctype/delivery_note/delivery_note.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 570677aad78..2f2d840cce8 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -185,6 +185,8 @@ class SalesOrder(SellingController): super().__init__(*args, **kwargs) def onload(self) -> None: + super().onload() + if frappe.db.get_single_value("Stock Settings", "enable_stock_reservation"): if self.has_unreserved_stock(): self.set_onload("has_unreserved_stock", True) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 8a096aca80c..847a57baa72 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -208,6 +208,8 @@ class DeliveryNote(SellingController): ) def onload(self): + super().onload() + if self.docstatus == 0: self.set_onload("has_unpacked_items", self.has_unpacked_items()) From f45b1db1a402d57b7a6312d39f6ca975098a6ae5 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 9 Sep 2024 07:23:44 +0530 Subject: [PATCH 13/39] fix: multiple fixes related to remarks for GL Report (#42753) * fix: show remarks in report only if it exists * fix: additional fixes to reduce redundancy in report print format * fix: revert changes for supplier invoice reference * fix: update remarks before submit to ensure all available details before submit are used * fix: patch to update invoice remarks where it's not set * fix: update remarks in payment ledger entry (cherry picked from commit e5a49f738bb3593a42180491114cc9f5424f8685) --- .../purchase_invoice/purchase_invoice.py | 13 +++-- .../doctype/sales_invoice/sales_invoice.py | 13 +++-- .../report/general_ledger/general_ledger.html | 5 +- erpnext/patches.txt | 1 + .../patches/v15_0/update_invoice_remarks.py | 51 +++++++++++++++++++ 5 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 erpnext/patches/v15_0/update_invoice_remarks.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index de99c35e27e..623fb941b89 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -285,7 +285,6 @@ class PurchaseInvoice(BuyingController): self.set_against_expense_account() self.validate_write_off_account() self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount") - self.create_remarks() self.set_status() self.validate_purchase_receipt_if_update_stock() validate_inter_company_party( @@ -322,10 +321,11 @@ class PurchaseInvoice(BuyingController): def create_remarks(self): if not self.remarks: - if self.bill_no and self.bill_date: - self.remarks = _("Against Supplier Invoice {0} dated {1}").format( - self.bill_no, formatdate(self.bill_date) - ) + if self.bill_no: + self.remarks = _("Against Supplier Invoice {0}").format(self.bill_no) + if self.bill_date: + self.remarks += " " + _("dated {0}").format(formatdate(self.bill_date)) + else: self.remarks = _("No Remarks") @@ -747,6 +747,9 @@ class PurchaseInvoice(BuyingController): validate_docs_for_voucher_types(["Purchase Invoice"]) validate_docs_for_deferred_accounting([], [self.name]) + def before_submit(self): + self.create_remarks() + def on_submit(self): super().on_submit() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1966a78ef49..12e26623ad0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -278,7 +278,6 @@ class SalesInvoice(SellingController): self.check_sales_order_on_hold_or_close("sales_order") self.validate_debit_to_acc() self.clear_unallocated_advances("Sales Invoice Advance", "advances") - self.add_remarks() self.validate_fixed_asset() self.set_income_account_for_fixed_assets() self.validate_item_cost_centers() @@ -422,6 +421,9 @@ class SalesInvoice(SellingController): self.set_account_for_mode_of_payment() self.set_paid_amount() + def before_submit(self): + self.add_remarks() + def on_submit(self): self.validate_pos_paid_amount() @@ -946,10 +948,11 @@ class SalesInvoice(SellingController): def add_remarks(self): if not self.remarks: - if self.po_no and self.po_date: - self.remarks = _("Against Customer Order {0} dated {1}").format( - self.po_no, formatdate(self.po_date) - ) + if self.po_no: + self.remarks = _("Against Customer Order {0}").format(self.po_no) + if self.po_date: + self.remarks += " " + _("dated {0}").format(formatdate(self.po_data)) + else: self.remarks = _("No Remarks") diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 3c4e1a05c97..bdea568bdf4 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -48,8 +48,9 @@
{% } %} -
{%= __("Remarks") %}: {%= data[i].remarks %} - {% if(data[i].bill_no) { %} + {% if(data[i].remarks) { %} +
{%= __("Remarks") %}: {%= data[i].remarks %} + {% } else if(data[i].bill_no) { %}
{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %} {% } %} diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7350fa77e1c..5b1455ef723 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -371,6 +371,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.v15_0.update_invoice_remarks 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 diff --git a/erpnext/patches/v15_0/update_invoice_remarks.py b/erpnext/patches/v15_0/update_invoice_remarks.py new file mode 100644 index 00000000000..7060fe57e31 --- /dev/null +++ b/erpnext/patches/v15_0/update_invoice_remarks.py @@ -0,0 +1,51 @@ +import frappe +from frappe import _ + + +def execute(): + update_sales_invoice_remarks() + update_purchase_invoice_remarks() + + +def update_sales_invoice_remarks(): + si_list = frappe.db.get_all( + "Sales Invoice", + filters={ + "docstatus": 1, + "remarks": "No Remarks", + "po_no": ["!=", ""], + }, + fields=["name", "po_no"], + ) + + for doc in si_list: + remarks = _("Against Customer Order {0}").format(doc.po_no) + update_remarks("Sales Invoice", doc.name, remarks) + + +def update_purchase_invoice_remarks(): + pi_list = frappe.db.get_all( + "Purchase Invoice", + filters={ + "docstatus": 1, + "remarks": "No Remarks", + "bill_no": ["!=", ""], + }, + fields=["name", "bill_no"], + ) + + for doc in pi_list: + remarks = _("Against Supplier Invoice {0}").format(doc.bill_no) + update_remarks("Purchase Invoice", doc.name, remarks) + + +def update_remarks(doctype, docname, remarks): + filters = { + "voucher_type": doctype, + "remarks": "No Remarks", + "voucher_no": docname, + } + + frappe.db.set_value(doctype, docname, "remarks", remarks) + frappe.db.set_value("GL Entry", filters, "remarks", remarks) + frappe.db.set_value("Payment Ledger Entry", filters, "remarks", remarks) From 80b5c16a2e3183553c5ad95c0a70ecf94310aa12 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:56:29 +0530 Subject: [PATCH 14/39] fix: Cannot read properties of null (reading 'doc') (backport #43071) (#43118) fix: Cannot read properties of null (reading 'doc') (cherry picked from commit 62c3389bd620f4d31eb8ee4df6fbf3dec03e3ae6) Co-authored-by: Rohit Waghchaure --- erpnext/public/js/controllers/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d8bf4a5cf17..91b938acb50 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1510,8 +1510,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe item_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); }); - var show = (cint(cur_frm.doc.discount_amount)) || - ((cur_frm.doc.taxes || []).filter(function(d) {return d.included_in_print_rate===1}).length); + var show = (cint(this.frm.doc.discount_amount)) || + ((this.frm.doc.taxes || []).filter(function(d) {return d.included_in_print_rate===1}).length); $.each(["net_rate", "net_amount"], function(i, fname) { if(frappe.meta.get_docfield(item_grid.doctype, fname)) From d17baddb0d3c13212f8695967ffb971b55306224 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 6 Sep 2024 19:53:40 +0530 Subject: [PATCH 15/39] fix: check multi-currency on jv for common party accounting with foreign currency (cherry picked from commit 00938bfd4d1243b570e83f13f5774029ad0df3a1) --- erpnext/controllers/accounts_controller.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index fbd717db153..49198be9b48 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2420,6 +2420,8 @@ class AccountsController(TransactionBase): primary_account = get_party_account(primary_party_type, primary_party, self.company) secondary_account = get_party_account(secondary_party_type, secondary_party, self.company) + primary_account_currency = get_account_currency(primary_account) + secondary_account_currency = get_account_currency(secondary_account) jv = frappe.new_doc("Journal Entry") jv.voucher_type = "Journal Entry" @@ -2460,6 +2462,10 @@ class AccountsController(TransactionBase): advance_entry.credit_in_account_currency = self.outstanding_amount reconcilation_entry.debit_in_account_currency = self.outstanding_amount + default_currency = erpnext.get_company_currency(self.company) + if primary_account_currency != default_currency or secondary_account_currency != default_currency: + jv.multi_currency = 1 + jv.append("accounts", reconcilation_entry) jv.append("accounts", advance_entry) From 47b216373d55bc4b8a1517b32c538ef247306602 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 6 Sep 2024 20:01:35 +0530 Subject: [PATCH 16/39] test: add unit test for common party with foreign currency (cherry picked from commit 740a04a70437422dac2b824d058b264026228b79) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../sales_invoice/test_sales_invoice.py | 194 +++++++++++++++++- 1 file changed, 193 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ce8627be27f..5f9cf6f104b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -51,7 +51,7 @@ class TestSalesInvoice(FrappeTestCase): from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) - create_internal_parties() + # create_internal_parties() setup_accounts() frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) @@ -3915,6 +3915,198 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(len(res), 1) self.assertEqual(res[0][0], pos_return.return_against) +<<<<<<< HEAD +======= + def test_validation_on_opening_invoice_with_rounding(self): + si = create_sales_invoice(qty=1, rate=99.98, do_not_submit=True) + si.is_opening = "Yes" + si.items[0].income_account = "Temporary Opening - _TC" + si.save() + self.assertRaises(frappe.ValidationError, si.submit) + + def _create_opening_roundoff_account(self, company_name): + liability_root = frappe.db.get_all( + "Account", + filters={"company": company_name, "root_type": "Liability", "disabled": 0}, + order_by="lft", + limit=1, + )[0] + + # setup round off account + if acc := frappe.db.exists( + "Account", + { + "account_name": "Round Off for Opening", + "account_type": "Round Off for Opening", + "company": company_name, + }, + ): + frappe.db.set_value("Company", company_name, "round_off_for_opening", acc) + else: + acc = frappe.new_doc("Account") + acc.company = company_name + acc.parent_account = liability_root.name + acc.account_name = "Round Off for Opening" + acc.account_type = "Round Off for Opening" + acc.save() + frappe.db.set_value("Company", company_name, "round_off_for_opening", acc.name) + + def test_opening_invoice_with_rounding_adjustment(self): + si = create_sales_invoice(qty=1, rate=99.98, do_not_submit=True) + si.is_opening = "Yes" + si.items[0].income_account = "Temporary Opening - _TC" + si.save() + + self._create_opening_roundoff_account(si.company) + + si.reload() + si.submit() + res = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": si.name, "is_opening": "Yes"}, + fields=["account", "debit", "credit", "is_opening"], + ) + self.assertEqual(len(res), 3) + + def _create_opening_invoice_with_inclusive_tax(self): + si = create_sales_invoice(qty=1, rate=90, do_not_submit=True) + si.is_opening = "Yes" + si.items[0].income_account = "Temporary Opening - _TC" + item_template = si.items[0].as_dict() + item_template.name = None + item_template.rate = 55 + si.append("items", item_template) + si.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Testing...", + "rate": 5, + "included_in_print_rate": True, + }, + ) + # there will be 0.01 precision loss between Dr and Cr + # caused by 'included_in_print_tax' option + si.save() + return si + + def test_rounding_validation_for_opening_with_inclusive_tax(self): + si = self._create_opening_invoice_with_inclusive_tax() + # 'Round Off for Opening' not set in Company master + # Ledger level validation must be thrown + self.assertRaises(frappe.ValidationError, si.submit) + + def test_ledger_entries_on_opening_invoice_with_rounding_loss_by_inclusive_tax(self): + si = self._create_opening_invoice_with_inclusive_tax() + # 'Round Off for Opening' is set in Company master + self._create_opening_roundoff_account(si.company) + + si.submit() + actual = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": si.name, "is_opening": "Yes", "is_cancelled": False}, + fields=["account", "debit", "credit", "is_opening"], + order_by="account,debit", + ) + expected = [ + {"account": "_Test Account Service Tax - _TC", "debit": 0.0, "credit": 6.9, "is_opening": "Yes"}, + {"account": "Debtors - _TC", "debit": 145.0, "credit": 0.0, "is_opening": "Yes"}, + {"account": "Round Off for Opening - _TC", "debit": 0.0, "credit": 0.01, "is_opening": "Yes"}, + {"account": "Temporary Opening - _TC", "debit": 0.0, "credit": 138.09, "is_opening": "Yes"}, + ] + self.assertEqual(len(actual), 4) + self.assertEqual(expected, actual) + + def test_common_party_with_foreign_currency_jv(self): + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( + make_customer, + ) + from erpnext.accounts.doctype.party_link.party_link import create_party_link + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + from erpnext.setup.utils import get_exchange_rate + + creditors = create_account( + account_name="Creditors USD", + parent_account="Accounts Payable - _TC", + company="_Test Company", + account_currency="USD", + account_type="Payable", + ) + debtors = create_account( + account_name="Debtors USD", + parent_account="Accounts Receivable - _TC", + company="_Test Company", + account_currency="USD", + account_type="Receivable", + ) + + # create a customer + customer = make_customer(customer="_Test Common Party USD") + cust_doc = frappe.get_doc("Customer", customer) + cust_doc.default_currency = "USD" + test_account_details = { + "company": "_Test Company", + "account": debtors, + } + cust_doc.append("accounts", test_account_details) + cust_doc.save() + + # create a supplier + supplier = create_supplier(supplier_name="_Test Common Party USD").name + supp_doc = frappe.get_doc("Supplier", supplier) + supp_doc.default_currency = "USD" + test_account_details = { + "company": "_Test Company", + "account": creditors, + } + supp_doc.append("accounts", test_account_details) + supp_doc.save() + + # enable common party accounting + frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1) + + # create a party link between customer & supplier + party_link = create_party_link("Supplier", supplier, customer) + + # create a sales invoice + si = create_sales_invoice( + customer=customer, + currency="USD", + conversion_rate=get_exchange_rate("USD", "INR"), + debit_to=debtors, + do_not_save=1, + ) + si.party_account_currency = "USD" + si.save() + si.submit() + + # check outstanding of sales invoice + si.reload() + self.assertEqual(si.status, "Paid") + self.assertEqual(flt(si.outstanding_amount), 0.0) + + # check creation of journal entry + jv = frappe.get_all( + "Journal Entry Account", + { + "account": si.debit_to, + "party_type": "Customer", + "party": si.customer, + "reference_type": si.doctype, + "reference_name": si.name, + }, + pluck="credit_in_account_currency", + ) + self.assertTrue(jv) + self.assertEqual(jv[0], si.grand_total) + + party_link.delete() + frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0) + +>>>>>>> 740a04a704 (test: add unit test for common party with foreign currency) def set_advance_flag(company, flag, default_account): frappe.db.set_value( From 33174b1ba26e7002d3e3286816182dba2223954e Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 6 Sep 2024 20:03:56 +0530 Subject: [PATCH 17/39] fix: uncomment internal parties (cherry picked from commit 454e18ad5fef1ad81aab3efcca1d7886a0d80fbf) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 5f9cf6f104b..58db5bc7059 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -51,7 +51,7 @@ class TestSalesInvoice(FrappeTestCase): from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) - # create_internal_parties() + create_internal_parties() setup_accounts() frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) From a9bd11f59ae5aa1115a440812d87734aeba55c81 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 7 Sep 2024 11:39:06 +0530 Subject: [PATCH 18/39] refactor(test): use change_settings decorator (cherry picked from commit ee94fb37c81a28e89aa2175b933d231a5a6601f7) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 58db5bc7059..c5090815253 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4019,6 +4019,7 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(len(actual), 4) self.assertEqual(expected, actual) + @change_settings("Accounts Settings", {"enable_common_party_accounting": True}) def test_common_party_with_foreign_currency_jv(self): from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( @@ -4065,11 +4066,8 @@ class TestSalesInvoice(FrappeTestCase): supp_doc.append("accounts", test_account_details) supp_doc.save() - # enable common party accounting - frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1) - # create a party link between customer & supplier - party_link = create_party_link("Supplier", supplier, customer) + create_party_link("Supplier", supplier, customer) # create a sales invoice si = create_sales_invoice( @@ -4103,10 +4101,13 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(jv) self.assertEqual(jv[0], si.grand_total) +<<<<<<< HEAD party_link.delete() frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0) >>>>>>> 740a04a704 (test: add unit test for common party with foreign currency) +======= +>>>>>>> ee94fb37c8 (refactor(test): use change_settings decorator) def set_advance_flag(company, flag, default_account): frappe.db.set_value( From 354c34e4d8e423486e71161de2f64e4ae8de3fac Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 9 Sep 2024 10:51:45 +0530 Subject: [PATCH 19/39] chore: resolve conflict --- .../sales_invoice/test_sales_invoice.py | 111 ------------------ 1 file changed, 111 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c5090815253..beb347e07db 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3915,110 +3915,6 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(len(res), 1) self.assertEqual(res[0][0], pos_return.return_against) -<<<<<<< HEAD -======= - def test_validation_on_opening_invoice_with_rounding(self): - si = create_sales_invoice(qty=1, rate=99.98, do_not_submit=True) - si.is_opening = "Yes" - si.items[0].income_account = "Temporary Opening - _TC" - si.save() - self.assertRaises(frappe.ValidationError, si.submit) - - def _create_opening_roundoff_account(self, company_name): - liability_root = frappe.db.get_all( - "Account", - filters={"company": company_name, "root_type": "Liability", "disabled": 0}, - order_by="lft", - limit=1, - )[0] - - # setup round off account - if acc := frappe.db.exists( - "Account", - { - "account_name": "Round Off for Opening", - "account_type": "Round Off for Opening", - "company": company_name, - }, - ): - frappe.db.set_value("Company", company_name, "round_off_for_opening", acc) - else: - acc = frappe.new_doc("Account") - acc.company = company_name - acc.parent_account = liability_root.name - acc.account_name = "Round Off for Opening" - acc.account_type = "Round Off for Opening" - acc.save() - frappe.db.set_value("Company", company_name, "round_off_for_opening", acc.name) - - def test_opening_invoice_with_rounding_adjustment(self): - si = create_sales_invoice(qty=1, rate=99.98, do_not_submit=True) - si.is_opening = "Yes" - si.items[0].income_account = "Temporary Opening - _TC" - si.save() - - self._create_opening_roundoff_account(si.company) - - si.reload() - si.submit() - res = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": si.name, "is_opening": "Yes"}, - fields=["account", "debit", "credit", "is_opening"], - ) - self.assertEqual(len(res), 3) - - def _create_opening_invoice_with_inclusive_tax(self): - si = create_sales_invoice(qty=1, rate=90, do_not_submit=True) - si.is_opening = "Yes" - si.items[0].income_account = "Temporary Opening - _TC" - item_template = si.items[0].as_dict() - item_template.name = None - item_template.rate = 55 - si.append("items", item_template) - si.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Service Tax - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "Testing...", - "rate": 5, - "included_in_print_rate": True, - }, - ) - # there will be 0.01 precision loss between Dr and Cr - # caused by 'included_in_print_tax' option - si.save() - return si - - def test_rounding_validation_for_opening_with_inclusive_tax(self): - si = self._create_opening_invoice_with_inclusive_tax() - # 'Round Off for Opening' not set in Company master - # Ledger level validation must be thrown - self.assertRaises(frappe.ValidationError, si.submit) - - def test_ledger_entries_on_opening_invoice_with_rounding_loss_by_inclusive_tax(self): - si = self._create_opening_invoice_with_inclusive_tax() - # 'Round Off for Opening' is set in Company master - self._create_opening_roundoff_account(si.company) - - si.submit() - actual = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": si.name, "is_opening": "Yes", "is_cancelled": False}, - fields=["account", "debit", "credit", "is_opening"], - order_by="account,debit", - ) - expected = [ - {"account": "_Test Account Service Tax - _TC", "debit": 0.0, "credit": 6.9, "is_opening": "Yes"}, - {"account": "Debtors - _TC", "debit": 145.0, "credit": 0.0, "is_opening": "Yes"}, - {"account": "Round Off for Opening - _TC", "debit": 0.0, "credit": 0.01, "is_opening": "Yes"}, - {"account": "Temporary Opening - _TC", "debit": 0.0, "credit": 138.09, "is_opening": "Yes"}, - ] - self.assertEqual(len(actual), 4) - self.assertEqual(expected, actual) - @change_settings("Accounts Settings", {"enable_common_party_accounting": True}) def test_common_party_with_foreign_currency_jv(self): from erpnext.accounts.doctype.account.test_account import create_account @@ -4101,13 +3997,6 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(jv) self.assertEqual(jv[0], si.grand_total) -<<<<<<< HEAD - party_link.delete() - frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0) - ->>>>>>> 740a04a704 (test: add unit test for common party with foreign currency) -======= ->>>>>>> ee94fb37c8 (refactor(test): use change_settings decorator) def set_advance_flag(company, flag, default_account): frappe.db.set_value( From ea86bc2235acb0712ae7205cff97c3e14614c3c4 Mon Sep 17 00:00:00 2001 From: rahulgupta8848 <147691594+rahulgupta8848@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:12:00 +0530 Subject: [PATCH 20/39] =?UTF-8?q?feat:=20added=20revaluation=20surplus=20a?= =?UTF-8?q?nd=20impairment=20acc=20in=20standard=20charts=E2=80=A6=20(#430?= =?UTF-8?q?22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: added revaluation surplus and impairment acc in standard charts of accounts Co-authored-by: “rahulgupta8848” <“rahul.gupta@8848digital.com”> (cherry picked from commit 8202f505cce9c938b43e6f238129f4641cb1d6e8) --- .../verified/in_standard_chart_of_accounts.json | 6 ++++-- .../verified/standard_chart_of_accounts.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json index 2ec0b7f70c8..4d807b09c33 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json @@ -109,7 +109,8 @@ "Utility Expenses": {}, "Write Off": {}, "Exchange Gain/Loss": {}, - "Gain/Loss on Asset Disposal": {} + "Gain/Loss on Asset Disposal": {}, + "Impairment": {} }, "root_type": "Expense" }, @@ -132,7 +133,8 @@ "Source of Funds (Liabilities)": { "Capital Account": { "Reserves and Surplus": {}, - "Shareholders Funds": {} + "Shareholders Funds": {}, + "Revaluation Surplus": {} }, "Current Liabilities": { "Accounts Payable": { diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py index e30ad24a374..5a5e232db8d 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py @@ -72,6 +72,7 @@ def get(): _("Write Off"): {}, _("Exchange Gain/Loss"): {}, _("Gain/Loss on Asset Disposal"): {}, + _("Impairment"): {}, }, "root_type": "Expense", }, @@ -104,6 +105,7 @@ def get(): _("Dividends Paid"): {"account_type": "Equity"}, _("Opening Balance Equity"): {"account_type": "Equity"}, _("Retained Earnings"): {"account_type": "Equity"}, + _("Revaluation Surplus"): {"account_type": "Equity"}, "root_type": "Equity", }, } From 03e3374a8b0e90a3d84c8a61070510edf1c25b2b Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:27:59 +0530 Subject: [PATCH 21/39] fix: set today in 'On This Date' in Available Batch Report (cherry picked from commit 9fd55e4c83e4c0b715dd746c5032ac51217641d6) --- .../report/available_batch_report/available_batch_report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.js b/erpnext/stock/report/available_batch_report/available_batch_report.js index 011f7e09ca2..a13e4ca82f7 100644 --- a/erpnext/stock/report/available_batch_report/available_batch_report.js +++ b/erpnext/stock/report/available_batch_report/available_batch_report.js @@ -17,7 +17,7 @@ frappe.query_reports["Available Batch Report"] = { fieldtype: "Date", width: "80", reqd: 1, - default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + default: frappe.datetime.get_today(), }, { fieldname: "item_code", From 8f4dc8048d86c2f103b3ded40074be80df0e5870 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Sat, 31 Aug 2024 17:28:03 +0530 Subject: [PATCH 22/39] fix: validate the item code when updating the other item's price rule (cherry picked from commit 45de18069c5e69970115dbd04381c4cd296948d1) --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 91b938acb50..aa4a2ae85a0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1777,7 +1777,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (data && data.apply_rule_on_other_items && JSON.parse(data.apply_rule_on_other_items)) { me.frm.doc.items.forEach(d => { - if (in_list(JSON.parse(data.apply_rule_on_other_items), d[data.apply_rule_on])) { + if (in_list(JSON.parse(data.apply_rule_on_other_items), d[data.apply_rule_on]) && d.item_code === data.item_code) { for(var k in data) { if (data.pricing_rule_for == "Discount Percentage" && data.apply_rule_on_other_items && k == "discount_amount") { continue; From 2dddd7906bc0468bd8f8eb7b139e19af334b5f2e Mon Sep 17 00:00:00 2001 From: Prashant Kamble <99401472+pra17shant@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:21:05 +0000 Subject: [PATCH 23/39] fix: unreconcile allocation child table redirect url voucher no issue (cherry picked from commit 5d6f6a2fb972e4678fe55f5d3970903501f10a10) --- erpnext/public/js/utils/unreconcile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js index 6864e2865d3..c6ee8a330c7 100644 --- a/erpnext/public/js/utils/unreconcile.js +++ b/erpnext/public/js/utils/unreconcile.js @@ -69,7 +69,7 @@ erpnext.accounts.unreconcile_payment = { { label: __("Voucher Type"), fieldname: "voucher_type", - fieldtype: "Dynamic Link", + fieldtype: "Link", options: "DocType", in_list_view: 1, read_only: 1, @@ -77,7 +77,7 @@ erpnext.accounts.unreconcile_payment = { { label: __("Voucher No"), fieldname: "voucher_no", - fieldtype: "Link", + fieldtype: "Dynamic Link", options: "voucher_type", in_list_view: 1, read_only: 1, From ea4f7365ea8e6dc48af50369c2239b2ba665ab5e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 17 Aug 2024 02:27:36 +0200 Subject: [PATCH 24/39] fix(Delivery Note): translatability of validation errors (cherry picked from commit 34df6e39dcca685b6b107c5f0631721d7156768c) --- .../doctype/delivery_note/delivery_note.py | 70 ++++++++----------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 847a57baa72..d2d10cd9935 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -362,52 +362,42 @@ class DeliveryNote(SellingController): self.validate_sales_invoice_references() def validate_sales_order_references(self): - err_msg = "" + errors = [] for item in self.items: - if (item.against_sales_order and not item.so_detail) or ( - not item.against_sales_order and item.so_detail - ): - if not item.against_sales_order: - err_msg += ( - _("'Sales Order' reference ({1}) is missing in row {0}").format( - frappe.bold(item.idx), frappe.bold("against_sales_order") - ) - + "
" - ) - else: - err_msg += ( - _("'Sales Order Item' reference ({1}) is missing in row {0}").format( - frappe.bold(item.idx), frappe.bold("so_detail") - ) - + "
" - ) + missing_label = None + if item.against_sales_order and not item.so_detail: + missing_label = item.meta.get_label("so_detail") + elif item.so_detail and not item.against_sales_order: + missing_label = item.meta.get_label("against_sales_order") - if err_msg: - frappe.throw(err_msg, title=_("References to Sales Orders are Incomplete")) + if missing_label and missing_label != "No Label": + errors.append( + _("The field {0} in row {1} is not set").format( + frappe.bold(_(missing_label)), frappe.bold(item.idx) + ) + ) + + if errors: + frappe.throw("
".join(errors), title=_("References to Sales Orders are Incomplete")) def validate_sales_invoice_references(self): - err_msg = "" + errors = [] for item in self.items: - if (item.against_sales_invoice and not item.si_detail) or ( - not item.against_sales_invoice and item.si_detail - ): - if not item.against_sales_invoice: - err_msg += ( - _("'Sales Invoice' reference ({1}) is missing in row {0}").format( - frappe.bold(item.idx), frappe.bold("against_sales_invoice") - ) - + "
" - ) - else: - err_msg += ( - _("'Sales Invoice Item' reference ({1}) is missing in row {0}").format( - frappe.bold(item.idx), frappe.bold("si_detail") - ) - + "
" - ) + missing_label = None + if item.against_sales_invoice and not item.si_detail: + missing_label = item.meta.get_label("si_detail") + elif item.si_detail and not item.against_sales_invoice: + missing_label = item.meta.get_label("against_sales_invoice") - if err_msg: - frappe.throw(err_msg, title=_("References to Sales Invoices are Incomplete")) + if missing_label and missing_label != "No Label": + errors.append( + _("The field {0} in row {1} is not set").format( + frappe.bold(_(missing_label)), frappe.bold(item.idx) + ) + ) + + if errors: + frappe.throw("
".join(errors), title=_("References to Sales Invoices are Incomplete")) def validate_proj_cust(self): """check for does customer belong to same project as entered..""" From 0c0f103b836344cc6dbebfbc0fb1d8253a9cffc7 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 17 Aug 2024 02:36:16 +0200 Subject: [PATCH 25/39] refactor: extract common validation method (cherry picked from commit 08646b7ab7605aa76b1defe691c97070dc54c317) --- .../doctype/delivery_note/delivery_note.py | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index d2d10cd9935..5d95e7b66d3 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -362,32 +362,23 @@ class DeliveryNote(SellingController): self.validate_sales_invoice_references() def validate_sales_order_references(self): - errors = [] - for item in self.items: - missing_label = None - if item.against_sales_order and not item.so_detail: - missing_label = item.meta.get_label("so_detail") - elif item.so_detail and not item.against_sales_order: - missing_label = item.meta.get_label("against_sales_order") - - if missing_label and missing_label != "No Label": - errors.append( - _("The field {0} in row {1} is not set").format( - frappe.bold(_(missing_label)), frappe.bold(item.idx) - ) - ) - - if errors: - frappe.throw("
".join(errors), title=_("References to Sales Orders are Incomplete")) + self._validate_dependent_item_fields( + "against_sales_order", "so_detail", _("References to Sales Orders are Incomplete") + ) def validate_sales_invoice_references(self): + self._validate_dependent_item_fields( + "against_sales_invoice", "si_detail", _("References to Sales Invoices are Incomplete") + ) + + def _validate_dependent_item_fields(self, field_a: str, field_b: str, error_title: str): errors = [] for item in self.items: missing_label = None - if item.against_sales_invoice and not item.si_detail: - missing_label = item.meta.get_label("si_detail") - elif item.si_detail and not item.against_sales_invoice: - missing_label = item.meta.get_label("against_sales_invoice") + if item.get(field_a) and not item.get(field_b): + missing_label = item.meta.get_label(field_b) + elif item.get(field_b) and not item.get(field_a): + missing_label = item.meta.get_label(field_a) if missing_label and missing_label != "No Label": errors.append( @@ -397,7 +388,7 @@ class DeliveryNote(SellingController): ) if errors: - frappe.throw("
".join(errors), title=_("References to Sales Invoices are Incomplete")) + frappe.throw("
".join(errors), title=error_title) def validate_proj_cust(self): """check for does customer belong to same project as entered..""" From 3fd9df0d2e6efeef83384e480a0e81dc8e4966c7 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 4 Sep 2024 18:05:02 +0200 Subject: [PATCH 26/39] fix(Opening Invoice Creation Tool): translatability of messages (cherry picked from commit f3c5803198174f46caf607d1e36f8cb367f72fc0) --- .../opening_invoice_creation_tool.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js index f1efba8a954..4938e6690e5 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -28,7 +28,12 @@ frappe.ui.form.on("Opening Invoice Creation Tool", { frm.refresh_fields(); frm.page.clear_indicator(); frm.dashboard.hide_progress(); - frappe.msgprint(__("Opening {0} Invoices created", [frm.doc.invoice_type])); + + if (frm.doc.invoice_type == "Sales") { + frappe.msgprint(__("Opening Sales Invoices have been created.")); + } else { + frappe.msgprint(__("Opening Purchase Invoices have been created.")); + } }, 1500, data.title @@ -48,12 +53,19 @@ frappe.ui.form.on("Opening Invoice Creation Tool", { !frm.doc.import_in_progress && frm.trigger("make_dashboard"); frm.page.set_primary_action(__("Create Invoices"), () => { let btn_primary = frm.page.btn_primary.get(0); + let freeze_message; + if (frm.doc.invoice_type == "Sales") { + freeze_message = __("Creating Sales Invoices ..."); + } else { + freeze_message = __("Creating Purchase Invoices ..."); + } + return frm.call({ doc: frm.doc, btn: $(btn_primary), method: "make_invoices", freeze: 1, - freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]), + freeze_message: freeze_message, }); }); From 5110975c6dc13aa894a60caace1fc4ba33d178cf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 20:34:56 +0530 Subject: [PATCH 27/39] fix: incorrect actual cost in Procurement Tracker report (backport #43109) (#43138) fix: incorrect actual cost in Procurement Tracker report (#43109) (cherry picked from commit 80f101f92e2bd85927450b6adb073c575a327f42) Co-authored-by: rohitwaghchaure --- .../buying/report/procurement_tracker/procurement_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index a7e03c08fac..bd0798236b3 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -175,7 +175,7 @@ def get_data(filters): "purchase_order": po.parent, "supplier": po.supplier, "estimated_cost": flt(mr_record.get("amount")), - "actual_cost": flt(pi_records.get(po.name)), + "actual_cost": flt(pi_records.get(po.name)) or flt(po.amount), "purchase_order_amt": flt(po.amount), "purchase_order_amt_in_company_currency": flt(po.base_amount), "expected_delivery_date": po.schedule_date, From 208bd2b8ff4d55dc9061f90f9b48027bf247a68e Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Sat, 7 Sep 2024 01:33:12 +0530 Subject: [PATCH 28/39] fix: unhide action button after form redirect (cherry picked from commit 5ce5b1b6a2a64cc5a8a19a478f95781886bfff72) --- erpnext/assets/doctype/asset/asset.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 2477e443c4a..302fe816882 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -528,6 +528,7 @@ frappe.ui.form.on("Asset", { callback: function (r) { var doclist = frappe.model.sync(r.message); frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + $(".primary-action").prop("hidden", false); }, }); }, From 9e72a844f702d0e3e032765777ec1780d8a6b6ed Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:23:54 +0530 Subject: [PATCH 29/39] fix: pass company from asset to asset capitalization (cherry picked from commit f3445d645def648fdcd285125520dab13315beef) --- erpnext/assets/doctype/asset/asset.js | 2 ++ erpnext/assets/doctype/asset/asset.py | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 302fe816882..0a21516c177 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -506,6 +506,7 @@ frappe.ui.form.on("Asset", { create_asset_repair: function (frm) { frappe.call({ args: { + company: frm.doc.company, asset: frm.doc.name, asset_name: frm.doc.asset_name, }, @@ -520,6 +521,7 @@ frappe.ui.form.on("Asset", { create_asset_capitalization: function (frm) { frappe.call({ args: { + company: frm.doc.company, asset: frm.doc.name, asset_name: frm.doc.asset_name, item_code: frm.doc.item_code, diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index abb615ff277..58f813247f0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -895,18 +895,19 @@ def create_asset_maintenance(asset, item_code, item_name, asset_category, compan @frappe.whitelist() -def create_asset_repair(asset, asset_name): +def create_asset_repair(company, asset, asset_name): asset_repair = frappe.new_doc("Asset Repair") - asset_repair.update({"asset": asset, "asset_name": asset_name}) + asset_repair.update({"company": company, "asset": asset, "asset_name": asset_name}) return asset_repair @frappe.whitelist() -def create_asset_capitalization(asset, asset_name, item_code): +def create_asset_capitalization(company, asset, asset_name, item_code): asset_capitalization = frappe.new_doc("Asset Capitalization") asset_capitalization.update( { "target_asset": asset, + "company": company, "capitalization_method": "Choose a WIP composite asset", "target_asset_name": asset_name, "target_item_code": item_code, From d2923bae8582f2c2bc7b126ff6a41746f2f61262 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 19:26:38 +0200 Subject: [PATCH 30/39] fix: return type of `get_party_details` (backport #43131) (#43134) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> fix: return type of `get_party_details` (#43131) --- erpnext/accounts/party.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index e6015e081d6..06e285e4ada 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -68,7 +68,7 @@ def get_party_details( pos_profile=None, ): if not party: - return {} + return frappe._dict() if not frappe.db.exists(party_type, party): frappe.throw(_("{0}: {1} does not exists").format(party_type, party)) return _get_party_details( From 5929d50c72085a4a875b802b26f9364548342e49 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 9 Sep 2024 12:31:32 +0530 Subject: [PATCH 31/39] feat: utility report to identify invalid ledger entries (cherry picked from commit 832c4aaf82e6556a83429a3757efb3449b53de2a) --- .../report/invalid_ledger_entries/__init__.py | 0 .../invalid_ledger_entries.js | 13 +++++ .../invalid_ledger_entries.json | 23 +++++++++ .../invalid_ledger_entries.py | 48 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 erpnext/accounts/report/invalid_ledger_entries/__init__.py create mode 100644 erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js create mode 100644 erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.json create mode 100644 erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py diff --git a/erpnext/accounts/report/invalid_ledger_entries/__init__.py b/erpnext/accounts/report/invalid_ledger_entries/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js new file mode 100644 index 00000000000..548a6f7d951 --- /dev/null +++ b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js @@ -0,0 +1,13 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Invalid Ledger Entries"] = { + filters: [ + // { + // "fieldname": "my_filter", + // "label": __("My Filter"), + // "fieldtype": "Data", + // "reqd": 1, + // }, + ], +}; diff --git a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.json b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.json new file mode 100644 index 00000000000..00dbbfc5056 --- /dev/null +++ b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.json @@ -0,0 +1,23 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2024-09-09 12:31:25.295976", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letterhead": null, + "modified": "2024-09-09 12:31:25.295976", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Invalid Ledger Entries", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "Invalid Ledger Entries", + "report_type": "Script Report", + "roles": [], + "timeout": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py new file mode 100644 index 00000000000..4f4b1835227 --- /dev/null +++ b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py @@ -0,0 +1,48 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe import _ + + +def execute(filters: dict | None = None): + """Return columns and data for the report. + + This is the main entry point for the report. It accepts the filters as a + dictionary and should return columns and data. It is called by the framework + every time the report is refreshed or a filter is updated. + """ + columns = get_columns() + data = get_data() + + return columns, data + + +def get_columns() -> list[dict]: + """Return columns for the report. + + One field definition per column, just like a DocType field definition. + """ + return [ + { + "label": _("Column 1"), + "fieldname": "column_1", + "fieldtype": "Data", + }, + { + "label": _("Column 2"), + "fieldname": "column_2", + "fieldtype": "Int", + }, + ] + + +def get_data() -> list[list]: + """Return data for the report. + + The report data is a list of rows, with each row being a list of cell values. + """ + return [ + ["Row 1", 1], + ["Row 2", 2], + ] From 14e30d12b4d556ec6be3ea8cf70fbbd6afac8192 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 9 Sep 2024 13:55:10 +0530 Subject: [PATCH 32/39] refactor: standard filters (cherry picked from commit dccbc1f432a83f07ef46582ac36df8fd776d4a4d) --- .../invalid_ledger_entries.js | 55 ++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js index 548a6f7d951..ffaaf5d7cbf 100644 --- a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js +++ b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js @@ -1,13 +1,52 @@ // Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt +function get_filters() { + let filters = [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1, + }, + { + fieldname: "from_date", + label: __("Start Date"), + fieldtype: "Date", + reqd: 1, + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + fieldname: "to_date", + label: __("End Date"), + fieldtype: "Date", + reqd: 1, + default: frappe.datetime.get_today(), + }, + { + fieldname: "account", + label: __("Account"), + fieldtype: "MultiSelectList", + options: "Account", + get_data: function (txt) { + return frappe.db.get_link_options("Account", txt, { + company: frappe.query_report.get_filter_value("company"), + account_type: ["in", ["Receivable", "Payable"]], + }); + }, + }, + { + fieldname: "voucher_no", + label: __("Voucher No"), + fieldtype: "Data", + width: 100, + }, + ]; + return filters; +} + frappe.query_reports["Invalid Ledger Entries"] = { - filters: [ - // { - // "fieldname": "my_filter", - // "label": __("My Filter"), - // "fieldtype": "Data", - // "reqd": 1, - // }, - ], + filters: get_filters(), }; From 710d30074d6d65e18b70cae80d197d81b54242be Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 9 Sep 2024 17:53:59 +0530 Subject: [PATCH 33/39] refactor: barebones methods with basic logic (cherry picked from commit b05b378ef0fcd4c08404fd541857921702751676) --- .../invalid_ledger_entries.py | 121 +++++++++++++++--- 1 file changed, 105 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py index 4f4b1835227..e1599a6758e 100644 --- a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py +++ b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py @@ -1,8 +1,10 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe -from frappe import _ +import frappe +from frappe import _, qb +from frappe.query_builder import Criterion +from frappe.query_builder.custom import ConstantColumn def execute(filters: dict | None = None): @@ -12,8 +14,10 @@ def execute(filters: dict | None = None): dictionary and should return columns and data. It is called by the framework every time the report is refreshed or a filter is updated. """ + validate_filters(filters) + columns = get_columns() - data = get_data() + data = get_data(filters) return columns, data @@ -24,25 +28,110 @@ def get_columns() -> list[dict]: One field definition per column, just like a DocType field definition. """ return [ + {"label": _("Voucher Type"), "fieldname": "voucher_type", "fieldtype": "Link", "options": "DocType"}, { - "label": _("Column 1"), - "fieldname": "column_1", - "fieldtype": "Data", - }, - { - "label": _("Column 2"), - "fieldname": "column_2", - "fieldtype": "Int", + "label": _("Voucher No"), + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "options": "voucher_type", }, ] -def get_data() -> list[list]: +def get_data(filters) -> list[list]: """Return data for the report. The report data is a list of rows, with each row being a list of cell values. """ - return [ - ["Row 1", 1], - ["Row 2", 2], - ] + active_vouchers = get_active_vouchers_for_period(filters) + invalid_vouchers = identify_cancelled_vouchers(active_vouchers) + + return invalid_vouchers + + +def identify_cancelled_vouchers(active_vouchers: list[dict] | list | None = None) -> list[dict]: + cancelled_vouchers = [] + if active_vouchers: + # Group by voucher types and use single query to identify cancelled vouchers + vtypes = set([x.voucher_type for x in active_vouchers]) + + for _t in vtypes: + _names = [x.voucher_no for x in active_vouchers if x.voucher_type == _t] + dt = qb.DocType(_t) + non_active_vouchers = ( + qb.from_(dt) + .select(ConstantColumn(_t).as_("doctype"), dt.name) + .where(dt.docstatus.ne(1) & dt.name.isin(_names)) + .run() + ) + if non_active_vouchers: + cancelled_vouchers.extend(non_active_vouchers) + return cancelled_vouchers + + +def validate_filters(filters: dict | None = None): + if not filters: + frappe.throw(_("Filters missing")) + + if not filters.company: + frappe.throw(_("Company is mandatory")) + + if filters.from_date > filters.to_date: + frappe.throw(_("Start Date should be lower than End Date")) + + +def build_query_filters(filters: dict | None = None) -> list: + qb_filters = [] + if filters: + if filters.account: + qb_filters.append(qb.Field("account").isin(filters.account)) + + if filters.voucher_no: + qb_filters.append(qb.Field("voucher_no").eq(filters.voucher_no)) + + return qb_filters + + +def get_active_vouchers_for_period(filters: dict | None = None) -> list[dict]: + uniq_vouchers = [] + + if filters: + gle = qb.DocType("GL Entry") + ple = qb.DocType("Payment Ledger Entry") + + qb_filters = build_query_filters(filters) + + gl_vouchers = ( + qb.from_(gle) + .select(gle.voucher_type) + .distinct() + .select(gle.voucher_no) + .distinct() + .where( + gle.is_cancelled.eq(0) + & gle.company.eq(filters.company) + & gle.posting_date[filters.from_date : filters.to_date] + ) + .where(Criterion.all(qb_filters)) + .run(as_dict=True) + ) + + pl_vouchers = ( + qb.from_(ple) + .select(ple.voucher_type) + .distinct() + .select(ple.voucher_no) + .distinct() + .where( + ple.delinked.eq(0) + & ple.company.eq(filters.company) + & ple.posting_date[filters.from_date : filters.to_date] + ) + .where(Criterion.all(qb_filters)) + .run(as_dict=True) + ) + + uniq_vouchers.extend(gl_vouchers) + uniq_vouchers.extend(pl_vouchers) + + return uniq_vouchers From 5413372aebdab062c0433b3658ad92b7ba6df55e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 10 Sep 2024 12:58:05 +0530 Subject: [PATCH 34/39] refactor: fetch as dictionary (cherry picked from commit 2126b10a92c38aa12a35bc78b577fc2212d5da6a) --- .../report/invalid_ledger_entries/invalid_ledger_entries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py index e1599a6758e..33fda705cf2 100644 --- a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py +++ b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.py @@ -60,9 +60,9 @@ def identify_cancelled_vouchers(active_vouchers: list[dict] | list | None = None dt = qb.DocType(_t) non_active_vouchers = ( qb.from_(dt) - .select(ConstantColumn(_t).as_("doctype"), dt.name) + .select(ConstantColumn(_t).as_("voucher_type"), dt.name.as_("voucher_no")) .where(dt.docstatus.ne(1) & dt.name.isin(_names)) - .run() + .run(as_dict=True) ) if non_active_vouchers: cancelled_vouchers.extend(non_active_vouchers) From 9f09bf14cb4d2a0ab3a8de58e3cd0985f4b5ce73 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 10 Sep 2024 13:03:49 +0530 Subject: [PATCH 35/39] refactor: allow all accounts (cherry picked from commit 43198c946b498ab76ebbcd39de3d5cc031d897db) --- .../report/invalid_ledger_entries/invalid_ledger_entries.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js index ffaaf5d7cbf..47d478f2865 100644 --- a/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js +++ b/erpnext/accounts/report/invalid_ledger_entries/invalid_ledger_entries.js @@ -33,7 +33,6 @@ function get_filters() { get_data: function (txt) { return frappe.db.get_link_options("Account", txt, { company: frappe.query_report.get_filter_value("company"), - account_type: ["in", ["Receivable", "Payable"]], }); }, }, From a23e8b13bebf83e05363c9074c84e36f835e6d04 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 10 Sep 2024 14:29:13 +0530 Subject: [PATCH 36/39] fix: permission on guest PR creation (cherry picked from commit ea02e5f15ab58ad2f346e8f698ab5c63da5c5f1b) --- erpnext/accounts/doctype/payment_request/payment_request.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index f368c2a9613..83b43a15987 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -516,6 +516,8 @@ def make_payment_request(**args): if frappe.db.get_single_value("Accounts Settings", "create_pr_in_draft_status", cache=True): pr.insert(ignore_permissions=True) if args.submit_doc: + if pr.get("__unsaved"): + pr.insert(ignore_permissions=True) pr.submit() if args.order_type == "Shopping Cart": From cd57e009dd3023e419e7d9114ac32e5408b48881 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:01:53 +0530 Subject: [PATCH 37/39] fix: concurrency issue while picking materials (backport #43087) (#43152) fix: concurrency issue while picking materials (#43087) (cherry picked from commit 5c7dff0e846b3f28cd66a79ebec2d5cc18d68470) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/pick_list/pick_list.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 0c93fd6c402..27a37ef1bab 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -650,6 +650,8 @@ class PickList(Document): if self.name: query = query.where(pi_item.parent != self.name) + query = query.for_update() + return query.run(as_dict=True) def _get_product_bundles(self) -> dict[str, str]: From c9f49caeccb7b04cc20f43768b72a8238122c893 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:06:38 +0530 Subject: [PATCH 38/39] perf: timeout error (backport #43154) (#43158) perf: timeout error (#43154) (cherry picked from commit 1bf60248d9db816bb528e9500572a15c242f2025) Co-authored-by: rohitwaghchaure --- erpnext/public/js/controllers/transaction.js | 4 +++ .../serial_and_batch_bundle.py | 30 +++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index aa4a2ae85a0..3dcd36b2cff 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1879,6 +1879,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe const fields = ["discount_percentage", "discount_amount", "margin_rate_or_amount", "rate_with_margin"]; + if (!item) { + return; + } + if(item.remove_free_item) { let items = []; diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index ef4d475dca4..956eb08d9ff 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1558,13 +1558,14 @@ def get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos): serial_nos = set() data = get_stock_ledgers_for_serial_nos(kwargs) + bundle_wise_serial_nos = get_bundle_wise_serial_nos(data) for d in data: if d.serial_and_batch_bundle: - sns = get_serial_nos_from_bundle(d.serial_and_batch_bundle, kwargs.get("serial_nos", [])) - if d.actual_qty > 0: - serial_nos.update(sns) - else: - serial_nos.difference_update(sns) + if sns := bundle_wise_serial_nos.get(d.serial_and_batch_bundle): + if d.actual_qty > 0: + serial_nos.update(sns) + else: + serial_nos.difference_update(sns) elif d.serial_no: sns = get_serial_nos(d.serial_no) @@ -1581,6 +1582,25 @@ def get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos): return serial_nos +def get_bundle_wise_serial_nos(data): + bundle_wise_serial_nos = defaultdict(list) + bundles = [d.serial_and_batch_bundle for d in data if d.serial_and_batch_bundle] + if not bundles: + return bundle_wise_serial_nos + + bundle_data = frappe.get_all( + "Serial and Batch Entry", + fields=["serial_no", "parent"], + filters={"parent": ("in", bundles), "docstatus": 1, "serial_no": ("is", "set")}, + ) + + for d in bundle_data: + if d.parent: + bundle_wise_serial_nos[d.parent].append(d.serial_no) + + return bundle_wise_serial_nos + + def get_reserved_serial_nos(kwargs) -> list: """Returns a list of `Serial No` reserved in POS Invoice and Stock Reservation Entry.""" From 8c8dc241e50ba72775c7651b0c19636d3f5a2182 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:07:07 +0530 Subject: [PATCH 39/39] fix: bom cost update is not working (backport #43155) (#43157) fix: bom cost update is not working (#43155) (cherry picked from commit 05f9015c0bf341f25c340a69cfd95cebd7777115) Co-authored-by: rohitwaghchaure --- .../doctype/bom_update_log/bom_update_log.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py index 12d52ea51fd..34a3900015d 100644 --- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py +++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.model.document import Document from frappe.query_builder import DocType, Interval from frappe.query_builder.functions import Now -from frappe.utils import cint, cstr +from frappe.utils import cint, cstr, date_diff, today from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import ( get_leaf_boms, @@ -88,10 +88,12 @@ class BOMUpdateLog(Document): wip_log = frappe.get_all( "BOM Update Log", - {"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]}, + fields=["name", "modified"], + filters={"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]}, limit_page_length=1, ) - if wip_log: + + if wip_log and date_diff(today(), wip_log[0].modified) < 1: log_link = frappe.utils.get_link_to_form("BOM Update Log", wip_log[0].name) frappe.throw( _("BOM Updation already in progress. Please wait until {0} is complete.").format(log_link),