From 7c4b37c06586fd3b03f341274708c03897f3f9ec Mon Sep 17 00:00:00 2001 From: Tyler Matteson Date: Mon, 2 Dec 2024 17:05:58 -0500 Subject: [PATCH 1/5] wip: gantt UI for optimization editor --- .pre-commit-config.yaml | 4 +- inventory_tools/hooks.py | 4 +- .../inventory_tools/custom/workstation.json | 1 + .../inventory_tools/page/__init__.py | 2 + .../page/optimizer/__init__.py | 89 +++++++++ .../page/optimizer/optimizer.js | 179 ++++++++++++++++++ .../page/optimizer/optimizer.json | 29 +++ .../public/js/custom/work_order_list.js | 15 ++ 8 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 inventory_tools/inventory_tools/page/__init__.py create mode 100644 inventory_tools/inventory_tools/page/optimizer/__init__.py create mode 100644 inventory_tools/inventory_tools/page/optimizer/optimizer.js create mode 100644 inventory_tools/inventory_tools/page/optimizer/optimizer.json create mode 100644 inventory_tools/public/js/custom/work_order_list.js diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b446f79d..d81f55b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ exclude: 'node_modules|.git' -default_stages: [commit] +default_stages: [pre-commit] fail_fast: false repos: @@ -50,7 +50,7 @@ repos: - tomli - repo: https://github.com/agritheory/test_utils - rev: v0.16.0 + rev: v0.16.1 hooks: - id: update_pre_commit_config - id: mypy diff --git a/inventory_tools/hooks.py b/inventory_tools/hooks.py index 3cba4bbf..d42956b1 100644 --- a/inventory_tools/hooks.py +++ b/inventory_tools/hooks.py @@ -51,7 +51,9 @@ "Work Order": "public/js/custom/work_order_custom.js", "Workstation": "public/js/custom/workstation_custom.js", } -# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} + +doctype_list_js = {"Work Order": "public/js/custom/work_order_list.js"} + # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} diff --git a/inventory_tools/inventory_tools/custom/workstation.json b/inventory_tools/inventory_tools/custom/workstation.json index 12239df7..ad790f2f 100644 --- a/inventory_tools/inventory_tools/custom/workstation.json +++ b/inventory_tools/inventory_tools/custom/workstation.json @@ -31,6 +31,7 @@ "length": 0, "modified": "2024-08-20 05:01:28.504010", "modified_by": "Administrator", + "module": "Inventory Tools", "name": "Workstation-company", "no_copy": 0, "non_negative": 0, diff --git a/inventory_tools/inventory_tools/page/__init__.py b/inventory_tools/inventory_tools/page/__init__.py new file mode 100644 index 00000000..6b9109e0 --- /dev/null +++ b/inventory_tools/inventory_tools/page/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/page/optimizer/__init__.py b/inventory_tools/inventory_tools/page/optimizer/__init__.py new file mode 100644 index 00000000..3268366c --- /dev/null +++ b/inventory_tools/inventory_tools/page/optimizer/__init__.py @@ -0,0 +1,89 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +import frappe + + +@frappe.whitelist() +def get_work_order_gantt_data(work_order=None, production_item=None): + work_orders = get_work_order_dependencies(work_order, production_item) + dependency_map = {} + for d in work_orders: + if d.dependent_on: + if d.work_order not in dependency_map: + dependency_map[d.work_order] = [] + dependency_map[d.work_order].append(d.dependent_on) + return [ + { + "id": wo.work_order, + "name": f"{wo.production_item}:{wo.item_name}" + if wo.item_name != wo.production_item + else wo.production_item, + "start": wo.planned_start_date, + "end": wo.planned_end_date, + "progress": 100 if wo.status == "Completed" else 10, + "dependencies": ",".join(dependency_map.get(wo.work_order, [])), + } + for wo in work_orders + ] + + +def get_work_order_dependencies(work_order=None, production_item=None): + conditions = ["wo.status = 'Not Started'"] + if work_order: + conditions.append( + """( + wo.name = %(work_order)s + OR wo2.name = %(work_order)s + OR EXISTS ( + SELECT 1 FROM `tabWork Order Item` woi2 + WHERE woi2.parent = %(work_order)s + AND woi2.item_code = wo.production_item + ) + )""" + ) + if production_item: + conditions.append("wo.production_item = %(production_item)s") + + where_clause = "WHERE " + " AND ".join(conditions) + + query = f""" + WITH RECURSIVE work_order_tree AS ( + SELECT + wo.name as work_order, + wo.production_item, + wo.item_name, + wo2.name as dependent_on, + wo.planned_start_date, + wo.planned_end_date, + wo.status, + 0 as level + FROM `tabWork Order` wo + LEFT JOIN `tabWork Order Item` woi ON woi.parent = wo.name + LEFT JOIN `tabWork Order` wo2 ON wo2.production_item = woi.item_code + {where_clause} + + UNION ALL + + SELECT + t.work_order, + wo.production_item, + wo.item_name, + wo2.name, + wo.planned_start_date, + wo.planned_end_date, + wo.status, + t.level + 1 + FROM work_order_tree t + JOIN `tabWork Order Item` woi ON woi.parent = t.dependent_on + JOIN `tabWork Order` wo2 ON wo2.production_item = woi.item_code + JOIN `tabWork Order` wo ON wo.name = t.work_order + WHERE t.level < 10 + ) + SELECT * FROM work_order_tree + GROUP BY work_order + ORDER BY level + """ + return frappe.db.sql( + query, {"work_order": work_order, "production_item": production_item}, as_dict=True + ) diff --git a/inventory_tools/inventory_tools/page/optimizer/optimizer.js b/inventory_tools/inventory_tools/page/optimizer/optimizer.js new file mode 100644 index 00000000..4fc4d7ba --- /dev/null +++ b/inventory_tools/inventory_tools/page/optimizer/optimizer.js @@ -0,0 +1,179 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + +frappe.pages['optimizer'].on_page_load = wrapper => { + frappe.require( + [ + 'assets/frappe/node_modules/frappe-gantt/dist/frappe-gantt.css', + 'assets/frappe/node_modules/frappe-gantt/dist/frappe-gantt.min.js', + ], + () => { + const page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'Optimizer', + single_column: true, + }) + + frappe.pages['optimizer'].gantt_view = new OptimizerView(page) + } + ) +} + +class OptimizerView { + constructor(page) { + this.page = page + this.setup_filters() + this.setup_page() + this.init_gantt() + } + + setup_filters() { + this.page.add_field({ + label: 'Work Order', + fieldtype: 'Link', + options: 'Work Order', + fieldname: 'work_order', + onchange: () => this.init_gantt(), + }) + this.page.add_field({ + label: 'Production Item', + fieldtype: 'Link', + options: 'Item', + fieldname: 'production_item', + onchange: () => this.init_gantt(), + }) + } + + setup_page() { + this.list_paging_area = $(` +
+
+
+ + + + + +
+
+
+ `).appendTo($('.page-form')[0]) + + this.setup_paging_events() + + this.container = $('
').appendTo(this.page.main) + + this.output = $('
') + .css({ + overflow: 'auto', + minHeight: '200px', + }) + .appendTo(this.container)[0] + + this.resizer = $('
') + .css({ + height: '10px', + background: '#e0e0e0', + cursor: 'row-resize', + margin: '5px 0', + '&:hover': { + background: '#bdbdbd', + }, + }) + .appendTo(this.container) + + this.actual = $('
') + .css({ + overflow: 'auto', + minHeight: '200px', + }) + .appendTo(this.container)[0] + + $(this.container).css({ + display: 'grid', + 'grid-template-rows': '40vh 10px 40vh', + gap: '0', + height: '85vh', + }) + + this.setup_resizer() + } + + init_gantt() { + const filters = { + work_order: this.page.fields_dict.work_order.get_value(), + production_item: this.page.fields_dict.production_item.get_value(), + } + + frappe + .xcall('inventory_tools.inventory_tools.page.optimizer.get_work_order_gantt_data', { + ...filters, + }) + .then(r => { + this.all_tasks = r + this.actual_gantt = new Gantt(this.actual, r, { + view_mode: 'Quarter Day', + on_click: task => { + frappe.set_route('Form', 'Work Order', task.id) + }, + }) + + this.output_gantt = new Gantt(this.output, r, { + view_mode: 'Quarter Day', + on_click: task => { + frappe.set_route('Form', 'Work Order', task.id) + }, + on_date_change: (task, start, end) => { + this.update_work_order(task.id, start, end) + }, + }) + }) + } + + update_work_order(name, start, end) { + frappe.call({ + method: 'frappe.client.set_value', + args: { + doctype: 'Work Order', + name: name, + fieldname: { + planned_start_date: start, + planned_end_date: end, + }, + }, + }) + } + setup_resizer() { + return + let startY, startHeightTop, startHeightBottom + + this.resizer.on('mousedown', e => { + startY = e.clientY + startHeightTop = $(this.output).height() + startHeightBottom = $(this.actual).height() + + $(document).on('mousemove', mousemove) + $(document).on('mouseup', mouseup) + }) + + const mousemove = e => { + const diff = e.clientY - startY + $(this.output).height(startHeightTop + diff) + $(this.actual).height(startHeightBottom - diff) + } + + const mouseup = () => { + $(document).off('mousemove', mousemove) + $(document).off('mouseup', mouseup) + } + } + + setup_paging_events() { + this.list_paging_area.find('.btn-paging').click(e => { + const view_mode = $(e.target).data('value') + console.log(view_mode) + this.output_gantt?.change_view_mode(view_mode) + this.actual_gantt?.change_view_mode(view_mode) + }) + } +} diff --git a/inventory_tools/inventory_tools/page/optimizer/optimizer.json b/inventory_tools/inventory_tools/page/optimizer/optimizer.json new file mode 100644 index 00000000..2170611e --- /dev/null +++ b/inventory_tools/inventory_tools/page/optimizer/optimizer.json @@ -0,0 +1,29 @@ +{ + "content": null, + "creation": "2024-12-02 15:01:35.785409", + "docstatus": 0, + "doctype": "Page", + "icon": "gantt", + "idx": 0, + "modified": "2024-12-02 15:01:35.785409", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "optimizer", + "owner": "Administrator", + "page_name": "optimizer", + "roles": [ + { + "role": "Manufacturing Manager" + }, + { + "role": "System Manager" + }, + { + "role": "Stock Manager" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0 +} diff --git a/inventory_tools/public/js/custom/work_order_list.js b/inventory_tools/public/js/custom/work_order_list.js new file mode 100644 index 00000000..8fc005ad --- /dev/null +++ b/inventory_tools/public/js/custom/work_order_list.js @@ -0,0 +1,15 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + +frappe.listview_settings['Work Order'] = { + refresh: listview => { + listview.page.add_custom_menu_item( + $('[data-view]').parent(), + __('Optimizer'), + () => frappe.set_route('/optimizer'), + true, + null, + 'gantt' + ) + }, +} From 0babe98b8125648382c05d8c0fd74be555c207c8 Mon Sep 17 00:00:00 2001 From: coleandreoli <80193303+coleandreoli@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:07:23 +0100 Subject: [PATCH 2/5] Add date data (#114) * Added planned dates, shifts, and workstation hours * Black Formatted * updated dates and times * pre-commit formatting * Configured module --- CHANGELOG.md | 3 + inventory_tools/tests/fixtures.py | 403 ++++++++++++++++++++++++++---- inventory_tools/tests/setup.py | 65 ++++- 3 files changed, 418 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa8895bf..5136227a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ + + # CHANGELOG diff --git a/inventory_tools/tests/fixtures.py b/inventory_tools/tests/fixtures.py index 8d4f7dab..60defac4 100644 --- a/inventory_tools/tests/fixtures.py +++ b/inventory_tools/tests/fixtures.py @@ -74,25 +74,115 @@ ), ] +# workstations = [ +# ("Mix Pie Crust Station", "20"), +# ("Roll Pie Crust Station", "20"), +# ("Make Pie Filling Station", "20"), +# ("Cooling Station", "100"), +# ("Box Pie Station", "100"), +# ("Baking Station", "20"), +# ("Assemble Pie Station", "20"), +# ("Mix Pie Filling Station", "20"), +# ("Packaging Station", "2"), +# ("Food Prep Table 2", "10"), +# ("Food Prep Table 1", "5"), +# ("Range Station", "20"), +# ("Cooling Racks Station", "80"), +# ("Refrigerator Station", "200"), +# ("Oven Station", "20"), +# ("Mixer Station", "10"), +# ] + workstations = [ - ("Mix Pie Crust Station", "20"), - ("Roll Pie Crust Station", "20"), - ("Make Pie Filling Station", "20"), - ("Cooling Station", "100"), - ("Box Pie Station", "100"), - ("Baking Station", "20"), - ("Assemble Pie Station", "20"), - ("Mix Pie Filling Station", "20"), - ("Packaging Station", "2"), - ("Food Prep Table 2", "10"), - ("Food Prep Table 1", "5"), - ("Range Station", "20"), - ("Cooling Racks Station", "80"), - ("Refrigerator Station", "200"), - ("Oven Station", "20"), - ("Mixer Station", "10"), + { + "workstation_name": "Mix Pie Crust Station", + "production_capacity": "20", + "shift_types": ["Day Shift", "Evening Shift"], + }, + { + "workstation_name": "Roll Pie Crust Station", + "production_capacity": "20", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Make Pie Filling Station", + "production_capacity": "20", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Cooling Station", + "production_capacity": "100", + "shift_types": ["Day Shift", "Evening Shift"], + }, + { + "workstation_name": "Box Pie Station", + "production_capacity": "100", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Baking Station", + "production_capacity": "20", + "shift_types": ["Day Shift", "Evening Shift"], + }, + { + "workstation_name": "Assemble Pie Station", + "production_capacity": "20", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Mix Pie Filling Station", + "production_capacity": "20", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Packaging Station", + "production_capacity": "2", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Food Prep Table 2", + "production_capacity": "10", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Food Prep Table 1", + "production_capacity": "5", + "shift_types": ["Day Shift", "Evening Shift"], + }, + { + "workstation_name": "Range Station", + "production_capacity": "20", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Cooling Racks Station", + "production_capacity": "80", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Refrigerator Station", + "production_capacity": "200", + "shift_types": ["Day Shift"], + }, + { + "workstation_name": "Oven Station", + "production_capacity": "20", + "shift_types": ["Day Shift", "Evening Shift"], + }, + { + "workstation_name": "Mixer Station", + "production_capacity": "10", + "shift_types": ["Day Shift"], + }, +] + +shifts = [ + {"name": "Day Shift", "start_time": "06:00:00", "end_time": "14:00:00", "color": "Yellow"}, + {"name": "Evening Shift", "start_time": "14:00:00", "end_time": "22:00:00", "color": "Blue"}, + {"name": "Night Shift", "start_time": "22:00:00", "end_time": "06:00:00", "color": "Violet"}, ] + operations = [ ( "Gather Pie Crust Ingredients", @@ -570,7 +660,12 @@ "uom": "Nos", "items": [ {"item_code": "Bayberry Pie", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - {"item_code": "Bayberry Pocket", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + { + "item_code": "Bayberry Pocket", + "qty": 5.0, + "qty_consumed_per_unit": 1.0, + "uom": "Nos", + }, ], "operations": [ { @@ -586,8 +681,18 @@ "quantity": 5.0, "uom": "Nos", "items": [ - {"item_code": "Bayberry Pocket", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - {"item_code": "Bayberry Popper", "qty": 10.0, "qty_consumed_per_unit": 2.0, "uom": "Nos"}, + { + "item_code": "Bayberry Pocket", + "qty": 5.0, + "qty_consumed_per_unit": 1.0, + "uom": "Nos", + }, + { + "item_code": "Bayberry Popper", + "qty": 10.0, + "qty_consumed_per_unit": 2.0, + "uom": "Nos", + }, {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, ], "operations": [ @@ -607,7 +712,12 @@ {"item_code": "Flour", "qty": 1.5, "qty_consumed_per_unit": 0.3, "uom": "Pound"}, {"item_code": "Butter", "qty": 0.75, "qty_consumed_per_unit": 0.15, "uom": "Pound"}, {"item_code": "Sugar", "qty": 0.1, "qty_consumed_per_unit": 0.02, "uom": "Pound"}, - {"item_code": "Bayberry Popper", "qty": 15.0, "qty_consumed_per_unit": 3.0, "uom": "Nos"}, + { + "item_code": "Bayberry Popper", + "qty": 15.0, + "qty_consumed_per_unit": 3.0, + "uom": "Nos", + }, ], "operations": [ { @@ -670,7 +780,12 @@ "time_in_mins": 10.0, "workstation": "Food Prep Table 2", }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Bake Op", + "time_in_mins": 50.0, + "workstation": "Oven Station", + }, { "batch_size": 1, "operation": "Cool Pie Op", @@ -706,7 +821,12 @@ "time_in_mins": 10.0, "workstation": "Food Prep Table 2", }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Bake Op", + "time_in_mins": 50.0, + "workstation": "Oven Station", + }, { "batch_size": 1, "operation": "Cool Pie Op", @@ -742,7 +862,12 @@ "time_in_mins": 10.0, "workstation": "Food Prep Table 2", }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Bake Op", + "time_in_mins": 50.0, + "workstation": "Oven Station", + }, { "batch_size": 1, "operation": "Cool Pie Op", @@ -778,7 +903,12 @@ "time_in_mins": 10.0, "workstation": "Food Prep Table 2", }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Bake Op", + "time_in_mins": 50.0, + "workstation": "Oven Station", + }, { "batch_size": 1, "operation": "Cool Pie Op", @@ -800,7 +930,12 @@ "overproduction_percentage_for_work_order": 100, "items": [ {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - {"item_code": "Ambrosia Pie Filling", "qty": 20.0, "qty_consumed_per_unit": 4.0, "uom": "Cup"}, + { + "item_code": "Ambrosia Pie Filling", + "qty": 20.0, + "qty_consumed_per_unit": 4.0, + "uom": "Cup", + }, {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, ], "operations": [ @@ -810,7 +945,12 @@ "time_in_mins": 10.0, "workstation": "Food Prep Table 2", }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Bake Op", + "time_in_mins": 50.0, + "workstation": "Oven Station", + }, { "batch_size": 1, "operation": "Cool Pie Op", @@ -831,10 +971,25 @@ "uom": "Cup", "items": [ {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + { + "item_code": "Cornstarch", + "qty": 0.1, + "qty_consumed_per_unit": 0.005, + "uom": "Pound", + }, {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - {"item_code": "Bayberry", "qty": 15.0, "qty_consumed_per_unit": 0.05025, "uom": "Pound"}, + { + "item_code": "Butter", + "qty": 0.313, + "qty_consumed_per_unit": 0.01565, + "uom": "Pound", + }, + { + "item_code": "Bayberry", + "qty": 15.0, + "qty_consumed_per_unit": 0.05025, + "uom": "Pound", + }, ], "operations": [ { @@ -857,11 +1012,31 @@ "uom": "Cup", "items": [ {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + { + "item_code": "Cornstarch", + "qty": 0.1, + "qty_consumed_per_unit": 0.005, + "uom": "Pound", + }, {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - {"item_code": "Cocoplum", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, - {"item_code": "Damson Plum", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, + { + "item_code": "Butter", + "qty": 0.313, + "qty_consumed_per_unit": 0.01565, + "uom": "Pound", + }, + { + "item_code": "Cocoplum", + "qty": 7.5, + "qty_consumed_per_unit": 0.02515, + "uom": "Pound", + }, + { + "item_code": "Damson Plum", + "qty": 7.5, + "qty_consumed_per_unit": 0.02515, + "uom": "Pound", + }, ], "operations": [ { @@ -884,11 +1059,31 @@ "uom": "Cup", "items": [ {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + { + "item_code": "Cornstarch", + "qty": 0.1, + "qty_consumed_per_unit": 0.005, + "uom": "Pound", + }, {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - {"item_code": "Kaduka Lime", "qty": 10.0, "qty_consumed_per_unit": 0.0335, "uom": "Pound"}, - {"item_code": "Limequat", "qty": 5.0, "qty_consumed_per_unit": 0.01675, "uom": "Pound"}, + { + "item_code": "Butter", + "qty": 0.313, + "qty_consumed_per_unit": 0.01565, + "uom": "Pound", + }, + { + "item_code": "Kaduka Lime", + "qty": 10.0, + "qty_consumed_per_unit": 0.0335, + "uom": "Pound", + }, + { + "item_code": "Limequat", + "qty": 5.0, + "qty_consumed_per_unit": 0.01675, + "uom": "Pound", + }, ], "operations": [ { @@ -911,10 +1106,25 @@ "uom": "Cup", "items": [ {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + { + "item_code": "Cornstarch", + "qty": 0.1, + "qty_consumed_per_unit": 0.005, + "uom": "Pound", + }, {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - {"item_code": "Gooseberry", "qty": 15.0, "qty_consumed_per_unit": 0.05025, "uom": "Pound"}, + { + "item_code": "Butter", + "qty": 0.313, + "qty_consumed_per_unit": 0.01565, + "uom": "Pound", + }, + { + "item_code": "Gooseberry", + "qty": 15.0, + "qty_consumed_per_unit": 0.05025, + "uom": "Pound", + }, ], "operations": [ { @@ -937,8 +1147,18 @@ "uom": "Cup", "items": [ {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, + { + "item_code": "Cornstarch", + "qty": 0.1, + "qty_consumed_per_unit": 0.005, + "uom": "Pound", + }, + { + "item_code": "Butter", + "qty": 0.313, + "qty_consumed_per_unit": 0.01565, + "uom": "Pound", + }, { "item_code": "Hairless Rambutan", "qty": 5.0, @@ -946,7 +1166,12 @@ "uom": "Pound", }, {"item_code": "Tayberry", "qty": 2.5, "qty_consumed_per_unit": 0.0084, "uom": "Pound"}, - {"item_code": "Cloudberry", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, + { + "item_code": "Cloudberry", + "qty": 7.5, + "qty_consumed_per_unit": 0.02515, + "uom": "Pound", + }, {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, ], "operations": [ @@ -976,7 +1201,12 @@ {"item_code": "Butter", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Pound"}, # {"item_code": "Ice Water", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Cup"}, {"item_code": "Salt", "qty": 0.05, "qty_consumed_per_unit": 0.01, "uom": "Pound"}, - {"item_code": "Parchment Paper", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + { + "item_code": "Parchment Paper", + "qty": 5.0, + "qty_consumed_per_unit": 1.0, + "uom": "Nos", + }, {"item_code": "Pie Tin", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, ], "operations": [], # Subcontracted item -> operations done by supplier @@ -990,7 +1220,12 @@ {"item_code": "Butter", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Pound"}, {"item_code": "Ice Water", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Cup"}, {"item_code": "Salt", "qty": 0.05, "qty_consumed_per_unit": 0.01, "uom": "Pound"}, - {"item_code": "Parchment Paper", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + { + "item_code": "Parchment Paper", + "qty": 5.0, + "qty_consumed_per_unit": 1.0, + "uom": "Nos", + }, {"item_code": "Pie Tin", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, ], "operations": [ @@ -1178,3 +1413,81 @@ "Color": ["Yellow", "Red"], }, } + +planned_dates = { + "Ambrosia Pie": { + "planned_start_date": "01-01 07:17:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Double Plum Pie": { + "planned_start_date": "01-01 07:26:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Gooseberry Pie": { + "planned_start_date": "01-01 07:29:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Kaduka Key Lime Pie": { + "planned_start_date": "01-01 07:39:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Pocketful of Bay": { + "planned_start_date": "01-01 07:16:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Tower of Bay-bel": { + "planned_start_date": "01-01 07:30:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Pie Crust": { + "planned_start_date": "01-01 07:10:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Ambrosia Pie Filling": { + "planned_start_date": "01-01 07:16:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Kaduka Key Lime Pie Filling": { + "planned_start_date": "01-01 07:19:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Gooseberry Pie Filling": { + "planned_start_date": "01-01 07:18:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Bayberry Pocket": { + "planned_start_date": "01-01 07:25:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Bayberry Popper": { + "planned_start_date": "01-01 07:20:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Bayberry Pie": { + "planned_start_date": "01-01 07:11:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Bayberry Pie Filling": { + "planned_start_date": "01-01 07:22:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, + "Double Plum Pie Filling": { + "planned_start_date": "01-01 07:37:00", + "planned_end_date": "01-01", + "expected_delivery_date": "01-01", + }, +} diff --git a/inventory_tools/tests/setup.py b/inventory_tools/tests/setup.py index 6059abe5..9c574454 100644 --- a/inventory_tools/tests/setup.py +++ b/inventory_tools/tests/setup.py @@ -20,6 +20,8 @@ specifications, suppliers, workstations, + planned_dates, + shifts, ) @@ -89,6 +91,8 @@ def create_test_data(): frappe.db.set_single_value("Stock Settings", "default_warehouse", "") create_warehouses(settings) setup_manufacturing_settings(settings) + # create_shift_types is a pre-req for createworkstations + create_shift_types() create_workstations() create_operations() create_item_groups(settings) @@ -197,19 +201,46 @@ def setup_manufacturing_settings(settings): ) frappe.set_value("Inventory Tools Settings", settings.company, "create_purchase_orders", 0) frappe.set_value( - "Inventory Tools Settings", settings.company, "overproduction_percentage_for_work_order", 50 + "Inventory Tools Settings", + settings.company, + "overproduction_percentage_for_work_order", + 50, ) frappe.set_value("Inventory Tools Settings", settings.company, "show_on_website", 1) frappe.set_value("Inventory Tools Settings", settings.company, "show_in_listview", 1) +def create_shift_types(): + for shift in shifts: + if frappe.db.exists("Shift Type", shift["name"]): + continue + sh = frappe.new_doc("Shift Type") + sh.name = shift["name"] + sh.start_time = shift["start_time"] + sh.end_time = shift["end_time"] + sh.color = shift["color"] + sh.save() + + def create_workstations(): for ws in workstations: - if frappe.db.exists("Workstation", ws[0]): + if frappe.db.exists("Workstation", ws["workstation_name"]): continue work = frappe.new_doc("Workstation") - work.workstation_name = ws[0] - work.production_capacity = ws[1] + work.workstation_name = ws["workstation_name"] + work.production_capacity = ws["production_capacity"] + for shift_type in ws["shift_types"]: + for sh in shifts: + if sh["name"] == shift_type: + result = sh + work.append( + "working_hours", + { + "shift_type": shift_type, + "start_time": result["start_time"], + "end_time": result["end_time"], + }, + ) work.save() @@ -638,6 +669,12 @@ def create_production_plan(settings, prod_plan_from_doc): for wo in wos: wo = frappe.get_doc("Work Order", wo) wo.wip_warehouse = "Kitchen - APC" + current_year = str(settings.day.year) + wo.planned_start_date = f'{current_year}-{planned_dates[wo.item_name]["planned_start_date"]}' + wo.planned_end_date = f'{current_year}-{planned_dates[wo.item_name]["planned_end_date"]}' + wo.expected_delivery_date = ( + f'{current_year}-{planned_dates[wo.item_name]["expected_delivery_date"]}' + ) wo.save() wo.submit() job_cards = frappe.get_all("Job Card", {"work_order": wo.name}) @@ -731,7 +768,10 @@ def create_quotations(settings): "conversion_rate": 1, "transaction_date": nowdate(), "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "Ambrosia Pie", "qty": 1}, {"item_code": "Gooseberry Pie", "qty": 5}], + "items": [ + {"item_code": "Ambrosia Pie", "qty": 1}, + {"item_code": "Gooseberry Pie", "qty": 5}, + ], "company": settings.company, } quotation.update(values) @@ -748,7 +788,10 @@ def create_quotations(settings): "conversion_rate": 1, "transaction_date": nowdate(), "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "Ambrosia Pie", "qty": 1}, {"item_code": "Gooseberry Pie", "qty": 5}], + "items": [ + {"item_code": "Ambrosia Pie", "qty": 1}, + {"item_code": "Gooseberry Pie", "qty": 5}, + ], "company": settings.company, } quotation.update(values) @@ -765,7 +808,10 @@ def create_quotations(settings): "conversion_rate": 1, "transaction_date": nowdate(), "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "Ambrosia Pie", "qty": 2}, {"item_code": "Double Plum Pie", "qty": 1}], + "items": [ + {"item_code": "Ambrosia Pie", "qty": 2}, + {"item_code": "Double Plum Pie", "qty": 1}, + ], "company": settings.company, } quotation.update(values) @@ -782,7 +828,10 @@ def create_quotations(settings): "conversion_rate": 1, "transaction_date": nowdate(), "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "Ambrosia Pie", "qty": 5}, {"item_code": "Double Plum Pie", "qty": 10}], + "items": [ + {"item_code": "Ambrosia Pie", "qty": 5}, + {"item_code": "Double Plum Pie", "qty": 10}, + ], "company": "Chelsea Fruit Co", } quotation.update(values) From 375613d88590305f9d63c9750fb6ae8bc9acf52c Mon Sep 17 00:00:00 2001 From: Tyler Matteson Date: Fri, 13 Dec 2024 12:17:06 -0500 Subject: [PATCH 3/5] wip: optimizer UI --- .pre-commit-config.yaml | 2 +- .../page/optimizer/optimizer.js | 1 - inventory_tools/tests/fixtures.py | 78 ------------------- inventory_tools/tests/setup.py | 64 +++++++++------ 4 files changed, 43 insertions(+), 102 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d81f55b6..422e8d32 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,7 +50,7 @@ repos: - tomli - repo: https://github.com/agritheory/test_utils - rev: v0.16.1 + rev: v0.17.0 hooks: - id: update_pre_commit_config - id: mypy diff --git a/inventory_tools/inventory_tools/page/optimizer/optimizer.js b/inventory_tools/inventory_tools/page/optimizer/optimizer.js index 4fc4d7ba..1bc35888 100644 --- a/inventory_tools/inventory_tools/page/optimizer/optimizer.js +++ b/inventory_tools/inventory_tools/page/optimizer/optimizer.js @@ -117,7 +117,6 @@ class OptimizerView { frappe.set_route('Form', 'Work Order', task.id) }, }) - this.output_gantt = new Gantt(this.output, r, { view_mode: 'Quarter Day', on_click: task => { diff --git a/inventory_tools/tests/fixtures.py b/inventory_tools/tests/fixtures.py index 60defac4..9c901ab3 100644 --- a/inventory_tools/tests/fixtures.py +++ b/inventory_tools/tests/fixtures.py @@ -1413,81 +1413,3 @@ "Color": ["Yellow", "Red"], }, } - -planned_dates = { - "Ambrosia Pie": { - "planned_start_date": "01-01 07:17:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Double Plum Pie": { - "planned_start_date": "01-01 07:26:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Gooseberry Pie": { - "planned_start_date": "01-01 07:29:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Kaduka Key Lime Pie": { - "planned_start_date": "01-01 07:39:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Pocketful of Bay": { - "planned_start_date": "01-01 07:16:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Tower of Bay-bel": { - "planned_start_date": "01-01 07:30:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Pie Crust": { - "planned_start_date": "01-01 07:10:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Ambrosia Pie Filling": { - "planned_start_date": "01-01 07:16:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Kaduka Key Lime Pie Filling": { - "planned_start_date": "01-01 07:19:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Gooseberry Pie Filling": { - "planned_start_date": "01-01 07:18:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Bayberry Pocket": { - "planned_start_date": "01-01 07:25:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Bayberry Popper": { - "planned_start_date": "01-01 07:20:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Bayberry Pie": { - "planned_start_date": "01-01 07:11:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Bayberry Pie Filling": { - "planned_start_date": "01-01 07:22:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, - "Double Plum Pie Filling": { - "planned_start_date": "01-01 07:37:00", - "planned_end_date": "01-01", - "expected_delivery_date": "01-01", - }, -} diff --git a/inventory_tools/tests/setup.py b/inventory_tools/tests/setup.py index 9c574454..135e02d8 100644 --- a/inventory_tools/tests/setup.py +++ b/inventory_tools/tests/setup.py @@ -9,7 +9,7 @@ ) from erpnext.setup.utils import set_defaults_for_tests from frappe.desk.page.setup_wizard.setup_wizard import setup_complete -from frappe.utils.data import add_months, flt, getdate, nowdate, get_datetime +from frappe.utils.data import add_months, flt, getdate, nowdate from webshop.webshop.doctype.website_item.website_item import make_website_item from inventory_tools.tests.fixtures import ( @@ -20,7 +20,6 @@ specifications, suppliers, workstations, - planned_dates, shifts, ) @@ -91,7 +90,6 @@ def create_test_data(): frappe.db.set_single_value("Stock Settings", "default_warehouse", "") create_warehouses(settings) setup_manufacturing_settings(settings) - # create_shift_types is a pre-req for createworkstations create_shift_types() create_workstations() create_operations() @@ -625,22 +623,39 @@ def create_production_plan(settings, prod_plan_from_doc): }, ) pp.get_mr_items() - for item in pp.po_items: - item.planned_start_date = settings.day + + pp.po_items = sorted(pp.po_items, key=lambda x: x.get("item_code")) + + for idx, item in enumerate(pp.po_items): + item.planned_start_date = settings.day + datetime.timedelta(days=idx) + pp.get_sub_assembly_items() - for item in pp.sub_assembly_items: - item.schedule_date = settings.day + start_time = datetime.datetime(settings.day.year, settings.day.month, settings.day.day, 6, 0) + pp.append( + "sub_assembly_items", + { + "schedule_date": start_time, + "production_item": "Pie Crust", + "name": None, + "type_of_manufacturing": "In House", + "idx": 1, + "qty": 50, + "bom_no": "BOM-Pie Crust-001", + "bom_level": 1, + "supplier": None, + "for_warehouse": "Storeroom - APC", + }, + ) + for idx, item in enumerate( + sorted(pp.sub_assembly_items, key=lambda x: (-abs(x.bom_level), x.idx)), start=1 + ): + item.idx = idx + item.schedule_date = start_time if item.production_item == "Pie Crust": - idx = item.idx - item.type_of_manufacturing = "Subcontract" - item.supplier = "Credible Contract Baking" item.qty = 50 - pp.append("sub_assembly_items", pp.sub_assembly_items[idx - 1].as_dict()) - pp.sub_assembly_items[-1].name = None - pp.sub_assembly_items[-1].type_of_manufacturing = "In House" - pp.sub_assembly_items[-1].bom_no = "BOM-Pie Crust-001" - pp.sub_assembly_items[-1].supplier = None - pp.for_warehouse = "Storeroom - APC" + time = frappe.get_value("BOM Operation", {"parent": item.bom_no}, "SUM(time_in_mins) AS time") + if time: + start_time += datetime.timedelta(minutes=time + 2) raw_materials = get_items_for_material_requests( pp.as_dict(), warehouses=None, get_parent_warehouse_data=None ) @@ -670,15 +685,20 @@ def create_production_plan(settings, prod_plan_from_doc): wo = frappe.get_doc("Work Order", wo) wo.wip_warehouse = "Kitchen - APC" current_year = str(settings.day.year) - wo.planned_start_date = f'{current_year}-{planned_dates[wo.item_name]["planned_start_date"]}' - wo.planned_end_date = f'{current_year}-{planned_dates[wo.item_name]["planned_end_date"]}' - wo.expected_delivery_date = ( - f'{current_year}-{planned_dates[wo.item_name]["expected_delivery_date"]}' - ) wo.save() wo.submit() job_cards = frappe.get_all("Job Card", {"work_order": wo.name}) - start_time = get_datetime() + wo.planned_start_date = start_time + time = frappe.get_value("BOM Operation", {"parent": item.bom_no}, "SUM(time_in_mins) AS time") + if time: + wo.planned_end_date = wo.planned_start_date + datetime.timedelta(minutes=time + 2) + wo.required_items = sorted(wo.required_items, key=lambda x: x.get("item_code")) + for idx, w in enumerate(wo.required_items, start=1): + w.idx = idx + w.required_qty = flt(w.required_qty, 3) + wo.save() + wo.submit() + frappe.db.set_value("Work Order", wo.name, "creation", start_time) for job_card in job_cards: job_card = frappe.get_doc("Job Card", job_card) batch_size, total_operation_time = frappe.get_value( From e989f020be09748895e03aa0970901635b3792d9 Mon Sep 17 00:00:00 2001 From: coleandreoli Date: Mon, 27 Jan 2025 12:06:14 +0100 Subject: [PATCH 4/5] initial job shop integration --- inventory_tools/inventory_tools/page/optimizer/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/inventory_tools/inventory_tools/page/optimizer/__init__.py b/inventory_tools/inventory_tools/page/optimizer/__init__.py index 3268366c..0d402827 100644 --- a/inventory_tools/inventory_tools/page/optimizer/__init__.py +++ b/inventory_tools/inventory_tools/page/optimizer/__init__.py @@ -87,3 +87,10 @@ def get_work_order_dependencies(work_order=None, production_item=None): return frappe.db.sql( query, {"work_order": work_order, "production_item": production_item}, as_dict=True ) + + +def get_optimized_data(work_order_names=None, start_datetime=None): + from Job_Shop_Scheduling_Benchmark_Environments_and_Instances.frappe.frappe_parser import FrappeJobShop + op_schedule = FrappeJobShop(work_order_names) + op_schedule.solve_fjsp() + return op_schedule.get_optimizer_schedule(start_datetime) From 8783c9e1d55359b60c544a21827722ac4b787d92 Mon Sep 17 00:00:00 2001 From: Tyler Matteson Date: Mon, 3 Nov 2025 15:26:14 -0500 Subject: [PATCH 5/5] wip: pull changes --- inventory_tools/tests/fixtures.py | 34 +++++++++++++++---------------- inventory_tools/tests/setup.py | 2 +- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/inventory_tools/tests/fixtures.py b/inventory_tools/tests/fixtures.py index dc0879af..bec953c3 100644 --- a/inventory_tools/tests/fixtures.py +++ b/inventory_tools/tests/fixtures.py @@ -75,24 +75,6 @@ ), ] -# workstations = [ -# ("Mix Pie Crust Station", "20"), -# ("Roll Pie Crust Station", "20"), -# ("Make Pie Filling Station", "20"), -# ("Cooling Station", "100"), -# ("Box Pie Station", "100"), -# ("Baking Station", "20"), -# ("Assemble Pie Station", "20"), -# ("Mix Pie Filling Station", "20"), -# ("Packaging Station", "2"), -# ("Food Prep Table 2", "10"), -# ("Food Prep Table 1", "5"), -# ("Range Station", "20"), -# ("Cooling Racks Station", "80"), -# ("Refrigerator Station", "200"), -# ("Oven Station", "20"), -# ("Mixer Station", "10"), -# ] workstations = [ { @@ -101,6 +83,7 @@ "type": "Table", "off_status_image": "mixer.png", "on_status_image": "mixer.png", + "shift_types": ["Day Shift", "Evening Shift"], }, { "workstation_name": "Roll Pie Crust Station", @@ -108,6 +91,7 @@ "type": "Table", "off_status_image": "rolling.png", "on_status_image": "rolling.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Make Pie Filling Station", @@ -115,6 +99,7 @@ "type": "Table", "off_status_image": "table.png", "on_status_image": "table.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Cooling Station", @@ -122,6 +107,7 @@ "type": "Table", "off_status_image": "rack.png", "on_status_image": "rack.png", + "shift_types": ["Day Shift", "Evening Shift"], }, { "workstation_name": "Box Pie Station", @@ -129,6 +115,7 @@ "type": "Table", "off_status_image": "box.png", "on_status_image": "box.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Baking Station", @@ -136,6 +123,7 @@ "type": "Table", "off_status_image": "oven.png", "on_status_image": "oven.png", + "shift_types": ["Day Shift", "Evening Shift"], }, { "workstation_name": "Assemble Pie Station", @@ -143,6 +131,7 @@ "type": "Table", "off_status_image": "table.png", "on_status_image": "table.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Mix Pie Filling Station", @@ -150,6 +139,7 @@ "type": "Table", "off_status_image": "mixer.png", "on_status_image": "mixer.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Packaging Station", @@ -157,6 +147,7 @@ "type": "Table", "off_status_image": "box.png", "on_status_image": "box.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Food Prep Table 2", @@ -164,6 +155,7 @@ "type": "Table", "off_status_image": "table.png", "on_status_image": "table.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Food Prep Table 1", @@ -171,6 +163,7 @@ "type": "Table", "off_status_image": "table.png", "on_status_image": "table.png", + "shift_types": ["Day Shift", "Evening Shift"], }, { "workstation_name": "Range Station", @@ -178,6 +171,7 @@ "type": "Range", "off_status_image": "range.png", "on_status_image": "range.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Cooling Racks Station", @@ -185,6 +179,7 @@ "type": "Cooling", "off_status_image": "rack.png", "on_status_image": "rack.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Refrigerator Station", @@ -192,6 +187,7 @@ "type": "Refrigerator", "off_status_image": "fridge.png", "on_status_image": "fridge.png", + "shift_types": ["Day Shift"], }, { "workstation_name": "Oven Station", @@ -199,6 +195,7 @@ "type": "Oven", "off_status_image": "oven.png", "on_status_image": "oven.png", + "shift_types": ["Day Shift", "Evening Shift"], }, { "workstation_name": "Mixer Station", @@ -206,6 +203,7 @@ "type": "Mixer", "off_status_image": "mixer.png", "on_status_image": "mixer.png", + "shift_types": ["Day Shift"], }, ] diff --git a/inventory_tools/tests/setup.py b/inventory_tools/tests/setup.py index ba324c3e..9da51622 100644 --- a/inventory_tools/tests/setup.py +++ b/inventory_tools/tests/setup.py @@ -109,7 +109,7 @@ def create_test_data(): create_warehouse_locations() setup_manufacturing_settings(settings) create_shift_types() - create_workstations() + create_workstations(settings) create_operations() create_item_groups(settings) create_price_lists(settings)