diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js index c83897aa778..2c879a3f62d 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js @@ -90,69 +90,10 @@ frappe.ui.form.on("BOM Creator", { }, { fieldtype: "Section Break" }, { - label: __("Track Operations"), - fieldtype: "Check", - fieldname: "track_operations", - onchange: (r) => { - let track_operations = dialog.get_value("track_operations"); - if (r.type === "input" && !track_operations) { - dialog.set_value("track_semi_finished_goods", 0); - } - }, - }, - { fieldtype: "Column Break" }, - { - label: __("Track Semi Finished Goods"), - fieldtype: "Check", - fieldname: "track_semi_finished_goods", - depends_on: "eval:doc.track_operations", - }, - { - fieldtype: "Section Break", - label: __("Final Product Operation"), - depends_on: "eval:doc.track_semi_finished_goods", - }, - { - label: __("Operation"), + label: __("Routing"), fieldtype: "Link", - fieldname: "operation", - options: "Operation", - default: "Assembly", - mandatory_depends_on: "eval:doc.track_semi_finished_goods", - depends_on: "eval:doc.track_semi_finished_goods", - }, - { - label: __("Operation Time (in mins)"), - fieldtype: "Float", - fieldname: "operation_time", - mandatory_depends_on: "eval:doc.track_semi_finished_goods", - depends_on: "eval:doc.track_semi_finished_goods", - }, - { fieldtype: "Column Break" }, - { - label: __("Workstation Type"), - fieldtype: "Link", - fieldname: "workstation_type", - options: "Workstation", - depends_on: "eval:doc.track_semi_finished_goods", - }, - { - label: __("Workstation"), - fieldtype: "Link", - fieldname: "workstation", - options: "Workstation", - depends_on: "eval:doc.track_semi_finished_goods", - get_query() { - let workstation_type = dialog.get_value("workstation_type"); - - if (workstation_type) { - return { - filters: { - workstation_type: dialog.get_value("workstation_type"), - }, - }; - } - }, + fieldname: "routing", + options: "Routing", }, ], primary_action_label: __("Create"), diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json index 47660741260..3e4929c7af8 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json @@ -19,6 +19,8 @@ "qty", "project", "uom", + "section_break_xvld", + "routing", "raw_materials_tab", "currency_detail", "rm_cost_as_per", @@ -393,6 +395,17 @@ { "fieldname": "column_break_buha", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_xvld", + "fieldtype": "Section Break", + "label": "Operations Routing" + }, + { + "fieldname": "routing", + "fieldtype": "Link", + "label": "Routing", + "options": "Routing" } ], "hide_toolbar": 1, @@ -404,7 +417,7 @@ "link_fieldname": "bom_creator" } ], - "modified": "2024-09-20 09:05:52.945112", + "modified": "2024-09-26 17:07:32.111198", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator", diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index a456b5af8be..cc5ad0dfe24 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -28,6 +28,7 @@ BOM_ITEM_FIELDS = [ "stock_uom", "conversion_factor", "do_not_explode", + "operation", ] @@ -64,6 +65,7 @@ class BOMCreator(Document): raw_material_cost: DF.Currency remarks: DF.TextEditor | None rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List"] + routing: DF.Link | None set_rate_based_on_warehouse: DF.Check skip_material_transfer: DF.Check source_warehouse: DF.Link | None @@ -409,6 +411,11 @@ class BOMCreator(Document): }, ) + elif row.item_code == self.item_code and self.routing: + bom.routing = self.routing + bom.with_operations = 1 + bom.transfer_material_against = "Work Order" + for field in BOM_FIELDS: if self.get(field): bom.set(field, self.get(field)) @@ -595,6 +602,7 @@ def add_sub_assembly(**kwargs): { "item_code": row.item_code, "qty": row.qty, + "operation": row.operation, "fg_item": bom_item.item_code, "uom": item_info.stock_uom, "fg_reference_id": name, diff --git a/erpnext/manufacturing/doctype/plant_floor/plant_floor.js b/erpnext/manufacturing/doctype/plant_floor/plant_floor.js index 3d18900b70c..f8e4d04c485 100644 --- a/erpnext/manufacturing/doctype/plant_floor/plant_floor.js +++ b/erpnext/manufacturing/doctype/plant_floor/plant_floor.js @@ -40,6 +40,7 @@ frappe.ui.form.on("Plant Floor", { refresh(frm) { frm.trigger("prepare_stock_dashboard"); frm.trigger("prepare_workstation_dashboard"); + frm.trigger("update_realtime_status"); if (!frm.is_new()) { frm.trigger("add_workstation"); @@ -58,6 +59,12 @@ frappe.ui.form.on("Plant Floor", { }); }, + update_realtime_status(frm) { + frappe.realtime.on("update_workstation_status", (data) => { + frappe.visual_plant_floor.update_status(data); + }); + }, + prepare_stock_dashboard(frm) { if (!frm.doc.warehouse) { return; diff --git a/erpnext/manufacturing/doctype/plant_floor/plant_floor.json b/erpnext/manufacturing/doctype/plant_floor/plant_floor.json index 4c4dfd8f667..f595cfd5fc4 100644 --- a/erpnext/manufacturing/doctype/plant_floor/plant_floor.json +++ b/erpnext/manufacturing/doctype/plant_floor/plant_floor.json @@ -72,7 +72,7 @@ "hide_toolbar": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-09-19 18:06:36.481625", + "modified": "2024-09-25 10:27:41.139634", "modified_by": "Administrator", "module": "Manufacturing", "name": "Plant Floor", diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js index dbcb0113c8f..4c1c9d2c976 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.js +++ b/erpnext/manufacturing/doctype/workstation/workstation.js @@ -190,11 +190,11 @@ class WorkstationDashboard { setup_menu_actions() { let me = this; this.job_cards.forEach((data) => { - me.menu_actions = me.$wrapper.find(`.menu-actions[data-job-card='${data.name}']`); - $(me.menu_actions).find(".btn-start").hide(); - $(me.menu_actions).find(".btn-resume").hide(); - $(me.menu_actions).find(".btn-pause").hide(); - $(me.menu_actions).find(".btn-complete").hide(); + me.menu_btns = me.$wrapper.find(`.job-card-link[data-name='${data.name}']`); + + $(me.menu_btns).find(".btn-resume").hide(); + $(me.menu_btns).find(".btn-pause").hide(); + $(me.menu_btns).find(".btn-complete .btn").attr("disabled", true); if ( data.for_quantity + data.process_loss_qty > data.total_completed_qty && @@ -203,15 +203,18 @@ class WorkstationDashboard { !data.finished_good) ) { if (!data.time_logs?.length) { - $(me.menu_actions).find(".btn-start").show(); + $(me.menu_btns).find(".btn-start").show(); } else if (data.is_paused) { - $(me.menu_actions).find(".btn-resume").show(); + $(me.menu_btns).find(".btn-start").hide(); + $(me.menu_btns).find(".btn-resume").show(); } else if (data.for_quantity - data.manufactured_qty > 0) { + $(me.menu_btns).find(".btn-start").hide(); if (!data.is_paused) { - $(me.menu_actions).find(".btn-pause").show(); + $(me.menu_btns).find(".btn-pause").show(); } - $(me.menu_actions).find(".btn-complete").show(); + $(me.menu_btns).find(".btn-complete").show(); + $(me.menu_btns).find(".btn-complete .btn").attr("disabled", false); } } }); @@ -243,26 +246,26 @@ class WorkstationDashboard { }); this.$wrapper.find(".btn-start").on("click", (e) => { - let job_card = $(e.currentTarget).closest("ul").attr("data-job-card"); + let job_card = $(e.currentTarget).closest("div").attr("data-job-card"); this.start_job(job_card); }); this.$wrapper.find(".btn-pause").on("click", (e) => { - let job_card = $(e.currentTarget).closest("ul").attr("data-job-card"); + let job_card = $(e.currentTarget).closest("div").attr("data-job-card"); me.update_job_card(job_card, "pause_job", { end_time: frappe.datetime.now_datetime(), }); }); this.$wrapper.find(".btn-resume").on("click", (e) => { - let job_card = $(e.currentTarget).closest("ul").attr("data-job-card"); + let job_card = $(e.currentTarget).closest("div").attr("data-job-card"); me.update_job_card(job_card, "resume_job", { start_time: frappe.datetime.now_datetime(), }); }); this.$wrapper.find(".btn-complete").on("click", (e) => { - let job_card = $(e.currentTarget).closest("ul").attr("data-job-card"); + let job_card = $(e.currentTarget).closest("div").attr("data-job-card"); let for_quantity = $(e.currentTarget).attr("data-qty"); me.complete_job(job_card, for_quantity); }); @@ -315,6 +318,12 @@ class WorkstationDashboard { let me = this; return [ + { + label: __("Start Time"), + fieldname: "start_time", + fieldtype: "Datetime", + default: frappe.datetime.now_datetime(), + }, { label: __("Employee"), fieldname: "employee", @@ -337,12 +346,6 @@ class WorkstationDashboard { } }, }, - { - label: __("Start Time"), - fieldname: "start_time", - fieldtype: "Datetime", - default: frappe.datetime.now_datetime(), - }, { fieldtype: "Section Break" }, { label: __("Employees"), diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json index 846effe8ea1..34aa7f0f915 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.json +++ b/erpnext/manufacturing/doctype/workstation/workstation.json @@ -15,6 +15,7 @@ "workstation_name", "workstation_type", "plant_floor", + "disabled", "column_break_3", "production_capacity", "warehouse", @@ -240,13 +241,20 @@ "fieldname": "section_break_mqqv", "fieldtype": "Section Break", "hide_border": 1 + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" } ], + "hide_toolbar": 1, "icon": "icon-wrench", "idx": 1, "image_field": "on_status_image", "links": [], - "modified": "2024-06-20 14:17:13.806609", + "modified": "2024-09-26 13:41:12.279344", "modified_by": "Administrator", "module": "Manufacturing", "name": "Workstation", diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index 2b0125c3d68..d0f0c3b45e9 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -49,6 +49,7 @@ class Workstation(Document): ) description: DF.Text | None + disabled: DF.Check holiday_list: DF.Link | None hour_rate: DF.Currency hour_rate_consumable: DF.Currency @@ -71,6 +72,11 @@ class Workstation(Document): self.set_data_based_on_workstation_type() self.set_hour_rate() self.set_total_working_hours() + self.disabled_workstation() + + def disabled_workstation(self): + if self.disabled: + self.status = "Off" def set_total_working_hours(self): self.total_working_hours = 0.0 @@ -124,6 +130,28 @@ class Workstation(Document): self.validate_overlap_for_operation_timings() self.update_bom_operation() + if self.plant_floor: + self.publish_workstation_status() + + def publish_workstation_status(self): + if not self._doc_before_save: + return + + if self._doc_before_save.get("status") == self.status: + return + + data = get_workstations(plant_floor=self.plant_floor, workstation_name=self.name)[0] + + color_map = get_color_map() + data["old_color"] = color_map.get(self._doc_before_save.get("status"), "red") + + frappe.publish_realtime( + "update_workstation_status", + data, + doctype="Plant Floor", + docname=self.plant_floor, + ) + def validate_overlap_for_operation_timings(self): """Check if there is no overlap in setting Workstation Operating Hours""" for d in self.get("working_hours"): @@ -238,10 +266,13 @@ def get_job_cards(workstation, job_card=None): user_employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, "name") for row in jc_data: + if row.status == "Open": + row.status = "Not Started" + item_code = row.finished_good or row.production_item row.fg_uom = frappe.get_cached_value("Item", item_code, "stock_uom") - row.status_color = get_status_color(row.status) + row.status_colour = get_status_color(row.status) row.job_card_link = f""" {row.name} """ @@ -423,8 +454,8 @@ def get_workstations(**kwargs): _workstation.on_status_image, _workstation.off_status_image, ) - .orderby(_workstation.workstation_type, _workstation.name) - .where(_workstation.plant_floor == kwargs.plant_floor) + .orderby(_workstation.creation, _workstation.workstation_type, _workstation.name) + .where((_workstation.plant_floor == kwargs.plant_floor) & (_workstation.disabled == 0)) ) if kwargs.workstation: @@ -436,9 +467,28 @@ def get_workstations(**kwargs): if kwargs.workstation_status: query = query.where(_workstation.status == kwargs.workstation_status) + if kwargs.workstation_name: + query = query.where(_workstation.name == kwargs.workstation_name) + data = query.run(as_dict=True) - color_map = { + color_map = get_color_map() + + for d in data: + d.workstation_name = get_link_to_form("Workstation", d.name) + d.status_image = d.on_status_image + d.workstation_off = "" + d.color = color_map.get(d.status, "red") + d.workstation_link = get_url_to_form("Workstation", d.name) + if d.status != "Production": + d.status_image = d.off_status_image + d.workstation_off = "workstation-off" + + return data + + +def get_color_map(): + return { "Production": "green", "Off": "gray", "Idle": "gray", @@ -447,16 +497,6 @@ def get_workstations(**kwargs): "Setup": "blue", } - for d in data: - d.workstation_name = get_link_to_form("Workstation", d.name) - d.status_image = d.on_status_image - d.color = color_map.get(d.status, "red") - d.workstation_link = get_url_to_form("Workstation", d.name) - if d.status != "Production": - d.status_image = d.off_status_image - - return data - @frappe.whitelist() def update_job_card(job_card, method, **kwargs): diff --git a/erpnext/manufacturing/doctype/workstation/workstation_job_card.html b/erpnext/manufacturing/doctype/workstation/workstation_job_card.html index 847963b7088..2049f3fe6a5 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation_job_card.html +++ b/erpnext/manufacturing/doctype/workstation/workstation_job_card.html @@ -17,7 +17,7 @@