open_workshop/models/ows_models.py
gitea 5498b20ea5
All checks were successful
odoo-restore-open_workshop-install / run-odoo-backup-in-docker (push) Successful in 4m40s
fixed inverse unrelated fiels deletion, added tests
2025-04-23 17:43:31 +00:00

413 lines
17 KiB
Python

# -*- 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")
# Alte Felder (weiterhin sichtbar für externe Programme)
birthday = fields.Date(
string="Geburtstag",
compute='_compute_ows_user_fields',
inverse='_inverse_birthday',
store=False
)
rfid_card = fields.Text(
string="RFID Card ID",
compute='_compute_ows_user_fields',
inverse='_inverse_rfid_card',
store=False
)
security_briefing = fields.Boolean(
string="Haftungsausschluss",
compute='_compute_ows_user_fields',
inverse='_inverse_security_briefing',
store=False
)
security_id = fields.Text(
string="Haftungsausschluss ID",
compute='_compute_ows_user_fields',
inverse='_inverse_security_id',
store=False
)
# Neue direkte vvow_* Felder
vvow_birthday = fields.Date(string="Geburtstag (vvow)")
vvow_rfid_card = fields.Text(string="RFID Card ID (vvow)")
vvow_security_briefing = fields.Boolean(string="Haftungsausschluss (vvow)")
vvow_security_id = fields.Text(string="Haftungsausschluss ID (vvow)")
@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_birthday(self):
for partner in self:
user = partner.ows_user_id[0] if partner.ows_user_id else False
if user:
user.birthday = partner.birthday
def _inverse_rfid_card(self):
for partner in self:
user = partner.ows_user_id[0] if partner.ows_user_id else False
if user:
user.rfid_card = partner.rfid_card
def _inverse_security_briefing(self):
for partner in self:
user = partner.ows_user_id[0] if partner.ows_user_id else False
if user:
user.security_briefing = partner.security_briefing
def _inverse_security_id(self):
for partner in self:
user = partner.ows_user_id[0] if partner.ows_user_id else False
if user:
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') or vals.get('vvow_birthday'),
'rfid_card': vals.get('rfid_card') or vals.get('vvow_rfid_card'),
'security_briefing': vals.get('security_briefing') or vals.get('vvow_security_briefing'),
'security_id': vals.get('security_id') or vals.get('vvow_security_id'),
})
return partners
def write(self, vals):
res = super().write(vals)
for partner in self:
user = partner.ows_user_id[0] if partner.ows_user_id else False
if not user:
continue
# Synchronisation alt -> user
if 'birthday' in vals:
user.birthday = vals['birthday']
if 'rfid_card' in vals:
user.rfid_card = vals['rfid_card']
if 'security_briefing' in vals:
user.security_briefing = vals['security_briefing']
if 'security_id' in vals:
user.security_id = vals['security_id']
# Synchronisation vvow_* -> user + alt
if 'vvow_birthday' in vals:
user.birthday = vals['vvow_birthday']
partner.birthday = vals['vvow_birthday']
if 'vvow_rfid_card' in vals:
user.rfid_card = vals['vvow_rfid_card']
partner.rfid_card = vals['vvow_rfid_card']
if 'vvow_security_briefing' in vals:
user.security_briefing = vals['vvow_security_briefing']
partner.security_briefing = vals['vvow_security_briefing']
if 'vvow_security_id' in vals:
user.security_id = vals['vvow_security_id']
partner.security_id = vals['vvow_security_id']
return res
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 = "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem;'>"
for area in areas:
html += f"<div class='o_group' style='margin-bottom: 1em;'>"
html += f"<table class='o_group o_inner_group'>"
html += f"<thead><tr><th>{area.name}</th><th></th><th>Datum</th><th>Gültig bis</th></tr></thead><tbody>"
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 = "<span class='fa fa-check text-success'></span>" if access else "<span class='fa fa-times text-danger'></span>"
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"""
<tr>
<td class='o_td_label'><label>{machine.name}</label></td>
<td class='o_td_field'>{icon}</td>
<td class='o_td_field'>{date_granted}</td>
<td class='o_td_field'>{date_expiry}</td>
</tr>
"""
html += "</tbody></table></div>"
html += "</div>"
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')