Compare commits

...

2 Commits

Author SHA1 Message Date
1f59e16b26 working with pos sidebar, but no content 2025-06-28 21:58:58 +02:00
59e4b19dee almost done for POS 2025-06-28 18:49:49 +02:00
12 changed files with 76 additions and 290 deletions

View File

@ -21,14 +21,15 @@
'open_workshop/static/src/css/category_color.css',
],
'point_of_sale._assets_pos': [
'open_workshop/static/src/js/product_screen_template_patch.js',
'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/js/ows_pos_customer_sidebar.js',
'open_workshop/static/src/js/ows_pos_sidebar.js',
'open_workshop/static/src/js/ows_product_screen_template_patch.js',
'open_workshop/static/src/xml/ows_machine_access_list.xml',
'open_workshop/static/src/xml/ows_product_screen.xml',
'open_workshop/static/src/xml/ows_pos_customer_sidebar.xml',
'open_workshop/static/src/xml/ows_pos_sidebar.xml',
'open_workshop/static/src/xml/ows_product_screen_template_patch.xml',
],
},
'description': """

View File

@ -1,9 +1,10 @@
// @odoo-module
// @odoo-module ows_machine_access_list.js
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";
import { rpc } from "@web/core/network/rpc";
import { registry } from "@web/core/registry";
export class OwsMachineAccessList extends Component {
static template = 'open_workshop.OwsMachineAccessList';
@ -43,8 +44,10 @@ export class OwsMachineAccessList extends Component {
}
try {
const data = await jsonrpc("/open_workshop/partner_access", {
partner_id: partner.id,
const data = await rpc("/open_workshop/partner_access", {
params: {
partner_id: partner.id,
},
});
this.state.grouped_accesses = data.access_by_area || [];
@ -60,5 +63,12 @@ export class OwsMachineAccessList extends Component {
this.state.rfid_card = '';
this.state.birthday = '';
}
}
}
registry.category("templates").add("open_workshop.OwsMachineAccessList", OwsMachineAccessList);

View File

@ -1,10 +1,11 @@
// @odoo-module
// @odoo-module ows_pos_customer_sidebar.js
import { Component } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
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 { ask } from "@web/core/confirmation_dialog/confirmation_dialog";
import { deserializeDateTime, formatDateTime, parseDateTime } from "@web/core/l10n/dates";
export class OwsPosCustomerSidebar extends Component {
@ -12,8 +13,7 @@ export class OwsPosCustomerSidebar extends Component {
setup() {
this.pos = usePos();
this.popup = useService("popup");
this.dialog = useService("dialog");
}
addOrder() {
@ -28,11 +28,12 @@ export class OwsPosCustomerSidebar extends Component {
if (!order) return;
// 🛑 Sicherheitsabfrage: Order enthält bereits Positionen
// Hinweis: Verhalten angelehnt an ticket_screen.js in Odoo 17.0
if (order.get_orderlines().length > 0) {
const { confirmed } = await this.popup.add(ConfirmPopup, {
const confirmed = await ask(this.dialog, {
title: _t("Order enthält Positionen"),
body: _t("Möchtest du diese Order wirklich löschen?"),
confirmText: _t("Löschen"),
cancelText: _t("Abbrechen"),
});
if (!confirmed) return;
}
@ -45,10 +46,9 @@ export class OwsPosCustomerSidebar extends Component {
// ✅ Wenn noch andere Orders existieren, eine davon aktivieren
if (remainingOrders.length > 0) {
this.pos.set_order(remainingOrders[remainingOrders.length - 1]); // z.B. letzte Order aktivieren
this.pos.set_order(remainingOrders[remainingOrders.length - 1]);
}
// 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
}
@ -57,7 +57,8 @@ export class OwsPosCustomerSidebar extends Component {
}
getFilteredOrderList() {
return this.pos.get_order_list();
//return this.pos.get_order_list();
return this.pos.orders ? this.pos.orders.filter(order => order.get_partner()) : [];
}
getDate(order) {
@ -69,13 +70,14 @@ export class OwsPosCustomerSidebar extends Component {
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
this.env.bus.trigger('partner-changed');
}
}

View File

@ -0,0 +1,11 @@
// ows_pos_sidebar.js
// @odoo-module
import { Component } from "@odoo/owl";
import { OwsPosCustomerSidebar } from "./ows_pos_customer_sidebar";
import { OwsMachineAccessList } from "./ows_machine_access_list";
export class OwsPosSidebar extends Component {
static template = "open_workshop.OwsPosSidebar";
static components = { OwsPosCustomerSidebar, OwsMachineAccessList };
}

View File

@ -0,0 +1,15 @@
// product_screen_template_patch.js
// @odoo-module
import { registry } from "@web/core/registry";
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
import { OwsPosSidebar } from "./ows_pos_sidebar";
class OwsProductScreen extends ProductScreen {
static components = Object.assign({}, ProductScreen.components, {
OwsPosSidebar,
});
}
registry.category("pos_screens").remove("ProductScreen");
registry.category("pos_screens").add("ProductScreen", OwsProductScreen);

View File

@ -1,18 +0,0 @@
// @odoo-module
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,
};
}
registry.category("pos_screens").remove("ProductScreen");
registry.category("pos_screens").add("ProductScreen", OwsProductScreen);

View File

@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- overwrite client details (view) -->
<t t-extend="ClientDetails">
<t t-jquery=".client-details-left" t-operation="replace">
<div class='client-details-left'>
<div class='client-detail'>
<span class='label'>Address</span>
<t t-if='partner.address'>
<span class='detail client-address'>
<t t-esc='partner.address' />
</span>
</t>
<t t-if='!partner.address'>
<span class='detail client-address empty'>N/A</span>
</t>
</div>
<div class='client-detail'>
<span class='label'>Email</span>
<t t-if='partner.email'>
<span class='detail client-email'>
<t t-esc='partner.email' />
</span>
</t>
<t t-if='!partner.email'>
<span class='detail client-email empty'>N/A</span>
</t>
</div>
<div class='client-detail'>
<span class='label'>Geburtstag</span>
<t t-if='partner.birthday'>
<span class='detail client-vvow_birthday'>
<t t-esc='partner.birthday' />
</span>
</t>
<t t-if='!partner.birthday'>
<span class='detail client-vvow_birthday empty'>N/A</span>
</t>
</div>
<div class='client-detail'>
<span class='label'>Phone</span>
<t t-if='partner.phone'>
<span class='detail client-phone'>
<t t-esc='partner.phone' />
</span>
</t>
<t t-if='!partner.phone'>
<span class='detail client-phone empty'>N/A</span>
</t>
</div>
<div t-attf-class='client-detail #{widget.pos.pricelists.length &lt;= 1 ? "oe_hidden" : ""}'>
<span class='label'>Pricelist</span>
<t t-if='partner.property_product_pricelist'>
<span class='detail property_product_pricelist'>
<t t-esc='partner.property_product_pricelist[1]'/>
</span>
</t>
<t t-if='!partner.property_product_pricelist'>
<span class='detail property_product_pricelist empty'>N/A</span>
</t>
</div>
<div class='client-detail'>
<t t-if='!partner.security_briefing'><span class='detail client-details-vvow_sec_briefing_error'>Haftungsausschluss prüfen!</span></t>
</div>
</div>
</t>
<t t-jquery=".client-details-right" t-operation="replace"/>
</t>
</templates>

View File

@ -1,64 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- overwrite client details (view) -->
<t t-extend="ClientDetailsEdit">
<t t-jquery=".client-details-left" t-operation="replace">
<div class='client-details-left'>
<div class='client-detail'>
<span class='label'>Street</span>
<input class='detail client-address-street' name='street' t-att-value='partner.street || ""' placeholder='Street'></input>
</div>
<div class='client-detail'>
<span class='label'>Postcode</span>
<input class='detail client-address-zip' name='zip' t-att-value='partner.zip || ""' placeholder='ZIP'></input>
</div>
<div class='client-detail'>
<span class='label'>City</span>
<input class='detail client-address-city' name='city' t-att-value='partner.city || ""' placeholder='City'></input>
</div>
<div class='client-detail'>
<span class='label'>Country</span>
<select class='detail client-address-country needsclick' name='country_id'>
<option value=''>None</option>
<t t-foreach='widget.pos.countries' t-as='country'>
<option t-att-value='country.id' t-att-selected="partner.country_id ? ((country.id === partner.country_id[0]) ? true : undefined) : undefined">
<t t-esc='country.name'/>
</option>
</t>
</select>
</div>
<div class='client-detail'>
<span class='label'>Email</span>
<input class='detail client-email' name='email' type='email' t-att-value='partner.email || ""'></input>
</div>
<div class='client-detail'>
<span class='label'>Phone</span>
<input class='detail client-phone' name='phone' type='tel' t-att-value='partner.phone || ""'></input>
</div>
<div class='client-detail'>
<span class='label'>Geburtstag</span>
<input class='detail client-birthday' name='birthday' type='date' t-att-value='partner.birthday || ""'></input>
</div>
<div t-attf-class='client-detail #{widget.pos.pricelists.length &lt;= 1 ? "oe_hidden" : ""}'>
<span class='label'>Pricelist</span>
<select class='detail needsclick' name='property_product_pricelist'>
<t t-foreach='widget.pos.pricelists' t-as='pricelist'>
<option t-att-value='pricelist.id' t-att-selected="partner.property_product_pricelist ? (pricelist.id === partner.property_product_pricelist[0] ? true : undefined) : undefined">
<t t-esc='pricelist.display_name'/>
</option>
</t>
</select>
</div>
<div class='client-detail'>
<span class='label'>Haftungsauschschluß</span>
<select class='detail client-vvow_security_briefing-states needsclick' name='security_briefing'>
<option value='true' t-att-selected="partner.security_briefing ? true : undefined">Ja</option>
<option value='' t-att-selected="!partner.security_briefing ? true: undefined">Nein</option>
</select>
</div>
</div>
</t>
<t t-jquery=".client-details-right" t-operation="replace"/>
</t>
</templates>

View File

@ -1,70 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- Test replace logo -->
<t t-extend="Chrome">
<t t-jquery=".pos-branding" t-operation="replace">
<img style="max-height:48px; max-width: 100%; width:auto" src="/web/binary/company_logo" alt="HOBBYHIMMEL"/>
</t>
</t>
<!-- Remove old order-selector container -->
<t t-extend="Chrome">
<t t-jquery=".placeholder-OrderSelectorWidget" t-operation="replace">
</t>
</t>
<!-- insert order-selector container in new position -->
<t t-extend="Chrome">
<t t-jquery=".pos" t-operation="prepend">
<div class="placeholder-OrderSelectorWidget"></div>
</t>
</t>
<!-- completly overwrite OrderSelectorWidget -->
<t t-name="OrderSelectorWidget">
<div class="order-selector" >
<div class="new-order-button">
<span class="order-button square neworder-button">
<i class='fa fa-plus' role="img" aria-label="New order" title="New order"/>
</span>
<span class="order-button square deleteorder-button">
<i class='fa fa-minus' role="img" aria-label="Delete order" title="Delete order"/>
</span>
</div>
<span class="orders touch-scrollable">
<t t-foreach="widget.pos.get_order_list()" t-as="order">
<t t-if="order === widget.pos.get_order()">
<span class="order-button select-order selected" t-att-title="order.sequence_number" t-att-data-uid="order.uid">
<span class="order-time">
<t t-esc="moment(order.creation_date).format('HH:mm')"/>
</span>
<span class="order-customer">
<t t-if="order.get_client()">
<t t-esc="order.get_client().name" />
</t>
<t t-if="!order.get_client()">
?
</t>
</span>
</span>
</t>
<t t-if="order !== widget.pos.get_order()">
<span class="order-button select-order" t-att-title="order.sequence_number" t-att-data-uid="order.uid">
<span class="order-time">
<t t-esc="moment(order.creation_date).format('HH:mm')"/>
</span>
<span class="order-customer">
<t t-if="order.get_client()">
<t t-esc="order.get_client().name" />
</t>
<t t-if="!order.get_client()">
?
</t>
</span>
</span>
</t>
</t>
</span>
</div>
</t>
</templates>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="open_workshop.OwsPosSidebar" owl="1">
<div class="custompane d-flex flex-column border-end bg-200">
<OwsPosCustomerSidebar />
<OwsMachineAccessList />
</div>
</t>
</templates>

View File

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="open_workshop.ProductScreen" owl="1">
<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'"
t-att-class="{'flex-grow-1': ui.isSmall}"
class="leftpane d-flex flex-column border-end bg-200" >
<OrderWidget lines="currentOrder.orderlines" t-slot-scope="scope"
total="env.utils.formatCurrency(currentOrder.get_total_with_tax())"
tax="!env.utils.floatIsZero(currentOrder.get_total_tax()) and env.utils.formatCurrency(currentOrder.get_total_tax()) or ''">
<t t-set="line" t-value="scope.line" />
<Orderline line="line.getDisplayData()"
t-on-click="() => this.selectLine(line)"
class="{ ...line.getDisplayClasses(), 'selected' : line.selected }"/>
<t t-set-slot="details" />
</OrderWidget>
<div class="pads border-top">
<div class="control-buttons d-flex flex-wrap border-bottom overflow-hidden bg-300" t-if="!ui.isSmall">
<t t-foreach="controlButtons" t-as="cb" t-key="cb.name">
<t t-component="cb.component" t-key="cb.name"/>
</t>
</div>
<div class="subpads d-flex">
<ActionpadWidget partner="partner" actionName="constructor.numpadActionName" actionType="'payment'" onClickMore.bind="displayAllControlPopup" />
<Numpad buttons="getNumpadButtons()" onClick.bind="onNumpadClick" class="'w-100'"/>
</div>
</div>
</div>
<div class="rightpane overflow-auto d-flex flex-grow-1 flex-column bg-300 w-60" t-if="!ui.isSmall || pos.mobile_pane === 'right'">
<ProductsWidget />
<t t-if="ui.isSmall">
<div class="product-reminder d-flex justify-content-center align-items-center py-1 text-bg-warning bg-opacity-50 fw-bolder"
t-if="currentOrder.get_selected_orderline() and currentOrder.hasJustAddedProduct" t-key="animationKey" >
<span><t t-esc="selectedOrderlineQuantity"/> <t t-esc="selectedOrderlineDisplayName"/> <t t-esc="selectedOrderlineTotal"/></span>
</div>
</t>
</div>
</div>
</t>
</templates>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="point_of_sale.ProductScreen" t-inherit="point_of_sale.ProductScreen" t-inherit-mode="extension">
<xpath expr="//div[contains(@class, 'leftpane')]" position="before">
<OwsPosSidebar />
</xpath>
</t>
</templates>