From 785a71dcd2444cc085f286140c7595e95ad47d09 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sun, 6 Jun 2021 22:01:18 +0530 Subject: [PATCH 01/13] fix(Purchase Invoice): Resolve difference caused by change in exchange rate --- .../purchase_invoice/purchase_invoice.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 45d89ad1c8..85b4642604 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -634,6 +634,29 @@ class PurchaseInvoice(BuyingController): "project": item.project or self.project }, account_currency, item=item)) + # check if the exchange rate has changed + purchase_receipt_conversion_rate = frappe.db.get_value('Purchase Receipt', {'name': item.purchase_receipt}, ['conversion_rate']) + if self.conversion_rate != purchase_receipt_conversion_rate: + discrepancy_caused_by_exchange_rate_difference = (item.qty * item.rate) * (purchase_receipt_conversion_rate - self.conversion_rate) + gl_entries.append( + self.get_gl_dict({ + "account": expense_account, + "against": self.supplier, + "debit": discrepancy_caused_by_exchange_rate_difference, + "cost_center": item.cost_center, + "project": item.project or self.project + }, account_currency, item=item) + ) + gl_entries.append( + self.get_gl_dict({ + "account": self.get_company_default("exchange_gain_loss_account"), + "against": self.supplier, + "credit": discrepancy_caused_by_exchange_rate_difference, + "cost_center": item.cost_center, + "project": item.project or self.project + }, account_currency, item=item) + ) + # If asset is bought through this document and not linked to PR if self.update_stock and item.landed_cost_voucher_amount: expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") From 6233eaa3bb6d31df7c47dc0d0be15d52633ddf4a Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 7 Jun 2021 05:28:20 +0530 Subject: [PATCH 02/13] fix(Purchase Invoice): Fix condition --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 85b4642604..01df8060ac 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -636,7 +636,7 @@ class PurchaseInvoice(BuyingController): # check if the exchange rate has changed purchase_receipt_conversion_rate = frappe.db.get_value('Purchase Receipt', {'name': item.purchase_receipt}, ['conversion_rate']) - if self.conversion_rate != purchase_receipt_conversion_rate: + if purchase_receipt_conversion_rate and self.conversion_rate != purchase_receipt_conversion_rate: discrepancy_caused_by_exchange_rate_difference = (item.qty * item.rate) * (purchase_receipt_conversion_rate - self.conversion_rate) gl_entries.append( self.get_gl_dict({ From 206313d69bfc591afd59ee56b656717543bfd1c1 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 7 Jun 2021 06:11:57 +0530 Subject: [PATCH 03/13] fix(Purchase Invoice): Add test for exchange rate difference handling --- .../purchase_invoice/test_purchase_invoice.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2f5d36c8fa..7f350e7ed5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -230,6 +230,23 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) + def test_purchase_invoice_with_exchange_rate_difference(self): + set_gst_settings() + pr = make_purchase_receipt(currency = "USD", conversion_rate = 70) + pi = make_purchase_invoice(currency = "USD", conversion_rate = 80, do_not_save = "True") + + for item in pi.items: + item.purchase_receipt = pr.name + + pi.insert() + pi.submit() + + # fetching the latest GL Entry with 'Exchange Gain/Loss - _TC' account + gl_entries = frappe.get_all('GL Entry', filters = {'account': 'Exchange Gain/Loss - _TC'}) + voucher_no = frappe.get_value('GL Entry', gl_entries[0]['name'], 'voucher_no') + + self.assertEqual(pi.name, voucher_no) + def test_purchase_invoice_change_naming_series(self): pi = frappe.copy_doc(test_records[1]) pi.insert() @@ -1050,6 +1067,24 @@ def update_tax_witholding_category(company, account, date): 'account': account }) tds_category.save() +def set_gst_settings(): + gst_settings = frappe.get_doc("GST Settings") + + gst_account = frappe.get_all( + "GST Account", + fields=["cgst_account", "sgst_account", "igst_account"], + filters = {"company": "_Test Company"} + ) + + if not gst_account: + gst_settings.append("gst_accounts", { + "company": "_Test Company", + "cgst_account": "CGST - _TC", + "sgst_account": "SGST - _TC", + "igst_account": "IGST - _TC", + }) + + gst_settings.save() def unlink_payment_on_cancel_of_invoice(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") From d97505b277535760b2cf3f2352ea10660dcefd46 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 7 Jun 2021 07:09:11 +0530 Subject: [PATCH 04/13] fix(Purchase Receipt): Resolve difference caused by change in exchange rate --- .../purchase_receipt/purchase_receipt.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index e488b695b5..54cd33242b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -288,6 +288,10 @@ class PurchaseReceipt(BuyingController): self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks, stock_rbnb, account_currency=warehouse_account_currency, item=d) + print("*"* 30) + print(1) + print("warehouse_account_name: ", warehouse_account_name) + print("") # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation @@ -303,6 +307,19 @@ class PurchaseReceipt(BuyingController): self.add_gl_entry(gl_entries, account, d.cost_center, -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name, debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d) + + # check if the exchange rate has changed + purchase_invoice_conversion_rate = frappe.db.get_value('Purchase Invoice', {'name': d.purchase_invoice}, ['conversion_rate']) + if purchase_invoice_conversion_rate and self.conversion_rate != purchase_invoice_conversion_rate: + discrepancy_caused_by_exchange_rate_difference = (d.qty * d.rate) * (purchase_invoice_conversion_rate - self.conversion_rate) + + self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, discrepancy_caused_by_exchange_rate_difference, + remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, item=d) + + self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0, + remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, item=d) # Amount added through landed-cos-voucher if d.landed_cost_voucher_amount and landed_cost_entries: From 5314445d58f36e7abb527911e3446ebf909edcc6 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 7 Jun 2021 08:02:11 +0530 Subject: [PATCH 05/13] fix(Purchase Receipt): Add test for exchange rate difference handling --- .../purchase_receipt/test_purchase_receipt.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 2586a0fc0c..0ce4c3a669 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1051,6 +1051,28 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(len(item_two_gl_entry), 1) frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value) + def test_purchase_receipt_with_exchange_rate_difference(self): + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice + + pi = create_purchase_invoice(currency = "USD", conversion_rate = 70) + + create_warehouse("_Test Warehouse for Valuation", company="_Test Company with perpetual inventory", + properties={"account": '_Test Account Stock In Hand - TCP1'}) + pr = make_purchase_receipt(warehouse = '_Test Warehouse for Valuation - TCP1', + company="_Test Company with perpetual inventory", currency = "USD", conversion_rate = 80, + do_not_save = "True") + + for item in pr.items: + item.purchase_invoice = pi.name + + pr.insert() + pr.submit() + + # fetching the latest GL Entry with 'Exchange Gain/Loss - TCP1' account + gl_entries = frappe.get_all('GL Entry', filters = {'account': 'Exchange Gain/Loss - TCP1'}) + voucher_no = frappe.get_value('GL Entry', gl_entries[0]['name'], 'voucher_no') + + self.assertEqual(pr.name, voucher_no) def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference From feed97ec53767e777b8a638d5a8e3ffc0a04e500 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 11 Jun 2021 17:27:43 +0530 Subject: [PATCH 06/13] fix: Test --- ...tracted_raw_materials_to_be_transferred.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index 2448e17c50..f1784925c5 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -60,6 +60,7 @@ def transfer_subcontracted_raw_materials(po): rm_item = [ { 'name': po.supplied_items[0].name, +<<<<<<< HEAD 'item_code': item_1, 'rm_item_code': item_1, 'item_name': item_1, @@ -67,10 +68,20 @@ def transfer_subcontracted_raw_materials(po): 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100 * transfer_qty_map[item_1], +======= + 'item_code': '_Test Item Home Desktop 100', + 'rm_item_code': '_Test Item Home Desktop 100', + 'item_name': '_Test Item Home Desktop 100', + 'qty': 2, + 'warehouse': '_Test Warehouse - _TC', + 'rate': 100, + 'amount': 200, +>>>>>>> c4d851e45f (fix: Test) 'stock_uom': 'Nos' }, { 'name': po.supplied_items[1].name, +<<<<<<< HEAD 'item_code': item_2, 'rm_item_code': item_2, 'item_name': item_2, @@ -78,6 +89,15 @@ def transfer_subcontracted_raw_materials(po): 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100 * transfer_qty_map[item_2], +======= + 'item_code': '_Test Item', + 'rm_item_code': '_Test Item', + 'item_name': '_Test Item', + 'qty': 1, + 'warehouse': '_Test Warehouse - _TC', + 'rate': 100, + 'amount': 100, +>>>>>>> c4d851e45f (fix: Test) 'stock_uom': 'Nos' } ] From 3f5e7bf6d703807f6774d86839f39f6587b1f576 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 16 Jun 2021 06:13:38 +0530 Subject: [PATCH 07/13] fix(Purchase Invoice): Fetch Purchase Receipt details using a function --- .../purchase_invoice/purchase_invoice.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 01df8060ac..32e2eb82f8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -517,6 +517,8 @@ class PurchaseInvoice(BuyingController): if d.category in ('Valuation', 'Total and Valuation') and flt(d.base_tax_amount_after_discount_amount)] + purchase_receipt_details = self.get_purchase_receipt_details() + for item in self.get("items"): if flt(item.base_net_amount): account_currency = get_account_currency(item.expense_account) @@ -634,10 +636,13 @@ class PurchaseInvoice(BuyingController): "project": item.project or self.project }, account_currency, item=item)) - # check if the exchange rate has changed - purchase_receipt_conversion_rate = frappe.db.get_value('Purchase Receipt', {'name': item.purchase_receipt}, ['conversion_rate']) - if purchase_receipt_conversion_rate and self.conversion_rate != purchase_receipt_conversion_rate: - discrepancy_caused_by_exchange_rate_difference = (item.qty * item.rate) * (purchase_receipt_conversion_rate - self.conversion_rate) + if purchase_receipt_details[item.item_code]["conversion_rate"] and \ + self.conversion_rate != purchase_receipt_details[item.item_code]["conversion_rate"] and \ + item.net_rate == purchase_receipt_details[item.item_code]["net_rate"]: + + discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * \ + (purchase_receipt_details[item.item_code]["conversion_rate"] - self.conversion_rate) + gl_entries.append( self.get_gl_dict({ "account": expense_account, @@ -710,6 +715,23 @@ class PurchaseInvoice(BuyingController): self.negative_expense_to_be_booked += flt(item.item_tax_amount, \ item.precision("item_tax_amount")) + def get_purchase_receipt_details(self): + purchase_receipt_details = {} + for item in self.items: + if item.purchase_receipt: + purchase_receipt = frappe.get_doc('Purchase Receipt', item.purchase_receipt) + pr_item_details = { + "conversion_rate" : purchase_receipt.conversion_rate + } + + for pr_item in purchase_receipt.items: + if pr_item.item_code == item.item_code: + pr_item_details["net_rate"] = pr_item.net_rate + + purchase_receipt_details[item.item_code] = pr_item_details + + return purchase_receipt_details + def get_asset_gl_entry(self, gl_entries): arbnb_account = self.get_company_default("asset_received_but_not_billed") eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") From a826fb8f95144a9e14d961bbe6d779c4c5aa668c Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 16 Jun 2021 06:18:35 +0530 Subject: [PATCH 08/13] fix(Purchase Receipt): Remove print statements --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 54cd33242b..305eb02714 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -287,11 +287,7 @@ class PurchaseReceipt(BuyingController): continue self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks, - stock_rbnb, account_currency=warehouse_account_currency, item=d) - print("*"* 30) - print(1) - print("warehouse_account_name: ", warehouse_account_name) - print("") + stock_rbnb, account_currency=warehouse_account_currency, item=d) # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation From 8f52db788a898262c8b546cc42a31e86f1a43560 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 16 Jun 2021 06:38:39 +0530 Subject: [PATCH 09/13] fix(Purchase Invoice): Improve test for exchange rate difference handling --- .../doctype/purchase_invoice/test_purchase_invoice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 7f350e7ed5..c38b9017c8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -247,6 +247,11 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(pi.name, voucher_no) + exchange_gain_loss_amount = frappe.get_value('GL Entry', gl_entries[0]['name'], 'debit') + discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount) + + self.assertEqual(exchange_gain_loss_amount, discrepancy_caused_by_exchange_rate_diff) + def test_purchase_invoice_change_naming_series(self): pi = frappe.copy_doc(test_records[1]) pi.insert() From 506d5ac3d58e6266037f16e9fab4dec506171334 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 28 Jun 2021 15:08:28 +0530 Subject: [PATCH 10/13] fix: Make exchange rate handling more efficient --- .../purchase_invoice/purchase_invoice.py | 77 ++++++++++--------- .../purchase_receipt/purchase_receipt.py | 48 +++++++++--- 2 files changed, 78 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 32e2eb82f8..05d632219a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -517,7 +517,7 @@ class PurchaseInvoice(BuyingController): if d.category in ('Valuation', 'Total and Valuation') and flt(d.base_tax_amount_after_discount_amount)] - purchase_receipt_details = self.get_purchase_receipt_details() + exchange_rate_map, net_rate_map = self.get_purchase_receipt_details() for item in self.get("items"): if flt(item.base_net_amount): @@ -636,31 +636,33 @@ class PurchaseInvoice(BuyingController): "project": item.project or self.project }, account_currency, item=item)) - if purchase_receipt_details[item.item_code]["conversion_rate"] and \ - self.conversion_rate != purchase_receipt_details[item.item_code]["conversion_rate"] and \ - item.net_rate == purchase_receipt_details[item.item_code]["net_rate"]: + # check if the exchange rate has changed + if item.get('purchase_receipt'): + if exchange_rate_map[item.purchase_receipt] and \ + self.conversion_rate != exchange_rate_map[item.purchase_receipt] and \ + item.net_rate == net_rate_map[item.item_code]: - discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * \ - (purchase_receipt_details[item.item_code]["conversion_rate"] - self.conversion_rate) + discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * \ + (exchange_rate_map[item.purchase_receipt] - self.conversion_rate) - gl_entries.append( - self.get_gl_dict({ - "account": expense_account, - "against": self.supplier, - "debit": discrepancy_caused_by_exchange_rate_difference, - "cost_center": item.cost_center, - "project": item.project or self.project - }, account_currency, item=item) - ) - gl_entries.append( - self.get_gl_dict({ - "account": self.get_company_default("exchange_gain_loss_account"), - "against": self.supplier, - "credit": discrepancy_caused_by_exchange_rate_difference, - "cost_center": item.cost_center, - "project": item.project or self.project - }, account_currency, item=item) - ) + gl_entries.append( + self.get_gl_dict({ + "account": expense_account, + "against": self.supplier, + "debit": discrepancy_caused_by_exchange_rate_difference, + "cost_center": item.cost_center, + "project": item.project or self.project + }, account_currency, item=item) + ) + gl_entries.append( + self.get_gl_dict({ + "account": self.get_company_default("exchange_gain_loss_account"), + "against": self.supplier, + "credit": discrepancy_caused_by_exchange_rate_difference, + "cost_center": item.cost_center, + "project": item.project or self.project + }, account_currency, item=item) + ) # If asset is bought through this document and not linked to PR if self.update_stock and item.landed_cost_voucher_amount: @@ -716,21 +718,22 @@ class PurchaseInvoice(BuyingController): item.precision("item_tax_amount")) def get_purchase_receipt_details(self): - purchase_receipt_details = {} - for item in self.items: - if item.purchase_receipt: - purchase_receipt = frappe.get_doc('Purchase Receipt', item.purchase_receipt) - pr_item_details = { - "conversion_rate" : purchase_receipt.conversion_rate - } - - for pr_item in purchase_receipt.items: - if pr_item.item_code == item.item_code: - pr_item_details["net_rate"] = pr_item.net_rate + purchase_receipts = [] + pr_items = [] - purchase_receipt_details[item.item_code] = pr_item_details + for item in self.get('items'): + if item.get('purchase_receipt'): + purchase_receipts.append(item.purchase_receipt) + if item.get('pr_detail'): + pr_items.append(item.pr_detail) + + exchange_rate_map = frappe._dict(frappe.get_all('Purchase Receipt', filters={'name': ('in', + purchase_receipts)}, fields=['name', 'conversion_rate'], as_list=1)) - return purchase_receipt_details + net_rate_map = frappe._dict(frappe.get_all('Purchase Receipt Item', filters={'name': ('in', + pr_items)}, fields=['item_code', 'net_rate'], as_list=1)) + + return exchange_rate_map, net_rate_map def get_asset_gl_entry(self, gl_entries): arbnb_account = self.get_company_default("asset_received_but_not_billed") diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 305eb02714..7b002b0fc6 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -262,6 +262,8 @@ class PurchaseReceipt(BuyingController): warehouse_with_no_account = [] stock_items = self.get_stock_items() + exchange_rate_map, net_rate_map = self.get_purchase_invoice_details() + for d in self.get("items"): if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): if warehouse_account.get(d.warehouse): @@ -303,19 +305,23 @@ class PurchaseReceipt(BuyingController): self.add_gl_entry(gl_entries, account, d.cost_center, -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name, debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d) - + # check if the exchange rate has changed - purchase_invoice_conversion_rate = frappe.db.get_value('Purchase Invoice', {'name': d.purchase_invoice}, ['conversion_rate']) - if purchase_invoice_conversion_rate and self.conversion_rate != purchase_invoice_conversion_rate: - discrepancy_caused_by_exchange_rate_difference = (d.qty * d.rate) * (purchase_invoice_conversion_rate - self.conversion_rate) + if d.get('purchase_invoice'): + if exchange_rate_map[d.purchase_invoice] and \ + self.conversion_rate != exchange_rate_map[d.purchase_invoice] and \ + d.net_rate == net_rate_map[d.item_code]: - self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, discrepancy_caused_by_exchange_rate_difference, - remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, item=d) + discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * \ + (exchange_rate_map[d.purchase_invoice] - self.conversion_rate) - self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0, - remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, item=d) + self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, discrepancy_caused_by_exchange_rate_difference, + remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, item=d) + + self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0, + remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, item=d) # Amount added through landed-cos-voucher if d.landed_cost_voucher_amount and landed_cost_entries: @@ -495,6 +501,28 @@ class PurchaseReceipt(BuyingController): self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), remarks, expenses_included_in_asset_valuation, project=item.project, item=item) + def get_purchase_invoice_details(self): + purchase_invoices = [] + pi_items = [] + + for item in self.get('items'): + if item.get('purchase_invoice'): + purchase_invoices.append(item.purchase_invoice) + if item.get('purchase_invoice_item'): + pi_items.append(item.purchase_invoice_item) + + exchange_rate_map = frappe._dict(frappe.get_all('Purchase Invoice', filters={'name': ('in', + purchase_invoices)}, fields=['name', 'conversion_rate'], as_list=1)) + print("*"*50) + print("In get_purchase_invoice_details:") + print("exchange_rate_map: ", exchange_rate_map) + + net_rate_map = frappe._dict(frappe.get_all('Purchase Invoice Item', filters={'name': ('in', + pi_items)}, fields=['item_code', 'net_rate'], as_list=1)) + print("net_rate_map: ", net_rate_map) + + return exchange_rate_map, net_rate_map + def update_assets(self, item, valuation_rate): assets = frappe.db.get_all('Asset', filters={ 'purchase_receipt': self.name, 'item_code': item.item_code } From d748e7f49cbbdf46e6eabf4c6168ecb80cbe065a Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 28 Jun 2021 15:33:15 +0530 Subject: [PATCH 11/13] fix: Create common function for fetching conversion rate details of linked docs --- .../purchase_invoice/purchase_invoice.py | 50 ++++++++++++------- .../purchase_receipt/purchase_receipt.py | 26 ++-------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 05d632219a..c164868678 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -517,7 +517,7 @@ class PurchaseInvoice(BuyingController): if d.category in ('Valuation', 'Total and Valuation') and flt(d.base_tax_amount_after_discount_amount)] - exchange_rate_map, net_rate_map = self.get_purchase_receipt_details() + exchange_rate_map, net_rate_map = get_pr_or_pi_details(self) for item in self.get("items"): if flt(item.base_net_amount): @@ -717,24 +717,6 @@ class PurchaseInvoice(BuyingController): self.negative_expense_to_be_booked += flt(item.item_tax_amount, \ item.precision("item_tax_amount")) - def get_purchase_receipt_details(self): - purchase_receipts = [] - pr_items = [] - - for item in self.get('items'): - if item.get('purchase_receipt'): - purchase_receipts.append(item.purchase_receipt) - if item.get('pr_detail'): - pr_items.append(item.pr_detail) - - exchange_rate_map = frappe._dict(frappe.get_all('Purchase Receipt', filters={'name': ('in', - purchase_receipts)}, fields=['name', 'conversion_rate'], as_list=1)) - - net_rate_map = frappe._dict(frappe.get_all('Purchase Receipt Item', filters={'name': ('in', - pr_items)}, fields=['item_code', 'net_rate'], as_list=1)) - - return exchange_rate_map, net_rate_map - def get_asset_gl_entry(self, gl_entries): arbnb_account = self.get_company_default("asset_received_but_not_billed") eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") @@ -1189,6 +1171,36 @@ class PurchaseInvoice(BuyingController): if update: self.db_set('status', self.status, update_modified = update_modified) +# to get details of purchase invoice/receipt from which this doc was created for exchange rate difference handling +def get_pr_or_pi_details(doc): + if doc.doctype == 'Purchase Invoice': + pr_or_pi = 'purchase_receipt' + items_reference = 'pr_detail' + pr_or_pi_doctype = 'Purchase Receipt' + pr_or_pi_items_table = 'Purchase Receipt Item' + else: + pr_or_pi = 'purchase_invoice' + items_reference = 'purchase_invoice_item' + pr_or_pi_doctype = 'Purchase Invoice' + pr_or_pi_items_table = 'Purchase Invoice Item' + + purchase_receipts_or_invoices = [] + pr_or_pi_items = [] + + for item in doc.get('items'): + if item.get(pr_or_pi): + purchase_receipts_or_invoices.append(item.get(pr_or_pi)) + if item.get(items_reference): + pr_or_pi_items.append(item.get(items_reference)) + + exchange_rate_map = frappe._dict(frappe.get_all(pr_or_pi_doctype, filters={'name': ('in', + purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1)) + + net_rate_map = frappe._dict(frappe.get_all(pr_or_pi_items_table, filters={'name': ('in', + pr_or_pi_items)}, fields=['item_code', 'net_rate'], as_list=1)) + + return exchange_rate_map, net_rate_map + def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context list_context = get_list_context(context) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 7b002b0fc6..12fa5302d5 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -254,6 +254,8 @@ class PurchaseReceipt(BuyingController): return process_gl_map(gl_entries) def make_item_gl_entries(self, gl_entries, warehouse_account=None): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_pr_or_pi_details + stock_rbnb = self.get_company_default("stock_received_but_not_billed") landed_cost_entries = get_item_account_wise_additional_cost(self.name) expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") @@ -262,7 +264,7 @@ class PurchaseReceipt(BuyingController): warehouse_with_no_account = [] stock_items = self.get_stock_items() - exchange_rate_map, net_rate_map = self.get_purchase_invoice_details() + exchange_rate_map, net_rate_map = get_pr_or_pi_details(self) for d in self.get("items"): if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): @@ -501,28 +503,6 @@ class PurchaseReceipt(BuyingController): self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), remarks, expenses_included_in_asset_valuation, project=item.project, item=item) - def get_purchase_invoice_details(self): - purchase_invoices = [] - pi_items = [] - - for item in self.get('items'): - if item.get('purchase_invoice'): - purchase_invoices.append(item.purchase_invoice) - if item.get('purchase_invoice_item'): - pi_items.append(item.purchase_invoice_item) - - exchange_rate_map = frappe._dict(frappe.get_all('Purchase Invoice', filters={'name': ('in', - purchase_invoices)}, fields=['name', 'conversion_rate'], as_list=1)) - print("*"*50) - print("In get_purchase_invoice_details:") - print("exchange_rate_map: ", exchange_rate_map) - - net_rate_map = frappe._dict(frappe.get_all('Purchase Invoice Item', filters={'name': ('in', - pi_items)}, fields=['item_code', 'net_rate'], as_list=1)) - print("net_rate_map: ", net_rate_map) - - return exchange_rate_map, net_rate_map - def update_assets(self, item, valuation_rate): assets = frappe.db.get_all('Asset', filters={ 'purchase_receipt': self.name, 'item_code': item.item_code } From b4b6c8e2620f36212da55206aa3ff2c9e9fe779c Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 29 Jun 2021 17:58:39 +0530 Subject: [PATCH 12/13] Revert "fix: Test" This reverts commit feed97ec53767e777b8a638d5a8e3ffc0a04e500. --- ...tracted_raw_materials_to_be_transferred.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index f1784925c5..2448e17c50 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -60,7 +60,6 @@ def transfer_subcontracted_raw_materials(po): rm_item = [ { 'name': po.supplied_items[0].name, -<<<<<<< HEAD 'item_code': item_1, 'rm_item_code': item_1, 'item_name': item_1, @@ -68,20 +67,10 @@ def transfer_subcontracted_raw_materials(po): 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100 * transfer_qty_map[item_1], -======= - 'item_code': '_Test Item Home Desktop 100', - 'rm_item_code': '_Test Item Home Desktop 100', - 'item_name': '_Test Item Home Desktop 100', - 'qty': 2, - 'warehouse': '_Test Warehouse - _TC', - 'rate': 100, - 'amount': 200, ->>>>>>> c4d851e45f (fix: Test) 'stock_uom': 'Nos' }, { 'name': po.supplied_items[1].name, -<<<<<<< HEAD 'item_code': item_2, 'rm_item_code': item_2, 'item_name': item_2, @@ -89,15 +78,6 @@ def transfer_subcontracted_raw_materials(po): 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100 * transfer_qty_map[item_2], -======= - 'item_code': '_Test Item', - 'rm_item_code': '_Test Item', - 'item_name': '_Test Item', - 'qty': 1, - 'warehouse': '_Test Warehouse - _TC', - 'rate': 100, - 'amount': 100, ->>>>>>> c4d851e45f (fix: Test) 'stock_uom': 'Nos' } ] From cf7af62b053ed2ffbe39054b64aafc49557e87b9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 30 Jun 2021 14:14:32 +0530 Subject: [PATCH 13/13] fix: Code cleanup --- .../purchase_invoice/purchase_invoice.py | 32 +++++++++---------- .../purchase_invoice/test_purchase_invoice.py | 23 ++----------- .../purchase_receipt/purchase_receipt.py | 6 ++-- .../purchase_receipt/test_purchase_receipt.py | 15 ++++++--- 4 files changed, 31 insertions(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c164868678..c1cc092554 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -517,7 +517,7 @@ class PurchaseInvoice(BuyingController): if d.category in ('Valuation', 'Total and Valuation') and flt(d.base_tax_amount_after_discount_amount)] - exchange_rate_map, net_rate_map = get_pr_or_pi_details(self) + exchange_rate_map, net_rate_map = get_purchase_document_details(self) for item in self.get("items"): if flt(item.base_net_amount): @@ -640,7 +640,7 @@ class PurchaseInvoice(BuyingController): if item.get('purchase_receipt'): if exchange_rate_map[item.purchase_receipt] and \ self.conversion_rate != exchange_rate_map[item.purchase_receipt] and \ - item.net_rate == net_rate_map[item.item_code]: + item.net_rate == net_rate_map[item.pr_detail]: discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * \ (exchange_rate_map[item.purchase_receipt] - self.conversion_rate) @@ -1172,32 +1172,32 @@ class PurchaseInvoice(BuyingController): self.db_set('status', self.status, update_modified = update_modified) # to get details of purchase invoice/receipt from which this doc was created for exchange rate difference handling -def get_pr_or_pi_details(doc): +def get_purchase_document_details(doc): if doc.doctype == 'Purchase Invoice': - pr_or_pi = 'purchase_receipt' + doc_reference = 'purchase_receipt' items_reference = 'pr_detail' - pr_or_pi_doctype = 'Purchase Receipt' - pr_or_pi_items_table = 'Purchase Receipt Item' + parent_doctype = 'Purchase Receipt' + child_doctype = 'Purchase Receipt Item' else: - pr_or_pi = 'purchase_invoice' + doc_reference = 'purchase_invoice' items_reference = 'purchase_invoice_item' - pr_or_pi_doctype = 'Purchase Invoice' - pr_or_pi_items_table = 'Purchase Invoice Item' + parent_doctype = 'Purchase Invoice' + child_doctype = 'Purchase Invoice Item' purchase_receipts_or_invoices = [] - pr_or_pi_items = [] + items = [] for item in doc.get('items'): - if item.get(pr_or_pi): - purchase_receipts_or_invoices.append(item.get(pr_or_pi)) + if item.get(doc_reference): + purchase_receipts_or_invoices.append(item.get(doc_reference)) if item.get(items_reference): - pr_or_pi_items.append(item.get(items_reference)) + items.append(item.get(items_reference)) - exchange_rate_map = frappe._dict(frappe.get_all(pr_or_pi_doctype, filters={'name': ('in', + exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in', purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1)) - net_rate_map = frappe._dict(frappe.get_all(pr_or_pi_items_table, filters={'name': ('in', - pr_or_pi_items)}, fields=['item_code', 'net_rate'], as_list=1)) + net_rate_map = frappe._dict(frappe.get_all(child_doctype, filters={'name': ('in', + items)}, fields=['name', 'net_rate'], as_list=1)) return exchange_rate_map, net_rate_map diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c38b9017c8..ec93314c0f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -231,12 +231,11 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_values[gle.account][2], gle.credit) def test_purchase_invoice_with_exchange_rate_difference(self): - set_gst_settings() pr = make_purchase_receipt(currency = "USD", conversion_rate = 70) pi = make_purchase_invoice(currency = "USD", conversion_rate = 80, do_not_save = "True") - for item in pi.items: - item.purchase_receipt = pr.name + pi.items[0].purchase_receipt = pr.name + pi.items[0].pr_detail = pr.items[0].name pi.insert() pi.submit() @@ -1072,24 +1071,6 @@ def update_tax_witholding_category(company, account, date): 'account': account }) tds_category.save() -def set_gst_settings(): - gst_settings = frappe.get_doc("GST Settings") - - gst_account = frappe.get_all( - "GST Account", - fields=["cgst_account", "sgst_account", "igst_account"], - filters = {"company": "_Test Company"} - ) - - if not gst_account: - gst_settings.append("gst_accounts", { - "company": "_Test Company", - "cgst_account": "CGST - _TC", - "sgst_account": "SGST - _TC", - "igst_account": "IGST - _TC", - }) - - gst_settings.save() def unlink_payment_on_cancel_of_invoice(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 12fa5302d5..5ba9c7057b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -254,7 +254,7 @@ class PurchaseReceipt(BuyingController): return process_gl_map(gl_entries) def make_item_gl_entries(self, gl_entries, warehouse_account=None): - from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_pr_or_pi_details + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_purchase_document_details stock_rbnb = self.get_company_default("stock_received_but_not_billed") landed_cost_entries = get_item_account_wise_additional_cost(self.name) @@ -264,7 +264,7 @@ class PurchaseReceipt(BuyingController): warehouse_with_no_account = [] stock_items = self.get_stock_items() - exchange_rate_map, net_rate_map = get_pr_or_pi_details(self) + exchange_rate_map, net_rate_map = get_purchase_document_details(self) for d in self.get("items"): if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): @@ -312,7 +312,7 @@ class PurchaseReceipt(BuyingController): if d.get('purchase_invoice'): if exchange_rate_map[d.purchase_invoice] and \ self.conversion_rate != exchange_rate_map[d.purchase_invoice] and \ - d.net_rate == net_rate_map[d.item_code]: + d.net_rate == net_rate_map[d.purchase_invoice_item]: discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * \ (exchange_rate_map[d.purchase_invoice] - self.conversion_rate) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 0ce4c3a669..d56822a308 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1051,6 +1051,7 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(len(item_two_gl_entry), 1) frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value) + def test_purchase_receipt_with_exchange_rate_difference(self): from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice @@ -1058,22 +1059,26 @@ class TestPurchaseReceipt(unittest.TestCase): create_warehouse("_Test Warehouse for Valuation", company="_Test Company with perpetual inventory", properties={"account": '_Test Account Stock In Hand - TCP1'}) + pr = make_purchase_receipt(warehouse = '_Test Warehouse for Valuation - TCP1', company="_Test Company with perpetual inventory", currency = "USD", conversion_rate = 80, do_not_save = "True") - for item in pr.items: - item.purchase_invoice = pi.name + pr.items[0].purchase_invoice = pi.name + pr.items[0].purchase_invoice_item = pi.items[0].name pr.insert() - pr.submit() + pr.submit() # fetching the latest GL Entry with 'Exchange Gain/Loss - TCP1' account gl_entries = frappe.get_all('GL Entry', filters = {'account': 'Exchange Gain/Loss - TCP1'}) - voucher_no = frappe.get_value('GL Entry', gl_entries[0]['name'], 'voucher_no') - + voucher_no = frappe.get_value('GL Entry', gl_entries[0]['name'], 'voucher_no') self.assertEqual(pr.name, voucher_no) + exchange_gain_loss_amount = frappe.get_value('GL Entry', gl_entries[0]['name'], 'debit') + discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount) + self.assertEqual(exchange_gain_loss_amount, discrepancy_caused_by_exchange_rate_diff) + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s