From 3340463b8e2a0ba36f5cc4e783f40759a259cbb6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 14 Mar 2024 11:39:29 +0530 Subject: [PATCH 01/22] fix: Data too long for column 'stock_queue' (#40450) --- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.json | 4 ++-- 1 file changed, 2 insertions(+), 2 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 b07c0876a25..0a666b44fbd 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -226,7 +226,7 @@ }, { "fieldname": "stock_queue", - "fieldtype": "Text", + "fieldtype": "Long Text", "label": "FIFO Stock Queue (qty, rate)", "oldfieldname": "fcfs_stack", "oldfieldtype": "Text", @@ -324,7 +324,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-10-23 18:07:42.063615", + "modified": "2024-03-13 09:56:13.021696", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", From 5a2eaf90923c245d5b366718164355e9d4a0e120 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 14 Mar 2024 12:17:23 +0530 Subject: [PATCH 02/22] fix: warehouse issue in pick list (#40453) --- erpnext/selling/doctype/sales_order/sales_order.py | 6 +++++- erpnext/stock/doctype/pick_list/pick_list.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 3821a237161..81f9cedbc62 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1305,7 +1305,11 @@ def create_pick_list(source_name, target_doc=None): "Sales Order", source_name, { - "Sales Order": {"doctype": "Pick List", "validation": {"docstatus": ["=", 1]}}, + "Sales Order": { + "doctype": "Pick List", + "field_map": {"set_warehouse": "parent_warehouse"}, + "validation": {"docstatus": ["=", 1]}, + }, "Sales Order Item": { "doctype": "Pick List Item", "field_map": {"parent": "sales_order", "name": "sales_order_item"}, diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 86462476a5d..fb202a31f58 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -188,9 +188,9 @@ class PickList(Document): picked_items_details = self.get_picked_items_details(items) self.item_location_map = frappe._dict() - from_warehouses = None + from_warehouses = [self.parent_warehouse] if self.parent_warehouse else [] if self.parent_warehouse: - from_warehouses = get_descendants_of("Warehouse", self.parent_warehouse) + from_warehouses.extend(get_descendants_of("Warehouse", self.parent_warehouse)) # Create replica before resetting, to handle empty table on update after submit. locations_replica = self.get("locations") From b011a68a2ac7b718b591ac699a37172ced3791e0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 14 Mar 2024 16:48:55 +0530 Subject: [PATCH 03/22] refactor: disallow '0' qty return invoices with stock effect (cherry picked from commit 898affbee902b5f1d76f3cc7c4876b8d2eeb1a46) # Conflicts: # erpnext/controllers/accounts_controller.py --- erpnext/controllers/accounts_controller.py | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 8055b8180de..771c03c2179 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -157,6 +157,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) @@ -950,6 +957,46 @@ class AccountsController(TransactionBase): return gl_dict +<<<<<<< HEAD +======= + def get_voucher_subtype(self): + voucher_subtypes = { + "Journal Entry": "voucher_type", + "Payment Entry": "payment_type", + "Stock Entry": "stock_entry_type", + "Asset Capitalization": "entry_type", + } + if self.doctype in voucher_subtypes: + return self.get(voucher_subtypes[self.doctype]) + elif self.doctype == "Purchase Receipt" and self.is_return: + return "Purchase Return" + elif self.doctype == "Delivery Note" and self.is_return: + return "Sales Return" + elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice": + return "Credit Note" + elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice": + return "Debit Note" + return self.doctype + + def get_value_in_transaction_currency(self, account_currency, args, field): + if account_currency == self.get("currency"): + return args.get(field + "_in_account_currency") + 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]))) + ) + +>>>>>>> 898affbee9 (refactor: disallow '0' qty return invoices with stock effect) def validate_qty_is_not_zero(self): if self.doctype == "Purchase Receipt": return From a914eb5d4010962d6c0d567155cf7f820b65c7d7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 10:47:32 +0530 Subject: [PATCH 04/22] 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 ce5456da5d3..4d0f6446da4 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1569,6 +1569,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 0437274c58e4c6f3024b052254174fb7b608d4b5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 11:39:54 +0530 Subject: [PATCH 05/22] chore: resolve conflict --- erpnext/controllers/accounts_controller.py | 28 ---------------------- 1 file changed, 28 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 771c03c2179..61bdc4f52a9 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -957,33 +957,6 @@ class AccountsController(TransactionBase): return gl_dict -<<<<<<< HEAD -======= - def get_voucher_subtype(self): - voucher_subtypes = { - "Journal Entry": "voucher_type", - "Payment Entry": "payment_type", - "Stock Entry": "stock_entry_type", - "Asset Capitalization": "entry_type", - } - if self.doctype in voucher_subtypes: - return self.get(voucher_subtypes[self.doctype]) - elif self.doctype == "Purchase Receipt" and self.is_return: - return "Purchase Return" - elif self.doctype == "Delivery Note" and self.is_return: - return "Sales Return" - elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice": - return "Credit Note" - elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice": - return "Debit Note" - return self.doctype - - def get_value_in_transaction_currency(self, account_currency, args, field): - if account_currency == self.get("currency"): - return args.get(field + "_in_account_currency") - 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: @@ -996,7 +969,6 @@ class AccountsController(TransactionBase): ).format(frappe.bold(comma_and(["#" + str(x.idx) for x in rows]))) ) ->>>>>>> 898affbee9 (refactor: disallow '0' qty return invoices with stock effect) def validate_qty_is_not_zero(self): if self.doctype == "Purchase Receipt": return From 90a5155fc122cb31320f1d97568918e75d5afd59 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 16:44:54 +0530 Subject: [PATCH 06/22] 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 1e8a912030a..865362fbb48 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2169,7 +2169,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", @@ -2182,7 +2183,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 5f4a648dd4f91b4f8d8e076cd83ad4a92271ddae Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 13:05:07 +0530 Subject: [PATCH 07/22] refactor: validate SO and SI references (cherry picked from commit 4d090bd3b83c25e18b3e44b1f7dd75dd38faba7f) # Conflicts: # erpnext/stock/doctype/delivery_note/delivery_note.py --- .../doctype/delivery_note/delivery_note.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 4216ca0cdc3..c0a4b4fd09b 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -130,6 +130,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() @@ -195,6 +196,91 @@ class DeliveryNote(SellingController): ] ) +<<<<<<< HEAD +======= + def set_serial_and_batch_bundle_from_pick_list(self): + from erpnext.stock.serial_batch_bundle import SerialBatchCreation + + if not self.pick_list: + return + + for item in self.items: + if item.pick_list_item and not item.serial_and_batch_bundle: + filters = { + "item_code": item.item_code, + "voucher_type": "Pick List", + "voucher_no": self.pick_list, + "voucher_detail_no": item.pick_list_item, + } + + bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name") + + if bundle_id: + cls_obj = SerialBatchCreation( + { + "type_of_transaction": "Outward", + "serial_and_batch_bundle": bundle_id, + "item_code": item.get("item_code"), + } + ) + + cls_obj.duplicate_package() + + 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")) + +>>>>>>> 4d090bd3b8 (refactor: validate SO and SI references) def validate_proj_cust(self): """check for does customer belong to same project as entered..""" if self.project and self.customer: From f87f284616add2a3241b20713ec297b1cfd0f25a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 16 Mar 2024 14:39:57 +0530 Subject: [PATCH 08/22] test: SO reference validation (cherry picked from commit 4f396d304979a23c3265c467112aa1dbc99932bd) # Conflicts: # erpnext/selling/doctype/sales_order/test_sales_order.py --- erpnext/selling/doctype/sales_order/test_sales_order.py | 8 +++++++- .../stock/doctype/delivery_note/test_delivery_note.py | 9 +++++++++ 2 files changed, 16 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 2de0168812d..e971b4a53cd 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2225,13 +2225,19 @@ def make_sales_order(**args): return so +<<<<<<< HEAD def create_dn_against_so(so, delivered_qty=0): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) +======= +def create_dn_against_so(so, delivered_qty=0, do_not_submit=False): + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) +>>>>>>> 4f396d3049 (test: SO reference validation) 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 9f2c0be75d0..6e735ea67b7 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -723,6 +723,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 9d827e84beed72f66cd2ad8ef9300fce3ceda868 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 16 Mar 2024 15:01:46 +0530 Subject: [PATCH 09/22] chore: resolve conflicts --- .../doctype/sales_order/test_sales_order.py | 5 --- .../doctype/delivery_note/delivery_note.py | 33 ------------------- 2 files changed, 38 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index e971b4a53cd..69180d23d29 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2225,13 +2225,8 @@ def make_sales_order(**args): return so -<<<<<<< HEAD -def create_dn_against_so(so, delivered_qty=0): - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) -======= def create_dn_against_so(so, delivered_qty=0, do_not_submit=False): frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) ->>>>>>> 4f396d3049 (test: SO reference validation) dn = make_delivery_note(so) dn.get("items")[0].qty = delivered_qty or 5 diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index c0a4b4fd09b..a402bb5aed2 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -196,38 +196,6 @@ class DeliveryNote(SellingController): ] ) -<<<<<<< HEAD -======= - def set_serial_and_batch_bundle_from_pick_list(self): - from erpnext.stock.serial_batch_bundle import SerialBatchCreation - - if not self.pick_list: - return - - for item in self.items: - if item.pick_list_item and not item.serial_and_batch_bundle: - filters = { - "item_code": item.item_code, - "voucher_type": "Pick List", - "voucher_no": self.pick_list, - "voucher_detail_no": item.pick_list_item, - } - - bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name") - - if bundle_id: - cls_obj = SerialBatchCreation( - { - "type_of_transaction": "Outward", - "serial_and_batch_bundle": bundle_id, - "item_code": item.get("item_code"), - } - ) - - cls_obj.duplicate_package() - - 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() @@ -280,7 +248,6 @@ class DeliveryNote(SellingController): if err_msg: frappe.throw(err_msg, title=_("References to Sales Invoices are Incomplete")) ->>>>>>> 4d090bd3b8 (refactor: validate SO and SI references) def validate_proj_cust(self): """check for does customer belong to same project as entered..""" if self.project and self.customer: From 8813b6b2fd7334cf7125a7ab526b4896a29b26be Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 17 Mar 2024 07:32:13 +0530 Subject: [PATCH 10/22] 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 977cfe94f8a..00d62f3f601 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -606,21 +606,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 a27f386665612da003420e3a9509b996e3b791f7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 15:34:21 +0530 Subject: [PATCH 11/22] 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 d04f0ac7f3c..e0e8e2154a6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -350,7 +350,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 fe78214ea43a41b4dad4e6a5421a4e929bdeca82 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 20:24:05 +0530 Subject: [PATCH 12/22] 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 eb4396a5c6f..662077d027e 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 379616ab4b51d245eceb96a98967c1c23c6d89d7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 08:22:28 +0530 Subject: [PATCH 13/22] refactor: toggle between 'http' and 'https' on exchange rate API (cherry picked from commit 8b81274769f13a5b7ec91d79f4d2a13d48faeb9c) # Conflicts: # erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py --- .../currency_exchange_settings.js | 46 +++++++++++------- .../currency_exchange_settings.json | 10 +++- .../currency_exchange_settings.py | 48 ++++++++++++++++++- 3 files changed, 85 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 117d5ff21e8..31e42d0db48 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py @@ -9,6 +9,34 @@ from frappe.utils import nowdate class CurrencyExchangeSettings(Document): +<<<<<<< HEAD +======= + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + from erpnext.accounts.doctype.currency_exchange_settings_details.currency_exchange_settings_details import ( + CurrencyExchangeSettingsDetails, + ) + from erpnext.accounts.doctype.currency_exchange_settings_result.currency_exchange_settings_result import ( + CurrencyExchangeSettingsResult, + ) + + access_key: DF.Data | None + api_endpoint: DF.Data + disabled: DF.Check + req_params: DF.Table[CurrencyExchangeSettingsDetails] + 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 + +>>>>>>> 8b81274769 (refactor: toggle between 'http' and 'https' on exchange rate API) def validate(self): self.set_parameters_and_result() if frappe.flags.in_test or frappe.flags.in_install or frappe.flags.in_setup_wizard: @@ -29,7 +57,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"}) @@ -40,7 +68,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}"}) @@ -79,3 +107,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 33efa6960d7f4744e40df0aa567bcd22e008fe31 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 19 Mar 2024 10:14:24 +0530 Subject: [PATCH 14/22] chore: resolve conflicts --- .../currency_exchange_settings.py | 28 ------------------- 1 file changed, 28 deletions(-) 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 31e42d0db48..7a420f984ad 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py @@ -9,34 +9,6 @@ from frappe.utils import nowdate class CurrencyExchangeSettings(Document): -<<<<<<< HEAD -======= - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - from erpnext.accounts.doctype.currency_exchange_settings_details.currency_exchange_settings_details import ( - CurrencyExchangeSettingsDetails, - ) - from erpnext.accounts.doctype.currency_exchange_settings_result.currency_exchange_settings_result import ( - CurrencyExchangeSettingsResult, - ) - - access_key: DF.Data | None - api_endpoint: DF.Data - disabled: DF.Check - req_params: DF.Table[CurrencyExchangeSettingsDetails] - 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 - ->>>>>>> 8b81274769 (refactor: toggle between 'http' and 'https' on exchange rate API) def validate(self): self.set_parameters_and_result() if frappe.flags.in_test or frappe.flags.in_install or frappe.flags.in_setup_wizard: From 9f0ad7a4ea415a8b2f94631338810d2cc00f94d0 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 19 Mar 2024 12:12:26 +0530 Subject: [PATCH 15/22] 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 cfed706c150f3bf803faeed0605ff1a0ebd078ad 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 16/22] 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 b197c1926fb618a744001e5902fcfd425eb90f48 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 19 Mar 2024 17:54:35 +0530 Subject: [PATCH 17/22] fix: flaky ecommerce test case (#40534) * refactor(test): make use of fixtures * refactor(test): rating are now restricted with [0..1] * refactor(test): remove superfluous setup override --- .../doctype/item_review/test_item_review.py | 11 +++++------ .../doctype/website_item/test_website_item.py | 15 +++++++-------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/erpnext/e_commerce/doctype/item_review/test_item_review.py b/erpnext/e_commerce/doctype/item_review/test_item_review.py index 8a4befc800a..6147a9153e6 100644 --- a/erpnext/e_commerce/doctype/item_review/test_item_review.py +++ b/erpnext/e_commerce/doctype/item_review/test_item_review.py @@ -5,6 +5,7 @@ import unittest import frappe from frappe.core.doctype.user_permission.test_user_permission import create_user +from frappe.tests.utils import FrappeTestCase from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( setup_e_commerce_settings, @@ -19,7 +20,7 @@ from erpnext.e_commerce.shopping_cart.cart import get_party from erpnext.stock.doctype.item.test_item import make_item -class TestItemReview(unittest.TestCase): +class TestItemReview(FrappeTestCase): def setUp(self): item = make_item("Test Mobile Phone") if not frappe.db.exists("Website Item", {"item_code": "Test Mobile Phone"}): @@ -29,8 +30,7 @@ class TestItemReview(unittest.TestCase): frappe.local.shopping_cart_settings = None def tearDown(self): - frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete() - setup_e_commerce_settings({"enable_reviews": 0}) + frappe.db.rollback() def test_add_and_get_item_reviews_from_customer(self): "Add / Get Reviews from a User that is a valid customer (has added to cart or purchased in the past)" @@ -44,7 +44,7 @@ class TestItemReview(unittest.TestCase): # post review on "Test Mobile Phone" try: - add_item_review(web_item, "Great Product", 3, "Would recommend this product") + add_item_review(web_item, "Great Product", 1, "Would recommend this product") review_name = frappe.db.get_value("Item Review", {"website_item": web_item}) except Exception: self.fail(f"Error while publishing review for {web_item}") @@ -52,8 +52,7 @@ class TestItemReview(unittest.TestCase): review_data = get_item_reviews(web_item, 0, 10) self.assertEqual(len(review_data.reviews), 1) - self.assertEqual(review_data.average_rating, 3) - self.assertEqual(review_data.reviews_per_rating[2], 100) + self.assertEqual(review_data.average_rating, 1) # tear down frappe.set_user("Administrator") diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index 8eebfdb83af..8dffb4364e1 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -24,10 +24,11 @@ WEBITEM_PRICE_TESTS = ( "test_website_item_price_for_guest_user", ) +from frappe.tests.utils import FrappeTestCase -class TestWebsiteItem(unittest.TestCase): - @classmethod - def setUpClass(cls): + +class TestWebsiteItem(FrappeTestCase): + def setUp(self): setup_e_commerce_settings( { "company": "_Test Company", @@ -37,11 +38,6 @@ class TestWebsiteItem(unittest.TestCase): } ) - @classmethod - def tearDownClass(cls): - frappe.db.rollback() - - def setUp(self): if self._testMethodName in WEBITEM_DESK_TESTS: make_item( "Test Web Item", @@ -75,6 +71,9 @@ class TestWebsiteItem(unittest.TestCase): customer="_Test Customer", ) + def tearDown(self): + frappe.db.rollback() + def test_index_creation(self): "Check if index is getting created in db." from erpnext.e_commerce.doctype.website_item.website_item import on_doctype_update From 64f1844bc89b114856f3dceb6b3ba9099cd9c18b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:11:50 +0530 Subject: [PATCH 18/22] fix: Update Existing Price List Rate not working (backport #40333) (#40526) * fix: Update Existing Price List Rate not working (#40333) (cherry picked from commit 09ea7edb863dce39e28c799ea8e4dc0b12a3c69a) # Conflicts: # erpnext/selling/doctype/sales_order/test_sales_order.py # erpnext/setup/demo_data/item.json * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- .../doctype/sales_order/test_sales_order.py | 34 +++++++ erpnext/setup/demo_data/item.json | 92 +++++++++++++++++++ erpnext/stock/get_item_details.py | 4 +- 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 erpnext/setup/demo_data/item.json diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 69180d23d29..ee7cb323285 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2160,6 +2160,40 @@ class TestSalesOrder(FrappeTestCase): self.assertFalse(row.warehouse == rejected_warehouse) self.assertTrue(row.warehouse == warehouse) + 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 new file mode 100644 index 00000000000..17024341225 --- /dev/null +++ b/erpnext/setup/demo_data/item.json @@ -0,0 +1,92 @@ +[ + { + "doctype": "Item", + "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" + }, + { + "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" + }, + { + "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" + }, + { + "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" + }, + { + "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" + }, + { + "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" + }, + { + "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" + }, + { + "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" + }, + { + "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" + }, + { + "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 831fcac93ce..289a654ac12 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -854,7 +854,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 0aef7f9671477d9fa86b9312472882954dd6571b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 18 Mar 2024 21:57:12 +0530 Subject: [PATCH 19/22] fix: provisional entry for non-stock item --- .../purchase_invoice/purchase_invoice.py | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 4719876f4ec..14c7e0bf637 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -737,7 +737,6 @@ class PurchaseInvoice(BuyingController): "Company", self.company, "enable_provisional_accounting_for_non_stock_items" ) ) - self.provisional_enpenses_booked_in_pr = False if provisional_accounting_for_non_stock_items: self.get_provisional_accounts() @@ -982,37 +981,34 @@ class PurchaseInvoice(BuyingController): fields=["name", "provisional_expense_account", "qty", "base_rate"], ) default_provisional_account = self.get_company_default("default_provisional_account") + provisional_accounts = [ + d.provisional_expense_account if d.provisional_expense_account else default_provisional_account + for d in pr_items + ] + + provisional_gl_entries = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": "Purchase Receipt", + "voucher_no": ("in", linked_purchase_receipts), + "account": ("in", provisional_accounts), + "is_cancelled": 0, + }, + fields=["voucher_detail_no"], + ) + rows_with_provisional_entries = [d.voucher_detail_no for d in provisional_gl_entries] for item in pr_items: self.provisional_accounts[item.name] = { "provisional_account": item.provisional_expense_account or default_provisional_account, "qty": item.qty, "base_rate": item.base_rate, + "has_provisional_entry": item.name in rows_with_provisional_entries, } def make_provisional_gl_entry(self, gl_entries, item): if item.purchase_receipt: - if not self.provisional_enpenses_booked_in_pr: - pr_item = self.provisional_accounts.get(item.pr_detail, {}) - provisional_account = pr_item.get("provisional_account") - pr_qty = pr_item.get("qty") - pr_base_rate = pr_item.get("base_rate") - - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - provision_gle_against_pr = frappe.db.get_value( - "GL Entry", - { - "is_cancelled": 0, - "voucher_type": "Purchase Receipt", - "voucher_no": item.purchase_receipt, - "voucher_detail_no": item.pr_detail, - "account": provisional_account, - }, - ["name"], - ) - if provision_gle_against_pr: - self.provisional_enpenses_booked_in_pr = True - - if self.provisional_enpenses_booked_in_pr: + pr_item = self.provisional_accounts.get(item.pr_detail, {}) + if pr_item.get("has_provisional_entry"): purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt) # Intentionally passing purchase invoice item to handle partial billing @@ -1020,9 +1016,9 @@ class PurchaseInvoice(BuyingController): item, gl_entries, self.posting_date, - provisional_account, + pr_item.get("provisional_account"), reverse=1, - item_amount=(min(item.qty, pr_qty) * pr_base_rate), + item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")), ) def update_gross_purchase_amount_for_linked_assets(self, item): From a1c8b93e99173530aae14ae8bcfa492186affd49 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 19 Mar 2024 18:35:11 +0530 Subject: [PATCH 20/22] fix: added index for voucher_detail_no in gl entry --- erpnext/accounts/doctype/gl_entry/gl_entry.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 592eaecc1c5..eb99768b05e 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -174,7 +174,8 @@ "fieldname": "voucher_detail_no", "fieldtype": "Data", "label": "Voucher Detail No", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "project", @@ -256,7 +257,8 @@ "icon": "fa fa-list", "idx": 1, "in_create": 1, - "modified": "2020-04-07 16:22:33.766994", + "links": [], + "modified": "2024-03-19 18:30:49.613401", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", @@ -288,5 +290,6 @@ "quick_entry": 1, "search_fields": "voucher_no,account,posting_date,against_voucher", "sort_field": "modified", - "sort_order": "DESC" -} + "sort_order": "DESC", + "states": [] +} \ No newline at end of file From 39d958c507091ccbb76e80331d1abd198f0b538c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 19 Mar 2024 21:06:16 +0530 Subject: [PATCH 21/22] fix: get unique provisional accounts --- .../doctype/purchase_invoice/purchase_invoice.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 14c7e0bf637..f54787de717 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -981,10 +981,12 @@ class PurchaseInvoice(BuyingController): fields=["name", "provisional_expense_account", "qty", "base_rate"], ) default_provisional_account = self.get_company_default("default_provisional_account") - provisional_accounts = [ - d.provisional_expense_account if d.provisional_expense_account else default_provisional_account - for d in pr_items - ] + provisional_accounts = set( + [ + d.provisional_expense_account if d.provisional_expense_account else default_provisional_account + for d in pr_items + ] + ) provisional_gl_entries = frappe.get_all( "GL Entry", From 785131121d5fb9b016ff98c37327808c43ff2457 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 23:37:32 +0530 Subject: [PATCH 22/22] fix: currency symbol for Landed Cost Voucher Amount (backport #40550) (#40553) * fix: currency symbol for Landed Cost Voucher Amount (#40550) (cherry picked from commit 0725707cb12e4431703273d12f4e39c6201a47ba) # Conflicts: # erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 3d59d288e4d..cafdc0e12c6 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -740,6 +740,7 @@ "fieldtype": "Currency", "label": "Landed Cost Voucher Amount", "no_copy": 1, + "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 }, @@ -893,7 +894,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-12-25 22:00:28.043555", + "modified": "2024-03-19 19:09:47.210965", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", @@ -903,4 +904,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +}