fix: set landed cost based on purchase invoice rate

(cherry picked from commit 17d415b105)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
#	erpnext/patches.txt
#	erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
This commit is contained in:
Mihir Kandoi
2025-02-17 14:36:15 +05:30
committed by Mergify
parent c85fd36960
commit fdaf5fafda
9 changed files with 195 additions and 21 deletions

View File

@@ -2464,6 +2464,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
<<<<<<< HEAD
def test_last_purchase_rate(self):
item = create_item("_Test Item For Last Purchase Rate from PI", is_stock_item=1)
pi1 = make_purchase_invoice(item_code=item.item_code, qty=10, rate=100)
@@ -2481,6 +2482,77 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi1.cancel()
item.reload()
self.assertEqual(item.last_purchase_rate, 0)
=======
def test_adjust_incoming_rate_from_pi_with_multi_currency_and_partial_billing(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
pr = make_purchase_receipt(
qty=10, rate=10, currency="USD", do_not_save=1, supplier="_Test Supplier USD"
)
pr.conversion_rate = 5300
pr.save()
pr.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
self.assertEqual(incoming_rate, 53000) # Asserting to confirm if the default calculation is correct
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 1 : Incoming rate should not change as only the qty has changed and not the rate (this was not the case before)
self.assertEqual(incoming_rate, 53000)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
row.rate = 9
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 2 : Rate in new PI is lower than PR, so incoming rate should also be lower
self.assertEqual(incoming_rate, 50350)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
row.rate = 12
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 3 : Rate in new PI is higher than PR, so incoming rate should also be higher
self.assertEqual(incoming_rate, 54766.667)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate)
def test_opening_invoice_rounding_adjustment_validation(self):
pi = make_purchase_invoice(do_not_save=1)

View File

@@ -333,7 +333,7 @@ class BuyingController(SubcontractingController):
net_rate
+ item.item_tax_amount
+ flt(item.landed_cost_voucher_amount)
+ flt(item.get("rate_difference_with_purchase_invoice"))
+ flt(item.get("amount_difference_with_purchase_invoice"))
) / qty_in_stock_uom
else:
item.valuation_rate = 0.0

View File

@@ -261,7 +261,11 @@ erpnext.patches.v14_0.show_loan_management_deprecation_warning
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True)
erpnext.patches.v14_0.update_proprietorship_to_individual
<<<<<<< HEAD
erpnext.patches.v15_0.rename_subcontracting_fields
=======
erpnext.stock.doctype.purchase_receipt_item.patches.rename_field_from_rate_difference_to_amount_difference
>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate)
[post_model_sync]
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
@@ -393,8 +397,12 @@ erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
erpnext.patches.v15_0.sync_auto_reconcile_config
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
erpnext.patches.v14_0.disable_add_row_in_gross_profit
<<<<<<< HEAD
erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment
erpnext.patches.v14_0.update_posting_datetime
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
erpnext.patches.v15_0.rename_sla_fields
erpnext.patches.v15_0.update_query_report
=======
erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_difference_field
>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate)

View File

@@ -424,6 +424,14 @@ class PurchaseReceipt(BuyingController):
self.delete_auto_created_batches()
self.set_consumed_qty_in_subcontract_order()
def before_cancel(self):
super().before_cancel()
self.remove_amount_difference_with_purchase_invoice()
def remove_amount_difference_with_purchase_invoice(self):
for item in self.items:
item.amount_difference_with_purchase_invoice = 0
def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False):
from erpnext.accounts.general_ledger import process_gl_map
@@ -571,15 +579,15 @@ class PurchaseReceipt(BuyingController):
item=item,
)
def make_rate_difference_entry(item):
if item.rate_difference_with_purchase_invoice and stock_asset_rbnb:
def make_amount_difference_entry(item):
if item.amount_difference_with_purchase_invoice and stock_asset_rbnb:
account_currency = get_account_currency(stock_asset_rbnb)
self.add_gl_entry(
gl_entries=gl_entries,
account=stock_asset_rbnb,
cost_center=item.cost_center,
debit=0.0,
credit=flt(item.rate_difference_with_purchase_invoice),
credit=flt(item.amount_difference_with_purchase_invoice),
remarks=_("Adjustment based on Purchase Invoice rate"),
against_account=stock_asset_account_name,
account_currency=account_currency,
@@ -612,7 +620,7 @@ class PurchaseReceipt(BuyingController):
+ flt(item.landed_cost_voucher_amount)
+ flt(item.rm_supp_cost)
+ flt(item.item_tax_amount)
+ flt(item.rate_difference_with_purchase_invoice)
+ flt(item.amount_difference_with_purchase_invoice)
)
divisional_loss = flt(
@@ -712,7 +720,7 @@ class PurchaseReceipt(BuyingController):
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
outgoing_amount = make_stock_received_but_not_billed_entry(d)
make_landed_cost_gl_entries(d)
make_rate_difference_entry(d)
make_amount_difference_entry(d)
make_sub_contracting_gl_entries(d)
make_divisional_loss_gl_entry(d, outgoing_amount)
elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or (
@@ -1094,11 +1102,19 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
if adjust_incoming_rate:
adjusted_amt = 0.0
if item.billed_amt is not None and item.amount is not None:
adjusted_amt = flt(item.billed_amt) - flt(item.amount)
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc)
adjusted_amt = adjusted_amt * flt(pr_doc.conversion_rate)
item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
if (
item.billed_amt is not None
and item.amount is not None
and item_wise_billed_qty.get(item.name)
):
adjusted_amt = (
flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate)
) * item.qty
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)
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
pr_doc.db_set("per_billed", percent_billed)
@@ -1111,6 +1127,21 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
adjust_incoming_rate_for_pr(pr_doc)
def get_billed_qty_against_purchase_receipt(pr_doc):
pr_names = [d.name for d in pr_doc.items]
table = frappe.qb.DocType("Purchase Invoice Item")
query = (
frappe.qb.from_(table)
.select(table.pr_detail, fn.Sum(table.qty).as_("qty"))
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
)
invoice_data = query.run(as_list=1)
if not invoice_data:
return frappe._dict()
return frappe._dict(invoice_data)
def adjust_incoming_rate_for_pr(doc):
doc.update_valuation_rate(reset_outgoing_rate=False)

View File

@@ -0,0 +1,37 @@
import frappe
from frappe.utils import flt
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
adjust_incoming_rate_for_pr,
get_billed_qty_against_purchase_receipt,
)
def execute():
table = frappe.qb.DocType("Purchase Receipt Item")
query = (
frappe.qb.from_(table)
.select(table.parent)
.distinct()
.where((table.amount_difference_with_purchase_invoice > 0) & (table.docstatus == 1))
)
pr_names = [item.parent for item in query.run(as_dict=True)]
for pr_name in pr_names:
pr_doc = frappe.get_doc("Purchase Receipt", pr_name)
for item in pr_doc.items:
adjusted_amt = 0.0
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc)
if (
item.billed_amt is not None
and item.amount is not None
and item_wise_billed_qty.get(item.name)
):
adjusted_amt = (
flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate)
) * item.qty
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)
adjust_incoming_rate_for_pr(pr_doc)

View File

@@ -0,0 +1,17 @@
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
frappe.db.set_value(
"DocField",
{"parent": "Purchase Receipt Item", "fieldname": "rate_difference_with_purchase_invoice"},
"label",
"Amount Difference with Purchase Invoice",
)
rename_field(
"Purchase Receipt Item",
"rate_difference_with_purchase_invoice",
"amount_difference_with_purchase_invoice",
)
frappe.clear_cache(doctype="Purchase Receipt Item")

View File

@@ -71,7 +71,7 @@
"item_tax_amount",
"rm_supp_cost",
"landed_cost_voucher_amount",
"rate_difference_with_purchase_invoice",
"amount_difference_with_purchase_invoice",
"billed_amt",
"warehouse_and_reference",
"warehouse",
@@ -998,14 +998,6 @@
"label": "Has Item Scanned",
"read_only": 1
},
{
"fieldname": "rate_difference_with_purchase_invoice",
"fieldtype": "Currency",
"label": "Rate Difference with Purchase Invoice",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "serial_and_batch_bundle",
@@ -1135,12 +1127,29 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1
<<<<<<< HEAD
=======
},
{
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
},
{
"fieldname": "amount_difference_with_purchase_invoice",
"fieldtype": "Currency",
"label": "Amount Difference with Purchase Invoice",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate)
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-07-19 12:14:21.521466",
"modified": "2025-02-17 13:15:36.692202",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@@ -16,6 +16,7 @@ class PurchaseReceiptItem(Document):
allow_zero_valuation_rate: DF.Check
amount: DF.Currency
amount_difference_with_purchase_invoice: DF.Currency
apply_tds: DF.Check
asset_category: DF.Link | None
asset_location: DF.Link | None
@@ -76,7 +77,6 @@ class PurchaseReceiptItem(Document):
qty: DF.Float
quality_inspection: DF.Link | None
rate: DF.Currency
rate_difference_with_purchase_invoice: DF.Currency
rate_with_margin: DF.Currency
received_qty: DF.Float
received_stock_qty: DF.Float