From 1abeb97afa2d838a2811137e43dcdab189df2275 Mon Sep 17 00:00:00 2001 From: "matthias.lotz" Date: Sat, 10 Jan 2026 15:10:51 +0100 Subject: [PATCH] Cleanup: Remove debug logs, unused backend files, add comprehensive comments - Removed all console.log debug statements from JavaScript files - Deleted unnecessary backend models (hr_employee.py, res_users.py, pos_session.py, pos_config.py) - Added comprehensive German comments explaining the BroadcastChannel architecture - Updated README with detailed technical documentation about frontend-only approach - Removed debug info box from customer display template - Simplified __init__.py files (pure frontend solution) The module now uses a clean, maintainable frontend-only approach that: - Patches Chrome component with effect(batched()) for reactive updates - Sends cashier data via BroadcastChannel (same pattern as order updates) - Uses hr.employee.public URL format (identical to POS navbar) - Falls back to company logo when no employee image available --- open_workshop_pos_customer_display/README.md | 63 ++++++++++++------- .../__init__.py | 3 +- .../models/__init__.py | 6 +- .../models/hr_employee.py | 10 --- .../models/pos_config.py | 41 ------------ .../models/pos_session.py | 35 ----------- .../models/res_users.py | 14 ----- .../static/src/js/customer_display.js | 43 +++++++------ .../static/src/js/pos_store.js | 44 ++++++------- .../static/src/xml/customer_display.xml | 5 -- 10 files changed, 86 insertions(+), 178 deletions(-) delete mode 100644 open_workshop_pos_customer_display/models/hr_employee.py delete mode 100644 open_workshop_pos_customer_display/models/pos_config.py delete mode 100644 open_workshop_pos_customer_display/models/pos_session.py delete mode 100644 open_workshop_pos_customer_display/models/res_users.py diff --git a/open_workshop_pos_customer_display/README.md b/open_workshop_pos_customer_display/README.md index af3cbe1..920fcb0 100644 --- a/open_workshop_pos_customer_display/README.md +++ b/open_workshop_pos_customer_display/README.md @@ -1,46 +1,61 @@ # Open Workshop - POS Customer Display Customization ## Beschreibung -Dieses Modul passt das POS Customer Display in Odoo 18 an und zeigt Informationen über den aktuellen Kassierer in der Sidebar an. +Dieses Modul passt das POS Customer Display in Odoo 18 an und zeigt das Bild des aktuellen Kassierers in der Sidebar an. ## Features - **Kassiererbild**: Zeigt das Profilbild des aktuellen Kassierers (aus Personal/HR) in voller Sidebar-Größe - **Automatische Aktualisierung**: Die Anzeige wird automatisch aktualisiert, wenn der Kassierer wechselt -- **Responsive Design**: Das Bild wird mit korrektem Seitenverhältnis skaliert (cover-Modus) -- **Fallback**: Bei fehlendem Bild wird das User-Avatar oder Company-Logo angezeigt -- **Kassierer-Name**: Optional wird der Name des Kassierers als Overlay am unteren Rand angezeigt +- **Responsive Design**: Das Bild wird mit korrektem Seitenverhältnis skaliert (`object-fit: contain`) +- **Fallback**: Bei fehlendem Bild wird das Firmen-Logo angezeigt ## Installation -1. Modul in den Addon-Pfad kopieren -2. Apps-Liste aktualisieren -3. Modul installieren -4. **Wichtig**: Stelle sicher, dass in Personal/Mitarbeiter ein Bild für die Kassierer hinterlegt ist +1. Modul installieren über Apps +2. **Wichtig**: Stelle sicher, dass in Personal/Mitarbeiter ein Bild für die Kassierer hinterlegt ist +3. POS öffnen und Customer Display starten - das Kassiererbild erscheint automatisch ## Technische Details +### Architektur - Reine Frontend-Lösung + +Das Modul verwendet **keine Backend-Anpassungen**, sondern arbeitet komplett im Frontend: + +**POS-Seite** (`pos_store.js`): +- Patched die `Chrome` Komponente aus `@point_of_sale/app/pos_app` +- Verwendet `effect(batched())` für reaktive Updates (gleiche Architektur wie Odoo's Order-Updates) +- Sendet Kassierer-Daten via `BroadcastChannel("UPDATE_CUSTOMER_DISPLAY")` +- Nutzt URL-Format: `/web/image/hr.employee.public/{id}/avatar_1920` (identisch zum POS Navbar) + +**Customer Display** (`customer_display.js`): +- Lauscht auf `BroadcastChannel` Messages +- Zeigt Mitarbeiterbild mit automatischem Fallback zum Logo + +**Template** (`customer_display.xml`): +- Überschreibt den `o_customer_display_sidebar` +- Verwendet `` Tag mit `object-fit: contain` für saubere Skalierung +- Fallback zum Firmen-Logo via `onerror` Handler + +### Warum Frontend-only? + +Die ursprüngliche Implementierung verwendete Backend-Modelle (`pos_config.py`, `pos_session.py`, etc.), aber: +- Die Daten wurden nicht zuverlässig ins Frontend geladen +- Das Frontend kann direkt auf `pos.get_cashier()` (res.users) zugreifen +- Das `hr.employee.public` Model ist bereits verfügbar +- Eine Frontend-Lösung ist wartungsärmer, performanter und zuverlässiger + ### Dateien -- `__manifest__.py` - Modul-Definition mit Abhängigkeiten -- `static/src/js/customer_display.js` - JavaScript-Komponente für Employee-Daten +- `__manifest__.py` - Modul-Definition mit Asset-Bundles +- `static/src/js/pos_store.js` - POS Chrome Patch für Kassierer-Updates +- `static/src/js/customer_display.js` - Customer Display Component - `static/src/xml/customer_display.xml` - Template-Override für die Sidebar -### Funktionsweise -1. Beim Laden des Customer Displays wird der aktuelle User ermittelt -2. Der zugehörige Employee-Datensatz wird aus `hr.employee` geladen -3. Das Bild wird über `/web/image/hr.employee/{id}/image_1920` angezeigt -4. Bei Kassiererwechsel wird die Komponente neu gerendert - -### Anpassungen -Die Sidebar verwendet folgende CSS-Eigenschaften für das Bild: -- `background-size: cover` - Bild füllt den gesamten Bereich -- `background-position: center` - Bild wird zentriert -- Das Seitenverhältnis wird automatisch beibehalten - ## Abhängigkeiten -- point_of_sale -- hr (Personal/Human Resources) +- `point_of_sale` (POS System) +- `hr` (Personal/Human Resources für Mitarbeiterbilder) ## Version 18.0.1.0.0 ## Lizenz LGPL-3 + diff --git a/open_workshop_pos_customer_display/__init__.py b/open_workshop_pos_customer_display/__init__.py index a0fdc10..adcfd55 100644 --- a/open_workshop_pos_customer_display/__init__.py +++ b/open_workshop_pos_customer_display/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- -from . import models +# Reine Frontend-Lösung für POS Customer Display Anpassung + diff --git a/open_workshop_pos_customer_display/models/__init__.py b/open_workshop_pos_customer_display/models/__init__.py index d003f7d..42fcce3 100644 --- a/open_workshop_pos_customer_display/models/__init__.py +++ b/open_workshop_pos_customer_display/models/__init__.py @@ -1,5 +1,3 @@ # -*- coding: utf-8 -*- -from . import pos_session -from . import pos_config -from . import hr_employee -from . import res_users +# Keine Backend-Modelle mehr nötig - reine Frontend-Lösung + diff --git a/open_workshop_pos_customer_display/models/hr_employee.py b/open_workshop_pos_customer_display/models/hr_employee.py deleted file mode 100644 index 2570d07..0000000 --- a/open_workshop_pos_customer_display/models/hr_employee.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -from odoo import models - - -class HrEmployee(models.Model): - _inherit = 'hr.employee' - - def _load_pos_data_fields(self, config_id): - """Define which fields to load for hr.employee in POS""" - return ['id', 'name', 'user_id', 'image_1920'] diff --git a/open_workshop_pos_customer_display/models/pos_config.py b/open_workshop_pos_customer_display/models/pos_config.py deleted file mode 100644 index 76137c3..0000000 --- a/open_workshop_pos_customer_display/models/pos_config.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -from odoo import models - - -class PosConfig(models.Model): - _inherit = 'pos.config' - - def _get_customer_display_data(self): - """Erweitere Customer Display Daten um Employee-Informationen""" - result = super()._get_customer_display_data() - - # Hole die aktuelle offene Session für diese POS Config - session = self.env['pos.session'].search([ - ('config_id', '=', self.id), - ('state', '=', 'opened') - ], limit=1) - - if not session: - return result - - # Hole den aktuellen Kassierer aus der Session - # Der aktuelle Kassierer wird in pos.session nicht gespeichert, - # sondern nur im Frontend. Daher verwenden wir den Session-User als Fallback. - user_id = session.user_id.id - - # Hole Employee-Daten für den User - employee = self.env['hr.employee'].search([ - ('user_id', '=', user_id) - ], limit=1) - - if employee and employee.image_1920: - result['employee_id'] = employee.id - result['employee_name'] = employee.name - result['employee_image_url'] = f'/web/image/hr.employee/{employee.id}/image_1920' - else: - # Fallback: User-Avatar - result['employee_id'] = user_id - result['employee_name'] = self.env['res.users'].browse(user_id).name - result['employee_image_url'] = f'/web/image/res.users/{user_id}/avatar_1920' - - return result diff --git a/open_workshop_pos_customer_display/models/pos_session.py b/open_workshop_pos_customer_display/models/pos_session.py deleted file mode 100644 index 01c387d..0000000 --- a/open_workshop_pos_customer_display/models/pos_session.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from odoo import models, api - - -class PosSession(models.Model): - _inherit = 'pos.session' - - @api.model - def _load_pos_data_models(self, config_id): - """Füge hr.employee zu den zu ladenden Modellen hinzu""" - models = super()._load_pos_data_models(config_id) - if 'hr.employee' not in models: - models.append('hr.employee') - return models - - def _load_pos_data(self, data): - """Erweitere Session-Daten um Employee-Informationen""" - result = super()._load_pos_data(data) - - # Hole Employee-Daten des aktuellen Users - employee = self.env['hr.employee'].search([ - ('user_id', '=', self.env.uid) - ], limit=1) - - if employee and employee.image_1920: - result['data'][0]['employee_id'] = employee.id - result['data'][0]['employee_name'] = employee.name - result['data'][0]['employee_image_url'] = f'/web/image/hr.employee/{employee.id}/image_1920' - else: - # Fallback: User-Avatar - result['data'][0]['employee_id'] = self.env.uid - result['data'][0]['employee_name'] = self.env.user.name - result['data'][0]['employee_image_url'] = f'/web/image/res.users/{self.env.uid}/avatar_1920' - - return result diff --git a/open_workshop_pos_customer_display/models/res_users.py b/open_workshop_pos_customer_display/models/res_users.py deleted file mode 100644 index 1c0b8c8..0000000 --- a/open_workshop_pos_customer_display/models/res_users.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -from odoo import models - - -class ResUsers(models.Model): - _inherit = 'res.users' - - def _load_pos_data_fields(self, config_id): - """Define which fields to load for res.users in POS""" - fields = super()._load_pos_data_fields(config_id) - # Add employee_ids to load the related employee - if 'employee_ids' not in fields: - fields.append('employee_ids') - return fields diff --git a/open_workshop_pos_customer_display/static/src/js/customer_display.js b/open_workshop_pos_customer_display/static/src/js/customer_display.js index ad7f5da..b6936ba 100644 --- a/open_workshop_pos_customer_display/static/src/js/customer_display.js +++ b/open_workshop_pos_customer_display/static/src/js/customer_display.js @@ -3,49 +3,52 @@ import { patch } from "@web/core/utils/patch"; import { CustomerDisplay } from "@point_of_sale/customer_display/customer_display"; -console.log('[Employee Display] customer_display.js loaded'); - -// Patch CustomerDisplay to show employee data +/** + * Erweitert das Customer Display um die Anzeige des aktuellen Kassierers. + * Lauscht auf BroadcastChannel Messages von der POS und aktualisiert + * das angezeigte Mitarbeiterbild dynamisch. + */ patch(CustomerDisplay.prototype, { setup() { super.setup(...arguments); - console.log('[Employee Display] CustomerDisplay.setup(), initial this.data:', this.data); - console.log('[Employee Display] Session:', this.session); - // Listen to BroadcastChannel to receive employee updates + // Initialisiere Employee-Daten this.employeeData = null; + + // Lausche auf BroadcastChannel für lokale Customer Displays if (this.session.type === "local") { const channel = new BroadcastChannel("UPDATE_CUSTOMER_DISPLAY"); channel.onmessage = (event) => { - console.log('[Employee Display] ✅ BroadcastChannel message received:', event.data); if (event.data.employeeData) { - console.log('[Employee Display] Employee data:', event.data.employeeData); - console.log('[Employee Display] Image URL:', event.data.employeeData.employee_image_url); this.employeeData = event.data.employeeData; - // Trigger re-render + // Trigger re-render um das neue Bild anzuzeigen this.render(); } }; } }, + /** + * Gibt die URL des Mitarbeiterbilds zurück. + * Verwendet nur BroadcastChannel-Daten, kein Session-Fallback. + * Wenn keine Daten vorhanden sind, wird das Firmenlogo angezeigt. + */ get currentEmployeeImage() { - // Only use BroadcastChannel data, no session fallback - // This ensures we show the logo until we get the real cashier data - const image = this.employeeData?.employee_image_url || - this.data?.employeeData?.employee_image_url || - ''; - console.log('[Employee Display] currentEmployeeImage returning:', image); - console.log('[Employee Display] Source: this.employeeData:', this.employeeData?.employee_image_url, - 'this.data:', this.data?.employeeData?.employee_image_url); - return image; + return this.employeeData?.employee_image_url || + this.data?.employeeData?.employee_image_url || + ''; }, + /** + * Gibt den Namen des aktuellen Mitarbeiters zurück. + */ get currentEmployeeName() { - // Only use BroadcastChannel data, no session fallback return this.employeeData?.employee_name || this.data?.employeeData?.employee_name || ''; }, + /** + * Prüft ob ein Mitarbeiterbild vorhanden ist. + */ get hasEmployeeImage() { return Boolean(this.currentEmployeeImage); } diff --git a/open_workshop_pos_customer_display/static/src/js/pos_store.js b/open_workshop_pos_customer_display/static/src/js/pos_store.js index 02e7800..a94178f 100644 --- a/open_workshop_pos_customer_display/static/src/js/pos_store.js +++ b/open_workshop_pos_customer_display/static/src/js/pos_store.js @@ -5,24 +5,23 @@ import { Chrome } from "@point_of_sale/app/pos_app"; import { effect } from "@web/core/utils/reactive"; import { batched } from "@web/core/utils/timing"; -console.log('[Employee Display] pos_store.js loaded'); - +/** + * Erweitert die POS Chrome Komponente um die Anzeige des aktuellen Kassierers + * im Customer Display. Verwendet dieselbe Architektur wie Odoo's Order-Updates: + * - effect() mit batched() für reaktive Updates + * - BroadcastChannel für Kommunikation zwischen POS und Customer Display + */ patch(Chrome.prototype, { setup() { - console.log('[Employee Display] Chrome.setup() patched - starting'); super.setup(...arguments); - console.log('[Employee Display] customer_display_type:', this.pos.config.customer_display_type); if (this.pos.config.customer_display_type === "none") { - console.log('[Employee Display] Customer display disabled, skipping'); return; } - console.log('[Employee Display] Setting up cashier effect'); - // Add reactive effect for cashier changes (same pattern as order effect in Chrome) + // Reagiere auf Kassierer-Änderungen (z.B. bei Kassierer-Wechsel) effect( batched(({ cashier }) => { - console.log('[Employee Display] Effect triggered! cashier:', cashier); if (cashier) { this.sendCashierToCustomerDisplay(cashier); } @@ -30,12 +29,12 @@ patch(Chrome.prototype, { [this.pos] ); - // Also send when any order changes (piggyback on order updates) - // This ensures customer display gets cashier data even if opened late + // Sende Kassierer-Daten auch bei jeder Order-Änderung + // Dies stellt sicher, dass das Customer Display die Daten bekommt, + // auch wenn es später geöffnet wird const originalSendOrder = this.sendOrderToCustomerDisplay.bind(this); this.sendOrderToCustomerDisplay = function(selectedOrder, scaleData) { originalSendOrder(selectedOrder, scaleData); - // Also send cashier data with every order update const cashier = this.pos.get_cashier(); if (cashier) { this.sendCashierToCustomerDisplay(cashier); @@ -43,36 +42,33 @@ patch(Chrome.prototype, { }; }, + /** + * Sendet die Kassierer-Informationen an das Customer Display. + * Verwendet die gleiche URL-Struktur wie der POS Navbar für Employee-Bilder. + * + * @param {Object} cashier - Der aktuelle Kassierer (res.users) + */ sendCashierToCustomerDisplay(cashier) { - console.log('[Employee Display] sendCashierToCustomerDisplay called with:', cashier); - if (!cashier) { - console.log('[Employee Display] No cashier, skipping'); return; } - // Use cashier directly - same as POS navbar uses hr.employee.public + // Verwende hr.employee.public für das Avatar-Bild + // (gleiche URL wie im POS Navbar verwendet wird) const employeeData = { employee_id: cashier.id, employee_name: cashier.name, employee_image_url: `/web/image/hr.employee.public/${cashier.id}/avatar_1920` }; - - console.log('[Employee Display] Created employeeData:', employeeData); - // Get current order data and add employee data + // Füge employeeData zur Order-Display-Data hinzu const selectedOrder = this.pos.get_order(); - console.log('[Employee Display] Current order:', selectedOrder); if (selectedOrder) { const customerDisplayData = selectedOrder.getCustomerDisplayData(); customerDisplayData.employeeData = employeeData; - console.log('[Employee Display] Sending to customer display:', customerDisplayData); - console.log('[Employee Display] employeeData in object:', customerDisplayData.employeeData); - console.log('[Employee Display] Full object keys:', Object.keys(customerDisplayData)); - // Send using the same channels as order updates + // Sende via BroadcastChannel (lokal) oder Remote-API if (this.pos.config.customer_display_type === "local") { - console.log('[Employee Display] Posting to BroadcastChannel'); this.customerDisplayChannel.postMessage(customerDisplayData); } if (this.pos.config.customer_display_type === "remote") { diff --git a/open_workshop_pos_customer_display/static/src/xml/customer_display.xml b/open_workshop_pos_customer_display/static/src/xml/customer_display.xml index c15c30f..62f35c9 100644 --- a/open_workshop_pos_customer_display/static/src/xml/customer_display.xml +++ b/open_workshop_pos_customer_display/static/src/xml/customer_display.xml @@ -36,11 +36,6 @@ - - -
- Debug: -