mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-12 11:25:09 +00:00
fix: GL entries for different exchange rate in the purchase invoice
(cherry picked from commit a953709640)
# Conflicts:
# erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
This commit is contained in:
committed by
Mergify
parent
1146c9550a
commit
def62cf3fe
@@ -978,6 +978,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if provisional_accounting_for_non_stock_items:
|
if provisional_accounting_for_non_stock_items:
|
||||||
self.get_provisional_accounts()
|
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"):
|
for item in self.get("items"):
|
||||||
if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate):
|
if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate):
|
||||||
if item.item_code:
|
if item.item_code:
|
||||||
@@ -1146,7 +1150,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# check if the exchange rate has changed
|
# 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 (
|
if (
|
||||||
exchange_rate_map[item.purchase_receipt]
|
exchange_rate_map[item.purchase_receipt]
|
||||||
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
|
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
|
||||||
@@ -1183,6 +1191,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.auto_accounting_for_stock
|
self.auto_accounting_for_stock
|
||||||
and self.is_opening == "No"
|
and self.is_opening == "No"
|
||||||
|
|||||||
@@ -356,6 +356,12 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
make_purchase_invoice as create_purchase_invoice,
|
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(
|
pr = make_purchase_receipt(
|
||||||
company="_Test Company with perpetual inventory",
|
company="_Test Company with perpetual inventory",
|
||||||
warehouse="Stores - TCP1",
|
warehouse="Stores - TCP1",
|
||||||
@@ -376,12 +382,17 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
amount = frappe.db.get_value(
|
amount = frappe.db.get_value(
|
||||||
"GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit"
|
"GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit"
|
||||||
)
|
)
|
||||||
|
|
||||||
discrepancy_caused_by_exchange_rate_diff = abs(
|
discrepancy_caused_by_exchange_rate_diff = abs(
|
||||||
pi.items[0].base_net_amount - pr.items[0].base_net_amount
|
pi.items[0].base_net_amount - pr.items[0].base_net_amount
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, 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):
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||||
make_purchase_invoice as create_purchase_invoice,
|
make_purchase_invoice as create_purchase_invoice,
|
||||||
|
|||||||
@@ -175,6 +175,15 @@ def create_supplier(**args):
|
|||||||
if not args.without_supplier_group:
|
if not args.without_supplier_group:
|
||||||
doc.supplier_group = args.supplier_group or "Services"
|
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()
|
doc.insert()
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|||||||
@@ -1135,9 +1135,15 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
|||||||
|
|
||||||
total_amount, total_billed_amount, pi_landed_cost_amount = 0, 0, 0
|
total_amount, total_billed_amount, pi_landed_cost_amount = 0, 0, 0
|
||||||
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
|
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
|
||||||
|
billed_qty_amt = frappe._dict()
|
||||||
|
|
||||||
if adjust_incoming_rate:
|
if adjust_incoming_rate:
|
||||||
|
<<<<<<< HEAD
|
||||||
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc)
|
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(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)
|
||||||
|
>>>>>>> a953709640 (fix: GL entries for different exchange rate in the purchase invoice)
|
||||||
|
|
||||||
for item in pr_doc.items:
|
for item in pr_doc.items:
|
||||||
returned_qty = flt(item_wise_returned_qty.get(item.name))
|
returned_qty = flt(item_wise_returned_qty.get(item.name))
|
||||||
@@ -1166,13 +1172,55 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
|||||||
if (
|
if (
|
||||||
item.billed_amt is not None
|
item.billed_amt is not None
|
||||||
and item.amount is not None
|
and item.amount is not None
|
||||||
|
<<<<<<< HEAD
|
||||||
and item_wise_billed_qty.get(item.name)
|
and item_wise_billed_qty.get(item.name)
|
||||||
):
|
):
|
||||||
adjusted_amt = (
|
adjusted_amt = (
|
||||||
flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate)
|
flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate)
|
||||||
) * item.qty
|
) * item.qty
|
||||||
|
=======
|
||||||
|
and (
|
||||||
|
billed_qty_amt.get(item.name) or billed_qty_amt_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")
|
||||||
|
|
||||||
adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount"))
|
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_amt_based_on_po.get(item.purchase_order_item)["qty"]
|
||||||
|
|
||||||
|
billed_qty_amt_based_on_po[item.purchase_order_item]["qty"] -= 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
|
||||||
|
)
|
||||||
|
>>>>>>> a953709640 (fix: GL entries for different exchange rate in the purchase invoice)
|
||||||
|
|
||||||
|
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
|
pi_landed_cost_amount += adjusted_amt
|
||||||
item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
||||||
elif amount and item.billed_amt > amount:
|
elif amount and item.billed_amt > amount:
|
||||||
@@ -1201,22 +1249,85 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
|||||||
adjust_incoming_rate_for_pr(pr_doc)
|
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]
|
pr_names = [d.name for d in pr_doc.items]
|
||||||
|
parent_table = frappe.qb.DocType("Purchase Invoice")
|
||||||
table = frappe.qb.DocType("Purchase Invoice Item")
|
table = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(table)
|
frappe.qb.from_(parent_table)
|
||||||
.select(table.pr_detail, fn.Sum(table.qty).as_("qty"))
|
.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))
|
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
||||||
.groupby(table.pr_detail)
|
.groupby(table.pr_detail)
|
||||||
)
|
)
|
||||||
invoice_data = query.run(as_list=1)
|
invoice_data = query.run(as_dict=1)
|
||||||
|
|
||||||
if not invoice_data:
|
if not invoice_data:
|
||||||
return frappe._dict()
|
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
|
||||||
|
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
def get_billed_qty_amount_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:
|
||||||
|
parent_table = frappe.qb.DocType("Purchase Invoice")
|
||||||
|
table = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
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 = 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
|
||||||
|
|
||||||
|
|
||||||
|
>>>>>>> a953709640 (fix: GL entries for different exchange rate in the purchase invoice)
|
||||||
def adjust_incoming_rate_for_pr(doc):
|
def adjust_incoming_rate_for_pr(doc):
|
||||||
doc.update_valuation_rate(reset_outgoing_rate=False)
|
doc.update_valuation_rate(reset_outgoing_rate=False)
|
||||||
|
|
||||||
|
|||||||
@@ -5133,6 +5133,70 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
self.assertEqual(pr.total_qty, 12)
|
self.assertEqual(pr.total_qty, 12)
|
||||||
self.assertEqual(pr.total, 120)
|
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():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
@@ -5303,6 +5367,9 @@ def make_purchase_receipt(**args):
|
|||||||
pr.return_against = args.return_against
|
pr.return_against = args.return_against
|
||||||
pr.apply_putaway_rule = args.apply_putaway_rule
|
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
|
qty = args.qty if args.qty is not None else 5
|
||||||
rejected_qty = args.rejected_qty or 0
|
rejected_qty = args.rejected_qty or 0
|
||||||
received_qty = args.received_qty or flt(rejected_qty) + flt(qty)
|
received_qty = args.received_qty or flt(rejected_qty) + flt(qty)
|
||||||
|
|||||||
Reference in New Issue
Block a user