From 0182b95230d538c3810e93d9139829fe112a1a35 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 26 Jan 2024 21:46:19 +0530 Subject: [PATCH 1/5] 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 2b6c6b64377f..3793f97b24c9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1033,7 +1033,7 @@ def make_item_gl_entries(self, gl_entries): 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, @@ -1042,13 +1042,13 @@ def make_item_gl_entries(self, gl_entries): "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 67e6ff90e785..e249cf386fab 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -727,16 +727,19 @@ def make_divisional_loss_gl_entry(item, outgoing_amount): ) 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"] ) @@ -746,7 +749,7 @@ def add_provisional_gl_entry( 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, @@ -760,7 +763,7 @@ def add_provisional_gl_entry( 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 87596e6e29250ec86e2a17c846ebd5c2379b4aeb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 29 Jan 2024 11:34:06 +0530 Subject: [PATCH 2/5] 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 3793f97b24c9..139c694ba5e6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1023,9 +1023,12 @@ def make_item_gl_entries(self, gl_entries): 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: @@ -1033,7 +1036,7 @@ def make_item_gl_entries(self, gl_entries): 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, @@ -1042,13 +1045,18 @@ def make_item_gl_entries(self, gl_entries): "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 00d410cc1dbc110b74612c504be0df581ec274e0 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 5 Feb 2024 15:52:46 +0530 Subject: [PATCH 3/5] 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 139c694ba5e6..0a9b9345a322 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1023,8 +1023,10 @@ def make_item_gl_entries(self, gl_entries): 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" @@ -1056,7 +1058,7 @@ def make_item_gl_entries(self, gl_entries): 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 e55dc2a7c08cdd26c57c9168d57f8bc07ca69c4a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 5 Feb 2024 15:55:17 +0530 Subject: [PATCH 4/5] 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 e4e2cd79fb46..7f1e63dfba00 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1529,18 +1529,7 @@ def test_purchase_invoice_advance_taxes(self): 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) @@ -1584,8 +1573,58 @@ def test_provisional_accounting_entry(self): 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) @@ -2264,4 +2303,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 823abfd6e0cd49e4579a5da592d17440572bbdea Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 5 Feb 2024 15:58:06 +0530 Subject: [PATCH 5/5] 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 7f1e63dfba00..e0fbef4a9b4c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1626,6 +1626,45 @@ def test_provisional_accounting_entry_for_over_billing(self): 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)