diff --git a/open_workshop_employee_imagegenerator/__manifest__.py b/open_workshop_employee_imagegenerator/__manifest__.py index 7731409..dd84ed7 100644 --- a/open_workshop_employee_imagegenerator/__manifest__.py +++ b/open_workshop_employee_imagegenerator/__manifest__.py @@ -3,28 +3,29 @@ 'name': 'Open Workshop - Employee Image Generator', 'version': '18.0.1.0.0', 'category': 'Human Resources', - 'summary': 'Generate professional employee name badges with photo cropping', + 'summary': 'Upload and crop employee photos (369x492) with job focus', 'description': """ Employee Image Generator ========================= - Creates professional employee name badges (Thekenschilder) directly in Odoo. + Upload and crop employee photos directly in Odoo. Features: --------- - * Upload and crop employee photos with fixed aspect ratio (369x492) - * Add employee name and job description + * Upload and crop employee photos to fixed 369x492 pixels + * Add job focus areas (Schwerpunkte) * Integrated Cropper.js for professional image editing + * Fixed crop frame - only image moves and zooms * Direct integration in Employee form - * Replaces external thekenheld webapp + * Saved photo can be used in POS Customer Display Usage: ------ 1. Go to Employee form 2. Click "Namensschild erstellen" button - 3. Upload photo and crop it - 4. Enter name and job focus areas - 5. Save - the generated image becomes the employee avatar + 3. Upload photo, zoom and position it in fixed crop frame + 4. Enter job focus areas (optional) + 5. Save - photo and job_focus are saved to employee """, 'author': 'Open Workshop', 'website': 'https://www.open-workshop.de', @@ -39,9 +40,8 @@ 'open_workshop_employee_imagegenerator/static/lib/cropperjs/cropper.css', 'open_workshop_employee_imagegenerator/static/src/css/employee_image_widget.css', 'open_workshop_employee_imagegenerator/static/src/css/badge_template.css', - # Libraries + # Cropper.js Library 'open_workshop_employee_imagegenerator/static/lib/cropperjs/cropper.js', - 'open_workshop_employee_imagegenerator/static/lib/html2canvas.min.js', # Our custom widget 'open_workshop_employee_imagegenerator/static/src/js/employee_image_widget.js', 'open_workshop_employee_imagegenerator/static/src/xml/employee_image_widget.xml', diff --git a/open_workshop_employee_imagegenerator/static/src/js/employee_image_widget.js b/open_workshop_employee_imagegenerator/static/src/js/employee_image_widget.js index d0fd838..25f80c7 100644 --- a/open_workshop_employee_imagegenerator/static/src/js/employee_image_widget.js +++ b/open_workshop_employee_imagegenerator/static/src/js/employee_image_widget.js @@ -166,46 +166,25 @@ class EmployeeImageGenerator extends Component { // Keine zusätzliche Logik nötig - OWL reactive state macht das automatisch! } - // Schritt 4: Finale Speicherung - Konvertiere HTML-Template zu Bild mit html2canvas + // Schritt 4: Finale Speicherung - Speichere nur das zugeschnittene Foto + job_focus async saveFinalImage() { try { - const badgeElement = this.badgeTemplate.el; - if (!badgeElement) { - this.notification.add('Badge-Template nicht gefunden', { type: 'danger' }); + // Verwende das bereits zugeschnittene Foto (369×492px) + if (!this.state.croppedImageDataUrl) { + this.notification.add('Kein zugeschnittenes Foto vorhanden', { type: 'danger' }); return; } - // Entferne temporär die Skalierung vom Wrapper (nicht vom Badge selbst) - const wrapper = badgeElement.parentElement; - const originalTransform = wrapper ? wrapper.style.transform : ''; - if (wrapper) { - wrapper.style.transform = 'none'; - } - - // Konvertiere HTML-Element zu Canvas mit html2canvas in voller A4-Größe - const canvas = await window.html2canvas(badgeElement, { - width: this.badgeWidth, - height: this.badgeHeight, - scale: 1, // Wichtig: keine zusätzliche Skalierung - backgroundColor: '#ffffff', - }); - - // Stelle die Skalierung für die Vorschau wieder her - if (wrapper) { - wrapper.style.transform = originalTransform; - } - - // Konvertiere Canvas zu Base64 PNG - const finalImageBase64 = canvas.toDataURL('image/png'); - - // Speichere in Odoo (entferne data:image/png;base64, prefix) - const imageData = finalImageBase64.split(',')[1]; + // Extrahiere Base64-Daten (entferne data:image/jpeg;base64, prefix) + const imageData = this.state.croppedImageDataUrl.split(',')[1]; + // Speichere Foto + job_focus in Odoo await this.orm.write('hr.employee', [this.state.employeeId], { image_1920: imageData, + job_focus: this.state.jobFocus || '', }); - this.notification.add('Namensschild erfolgreich gespeichert!', { + this.notification.add('Foto erfolgreich gespeichert!', { type: 'success', }); diff --git a/open_workshop_pos_customer_display/__manifest__.py b/open_workshop_pos_customer_display/__manifest__.py index 011fc9b..2e73011 100644 --- a/open_workshop_pos_customer_display/__manifest__.py +++ b/open_workshop_pos_customer_display/__manifest__.py @@ -3,11 +3,21 @@ 'name': 'Open Workshop - POS Customer Display Customization', 'version': '18.0.1.0.0', 'category': 'Point of Sale', - 'summary': 'Anpassungen am POS Customer Display Sidebar', + 'summary': 'Show employee badge (Thekenschild) in POS Customer Display', 'description': """ - Dieses Modul erweitert das POS Customer Display und passt die Sidebar an. - - Zeigt das Bild des aktuellen Kassierers in der Sidebar an - - Aktualisiert die Anzeige bei Kassiererwechsel + POS Customer Display - Employee Badge + ====================================== + + Zeigt ein dynamisches Mitarbeiter-Namensschild im POS Customer Display. + + Features: + --------- + * Baut Namensschild dynamisch aus Employee-Daten auf + * Zeigt Mitarbeiterfoto (von open_workshop_employee_imagegenerator) + * Zeigt Name und Schwerpunkte (job_focus) + * Responsive Design basierend auf thekenheld print.html + * Aktualisiert automatisch bei Kassiererwechsel + * Fallback auf Company-Logo wenn kein Foto vorhanden """, 'author': 'Open Workshop', 'depends': [ @@ -20,6 +30,7 @@ 'open_workshop_pos_customer_display/static/src/js/pos_store.js', ], 'point_of_sale.customer_display_assets': [ + 'open_workshop_pos_customer_display/static/src/css/employee_badge.css', 'open_workshop_pos_customer_display/static/src/js/customer_display.js', 'open_workshop_pos_customer_display/static/src/xml/customer_display.xml', ], diff --git a/open_workshop_pos_customer_display/static/src/css/employee_badge.css b/open_workshop_pos_customer_display/static/src/css/employee_badge.css new file mode 100644 index 0000000..147beb7 --- /dev/null +++ b/open_workshop_pos_customer_display/static/src/css/employee_badge.css @@ -0,0 +1,109 @@ +/* Employee Badge Design - Basierend auf thekenheld print.html */ +/* Responsive Version für Customer Display - Screen-optimiert */ + +.employee-badge-container { + width: 100%; + min-height: 100vh; + background: white; + display: flex; + flex-direction: column; + position: relative; +} + +/* Header */ +.employee-badge-header { + background-color: rgb(255, 253, 253); + padding: 1.5vh 4vw; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.employee-badge-logo { + max-width: 15vw; + max-height: 7vh; + height: auto; + object-fit: contain; +} + +/* Content - wächst und scrollt bei Bedarf */ +.employee-badge-content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 2vh 3vw; + overflow-y: auto; + overflow-x: hidden; +} + +.employee-badge-headline1 { + font-size: clamp(1.2rem, 3.5vw, 2.5rem); + font-weight: bold; + margin-bottom: 1vh; +} + +.employee-badge-headline2 { + font-size: clamp(0.8rem, 2vw, 1.5rem); + margin-bottom: 2vh; +} + +.employee-badge-photo { + max-width: 40%; + max-height: 45%; + width: auto; + height: auto; + object-fit: cover; + margin: 2vh 0; + border-radius: 4px; +} + +.employee-badge-name { + font-size: clamp(1.5rem, 5vw, 4rem); + font-weight: bold; + margin: 2vh 0 1vh 0; + line-height: 1.2; +} + +.employee-badge-divider { + width: 80%; + border: 1px solid #333; + margin: 1vh 0; +} + +.employee-badge-headline3 { + font-size: clamp(1rem, 2.5vw, 1.8rem); + font-weight: bold; + margin: 1vh 0; +} + +.employee-badge-topics { + font-size: clamp(0.9rem, 2.2vw, 1.6rem); + margin: 1vh 0; + max-width: 80%; + max-height: 15vh; + overflow-y: auto; + overflow-wrap: break-word; + word-wrap: break-word; + hyphens: auto; +} + +/* Footer - bleibt immer am unteren Bildschirmrand */ +.employee-badge-footer { + background-color: rgb(255, 255, 255); + color: red; + padding: 1.5vh 3vw; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.employee-badge-warning { + font-size: clamp(0.9rem, 2.5vw, 2rem); + font-weight: bold; + text-align: center; +} 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 b6936ba..d48813a 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 @@ -4,9 +4,9 @@ import { patch } from "@web/core/utils/patch"; import { CustomerDisplay } from "@point_of_sale/customer_display/customer_display"; /** - * Erweitert das Customer Display um die Anzeige des aktuellen Kassierers. + * Erweitert das Customer Display um die Anzeige des aktuellen Kassierers als Badge. * Lauscht auf BroadcastChannel Messages von der POS und aktualisiert - * das angezeigte Mitarbeiterbild dynamisch. + * das angezeigte Mitarbeiterbild + job_focus dynamisch. */ patch(CustomerDisplay.prototype, { setup() { @@ -30,8 +30,6 @@ patch(CustomerDisplay.prototype, { /** * 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() { return this.employeeData?.employee_image_url || @@ -46,6 +44,20 @@ patch(CustomerDisplay.prototype, { return this.employeeData?.employee_name || this.data?.employeeData?.employee_name || ''; }, + /** + * Gibt die Schwerpunkte des aktuellen Mitarbeiters zurück. + */ + get currentEmployeeJobFocus() { + return this.employeeData?.employee_job_focus || this.data?.employeeData?.employee_job_focus || ''; + }, + + /** + * Gibt die URL des Company-Logos zurück. + */ + get companyLogo() { + return `/logo?company=${this.session.company_id}`; + }, + /** * Prüft ob ein Mitarbeiterbild vorhanden ist. */ 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 a94178f..9379279 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 @@ -45,21 +45,37 @@ patch(Chrome.prototype, { /** * Sendet die Kassierer-Informationen an das Customer Display. * Verwendet die gleiche URL-Struktur wie der POS Navbar für Employee-Bilder. + * Lädt auch job_focus aus hr.employee. * * @param {Object} cashier - Der aktuelle Kassierer (res.users) */ - sendCashierToCustomerDisplay(cashier) { + async sendCashierToCustomerDisplay(cashier) { if (!cashier) { return; } - // Verwende hr.employee.public für das Avatar-Bild - // (gleiche URL wie im POS Navbar verwendet wird) + // Hole job_focus direkt mit der cashier.id (ist bereits hr.employee ID) + let jobFocus = ''; + try { + const employees = await this.pos.data.searchRead('hr.employee', [['id', '=', cashier.id]], ['job_focus']); + console.log('[POS] Employee search for employee_id', cashier.id, ':', employees); + if (employees && employees.length > 0) { + jobFocus = employees[0].job_focus || ''; + console.log('[POS] job_focus loaded:', jobFocus); + } + } catch (error) { + console.error('[POS] Fehler beim Laden von job_focus:', error); + } + + // Verwende hr.employee.public mit cashier.id const employeeData = { employee_id: cashier.id, employee_name: cashier.name, - employee_image_url: `/web/image/hr.employee.public/${cashier.id}/avatar_1920` + employee_image_url: `/web/image/hr.employee.public/${cashier.id}/avatar_1920`, + employee_job_focus: jobFocus, }; + + console.log('[POS] Sending to Customer Display:', employeeData); // Füge employeeData zur Order-Display-Data hinzu const selectedOrder = this.pos.get_order(); 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 62f35c9..890f505 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 @@ -8,34 +8,45 @@
- - + +
+ +
+ +
+ + +
+
An der Theke für Dich
+
Werkstattaufsicht und Werkzeugausgabe
+ + + +
+ +
+ +
Themenschwerpunkte:
+
+
+ + + +
- + - - - - - -
-
- -
-