mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-09 09:55:08 +00:00
fix: GL entries for different exchange rate in the purchase invoice
(cherry picked from commit a953709640)
This commit is contained in:
committed by
Mergify
parent
2de51be5ae
commit
5719992cda
@@ -983,6 +983,10 @@ class PurchaseInvoice(BuyingController):
|
||||
if provisional_accounting_for_non_stock_items:
|
||||
self.get_provisional_accounts()
|
||||
|
||||
adjust_incoming_rate = frappe.db.get_single_value(
|
||||
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
|
||||
)
|
||||
|
||||
for item in self.get("items"):
|
||||
if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate):
|
||||
if item.item_code:
|
||||
@@ -1161,7 +1165,11 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
# check if the exchange rate has changed
|
||||
if item.get("purchase_receipt") and self.auto_accounting_for_stock:
|
||||
if (
|
||||
not adjust_incoming_rate
|
||||
and item.get("purchase_receipt")
|
||||
and self.auto_accounting_for_stock
|
||||
):
|
||||
if (
|
||||
exchange_rate_map[item.purchase_receipt]
|
||||
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
|
||||
@@ -1198,6 +1206,7 @@ class PurchaseInvoice(BuyingController):
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
self.auto_accounting_for_stock
|
||||
and self.is_opening == "No"
|
||||
|
||||
@@ -350,6 +350,12 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
||||
make_purchase_invoice as create_purchase_invoice,
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
company="_Test Company with perpetual inventory",
|
||||
warehouse="Stores - TCP1",
|
||||
@@ -368,14 +374,19 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
||||
|
||||
# fetching the latest GL Entry with exchange gain and loss account account
|
||||
amount = frappe.db.get_value(
|
||||
"GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "credit"
|
||||
"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):
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||
make_purchase_invoice as create_purchase_invoice,
|
||||
|
||||
@@ -167,6 +167,15 @@ def create_supplier(**args):
|
||||
if not args.without_supplier_group:
|
||||
doc.supplier_group = args.supplier_group or "Services"
|
||||
|
||||
if args.get("party_account"):
|
||||
doc.append(
|
||||
"accounts",
|
||||
{
|
||||
"company": frappe.db.get_value("Account", args.get("party_account"), "company"),
|
||||
"account": args.get("party_account"),
|
||||
},
|
||||
)
|
||||
|
||||
doc.insert()
|
||||
|
||||
return doc
|
||||
|
||||
@@ -1259,11 +1259,11 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
||||
|
||||
total_amount, total_billed_amount, pi_landed_cost_amount = 0, 0, 0
|
||||
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
|
||||
billed_qty_amt = frappe._dict()
|
||||
|
||||
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)
|
||||
billed_qty_amt = get_billed_qty_amount_against_purchase_receipt(pr_doc)
|
||||
billed_qty_amt_based_on_po = get_billed_qty_amount_against_purchase_order(pr_doc)
|
||||
|
||||
for item in pr_doc.items:
|
||||
returned_qty = flt(item_wise_returned_qty.get(item.name))
|
||||
@@ -1293,22 +1293,46 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
||||
item.billed_amt is not None
|
||||
and item.amount is not None
|
||||
and (
|
||||
item_wise_billed_qty.get(item.name)
|
||||
or billed_qty_based_on_po.get(item.purchase_order_item)
|
||||
billed_qty_amt.get(item.name) or billed_qty_amt_based_on_po.get(item.purchase_order_item)
|
||||
)
|
||||
):
|
||||
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 = None
|
||||
if billed_qty_amt.get(item.name):
|
||||
qty = billed_qty_amt.get(item.name).get("qty")
|
||||
|
||||
if not qty and billed_qty_amt_based_on_po.get(item.purchase_order_item):
|
||||
if item.qty < billed_qty_amt_based_on_po.get(item.purchase_order_item)["qty"]:
|
||||
qty = item.qty
|
||||
else:
|
||||
qty = billed_qty_based_on_po.get(item.purchase_order_item)
|
||||
qty = billed_qty_amt_based_on_po.get(item.purchase_order_item)["qty"]
|
||||
|
||||
billed_qty_based_on_po[item.purchase_order_item] -= qty
|
||||
billed_qty_amt_based_on_po[item.purchase_order_item]["qty"] -= qty
|
||||
|
||||
adjusted_amt = (flt(item.billed_amt / qty) - flt(item.rate)) * item.qty
|
||||
billed_amt = item.billed_amt
|
||||
if billed_qty_amt.get(item.name):
|
||||
billed_amt = flt(billed_qty_amt.get(item.name).get("amount"))
|
||||
elif billed_qty_amt_based_on_po.get(item.purchase_order_item):
|
||||
total_billed_qty = (
|
||||
billed_qty_amt_based_on_po.get(item.purchase_order_item).get("qty") + qty
|
||||
)
|
||||
|
||||
adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount"))
|
||||
if total_billed_qty:
|
||||
billed_amt = flt(
|
||||
flt(billed_qty_amt_based_on_po.get(item.purchase_order_item).get("amount"))
|
||||
* (qty / total_billed_qty)
|
||||
)
|
||||
else:
|
||||
billed_amt = 0.0
|
||||
|
||||
# Reduce billed amount based on PO for next iterations
|
||||
billed_qty_amt_based_on_po[item.purchase_order_item]["amount"] -= billed_amt
|
||||
|
||||
if qty:
|
||||
adjusted_amt = (
|
||||
flt(billed_amt / qty) - (flt(item.rate) * flt(pr_doc.conversion_rate))
|
||||
) * item.qty
|
||||
|
||||
adjusted_amt = flt(adjusted_amt, item.precision("amount"))
|
||||
pi_landed_cost_amount += adjusted_amt
|
||||
item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
||||
elif amount and item.billed_amt > amount:
|
||||
@@ -1337,23 +1361,40 @@ 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):
|
||||
def get_billed_qty_amount_against_purchase_receipt(pr_doc):
|
||||
pr_names = [d.name for d in pr_doc.items]
|
||||
parent_table = frappe.qb.DocType("Purchase Invoice")
|
||||
table = frappe.qb.DocType("Purchase Invoice Item")
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(table.pr_detail, fn.Sum(table.qty).as_("qty"))
|
||||
frappe.qb.from_(parent_table)
|
||||
.inner_join(table)
|
||||
.on(parent_table.name == table.parent)
|
||||
.select(
|
||||
table.pr_detail,
|
||||
fn.Sum(table.amount * parent_table.conversion_rate).as_("amount"),
|
||||
fn.Sum(table.qty).as_("qty"),
|
||||
)
|
||||
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
||||
.groupby(table.pr_detail)
|
||||
)
|
||||
invoice_data = query.run(as_list=1)
|
||||
invoice_data = query.run(as_dict=1)
|
||||
|
||||
if not invoice_data:
|
||||
return frappe._dict()
|
||||
return frappe._dict(invoice_data)
|
||||
|
||||
billed_qty_amt = frappe._dict()
|
||||
|
||||
for row in invoice_data:
|
||||
if row.pr_detail not in billed_qty_amt:
|
||||
billed_qty_amt[row.pr_detail] = {"amount": 0, "qty": 0}
|
||||
|
||||
billed_qty_amt[row.pr_detail]["amount"] += flt(row.amount)
|
||||
billed_qty_amt[row.pr_detail]["qty"] += flt(row.qty)
|
||||
|
||||
return billed_qty_amt
|
||||
|
||||
|
||||
def get_billed_qty_against_purchase_order(pr_doc):
|
||||
def get_billed_qty_amount_against_purchase_order(pr_doc):
|
||||
po_names = list(
|
||||
set(
|
||||
[
|
||||
@@ -1366,15 +1407,32 @@ def get_billed_qty_against_purchase_order(pr_doc):
|
||||
|
||||
invoice_data_po_based = frappe._dict()
|
||||
if po_names:
|
||||
parent_table = frappe.qb.DocType("Purchase Invoice")
|
||||
table = frappe.qb.DocType("Purchase Invoice Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(table.po_detail, fn.Sum(table.qty).as_("qty"))
|
||||
frappe.qb.from_(parent_table)
|
||||
.inner_join(table)
|
||||
.on(parent_table.name == table.parent)
|
||||
.select(
|
||||
table.po_detail,
|
||||
fn.Sum(table.qty).as_("qty"),
|
||||
fn.Sum(table.amount * parent_table.conversion_rate).as_("amount"),
|
||||
)
|
||||
.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)
|
||||
|
||||
invoice_data = query.run(as_dict=1)
|
||||
if not invoice_data:
|
||||
return frappe._dict()
|
||||
|
||||
for row in invoice_data:
|
||||
if row.po_detail not in invoice_data_po_based:
|
||||
invoice_data_po_based[row.po_detail] = {"amount": 0, "qty": 0}
|
||||
|
||||
invoice_data_po_based[row.po_detail]["amount"] += flt(row.amount)
|
||||
invoice_data_po_based[row.po_detail]["qty"] += flt(row.qty)
|
||||
|
||||
return invoice_data_po_based
|
||||
|
||||
|
||||
@@ -5450,6 +5450,70 @@ class TestPurchaseReceipt(ERPNextTestSuite):
|
||||
self.assertEqual(pr.total_qty, 12)
|
||||
self.assertEqual(pr.total, 120)
|
||||
|
||||
def test_different_exchange_rate_in_pr_and_pi(self):
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
|
||||
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", 1)
|
||||
|
||||
party_account = create_account(
|
||||
account_name="USD Party Account Creditors",
|
||||
parent_account="Accounts Payable - TCP1",
|
||||
account_type="Payable",
|
||||
company="_Test Company with perpetual inventory",
|
||||
account_currency="USD",
|
||||
)
|
||||
|
||||
supplier = create_supplier(
|
||||
supplier_name="_Test USD Supplier New 1", default_currency="USD", party_account=party_account
|
||||
).name
|
||||
item_code = make_item("Test Item for Different Exchange Rate", {"is_stock_item": 1}).name
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
currency="USD",
|
||||
conversion_rate=80,
|
||||
rate=100,
|
||||
company="_Test Company with perpetual inventory",
|
||||
warehouse=frappe.get_value(
|
||||
"Warehouse", {"company": "_Test Company with perpetual inventory"}, "name"
|
||||
),
|
||||
supplier=supplier,
|
||||
)
|
||||
|
||||
self.assertEqual(pr.currency, "USD")
|
||||
self.assertEqual(pr.conversion_rate, 80)
|
||||
|
||||
gl_entries = get_gl_entries(pr.doctype, pr.name)
|
||||
self.assertTrue(len(gl_entries) == 2)
|
||||
for row in gl_entries:
|
||||
amount = row.credit or row.debit
|
||||
self.assertEqual(amount, 8000.0)
|
||||
|
||||
pi = make_purchase_invoice(pr.name)
|
||||
pi.conversion_rate = 90
|
||||
pi.currency = "USD"
|
||||
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
gl_entries = get_gl_entries(pi.doctype, pi.name)
|
||||
self.assertTrue(len(gl_entries) == 2)
|
||||
|
||||
accounts = ["USD Party Account Creditors - TCP1", "Stock Received But Not Billed - TCP1"]
|
||||
for row in gl_entries:
|
||||
amount = row.credit or row.debit
|
||||
self.assertEqual(amount, 9000.0)
|
||||
self.assertTrue(row.account in accounts)
|
||||
|
||||
frappe.db.set_single_value(
|
||||
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value
|
||||
)
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
@@ -5620,6 +5684,9 @@ def make_purchase_receipt(**args):
|
||||
pr.return_against = args.return_against
|
||||
pr.apply_putaway_rule = args.apply_putaway_rule
|
||||
|
||||
if args.get("conversion_rate") is not None:
|
||||
pr.conversion_rate = args.conversion_rate
|
||||
|
||||
qty = args.qty if args.qty is not None else 5
|
||||
rejected_qty = args.rejected_qty or 0
|
||||
received_qty = args.received_qty or flt(rejected_qty) + flt(qty)
|
||||
|
||||
Reference in New Issue
Block a user