From d272e9f6e9753a0a2de3b6dc163f598dea0404b5 Mon Sep 17 00:00:00 2001 From: Hicham Lahlou Date: Thu, 15 Feb 2024 17:01:10 +0100 Subject: [PATCH 1/2] handle incremental MO/WO export --- frepple/controllers/inbound.py | 144 ++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 40 deletions(-) diff --git a/frepple/controllers/inbound.py b/frepple/controllers/inbound.py index ed2d0f0d..dbcf0c55 100644 --- a/frepple/controllers/inbound.py +++ b/frepple/controllers/inbound.py @@ -141,7 +141,18 @@ def run(self): mo_references = {} wo_data = [] + # Workcenters of a workorder to update + resources = [] + for event, elem in iterparse(self.datafile, events=("start", "end")): + if ( + elem.tag == "operationplan" + and elem.get("ordertype") == "WO" + and event == "start" + ): + resources = [] + elif elem.tag == "resource" and event == "end": + resources.append(elem.get("id")) if event == "start" and elem.tag == "workorder" and elem.get("operation"): try: wo = { @@ -436,26 +447,55 @@ def run(self): # Can't filter on the computed display_name field in the search... continue if wo: - wo.write( - { - "date_planned_start": self.timezone.localize( - datetime.strptime( - elem.get("start"), - "%Y-%m-%d %H:%M:%S", - ) + data = { + "date_planned_start": self.timezone.localize( + datetime.strptime( + elem.get("start"), + "%Y-%m-%d %H:%M:%S", + ) + ) + .astimezone(UTC) + .replace(tzinfo=None), + "date_planned_finished": self.timezone.localize( + datetime.strptime( + elem.get("end"), + "%Y-%m-%d %H:%M:%S", ) - .astimezone(UTC) - .replace(tzinfo=None), - "date_planned_finished": self.timezone.localize( - datetime.strptime( - elem.get("end"), - "%Y-%m-%d %H:%M:%S", + ) + .astimezone(UTC) + .replace(tzinfo=None), + } + for res_id in resources: + res = mfg_workcenter.search( + [("id", "=", res_id)] + ) + if not res: + continue + if ( + not wo.operation_id # No operation defined + or ( + wo.operation_id.workcenter_id + == res # Same workcenter + or ( + # New member of a pool + wo.operation_id.workcenter_id + and wo.operation_id.workcenter_id + == res.owner ) ) - .astimezone(UTC) - .replace(tzinfo=None), - } - ) + ): + # Change primary work center + data["workcenter_id"] = res.id + else: + # Check assigned secondary resources + for sec in wo.secondary_workcenters: + if sec.workcenter_id.owner == res: + break + if sec.workcenter_id.owner == res.owner: + # Change secondary work center + sec.write({"workcenter_id": res.id}) + break + wo.write(data) break else: # Create manufacturing order @@ -487,32 +527,56 @@ def run(self): "ignore_secondary_workcenters": True, } ) + if (elem.get("status") or "proposed") == "proposed": + # MO creation + mo = mfg_order.with_context(context).create( + { + "product_qty": elem.get("quantity"), + "date_planned_start": elem.get("start"), + "date_planned_finished": elem.get("end"), + "product_id": int(item_id), + "company_id": self.company.id, + "product_uom_id": int(uom_id), + "picking_type_id": picking.id, + "bom_id": int( + elem.get("operation").rsplit(" ", 1)[1] + ), + "qty_producing": 0.00, + # TODO no place to store the criticality + # elem.get('criticality'), + "origin": "frePPLe", + } + ) + else: + # MO update + mo = mfg_order.with_context(context).search( + [("name", "=", elem.get("reference"))] + )[0] + if mo: + mo.write( + { + "product_qty": elem.get("quantity"), + "date_planned_start": elem.get("start"), + "date_planned_finished": elem.get("end"), + "product_id": int(item_id), + "company_id": self.company.id, + "product_uom_id": int(uom_id), + "picking_type_id": picking.id, + "origin": "frePPLe", + } + ) + mo_references[elem.get("reference")] = mo - mo = mfg_order.with_context(context).create( - { - "product_qty": elem.get("quantity"), - "date_planned_start": elem.get("start"), - "date_planned_finished": elem.get("end"), - "product_id": int(item_id), - "company_id": self.company.id, - "product_uom_id": int(uom_id), - "picking_type_id": picking.id, - "bom_id": int(elem.get("operation").rsplit(" ", 1)[1]), - "qty_producing": 0.00, - # TODO no place to store the criticality - # elem.get('criticality'), - "origin": "frePPLe", - } - ) # Remember odoo name for the MO reference passed by frepple. # This mapping is later used when importing WO. - mo_references[elem.get("reference")] = mo - mo._onchange_workorder_ids() - mo._onchange_move_raw() - mo._create_update_move_finished() - # mo.action_confirm() # confirm MO - # mo._plan_workorders() # plan MO - # mo.action_assign() # reserve material + if mo: + mo_references[elem.get("reference")] = mo + mo._onchange_workorder_ids() + mo._onchange_move_raw() + mo._create_update_move_finished() + # mo.action_confirm() # confirm MO + # mo._plan_workorders() # plan MO + # mo.action_assign() # reserve material # Process the workorder information we received if wo_data: From 53020fd2db6c2a0dd04d071766f61d6866da7963 Mon Sep 17 00:00:00 2001 From: Hicham Lahlou Date: Thu, 15 Feb 2024 21:38:02 +0100 Subject: [PATCH 2/2] Handle change of product qty for an approved/confirmed MO --- frepple/controllers/inbound.py | 36 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/frepple/controllers/inbound.py b/frepple/controllers/inbound.py index dbcf0c55..4f212f9f 100644 --- a/frepple/controllers/inbound.py +++ b/frepple/controllers/inbound.py @@ -89,6 +89,9 @@ def run(self): stck_move = self.env["stock.move"].with_user(self.actual_user) stck_warehouse = self.env["stock.warehouse"].with_user(self.actual_user) stck_location = self.env["stock.location"].with_user(self.actual_user) + change_product_qty = self.env["change.production.qty"].with_user( + self.actual_user + ) else: proc_order = self.env["purchase.order"] proc_orderline = self.env["purchase.order.line"] @@ -101,6 +104,7 @@ def run(self): stck_move = self.env["stock.move"] stck_warehouse = self.env["stock.warehouse"] stck_location = self.env["stock.location"] + change_product_qty = self.env["change.production.qty"] if self.mode == 1: # Cancel previous draft purchase quotations m = self.env["purchase.order"] @@ -547,21 +551,30 @@ def run(self): "origin": "frePPLe", } ) + mo_references[elem.get("reference")] = mo + mo._onchange_product_qty() + mo._onchange_workorder_ids() + mo._onchange_move_raw() + mo._create_update_move_finished() else: # MO update mo = mfg_order.with_context(context).search( [("name", "=", elem.get("reference"))] )[0] if mo: + new_qty = float(elem.get("quantity")) + if mo.product_qty != new_qty: + cpq = change_product_qty.create( + { + "mo_id": mo.id, + "product_qty": new_qty, + } + ) + cpq.change_prod_qty() mo.write( { - "product_qty": elem.get("quantity"), "date_planned_start": elem.get("start"), "date_planned_finished": elem.get("end"), - "product_id": int(item_id), - "company_id": self.company.id, - "product_uom_id": int(uom_id), - "picking_type_id": picking.id, "origin": "frePPLe", } ) @@ -569,14 +582,11 @@ def run(self): # Remember odoo name for the MO reference passed by frepple. # This mapping is later used when importing WO. - if mo: - mo_references[elem.get("reference")] = mo - mo._onchange_workorder_ids() - mo._onchange_move_raw() - mo._create_update_move_finished() - # mo.action_confirm() # confirm MO - # mo._plan_workorders() # plan MO - # mo.action_assign() # reserve material + # if mo: + + # mo.action_confirm() # confirm MO + # mo._plan_workorders() # plan MO + # mo.action_assign() # reserve material # Process the workorder information we received if wo_data: