mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-26 21:35:19 +00:00
feat: validate purchase receipt exchange rate parity on purchase invoice (#58177)
(cherry picked from commit 70a8a2d0c5)
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
This commit is contained in:
@@ -298,6 +298,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount")
|
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount")
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.validate_purchase_receipt_if_update_stock()
|
self.validate_purchase_receipt_if_update_stock()
|
||||||
|
self.validate_exchange_rate_with_purchase_receipt()
|
||||||
validate_inter_company_party(
|
validate_inter_company_party(
|
||||||
self.doctype, self.supplier, self.company, self.inter_company_invoice_reference
|
self.doctype, self.supplier, self.company, self.inter_company_invoice_reference
|
||||||
)
|
)
|
||||||
@@ -321,6 +322,47 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if total_billed_qty and total_received_qty:
|
if total_billed_qty and total_received_qty:
|
||||||
self.per_received = total_received_qty / total_billed_qty * 100
|
self.per_received = total_received_qty / total_billed_qty * 100
|
||||||
|
|
||||||
|
def validate_exchange_rate_with_purchase_receipt(self):
|
||||||
|
if self.is_internal_transfer() or not erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
|
return
|
||||||
|
|
||||||
|
stock_items = self.get_stock_items()
|
||||||
|
receipts = {
|
||||||
|
item.purchase_receipt
|
||||||
|
for item in self.items
|
||||||
|
if item.purchase_receipt and item.item_code in stock_items
|
||||||
|
}
|
||||||
|
if not receipts:
|
||||||
|
return
|
||||||
|
|
||||||
|
if frappe.db.get_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"):
|
||||||
|
return
|
||||||
|
|
||||||
|
mismatched = [
|
||||||
|
f"{frappe.bold(row.name)} ({row.conversion_rate})"
|
||||||
|
for row in frappe.get_all(
|
||||||
|
"Purchase Receipt",
|
||||||
|
filters={"name": ("in", list(receipts))},
|
||||||
|
fields=["name", "currency", "conversion_rate"],
|
||||||
|
)
|
||||||
|
if row.currency == self.currency
|
||||||
|
and flt(row.conversion_rate)
|
||||||
|
and flt(row.conversion_rate) != flt(self.conversion_rate)
|
||||||
|
]
|
||||||
|
if not mismatched:
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Exchange rate {0} does not match the exchange rate of Purchase Receipt {1}. Use the same exchange rate as the Purchase Receipt or enable {2} in {3} to adjust the landed cost based on this invoice."
|
||||||
|
).format(
|
||||||
|
frappe.bold(self.conversion_rate),
|
||||||
|
", ".join(mismatched),
|
||||||
|
frappe.bold(_("Set Landed Cost Based on Purchase Invoice Rate")),
|
||||||
|
get_link_to_form("Buying Settings", "Buying Settings", _("Buying Settings")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_invoice_hold(self):
|
def validate_invoice_hold(self):
|
||||||
if self.is_return:
|
if self.is_return:
|
||||||
frappe.throw(_("Return Purchase Invoice cannot be held."))
|
frappe.throw(_("Return Purchase Invoice cannot be held."))
|
||||||
|
|||||||
@@ -507,6 +507,12 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
|
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
|
||||||
|
self.addCleanup(
|
||||||
|
frappe.db.set_single_value,
|
||||||
|
"Buying Settings",
|
||||||
|
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||||
|
original_value,
|
||||||
|
)
|
||||||
|
|
||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
company="_Test Company with perpetual inventory",
|
company="_Test Company with perpetual inventory",
|
||||||
@@ -518,25 +524,15 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
|||||||
pi = create_purchase_invoice(pr.name)
|
pi = create_purchase_invoice(pr.name)
|
||||||
pi.conversion_rate = 80
|
pi.conversion_rate = 80
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.insert)
|
||||||
|
|
||||||
|
pi.conversion_rate = 70
|
||||||
pi.insert()
|
pi.insert()
|
||||||
pi.submit()
|
pi.submit()
|
||||||
|
|
||||||
# Get exchnage gain and loss account
|
|
||||||
exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account")
|
exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account")
|
||||||
|
self.assertFalse(
|
||||||
# fetching the latest GL Entry with exchange gain and loss account account
|
frappe.db.exists("GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name})
|
||||||
amount = frappe.db.get_value(
|
|
||||||
"GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit"
|
|
||||||
)
|
|
||||||
|
|
||||||
discrepancy_caused_by_exchange_rate_diff = abs(
|
|
||||||
pi.items[0].base_net_amount - pr.items[0].base_net_amount
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
|
||||||
|
|
||||||
frappe.db.set_single_value(
|
|
||||||
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_purchase_invoice_with_exchange_rate_difference_for_non_stock_item(self):
|
def test_purchase_invoice_with_exchange_rate_difference_for_non_stock_item(self):
|
||||||
@@ -544,7 +540,17 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
|||||||
make_purchase_invoice as create_purchase_invoice,
|
make_purchase_invoice as create_purchase_invoice,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Creating Purchase Invoice with USD currency
|
original_value = frappe.db.get_single_value(
|
||||||
|
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
|
||||||
|
)
|
||||||
|
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
|
||||||
|
self.addCleanup(
|
||||||
|
frappe.db.set_single_value,
|
||||||
|
"Buying Settings",
|
||||||
|
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||||
|
original_value,
|
||||||
|
)
|
||||||
|
|
||||||
pr = frappe.new_doc("Purchase Receipt")
|
pr = frappe.new_doc("Purchase Receipt")
|
||||||
pr.currency = "USD"
|
pr.currency = "USD"
|
||||||
pr.company = "_Test Company with perpetual inventory"
|
pr.company = "_Test Company with perpetual inventory"
|
||||||
@@ -558,34 +564,20 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
|||||||
"rate": 100,
|
"rate": 100,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
pr.append(
|
|
||||||
"items",
|
|
||||||
{"item_code": "_Test Item", "qty": 1, "rate": 5, "warehouse": "Stores - TCP1"},
|
|
||||||
)
|
|
||||||
pr.insert()
|
pr.insert()
|
||||||
pr.submit()
|
pr.submit()
|
||||||
|
|
||||||
# Createing purchase invoice against Purchase Receipt
|
|
||||||
pi = create_purchase_invoice(pr.name)
|
pi = create_purchase_invoice(pr.name)
|
||||||
pi.conversion_rate = 80
|
pi.conversion_rate = 80
|
||||||
pi.credit_to = "_Test Payable USD - TCP1"
|
pi.credit_to = "_Test Payable USD - TCP1"
|
||||||
pi.insert()
|
pi.insert()
|
||||||
pi.submit()
|
pi.submit()
|
||||||
|
|
||||||
# Get exchnage gain and loss account
|
|
||||||
exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account")
|
exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account")
|
||||||
|
self.assertFalse(
|
||||||
# fetching the latest GL Entry with exchange gain and loss account account
|
frappe.db.exists("GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name})
|
||||||
amount = frappe.db.get_value(
|
|
||||||
"GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "credit"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
discrepancy_caused_by_exchange_rate_diff = abs(
|
|
||||||
pi.items[1].base_net_amount - pr.items[1].base_net_amount
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(flt(discrepancy_caused_by_exchange_rate_diff, 2), amount)
|
|
||||||
|
|
||||||
def test_purchase_invoice_change_naming_series(self):
|
def test_purchase_invoice_change_naming_series(self):
|
||||||
pi = frappe.copy_doc(self.globalTestRecords["Purchase Invoice"][1])
|
pi = frappe.copy_doc(self.globalTestRecords["Purchase Invoice"][1])
|
||||||
pi.insert()
|
pi.insert()
|
||||||
|
|||||||
Reference in New Issue
Block a user