From ae77c609fff563223d6730413b354e4c340c0589 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 27 Jun 2025 14:50:49 +0530 Subject: [PATCH] fix: option to pick serial / batch for asset repair --- .../doctype/asset_repair/asset_repair.js | 35 +++++++++++++++++++ .../doctype/asset_repair/asset_repair.py | 9 +++++ .../asset_repair_consumed_item.json | 16 +++++++-- erpnext/public/js/controllers/transaction.js | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 22 ++++++++++++ erpnext/stock/serial_batch_bundle.py | 22 ++++++++---- 6 files changed, 96 insertions(+), 10 deletions(-) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index 3ce1d5390db..5dc32d363d4 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -3,6 +3,8 @@ frappe.ui.form.on("Asset Repair", { setup: function (frm) { + frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"]; + frm.fields_dict.cost_center.get_query = function (doc) { return { filters: { @@ -177,4 +179,37 @@ frappe.ui.form.on("Asset Repair Consumed Item", { var row = locals[cdt][cdn]; frappe.model.set_value(cdt, cdn, "total_value", row.consumed_quantity * row.valuation_rate); }, + + pick_serial_and_batch(frm, cdt, cdn) { + let item = locals[cdt][cdn]; + let doc = frm.doc; + + frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]).then((r) => { + if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { + item.has_serial_no = r.message.has_serial_no; + item.has_batch_no = r.message.has_batch_no; + item.qty = item.consumed_quantity; + item.type_of_transaction = item.consumed_quantity > 0 ? "Outward" : "Inward"; + + item.title = item.has_serial_no ? __("Select Serial No") : __("Select Batch No"); + + if (item.has_serial_no && item.has_batch_no) { + item.title = __("Select Serial and Batch"); + } + frm.doc.posting_date = frappe.datetime.get_today(); + frm.doc.posting_time = frappe.datetime.now_time(); + + new erpnext.SerialBatchPackageSelector(frm, item, (r) => { + if (r) { + frappe.model.set_value(item.doctype, item.name, { + serial_and_batch_bundle: r.name, + use_serial_batch_fields: 0, + valuation_rate: r.avg_rate, + consumed_quantity: Math.abs(r.total_qty), + }); + } + }); + } + }); + }, }); diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 8a4349233e5..e487c22cd0d 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -156,6 +156,13 @@ class AssetRepair(AccountsController): self.make_gl_entries() + def cancel_sabb(self): + for row in self.stock_items: + if sabb := row.serial_and_batch_bundle: + row.db_set("serial_and_batch_bundle", None) + doc = frappe.get_doc("Serial and Batch Bundle", sabb) + doc.cancel() + def on_cancel(self): self.asset_doc = frappe.get_doc("Asset", self.asset) if self.get("capitalize_repair_cost"): @@ -167,6 +174,8 @@ class AssetRepair(AccountsController): reschedule_depreciation(self.asset_doc, depreciation_note) self.add_asset_activity() + self.cancel_sabb() + def after_delete(self): frappe.get_doc("Asset", self.asset).set_status() diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json index 763308139e8..5ee245339eb 100644 --- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json +++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json @@ -11,6 +11,8 @@ "consumed_quantity", "total_value", "serial_no", + "column_break_xzfr", + "pick_serial_and_batch", "serial_and_batch_bundle" ], "fields": [ @@ -61,19 +63,29 @@ "label": "Warehouse", "options": "Warehouse", "reqd": 1 + }, + { + "fieldname": "pick_serial_and_batch", + "fieldtype": "Button", + "label": "Pick Serial / Batch" + }, + { + "fieldname": "column_break_xzfr", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-06-13 12:01:47.147333", + "modified": "2025-06-27 14:52:56.311166", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair Consumed Item", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 910568aaec0..f534a3928c4 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -8,7 +8,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let me = this; this.set_fields_onload_for_line_item(); - this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle']; + this.frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"]; frappe.flags.hide_serial_batch_dialog = true; frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 866f0bb778f..9565116023d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -270,6 +270,7 @@ class StockEntry(StockController): self.set_material_request_transfer_status("Completed") def on_cancel(self): + self.delink_asset_repair_sabb() self.validate_closed_subcontracting_order() self.update_subcontract_order_supplied_items() self.update_subcontracting_order_status() @@ -380,6 +381,27 @@ class StockEntry(StockController): ): frappe.delete_doc("Stock Entry", d.name) + def delink_asset_repair_sabb(self): + if not self.asset_repair: + return + + for row in self.items: + if row.serial_and_batch_bundle: + voucher_detail_no = frappe.db.get_value( + "Asset Repair Consumed Item", + {"parent": self.asset_repair, "serial_and_batch_bundle": row.serial_and_batch_bundle}, + "name", + ) + + doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle) + doc.db_set( + { + "voucher_type": "Asset Repair", + "voucher_no": self.asset_repair, + "voucher_detail_no": voucher_detail_no, + } + ) + def set_transfer_qty(self): self.validate_qty_is_not_zero() for item in self.get("items"): diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 312f2799955..758fae6ab97 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -285,7 +285,7 @@ class SerialBatchBundle: frappe.throw(_(msg)) def delink_serial_and_batch_bundle(self): - if self.is_pos_transaction(): + if self.is_pos_or_asset_repair_transaction(): return update_values = { @@ -338,21 +338,29 @@ class SerialBatchBundle: self.cancel_serial_and_batch_bundle() def cancel_serial_and_batch_bundle(self): - if self.is_pos_transaction(): + if self.is_pos_or_asset_repair_transaction(): return doc = frappe.get_cached_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle) if doc.docstatus == 1: doc.cancel() - def is_pos_transaction(self): + def is_pos_or_asset_repair_transaction(self): + voucher_type = frappe.get_cached_value( + "Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "voucher_type" + ) + if ( self.sle.voucher_type == "Sales Invoice" and self.sle.serial_and_batch_bundle - and frappe.get_cached_value( - "Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "voucher_type" - ) - == "POS Invoice" + and voucher_type == "POS Invoice" + ): + return True + + if ( + self.sle.voucher_type == "Stock Entry" + and self.sle.serial_and_batch_bundle + and voucher_type == "Asset Repair" ): return True