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 @@