diff --git a/frepple/__manifest__.py b/frepple/__manifest__.py index 74e0c44..6eadfda 100644 --- a/frepple/__manifest__.py +++ b/frepple/__manifest__.py @@ -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", diff --git a/frepple/controllers/inbound.py b/frepple/controllers/inbound.py index cb05c35..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) @@ -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"), ] ) @@ -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...) @@ -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'), @@ -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 diff --git a/frepple/controllers/outbound.py b/frepple/controllers/outbound.py index a3595e8..6d47a0f 100644 --- a/frepple/controllers/outbound.py +++ b/frepple/controllers/outbound.py @@ -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__) @@ -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 '\n' % quoteattr(json.dumps(users)) def export_calendar(self): @@ -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 = {} @@ -1012,6 +1008,7 @@ def export_items(self): "categ_id", "product_variant_ids", "route_ids", + "product_tag_ids", ] + ( [ @@ -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 '%s\n' % ( + yield '%s\n' % ( quoteattr(name), ( ("description=%s" % (quoteattr(description),)) @@ -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 "" + ), ( ( "" @@ -1405,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"])) @@ -1417,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, @@ -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), @@ -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) @@ -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 @@ -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, @@ -2397,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: @@ -2429,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), @@ -2470,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 '' % ( @@ -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] @@ -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: diff --git a/frepple/migrations/18.0.0.1/pre-migrate.py b/frepple/migrations/18.0.0.1/pre-migrate.py new file mode 100644 index 0000000..d905b58 --- /dev/null +++ b/frepple/migrations/18.0.0.1/pre-migrate.py @@ -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() + diff --git a/frepple/views/mrp_routing_workcenter_inherit.xml b/frepple/views/mrp_routing_workcenter_inherit.xml index e736e30..b74cdc4 100644 --- a/frepple/views/mrp_routing_workcenter_inherit.xml +++ b/frepple/views/mrp_routing_workcenter_inherit.xml @@ -32,7 +32,7 @@ - + diff --git a/frepple/views/mrp_secondary_workcenter.xml b/frepple/views/mrp_secondary_workcenter.xml index 9ed03f8..0d48cf0 100644 --- a/frepple/views/mrp_secondary_workcenter.xml +++ b/frepple/views/mrp_secondary_workcenter.xml @@ -4,14 +4,14 @@ mrp.secondary.workcenter.tree mrp.secondary.workcenter -
+ - +
@@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/frepple/views/mrp_skill.xml b/frepple/views/mrp_skill.xml index 5546442..6d32ffd 100644 --- a/frepple/views/mrp_skill.xml +++ b/frepple/views/mrp_skill.xml @@ -4,7 +4,7 @@ Skill mrp.skill ir.actions.act_window - tree,form + list,form @@ -26,10 +26,10 @@ MRP Skill List mrp.skill -
+ - +
- \ No newline at end of file + diff --git a/frepple/views/mrp_workcenter_inherit.xml b/frepple/views/mrp_workcenter_inherit.xml index 9c55e83..2d3a283 100644 --- a/frepple/views/mrp_workcenter_inherit.xml +++ b/frepple/views/mrp_workcenter_inherit.xml @@ -32,9 +32,9 @@ - + - \ No newline at end of file + diff --git a/frepple/views/mrp_workcenter_skill.xml b/frepple/views/mrp_workcenter_skill.xml index 54f1836..73945dd 100644 --- a/frepple/views/mrp_workcenter_skill.xml +++ b/frepple/views/mrp_workcenter_skill.xml @@ -4,7 +4,7 @@ Work Center Skill mrp.workcenter.skill ir.actions.act_window - tree,form + list,form @@ -26,11 +26,11 @@ MRP Work Center Skill List mrp.workcenter.skill -
+ - +
- \ No newline at end of file + diff --git a/frepple/views/mrp_workorder_secondary_workcenter.xml b/frepple/views/mrp_workorder_secondary_workcenter.xml index e0a9fd7..1460a56 100644 --- a/frepple/views/mrp_workorder_secondary_workcenter.xml +++ b/frepple/views/mrp_workorder_secondary_workcenter.xml @@ -5,11 +5,11 @@ mrp.workorder.secondary.workcenter.tree mrp.workorder.secondary.workcenter -
+ - +
@@ -18,7 +18,7 @@
- + @@ -27,4 +27,4 @@
- \ No newline at end of file + diff --git a/frepple/views/product_supplierinfo_inherit.xml b/frepple/views/product_supplierinfo_inherit.xml index c0b468b..1869ddc 100644 --- a/frepple/views/product_supplierinfo_inherit.xml +++ b/frepple/views/product_supplierinfo_inherit.xml @@ -1,7 +1,4 @@ - - - - + product.supplierinfo.inherit.tree.view @@ -14,5 +11,3 @@ - - \ No newline at end of file diff --git a/frepple/views/quote_views.xml b/frepple/views/quote_views.xml index 7ae0b3f..ec22d85 100644 --- a/frepple/views/quote_views.xml +++ b/frepple/views/quote_views.xml @@ -89,20 +89,20 @@ frepple_quote.post.view.tree frepple.quote -
+ - +
FrePPLe Quotes frepple.quote - tree,kanban,form + list,kanban,form

Make your first FrePPLe quote! @@ -113,4 +113,4 @@ - \ No newline at end of file + diff --git a/frepple/views/sale_views.xml b/frepple/views/sale_views.xml index 8d405ea..9dc2bd4 100644 --- a/frepple/views/sale_views.xml +++ b/frepple/views/sale_views.xml @@ -1,6 +1,5 @@ - sale.order.form.inherit.sale.product.configurator sale.order @@ -12,5 +11,4 @@ - - \ No newline at end of file +