diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index e52a2bf63b944475f7a75e6e2f3f7a792e542cef..c8f80637cb0ec7bbe92908dd9f987253bab10f4a 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -45,6 +45,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 && @@ -91,11 +92,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"); }); @@ -105,7 +106,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"); }); @@ -132,7 +133,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"), () => { @@ -168,7 +170,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({ @@ -217,12 +220,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", }, ]; @@ -236,7 +275,7 @@ frappe.ui.form.on("Job Card", { }); } - frappe.prompt( + frm.job_completion_dialog = frappe.prompt( fields, (data) => { if (data.qty <= 0) { @@ -247,7 +286,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) { @@ -628,7 +668,15 @@ frappe.ui.form.on("Job Card", { }); frappe.ui.form.on("Job Card Time Log", { - completed_qty: function (frm) { + 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 1f9ebbccff8dd638a8f0e1d339e82c10de774dd4..ff6850a12837824fd8b1b2e277615cde6f12744d 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 6971523594b35b741dfedd069222ed39b6bc5b8f..bc8c2795b063bf6c6eabfec2cd223f0b6830c795 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 @@ -723,7 +724,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": @@ -794,7 +795,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: @@ -1037,7 +1038,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" @@ -1254,6 +1255,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)