From 6ac7b005a61e8ab55825635783577fc114f38fcd Mon Sep 17 00:00:00 2001 From: pandiyan Date: Fri, 19 Jun 2026 17:42:17 +0530 Subject: [PATCH] fix: add partially transferred status and fix button visibility for partial material transfer on job card (cherry picked from commit a22b83a97fe1dcf957d3d9b8bba7240c63640a82) # Conflicts: # erpnext/manufacturing/doctype/job_card/job_card.py --- .../doctype/job_card/job_card.js | 14 ++-- .../doctype/job_card/job_card.json | 4 +- .../doctype/job_card/job_card.py | 80 +++++++++++++++++++ .../doctype/job_card/job_card_list.js | 1 + 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index f550d6fb20a..762b0e1eeb0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -207,7 +207,9 @@ frappe.ui.form.on("Job Card", { 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; + const to_transfer = + has_items && doc.items.some((row) => flt(row.transferred_qty) < flt(row.required_qty)); + const to_request = to_transfer; if (has_items && (to_request || excess_transfer_allowed)) { frm.add_custom_button( @@ -217,9 +219,6 @@ frappe.ui.form.on("Job Card", { ); } - // 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"), @@ -586,11 +585,10 @@ frappe.ui.form.on("Job Card", { // ── Determine which action buttons to show ──────────────────────── const has_remaining_qty = doc.for_quantity + doc.process_loss_qty > doc.total_completed_qty; + const pending_transfer = + has_items && doc.items.some((row) => flt(row.transferred_qty) < flt(row.required_qty)); const materials_ready = - doc.skip_material_transfer || - doc.transferred_qty >= doc.for_quantity + doc.process_loss_qty || - !doc.finished_good || - !has_items?.length; + doc.skip_material_transfer || !pending_transfer || !doc.finished_good || !has_items; let last_row = {}; const has_sub_ops_or_pending_qty = doc.sub_operations?.length || doc.pending_qty > 0; diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 69a156009b1..c215aee42d4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -272,7 +272,7 @@ "fieldtype": "Select", "label": "Status", "no_copy": 1, - "options": "Open\nWork In Progress\nMaterial Transferred\nOn Hold\nSubmitted\nCancelled\nCompleted", + "options": "Open\nWork In Progress\nPartially Transferred\nMaterial Transferred\nOn Hold\nSubmitted\nCancelled\nCompleted", "read_only": 1 }, { @@ -695,7 +695,7 @@ "grid_page_length": 50, "is_submittable": 1, "links": [], - "modified": "2026-05-21 18:37:05.688342", + "modified": "2026-06-19 17:39:42.293242", "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 7647931c56f..637b69e5cc7 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -123,6 +123,7 @@ class JobCard(Document): status: DF.Literal[ "Open", "Work In Progress", + "Partially Transferred", "Material Transferred", "On Hold", "Submitted", @@ -1168,6 +1169,48 @@ class JobCard(Document): frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)) +<<<<<<< HEAD +======= + self.set_status(update_status=True) + + def get_job_card_items_transferred_qty(self, ste_doc): + from frappe.query_builder.functions import Sum + + job_card_items = [x.get("job_card_item") for x in ste_doc.get("items") if x.get("job_card_item")] + if not job_card_items: + return {} + + se = frappe.qb.DocType("Stock Entry") + sed = frappe.qb.DocType("Stock Entry Detail") + + query = ( + frappe.qb.from_(sed) + .join(se) + .on(sed.parent == se.name) + .select(sed.job_card_item, Sum(sed.qty)) + .where( + (sed.job_card_item.isin(job_card_items)) + & (se.docstatus == 1) + & (se.purpose == "Material Transfer for Manufacture") + ) + .groupby(sed.job_card_item) + ) + + return frappe._dict(query.run(as_list=True)) + + def validate_over_transfer(self, ste_doc, row, transferred_qty): + "Block over transfer of items if not allowed in settings." + required_qty = frappe.db.get_value("Job Card Item", row.job_card_item, "required_qty") + if flt(transferred_qty) > flt(required_qty): + frappe.throw( + _( + "Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}" + ).format(row.idx, frappe.bold(required_qty), frappe.bold(row.item_code), ste_doc.job_card), + title=_("Excess Transfer"), + exc=JobCardOverTransferError, + ) + +>>>>>>> a22b83a97f (fix: add partially transferred status and fix button visibility for partial material transfer on job card) def set_transferred_qty(self, update_status=False): from frappe.query_builder.functions import Sum @@ -1249,6 +1292,42 @@ class JobCard(Document): if self.workstation: self.update_workstation_status() +<<<<<<< HEAD +======= + def set_finished_good_status(self): + if (self.manufactured_qty + self.process_loss_qty) >= self.for_quantity: + self.status = "Completed" + elif self.transferred_qty > 0 or self.skip_material_transfer: + self.status = "Work In Progress" + + def set_non_semi_fg_status(self): + if self.items: + item_data = frappe.get_all( + "Job Card Item", + filters={"parent": self.name}, + fields=["transferred_qty", "required_qty"], + ) + all_transferred = item_data and all( + flt(d.transferred_qty) >= flt(d.required_qty) for d in item_data + ) + any_transferred = any(flt(d.transferred_qty) > 0 for d in item_data) + + if all_transferred: + self.status = "Material Transferred" + elif any_transferred: + self.status = "Partially Transferred" + elif flt(self.for_quantity) <= flt(self.transferred_qty): + self.status = "Material Transferred" + + if self.time_logs: + self.status = "Work In Progress" + + if self.docstatus == 1 and ( + self.for_quantity <= (self.total_completed_qty + self.process_loss_qty) or not self.items + ): + self.status = "Completed" + +>>>>>>> a22b83a97f (fix: add partially transferred status and fix button visibility for partial material transfer on job card) def set_wip_warehouse(self): if not self.wip_warehouse: self.wip_warehouse = frappe.get_cached_value("Company", self.company, "default_wip_warehouse") @@ -1780,6 +1859,7 @@ def get_job_details(start, end, filters=None): event_color = { "Completed": "#cdf5a6", "Material Transferred": "#ffdd9e", + "Partially Transferred": "#ffe5b4", "Work In Progress": "#D3D3D3", } diff --git a/erpnext/manufacturing/doctype/job_card/job_card_list.js b/erpnext/manufacturing/doctype/job_card/job_card_list.js index e417b7f576d..bddb11d00bb 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card_list.js +++ b/erpnext/manufacturing/doctype/job_card/job_card_list.js @@ -7,6 +7,7 @@ frappe.listview_settings["Job Card"] = { Completed: "green", Cancelled: "red", "Material Transferred": "blue", + "Partially Transferred": "yellow", Open: "red", }; const status = doc.status || "Open";