erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { constructor(frm, item, callback) { this.frm = frm; this.item = item; this.qty = item.qty; this.callback = callback; this.bundle = this.item?.is_rejected ? this.item.rejected_serial_and_batch_bundle : this.item.serial_and_batch_bundle; this.make(); this.render_data(); } make() { 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) { label = __("Serial Nos / Batch Nos"); } primary_label += " " + label; this.dialog = new frappe.ui.Dialog({ title: this.item?.title || primary_label, fields: this.get_dialog_fields(), primary_action_label: primary_label, primary_action: () => this.update_bundle_entries(), secondary_action_label: __("Edit Full Form"), secondary_action: () => this.edit_full_form(), }); this.dialog.show(); this.$scan_btn = this.dialog.$wrapper.find(".link-btn"); this.$scan_btn.css("display", "inline"); let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty; if (this.item?.is_rejected) { qty = this.item.rejected_qty; } qty = Math.abs(qty); if (qty > 0) { this.dialog.set_value("qty", qty).then(() => { if (this.item.serial_no && !this.item.serial_and_batch_bundle) { let serial_nos = this.item.serial_no.split("\n"); if (serial_nos.length > 1) { serial_nos.forEach((serial_no) => { this.dialog.fields_dict.entries.df.data.push({ serial_no: serial_no, batch_no: this.item.batch_no, }); }); } else { this.dialog.set_value("scan_serial_no", this.item.serial_no); } frappe.model.set_value(this.item.doctype, this.item.name, "serial_no", ""); } else if (this.item.batch_no && !this.item.serial_and_batch_bundle) { this.dialog.set_value("scan_batch_no", this.item.batch_no); frappe.model.set_value(this.item.doctype, this.item.name, "batch_no", ""); } this.dialog.fields_dict.entries.grid.refresh(); }); } } get_serial_no_filters() { let warehouse = this.item?.type_of_transaction === "Outward" ? this.item.warehouse || this.item.s_warehouse : ""; if (this.frm.doc.doctype === "Stock Entry") { warehouse = this.item.s_warehouse || this.item.t_warehouse; } if (!warehouse && this.frm.doc.doctype === "Stock Reconciliation") { warehouse = this.get_warehouse(); } return { item_code: this.item.item_code, warehouse: ["=", warehouse], }; } get_dialog_fields() { let fields = []; fields.push({ fieldtype: "Link", fieldname: "warehouse", label: __("Warehouse"), options: "Warehouse", default: this.get_warehouse(), onchange: () => { this.item.warehouse = this.dialog.get_value("warehouse"); this.get_auto_data(); }, get_query: () => { return { filters: { is_group: 0, company: this.frm.doc.company, }, }; }, }); if (this.frm.doc.doctype === "Stock Entry" && this.frm.doc.purpose === "Manufacture") { fields.push({ fieldtype: "Column Break", }); fields.push({ fieldtype: "Link", fieldname: "work_order", label: __("For Work Order"), options: "Work Order", read_only: 1, default: this.frm.doc.work_order, }); fields.push({ fieldtype: "Section Break", }); } fields.push({ fieldtype: "Column Break", }); if (this.item.has_serial_no) { fields.push({ fieldtype: "Data", options: "Barcode", fieldname: "scan_serial_no", label: __("Scan Serial No"), get_query: () => { return { filters: this.get_serial_no_filters(), }; }, onchange: () => this.scan_barcode_data(), }); } if (this.item.has_batch_no && !this.item.has_serial_no) { fields.push({ fieldtype: "Data", options: "Barcode", fieldname: "scan_batch_no", label: __("Scan Batch No"), onchange: () => this.scan_barcode_data(), }); } if (this.item?.type_of_transaction === "Outward") { fields = [...this.get_filter_fields(), ...fields]; } else { fields = [...fields, ...this.get_attach_field()]; } fields.push({ fieldtype: "Section Break", }); fields.push({ fieldname: "entries", fieldtype: "Table", allow_bulk_edit: true, data: [], fields: this.get_dialog_table_fields(), }); return fields; } get_attach_field() { 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 = [ { fieldtype: "Section Break", label: __("{0} {1} via CSV File", [primary_label, label]), }, ]; 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: "Small Text", label: __("Enter Serial Nos"), fieldname: "upload_serial_nos", depends_on: "eval:doc.import_using_csv_file === 0", 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; } create_serial_nos() { let { upload_serial_nos } = this.dialog.get_values(); if (!upload_serial_nos) { frappe.throw(__("Please enter Serial Nos")); } frappe.call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.create_serial_nos", args: { item_code: this.item.item_code, serial_nos: upload_serial_nos, }, callback: (r) => { if (r.message) { this.dialog.fields_dict.entries.df.data = []; this.set_data(r.message); this.update_bundle_entries(); } }, }); } download_csv_file() { let csvFileData = ["Serial No"]; if (this.item.has_serial_no && this.item.has_batch_no) { csvFileData = ["Serial No", "Batch No", "Quantity"]; } else if (this.item.has_batch_no) { csvFileData = ["Batch No", "Quantity"]; } const method = `/api/method/erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.download_blank_csv_template?content=${encodeURIComponent( JSON.stringify(csvFileData) )}`; const w = window.open(frappe.urllib.get_full_url(method)); if (!w) { frappe.msgprint(__("Please enable pop-ups")); } } upload_csv_file() { const file_path = this.dialog.get_value("attach_serial_batch_csv"); frappe.call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.upload_csv_file", args: { item_code: this.item.item_code, file_path: file_path, }, callback: (r) => { if (r.message.serial_nos && r.message.serial_nos.length) { this.set_data(r.message.serial_nos); } else if (r.message.batch_nos && r.message.batch_nos.length) { this.set_data(r.message.batch_nos); } }, }); } get_filter_fields() { return [ { fieldtype: "Section Break", label: __("Auto Fetch"), }, { fieldtype: "Float", fieldname: "qty", label: __("Qty to Fetch"), onchange: () => this.get_auto_data(), }, { fieldtype: "Column Break", }, { fieldtype: "Select", options: ["FIFO", "LIFO", "Expiry"], default: "FIFO", fieldname: "based_on", label: __("Fetch Based On"), onchange: () => this.get_auto_data(), }, { fieldtype: "Section Break", }, ]; } get_dialog_table_fields() { let fields = []; if (this.item.has_serial_no) { fields.push({ fieldtype: "Link", options: "Serial No", fieldname: "serial_no", label: __("Serial No"), in_list_view: 1, get_query: () => { return { filters: this.get_serial_no_filters(), }; }, }); } let batch_fields = []; if (this.item.has_batch_no) { batch_fields = [ { fieldtype: "Link", options: "Batch", fieldname: "batch_no", label: __("Batch No"), in_list_view: 1, get_query: () => { let is_inward = false; if ( (["Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype) && !this.frm.doc.is_return) || (this.frm.doc.doctype === "Stock Entry" && this.frm.doc.purpose === "Material Receipt") ) { is_inward = true; } return { query: "erpnext.controllers.queries.get_batch_no", filters: { item_code: this.item.item_code, warehouse: this.item.s_warehouse || this.item.t_warehouse || this.item.warehouse, is_inward: is_inward, }, }; }, }, ]; if (!this.item.has_serial_no) { batch_fields.push({ fieldtype: "Float", fieldname: "qty", label: __("Quantity"), in_list_view: 1, }); } } fields = [...fields, ...batch_fields]; fields.push({ fieldtype: "Data", fieldname: "name", label: __("Name"), hidden: 1, }); return fields; } get_auto_data() { let { qty, based_on } = this.dialog.get_values(); if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) { if (qty === this.qty) { return; } } if (this.item.serial_no || this.item.batch_no) { return; } if (!based_on) { based_on = "FIFO"; } if (qty) { frappe.call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_auto_data", args: { item_code: this.item.item_code, warehouse: this.item.warehouse || this.item.s_warehouse, has_serial_no: this.item.has_serial_no, has_batch_no: this.item.has_batch_no, qty: qty, based_on: based_on, }, callback: (r) => { if (r.message) { this.dialog.fields_dict.entries.df.data = r.message; this.dialog.fields_dict.entries.grid.refresh(); } }, }); } } scan_barcode_data() { const { scan_serial_no, scan_batch_no } = this.dialog.get_values(); 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", args: { item_code: this.item.item_code, type_of_transaction: this.item.type_of_transaction, serial_no: scan_serial_no, batch_no: scan_batch_no, }, callback: (r) => { this.update_serial_batch_no(); }, }); } } update_serial_batch_no() { const { scan_serial_no, scan_batch_no } = this.dialog.get_values(); if (scan_serial_no) { let existing_row = this.dialog.fields_dict.entries.df.data.filter((d) => { if (d.serial_no === scan_serial_no) { return d; } }); if (existing_row?.length) { frappe.throw(__("Serial No {0} already exists", [scan_serial_no])); } if (!this.item.has_batch_no) { this.dialog.fields_dict.entries.df.data.push({ serial_no: scan_serial_no, }); this.dialog.fields_dict.scan_serial_no.set_value(""); } else { frappe.call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_batch_no_from_serial_no", args: { 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(""); } }, }); } } else if (scan_batch_no) { let existing_row = this.dialog.fields_dict.entries.df.data.filter((d) => { if (d.batch_no === scan_batch_no) { return d; } }); if (existing_row?.length) { existing_row[0].qty += 1; } else { this.dialog.fields_dict.entries.df.data.push({ batch_no: scan_batch_no, qty: 1, }); } this.dialog.fields_dict.scan_batch_no.set_value(""); } this.dialog.fields_dict.entries.grid.refresh(); } update_bundle_entries() { let entries = this.dialog.get_values().entries; let warehouse = this.dialog.get_value("warehouse"); if ((entries && !entries.length) || !entries) { frappe.throw(__("Please add atleast one Serial No / Batch No")); } frappe .call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers", args: { entries: entries, child_row: this.item, doc: this.frm.doc, warehouse: warehouse, }, }) .then((r) => { this.callback && this.callback(r.message); this.frm.save(); this.dialog.hide(); }); } edit_full_form() { let bundle_id = this.item.serial_and_batch_bundle; if (!bundle_id) { let _new = frappe.model.get_new_doc("Serial and Batch Bundle", null, null, true); _new.item_code = this.item.item_code; _new.warehouse = this.get_warehouse(); _new.has_serial_no = this.item.has_serial_no; _new.has_batch_no = this.item.has_batch_no; _new.type_of_transaction = this.item.type_of_transaction; _new.company = this.frm.doc.company; _new.voucher_type = this.frm.doc.doctype; bundle_id = _new.name; } frappe.set_route("Form", "Serial and Batch Bundle", bundle_id); this.dialog.hide(); } get_warehouse() { return this.item?.type_of_transaction === "Outward" ? this.item.warehouse || this.item.s_warehouse : this.item.warehouse || this.item.t_warehouse; } render_data() { if (this.bundle) { frappe .call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_serial_batch_ledgers", args: { item_code: this.item.item_code, name: this.bundle, voucher_no: !this.frm.is_new() ? this.item.parent : "", }, }) .then((r) => { if (r.message) { this.set_data(r.message); } }); } } set_data(data) { data.forEach((d) => { d.qty = Math.abs(d.qty); this.dialog.fields_dict.entries.df.data.push(d); }); this.dialog.fields_dict.entries.grid.refresh(); } };