From e4e96d2a442b771ff0336be0b52afe359831b919 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 25 Sep 2024 22:15:10 +0530 Subject: [PATCH] fix: serial and batch no selector (#43387) --- erpnext/public/js/controllers/buying.js | 18 ++- .../js/utils/serial_no_batch_selector.js | 150 +++++++++++------- .../serial_and_batch_bundle.py | 23 ++- 3 files changed, 125 insertions(+), 66 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index b5a8b7577060..1d0680de861e 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -342,12 +342,15 @@ erpnext.buying = { add_serial_batch_bundle(doc, cdt, cdn) { let item = locals[cdt][cdn]; let me = this; + let fields = ["has_batch_no", "has_serial_no"]; - frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + frappe.db.get_value("Item", item.item_code, fields) .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; + fields.forEach((field) => { + item[field] = r.message[field]; + }); + item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; item.is_rejected = false; @@ -380,12 +383,15 @@ erpnext.buying = { add_serial_batch_for_rejected_qty(doc, cdt, cdn) { let item = locals[cdt][cdn]; let me = this; + let fields = ["has_batch_no", "has_serial_no"]; - frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + frappe.db.get_value("Item", item.item_code, fields) .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; + fields.forEach((field) => { + item[field] = r.message[field]; + }); + item.type_of_transaction = item.rejected_qty > 0 ? "Inward" : "Outward"; item.is_rejected = true; diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 2ddfc24adb1c..46ff6366de8a 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -16,7 +16,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { let label = this.item?.has_serial_no ? __("Serial Nos") : __("Batch Nos"); let primary_label = this.bundle ? __("Update") : __("Add"); - if (this.item?.has_serial_no && this.item?.batch_no) { + if (this.item?.has_serial_no && this.item?.has_batch_no) { label = __("Serial Nos / Batch Nos"); } @@ -24,6 +24,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { this.dialog = new frappe.ui.Dialog({ title: this.item?.title || primary_label, + size: "large", fields: this.get_dialog_fields(), primary_action_label: primary_label, primary_action: () => this.update_bundle_entries(), @@ -164,12 +165,14 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { fields.push({ fieldtype: "Section Break", + depends_on: "eval:doc.enter_manually !== 1 || doc.entries?.length > 0", }); fields.push({ fieldname: "entries", fieldtype: "Table", allow_bulk_edit: true, + depends_on: "eval:doc.enter_manually !== 1 || doc.entries?.length > 0", data: [], fields: this.get_dialog_table_fields(), }); @@ -178,6 +181,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_attach_field() { + let me = this; let label = this.item?.has_serial_no ? __("Serial Nos") : __("Batch Nos"); let primary_label = this.bundle ? __("Update") : __("Add"); @@ -185,86 +189,103 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { label = __("Serial Nos / Batch Nos"); } - let fields = [ + let fields = []; + if (this.item.has_serial_no) { + fields.push({ + fieldtype: "Check", + label: __("Enter Manually"), + fieldname: "enter_manually", + default: 1, + depends_on: "eval:doc.import_using_csv_file !== 1", + change() { + if (me.dialog.get_value("enter_manually")) { + me.dialog.set_value("import_using_csv_file", 0); + } + }, + }); + } + + fields = [ + ...fields, + { + fieldtype: "Check", + label: __("Import Using CSV file"), + fieldname: "import_using_csv_file", + depends_on: "eval:doc.enter_manually !== 1", + default: !this.item.has_serial_no ? 1 : 0, + change() { + if (me.dialog.get_value("import_using_csv_file")) { + me.dialog.set_value("enter_manually", 0); + } + }, + }, { fieldtype: "Section Break", + depends_on: "eval:doc.import_using_csv_file === 1", label: __("{0} {1} via CSV File", [primary_label, label]), }, + { + fieldtype: "Button", + fieldname: "download_csv", + label: __("Download CSV Template"), + click: () => this.download_csv_file(), + }, + { + fieldtype: "Column Break", + }, + { + fieldtype: "Attach", + fieldname: "attach_serial_batch_csv", + label: __("Attach CSV File"), + onchange: () => this.upload_csv_file(), + }, ]; if (this.item?.has_serial_no) { fields = [ ...fields, - { - fieldtype: "Check", - label: __("Import Using CSV file"), - fieldname: "import_using_csv_file", - default: 0, - }, { fieldtype: "Section Break", label: __("{0} {1} Manually", [primary_label, label]), - depends_on: "eval:doc.import_using_csv_file === 0", + depends_on: "eval:doc.enter_manually === 1", }, { fieldtype: "Data", - label: __("Enter Serial No Range"), + label: __("Serial No Range"), fieldname: "serial_no_range", - depends_on: "eval:doc.import_using_csv_file === 0", - description: __('Enter "ABC-001::100" for serial nos "ABC-001" to "ABC-100".'), + depends_on: "eval:doc.enter_manually === 1 && !doc.serial_no_series", + description: __('"SN-01::10" for "SN-01" to "SN-10"'), onchange: () => { this.set_serial_nos_from_range(); }, }, + ]; + } + + if (this.item?.has_serial_no) { + fields = [ + ...fields, + { + fieldtype: "Column Break", + depends_on: "eval:doc.enter_manually === 1", + }, { fieldtype: "Small Text", label: __("Enter Serial Nos"), fieldname: "upload_serial_nos", - depends_on: "eval:doc.import_using_csv_file === 0", + depends_on: "eval:doc.enter_manually === 1", description: __("Enter each serial no in a new line"), }, - { - fieldtype: "Column Break", - depends_on: "eval:doc.import_using_csv_file === 0", - }, - { - fieldtype: "Button", - fieldname: "make_serial_nos", - label: __("Create Serial Nos"), - depends_on: "eval:doc.import_using_csv_file === 0", - click: () => { - this.create_serial_nos(); - }, - }, - { - fieldtype: "Section Break", - depends_on: "eval:doc.import_using_csv_file === 1", - }, ]; } - fields = [ - ...fields, - { - fieldtype: "Button", - fieldname: "download_csv", - label: __("Download CSV Template"), - click: () => this.download_csv_file(), - }, - { - fieldtype: "Column Break", - }, - { - fieldtype: "Attach", - fieldname: "attach_serial_batch_csv", - label: __("Attach CSV File"), - onchange: () => this.upload_csv_file(), - }, - ]; - return fields; } + set_serial_nos_from_series() {} + + set_batch_nos_from_series() {} + set_serial_nos_from_range() { const serial_no_range = this.dialog.get_value("serial_no_range"); @@ -511,6 +532,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { scan_barcode_data() { const { scan_serial_no, scan_batch_no } = this.dialog.get_values(); + this.dialog.set_value("enter_manually", 0); + if (scan_serial_no || scan_batch_no) { frappe.call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_serial_batch_no_exists", @@ -554,14 +577,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { serial_no: scan_serial_no, }, callback: (r) => { - if (r.message) { - this.dialog.fields_dict.entries.df.data.push({ - serial_no: scan_serial_no, - batch_no: r.message, - }); + this.dialog.fields_dict.entries.df.data.push({ + serial_no: scan_serial_no, + batch_no: r.message, + }); - this.dialog.fields_dict.scan_serial_no.set_value(""); - } + this.dialog.fields_dict.scan_serial_no.set_value(""); + this.dialog.fields_dict.entries.grid.refresh(); }, }); } @@ -590,6 +612,12 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { update_bundle_entries() { let entries = this.dialog.get_values().entries; let warehouse = this.dialog.get_value("warehouse"); + let upload_serial_nos = this.dialog.get_value("upload_serial_nos"); + + if (!entries?.length && upload_serial_nos) { + this.create_serial_nos(); + return; + } if ((entries && !entries.length) || !entries) { frappe.throw(__("Please add atleast one Serial No / Batch No")); @@ -610,9 +638,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }, }) .then((r) => { - this.callback && this.callback(r.message); - this.frm.save(); - this.dialog.hide(); + frappe.run_serially([ + () => { + this.callback && this.callback(r.message); + }, + () => this.frm.save(), + () => this.dialog.hide(), + ]); }); } diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 956eb08d9ff2..a1944732fb65 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1329,6 +1329,15 @@ def create_serial_batch_no_ledgers( } ) + batch_no = None + + if ( + not entries[0].get("batch_no") + and entries[0].get("serial_no") + and frappe.get_cached_value("Item", child_row.item_code, "has_batch_no") + ): + batch_no = get_batch(child_row.item_code) + for row in entries: row = frappe._dict(row) doc.append( @@ -1336,7 +1345,7 @@ def create_serial_batch_no_ledgers( { "qty": (flt(row.qty) or 1.0) * (1 if type_of_transaction == "Inward" else -1), "warehouse": warehouse, - "batch_no": row.batch_no, + "batch_no": row.batch_no or batch_no, "serial_no": row.serial_no, }, ) @@ -1351,6 +1360,18 @@ def create_serial_batch_no_ledgers( return doc +def get_batch(item_code): + from erpnext.stock.doctype.batch.batch import make_batch + + return make_batch( + frappe._dict( + { + "item": item_code, + } + ) + ) + + def get_type_of_transaction(parent_doc, child_row): type_of_transaction = child_row.get("type_of_transaction") if parent_doc.get("doctype") == "Stock Entry":