mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-21 02:09:58 +00:00
Merge pull request #58044 from mihir-kandoi/fix-po-billing-qty-sync-develop
fix: keep PO billed qty in sync when allocating amount to receipts
This commit is contained in:
@@ -72,14 +72,15 @@ def update_billed_amount_based_on_po(po_details: list, update_modified: bool = T
|
||||
)
|
||||
else:
|
||||
pending_to_bill = flt(pr_item.amount) - billed_amt_against_pr
|
||||
if pending_to_bill <= billed_amt_against_po:
|
||||
billed_amt_against_pr += pending_to_bill
|
||||
billed_amt_against_po -= pending_to_bill
|
||||
else:
|
||||
billed_amt_against_pr += billed_amt_against_po
|
||||
billed_amt_against_po = 0
|
||||
consumed_amt_against_po = min(pending_to_bill, billed_amt_against_po)
|
||||
billed_amt_against_pr += consumed_amt_against_po
|
||||
|
||||
po_billed_amt_details[pr_item.purchase_order_item]["billed_amt"] = billed_amt_against_po
|
||||
po_billed_amt_details[pr_item.purchase_order_item]["billed_amt"] = (
|
||||
billed_amt_against_po - consumed_amt_against_po
|
||||
)
|
||||
po_billed_amt_details[pr_item.purchase_order_item]["billed_qty"] = billed_qty_against_po * (
|
||||
1 - consumed_amt_against_po / billed_amt_against_po
|
||||
)
|
||||
|
||||
if pr_item.billed_amt != billed_amt_against_pr:
|
||||
# update existing doc if possible
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user