From f4ffc57b5107f0d9da0b16824dbac23952befc9a Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 2 May 2025 18:55:53 +0530 Subject: [PATCH 1/3] fix: added PR/PI overbilling validation --- .../doctype/purchase_invoice/test_purchase_invoice.py | 3 +++ .../stock/doctype/purchase_receipt/purchase_receipt.py | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 68fd44cfdd6..4c2ac0f44aa 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1702,6 +1702,9 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): # Configure Buying Settings to allow rate change frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) + # Configure Accounts Settings to allow 300% over billing + frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", 300) + # Create PR: rate = 1000, qty = 5 pr = make_purchase_receipt( item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 49f04931932..d499ffd219a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1105,6 +1105,7 @@ def get_billed_amount_against_po(po_items): def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False): # Update Billing % based on pending accepted qty buying_settings = frappe.get_single("Buying Settings") + over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance") total_amount, total_billed_amount = 0, 0 item_wise_returned_qty = get_item_wise_returned_qty(pr_doc) @@ -1143,6 +1144,14 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount")) item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False) + elif item.billed_amt > item.amount: + per_over_billed = (flt(item.billed_amt / item.amount, 2) * 100) - 100 + if per_over_billed > over_billing_allowance: + frappe.throw( + _("Over Billing Allowance exceeded for Purchase Receipt Item {0} ({1}) by {2}%").format( + item.name, frappe.bold(item.item_code), per_over_billed - over_billing_allowance + ) + ) percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6) pr_doc.db_set("per_billed", percent_billed) From b406ec724b2f1703d8e5dbacf7bb82ffe3e0cc8a Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 8 May 2025 14:44:01 +0530 Subject: [PATCH 2/3] test: added test --- .../purchase_invoice/test_purchase_invoice.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 4c2ac0f44aa..dfcaa49e4ea 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2776,6 +2776,42 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): self.assertEqual(invoice.grand_total, 300) + def test_pr_pi_over_billing(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_invoice as make_purchase_invoice_from_pr, + ) + + # Configure Buying Settings to allow rate change + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) + + pr = make_purchase_receipt(qty=10, rate=10) + pi = make_purchase_invoice_from_pr(pr.name) + + pi.items[0].rate = 12 + + # Test 1 - This will fail because over billing is not allowed + self.assertRaises(frappe.ValidationError, pi.submit) + + frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1) + # Test 2 - This will now submit because over billing allowance is ignored when set_landed_cost_based_on_purchase_invoice_rate is checked + pi.submit() + + frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0) + frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", 20) + pi.cancel() + pi = make_purchase_invoice_from_pr(pr.name) + pi.items[0].rate = 12 + + # Test 3 - This will now submit because over billing is allowed upto 20% + pi.submit() + + pi.reload() + pi.cancel() + pi = make_purchase_invoice_from_pr(pr.name) + pi.items[0].rate = 13 + + # Test 4 - Since this PI is overbilled by 130% and only 120% is allowed, it will fail + self.assertRaises(frappe.ValidationError, pi.submit) def set_advance_flag(company, flag, default_account): frappe.db.set_value( From 27e842ba02a546043c8ed1c0be9888c9b1c132d9 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 8 May 2025 14:47:19 +0530 Subject: [PATCH 3/3] fix: linter error --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index dfcaa49e4ea..52bce93ec8b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2813,6 +2813,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): # Test 4 - Since this PI is overbilled by 130% and only 120% is allowed, it will fail self.assertRaises(frappe.ValidationError, pi.submit) + def set_advance_flag(company, flag, default_account): frappe.db.set_value( "Company",