From f83098e4936ca4a37349e47c4a41de4b440c9dfd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 11 Mar 2024 14:02:05 +0530 Subject: [PATCH 01/53] refactor: checkbox to toggle always standalone credit note (cherry picked from commit 2cefe2a20eddf96929d4112db1a082645a5beeef) # Conflicts: # erpnext/accounts/doctype/sales_invoice/sales_invoice.json --- .../doctype/sales_invoice/sales_invoice.json | 26 ++++++++++++++++++- .../doctype/sales_invoice/sales_invoice.py | 5 +++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index f2094874e0e..7cb9a4abfa7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -25,6 +25,7 @@ "is_consolidated", "is_return", "return_against", + "update_outstanding_for_self", "update_billed_amount_in_sales_order", "update_billed_amount_in_delivery_note", "is_debit_note", @@ -2162,6 +2163,25 @@ "fieldname": "update_billed_amount_in_delivery_note", "fieldtype": "Check", "label": "Update Billed Amount in Delivery Note" +<<<<<<< HEAD +======= + }, + { + "default": "0", + "depends_on": "loyalty_program", + "fieldname": "dont_create_loyalty_points", + "fieldtype": "Check", + "label": "Don't Create Loyalty Points", + "no_copy": 1 + }, + { + "default": "1", + "depends_on": "eval: doc.is_return && doc.return_against", + "description": "Credit Note will update it's own outstanding amount, even if \"Return Against\" is specified.", + "fieldname": "update_outstanding_for_self", + "fieldtype": "Check", + "label": "Update Outstanding for Self" +>>>>>>> 2cefe2a20e (refactor: checkbox to toggle always standalone credit note) } ], "icon": "fa fa-file-text", @@ -2174,7 +2194,11 @@ "link_fieldname": "consolidated_invoice" } ], +<<<<<<< HEAD "modified": "2023-11-23 16:56:29.679499", +======= + "modified": "2024-03-11 14:20:34.874192", +>>>>>>> 2cefe2a20e (refactor: checkbox to toggle always standalone credit note) "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -2229,4 +2253,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1343b39352c..8bcfaeeb554 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -220,6 +220,7 @@ class SalesInvoice(SellingController): unrealized_profit_loss_account: DF.Link | None update_billed_amount_in_delivery_note: DF.Check update_billed_amount_in_sales_order: DF.Check + update_outstanding_for_self: DF.Check update_stock: DF.Check use_company_roundoff_cost_center: DF.Check write_off_account: DF.Link | None @@ -1241,7 +1242,9 @@ class SalesInvoice(SellingController): "debit_in_account_currency": base_grand_total if self.party_account_currency == self.company_currency else grand_total, - "against_voucher": self.name, + "against_voucher": self.name + if self.is_return and self.return_against and self.update_outstanding_for_self + else self.return_against, "against_voucher_type": self.doctype, "cost_center": self.cost_center, "project": self.project, From b5f22c39ec2720266a5482e2765f3df438f0f0d4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 11 Mar 2024 14:09:51 +0530 Subject: [PATCH 02/53] chore: update popup message (cherry picked from commit adf13a19c468ca39162f9acea47fe5e45a36bd97) # Conflicts: # erpnext/controllers/accounts_controller.py --- erpnext/controllers/accounts_controller.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 301d0e20cc3..033ef1df61d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -218,15 +218,19 @@ class AccountsController(TransactionBase): ) if self.get("is_return") and self.get("return_against") and not self.get("is_pos"): - # if self.get("is_return") and self.get("return_against"): document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" frappe.msgprint( _( - "{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}." + "{0} will be treated as a standalone {0}. If you want {1}'s outstanding to be updated, uncheck {2} checkbox.

Or you can use {3} tool to reconcile against {1} later." ).format( document_type, +<<<<<<< HEAD get_link_to_form("Payment Reconciliation", "Payment Reconciliation"), +======= +>>>>>>> adf13a19c4 (chore: update popup message) get_link_to_form(self.doctype, self.get("return_against")), + frappe.bold("Update Outstanding for Self"), + get_link_to_form("Payment Reconciliation"), ) ) From 824d6b4102cd4029102a0fb2ea5d49f868e55383 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 11 Mar 2024 14:41:36 +0530 Subject: [PATCH 03/53] refactor: checkbox in purchase invoice (cherry picked from commit 767f2157e6f2977b73399f1d77fecb2e87d3471d) --- .../doctype/purchase_invoice/purchase_invoice.json | 11 ++++++++++- .../doctype/purchase_invoice/purchase_invoice.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 313a314ee26..d6e606f73b5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -22,6 +22,7 @@ "is_paid", "is_return", "return_against", + "update_outstanding_for_self", "update_billed_amount_in_purchase_order", "update_billed_amount_in_purchase_receipt", "apply_tds", @@ -1622,13 +1623,21 @@ "fieldtype": "Link", "label": "Supplier Group", "options": "Supplier Group" + }, + { + "default": "1", + "depends_on": "eval: doc.is_return && doc.return_against", + "description": "Debit Note will update it's own outstanding amount, even if \"Return Against\" is specified.", + "fieldname": "update_outstanding_for_self", + "fieldtype": "Check", + "label": "Update Outstanding for Self" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2024-02-25 11:20:28.366808", + "modified": "2024-03-11 14:46:30.298184", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 18ecd91d728..5b8f7fc7b37 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -216,6 +216,7 @@ class PurchaseInvoice(BuyingController): unrealized_profit_loss_account: DF.Link | None update_billed_amount_in_purchase_order: DF.Check update_billed_amount_in_purchase_receipt: DF.Check + update_outstanding_for_self: DF.Check update_stock: DF.Check use_company_roundoff_cost_center: DF.Check use_transaction_date_exchange_rate: DF.Check From 1c68946fb9df79faf43edb065e4a558d5ec98453 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 11 Mar 2024 14:49:28 +0530 Subject: [PATCH 04/53] refactor: post ledger entries based on toggle (cherry picked from commit 77aac6f5716cf852b1f4781589e6c6c560bf4b43) --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 6 +++++- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5b8f7fc7b37..ee3ec82ea89 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -829,6 +829,10 @@ class PurchaseInvoice(BuyingController): ) if grand_total and not self.is_internal_transfer(): + against_voucher = self.name + if self.is_return and self.return_against and not self.update_outstanding_for_self: + against_voucher = self.return_against + # Did not use base_grand_total to book rounding loss gle gl_entries.append( self.get_gl_dict( @@ -842,7 +846,7 @@ class PurchaseInvoice(BuyingController): "credit_in_account_currency": base_grand_total if self.party_account_currency == self.company_currency else grand_total, - "against_voucher": self.name, + "against_voucher": against_voucher, "against_voucher_type": self.doctype, "project": self.project, "cost_center": self.cost_center, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 8bcfaeeb554..02a61d1cd9c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1229,6 +1229,10 @@ class SalesInvoice(SellingController): ) if grand_total and not self.is_internal_transfer(): + against_voucher = self.name + if self.is_return and self.return_against and not self.update_outstanding_for_self: + against_voucher = self.return_against + # Did not use base_grand_total to book rounding loss gle gl_entries.append( self.get_gl_dict( @@ -1242,9 +1246,7 @@ class SalesInvoice(SellingController): "debit_in_account_currency": base_grand_total if self.party_account_currency == self.company_currency else grand_total, - "against_voucher": self.name - if self.is_return and self.return_against and self.update_outstanding_for_self - else self.return_against, + "against_voucher": against_voucher, "against_voucher_type": self.doctype, "cost_center": self.cost_center, "project": self.project, From 4b44ae435aa9b4d27d44ea1df0bb889e602826f1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Mar 2024 16:05:50 +0530 Subject: [PATCH 05/53] chore: patch for updating flag in Cr/Dr notes (cherry picked from commit 849f47889498bb8c6cbf237dad711fbc0475ef93) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 4 ++ .../v14_0/update_flag_for_return_invoices.py | 62 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 erpnext/patches/v14_0/update_flag_for_return_invoices.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d5c0a4e80e4..67d4693fb5a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -353,7 +353,11 @@ erpnext.patches.v14_0.update_zero_asset_quantity_field execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") erpnext.patches.v14_0.update_total_asset_cost_field erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool +<<<<<<< HEAD erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes +======= +erpnext.patches.v14_0.update_flag_for_return_invoices +>>>>>>> 849f478894 (chore: patch for updating flag in Cr/Dr notes) # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 diff --git a/erpnext/patches/v14_0/update_flag_for_return_invoices.py b/erpnext/patches/v14_0/update_flag_for_return_invoices.py new file mode 100644 index 00000000000..feb43beacf8 --- /dev/null +++ b/erpnext/patches/v14_0/update_flag_for_return_invoices.py @@ -0,0 +1,62 @@ +from frappe import qb + + +def execute(): + # Set "update_outstanding_for_self" flag in Credit/Debit Notes + # Fetch Credit/Debit notes that does have 'return_against' but still post ledger entries against themselves. + + gle = qb.DocType("GL Entry") + + # Use hardcoded 'creation' date to isolate Credit/Debit notes created post v14 backport + # https://github.com/frappe/erpnext/pull/39497 + creation_date = "2024-01-25" + + si = qb.DocType("Sales Invoice") + if cr_notes := ( + qb.from_(si) + .select(si.name) + .where( + (si.creation.gte(creation_date)) + & (si.docstatus == 1) + & (si.is_return == True) + & (si.return_against.notnull()) + ) + .run() + ): + cr_notes = [x[0] for x in cr_notes] + if docs_that_require_update := ( + qb.from_(gle) + .select(gle.voucher_no) + .distinct() + .where((gle.voucher_no.isin(cr_notes)) & (gle.voucher_no == gle.against_voucher)) + .run() + ): + docs_that_require_update = [x[0] for x in docs_that_require_update] + qb.update(si).set(si.update_outstanding_for_self, True).where( + si.name.isin(docs_that_require_update) + ).run() + + pi = qb.DocType("Purchase Invoice") + if dr_notes := ( + qb.from_(pi) + .select(pi.name) + .where( + (pi.creation.gte(creation_date)) + & (pi.docstatus == 1) + & (pi.is_return == True) + & (pi.return_against.notnull()) + ) + .run() + ): + dr_notes = [x[0] for x in dr_notes] + if docs_that_require_update := ( + qb.from_(gle) + .select(gle.voucher_no) + .distinct() + .where((gle.voucher_no.isin(dr_notes)) & (gle.voucher_no == gle.against_voucher)) + .run() + ): + docs_that_require_update = [x[0] for x in docs_that_require_update] + qb.update(pi).set(pi.update_outstanding_for_self, True).where( + pi.name.isin(docs_that_require_update) + ).run() From a0f78584e938e9869f913bc867d5d12f114887eb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Mar 2024 09:55:23 +0530 Subject: [PATCH 06/53] refactor: make AR/AP report aware of always standalone cr/dr notes (cherry picked from commit 4d0c2d8e8226e4ba093d6d19f975743a1023aaa1) --- .../report/accounts_receivable/accounts_receivable.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 92d9755897c..6ed6ca22e63 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -690,7 +690,12 @@ class ReceivablePayableReport(object): def get_return_entries(self): doctype = "Sales Invoice" if self.account_type == "Receivable" else "Purchase Invoice" - filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company} + filters = { + "is_return": 1, + "docstatus": 1, + "company": self.filters.company, + "update_outstanding_for_self": 0, + } or_filters = {} for party_type in self.party_type: party_field = scrub(party_type) From 752b01d83b55f654fb524f71f00f729a607f44b3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Mar 2024 10:14:21 +0530 Subject: [PATCH 07/53] test: cr note flag to update self (cherry picked from commit ce3b1f09f51f28a8e3733020c70c0e381a2dc8c3) --- .../test_accounts_receivable.py | 69 ++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 6ff81be0ab7..a0f8af5d419 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -62,7 +62,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): pe.insert() pe.submit() - def create_credit_note(self, docname): + def create_credit_note(self, docname, do_not_submit=False): credit_note = create_sales_invoice( company=self.company, customer=self.customer, @@ -72,6 +72,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): cost_center=self.cost_center, is_return=1, return_against=docname, + do_not_submit=do_not_submit, ) return credit_note @@ -149,7 +150,9 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): ) # check invoice grand total, invoiced, paid and outstanding column's value after credit note - self.create_credit_note(si.name) + cr_note = self.create_credit_note(si.name, do_not_submit=True) + cr_note.update_outstanding_for_self = False + cr_note.save().submit() report = execute(filters) expected_data_after_credit_note = [100, 0, 0, 40, -40, self.debit_to] @@ -167,6 +170,68 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): ], ) + def test_cr_note_flag_to_update_self(self): + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "show_remarks": True, + } + + # check invoice grand total and invoiced column's value for 3 payment terms + si = self.create_sales_invoice(no_payment_schedule=True) + name = si.name + + report = execute(filters) + + expected_data = [100, 100, "No Remarks"] + + self.assertEqual(len(report[1]), 1) + row = report[1][0] + self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks]) + + # check invoice grand total, invoiced, paid and outstanding column's value after payment + self.create_payment_entry(si.name) + report = execute(filters) + + expected_data_after_payment = [100, 100, 40, 60] + self.assertEqual(len(report[1]), 1) + row = report[1][0] + self.assertEqual( + expected_data_after_payment, + [row.invoice_grand_total, row.invoiced, row.paid, row.outstanding], + ) + + # check invoice grand total, invoiced, paid and outstanding column's value after credit note + cr_note = self.create_credit_note(si.name, do_not_submit=True) + cr_note.posting_date = add_days(today(), 1) + cr_note.update_outstanding_for_self = True + cr_note.save().submit() + report = execute(filters) + + expected_data_after_credit_note = [ + [100.0, 100.0, 40.0, 0.0, 60.0, self.debit_to], + [0, 0, 100.0, 0.0, -100.0, self.debit_to], + ] + self.assertEqual(len(report[1]), 2) + for i in range(2): + row = report[1][i - 1] + # row = report[1][0] + self.assertEqual( + expected_data_after_credit_note[i - 1], + [ + row.invoice_grand_total, + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + row.party_account, + ], + ) + def test_payment_againt_po_in_receivable_report(self): """ Payments made against Purchase Order will show up as outstanding amount From d87b98d9456ad51400624c512df3146f37749098 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Mar 2024 11:03:56 +0530 Subject: [PATCH 08/53] chore: better popup message (cherry picked from commit 445d2acf5071f2376bee992b5eb6aff71910f562) # Conflicts: # erpnext/controllers/accounts_controller.py --- erpnext/controllers/accounts_controller.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 033ef1df61d..ea529005051 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -218,6 +218,7 @@ class AccountsController(TransactionBase): ) if self.get("is_return") and self.get("return_against") and not self.get("is_pos"): +<<<<<<< HEAD document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" frappe.msgprint( _( @@ -231,8 +232,20 @@ class AccountsController(TransactionBase): get_link_to_form(self.doctype, self.get("return_against")), frappe.bold("Update Outstanding for Self"), get_link_to_form("Payment Reconciliation"), +======= + if self.get("update_outstanding_for_self"): + document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" + frappe.msgprint( + _( + "We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck '{2}' checkbox.

Or you can use {3} tool to reconcile against {1} later." + ).format( + frappe.bold(document_type), + get_link_to_form(self.doctype, self.get("return_against")), + frappe.bold("Update Outstanding for Self"), + get_link_to_form("Payment Reconciliation"), + ) +>>>>>>> 445d2acf50 (chore: better popup message) ) - ) pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): From 1093e0d46f4e591c11ef4794f8973b2118246a80 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Mar 2024 12:16:44 +0530 Subject: [PATCH 09/53] chore: resolve conflicts --- .../doctype/sales_invoice/sales_invoice.json | 15 --------------- erpnext/controllers/accounts_controller.py | 16 ---------------- erpnext/patches.txt | 3 --- 3 files changed, 34 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 7cb9a4abfa7..8fd897bddf1 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2163,16 +2163,6 @@ "fieldname": "update_billed_amount_in_delivery_note", "fieldtype": "Check", "label": "Update Billed Amount in Delivery Note" -<<<<<<< HEAD -======= - }, - { - "default": "0", - "depends_on": "loyalty_program", - "fieldname": "dont_create_loyalty_points", - "fieldtype": "Check", - "label": "Don't Create Loyalty Points", - "no_copy": 1 }, { "default": "1", @@ -2181,7 +2171,6 @@ "fieldname": "update_outstanding_for_self", "fieldtype": "Check", "label": "Update Outstanding for Self" ->>>>>>> 2cefe2a20e (refactor: checkbox to toggle always standalone credit note) } ], "icon": "fa fa-file-text", @@ -2194,11 +2183,7 @@ "link_fieldname": "consolidated_invoice" } ], -<<<<<<< HEAD - "modified": "2023-11-23 16:56:29.679499", -======= "modified": "2024-03-11 14:20:34.874192", ->>>>>>> 2cefe2a20e (refactor: checkbox to toggle always standalone credit note) "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ea529005051..f0603a0ff69 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -218,21 +218,6 @@ class AccountsController(TransactionBase): ) if self.get("is_return") and self.get("return_against") and not self.get("is_pos"): -<<<<<<< HEAD - document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" - frappe.msgprint( - _( - "{0} will be treated as a standalone {0}. If you want {1}'s outstanding to be updated, uncheck {2} checkbox.

Or you can use {3} tool to reconcile against {1} later." - ).format( - document_type, -<<<<<<< HEAD - get_link_to_form("Payment Reconciliation", "Payment Reconciliation"), -======= ->>>>>>> adf13a19c4 (chore: update popup message) - get_link_to_form(self.doctype, self.get("return_against")), - frappe.bold("Update Outstanding for Self"), - get_link_to_form("Payment Reconciliation"), -======= if self.get("update_outstanding_for_self"): document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" frappe.msgprint( @@ -244,7 +229,6 @@ class AccountsController(TransactionBase): frappe.bold("Update Outstanding for Self"), get_link_to_form("Payment Reconciliation"), ) ->>>>>>> 445d2acf50 (chore: better popup message) ) pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 67d4693fb5a..c024c86c5e5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -353,11 +353,8 @@ erpnext.patches.v14_0.update_zero_asset_quantity_field execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") erpnext.patches.v14_0.update_total_asset_cost_field erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool -<<<<<<< HEAD erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes -======= erpnext.patches.v14_0.update_flag_for_return_invoices ->>>>>>> 849f478894 (chore: patch for updating flag in Cr/Dr notes) # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 From 70375dfbded212fa8faf9fc70d4ac07b7272d697 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Mar 2024 13:15:07 +0530 Subject: [PATCH 10/53] refactor: pass 'name' to get_link_to_form --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f0603a0ff69..fe3c5d7a836 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -227,7 +227,7 @@ class AccountsController(TransactionBase): frappe.bold(document_type), get_link_to_form(self.doctype, self.get("return_against")), frappe.bold("Update Outstanding for Self"), - get_link_to_form("Payment Reconciliation"), + get_link_to_form("Payment Reconciliation", "Payment Reconciliation"), ) ) From ce2626e911379efdb17c00daddd1da77e7ea5a33 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 21:56:20 +0530 Subject: [PATCH 11/53] fix: incorrect gross profit on the quotation (backport #40438) (#40442) fix: incorrect gross profit on the quotation (#40438) (cherry picked from commit f4a945aee405a3151bd47bf3a91886c3e7448516) Co-authored-by: rohitwaghchaure --- erpnext/controllers/accounts_controller.py | 1 + .../doctype/quotation/test_quotation.py | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index fe3c5d7a836..cb8f5250e0a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -89,6 +89,7 @@ force_item_fields = ( "weight_per_unit", "weight_uom", "total_weight", + "valuation_rate", ) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index d06acb81f1d..4ec01814b45 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -30,6 +30,39 @@ class TestQuotation(FrappeTestCase): self.assertTrue(sales_order.get("payment_schedule")) + def test_gross_profit(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + from erpnext.stock.get_item_details import insert_item_price + + item_doc = make_item("_Test Item for Gross Profit", {"is_stock_item": 1}) + item_code = item_doc.name + make_stock_entry(item_code=item_code, qty=10, rate=100, target="_Test Warehouse - _TC") + + selling_price_list = frappe.get_all("Price List", filters={"selling": 1}, limit=1)[0].name + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) + insert_item_price( + frappe._dict( + { + "item_code": item_code, + "price_list": selling_price_list, + "price_list_rate": 300, + "rate": 300, + "conversion_factor": 1, + "discount_amount": 0.0, + "currency": frappe.db.get_value("Price List", selling_price_list, "currency"), + "uom": item_doc.stock_uom, + } + ) + ) + + quotation = make_quotation( + item_code=item_code, qty=1, rate=300, selling_price_list=selling_price_list + ) + self.assertEqual(quotation.items[0].valuation_rate, 100) + self.assertEqual(quotation.items[0].gross_profit, 200) + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0) + def test_maintain_rate_in_sales_cycle_is_enforced(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order From 0bb4a7d13a1418f241fc70a5a788dc614eda4e07 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Mar 2024 21:24:24 +0530 Subject: [PATCH 12/53] fix: flaky Accounts Receivable test case (cherry picked from commit 40bce240bde508801d07b9c38ededea5b4f297c8) --- .../report/accounts_receivable/test_accounts_receivable.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index a0f8af5d419..0a743423d52 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -207,7 +207,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): # check invoice grand total, invoiced, paid and outstanding column's value after credit note cr_note = self.create_credit_note(si.name, do_not_submit=True) - cr_note.posting_date = add_days(today(), 1) cr_note.update_outstanding_for_self = True cr_note.save().submit() report = execute(filters) @@ -218,10 +217,9 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): ] self.assertEqual(len(report[1]), 2) for i in range(2): - row = report[1][i - 1] - # row = report[1][0] + row = report[1][i] self.assertEqual( - expected_data_after_credit_note[i - 1], + expected_data_after_credit_note[i], [ row.invoice_grand_total, row.invoiced, From df1351783edf4db4e508b99e1f3992fc964376cb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 14 Mar 2024 10:00:58 +0530 Subject: [PATCH 13/53] fix(test): manually filter rows and assert --- .../test_accounts_receivable.py | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 0a743423d52..de49139adc1 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -182,8 +182,10 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): } # check invoice grand total and invoiced column's value for 3 payment terms - si = self.create_sales_invoice(no_payment_schedule=True) - name = si.name + si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si.set_posting_time = True + si.posting_date = add_days(today(), -1) + si.save().submit() report = execute(filters) @@ -212,23 +214,37 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): report = execute(filters) expected_data_after_credit_note = [ - [100.0, 100.0, 40.0, 0.0, 60.0, self.debit_to], - [0, 0, 100.0, 0.0, -100.0, self.debit_to], + [100.0, 100.0, 40.0, 0.0, 60.0, si.name], + [0, 0, 100.0, 0.0, -100.0, cr_note.name], ] self.assertEqual(len(report[1]), 2) - for i in range(2): - row = report[1][i] - self.assertEqual( - expected_data_after_credit_note[i], - [ - row.invoice_grand_total, - row.invoiced, - row.paid, - row.credit_note, - row.outstanding, - row.party_account, - ], - ) + si_row = [ + [ + row.invoice_grand_total, + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + row.voucher_no, + ] + for row in report[1] + if row.voucher_no == si.name + ][0] + + cr_note_row = [ + [ + row.invoice_grand_total, + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + row.voucher_no, + ] + for row in report[1] + if row.voucher_no == cr_note.name + ][0] + self.assertEqual(expected_data_after_credit_note[0], si_row) + self.assertEqual(expected_data_after_credit_note[1], cr_note_row) def test_payment_againt_po_in_receivable_report(self): """ From ba6b8561c9695a4ac00d6bac7a94ca3f3ab68d0d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:49:10 +0530 Subject: [PATCH 14/53] fix: Data too long for column 'stock_queue' (backport #40436) (#40451) fix: Data too long for column 'stock_queue' (#40436) (cherry picked from commit cd79dcccb6ce34f2d4be108f41196c12bdbaae84) Co-authored-by: rohitwaghchaure --- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.json | 4 ++-- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 3a094f1e8f5..e8e82af25ac 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -230,7 +230,7 @@ }, { "fieldname": "stock_queue", - "fieldtype": "Text", + "fieldtype": "Long Text", "label": "FIFO Stock Queue (qty, rate)", "oldfieldname": "fcfs_stack", "oldfieldtype": "Text", @@ -360,7 +360,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-02-07 09:18:13.999231", + "modified": "2024-03-13 09:56:13.021696", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 186567a996a..e608e930276 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -58,7 +58,7 @@ class StockLedgerEntry(Document): recalculate_rate: DF.Check serial_and_batch_bundle: DF.Link | None serial_no: DF.LongText | None - stock_queue: DF.Text | None + stock_queue: DF.LongText | None stock_uom: DF.Link | None stock_value: DF.Currency stock_value_difference: DF.Currency From 75b610d7fc18efe29ea271cc682ede1d7666de53 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 13 Mar 2024 13:59:50 +0530 Subject: [PATCH 15/53] fix: Taxes not getting updated on change (cherry picked from commit 65ae9616ba5555f98c415ff2b4d7cfe17b5042e5) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 ++ .../purchase_taxes_and_charges_template.js | 1 + erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 2 ++ .../sales_taxes_and_charges_template.js | 1 + erpnext/buying/doctype/purchase_order/purchase_order.js | 2 ++ erpnext/selling/doctype/quotation/quotation.js | 2 ++ erpnext/selling/doctype/sales_order/sales_order.js | 2 ++ erpnext/stock/doctype/delivery_note/delivery_note.js | 2 ++ erpnext/stock/doctype/purchase_receipt/purchase_receipt.js | 2 ++ 9 files changed, 16 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index d6455b2002a..957611f7858 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -3,6 +3,8 @@ frappe.provide("erpnext.accounts"); +cur_frm.cscript.tax_table = "Purchase Taxes and Charges"; + erpnext.accounts.payment_triggers.setup("Purchase Invoice"); erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Purchase Invoice"); diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js index 66a9cbe8440..4c94503c184 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js @@ -1,6 +1,7 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +cur_frm.cscript.tax_table = "Purchase Taxes and Charges"; erpnext.accounts.taxes.setup_tax_validations("Purchase Taxes and Charges Template"); erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 17101cd2720..c7505ce007d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -3,6 +3,8 @@ frappe.provide("erpnext.accounts"); +cur_frm.cscript.tax_table = "Sales Taxes and Charges"; + erpnext.accounts.taxes.setup_tax_validations("Sales Invoice"); erpnext.accounts.payment_triggers.setup("Sales Invoice"); erpnext.accounts.pos.setup("Sales Invoice"); diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js index 91d4d047f8f..c42623addb5 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js @@ -1,5 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +cur_frm.cscript.tax_table = "Sales Taxes and Charges"; erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template"); erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 7875646ab72..2bb602c27ad 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -4,6 +4,8 @@ frappe.provide("erpnext.buying"); frappe.provide("erpnext.accounts.dimensions"); +cur_frm.cscript.tax_table = "Purchase Taxes and Charges"; + erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Purchase Order"); erpnext.buying.setup_buying_controller(); diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 6e2b7262641..95cbfd0f32b 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -1,6 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +cur_frm.cscript.tax_table = "Sales Taxes and Charges"; + erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template"); erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); erpnext.pre_sales.set_as_lost("Quotation"); diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 2e3070c3927..10ba6a73fd7 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -1,6 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +cur_frm.cscript.tax_table = "Sales Taxes and Charges"; + erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Sales Order"); erpnext.sales_common.setup_selling_controller(); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index c04d5c188a0..23d0adc5708 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -3,6 +3,8 @@ cur_frm.add_fetch("customer", "tax_id", "tax_id"); +cur_frm.cscript.tax_table = "Sales Taxes and Charges"; + frappe.provide("erpnext.stock"); frappe.provide("erpnext.stock.delivery_note"); frappe.provide("erpnext.accounts.dimensions"); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 997cdd0e56f..bfac4381a06 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -3,6 +3,8 @@ frappe.provide("erpnext.stock"); +cur_frm.cscript.tax_table = "Purchase Taxes and Charges"; + erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Purchase Receipt"); erpnext.buying.setup_buying_controller(); From 54b5064f76e70600a00a15d2b1a9d9a5f9f0106f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:43:05 +0530 Subject: [PATCH 16/53] chore: fix test case (backport #40457) (#40458) chore: fix test case (cherry picked from commit ec83d54826d7218aadc18d8ef7ddeb6f8bd99fc3) Co-authored-by: Rohit Waghchaure --- .../doctype/closing_stock_balance/closing_stock_balance.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py index 1c7018366af..e99a0b1add2 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py @@ -123,7 +123,9 @@ class ClosingStockBalance(Document): ) ) - create_json_gz_file({"columns": columns, "data": data}, self.doctype, self.name) + create_json_gz_file( + {"columns": columns, "data": data}, self.doctype, self.name, "closing-stock-balance" + ) def get_prepared_data(self): if attachments := get_attachments(self.doctype, self.name): From bfa78aaf7455aadf08de7718cddbe1a48f2ed2f9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Mar 2024 21:29:44 +0530 Subject: [PATCH 17/53] fix: advance journal fetching logic in Advances section (cherry picked from commit e786a93c2ece97d9b680127003d058b9a9e21d26) --- erpnext/controllers/accounts_controller.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cb8f5250e0a..246c0c02d61 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2669,14 +2669,20 @@ def get_advance_journal_entries( else: q = q.where(journal_acc.debit_in_account_currency > 0) + reference_or_condition = [] + if include_unallocated: - q = q.where((journal_acc.reference_name.isnull()) | (journal_acc.reference_name == "")) + reference_or_condition.append(journal_acc.reference_name.isnull()) + reference_or_condition.append(journal_acc.reference_name == "") if order_list: - q = q.where( + reference_or_condition.append( (journal_acc.reference_type == order_doctype) & ((journal_acc.reference_name).isin(order_list)) ) + if reference_or_condition: + q = q.where(Criterion.any(reference_or_condition)) + q = q.orderby(journal_entry.posting_date) journal_entries = q.run(as_dict=True) From 144aec8d6ac81b396ca4fdbd1d596aca429106f1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 14 Mar 2024 16:48:55 +0530 Subject: [PATCH 18/53] refactor: disallow '0' qty return invoices with stock effect (cherry picked from commit 898affbee902b5f1d76f3cc7c4876b8d2eeb1a46) --- erpnext/controllers/accounts_controller.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 246c0c02d61..708da8993df 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -169,6 +169,13 @@ class AccountsController(TransactionBase): if not self.get("is_return") and not self.get("is_debit_note"): self.validate_qty_is_not_zero() + if ( + self.doctype in ["Sales Invoice", "Purchase Invoice"] + and self.get("is_return") + and self.get("update_stock") + ): + self.validate_zero_qty_for_return_invoices_with_stock() + if self.get("_action") and self._action != "update_after_submit": self.set_missing_values(for_validate=True) @@ -1045,6 +1052,18 @@ class AccountsController(TransactionBase): else: return flt(args.get(field, 0) / self.get("conversion_rate", 1)) + def validate_zero_qty_for_return_invoices_with_stock(self): + rows = [] + for item in self.items: + if not flt(item.qty): + rows.append(item) + if rows: + frappe.throw( + _( + "For Return Invoices with Stock effect, '0' qty Items are not allowed. Following rows are affected: {0}" + ).format(frappe.bold(comma_and(["#" + str(x.idx) for x in rows]))) + ) + def validate_qty_is_not_zero(self): if self.doctype == "Purchase Receipt": return From 5957ef9b9de6b04aea54e01d12d1a332a954e7aa Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 10:47:32 +0530 Subject: [PATCH 19/53] test: validation to prevent '0' qty return with stock effect (cherry picked from commit 647bba0f00d24a4b88c9e258cc86116783db81fe) --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6a01ccf3409..1dda90a25d0 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1571,6 +1571,12 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000) self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500) + def test_zero_qty_return_invoice_with_stock_effect(self): + cr_note = create_sales_invoice(qty=-1, rate=300, is_return=1, do_not_submit=True) + cr_note.update_stock = True + cr_note.items[0].qty = 0 + self.assertRaises(frappe.ValidationError, cr_note.save) + def test_return_invoice_with_account_mismatch(self): debtors2 = create_account( parent_account="Accounts Receivable - _TC", From 9cdde50f7ccf17f499da3e5e809916268593f55f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 17:42:25 +0530 Subject: [PATCH 20/53] fix: auto-update due date for invoices via data import (cherry picked from commit c09613389729f26026dd7918bb2da12087edda43) --- erpnext/controllers/accounts_controller.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 246c0c02d61..fcff9ffb428 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -603,23 +603,32 @@ class AccountsController(TransactionBase): ) def validate_due_date(self): - if self.get("is_pos"): + if self.get("is_pos") or self.doctype not in ["Sales Invoice", "Purchase Invoice"]: return from erpnext.accounts.party import validate_due_date - if self.doctype == "Sales Invoice": + posting_date = ( + self.posting_date if self.doctype == "Sales Invoice" else (self.bill_date or self.posting_date) + ) + via_data_import = ( + self.flags.updater_reference and self.flags.updater_reference.get("doctype") == "Data Import" + ) + if via_data_import and getdate(self.due_date) < getdate(posting_date): + self.due_date = posting_date + + elif self.doctype == "Sales Invoice": if not self.due_date: frappe.throw(_("Due Date is mandatory")) validate_due_date( - self.posting_date, + posting_date, self.due_date, self.payment_terms_template, ) elif self.doctype == "Purchase Invoice": validate_due_date( - self.bill_date or self.posting_date, + posting_date, self.due_date, self.bill_date, self.payment_terms_template, From 8242a9cd465e706a7be1a37a31298b3f0109df12 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 15 Mar 2024 12:00:38 +0530 Subject: [PATCH 21/53] fix: use in_import flag for checking imported records (cherry picked from commit f504f2caf37116019123df8369f868e99cbdf509) --- erpnext/controllers/accounts_controller.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index fcff9ffb428..ca75bc5233a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -611,10 +611,9 @@ class AccountsController(TransactionBase): posting_date = ( self.posting_date if self.doctype == "Sales Invoice" else (self.bill_date or self.posting_date) ) - via_data_import = ( - self.flags.updater_reference and self.flags.updater_reference.get("doctype") == "Data Import" - ) - if via_data_import and getdate(self.due_date) < getdate(posting_date): + + # skip due date validation for records via Data Import + if frappe.flags.in_import and getdate(self.due_date) < getdate(posting_date): self.due_date = posting_date elif self.doctype == "Sales Invoice": From 17fb287cd978416dcaf498b80b0d733dd4e66cab Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 2 Mar 2024 19:29:58 +0530 Subject: [PATCH 22/53] refactor: support payment against reverse payment reconciliation (cherry picked from commit 5f15297f28d02289746946f46366044bc2b18c8e) --- .../accounts/doctype/payment_entry/payment_entry.py | 11 +++++++---- .../payment_reconciliation/payment_reconciliation.py | 7 ++++++- erpnext/accounts/utils.py | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c031be53d65..87fb8c16263 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -526,9 +526,9 @@ class PaymentEntry(AccountsController): def get_valid_reference_doctypes(self): if self.party_type == "Customer": - return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning") + return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry") elif self.party_type == "Supplier": - return ("Purchase Order", "Purchase Invoice", "Journal Entry") + return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry") elif self.party_type == "Shareholder": return ("Journal Entry",) elif self.party_type == "Employee": @@ -1191,6 +1191,7 @@ class PaymentEntry(AccountsController): "Journal Entry", "Sales Order", "Purchase Order", + "Payment Entry", ): self.add_advance_gl_for_reference(gl_entries, ref) @@ -1213,7 +1214,9 @@ class PaymentEntry(AccountsController): if getdate(posting_date) < getdate(self.posting_date): posting_date = self.posting_date - dr_or_cr = "credit" if invoice.reference_doctype in ["Sales Invoice", "Sales Order"] else "debit" + dr_or_cr = ( + "credit" if invoice.reference_doctype in ["Sales Invoice", "Payment Entry"] else "debit" + ) args_dict["account"] = invoice.account args_dict[dr_or_cr] = invoice.allocated_amount args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount @@ -1660,7 +1663,7 @@ def get_outstanding_reference_documents(args, validate=False): outstanding_invoices = get_outstanding_invoices( args.get("party_type"), args.get("party"), - party_account, + [party_account], common_filter=common_filter, posting_date=posting_and_due_date, min_outstanding=args.get("outstanding_amt_greater_than"), diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 1bf1acee70d..2c4952a0c66 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -340,10 +340,15 @@ class PaymentReconciliation(Document): self.build_qb_filter_conditions(get_invoices=True) + accounts = [self.receivable_payable_account] + + if self.default_advance_account: + accounts.append(self.default_advance_account) + non_reconciled_invoices = get_outstanding_invoices( self.party_type, self.party, - self.receivable_payable_account, + accounts, common_filter=self.common_filter_conditions, posting_date=self.ple_posting_date_filter, min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None, diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 558eeaa6d35..0e6c041d24d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1015,7 +1015,7 @@ def get_outstanding_invoices( if account: root_type, account_type = frappe.get_cached_value( - "Account", account, ["root_type", "account_type"] + "Account", account[0], ["root_type", "account_type"] ) party_account_type = "Receivable" if root_type == "Asset" else "Payable" party_account_type = account_type or party_account_type @@ -1026,7 +1026,7 @@ def get_outstanding_invoices( common_filter = common_filter or [] common_filter.append(ple.account_type == party_account_type) - common_filter.append(ple.account == account) + common_filter.append(ple.account.isin(account)) common_filter.append(ple.party_type == party_type) common_filter.append(ple.party == party) From 1762f9919a6168f4f6f9d97189ae2ad34986a38e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 10 Mar 2024 19:51:41 +0530 Subject: [PATCH 23/53] test: reverse payment reconciliation (cherry picked from commit 6d9074d5858bd893d24a973dc3f86efbd328f33a) # Conflicts: # erpnext/accounts/doctype/payment_entry/test_payment_entry.py --- .../payment_entry/test_payment_entry.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 04066666c9e..754aa09845c 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1475,6 +1475,7 @@ class TestPaymentEntry(FrappeTestCase): for field in ["account", "debit", "credit"]: self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) +<<<<<<< HEAD def test_outstanding_invoices_api(self): """ Test if `get_outstanding_reference_documents` fetches invoices in the right order. @@ -1513,6 +1514,45 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(references[2].voucher_no, si2.name) self.assertEqual(references[1].payment_term, "Basic Amount Receivable") self.assertEqual(references[2].payment_term, "Tax Receivable") +======= + def test_reverse_payment_reconciliation(self): + pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + pe.submit() + + reverse_pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Pay", + paid_from="_Test Cash - _TC", + paid_to="Debtors - _TC", + ) + reverse_pe.submit() + + pr = frappe.get_doc("Payment Reconciliation") + pr.company = "_Test Company" + pr.party_type = "Customer" + pr.party = "_Test Customer" + pr.receivable_payable_account = "Debtors - _TC" + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + + self.assertEqual(reverse_pe.name, pr.invoices[0].invoice_number) + self.assertEqual(pe.name, pr.payments[0].reference_name) + + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[0].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) +>>>>>>> 6d9074d585 (test: reverse payment reconciliation) def create_payment_entry(**args): From 7ea1edc4ffb4b0b382509a2f9a6b1124062f2cae Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 11 Mar 2024 12:32:31 +0530 Subject: [PATCH 24/53] test: advance payment reconciliation against payment 'advance' payments booked in separate party account (cherry picked from commit 2a08072443caaccc2798464f0a375db29543e9c1) --- .../payment_entry/test_payment_entry.py | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 754aa09845c..3979a6d52cf 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1554,6 +1554,128 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(len(pr.payments), 0) >>>>>>> 6d9074d585 (test: reverse payment reconciliation) + def test_advance_reverse_payment_reconciliation(self): + from erpnext.accounts.doctype.account.test_account import create_account + + company = "_Test Company" + advance_account = create_account( + parent_account="Current Assets - _TC", + account_name="Advances Received", + company=company, + account_type="Receivable", + ) + + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_received_account": advance_account, + }, + ) + # Reverse Payment(essentially an Invoice) + reverse_pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Pay", + paid_from="_Test Cash - _TC", + paid_to=advance_account, + ) + reverse_pe.save() # use save() to trigger set_liability_account() + reverse_pe.submit() + + # Advance Payment + pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from=advance_account, + paid_to="_Test Cash - _TC", + ) + pe.save() # use save() to trigger set_liability_account() + pe.submit() + + # Partially reconcile advance against invoice + pr = frappe.get_doc("Payment Reconciliation") + pr.company = company + pr.party_type = "Customer" + pr.party = "_Test Customer" + pr.receivable_payable_account = "Debtors - _TC" + pr.default_advance_account = advance_account + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + + invoices = [x.as_dict() for x in pr.get("invoices")] + payments = [x.as_dict() for x in pr.get("payments")] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].allocated_amount = 400 + pr.reconcile() + + # assert General and Payment Ledger entries post partial reconciliation + self.expected_gle = [ + {"account": "Debtors - _TC", "debit": 0.0, "credit": 400.0}, + {"account": advance_account, "debit": 400.0, "credit": 0.0}, + {"account": advance_account, "debit": 0.0, "credit": 1000.0}, + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, + ] + self.expected_ple = [ + { + "account": advance_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -1000.0, + }, + { + "account": "Debtors - _TC", + "voucher_no": pe.name, + "against_voucher_no": reverse_pe.name, + "amount": -400.0, + }, + { + "account": advance_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": 400.0, + }, + ] + self.voucher_no = pe.name + self.check_gl_entries() + self.check_pl_entries() + + # Unreconcile + unrecon = ( + frappe.get_doc( + { + "doctype": "Unreconcile Payment", + "company": company, + "voucher_type": pe.doctype, + "voucher_no": pe.name, + "allocations": [{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}], + } + ) + .save() + .submit() + ) + + # assert General and Payment Ledger entries post unreconciliation + self.expected_gle = [ + {"account": advance_account, "debit": 0.0, "credit": 1000.0}, + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, + ] + self.expected_ple = [ + { + "account": advance_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -1000.0, + }, + ] + self.voucher_no = pe.name + self.check_gl_entries() + self.check_pl_entries() + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From d82b1fc6009f0d69b1e04d4ba9eda1ea0aae777d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 12:08:32 +0530 Subject: [PATCH 25/53] refactor(test): generate and use unique party (cherry picked from commit a3d4aff29c61f41629136d93a13b74687ed13d36) --- .../doctype/payment_entry/test_payment_entry.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 3979a6d52cf..c0e425a912a 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1516,9 +1516,10 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(references[2].payment_term, "Tax Receivable") ======= def test_reverse_payment_reconciliation(self): + customer = create_customer(frappe.generate_hash(length=10), "INR") pe = create_payment_entry( party_type="Customer", - party="_Test Customer", + party=customer, payment_type="Receive", paid_from="Debtors - _TC", paid_to="_Test Cash - _TC", @@ -1527,7 +1528,7 @@ class TestPaymentEntry(FrappeTestCase): reverse_pe = create_payment_entry( party_type="Customer", - party="_Test Customer", + party=customer, payment_type="Pay", paid_from="_Test Cash - _TC", paid_to="Debtors - _TC", @@ -1537,7 +1538,7 @@ class TestPaymentEntry(FrappeTestCase): pr = frappe.get_doc("Payment Reconciliation") pr.company = "_Test Company" pr.party_type = "Customer" - pr.party = "_Test Customer" + pr.party = customer pr.receivable_payable_account = "Debtors - _TC" pr.get_unreconciled_entries() self.assertEqual(len(pr.invoices), 1) @@ -1558,6 +1559,7 @@ class TestPaymentEntry(FrappeTestCase): from erpnext.accounts.doctype.account.test_account import create_account company = "_Test Company" + customer = create_customer(frappe.generate_hash(length=10), "INR") advance_account = create_account( parent_account="Current Assets - _TC", account_name="Advances Received", @@ -1576,7 +1578,7 @@ class TestPaymentEntry(FrappeTestCase): # Reverse Payment(essentially an Invoice) reverse_pe = create_payment_entry( party_type="Customer", - party="_Test Customer", + party=customer, payment_type="Pay", paid_from="_Test Cash - _TC", paid_to=advance_account, @@ -1587,7 +1589,7 @@ class TestPaymentEntry(FrappeTestCase): # Advance Payment pe = create_payment_entry( party_type="Customer", - party="_Test Customer", + party=customer, payment_type="Receive", paid_from=advance_account, paid_to="_Test Cash - _TC", @@ -1599,7 +1601,7 @@ class TestPaymentEntry(FrappeTestCase): pr = frappe.get_doc("Payment Reconciliation") pr.company = company pr.party_type = "Customer" - pr.party = "_Test Customer" + pr.party = customer pr.receivable_payable_account = "Debtors - _TC" pr.default_advance_account = advance_account pr.get_unreconciled_entries() From fba13c6dbc530dfd53f9320744d04febd864bbe6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 16:44:54 +0530 Subject: [PATCH 26/53] refactor: enable no-copy for update_outstanding_for_self (cherry picked from commit a1e8caa5c1f656e649f5f722b921cc000a4215ec) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 8fd897bddf1..4f6e5ba9206 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2170,7 +2170,8 @@ "description": "Credit Note will update it's own outstanding amount, even if \"Return Against\" is specified.", "fieldname": "update_outstanding_for_self", "fieldtype": "Check", - "label": "Update Outstanding for Self" + "label": "Update Outstanding for Self", + "no_copy": 1 } ], "icon": "fa fa-file-text", @@ -2183,7 +2184,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-03-11 14:20:34.874192", + "modified": "2024-03-15 16:44:17.778370", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From 61d59b392a7c97743a4b07959ea3a0eeb22958b8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 18:19:56 +0530 Subject: [PATCH 27/53] fix: serial and batch for internal transfer (backport #40467) (#40477) fix: serial and batch for internal transfer (#40467) * fix: serial and batch for internal transfer * chore: fix test cases (cherry picked from commit 59222813af2cf1407da5708814795e302ce6be34) Co-authored-by: rohitwaghchaure --- .../sales_invoice/test_sales_invoice.py | 1 - erpnext/controllers/buying_controller.py | 37 ++- .../controllers/sales_and_purchase_return.py | 22 +- erpnext/controllers/selling_controller.py | 33 ++- erpnext/controllers/stock_controller.py | 11 +- .../js/utils/serial_no_batch_selector.js | 4 + .../doctype/delivery_note/delivery_note.py | 1 + .../purchase_receipt/test_purchase_receipt.py | 274 ++++++++++++++++++ .../serial_and_batch_bundle.json | 3 +- .../serial_and_batch_bundle.py | 1 + .../stock/doctype/stock_entry/stock_entry.py | 1 + .../doctype/stock_entry/test_stock_entry.py | 1 + erpnext/stock/serial_batch_bundle.py | 4 + 13 files changed, 374 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6a01ccf3409..2876ebd4eb3 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3932,7 +3932,6 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter ) supplier.append("companies", {"company": allowed_to_interact_with}) - supplier.insert() supplier_name = supplier.name else: diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 821185766eb..c5307270156 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -513,6 +513,14 @@ class BuyingController(SubcontractingController): (not cint(self.is_return) and self.docstatus == 1) or (cint(self.is_return) and self.docstatus == 2) ): + serial_and_batch_bundle = d.get("serial_and_batch_bundle") + if self.is_internal_transfer() and self.is_return and self.docstatus == 2: + serial_and_batch_bundle = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_detail_no": d.name, "warehouse": d.from_warehouse}, + "serial_and_batch_bundle", + ) + from_warehouse_sle = self.get_sl_entries( d, { @@ -521,19 +529,24 @@ class BuyingController(SubcontractingController): "outgoing_rate": d.rate, "recalculate_rate": 1, "dependant_sle_voucher_detail_no": d.name, + "serial_and_batch_bundle": serial_and_batch_bundle, }, ) sl_entries.append(from_warehouse_sle) + type_of_transaction = "Inward" + if self.docstatus == 2: + type_of_transaction = "Outward" + sle = self.get_sl_entries( d, { "actual_qty": flt(pr_qty), "serial_and_batch_bundle": ( d.serial_and_batch_bundle - if not self.is_internal_transfer() - else self.get_package_for_target_warehouse(d) + if not self.is_internal_transfer() or self.is_return + else self.get_package_for_target_warehouse(d, type_of_transaction=type_of_transaction) ), }, ) @@ -570,7 +583,17 @@ class BuyingController(SubcontractingController): or (cint(self.is_return) and self.docstatus == 1) ): from_warehouse_sle = self.get_sl_entries( - d, {"actual_qty": -1 * pr_qty, "warehouse": d.from_warehouse, "recalculate_rate": 1} + d, + { + "actual_qty": -1 * pr_qty, + "warehouse": d.from_warehouse, + "recalculate_rate": 1, + "serial_and_batch_bundle": ( + self.get_package_for_target_warehouse(d, d.from_warehouse, "Inward") + if self.is_internal_transfer() and self.is_return + else None + ), + }, ) sl_entries.append(from_warehouse_sle) @@ -597,13 +620,15 @@ class BuyingController(SubcontractingController): via_landed_cost_voucher=via_landed_cost_voucher, ) - def get_package_for_target_warehouse(self, item) -> str: + def get_package_for_target_warehouse(self, item, warehouse=None, type_of_transaction=None) -> str: if not item.serial_and_batch_bundle: return "" + if not warehouse: + warehouse = item.warehouse + return self.make_package_for_transfer( - item.serial_and_batch_bundle, - item.warehouse, + item.serial_and_batch_bundle, warehouse, type_of_transaction=type_of_transaction ) def update_ordered_and_reserved_qty(self): diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index de431f3d42d..3ea3bcdfcc8 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -423,6 +423,15 @@ def make_return_doc( ]: type_of_transaction = "Outward" + warehouse = source_doc.warehouse if qty_field == "stock_qty" else source_doc.rejected_warehouse + if source_parent.doctype in [ + "Sales Invoice", + "POS Invoice", + "Delivery Note", + ] and source_parent.get("is_internal_customer"): + type_of_transaction = "Outward" + warehouse = source_doc.target_warehouse + cls_obj = SerialBatchCreation( { "type_of_transaction": type_of_transaction, @@ -432,7 +441,7 @@ def make_return_doc( "returned_serial_nos": returned_serial_nos, "voucher_type": source_parent.doctype, "do_not_submit": True, - "warehouse": source_doc.warehouse, + "warehouse": warehouse, "has_serial_no": item_details.has_serial_no, "has_batch_no": item_details.has_batch_no, } @@ -575,11 +584,14 @@ def make_return_doc( if not item_details.has_batch_no and not item_details.has_serial_no: return - for qty_field in ["stock_qty", "rejected_qty"]: - if target_doc.get(qty_field) and not target_doc.get("use_serial_batch_fields"): + if not target_doc.get("use_serial_batch_fields"): + for qty_field in ["stock_qty", "rejected_qty"]: + if not target_doc.get(qty_field): + continue + update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field) - elif target_doc.get(qty_field) and target_doc.get("use_serial_batch_fields"): - update_non_bundled_serial_nos(source_doc, target_doc, source_parent) + elif target_doc.get("use_serial_batch_fields"): + update_non_bundled_serial_nos(source_doc, target_doc, source_parent) def update_non_bundled_serial_nos(source_doc, target_doc, source_parent): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index ecc28a89fa5..747b4e061b6 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -442,8 +442,10 @@ class SellingController(StockController): # Get incoming rate based on original item cost based on valuation method qty = flt(d.get("stock_qty") or d.get("actual_qty")) - if not d.incoming_rate or ( - get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return") + if ( + not d.incoming_rate + or self.is_internal_transfer() + or (get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")) ): d.incoming_rate = get_incoming_rate( { @@ -458,6 +460,8 @@ class SellingController(StockController): "voucher_no": self.name, "voucher_detail_no": d.name, "allow_zero_valuation": d.get("allow_zero_valuation"), + "batch_no": d.batch_no, + "serial_no": d.serial_no, }, raise_error_if_no_rate=False, ) @@ -530,13 +534,26 @@ class SellingController(StockController): self.make_sl_entries(sl_entries) def get_sle_for_source_warehouse(self, item_row): + serial_and_batch_bundle = item_row.serial_and_batch_bundle + if serial_and_batch_bundle and self.is_internal_transfer() and self.is_return: + if self.docstatus == 1: + serial_and_batch_bundle = self.make_package_for_transfer( + serial_and_batch_bundle, item_row.warehouse, type_of_transaction="Inward" + ) + else: + serial_and_batch_bundle = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_detail_no": item_row.name, "warehouse": item_row.warehouse}, + "serial_and_batch_bundle", + ) + sle = self.get_sl_entries( item_row, { "actual_qty": -1 * flt(item_row.qty), "incoming_rate": item_row.incoming_rate, "recalculate_rate": cint(self.is_return), - "serial_and_batch_bundle": item_row.serial_and_batch_bundle, + "serial_and_batch_bundle": serial_and_batch_bundle, }, ) if item_row.target_warehouse and not cint(self.is_return): @@ -557,9 +574,15 @@ class SellingController(StockController): if item_row.warehouse: sle.dependant_sle_voucher_detail_no = item_row.name - if item_row.serial_and_batch_bundle: + if item_row.serial_and_batch_bundle and not cint(self.is_return): + type_of_transaction = "Inward" + if cint(self.is_return): + type_of_transaction = "Outward" + sle["serial_and_batch_bundle"] = self.make_package_for_transfer( - item_row.serial_and_batch_bundle, item_row.target_warehouse + item_row.serial_and_batch_bundle, + item_row.target_warehouse, + type_of_transaction=type_of_transaction, ) return sle diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 688600774cc..2b607eafdf8 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -236,6 +236,14 @@ class StockController(AccountsController): qty = row.get("rejected_qty") warehouse = row.get("rejected_warehouse") + if ( + self.is_internal_transfer() + and self.doctype in ["Sales Invoice", "Delivery Note"] + and self.is_return + ): + warehouse = row.get("target_warehouse") or row.get("warehouse") + type_of_transaction = "Outward" + bundle_details.update( { "qty": qty, @@ -579,7 +587,7 @@ class StockController(AccountsController): bundle_doc.warehouse = warehouse bundle_doc.type_of_transaction = type_of_transaction bundle_doc.voucher_type = self.doctype - bundle_doc.voucher_no = self.name + bundle_doc.voucher_no = "" if self.is_new() or self.docstatus == 2 else self.name bundle_doc.is_cancelled = 0 for row in bundle_doc.entries: @@ -595,6 +603,7 @@ class StockController(AccountsController): bundle_doc.calculate_qty_and_amount() bundle_doc.flags.ignore_permissions = True + bundle_doc.flags.ignore_validate = True bundle_doc.save(ignore_permissions=True) return bundle_doc.name diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 24133b8cdc3..42d37bf493b 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -542,6 +542,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { frappe.throw(__("Please add atleast one Serial No / Batch No")); } + if (!warehouse) { + frappe.throw(__("Please select a Warehouse")); + } + frappe .call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index fdc9753cfc7..a7fcd04c634 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -333,6 +333,7 @@ class DeliveryNote(SellingController): "type_of_transaction": "Outward", "serial_and_batch_bundle": bundle_id, "item_code": item.get("item_code"), + "warehouse": item.get("warehouse"), } ) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 8c3c1f750ab..b4fc464b534 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2522,6 +2522,280 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(row.serial_no, "\n".join(serial_nos[:2])) self.assertEqual(row.rejected_serial_no, serial_nos[2]) + def test_internal_transfer_with_serial_batch_items_and_their_valuation(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + prepare_data_for_internal_transfer() + + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + batch_item_doc = make_item( + "_Test Batch Item For Stock Transfer", + {"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "BT-BIFST-.####"}, + ) + + serial_item_doc = make_item( + "_Test Serial No Item For Stock Transfer", + {"has_serial_no": 1, "serial_no_series": "BT-BIFST-.####"}, + ) + + inward_entry = make_purchase_receipt( + item_code=batch_item_doc.name, + qty=10, + rate=150, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + use_serial_batch_fields=1, + do_not_submit=1, + ) + + inward_entry.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 250, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "use_serial_batch_fields": 1, + }, + ) + + inward_entry.submit() + inward_entry.reload() + + for row in inward_entry.items: + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_dn = create_delivery_note( + item_code=inward_entry.items[0].item_code, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=500, + warehouse="Stores - TCP1", + target_warehouse="Work In Progress - TCP1", + batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle), + use_serial_batch_fields=1, + do_not_submit=1, + ) + + inter_transfer_dn.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 350, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "target_warehouse": "Work In Progress - TCP1", + "serial_no": "\n".join( + get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle) + ), + "use_serial_batch_fields": 1, + }, + ) + + inter_transfer_dn.submit() + inter_transfer_dn.reload() + for row in inter_transfer_dn.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name) + for row in inter_transfer_pr.items: + row.from_warehouse = "Work In Progress - TCP1" + row.warehouse = "Stores - TCP1" + inter_transfer_pr.submit() + + for row in inter_transfer_pr.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name) + + inter_transfer_pr_return.submit() + inter_transfer_pr_return.reload() + for row in inter_transfer_pr_return.items: + self.assertTrue(row.serial_and_batch_bundle) + if row.item_code == serial_item_doc.name: + self.assertEqual(row.rate, 250.0) + serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle) + for sn in serial_nos: + serial_no_details = frappe.db.get_value("Serial No", sn, ["status", "warehouse"], as_dict=1) + self.assertTrue(serial_no_details.status == "Active") + self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1") + + inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name) + inter_transfer_dn_return.posting_date = today() + inter_transfer_dn_return.posting_time = nowtime() + for row in inter_transfer_dn_return.items: + row.target_warehouse = "Work In Progress - TCP1" + + inter_transfer_dn_return.submit() + inter_transfer_dn_return.reload() + + for row in inter_transfer_dn_return.items: + self.assertTrue(row.serial_and_batch_bundle) + + def test_internal_transfer_with_serial_batch_items_without_user_serial_batch_fields(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) + + prepare_data_for_internal_transfer() + + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + batch_item_doc = make_item( + "_Test Batch Item For Stock Transfer USE SERIAL BATCH FIELDS", + {"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF-BT-BIFST-.####"}, + ) + + serial_item_doc = make_item( + "_Test Serial No Item For Stock Transfer USE SERIAL BATCH FIELDS", + {"has_serial_no": 1, "serial_no_series": "USBF-BT-BIFST-.####"}, + ) + + inward_entry = make_purchase_receipt( + item_code=batch_item_doc.name, + qty=10, + rate=150, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + use_serial_batch_fields=0, + do_not_submit=1, + ) + + inward_entry.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 250, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "use_serial_batch_fields": 0, + }, + ) + + inward_entry.submit() + inward_entry.reload() + + for row in inward_entry.items: + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_dn = create_delivery_note( + item_code=inward_entry.items[0].item_code, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=500, + warehouse="Stores - TCP1", + target_warehouse="Work In Progress - TCP1", + batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle), + use_serial_batch_fields=0, + do_not_submit=1, + ) + + inter_transfer_dn.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 350, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "target_warehouse": "Work In Progress - TCP1", + "serial_no": "\n".join( + get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle) + ), + "use_serial_batch_fields": 0, + }, + ) + + inter_transfer_dn.submit() + inter_transfer_dn.reload() + for row in inter_transfer_dn.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name) + for row in inter_transfer_pr.items: + row.from_warehouse = "Work In Progress - TCP1" + row.warehouse = "Stores - TCP1" + inter_transfer_pr.submit() + + for row in inter_transfer_pr.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name) + + inter_transfer_pr_return.submit() + inter_transfer_pr_return.reload() + for row in inter_transfer_pr_return.items: + self.assertTrue(row.serial_and_batch_bundle) + if row.item_code == serial_item_doc.name: + self.assertEqual(row.rate, 250.0) + serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle) + for sn in serial_nos: + serial_no_details = frappe.db.get_value("Serial No", sn, ["status", "warehouse"], as_dict=1) + self.assertTrue(serial_no_details.status == "Active") + self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1") + + inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name) + inter_transfer_dn_return.posting_date = today() + inter_transfer_dn_return.posting_time = nowtime() + for row in inter_transfer_dn_return.items: + row.target_warehouse = "Work In Progress - TCP1" + + inter_transfer_dn_return.submit() + inter_transfer_dn_return.reload() + + for row in inter_transfer_dn_return.items: + self.assertTrue(row.serial_and_batch_bundle) + + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json index 7a58462357b..59ef43e31a8 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json @@ -113,6 +113,7 @@ { "fieldname": "voucher_no", "fieldtype": "Dynamic Link", + "in_standard_filter": 1, "label": "Voucher No", "no_copy": 1, "options": "voucher_type", @@ -250,7 +251,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-12-07 17:56:55.528563", + "modified": "2024-03-15 15:22:24.003486", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Bundle", 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 08cb3ca3074..9a7395fc667 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 @@ -801,6 +801,7 @@ class SerialandBatchBundle(Document): self.set_purchase_document_no() def on_submit(self): + self.validate_batch_inventory() self.validate_serial_nos_inventory() def set_purchase_document_no(self): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4421a3e7938..c317a889ec6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2606,6 +2606,7 @@ def move_sample_to_retention_warehouse(company, items): "type_of_transaction": "Outward", "serial_and_batch_bundle": item.get("serial_and_batch_bundle"), "item_code": item.get("item_code"), + "warehouse": item.get("t_warehouse"), } ) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 902b8ffa90c..ce3c4958cec 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -999,6 +999,7 @@ class TestStockEntry(FrappeTestCase): "type_of_transaction": "Inward", "serial_and_batch_bundle": s2.items[0].serial_and_batch_bundle, "item_code": "_Test Serialized Item", + "warehouse": "_Test Warehouse - _TC", } ) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 4e87fa022d8..7b42103bdeb 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -820,6 +820,10 @@ class SerialBatchCreation: self.remove_returned_serial_nos(new_package) new_package.docstatus = 0 + new_package.warehouse = self.warehouse + new_package.voucher_no = "" + new_package.posting_date = today() + new_package.posting_time = nowtime() new_package.type_of_transaction = self.type_of_transaction new_package.returned_against = self.get("returned_against") new_package.save() From ed4c8461d380f25187a3896a2b44e9eef4dca32c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 20:16:28 +0530 Subject: [PATCH 28/53] chore: resolve conflict --- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index c0e425a912a..2b226e1b241 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1475,7 +1475,6 @@ class TestPaymentEntry(FrappeTestCase): for field in ["account", "debit", "credit"]: self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) -<<<<<<< HEAD def test_outstanding_invoices_api(self): """ Test if `get_outstanding_reference_documents` fetches invoices in the right order. @@ -1514,7 +1513,7 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(references[2].voucher_no, si2.name) self.assertEqual(references[1].payment_term, "Basic Amount Receivable") self.assertEqual(references[2].payment_term, "Tax Receivable") -======= + def test_reverse_payment_reconciliation(self): customer = create_customer(frappe.generate_hash(length=10), "INR") pe = create_payment_entry( @@ -1553,7 +1552,6 @@ class TestPaymentEntry(FrappeTestCase): pr.reconcile() self.assertEqual(len(pr.invoices), 0) self.assertEqual(len(pr.payments), 0) ->>>>>>> 6d9074d585 (test: reverse payment reconciliation) def test_advance_reverse_payment_reconciliation(self): from erpnext.accounts.doctype.account.test_account import create_account From 74b30a6a1d9679f3ce8be20c71200353c8a9797d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:35:56 +0530 Subject: [PATCH 29/53] fix: stopped mr showing in the popup (backport #40478) (#40480) fix: stopped mr showing in the popup (#40478) (cherry picked from commit 1ff8a6f24ff8b5b4ea1ef91215c5f5b8b4ca86f8) Co-authored-by: rohitwaghchaure --- erpnext/buying/doctype/purchase_order/purchase_order.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 7875646ab72..023898c94b4 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -507,7 +507,6 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( target: me.frm, setters: { schedule_date: undefined, - status: undefined, }, get_query_filters: { material_request_type: "Purchase", From 28d3525b42761f2f0d529d80dbc37f27a13586c1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 13:05:07 +0530 Subject: [PATCH 30/53] refactor: validate SO and SI references (cherry picked from commit 4d090bd3b83c25e18b3e44b1f7dd75dd38faba7f) --- .../doctype/delivery_note/delivery_note.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index a7fcd04c634..4c2c023b6ae 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -251,6 +251,7 @@ class DeliveryNote(SellingController): def validate(self): self.validate_posting_time() super(DeliveryNote, self).validate() + self.validate_references() self.set_status() self.so_required() self.validate_proj_cust() @@ -341,6 +342,58 @@ class DeliveryNote(SellingController): item.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle + def validate_references(self): + self.validate_sales_order_references() + self.validate_sales_invoice_references() + + def validate_sales_order_references(self): + err_msg = "" + 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") + ) + + "
" + ) + + if err_msg: + frappe.throw(err_msg, title=_("References to Sales Orders are Incomplete")) + + def validate_sales_invoice_references(self): + err_msg = "" + 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") + ) + + "
" + ) + + if err_msg: + frappe.throw(err_msg, title=_("References to Sales Invoices are Incomplete")) + def validate_proj_cust(self): """check for does customer belong to same project as entered..""" if self.project and self.customer: From 409a65ec64b86d1932c728194e1fb760f55593a2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 16 Mar 2024 14:39:57 +0530 Subject: [PATCH 31/53] test: SO reference validation (cherry picked from commit 4f396d304979a23c3265c467112aa1dbc99932bd) --- erpnext/selling/doctype/sales_order/test_sales_order.py | 5 +++-- .../stock/doctype/delivery_note/test_delivery_note.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 261566ec001..375accd65b6 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2156,13 +2156,14 @@ def make_sales_order(**args): return so -def create_dn_against_so(so, delivered_qty=0): +def create_dn_against_so(so, delivered_qty=0, do_not_submit=False): frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) dn = make_delivery_note(so) dn.get("items")[0].qty = delivered_qty or 5 dn.insert() - dn.submit() + if not do_not_submit: + dn.submit() return dn diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 80886294958..f9341393ad0 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -813,6 +813,15 @@ class TestDeliveryNote(FrappeTestCase): dn.cancel() self.assertEqual(dn.status, "Cancelled") + def test_sales_order_reference_validation(self): + so = make_sales_order(po_no="12345") + dn = create_dn_against_so(so.name, delivered_qty=2, do_not_submit=True) + dn.items[0].against_sales_order = None + self.assertRaises(frappe.ValidationError, dn.save) + dn.reload() + dn.items[0].so_detail = None + self.assertRaises(frappe.ValidationError, dn.save) + def test_dn_billing_status_case1(self): # SO -> DN -> SI so = make_sales_order(po_no="12345") From 2512d4c7f24876070b7912e2faa84292ec2555c8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 13 Mar 2024 11:59:43 +0530 Subject: [PATCH 32/53] chore: Remove custom party queries (cherry picked from commit c9623b6fd66beebd94627a6a63698b0240b3b6ca) --- .../buying_settings/buying_settings.py | 26 +++++++++++++ erpnext/buying/doctype/supplier/supplier.json | 4 +- .../buying/doctype/supplier/test_supplier.py | 38 ------------------- erpnext/controllers/queries.py | 33 ---------------- erpnext/controllers/tests/test_queries.py | 6 --- erpnext/public/js/controllers/buying.js | 5 --- erpnext/public/js/queries.js | 4 -- erpnext/stock/doctype/item/item.js | 4 -- .../material_request/material_request.js | 6 --- 9 files changed, 28 insertions(+), 98 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py index ec9b88888b7..98935c1e57d 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.py +++ b/erpnext/buying/doctype/buying_settings/buying_settings.py @@ -54,9 +54,35 @@ class BuyingSettings(Document): hide_name_field=False, ) + set_search_fields("Supplier", "supplier_name", self.get("supp_master_name") == "Naming Series") + def before_save(self): self.check_maintain_same_rate() def check_maintain_same_rate(self): if self.maintain_same_rate: self.set_landed_cost_based_on_purchase_invoice_rate = 0 + + +def set_search_fields(doctype, fieldname, naming_series): + """Change a doctype's naming to user naming series""" + from frappe.custom.doctype.property_setter.property_setter import make_property_setter + + searchfields = frappe.get_meta(doctype).get_search_fields() + + if naming_series: + if fieldname not in searchfields: + searchfields.append(fieldname) + else: + if fieldname in searchfields: + searchfields.remove(fieldname) + + make_property_setter( + doctype, + "", + "search_fields", + ", ".join(searchfields), + "Data", + for_doctype=True, + validate_fields_for_doctype=False, + ) diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 60dd54c2385..3dae0442cce 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -485,7 +485,7 @@ "link_fieldname": "party" } ], - "modified": "2023-10-19 16:55:15.148325", + "modified": "2024-03-13 11:14:06.516519", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", @@ -544,7 +544,7 @@ } ], "quick_entry": 1, - "search_fields": "supplier_name, supplier_group", + "search_fields": "supplier_group", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index 350a25f26e7..55974ea6c46 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -154,44 +154,6 @@ class TestSupplier(FrappeTestCase): # Rollback address.delete() - def test_serach_fields_for_supplier(self): - from erpnext.controllers.queries import supplier_query - - frappe.db.set_single_value("Buying Settings", "supp_master_name", "Naming Series") - - supplier_name = create_supplier(supplier_name="Test Supplier 1").name - - make_property_setter( - "Supplier", None, "search_fields", "supplier_group", "Data", for_doctype="Doctype" - ) - - data = supplier_query( - "Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True - ) - - self.assertEqual(data[0].name, supplier_name) - self.assertEqual(data[0].supplier_group, "Services") - self.assertTrue("supplier_type" not in data[0]) - - make_property_setter( - "Supplier", - None, - "search_fields", - "supplier_group, supplier_type", - "Data", - for_doctype="Doctype", - ) - data = supplier_query( - "Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True - ) - - self.assertEqual(data[0].name, supplier_name) - self.assertEqual(data[0].supplier_group, "Services") - self.assertEqual(data[0].supplier_type, "Company") - self.assertTrue("supplier_type" in data[0]) - - frappe.db.set_single_value("Buying Settings", "supp_master_name", "Supplier Name") - def create_supplier(**args): args = frappe._dict(args) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 6463d17cd90..bef2ae66bb2 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -126,39 +126,6 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict= ) -# searches for supplier -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): - doctype = "Supplier" - supp_master_name = frappe.defaults.get_user_default("supp_master_name") - - fields = ["name"] - if supp_master_name != "Supplier Name": - fields.append("supplier_name") - - fields = get_fields(doctype, fields) - - return frappe.db.sql( - """select {field} from `tabSupplier` - where docstatus < 2 - and ({key} like %(txt)s - or supplier_name like %(txt)s) and disabled=0 - and (on_hold = 0 or (on_hold = 1 and CURRENT_DATE > release_date)) - {mcond} - order by - (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end), - (case when locate(%(_txt)s, supplier_name) > 0 then locate(%(_txt)s, supplier_name) else 99999 end), - idx desc, - name, supplier_name - limit %(page_len)s offset %(start)s""".format( - **{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)} - ), - {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len}, - as_dict=as_dict, - ) - - @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def tax_account_query(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py index 3a3bc1cd725..0ec3bd6a180 100644 --- a/erpnext/controllers/tests/test_queries.py +++ b/erpnext/controllers/tests/test_queries.py @@ -37,12 +37,6 @@ class TestQueries(unittest.TestCase): self.assertGreaterEqual(len(query(txt="_Test Customer")), 7) self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1) - def test_supplier_query(self): - query = add_default_params(queries.supplier_query, "Supplier") - - self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7) - self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1) - def test_item_query(self): query = add_default_params(queries.item_query, "Item") diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 1d0d47ec3d3..934becf6641 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -74,11 +74,6 @@ erpnext.buying = { me.frm.set_query('billing_address', erpnext.queries.company_address_query); erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype); - if(this.frm.fields_dict.supplier) { - this.frm.set_query("supplier", function() { - return{ query: "erpnext.controllers.queries.supplier_query" }}); - } - this.frm.set_query("item_code", "items", function() { if (me.frm.doc.is_subcontracted) { var filters = {'supplier': me.frm.doc.supplier}; diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js index b7e685cd6fb..e20ea7b3adc 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -16,10 +16,6 @@ $.extend(erpnext.queries, { return { query: "erpnext.controllers.queries.customer_query" }; }, - supplier: function () { - return { query: "erpnext.controllers.queries.supplier_query" }; - }, - item: function (filters) { var args = { query: "erpnext.controllers.queries.item_query" }; if (filters) args["filters"] = filters; diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 7a38024872e..93a2939f977 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -410,10 +410,6 @@ $.extend(erpnext.item, { return { query: "erpnext.controllers.queries.customer_query" }; }; - frm.fields_dict.supplier_items.grid.get_field("supplier").get_query = function (doc, cdt, cdn) { - return { query: "erpnext.controllers.queries.supplier_query" }; - }; - frm.fields_dict["item_defaults"].grid.get_field("default_warehouse").get_query = function ( doc, cdt, diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index f04acc85ed5..0b77465d0d8 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -365,12 +365,6 @@ frappe.ui.form.on("Material Request", { description: __( "Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only." ), - get_query: () => { - return { - query: "erpnext.stock.doctype.material_request.material_request.get_default_supplier_query", - filters: { doc: frm.doc.name }, - }; - }, }, (values) => { frappe.model.open_mapped_doc({ From 2e38f4d5999848b162b9a730da7ea37ebe5da4c1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 13 Mar 2024 12:02:06 +0530 Subject: [PATCH 33/53] chore: remove invalid comment (cherry picked from commit e87b44f758f2f658c74982091cb64c2f6237cdec) --- erpnext/buying/doctype/buying_settings/buying_settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py index 98935c1e57d..70759aacef7 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.py +++ b/erpnext/buying/doctype/buying_settings/buying_settings.py @@ -65,7 +65,6 @@ class BuyingSettings(Document): def set_search_fields(doctype, fieldname, naming_series): - """Change a doctype's naming to user naming series""" from frappe.custom.doctype.property_setter.property_setter import make_property_setter searchfields = frappe.get_meta(doctype).get_search_fields() From e3336c603c5318ab7df99ba7e8b2acbb2a1e7e44 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 13 Mar 2024 12:06:32 +0530 Subject: [PATCH 34/53] chore: Add supplier query back (cherry picked from commit effc9d14924c9eecaa78db1e1c8a579f219e8c93) --- erpnext/stock/doctype/material_request/material_request.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 0b77465d0d8..f04acc85ed5 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -365,6 +365,12 @@ frappe.ui.form.on("Material Request", { description: __( "Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only." ), + get_query: () => { + return { + query: "erpnext.stock.doctype.material_request.material_request.get_default_supplier_query", + filters: { doc: frm.doc.name }, + }; + }, }, (values) => { frappe.model.open_mapped_doc({ From 313350534f5b4ddbde3e7283d2de3ee4df91a666 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 16 Mar 2024 19:47:08 +0530 Subject: [PATCH 35/53] fix: Remove custom queries for customer (cherry picked from commit 60ba25caf2c02b64ae19ae8797c46aa2728049bc) --- .../doctype/payment_entry/payment_entry.js | 4 -- .../buying_settings/buying_settings.py | 25 ------------ erpnext/controllers/queries.py | 40 ------------------- erpnext/controllers/tests/test_queries.py | 6 --- erpnext/crm/doctype/lead/lead.js | 4 -- erpnext/hooks.py | 3 -- erpnext/projects/doctype/project/project.js | 2 - erpnext/public/js/queries.js | 4 -- .../selling/doctype/customer/customer.json | 4 +- .../selling/doctype/customer/test_customer.py | 31 -------------- .../page/point_of_sale/pos_item_cart.js | 10 +++-- erpnext/stock/doctype/item/item.js | 4 -- 12 files changed, 9 insertions(+), 128 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index ab50c38b1e2..961ee204d47 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -395,10 +395,6 @@ frappe.ui.form.on("Payment Entry", { return { query: "erpnext.controllers.queries.employee_query", }; - } else if (frm.doc.party_type == "Customer") { - return { - query: "erpnext.controllers.queries.customer_query", - }; } }); diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py index 70759aacef7..ec9b88888b7 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.py +++ b/erpnext/buying/doctype/buying_settings/buying_settings.py @@ -54,34 +54,9 @@ class BuyingSettings(Document): hide_name_field=False, ) - set_search_fields("Supplier", "supplier_name", self.get("supp_master_name") == "Naming Series") - def before_save(self): self.check_maintain_same_rate() def check_maintain_same_rate(self): if self.maintain_same_rate: self.set_landed_cost_based_on_purchase_invoice_rate = 0 - - -def set_search_fields(doctype, fieldname, naming_series): - from frappe.custom.doctype.property_setter.property_setter import make_property_setter - - searchfields = frappe.get_meta(doctype).get_search_fields() - - if naming_series: - if fieldname not in searchfields: - searchfields.append(fieldname) - else: - if fieldname in searchfields: - searchfields.remove(fieldname) - - make_property_setter( - doctype, - "", - "search_fields", - ", ".join(searchfields), - "Data", - for_doctype=True, - validate_fields_for_doctype=False, - ) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index bef2ae66bb2..a7b2d572733 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -85,46 +85,6 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len}, ) - # searches for customer - - -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): - doctype = "Customer" - conditions = [] - cust_master_name = frappe.defaults.get_user_default("cust_master_name") - - fields = ["name"] - if cust_master_name != "Customer Name": - fields.append("customer_name") - - fields = get_fields(doctype, fields) - searchfields = frappe.get_meta(doctype).get_search_fields() - searchfields = " or ".join(field + " like %(txt)s" for field in searchfields) - - return frappe.db.sql( - """select {fields} from `tabCustomer` - where docstatus < 2 - and ({scond}) and disabled=0 - {fcond} {mcond} - order by - (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end), - (case when locate(%(_txt)s, customer_name) > 0 then locate(%(_txt)s, customer_name) else 99999 end), - idx desc, - name, customer_name - limit %(page_len)s offset %(start)s""".format( - **{ - "fields": ", ".join(fields), - "scond": searchfields, - "mcond": get_match_cond(doctype), - "fcond": get_filters_cond(doctype, filters, conditions).replace("%", "%%"), - } - ), - {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len}, - as_dict=as_dict, - ) - @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py index 0ec3bd6a180..c536d1cb3da 100644 --- a/erpnext/controllers/tests/test_queries.py +++ b/erpnext/controllers/tests/test_queries.py @@ -31,12 +31,6 @@ class TestQueries(unittest.TestCase): self.assertGreaterEqual(len(query(txt="_Test Lead")), 4) self.assertEqual(len(query(txt="_Test Lead 4")), 1) - def test_customer_query(self): - query = add_default_params(queries.customer_query, "Customer") - - self.assertGreaterEqual(len(query(txt="_Test Customer")), 7) - self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1) - def test_item_query(self): query = add_default_params(queries.item_query, "Item") diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 0b6cdf25aac..609eab7f9a2 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -17,10 +17,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } onload() { - this.frm.set_query("customer", function (doc, cdt, cdn) { - return { query: "erpnext.controllers.queries.customer_query" }; - }); - this.frm.set_query("lead_owner", function (doc, cdt, cdn) { return { query: "frappe.core.doctype.user.user.user_query" }; }); diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 911907c4229..369704812e4 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -288,9 +288,6 @@ has_website_permission = { before_tests = "erpnext.setup.utils.before_tests" -standard_queries = { - "Customer": "erpnext.controllers.queries.customer_query", -} period_closing_doctypes = [ "Sales Invoice", diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 16ac8db024b..49e8d8486a5 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -27,8 +27,6 @@ frappe.ui.form.on("Project", { }; }; - frm.set_query("customer", "erpnext.controllers.queries.customer_query"); - frm.set_query("user", "users", function () { return { query: "erpnext.projects.doctype.project.project.get_users_for_project", diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js index e20ea7b3adc..d7edac3cb9f 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -12,10 +12,6 @@ $.extend(erpnext.queries, { return { query: "erpnext.controllers.queries.lead_query" }; }, - customer: function () { - return { query: "erpnext.controllers.queries.customer_query" }; - }, - item: function (filters) { var args = { query: "erpnext.controllers.queries.item_query" }; if (filters) args["filters"] = filters; diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index db712d96b50..41c6311553c 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -583,7 +583,7 @@ "link_fieldname": "party" } ], - "modified": "2023-12-28 13:15:36.298369", + "modified": "2024-03-16 19:41:47.971815", "modified_by": "Administrator", "module": "Selling", "name": "Customer", @@ -661,7 +661,7 @@ } ], "quick_entry": 1, - "search_fields": "customer_name,customer_group,territory, mobile_no,primary_address", + "search_fields": "customer_group,territory, mobile_no,primary_address", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index a50d783d394..f55644380c7 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -370,37 +370,6 @@ class TestCustomer(FrappeTestCase): due_date = get_due_date("2017-01-22", "Customer", "_Test Customer") self.assertEqual(due_date, "2017-01-22") - def test_serach_fields_for_customer(self): - from erpnext.controllers.queries import customer_query - - frappe.db.set_single_value("Selling Settings", "cust_master_name", "Naming Series") - - make_property_setter( - "Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype" - ) - - data = customer_query( - "Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True - ) - - self.assertEqual(data[0].name, "_Test Customer") - self.assertEqual(data[0].customer_group, "_Test Customer Group") - self.assertTrue("territory" not in data[0]) - - make_property_setter( - "Customer", None, "search_fields", "customer_group, territory", "Data", for_doctype="Doctype" - ) - data = customer_query( - "Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True - ) - - self.assertEqual(data[0].name, "_Test Customer") - self.assertEqual(data[0].customer_group, "_Test Customer Group") - self.assertEqual(data[0].territory, "_Test Territory") - self.assertTrue("territory" in data[0]) - - frappe.db.set_single_value("Selling Settings", "cust_master_name", "Customer Name") - def test_parse_full_name(self): first, middle, last = parse_full_name("John") self.assertEqual(first, "John") diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index d95ef5893d9..fbee9c16267 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -295,10 +295,10 @@ erpnext.PointOfSale.ItemCart = class {
`); const me = this; - const query = { query: "erpnext.controllers.queries.customer_query" }; const allowed_customer_group = this.allowed_customer_groups || []; + let filters = {}; if (allowed_customer_group.length) { - query.filters = { + filters = { customer_group: ["in", allowed_customer_group], }; } @@ -308,7 +308,11 @@ erpnext.PointOfSale.ItemCart = class { fieldtype: "Link", options: "Customer", placeholder: __("Search by customer name, phone, email."), - get_query: () => query, + get_query: function () { + return { + filters: filters, + }; + }, onchange: function () { if (this.value) { const frm = me.events.get_frm(); diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 93a2939f977..5310a0f4d26 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -406,10 +406,6 @@ $.extend(erpnext.item, { }; }; - frm.fields_dict.customer_items.grid.get_field("customer_name").get_query = function (doc, cdt, cdn) { - return { query: "erpnext.controllers.queries.customer_query" }; - }; - frm.fields_dict["item_defaults"].grid.get_field("default_warehouse").get_query = function ( doc, cdt, From ebd967d7d3c3ad02a8ee0a9328377ae8cbe0c26a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 17 Mar 2024 07:32:13 +0530 Subject: [PATCH 36/53] fix: index error on Exchange Rate Revaluation creation (cherry picked from commit bb279e368c010667a8d23c06a37dc634b9171c67) --- .../exchange_rate_revaluation.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index e9cbb337d5b..3797828081a 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -628,21 +628,21 @@ def get_account_details( if account_balance and ( account_balance[0].balance or account_balance[0].balance_in_account_currency ): - account_with_new_balance = ExchangeRateRevaluation.calculate_new_account_balance( + if account_with_new_balance := ExchangeRateRevaluation.calculate_new_account_balance( company, posting_date, account_balance - ) - row = account_with_new_balance[0] - account_details.update( - { - "balance_in_base_currency": row["balance_in_base_currency"], - "balance_in_account_currency": row["balance_in_account_currency"], - "current_exchange_rate": row["current_exchange_rate"], - "new_exchange_rate": row["new_exchange_rate"], - "new_balance_in_base_currency": row["new_balance_in_base_currency"], - "new_balance_in_account_currency": row["new_balance_in_account_currency"], - "zero_balance": row["zero_balance"], - "gain_loss": row["gain_loss"], - } - ) + ): + row = account_with_new_balance[0] + account_details.update( + { + "balance_in_base_currency": row["balance_in_base_currency"], + "balance_in_account_currency": row["balance_in_account_currency"], + "current_exchange_rate": row["current_exchange_rate"], + "new_exchange_rate": row["new_exchange_rate"], + "new_balance_in_base_currency": row["new_balance_in_base_currency"], + "new_balance_in_account_currency": row["new_balance_in_account_currency"], + "zero_balance": row["zero_balance"], + "gain_loss": row["gain_loss"], + } + ) return account_details From 5729ee563c110b4b28040c22b06e5d0a5f7952ff Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 17 Mar 2024 06:34:17 +0530 Subject: [PATCH 37/53] fix: don't validate for currency, if account is unavailable (cherry picked from commit 3cde81be651313d4ec88560e500f5ef10a521402) --- .../bank_transaction/bank_transaction.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 5e17881b6c1..4246ba5c032 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -56,17 +56,17 @@ class BankTransaction(Document): Bank Transaction should be on the same currency as the Bank Account. """ if self.currency and self.bank_account: - account = frappe.get_cached_value("Bank Account", self.bank_account, "account") - account_currency = frappe.get_cached_value("Account", account, "account_currency") + if account := frappe.get_cached_value("Bank Account", self.bank_account, "account"): + account_currency = frappe.get_cached_value("Account", account, "account_currency") - if self.currency != account_currency: - frappe.throw( - _( - "Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}" - ).format( - frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency) + if self.currency != account_currency: + frappe.throw( + _( + "Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}" + ).format( + frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency) + ) ) - ) def set_status(self): if self.docstatus == 2: From a0bf22c9a577e92d8698e7c5537f5b3e8103ed9e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 17 Mar 2024 06:46:06 +0530 Subject: [PATCH 38/53] fix: attribute error on Bank Statement Import retry (cherry picked from commit aec630c74f9f218147006a382456c57a0215318a) --- .../doctype/bank_statement_import/bank_statement_import.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 1a4747c55b8..7f5b52e4615 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -148,6 +148,9 @@ def start_import( import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records") data = parse_data_from_template(import_file.raw_data) + # Importer expects 'Data Import' class, which has 'payload_count' attribute + if not data_import.get("payload_count"): + data_import.payload_count = len(data) - 1 if import_file_path: add_bank_account(data, bank_account) From 1ac888715c2039f807297da92f268177e117165b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 13 Mar 2024 12:18:25 +0530 Subject: [PATCH 39/53] fix: achieved targets for sales partners --- .../item_group_wise_sales_target_variance.py | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py index f2f1e4cfbaa..d4e704a7e33 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py @@ -209,31 +209,32 @@ def get_actual_data(filters, sales_users_or_territory_data, date_field, sales_fi parent_doc = frappe.qb.DocType(filters.get("doctype")) child_doc = frappe.qb.DocType(filters.get("doctype") + " Item") - sales_team = frappe.qb.DocType("Sales Team") - query = ( - frappe.qb.from_(parent_doc) - .inner_join(child_doc) - .on(child_doc.parent == parent_doc.name) - .inner_join(sales_team) - .on(sales_team.parent == parent_doc.name) - .select( - child_doc.item_group, - (child_doc.stock_qty * sales_team.allocated_percentage / 100).as_("stock_qty"), - (child_doc.base_net_amount * sales_team.allocated_percentage / 100).as_("base_net_amount"), - sales_team.sales_person, - parent_doc[date_field], - ) - .where( - (parent_doc.docstatus == 1) - & (parent_doc[date_field].between(fiscal_year.year_start_date, fiscal_year.year_end_date)) - ) - ) + query = frappe.qb.from_(parent_doc).inner_join(child_doc).on(child_doc.parent == parent_doc.name) if sales_field == "sales_person": - query = query.where(sales_team.sales_person.isin(sales_users_or_territory_data)) + sales_team = frappe.qb.DocType("Sales Team") + stock_qty = child_doc.stock_qty * sales_team.allocated_percentage / 100 + net_amount = child_doc.base_net_amount * sales_team.allocated_percentage / 100 + sales_field_col = sales_team[sales_field] + + query = query.inner_join(sales_team).on(sales_team.parent == parent_doc.name) else: - query = query.where(parent_doc[sales_field].isin(sales_users_or_territory_data)) + stock_qty = child_doc.stock_qty + net_amount = child_doc.base_net_amount + sales_field_col = parent_doc[sales_field] + + query = query.select( + child_doc.item_group, + parent_doc[date_field], + (stock_qty).as_("stock_qty"), + (net_amount).as_("base_net_amount"), + sales_field_col, + ).where( + (parent_doc.docstatus == 1) + & (parent_doc[date_field].between(fiscal_year.year_start_date, fiscal_year.year_end_date)) + & (sales_field_col.isin(sales_users_or_territory_data)) + ) return query.run(as_dict=True) From 1a3ea0c108b51c3cd63501cee12e70328846bee6 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 13 Mar 2024 13:35:24 +0530 Subject: [PATCH 40/53] test: sales partner targets with invoices --- ...ner_target_variance_based_on_item_group.py | 57 +++++++++++++++++++ ...son_target_variance_based_on_item_group.py | 40 ++++++++----- 2 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 erpnext/selling/report/sales_partner_target_variance_based_on_item_group/test_sales_partner_target_variance_based_on_item_group.py diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/test_sales_partner_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/test_sales_partner_target_variance_based_on_item_group.py new file mode 100644 index 00000000000..17186687d97 --- /dev/null +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/test_sales_partner_target_variance_based_on_item_group.py @@ -0,0 +1,57 @@ +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import flt, nowdate + +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.utils import get_fiscal_year +from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.sales_partner_target_variance_based_on_item_group import ( + execute, +) +from erpnext.selling.report.sales_person_target_variance_based_on_item_group.test_sales_person_target_variance_based_on_item_group import ( + create_sales_target_doc, + create_target_distribution, +) + + +class TestSalesPartnerTargetVarianceBasedOnItemGroup(FrappeTestCase): + def setUp(self): + self.fiscal_year = get_fiscal_year(nowdate())[0] + + def tearDown(self): + frappe.db.rollback() + + def test_achieved_target_and_variance_for_partner(self): + # Create a Target Distribution + distribution = create_target_distribution(self.fiscal_year) + + # Create Sales Partner with targets for the current fiscal year + sales_partner = create_sales_target_doc( + "Sales Partner", "partner_name", "Sales Partner 1", self.fiscal_year, distribution.name + ) + + # Create a Sales Invoice for the Partner + si = create_sales_invoice( + rate=1000, + qty=20, + do_not_submit=True, + ) + si.sales_partner = sales_partner + si.commission_rate = 5 + si.submit() + + # Check Achieved Target and Variance for the Sales Partner + result = execute( + frappe._dict( + { + "fiscal_year": self.fiscal_year, + "doctype": "Sales Invoice", + "period": "Yearly", + "target_on": "Quantity", + } + ) + )[1] + row = frappe._dict(result[0]) + self.assertSequenceEqual( + [flt(value, 2) for value in (row.total_target, row.total_achieved, row.total_variance)], + [50, 20, -30], + ) diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py index 4ae5d2bee88..73ae6d0c852 100644 --- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py +++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py @@ -18,17 +18,17 @@ class TestSalesPersonTargetVarianceBasedOnItemGroup(FrappeTestCase): def test_achieved_target_and_variance(self): # Create a Target Distribution - distribution = frappe.new_doc("Monthly Distribution") - distribution.distribution_id = "Target Report Distribution" - distribution.fiscal_year = self.fiscal_year - distribution.get_months() - distribution.insert() + distribution = create_target_distribution(self.fiscal_year) - # Create sales people with targets - person_1 = create_sales_person_with_target("Sales Person 1", self.fiscal_year, distribution.name) - person_2 = create_sales_person_with_target("Sales Person 2", self.fiscal_year, distribution.name) + # Create sales people with targets for the current fiscal year + person_1 = create_sales_target_doc( + "Sales Person", "sales_person_name", "Sales Person 1", self.fiscal_year, distribution.name + ) + person_2 = create_sales_target_doc( + "Sales Person", "sales_person_name", "Sales Person 2", self.fiscal_year, distribution.name + ) - # Create a Sales Order with 50-50 contribution + # Create a Sales Order with 50-50 contribution between both Sales people so = make_sales_order( rate=1000, qty=20, @@ -69,10 +69,20 @@ class TestSalesPersonTargetVarianceBasedOnItemGroup(FrappeTestCase): ) -def create_sales_person_with_target(sales_person_name, fiscal_year, distribution_id): - sales_person = frappe.new_doc("Sales Person") - sales_person.sales_person_name = sales_person_name - sales_person.append( +def create_target_distribution(fiscal_year): + distribution = frappe.new_doc("Monthly Distribution") + distribution.distribution_id = "Target Report Distribution" + distribution.fiscal_year = fiscal_year + distribution.get_months() + return distribution.insert() + + +def create_sales_target_doc( + sales_field_dt, sales_field_name, sales_field_value, fiscal_year, distribution_id +): + sales_target_doc = frappe.new_doc(sales_field_dt) + sales_target_doc.set(sales_field_name, sales_field_value) + sales_target_doc.append( "targets", { "fiscal_year": fiscal_year, @@ -81,4 +91,6 @@ def create_sales_person_with_target(sales_person_name, fiscal_year, distribution "distribution_id": distribution_id, }, ) - return sales_person.insert() + if sales_field_dt == "Sales Partner": + sales_target_doc.commission_rate = 5 + return sales_target_doc.insert() From bc78bc33f1a0a8ecac34338298ab334bd51aa8a7 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 14 Mar 2024 12:51:51 +0530 Subject: [PATCH 41/53] fix: show correct variance for durations with no vouchers --- .../item_group_wise_sales_target_variance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py index d4e704a7e33..42bdf571738 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py @@ -197,6 +197,8 @@ def prepare_data( ): details[p_key] += r.get(qty_or_amount_field, 0) details[variance_key] = details.get(p_key) - details.get(target_key) + else: + details[variance_key] = details.get(p_key) - details.get(target_key) details["total_achieved"] += details.get(p_key) details["total_variance"] = details.get("total_achieved") - details.get("total_target") From 69f442dd79395431feeb1298976e0dde174f7483 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:03:27 +0530 Subject: [PATCH 42/53] fix: balance qty in the stock ledger report (backport #40506) (#40507) fix: balance qty in the stock ledger report (#40506) (cherry picked from commit 00de529a1ec033037ab2d5f1e5347deac272cb25) Co-authored-by: rohitwaghchaure --- erpnext/stock/report/stock_ledger/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 2e4b08c3ea5..e98351a0a8b 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -60,6 +60,7 @@ def execute(filters=None): if filters.get("batch_no") or inventory_dimension_filters_applied: actual_qty += flt(sle.actual_qty, precision) stock_value += sle.stock_value_difference + batch_balance_dict[sle.batch_no] += sle.actual_qty if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty: actual_qty = sle.qty_after_transaction From 917a21b989744774796c42d2e14e7793fb13805e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 15:26:32 +0530 Subject: [PATCH 43/53] fix: not able to delete cancelled delivery note (backport #40508) (#40510) fix: not able to delete cancelled delivery note (#40508) (cherry picked from commit 7695759f3ce9c3acb1db986c6cd3a82d520289c5) Co-authored-by: rohitwaghchaure --- .../doctype/work_order/work_order.js | 2 ++ .../delivery_note/test_delivery_note.py | 21 +++++++++++++++++++ erpnext/stock/doctype/pick_list/pick_list.py | 6 +++++- .../serial_and_batch_bundle.py | 4 ++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 42f69438aef..70e803dbba1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -9,6 +9,8 @@ frappe.ui.form.on("Work Order", { "Job Card": "Create Job Card", }; + frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"]; + // Set query for warehouses frm.set_query("wip_warehouse", function () { return { diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index f9341393ad0..24544070d3b 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1097,9 +1097,30 @@ class TestDeliveryNote(FrappeTestCase): dn.load_from_db() batch_no = get_batch_from_bundle(dn.packed_items[0].serial_and_batch_bundle) + packed_name = dn.packed_items[0].name self.assertTrue(batch_no) + dn.cancel() + + # Cancel the reposting entry + reposting_entries = frappe.get_all("Repost Item Valuation", filters={"docstatus": 1}) + for entry in reposting_entries: + doc = frappe.get_doc("Repost Item Valuation", entry.name) + doc.cancel() + doc.delete() + + frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 1) + + dn.reload() + dn.delete() + + bundle = frappe.db.get_value( + "Serial and Batch Bundle", {"voucher_detail_no": packed_name}, "name" + ) + self.assertFalse(bundle) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 0) def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 8a1f79d4a27..627520c1dcd 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -184,7 +184,11 @@ class PickList(Document): def delink_serial_and_batch_bundle(self): for row in self.locations: - if row.serial_and_batch_bundle: + if ( + row.serial_and_batch_bundle + and frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "docstatus") + == 1 + ): frappe.db.set_value( "Serial and Batch Bundle", row.serial_and_batch_bundle, 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 9a7395fc667..1fb4969e306 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 @@ -778,6 +778,10 @@ class SerialandBatchBundle(Document): or_filters=or_filters, ) + if not vouchers and self.voucher_type == "Delivery Note": + frappe.db.set_value("Packed Item", self.voucher_detail_no, "serial_and_batch_bundle", None) + return + for voucher in vouchers: if voucher.get("current_serial_and_batch_bundle"): frappe.db.set_value(self.child_table, voucher.name, "current_serial_and_batch_bundle", None) From 72c9cd5582c99f3a2b48c619ea28c7b77d429564 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 15:34:21 +0530 Subject: [PATCH 44/53] fix: invalid exchange loss booking on invoice againts base accounts (cherry picked from commit 3d5dba6976e7aee6e9a291a951e8367e1b8f36af) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 87fb8c16263..29603f7458b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -404,7 +404,9 @@ class PaymentEntry(AccountsController): ref_details = get_reference_details( d.reference_doctype, d.reference_name, self.party_account_currency ) - if ref_exchange_rate: + + # Only update exchange rate when the reference is Journal Entry + if ref_exchange_rate and d.reference_doctype == "Journal Entry": ref_details.update({"exchange_rate": ref_exchange_rate}) for field, value in ref_details.items(): From dd30a46f29b3043e0c0bd4de50da319438f885e6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 20:24:05 +0530 Subject: [PATCH 45/53] refactor(test): ensure Exchange gain/loss journals aren't created (cherry picked from commit e1c2d006caa634f52661026fdc4e82762256251c) --- .../test_payment_reconciliation.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 301e6ef625c..1d20a5b954d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1130,6 +1130,17 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(pr.allocation[0].allocated_amount, 85) self.assertEqual(pr.allocation[0].difference_amount, 0) + pr.reconcile() + si.reload() + self.assertEqual(si.outstanding_amount, 0) + # No Exchange Gain/Loss journal should be generated + exc_gain_loss_journals = frappe.db.get_all( + "Journal Entry Account", + filters={"reference_type": si.doctype, "reference_name": si.name, "docstatus": 1}, + fields=["parent"], + ) + self.assertEqual(exc_gain_loss_journals, []) + def test_reconciliation_purchase_invoice_against_return(self): self.supplier = "_Test Supplier USD" pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True) From 1a0a70b5cd000cc12336d1d7874932b547bbd12f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 08:22:28 +0530 Subject: [PATCH 46/53] refactor: toggle between 'http' and 'https' on exchange rate API (cherry picked from commit 8b81274769f13a5b7ec91d79f4d2a13d48faeb9c) --- .../currency_exchange_settings.js | 46 ++++++++++++------- .../currency_exchange_settings.json | 10 +++- .../currency_exchange_settings.py | 21 ++++++++- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js index d931f627dbd..ad68352c2a4 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js @@ -3,22 +3,36 @@ frappe.ui.form.on("Currency Exchange Settings", { service_provider: function (frm) { - if (frm.doc.service_provider == "exchangerate.host") { - let result = ["result"]; - let params = { - date: "{transaction_date}", - from: "{from_currency}", - to: "{to_currency}", - }; - add_param(frm, "https://api.exchangerate.host/convert", params, result); - } else if (frm.doc.service_provider == "frankfurter.app") { - let result = ["rates", "{to_currency}"]; - let params = { - base: "{from_currency}", - symbols: "{to_currency}", - }; - add_param(frm, "https://frankfurter.app/{transaction_date}", params, result); - } + frm.call({ + method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint", + args: { + service_provider: frm.doc.service_provider, + use_http: frm.doc.use_http, + }, + callback: function (r) { + if (r && r.message) { + if (frm.doc.service_provider == "exchangerate.host") { + let result = ["result"]; + let params = { + date: "{transaction_date}", + from: "{from_currency}", + to: "{to_currency}", + }; + add_param(frm, r.message, params, result); + } else if (frm.doc.service_provider == "frankfurter.app") { + let result = ["rates", "{to_currency}"]; + let params = { + base: "{from_currency}", + symbols: "{to_currency}", + }; + add_param(frm, r.message, params, result); + } + } + }, + }); + }, + use_http: function (frm) { + frm.trigger("service_provider"); }, }); diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json index df232a5848c..bd90b8add80 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json @@ -9,6 +9,7 @@ "disabled", "service_provider", "api_endpoint", + "use_http", "access_key", "url", "column_break_3", @@ -91,12 +92,19 @@ "fieldname": "access_key", "fieldtype": "Data", "label": "Access Key" + }, + { + "default": "0", + "depends_on": "eval: doc.service_provider != \"Custom\"", + "fieldname": "use_http", + "fieldtype": "Check", + "label": "Use HTTP Protocol" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-04 15:30:25.333860", + "modified": "2024-03-18 08:32:26.895076", "modified_by": "Administrator", "module": "Accounts", "name": "Currency Exchange Settings", diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py index 3393d4170bc..b8817c60572 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py @@ -31,6 +31,7 @@ class CurrencyExchangeSettings(Document): result_key: DF.Table[CurrencyExchangeSettingsResult] service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"] url: DF.Data | None + use_http: DF.Check # end: auto-generated types def validate(self): @@ -53,7 +54,7 @@ class CurrencyExchangeSettings(Document): self.set("result_key", []) self.set("req_params", []) - self.api_endpoint = "https://api.exchangerate.host/convert" + self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http) self.append("result_key", {"key": "result"}) self.append("req_params", {"key": "access_key", "value": self.access_key}) self.append("req_params", {"key": "amount", "value": "1"}) @@ -64,7 +65,7 @@ class CurrencyExchangeSettings(Document): self.set("result_key", []) self.set("req_params", []) - self.api_endpoint = "https://frankfurter.app/{transaction_date}" + self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http) self.append("result_key", {"key": "rates"}) self.append("result_key", {"key": "{to_currency}"}) self.append("req_params", {"key": "base", "value": "{from_currency}"}) @@ -103,3 +104,19 @@ class CurrencyExchangeSettings(Document): frappe.throw(_("Returned exchange rate is neither integer not float.")) self.url = response.url + + +@frappe.whitelist() +def get_api_endpoint(service_provider: str = None, use_http: bool = False): + if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]: + if service_provider == "exchangerate.host": + api = "api.exchangerate.host/convert" + elif service_provider == "frankfurter.app": + api = "frankfurter.app/{transaction_date}" + + protocol = "https://" + if use_http: + protocol = "http://" + + return protocol + api + return None From dacc69c56f851a5024f88f0dccb2141e4dcd0621 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 19 Mar 2024 12:12:26 +0530 Subject: [PATCH 47/53] fix: missing range for ageing summary (cherry picked from commit 643cc022fdada0839e3c55d7c56aa475fdc4de58) --- .../process_statement_of_accounts.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 5307ccb1931..81ebf9744c4 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -89,10 +89,11 @@ - - - - + + + + + @@ -101,6 +102,7 @@ +
30 Days60 Days90 Days120 Days0 - 30 Days30 - 60 Days60 - 90 Days90 - 120 DaysAbove 120 Days
{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }} {{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }} {{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}
From 44bbf4b9838c43a488415c17538217ce0626cdc4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:26:41 +0530 Subject: [PATCH 48/53] fix: Update Existing Price List Rate not working (backport #40333) (#40527) fix: Update Existing Price List Rate not working (#40333) (cherry picked from commit 09ea7edb863dce39e28c799ea8e4dc0b12a3c69a) Co-authored-by: rohitwaghchaure --- .../doctype/sales_order/test_sales_order.py | 34 +++++++++++++++++++ erpnext/setup/demo_data/item.json | 10 ++++++ erpnext/stock/get_item_details.py | 4 ++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 375accd65b6..cce550270f0 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2091,6 +2091,40 @@ class TestSalesOrder(FrappeTestCase): dn.submit() dn.reload() + def test_auto_update_price_list(self): + item = make_item( + "_Test Auto Update Price List Item", + ) + + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) + so = make_sales_order( + item_code=item.name, currency="USD", qty=1, rate=100, price_list_rate=100, do_not_submit=True + ) + so.save() + + item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate") + self.assertEqual(item_price, 100) + + so = make_sales_order( + item_code=item.name, currency="USD", qty=1, rate=200, price_list_rate=100, do_not_submit=True + ) + so.save() + + item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate") + self.assertEqual(item_price, 100) + + frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 1) + so = make_sales_order( + item_code=item.name, currency="USD", qty=1, rate=200, price_list_rate=200, do_not_submit=True + ) + so.save() + + item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate") + self.assertEqual(item_price, 200) + + frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0) + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0) + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") diff --git a/erpnext/setup/demo_data/item.json b/erpnext/setup/demo_data/item.json index 330e114dd53..17024341225 100644 --- a/erpnext/setup/demo_data/item.json +++ b/erpnext/setup/demo_data/item.json @@ -4,6 +4,7 @@ "item_group": "Demo Item Group", "item_code": "SKU001", "item_name": "T-shirt", + "valuation_rate": 400.0, "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/1484808/pexels-photo-1484808.jpeg" }, @@ -11,6 +12,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU002", + "valuation_rate": 300.0, "item_name": "Laptop", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/3999538/pexels-photo-3999538.jpeg" @@ -19,6 +21,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU003", + "valuation_rate": 523.0, "item_name": "Book", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/2422178/pexels-photo-2422178.jpeg" @@ -27,6 +30,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU004", + "valuation_rate": 725.0, "item_name": "Smartphone", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/1647976/pexels-photo-1647976.jpeg" @@ -35,6 +39,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU005", + "valuation_rate": 222.0, "item_name": "Sneakers", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/1598505/pexels-photo-1598505.jpeg" @@ -43,6 +48,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU006", + "valuation_rate": 420.0, "item_name": "Coffee Mug", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/585753/pexels-photo-585753.jpeg" @@ -51,6 +57,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU007", + "valuation_rate": 375.0, "item_name": "Television", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/8059376/pexels-photo-8059376.jpeg" @@ -59,6 +66,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU008", + "valuation_rate": 333.0, "item_name": "Backpack", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/3731256/pexels-photo-3731256.jpeg" @@ -67,6 +75,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU009", + "valuation_rate": 700.0, "item_name": "Headphones", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/3587478/pexels-photo-3587478.jpeg" @@ -75,6 +84,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU010", + "valuation_rate": 500.0, "item_name": "Camera", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/51383/photo-camera-subject-photographer-51383.jpeg" diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1cb10575cd1..85da0348265 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -816,7 +816,9 @@ def get_price_list_rate(args, item_doc, out=None): price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) # insert in database - if price_list_rate is None: + if price_list_rate is None or frappe.db.get_single_value( + "Stock Settings", "update_existing_price_list_rate" + ): if args.price_list and args.rate: insert_item_price(args) return out From 7f1f7809c854ecb8a6e679ea35f553f1dc84faa4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:18:50 +0530 Subject: [PATCH 49/53] fix: stock reco negative batch (backport #40533) (#40535) fix: stock reco negative batch (#40533) (cherry picked from commit a64c2ecf396a009bfcaa1e58ad34c61d1b671fe6) Co-authored-by: rohitwaghchaure --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 7 +++++++ .../doctype/stock_reconciliation/stock_reconciliation.py | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) 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 1fb4969e306..58971e8f19d 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 @@ -832,6 +832,13 @@ class SerialandBatchBundle(Document): if not self.has_batch_no: return + if ( + self.voucher_type == "Stock Reconciliation" + and self.type_of_transaction == "Outward" + and frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty") > 0 + ): + return + batches = [d.batch_no for d in self.entries if d.batch_no] if not batches: return diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 3356ad5f300..0311481b6ca 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -154,7 +154,6 @@ class StockReconciliation(StockController): { "current_serial_and_batch_bundle": sn_doc.name, "current_serial_no": "", - "batch_no": "", } ) From 0bdda1226f947f03348a82853417ef004fb66116 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:37:37 +0530 Subject: [PATCH 50/53] fix: rate changing while making PR (negative discount) (backport #40539) (#40540) fix: rate changing while making PR (negative discount) (#40539) (cherry picked from commit 81369544840c4eded80bf40fd3e45ed0eb6f52b1) Co-authored-by: rohitwaghchaure --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 527b762ace0..4f666022b33 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -26,7 +26,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100; } - if (item.discount_amount) { + if (item.discount_amount > 0) { item_rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item)); item.discount_percentage = 100 * flt(item.discount_amount) / flt(item.rate_with_margin); } From f0e00daa1cdd605b4b5a7bea76e9fc76f3f97319 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:03:36 +0100 Subject: [PATCH 51/53] refactor: usage of in_list (cherry picked from commit d238751e6ba0666b4098f1db9be38e9bd8163770) --- .../doctype/journal_entry/journal_entry.js | 6 ++--- .../doctype/payment_entry/payment_entry.js | 16 +++++++------- .../payment_request/payment_request.js | 2 +- erpnext/assets/doctype/asset/asset.js | 6 ++--- .../doctype/purchase_order/purchase_order.js | 4 ++-- erpnext/manufacturing/doctype/bom/bom.js | 2 +- .../production_plan/production_plan.js | 2 +- .../doctype/work_order/work_order.js | 4 ++-- erpnext/public/js/communication.js | 2 +- erpnext/public/js/controllers/accounts.js | 2 +- erpnext/public/js/controllers/buying.js | 2 +- .../public/js/controllers/taxes_and_totals.js | 22 +++++++++---------- erpnext/public/js/controllers/transaction.js | 20 ++++++++--------- erpnext/public/js/payment/payments.js | 2 +- erpnext/public/js/sms_manager.js | 4 ++-- erpnext/public/js/utils/party.js | 4 ++-- erpnext/public/js/utils/sales_common.js | 6 ++--- .../import_supplier_invoice.js | 2 +- .../point_of_sale/pos_past_order_summary.js | 2 +- .../closing_stock_balance.js | 2 +- .../delivery_trip/delivery_trip_list.js | 4 ++-- erpnext/templates/form_grid/item_grid.html | 2 +- 22 files changed, 59 insertions(+), 59 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index f6d35fe2bba..3186d07adcc 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -196,7 +196,7 @@ frappe.ui.form.on("Journal Entry", { !(frm.doc.accounts || []).length || ((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account) ) { - if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) { + if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) { return frappe.call({ type: "GET", method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", @@ -308,7 +308,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro filters: [[jvd.reference_type, "docstatus", "=", 1]], }; - if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) { + if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) { out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]); // Filter by cost center if (jvd.cost_center) { @@ -320,7 +320,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]); } - if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) { + if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) { // party_type and party mandatory frappe.model.validate_missing(jvd, "party_type"); frappe.model.validate_missing(jvd, "party"); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 961ee204d47..0cb1a3d4997 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -32,7 +32,7 @@ frappe.ui.form.on("Payment Entry", { frm.set_query("paid_from", function () { frm.events.validate_company(frm); - var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) + var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type) ? ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; return { @@ -87,7 +87,7 @@ frappe.ui.form.on("Payment Entry", { frm.set_query("paid_to", function () { frm.events.validate_company(frm); - var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) + var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type) ? ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; return { @@ -134,7 +134,7 @@ frappe.ui.form.on("Payment Entry", { frm.set_query("payment_term", "references", function (frm, cdt, cdn) { const child = locals[cdt][cdn]; if ( - in_list(["Purchase Invoice", "Sales Invoice"], child.reference_doctype) && + ["Purchase Invoice", "Sales Invoice"].includes(child.reference_doctype) && child.reference_name ) { return { @@ -623,7 +623,7 @@ frappe.ui.form.on("Payment Entry", { if (frm.doc.paid_from_account_currency == company_currency) { frm.set_value("source_exchange_rate", 1); } else if (frm.doc.paid_from) { - if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) { + if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) { let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", @@ -1042,7 +1042,7 @@ frappe.ui.form.on("Payment Entry", { } var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding; - } else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) { + } else if (["Customer", "Supplier"].includes(frm.doc.party_type)) { total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount")); if (paid_amount > total_negative_outstanding) { if (total_negative_outstanding == 0) { @@ -1213,7 +1213,7 @@ frappe.ui.form.on("Payment Entry", { if ( frm.doc.party_type == "Customer" && - !in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype) + !["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"].includes(row.reference_doctype) ) { frappe.model.set_value(row.doctype, row.name, "reference_doctype", null); frappe.msgprint( @@ -1227,7 +1227,7 @@ frappe.ui.form.on("Payment Entry", { if ( frm.doc.party_type == "Supplier" && - !in_list(["Purchase Order", "Purchase Invoice", "Journal Entry"], row.reference_doctype) + !["Purchase Order", "Purchase Invoice", "Journal Entry"].includes(row.reference_doctype) ) { frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null); frappe.msgprint( @@ -1323,7 +1323,7 @@ frappe.ui.form.on("Payment Entry", { bank_account: function (frm) { const field = frm.doc.payment_type == "Pay" ? "paid_from" : "paid_to"; - if (frm.doc.bank_account && in_list(["Pay", "Receive"], frm.doc.payment_type)) { + if (frm.doc.bank_account && ["Pay", "Receive"].includes(frm.doc.payment_type)) { frappe.call({ method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details", args: { diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index e5a6040c735..e45aa512fe8 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -28,7 +28,7 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) { if ( frm.doc.payment_request_type == "Inward" && frm.doc.payment_channel !== "Phone" && - !in_list(["Initiated", "Paid"], frm.doc.status) && + !["Initiated", "Paid"].includes(frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus == 1 ) { diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 6dbb53a078f..0f71e5d6f60 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -79,7 +79,7 @@ frappe.ui.form.on("Asset", { frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); if (frm.doc.docstatus == 1) { - if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) { + if (["Submitted", "Partially Depreciated", "Fully Depreciated"].includes(frm.doc.status)) { frm.add_custom_button( __("Transfer Asset"), function () { @@ -365,7 +365,7 @@ frappe.ui.form.on("Asset", { if (v.journal_entry) { asset_values.push(asset_value); } else { - if (in_list(["Scrapped", "Sold"], frm.doc.status)) { + if (["Scrapped", "Sold"].includes(frm.doc.status)) { asset_values.push(null); } else { asset_values.push(asset_value); @@ -400,7 +400,7 @@ frappe.ui.form.on("Asset", { }); } - if (in_list(["Scrapped", "Sold"], frm.doc.status)) { + if (["Scrapped", "Sold"].includes(frm.doc.status)) { x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: "Date" })); asset_values.push(0); } diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index e62d22bd15b..cf383021b06 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -291,7 +291,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( this.frm.fields_dict.items_section.wrapper.removeClass("hide-border"); } - if (!in_list(["Closed", "Delivered"], doc.status)) { + if (!["Closed", "Delivered"].includes(doc.status)) { if ( this.frm.doc.status !== "Closed" && flt(this.frm.doc.per_received, 2) < 100 && @@ -336,7 +336,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( this.frm.page.set_inner_btn_group_as_primary(__("Status")); } - } else if (in_list(["Closed", "Delivered"], doc.status)) { + } else if (["Closed", "Delivered"].includes(doc.status)) { if (this.frm.has_perm("submit")) { this.frm.add_custom_button( __("Re-open"), diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 2ac28ea885f..6267ee4d029 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -400,7 +400,7 @@ frappe.ui.form.on("BOM", { }, rm_cost_as_per(frm) { - if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) { + if (["Valuation Rate", "Last Purchase Rate"].includes(frm.doc.rm_cost_as_per)) { frm.set_value("plc_conversion_rate", 1.0); } }, diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 54d1414c814..6db901c71a4 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -129,7 +129,7 @@ frappe.ui.form.on("Production Plan", { if ( frm.doc.mr_items && frm.doc.mr_items.length && - !in_list(["Material Requested", "Closed"], frm.doc.status) + !["Material Requested", "Closed"].includes(frm.doc.status) ) { frm.add_custom_button( __("Material Request"), diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 70e803dbba1..1da33f0ad9b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -196,7 +196,7 @@ frappe.ui.form.on("Work Order", { }, add_custom_button_to_return_components: function (frm) { - if (frm.doc.docstatus === 1 && in_list(["Closed", "Completed"], frm.doc.status)) { + if (frm.doc.docstatus === 1 && ["Closed", "Completed"].includes(frm.doc.status)) { let non_consumed_items = frm.doc.required_items.filter((d) => { return flt(d.consumed_qty) < flt(d.transferred_qty - d.returned_qty); }); @@ -596,7 +596,7 @@ erpnext.work_order = { ); } - if (doc.docstatus === 1 && !in_list(["Closed", "Completed"], doc.status)) { + if (doc.docstatus === 1 && !["Closed", "Completed"].includes(doc.status)) { if (doc.status != "Stopped" && doc.status != "Completed") { frm.add_custom_button( __("Stop"), diff --git a/erpnext/public/js/communication.js b/erpnext/public/js/communication.js index d9187f8b678..c8905e14af2 100644 --- a/erpnext/public/js/communication.js +++ b/erpnext/public/js/communication.js @@ -20,7 +20,7 @@ frappe.ui.form.on("Communication", { ); } - if (!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) { + if (!["Lead", "Opportunity"].includes(frm.doc.reference_doctype)) { frm.add_custom_button( __("Lead"), () => { diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 7879173cd11..c39fb524264 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -11,7 +11,7 @@ erpnext.accounts.taxes = { setup: function(frm) { // set conditional display for rate column in taxes $(frm.wrapper).on('grid-row-render', function(e, grid_row) { - if(in_list(['Sales Taxes and Charges', 'Purchase Taxes and Charges'], grid_row.doc.doctype)) { + if(['Sales Taxes and Charges', 'Purchase Taxes and Charges'].includes(grid_row.doc.doctype)) { me.set_conditional_mandatory_rate_or_amount(grid_row); } }); diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 934becf6641..1e94c0032ab 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -129,7 +129,7 @@ erpnext.buying = { } toggle_subcontracting_fields() { - if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) { + if (['Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doc.doctype)) { this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM'); diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 4f666022b33..10fa7cebca8 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -9,7 +9,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { apply_pricing_rule_on_item(item) { let effective_item_rate = item.price_list_rate; let item_rate = item.rate; - if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) { + if (["Sales Order", "Quotation"].includes(item.parenttype) && item.blanket_order_rate) { effective_item_rate = item.blanket_order_rate; } if (item.margin_type == "Percentage") { @@ -52,7 +52,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { // Advance calculation applicable to Sales/Purchase Invoice if ( - in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype) + ["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.docstatus < 2 && !this.frm.doc.is_return ) { @@ -60,7 +60,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } if ( - in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) + ["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_pos && this.frm.doc.is_return ) { @@ -69,7 +69,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } // Sales person's commission - if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) { + if (["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"].includes(this.frm.doc.doctype)) { this.calculate_commission(); this.calculate_contribution(); } @@ -562,7 +562,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { ? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment) : this.frm.doc.net_total); - if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) { + if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) { this.frm.doc.base_grand_total = (this.frm.doc.total_taxes_and_charges) ? flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total; } else { @@ -570,7 +570,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.taxes_and_charges_added = this.frm.doc.taxes_and_charges_deducted = 0.0; if(tax_count) { $.each(this.frm.doc["taxes"] || [], function(i, tax) { - if (in_list(["Valuation and Total", "Total"], tax.category)) { + if (["Valuation and Total", "Total"].includes(tax.category)) { if(tax.add_deduct_tax == "Add") { me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount); } else { @@ -717,7 +717,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var actual_taxes_dict = {}; $.each(this.frm.doc["taxes"] || [], function(i, tax) { - if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) { + if (["Actual", "On Item Quantity"].includes(tax.charge_type)) { var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount; tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; actual_taxes_dict[tax.idx] = tax_amount; @@ -762,7 +762,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { // NOTE: // paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice // total_advance is only for non POS Invoice - if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_return){ + if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_return){ this.calculate_paid_amount(); } @@ -770,7 +770,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); - if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) { + if(["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) { let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total; @@ -793,7 +793,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.refresh_field("base_paid_amount"); } - if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) { + if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) { let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount) ? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total")) : total_amount_to_pay; @@ -897,7 +897,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { calculate_change_amount(){ this.frm.doc.change_amount = 0.0; this.frm.doc.base_change_amount = 0.0; - if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) + if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) { var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; }); diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 92cd737e465..9e3fac15305 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -315,7 +315,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } setup_quality_inspection() { - if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)) { + if(!["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)) { return; } @@ -327,7 +327,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.page.set_inner_btn_group_as_primary(__('Create')); } - const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype) + const inspection_type = ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype) ? "Incoming" : "Outgoing"; let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); @@ -359,7 +359,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe make_payment_request() { let me = this; - const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype)) + const payment_request_type = (['Sales Order', 'Sales Invoice'].includes(this.frm.doc.doctype)) ? "Inward" : "Outward"; frappe.call({ @@ -474,7 +474,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe setup_sms() { var me = this; let blacklist = ['Purchase Invoice', 'BOM']; - if(this.frm.doc.docstatus===1 && !in_list(["Lost", "Stopped", "Closed"], this.frm.doc.status) + if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status) && !blacklist.includes(this.frm.doctype)) { this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); }); } @@ -760,7 +760,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } on_submit() { - if (in_list(["Purchase Invoice", "Sales Invoice"], this.frm.doc.doctype) + if (["Purchase Invoice", "Sales Invoice"].includes(this.frm.doc.doctype) && !this.frm.doc.update_stock) { return; } @@ -864,7 +864,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } var set_party_account = function(set_pricing) { - if (in_list(["Sales Invoice", "Purchase Invoice"], me.frm.doc.doctype)) { + if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) { if(me.frm.doc.doctype=="Sales Invoice") { var party_type = "Customer"; var party_account_field = 'debit_to'; @@ -899,7 +899,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") && - in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) { + ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doctype)) { erpnext.utils.get_shipping_address(this.frm, function() { set_party_account(set_pricing); }); @@ -1610,7 +1610,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "doctype": me.frm.doc.doctype, "name": me.frm.doc.name, "is_return": cint(me.frm.doc.is_return), - "update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0, + "update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0, "conversion_factor": me.frm.doc.conversion_factor, "pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '', "coupon_code": me.frm.doc.coupon_code @@ -2256,7 +2256,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe get_method_for_payment() { var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){ - if(in_list(['Sales Invoice', 'Purchase Invoice'], cur_frm.doc.doctype)){ + if(['Sales Invoice', 'Purchase Invoice'].includes( cur_frm.doc.doctype)){ method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice"; }else { method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order"; @@ -2496,7 +2496,7 @@ erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close } frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { - if (in_list(["Sales Invoice", "Delivery Note"], frm.doc.doctype)) { + if (["Sales Invoice", "Delivery Note"].includes(frm.doc.doctype)) { item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward"; } else { item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward"; diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js index 0e584205396..c91bb046a52 100644 --- a/erpnext/public/js/payment/payments.js +++ b/erpnext/public/js/payment/payments.js @@ -218,7 +218,7 @@ erpnext.payments = class payments extends erpnext.stock.StockController { update_paid_amount(update_write_off) { var me = this; - if (in_list(["change_amount", "write_off_amount"], this.idx)) { + if (["change_amount", "write_off_amount"].includes(this.idx)) { var value = me.selected_mode.val(); if (me.idx == "change_amount") { me.change_amount(value); diff --git a/erpnext/public/js/sms_manager.js b/erpnext/public/js/sms_manager.js index d3147bb4600..63833da5af3 100644 --- a/erpnext/public/js/sms_manager.js +++ b/erpnext/public/js/sms_manager.js @@ -28,11 +28,11 @@ erpnext.SMSManager = function SMSManager(doc) { "Purchase Receipt": "Items has been received against purchase receipt: " + doc.name, }; - if (in_list(["Sales Order", "Delivery Note", "Sales Invoice"], doc.doctype)) + if (["Sales Order", "Delivery Note", "Sales Invoice"].includes(doc.doctype)) this.show(doc.contact_person, "Customer", doc.customer, "", default_msg[doc.doctype]); else if (doc.doctype === "Quotation") this.show(doc.contact_person, "Customer", doc.party_name, "", default_msg[doc.doctype]); - else if (in_list(["Purchase Order", "Purchase Receipt"], doc.doctype)) + else if (["Purchase Order", "Purchase Receipt"].includes(doc.doctype)) this.show(doc.contact_person, "Supplier", doc.supplier, "", default_msg[doc.doctype]); else if (doc.doctype == "Lead") this.show("", "", "", doc.mobile_no, default_msg[doc.doctype]); else if (doc.doctype == "Opportunity") diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 801376b2ed7..623941755d1 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -14,10 +14,10 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) { if (!args) { if ( (frm.doctype != "Purchase Order" && frm.doc.customer) || - (frm.doc.party_name && in_list(["Quotation", "Opportunity"], frm.doc.doctype)) + (frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype)) ) { let party_type = "Customer"; - if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) { + if (frm.doc.quotation_to && ["Lead", "Prospect"].includes(frm.doc.quotation_to)) { party_type = frm.doc.quotation_to; } diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index f2b7331cf3b..00df1c5c191 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -303,7 +303,7 @@ erpnext.sales_common = { if ((doc.packed_items || []).length) { $(this.frm.fields_dict.packing_list.row.wrapper).toggle(true); - if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) { + if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) { var help_msg = "
" + __( @@ -315,7 +315,7 @@ erpnext.sales_common = { } } else { $(this.frm.fields_dict.packing_list.row.wrapper).toggle(false); - if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) { + if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) { frappe.meta.get_docfield(doc.doctype, "product_bundle_help", doc.name).options = ""; } } @@ -416,7 +416,7 @@ erpnext.sales_common = { project() { let me = this; - if (in_list(["Delivery Note", "Sales Invoice", "Sales Order"], this.frm.doc.doctype)) { + if (["Delivery Note", "Sales Invoice", "Sales Order"].includes(this.frm.doc.doctype)) { if (this.frm.doc.project) { frappe.call({ method: "erpnext.projects.doctype.project.project.get_cost_center_name", diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index 5fbb5cb7e01..7aa8012f0b6 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -34,7 +34,7 @@ frappe.ui.form.on("Import Supplier Invoice", { }, toggle_read_only_fields: function (frm) { - if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) { + if (["File Import Completed", "Processing File Data"].includes(frm.doc.status)) { cur_frm.set_read_only(); cur_frm.refresh_fields(); frm.set_df_property("import_invoices", "hidden", 1); diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index 448dbcab43a..c399005643c 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -73,7 +73,7 @@ erpnext.PointOfSale.PastOrderSummary = class { const { status } = doc; let indicator_color = ""; - in_list(["Paid", "Consolidated"], status) && (indicator_color = "green"); + ["Paid", "Consolidated"].includes(status) && (indicator_color = "green"); status === "Draft" && (indicator_color = "red"); status === "Return" && (indicator_color = "grey"); diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js index 0f0221fa562..aec752aec77 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js @@ -8,7 +8,7 @@ frappe.ui.form.on("Closing Stock Balance", { }, generate_closing_balance(frm) { - if (in_list(["Queued", "Failed"], frm.doc.status)) { + if (["Queued", "Failed"].includes(frm.doc.status)) { frm.add_custom_button(__("Generate Closing Stock Balance"), () => { frm.call({ method: "enqueue_job", diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js b/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js index 230107caadb..65a1be33224 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js @@ -1,9 +1,9 @@ frappe.listview_settings["Delivery Trip"] = { add_fields: ["status"], get_indicator: function (doc) { - if (in_list(["Cancelled", "Draft"], doc.status)) { + if (["Cancelled", "Draft"].includes(doc.status)) { return [__(doc.status), "red", "status,=," + doc.status]; - } else if (in_list(["In Transit", "Scheduled"], doc.status)) { + } else if (["In Transit", "Scheduled"].includes(doc.status)) { return [__(doc.status), "orange", "status,=," + doc.status]; } else if (doc.status === "Completed") { return [__(doc.status), "green", "status,=," + doc.status]; diff --git a/erpnext/templates/form_grid/item_grid.html b/erpnext/templates/form_grid/item_grid.html index 027046fd92f..72db6c8e653 100644 --- a/erpnext/templates/form_grid/item_grid.html +++ b/erpnext/templates/form_grid/item_grid.html @@ -18,7 +18,7 @@ actual_qty = (frm.doc.doctype==="Sales Order" ? doc.projected_qty : doc.actual_qty); if(flt(frm.doc.per_delivered, 2) < 100 - && in_list(["Sales Order Item", "Delivery Note Item"], doc.doctype)) { + && ["Sales Order Item", "Delivery Note Item"].includes(doc.doctype)) { if(actual_qty != undefined) { if(actual_qty >= doc.qty) { var color = "green"; From 1e16d2ca25de711fc210392a6124f8e2d87a9585 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:34:23 +0100 Subject: [PATCH 52/53] fix(Supplier Quotation Comparison): group by options (cherry picked from commit 87e36d290e90fd5d2f958075273edaf99c827126) --- .../supplier_quotation_comparison.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js index c109abd8146..f7d0d947b61 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js @@ -77,7 +77,10 @@ frappe.query_reports["Supplier Quotation Comparison"] = { fieldname: "group_by", label: __("Group by"), fieldtype: "Select", - options: [__("Group by Supplier"), __("Group by Item")], + options: [ + { label: __("Group by Supplier"), value: "Group by Supplier" }, + { label: __("Group by Item"), value: "Group by Item" }, + ], default: __("Group by Supplier"), }, { From 1c9079787948f316336608bb4c2c741839188006 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 20:42:57 +0530 Subject: [PATCH 53/53] fix: currency symbol for Landed Cost Voucher Amount (backport #40550) (#40554) fix: currency symbol for Landed Cost Voucher Amount (#40550) (cherry picked from commit 0725707cb12e4431703273d12f4e39c6201a47ba) Co-authored-by: rohitwaghchaure --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 3ee4214ae71..66df76a3af0 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -745,6 +745,7 @@ "fieldtype": "Currency", "label": "Landed Cost Voucher Amount", "no_copy": 1, + "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 }, @@ -938,7 +939,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-02-04 14:11:52.742228", + "modified": "2024-03-19 19:09:47.210965", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item",