diff --git a/frepple/controllers/inbound.py b/frepple/controllers/inbound.py index 305cb76..29be874 100644 --- a/frepple/controllers/inbound.py +++ b/frepple/controllers/inbound.py @@ -90,6 +90,7 @@ def run(self): stck_picking_type = self.env["stock.picking.type"].with_user( self.actual_user ) + bom_type = self.env["mrp.bom"].with_user(self.actual_user) stck_picking = self.env["stock.picking"].with_user(self.actual_user) stck_move = self.env["stock.move"].with_user(self.actual_user) stck_warehouse = self.env["stock.warehouse"].with_user(self.actual_user) @@ -706,6 +707,19 @@ def run(self): remark = "frePPLe - %s" % remark else: remark = "frePPLe" + bom_id = int(elem.get("operation").rsplit(" ", 1)[1]) + try: + bom = bom_type.search( + [ + ("id", "=", bom_id), + ], + limit=1, + ) + if not bom and bom.type == "phantom": + # Avoid creating MO on a) non-existing BOMs and b) phantom/kit BOMs + continue + except Exception: + pass mo = mfg_order.with_context(context).create( { "product_qty": elem.get("quantity"), @@ -715,9 +729,7 @@ def run(self): "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] - ), + "bom_id": bom_id, "qty_producing": 0.00, # TODO no place to store the criticality # elem.get('criticality'), diff --git a/frepple/controllers/outbound.py b/frepple/controllers/outbound.py index c965202..6d47a0f 100644 --- a/frepple/controllers/outbound.py +++ b/frepple/controllers/outbound.py @@ -1418,11 +1418,11 @@ def export_boms(self): quoteattr(location), ) else: - duration_per = (i["produce_delay"] or 0) + ( + duration = (i["produce_delay"] or 0) + ( i["days_to_prepare_mo"] or 0 ) - yield '\n' "\n" % ( + yield '\n' "\n" % ( quoteattr(operation), ( ("description=%s " % quoteattr(i["code"])) @@ -1430,8 +1430,8 @@ def export_boms(self): else "" ), ( - self.convert_float_time(duration_per) - if duration_per and duration_per > 0 + self.convert_float_time(duration) + if duration and duration > 0 else "P0D" ), self.manufacturing_lead, @@ -2370,6 +2370,7 @@ def export_manufacturingorders(self): ): # Filter out irrelevant manufacturing orders location = self.map_locations.get(i.location_dest_id.id, None) + operation = i.name if not location and i.picking_type_id: # For subcontracting MO we find the warehouse on the operation type operation_type = self.operation_types.get(i.picking_type_id.id, None) @@ -2378,7 +2379,7 @@ def export_manufacturingorders(self): if location: code = self.subcontracting_mo_po_mapping.get(i.id, None) if code: - i.name = code + operation = code item = self.product_product.get(i.product_id.id, None) if not item or not location: continue @@ -2388,14 +2389,16 @@ def export_manufacturingorders(self): # materials. # To reflect this flexibility we need a frepple operation specific # to each manufacturing order. - operation = i.name try: startdate = self.formatDateTime( i.date_start if i.date_start else i.date_planned_start ) - # enddate = self.formatDateTime(i.date_planned_finished) except Exception: continue + try: + enddate = self.formatDateTime(i.date_finished) + except Exception: + enddate = None qty = self.convert_qty_uom( i.qty_producing if i.qty_producing else i.product_qty, i.product_uom_id.id, @@ -2415,31 +2418,24 @@ def export_manufacturingorders(self): batch = mto_mo[0].display_name if mto_mo else i.name # Create a record for the MO - # Option 1: compute MO end date based on the start date - yield '\n' % ( + yield '\n' % ( quoteattr(i.name), quoteattr(batch), - startdate, + ( + "start" # Option 1: compute MO end date based on the start date + if self.manage_work_orders or not enddate + else "end" # Option 2: compute MO start date based on the end date + ), + (startdate if self.manage_work_orders or not enddate else enddate), qty, - "approved", # In the "approved" status, frepple can still reschedule the MO in function of material and capacity - # "confirmed", # In the "confirmed" status, frepple sees the MO as frozen and unchangeable - # "approved" if i["status"] == "confirmed" else "confirmed", # In-progress can't be rescheduled in frepple, but confirmed MOs + # In the "approved" status, frepple can still reschedule the MO in function of material and capacity + # In the "confirmed" status, frepple sees the MO as frozen and unchangeable + ( + "approved" + if self.manage_work_orders or i.state in ("confirmed", "draft") + else "confirmed" + ), ) - # Option 2: compute MO start date based on the end date - # yield '\n' % ( - # quoteattr(i["name"]), - # enddate, - # qty, - # # "approved", # In the "approved" status, frepple can still reschedule the MO in function of material and capacity - # "confirmed", # In the "confirmed" status, frepple sees the MO as frozen and unchangeable - # quoteattr(operation), - # ) - - # Collect work order info - if self.manage_work_orders: - wo_list = i.workorder_ids - else: - wo_list = [] # Collect move info if i.move_raw_ids: @@ -2447,8 +2443,8 @@ def export_manufacturingorders(self): else: mv_list = [] - if not wo_list: - # There are no workorders on the manufacturing order + if not self.manage_work_orders or not getattr(i, "workorder_ids", None): + # There are no workorders on the manufacturing order (or we don't want to see them in frepple) yield '' % ( quoteattr(operation), quoteattr(location), @@ -2488,7 +2484,37 @@ def export_manufacturingorders(self): yield '\n' % ( quoteattr(item["name"]), ) - yield "" + yield "" + # Pick up work center loading of all work orders + loads = {} + for wo in getattr(i, "workorder_ids", []): + # Get remaining duration of the WO + time_left = wo.duration_expected - wo.duration_unit + if wo.is_user_working and wo.time_ids: + # The WO is currently being worked on + for tm in wo.time_ids: + if tm.date_start and not tm.date_end: + time_left -= round( + (now - tm.date_start).total_seconds() / 60 + ) + if ( + time_left > 0 + and wo.workcenter_id.id in self.map_workcenters + and wo.state not in ("done", "cancel") + ): + loads[self.map_workcenters[wo.workcenter_id.id]] = ( + loads.get(self.map_workcenters[wo.workcenter_id.id], 0) + + time_left + ) + if loads: + yield "" + for r, q in loads.items(): + yield '' % ( + q, + quoteattr(r), + ) + yield "" + yield "" else: # Define an operation for the MO yield '' % ( @@ -2499,7 +2525,7 @@ def export_manufacturingorders(self): # Define operations for each WO idx = 10 first_wo = True - for wo in wo_list: + for wo in i.workorder_ids: suboperation = wo.display_name if len(suboperation) > 300: suboperation = suboperation[0:300] @@ -2624,7 +2650,7 @@ def export_manufacturingorders(self): # Create operationplans for each WO, starting with the last one idx = 0 - for wo in reversed(wo_list): + for wo in reversed(i.workorder_ids): idx += 1.0 suboperation = wo.display_name if len(suboperation) > 300: