From b3c9697b7c534c84e2e17896e464fc2b8b636d27 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:08:51 +0530 Subject: [PATCH 1/3] fix: resolve gl entries duplication in asset purchase workflow (#41845) * fix: resolve gl entries duplication in asset purchase workflow * fix: prevent duplicate entry when creating purchase receipt from purchase invoice * chore: test case added * fix: fixed missing asset category issue (cherry picked from commit 55a4bd469b17f8c8abe5e77d7c7f5413cc09c5bc) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py --- .../purchase_invoice/purchase_invoice.py | 15 ++ .../purchase_invoice/test_purchase_invoice.py | 176 +++++++++++++++++- .../purchase_receipt/purchase_receipt.py | 2 +- 3 files changed, 191 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 698744b6151..e9e4ffea804 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -369,6 +369,21 @@ class PurchaseInvoice(BuyingController): item.expense_account = stock_not_billed_account elif item.is_fixed_asset: account = None + if not item.pr_detail and item.po_detail: + receipt_item = frappe.get_cached_value( + "Purchase Receipt Item", + { + "purchase_order": item.purchase_order, + "purchase_order_item": item.po_detail, + "docstatus": 1, + }, + ["name", "parent"], + as_dict=1, + ) + if receipt_item: + item.pr_detail = receipt_item.name + item.purchase_receipt = receipt_item.parent + if item.pr_detail: if not self.asset_received_but_not_billed: self.asset_received_but_not_billed = self.get_company_default( diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index f1f7f54a135..b4e5fcabc1b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -10,7 +10,11 @@ import erpnext from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice -from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice as make_pi_from_po +from erpnext.buying.doctype.purchase_order.test_purchase_order import ( + create_pr_against_po, + create_purchase_order, +) from erpnext.buying.doctype.supplier.test_supplier import create_supplier from erpnext.controllers.accounts_controller import get_payment_terms from erpnext.controllers.buying_controller import QtyMismatchError @@ -1957,6 +1961,176 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): self.assertRaises(frappe.ValidationError, dr_note.save) +<<<<<<< HEAD +======= + def test_debit_note_without_item(self): + pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True) + pi.items[0].item_code = "" + pi.save() + + self.assertFalse(pi.items[0].item_code) + pi.submit() + + return_pi = make_purchase_invoice( + item_name="_Test Item", + is_return=1, + return_against=pi.name, + qty=-10, + do_not_save=True, + ) + return_pi.items[0].item_code = "" + return_pi.save() + return_pi.submit() + self.assertEqual(return_pi.docstatus, 1) + + def test_purchase_invoice_with_use_serial_batch_field_for_rejected_qty(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + batch_item = make_item( + "_Test Purchase Invoice Batch Item For Rejected Qty", + properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1}, + ).name + + serial_item = make_item( + "_Test Purchase Invoice Serial Item for Rejected Qty", + properties={"has_serial_no": 1, "is_stock_item": 1}, + ).name + + rej_warehouse = create_warehouse("_Test Purchase INV Warehouse For Rejected Qty") + + batch_no = "BATCH-PI-BNU-TPRBI-0001" + serial_nos = ["SNU-PI-TPRSI-0001", "SNU-PI-TPRSI-0002", "SNU-PI-TPRSI-0003"] + + if not frappe.db.exists("Batch", batch_no): + frappe.get_doc( + { + "doctype": "Batch", + "batch_id": batch_no, + "item": batch_item, + } + ).insert() + + for serial_no in serial_nos: + if not frappe.db.exists("Serial No", serial_no): + frappe.get_doc( + { + "doctype": "Serial No", + "item_code": serial_item, + "serial_no": serial_no, + } + ).insert() + + pi = make_purchase_invoice( + item_code=batch_item, + received_qty=10, + qty=8, + rejected_qty=2, + update_stock=1, + rejected_warehouse=rej_warehouse, + use_serial_batch_fields=1, + batch_no=batch_no, + rate=100, + do_not_submit=1, + ) + + pi.append( + "items", + { + "item_code": serial_item, + "qty": 2, + "rate": 100, + "base_rate": 100, + "item_name": serial_item, + "uom": "Nos", + "stock_uom": "Nos", + "conversion_factor": 1, + "rejected_qty": 1, + "warehouse": pi.items[0].warehouse, + "rejected_warehouse": rej_warehouse, + "use_serial_batch_fields": 1, + "serial_no": "\n".join(serial_nos[:2]), + "rejected_serial_no": serial_nos[2], + }, + ) + + pi.save() + pi.submit() + + pi.reload() + + for row in pi.items: + self.assertTrue(row.serial_and_batch_bundle) + self.assertTrue(row.rejected_serial_and_batch_bundle) + + if row.item_code == batch_item: + self.assertEqual(row.batch_no, batch_no) + else: + self.assertEqual(row.serial_no, "\n".join(serial_nos[:2])) + self.assertEqual(row.rejected_serial_no, serial_nos[2]) + + def test_make_pr_and_pi_from_po(self): + from erpnext.assets.doctype.asset.test_asset import create_asset_category + + if not frappe.db.exists("Asset Category", "Computers"): + create_asset_category() + + item = create_item( + item_code="_Test_Item", is_stock_item=0, is_fixed_asset=1, asset_category="Computers" + ) + po = create_purchase_order(item_code=item.item_code) + pr = create_pr_against_po(po.name, 10) + pi = make_pi_from_po(po.name) + pi.insert() + pi.submit() + + pr_gl_entries = frappe.db.sql( + """select account, debit, credit + from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s + order by account asc""", + pr.name, + as_dict=1, + ) + + pr_expected_values = [ + ["Asset Received But Not Billed - _TC", 0, 5000], + ["CWIP Account - _TC", 5000, 0], + ] + + for i, gle in enumerate(pr_gl_entries): + self.assertEqual(pr_expected_values[i][0], gle.account) + self.assertEqual(pr_expected_values[i][1], gle.debit) + self.assertEqual(pr_expected_values[i][2], gle.credit) + + pi_gl_entries = frappe.db.sql( + """select account, debit, credit + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", + pi.name, + as_dict=1, + ) + pi_expected_values = [ + ["Asset Received But Not Billed - _TC", 5000, 0], + ["Creditors - _TC", 0, 5000], + ] + + for i, gle in enumerate(pi_gl_entries): + self.assertEqual(pi_expected_values[i][0], gle.account) + self.assertEqual(pi_expected_values[i][1], gle.debit) + self.assertEqual(pi_expected_values[i][2], gle.credit) + + +def set_advance_flag(company, flag, default_account): + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": flag, + "default_advance_paid_account": default_account, + }, + ) + +>>>>>>> 55a4bd469b (fix: resolve gl entries duplication in asset purchase workflow (#41845)) def check_gl_entries( doc, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1909874601d..8ed59f452e2 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -546,7 +546,7 @@ class PurchaseReceipt(BuyingController): if not ( (erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items) - or d.is_fixed_asset + or (d.is_fixed_asset and not d.purchase_invoice) ): continue From 91bff147e038ac21bf6b5fdcc0c9040d34f7f84c Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:05:25 +0530 Subject: [PATCH 2/3] chore: resolved conflicts --- .../purchase_invoice/test_purchase_invoice.py | 170 ------------------ 1 file changed, 170 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b4e5fcabc1b..23c371c7fe3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1961,176 +1961,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): self.assertRaises(frappe.ValidationError, dr_note.save) -<<<<<<< HEAD -======= - def test_debit_note_without_item(self): - pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True) - pi.items[0].item_code = "" - pi.save() - - self.assertFalse(pi.items[0].item_code) - pi.submit() - - return_pi = make_purchase_invoice( - item_name="_Test Item", - is_return=1, - return_against=pi.name, - qty=-10, - do_not_save=True, - ) - return_pi.items[0].item_code = "" - return_pi.save() - return_pi.submit() - self.assertEqual(return_pi.docstatus, 1) - - def test_purchase_invoice_with_use_serial_batch_field_for_rejected_qty(self): - from erpnext.stock.doctype.item.test_item import make_item - from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - - batch_item = make_item( - "_Test Purchase Invoice Batch Item For Rejected Qty", - properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1}, - ).name - - serial_item = make_item( - "_Test Purchase Invoice Serial Item for Rejected Qty", - properties={"has_serial_no": 1, "is_stock_item": 1}, - ).name - - rej_warehouse = create_warehouse("_Test Purchase INV Warehouse For Rejected Qty") - - batch_no = "BATCH-PI-BNU-TPRBI-0001" - serial_nos = ["SNU-PI-TPRSI-0001", "SNU-PI-TPRSI-0002", "SNU-PI-TPRSI-0003"] - - if not frappe.db.exists("Batch", batch_no): - frappe.get_doc( - { - "doctype": "Batch", - "batch_id": batch_no, - "item": batch_item, - } - ).insert() - - for serial_no in serial_nos: - if not frappe.db.exists("Serial No", serial_no): - frappe.get_doc( - { - "doctype": "Serial No", - "item_code": serial_item, - "serial_no": serial_no, - } - ).insert() - - pi = make_purchase_invoice( - item_code=batch_item, - received_qty=10, - qty=8, - rejected_qty=2, - update_stock=1, - rejected_warehouse=rej_warehouse, - use_serial_batch_fields=1, - batch_no=batch_no, - rate=100, - do_not_submit=1, - ) - - pi.append( - "items", - { - "item_code": serial_item, - "qty": 2, - "rate": 100, - "base_rate": 100, - "item_name": serial_item, - "uom": "Nos", - "stock_uom": "Nos", - "conversion_factor": 1, - "rejected_qty": 1, - "warehouse": pi.items[0].warehouse, - "rejected_warehouse": rej_warehouse, - "use_serial_batch_fields": 1, - "serial_no": "\n".join(serial_nos[:2]), - "rejected_serial_no": serial_nos[2], - }, - ) - - pi.save() - pi.submit() - - pi.reload() - - for row in pi.items: - self.assertTrue(row.serial_and_batch_bundle) - self.assertTrue(row.rejected_serial_and_batch_bundle) - - if row.item_code == batch_item: - self.assertEqual(row.batch_no, batch_no) - else: - self.assertEqual(row.serial_no, "\n".join(serial_nos[:2])) - self.assertEqual(row.rejected_serial_no, serial_nos[2]) - - def test_make_pr_and_pi_from_po(self): - from erpnext.assets.doctype.asset.test_asset import create_asset_category - - if not frappe.db.exists("Asset Category", "Computers"): - create_asset_category() - - item = create_item( - item_code="_Test_Item", is_stock_item=0, is_fixed_asset=1, asset_category="Computers" - ) - po = create_purchase_order(item_code=item.item_code) - pr = create_pr_against_po(po.name, 10) - pi = make_pi_from_po(po.name) - pi.insert() - pi.submit() - - pr_gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s - order by account asc""", - pr.name, - as_dict=1, - ) - - pr_expected_values = [ - ["Asset Received But Not Billed - _TC", 0, 5000], - ["CWIP Account - _TC", 5000, 0], - ] - - for i, gle in enumerate(pr_gl_entries): - self.assertEqual(pr_expected_values[i][0], gle.account) - self.assertEqual(pr_expected_values[i][1], gle.debit) - self.assertEqual(pr_expected_values[i][2], gle.credit) - - pi_gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", - pi.name, - as_dict=1, - ) - pi_expected_values = [ - ["Asset Received But Not Billed - _TC", 5000, 0], - ["Creditors - _TC", 0, 5000], - ] - - for i, gle in enumerate(pi_gl_entries): - self.assertEqual(pi_expected_values[i][0], gle.account) - self.assertEqual(pi_expected_values[i][1], gle.debit) - self.assertEqual(pi_expected_values[i][2], gle.credit) - - -def set_advance_flag(company, flag, default_account): - frappe.db.set_value( - "Company", - company, - { - "book_advance_payments_in_separate_party_account": flag, - "default_advance_paid_account": default_account, - }, - ) - ->>>>>>> 55a4bd469b (fix: resolve gl entries duplication in asset purchase workflow (#41845)) def check_gl_entries( doc, From 6fda0e8e1b678b93dc408cba7117270a62a0cb9c Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 28 Aug 2024 15:22:07 +0530 Subject: [PATCH 3/3] chore: linters check --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 23c371c7fe3..950a7a3eb29 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -12,7 +12,6 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice as make_pi_from_po from erpnext.buying.doctype.purchase_order.test_purchase_order import ( - create_pr_against_po, create_purchase_order, ) from erpnext.buying.doctype.supplier.test_supplier import create_supplier