Skip to content

Commit

Permalink
Merge pull request #436 from frePPLe/16.0
Browse files Browse the repository at this point in the history
Syncing from upstream frePPLe/odoo (16.0)
  • Loading branch information
bt-admin authored Apr 25, 2024
2 parents a6a84ca + cc7b9ec commit 4f49744
Showing 1 changed file with 81 additions and 116 deletions.
197 changes: 81 additions & 116 deletions frepple/controllers/outbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -2103,13 +2103,19 @@ def export_purchaseorders(self):
if not item:
continue

# MTO links
if (
self.route_mto
in self.product_templates[item["template"]]["route_ids"]
):
batch = mv.move_dest_ids.group_id.sale_id.name or ""
mto_so = mv.move_dest_ids.group_id.sale_id
batch = mto_so[0].name if mto_so else None
if not batch:
mto_mo = j._get_mrp_productions()
if mto_mo:
batch = mto_mo[0].display_name
else:
batch = ""
batch = None

# if PO status is done, we should ignore this receipt
if j.state == "done":
Expand Down Expand Up @@ -2161,13 +2167,21 @@ def export_purchaseorders(self):
i.product_uom,
self.product_product[i.product_id.id]["template"],
)

# MTO links
if (
self.route_mto
in self.product_templates[i.product_id.id]["route_ids"]
in self.product_templates[item["template"]]["route_ids"]
):
batch = i.move_dest_ids.group_id.sale_id.name or ""
mto_so = i.move_dest_ids.group_id.sale_id
batch = mto_so[0].name if mto_so else None
if not batch:
mto_mo = j._get_mrp_productions()
if mto_mo:
batch = mto_mo[0].display_name
else:
batch = ""
batch = None

yield '<operationplan reference=%s %sordertype="PO" start="%s" end="%s" quantity="%f" status="confirmed">' "<item name=%s/><location name=%s/><supplier name=%s/></operationplan>\n" % (
quoteattr("%s - %s" % (j.name, i.id)),
"batch=%s " % quoteattr(batch) if batch else "",
Expand Down Expand Up @@ -2202,43 +2216,20 @@ def export_manufacturingorders(self):
search=[("state", "in", ["progress", "confirmed", "to_close"])],
# Option 2: Also import draft manufacturing order from odoo (to avoid that frepple reproposes it another time)
# search=[("state", "in", ["draft", "progress", "confirmed", "to_close"])],
fields=(
[
"bom_id",
"date_start",
"date_planned_start",
"date_planned_finished",
"name",
"state",
"qty_producing",
"product_qty",
"product_uom_id",
"location_dest_id",
"product_id",
"move_raw_ids",
"picking_type_id",
]
+ ["workorder_ids"]
if self.manage_work_orders
else []
),
object=True,
):
# Filter out irrelevant manufacturing orders
location = self.map_locations.get(i["location_dest_id"][0], None)
if not location and i["picking_type_id"]:
location = self.map_locations.get(i.location_dest_id.id, None)
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"][0], None)
operation_type = self.operation_types.get(i.picking_type_id.id, None)
if operation_type:
location = operation_type["warehouse_id"]
if location:
code = self.subcontracting_mo_po_mapping.get(i["id"], None)
code = self.subcontracting_mo_po_mapping.get(i.id, None)
if code:
i["name"] = code
item = (
self.product_product[i["product_id"][0]]
if i["product_id"][0] in self.product_product
else None
)
i.name = code
item = self.product_product.get(i.product_id.id, None)
if not item or not location:
continue

Expand All @@ -2247,30 +2238,37 @@ def export_manufacturingorders(self):
# materials.
# To reflect this flexibility we need a frepple operation specific
# to each manufacturing order.
operation = i["name"]
operation = i.name
try:
startdate = self.formatDateTime(
i["date_start"] if i["date_start"] else i["date_planned_start"]
i.date_start if i.date_start else i.date_planned_start
)
# enddate = (
# i["date_planned_finished"]
# .astimezone(timezone(self.timezone))
# .strftime(self.timeformat)
# )
# enddate = self.formatDateTime(i.date_planned_finished)
except Exception:
continue
qty = self.convert_qty_uom(
i["qty_producing"] if i["qty_producing"] else i["product_qty"],
i["product_uom_id"],
self.product_product[i["product_id"][0]]["template"],
i.qty_producing if i.qty_producing else i.product_qty,
i.product_uom_id.id,
self.product_product[i.product_id.id]["template"],
)
if not qty:
continue

# Get MTO link
mto_so = (
i.procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id
)
if mto_so:
batch = mto_so[0].name
else:
mto_mo = i._get_sources()
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 '<operationplan type="MO" reference=%s start="%s" quantity="%s" status="%s">\n' % (
quoteattr(i["name"]),
yield '<operationplan type="MO" reference=%s batch=%s start="%s" quantity="%s" status="%s">\n' % (
quoteattr(i.name),
quoteattr(batch),
startdate,
qty,
"approved", # In the "approved" status, frepple can still reschedule the MO in function of material and capacity
Expand All @@ -2289,38 +2287,13 @@ def export_manufacturingorders(self):

# Collect work order info
if self.manage_work_orders:
wo_list = [
wo
for wo in self.generator.getData(
"mrp.workorder",
ids=i["workorder_ids"],
object=True,
order="id",
)
]
wo_list = i.workorder_ids
else:
wo_list = []

# Collect move info
if i["move_raw_ids"]:
mv_list = [
mv
for mv in self.generator.getData(
"stock.move",
ids=i["move_raw_ids"],
fields=[
"product_id",
"product_qty",
"product_uom",
"date",
"reference",
"workorder_id",
"operation_id",
"should_consume_qty",
"reserved_availability",
],
)
]
if i.move_raw_ids:
mv_list = i.move_raw_ids
else:
mv_list = []

Expand All @@ -2334,25 +2307,21 @@ def export_manufacturingorders(self):
# dictionary needed as BOM in Odoo might have multiple lines with the same product
operation_materials = {}
for mv in mv_list:
consumed_item = (
self.product_product[mv["product_id"][0]]
if mv["product_id"][0] in self.product_product
else None
)
consumed_item = self.product_product.get(mv.product_id.id, None)
if not consumed_item:
continue
qty_flow = self.convert_qty_uom(
max(
0,
mv["product_qty"]
mv.product_qty
- (
mv["reserved_availability"]
mv.reserved_availability
if self.respect_reservations
else 0
),
),
mv["product_uom"],
self.product_product[mv["product_id"][0]]["template"],
mv.product_uom.id,
consumed_item["template"],
)
if qty_flow > 0:
operation_materials[consumed_item["name"]] = (
Expand All @@ -2379,22 +2348,22 @@ def export_manufacturingorders(self):
idx = 10
first_wo = True
for wo in wo_list:
suboperation = wo["display_name"]
suboperation = wo.display_name
if len(suboperation) > 300:
suboperation = suboperation[0:300]

# Get remaining duration of the WO
time_left = wo["duration_expected"] - wo["duration_unit"]
if wo["is_user_working"] and wo["time_ids"]:
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"]:
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
(now - tm.date_start).total_seconds() / 60
)

yield '<suboperation><operation name=%s priority="%s" type="operation_fixed_time" duration="%s"><location name=%s/><flows>' % (
quoteattr("%s - %s" % (suboperation, wo["id"])),
quoteattr("%s - %s" % (suboperation, wo.id)),
idx,
self.convert_float_time(
max(time_left, 1), # Miniminum 1 minute remaining :-)
Expand All @@ -2406,11 +2375,7 @@ def export_manufacturingorders(self):
# dictionary needed as BOM in Odoo might have multiple lines with the same product
operation_materials = {}
for mv in mv_list:
item = (
self.product_product[mv["product_id"][0]]
if mv["product_id"][0] in self.product_product
else None
)
item = self.product_product.get(mv.product_id.id, None)
if not item:
continue

Expand All @@ -2421,24 +2386,24 @@ def export_manufacturingorders(self):
# In frePPLe we want to consume them in the *FIRST* work order
# instead. This is a much more correct & realistic representation
# from a planning point of view.
if mv["workorder_id"] and mv["operation_id"]:
if mv["workorder_id"][0] != wo["id"]:
if mv.workorder_id and mv.operation_id:
if mv.workorder_id.id != wo.id:
continue
elif not first_wo:
continue

qty_flow = self.convert_qty_uom(
max(
0,
mv["product_qty"]
mv.product_qty
- (
mv["reserved_availability"]
mv.reserved_availability
if self.respect_reservations
else 0
),
),
mv["product_uom"],
self.product_product[mv["product_id"][0]]["template"],
mv.product_uom.id,
item["template"],
)
operation_materials[item["name"]] = operation_materials.get(
item["name"], 0
Expand Down Expand Up @@ -2510,35 +2475,35 @@ def export_manufacturingorders(self):
idx = 0
for wo in reversed(wo_list):
idx += 1.0
suboperation = wo["display_name"]
suboperation = wo.display_name
if len(suboperation) > 300:
suboperation = suboperation[0:300]

# 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
if wo["state"] == "progress":
if wo.state == "progress":
state = "confirmed"
elif wo["state"] in ("done", "to_close", "cancel"):
elif wo.state in ("done", "to_close", "cancel"):
state = "completed"
else:
state = "approved"
try:
if wo["date_finished"]:
if wo.date_finished:
wo_date = ' end="%s"' % self.formatDateTime(
wo["date_finished"]
wo.date_finished
)
else:
if wo["is_user_working"]:
if wo.is_user_working:
dt = now
else:
dt = max(
(
wo["date_start"]
if wo["date_start"]
wo.date_start
if wo.date_start
else (
wo["date_planned_start"]
if wo["date_planned_start"]
else i["date_planned_start"]
wo.date_planned_start
if wo.date_planned_start
else i.date_planned_start
)
),
now,
Expand All @@ -2547,12 +2512,12 @@ def export_manufacturingorders(self):
except Exception:
wo_date = ""
yield '<operationplan type="MO" reference=%s%s quantity="%s" status="%s"><operation name=%s/><owner reference=%s/>' % (
quoteattr(wo["display_name"]),
quoteattr(wo.display_name),
wo_date,
qty,
state,
quoteattr("%s - %s" % (suboperation, wo["id"])),
quoteattr(i["name"]),
quoteattr("%s - %s" % (suboperation, wo.id)),
quoteattr(i.name),
)
if (
wo.operation_id
Expand Down

0 comments on commit 4f49744

Please sign in to comment.