diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 87c804e026fee83f9da25796b3b56cdfe7745fe3..fcd585bc49cc6bcb7dbefd05a4d1cbe732d99bc5 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -374,12 +374,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; @@ -412,15 +415,16 @@ 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)) { fields.forEach((field) => { item[field] = r.message[field]; }); - item.type_of_transaction = !doc.is_return > 0 ? "Inward" : "Outward"; + item.type_of_transaction = item.rejected_qty > 0 ? "Inward" : "Outward"; item.is_rejected = true; new erpnext.SerialBatchPackageSelector( diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 575d50bce92f98106c825b25d2fcadfdc826d166..36d8c2b35a97f0650d408b988e0777d7d0d2897a 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -16,14 +16,15 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { let primary_label = this.bundle ? __('Update') : __('Add'); - if (this.item?.has_serial_no && this.item?.batch_no) { - label = __('Serial Nos / Batch Nos'); + if (this.item?.has_serial_no && this.item?.has_batch_no) { + label = __("Serial Nos / Batch Nos"); } primary_label += ' ' + label; 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(), @@ -169,13 +170,15 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } fields.push({ - fieldtype: 'Section Break', + 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(), }); @@ -184,92 +187,110 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_attach_field() { - let label = this.item?.has_serial_no ? __('Serial Nos') : __('Batch Nos'); - let primary_label = this.bundle - ? __('Update') : __('Add'); + let me = this; + 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?.has_batch_no) { 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: 'Section Break', - label: __('{0} {1} via CSV File', [primary_label, label]) - } - ] + 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', + fieldtype: "Section Break", + label: __("{0} {1} Manually", [primary_label, label]), + 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"); @@ -534,6 +555,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', @@ -578,17 +601,15 @@ 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.scan_serial_no.set_value(''); - } - } + 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.entries.grid.refresh(); + }, + }); } } else if (scan_batch_no) { let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => { @@ -614,7 +635,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { update_bundle_entries() { let entries = this.dialog.get_values().entries; - let warehouse = this.dialog.get_value('warehouse'); + 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')); @@ -639,10 +666,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }, }) .then((r) => { - this.callback && this.callback(r.message); - frappe.ui.form.dont_update_route_after_rename = true; - 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 8c8de27df94073ed67df00896bd7e94658c4cd58..60aae8c65dc87e0d5e2da83cab553543c07adf1f 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 @@ -1554,6 +1554,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( @@ -1561,7 +1570,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, }, ) @@ -1576,6 +1585,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":