[FIX] point_of_sale,pos_restaurant: ui issues
In this PR we fix a number of small issues related to the pos ui. In addition to the specific fixes, this PR also simplifies many of the components, most notably the `category_selector`. In the interest of providing a cleaner API, this PR introduces a new folder called `generic_components` whose goal is to contain stateless components. These stateless components would allow for more reusability, while simplifying the logic. The `category_selector` is the first component in this folder. Descriptions of each of the issues fixed in this PR can be found in the closes odoo/odoo#131038 Tasks: 3457213 Related: odoo/enterprise#45600 Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="l10n_fr_pos_cert.OrderLinesReceipt" t-inherit="point_of_sale.OrderLinesReceipt" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//t[@t-foreach='receipt.orderlines']" position="inside">
|
||||
<xpath expr="//div[@t-foreach='receipt.orderlines']" position="inside">
|
||||
<t t-if="receipt.l10n_fr_hash !== false and line.price_type === 'manual'">
|
||||
<div class="pos-receipt-right-padding">
|
||||
Old unit price:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="l10n_in_pos.OrderLinesReceipt" t-inherit="point_of_sale.OrderLinesReceipt" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//t[@t-foreach='receipt.orderlines']" position="inside">
|
||||
<xpath expr="//div[@t-foreach='receipt.orderlines']" position="inside">
|
||||
<t t-if="line.l10n_in_hsn_code and pos.company.country and pos.company.country.code == 'IN'">
|
||||
<div class="pos-receipt-left-padding">
|
||||
<span>HSN Code: </span>
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* @typedef {Object} Category
|
||||
* @property {number} id
|
||||
* @property {string?} name
|
||||
* @property {string?} icon
|
||||
* @property {string?} separator
|
||||
* @property {string?} imageUrl
|
||||
*/
|
||||
export class CategorySelector extends Component {
|
||||
static template = "point_of_sale.CategorySelector";
|
||||
static props = {
|
||||
categories: {
|
||||
type: Array,
|
||||
element: Object,
|
||||
shape: {
|
||||
id: Number,
|
||||
name: { type: String, optional: true },
|
||||
icon: { type: String, optional: true },
|
||||
separator: { type: String, optional: true },
|
||||
imageUrl: { type: String, optional: true },
|
||||
},
|
||||
},
|
||||
class: { type: String, optional: true },
|
||||
onClick: { type: Function },
|
||||
};
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="point_of_sale.CategorySelector" owl="1">
|
||||
<div t-attf-class="{{props.class or ''}} d-flex overflow-auto">
|
||||
<div t-foreach="props.categories" t-as="category" t-key="category.id"
|
||||
class="d-flex align-items-center">
|
||||
<i t-if="category.separator"
|
||||
t-attf-class="px-2 fa {{category.separator}}"
|
||||
aria-hidden="true"/>
|
||||
<button class="category-button btn btn-light h-100 fw-bolder"
|
||||
t-on-click="() => props.onClick(category.id)">
|
||||
<div t-if="category.imageUrl"
|
||||
class="category-img">
|
||||
<img t-att-src="category.imageUrl" alt="Category" />
|
||||
</div>
|
||||
<i t-if="category.icon"
|
||||
t-attf-class="fa {{category.icon}}"
|
||||
aria-hidden="true"/>
|
||||
<t t-esc="category.name" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -6,7 +6,7 @@
|
||||
class="order-button floor-button d-flex align-items-center gap-2 px-4 fw-bolder cursor-pointer"
|
||||
t-on-click="backToFloorScreen"
|
||||
>
|
||||
<i class="fa fa-2x fa-angle-left" role="img" aria-label="Go Back" title="Go Back" />
|
||||
<i class="fa fa-2x fa-angle-left pb-1" role="img" aria-label="Go Back" title="Go Back" />
|
||||
<span t-if="!ui.isSmall">BACK</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
@@ -6,27 +6,20 @@
|
||||
margin: 0;
|
||||
background-color: #f0eeee;
|
||||
color: $gray-700;
|
||||
/* Some elements inside .pos are allowed to be moved/resized. When
|
||||
* this 'move/resize' event is performed in touch devices, .pos
|
||||
* element also tries to scroll (as the default action of touchstart+
|
||||
* touchmove events).
|
||||
* Position is fixed to prevent the movement of .pos element during
|
||||
* the described touch events.
|
||||
*/
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
/* ********* Generic element styling ********* */
|
||||
|
||||
.dvh-100 {
|
||||
// we specify the 100vh as a fallback in case the browser does not
|
||||
// know dvh
|
||||
height: 100vh !important;
|
||||
height: 100dvh !important;
|
||||
}
|
||||
|
||||
.pos a {
|
||||
text-decoration: none;
|
||||
color: $gray-700;
|
||||
@@ -59,9 +52,6 @@ td {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.oe_hidden{
|
||||
display: none !important;
|
||||
}
|
||||
.oe_invisible{
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.Chrome" owl="1">
|
||||
<div class="pos vh-100 d-flex flex-column">
|
||||
<div class="pos dvh-100 d-flex flex-column">
|
||||
<div class="pos-receipt-print"></div>
|
||||
<Navbar />
|
||||
<div class="pos-content w-100 h-100 bg-200 overflow-auto">
|
||||
<div class="pos-content flex-grow-1 overflow-auto w-100 bg-200">
|
||||
<!-- FIXME POSREF: better error handling in main screens (currently, a crash in owl lifecycle of a main screen blows up the application and the error can't be displayed) -->
|
||||
<t isShown="!pos.tempScreen" t-component="pos.mainScreen.component"
|
||||
t-props="pos.mainScreen.props"/>
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ export class PartnerDetailsEdit extends Component {
|
||||
}
|
||||
/**
|
||||
* Loading image is converted to a Promise to allow await when
|
||||
* loading an image. It resolves to the loaded image if succesful,
|
||||
* loading an image. It resolves to the loaded image if successful,
|
||||
* else, resolves to false.
|
||||
*
|
||||
* [Source](https://stackoverflow.com/questions/45788934/how-to-turn-this-callback-into-a-promise-using-async-await)
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
</t>
|
||||
<input type="file" class="image-uploader" t-on-change="uploadImage" />
|
||||
</div>
|
||||
<input class="detail partner-name form-control form-control-lg" name="name" t-att-value="props.partner.name or ''" placeholder="Name" t-on-change="captureChange" />
|
||||
<input class="detail partner-name form-control form-control-lg" t-model="changes.name" name="name" placeholder="Name" />
|
||||
</div>
|
||||
<div class="partner-details-box row row-cols-1 row-cols-sm-2 gy-3 mt-3">
|
||||
<t t-foreach="['Street', 'City', 'Zip', 'Email', 'Phone', 'Mobile', 'Barcode' ]" t-as="item" t-key="item">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="point_of_sale.PaymentScreen" owl="1">
|
||||
<t t-if="ui.isSmall">
|
||||
<div class="payment-screen screen d-flex flex-column h-100 bg-100" t-att-class="{ oe_hidden: !props.isShown }">
|
||||
<div class="payment-screen screen d-flex flex-column h-100 bg-100" t-att-class="{ 'd-none': !props.isShown }">
|
||||
<PaymentScreenStatus order="currentOrder" />
|
||||
<t t-call="point_of_sale.PaymentScreenMethods" />
|
||||
<t t-call="point_of_sale.PaymentScreenButtons" />
|
||||
@@ -11,7 +11,7 @@
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="payment-screen screen d-flex flex-column h-100 bg-100" t-att-class="{ oe_hidden: !props.isShown }">
|
||||
<div class="payment-screen screen d-flex flex-column h-100 bg-100" t-att-class="{ 'd-none': !props.isShown }">
|
||||
<t t-call="point_of_sale.PaymentScreenTop" />
|
||||
<div class="main-content d-flex overflow-auto h-100">
|
||||
<div class="left-content d-flex flex-column w-25 bg-400">
|
||||
|
||||
@@ -24,4 +24,7 @@ export class ActionpadWidget extends Component {
|
||||
get highlightPay() {
|
||||
return this.pos.get_order()?.orderlines?.length;
|
||||
}
|
||||
getMainButtonClasses() {
|
||||
return "button btn d-flex flex-column flex-fill align-items-center justify-content-center fw-bolder btn-lg py-5 rounded-0";
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.ActionpadWidget" owl="1">
|
||||
<div class="actionpad d-flex flex-column flex-grow-1 mw-50 p-0 border-end">
|
||||
<div class="actionpad d-flex flex-column flex-grow-1 p-0 border-end">
|
||||
<button class="button mobile-more-button btn btn-secondary flex-fill border-bottom" t-if="ui.isSmall and props.onClickMore" t-on-click="props.onClickMore">
|
||||
<span>More...</span>
|
||||
</button>
|
||||
@@ -20,10 +20,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="button pay validation pay-order-button btn btn-primary btn-lg py-5 rounded-0 highlight d-flex flex-column flex-fill align-items-center justify-content-center fw-bolder"
|
||||
<button class="pay validation pay-order-button btn-primary"
|
||||
t-attf-class="{{getMainButtonClasses()}}"
|
||||
t-att-class="{ 'with-more-button': props.onClickMore and ui.isSmall }"
|
||||
t-on-click="props.actionToTrigger ? this.props.actionToTrigger : () => pos.get_order().pay()">
|
||||
<div class="pay-circle d-flex align-items-center justify-content-center py-2 mb-2" t-att-class="{ 'highlight': this.highlightPay }">
|
||||
<div class="pay-circle d-flex align-items-center justify-content-center py-2 mb-2">
|
||||
<i class="oi oi-chevron-right" role="img" aria-label="Pay" title="Pay" />
|
||||
</div>
|
||||
<t class="text-white" t-esc="props.actionName" />
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class CategoryButton extends Component {
|
||||
static template = "point_of_sale.CategoryButton";
|
||||
|
||||
get imageUrl() {
|
||||
const category = this.props.category;
|
||||
return `/web/image?model=pos.category&field=image_128&id=${category.id}&unique=${category.write_date}`;
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.CategoryButton" owl="1">
|
||||
<button class="category-button btn btn-light" t-on-click="() => this.props.switchCategory(props.category.id)">
|
||||
<div class="category-img">
|
||||
<img t-att-src="imageUrl" alt="Category" />
|
||||
</div>
|
||||
<div class="category-name mt-2 fw-bolder">
|
||||
<t t-esc="props.category.name" />
|
||||
</div>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,6 +1,5 @@
|
||||
.numpad {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
min-width: 216px;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,35 +2,35 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.NumpadWidget" owl="1">
|
||||
<div class="numpad d-grid text-center flex-grow-1 w-50 bg-300">
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('1')">1</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('2')">2</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('3')">3</button>
|
||||
<div class="numpad d-grid text-center flex-grow-1 bg-300">
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('1')">1</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('2')">2</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('3')">3</button>
|
||||
<button class="mode-button btn btn-light w-100 rounded-0 py-3 fw-bolder" t-att-class="{'selected-mode active': props.activeMode === 'quantity'}"
|
||||
t-on-click="() => this.changeMode('quantity')">Qty</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('4')">4</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('5')">5</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('6')">6</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('4')">4</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('5')">5</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('6')">6</button>
|
||||
<button class="mode-button btn btn-light w-100 rounded-0 py-3 fw-bolder text-nowrap" t-att-class="{
|
||||
'selected-mode active': props.activeMode === 'discount',
|
||||
'disabled-mode': !hasManualDiscount
|
||||
}"
|
||||
t-att-disabled="!hasManualDiscount"
|
||||
t-on-click="() => this.changeMode('discount')">% Disc</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('7')">7</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('8')">8</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('9')">9</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('7')">7</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('8')">8</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('9')">9</button>
|
||||
<button class="mode-button btn btn-light w-100 rounded-0 py-3 fw-bolder" t-att-class="{
|
||||
'selected-mode active': props.activeMode === 'price',
|
||||
'disabled-mode': !hasPriceControlRights
|
||||
}" t-att-disabled="!hasPriceControlRights"
|
||||
t-on-click="() => this.changeMode('price')">Price</button>
|
||||
<button class="input-button numpad-minus btn btn-light w-100 rounded-0 py-3 fw-bolder" t-att-class="{ 'disabled-mode': props.disableSign }" t-on-click="() => this.sendInput('-')">+/-</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('0')">0</button>
|
||||
<button class="input-button number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput(decimalSeparator)">
|
||||
<button class="input-button px-4 numpad-minus btn btn-light w-100 rounded-0 py-3 fw-bolder" t-att-class="{ 'disabled-mode': props.disableSign }" t-on-click="() => this.sendInput('-')">+/-</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('0')">0</button>
|
||||
<button class="input-button px-4 number-char btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput(decimalSeparator)">
|
||||
<t t-esc="decimalSeparator" />
|
||||
</button>
|
||||
<button class="input-button numpad-backspace btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('Backspace')">
|
||||
<button class="input-button px-4 numpad-backspace btn btn-light w-100 rounded-0 py-3 fw-bolder" t-on-click="() => this.sendInput('Backspace')">
|
||||
<img style="pointer-events: none;" src="/point_of_sale/static/src/img/backspace.png"
|
||||
width="24" height="21" alt="Backspace" />
|
||||
</button>
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.ProductItem" owl="1">
|
||||
<article t-attf-class="product position-relative btn btn-light d-flex flex-column align-items-stretch p-0 m-0 text-start cursor-pointer overflow-hidden transition-base" tabindex="0" t-on-keypress="spaceClickProduct"
|
||||
<article t-attf-class="product btn btn-light d-flex flex-column align-items-stretch p-0 m-0 text-start cursor-pointer overflow-hidden transition-base" tabindex="0" t-on-keypress="spaceClickProduct"
|
||||
t-on-click="() => this.pos.addProductToCurrentOrder(props.product)"
|
||||
t-att-data-product-id="props.product.id"
|
||||
t-attf-aria-labelledby="article_product_{{props.product.id}}">
|
||||
<div class="product-img">
|
||||
<div class="product-img flex-grow-1">
|
||||
<img t-if="props.product.image_128" class="w-100" t-att-src="imageUrl" t-att-alt="props.product.display_name" />
|
||||
</div>
|
||||
<div class="product-content d-flex flex-column justify-content-between h-100 mx-2 py-1">
|
||||
<div class="product-content d-flex flex-column justify-content-between mx-2 py-1">
|
||||
<div t-attf-class="overflow-hidden lh-sm fw-bolder #{props.product.image_128 ? 'product-name' : 'product-name no-image' }" t-attf-id="article_product_{{props.product.id}}">
|
||||
<t t-esc="props.product.display_name" />
|
||||
</div>
|
||||
|
||||
+39
-4
@@ -2,14 +2,12 @@
|
||||
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||
|
||||
import { CategoryButton } from "@point_of_sale/app/screens/product_screen/category_button/category_button";
|
||||
|
||||
import { Component, onMounted, onWillUnmount, useRef, useState } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { CategorySelector } from "@point_of_sale/app/generic_components/category_selector/category_selector";
|
||||
|
||||
export class ProductsWidgetControlPanel extends Component {
|
||||
static components = { CategoryButton };
|
||||
static components = { CategorySelector };
|
||||
static template = "point_of_sale.ProductsWidgetControlPanel";
|
||||
|
||||
setup() {
|
||||
@@ -30,6 +28,43 @@ export class ProductsWidgetControlPanel extends Component {
|
||||
window.removeEventListener("resize", toggleIsMobile);
|
||||
});
|
||||
}
|
||||
|
||||
getCategoryImageUrl(category) {
|
||||
return `/web/image?model=pos.category&field=image_128&id=${category.id}&unique=${category.write_date}`;
|
||||
}
|
||||
/**
|
||||
* @param {Object} category - the object from `this.pos.db.category_by_id`
|
||||
* @returns {import("@point_of_sale/app/generic_components/category_selector/category_selector").Category}
|
||||
*/
|
||||
formatCategoryObject(category) {
|
||||
const isRootCategory = category.id === this.pos.db.root_category_id;
|
||||
const hasSeparator =
|
||||
!isRootCategory &&
|
||||
[
|
||||
...this.pos.db.get_category_ancestors_ids(this.pos.selectedCategoryId),
|
||||
this.pos.selectedCategoryId,
|
||||
].includes(category.id);
|
||||
return {
|
||||
id: category.id,
|
||||
name: !isRootCategory ? category.name : "",
|
||||
icon: isRootCategory ? "fa-home fa-2x" : "",
|
||||
separator: hasSeparator ? "fa-caret-right" : "",
|
||||
imageUrl: category?.has_image && this.getCategoryImageUrl(category),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @returns {import("@point_of_sale/app/generic_components/category_selector/category_selector").Category[]}
|
||||
*/
|
||||
getCategories() {
|
||||
return [
|
||||
...this.pos.db.get_category_ancestors_ids(this.pos.selectedCategoryId),
|
||||
this.pos.selectedCategoryId,
|
||||
...this.pos.db.get_category_childs_ids(this.pos.selectedCategoryId),
|
||||
]
|
||||
.map((id) => this.pos.db.category_by_id[id])
|
||||
.map((category) => this.formatCategoryObject(category));
|
||||
}
|
||||
|
||||
toggleIsMobile() {
|
||||
// In addition to the UI service we need to check the width of the window
|
||||
// because the search bar glitches on a specific width between when the
|
||||
|
||||
-12
@@ -1,8 +1,4 @@
|
||||
|
||||
.category-list-scroller{
|
||||
max-height:40vh;
|
||||
}
|
||||
|
||||
.category-button {
|
||||
.category-img {
|
||||
width: 120px;
|
||||
@@ -33,11 +29,3 @@
|
||||
display: block;
|
||||
margin : 3px auto 0 auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.pos .search-bar-container.sb-product {
|
||||
max-width: none;
|
||||
width: auto;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
+17
-56
@@ -2,66 +2,27 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.ProductsWidgetControlPanel" owl="1">
|
||||
<div class="products-widget-control d-grid shadow-sm z-index-1" t-ref="products-widget-control">
|
||||
<div class="rightpane-header d-flex justify-content-between align-items-center text-center bg-view p-1 overflow-x-auto overflow-y-hidden">
|
||||
<t t-if="!props.hasNoCategories">
|
||||
<div class="categories-header d-flex overflow-x-auto">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="breadcrumbs d-flex">
|
||||
<span class="breadcrumb float-start d-flex align-items-center">
|
||||
<button class="breadcrumb-button breadcrumb-home btn btn-light fw-bolder" t-on-click="() => this.props.switchCategory(0)">
|
||||
<i class="fa fa-home fa-2x py-1" role="img" aria-label="Home" title="Home"></i>
|
||||
</button>
|
||||
</span>
|
||||
<t t-foreach="props.breadcrumbs" t-as="category" t-key="category.id">
|
||||
<span class="breadcrumb float-start d-flex align-items-center">
|
||||
<i class="breadcrumb-arrow fa fa-caret-right px-2" aria-hidden="true"/>
|
||||
<button class="breadcrumb-button btn btn-light h-100 fw-bolder"
|
||||
t-on-click="() => this.props.switchCategory(category.id)">
|
||||
<t t-esc="category.name"></t>
|
||||
</button>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
<!-- Subcategories -->
|
||||
<div t-if="props.subcategories.length > 0 and !displayCategImages" class="category-list simple d-flex align-items-center ms-1">
|
||||
<t t-foreach="props.subcategories" t-as="category" t-key="category.id">
|
||||
<button class="category-simple-button btn btn-light h-100 fw-bolder"
|
||||
t-on-click="() => this.props.switchCategory(category.id)">
|
||||
<t t-esc="category.name" />
|
||||
</button>
|
||||
</t>
|
||||
<div class="products-widget-control d-flex bg-view shadow-sm" t-ref="products-widget-control">
|
||||
<CategorySelector
|
||||
categories="getCategories()"
|
||||
onClick="(id) => this.pos.setSelectedCategoryId(id)"
|
||||
/>
|
||||
<div class="ms-auto search-bar-container d-flex align-items-center">
|
||||
<button t-if="state.isMobile"
|
||||
class="p-3 rounded"
|
||||
t-on-click="toggleMobileSearchBar">
|
||||
<i t-attf-class="fa {{state.mobileSearchBarIsShown ? 'fa-arrow-right' : 'fa-search'}}"/>
|
||||
</button>
|
||||
<t t-if="!state.isMobile || state.mobileSearchBarIsShown">
|
||||
<div class="input-group h-100">
|
||||
<div class="pos-search-bar form-control d-flex align-items-center py-1 bg-view">
|
||||
<i class="oi oi-search me-2"/>
|
||||
<input class="flex-grow-1 w-auto h-100 border-0 px-2 me-4" t-model="this.pos.searchProductWord" placeholder="Search Products..." type="text" autofocus="autofocus" t-on-keyup="updateSearch" />
|
||||
<i t-if="this.pos.searchProductWord" class="fa fa-times position-absolute end-0 me-2 pe-1 cursor-pointer" t-on-click="_clearSearch"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<div class="search-bar-container sb-product d-flex align-items-center h-100">
|
||||
<span t-if="state.isMobile" class="oe_status oe_icon icon d-flex align-items-center h-100 me-1 px-4 rounded text-bg-secondary cursor-pointer" t-on-click="toggleMobileSearchBar">
|
||||
<i t-attf-class="fa {{state.mobileSearchBarIsShown ? 'fa-arrow-right' : 'fa-search'}}"/>
|
||||
</span>
|
||||
<t t-if="!state.isMobile || state.mobileSearchBarIsShown">
|
||||
<div class="input-group h-100">
|
||||
<div class="pos-search-bar form-control d-flex align-items-center py-1 bg-view">
|
||||
<i class="oi oi-search me-2"/>
|
||||
<input class="flex-grow-1 w-auto h-100 border-0 px-2 me-4" t-model="this.pos.searchProductWord" placeholder="Search Products..." type="text" autofocus="autofocus" t-on-keyup="updateSearch" />
|
||||
<i t-if="this.pos.searchProductWord" class="fa fa-times position-absolute end-0 me-2 pe-1 cursor-pointer" t-on-click="_clearSearch"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="props.subcategories.length > 0 and displayCategImages">
|
||||
<div class="categories border-bottom shadow-sm">
|
||||
<div class="category-list-scroller w-100 h-100 overflow-hidden overflow-y-auto">
|
||||
<div class="category-list d-flex flex-wrap px-1 py-2 gap-3">
|
||||
<t t-foreach="props.subcategories" t-as="category" t-key="category.id">
|
||||
<CategoryButton
|
||||
switchCategory.bind="props.switchCategory"
|
||||
category="category" />
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
-16
@@ -52,22 +52,6 @@ export class ProductsWidget extends Component {
|
||||
return a.display_name.localeCompare(b.display_name);
|
||||
});
|
||||
}
|
||||
get subcategories() {
|
||||
const { db } = this.pos;
|
||||
return db
|
||||
.get_category_childs_ids(this.selectedCategoryId)
|
||||
.map((id) => db.get_category_by_id(id));
|
||||
}
|
||||
get breadcrumbs() {
|
||||
const { db } = this.pos;
|
||||
if (this.selectedCategoryId === db.root_category_id) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
...db.get_category_ancestors_ids(this.selectedCategoryId).slice(1),
|
||||
this.selectedCategoryId,
|
||||
].map((id) => db.get_category_by_id(id));
|
||||
}
|
||||
get hasNoCategories() {
|
||||
return this.pos.db.get_category_childs_ids(0).length === 0;
|
||||
}
|
||||
|
||||
+4
-8
@@ -2,17 +2,13 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.ProductsWidget" owl="1">
|
||||
<div class="products-widget d-flex flex-column flex-grow-1 overflow-hidden">
|
||||
<div class="products-widget d-flex flex-column flex-grow-1 overflow-auto">
|
||||
<ProductsWidgetControlPanel
|
||||
updateProductList.bind="updateProductList"
|
||||
clearSearch.bind="clearSearch"
|
||||
updateSearch.bind="updateSearch"
|
||||
switchCategory.bind="switchCategory"
|
||||
breadcrumbs="breadcrumbs"
|
||||
subcategories="subcategories"
|
||||
hasNoCategories="hasNoCategories"
|
||||
loadProductFromServer.bind="onPressEnterKey" />
|
||||
<div class="product-list-container flex-grow-1 overflow-hidden overflow-y-auto">
|
||||
<div class="product-list-container flex-grow-1 overflow-y-auto">
|
||||
<div t-if="productsToDisplay.length != 0 and posHasValidProduct" t-attf-class="{{this.pos.productListViewMode}} product-list d-grid gap-1 p-1">
|
||||
<t t-foreach="productsToDisplay" t-as="product" t-key="product.id">
|
||||
<ProductItem
|
||||
@@ -30,7 +26,7 @@
|
||||
<t t-else="">
|
||||
<div>
|
||||
<p>No products here, but don't worry, we've got your back.</p>
|
||||
<p>Play with <a role="button" class="button-no-demo btn btn-lg btn-primary" t-on-click="loadDemoDataProducts">our demo products</a> or <a role="button" class="button-no-demo" t-on-click="createNewProducts">create your own products</a></p>
|
||||
<p>Play with <a role="button" class="text-primary" t-on-click="loadDemoDataProducts">our demo products</a> or <a role="button" class="text-primary" t-on-click="createNewProducts">create your own products</a></p>
|
||||
<div t-if="state.showReloadMessage" class="info-load-product-message">
|
||||
Please reload this page once products have been created
|
||||
</div>
|
||||
@@ -41,7 +37,7 @@
|
||||
<div t-if="searchWord" class="search-more-button d-flex justify-content-center">
|
||||
<button class="btn btn-primary btn-lg" t-on-click="onPressEnterKey">Search more</button>
|
||||
</div>
|
||||
<div class="portal search-database-button no-results-message" t-att-class="{ oe_hidden: !shouldShowButton }"></div>
|
||||
<div class="portal search-database-button no-results-message" t-att-class="{ 'd-none': !shouldShowButton }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -2,48 +2,47 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.ProductScreen" owl="1">
|
||||
<div class="product-screen screen h-100 bg-100" t-att-class="{ oe_hidden: !props.isShown }">
|
||||
<div class="screen-full-width d-flex w-100 h-100">
|
||||
<div class="leftpane pane-border d-flex flex-column flex-grow-1 w-100 h-100 border-end bg-200" t-if="!ui.isSmall || pos.mobile_pane === 'left'">
|
||||
<OrderWidget/>
|
||||
<div class="pads border-top">
|
||||
<div class="control-buttons d-flex flex-wrap border-bottom overflow-hidden bg-300">
|
||||
<t 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>
|
||||
</t>
|
||||
</div>
|
||||
<div class="subpads d-flex">
|
||||
<ActionpadWidget
|
||||
partner="partner"
|
||||
actionName="constructor.numpadActionName"
|
||||
actionType="'payment'"
|
||||
onClickMore.bind="displayAllControlPopup" />
|
||||
<NumpadWidget
|
||||
updateSelectedOrderline.bind="updateSelectedOrderline"
|
||||
activeMode="pos.numpadMode" />
|
||||
</div>
|
||||
<div class="product-screen d-flex h-100 bg-100" t-att-class="{ 'd-none': !props.isShown }">
|
||||
<div class="leftpane d-flex flex-column border-end bg-200"
|
||||
t-if="!ui.isSmall || pos.mobile_pane === 'left'"
|
||||
t-att-class="{'flex-grow-1': ui.isSmall}">
|
||||
<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" />
|
||||
<NumpadWidget
|
||||
updateSelectedOrderline.bind="updateSelectedOrderline"
|
||||
activeMode="pos.numpadMode" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightpane d-flex flex-column flex-grow-1 w-100 h-100 bg-300" 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="showProductReminder" t-key="animationKey" >
|
||||
<span><t t-esc="selectedOrderlineQuantity"/> <t t-esc="selectedOrderlineDisplayName"/> <t t-esc="selectedOrderlineTotal"/></span>
|
||||
</div>
|
||||
<div class="switchpane d-flex">
|
||||
<button class="btn-switchpane pay-button btn btn-primary w-50 rounded-0 fw-bolder" t-att-class="{'primary': primaryPayButton(), 'secondary': !primaryPayButton()}" t-on-click="() => currentOrder.pay()">
|
||||
<span class="fs-1 d-block">Pay</span>
|
||||
<span><t t-esc="total" /></span>
|
||||
</button>
|
||||
<button class="btn-switchpane btn btn-secondary w-50 rounded-0 fw-bolder" t-att-class="{'primary': primaryReviewButton(), 'secondary': !primaryReviewButton()}" t-on-click="switchPane">
|
||||
<span class="fs-1 d-block">Review</span>
|
||||
<span><t t-esc="items"/> items</span>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightpane overflow-auto d-flex flex-grow-1 flex-column bg-300" 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="showProductReminder" t-key="animationKey" >
|
||||
<span><t t-esc="selectedOrderlineQuantity"/> <t t-esc="selectedOrderlineDisplayName"/> <t t-esc="selectedOrderlineTotal"/></span>
|
||||
</div>
|
||||
<div class="switchpane d-flex">
|
||||
<button class="btn-switchpane pay-button btn btn-primary w-50 rounded-0 fw-bolder" t-att-class="{'primary': primaryPayButton(), 'secondary': !primaryPayButton()}" t-on-click="() => currentOrder.pay()">
|
||||
<span class="fs-1 d-block">Pay</span>
|
||||
<span><t t-esc="total" /></span>
|
||||
</button>
|
||||
<button class="btn-switchpane btn btn-secondary w-50 rounded-0 fw-bolder" t-att-class="{'primary': primaryReviewButton(), 'secondary': !primaryReviewButton()}" t-on-click="switchPane">
|
||||
<span class="fs-1 d-block">Review</span>
|
||||
<span><t t-esc="items"/> items</span>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="point_of_sale.OrderLinesReceipt" owl="1">
|
||||
<t t-foreach="receipt.orderlines" t-as="line" t-key="line.id">
|
||||
<div t-foreach="receipt.orderlines" t-as="line" t-key="line.id">
|
||||
<div t-esc="line.product_name_wrapped[0]" />
|
||||
<WrappedProductNameLines line="line" />
|
||||
<t t-if="line.display_discount_policy == 'without_discount' and line.price != line.price_lst">
|
||||
@@ -237,7 +237,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -580,7 +580,8 @@ export class TicketScreen extends Component {
|
||||
modelField: "pos_reference",
|
||||
},
|
||||
DATE: {
|
||||
repr: (order) => deserializeDate(order.creation_date).toFormat("yyyy-MM-dd HH:mm a"),
|
||||
repr: (order) =>
|
||||
deserializeDate(order.creation_date).toFormat("yyyy-MM-dd HH:mm a"),
|
||||
displayName: this.env._t("Date"),
|
||||
modelField: "date_order",
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.TicketScreen" owl="1">
|
||||
<div class="ticket-screen screen h-100 bg-100" t-att-class="{ oe_hidden: !props.isShown }">
|
||||
<div class="ticket-screen screen h-100 bg-100" t-att-class="{ 'd-none': !props.isShown }">
|
||||
<div class="screen-full-width d-flex w-100 h-100">
|
||||
<div class="rightpane pane-border d-flex flex-column flex-grow-1 w-100 h-50 h-md-100 bg-300 overflow-y-auto">
|
||||
<div class="controls d-flex align-items-center justify-content-between mt-1 mt-md-0 p-2 bg-400">
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="orders overflow-y-auto">
|
||||
<t t-set="_filteredOrderList" t-value="getFilteredOrderList()" />
|
||||
<t t-if="_filteredOrderList.length !== 0">
|
||||
<div class="header-row d-flex text-bg-700 fw-bolder" t-att-class="{ oe_hidden: ui.isSmall }">
|
||||
<div class="header-row d-flex text-bg-700 fw-bolder" t-att-class="{ 'd-none': ui.isSmall }">
|
||||
<div class="col wide p-2">Date</div>
|
||||
<div class="col wide p-2">Receipt Number</div>
|
||||
<div class="col p-2">Customer</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { ProductScreen } from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
function add_product_to_order(product_name) {
|
||||
@@ -91,15 +92,12 @@ function fillPaymentLineAmountMobile(lineName, keys) {
|
||||
content: "click confirm button",
|
||||
trigger: ".popup .footer .confirm",
|
||||
mobile: true,
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function fillPaymentValue(lineName, val) {
|
||||
return [
|
||||
...press_payment_numpad(val),
|
||||
...fillPaymentLineAmountMobile(lineName, val),
|
||||
]
|
||||
return [...press_payment_numpad(val), ...fillPaymentLineAmountMobile(lineName, val)];
|
||||
}
|
||||
|
||||
function press_product_numpad(val) {
|
||||
@@ -230,7 +228,7 @@ function finish_order() {
|
||||
},
|
||||
{
|
||||
content: "check if we left the receipt screen",
|
||||
trigger: ".pos-content .screen:not(:has(.receipt-screen))",
|
||||
trigger: ".pos-content div:not(:has(.receipt-screen))",
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
@@ -242,12 +240,8 @@ var steps = [
|
||||
trigger: "body:not(:has(.loader))",
|
||||
run: function () {},
|
||||
},
|
||||
{
|
||||
// Leave category displayed by default
|
||||
content: "click category switch",
|
||||
trigger: ".breadcrumb-home",
|
||||
},
|
||||
];
|
||||
steps = steps.concat(...ProductScreen.do.clickHomeCategory());
|
||||
|
||||
steps = steps.concat(add_product_to_order("Desk Organizer"));
|
||||
steps = steps.concat(verify_order_total("5.10"));
|
||||
@@ -353,4 +347,6 @@ steps = steps.concat([
|
||||
},
|
||||
]);
|
||||
|
||||
registry.category("web_tour.tours").add("pos_basic_order", { test: true, url: "/pos/ui", steps: () => steps });
|
||||
registry
|
||||
.category("web_tour.tours")
|
||||
.add("pos_basic_order", { test: true, url: "/pos/ui", steps: () => steps });
|
||||
|
||||
@@ -65,7 +65,7 @@ class Check {
|
||||
return [
|
||||
{
|
||||
content: "product configurator is shown",
|
||||
trigger: ".product-configurator-popup:not(:has(.oe_hidden))",
|
||||
trigger: ".product-configurator-popup:not(:has(.d-none))",
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -41,11 +41,11 @@ class Do {
|
||||
return [
|
||||
{
|
||||
content: `selecting '${name}' subcategory`,
|
||||
trigger: `.products-widget > .products-widget-control .category-simple-button:contains("${name}")`,
|
||||
trigger: `.products-widget > .products-widget-control .category-button:contains("${name}")`,
|
||||
},
|
||||
{
|
||||
content: `'${name}' subcategory selected`,
|
||||
trigger: `.breadcrumbs .breadcrumb-button:contains("${name}")`,
|
||||
trigger: `i.fa-caret-right ~ button.category-button:contains("${name}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
@@ -55,7 +55,7 @@ class Do {
|
||||
return [
|
||||
{
|
||||
content: `click Home subcategory`,
|
||||
trigger: `.breadcrumbs .breadcrumb-home`,
|
||||
trigger: `button.category-button i.fa-home`,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -281,8 +281,10 @@ class Do {
|
||||
return [
|
||||
{
|
||||
content: `PoS model scan barcode '${barcode}'`,
|
||||
trigger: '.pos', // The element here does not really matter as long as it is present
|
||||
run: () => { window.posmodel.env.services.barcode_reader.scan(barcode); },
|
||||
trigger: ".pos", // The element here does not really matter as long as it is present
|
||||
run: () => {
|
||||
window.posmodel.env.services.barcode_reader.scan(barcode);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -290,12 +292,10 @@ class Do {
|
||||
return [
|
||||
{
|
||||
content: `PoS model scan EAN13 barcode '${barcode}'`,
|
||||
trigger: '.pos', // The element here does not really matter as long as it is present
|
||||
trigger: ".pos", // The element here does not really matter as long as it is present
|
||||
run: () => {
|
||||
const barcode_reader = window.posmodel.env.services.barcode_reader;
|
||||
barcode_reader.scan(
|
||||
barcode_reader.parser.sanitize_ean(barcode)
|
||||
);
|
||||
barcode_reader.scan(barcode_reader.parser.sanitize_ean(barcode));
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -307,7 +307,7 @@ class Do {
|
||||
trigger: ".pos-rightheader .floor-button",
|
||||
mobile: true,
|
||||
},
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/* global posmodel */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ProductScreen } from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods";
|
||||
import { roundDecimals as round_di } from "@web/core/utils/numbers";
|
||||
import { nbsp } from "@web/core/utils/strings";
|
||||
|
||||
@@ -100,11 +101,7 @@ steps = steps.concat([
|
||||
trigger: ".pos.done-testing",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "click category switch",
|
||||
trigger: ".breadcrumb-home",
|
||||
run: "click",
|
||||
},
|
||||
...ProductScreen.do.clickHomeCategory(),
|
||||
{
|
||||
content: "click review button",
|
||||
trigger: ".btn-switchpane:contains('Review')",
|
||||
@@ -324,8 +321,8 @@ steps = steps.concat([
|
||||
},
|
||||
]);
|
||||
|
||||
registry.category("web_tour.tours").add("pos_pricelist", {
|
||||
test: true,
|
||||
url: "/pos/ui",
|
||||
registry.category("web_tour.tours").add("pos_pricelist", {
|
||||
test: true,
|
||||
url: "/pos/ui",
|
||||
steps: () => steps,
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="login-overlay fixed-top w-100 h-100 bg-view">
|
||||
<div class="screen-login position-absolute top-0 start-0 bottom-0 end-0 d-flex flex-column py-4 m-auto w-100 rounded bg-view text-center fw-bolder">
|
||||
<div class="login-title fs-2 mb-3 mb-md-0">Log in to
|
||||
<span class="text-primary"><t t-esc="shopName" /></span>
|
||||
<span class="text-primary" t-esc="shopName" />
|
||||
</div>
|
||||
<div class="login-body d-flex d-flex flex-column flex-sm-row align-items-center justify-content-around px-3 py-4">
|
||||
<span class="login-element border p-3 rounded">
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="pos_restaurant.Table" owl="1">
|
||||
<div class="table position-absolute d-flex flex-column align-items-center justify-content-between cursor-pointer overflow-hidden"
|
||||
<div class="table position-absolute d-flex flex-column align-items-center justify-content-between cursor-pointer"
|
||||
t-on-click.stop="(ev) => props.onClick(props.table, ev)"
|
||||
t-att-style="style">
|
||||
<div class="infos d-flex align-items-center flex-grow-1">
|
||||
<span class="label fw-bolder fs-4 z-index-1"
|
||||
<span class="label fw-bolder fs-4"
|
||||
t-esc="props.table.name" />
|
||||
<span
|
||||
t-att-class="orderCountClass"
|
||||
class="badge d-flex align-items-center justify-content-center position-absolute top-0 start-100 translate-middle rounded-pill"
|
||||
class="badge d-flex align-items-center justify-content-center position-absolute rounded-pill"
|
||||
t-attf-class="{{ orderCount === 0 ? 'd-none' : ''}}"
|
||||
t-att-style="badgeStyle"
|
||||
t-esc="orderCount"/>
|
||||
</div>
|
||||
<span class="table-seats position-absolute bottom-0 start-50 translate-middle-x mb-1 px-2 py-1 rounded text-bg-dark bg-opacity-25 fs-4 z-index-1">
|
||||
<span class="table-seats position-absolute bottom-0 start-50 translate-middle-x mb-1 px-2 py-1 rounded text-bg-dark bg-opacity-25 fs-4">
|
||||
<div class="cover" t-att-style="`width: ${Math.ceil(fill * 100)}%`" />
|
||||
<t t-esc="customerCountDisplay" />
|
||||
</span>
|
||||
|
||||
+6
-15
@@ -1,7 +1,6 @@
|
||||
/** @odoo-module */
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { ActionpadWidget } from "@point_of_sale/app/screens/product_screen/action_pad/action_pad";
|
||||
import { nbsp } from "@web/core/utils/strings";
|
||||
/**
|
||||
* @props partner
|
||||
*/
|
||||
@@ -13,15 +12,11 @@ patch(ActionpadWidget.prototype, {
|
||||
get currentOrder() {
|
||||
return this.pos.get_order();
|
||||
},
|
||||
get addedClasses() {
|
||||
if (!this.currentOrder) {
|
||||
return {};
|
||||
}
|
||||
const hasChanges = this.currentOrder.hasChangesToPrint();
|
||||
const skipped = hasChanges ? false : this.currentOrder.hasSkippedChanges();
|
||||
get swapButtonClasses() {
|
||||
return {
|
||||
highlight: hasChanges,
|
||||
altlight: skipped,
|
||||
"highlight btn-primary": this.currentOrder?.hasChangesToPrint(),
|
||||
altlight:
|
||||
!this.currentOrder?.hasChangesToPrint() && this.currentOrder?.hasSkippedChanges(),
|
||||
};
|
||||
},
|
||||
async submitOrder() {
|
||||
@@ -61,12 +56,8 @@ patch(ActionpadWidget.prototype, {
|
||||
}
|
||||
const category = this.pos.db.category_by_id[categoryId].name;
|
||||
const numProd = orderline.quantity;
|
||||
categories[category] = categories[category] ? categories[category] + numProd : numProd;
|
||||
categories[category] = categories[category] ?? 0 + numProd;
|
||||
}
|
||||
let result = "";
|
||||
for (const key in categories) {
|
||||
result = result + categories[key] + nbsp + key + " | ";
|
||||
}
|
||||
return result.slice(0, -2);
|
||||
return Object.entries(categories);
|
||||
},
|
||||
});
|
||||
|
||||
+17
-11
@@ -1,21 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="pos_restaurant.ActionpadWidget" t-inherit="point_of_sale.ActionpadWidget" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('actionpad')]" position="attributes">
|
||||
<attribute name="t-att-class">{'w-50' : this.swapButton}</attribute>
|
||||
</xpath>
|
||||
|
||||
<!-- Replace the payment button by the order button -->
|
||||
<xpath expr="//button[hasclass('validation')]" position="after">
|
||||
<button
|
||||
t-if="this.swapButton"
|
||||
class="button submit-order btn btn-lg py-5 rounded-0 highlight d-flex flex-column flex-fill align-items-center justify-content-center fw-bolder"
|
||||
t-att-class="addedClasses" t-attf-class="{{ !this.highlightPay ? 'highlight btn-primary' : 'btn-secondary' }}"
|
||||
t-attf-class="submit-order w-100 h-100 {{getMainButtonClasses()}}"
|
||||
t-att-class="swapButtonClasses"
|
||||
t-on-click="submitOrder">
|
||||
<div t-att-class ="categoryCount ? 'smaller-display' : 'none'">
|
||||
<div t-att-class = "categoryCount ? 'submit-circle-smaller' : 'submit-circle'">
|
||||
<i class="fa fa-cutlery"></i>
|
||||
</div>
|
||||
Order
|
||||
</div>
|
||||
<div class = "break-line">
|
||||
<span t-esc="categoryCount"/>
|
||||
<i class="fa fa-cutlery"></i>
|
||||
Order
|
||||
<div class="break-line">
|
||||
<t t-foreach="categoryCount" t-as="category" t-key="category[0]">
|
||||
<t t-esc="category[1]"/> <t t-esc="category[0]"/> |
|
||||
</t>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
@@ -25,7 +27,11 @@
|
||||
|
||||
<!-- Replace the customer button by the payment button, the customer button will be added in the mixins -->
|
||||
<xpath expr="//button[hasclass('set-partner')]" position="after">
|
||||
<button t-on-click="() => pos.get_order().pay()" class="button pay-order-button btn btn-lg rounded-0" t-attf-class="{{ this.highlightPay ? 'highlight btn-primary' : 'btn-secondary' }}" t-if="this.swapButton">
|
||||
<button t-if="this.swapButton"
|
||||
t-on-click="() => pos.get_order().pay()"
|
||||
class="button pay-order-button btn btn-lg rounded-0"
|
||||
t-attf-class="{{ this.highlightPay ? 'highlight btn-primary' : 'btn-secondary' }}"
|
||||
>
|
||||
<i class="oi oi-chevron-right" role="img" aria-label="Pay" title="Pay" />
|
||||
Payment
|
||||
</button>
|
||||
|
||||
+2
-8
@@ -10,10 +10,7 @@ patch(ProductScreen.prototype, {
|
||||
get selectedOrderlineQuantity() {
|
||||
const order = this.pos.get_order();
|
||||
const orderline = order.get_selected_orderline();
|
||||
if (
|
||||
this.pos.config.module_pos_restaurant &&
|
||||
this.pos.orderPreparationCategories.size
|
||||
) {
|
||||
if (this.pos.config.module_pos_restaurant && this.pos.orderPreparationCategories.size) {
|
||||
let orderline_name = orderline.product.display_name;
|
||||
if (orderline.description) {
|
||||
orderline_name += " (" + orderline.description + ")";
|
||||
@@ -31,10 +28,7 @@ patch(ProductScreen.prototype, {
|
||||
);
|
||||
},
|
||||
get swapButton() {
|
||||
return (
|
||||
this.pos.config.module_pos_restaurant &&
|
||||
this.pos.orderPreparationCategories.size
|
||||
);
|
||||
return this.pos.config.module_pos_restaurant && this.pos.orderPreparationCategories.size;
|
||||
},
|
||||
submitOrder() {
|
||||
this.pos.sendOrderInPreparation(this.pos.get_order());
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO this class is used also in other modules.
|
||||
// Is there a way to not duplicate it? ( ex: `pos_self_order` )
|
||||
.break-line {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="pos_sale.SaleOrderList" owl="1">
|
||||
<div class="orders overflow-y-auto">
|
||||
<div class="header-row d-flex text-bg-700 fw-bolder" t-att-class="{ oe_hidden: ui.isSmall }">
|
||||
<div class="header-row d-flex text-bg-700 fw-bolder" t-att-class="{ 'd-none': ui.isSmall }">
|
||||
<div class="col name p-2">Order</div>
|
||||
<div class="col date p-2">Date</div>
|
||||
<div class="col customer p-2">Customer</div>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="pos_sale.SaleOrderManagementScreen" owl="1">
|
||||
<div class="order-management-screen screen h-100 bg-100 overflow-auto" t-att-class="{ oe_hidden: !props.isShown }" t-ref="root">
|
||||
<div class="order-management-screen screen h-100 bg-100 overflow-auto" t-att-class="{ 'd-none': !props.isShown }" t-ref="root">
|
||||
<div class="rightpane">
|
||||
<div class="flex-container flex-container d-flex flex-column h-100">
|
||||
<SaleOrderManagementControlPanel
|
||||
|
||||
Reference in New Issue
Block a user