diff --git a/erpnext/stock/doctype/purchase_receipt/services/billing_status.py b/erpnext/stock/doctype/purchase_receipt/services/billing_status.py index 55e4914384b..340cf798a06 100644 --- a/erpnext/stock/doctype/purchase_receipt/services/billing_status.py +++ b/erpnext/stock/doctype/purchase_receipt/services/billing_status.py @@ -200,10 +200,14 @@ def update_billing_percentage( returned_qty = flt(item_wise_returned_qty.get(item.name)) returned_amount = flt(returned_qty) * flt(item.rate) pending_amount = flt(item.amount) - returned_amount - if buying_settings.bill_for_rejected_quantity_in_purchase_invoice: - pending_amount = flt(item.amount) - total_billable_amount = abs(flt(item.amount)) + # When rejected qty is billable, its value is part of the billable base too + rejected_amount = 0.0 + if buying_settings.bill_for_rejected_quantity_in_purchase_invoice: + rejected_amount = flt(item.rejected_qty * item.rate, item.precision("amount")) + pending_amount = flt(item.amount) + rejected_amount + + total_billable_amount = abs(flt(item.amount) + rejected_amount) if pending_amount > 0: total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt @@ -213,9 +217,7 @@ def update_billing_percentage( if pr_doc.get("is_return") and not total_amount and total_billed_amount: total_amount = total_billed_amount - amount = item.amount - if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"): - amount += flt(item.rejected_qty * item.rate, item.precision("amount")) + amount = flt(item.amount) + rejected_amount if adjust_incoming_rate: adjusted_amt = 0.0 diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 5e1e8b1c3e2..0b97c4cd2b8 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -617,6 +617,44 @@ class TestPurchaseReceipt(ERPNextTestSuite): return_pr.cancel() pr.cancel() + def test_per_billed_for_fully_rejected_receipt(self): + from erpnext.stock.doctype.purchase_receipt.services.billing_status import ( + update_billing_percentage, + ) + + bill_rejected = frappe.db.get_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice" + ) + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1) + + try: + # Fully rejected receipt: accepted qty 0, whole qty in rejected warehouse + pr = make_purchase_receipt( + received_qty=10, + qty=0, + rejected_qty=10, + rate=9.5, + rejected_warehouse="_Test Warehouse 1 - _TC", + do_not_save=True, + ) + pr.items[0].warehouse = "" + pr.submit() + + # Bill the rejected qty (10 x 9.5) directly against the receipt item + pr.items[0].db_set("billed_amt", 95) + update_billing_percentage(pr) + + pr.load_from_db() + # Billing the rejected qty must not push per_billed above 100 + self.assertEqual(pr.per_billed, 100) + self.assertEqual(pr.status, "Completed") + + pr.cancel() + finally: + frappe.db.set_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", bill_rejected + ) + def test_purchase_receipt_for_rejected_gle_without_accepted_warehouse(self): from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse