diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 44d8a407017..0abc925ec34 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1267,7 +1267,11 @@ class PurchaseInvoice(BuyingController): def update_gross_purchase_amount_for_linked_assets(self, item): assets = frappe.db.get_all( "Asset", - filters={"purchase_invoice": self.name, "item_code": item.item_code}, + filters={ + "purchase_invoice": self.name, + "item_code": item.item_code, + "purchase_invoice_item": ("in", [item.name, ""]), + }, fields=["name", "asset_quantity"], ) for asset in assets: diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index eea44697d63..9642403d726 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -670,6 +670,11 @@ frappe.ui.form.on("Asset", { if (item.asset_location) { frm.set_value("location", item.asset_location); } + if (doctype === "Purchase Receipt") { + frm.set_value("purchase_receipt_item", item.name); + } else if (doctype === "Purchase Invoice") { + frm.set_value("purchase_invoice_item", item.name); + } }); }, diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 07228c5f255..92537e0e3cc 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -33,14 +33,16 @@ "dimension_col_break", "purchase_details_section", "purchase_receipt", + "purchase_receipt_item", "purchase_invoice", + "purchase_invoice_item", + "purchase_date", "available_for_use_date", - "total_asset_cost", - "additional_asset_cost", "column_break_23", "gross_purchase_amount", "asset_quantity", - "purchase_date", + "additional_asset_cost", + "total_asset_cost", "section_break_23", "calculate_depreciation", "column_break_33", @@ -536,6 +538,20 @@ "fieldname": "opening_number_of_booked_depreciations", "fieldtype": "Int", "label": "Opening Number of Booked Depreciations" + }, + { + "fieldname": "purchase_receipt_item", + "fieldtype": "Link", + "hidden": 1, + "label": "Purchase Receipt Item", + "options": "Purchase Receipt Item" + }, + { + "fieldname": "purchase_invoice_item", + "fieldtype": "Link", + "hidden": 1, + "label": "Purchase Invoice Item", + "options": "Purchase Invoice Item" } ], "idx": 72, @@ -579,7 +595,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2024-08-01 16:39:09.340973", + "modified": "2024-08-26 23:28:29.095139", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index cf4cbdbdb44..320b53ac0c8 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -94,7 +94,9 @@ class Asset(AccountsController): purchase_amount: DF.Currency purchase_date: DF.Date | None purchase_invoice: DF.Link | None + purchase_invoice_item: DF.Link | None purchase_receipt: DF.Link | None + purchase_receipt_item: DF.Link | None split_from: DF.Link | None status: DF.Literal[ "Draft", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 0f53322bbd8..9b095df2d0c 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -824,6 +824,8 @@ class BuyingController(SubcontractingController): "asset_quantity": asset_quantity, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, "purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None, + "purchase_receipt_item": row.name if self.doctype == "Purchase Receipt" else None, + "purchase_invoice_item": row.name if self.doctype == "Purchase Invoice" else None, } ) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index fc284540c75..8b155d70c6f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -379,3 +379,4 @@ erpnext.patches.v15_0.drop_index_posting_datetime_from_sle erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1 erpnext.patches.v15_0.set_standard_stock_entry_type erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment +erpnext.patches.v15_0.link_purchase_item_to_asset_doc diff --git a/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py b/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py new file mode 100644 index 00000000000..662858e52a4 --- /dev/null +++ b/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py @@ -0,0 +1,74 @@ +import frappe + + +def execute(): + if frappe.db.has_column("Asset", "purchase_invoice_item") and frappe.db.has_column( + "Asset", "purchase_receipt_item" + ): + # Get all assets with their related Purchase Invoice and Purchase Receipt + assets = frappe.get_all( + "Asset", + filters={"docstatus": 0}, + fields=[ + "name", + "item_code", + "purchase_invoice", + "purchase_receipt", + "gross_purchase_amount", + "asset_quantity", + "purchase_invoice_item", + "purchase_receipt_item", + ], + ) + + for asset in assets: + # Get Purchase Invoice Items + if asset.purchase_invoice and not asset.purchase_invoice_item: + purchase_invoice_item = get_linked_item( + "Purchase Invoice Item", + asset.purchase_invoice, + asset.item_code, + asset.gross_purchase_amount, + asset.asset_quantity, + ) + frappe.db.set_value("Asset", asset.name, "purchase_invoice_item", purchase_invoice_item) + + # Get Purchase Receipt Items + if asset.purchase_receipt and not asset.purchase_receipt_item: + purchase_receipt_item = get_linked_item( + "Purchase Receipt Item", + asset.purchase_receipt, + asset.item_code, + asset.gross_purchase_amount, + asset.asset_quantity, + ) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_item", purchase_receipt_item) + + +def get_linked_item(doctype, parent, item_code, amount, quantity): + items = frappe.get_all( + doctype, + filters={ + "parenttype": doctype.replace(" Item", ""), + "parent": parent, + "item_code": item_code, + }, + fields=["name", "rate", "amount", "qty", "landed_cost_voucher_amount"], + ) + if len(items) == 1: + # If only one item exists, return it directly + return items[0].name + + for item in items: + landed_cost = item.get("landed_cost_voucher_amount", 0) + # Check if the asset is grouped + if quantity > 1: + if item.amount + landed_cost == amount and item.qty == quantity: + return item.name + elif item.qty == quantity: + return item.name + else: + if item.rate + (landed_cost / item.qty) == amount: + return item.name + + return items[0].name if items else None diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index e611361a3e5..0970c65b9f7 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -828,7 +828,11 @@ class PurchaseReceipt(BuyingController): def update_assets(self, item, valuation_rate): assets = frappe.db.get_all( "Asset", - filters={"purchase_receipt": self.name, "item_code": item.item_code}, + filters={ + "purchase_receipt": self.name, + "item_code": item.item_code, + "purchase_receipt_item": ("in", [item.name, ""]), + }, fields=["name", "asset_quantity"], ) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 804dfe34b35..9c27ce3f6f8 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1533,7 +1533,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc operator = "<=" voucher_condition = f"and creation < '{creation}'" - sle = frappe.db.sql( + sle = frappe.db.sql( # nosemgrep f""" select *, posting_datetime as "timestamp" from `tabStock Ledger Entry` @@ -1630,6 +1630,7 @@ def get_stock_ledger_entries( if extra_cond: conditions += f"{extra_cond}" + # nosemgrep return frappe.db.sql( """ select *, posting_datetime as "timestamp" @@ -1745,7 +1746,7 @@ def get_valuation_rate( return batch_obj.get_incoming_rate() # Get valuation rate from last sle for the same item and warehouse - if last_valuation_rate := frappe.db.sql( + if last_valuation_rate := frappe.db.sql( # nosemgrep """select valuation_rate from `tabStock Ledger Entry` force index (item_warehouse) where @@ -1825,7 +1826,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): detail = next_stock_reco_detail[0] datetime_limit_condition = get_datetime_limit_condition(detail) - frappe.db.sql( + frappe.db.sql( # nosemgrep f""" update `tabStock Ledger Entry` set qty_after_transaction = qty_after_transaction + {qty_shift} @@ -1991,8 +1992,8 @@ def is_negative_with_precision(neg_sle, is_batch=False): return qty_deficit < 0 and abs(qty_deficit) > 0.0001 -def get_future_sle_with_negative_qty(args): - return frappe.db.sql( +def get_future_sle_with_negative_qty(sle_args): + return frappe.db.sql( # nosemgrep """ select qty_after_transaction, posting_date, posting_time, @@ -2008,13 +2009,13 @@ def get_future_sle_with_negative_qty(args): order by posting_date asc, posting_time asc limit 1 """, - args, + sle_args, as_dict=1, ) -def get_future_sle_with_negative_batch_qty(args): - return frappe.db.sql( +def get_future_sle_with_negative_batch_qty(sle_args): + return frappe.db.sql( # nosemgrep """ with batch_ledger as ( select @@ -2034,7 +2035,7 @@ def get_future_sle_with_negative_batch_qty(args): and posting_datetime >= %(posting_datetime)s limit 1 """, - args, + sle_args, as_dict=1, )