From baa3fee1bf9ce814a889ecf264fb6bac4ebaf93c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 09:04:24 +0530 Subject: [PATCH 1/5] fix: add LCV flag to determine negative expenses --- erpnext/controllers/stock_controller.py | 4 +-- .../landed_cost_voucher.py | 2 +- .../purchase_receipt/purchase_receipt.py | 27 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a67fbdca62ca..1d7fb145f3e6 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -84,7 +84,7 @@ def validate_duplicate_serial_and_batch_bundle(self): ) ) - def make_gl_entries(self, gl_entries=None, from_repost=False): + def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False): if self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) @@ -105,7 +105,7 @@ def make_gl_entries(self, gl_entries=None, from_repost=False): if self.docstatus == 1: if not gl_entries: - gl_entries = self.get_gl_entries(warehouse_account) + gl_entries = self.get_gl_entries(warehouse_account, via_landed_cost_voucher) make_gl_entries(gl_entries, from_repost=from_repost) def validate_serialized_batch(self): diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index baff54059dc9..2c92cddff405 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -250,7 +250,7 @@ def update_landed_cost(self): # update stock & gl entries for submit state of PR doc.docstatus = 1 doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) - doc.make_gl_entries() + doc.make_gl_entries(via_landed_cost_voucher=True) doc.repost_future_sle_and_gle() def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1e2d8eb1b1a8..b2bc4a9154b4 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -420,13 +420,13 @@ def on_cancel(self): self.delete_auto_created_batches() self.set_consumed_qty_in_subcontract_order() - def get_gl_entries(self, warehouse_account=None): + def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False): from erpnext.accounts.general_ledger import process_gl_map gl_entries = [] self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) - self.make_tax_gl_entries(gl_entries) + self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher) update_regional_gl_entries(gl_entries, self) return process_gl_map(gl_entries) @@ -773,7 +773,7 @@ def add_provisional_gl_entry( posting_date=posting_date, ) - def make_tax_gl_entries(self, gl_entries): + def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False): negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) # Cost center-wise amount breakup for other charges included for valuation @@ -808,18 +808,17 @@ def make_tax_gl_entries(self, gl_entries): i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): - negative_expense_booked_in_pi = frappe.db.sql( - """select name from `tabPurchase Invoice Item` pi - where docstatus = 1 and purchase_receipt=%s - and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' - and voucher_no=pi.parent and account=%s)""", - (self.name, tax.account_head), - ) - - if negative_expense_booked_in_pi: - account = stock_rbnb - else: + if via_landed_cost_voucher: account = tax.account_head + else: + negative_expense_booked_in_pi = frappe.db.sql( + """select name from `tabPurchase Invoice Item` pi + where docstatus = 1 and purchase_receipt=%s + and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' + and voucher_no=pi.parent and account=%s)""", + (self.name, tax.account_head), + ) + account = stock_rbnb if negative_expense_booked_in_pi else tax.account_head if i == len(valuation_tax): applicable_amount = amount_including_divisional_loss From 53642e7417c54197ba526625902d2671a7a564c2 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 11:26:31 +0530 Subject: [PATCH 2/5] test: LCV entries after billing --- erpnext/controllers/stock_controller.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 67 +++++++++++++++++-- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 1d7fb145f3e6..ecf7b2b8eef9 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -105,7 +105,11 @@ def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_vo if self.docstatus == 1: if not gl_entries: - gl_entries = self.get_gl_entries(warehouse_account, via_landed_cost_voucher) + gl_entries = ( + self.get_gl_entries(warehouse_account, via_landed_cost_voucher) + if self.doctype == "Purchase Receipt" + else self.get_gl_entries(warehouse_account) + ) make_gl_entries(gl_entries, from_repost=from_repost) def validate_serialized_batch(self): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index aa17ab4be917..94fa223bafa5 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2477,6 +2477,49 @@ def test_pr_billed_amount_against_return_entry(self): pr.reload() self.assertEqual(pr.per_billed, 100) + def test_valuation_taxes_lcv_repost_after_billing(self): + from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( + make_landed_cost_voucher, + ) + + company = frappe.get_doc("Company", "_Test Company") + company.enable_perpetual_inventory = 1 + company.default_inventory_account = "Stock In Hand - _TC" + company.stock_received_but_not_billed = "Stock Received But Not Billed - _TC" + company.save() + + pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1) + pr.append( + "taxes", + { + "category": "Valuation and Total", + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "tax_amount": 2000, + "description": "Test", + }, + ) + pr.submit() + pi = make_purchase_invoice(pr.name) + pi.submit() + lcv = make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=2000, + distribute_charges_based_on="Qty", + expense_account="Expenses Included In Valuation - _TC", + ) + + gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False) + expected_gle = ( + ("Stock Received But Not Billed - _TC", 0, 10000, "Main - _TC"), + ("Stock In Hand - _TC", 14000, 0, "Main - _TC"), + ("Freight and Forwarding Charges - _TC", 0, 2000, "Main - _TC"), + ("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"), + ) + self.assertSequenceEqual(expected_gle, gl_entries) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier @@ -2523,14 +2566,24 @@ def get_sl_entries(voucher_type, voucher_no): ) -def get_gl_entries(voucher_type, voucher_no): - return frappe.db.sql( - """select account, debit, credit, cost_center, is_cancelled - from `tabGL Entry` where voucher_type=%s and voucher_no=%s - order by account desc""", - (voucher_type, voucher_no), - as_dict=1, +def get_gl_entries(voucher_type, voucher_no, skip_cancelled=False, as_dict=True): + gl = frappe.qb.DocType("GL Entry") + gl_query = ( + frappe.qb.from_(gl) + .select( + gl.account, + gl.debit, + gl.credit, + gl.cost_center, + ) + .where((gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no)) + .orderby(gl.account, order=frappe.qb.desc) ) + if skip_cancelled: + gl_query = gl_query.where(gl.is_cancelled == 0) + else: + gl_query = gl_query.select(gl.is_cancelled) + return gl_query.run(as_dict=as_dict) def get_taxes(**args): From 8b3d46610eacd27edbceaefff6452efb68a83365 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 11:58:14 +0530 Subject: [PATCH 3/5] fix: parameters for PI references --- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.py | 5 ++++- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 2c92cddff405..3a95f556b078 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -250,7 +250,10 @@ def update_landed_cost(self): # update stock & gl entries for submit state of PR doc.docstatus = 1 doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) - doc.make_gl_entries(via_landed_cost_voucher=True) + if d.receipt_document_type == "Purchase Receipt": + doc.make_gl_entries(via_landed_cost_voucher=True) + else: + doc.make_gl_entries() doc.repost_future_sle_and_gle() def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 94fa223bafa5..de2fc47296a9 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2519,6 +2519,7 @@ def test_valuation_taxes_lcv_repost_after_billing(self): ("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"), ) self.assertSequenceEqual(expected_gle, gl_entries) + frappe.db.rollback() def prepare_data_for_internal_transfer(): From 0b36cbe307fec68f72ddfdfdeee401134887a884 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 29 Feb 2024 13:04:01 +0530 Subject: [PATCH 4/5] fix: reset perpetual inventory flag after test --- .../purchase_receipt/test_purchase_receipt.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index de2fc47296a9..a0bd188f1465 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -10,6 +10,7 @@ from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.controllers.accounts_controller import InvalidQtyError from erpnext.controllers.buying_controller import QtyMismatchError +from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( @@ -1730,7 +1731,6 @@ def test_validate_received_qty_for_internal_pr(self): frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0) def test_internal_pr_gl_entries(self): - from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -2482,11 +2482,14 @@ def test_valuation_taxes_lcv_repost_after_billing(self): make_landed_cost_voucher, ) - company = frappe.get_doc("Company", "_Test Company") - company.enable_perpetual_inventory = 1 - company.default_inventory_account = "Stock In Hand - _TC" - company.stock_received_but_not_billed = "Stock Received But Not Billed - _TC" - company.save() + old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company") + frappe.local.enable_perpetual_inventory["_Test Company"] = 1 + frappe.db.set_value( + "Company", + "_Test Company", + "stock_received_but_not_billed", + "Stock Received But Not Billed - _TC", + ) pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1) pr.append( @@ -2502,7 +2505,7 @@ def test_valuation_taxes_lcv_repost_after_billing(self): pr.submit() pi = make_purchase_invoice(pr.name) pi.submit() - lcv = make_landed_cost_voucher( + make_landed_cost_voucher( company=pr.company, receipt_document_type="Purchase Receipt", receipt_document=pr.name, @@ -2512,14 +2515,15 @@ def test_valuation_taxes_lcv_repost_after_billing(self): ) gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False) + warehouse_account = get_warehouse_account_map("_Test Company") expected_gle = ( ("Stock Received But Not Billed - _TC", 0, 10000, "Main - _TC"), - ("Stock In Hand - _TC", 14000, 0, "Main - _TC"), ("Freight and Forwarding Charges - _TC", 0, 2000, "Main - _TC"), ("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"), + (warehouse_account[pr.items[0].warehouse]["account"], 14000, 0, "Main - _TC"), ) self.assertSequenceEqual(expected_gle, gl_entries) - frappe.db.rollback() + frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory def prepare_data_for_internal_transfer(): From 54a58e9205af43f4aabe1668e8d8cf925a226536 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 21 Apr 2024 19:07:08 +0530 Subject: [PATCH 5/5] chore: resolve conflicts --- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 3a95f556b078..17ca937de2ac 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -254,7 +254,7 @@ def update_landed_cost(self): doc.make_gl_entries(via_landed_cost_voucher=True) else: doc.make_gl_entries() - doc.repost_future_sle_and_gle() + doc.repost_future_sle_and_gle(via_landed_cost_voucher=True) def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): for item in self.get("items"):