diff --git a/frepple/controllers/outbound.py b/frepple/controllers/outbound.py index 5f409ca3..c119751c 100644 --- a/frepple/controllers/outbound.py +++ b/frepple/controllers/outbound.py @@ -489,6 +489,43 @@ def export_calendar(self): cal_tz[i["name"]] = i["tz"] cal_ids.add(i["id"]) + # Read the resource calendar association + calendar_resource = {} + for i in self.generator.getData( + "mrp.workcenter", + search=[("resource_calendar_id", "!=", False)], + fields=[ + "id", + "resource_calendar_id", + ], + ): + if i["resource_calendar_id"][0] not in calendar_resource: + calendar_resource[i["resource_calendar_id"][0]] = set() + calendar_resource[i["resource_calendar_id"][0]].add(i["id"]) + + # Read from the attendance/leaves which resource has specific entries + self.resources_with_specific_calendars = {} + for i in self.generator.getData( + "resource.calendar.attendance", + search=[("resource_id", "!=", False)], + fields=[ + "resource_id", + ], + ): + self.resources_with_specific_calendars[i["resource_id"][0]] = i[ + "resource_id" + ][1] + for i in self.generator.getData( + "resource.calendar.leaves", + search=[("resource_id", "!=", False), ("time_type", "=", "leave")], + fields=[ + "resource_id", + ], + ): + self.resources_with_specific_calendars[i["resource_id"][0]] = i[ + "resource_id" + ][1] + # Read the attendance for all calendars for i in self.generator.getData( "resource.calendar.attendance", @@ -501,13 +538,39 @@ def export_calendar(self): "hour_to", "calendar_id", "week_type", + "resource_id", ], ): if i["calendar_id"] and i["calendar_id"][0] in cal_ids: - if i["calendar_id"][1] not in calendars: - calendars[i["calendar_id"][1]] = [] - i["attendance"] = True - calendars[i["calendar_id"][1]].append(i) + if not i["resource_id"]: + if i["calendar_id"][1] not in calendars: + calendars[i["calendar_id"][1]] = [] + i["attendance"] = True + calendars[i["calendar_id"][1]].append(i) + + if calendar_resource.get(i["calendar_id"][0]): + for res in calendar_resource.get(i["calendar_id"][0]): + if i["resource_id"] and res != i["resource_id"][0]: + continue + if res in self.resources_with_specific_calendars: + if ( + "calendar for %s" + % (self.resources_with_specific_calendars[res],) + not in calendars + ): + calendars[ + "calendar for %s" + % (self.resources_with_specific_calendars[res],) + ] = [] + cal_tz[ + "calendar for %s" + % (self.resources_with_specific_calendars[res],) + ] = cal_tz[i["calendar_id"][1]] + i["attendance"] = True + calendars[ + "calendar for %s" + % (self.resources_with_specific_calendars[res],) + ].append(i) # Read the leaves for all calendars for i in self.generator.getData( @@ -517,13 +580,39 @@ def export_calendar(self): "date_from", "date_to", "calendar_id", + "resource_id", ], ): if i["calendar_id"] and i["calendar_id"][0] in cal_ids: - if i["calendar_id"][1] not in calendars: - calendars[i["calendar_id"][1]] = [] - i["attendance"] = False - calendars[i["calendar_id"][1]].append(i) + if not i["resource_id"]: + if i["calendar_id"][1] not in calendars: + calendars[i["calendar_id"][1]] = [] + i["attendance"] = False + calendars[i["calendar_id"][1]].append(i) + + if calendar_resource.get(i["calendar_id"][0]): + for res in calendar_resource.get(i["calendar_id"][0]): + if i["resource_id"] and res != i["resource_id"][0]: + continue + if res in self.resources_with_specific_calendars: + if ( + "calendar for %s" + % (self.resources_with_specific_calendars[res],) + not in calendars + ): + calendars[ + "calendar for %s" + % (self.resources_with_specific_calendars[res],) + ] = [] + cal_tz[ + "calendar for %s" + % (self.resources_with_specific_calendars[res],) + ] = cal_tz[i["calendar_id"][1]] + i["attendance"] = False + calendars[ + "calendar for %s" + % (self.resources_with_specific_calendars[res],) + ].append(i) # Iterate over the results: for i in calendars: @@ -556,7 +645,7 @@ def export_calendar(self): "1" if j["attendance"] else "0", (2 ** ((int(j["dayofweek"]) + 1) % 7)) if "dayofweek" in j - else (2 ** 7) - 1, + else (2**7) - 1, priority_attendance if j["attendance"] else priority_leave, # In odoo, monday = 0. In frePPLe, sunday = 0. ("PT%dM" % round(j["hour_from"] * 60)) @@ -589,7 +678,7 @@ def export_calendar(self): "1", (2 ** ((int(j["dayofweek"]) + 1) % 7)) if "dayofweek" in j - else (2 ** 7) - 1, + else (2**7) - 1, priority_attendance, # In odoo, monday = 0. In frePPLe, sunday = 0. ("PT%dM" % round(j["hour_from"] * 60)) @@ -688,17 +777,28 @@ def export_customers(self): """ self.map_customers = {} first = True + individual_inserted = False for i in self.generator.getData( "res.partner", - search=[("is_company", "=", True)], - fields=["name"], + search=[], + fields=["name", "parent_id", "is_company"], + order="id", ): if first: yield "\n" yield "\n" first = False - name = "%s %s" % (i["name"], i["id"]) - yield "\n" % quoteattr(name) + if i["is_company"]: + name = "%s %s" % (i["name"], i["id"]) + yield "\n" % quoteattr(name) + elif i["parent_id"] == False or i["id"] == i["parent_id"][0]: + name = "Individuals" + if not individual_inserted: + yield "\n" % quoteattr(name) + individual_inserted = True + else: + name = self.map_customers[i["parent_id"][0]] + self.map_customers[i["id"]] = name if not first: yield "\n" @@ -712,16 +812,12 @@ def export_suppliers(self): res.partner.id res.partner.name -> supplier.name """ first = True - for i in self.generator.getData( - "res.partner", - search=[("is_company", "=", True)], - fields=["name"], - ): + for i in self.map_customers: if first: yield "\n" yield "\n" first = False - yield "\n" % quoteattr("%d %s" % (i["id"], i["name"])) + yield "\n" % quoteattr(self.map_customers[i]) if not first: yield "\n" @@ -797,7 +893,14 @@ def export_workcenters(self): first = False name = i["name"] owner = i["owner"] - available = i["resource_calendar_id"] + available = ( + i["resource_calendar_id"] + if not self.resources_with_specific_calendars.get(i["id"]) + else ( + 0, + "calendar for %s" % (name,), + ) + ) self.map_workcenters[i["id"]] = name yield '%s%s\n' % ( quoteattr(name), @@ -841,6 +944,13 @@ def export_items(self): self.product_product = {} self.product_template_product = {} self.product_templates = {} + self.routes = { + i["id"]: i for i in self.generator.getData("stock.route", fields=["name"]) + } + self.route_mto = None + for k, v in self.routes.items(): + if v["name"] == "Replenish on Order (MTO)": + self.route_mto = k for i in self.generator.getData( "product.template", search=[("type", "not in", ("service", "consu"))], @@ -853,6 +963,7 @@ def export_items(self): "uom_id", "categ_id", "product_variant_ids", + "route_ids", ], ): self.product_templates[i["id"]] = i @@ -907,8 +1018,7 @@ def export_items(self): } self.product_product[i["id"]] = prod_obj 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 '\n' % ( + yield '\n' % ( quoteattr(name), quoteattr(tmpl["uom_id"][1]) if tmpl["uom_id"] else "", i["volume"] or 0, @@ -921,6 +1031,7 @@ def export_items(self): quoteattr(tmpl["categ_id"][1]) if tmpl["categ_id"] else '""', self.uom_categories[self.uom[tmpl["uom_id"][0]]["category"]], i["id"], + ' type="item_mto"' if self.route_mto in tmpl["route_ids"] else "" ) # Export suppliers for the item, if the item is allowed to be purchased if tmpl["purchase_ok"]: @@ -941,7 +1052,7 @@ def export_items(self): ) suppliers = {} for sup in results: - name = "%d %s" % (sup["partner_id"][0], sup["partner_id"][1]) + name = self.map_customers.get(sup["partner_id"][0]) if sup.get("is_subcontractor", False): if not hasattr(tmpl, "subcontractors"): tmpl["subcontractors"] = [] @@ -1443,24 +1554,29 @@ def export_boms(self): not in self.map_workcenters ): continue - secondary_workcenter_str += '%s' % ( - 1 - if not secondary_workcenter["duration"] - or step["time_cycle"] == 0 - else secondary_workcenter["duration"] - / step["time_cycle"], - quoteattr(secondary_workcenter["search_mode"]), - quoteattr( - self.map_workcenters[ - secondary_workcenter["workcenter_id"][0] - ] - ), - ( - "" - % quoteattr(secondary_workcenter["skill"][1]) + secondary_workcenter_str += ( + '%s' + % ( + 1 + if not secondary_workcenter["duration"] + or step["time_cycle"] == 0 + else secondary_workcenter["duration"] + / step["time_cycle"], + quoteattr(secondary_workcenter["search_mode"]), + quoteattr( + self.map_workcenters[ + secondary_workcenter["workcenter_id"][0] + ] + ), + ( + "" + % quoteattr( + secondary_workcenter["skill"][1] + ) + ) + if secondary_workcenter["skill"] + else "", ) - if secondary_workcenter["skill"] - else "", ) yield "" '\n' "\n" '%s%s\n' % ( @@ -1627,19 +1743,7 @@ def getReservedQuantity(stock_move_id): j = so[i["order_id"][0]] location = j["warehouse_id"][1] customer = self.map_customers.get(j["partner_id"][0], None) - if not customer: - # The customer may be an individual. - # We check whether his/her company is in the map. - for c in self.generator.getData( - "res.partner", - ids=[j["partner_id"][0]], - fields=["commercial_partner_id"], - ): - customer = self.map_customers.get( - c["commercial_partner_id"][0], None - ) - if customer: - break + if not customer or not location or not product: # Not interested in this sales order... continue @@ -1890,7 +1994,7 @@ def export_purchaseorders(self): qty, quoteattr(item["name"]), quoteattr(location), - quoteattr("%d %s" % (j["partner_id"][0], j["partner_id"][1])), + quoteattr(self.map_customers.get(j["partner_id"][0])), ) yield "\n" @@ -1968,7 +2072,7 @@ def export_purchaseorders(self): qty, quoteattr(item["name"]), quoteattr(location), - quoteattr("%d %s" % (j["partner_id"][0], j["partner_id"][1])), + quoteattr(self.map_customers.get(j["partner_id"][0])), ) yield "\n" @@ -2157,6 +2261,8 @@ def export_manufacturingorders(self): quoteattr(location), quoteattr(item["name"]), ) + # 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]] @@ -2179,10 +2285,16 @@ def export_manufacturingorders(self): self.product_product[mv["product_id"][0]]["template"], ) if qty_flow > 0: - yield '\n' % ( - -qty_flow / qty, - quoteattr(consumed_item["name"]), + operation_materials[ + consumed_item["name"] + ] = operation_materials.get(consumed_item["name"], 0) + ( + -qty_flow / qty ) + for key in operation_materials: + yield '\n' % ( + operation_materials[key], + quoteattr(key), + ) yield '\n' % ( quoteattr(item["name"]), ) @@ -2226,6 +2338,8 @@ def export_manufacturingorders(self): quoteattr(location), ) idx += 10 + # 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]] @@ -2261,9 +2375,13 @@ def export_manufacturingorders(self): mv["product_uom"], self.product_product[mv["product_id"][0]]["template"], ) + operation_materials[item["name"]] = operation_materials.get( + item["name"], 0 + ) + (-qty_flow / qty) + for key in operation_materials: yield '\n' % ( - -qty_flow / qty, - quoteattr(item["name"]), + operation_materials[key], + quoteattr(key), ) yield "" if (