From 31b4f7e7a289170d853d6b0931c67b6a2f352d2d Mon Sep 17 00:00:00 2001 From: gitea Date: Tue, 6 May 2025 17:19:57 +0000 Subject: [PATCH 1/4] [FIX] open_workshop: Mehrere Maschinenfreigaben pro Einweisungsprodukt im POS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim Kauf eines Einweisungsprodukts wurden bisher nur eine Maschinenfreigabe erstellt, selbst wenn das Produkt mehreren Maschinen zugeordnet war. Dieser Fix passt _process_order an, sodass alle zugehörigen Maschinen erfasst und ggf. neue Freigaben für den Kunden erstellt werden. + Nutzung von defaultdict zur besseren Produkt-Maschine-Zuordnung + Klares Logging zur Nachvollziehbarkeit + Verhindert doppelte Freigaben --- models/pos_order.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/models/pos_order.py b/models/pos_order.py index ddffff6..bfd7019 100644 --- a/models/pos_order.py +++ b/models/pos_order.py @@ -1,4 +1,5 @@ from odoo import models, fields, api +from collections import defaultdict #import debugpy import logging @@ -7,9 +8,9 @@ _logger = logging.getLogger(__name__) _logger.info("✅ pos_order.py geladen") # debugpy.listen(("0.0.0.0", 5678)) -print("✅ debugpy wartet auf Verbindung (Port 5678) ...") +# print("✅ debugpy wartet auf Verbindung (Port 5678) ...") # Optional: Starte erst, wenn VS Code verbunden ist -#debugpy.wait_for_client() +# debugpy.wait_for_client() class PosOrder(models.Model): _inherit = 'pos.order' @@ -19,10 +20,9 @@ class PosOrder(models.Model): pos_order = self.browse(pos_order_id) training_products = self.env['ows.machine.training'].search([]) - product_map = { - tp.training_id.product_tmpl_id.id: tp.machine_id.id - for tp in training_products - } + product_map = defaultdict(list) + for tp in training_products: + product_map[tp.training_id.product_tmpl_id.id].append(tp.machine_id.id) partner = pos_order.partner_id if not partner: @@ -31,15 +31,13 @@ class PosOrder(models.Model): for line in pos_order.lines: product_tmpl_id = line.product_id.product_tmpl_id.id - machine_id = product_map.get(product_tmpl_id) - - _logger.info("🔍 Prüfe Produkt %s → Maschine ID: %s", line.product_id.display_name, machine_id) - - if machine_id: + machine_ids = product_map.get(product_tmpl_id, []) + _logger.info("🔍 Prüfe Produkt %s → Maschinen IDs: %s", line.product_id.display_name, machine_ids) + for machine_id in machine_ids: already_exists = self.env['ows.machine.access'].search([ ('partner_id', '=', partner.id), ('machine_id', '=', machine_id) - ]) + ], limit=1) if not already_exists: self.env['ows.machine.access'].create({ 'partner_id': partner.id, From e9db046765971c8c215479a86acfde53eed3b696 Mon Sep 17 00:00:00 2001 From: gitea Date: Sun, 18 May 2025 08:15:08 +0000 Subject: [PATCH 2/4] fixed pos receipt print, machine-access-sidebar is now invisible --- static/src/css/pos.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/src/css/pos.css b/static/src/css/pos.css index 0748dde..08c2416 100644 --- a/static/src/css/pos.css +++ b/static/src/css/pos.css @@ -1606,6 +1606,9 @@ td { .order-selector { display: none !important; } + .machine-access-sidebar { + display: none !important; + } .blockUI { display: none !important; } From 2d806ae3333e1bc7f74932fcb54ecbfaea0908c8 Mon Sep 17 00:00:00 2001 From: "matthias.lotz" Date: Wed, 25 Jun 2025 21:41:03 +0200 Subject: [PATCH 3/4] update machine model --- models/ows_models.py | 106 ++++++++++++++++++++++++++++++++++++---- views/machine_views.xml | 42 ++++++++++------ 2 files changed, 124 insertions(+), 24 deletions(-) diff --git a/models/ows_models.py b/models/ows_models.py index 1bf2fec..7609f01 100644 --- a/models/ows_models.py +++ b/models/ows_models.py @@ -378,17 +378,56 @@ class OwsUser(models.Model): ('partner_unique', 'unique(partner_id)', 'Jeder Partner darf nur einen OWS-Datensatz haben.') ] +AVAILABLE_COLORS = [ + ('#000000', 'schwarz'), + ('#ff0000', 'Rot'), + ('#E91E63', 'Pink'), + ('#9C27B0', 'Lila'), + ('#3F51B5', 'Indigo'), + ('#0000ff', 'Blau'), + ('#008000', 'Grün'), + ('#ffff00', 'Gelb'), + ('#FF9800', 'Orange'), + ('#795548', 'Braun'), + ('#1f1f1f', 'Grau'), +] class OwsMachineArea(models.Model): _name = 'ows.machine.area' - _table = "ows_machine_area" + _table = 'ows_machine_area' _description = 'OWS: Maschinenbereich' _order = 'name' - name = fields.Char(required=True, translate=True) - #color = fields.Integer(string="Farbe") - color_hex = fields.Char(string="Farbe (Hex)", help="Hex-Farbcode wie #FF0000 für Rot") + name = fields.Char(string="Name", required=True, translate=True) + color_hex = fields.Selection( + selection=AVAILABLE_COLORS, + string="Farbe (Hex)", + required=True, + ) + + color_hex_value = fields.Char( + string="Farbcode", + compute='_compute_color_hex_value', + store=False + ) + + color_name = fields.Char( + string="Farbname", + compute='_compute_color_name', + store=False + ) + + @api.depends('color_hex') + def _compute_color_hex_value(self): + for rec in self: + rec.color_hex_value = rec.color_hex or '' + + @api.depends('color_hex') + def _compute_color_name(self): + label_dict = dict(AVAILABLE_COLORS) + for rec in self: + rec.color_name = label_dict.get(rec.color_hex, 'Unbekannt') class OwsMachine(models.Model): _name = 'ows.machine' @@ -397,13 +436,37 @@ class OwsMachine(models.Model): name = fields.Char(required=True, translate=True) code = fields.Char(required=True, help="Eindeutiger Kurzcode, z.B. 'lasercutter'") - description = fields.Text() - active = fields.Boolean(default=True) - area_id = fields.Many2one('ows.machine.area', string='Bereich') - product_ids = fields.One2many('ows.machine.product', 'machine_id', string="Nutzungsprodukte") - product_names = fields.Char(string="Nutzungsprodukte Liste", compute="_compute_product_using_names", store=False,) + category = fields.Selection([ + ('green', 'Kategorie 1: grün'), + ('yellow', 'Kategorie 2: gelb'), + ('red', 'Kategorie 3: rot'), + ], string="Sicherheitskategorie", required=True, default='red', help="Sicherheitsrelevante Maschinenkategorie:\n" + "- grün: keine Einweisungspflicht\n" + "- gelb: empfohlene Einweisung\n" + "- rot: Einweisung zwingend erforderlich") + + category_icon = fields.Char(string="Kategorie-Symbol", compute="_compute_category_icon", store=False) + + @api.depends('category') + def _compute_category_icon(self): + for rec in self: + icon_map = { + 'green': '🟢', + 'yellow': '🟡', + 'red': '🔴', + } + rec.category_icon = icon_map.get(rec.category, '⚪') + + description = fields.Text(string="Gerätebeschreibung", translate=True, help="Beschreibung der Maschine oder des Geräts.") + active = fields.Boolean(string="Aktive", default=True, help="Ist die Maschine oder das Gerät aktiv? Inaktive Maschinen werden nicht mehr in der POS-Ansicht angezeigt.") + area_id = fields.Many2one('ows.machine.area', string='Bereich', help="Bereich in der Werkstatt, in dem die Maschine oder das Gerät steht.") + product_ids = fields.One2many('ows.machine.product', 'machine_id', string="Nutzungsprodukte", help="Dies ist das zugehörige Produkt, falls die Maschine oder das Geräte eine zeitliche Nutzungsgebühr hat.") + product_names = fields.Char(string="Liste der Nutzungsprodukte", compute="_compute_product_using_names", store=False,) training_ids = fields.One2many('ows.machine.training', 'machine_id', string="Einweisungsprodukte") - training_names = fields.Char(string="Einweisungsprodukte Liste", compute="_compute_product_training_names", store=False,) + training_names = fields.Char(string="Liste der Einweisungsprodukte", compute="_compute_product_training_names", store=False,) + storage_location = fields.Char(string="Lagerort", help="Lagerort der Maschine oder des Geräts.") + purchase_price = fields.Float(string="Kaufpreis", help="Kaufpreis der Maschine oder des Geräts.") + purchase_date = fields.Date(string="Kaufdatum", help="Kaufdatum der Maschine oder des Geräts.") @api.depends('product_ids.product_id.name') def _compute_product_using_names(self): @@ -426,6 +489,29 @@ class OwsMachine(models.Model): @api.model def get_access_list_grouped(self, partner_id): + """ + Gibt eine gruppierte Liste von Maschinenzugängen für einen bestimmten Partner zurück. Diese Funktion wird in + Odoo POS Frontend verwendet um die Ansicht zu erzeugen auf Welche Maschinen der Partner Zugriff hat. + + Für einen gegebenen Partner (über die partner_id) werden alle Maschinenbereiche (areas) abgefragt. + Für jeden Bereich wird geprüft, auf welche Maschinen der Partner Zugriff hat. Das Ergebnis wird + als Liste von Bereichen mit jeweils zugehörigen Maschinen und Zugriffsstatus zurückgegeben. + + Zusätzlich werden sicherheitsrelevante Informationen des Partners (wie Sicherheitsunterweisung, + Sicherheits-ID, RFID-Karte und Geburtstag) aus dem zugehörigen ows_user ermittelt und mitgeliefert. + + Args: + partner_id (int): Die ID des Partners, für den die Zugriffsübersicht erstellt werden soll. + + Returns: + dict: Ein Dictionary mit folgenden Schlüsseln: + - 'access_by_area': Liste von Bereichen mit Maschinen und Zugriffsstatus. + - 'security_briefing': Sicherheitsunterweisung des Nutzers (bool oder False). + - 'security_id': Sicherheits-ID des Nutzers (str oder ''). + - 'rfid_card': RFID-Kartennummer des Nutzers (str oder ''). + - 'birthday': Geburtstag des Nutzers (str oder ''). + """ + areas = self.env['ows.machine.area'].search([], order="name") _logger.info("🔍 Maschinenbereiche: %s", areas.mapped('name')) _logger.info("🔍 Partner_id: %s", partner_id) diff --git a/views/machine_views.xml b/views/machine_views.xml index 0dd3ec6..a2b3fc9 100644 --- a/views/machine_views.xml +++ b/views/machine_views.xml @@ -6,30 +6,44 @@ ows.machine + + + + + - - ows.machine.form - ows.machine - -
- - - - - - - - + + ows.machine.form + ows.machine + + + + + + + + + + + + + + + + + + + @@ -43,7 +57,7 @@ - + From 6f8f788d9d2434703f2ecfaa89cc6cbcacae0060 Mon Sep 17 00:00:00 2001 From: "matthias.lotz" Date: Thu, 26 Jun 2025 17:34:13 +0200 Subject: [PATCH 4/4] merge with open_workshop 17.0 --- models/ows_models.py | 30 ++++++++++++++++--------- static/src/js/machine_access_sidebar.js | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/models/ows_models.py b/models/ows_models.py index 7609f01..325326a 100644 --- a/models/ows_models.py +++ b/models/ows_models.py @@ -511,13 +511,12 @@ class OwsMachine(models.Model): - 'rfid_card': RFID-Kartennummer des Nutzers (str oder ''). - 'birthday': Geburtstag des Nutzers (str oder ''). """ - + partner = self.env['res.partner'].browse(partner_id) areas = self.env['ows.machine.area'].search([], order="name") - _logger.info("🔍 Maschinenbereiche: %s", areas.mapped('name')) - _logger.info("🔍 Partner_id: %s", partner_id) - res = [] + + access_by_area = [] for area in areas: - machines = self.search([('area_id', '=', area.id)], order="name") + machines = self.search([('area_id', '=', area.id), ('category', '=', 'red')], order="name") machine_list = [] for machine in machines: has_access = bool(self.env['ows.machine.access'].search([ @@ -528,12 +527,21 @@ class OwsMachine(models.Model): 'name': machine.name, 'has_access': has_access, }) - res.append({ - 'area': area.name, - 'color_hex': area.color_hex or '#000000', - 'machines': machine_list - }) - return res + if machine_list: + access_by_area.append({ + 'area': area.name, + 'color_hex': area.color_hex or '#000000', + 'machines': machine_list + }) + + user = partner.ows_user_id[:1] + return { + 'access_by_area': access_by_area, + 'security_briefing': user.security_briefing if user else False, + 'security_id': user.security_id if user else '', + 'rfid_card': user.rfid_card if user else '', + 'birthday': user.birthday if user else '', + } class OwsMachineAccess(models.Model): diff --git a/static/src/js/machine_access_sidebar.js b/static/src/js/machine_access_sidebar.js index f94cb2b..2cf1dfa 100644 --- a/static/src/js/machine_access_sidebar.js +++ b/static/src/js/machine_access_sidebar.js @@ -53,7 +53,7 @@ odoo.define('open_workshop.machine_access_sidebar', function (require) { }).then(function (result) { partner.create_date = partner.create_date && partner.create_date.substring(0, 10); var html = QWeb.render('PartnerMachineAccessList', { - areas: result || [], + areas: result.access_by_area || [], partner: partner, }); self.$('.access-content').html(html);