17.0_dev-target #1

Merged
matthias.lotz merged 3 commits from 17.0_dev-target into 17.0_dev 2025-06-03 17:56:19 +02:00
11 changed files with 200 additions and 232 deletions

View File

@ -23,6 +23,8 @@
'open_workshop/static/src/css/pos.css',
'open_workshop/static/src/js/ows_pos_customer_sidebar.js',
'open_workshop/static/src/xml/ows_pos_customer_sidebar.xml',
'open_workshop/static/src/js/ows_machine_access_list.js',
'open_workshop/static/src/xml/ows_machine_access_list.xml',
'open_workshop/static/src/xml/ows_product_screen.xml',
],
},

View File

@ -424,10 +424,10 @@ class OwsMachine(models.Model):
@api.model
def get_access_list_grouped(self, partner_id):
partner = self.env['res.partner'].browse(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 = []
access_by_area = []
for area in areas:
machines = self.search([('area_id', '=', area.id)], order="name")
machine_list = []
@ -440,12 +440,22 @@ class OwsMachine(models.Model):
'name': machine.name,
'has_access': has_access,
})
res.append({
'area': area.name,
'color_hex': area.color_hex or '#000000',
'machines': machine_list
})
return res
if machine_list:
access_by_area.append({
'area': area.name,
'color_hex': area.color_hex or '#000000',
'machines': machine_list
})
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):

View File

@ -1,3 +1,24 @@
.ows-sidebar { width: 220px; }
.order-entry:hover { cursor: pointer; }
.order-entry.selected { background-color: #007bff; color: white; }
.sidebar-line {
display: flex;
justify-content: space-between;
gap: 0.5em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0.2em 0;
}
.sidebar-date {
flex-shrink: 0;
}
.sidebar-name {
flex-shrink: 1;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -1,132 +0,0 @@
/* This file is based on the original code from the OrderWidget in Odoo Point of Sale module (screen.js).
* It has been modified to create a sidebar that displays machine access information for the selected customer.*/
odoo.define('open_workshop.machine_access_sidebar', function (require) {
"use strict";
const DUMMY_PARTNER = {
id: 1,
name: "AAAA Max Mustermann",
security_briefing: false,
security_id: null,
create_date: null,
};
var rpc = require('web.rpc');
var screens = require('point_of_sale.screens');
var chrome = require('point_of_sale.chrome');
var core = require('web.core');
var QWeb = core.qweb;
var MachineAccessSidebar = screens.ScreenWidget.extend({
template: 'MachineAccessSidebar',
init: function(parent, options) {
this._super(parent, options);
this.partner = null;
this.pos.bind('change:selectedOrder', this.bind_order_events, this);
},
show: function() {
this._super();
this.render_access();
},
update_machine_access: function(partner) {
console.log("🔁 Sidebar aktualisiert Maschinenfreigaben für Partner:", partner);
this.partner = partner;
this.render_access();
},
render_access: function() {
var self = this;
var partner = this.partner || DUMMY_PARTNER;
rpc.query({
model: 'ows.machine',
method: 'get_access_list_grouped',
args: [partner.id],
}).then(function (result) {
partner.create_date = partner.create_date && partner.create_date.substring(0, 10);
var html = QWeb.render('PartnerMachineAccessList', {
areas: result || [],
partner: partner,
});
self.$('.access-content').html(html);
});
},
bind_order_events: function () {
var order = this.pos.get_order();
if (!order) return;
/*order.unbind('change:client', this);
order.bind('change:client', this, function () {
this.update_machine_access(order.get_client());
});*/
this.update_machine_access(order.get_client());
}
});
// Sidebar aktualisieren bei Klick im ClientListScreenWidget (Vorschau)
screens.ClientListScreenWidget.include({
show: function () {
this._super();
$('.order-selector').hide(); // ← wird hier versteckt
},
hide: function () {
this._super();
$('.order-selector').show(); // ← beim Verlassen wieder zeigen
},
display_client_details: function (visibility, partner, clickpos) {
this._super(visibility, partner, clickpos);
try {
if (partner && typeof partner === 'object' && 'id' in partner) {
var sidebar = this.pos.chrome.sidebar_widget;
if (sidebar) {
console.log("👤 ClientListScreen: Vorschau für", partner.name);
sidebar.update_machine_access(partner);
}
}
} catch (e) {
console.warn("⚠️ Fehler beim Update der Sidebar nach Kundenauswahl:", e);
}
}
});
chrome.Chrome.include({
build_widgets: function () {
this._super();
var sidebar = new MachineAccessSidebar(this, {});
this.sidebar_widget = sidebar;
sidebar.appendTo(this.$el);
//this.pos.bind('change:selectedOrder', sidebar.bind_order_events, sidebar);
if (this.pos.get_order()) {
sidebar.bind_order_events();
}
}
});
});
odoo.define('open_workshop.models', function (require) {
"use strict";
var models = require('point_of_sale.models');
var field_utils = require('web.field_utils');
models.load_fields('res.partner', 'create_date');
models.load_fields('res.partner', 'birthday');
models.load_fields('res.partner', 'security_briefing');
models.load_fields('res.partner', 'security_id');
models.load_fields('res.partner', 'rfid_card');
});

View File

@ -0,0 +1,63 @@
// @odoo-module
import { Component, useState } from "@odoo/owl";
import { useBus } from "@web/core/utils/hooks";
import { usePos } from "@point_of_sale/app/store/pos_hook";
import { jsonrpc } from "@web/core/network/rpc_service";
export class OwsMachineAccessList extends Component {
static template = 'open_workshop.OwsMachineAccessList';
setup() {
this.pos = usePos();
this.state = useState({
grouped_accesses: [],
security_briefing: false,
security_id: '',
rfid_card: '',
birthday: '',
});
// 🔁 Reagiere auf Partnerwechsel über den Odoo-Bus
useBus(this.env.bus, 'partner-changed', () => {
this.updateAccessList();
});
// 🔃 Beim Mounten initiale Daten laden
this.updateAccessList();
}
async updateAccessList() {
const order = this.pos.get_order();
const partner = order?.get_partner?.();
if (!partner) {
this.state.grouped_accesses = [];
this.state.security_briefing = false;
this.state.security_id = '';
this.state.rfid_card = '';
this.state.birthday = '';
return;
}
try {
const data = await jsonrpc("/open_workshop/partner_access", {
partner_id: partner.id,
});
this.state.grouped_accesses = data.access_by_area || [];
this.state.security_briefing = data.security_briefing;
this.state.security_id = data.security_id;
this.state.rfid_card = data.rfid_card;
this.state.birthday = data.birthday;
} catch (error) {
console.error("Fehler beim Laden der Einweisungen:", error);
this.state.grouped_accesses = [];
this.state.security_briefing = false;
this.state.security_id = '';
this.state.rfid_card = '';
this.state.birthday = '';
}
}
}

View File

@ -5,6 +5,7 @@ import { useService } from "@web/core/utils/hooks";
import { usePos } from "@point_of_sale/app/store/pos_hook";
import { _t } from "@web/core/l10n/translation";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
import { deserializeDateTime, formatDateTime, parseDateTime } from "@web/core/l10n/dates";
export class OwsPosCustomerSidebar extends Component {
static template = "open_workshop.OwsPosCustomerSidebar";
@ -12,11 +13,13 @@ export class OwsPosCustomerSidebar extends Component {
setup() {
this.pos = usePos();
this.popup = useService("popup");
}
addOrder() {
this.pos.add_new_order(); // neue Order wird aktive Order
this.pos.selectPartner();
this.env.bus.trigger('partner-changed'); // ✅ korrektes Event feuern
}
async removeCurrentOrder() {
@ -45,6 +48,7 @@ export class OwsPosCustomerSidebar extends Component {
}
// Hinweis: Weitere Funktionen wie Sync mit Server (siehe ticket_screen.js) können hier ergänzt werden.
this.env.bus.trigger('partner-changed'); // ✅ korrektes Event feuern
}
openTicketScreen() {
@ -56,15 +60,21 @@ export class OwsPosCustomerSidebar extends Component {
}
getDate(order) {
const date = new Date(order.creationDate || order.creation_date || Date.now());
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const date = new Date(order.date_order);
const dd = String(date.getDate()).padStart(2, '0');
const mm = String(date.getMonth() + 1).padStart(2, '0');
const hh = String(date.getHours()).padStart(2, '0');
const mi = String(date.getMinutes()).padStart(2, '0');
return `${dd}.${mm}. ${hh}:${mi}`;
}
getPartner(order) {
return order.get_partner()?.name || "Kein Kunde";
}
selectOrder(order) {
this.pos.set_order(order);
this.env.bus.trigger('partner-changed'); // ✅ korrektes Event feuern
}
}

View File

@ -3,12 +3,14 @@
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
import { registry } from "@web/core/registry";
import { OwsPosCustomerSidebar } from "@open_workshop/js/ows_pos_customer_sidebar";
import { OwsMachineAccessList } from "@open_workshop/js/ows_machine_access_list";
export class OwsProductScreen extends ProductScreen {
static template = "open_workshop.ProductScreen";
static components = {
...ProductScreen.components,
OwsPosCustomerSidebar,
OwsMachineAccessList,
};
}

View File

@ -0,0 +1,76 @@
<t t-name="open_workshop.OwsMachineAccessList">
<div class="client-details-grid p-2 small">
<!-- ✅ Sicherheitsbereich -->
<t t-if="state.client">
<div class="client-details-header">
<ul>
<li><span class="client-details-label">Einweisungen</span></li>
</ul>
<div class="client-details-area border" t-att-style="'border: solid 3px #ffffff; margin: 5px;'">
<ul>
<li class="client-detail">
<span class="detail client-details-vvow_briefing"></span>
<span class="briefinglabel">Werkstatt</span>
</li>
<li class="client-detail">
<t t-if="!state.security_briefing">
<span class="detail client-details-vvow_briefing_error"></span>
</t>
<t t-if="state.security_briefing">
<span class="detail client-details-vvow_briefing"></span>
</t>
<span class="briefinglabel">Haftungsausschluss</span>
</li>
<t t-if="!state.security_briefing">
<li class="client-detail">
<ul class="subpoints">
<span class="detail client-details-vvow_sec_briefing_error">Bitte Prüfen‼</span>
</ul>
</li>
</t>
<t t-if="state.security_briefing">
<ul class="subpoints">
<li class="client-detail">
<span class="label">Id:</span>
<span class="detail client-details-vvow_security_id">
<t t-esc="state.security_id || 'N/A'" />
</span>
</li>
<li class="client-detail">
<span class="label">Geburtstag:</span>
<span class="detail client-details-vvow_security_id">
<t t-esc="state.birthday || 'N/A'" />
</span>
</li>
</ul>
</t>
</ul>
</div>
</div>
</t>
<!-- ✅ Maschinenliste: immer sichtbar, gefiltert -->
<t t-foreach="state.grouped_accesses" t-as="area" t-key="area.area">
<t t-if="area.machines.length > 0">
<div class="client-details-area" t-att-style="'border: solid 3px ' + area.color_hex + '; margin: 5px;'">
<ul>
<t t-foreach="area.machines" t-as="machine" t-key="machine.name">
<li class="client-detail">
<span t-attf-class="detail {{ machine.has_access ? 'client-details-vvow_briefing' : 'client-details-vvow_briefing_error' }}">
<t t-esc="machine.has_access ? '✅' : '❌'" />
</span>
<span class="briefinglabel"><t t-esc="machine.name"/></span>
</li>
</t>
</ul>
</div>
</t>
</t>
</div>
</t>

View File

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="MachineAccessSidebar">
<div class="machine-access-sidebar">
<div class="access-content">
</div>
</div>
</t>
<t t-name="PartnerMachineAccessList">
<div class="client-details-grid">
<div class="client-details-header">
<ul>
<li><span class="client-details-label">Einweisungen</span></li>
</ul>
<div class="client-details-area" t-att-style="'border: solid 3px #ffffff; margin: 5px;'">
<ul>
<li class="client-detail">
<span class="detail client-details-vvow_briefing"></span>
<span class="briefinglabel">Werkstatt</span>
</li>
<li class="client-detail">
<t t-if="!partner.security_briefing">
<span class="detail client-details-vvow_briefing_error"></span>
</t>
<t t-if="partner.security_briefing">
<span class="detail client-details-vvow_briefing"></span>
</t>
<span class="briefinglabel">Haftungsausschluss</span>
</li>
<t t-if="!partner.security_briefing">
<li class="client-detail">
<ul class="subpoints"><span class="detail client-details-vvow_sec_briefing_error">Bitte Prüfen‼</span></ul>
</li>
</t>
<t t-if="partner.security_briefing">
<ul class="subpoints">
<li class="client-detail">
<span class="label">Id:</span>
<t t-if="partner.security_id">
<span class="detail client-details-vvow_security_id"><t t-esc="partner.security_id"/></span>
</t>
<t t-if="!partner.security_id">
<span class="detail client-details-vvow_security_id">N/A</span>
</t>
</li>
<li class="client-detail">
<span class="label">Erstellt:</span>
<t t-if="partner.create_date">
<span class="detail client-details-vvow_security_id"><t t-esc="partner.create_date"/></span>
</t>
<t t-if="!partner.create_date">
<span class="detail client-vvow_birthday">N/A</span>
</t>
</li>
</ul>
</t>
</ul>
</div>
</div>
<t t-foreach="areas" t-as="area">
<div class="client-details-area" t-att-style="'border: solid 3px ' + area.color_hex + '; margin: 5px;'">
<ul>
<t t-foreach="area.machines" t-as="machine">
<li class="client-detail">
<t t-if="!machine.has_access">
<span class="detail client-details-vvow_briefing_error"></span>
</t>
<t t-if="machine.has_access">
<span class="detail client-details-vvow_briefing"></span>
</t>
<span class="briefinglabel"><t t-esc="machine.name"/></span>
</li>
</t>
</ul>
</div>
</t>
</div>
</t>
</templates>

View File

@ -15,7 +15,10 @@
t-att-class="order === pos.get_order() ? 'bg-primary text-white' : 'bg-white'"
t-on-click="() => selectOrder(order)"-->
<div t-att-class="'order-entry' + (order === pos.get_order() ? ' selected' : '')" t-on-click="() => this.selectOrder(order)">
<div><t t-esc="getDate(order)"/> <t t-esc="getPartner(order)"/></div>
<div class="sidebar-line">
<span class="sidebar-date"><t t-esc="getDate(order)"/></span>
<span class="sidebar-name" t-att-title="getPartner(order)"><t t-esc="getPartner(order)"/></span>
</div>
</div>
</t>
</div>

View File

@ -4,6 +4,7 @@
<div class="product-screen d-flex h-100 bg-100" t-att-class="{ 'd-none': !props.isShown }">
<div class="custompane d-flex flex-column border-end bg-200">
<OwsPosCustomerSidebar />
<OwsMachineAccessList />
</div>
<div t-if="!ui.isSmall || pos.mobile_pane === 'left'"