diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index ef2682dc634..ac544b0fe0c 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -2,79 +2,53 @@ // For license information, please see license.txt frappe.ui.form.on("Job Card", { - setup: function (frm) { - frm.set_query("operation", function () { - return { - query: "erpnext.manufacturing.doctype.job_card.job_card.get_operations", - filters: { - work_order: frm.doc.work_order, - }, - }; - }); + setup(frm) { + frm.set_query("operation", () => ({ + query: "erpnext.manufacturing.doctype.job_card.job_card.get_operations", + filters: { work_order: frm.doc.work_order }, + })); - frm.set_query("serial_and_batch_bundle", () => { - return { - filters: { - item_code: frm.doc.production_item, - voucher_type: frm.doc.doctype, - voucher_no: ["in", [frm.doc.name, ""]], - is_cancelled: 0, - }, - }; - }); + frm.set_query("serial_and_batch_bundle", () => ({ + filters: { + item_code: frm.doc.production_item, + voucher_type: frm.doc.doctype, + voucher_no: ["in", [frm.doc.name, ""]], + is_cancelled: 0, + }, + })); - frm.set_query("item_code", "secondary_items", () => { - return { - filters: { - disabled: 0, - }, - }; - }); + frm.set_query("item_code", "secondary_items", () => ({ + filters: { disabled: 0 }, + })); frm.set_query("operation", "time_logs", () => { - let operations = (frm.doc.sub_operations || []).map((d) => d.sub_operation); - return { - filters: { - name: ["in", operations], - }, - }; + const operations = (frm.doc.sub_operations || []).map((d) => d.sub_operation); + return { filters: { name: ["in", operations] } }; }); - frm.set_query("work_order", function () { - return { - filters: { - status: ["not in", ["Cancelled", "Closed", "Stopped"]], - }, - }; - }); + frm.set_query("work_order", () => ({ + filters: { status: ["not in", ["Cancelled", "Closed", "Stopped"]] }, + })); frm.events.set_company_filters(frm, "target_warehouse"); frm.events.set_company_filters(frm, "source_warehouse"); frm.events.set_company_filters(frm, "wip_warehouse"); - frm.set_query("source_warehouse", "items", () => { - return { - filters: { - company: frm.doc.company, - }, - }; + + frm.set_query("source_warehouse", "items", () => ({ + filters: { company: frm.doc.company }, + })); + + frm.set_indicator_formatter("sub_operation", (doc) => { + if (doc.status === "Pending") return "red"; + return doc.status === "Complete" ? "green" : "orange"; }); - frm.set_indicator_formatter("sub_operation", function (doc) { - if (doc.status == "Pending") { - return "red"; - } else { - return doc.status === "Complete" ? "green" : "orange"; - } - }); - - frm.set_query("employee", () => { - return { - filters: { - company: frm.doc.company, - status: "Active", - }, - }; - }); + frm.set_query("employee", () => ({ + filters: { + company: frm.doc.company, + status: "Active", + }, + })); }, pending_qty(frm) { @@ -88,7 +62,7 @@ frappe.ui.form.on("Job Card", { frappe.throw(__("Pending Quantity cannot be less than 0")); } - let remaining_qty = flt(frm.doc.for_quantity) - flt(frm.doc.total_completed_qty); + const remaining_qty = flt(frm.doc.for_quantity) - flt(frm.doc.total_completed_qty); if (remaining_qty < frm.doc.pending_qty) { frm.doc.pending_qty = 0.0; @@ -96,19 +70,15 @@ frappe.ui.form.on("Job Card", { frappe.throw(__("Pending Quantity cannot be greater than {0}", [remaining_qty])); } - let process_loss_qty = flt(remaining_qty) - flt(frm.doc.pending_qty); + const process_loss_qty = flt(remaining_qty) - flt(frm.doc.pending_qty); frm.doc.process_loss_qty = process_loss_qty >= 0 ? process_loss_qty : 0; refresh_field("process_loss_qty"); }, set_company_filters(frm, fieldname) { - frm.set_query(fieldname, () => { - return { - filters: { - company: frm.doc.company, - }, - }; - }); + frm.set_query(fieldname, () => ({ + filters: { company: frm.doc.company }, + })); }, make_fields_read_only(frm) { @@ -123,33 +93,29 @@ frappe.ui.form.on("Job Card", { }, setup_stock_entry(frm) { - if ( - frm.doc.track_semi_finished_goods && - frm.doc.docstatus === 1 && - !frm.doc.is_subcontracted && - (frm.doc.skip_material_transfer || frm.doc.transferred_qty > 0) && - flt(frm.doc.manufactured_qty) + flt(frm.doc.process_loss_qty) < flt(frm.doc.for_quantity) - ) { - frm.add_custom_button(__("Make Stock Entry"), () => { - frappe.confirm( - __("Do you want to submit the stock entry?"), - () => { - frm.events.make_manufacture_stock_entry(frm, 1); - }, - () => { - frm.events.make_manufacture_stock_entry(frm, 0); - } - ); - }).addClass("btn-primary"); - } + const { doc } = frm; + const can_make_stock_entry = + doc.track_semi_finished_goods && + doc.docstatus === 1 && + !doc.is_subcontracted && + (doc.skip_material_transfer || doc.transferred_qty > 0) && + flt(doc.manufactured_qty) + flt(doc.process_loss_qty) < flt(doc.for_quantity); + + if (!can_make_stock_entry) return; + + frm.add_custom_button(__("Make Stock Entry"), () => { + frappe.confirm( + __("Do you want to submit the stock entry?"), + () => frm.events.make_manufacture_stock_entry(frm, 1), + () => frm.events.make_manufacture_stock_entry(frm, 0) + ); + }).addClass("btn-primary"); }, make_manufacture_stock_entry(frm, submit_entry) { frm.call({ method: "make_stock_entry_for_semi_fg_item", - args: { - auto_submit: submit_entry, - }, + args: { auto_submit: submit_entry }, doc: frm.doc, freeze: true, callback() { @@ -158,80 +124,58 @@ frappe.ui.form.on("Job Card", { }); }, +<<<<<<< HEAD refresh: function (frm) { frm.trigger("setup_stock_entry"); let has_items = frm.doc.items && frm.doc.items.length; +======= + refresh(frm) { + const { doc } = frm; + const has_items = doc.items && doc.items.length; + +>>>>>>> 0a215b0717 (refactor: job_card.js code for better readability) frm.trigger("make_fields_read_only"); - if (!frm.is_new() && frm.doc.__onload?.work_order_closed) { + if (!frm.is_new() && doc.__onload?.work_order_closed) { frm.disable_save(); return; } - if (frm.doc.is_subcontracted) { + if (doc.is_subcontracted) { frm.trigger("make_subcontracting_po"); return; } - if (frm.doc.docstatus > 0) { + if (doc.docstatus > 0) { frm.set_df_property("pending_qty", "read_only", 1); } - let has_stock_entry = frm.doc.__onload && frm.doc.__onload.has_stock_entry ? true : false; - + const has_stock_entry = !!doc.__onload?.has_stock_entry; frm.toggle_enable("for_quantity", !has_stock_entry); - if (frm.doc.docstatus != 0) { + if (doc.docstatus != 0) { frm.fields_dict["time_logs"].grid.update_docfield_property("completed_qty", "read_only", 1); frm.fields_dict["time_logs"].grid.update_docfield_property("time_in_mins", "read_only", 1); } - if (!frm.is_new() && !frm.doc.skip_material_transfer && frm.doc.docstatus < 2) { - let to_request = frm.doc.for_quantity > frm.doc.transferred_qty; - let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; + frm.events.setup_material_transfer_buttons(frm, has_items); - if (has_items && (to_request || excess_transfer_allowed)) { - frm.add_custom_button( - __("Material Request"), - () => { - frm.trigger("make_material_request"); - }, - __("Create") - ); - } - - // check if any row has untransferred materials - // in case of multiple items in JC - let to_transfer = frm.doc.items.some((row) => row.transferred_qty < row.required_qty); - - if (has_items && (to_transfer || excess_transfer_allowed)) { - frm.add_custom_button( - __("Material Transfer"), - () => { - frm.trigger("make_stock_entry"); - }, - __("Create") - ); - } - } - - if (frm.doc.docstatus == 1 && !frm.doc.is_corrective_job_card && !frm.doc.finished_good) { + if (doc.docstatus == 1 && !doc.is_corrective_job_card && !doc.finished_good) { frm.trigger("setup_corrective_job_card"); } - frm.set_query("quality_inspection", function () { - return { - query: "erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query", - filters: { - item_code: frm.doc.production_item, - reference_name: frm.doc.name, - }, - }; - }); + frm.set_query("quality_inspection", () => ({ + query: "erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query", + filters: { + item_code: doc.production_item, + reference_name: doc.name, + }, + })); frm.trigger("toggle_operation_number"); +<<<<<<< HEAD if ( frm.doc.for_quantity + frm.doc.process_loss_qty > frm.doc.total_completed_qty && (frm.doc.skip_material_transfer || @@ -312,36 +256,165 @@ frappe.ui.form.on("Job Card", { frm.trigger("make_dashboard"); } } +======= + const is_timer_running = frm.events.setup_job_action_buttons(frm, has_items); +>>>>>>> 0a215b0717 (refactor: job_card.js code for better readability) frm.trigger("setup_quality_inspection"); - if (frm.doc.work_order) { - frappe.db.get_value("Work Order", frm.doc.work_order, "transfer_material_against").then((r) => { - if (r.message.transfer_material_against == "Work Order" && !frm.doc.operation_row_id) { + if (doc.work_order) { + frappe.db.get_value("Work Order", doc.work_order, "transfer_material_against").then((r) => { + if (r.message.transfer_material_against == "Work Order" && !doc.operation_row_id) { frm.set_df_property("items", "hidden", 1); } }); } - let sbb_field = frm.get_docfield("serial_and_batch_bundle"); + const sbb_field = frm.get_docfield("serial_and_batch_bundle"); if (sbb_field) { - sbb_field.get_route_options_for_new_doc = () => { - return { - item_code: frm.doc.production_item, - warehouse: frm.doc.wip_warehouse, - voucher_type: frm.doc.doctype, - }; - }; + sbb_field.get_route_options_for_new_doc = () => ({ + item_code: doc.production_item, + warehouse: doc.wip_warehouse, + voucher_type: doc.doctype, + }); } }, + // Adds Material Request and Material Transfer buttons when items need to be transferred. + setup_material_transfer_buttons(frm, has_items) { + const { doc } = frm; + + if (frm.is_new() || doc.skip_material_transfer || doc.docstatus >= 2) return; + + const excess_transfer_allowed = doc.__onload.job_card_excess_transfer; + const to_request = doc.for_quantity > doc.transferred_qty; + + if (has_items && (to_request || excess_transfer_allowed)) { + frm.add_custom_button( + __("Material Request"), + () => frm.trigger("make_material_request"), + __("Create") + ); + } + + // check if any row has untransferred materials in case of multiple items in JC + const to_transfer = doc.items.some((row) => row.transferred_qty < row.required_qty); + + if (has_items && (to_transfer || excess_transfer_allowed)) { + frm.add_custom_button( + __("Material Transfer"), + () => frm.trigger("make_stock_entry"), + __("Create") + ); + } + }, + + // Renders the correct action button (Start / Resume / Pause + Complete) based on job state. + // Returns true if the job timer is actively running, so the caller can skip the stock entry button. + setup_job_action_buttons(frm, has_items) { + const { doc } = frm; + + const has_remaining_qty = doc.for_quantity + doc.process_loss_qty > doc.total_completed_qty; + const materials_ready = + doc.skip_material_transfer || + doc.transferred_qty >= doc.for_quantity + doc.process_loss_qty || + !doc.finished_good || + !has_items?.length; + + if (!has_remaining_qty || !materials_ready) return false; + + let last_row = {}; + const has_sub_ops_or_pending_qty = doc.sub_operations?.length || doc.pending_qty > 0; + if (has_sub_ops_or_pending_qty && doc.time_logs?.length) { + last_row = get_last_row(doc.time_logs); + } + + const no_time_logs_yet = !doc.time_logs?.length; + const pending_qty_cycle_done = flt(doc.pending_qty) > 0.0 && last_row?.to_time; + const sub_operation_cycle_done = doc.sub_operations?.length && last_row?.to_time; + const should_show_start = + (no_time_logs_yet || pending_qty_cycle_done || sub_operation_cycle_done) && !doc.is_paused; + + if (should_show_start) { + frm.events.add_start_job_button(frm); + return false; + } + + if (doc.is_paused) { + frm.add_custom_button(__("Resume Job"), () => { + frm.call({ + method: "resume_job", + doc: frm.doc, + args: { start_time: frappe.datetime.now_datetime() }, + callback() { + frm.reload_doc(); + }, + }); + }); + return false; + } + + // Job is actively running — show Pause and Complete buttons. + const manufactured_qty = doc.manufactured_qty || doc.total_completed_qty; + const qty_yet_to_manufacture = doc.for_quantity - (manufactured_qty + doc.process_loss_qty); + + if (qty_yet_to_manufacture > 0) { + if (!doc.is_paused) { + frm.add_custom_button(__("Pause Job"), () => { + frm.call({ + method: "pause_job", + doc: frm.doc, + args: { end_time: frappe.datetime.now_datetime() }, + callback() { + frm.reload_doc(); + }, + }); + }); + } + + frm.add_custom_button(__("Complete Job"), () => { + frm.trigger("complete_job_card"); + }); + + frm.trigger("make_dashboard"); + return true; + } + + frm.trigger("make_dashboard"); + return false; + }, + + add_start_job_button(frm) { + frm.add_custom_button(__("Start Job"), () => { + const from_time = frappe.datetime.now_datetime(); + const has_no_employee = (frm.doc.employee && !frm.doc.employee.length) || !frm.doc.employee; + + if (has_no_employee) { + frappe.prompt( + { + fieldtype: "Table MultiSelect", + label: __("Select Employees"), + options: "Job Card Time Log", + fieldname: "employees", + reqd: 1, + filters: { status: "Active" }, + }, + (d) => frm.events.start_timer(frm, from_time, d.employees), + __("Assign Job to Employee") + ); + } else { + frm.events.start_timer(frm, from_time, frm.doc.employee); + } + }); + }, + complete_job_card(frm) { let pending_qty = frm.doc.for_quantity - frm.doc.total_completed_qty; if (frm.doc.pending_qty > 0) { pending_qty = frm.doc.pending_qty; } - let fields = [ + const fields = [ { fieldtype: "Float", label: __("Qty to Manufacture"), @@ -349,10 +422,9 @@ frappe.ui.form.on("Job Card", { reqd: 1, default: pending_qty, change() { - let doc = frm.job_completion_dialog; - - doc.set_value("completed_qty", doc.get_value("for_quantity")); - doc.set_value("process_loss_qty", 0); + const dialog = frm.job_completion_dialog; + dialog.set_value("completed_qty", dialog.get_value("for_quantity")); + dialog.set_value("process_loss_qty", 0); }, }, { @@ -362,11 +434,10 @@ frappe.ui.form.on("Job Card", { reqd: 1, default: pending_qty, change() { - let doc = frm.job_completion_dialog; - - let pending_qty = doc.get_value("for_quantity") - doc.get_value("completed_qty"); - if (pending_qty > 0 && pending_qty != doc.get_value("pending_qty")) { - doc.set_value("pending_qty", pending_qty); + const dialog = frm.job_completion_dialog; + const remaining = dialog.get_value("for_quantity") - dialog.get_value("completed_qty"); + if (remaining > 0 && remaining != dialog.get_value("pending_qty")) { + dialog.set_value("pending_qty", remaining); } }, }, @@ -376,14 +447,13 @@ frappe.ui.form.on("Job Card", { fieldname: "pending_qty", default: 0.0, change() { - let doc = frm.job_completion_dialog; - - let process_loss_qty = - doc.get_value("for_quantity") - - doc.get_value("completed_qty") - - doc.get_value("pending_qty"); - if (process_loss_qty >= 0 && process_loss_qty != doc.get_value("process_loss_qty")) { - doc.set_value("process_loss_qty", process_loss_qty); + const dialog = frm.job_completion_dialog; + const process_loss_qty = + dialog.get_value("for_quantity") - + dialog.get_value("completed_qty") - + dialog.get_value("pending_qty"); + if (process_loss_qty >= 0 && process_loss_qty != dialog.get_value("process_loss_qty")) { + dialog.set_value("process_loss_qty", process_loss_qty); } }, }, @@ -392,14 +462,13 @@ frappe.ui.form.on("Job Card", { label: __("Process Loss Quantity"), fieldname: "process_loss_qty", onchange() { - let doc = frm.job_completion_dialog; - - let pending_qty = - doc.get_value("for_quantity") - - doc.get_value("completed_qty") - - doc.get_value("process_loss_qty"); - if (pending_qty >= 0 && pending_qty != doc.get_value("pending_qty")) { - doc.set_value("pending_qty", pending_qty); + const dialog = frm.job_completion_dialog; + const remaining = + dialog.get_value("for_quantity") - + dialog.get_value("completed_qty") - + dialog.get_value("process_loss_qty"); + if (remaining >= 0 && remaining != dialog.get_value("pending_qty")) { + dialog.set_value("pending_qty", remaining); } }, }, @@ -415,20 +484,16 @@ frappe.ui.form.on("Job Card", { fieldname: "sub_operation", options: "Operation", get_query() { - let non_completed_operations = frm.doc.sub_operations.filter( - (d) => d.status === "Pending" - ); + const non_completed = frm.doc.sub_operations.filter((d) => d.status === "Pending"); return { - filters: { - name: ["in", non_completed_operations.map((d) => d.sub_operation)], - }, + filters: { name: ["in", non_completed.map((d) => d.sub_operation)] }, }; }, reqd: 1, }); } - let last_completed_row = get_last_completed_row(frm.doc.time_logs); + const last_completed_row = get_last_completed_row(frm.doc.time_logs); let last_row = {}; if (frm.doc.sub_operations?.length && frm.doc.time_logs?.length) { last_row = get_last_row(frm.doc.time_logs); @@ -461,7 +526,7 @@ frappe.ui.form.on("Job Card", { end_time: data.end_time, sub_operation: data.sub_operation, }, - callback: function (r) { + callback() { frm.reload_doc(); }, }); @@ -487,11 +552,8 @@ frappe.ui.form.on("Job Card", { frm.call({ method: "start_timer", doc: frm.doc, - args: { - start_time: start_time, - employees: employees, - }, - callback: function (r) { + args: { start_time, employees }, + callback() { frm.reload_doc(); frm.trigger("make_dashboard"); }, @@ -499,7 +561,7 @@ frappe.ui.form.on("Job Card", { }, make_finished_good(frm) { - let fields = [ + const fields = [ { fieldtype: "Float", label: __("Completed Quantity"), @@ -525,12 +587,9 @@ frappe.ui.form.on("Job Card", { frm.call({ method: "make_finished_good", doc: frm.doc, - args: { - qty: data.qty, - end_time: data.end_time, - }, - callback: function (r) { - var doc = frappe.model.sync(r.message); + args: { qty: data.qty, end_time: data.end_time }, + callback(r) { + const doc = frappe.model.sync(r.message); frappe.set_route("Form", doc[0].doctype, doc[0].name); }, }); @@ -541,8 +600,8 @@ frappe.ui.form.on("Job Card", { ); }, - setup_quality_inspection: function (frm) { - let quality_inspection_field = frm.get_docfield("quality_inspection"); + setup_quality_inspection(frm) { + const quality_inspection_field = frm.get_docfield("quality_inspection"); quality_inspection_field.get_route_options_for_new_doc = function (frm) { return { inspection_type: "In Process", @@ -557,24 +616,22 @@ frappe.ui.form.on("Job Card", { }; }, - setup_corrective_job_card: function (frm) { + setup_corrective_job_card(frm) { frm.add_custom_button( __("Corrective Job Card"), () => { - let operations = frm.doc.sub_operations.map((d) => d.sub_operation).concat(frm.doc.operation); + const operations = frm.doc.sub_operations + .map((d) => d.sub_operation) + .concat(frm.doc.operation); - let fields = [ + const fields = [ { fieldtype: "Link", label: __("Corrective Operation"), options: "Operation", fieldname: "operation", get_query() { - return { - filters: { - is_corrective_operation: 1, - }, - }; + return { filters: { is_corrective_operation: 1 } }; }, }, { @@ -583,20 +640,14 @@ frappe.ui.form.on("Job Card", { options: "Operation", fieldname: "for_operation", get_query() { - return { - filters: { - name: ["in", operations], - }, - }; + return { filters: { name: ["in", operations] } }; }, }, ]; frappe.prompt( fields, - (d) => { - frm.events.make_corrective_job_card(frm, d.operation, d.for_operation); - }, + (d) => frm.events.make_corrective_job_card(frm, d.operation, d.for_operation), __("Select Corrective Operation") ); }, @@ -604,7 +655,7 @@ frappe.ui.form.on("Job Card", { ); }, - make_corrective_job_card: function (frm, operation, for_operation) { + make_corrective_job_card(frm, operation, for_operation) { frappe.call({ method: "erpnext.manufacturing.doctype.job_card.job_card.make_corrective_job_card", args: { @@ -612,7 +663,7 @@ frappe.ui.form.on("Job Card", { operation: operation, for_operation: for_operation, }, - callback: function (r) { + callback(r) { if (r.message) { frappe.model.sync(r.message); frappe.set_route("Form", r.message.doctype, r.message.name); @@ -621,7 +672,7 @@ frappe.ui.form.on("Job Card", { }); }, - operation: function (frm) { + operation(frm) { frm.trigger("toggle_operation_number"); if (frm.doc.operation && frm.doc.work_order) { @@ -631,28 +682,22 @@ frappe.ui.form.on("Job Card", { work_order: frm.doc.work_order, operation: frm.doc.operation, }, - callback: function (r) { - if (r.message) { - if (r.message.length == 1) { - frm.set_value("operation_id", r.message[0].name); - } else { - let args = []; + callback(r) { + if (!r.message) return; - r.message.forEach((row) => { - args.push({ label: row.idx, value: row.name }); - }); - - let description = __("Operation {0} added multiple times in the work order {1}", [ - frm.doc.operation, - frm.doc.work_order, - ]); - - frm.set_df_property("operation_row_number", "options", args); - frm.set_df_property("operation_row_number", "description", description); - } - - frm.trigger("toggle_operation_number"); + if (r.message.length == 1) { + frm.set_value("operation_id", r.message[0].name); + } else { + const args = r.message.map((row) => ({ label: row.idx, value: row.name })); + const description = __("Operation {0} added multiple times in the work order {1}", [ + frm.doc.operation, + frm.doc.work_order, + ]); + frm.set_df_property("operation_row_number", "options", args); + frm.set_df_property("operation_row_number", "description", description); } + + frm.trigger("toggle_operation_number"); }, }); } @@ -669,65 +714,35 @@ frappe.ui.form.on("Job Card", { frm.toggle_reqd("operation_row_number", !frm.doc.operation_id && frm.doc.operation); }, - make_time_log: function (frm, args) { + make_time_log(frm, args) { frm.events.update_sub_operation(frm, args); frappe.call({ method: "erpnext.manufacturing.doctype.job_card.job_card.make_time_log", - args: { - args: args, - }, + args: { args }, freeze: true, - callback: function () { + callback() { frm.reload_doc(); frm.trigger("make_dashboard"); }, }); }, - update_sub_operation: function (frm, args) { - if (frm.doc.sub_operations && frm.doc.sub_operations.length) { - let sub_operations = frm.doc.sub_operations.filter((d) => d.status != "Complete"); - if (sub_operations && sub_operations.length) { - args["sub_operation"] = sub_operations[0].sub_operation; + update_sub_operation(frm, args) { + if (frm.doc.sub_operations?.length) { + const pending_sub_ops = frm.doc.sub_operations.filter((d) => d.status != "Complete"); + if (pending_sub_ops.length) { + args["sub_operation"] = pending_sub_ops[0].sub_operation; } } }, - make_dashboard: function (frm) { + make_dashboard(frm) { if (frm.doc.__islocal) return; - var section = ""; - - function setCurrentIncrement() { - currentIncrement += 1; - return currentIncrement; - } - - function updateStopwatch(increment) { - var hours = Math.floor(increment / 3600); - var minutes = Math.floor((increment - hours * 3600) / 60); - var seconds = Math.floor(flt(increment - hours * 3600 - minutes * 60, 2)); - - $(section) - .find(".hours") - .text(hours < 10 ? "0" + hours.toString() : hours.toString()); - $(section) - .find(".minutes") - .text(minutes < 10 ? "0" + minutes.toString() : minutes.toString()); - $(section) - .find(".seconds") - .text(seconds < 10 ? "0" + seconds.toString() : seconds.toString()); - } - - function initialiseTimer() { - const interval = setInterval(function () { - var current = setCurrentIncrement(); - updateStopwatch(current); - }, 1000); - } frm.dashboard.refresh(); - const timer = ` + + const timer_html = `
00 @@ -737,20 +752,44 @@ frappe.ui.form.on("Job Card", { 00
`; + let section; if (frappe.utils.is_xs()) { - frm.dashboard.add_comment(timer, "white", true); + frm.dashboard.add_comment(timer_html, "white", true); section = frm.layout.wrapper.find(".form-message-container"); } else { - section = frm.toolbar.page.add_inner_message(timer); + section = frm.toolbar.page.add_inner_message(timer_html); } - let currentIncrement = frm.events.get_current_time(frm); - if (frm.doc.time_logs?.length && frm.doc.time_logs[cint(frm.doc.time_logs.length) - 1].to_time) { - updateStopwatch(currentIncrement); - } else if (frm.doc.status == "On Hold") { - updateStopwatch(currentIncrement); + const pad = (n) => String(n).padStart(2, "0"); + + const update_stopwatch = (increment) => { + const hours = Math.floor(increment / 3600); + const minutes = Math.floor((increment - hours * 3600) / 60); + const seconds = Math.floor(flt(increment - hours * 3600 - minutes * 60, 2)); + + section.find(".hours").text(pad(hours)); + section.find(".minutes").text(pad(minutes)); + section.find(".seconds").text(pad(seconds)); + }; + + let current_increment = frm.events.get_current_time(frm); + + const start_timer = () => { + setInterval(() => { + current_increment += 1; + update_stopwatch(current_increment); + }, 1000); + }; + + const { time_logs, status } = frm.doc; + const last_log_complete = time_logs?.length && time_logs[cint(time_logs.length) - 1].to_time; + + if (last_log_complete) { + update_stopwatch(current_increment); + } else if (status == "On Hold") { + update_stopwatch(current_increment); } else { - initialiseTimer(); + start_timer(); } }, @@ -772,22 +811,22 @@ frappe.ui.form.on("Job Card", { return current_time; }, - hide_timer: function (frm) { + hide_timer(frm) { frm.toolbar.page.inner_toolbar.find(".stopwatch").remove(); }, - for_quantity: function (frm) { + for_quantity(frm) { frm.doc.items = []; frm.call({ method: "get_required_items", doc: frm.doc, - callback: function () { + callback() { refresh_field("items"); }, }); }, - make_material_request: function (frm) { + make_material_request(frm) { frappe.model.open_mapped_doc({ method: "erpnext.manufacturing.doctype.job_card.job_card.make_material_request", frm: frm, @@ -795,7 +834,7 @@ frappe.ui.form.on("Job Card", { }); }, - make_stock_entry: function (frm) { + make_stock_entry(frm) { frappe.model.open_mapped_doc({ method: "erpnext.manufacturing.doctype.job_card.job_card.make_stock_entry", frm: frm, @@ -803,11 +842,11 @@ frappe.ui.form.on("Job Card", { }); }, - timer: function (frm) { + timer(frm) { return ``; }, - set_total_completed_qty: function (frm) { + set_total_completed_qty(frm) { frm.doc.total_completed_qty = 0; frm.doc.time_logs.forEach((d) => { if (d.completed_qty) { @@ -816,10 +855,9 @@ frappe.ui.form.on("Job Card", { }); if (frm.doc.total_completed_qty && frm.doc.for_quantity > frm.doc.total_completed_qty) { - let flt_precision = precision("for_quantity", frm.doc); - let process_loss_qty = + const flt_precision = precision("for_quantity", frm.doc); + const process_loss_qty = flt(frm.doc.for_quantity, flt_precision) - flt(frm.doc.total_completed_qty, flt_precision); - frm.set_value("process_loss_qty", process_loss_qty); } @@ -836,8 +874,8 @@ frappe.ui.form.on("Job Card", { }); frappe.ui.form.on("Job Card Time Log", { - completed_qty: function (frm, cdt, cdn) { - let row = locals[cdt][cdn]; + completed_qty(frm, cdt, cdn) { + const row = locals[cdt][cdn]; if (!row.completed_qty) { frappe.model.set_value(row.doctype, row.name, { time_in_mins: 0, @@ -854,12 +892,8 @@ function get_seconds_diff(d1, d2) { } function get_last_completed_row(time_logs) { - let completed_rows = time_logs.filter((d) => d.to_time); - - if (completed_rows?.length) { - let last_completed_row = completed_rows[completed_rows.length - 1]; - return last_completed_row; - } + const completed_rows = time_logs.filter((d) => d.to_time); + return completed_rows[completed_rows.length - 1]; } function get_last_row(time_logs) {