From 953ce2ea85c5f05dfca79b2dbc928898bbe9946b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 30 Mar 2025 18:59:12 +0530 Subject: [PATCH] fix: UX for partial job card completion --- .../doctype/job_card/job_card.js | 68 ++++++++++++++++--- .../doctype/job_card/job_card.json | 13 +++- .../doctype/job_card/job_card.py | 11 ++- 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 8dee4ffb5d..034ed8a49a 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -47,6 +47,7 @@ frappe.ui.form.on('Job Card', { setup_stock_entry(frm) { if ( + frm.doc.manufactured_qty && frm.doc.finished_good && frm.doc.docstatus === 1 && !frm.doc.is_subcontracted && @@ -93,11 +94,11 @@ frappe.ui.form.on('Job Card', { frm.fields_dict["time_logs"].grid.update_docfield_property("time_in_mins", "read_only", 1); } - if (!frm.is_new() && !frm.doc.skip_material_transfer && has_items && frm.doc.docstatus < 2) { + 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; - if (to_request || excess_transfer_allowed) { + if (has_items && (to_request || excess_transfer_allowed)) { frm.add_custom_button(__("Material Request"), () => { frm.trigger("make_material_request"); }); @@ -107,7 +108,7 @@ frappe.ui.form.on('Job Card', { // in case of multiple items in JC let to_transfer = frm.doc.items.some((row) => row.transferred_qty < row.required_qty); - if (to_transfer || excess_transfer_allowed) { + if (has_items && (to_transfer || excess_transfer_allowed)) { frm.add_custom_button(__("Material Transfer"), () => { frm.trigger("make_stock_entry"); }); @@ -134,7 +135,8 @@ frappe.ui.form.on('Job Card', { frm.doc.for_quantity + frm.doc.process_loss_qty > frm.doc.total_completed_qty && (frm.doc.skip_material_transfer || frm.doc.transferred_qty >= frm.doc.for_quantity + frm.doc.process_loss_qty || - !frm.doc.finished_good) + !frm.doc.finished_good || + !has_items?.length) ) { if (!frm.doc.time_logs?.length) { frm.add_custom_button(__("Start Job"), () => { @@ -170,7 +172,8 @@ frappe.ui.form.on('Job Card', { }); }); } else { - if (frm.doc.for_quantity - frm.doc.manufactured_qty > 0) { + let manufactured_qty = frm.doc.manufactured_qty || frm.doc.total_completed_qty; + if (frm.doc.for_quantity - (manufactured_qty + frm.doc.process_loss_qty) > 0) { if (!frm.doc.is_paused) { frm.add_custom_button(__("Pause Job"), () => { frm.call({ @@ -219,12 +222,48 @@ frappe.ui.form.on('Job Card', { complete_job_card(frm) { let fields = [ + { + fieldtype: "Float", + label: __("Qty to Manufacture"), + fieldname: "for_quantity", + reqd: 1, + default: frm.doc.for_quantity, + change() { + let doc = frm.job_completion_dialog; + + doc.set_value("completed_qty", doc.get_value("for_quantity")); + doc.set_value("process_loss_qty", 0); + }, + }, { fieldtype: "Float", label: __("Completed Quantity"), - fieldname: "qty", + fieldname: "completed_qty", reqd: 1, default: frm.doc.for_quantity - frm.doc.total_completed_qty, + change() { + let doc = frm.job_completion_dialog; + + let process_loss_qty = doc.get_value("for_quantity") - doc.get_value("completed_qty"); + if (process_loss_qty > 0 && process_loss_qty != doc.get_value("process_loss_qty")) { + doc.set_value("process_loss_qty", process_loss_qty); + } + }, + }, + { + fieldtype: "Float", + label: __("Process Loss Quantity"), + fieldname: "process_loss_qty", + reqd: 1, + onchange() { + let doc = frm.job_completion_dialog; + + let completed_qty = doc.get_value("for_quantity") - doc.get_value("process_loss_qty"); + doc.set_value("completed_qty", completed_qty); + }, + }, + { + fieldtype: "Section Break", }, ]; @@ -238,7 +277,7 @@ frappe.ui.form.on('Job Card', { }); } - frappe.prompt( + frm.job_completion_dialog = frappe.prompt( fields, (data) => { if (data.qty <= 0) { @@ -249,7 +288,8 @@ frappe.ui.form.on('Job Card', { method: "complete_job_card", doc: frm.doc, args: { - qty: data.qty, + qty: data.completed_qty, + for_quantity: data.for_quantity, end_time: data.end_time, }, callback: function (r) { @@ -615,8 +655,16 @@ frappe.ui.form.on('Job Card', { }, }); -frappe.ui.form.on('Job Card Time Log', { - completed_qty: function(frm) { +frappe.ui.form.on("Job Card Time Log", { + completed_qty: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (!row.completed_qty) { + frappe.model.set_value(row.doctype, row.name, { + time_in_mins: 0, + to_time: "", + }); + } + frm.events.set_total_completed_qty(frm); }, diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 1f9ebbccff..ff6850a128 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -73,6 +73,7 @@ "status", "operation_row_id", "is_paused", + "track_semi_finished_goods", "column_break_20", "operation_row_number", "operation_id", @@ -530,10 +531,11 @@ "read_only": 1 }, { + "depends_on": "eval:doc.track_semi_finished_goods", "fieldname": "target_warehouse", "fieldtype": "Link", "label": "Target Warehouse", - "mandatory_depends_on": "eval:doc.finished_good", + "mandatory_depends_on": "eval:doc.track_semi_finished_goods", "options": "Warehouse" }, { @@ -610,12 +612,19 @@ "fieldtype": "Check", "label": "Is Paused", "read_only": 1 + }, + { + "default": "0", + "fetch_from": "work_order.track_semi_finished_goods", + "fieldname": "track_semi_finished_goods", + "fieldtype": "Check", + "label": "Track Semi Finished Goods" } ], "grid_page_length": 50, "is_submittable": 1, "links": [], - "modified": "2025-03-25 17:50:18.608869", + "modified": "2025-03-30 18:53:38.206399", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 89715f6b78..a873e03b5b 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -129,6 +129,7 @@ class JobCard(Document): time_required: DF.Float total_completed_qty: DF.Float total_time_in_mins: DF.Float + track_semi_finished_goods: DF.Check transferred_qty: DF.Float wip_warehouse: DF.Link | None work_order: DF.Link @@ -719,7 +720,7 @@ class JobCard(Document): ) def validate_job_card(self): - if self.finished_good: + if self.track_semi_finished_goods: return if self.work_order and frappe.get_cached_value("Work Order", self.work_order, "status") == "Stopped": @@ -792,7 +793,7 @@ class JobCard(Document): ) def update_work_order(self): - if self.finished_good: + if self.track_semi_finished_goods: return if not self.work_order: @@ -1039,7 +1040,7 @@ class JobCard(Document): if self.docstatus == 0 and self.time_logs: self.status = "Work In Progress" - if not self.finished_good and self.docstatus < 2: + if not self.track_semi_finished_goods and self.docstatus < 2: if flt(self.for_quantity) <= flt(self.transferred_qty): self.status = "Material Transferred" @@ -1255,6 +1256,10 @@ class JobCard(Document): if kwargs.end_time: self.add_time_logs(to_time=kwargs.end_time, completed_qty=kwargs.qty, employees=self.employee) + + if kwargs.for_quantity: + self.for_quantity = kwargs.for_quantity + self.save() else: self.add_time_logs(completed_qty=kwargs.qty, employees=self.employee) -- GitLab