fix: added PR/PI overbilling validation (backport #47385) (#47497)

* fix: added PR/PI overbilling validation

(cherry picked from commit f4ffc57b51)

* test: added test

(cherry picked from commit b406ec724b)

* fix: linter error

(cherry picked from commit 27e842ba02)

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
This commit is contained in:
mergify[bot]
2025-05-11 14:31:46 +05:30
committed by GitHub
parent 775b2432ab
commit 309ea7b9cf
2 changed files with 49 additions and 0 deletions

View File

@@ -1695,6 +1695,9 @@ class TestPurchaseInvoice(FrappeTestCase, 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)
@@ -2756,6 +2759,43 @@ class TestPurchaseInvoice(FrappeTestCase, 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(

View File

@@ -1081,6 +1081,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)
@@ -1119,6 +1120,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)