Skip to content

Commit

Permalink
Merge branch '18.0' of https://github.com/frePPLe/odoo into 18.0
Browse files Browse the repository at this point in the history
  • Loading branch information
hichamlahlou committed Jan 17, 2025
2 parents 9cd218d + 4b8b20a commit 2a8d504
Show file tree
Hide file tree
Showing 13 changed files with 146 additions and 88 deletions.
2 changes: 1 addition & 1 deletion frepple/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "frepple",
"version": "18.0.0.0",
"version": "18.0.0.1",
"category": "Manufacturing",
"summary": "Advanced planning and scheduling",
"author": "frePPLe",
Expand Down
26 changes: 18 additions & 8 deletions frepple/controllers/inbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -161,7 +162,7 @@ def run(self):
"|",
("date_end", "=", False),
("date_end", ">=", datetime.now()),
("type_id.name", "=", "Blanket Order"),
("requisition_type", "=", "blanket_order"),
("state", "=", "ongoing"),
]
)
Expand Down Expand Up @@ -448,10 +449,10 @@ def run(self):
"=",
supplier_id,
),
]
],
limit=1,
):
po_line.order_id.requisition_id = i.requisition_id
break

# Then let odoo computes all the fields (taxes, name, description...)

Expand Down Expand Up @@ -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"),
Expand All @@ -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'),
Expand All @@ -729,8 +741,6 @@ def run(self):
mo_references[elem.get("reference")] = mo
mo._create_update_move_finished()
# mo.action_confirm() # confirm MO
# mo._plan_workorders() # plan MO
# mo.action_assign() # reserve material
create = True
else:
# MO update
Expand Down
140 changes: 92 additions & 48 deletions frepple/controllers/outbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,11 @@
import json
import logging
import pytz
import xmlrpc.client
from xml.sax.saxutils import quoteattr
from datetime import datetime, timedelta
from pytz import timezone
import ssl

try:
import odoo
except ImportError:
pass
import odoo

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -412,14 +407,9 @@ def export_users(self):
for usr in self.generator.getData(
"res.users",
ids=grp["users"],
fields=["name", "login"],
fields=["name", "login", "lang"],
):
users.append(
(
usr["name"],
usr["login"],
)
)
users.append((usr["name"], usr["login"], usr["lang"]))
yield '<stringproperty name="users" value=%s/>\n' % quoteattr(json.dumps(users))

def export_calendar(self):
Expand Down Expand Up @@ -989,6 +979,12 @@ def export_items(self):
supplierinfo.sequence -> itemsupplier.priority
"""

# Read the product tags
product_tags = {
i["id"]: i["name"]
for i in self.generator.getData("product.tag", fields=["name"])
}

# Read the product templates
self.product_product = {}
self.product_template_product = {}
Expand All @@ -1012,6 +1008,7 @@ def export_items(self):
"categ_id",
"product_variant_ids",
"route_ids",
"product_tag_ids",
]
+ (
[
Expand Down Expand Up @@ -1133,7 +1130,7 @@ def export_items(self):
self.product_template_product[i["product_tmpl_id"][0]] = prod_obj

# For make-to-order items the next line needs to XML snippet ' type="item_mto"'.
yield '<item name=%s %s uom=%s volume="%f" weight="%f" cost="%f" subcategory="%s,%s"%s%s>%s\n' % (
yield '<item name=%s %s uom=%s volume="%f" weight="%f" cost="%f" subcategory="%s,%s"%s%s%s>%s\n' % (
quoteattr(name),
(
("description=%s" % (quoteattr(description),))
Expand Down Expand Up @@ -1161,6 +1158,22 @@ def export_items(self):
and tmpl["expiration_time"] > 0
else ""
),
(
(
" category=%s"
% quoteattr(
", ".join(
[
product_tags[i]
for i in tmpl["product_tag_ids"]
if i in product_tags
]
)
)
)
if tmpl["product_tag_ids"]
else ""
),
(
(
"<owner name=%s/>"
Expand Down Expand Up @@ -1405,20 +1418,20 @@ 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 '<operation name=%s %ssize_multiple="1" duration_per="%s" posttime="P%dD" priority="%s" xsi:type="operation_time_per">\n' "<item name=%s/><location name=%s/>\n" % (
yield '<operation name=%s %ssize_multiple="1" duration="%s" posttime="P%dD" priority="%s" xsi:type="operation_fixed_time">\n' "<item name=%s/><location name=%s/>\n" % (
quoteattr(operation),
(
("description=%s " % quoteattr(i["code"]))
if i["code"]
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,
Expand Down Expand Up @@ -1987,7 +2000,12 @@ def getReservedQuantity(stock_move_id):
),
due,
priority,
qty - reserved_quantity if j["picking_policy"] == "one" and qty - reserved_quantity > 0 else 0.0,
(
qty - reserved_quantity
if j["picking_policy"] == "one"
and qty - reserved_quantity > 0
else 0.0
),
"open" if qty - reserved_quantity > 0 else "closed",
quoteattr(product["name"]),
quoteattr(customer),
Expand Down Expand Up @@ -2352,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)
Expand All @@ -2360,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
Expand All @@ -2370,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,
Expand All @@ -2397,40 +2418,33 @@ 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 '<operationplan type="MO" reference=%s batch=%s start="%s" quantity="%s" status="%s">\n' % (
yield '<operationplan type="MO" reference=%s batch=%s %s="%s" quantity="%s" status="%s">\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 '<operationplan type="MO" reference=%s end="%s" quantity="%s" status="%s"><operation name=%s/><flowplans>\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:
mv_list = i.move_raw_ids
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 '<operation name=%s xsi:type="operation_fixed_time" priority="0"><location name=%s/><item name=%s/><flows>' % (
quoteattr(operation),
quoteattr(location),
Expand Down Expand Up @@ -2470,7 +2484,37 @@ def export_manufacturingorders(self):
yield '<flow xsi:type="flow_end" quantity="1"><item name=%s/></flow>\n' % (
quoteattr(item["name"]),
)
yield "</flows></operation></operationplan>"
yield "</flows>"
# 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 "<loads>"
for r, q in loads.items():
yield '<load quantity_fixed="%s" quantity="0"><resource name=%s/></load>' % (
q,
quoteattr(r),
)
yield "</loads>"
yield "</operation></operationplan>"
else:
# Define an operation for the MO
yield '<operation name=%s xsi:type="operation_routing" priority="0"><item name=%s/><location name=%s/><suboperations>' % (
Expand All @@ -2481,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]
Expand Down Expand Up @@ -2606,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:
Expand Down
11 changes: 11 additions & 0 deletions frepple/migrations/18.0.0.1/pre-migrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import SUPERUSER_ID, api


def migrate(cr, version):
"""Update xml id of roles which have"""
if not version:
return
# Can not change form to list on existing database. So delete the form views before upgrading the module
env = api.Environment(cr, SUPERUSER_ID, {})
env["ir.ui.view"].search([('arch_fs', 'ilike', 'frepple/'), ('type', '=', 'form')]).unlink()

2 changes: 1 addition & 1 deletion frepple/views/mrp_routing_workcenter_inherit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<field name="arch" type="xml">
<notebook position="inside">
<page string="Secondary Workcenters" name="Secondary Workcenters">
<field name="secondary_workcenter" context="{'routing_workcenter_id_invisible': True, 'default_routing_workcenter_id': id, 'tree_view_ref': 'mrp.mrp_secondary_workcenter_tree_view'}"/>
<field name="secondary_workcenter" context="{'routing_workcenter_id_invisible': True, 'default_routing_workcenter_id': id, 'list_view_ref': 'frepple.mrp_secondary_workcenter_tree_view'}"/>
</page>
</notebook>
</field>
Expand Down
Loading

0 comments on commit 2a8d504

Please sign in to comment.