From e62c49d9a69f7549a2ca425b000cea648a802daf Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 26 Jan 2024 21:46:19 +0530 Subject: [PATCH 1/6] fix: provisional reverse entry amount (cherry picked from commit 3e59c668064c037548c7c29c4d944efbcb37ff7b) --- .../doctype/purchase_invoice/purchase_invoice.py | 6 +++--- .../stock/doctype/purchase_receipt/purchase_receipt.py | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f6fcd7e70e0..7ad9417173e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -869,7 +869,7 @@ class PurchaseInvoice(BuyingController): purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - expense_booked_in_pr = frappe.db.get_value( + expense_booked_in_pr, credit = frappe.db.get_value( "GL Entry", { "is_cancelled": 0, @@ -878,13 +878,13 @@ class PurchaseInvoice(BuyingController): "voucher_detail_no": item.pr_detail, "account": provisional_account, }, - ["name"], + ["name", "credit"], ) if expense_booked_in_pr: # Intentionally passing purchase invoice item to handle partial billing purchase_receipt_doc.add_provisional_gl_entry( - item, gl_entries, self.posting_date, provisional_account, reverse=1 + item, gl_entries, self.posting_date, provisional_account, reverse=1, item_amount=credit ) if not self.is_internal_transfer(): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index bf5f9f4d1d4..79e6ab84d95 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -615,16 +615,19 @@ class PurchaseReceipt(BuyingController): ) def add_provisional_gl_entry( - self, item, gl_entries, posting_date, provisional_account, reverse=0 + self, item, gl_entries, posting_date, provisional_account, reverse=0, item_amount=None ): credit_currency = get_account_currency(provisional_account) expense_account = item.expense_account debit_currency = get_account_currency(item.expense_account) remarks = self.get("remarks") or _("Accounting Entry for Service") multiplication_factor = 1 + amount = item.base_amount if reverse: multiplication_factor = -1 + # Post reverse entry for previously posted amount + amount = item_amount expense_account = frappe.db.get_value( "Purchase Receipt Item", {"name": item.get("pr_detail")}, ["expense_account"] ) @@ -634,7 +637,7 @@ class PurchaseReceipt(BuyingController): account=provisional_account, cost_center=item.cost_center, debit=0.0, - credit=multiplication_factor * item.base_amount, + credit=multiplication_factor * amount, remarks=remarks, against_account=expense_account, account_currency=credit_currency, @@ -648,7 +651,7 @@ class PurchaseReceipt(BuyingController): gl_entries=gl_entries, account=expense_account, cost_center=item.cost_center, - debit=multiplication_factor * item.base_amount, + debit=multiplication_factor * amount, credit=0.0, remarks=remarks, against_account=provisional_account, From 5f23614960aa0f41b11d8b5f651d85cbfbf2753c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 29 Jan 2024 11:34:06 +0530 Subject: [PATCH 2/6] fix: handle partial invoice against provisional entry (cherry picked from commit 2a46799188a0267d76bdd292062f5a5f82429493) --- .../purchase_invoice/purchase_invoice.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7ad9417173e..bede2641f15 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -859,9 +859,12 @@ class PurchaseInvoice(BuyingController): if provisional_accounting_for_non_stock_items: if item.purchase_receipt: - provisional_account = frappe.db.get_value( - "Purchase Receipt Item", item.pr_detail, "provisional_expense_account" - ) or self.get_company_default("default_provisional_account") + provisional_account, pr_base_rate = frappe.get_cached_value( + "Purchase Receipt Item", item.pr_detail, ["provisional_expense_account", "base_rate"] + ) + provisional_account = provisional_account or self.get_company_default( + "default_provisional_account" + ) purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt) if not purchase_receipt_doc: @@ -869,7 +872,7 @@ class PurchaseInvoice(BuyingController): purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - expense_booked_in_pr, credit = frappe.db.get_value( + expense_booked_in_pr = frappe.db.get_value( "GL Entry", { "is_cancelled": 0, @@ -878,13 +881,18 @@ class PurchaseInvoice(BuyingController): "voucher_detail_no": item.pr_detail, "account": provisional_account, }, - ["name", "credit"], + "name", ) if expense_booked_in_pr: # Intentionally passing purchase invoice item to handle partial billing purchase_receipt_doc.add_provisional_gl_entry( - item, gl_entries, self.posting_date, provisional_account, reverse=1, item_amount=credit + item, + gl_entries, + self.posting_date, + provisional_account, + reverse=1, + item_amount=(item.qty * pr_base_rate), ) if not self.is_internal_transfer(): From 14d9d29913e78a585230763b3f25a2e903c263a7 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 5 Feb 2024 15:52:46 +0530 Subject: [PATCH 3/6] fix: over billing qty along with rate (cherry picked from commit cc96d2b50cbce3c137eebd754956344718cd1771) --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index bede2641f15..cfaaf767786 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -859,8 +859,10 @@ class PurchaseInvoice(BuyingController): if provisional_accounting_for_non_stock_items: if item.purchase_receipt: - provisional_account, pr_base_rate = frappe.get_cached_value( - "Purchase Receipt Item", item.pr_detail, ["provisional_expense_account", "base_rate"] + provisional_account, pr_qty, pr_base_rate = frappe.get_cached_value( + "Purchase Receipt Item", + item.pr_detail, + ["provisional_expense_account", "qty", "base_rate"], ) provisional_account = provisional_account or self.get_company_default( "default_provisional_account" @@ -892,7 +894,7 @@ class PurchaseInvoice(BuyingController): self.posting_date, provisional_account, reverse=1, - item_amount=(item.qty * pr_base_rate), + item_amount=(min(item.qty, pr_qty) * pr_base_rate), ) if not self.is_internal_transfer(): From aa52cd67bdeddc54303151180529250519f882c8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 5 Feb 2024 15:55:17 +0530 Subject: [PATCH 4/6] test: overbilling for provisional accounting (cherry picked from commit ff3ca50a4b1787cd557dffefda4997a7f20cd643) --- .../purchase_invoice/test_purchase_invoice.py | 89 ++++++++++++++++--- 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 6942e28726f..1ce5a81eae5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1520,18 +1520,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): self.assertEqual(payment_entry.taxes[0].allocated_amount, 0) def test_provisional_accounting_entry(self): - create_item("_Test Non Stock Item", is_stock_item=0) - - provisional_account = create_account( - account_name="Provision Account", - parent_account="Current Liabilities - _TC", - company="_Test Company", - ) - - company = frappe.get_doc("Company", "_Test Company") - company.enable_provisional_accounting_for_non_stock_items = 1 - company.default_provisional_account = provisional_account - company.save() + setup_provisional_accounting() pr = make_purchase_receipt( item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2) @@ -1575,8 +1564,58 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date ) - company.enable_provisional_accounting_for_non_stock_items = 0 - company.save() + toggle_provisional_accounting_setting() + + def test_provisional_accounting_entry_for_over_billing(self): + setup_provisional_accounting() + + # Configure Buying Settings to allow rate change + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) + + # Create PR: rate = 1000, qty = 5 + pr = make_purchase_receipt( + item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2) + ) + + # Overbill PR: rate = 2000, qty = 10 + pi = create_purchase_invoice_from_receipt(pr.name) + pi.set_posting_time = 1 + pi.posting_date = add_days(pr.posting_date, -1) + pi.items[0].qty = 10 + pi.items[0].rate = 2000 + pi.items[0].expense_account = "Cost of Goods Sold - _TC" + pi.save() + pi.submit() + + expected_gle = [ + ["Cost of Goods Sold - _TC", 20000, 0, add_days(pr.posting_date, -1)], + ["Creditors - _TC", 0, 20000, add_days(pr.posting_date, -1)], + ] + + check_gl_entries(self, pi.name, expected_gle, pi.posting_date) + + expected_gle_for_purchase_receipt = [ + ["Provision Account - _TC", 5000, 0, pr.posting_date], + ["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date], + ["Provision Account - _TC", 0, 5000, pi.posting_date], + ["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date], + ] + + check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date) + + # Cancel purchase invoice to check reverse provisional entry cancellation + pi.cancel() + + expected_gle_for_purchase_receipt_post_pi_cancel = [ + ["Provision Account - _TC", 0, 5000, pi.posting_date], + ["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date], + ] + + check_gl_entries( + self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date + ) + + toggle_provisional_accounting_setting() def test_adjust_incoming_rate(self): frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) @@ -2091,4 +2130,26 @@ def make_purchase_invoice_against_cost_center(**args): return pi +def setup_provisional_accounting(**args): + args = frappe._dict(args) + create_item("_Test Non Stock Item", is_stock_item=0) + company = args.company or "_Test Company" + provisional_account = create_account( + account_name=args.account_name or "Provision Account", + parent_account=args.parent_account or "Current Liabilities - _TC", + company=company, + ) + toggle_provisional_accounting_setting( + enable=1, company=company, provisional_account=provisional_account + ) + + +def toggle_provisional_accounting_setting(**args): + args = frappe._dict(args) + company = frappe.get_doc("Company", args.company or "_Test Company") + company.enable_provisional_accounting_for_non_stock_items = args.enable or 0 + company.default_provisional_account = args.provisional_account + company.save() + + test_records = frappe.get_test_records("Purchase Invoice") From c8b31833f5fff9b77455cf71eac85e5f599947b0 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 5 Feb 2024 15:58:06 +0530 Subject: [PATCH 5/6] test: partial billing for provisional accounting (cherry picked from commit c5770f2eccc62c27f18a108ad60ae4f42946a9d6) --- .../purchase_invoice/test_purchase_invoice.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 1ce5a81eae5..ba97fc685a3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1617,6 +1617,45 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): toggle_provisional_accounting_setting() + def test_provisional_accounting_entry_for_partial_billing(self): + setup_provisional_accounting() + + # Configure Buying Settings to allow rate change + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) + + # Create PR: rate = 1000, qty = 5 + pr = make_purchase_receipt( + item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2) + ) + + # Partially bill PR: rate = 500, qty = 2 + pi = create_purchase_invoice_from_receipt(pr.name) + pi.set_posting_time = 1 + pi.posting_date = add_days(pr.posting_date, -1) + pi.items[0].qty = 2 + pi.items[0].rate = 500 + pi.items[0].expense_account = "Cost of Goods Sold - _TC" + pi.save() + pi.submit() + + expected_gle = [ + ["Cost of Goods Sold - _TC", 1000, 0, add_days(pr.posting_date, -1)], + ["Creditors - _TC", 0, 1000, add_days(pr.posting_date, -1)], + ] + + check_gl_entries(self, pi.name, expected_gle, pi.posting_date) + + expected_gle_for_purchase_receipt = [ + ["Provision Account - _TC", 5000, 0, pr.posting_date], + ["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date], + ["Provision Account - _TC", 0, 1000, pi.posting_date], + ["_Test Account Cost for Goods Sold - _TC", 1000, 0, pi.posting_date], + ] + + check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date) + + toggle_provisional_accounting_setting() + def test_adjust_incoming_rate(self): frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) From 89e7ad790fd2c135299b465d1483443d01a3ada0 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 3 Mar 2024 18:00:10 +0530 Subject: [PATCH 6/6] chore: linting issue --- erpnext/accounts/doctype/bank_account/bank_account.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index ec3fa831200..28a4a41e28e 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -30,7 +30,9 @@ class BankAccount(Document): def validate_account(self): if self.account: - if accounts := frappe.db.get_all("Bank Account", filters={"account": self.account, 'name':['!=', self.name]}, as_list=1): + if accounts := frappe.db.get_all( + "Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1 + ): frappe.throw( _("'{0}' account is already used by {1}. Use another account.").format( frappe.bold(self.account),