From 0513481ec8bcc86ba0f11818f0e1591b12b38553 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 30 Apr 2024 13:28:43 +0530 Subject: [PATCH 01/28] fix: Purchase Invoice gain loss gl entry for periodic inventory (cherry picked from commit 24024475681e93d58697a1c6d740f70ebaf88f7b) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index dbc9ab474d2..272a180ca8d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1091,7 +1091,7 @@ class PurchaseInvoice(BuyingController): ) # check if the exchange rate has changed - if item.get("purchase_receipt"): + if item.get("purchase_receipt") and self.auto_accounting_for_stock: if ( exchange_rate_map[item.purchase_receipt] and self.conversion_rate != exchange_rate_map[item.purchase_receipt] From 112f96bae0d7734e2db56aa510bf6bf3bd8248fd Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 10:28:08 +0530 Subject: [PATCH 02/28] fix: search for item price in stock UOM (backport #41075) (#41313) fix: search for item price in stock UOM (#41075) (cherry picked from commit e4db0562ac7e6e1e7137989052e35975a9e04a1c) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/stock/get_item_details.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 22f318d9908..5da3c066869 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -838,7 +838,12 @@ def insert_item_price(args): item_price = frappe.db.get_value( "Item Price", - {"item_code": args.item_code, "price_list": args.price_list, "currency": args.currency}, + { + "item_code": args.item_code, + "price_list": args.price_list, + "currency": args.currency, + "uom": args.stock_uom, + }, ["name", "price_list_rate"], as_dict=1, ) From cab0e30cebc5c7a2c9f6a264a7bd554d5b89e58f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 11:17:12 +0530 Subject: [PATCH 03/28] feat(Item Price): make UOM mandatory (backport #40588) (#41312) * feat(Item Price): make UOM mandatory (#40588) (cherry picked from commit a61148c464def5b1cc8f60d263e18a745f986bee) # Conflicts: # erpnext/stock/doctype/item_price/item_price.json * chore: `conflicts` --------- Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Co-authored-by: s-aga-r --- erpnext/stock/doctype/item_price/item_price.json | 7 +++++-- erpnext/stock/doctype/item_price/item_price.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index b92026dc97b..3daf4dc2bd8 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -52,10 +52,13 @@ "search_index": 1 }, { + "fetch_from": "item_code.stock_uom", + "fetch_if_empty": 1, "fieldname": "uom", "fieldtype": "Link", "label": "UOM", - "options": "UOM" + "options": "UOM", + "reqd": 1 }, { "default": "0", @@ -220,7 +223,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-01-30 14:02:19.304854", + "modified": "2024-04-02 22:18:00.450641", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 34c486b37ac..5445e1b88b0 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -38,7 +38,7 @@ class ItemPrice(Document): reference: DF.Data | None selling: DF.Check supplier: DF.Link | None - uom: DF.Link | None + uom: DF.Link valid_from: DF.Date | None valid_upto: DF.Date | None # end: auto-generated types From b27a55e802899c4fc6d3508c19614f0e3d3d5366 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 27 Apr 2024 23:07:14 +0530 Subject: [PATCH 04/28] fix: future subscripition updates (cherry picked from commit 8e30debc1017193ffa5f0cb9c56d3ebfe7a1f09f) --- .../doctype/subscription/subscription.py | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 0d5f24eb199..a9dbf333335 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -112,11 +112,7 @@ class Subscription(Document): """ _current_invoice_start = None - if ( - self.is_new_subscription() - and self.trial_period_end - and getdate(self.trial_period_end) > getdate(self.start_date) - ): + if self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date): _current_invoice_start = add_days(self.trial_period_end, 1) elif self.trial_period_start and self.is_trialling(): _current_invoice_start = self.trial_period_start @@ -143,7 +139,7 @@ class Subscription(Document): else: billing_cycle_info = self.get_billing_cycle_data() if billing_cycle_info: - if self.is_new_subscription() and getdate(self.start_date) < getdate(date): + if getdate(self.start_date) < getdate(date): _current_invoice_end = add_to_date(self.start_date, **billing_cycle_info) # For cases where trial period is for an entire billing interval @@ -234,14 +230,14 @@ class Subscription(Document): self.cancelation_date = getdate(posting_date) if self.status == "Cancelled" else None elif self.current_invoice_is_past_due() and not self.is_past_grace_period(): self.status = "Past Due Date" - elif not self.has_outstanding_invoice() or self.is_new_subscription(): + elif not self.has_outstanding_invoice(): self.status = "Active" def is_trialling(self) -> bool: """ Returns `True` if the `Subscription` is in trial period. """ - return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription() + return not self.period_has_passed(self.trial_period_end) @staticmethod def period_has_passed( @@ -288,14 +284,6 @@ class Subscription(Document): def invoice_document_type(self) -> str: return "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice" - def is_new_subscription(self) -> bool: - """ - Returns `True` if `Subscription` has never generated an invoice - """ - return self.is_new() or not frappe.db.exists( - {"doctype": self.invoice_document_type, "subscription": self.name} - ) - def validate(self) -> None: self.validate_trial_period() self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval()) @@ -604,7 +592,7 @@ class Subscription(Document): return False if self.generate_invoice_at == "Beginning of the current subscription period" and ( - getdate(posting_date) == getdate(self.current_invoice_start) or self.is_new_subscription() + getdate(posting_date) == getdate(self.current_invoice_start) ): return True elif self.generate_invoice_at == "Days before the current subscription period" and ( From 31254009cca12e27d36f1b97a0f73b80d24a11d1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 May 2024 09:25:26 +0530 Subject: [PATCH 05/28] test: Add posting dates (cherry picked from commit 7fa22069d82af34a807bade49cc5af105ea40ca7) --- erpnext/accounts/doctype/subscription/test_subscription.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index cae112d9440..cb4f0200478 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -445,11 +445,11 @@ class TestSubscription(FrappeTestCase): # Process subscription and create first invoice # Subscription status will be unpaid since due date has already passed - subscription.process() + subscription.process(posting_date="2018-01-01") self.assertEqual(len(subscription.invoices), 1) self.assertEqual(subscription.status, "Unpaid") - subscription.process() + subscription.process(posting_date="2018-04-01") self.assertEqual(len(subscription.invoices), 1) def test_multi_currency_subscription(self): @@ -462,7 +462,7 @@ class TestSubscription(FrappeTestCase): party=party, ) - subscription.process() + subscription.process(posting_date="2018-01-01") self.assertEqual(len(subscription.invoices), 1) self.assertEqual(subscription.status, "Unpaid") From e49a401c23e64c46d34cdc935339ca504ed07f06 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 1 May 2024 12:17:08 +0530 Subject: [PATCH 06/28] fix: GL Entries against orders as an advance (cherry picked from commit 8289f3c7247512dd651d7da0ffbd3a5ece7761d9) --- .../doctype/payment_entry/payment_entry.py | 4 +- .../payment_entry/test_payment_entry.py | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 2fb62b79ab1..26c88536e45 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1227,7 +1227,7 @@ class PaymentEntry(AccountsController): def add_advance_gl_entries(self, gl_entries: list, entry: object | dict | None): """ - If 'entry' is passed, GL enties only for that reference is added. + If 'entry' is passed, GL entries only for that reference is added. """ if self.book_advance_payments_in_separate_party_account: references = [x for x in self.get("references")] @@ -1239,8 +1239,6 @@ class PaymentEntry(AccountsController): "Sales Invoice", "Purchase Invoice", "Journal Entry", - "Sales Order", - "Purchase Order", "Payment Entry", ): self.add_advance_gl_for_reference(gl_entries, ref) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index cafdaaaa957..26ed33c22b5 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1440,6 +1440,68 @@ class TestPaymentEntry(FrappeTestCase): self.check_gl_entries() self.check_pl_entries() + def test_advance_as_liability_against_order(self): + from erpnext.buying.doctype.purchase_order.purchase_order import ( + make_purchase_invoice as _make_purchase_invoice, + ) + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + + company = "_Test Company" + + advance_account = create_account( + parent_account="Current Liabilities - _TC", + account_name="Advances Paid", + company=company, + account_type="Liability", + ) + + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_paid_account": advance_account, + }, + ) + + po = create_purchase_order(supplier="_Test Supplier") + pe = get_payment_entry("Purchase Order", po.name) + pe.save().submit() + + pre_reconciliation_gle = [ + {"account": advance_account, "debit": 5000.0, "credit": 0.0}, + {"account": "_Test Bank 2 - _TC", "debit": 0.0, "credit": 5000.0}, + ] + + self.voucher_no = pe.name + self.expected_gle = pre_reconciliation_gle + self.check_gl_entries() + + # Make Purchase Invoice against the order + pi = _make_purchase_invoice(po.name) + pi.append( + "advances", + { + "reference_type": pe.doctype, + "reference_name": pe.name, + "reference_row": pe.references[0].name, + "advance_amount": 5000, + "allocated_amount": 5000, + }, + ) + pi.save().submit() + + # # assert General and Payment Ledger entries post partial reconciliation + self.expected_gle = [ + {"account": pi.credit_to, "debit": 5000.0, "credit": 0.0}, + {"account": advance_account, "debit": 5000.0, "credit": 0.0}, + {"account": advance_account, "debit": 0.0, "credit": 5000.0}, + {"account": "_Test Bank 2 - _TC", "debit": 0.0, "credit": 5000.0}, + ] + + self.voucher_no = pe.name + self.check_gl_entries() + def check_pl_entries(self): ple = frappe.qb.DocType("Payment Ledger Entry") pl_entries = ( From c417b0c15a269b147c3efef560c838a37f8a2601 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 May 2024 15:56:29 +0530 Subject: [PATCH 07/28] fix: Add PO reference (cherry picked from commit eb310170582ae0bdaa50d36ef5ffa1c443641afb) --- .../doctype/payment_entry/payment_entry.py | 101 ++++++++---------- 1 file changed, 42 insertions(+), 59 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 26c88536e45..453a7b1202d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1131,73 +1131,49 @@ class PaymentEntry(AccountsController): ) dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" - if self.book_advance_payments_in_separate_party_account: + + for d in self.get("references"): + # re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse + dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" + cost_center = self.cost_center + if d.reference_doctype == "Sales Invoice" and not cost_center: + cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center") + gle = party_gl_dict.copy() - if self.payment_type == "Receive": - amount = self.base_paid_amount - else: - amount = self.base_received_amount + allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) + reverse_dr_or_cr = 0 + + if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: + is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") + payable_party_types = get_party_types_from_account_type("Payable") + receivable_party_types = get_party_types_from_account_type("Receivable") + if ( + is_return + and self.party_type in receivable_party_types + and (self.payment_type == "Pay") + ): + reverse_dr_or_cr = 1 + elif ( + is_return + and self.party_type in payable_party_types + and (self.payment_type == "Receive") + ): + reverse_dr_or_cr = 1 + + if is_return and not reverse_dr_or_cr: + dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" - exchange_rate = self.get_exchange_rate() - amount_in_account_currency = amount * exchange_rate gle.update( { - dr_or_cr: amount, - dr_or_cr + "_in_account_currency": amount_in_account_currency, - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, - "cost_center": self.cost_center, + dr_or_cr: abs(allocated_amount_in_company_currency), + dr_or_cr + "_in_account_currency": abs(d.allocated_amount), + "against_voucher_type": d.reference_doctype, + "against_voucher": d.reference_name, + "cost_center": cost_center, } ) gl_entries.append(gle) - else: - for d in self.get("references"): - # re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse - dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" - cost_center = self.cost_center - if d.reference_doctype == "Sales Invoice" and not cost_center: - cost_center = frappe.db.get_value( - d.reference_doctype, d.reference_name, "cost_center" - ) - - gle = party_gl_dict.copy() - - allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference( - d - ) - reverse_dr_or_cr = 0 - - if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: - is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") - payable_party_types = get_party_types_from_account_type("Payable") - receivable_party_types = get_party_types_from_account_type("Receivable") - if ( - is_return - and self.party_type in receivable_party_types - and (self.payment_type == "Pay") - ): - reverse_dr_or_cr = 1 - elif ( - is_return - and self.party_type in payable_party_types - and (self.payment_type == "Receive") - ): - reverse_dr_or_cr = 1 - - if is_return and not reverse_dr_or_cr: - dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" - - gle.update( - { - dr_or_cr: abs(allocated_amount_in_company_currency), - dr_or_cr + "_in_account_currency": abs(d.allocated_amount), - "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name, - "cost_center": cost_center, - } - ) - gl_entries.append(gle) if self.unallocated_amount: dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" @@ -1212,6 +1188,13 @@ class PaymentEntry(AccountsController): } ) + if self.book_advance_payments_in_separate_party_account: + gle.update( + { + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, + } + ) gl_entries.append(gle) def make_advance_gl_entries( From d384860c345756e8c821969b65c681bab2202efd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 May 2024 17:04:37 +0530 Subject: [PATCH 08/28] test: Update failing tests (cherry picked from commit 42ef95759d75a572465b26a6aab9a5f9de7a3bbb) --- .../doctype/payment_entry/payment_entry.py | 32 +++++++++---------- .../purchase_order/test_purchase_order.py | 23 +------------ 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 453a7b1202d..9f98649248d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1175,27 +1175,27 @@ class PaymentEntry(AccountsController): ) gl_entries.append(gle) - if self.unallocated_amount: - dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" - exchange_rate = self.get_exchange_rate() - base_unallocated_amount = self.unallocated_amount * exchange_rate + if self.unallocated_amount: + dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" + exchange_rate = self.get_exchange_rate() + base_unallocated_amount = self.unallocated_amount * exchange_rate - gle = party_gl_dict.copy() + gle = party_gl_dict.copy() + gle.update( + { + dr_or_cr + "_in_account_currency": self.unallocated_amount, + dr_or_cr: base_unallocated_amount, + } + ) + + if self.book_advance_payments_in_separate_party_account: gle.update( { - dr_or_cr + "_in_account_currency": self.unallocated_amount, - dr_or_cr: base_unallocated_amount, + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, } ) - - if self.book_advance_payments_in_separate_party_account: - gle.update( - { - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, - } - ) - gl_entries.append(gle) + gl_entries.append(gle) def make_advance_gl_entries( self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes" diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 1d87aac2c36..dbfcbf60b26 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -764,12 +764,7 @@ class TestPurchaseOrder(FrappeTestCase): } ).insert() else: - account = frappe.db.get_value( - "Account", - filters={"account_name": account_name, "company": company}, - fieldname="name", - pluck=True, - ) + account = frappe.get_doc("Account", {"account_name": account_name, "company": company}) return account @@ -800,22 +795,6 @@ class TestPurchaseOrder(FrappeTestCase): from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice - pi = make_purchase_invoice(po_doc.name) - pi.append( - "advances", - { - "reference_type": pe.doctype, - "reference_name": pe.name, - "reference_row": pe.references[0].name, - "advance_amount": 5000, - "allocated_amount": 5000, - }, - ) - pi.save().submit() - pe.reload() - po_doc.reload() - self.assertEqual(po_doc.advance_paid, 0) - company_doc.book_advance_payments_in_separate_party_account = False company_doc.save() From 2faeefb063daa1a3536e51445ac5547a7bd0f0cf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 May 2024 18:30:47 +0530 Subject: [PATCH 09/28] test: Add bank account (cherry picked from commit eac7be2d0f1fa28e9179aa2a7868475f2b531f19) --- .../accounts/doctype/payment_entry/test_payment_entry.py | 6 +++--- 1 file changed, 3 insertions(+), 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 26ed33c22b5..d22350d20fc 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1465,12 +1465,12 @@ class TestPaymentEntry(FrappeTestCase): ) po = create_purchase_order(supplier="_Test Supplier") - pe = get_payment_entry("Purchase Order", po.name) + pe = get_payment_entry("Purchase Order", po.name, bank_account="Cash - _TC") pe.save().submit() pre_reconciliation_gle = [ + {"account": "Cash - _TC", "debit": 0.0, "credit": 5000.0}, {"account": advance_account, "debit": 5000.0, "credit": 0.0}, - {"account": "_Test Bank 2 - _TC", "debit": 0.0, "credit": 5000.0}, ] self.voucher_no = pe.name @@ -1494,9 +1494,9 @@ class TestPaymentEntry(FrappeTestCase): # # assert General and Payment Ledger entries post partial reconciliation self.expected_gle = [ {"account": pi.credit_to, "debit": 5000.0, "credit": 0.0}, + {"account": "Cash - _TC", "debit": 0.0, "credit": 5000.0}, {"account": advance_account, "debit": 5000.0, "credit": 0.0}, {"account": advance_account, "debit": 0.0, "credit": 5000.0}, - {"account": "_Test Bank 2 - _TC", "debit": 0.0, "credit": 5000.0}, ] self.voucher_no = pe.name From 1999b7a6c5cdff5432644d8f63069e7b62887fb7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 May 2024 11:47:43 +0530 Subject: [PATCH 10/28] fix: Do not deduct TCS for opening invoices (cherry picked from commit eb9f579b8f1493c6b4822258f6addc575fa4deca) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 +++ .../tax_withholding_category/tax_withholding_category.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index abdfcaeb512..63f13ae251f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -388,6 +388,9 @@ class SalesInvoice(SellingController): validate_account_head(item.idx, item.income_account, self.company, "Income") def set_tax_withholding(self): + if self.get("is_opening") == "Yes": + return + tax_withholding_details = get_party_tax_withholding_details(self) if not tax_withholding_details: diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index a9a4090a02c..44096714ca7 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -9,6 +9,8 @@ from frappe.query_builder import Criterion from frappe.query_builder.functions import Abs, Sum from frappe.utils import cint, flt, getdate +from erpnext.controllers.accounts_controller import validate_account_head + class TaxWithholdingCategory(Document): # begin: auto-generated types @@ -53,6 +55,7 @@ class TaxWithholdingCategory(Document): if d.get("account") in existing_accounts: frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account")))) + validate_account_head(d.idx, d.get("account"), d.get("company")) existing_accounts.append(d.get("account")) def validate_thresholds(self): From fbbc0af7bcb6f12d5c3249ff670ebe09fb81626e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 May 2024 18:34:27 +0530 Subject: [PATCH 11/28] fix: Cost center not getting saved in PSOA (cherry picked from commit 58f70396307846ac014398f3016cc94c0318d656) # Conflicts: # erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json --- .../doctype/psoa_cost_center/psoa_cost_center.json | 8 +++++++- .../accounts/doctype/psoa_cost_center/psoa_cost_center.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json index e292b60d68d..9a19ff9ad90 100644 --- a/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json +++ b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json @@ -11,13 +11,19 @@ { "fieldname": "cost_center_name", "fieldtype": "Link", + "in_list_view": 1, "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "reqd": 1 } ], "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2020-08-03 16:56:45.744905", +======= + "modified": "2024-05-03 17:16:51.666461", +>>>>>>> 58f7039630 (fix: Cost center not getting saved in PSOA) "modified_by": "Administrator", "module": "Accounts", "name": "PSOA Cost Center", diff --git a/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.py b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.py index 683cd78df04..61961147069 100644 --- a/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.py +++ b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.py @@ -15,7 +15,7 @@ class PSOACostCenter(Document): if TYPE_CHECKING: from frappe.types import DF - cost_center_name: DF.Link | None + cost_center_name: DF.Link parent: DF.Data parentfield: DF.Data parenttype: DF.Data From 0b44cebe00bc07818d99bbbed69ed994393e6022 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 6 May 2024 11:12:11 +0530 Subject: [PATCH 12/28] chore: resolve conflicts --- .../accounts/doctype/psoa_cost_center/psoa_cost_center.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json index 9a19ff9ad90..ef4a55861fb 100644 --- a/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json +++ b/erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json @@ -19,11 +19,7 @@ ], "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2020-08-03 16:56:45.744905", -======= "modified": "2024-05-03 17:16:51.666461", ->>>>>>> 58f7039630 (fix: Cost center not getting saved in PSOA) "modified_by": "Administrator", "module": "Accounts", "name": "PSOA Cost Center", @@ -33,4 +29,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From bb619a64fe0e5583f3394f67a742ca1ea703ad4d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 11:42:25 +0530 Subject: [PATCH 13/28] fix: update project URLs (backport #41331) (#41332) fix: update project URLs (#41331) (cherry picked from commit 6142d07f1a3c26d0d2079ff68e7f5dde7c829def) Co-authored-by: Ankush Menat --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 799f5cbe3a1..aaac05d7ed0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,3 +67,9 @@ typing-modules = ["frappe.types.DF"] quote-style = "double" indent-style = "tab" docstring-code-format = true + + +[project.urls] +Homepage = "https://erpnext.com/" +Repository = "https://github.com/frappe/erpnext.git" +"Bug Reports" = "https://github.com/frappe/erpnext/issues" From 20daae4de975be6db46679eb675c0e956fae77fb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 6 May 2024 12:07:33 +0530 Subject: [PATCH 14/28] fix: incorrect query for Purchase Invoice rate in GP (cherry picked from commit bd8382c59259a9115fdd2c04ccfc297c89946d5d) --- erpnext/accounts/report/gross_profit/gross_profit.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 4e14c8aa325..c8c8dd9b494 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -720,20 +720,22 @@ class GrossProfitGenerator: frappe.qb.from_(purchase_invoice_item) .inner_join(purchase_invoice) .on(purchase_invoice.name == purchase_invoice_item.parent) - .select(purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor) + .select( + purchase_invoice.name, + purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor, + ) .where(purchase_invoice.docstatus == 1) .where(purchase_invoice.posting_date <= self.filters.to_date) .where(purchase_invoice_item.item_code == item_code) ) if row.project: - query.where(purchase_invoice_item.project == row.project) + query = query.where(purchase_invoice_item.project == row.project) if row.cost_center: - query.where(purchase_invoice_item.cost_center == row.cost_center) + query = query.where(purchase_invoice_item.cost_center == row.cost_center) - query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc) - query.limit(1) + query = query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc).limit(1) last_purchase_rate = query.run() return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0 From 1b1dfa8893c23691fbfea1754af78cb39266e9a8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 13:36:52 +0530 Subject: [PATCH 15/28] fix: pick list with multiple batch issue (backport #41335) (#41338) fix: pick list with multiple batch issue (#41335) fix: pick list with batchb issue (cherry picked from commit ebfbe94aaf6c91263daed7074a335f0f0f904064) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/pick_list/pick_list.py | 31 +++++++++++----- .../stock/doctype/pick_list/test_pick_list.py | 36 +++++++++++++++++++ 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index e3dbdb5726b..d7e84d2fb93 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -790,7 +790,7 @@ def get_available_item_locations( locations = get_locations_based_on_required_qty(locations, required_qty) if not ignore_validation: - validate_picked_materials(item_code, required_qty, locations) + validate_picked_materials(item_code, required_qty, locations, picked_item_details) return locations @@ -810,7 +810,7 @@ def get_locations_based_on_required_qty(locations, required_qty): return filtered_locations -def validate_picked_materials(item_code, required_qty, locations): +def validate_picked_materials(item_code, required_qty, locations, picked_item_details=None): for location in list(locations): if location["qty"] < 0: locations.remove(location) @@ -819,15 +819,25 @@ def validate_picked_materials(item_code, required_qty, locations): remaining_qty = required_qty - total_qty_available if remaining_qty > 0: - frappe.msgprint( - _("{0} units of Item {1} is picked in another Pick List.").format( - remaining_qty, get_link_to_form("Item", item_code) - ), - title=_("Already Picked"), - ) + if picked_item_details: + frappe.msgprint( + _("{0} units of Item {1} is picked in another Pick List.").format( + remaining_qty, get_link_to_form("Item", item_code) + ), + title=_("Already Picked"), + ) + + else: + frappe.msgprint( + _("{0} units of Item {1} is not available in any of the warehouses.").format( + remaining_qty, get_link_to_form("Item", item_code) + ), + title=_("Insufficient Stock"), + ) def filter_locations_by_picked_materials(locations, picked_item_details) -> list[dict]: + filterd_locations = [] for row in locations: key = row.warehouse if row.batch_no: @@ -845,7 +855,10 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list if row.serial_nos: row.serial_nos = list(set(row.serial_nos) - set(picked_item_details[key].get("serial_no"))) - return locations + if row.qty > 0: + filterd_locations.append(row) + + return filterd_locations def get_available_item_locations_for_serial_and_batched_item( diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 87a71503be5..499eaa84282 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -977,3 +977,39 @@ class TestPickList(FrappeTestCase): so = make_sales_order(item_code=item, qty=4, rate=100) pl = create_pick_list(so.name) self.assertFalse(hasattr(pl, "locations")) + + def test_pick_list_validation_for_multiple_batches_and_sales_order(self): + warehouse = "_Test Warehouse - _TC" + item = make_item( + "Test Batch Pick List Item For Multiple Batches", + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "SN-BT-BATCH-SPLIMBATCH-.####", + "create_new_batch": 1, + }, + ).name + + make_stock_entry(item=item, to_warehouse=warehouse, qty=5) + make_stock_entry(item=item, to_warehouse=warehouse, qty=5) + + so = make_sales_order(item_code=item, qty=6, rate=100) + + pl1 = create_pick_list(so.name) + pl1.save() + self.assertEqual(pl1.locations[0].qty, 5.0) + self.assertEqual(pl1.locations[1].qty, 1.0) + + so = make_sales_order(item_code=item, qty=4, rate=100) + + pl = create_pick_list(so.name) + pl.save() + self.assertEqual(pl.locations[0].qty, 4.0) + self.assertTrue(hasattr(pl, "locations")) + + pl1.submit() + + pl.reload() + pl.submit() + self.assertEqual(pl.locations[0].qty, 4.0) + self.assertTrue(hasattr(pl, "locations")) From 5c1043fe88d31a6c0621d00ad6ac914605ec88c4 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 6 May 2024 21:36:27 +0530 Subject: [PATCH 16/28] fix: correct ordered qty on SO when removing PO item - v15 (#41342) --- .../doctype/purchase_order/purchase_order.py | 14 ++++++++++++++ erpnext/controllers/accounts_controller.py | 3 +++ 2 files changed, 17 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 18f2e007d12..5351ff6d791 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -612,6 +612,20 @@ class PurchaseOrder(BuyingController): return result + def update_ordered_qty_in_so_for_removed_items(self, removed_items): + """ + Updates ordered_qty in linked SO when item rows are removed using Update Items + """ + if not self.is_against_so(): + return + for item in removed_items: + prev_ordered_qty = frappe.get_cached_value( + "Sales Order Item", item.get("sales_order_item"), "ordered_qty" + ) + frappe.db.set_value( + "Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty + ) + def auto_create_subcontracting_order(self): if self.is_subcontracted and not self.is_old_subcontracting_flow: if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f161583458b..c527a02376c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3158,6 +3158,9 @@ def validate_and_delete_children(parent, data) -> bool: d.cancel() d.delete() + if parent.doctype == "Purchase Order": + parent.update_ordered_qty_in_so_for_removed_items(deleted_children) + # need to update ordered qty in Material Request first # bin uses Material Request Items to recalculate & update parent.update_prevdoc_status() From 649157750101028127b801e6138839d357c49bec Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 21:45:27 +0530 Subject: [PATCH 17/28] fix: missing Item Name on Save for Quotation created from Item (backport #41233) (#41304) fix: missing Item Name on Save for Quotation created from Item (#41233) * fix: missing Item Name on Save for Quotation created from Item * fix: missing Item Name on Save for Quotation created from Item (cherry picked from commit c8e92cb1b2a5f9c0a116e2deb53eea440577cdb6) Co-authored-by: HENRY Florian --- erpnext/stock/doctype/item/item.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 5310a0f4d26..d92a998a471 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -15,6 +15,9 @@ frappe.ui.form.on("Item", { frm.add_fetch("tax_type", "tax_rate", "tax_rate"); frm.make_methods = { + Quotation: () => { + open_form(frm, "Quotation", "Quotation Item", "items"); + }, "Sales Order": () => { open_form(frm, "Sales Order", "Sales Order Item", "items"); }, From cd3ca1ee253c2124a2f4fe234248348f4dadbb17 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 20:51:11 +0200 Subject: [PATCH 18/28] fix(Item): allow UOM conversion for non-stock items (backport #41267) (#41346) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> fix(Item): allow UOM conversion for non-stock items (#41267) --- erpnext/stock/doctype/item/item.json | 8 ++++---- erpnext/stock/doctype/item/item.py | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index ec03be52ae1..2c56b6a80bc 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -36,6 +36,8 @@ "section_break_11", "description", "brand", + "unit_of_measure_conversion", + "uoms", "dashboard_tab", "inventory_section", "inventory_settings_section", @@ -52,8 +54,6 @@ "barcodes", "reorder_section", "reorder_levels", - "unit_of_measure_conversion", - "uoms", "serial_nos_and_batches", "has_batch_no", "create_new_batch", @@ -891,7 +891,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2024-01-08 18:09:30.225085", + "modified": "2024-04-30 13:46:39.098753", "modified_by": "Administrator", "module": "Stock", "name": "Item", @@ -964,4 +964,4 @@ "states": [], "title_field": "item_name", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 132e70f2aed..1c43233d7c2 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -65,15 +65,13 @@ class Item(Document): from erpnext.stock.doctype.item_reorder.item_reorder import ItemReorder from erpnext.stock.doctype.item_supplier.item_supplier import ItemSupplier from erpnext.stock.doctype.item_tax.item_tax import ItemTax - from erpnext.stock.doctype.item_variant_attribute.item_variant_attribute import ( - ItemVariantAttribute, - ) + from erpnext.stock.doctype.item_variant_attribute.item_variant_attribute import ItemVariantAttribute from erpnext.stock.doctype.uom_conversion_detail.uom_conversion_detail import UOMConversionDetail allow_alternative_item: DF.Check allow_negative_stock: DF.Check asset_category: DF.Link | None - asset_naming_series: DF.Literal + asset_naming_series: DF.Literal[None] attributes: DF.Table[ItemVariantAttribute] auto_create_assets: DF.Check barcodes: DF.Table[ItemBarcode] From b93828cd337be9c6b71903f1a9cfc54fec956b9f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 2 May 2024 09:37:24 +0530 Subject: [PATCH 19/28] fix: pricing rule rounding Consider a pricing rule of 20:1 with recursion enabled, free items should follow the below progression | Qty | Free item qty | |-------+---------------| | 0-19 | 0 | | 20-39 | 1 | | 40-59 | 2 | (cherry picked from commit 9bf37426c13d6a92db46a49d58b4d36faef048f2) --- erpnext/accounts/doctype/pricing_rule/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index f1d4d2be458..44f7f33a319 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -6,6 +6,7 @@ import copy import json +import math import frappe from frappe import _, bold @@ -653,7 +654,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): if transaction_qty: qty = flt(transaction_qty) * qty / pricing_rule.recurse_for if pricing_rule.round_free_qty: - qty = round(qty) + qty = math.floor(qty) free_item_data_args = { "item_code": free_item, From f2864ebdf6459681018d1d482af06193328279bd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 May 2024 09:44:10 +0530 Subject: [PATCH 20/28] refactor(test): test floor based rounding (cherry picked from commit c41a037174c6f6136086de8b3c822ffff2bb9f60) --- erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 676ed4c2ad5..5df689e5a2b 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -1102,7 +1102,7 @@ class TestPricingRule(unittest.TestCase): so.load_from_db() self.assertEqual(so.items[1].is_free_item, 1) self.assertEqual(so.items[1].item_code, "_Test Item") - self.assertEqual(so.items[1].qty, 4) + self.assertEqual(so.items[1].qty, 3) def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") From 913cea001815a66dd994673056e9968691d969a5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 00:15:10 +0530 Subject: [PATCH 21/28] fix: filter validation for batch-wise balance history report (backport #41356) (#41361) fix: filter validation for batch-wise balance history report (#41356) fix: filter validation for batchwise balance history report (cherry picked from commit 544fc60093cfdc1a92c4ccced7ab5794626fd87c) Co-authored-by: rohitwaghchaure --- .../batch_wise_balance_history.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 2cce803481c..16a0de57a5d 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -30,8 +30,15 @@ def execute(filters=None): sle_count = _estimate_table_row_count("Stock Ledger Entry") - if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"): - frappe.throw(_("Please select either the Item or Warehouse filter to generate the report.")) + if ( + sle_count > SLE_COUNT_LIMIT + and not filters.get("item_code") + and not filters.get("warehouse") + and not filters.get("warehouse_type") + ): + frappe.throw( + _("Please select either the Item or Warehouse or Warehouse Type filter to generate the report.") + ) if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) From bba738f5f4f63432068ce7f19ce553b2e7563208 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 11:37:51 +0530 Subject: [PATCH 22/28] perf: index on item code for the Pick List Item doctype (backport #41357) (#41364) * perf: index on item code for the Pick List Item doctype (#41357) (cherry picked from commit 0887161f2afcb76d88edb8d5c568b5c766568f71) # Conflicts: # erpnext/stock/doctype/pick_list_item/pick_list_item.json * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/pick_list_item/pick_list_item.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index 962fa9f09de..d33252aa3ff 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -132,7 +132,8 @@ "in_list_view": 1, "label": "Item", "options": "Item", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fieldname": "quantity_section", @@ -240,7 +241,7 @@ ], "istable": 1, "links": [], - "modified": "2024-02-04 16:12:16.257951", + "modified": "2024-05-07 15:32:42.905446", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", @@ -251,4 +252,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 9a58823867dff30491c2bf4b1fa4d921955b4b1c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 29 Apr 2024 17:01:48 +0530 Subject: [PATCH 23/28] fix: Merge debit and credit in transaction currency while merging gle with similar head (cherry picked from commit e43697d359a9253bca7dbb62827ba0332dbd49dd) --- .../sales_invoice/test_sales_invoice.py | 43 +++++++++++++++++++ erpnext/accounts/general_ledger.py | 6 +++ 2 files changed, 49 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 7d7e1633044..ee8658a9414 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1766,6 +1766,49 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(gle) + def test_gle_in_transaction_currency(self): + # create multi currency sales invoice with 2 items with same income account + si = create_sales_invoice( + customer="_Test Customer USD", + debit_to="_Test Receivable USD - _TC", + currency="USD", + conversion_rate=50, + do_not_submit=True, + ) + # add 2nd item with same income account + si.append( + "items", + { + "item_code": "_Test Item", + "qty": 1, + "rate": 80, + "income_account": "Sales - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ) + si.submit() + + gl_entries = frappe.db.sql( + """select transaction_currency, transaction_exchange_rate, + debit_in_transaction_currency, credit_in_transaction_currency + from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s and account = 'Sales - _TC' + order by account asc""", + si.name, + as_dict=1, + ) + + expected_gle = { + "transaction_currency": "USD", + "transaction_exchange_rate": 50, + "debit_in_transaction_currency": 0, + "credit_in_transaction_currency": 180, + } + + for gle in gl_entries: + for field in expected_gle: + self.assertEqual(expected_gle[field], gle[field]) + def test_invoice_exchange_rate(self): si = create_sales_invoice( customer="_Test Customer USD", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 74868d413b4..0ff9e973e59 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -238,10 +238,16 @@ def merge_similar_entries(gl_map, precision=None): same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt( entry.debit_in_account_currency ) + same_head.debit_in_transaction_currency = flt(same_head.debit_in_transaction_currency) + flt( + entry.debit_in_transaction_currency + ) same_head.credit = flt(same_head.credit) + flt(entry.credit) same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt( entry.credit_in_account_currency ) + same_head.credit_in_transaction_currency = flt(same_head.credit_in_transaction_currency) + flt( + entry.credit_in_transaction_currency + ) else: merged_gl_map.append(entry) From f7e165b5ffd029973bd4cb54d1f852d22dda96df Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 29 Apr 2024 17:24:31 +0530 Subject: [PATCH 24/28] fix: Patch to fix the incorrect debit and credit in transaction currency (cherry picked from commit e0d12ba4d02776e5de779d5b64c9713b6521ecb8) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 8 ++++++- ...ix_debit_credit_in_transaction_currency.py | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v15_0/fix_debit_credit_in_transaction_currency.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c74a2839958..4a664a41298 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -360,4 +360,10 @@ erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 erpnext.patches.v14_0.set_maintain_stock_for_bom_item -erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records \ No newline at end of file +<<<<<<< HEAD +erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records +======= +erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records +erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset +erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency +>>>>>>> e0d12ba4d0 (fix: Patch to fix the incorrect debit and credit in transaction currency) diff --git a/erpnext/patches/v15_0/fix_debit_credit_in_transaction_currency.py b/erpnext/patches/v15_0/fix_debit_credit_in_transaction_currency.py new file mode 100644 index 00000000000..e0cc8f85a55 --- /dev/null +++ b/erpnext/patches/v15_0/fix_debit_credit_in_transaction_currency.py @@ -0,0 +1,21 @@ +import frappe + + +def execute(): + # update debit and credit in transaction currency: + # if transaction currency is same as account currency, + # then debit and credit in transaction currency is same as debit and credit in account currency + # else debit and credit divided by exchange rate + + # nosemgrep + frappe.db.sql( + """ + UPDATE `tabGL Entry` + SET + debit_in_transaction_currency = IF(transaction_currency = account_currency, debit_in_account_currency, debit / transaction_exchange_rate), + credit_in_transaction_currency = IF(transaction_currency = account_currency, credit_in_account_currency, credit / transaction_exchange_rate) + WHERE + transaction_exchange_rate > 0 + and transaction_currency is not null + """ + ) From 81afdcf745a69c335710a0630173810bd517fc19 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 8 May 2024 17:57:43 +0530 Subject: [PATCH 25/28] fix: resolved conflict --- erpnext/patches.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4a664a41298..91c77219510 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -360,10 +360,6 @@ erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 erpnext.patches.v14_0.set_maintain_stock_for_bom_item -<<<<<<< HEAD -erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records -======= erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency ->>>>>>> e0d12ba4d0 (fix: Patch to fix the incorrect debit and credit in transaction currency) From a755540708ef024d28c8c5967554d5d19292653b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 26 Feb 2024 12:57:19 +0530 Subject: [PATCH 26/28] fix: Patch to remove cancelled asset capitalization from asset (cherry picked from commit 1951f71eeb236d4fe1be8159419a87f15e292dae) --- .../asset_capitalization/asset_capitalization.py | 4 ++++ erpnext/patches.txt | 3 ++- ...emove_cancelled_asset_capitalization_from_asset.py | 11 +++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index ae3c85f1ede..8d3bcfc153d 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -143,6 +143,10 @@ class AssetCapitalization(StockController): self.make_gl_entries() self.restore_consumed_asset_items() + def on_trash(self): + frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None) + super(AssetCapitalization, self).on_trash() + def cancel_target_asset(self): if self.entry_type == "Capitalization" and self.target_asset: asset_doc = frappe.get_doc("Asset", self.target_asset) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c74a2839958..6d830995028 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -360,4 +360,5 @@ erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 erpnext.patches.v14_0.set_maintain_stock_for_bom_item -erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records \ No newline at end of file +erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records +erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset \ No newline at end of file diff --git a/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py b/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py new file mode 100644 index 00000000000..cb39a9280e4 --- /dev/null +++ b/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py @@ -0,0 +1,11 @@ +import frappe + + +def execute(): + cancelled_asset_capitalizations = frappe.get_all( + "Asset Capitalization", + filters={"docstatus": 2}, + fields=["name", "target_asset"], + ) + for asset_capitalization in cancelled_asset_capitalizations: + frappe.db.set_value("Asset", asset_capitalization.target_asset, "capitalized_in", None) From 5039f45be461f0467f6c50a0b770fc35d24896ae Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 8 May 2024 18:23:30 +0530 Subject: [PATCH 27/28] fix: resolved conflict --- erpnext/patches.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 91c77219510..5b3462231d3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -361,5 +361,4 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 erpnext.patches.v14_0.set_maintain_stock_for_bom_item erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records -erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency From a293ec0db31b5c05c58ce29910df132f74327ecb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 23:15:21 +0530 Subject: [PATCH 28/28] fix: incorrect qty picked in the pick list (backport #41378) (#41381) fix: incorrect qty picked in the pick list (#41378) (cherry picked from commit 5ed1b6b8fb54befb2e247409da1387534e1f055d) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/pick_list/pick_list.py | 1 + .../stock/doctype/pick_list/test_pick_list.py | 118 ++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index d7e84d2fb93..4b5ab3836c5 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -845,6 +845,7 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list picked_qty = picked_item_details.get(key, {}).get("picked_qty", 0) if not picked_qty: + filterd_locations.append(row) continue if picked_qty > row.qty: row.qty = 0 diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 499eaa84282..65fe853ec8d 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -1013,3 +1013,121 @@ class TestPickList(FrappeTestCase): pl.submit() self.assertEqual(pl.locations[0].qty, 4.0) self.assertTrue(hasattr(pl, "locations")) + + def test_pick_list_for_multiple_sales_order_with_multiple_batches(self): + warehouse = "_Test Warehouse - _TC" + item = make_item( + "Test Batch Pick List Item For Multiple Batches and Sales Order", + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "SN-SOO-BT-SPLIMBATCH-.####", + "create_new_batch": 1, + }, + ).name + + make_stock_entry(item=item, to_warehouse=warehouse, qty=100) + make_stock_entry(item=item, to_warehouse=warehouse, qty=100) + + so = make_sales_order(item_code=item, qty=10, rate=100) + + pl1 = create_pick_list(so.name) + pl1.save() + self.assertEqual(pl1.locations[0].qty, 10) + + so = make_sales_order(item_code=item, qty=110, rate=100) + + pl = create_pick_list(so.name) + pl.save() + self.assertEqual(pl.locations[0].qty, 90.0) + self.assertEqual(pl.locations[1].qty, 20.0) + self.assertTrue(hasattr(pl, "locations")) + + pl1.submit() + + pl.reload() + pl.submit() + self.assertEqual(pl.locations[0].qty, 90.0) + self.assertEqual(pl.locations[1].qty, 20.0) + self.assertTrue(hasattr(pl, "locations")) + + def test_pick_list_for_multiple_sales_order_with_multiple_serial_nos(self): + warehouse = "_Test Warehouse - _TC" + item = make_item( + "Test Serial No Pick List Item For Multiple Batches and Sales Order", + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "SNNN-SOO-BT-SPLIMBATCH-.####", + }, + ).name + + make_stock_entry(item=item, to_warehouse=warehouse, qty=100) + make_stock_entry(item=item, to_warehouse=warehouse, qty=100) + + so = make_sales_order(item_code=item, qty=10, rate=100) + + pl1 = create_pick_list(so.name) + pl1.save() + self.assertEqual(pl1.locations[0].qty, 10) + + serial_nos = pl1.locations[0].serial_no.split("\n") + self.assertEqual(len(serial_nos), 10) + + so = make_sales_order(item_code=item, qty=110, rate=100) + + pl = create_pick_list(so.name) + pl.save() + self.assertEqual(pl.locations[0].qty, 110.0) + self.assertTrue(hasattr(pl, "locations")) + + new_serial_nos = pl.locations[0].serial_no.split("\n") + self.assertEqual(len(new_serial_nos), 110) + + for sn in serial_nos: + self.assertFalse(sn in new_serial_nos) + + pl1.submit() + + pl.reload() + pl.submit() + self.assertEqual(pl.locations[0].qty, 110.0) + self.assertTrue(hasattr(pl, "locations")) + + def test_pick_list_for_multiple_sales_orders_for_non_serialized_item(self): + warehouse = "_Test Warehouse - _TC" + item = make_item( + "Test Non Serialized Pick List Item For Multiple Batches and Sales Order", + properties={ + "is_stock_item": 1, + }, + ).name + + make_stock_entry(item=item, to_warehouse=warehouse, qty=100) + make_stock_entry(item=item, to_warehouse=warehouse, qty=100) + + so = make_sales_order(item_code=item, qty=10, rate=100) + + pl1 = create_pick_list(so.name) + pl1.save() + self.assertEqual(pl1.locations[0].qty, 10) + + so = make_sales_order(item_code=item, qty=110, rate=100) + + pl = create_pick_list(so.name) + pl.save() + self.assertEqual(pl.locations[0].qty, 110.0) + self.assertTrue(hasattr(pl, "locations")) + + pl1.submit() + + pl.reload() + pl.submit() + self.assertEqual(pl.locations[0].qty, 110.0) + self.assertTrue(hasattr(pl, "locations")) + + so = make_sales_order(item_code=item, qty=110, rate=100) + pl = create_pick_list(so.name) + pl.save() + + self.assertEqual(pl.locations[0].qty, 80.0)