fix: LCV rate not updated based on PI rate if PI created from PO

This commit is contained in:
Rohit Waghchaure
2026-01-01 14:45:41 +05:30
parent cb5926f59b
commit 50b30fce4a
2 changed files with 111 additions and 15 deletions

View File

@@ -1121,22 +1121,30 @@ def update_billed_amount_based_on_po(po_details, update_modified=True, pr_doc=No
updated_pr = []
for pr_item in pr_details:
billed_against_po = flt(po_billed_amt_details.get(pr_item.purchase_order_item))
billed_amt_against_po, billed_qty_against_po = 0, 0
if billed_details := po_billed_amt_details.get(pr_item.purchase_order_item):
billed_amt_against_po = flt(billed_details["billed_amt"])
billed_qty_against_po = flt(billed_details["billed_qty"])
# Get billed amount directly against Purchase Receipt
billed_amt_against_pr = flt(pr_items_billed_amount.get(pr_item.name, 0))
# Distribute billed amount directly against PO between PRs based on FIFO
if billed_against_po and billed_amt_against_pr < pr_item.amount:
pending_to_bill = flt(pr_item.amount) - billed_amt_against_pr
if pending_to_bill <= billed_against_po:
billed_amt_against_pr += pending_to_bill
billed_against_po -= pending_to_bill
if billed_amt_against_po and billed_amt_against_pr < pr_item.amount:
if not billed_amt_against_pr and billed_qty_against_po and billed_qty_against_po > pr_item.qty:
billed_amt_against_pr = flt(flt(billed_amt_against_po) * flt(pr_item.qty)) / flt(
billed_qty_against_po
)
else:
billed_amt_against_pr += billed_against_po
billed_against_po = 0
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
po_billed_amt_details[pr_item.purchase_order_item] = billed_against_po
po_billed_amt_details[pr_item.purchase_order_item]["billed_amt"] = billed_amt_against_po
if pr_item.billed_amt != billed_amt_against_pr:
# update existing doc if possible
@@ -1170,6 +1178,7 @@ def get_purchase_receipts_against_po_details(po_details):
.on(purchase_receipt.name == purchase_receipt_item.parent)
.select(
purchase_receipt_item.name,
purchase_receipt_item.qty,
purchase_receipt_item.parent,
purchase_receipt_item.amount,
purchase_receipt_item.billed_amt,
@@ -1217,7 +1226,11 @@ def get_billed_amount_against_po(po_items):
frappe.qb.from_(purchase_invoice_item)
.inner_join(purchase_invoice)
.on(purchase_invoice_item.parent == purchase_invoice.name)
.select(fn.Sum(purchase_invoice_item.amount).as_("billed_amt"), purchase_invoice_item.po_detail)
.select(
fn.Sum(purchase_invoice_item.amount).as_("billed_amt"),
fn.Sum(purchase_invoice_item.qty).as_("qty"),
purchase_invoice_item.po_detail,
)
.where(
(purchase_invoice_item.po_detail.isin(po_items))
& (purchase_invoice.docstatus == 1)
@@ -1227,7 +1240,7 @@ def get_billed_amount_against_po(po_items):
.groupby(purchase_invoice_item.po_detail)
).run(as_dict=1)
return {d.po_detail: flt(d.billed_amt) for d in query}
return {d.po_detail: {"billed_amt": flt(d.billed_amt), "billed_qty": flt(d.qty)} for d in query}
def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False):
@@ -1241,6 +1254,8 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
if adjust_incoming_rate:
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc)
billed_qty_based_on_po = get_billed_qty_against_purchase_order(pr_doc)
for item in pr_doc.items:
returned_qty = flt(item_wise_returned_qty.get(item.name))
returned_amount = flt(returned_qty) * flt(item.rate)
@@ -1268,11 +1283,21 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
if (
item.billed_amt is not None
and item.amount is not None
and item_wise_billed_qty.get(item.name)
and (
item_wise_billed_qty.get(item.name)
or billed_qty_based_on_po.get(item.purchase_order_item)
)
):
adjusted_amt = (
flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate)
) * item.qty
qty = item_wise_billed_qty.get(item.name)
if not qty:
if item.qty < billed_qty_based_on_po.get(item.purchase_order_item):
qty = item.qty
else:
qty = billed_qty_based_on_po.get(item.purchase_order_item)
billed_qty_based_on_po[item.purchase_order_item] -= qty
adjusted_amt = (flt(item.billed_amt / qty) - flt(item.rate)) * item.qty
adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount"))
pi_landed_cost_amount += adjusted_amt
@@ -1316,6 +1341,32 @@ def get_billed_qty_against_purchase_receipt(pr_doc):
return frappe._dict(invoice_data)
def get_billed_qty_against_purchase_order(pr_doc):
po_names = list(
set(
[
d.purchase_order_item
for d in pr_doc.items
if d.purchase_order_item and not d.purchase_invoice_item
]
)
)
invoice_data_po_based = frappe._dict()
if po_names:
table = frappe.qb.DocType("Purchase Invoice Item")
query = (
frappe.qb.from_(table)
.select(table.po_detail, fn.Sum(table.qty).as_("qty"))
.where((table.po_detail.isin(po_names)) & (table.docstatus == 1) & (table.pr_detail.isnull()))
.groupby(table.po_detail)
)
invoice_data_po_based = query.run(as_list=1)
invoice_data_po_based = frappe._dict(invoice_data_po_based)
return invoice_data_po_based
def adjust_incoming_rate_for_pr(doc):
doc.update_valuation_rate(reset_outgoing_rate=False)

View File

@@ -684,6 +684,8 @@ class TestPurchaseReceipt(IntegrationTestCase):
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
frappe.flags.print_test_messages = False
# Qty: 10, Rate: 500
po = create_purchase_order()
pr1 = make_purchase_receipt(po.name)
@@ -703,6 +705,7 @@ class TestPurchaseReceipt(IntegrationTestCase):
pi2.get("items")[0].qty = 4
pi2.submit()
frappe.flags.print_test_messages = True
pr2 = make_purchase_receipt(po.name)
pr2.posting_date = today()
pr2.posting_time = "08:00"
@@ -4750,6 +4753,48 @@ class TestPurchaseReceipt(IntegrationTestCase):
self.assertRaises(NegativeStockError, pr.cancel)
@IntegrationTestCase.change_settings(
"Buying Settings", {"set_landed_cost_based_on_purchase_invoice_rate": 1, "maintain_same_rate": 0}
)
def test_set_lcv_from_pi_created_against_po(self):
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_invoice as make_pi_against_po,
)
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_receipt as make_pr_against_po,
)
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
original_value = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", 100)
item_code = create_item("Test Item for LCV from PI against PO").name
po = create_purchase_order(item_code=item_code, qty=10, rate=400)
pr = make_pr_against_po(po.name)
pr.items[0].qty = 5
item = frappe.copy_doc(pr.items[0])
item.qty = 2
pr.append("items", item)
item = frappe.copy_doc(pr.items[0])
item.qty = 3
pr.append("items", item)
pr.submit()
pi = make_pi_against_po(po.name)
pi.items[0].rate = 500
pi.submit()
pr.reload()
for row in pr.items:
self.assertTrue(row.amount_difference_with_purchase_invoice)
amt_diff = 5000 * (row.qty / 10) - row.amount
self.assertEqual(row.amount_difference_with_purchase_invoice, amt_diff)
frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", original_value)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier