diff --git a/inventory_tools/hooks.py b/inventory_tools/hooks.py index 3824443..ebc5f67 100644 --- a/inventory_tools/hooks.py +++ b/inventory_tools/hooks.py @@ -35,6 +35,8 @@ "Purchase Order": "public/js/purchase_order_custom.js", "Purchase Invoice": "public/js/purchase_invoice_custom.js", "Stock Entry": "public/js/stock_entry_custom.js", + "Job Card": "public/js/job_card_custom.js", + "Operation": "public/js/operation_custom.js", } # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} @@ -139,6 +141,11 @@ "Warehouse": { "validate": ["inventory_tools.inventory_tools.overrides.warehouse.update_warehouse_path"] }, + "Operation": { + "validate": [ + "inventory_tools.inventory_tools.overrides.operation.validate_alternative_workstation" + ] + }, } # Scheduled Tasks diff --git a/inventory_tools/inventory_tools/custom/operation.json b/inventory_tools/inventory_tools/custom/operation.json new file mode 100644 index 0000000..f48d696 --- /dev/null +++ b/inventory_tools/inventory_tools/custom/operation.json @@ -0,0 +1,70 @@ +{ + "custom_fields": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2024-02-16 05:04:42.422068", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Operation", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "alternative_workstations", + "fieldtype": "Table MultiSelect", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "workstation", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Alternative Workstations", + "length": 0, + "mandatory_depends_on": null, + "modified": "2024-02-16 05:04:42.422068", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Operation-alternative_workstations", + "no_copy": 0, + "non_negative": 0, + "options": "Alternative Workstations", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + } + ], + "custom_perms": [], + "doctype": "Operation", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} \ No newline at end of file diff --git a/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.json b/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.json new file mode 100644 index 0000000..470af37 --- /dev/null +++ b/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-02-01 04:03:58.033322", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "workstation" + ], + "fields": [ + { + "fieldname": "workstation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Workstation", + "options": "Workstation" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-02-01 07:04:30.059469", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Alternative Workstations", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.py b/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.py new file mode 100644 index 0000000..c262a29 --- /dev/null +++ b/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class AlternativeWorkstations(Document): + pass diff --git a/inventory_tools/inventory_tools/overrides/operation.py b/inventory_tools/inventory_tools/overrides/operation.py new file mode 100644 index 0000000..d649d48 --- /dev/null +++ b/inventory_tools/inventory_tools/overrides/operation.py @@ -0,0 +1,8 @@ +import frappe + + +def validate_alternative_workstation(self, method): + if self.workstation: + for row in self.alternative_workstations: + if row.workstation == self.workstation: + frappe.throw("Default Workstation should not be selected as alternative workstation") diff --git a/inventory_tools/inventory_tools/overrides/workstation.py b/inventory_tools/inventory_tools/overrides/workstation.py new file mode 100644 index 0000000..2d3689f --- /dev/null +++ b/inventory_tools/inventory_tools/overrides/workstation.py @@ -0,0 +1,62 @@ +import json + +import frappe + +""" + This function fetch workstation of the document operation. + In Operation you can select multiple workstations in Alternative Workstation field. + In the Work Order, Operation table, and Jobcard, there exists an operation field. + When selecting an operation, this function is responsible for fetching the workstations + both from the Alternative Workstation and the default workstation. + + Example : Operation : Cool Pie Op + Default Workstation: Cooling Racks Station + Alternative Workstation: + ````````````````````````````````````````````````````` + : Cooling Station , Refrigerator Station , : + : : + : : + `````````````````````````````````````````````````````` + In work order and job card when you select operation Cool Pie Op then you find below workstation in workstation field + : Cooling Station : + : Refrigerator Station : + : Cooling Racks Station : +""" + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_alternative_workstations(doctype, txt, searchfield, start, page_len, filters): + operation = filters.get("operation") + if not operation: + frappe.throw("Please select a Operation first.") + + if txt: + searchfields = frappe.get_meta(doctype).get_search_fields() + searchfields = " or ".join(["ws." + field + f" LIKE '%{txt}%'" for field in searchfields]) + + conditions = "" + if txt and searchfields: + conditions = f"and ({searchfields})" + + workstation = frappe.db.sql( + """ + Select aw.workstation, ws.workstation_type, ws.description + From `tabOperation` as op + Left Join `tabAlternative Workstations` as aw ON aw.parent = op.name + Left Join `tabWorkstation` as ws ON ws.name = aw.workstation + Where op.name = '{operation}' {conditions} + """.format( + conditions=conditions, operation=operation + ) + ) + + default_workstation = frappe.db.get_value("Operation", operation, "workstation") + flag = True + for row in workstation: + if row[0] == None: + workstation = ((default_workstation,),) + flag = False + if flag: + workstation += ((default_workstation,),) + return workstation diff --git a/inventory_tools/public/js/job_card_custom.js b/inventory_tools/public/js/job_card_custom.js new file mode 100644 index 0000000..b46df09 --- /dev/null +++ b/inventory_tools/public/js/job_card_custom.js @@ -0,0 +1,21 @@ +frappe.ui.form.on('Job Card', { + refresh: frm => { + if (frm.doc.operation) { + set_workstation_query(frm) + } + }, + operation: frm => { + set_workstation_query(frm) + }, +}) + +function set_workstation_query(frm) { + frm.set_query('workstation', doc => { + return { + query: 'inventory_tools.inventory_tools.overrides.workstation.get_alternative_workstations', + filters: { + operation: doc.operation, + }, + } + }) +} diff --git a/inventory_tools/public/js/operation_custom.js b/inventory_tools/public/js/operation_custom.js new file mode 100644 index 0000000..2091221 --- /dev/null +++ b/inventory_tools/public/js/operation_custom.js @@ -0,0 +1,18 @@ +frappe.ui.form.on('Operation', { + refresh: frm => { + get_filter_workstations(frm) + }, + workstation: frm => { + get_filter_workstations(frm) + }, +}) + +function get_filter_workstations(frm) { + cur_frm.fields_dict.alternative_workstations.get_query = function (doc) { + return { + filters: { + workstation_name: ['!=', frm.doc.workstation], + }, + } + } +} diff --git a/inventory_tools/public/js/work_order_custom.js b/inventory_tools/public/js/work_order_custom.js index a9e4f9d..7a87ae6 100644 --- a/inventory_tools/public/js/work_order_custom.js +++ b/inventory_tools/public/js/work_order_custom.js @@ -7,9 +7,28 @@ frappe.ui.form.on('Work Order', { }, refresh: frm => { manage_subcontracting_buttons(frm) + get_workstations(frm) + }, + operation: frm => { + get_workstations(frm) }, }) +function get_workstations(frm) { + frm.set_query('workstation', 'operations', (doc, cdt, cdn) => { + var d = locals[cdt][cdn] + if (!d.operation) { + frappe.throw('Please select a Operation first.') + } + return { + query: 'inventory_tools.inventory_tools.overrides.workstation.get_alternative_workstations', + filters: { + operation: d.operation, + }, + } + }) +} + function manage_subcontracting_buttons(frm) { if (frm.doc.company) { frappe.db.get_value('BOM', { name: frm.doc.bom_no }, 'is_subcontracted').then(r => { diff --git a/inventory_tools/tests/fixtures.py b/inventory_tools/tests/fixtures.py index d8fc8a2..007c456 100644 --- a/inventory_tools/tests/fixtures.py +++ b/inventory_tools/tests/fixtures.py @@ -85,6 +85,7 @@ - Remove butter and ice water from refrigerator - Place ingredients at workstation - Measure amounts for batch size into mixing bowl""", + ["Food Prep Table 1"], ), ( "Gather Pie Filling Ingredients", @@ -94,6 +95,7 @@ - Remove sugar and cornstarch - Get water from sink - Measure ingredients and place in pot, excluding 1/4 of fruit and butter""", + ["Food Prep Table 2"], ), ( "Assemble Pie Op", @@ -104,6 +106,7 @@ - Fill bottom crust with filling - Create decorative cut out for top crust - Layer top crust over bottom crust / filling and create a crimped seal""", + ["Food Prep Table 1", "Assemble Pie Station"], ), ( "Cook Pie Filling Operation", @@ -121,6 +124,7 @@ - Pulse for 30 seconds - Divide into equal-sized portions, one portion for each pie crust being made - Put in refrigerator""", + ["Mix Pie Crust Station", "Mix Pie Filling Station"], ), ("Box Pie Op", "Packaging Station", "5", "- Place pie into box for sale"), ( @@ -131,8 +135,9 @@ - Separate each portion into two (one for bottom crust, one for top) - Flour board and roll out each portion into a circle - Place bottom crust into pie tin, then layer a piece of parchment paper, followed by the top crust""", + ["Food Prep Table 1", "Roll Pie Crust Station"], ), - ("Divide Dough Op", "Food Prep Table 2", "1", "Divide Dough Op"), + ("Divide Dough Op", "Food Prep Table 2", "1", "Divide Dough Op", ["Food Prep Table 1"]), ( "Bake Op", "Oven Station", @@ -140,13 +145,21 @@ """- Place assembled pies into oven - Bake at 375F for 50 minutes - Remove from oven""", + ["Baking Station"], + ), + ( + "Chill Pie Crust Op", + "Refrigerator Station", + "1", + "- Chill pie crust for at least 30 minutes", + ["Cooling Station", "Cooling Racks Station"], ), - ("Chill Pie Crust Op", "Refrigerator Station", "1", "- Chill pie crust for at least 30 minutes"), ( "Cool Pie Op", "Cooling Racks Station", "1", "Cool baked pies for at least 30 minutes before boxing", + ["Cooling Station", "Refrigerator Station"], ), ] diff --git a/inventory_tools/tests/setup.py b/inventory_tools/tests/setup.py index 79322ec..23dbf81 100644 --- a/inventory_tools/tests/setup.py +++ b/inventory_tools/tests/setup.py @@ -202,6 +202,14 @@ def create_operations(): oper.workstation = op[1] oper.batch_size = op[2] oper.description = op[3] + if len(op) == 5: + for aw in op[4]: + oper.append( + "alternative_workstations", + { + "workstation": aw, + }, + ) oper.save()