Compare commits
13 Commits
18.0-targe
...
13.0_dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 1644f756cd | |||
| 6f8f788d9d | |||
| 2d806ae333 | |||
| e9db046765 | |||
| 31b4f7e7a2 | |||
| b40e2f7837 | |||
| ea6d809020 | |||
| 63337a28bd | |||
| a19be91685 | |||
| da4cd0ba5c | |||
| 76b1dc29f2 | |||
| 0b45c9df62 | |||
| 92696827d3 |
69
.gitea/ISSUE_TEMPLATE/odoo-test-feedback.yml
Normal file
69
.gitea/ISSUE_TEMPLATE/odoo-test-feedback.yml
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
name: POS Test Feedback
|
||||||
|
about: Rückmeldung zu einem Test des POS-Systems geben
|
||||||
|
title: "[Feedback] "
|
||||||
|
labels: [feedback, test]
|
||||||
|
assignees: []
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## 🧪 POS-Test: Feedbackformular
|
||||||
|
|
||||||
|
Bitte gib uns Rückmeldung zu den einzelnen Funktionen. Beschreibe ggf. Probleme oder Auffälligkeiten.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: sonstiges
|
||||||
|
attributes:
|
||||||
|
label: Sonstiges Feedback oder Fehler
|
||||||
|
description: Alles andere, was dir beim Test aufgefallen ist (z. B. Layout, Ladezeiten, Fehlermeldungen).
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: nutzerauswahl
|
||||||
|
attributes:
|
||||||
|
label: Nutzer auswählen
|
||||||
|
description: Funktioniert die Auswahl des Nutzers im POS wie erwartet?
|
||||||
|
placeholder: z.B. Nutzer nicht auffindbar, Anzeige langsam etc.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: haftung
|
||||||
|
attributes:
|
||||||
|
label: Haftungsausschluss prüfen
|
||||||
|
description: Wird der Haftungsausschluss korrekt angezeigt bzw. berücksichtigt?
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: maschinenfreigabe
|
||||||
|
attributes:
|
||||||
|
label: Maschinenfreigabe prüfen
|
||||||
|
description: Wird korrekt angezeigt, ob der Nutzer eine Einweisung für eine Maschine hat?
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: abrechnung
|
||||||
|
attributes:
|
||||||
|
label: Abrechnung (Bargeld / SumUp)
|
||||||
|
description: Funktioniert die Abrechnung für den Nutzer?
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: coupon
|
||||||
|
attributes:
|
||||||
|
label: Coupons (erstellen / einlösen)
|
||||||
|
description: Funktionieren Erstellen und Einlösen von Coupons korrekt?
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: nutzerdaten
|
||||||
|
attributes:
|
||||||
|
label: Nutzerdaten aktualisieren / Haftungsausschluss abwählen
|
||||||
|
description: Lassen sich Nutzerdaten wie RFID oder der Haftungsausschluss korrekt ändern?
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: einweisung
|
||||||
|
attributes:
|
||||||
|
label: Einweisung verkaufen / prüfen
|
||||||
|
description: Lässt sich eine Einweisung verkaufen und wird sie korrekt zugewiesen?
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: backend
|
||||||
|
attributes:
|
||||||
|
label: Backend-Funktionen
|
||||||
|
description: Können Maschinen und Einweisungen im Backend wie erwartet verwaltet werden?
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -8,6 +8,33 @@ import logging
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
_logger.info("✅ ows_models.py geladen")
|
_logger.info("✅ ows_models.py geladen")
|
||||||
|
|
||||||
|
class HREmployee(models.Model):
|
||||||
|
_inherit = 'hr.employee'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def anonymize_for_testsystem(self):
|
||||||
|
"""Benennt Admin-Angestellten um und archiviert alle anderen für das Testsystem."""
|
||||||
|
admin_user = self.env['res.users'].search([('name', '=', 'Administrator')], limit=1)
|
||||||
|
_logger.info(f"[OWS] Administrator-Benutzer gefunden: {admin_user.name} (ID: {admin_user.id})")
|
||||||
|
admin_employee = self.search([('user_id', '=', admin_user.id)], limit=1)
|
||||||
|
|
||||||
|
if admin_employee:
|
||||||
|
admin_employee.write({
|
||||||
|
'name': 'TESTSYSTEM',
|
||||||
|
'job_title': 'Testumgebung',
|
||||||
|
'work_email': False,
|
||||||
|
'work_phone': False,
|
||||||
|
})
|
||||||
|
_logger.info("[OWS] Admin-Angestellter wurde umbenannt.")
|
||||||
|
else:
|
||||||
|
_logger.warning("[OWS] Kein Angestellter für user_admin gefunden.")
|
||||||
|
|
||||||
|
# Alle anderen Angestellten archivieren
|
||||||
|
other_employees = self.search([('id', '!=', admin_employee.id)])
|
||||||
|
other_employees.write({'active': False})
|
||||||
|
_logger.info("[OWS] %d Angestellte archiviert.", len(other_employees))
|
||||||
|
|
||||||
|
|
||||||
class ResPartner(models.Model):
|
class ResPartner(models.Model):
|
||||||
_inherit = 'res.partner'
|
_inherit = 'res.partner'
|
||||||
_logger.info("✅ ows ResPartner geladen")
|
_logger.info("✅ ows ResPartner geladen")
|
||||||
|
|
@ -141,32 +168,52 @@ class ResPartner(models.Model):
|
||||||
def _compute_machine_access_html(self):
|
def _compute_machine_access_html(self):
|
||||||
areas = self.env['ows.machine.area'].search([], order="name")
|
areas = self.env['ows.machine.area'].search([], order="name")
|
||||||
for partner in self:
|
for partner in self:
|
||||||
html = "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem;'>"
|
html = "<div class='tab-content'><div class='tab-pane active' id='machine_access_tab'>"
|
||||||
|
html += "<div class='o_group'>"
|
||||||
|
|
||||||
for area in areas:
|
for area in areas:
|
||||||
html += f"<div class='o_group' style='margin-bottom: 1em;'>"
|
html += "<table class='o_group o_inner_group o_group_col_6'><tbody>"
|
||||||
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>"
|
# Bereichsüberschrift
|
||||||
|
html += f"""
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="width: 100%;">
|
||||||
|
<div class="o_horizontal_separator">{area.name}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="o_td_label"><label class="o_form_label"></label></td>
|
||||||
|
<td class="o_td_label"><label class="o_form_label">Datum</label></td>
|
||||||
|
<td class="o_td_label"><label class="o_form_label">Gültig bis</label></td>
|
||||||
|
</tr>
|
||||||
|
"""
|
||||||
|
|
||||||
machines = self.env['ows.machine'].search([('area_id', '=', area.id)], order="name")
|
machines = self.env['ows.machine'].search([('area_id', '=', area.id)], order="name")
|
||||||
|
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
access = self.env['ows.machine.access'].search([
|
access = self.env['ows.machine.access'].search([
|
||||||
('partner_id', '=', partner.id),
|
('partner_id', '=', partner.id),
|
||||||
('machine_id', '=', machine.id),
|
('machine_id', '=', machine.id),
|
||||||
], limit=1)
|
], limit=1)
|
||||||
icon = "<span class='fa fa-check text-success'></span>" if access else "<span class='fa fa-times text-danger'></span>"
|
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_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 ""
|
date_expiry = access.date_expiry.strftime('%Y-%m-%d') if access and access.date_expiry else "-"
|
||||||
|
|
||||||
html += f"""
|
html += f"""
|
||||||
<tr>
|
<tr>
|
||||||
<td class='o_td_label'><label>{machine.name}</label></td>
|
<td class="o_td_label"><label class="o_form_label">{icon} {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_granted}</td>
|
<td class="o_td_field">{date_expiry}</td>
|
||||||
<td class='o_td_field'>{date_expiry}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
"""
|
"""
|
||||||
html += "</tbody></table></div>"
|
|
||||||
html += "</div>"
|
html += "</tbody></table>"
|
||||||
|
|
||||||
|
html += "</div></div></div>"
|
||||||
partner.machine_access_html = html
|
partner.machine_access_html = html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def migrate_existing_partners(self):
|
def migrate_existing_partners(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -284,6 +331,31 @@ class ResPartner(models.Model):
|
||||||
_logger.info(f"[OWS Migration] ✅ Maschinenfreigaben erstellt: {count_created}")
|
_logger.info(f"[OWS Migration] ✅ Maschinenfreigaben erstellt: {count_created}")
|
||||||
self.env.cr.commit()
|
self.env.cr.commit()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def archive_partners_without_users(self):
|
||||||
|
"""
|
||||||
|
Archiviert alle Partner (res.partner), die keine Benutzer (res.users) sind.
|
||||||
|
"""
|
||||||
|
Partner = self.env['res.partner']
|
||||||
|
User = self.env['res.users']
|
||||||
|
|
||||||
|
# IDs aller Partner, die ein Benutzerkonto haben
|
||||||
|
user_partner_ids = User.search([]).mapped('partner_id').ids
|
||||||
|
|
||||||
|
# Alle Partner ohne Benutzerkonto
|
||||||
|
partners_to_archive = Partner.search([
|
||||||
|
('id', 'not in', user_partner_ids),
|
||||||
|
('active', '=', True),
|
||||||
|
])
|
||||||
|
|
||||||
|
count = len(partners_to_archive)
|
||||||
|
partners_to_archive.write({'active': False})
|
||||||
|
for p in partners_to_archive:
|
||||||
|
_logger.debug(f"[OWS] Archiviert Partner: {p.name} (ID {p.id})")
|
||||||
|
_logger.info(f"[OWS] Archiviert {count} Partner ohne Benutzerkonto.")
|
||||||
|
self.env.cr.commit()
|
||||||
|
|
||||||
|
|
||||||
class OwsUser(models.Model):
|
class OwsUser(models.Model):
|
||||||
_name = 'ows.user'
|
_name = 'ows.user'
|
||||||
_description = 'OWS: Benutzerdaten'
|
_description = 'OWS: Benutzerdaten'
|
||||||
|
|
@ -306,17 +378,56 @@ class OwsUser(models.Model):
|
||||||
('partner_unique', 'unique(partner_id)', 'Jeder Partner darf nur einen OWS-Datensatz haben.')
|
('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):
|
class OwsMachineArea(models.Model):
|
||||||
_name = 'ows.machine.area'
|
_name = 'ows.machine.area'
|
||||||
_table = "ows_machine_area"
|
_table = 'ows_machine_area'
|
||||||
_description = 'OWS: Maschinenbereich'
|
_description = 'OWS: Maschinenbereich'
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
|
|
||||||
name = fields.Char(required=True, translate=True)
|
name = fields.Char(string="Name", required=True, translate=True)
|
||||||
#color = fields.Integer(string="Farbe")
|
|
||||||
color_hex = fields.Char(string="Farbe (Hex)", help="Hex-Farbcode wie #FF0000 für Rot")
|
|
||||||
|
|
||||||
|
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):
|
class OwsMachine(models.Model):
|
||||||
_name = 'ows.machine'
|
_name = 'ows.machine'
|
||||||
|
|
@ -325,13 +436,37 @@ class OwsMachine(models.Model):
|
||||||
|
|
||||||
name = fields.Char(required=True, translate=True)
|
name = fields.Char(required=True, translate=True)
|
||||||
code = fields.Char(required=True, help="Eindeutiger Kurzcode, z.B. 'lasercutter'")
|
code = fields.Char(required=True, help="Eindeutiger Kurzcode, z.B. 'lasercutter'")
|
||||||
description = fields.Text()
|
category = fields.Selection([
|
||||||
active = fields.Boolean(default=True)
|
('green', 'Kategorie 1: grün'),
|
||||||
area_id = fields.Many2one('ows.machine.area', string='Bereich')
|
('yellow', 'Kategorie 2: gelb'),
|
||||||
product_ids = fields.One2many('ows.machine.product', 'machine_id', string="Nutzungsprodukte")
|
('red', 'Kategorie 3: rot'),
|
||||||
product_names = fields.Char(string="Nutzungsprodukte Liste", compute="_compute_product_using_names", store=False,)
|
], 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_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')
|
@api.depends('product_ids.product_id.name')
|
||||||
def _compute_product_using_names(self):
|
def _compute_product_using_names(self):
|
||||||
|
|
@ -354,12 +489,34 @@ class OwsMachine(models.Model):
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_access_list_grouped(self, partner_id):
|
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 '').
|
||||||
|
"""
|
||||||
|
partner = self.env['res.partner'].browse(partner_id)
|
||||||
areas = self.env['ows.machine.area'].search([], order="name")
|
areas = self.env['ows.machine.area'].search([], order="name")
|
||||||
_logger.info("🔍 Maschinenbereiche: %s", areas.mapped('name'))
|
|
||||||
_logger.info("🔍 Partner_id: %s", partner_id)
|
access_by_area = []
|
||||||
res = []
|
|
||||||
for area in areas:
|
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 = []
|
machine_list = []
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
has_access = bool(self.env['ows.machine.access'].search([
|
has_access = bool(self.env['ows.machine.access'].search([
|
||||||
|
|
@ -370,12 +527,21 @@ class OwsMachine(models.Model):
|
||||||
'name': machine.name,
|
'name': machine.name,
|
||||||
'has_access': has_access,
|
'has_access': has_access,
|
||||||
})
|
})
|
||||||
res.append({
|
if machine_list:
|
||||||
|
access_by_area.append({
|
||||||
'area': area.name,
|
'area': area.name,
|
||||||
'color_hex': area.color_hex or '#000000',
|
'color_hex': area.color_hex or '#000000',
|
||||||
'machines': machine_list
|
'machines': machine_list
|
||||||
})
|
})
|
||||||
return res
|
|
||||||
|
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):
|
class OwsMachineAccess(models.Model):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
#import debugpy
|
#import debugpy
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -7,9 +8,9 @@ _logger = logging.getLogger(__name__)
|
||||||
_logger.info("✅ pos_order.py geladen")
|
_logger.info("✅ pos_order.py geladen")
|
||||||
|
|
||||||
# debugpy.listen(("0.0.0.0", 5678))
|
# 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
|
# Optional: Starte erst, wenn VS Code verbunden ist
|
||||||
#debugpy.wait_for_client()
|
# debugpy.wait_for_client()
|
||||||
|
|
||||||
class PosOrder(models.Model):
|
class PosOrder(models.Model):
|
||||||
_inherit = 'pos.order'
|
_inherit = 'pos.order'
|
||||||
|
|
@ -19,10 +20,9 @@ class PosOrder(models.Model):
|
||||||
pos_order = self.browse(pos_order_id)
|
pos_order = self.browse(pos_order_id)
|
||||||
|
|
||||||
training_products = self.env['ows.machine.training'].search([])
|
training_products = self.env['ows.machine.training'].search([])
|
||||||
product_map = {
|
product_map = defaultdict(list)
|
||||||
tp.training_id.product_tmpl_id.id: tp.machine_id.id
|
for tp in training_products:
|
||||||
for tp in training_products
|
product_map[tp.training_id.product_tmpl_id.id].append(tp.machine_id.id)
|
||||||
}
|
|
||||||
|
|
||||||
partner = pos_order.partner_id
|
partner = pos_order.partner_id
|
||||||
if not partner:
|
if not partner:
|
||||||
|
|
@ -31,15 +31,13 @@ class PosOrder(models.Model):
|
||||||
|
|
||||||
for line in pos_order.lines:
|
for line in pos_order.lines:
|
||||||
product_tmpl_id = line.product_id.product_tmpl_id.id
|
product_tmpl_id = line.product_id.product_tmpl_id.id
|
||||||
machine_id = product_map.get(product_tmpl_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)
|
||||||
_logger.info("🔍 Prüfe Produkt %s → Maschine ID: %s", line.product_id.display_name, machine_id)
|
for machine_id in machine_ids:
|
||||||
|
|
||||||
if machine_id:
|
|
||||||
already_exists = self.env['ows.machine.access'].search([
|
already_exists = self.env['ows.machine.access'].search([
|
||||||
('partner_id', '=', partner.id),
|
('partner_id', '=', partner.id),
|
||||||
('machine_id', '=', machine_id)
|
('machine_id', '=', machine_id)
|
||||||
])
|
], limit=1)
|
||||||
if not already_exists:
|
if not already_exists:
|
||||||
self.env['ows.machine.access'].create({
|
self.env['ows.machine.access'].create({
|
||||||
'partner_id': partner.id,
|
'partner_id': partner.id,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,19 @@ def run_migration(cr, registry):
|
||||||
_logger.error(f"[OWS] Fehler bei automatischer Felder-Migration: {e}")
|
_logger.error(f"[OWS] Fehler bei automatischer Felder-Migration: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# Testsystem-Anpassungen (Admin umbenennen + andere archivieren)
|
||||||
|
try:
|
||||||
|
env['hr.employee'].anonymize_for_testsystem()
|
||||||
|
_logger.info("[OWS] Testsystem-Anpassung der Mitarbeiter abgeschlossen.")
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f"[OWS] Fehler bei Testsystem-Anpassung der Mitarbeiter: {e}")
|
||||||
|
|
||||||
|
# Archivierung aller Kontakte die keinen User Account haben
|
||||||
|
try:
|
||||||
|
env['res.partner'].archive_partners_without_users()
|
||||||
|
_logger.info("[OWS] Testsystem-Anpassung der Kontakte abgeschlossen.")
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f"[OWS] Fehler bei Testsystem-Anpassung der Kontakte: {e}")
|
||||||
|
|
||||||
|
|
||||||
#import_machine_products.run_import(cr, registry)
|
#import_machine_products.run_import(cr, registry)
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,7 @@ td {
|
||||||
margin:0;
|
margin:0;
|
||||||
padding:0;
|
padding:0;
|
||||||
color: gray;
|
color: gray;
|
||||||
background: #393939;
|
background: #ff0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* a) The left part of the top-bar */
|
/* a) The left part of the top-bar */
|
||||||
|
|
@ -1606,6 +1606,9 @@ td {
|
||||||
.order-selector {
|
.order-selector {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
.machine-access-sidebar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
.blockUI {
|
.blockUI {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
@ -1701,7 +1704,7 @@ td {
|
||||||
}
|
}
|
||||||
|
|
||||||
.pos .clientlist-screen {
|
.pos .clientlist-screen {
|
||||||
display: grid;
|
/* display: grid; */
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 0px;
|
gap: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ odoo.define('open_workshop.machine_access_sidebar', function (require) {
|
||||||
}).then(function (result) {
|
}).then(function (result) {
|
||||||
partner.create_date = partner.create_date && partner.create_date.substring(0, 10);
|
partner.create_date = partner.create_date && partner.create_date.substring(0, 10);
|
||||||
var html = QWeb.render('PartnerMachineAccessList', {
|
var html = QWeb.render('PartnerMachineAccessList', {
|
||||||
areas: result || [],
|
areas: result.access_by_area || [],
|
||||||
partner: partner,
|
partner: partner,
|
||||||
});
|
});
|
||||||
self.$('.access-content').html(html);
|
self.$('.access-content').html(html);
|
||||||
|
|
|
||||||
1
todo.md
1
todo.md
|
|
@ -1,3 +1,4 @@
|
||||||
[ ] Help System
|
[ ] Help System
|
||||||
[ ] Möglichkeit, Einweisungen manuell zu setzen?
|
[ ] Möglichkeit, Einweisungen manuell zu setzen?
|
||||||
|
[ ] Möglichkeit, Einweisungen von Personen im Backend zurückzusetzen (geht im Moment nur über die Datenbank direkt)
|
||||||
[ ]
|
[ ]
|
||||||
|
|
@ -6,18 +6,23 @@
|
||||||
<field name="model">ows.machine</field>
|
<field name="model">ows.machine</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree>
|
<tree>
|
||||||
|
<field name="category_icon" string="⚙" readonly="1"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="category"/>
|
||||||
<field name="code"/>
|
<field name="code"/>
|
||||||
<field name="area_id" widget="many2one_color"/>
|
<field name="area_id" widget="many2one_color"/>
|
||||||
<field name="product_names"/>
|
<field name="product_names"/>
|
||||||
<field name="training_names"/>
|
<field name="training_names"/>
|
||||||
|
<field name="storage_location"/>
|
||||||
|
<field name="purchase_price"/>
|
||||||
|
<field name="purchase_date"/>
|
||||||
<field name="active"/>
|
<field name="active"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Maschinen Formularansicht -->
|
<!-- Maschinen Formularansicht -->
|
||||||
<record id="view_machine_form" model="ir.ui.view">
|
<record id="view_machine_form" model="ir.ui.view">
|
||||||
<field name="name">ows.machine.form</field>
|
<field name="name">ows.machine.form</field>
|
||||||
<field name="model">ows.machine</field>
|
<field name="model">ows.machine</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
|
|
@ -25,9 +30,18 @@
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="category"/>
|
||||||
|
<field name="category_icon" string="⚙" readonly="1"/>
|
||||||
<field name="code"/>
|
<field name="code"/>
|
||||||
<field name="area_id"/>
|
<field name="area_id"/>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
|
||||||
<field name="description"/>
|
<field name="description"/>
|
||||||
|
<field name="storage_location"/>
|
||||||
|
<field name="purchase_price"/>
|
||||||
|
<field name="purchase_date"/>
|
||||||
<field name="active"/>
|
<field name="active"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
|
|
@ -43,7 +57,7 @@
|
||||||
<page string="Einweisungsprodukte">
|
<page string="Einweisungsprodukte">
|
||||||
<field name="training_ids" context="{'default_machine_id': active_id}">
|
<field name="training_ids" context="{'default_machine_id': active_id}">
|
||||||
<tree editable="bottom">
|
<tree editable="bottom">
|
||||||
<field name="training_id" domain="[('categ_id.name', '=', 'Einweisungen')]" />
|
<field name="training_id" domain="[('categ_id.name', 'in', ['Einweisungen', 'Kurse'])]" />
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,14 @@
|
||||||
<!-- res_partner_view.xml -->
|
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- View-Erweiterung für res.partner: Tab mit HTML-Tabelle
|
<!-- Zentrale View für alle drei Tabs in garantierter Reihenfolge -->
|
||||||
Der Inhalt wird in der Methode _compute_machine_access_html() generiert.
|
<record id="view_partner_form_inherit_open_workshop_tabs" model="ir.ui.view">
|
||||||
Diese Methode wird in der Klasse res.partner definiert in der Datei models/ows_models.py.
|
<field name="name">res.partner.form.ows.tabs</field>
|
||||||
Die Methode wird aufgerufen, wenn das Partnerformular geöffnet wird.
|
|
||||||
Die HTML-Tabelle wird in der Variable machine_access_html gespeichert.
|
|
||||||
Die Variable wird in der View angezeigt.
|
|
||||||
-->
|
|
||||||
<record id="view_partner_form_inherit_open_workshop_html" model="ir.ui.view">
|
|
||||||
<field name="name">res.partner.form.ows.machine.access.html</field>
|
|
||||||
<field name="model">res.partner</field>
|
<field name="model">res.partner</field>
|
||||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="priority" eval="10"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<notebook position="inside">
|
<xpath expr="//page[@name='sales_purchases']" position="before">
|
||||||
<page string="HOBBYHIMMEL Einweisungen">
|
|
||||||
<field name="machine_access_html" readonly="1" widget="html"/>
|
|
||||||
</page>
|
|
||||||
</notebook>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Teil 1: Maschinenfreigaben-Tabelle -->
|
<!-- Tab 1: HOBBYHIMMEL Basis -->
|
||||||
<record id="view_partner_form_inherit_open_workshop" model="ir.ui.view">
|
|
||||||
<field name="name">res.partner.form.ows.machine.access</field>
|
|
||||||
<field name="model">res.partner</field>
|
|
||||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<notebook position="inside">
|
|
||||||
<page string="Einweisungen (Liste)">
|
|
||||||
<field name="machine_access_ids">
|
|
||||||
<tree>
|
|
||||||
<field name="machine_id"/>
|
|
||||||
<field name="date_granted"/>
|
|
||||||
<field name="date_expiry"/>
|
|
||||||
<field name="granted_by_pos"/>
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</page>
|
|
||||||
</notebook>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Teil 2: HOBBYHIMMEL Basis (ows_user_id Felder) -->
|
|
||||||
<record id="view_partner_form_inherit_ows_user" model="ir.ui.view">
|
|
||||||
<field name="name">res.partner.form.ows.user</field>
|
|
||||||
<field name="model">res.partner</field>
|
|
||||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
|
|
||||||
<!-- Geburtstag direkt unter USt-ID -->
|
|
||||||
<xpath expr="//field[@name='vat']" position="after">
|
|
||||||
<field name="birthday"/>
|
|
||||||
</xpath>
|
|
||||||
|
|
||||||
<!-- Eigene Seite "Basis" nach der Verkaufsseite -->
|
|
||||||
<xpath expr="//page[@name='sales_purchases']" position="after">
|
|
||||||
<page name="ows_basic" string="HOBBYHIMMEL Basis">
|
<page name="ows_basic" string="HOBBYHIMMEL Basis">
|
||||||
<group name="container_row_2">
|
<group name="container_row_2">
|
||||||
<group string="Sicherheit">
|
<group string="Sicherheit">
|
||||||
|
|
@ -66,28 +20,42 @@
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
</xpath>
|
|
||||||
|
|
||||||
|
<!-- Tab 2: HOBBYHIMMEL Einweisungen (HTML) -->
|
||||||
|
<page name="ows_machine_access_html" string="HOBBYHIMMEL Einweisungen">
|
||||||
|
<field name="machine_access_html" readonly="1" widget="html"/>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<!-- Tab 3: Einweisungen (Liste) -->
|
||||||
|
<page name="ows_machine_access_list" string="Einweisungen (Liste)">
|
||||||
|
<field name="machine_access_ids">
|
||||||
|
<tree>
|
||||||
|
<field name="machine_id"/>
|
||||||
|
<field name="date_granted"/>
|
||||||
|
<field name="date_expiry"/>
|
||||||
|
<field name="granted_by_pos"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="contacts.action_contacts" model="ir.actions.act_window">
|
|
||||||
<field name="view_mode">tree,kanban,form,activity</field>
|
<!-- Geburtstag direkt nach der USt-ID -->
|
||||||
|
<record id="view_partner_form_inherit_ows_birthday" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.form.ows.birthday</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="priority" eval="15"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='vat']" position="after">
|
||||||
|
<field name="birthday"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="contacts.action_contacts_view_kanban" model="ir.actions.act_window.view">
|
|
||||||
<field name="sequence" eval="1"/>
|
<!-- List View Anpassung -->
|
||||||
</record>
|
|
||||||
<record id="contacts.action_contacts_view_tree" model="ir.actions.act_window.view">
|
|
||||||
<field name="sequence" eval="0"/>
|
|
||||||
<!--tree default_order="create_date desc"/-->
|
|
||||||
</record>
|
|
||||||
<!-- Action to set default view to list view for Contacts
|
|
||||||
<record id="contacts.action_contacts" model="ir.actions.act_window">
|
|
||||||
<field name="name">Contacts</field>
|
|
||||||
<field name="res_model">res.partner</field>
|
|
||||||
<field name="view_mode">tree,kanban,form</field>
|
|
||||||
<field name="view_id" ref="base.view_partner_tree"/>
|
|
||||||
</record>
|
|
||||||
-->
|
|
||||||
<record id="ows_userList_inherit" model="ir.ui.view">
|
<record id="ows_userList_inherit" model="ir.ui.view">
|
||||||
<field name="name">res.partner.ows.tree</field>
|
<field name="name">res.partner.ows.tree</field>
|
||||||
<field name="model">res.partner</field>
|
<field name="model">res.partner</field>
|
||||||
|
|
@ -99,7 +67,6 @@
|
||||||
<field name="security_id" optional="show"/>
|
<field name="security_id" optional="show"/>
|
||||||
<field name="rfid_card" optional="show"/>
|
<field name="rfid_card" optional="show"/>
|
||||||
<field name="category_id" widget="many2many_tags"/>
|
<field name="category_id" widget="many2many_tags"/>
|
||||||
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='vat']" position="replace">
|
<xpath expr="//field[@name='vat']" position="replace">
|
||||||
<field name="vat" invisible="1"/>
|
<field name="vat" invisible="1"/>
|
||||||
|
|
@ -118,6 +85,8 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- Standardwerte setzen (company_type = person) -->
|
||||||
<record id="view_partner_form_inherit" model="ir.ui.view">
|
<record id="view_partner_form_inherit" model="ir.ui.view">
|
||||||
<field name="name">res.partner.form.inherit.default_person</field>
|
<field name="name">res.partner.form.inherit.default_person</field>
|
||||||
<field name="model">res.partner</field>
|
<field name="model">res.partner</field>
|
||||||
|
|
@ -127,5 +96,16 @@
|
||||||
<attribute name="default">person</attribute>
|
<attribute name="default">person</attribute>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- Optional: Kontakte-Action, falls gebraucht -->
|
||||||
|
<record id="contacts.action_contacts" model="ir.actions.act_window">
|
||||||
|
<field name="view_mode">tree,kanban,form,activity</field>
|
||||||
|
</record>
|
||||||
|
<record id="contacts.action_contacts_view_kanban" model="ir.actions.act_window.view">
|
||||||
|
<field name="sequence" eval="1"/>
|
||||||
|
</record>
|
||||||
|
<record id="contacts.action_contacts_view_tree" model="ir.actions.act_window.view">
|
||||||
|
<field name="sequence" eval="0"/>
|
||||||
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user