# -*- coding: utf-8 -*- # ows_models.py # Part of Odoo Open Workshop from odoo import models, fields, api from markupsafe import escape as html_escape import logging _logger = logging.getLogger(__name__) _logger.info("✅ ows_models.py geladen") class ResPartner(models.Model): _inherit = 'res.partner' _logger.info("✅ ows ResPartner geladen") ows_user_id = fields.One2many('ows.user', 'partner_id', string="OWS Benutzerdaten") # ✳️ Zugriff auf Felder aus ows.user per compute + inverse (statt related) birthday = fields.Date( string="Geburtstag", compute='_compute_ows_user_fields', inverse='_inverse_ows_user_fields', store=False ) rfid_card = fields.Text( string="RFID Card ID", compute='_compute_ows_user_fields', inverse='_inverse_ows_user_fields', store=False ) security_briefing = fields.Boolean( string="Haftungsausschluss", compute='_compute_ows_user_fields', inverse='_inverse_ows_user_fields', store=False ) security_id = fields.Text( string="Haftungsausschluss ID", compute='_compute_ows_user_fields', inverse='_inverse_ows_user_fields', store=False ) @api.depends('ows_user_id') def _compute_ows_user_fields(self): for partner in self: user = partner.ows_user_id[0] if partner.ows_user_id else False partner.birthday = user.birthday if user else False partner.rfid_card = user.rfid_card if user else False partner.security_briefing = user.security_briefing if user else False partner.security_id = user.security_id if user else False def _inverse_ows_user_fields(self): for partner in self: user = partner.ows_user_id[0] if partner.ows_user_id else False if user: user.birthday = partner.birthday user.rfid_card = partner.rfid_card user.security_briefing = partner.security_briefing user.security_id = partner.security_id @api.model_create_multi def create(self, vals_list): partners = super().create(vals_list) for vals, partner in zip(vals_list, partners): self.env['ows.user'].create({ 'partner_id': partner.id, 'birthday': vals.get('birthday'), 'rfid_card': vals.get('rfid_card'), 'security_briefing': vals.get('security_briefing'), 'security_id': vals.get('security_id'), }) return partners machine_access_ids = fields.One2many( 'ows.machine.access', 'partner_id', string='Maschinenfreigaben' ) machine_access_html = fields.Html( string="Maschinenfreigabe", compute="_compute_machine_access_html", sanitize=False ) @api.depends('machine_access_ids') def _compute_machine_access_html(self): areas = self.env['ows.machine.area'].search([], order="name") for partner in self: html = "
" for area in areas: html += f"
" html += f"" html += f"" machines = self.env['ows.machine'].search([('area_id', '=', area.id)], order="name") for machine in machines: access = self.env['ows.machine.access'].search([ ('partner_id', '=', partner.id), ('machine_id', '=', machine.id), ], limit=1) icon = "" if access else "" date_granted = access.date_granted.strftime('%Y-%m-%d') if access and access.date_granted else "" date_expiry = access.date_expiry.strftime('%Y-%m-%d') if access and access.date_expiry else "" html += f""" """ html += "
{area.name}DatumGültig bis
{icon} {date_granted} {date_expiry}
" html += "
" partner.machine_access_html = html @api.model def migrate_existing_partners(self): """ Erstellt für alle vorhandenen res.partner einen ows.user, wenn noch keiner existiert, und übernimmt alte vvow_* Felder. Führt am Ende automatisch einen commit durch. Verwendung in der odoo shell: env['res.partner'].migrate_existing_partners() env['ows.user'].search_count([]) env['ows.user'].search([]).mapped('partner_id.name') """ migrated = 0 skipped = 0 partners = self.env['res.partner'].search([]) for partner in partners: if partner.ows_user_id: skipped += 1 continue # Werte lesen (werden evtl. durch _inherit hinzugefügt) vals = { 'partner_id': partner.id, 'birthday': getattr(partner, 'vvow_birthday', False), 'rfid_card': getattr(partner, 'vvow_rfid_card', False), 'security_briefing': getattr(partner, 'vvow_security_briefing', False), 'security_id': getattr(partner, 'vvow_security_id', False), } self.env['ows.user'].create(vals) migrated += 1 _logger.info(f"Erzeuge ows.user für Partner {partner.id} ({partner.name}) mit Werten: {vals}") _logger.info(f"[OWS Migration] ✅ Migriert: {migrated}, ❌ Übersprungen: {skipped}") # 🔐 Commit am Ende self.env.cr.commit() return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': "Migration abgeschlossen", 'message': f"{migrated} Partner migriert, {skipped} übersprungen.", 'sticky': False, } } @api.model def migrate_machine_access_from_old_fields(self): """ Migriert alte vvow_* Boolean-Felder in strukturierte ows.machine.access Einträge. Das Freigabe-Datum wird aus dem Änderungsverlauf (mail.message.tracking_value_ids) extrahiert. """ mapping = [ ('vvow_holz_sander', 'bandschleifer'), ('vvow_holz_felder_bandsaw', 'bandsaege_holz'), ('vvow_holz_felder_jointer', 'dickenhobel'), ('vvow_holz_felder_mill', 'felder_fraese'), ('vvow_holz_felder_tablesaw', 'formatkreissaege'), ('vvow_holz_mill', 'tischfraese'), ('vvow_holz_lathe', 'drechselbank'), ('vvow_holz_domino', 'festool_domino'), ('vvow_holz_duoduebler', 'maffel_duo'), ('vvow_holz_lamello', 'lamello_zeta_p2'), ('vvow_fablab_laser_sabko', 'sabako_laser'), ('vvow_fablab_3dprint_delta', '3d_delta'), ('vvow_fablab_3dprint_prusa', 'prusa'), ('vvow_fablab_3dprint_prusa_mmu', 'prusa_mmu'), ('vvow_fablab_cnc_beamicon', 'cnc_beamicon'), ('vvow_metall_welding_mig', 'mig_mag'), ('vvow_metall_welding_wig', 'wig'), ('vvow_metall_welding_misc', 'schweissen_allgemein'), ('vvow_metall_lathe', 'drehbank'), ('vvow_metall_bandsaw', 'bandsaege_metall'), ('vvow_metall_mill_fp2', 'fraese'), ('vvow_metall_bende', 'abkantbank'), ('vvow_metall_chop_saw', 'kreissaege_metall'), ] MachineAccess = self.env['ows.machine.access'] MailMessage = self.env['mail.message'] TrackingValue = self.env['mail.tracking.value'] count_created = 0 for partner in self.search([]): for field_name, machine_code in mapping: if not getattr(partner, field_name, False): continue machine = self.env['ows.machine'].search([('code', '=', machine_code)], limit=1) if not machine: continue # Änderungsverlauf durchsuchen: Wann wurde das Feld auf True gesetzt? tracking = TrackingValue.search([ ('field', '=', field_name), ('field_type', '=', 'boolean'), ('new_value_integer', '=', 1), ('mail_message_id.model', '=', 'res.partner'), ('mail_message_id.res_id', '=', partner.id), ], order='id ASC', limit=1) date_granted = tracking.mail_message_id.date if tracking else fields.Datetime.now() if not MachineAccess.search([('partner_id', '=', partner.id), ('machine_id', '=', machine.id)]): MachineAccess.create({ 'partner_id': partner.id, 'machine_id': machine.id, 'date_granted': date_granted, }) count_created += 1 _logger.info(f"[OWS Migration] ✅ Maschinenfreigaben erstellt: {count_created}") self.env.cr.commit() class OwsUser(models.Model): _name = 'ows.user' _description = 'OWS: Benutzerdaten' _table = 'ows_user' _logger.info("✅ ows_user geladen") partner_id = fields.Many2one( 'res.partner', required=True, ondelete='cascade', string='Kontakt' ) birthday = fields.Date('Geburtstag') rfid_card = fields.Text('RFID Card ID') security_briefing = fields.Boolean('Haftungsausschluss', default=False) security_id = fields.Text('Haftungsausschluss ID') _sql_constraints = [ ('partner_unique', 'unique(partner_id)', 'Jeder Partner darf nur einen OWS-Datensatz haben.') ] class OwsMachineArea(models.Model): _name = '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") class OwsMachine(models.Model): _name = 'ows.machine' _table = 'ows_machine' _description = 'OWS: Maschine' 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,) 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,) @api.depends('product_ids.product_id.name') def _compute_product_using_names(self): for machine in self: names = machine.product_ids.mapped('product_id.name') machine.product_names = ", ".join(names) @api.depends('training_ids.training_id.name') def _compute_product_training_names(self): for machine in self: names = machine.training_ids.mapped('training_id.name') machine.training_names = ", ".join(names) _sql_constraints = [ ('code_unique', 'unique(code)', 'Maschinencode muss eindeutig sein.') ] def name_get(self): return [(rec.id, f"{rec.name} ({rec.code})") for rec in self] @api.model def get_access_list_grouped(self, 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 = [] for area in areas: machines = self.search([('area_id', '=', area.id)], order="name") machine_list = [] for machine in machines: has_access = bool(self.env['ows.machine.access'].search([ ('partner_id', '=', partner_id), ('machine_id', '=', machine.id), ], limit=1)) machine_list.append({ 'name': machine.name, 'has_access': has_access, }) res.append({ 'area': area.name, 'color_hex': area.color_hex or '#000000', 'machines': machine_list }) return res class OwsMachineAccess(models.Model): _name = 'ows.machine.access' _table = 'ows_machine_access' _description = 'OWS: Maschinenfreigabe' _order = 'partner_id, machine_id' partner_id = fields.Many2one('res.partner', required=True, ondelete='cascade') machine_id = fields.Many2one('ows.machine', required=True) date_granted = fields.Date(default=fields.Date.today) date_expiry = fields.Date(string="Ablaufdatum") granted_by_pos = fields.Boolean(default=True) _sql_constraints = [ ('partner_machine_unique', 'unique(partner_id, machine_id)', 'Der Kunde hat diese Freigabe bereits.') ] class OwsMachineProduct(models.Model): _name = 'ows.machine.product' _table = 'ows_machine_product' _description = 'OWS: Zurordnung Produkt der Nutzung zur die Maschine' product_id = fields.Many2one('product.product', required=True, domain=[('available_in_pos', '=', True)], ondelete='cascade') machine_id = fields.Many2one('ows.machine', required=True, ondelete='cascade') class OwsMachineTraining(models.Model): _name = 'ows.machine.training' _table = 'ows_machine_training' _description = 'OWS: Zurordnung Produkt der Einweisung zur die Maschine' training_id = fields.Many2one('product.product', required=True, domain=[('available_in_pos', '=', True)], ondelete='cascade') machine_id = fields.Many2one('ows.machine', required=True, ondelete='cascade')