test: cover mixed direct and PO-invoice billing across receipts

This commit is contained in:
Mihir Kandoi
2026-08-11 18:40:35 +05:30
parent 7803998fce
commit 031e7c0eb7

View File

@@ -947,6 +947,65 @@ class TestPurchaseReceipt(ERPNextTestSuite):
po.reload()
po.cancel()
def test_pr_billing_status_with_mixed_direct_and_po_invoice(self):
"""A receipt with partial direct billing consumes PO-invoiced amount through
the amount-capped branch. The consumed qty must shrink along with the amount,
otherwise the next receipt divides by a stale qty and is under-billed.
Flow:
1. PO (Qty: 10, Rate: 500) -> PI for Qty 5 (Amount 2500)
2. PO -> PR1 (Qty 3), then a direct PI for 500 against PR1
3. PO -> PR2 (Qty 3) -> reallocation must leave both receipts fully billed
"""
from erpnext.buying.doctype.purchase_order.mapper import (
make_purchase_invoice as make_purchase_invoice_from_po,
)
from erpnext.buying.doctype.purchase_order.mapper import make_purchase_receipt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
# Qty: 10, Rate: 500
po = create_purchase_order()
pi = make_purchase_invoice_from_po(po.name)
pi.get("items")[0].qty = 5
pi.submit()
pr1 = make_purchase_receipt(po.name)
pr1.get("items")[0].received_qty = 3
pr1.get("items")[0].qty = 3
pr1.submit()
direct_pi = make_purchase_invoice(pr1.name)
direct_pi.get("items")[0].qty = 1
direct_pi.submit()
pr2 = make_purchase_receipt(po.name)
pr2.get("items")[0].received_qty = 3
pr2.get("items")[0].qty = 3
pr2.submit()
# PR1: 500 direct + 1000 from the PO invoice (2 qty worth) -> fully billed.
pr1.load_from_db()
self.assertEqual(pr1.get("items")[0].billed_amt, 1500)
self.assertEqual(pr1.per_billed, 100)
self.assertEqual(pr1.status, "Completed")
# PR2 gets the remaining 1500 (3 qty worth), not 1500 * 3/5 = 900.
pr2.load_from_db()
self.assertEqual(pr2.get("items")[0].billed_amt, 1500)
self.assertEqual(pr2.per_billed, 100)
self.assertEqual(pr2.status, "Completed")
pr2.cancel()
direct_pi.reload()
direct_pi.cancel()
pi.reload()
pi.cancel()
pr1.reload()
pr1.cancel()
po.reload()
po.cancel()
def test_serial_no_against_purchase_receipt(self):
item_code = "Test Manual Created Serial No"
if not frappe.db.exists("Item", item_code):