[IMP] point_of_sale,pos_*: top bar improvements and ticket list
The following changes are made in this task: 1. Remove order tabs and replace with ticket list. 2. Top bar color change. 3. Search bar in the product screen is moved in the top bar. 4. List of sub-categories are now in the same line as the category breadcrumbs. closes odoo/odoo#55100 Task-id: 2276678 Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
This commit is contained in:
@@ -64,6 +64,7 @@
|
||||
'static/src/xml/Screens/OrderManagementScreen/OrderDetails.xml',
|
||||
'static/src/xml/Screens/OrderManagementScreen/OrderlineDetails.xml',
|
||||
'static/src/xml/Screens/OrderManagementScreen/ReprintReceiptScreen.xml',
|
||||
'static/src/xml/Screens/TicketScreen/TicketScreen.xml',
|
||||
'static/src/xml/Screens/PaymentScreen/PSNumpadInputButton.xml',
|
||||
'static/src/xml/Screens/PaymentScreen/PaymentScreenNumpad.xml',
|
||||
'static/src/xml/Screens/PaymentScreen/PaymentScreenElectronicPayment.xml',
|
||||
@@ -88,16 +89,17 @@
|
||||
'static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml',
|
||||
'static/src/xml/Screens/ReceiptScreen/ReceiptScreen.xml',
|
||||
'static/src/xml/Screens/ScaleScreen/ScaleScreen.xml',
|
||||
'static/src/xml/ChromeWidgets/OrderSelector.xml',
|
||||
'static/src/xml/ChromeWidgets/CashierName.xml',
|
||||
'static/src/xml/ChromeWidgets/ProxyStatus.xml',
|
||||
'static/src/xml/ChromeWidgets/SyncNotification.xml',
|
||||
'static/src/xml/ChromeWidgets/OrderManagementButton.xml',
|
||||
'static/src/xml/ChromeWidgets/HeaderButton.xml',
|
||||
'static/src/xml/ChromeWidgets/SaleDetailsButton.xml',
|
||||
'static/src/xml/ChromeWidgets/TicketButton.xml',
|
||||
'static/src/xml/SaleDetailsReport.xml',
|
||||
'static/src/xml/Misc/Draggable.xml',
|
||||
'static/src/xml/Misc/NotificationSound.xml',
|
||||
'static/src/xml/Misc/SearchBar.xml',
|
||||
'static/src/xml/ChromeWidgets/DebugWidget.xml',
|
||||
'static/src/xml/Popups/ErrorPopup.xml',
|
||||
'static/src/xml/Popups/ErrorBarcodePopup.xml',
|
||||
|
||||
@@ -578,6 +578,8 @@ class PosOrder(models.Model):
|
||||
"""
|
||||
orders = self.search([('id', 'in', server_ids),('state', '=', 'draft')])
|
||||
orders.write({'state': 'cancel'})
|
||||
# TODO Looks like delete cascade is a better solution.
|
||||
orders.mapped('payment_ids').sudo().unlink()
|
||||
orders.sudo().unlink()
|
||||
return orders.ids
|
||||
|
||||
|
||||
@@ -319,22 +319,24 @@ td {
|
||||
margin:0;
|
||||
padding:0;
|
||||
color: gray;
|
||||
background: #393939;
|
||||
background: #875A7B;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* a) The left part of the top-bar */
|
||||
|
||||
.pos .pos-branding{
|
||||
max-width: 459px;
|
||||
min-width: 503px;
|
||||
max-width: 503px;
|
||||
flex-grow: 1;
|
||||
height:100%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
border-right: 1px solid #373737;
|
||||
text-align:left;
|
||||
line-height:100%;
|
||||
vertical-align: middle;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.pos .pos-logo {
|
||||
height: 35px;
|
||||
@@ -352,6 +354,10 @@ td {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pos .ticket-button {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* b) The right part of the top-bar */
|
||||
|
||||
.pos .pos-rightheader {
|
||||
@@ -362,7 +368,17 @@ td {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.pos .pos-rightheader > * {
|
||||
border-right: 1px solid #292929;
|
||||
border-right: 1px solid #875A7B;
|
||||
}
|
||||
|
||||
.pos .status-buttons-portal {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pos .status-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pos .order-button{
|
||||
@@ -456,8 +472,8 @@ td {
|
||||
height: 48px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
border-right: 1px solid #292929;
|
||||
border-left: 1px solid #292929;
|
||||
border-right: 1px solid #875A7B;
|
||||
border-left: 1px solid #875A7B;
|
||||
color: #DDD;
|
||||
line-height: 48px;
|
||||
text-align: center;
|
||||
@@ -468,7 +484,7 @@ td {
|
||||
-webkit-transition-timing-function: ease-out;
|
||||
}
|
||||
.pos .pos-rightheader .header-button:last-child{
|
||||
border-left: 1px solid #3a3a3a;
|
||||
border-left: 1px solid #875A7B;
|
||||
}
|
||||
.pos .pos-rightheader .header-button:active{
|
||||
background: rgba(0,0,0,0.2);
|
||||
@@ -852,11 +868,18 @@ td {
|
||||
|
||||
.pos .rightpane-header {
|
||||
padding: 0;
|
||||
height: 48px;
|
||||
border-bottom: 1px solid #c7c7c7;
|
||||
background: #d3d3d3;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.pos .green-border-bottom {
|
||||
border-bottom: solid 3px rgb(110, 200, 155);
|
||||
}
|
||||
|
||||
.pos .grey-border-bottom {
|
||||
border-bottom: 1px solid #c7c7c7;
|
||||
}
|
||||
|
||||
/* ********* The product list ********* */
|
||||
@@ -885,11 +908,6 @@ td {
|
||||
|
||||
/* a) the product list navigation bar */
|
||||
|
||||
.pos .breadcrumbs{
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
float:left;
|
||||
}
|
||||
.pos .breadcrumb{
|
||||
float: left;
|
||||
display: inline-block;
|
||||
@@ -911,8 +929,6 @@ td {
|
||||
}
|
||||
.pos .breadcrumb-button.breadcrumb-home {
|
||||
line-height: 50px;
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
font-size: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -936,16 +952,13 @@ td {
|
||||
/* b) the search box */
|
||||
|
||||
.pos .searchbox {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
}
|
||||
.pos .searchbox input {
|
||||
width: 100px;
|
||||
width: 150px;
|
||||
border: 1px solid #cecbcb;
|
||||
padding: 10px 20px;
|
||||
padding-left: 38px;
|
||||
padding-right: 33px;
|
||||
margin: 6px;
|
||||
background-color: white;
|
||||
border-radius: 20px;
|
||||
@@ -959,7 +972,6 @@ td {
|
||||
}
|
||||
|
||||
.pos .search-clear {
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
@@ -971,12 +983,13 @@ td {
|
||||
.search-clear.left {
|
||||
left: 11px;
|
||||
color: #808080;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
.search-clear.right {
|
||||
left: 145px;
|
||||
color: #808080;
|
||||
cursor: pointer;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
@@ -1004,6 +1017,7 @@ td {
|
||||
.pos .categories {
|
||||
position: relative;
|
||||
border-bottom: solid 3px rgb(110, 200, 155);
|
||||
flex: 1;
|
||||
}
|
||||
.pos .categories h4 {
|
||||
display: inline-block;
|
||||
@@ -1016,7 +1030,7 @@ td {
|
||||
box-sizing: border-box;
|
||||
width:100%;
|
||||
height:100%;
|
||||
max-height:60vh;
|
||||
max-height:40vh;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
@@ -1027,16 +1041,13 @@ td {
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
background: rgb(229, 229, 229);
|
||||
border-bottom: 1px solid #cecece;
|
||||
}
|
||||
.pos .category-list.simple {
|
||||
padding: 0px;
|
||||
background: #cecece;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-flow: row wrap;
|
||||
flex-flow: row wrap;
|
||||
border-bottom: none;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1058,17 +1069,16 @@ td {
|
||||
}
|
||||
|
||||
.pos .category-simple-button{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
margin-right: 1px;
|
||||
margin-bottom: 1px;
|
||||
padding: 5px 12px;
|
||||
line-height: 32px;
|
||||
flex-grow: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
cursor: pointer;
|
||||
background: #e2e2e2;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
background: rgb(229, 229, 229);
|
||||
border-right: solid 1px #d3d3d3;
|
||||
border-top: solid 1px #d3d3d3;
|
||||
}
|
||||
.pos .category-simple-button:active{
|
||||
color: white;
|
||||
@@ -1372,12 +1382,6 @@ td {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.pos .screen .header-row {
|
||||
border:none;
|
||||
width:100%;
|
||||
height:0px;
|
||||
}
|
||||
|
||||
.pos .screen .header-cell{
|
||||
border:none;
|
||||
width:100%;
|
||||
@@ -3144,3 +3148,332 @@ td {
|
||||
.pos .product_configurator_attributes .configurator_color.active input {
|
||||
margin: 18px;
|
||||
}
|
||||
|
||||
/* TICKET SCREEN */
|
||||
|
||||
.ticket-screen {
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
.ticket-screen .orders {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
overflow: hidden;
|
||||
overflow-y: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .header-row{
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
flex: 1;
|
||||
justify-content: space-evenly;
|
||||
background: #868686;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .order-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
flex: 1;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .col {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .col.start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .col.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .col.end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .col.very-narrow {
|
||||
flex: 0.2;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .col.narrow {
|
||||
flex: 0.5;
|
||||
}
|
||||
|
||||
.ticket-screen .orders .col.wide {
|
||||
flex: 1.5;
|
||||
}
|
||||
|
||||
.ticket-screen .order-row:nth-child(odd) {
|
||||
background: #DDD;
|
||||
}
|
||||
|
||||
.ticket-screen .order-row:nth-child(even) {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.ticket-screen .order-row:hover {
|
||||
background: rgb(110,200,155);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ticket-screen .pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ticket-screen .controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 0px 20px;
|
||||
border-bottom: dashed 1px rgb(215,215,215);
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
|
||||
.ticket-screen .controls button {
|
||||
font-size: medium;
|
||||
padding: 12px;
|
||||
margin-right: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ticket-screen .controls button.discard {
|
||||
background: rgb(230, 230, 230);
|
||||
border: solid 1px rgb(209, 209, 209);
|
||||
}
|
||||
|
||||
.ticket-screen .controls button.highlight {
|
||||
background: #6EC89B;
|
||||
border: solid 1px #64AF8A;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar {
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
display: flex;
|
||||
max-width: 500px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search input {
|
||||
height: 40px;
|
||||
font-size: medium;
|
||||
color: #63717f;
|
||||
padding-left: 40px;
|
||||
border: solid 1px rgb(209, 209, 209);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .radius-right {
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .radius-left {
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search .search-icon {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 12px;
|
||||
z-index: 1;
|
||||
color: #4f5b66;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search ul {
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: calc(100% + 5px);
|
||||
right: 2px;
|
||||
left: 2px;
|
||||
box-shadow: 1px 1px 3px grey;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search li {
|
||||
color: rgb(1,160,157);
|
||||
margin: 0.2em 0;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
padding-left: 35px;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search li:hover {
|
||||
background: #DDD;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search li .field {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search li .term {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search li.highlight {
|
||||
background: #DDD;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .filter .down-icon {
|
||||
position: absolute;
|
||||
right: 13px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .filter {
|
||||
height: 40px;
|
||||
background: white;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
padding-left: 20px;
|
||||
padding-right: 40px;
|
||||
border: solid 1px rgb(209, 209, 209);
|
||||
border-left: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 150px;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .filter:hover {
|
||||
color: #868686;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .filter .options {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: calc(100% + 5px);
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
box-shadow: 1px 1px 5px grey;
|
||||
padding: 0.5em 0;
|
||||
background: white;
|
||||
color: #555555;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .filter ul.options li {
|
||||
padding: 0.2em 1.2em;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .filter ul.options li:hover {
|
||||
background-color: #DDD;
|
||||
}
|
||||
|
||||
.ticket-screen .pos-search-bar .search {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ticket-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
font-size: medium;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ticket-button.highlight {
|
||||
background: rgb(104,69,95);
|
||||
}
|
||||
|
||||
.ticket-button:hover {
|
||||
background: rgb(104,69,95);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ticket-button .with-badge {
|
||||
margin-right: 0.7em;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
[badge] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[badge]:after {
|
||||
background: rgb(1,160,157);
|
||||
border-radius: 10rem;
|
||||
color: #fff;
|
||||
content: attr(badge);
|
||||
font-size: small;
|
||||
min-width: 20px;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: 0.6em;
|
||||
bottom: 0.6em;
|
||||
}
|
||||
|
||||
[badge^="-"]:after,
|
||||
[badge="0"]:after,
|
||||
[badge=""]:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Product Screen Search Bar */
|
||||
|
||||
.search-bar-portal {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search-bar-portal .search-box {
|
||||
font-size: medium;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.search-bar-portal .search-box input {
|
||||
font-size: medium;
|
||||
height: 30px;
|
||||
border: none;
|
||||
color: #63717f;
|
||||
padding-left: 38px;
|
||||
padding-right: 38px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.search-bar-portal .search-box input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-bar-portal .search-box .icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
margin-left: 12px;
|
||||
z-index: 1;
|
||||
color: #4f5b66;
|
||||
}
|
||||
|
||||
.search-bar-portal .search-box .clear-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-right: 12px;
|
||||
z-index: 1;
|
||||
color: #4f5b66;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
this.showScreen(name, props);
|
||||
}
|
||||
_getSavedScreen(order) {
|
||||
return order.get_screen_data('screen') || { name: 'ProductScreen' };
|
||||
return order.get_screen_data();
|
||||
}
|
||||
__showTempScreen(event) {
|
||||
const { name, props, resolve } = event.detail;
|
||||
@@ -236,7 +236,7 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
_setScreenData(name, props) {
|
||||
const order = this.env.pos.get_order();
|
||||
if (order) {
|
||||
order.set_screen_data('screen', { name, props });
|
||||
order.set_screen_data({ name, props });
|
||||
}
|
||||
}
|
||||
async _closePos() {
|
||||
@@ -325,6 +325,10 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
}
|
||||
}
|
||||
|
||||
get isTicketScreenShown() {
|
||||
return this.mainScreen.name === 'TicketScreen';
|
||||
}
|
||||
|
||||
// MISC METHODS //
|
||||
|
||||
async _loadDemoData() {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
odoo.define('point_of_sale.OrderSelector', function(require) {
|
||||
'use strict';
|
||||
|
||||
const { useContext } = owl.hooks;
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const contexts = require('point_of_sale.PosContext');
|
||||
|
||||
// Previously OrderSelectorWidget
|
||||
class OrderSelector extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
// this is used in its template
|
||||
this.chromeContext = useContext(contexts.chrome);
|
||||
}
|
||||
mounted() {
|
||||
this.env.pos.get('orders').on('add remove change', () => this.render(), this);
|
||||
this.env.pos.on('change:selectedOrder', () => this.render(), this);
|
||||
}
|
||||
willUnmount() {
|
||||
this.env.pos.get('orders').off('add remove change', null, this);
|
||||
this.env.pos.off('change:selectedOrder', null, this);
|
||||
}
|
||||
selectOrder(order) {
|
||||
this.env.pos.set_order(order);
|
||||
}
|
||||
addNewOrder() {
|
||||
this.env.pos.add_new_order();
|
||||
}
|
||||
get orderList() {
|
||||
return this.env.pos.get_order_list();
|
||||
}
|
||||
async deleteCurrentOrder() {
|
||||
const order = this.env.pos.get_order();
|
||||
if (!order) {
|
||||
return;
|
||||
} else if (!order.is_empty()) {
|
||||
const { confirmed } = await this.showPopup('ConfirmPopup', {
|
||||
title: this.env._t('Destroy Current Order ?'),
|
||||
body: this.env._t('You will lose any data associated with the current order'),
|
||||
});
|
||||
if (confirmed) this.env.pos.delete_current_order();
|
||||
} else {
|
||||
this.env.pos.delete_current_order();
|
||||
}
|
||||
}
|
||||
creationTime(order) {
|
||||
return moment(order.creation_date).format('hh:mm');
|
||||
}
|
||||
}
|
||||
OrderSelector.template = 'OrderSelector';
|
||||
|
||||
Registries.Component.add(OrderSelector);
|
||||
|
||||
return OrderSelector;
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
odoo.define('point_of_sale.TicketButton', function (require) {
|
||||
'use strict';
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { posbus } = require('point_of_sale.utils');
|
||||
|
||||
class TicketButton extends PosComponent {
|
||||
onClick() {
|
||||
if (this.props.isTicketScreenShown) {
|
||||
posbus.trigger('ticket-button-clicked');
|
||||
} else {
|
||||
this.showScreen('TicketScreen');
|
||||
}
|
||||
}
|
||||
willPatch() {
|
||||
posbus.off('order-deleted', this);
|
||||
}
|
||||
patched() {
|
||||
posbus.on('order-deleted', this, this.render);
|
||||
}
|
||||
mounted() {
|
||||
posbus.on('order-deleted', this, this.render);
|
||||
}
|
||||
willUnmount() {
|
||||
posbus.off('order-deleted', this);
|
||||
}
|
||||
get count() {
|
||||
if (this.env.pos) {
|
||||
return this.env.pos.get_order_list().length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
TicketButton.template = 'TicketButton';
|
||||
|
||||
Registries.Component.add(TicketButton);
|
||||
|
||||
return TicketButton;
|
||||
});
|
||||
@@ -4,13 +4,18 @@ odoo.define('point_of_sale.IndependentToOrderScreen', function (require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
|
||||
class IndependentToOrderScreen extends PosComponent {
|
||||
/**
|
||||
* Alias the forceTriggerSelectedOrder method as it also
|
||||
* means 'closing' this screen.
|
||||
*/
|
||||
close() {
|
||||
// To close this order-indenpendent screen, we forcefully trigger change
|
||||
this.forceTriggerSelectedOrder();
|
||||
}
|
||||
forceTriggerSelectedOrder() {
|
||||
// Calling this method forcefully trigger change
|
||||
// on the selectedOrder attribute, which then shows the screen of the
|
||||
// current order.
|
||||
this.env.pos.trigger('change:selectedOrder', this.env.pos, this.env.pos.get_order(), {
|
||||
silent: true,
|
||||
});
|
||||
// current order, essentially closing this screen.
|
||||
this.env.pos.trigger('change:selectedOrder', this.env.pos, this.env.pos.get_order());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
odoo.define('point_of_sale.SearchBar', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { useState, useExternalListener } = owl.hooks;
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
/**
|
||||
* This is a simple configurable search bar component. It has search fields
|
||||
* and selection filter. Search fields allow the users to specify the type
|
||||
* of their searches. The filter is a dropdown menu for selection. Depending on
|
||||
* user's action, this component emits corresponding event with the action
|
||||
* information (payload).
|
||||
*
|
||||
* TODO: This component can be made more generic and be able to replace
|
||||
* all the search bars across pos ui.
|
||||
*
|
||||
* @prop {{
|
||||
* config: {
|
||||
* searchFields: string[],
|
||||
* filter: { show: boolean, options: string[] }
|
||||
* },
|
||||
* placeholder: string,
|
||||
* }}
|
||||
* @emits search @payload { fieldValue: string, searchTerm: '' }
|
||||
* @emits filter-selected @payload { filter: string }
|
||||
*
|
||||
* NOTE: The payload of the emitted event is accessible via the `detail`
|
||||
* field of the event.
|
||||
*/
|
||||
class SearchBar extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.config = this.props.config;
|
||||
this.state = useState({
|
||||
searchInput: '',
|
||||
selectedFieldId: this.config.searchFields.length ? 0 : null,
|
||||
showSearchFields: false,
|
||||
showFilterOptions: false,
|
||||
selectedFilter: this.config.filter.options[0] || 'Select',
|
||||
});
|
||||
useExternalListener(window, 'click', this._hideOptions);
|
||||
}
|
||||
selectFilter(option) {
|
||||
this.state.selectedFilter = option;
|
||||
this.trigger('filter-selected', { filter: this.state.selectedFilter });
|
||||
}
|
||||
get placeholder() {
|
||||
return this.props.placeholder;
|
||||
}
|
||||
/**
|
||||
* When vertical arrow keys are pressed, select fields for searching.
|
||||
* When enter key is pressed, trigger search event if there is searchInput.
|
||||
*/
|
||||
onKeydown(event) {
|
||||
if (['ArrowUp', 'ArrowDown'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
this.state.selectedFieldId = this._fieldIdToSelect(event.key);
|
||||
} else if (event.key === 'Enter') {
|
||||
this.trigger('search', {
|
||||
fieldValue: this.config.searchFields[this.state.selectedFieldId],
|
||||
searchTerm: this.state.searchInput,
|
||||
});
|
||||
this.state.showSearchFields = false;
|
||||
} else {
|
||||
if (this.state.selectedFieldId === null && this.config.searchFields.length) {
|
||||
this.state.selectedFieldId = 0;
|
||||
}
|
||||
this.state.showSearchFields = true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called when a search field is clicked.
|
||||
*/
|
||||
onClickSearchField(id) {
|
||||
this.state.showSearchFields = false;
|
||||
this.trigger('search', {
|
||||
fieldValue: this.config.searchFields[id],
|
||||
searchTerm: this.state.searchInput,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Given an arrow key, return the next selectedFieldId.
|
||||
* E.g. If the selectedFieldId is 1 and ArrowDown is pressed, return 2.
|
||||
*
|
||||
* @param {string} key vertical arrow key
|
||||
*/
|
||||
_fieldIdToSelect(key) {
|
||||
const length = this.config.searchFields.length;
|
||||
if (!length) return null;
|
||||
if (this.state.selectedFieldId === null) return 0;
|
||||
const current = this.state.selectedFieldId || length;
|
||||
return (current + (key === 'ArrowDown' ? 1 : -1)) % length;
|
||||
}
|
||||
_hideOptions() {
|
||||
this.state.showFilterOptions = false;
|
||||
this.state.showSearchFields = false;
|
||||
}
|
||||
}
|
||||
SearchBar.template = 'point_of_sale.SearchBar';
|
||||
SearchBar.defaultProps = {
|
||||
config: {
|
||||
searchFields: [],
|
||||
filter: {
|
||||
show: false,
|
||||
options: [],
|
||||
},
|
||||
},
|
||||
placeholder: 'Search ...',
|
||||
};
|
||||
|
||||
Registries.Component.add(SearchBar);
|
||||
|
||||
return SearchBar;
|
||||
});
|
||||
+1
-2
@@ -86,10 +86,9 @@ odoo.define('point_of_sale.OrderManagementScreen', function (require) {
|
||||
* @param {models.Order} order
|
||||
*/
|
||||
_setOrder(order) {
|
||||
this.env.pos.set_order(order);
|
||||
if (order === this.env.pos.get_order()) {
|
||||
this.close();
|
||||
} else {
|
||||
this.env.pos.set_order(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
|
||||
useListener('send-payment-cancel', this._sendPaymentCancel);
|
||||
useListener('send-payment-reverse', this._sendPaymentReverse);
|
||||
useListener('send-force-done', this._sendForceDone);
|
||||
onChangeOrder(this._onPrevOrder, this._onNewOrder);
|
||||
useErrorHandlers();
|
||||
NumberBuffer.use({
|
||||
// The numberBuffer listens to this event to update its state.
|
||||
// Basically means 'update the buffer when this event is triggered'
|
||||
@@ -33,6 +31,8 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
|
||||
// Note that the component listens to it.
|
||||
triggerAtInput: 'update-selected-paymentline',
|
||||
});
|
||||
onChangeOrder(this._onPrevOrder, this._onNewOrder);
|
||||
useErrorHandlers();
|
||||
this.payment_interface = null;
|
||||
}
|
||||
get currentOrder() {
|
||||
|
||||
@@ -54,6 +54,9 @@ odoo.define('point_of_sale.ProductsWidget', function(require) {
|
||||
this.selectedCategoryId,
|
||||
].map(id => this.env.pos.db.get_category_by_id(id));
|
||||
}
|
||||
get hasNoCategories() {
|
||||
return this.env.pos.db.get_category_childs_ids(0).length === 0;
|
||||
}
|
||||
_switchCategory(event) {
|
||||
this.env.pos.set('selectedCategoryId', event.detail);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
odoo.define('point_of_sale.TicketScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const IndependentToOrderScreen = require('point_of_sale.IndependentToOrderScreen');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { posbus } = require('point_of_sale.utils');
|
||||
|
||||
class TicketScreen extends IndependentToOrderScreen {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
useListener('close-screen', this.close);
|
||||
useListener('filter-selected', this._onFilterSelected);
|
||||
useListener('search', this._onSearch);
|
||||
this.searchDetails = {};
|
||||
this.filter = null;
|
||||
this._initializeSearchFieldConstants();
|
||||
}
|
||||
mounted() {
|
||||
posbus.on('ticket-button-clicked', this, this.close);
|
||||
this.env.pos.get('orders').on('add remove change', () => this.render(), this);
|
||||
this.env.pos.on('change:selectedOrder', () => this.render(), this);
|
||||
}
|
||||
willUnmount() {
|
||||
posbus.off('ticket-button-clicked', this);
|
||||
this.env.pos.get('orders').off('add remove change', null, this);
|
||||
this.env.pos.off('change:selectedOrder', null, this);
|
||||
}
|
||||
_onFilterSelected(event) {
|
||||
this.filter = event.detail.filter;
|
||||
this.render();
|
||||
}
|
||||
_onSearch(event) {
|
||||
const searchDetails = event.detail;
|
||||
Object.assign(this.searchDetails, searchDetails);
|
||||
this.render();
|
||||
}
|
||||
/**
|
||||
* Override to conditionally show the new ticket button.
|
||||
*/
|
||||
get showNewTicketButton() {
|
||||
return true;
|
||||
}
|
||||
get orderList() {
|
||||
return this.env.pos.get_order_list();
|
||||
}
|
||||
get filteredOrderList() {
|
||||
const filterCheck = (order) => {
|
||||
if (this.filter && this.filter !== 'All Tickets') {
|
||||
const screen = order.get_screen_data();
|
||||
return this.filter === this.constants.screenToStatusMap[screen.name];
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const { fieldValue, searchTerm } = this.searchDetails;
|
||||
const fieldAccessor = this._searchFields[fieldValue];
|
||||
const searchCheck = (order) => {
|
||||
if (!fieldAccessor) return true;
|
||||
const fieldValue = fieldAccessor(order);
|
||||
if (fieldValue === null) return true;
|
||||
if (!searchTerm) return true;
|
||||
return fieldValue && fieldValue.toString().toLowerCase().includes(searchTerm.toLowerCase());
|
||||
};
|
||||
const predicate = (order) => {
|
||||
return filterCheck(order) && searchCheck(order);
|
||||
};
|
||||
return this.orderList.filter(predicate);
|
||||
}
|
||||
selectOrder(order) {
|
||||
this._setOrder(order);
|
||||
if (order === this.env.pos.get_order()) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
_setOrder(order) {
|
||||
this.env.pos.set_order(order);
|
||||
}
|
||||
createNewOrder() {
|
||||
this.env.pos.add_new_order();
|
||||
}
|
||||
async deleteOrder(order) {
|
||||
const screen = order.get_screen_data();
|
||||
if (['ProductScreen', 'PaymentScreen'].includes(screen.name) && order.get_orderlines().length > 0) {
|
||||
const { confirmed } = await this.showPopup('ConfirmPopup', {
|
||||
title: 'Existing orderlines',
|
||||
body: `${order.name} has total amount of ${this.getTotal(
|
||||
order
|
||||
)}, are you sure you want delete this order?`,
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
if (order) {
|
||||
order.destroy({ reason: 'abandon' });
|
||||
}
|
||||
posbus.trigger('order-deleted');
|
||||
}
|
||||
getDate(order) {
|
||||
return moment(order.creation_date).format('YYYY-MM-DD hh:mm A');
|
||||
}
|
||||
getTotal(order) {
|
||||
return this.env.pos.format_currency(order.get_total_with_tax());
|
||||
}
|
||||
getCustomer(order) {
|
||||
return order.get_client_name();
|
||||
}
|
||||
getEmployee(order) {
|
||||
return order.employee.name;
|
||||
}
|
||||
getStatus(order) {
|
||||
const screen = order.get_screen_data();
|
||||
return this.constants.screenToStatusMap[screen.name];
|
||||
}
|
||||
/**
|
||||
* Hide the delete button if one of the payments is a 'done' electronic payment.
|
||||
*/
|
||||
hideDeleteButton(order) {
|
||||
return order
|
||||
.get_paymentlines()
|
||||
.some((payment) => payment.is_electronic() && payment.get_payment_status() === 'done');
|
||||
}
|
||||
get searchBarConfig() {
|
||||
return {
|
||||
searchFields: this.constants.searchFieldNames,
|
||||
filter: { show: true, options: ['All Tickets', 'Ongoing', 'Payment', 'Receipt'] },
|
||||
};
|
||||
}
|
||||
/**
|
||||
* An object with keys containing the search field names which map to functions.
|
||||
* The mapped functions will be used to generate representative string for the order
|
||||
* to match the search term when searching.
|
||||
* E.g. Given 2 orders, search those with `Receipt Number` containing `1111`.
|
||||
* ```
|
||||
* orders = [{
|
||||
* name: '000-1111-222'
|
||||
* total: 10,
|
||||
* }, {
|
||||
* name: '444-5555-666'
|
||||
* total: 15,
|
||||
* }]
|
||||
* ```
|
||||
* `Receipt Number` search field maps to the `name` of the order. So, the orders will be
|
||||
* represented by their name, and the search will result to:
|
||||
* ```
|
||||
* result = [{
|
||||
* name: '000-1111-222',
|
||||
* total: 10,
|
||||
* }]
|
||||
* ```
|
||||
* @returns Record<string, (models.Order) => string>
|
||||
*/
|
||||
get _searchFields() {
|
||||
return {
|
||||
'Receipt Number': (order) => order.name,
|
||||
Date: (order) => moment(order.creation_date).format('YYYY-MM-DD hh:mm A'),
|
||||
Customer: (order) => order.get_client_name(),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Maps the order screen params to order status.
|
||||
*/
|
||||
get _screenToStatusMap() {
|
||||
return {
|
||||
ProductScreen: 'Ongoing',
|
||||
PaymentScreen: 'Payment',
|
||||
ReceiptScreen: 'Receipt',
|
||||
};
|
||||
}
|
||||
_initializeSearchFieldConstants() {
|
||||
this.constants = {};
|
||||
Object.assign(this.constants, {
|
||||
searchFieldNames: Object.keys(this._searchFields),
|
||||
screenToStatusMap: this._screenToStatusMap,
|
||||
});
|
||||
}
|
||||
}
|
||||
TicketScreen.template = 'TicketScreen';
|
||||
|
||||
Registries.Component.add(TicketScreen);
|
||||
|
||||
return TicketScreen;
|
||||
});
|
||||
@@ -11,6 +11,8 @@ odoo.define('web.web_client', function (require) {
|
||||
owl.config.mode = env.isDebug() ? 'dev' : 'prod';
|
||||
owl.Component.env = env;
|
||||
|
||||
Registries.Component.add(owl.misc.Portal);
|
||||
|
||||
function setupResponsivePlugin(env) {
|
||||
const isMobile = () => window.innerWidth <= 768;
|
||||
env.isMobile = isMobile();
|
||||
|
||||
@@ -717,11 +717,11 @@ exports.PosModel = Backbone.Model.extend({
|
||||
var order_list = this.get_order_list();
|
||||
if( (reason === 'abandon' || removed_order.temporary) && order_list.length > 0){
|
||||
// when we intentionally remove an unfinished order, and there is another existing one
|
||||
this.set_order(order_list[index] || order_list[order_list.length -1]);
|
||||
this.set_order(order_list[index] || order_list[order_list.length - 1], { silent: true });
|
||||
}else{
|
||||
// when the order was automatically removed after completion,
|
||||
// or when we intentionally delete the only concurrent order
|
||||
this.add_new_order();
|
||||
this.add_new_order({ silent: true });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -739,10 +739,10 @@ exports.PosModel = Backbone.Model.extend({
|
||||
this.db.set_cashier(this.get('cashier'));
|
||||
},
|
||||
// creates a new empty order and sets it as the current order
|
||||
add_new_order: function(){
|
||||
add_new_order: function(options){
|
||||
var order = new exports.Order({},{pos:this});
|
||||
this.get('orders').add(order);
|
||||
this.set('selectedOrder', order);
|
||||
this.set('selectedOrder', order, options);
|
||||
return order;
|
||||
},
|
||||
/**
|
||||
@@ -810,8 +810,8 @@ exports.PosModel = Backbone.Model.extend({
|
||||
},
|
||||
|
||||
// change the current order
|
||||
set_order: function(order){
|
||||
this.set({ selectedOrder: order });
|
||||
set_order: function(order, options){
|
||||
this.set({ selectedOrder: order }, options);
|
||||
},
|
||||
|
||||
// return the list of unpaid orders
|
||||
@@ -2469,6 +2469,12 @@ exports.Paymentline = Backbone.Model.extend({
|
||||
ticket: this.ticket,
|
||||
};
|
||||
},
|
||||
// If payment status is a non-empty string, then it is an electronic payment.
|
||||
// TODO: There has to be a less confusing way to distinguish simple payments
|
||||
// from electronic transactions. Perhaps use a flag?
|
||||
is_electronic: function() {
|
||||
return Boolean(this.get_payment_status());
|
||||
},
|
||||
});
|
||||
|
||||
var PaymentlineCollection = Backbone.Collection.extend({
|
||||
@@ -3280,23 +3286,27 @@ exports.Order = Backbone.Model.extend({
|
||||
// the order also stores the screen status, as the PoS supports
|
||||
// different active screens per order. This method is used to
|
||||
// store the screen status.
|
||||
set_screen_data: function(key,value){
|
||||
if(arguments.length === 2){
|
||||
this.screen_data[key] = value;
|
||||
}else if(arguments.length === 1){
|
||||
for(var key in arguments[0]){
|
||||
this.screen_data[key] = arguments[0][key];
|
||||
}
|
||||
}
|
||||
set_screen_data: function(value){
|
||||
this.screen_data['value'] = value;
|
||||
},
|
||||
//see set_screen_data
|
||||
get_screen_data: function(key){
|
||||
return this.screen_data[key];
|
||||
get_screen_data: function(){
|
||||
const screen = this.screen_data['value'];
|
||||
// If no screen data is saved
|
||||
// no payment line -> product screen
|
||||
// with payment line -> payment screen
|
||||
if (!screen) {
|
||||
if (this.get_paymentlines().length > 0) return { name: 'PaymentScreen' };
|
||||
return { name: 'ProductScreen' };
|
||||
}
|
||||
if (screen.name !== 'ReceiptScreen' && this.get_paymentlines().length > 0) {
|
||||
return { name: 'PaymentScreen' };
|
||||
}
|
||||
return screen;
|
||||
},
|
||||
wait_for_push_order: function () {
|
||||
return this.is_to_email();
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Object} object to use as props for instantiating OrderReceipt.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
odoo.define('point_of_sale.utils', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { EventBus } = owl.core;
|
||||
|
||||
function getFileAsText(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!file) {
|
||||
@@ -43,5 +45,5 @@ odoo.define('point_of_sale.utils', function (require) {
|
||||
);
|
||||
}
|
||||
|
||||
return { getFileAsText, nextFrame, isRpcError };
|
||||
return { getFileAsText, nextFrame, isRpcError, posbus: new EventBus() };
|
||||
});
|
||||
|
||||
@@ -4,15 +4,22 @@
|
||||
<t t-name="Chrome" owl="1">
|
||||
<div class="pos" t-att-class="{ 'big-scrollbars': state.uiState.hasBigScrollBars }">
|
||||
<div class="pos-receipt-print"></div>
|
||||
<div class="pos-topheader" t-att-class="{ oe_hidden: state.uiState !== 'READY' }">
|
||||
<div t-if="tempScreen.isShown" class="block-top-header" />
|
||||
<div class="pos-branding" t-if= "!env.isMobile">
|
||||
<img class="pos-logo" t-on-click="trigger('toggle-debug-widget')"
|
||||
src="/point_of_sale/static/src/img/logo.png" alt="Logo" />
|
||||
<TicketButton isTicketScreenShown="isTicketScreenShown" />
|
||||
</div>
|
||||
<div class="pos-rightheader">
|
||||
<TicketButton isTicketScreenShown="isTicketScreenShown" t-if="env.isMobile" />
|
||||
<div class="search-bar-portal" />
|
||||
<div class="status-buttons-portal" />
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="state.uiState === 'READY'">
|
||||
<div class="pos-topheader">
|
||||
<div t-if="tempScreen.isShown" class="block-top-header" />
|
||||
<div class="pos-branding">
|
||||
<img t-if= "!env.isMobile" class="pos-logo" t-on-click="trigger('toggle-debug-widget')"
|
||||
src="/point_of_sale/static/src/img/logo.png" alt="Logo" />
|
||||
</div>
|
||||
<div class="pos-rightheader">
|
||||
<OrderSelector />
|
||||
<Portal target="'.pos .status-buttons-portal'">
|
||||
<div class="status-buttons">
|
||||
<t t-if="!env.isMobile">
|
||||
<CashierName />
|
||||
</t>
|
||||
@@ -23,7 +30,7 @@
|
||||
<SyncNotification />
|
||||
<HeaderButton />
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
<div class="pos-content">
|
||||
<div class="window">
|
||||
<div class="subwindow">
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<div class="order-selector" t-name="OrderSelector" owl="1">
|
||||
<t t-if="chromeContext.showOrderSelector">
|
||||
<span class="orders">
|
||||
<t t-foreach="orderList" t-as="order" t-key="order.cid">
|
||||
<t t-if="order === env.pos.get_order()">
|
||||
<span class="order-button select-order selected">
|
||||
<span class="order-sequence">
|
||||
<t t-esc="order.sequence_number" />
|
||||
</span>
|
||||
<t t-esc="creationTime(order)" />
|
||||
</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="order-button select-order" t-on-click="selectOrder(order)">
|
||||
<span class="order-sequence">
|
||||
<t t-esc="order.sequence_number" />
|
||||
</span>
|
||||
</span>
|
||||
</t>
|
||||
</t>
|
||||
</span>
|
||||
<span class="order-button square neworder-button" t-on-click="addNewOrder">
|
||||
<i class="fa fa-plus" role="img" aria-label="New order" title="New order" />
|
||||
</span>
|
||||
<span class="order-button square deleteorder-button" t-on-click="deleteCurrentOrder">
|
||||
<i class="fa fa-minus" role="img" aria-label="Delete order" title="Delete order" />
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="TicketButton" owl="1">
|
||||
<div class="ticket-button" t-att-class="{ highlight: props.isTicketScreenShown }" t-on-click="onClick">
|
||||
<div class="with-badge" t-att-badge="count">
|
||||
<i class="fa fa-ticket" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div>Tickets</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.SearchBar" owl="1">
|
||||
<div class="pos-search-bar">
|
||||
<div class="search">
|
||||
<span class="search-icon"><i class="fa fa-search"></i></span>
|
||||
<input class="radius-left" t-att-class="{ 'radius-right': !props.config.filter.show }"
|
||||
t-model="state.searchInput" t-on-keydown="onKeydown" type="text" t-att-placeholder="placeholder" />
|
||||
<ul t-if="state.showSearchFields and state.searchInput" class="fields">
|
||||
<t t-foreach="config.searchFields" t-as="value" t-key="value_index">
|
||||
<li t-att-class="{ highlight: value_index == state.selectedFieldId }"
|
||||
t-on-click="onClickSearchField(value_index)">
|
||||
<span class="field">
|
||||
<t t-esc="value"></t>
|
||||
</span>
|
||||
<span>: </span>
|
||||
<span class="term">
|
||||
<t t-esc="state.searchInput"></t>
|
||||
</span>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<div t-if="props.config.filter.show" class="filter radius-right"
|
||||
t-on-click.stop="state.showFilterOptions = !state.showFilterOptions">
|
||||
<span class="down-icon">
|
||||
<i class="fa fa-chevron-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span>
|
||||
<t t-esc="state.selectedFilter" />
|
||||
</span>
|
||||
<ul t-if="state.showFilterOptions" class="options">
|
||||
<t t-foreach="config.filter.options" t-as="option" t-key="option">
|
||||
<li t-on-click="selectFilter(option)">
|
||||
<t t-esc="option"></t>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
<t t-name="ProductsWidget" owl="1">
|
||||
<div class="products-widget">
|
||||
<ProductsWidgetControlPanel breadcrumbs="breadcrumbs"
|
||||
subcategories="subcategories" />
|
||||
|
||||
<t t-if="!hasNoCategories">
|
||||
<ProductsWidgetControlPanel breadcrumbs="breadcrumbs" subcategories="subcategories" />
|
||||
</t>
|
||||
<ProductList products="productsToDisplay" searchWord="searchWord" />
|
||||
</div>
|
||||
</t>
|
||||
|
||||
+22
-22
@@ -3,7 +3,10 @@
|
||||
|
||||
<t t-name="ProductsWidgetControlPanel" owl="1">
|
||||
<div class="products-widget-control">
|
||||
<div class="rightpane-header">
|
||||
<div class="rightpane-header" t-att-class="{
|
||||
'green-border-bottom': !env.pos.config.iface_display_categ_images,
|
||||
'grey-border-bottom': env.pos.config.iface_display_categ_images,
|
||||
}">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="breadcrumbs">
|
||||
<HomeCategoryBreadcrumb subcategories="props.subcategories" currentCat="props.breadcrumbs[props.breadcrumbs.length - 1]"/>
|
||||
@@ -13,36 +16,33 @@
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<!-- Searchbox -->
|
||||
<div class="searchbox">
|
||||
<input t-ref="search-word-input" placeholder="Search Products"
|
||||
t-on-keyup="updateSearch" />
|
||||
<span class="search-clear left">
|
||||
<i class="fa fa-search" />
|
||||
</span>
|
||||
<span class="search-clear right" t-on-click="clearSearch">
|
||||
<i class="fa fa-remove" />
|
||||
</span>
|
||||
</div>
|
||||
<!-- Subcategories -->
|
||||
<t t-if="props.subcategories.length > 0 and !env.pos.config.iface_display_categ_images and !env.isMobile">
|
||||
<t t-foreach="props.subcategories" t-as="category" t-key="category.id">
|
||||
<CategorySimpleButton category="category" />
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<!-- Subcategories -->
|
||||
<t t-if="props.subcategories.length > 0 && !env.isMobile">
|
||||
<t t-if="props.subcategories.length > 0 and env.pos.config.iface_display_categ_images and !env.isMobile">
|
||||
<div class="categories">
|
||||
<div class="category-list-scroller">
|
||||
<div class="category-list"
|
||||
t-att-class="{simple: !env.pos.config.iface_display_categ_images}">
|
||||
<div class="category-list">
|
||||
<t t-foreach="props.subcategories" t-as="category" t-key="category.id">
|
||||
<t t-if="env.pos.config.iface_display_categ_images">
|
||||
<CategoryButton category="category" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<CategorySimpleButton category="category" />
|
||||
</t>
|
||||
<CategoryButton category="category" />
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<Portal target="'.pos .search-bar-portal'">
|
||||
<div class="search-box">
|
||||
<span class="icon"><i class="fa fa-search"></i></span>
|
||||
<span t-on-click="clearSearch" class="clear-icon">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
</span>
|
||||
<input t-ref="search-word-input" type="text" placeholder="Search Products..." t-on-keyup="updateSearch" />
|
||||
</div>
|
||||
</Portal>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="TicketScreen" owl="1">
|
||||
<div class="ticket-screen screen">
|
||||
<div class="screen-content">
|
||||
<div class="controls">
|
||||
<div class="buttons">
|
||||
<button t-if="showNewTicketButton" class="highlight" t-on-click="createNewOrder">New Ticket</button>
|
||||
<button class="discard" t-on-click="trigger('close-screen')">Discard</button>
|
||||
</div>
|
||||
<SearchBar config="searchBarConfig" placeholder="'Search Tickets...'" />
|
||||
</div>
|
||||
<div class="orders">
|
||||
<div class="header-row">
|
||||
<div class="col start wide">Date</div>
|
||||
<div class="col start wide">Receipt Number</div>
|
||||
<div class="col start">Customer</div>
|
||||
<div class="col start">Employee</div>
|
||||
<div class="col end">Total</div>
|
||||
<div class="col start narrow">Status</div>
|
||||
<div class="col center very-narrow" name="delete"></div>
|
||||
</div>
|
||||
<t t-foreach="filteredOrderList" t-as="order" t-key="order.cid">
|
||||
<div class="order-row pointer" t-on-click="selectOrder(order)">
|
||||
<div class="col start wide">
|
||||
<t t-esc="getDate(order)"></t>
|
||||
</div>
|
||||
<div class="col start wide">
|
||||
<t t-esc="order.name"></t>
|
||||
</div>
|
||||
<div class="col start">
|
||||
<t t-esc="getCustomer(order)"></t>
|
||||
</div>
|
||||
<div class="col start">
|
||||
<t t-esc="getEmployee(order)"></t>
|
||||
</div>
|
||||
<div class="col end">
|
||||
<t t-esc="getTotal(order)"></t>
|
||||
</div>
|
||||
<div class="col start narrow">
|
||||
<t t-esc="getStatus(order)"></t>
|
||||
</div>
|
||||
<div t-if="!hideDeleteButton(order)" class="col center very-narrow delete-button" name="delete" t-on-click.stop="deleteOrder(order)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div t-else="" class="col center very-narrow delete-button"></div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -4,6 +4,7 @@ odoo.define('point_of_sale.tour.Chrome', function (require) {
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
|
||||
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
@@ -13,62 +14,75 @@ odoo.define('point_of_sale.tour.Chrome', function (require) {
|
||||
// Order 1 is at Product Screen
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '1', '2');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.checkStatus('-0001', 'Ongoing');
|
||||
|
||||
// Order 2 is at Payment Screen
|
||||
Chrome.do.newOrder();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Monitor Stand', '3', '4');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.checkStatus('-0002', 'Payment');
|
||||
|
||||
// Order 3 is at Receipt Screen
|
||||
Chrome.do.newOrder();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '5', '6');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.checkStatus('-0003', 'Receipt');
|
||||
|
||||
// Select order 1, should be at Product Screen
|
||||
Chrome.do.selectOrder('1');
|
||||
TicketScreen.do.selectOrder('-0001');
|
||||
ProductScreen.check.productIsDisplayed('Desk Pad');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Pad', '1.0', '2.0');
|
||||
|
||||
// Select order 2, should be at Payment Screen
|
||||
Chrome.do.selectOrder('2');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectOrder('-0002');
|
||||
PaymentScreen.check.emptyPaymentlines('12.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(false);
|
||||
|
||||
// Select order 3, should be at Receipt Screen
|
||||
Chrome.do.selectOrder('3');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
ReceiptScreen.check.changeIs('0.0');
|
||||
|
||||
// Pay order 1, with change
|
||||
Chrome.do.selectOrder('1');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectOrder('-0001');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('2 0');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.changeIs('18.0');
|
||||
|
||||
// Order 1 now should have Receipt status
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.checkStatus('-0001', 'Receipt');
|
||||
|
||||
// Select order 3, should still be at Receipt Screen
|
||||
// but change should be different.
|
||||
Chrome.do.selectOrder('3');
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
ReceiptScreen.check.changeIs('0.0');
|
||||
|
||||
// click next screen on order 3
|
||||
// then delete the new empty order
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
Chrome.do.deleteOrder();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.deleteOrder('-0004');
|
||||
TicketScreen.do.deleteOrder('-0001');
|
||||
|
||||
// Order 2 should be the current order
|
||||
// Deleting it should open a popup, confirm it.
|
||||
Chrome.do.deleteOrder();
|
||||
// After deleting order 1 above, order 2 became
|
||||
// the 2nd-row order and it has payment status
|
||||
TicketScreen.check.nthRowContains(2, 'Payment')
|
||||
TicketScreen.do.deleteOrder('-0002');
|
||||
Chrome.do.confirmPopup();
|
||||
|
||||
// Now left with order 1 in payment screen
|
||||
// go next screen
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
|
||||
// Invoice an order
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '5', '6');
|
||||
|
||||
@@ -5,6 +5,7 @@ odoo.define('point_of_sale.tour.OrderManagementScreen', function (require) {
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { ClientListScreen } = require('point_of_sale.tour.ClientListScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');
|
||||
const { makeFullOrder } = require('point_of_sale.tour.CompositeTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
@@ -70,11 +71,13 @@ odoo.define('point_of_sale.tour.OrderManagementScreen', function (require) {
|
||||
|
||||
// Add 2 orders, they should appear in order management screen
|
||||
// order 0006
|
||||
Chrome.do.newOrder();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '66', '6');
|
||||
|
||||
// order 0007, should be at payment screen
|
||||
Chrome.do.newOrder();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Monitor Stand', '55', '5');
|
||||
ProductScreen.do.clickCustomerButton();
|
||||
ClientListScreen.exec.setClient('Azure Interior');
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
odoo.define('point_of_sale.tour.TicketScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
|
||||
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '1', '2');
|
||||
ProductScreen.do.clickCustomerButton();
|
||||
ProductScreen.do.clickCustomer('Nicole Ford');
|
||||
ProductScreen.do.clickSetCustomer();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.nthRowContains(2, 'Nicole Ford');
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '1', '3');
|
||||
ProductScreen.do.clickCustomerButton();
|
||||
ProductScreen.do.clickCustomer('Brandon Freeman');
|
||||
ProductScreen.do.clickSetCustomer();
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.nthRowContains(3, 'Brandon Freeman');
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '1', '4');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.nthRowContains(4, 'Receipt');
|
||||
TicketScreen.do.selectFilter('Receipt');
|
||||
TicketScreen.check.nthRowContains(2, 'Receipt');
|
||||
TicketScreen.do.selectFilter('Payment');
|
||||
TicketScreen.check.nthRowContains(2, 'Payment');
|
||||
TicketScreen.do.selectFilter('Ongoing');
|
||||
TicketScreen.check.nthRowContains(2, 'Ongoing');
|
||||
TicketScreen.do.selectFilter('All');
|
||||
TicketScreen.check.nthRowContains(4, 'Receipt');
|
||||
TicketScreen.do.search('Customer', 'Nicole');
|
||||
TicketScreen.check.nthRowContains(2, 'Nicole');
|
||||
TicketScreen.do.search('Customer', 'Brandon');
|
||||
TicketScreen.check.nthRowContains(2, 'Brandon');
|
||||
TicketScreen.do.search('Receipt Number', '-0003');
|
||||
TicketScreen.check.nthRowContains(2, 'Receipt');
|
||||
|
||||
Tour.register('TicketScreenTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
});
|
||||
@@ -4,30 +4,6 @@ odoo.define('point_of_sale.tour.ChromeTourMethods', function (require) {
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
newOrder() {
|
||||
return [
|
||||
{
|
||||
content: 'new order',
|
||||
trigger: '.order-selector .neworder-button',
|
||||
},
|
||||
];
|
||||
}
|
||||
deleteOrder() {
|
||||
return [
|
||||
{
|
||||
content: 'delete current order',
|
||||
trigger: '.order-selector .deleteorder-button',
|
||||
},
|
||||
];
|
||||
}
|
||||
selectOrder(orderSequence) {
|
||||
return [
|
||||
{
|
||||
content: `select order '${orderSequence}'`,
|
||||
trigger: `.order-selector .order-sequence:contains("${orderSequence}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
confirmPopup() {
|
||||
return [
|
||||
{
|
||||
@@ -49,6 +25,17 @@ odoo.define('point_of_sale.tour.ChromeTourMethods', function (require) {
|
||||
},
|
||||
];
|
||||
}
|
||||
clickTicketButton() {
|
||||
return [
|
||||
{
|
||||
trigger: '.pos-topheader .ticket-button > div:contains("Tickets")',
|
||||
},
|
||||
{
|
||||
trigger: '.subwindow .ticket-screen',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('Chrome', Do);
|
||||
|
||||
@@ -31,7 +31,7 @@ odoo.define('point_of_sale.tour.ProductScreenTourMethods', function (require) {
|
||||
return [
|
||||
{
|
||||
content: `selecting '${name}' subcategory`,
|
||||
trigger: `.category-list .category-simple-button:contains("${name}")`,
|
||||
trigger: `.products-widget > .products-widget-control .category-simple-button:contains("${name}")`,
|
||||
},
|
||||
{
|
||||
content: `'${name}' subcategory selected`,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
odoo.define('point_of_sale.tour.TicketScreenTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
clickNewTicket() {
|
||||
return [{ trigger: '.ticket-screen .highlight' }];
|
||||
}
|
||||
clickDiscard() {
|
||||
return [{ trigger: '.ticket-screen button.discard' }];
|
||||
}
|
||||
selectOrder(orderName) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .order-row > .col:nth-child(2):contains("${orderName}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
deleteOrder(orderName) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .orders > .order-row > .col:contains("${orderName}") ~ .col[name="delete"]`,
|
||||
},
|
||||
];
|
||||
}
|
||||
selectFilter(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.pos-search-bar .filter`,
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .filter ul`,
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .filter ul li:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
search(field, searchWord) {
|
||||
return [
|
||||
{
|
||||
trigger: '.pos-search-bar input',
|
||||
run: `text ${searchWord}`,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* Manually trigger keydown event to show the search field list
|
||||
* because the previous step do not trigger keydown event.
|
||||
*/
|
||||
trigger: '.pos-search-bar input',
|
||||
run: function () {
|
||||
document
|
||||
.querySelector('.pos-search-bar input')
|
||||
.dispatchEvent(new KeyboardEvent('keydown', { key: '' }));
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .search ul li:contains("${field}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
checkStatus(orderName, status) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .order-row > .col:nth-child(2):contains("${orderName}") ~ .col:nth-child(6):contains(${status})`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if the nth row contains the given string.
|
||||
* Note that 1st row is the header-row.
|
||||
*/
|
||||
nthRowContains(n, string) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .orders > .order-row:nth-child(${n}):contains("${string}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
noNewTicketButton() {
|
||||
return [
|
||||
{
|
||||
trigger: '.ticket-screen .controls .buttons:nth-child(1):has(.discard)',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Execute {}
|
||||
|
||||
return createTourMethods('TicketScreen', Do, Check, Execute);
|
||||
});
|
||||
@@ -11,6 +11,9 @@ odoo.define('point_of_sale.test_env', async function (require) {
|
||||
const makeTestEnvironment = require('web.test_env');
|
||||
const env = require('web.env');
|
||||
const models = require('point_of_sale.models');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
Registries.Component.add(owl.misc.Portal);
|
||||
|
||||
await env.session.is_bound;
|
||||
const pos = new models.PosModel({
|
||||
|
||||
@@ -49,53 +49,6 @@ odoo.define('point_of_sale.tests.ChromeWidgets', function (require) {
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('OrderSelector', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
class Parent extends PopupControllerMixin(PosComponent) {}
|
||||
Parent.env = makePosTestEnv();
|
||||
Parent.env.chrome = new owl.Context({ showOrderSelector: true });
|
||||
Parent.template = xml/* html */ `
|
||||
<div>
|
||||
<OrderSelector></OrderSelector>
|
||||
<t t-if="popup.isShown" t-component="popup.component" t-props="popupProps" t-key="popup.name" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
const pos = Parent.env.pos;
|
||||
|
||||
const parent = new Parent();
|
||||
await parent.mount(testUtils.prepareTarget());
|
||||
|
||||
const plusButton = parent.el.querySelector('.neworder-button');
|
||||
const minusButton = parent.el.querySelector('.deleteorder-button');
|
||||
|
||||
await testUtils.dom.click(plusButton);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(2, parent.el.querySelectorAll('.order-sequence').length);
|
||||
await testUtils.dom.click(minusButton);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(1, parent.el.querySelectorAll('.order-sequence').length);
|
||||
|
||||
const product = Object.values(pos.db.product_by_id)[0];
|
||||
pos.get_order().add_product(product);
|
||||
|
||||
// try deleting the order with orderline
|
||||
await testUtils.dom.click(minusButton);
|
||||
await testUtils.nextTick();
|
||||
|
||||
// confirm popup should appear
|
||||
assert.ok(parent.el.querySelector('.popup'));
|
||||
// confirm deletion
|
||||
await testUtils.dom.click(parent.el.querySelector('.confirm'));
|
||||
await testUtils.nextTick();
|
||||
// there should be new order created
|
||||
assert.strictEqual(1, parent.el.querySelectorAll('.order-sequence').length);
|
||||
|
||||
parent.unmount();
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SyncNotification', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
|
||||
@@ -244,8 +244,10 @@ odoo.define('point_of_sale.tests.ProductScreen', function (require) {
|
||||
}
|
||||
Parent.env = makePosTestEnv();
|
||||
Parent.template = xml/* html */ `
|
||||
<div>
|
||||
<ProductsWidgetControlPanel breadcrumbs="breadcrumbs" subcategories="subcategories" />
|
||||
<div class="pos">
|
||||
<div class="search-bar-portal">
|
||||
<ProductsWidgetControlPanel breadcrumbs="breadcrumbs" subcategories="subcategories" />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -337,7 +339,7 @@ odoo.define('point_of_sale.tests.ProductScreen', function (require) {
|
||||
});
|
||||
};
|
||||
|
||||
const inputEl = parent.el.querySelector('.searchbox input');
|
||||
const inputEl = parent.el.querySelector('.search-box input');
|
||||
await testUtils.dom.triggerEvent(inputEl, 'keyup', { key: 'A' });
|
||||
// Triggering keyup event doesn't type the key to the input
|
||||
// so we manually assign the value of the input.
|
||||
@@ -358,7 +360,7 @@ odoo.define('point_of_sale.tests.ProductScreen', function (require) {
|
||||
assert.verifySteps(['ABCD']);
|
||||
|
||||
// clear the search bar
|
||||
await testUtils.dom.click(parent.el.querySelector('.search-clear.right'));
|
||||
await testUtils.dom.click(parent.el.querySelector('.search-box .clear-icon'));
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps(['cleared']);
|
||||
assert.ok(inputEl.value === '', 'value of the input element should be empty');
|
||||
|
||||
@@ -510,3 +510,7 @@ class TestUi(TestPointOfSaleHttpCommon):
|
||||
self.main_pos_config.write({ 'product_configurator': True })
|
||||
self.main_pos_config.open_session_cb(check_coa=False)
|
||||
self.start_tour("/pos/web?config_id=%d" % self.main_pos_config, 'ProductConfiguratorTour', login="admin", step_delay=50)
|
||||
|
||||
def test_05_ticket_screen(self):
|
||||
self.main_pos_config.open_session_cb(check_coa=False)
|
||||
self.start_tour("/pos/web?config_id=%d" % self.main_pos_config.id, 'TicketScreenTour', login="admin", step_delay=50)
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderDetails.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderlineDetails.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/OrderManagementScreen/ReprintReceiptScreen.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenElectronicPayment.js"></script>
|
||||
@@ -68,17 +69,18 @@
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/OrderSelector.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/CashierName.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/OrderManagementButton.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Misc/Draggable.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Misc/NotificationSound.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Misc/SearchBar.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/ChromeWidgets/DebugWidget.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/Popups/ErrorPopup.js"></script>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/helpers/utils.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/helpers/TicketScreenTourMethods.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/helpers/PaymentScreenTourMethods.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/helpers/ProductConfiguratorTourMethods.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/helpers/OrderManagementScreenTourMethods.js"></script>
|
||||
@@ -23,6 +24,7 @@
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/OrderManagementScreen.tour.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/ReceiptScreen.tour.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/Chrome.tour.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/tests/tours/TicketScreen.tour.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -122,11 +122,11 @@
|
||||
|
||||
.pos .pos-rightheader .header-button.lock-button {
|
||||
font-size: 20px;
|
||||
color: green;
|
||||
color: rgb(94, 185, 55);
|
||||
transition: all 200ms ease-in-out;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.pos .pos-rightheader .header-button.lock-button:hover {
|
||||
color: red;
|
||||
color: rgb(197, 52, 0);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ This module adds several features to the Point of Sale that are specific to rest
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/Resizeable.xml',
|
||||
'static/src/xml/Chrome.xml',
|
||||
'static/src/xml/Screens/TicketScreen.xml',
|
||||
'static/src/xml/Screens/OrderManagementScreen/OrderList.xml',
|
||||
'static/src/xml/Screens/OrderManagementScreen/OrderRow.xml',
|
||||
'static/src/xml/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.xml',
|
||||
@@ -44,7 +46,6 @@ This module adds several features to the Point of Sale that are specific to rest
|
||||
'static/src/xml/Screens/FloorScreen/EditBar.xml',
|
||||
'static/src/xml/Screens/FloorScreen/TableWidget.xml',
|
||||
'static/src/xml/Screens/FloorScreen/EditableTable.xml',
|
||||
'static/src/xml/ChromeWidgets/OrderSelector.xml',
|
||||
'static/src/xml/ChromeWidgets/BackToFloorButton.xml',
|
||||
'static/src/xml/multiprint.xml',
|
||||
],
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
odoo.define('pos_restaurant.BackToFloorButton', function(require) {
|
||||
odoo.define('pos_restaurant.BackToFloorButton', function (require) {
|
||||
'use strict';
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { posbus } = require('point_of_sale.utils');
|
||||
|
||||
class BackToFloorButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
useListener('click', this._backToFloorScreen);
|
||||
mounted() {
|
||||
posbus.on('table-set', this, this.render);
|
||||
}
|
||||
willUnmount() {
|
||||
posbus.on('table-set', this);
|
||||
}
|
||||
get table() {
|
||||
return this.props.table;
|
||||
return (this.env.pos && this.env.pos.table) || null;
|
||||
}
|
||||
get floor() {
|
||||
return this.props.table.floor;
|
||||
const table = this.table;
|
||||
return table ? table.floor : null;
|
||||
}
|
||||
async _backToFloorScreen() {
|
||||
get hasTable() {
|
||||
return this.table !== null;
|
||||
}
|
||||
backToFloorScreen() {
|
||||
this.showScreen('FloorScreen', { floor: this.floor });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
odoo.define('pos_restaurant.OrderSelector', function(require) {
|
||||
'use strict';
|
||||
|
||||
const OrderSelector = require('point_of_sale.OrderSelector');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const PosResOrderSelector = OrderSelector =>
|
||||
class extends OrderSelector {
|
||||
get currentTable() {
|
||||
return this.env.pos.table;
|
||||
}
|
||||
get backToFloorButtonIsShown() {
|
||||
return this.env.pos.config.iface_floorplan && this.currentTable;
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(OrderSelector, PosResOrderSelector);
|
||||
|
||||
return OrderSelector;
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
odoo.define('pos_restaurant.TicketButton', function (require) {
|
||||
'use strict';
|
||||
|
||||
const TicketButton = require('point_of_sale.TicketButton');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { posbus } = require('point_of_sale.utils');
|
||||
|
||||
const PosResTicketButton = (TicketButton) =>
|
||||
class extends TicketButton {
|
||||
mounted() {
|
||||
posbus.on('table-set', this, this.render);
|
||||
}
|
||||
willUnmount() {
|
||||
posbus.off('table-set', this);
|
||||
}
|
||||
/**
|
||||
* If no table is set to pos, which means the current main screen
|
||||
* is floor screen, then the order count should be based on all the orders.
|
||||
*/
|
||||
get count() {
|
||||
if (!this.env.pos || !this.env.pos.config) return 0;
|
||||
if (this.env.pos.config.iface_floorplan && !this.env.pos.table) {
|
||||
return this.env.pos.get('orders').models.length;
|
||||
} else {
|
||||
return super.count;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(TicketButton, PosResTicketButton);
|
||||
|
||||
return TicketButton;
|
||||
});
|
||||
@@ -171,12 +171,12 @@ odoo.define('pos_restaurant.FloorScreen', function (require) {
|
||||
}
|
||||
}
|
||||
}
|
||||
async _onSelectTable(event) {
|
||||
_onSelectTable(event) {
|
||||
const table = event.detail;
|
||||
if (this.state.isEditMode) {
|
||||
this.state.selectedTableId = table.id;
|
||||
} else {
|
||||
await this.env.pos.set_table(table);
|
||||
this.env.pos.set_table(table);
|
||||
}
|
||||
}
|
||||
_onDeselectTable() {
|
||||
|
||||
+1
-2
@@ -12,10 +12,9 @@ odoo.define('pos_restaurant.OrderManagementScreen', function (require) {
|
||||
_setOrder(order) {
|
||||
if (this.env.pos.config.module_pos_restaurant) {
|
||||
const currentOrder = this.env.pos.get_order();
|
||||
this.env.pos.set_table(order.table, order);
|
||||
if (currentOrder && currentOrder.uid === order.uid) {
|
||||
this.close();
|
||||
} else {
|
||||
this.env.pos.set_table(order.table, order);
|
||||
}
|
||||
} else {
|
||||
super._setOrder(order);
|
||||
|
||||
@@ -20,6 +20,18 @@ odoo.define('pos_restaurant.ReceiptScreen', function(require) {
|
||||
return super.nextScreen;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If this order is synced to/from the server, then the draft needs to be deleted
|
||||
* after the order is paid and the receipt screen is exited via 'Next Order' button.
|
||||
* Thus, we set this order to be removed from server. Note that only the draft version
|
||||
* is deleted if we trace the algorithm.
|
||||
*/
|
||||
orderDone() {
|
||||
if (this.env.pos.config.iface_floorplan) {
|
||||
this.env.pos.db.set_order_to_remove_from_server(this.currentOrder);
|
||||
}
|
||||
super.orderDone();
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(ReceiptScreen, PosResReceiptScreen);
|
||||
|
||||
@@ -55,7 +55,7 @@ odoo.define('pos_restaurant.SplitBillScreen', function(require) {
|
||||
} else {
|
||||
this._setQuantityOnCurrentOrder();
|
||||
|
||||
this.newOrder.set_screen_data('screen', { name: 'PaymentScreen' });
|
||||
this.newOrder.set_screen_data({ name: 'PaymentScreen' });
|
||||
|
||||
// for the kitchen printer we assume that everything
|
||||
// has already been sent to the kitchen before splitting
|
||||
@@ -72,7 +72,7 @@ odoo.define('pos_restaurant.SplitBillScreen', function(require) {
|
||||
this.newOrder.set_customer_count(1);
|
||||
const newCustomerCount = this.currentOrder.get_customer_count() - 1;
|
||||
this.currentOrder.set_customer_count(newCustomerCount || 1);
|
||||
this.currentOrder.set_screen_data('screen', { name: 'ProductScreen' });
|
||||
this.currentOrder.set_screen_data({ name: 'ProductScreen' });
|
||||
|
||||
this.env.pos.get('orders').add(this.newOrder);
|
||||
this.env.pos.set('selectedOrder', this.newOrder);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
odoo.define('pos_restaurant.TicketScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const TicketScreen = require('point_of_sale.TicketScreen');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { posbus } = require('point_of_sale.utils');
|
||||
|
||||
const PosResTicketScreen = (TicketScreen) =>
|
||||
class extends TicketScreen {
|
||||
close() {
|
||||
super.close();
|
||||
if (!this.env.pos.config.iface_floorplan) {
|
||||
// Make sure the 'table-set' event is triggered
|
||||
// to properly rerender the components that listens to it.
|
||||
posbus.trigger('table-set');
|
||||
}
|
||||
}
|
||||
getTable(order) {
|
||||
return `${order.table.floor.name} (${order.table.name})`;
|
||||
}
|
||||
get _searchFields() {
|
||||
if (!this.env.pos.config.iface_floorplan) {
|
||||
return super._searchFields;
|
||||
}
|
||||
return Object.assign({}, super._searchFields, {
|
||||
Table: (order) => `${order.table.floor.name} (${order.table.name})`,
|
||||
});
|
||||
}
|
||||
_setOrder(order) {
|
||||
if (!this.env.pos.config.iface_floorplan) {
|
||||
super._setOrder(order);
|
||||
} else if (order !== this.env.pos.get_order()) {
|
||||
// Only call set_table if the order is not the same as the current order.
|
||||
// This is to prevent syncing to the server because syncing is only intended
|
||||
// when going back to the floorscreen or opening a table.
|
||||
this.env.pos.set_table(order.table, order);
|
||||
}
|
||||
}
|
||||
get showNewTicketButton() {
|
||||
return this.env.pos.config.iface_floorplan ? Boolean(this.env.pos.table) : super.showNewTicketButton;
|
||||
}
|
||||
get orderList() {
|
||||
if (this.env.pos.table) {
|
||||
return super.orderList;
|
||||
} else {
|
||||
return this.env.pos.get('orders').models;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(TicketScreen, PosResTicketScreen);
|
||||
|
||||
return TicketScreen;
|
||||
});
|
||||
@@ -2,6 +2,8 @@ odoo.define('pos_restaurant.floors', function (require) {
|
||||
"use strict";
|
||||
|
||||
var models = require('point_of_sale.models');
|
||||
const { Gui } = require('point_of_sale.Gui');
|
||||
const { posbus } = require('point_of_sale.utils');
|
||||
|
||||
// At POS Startup, load the floors, and add them to the pos model
|
||||
models.load_models({
|
||||
@@ -213,7 +215,12 @@ models.PosModel = models.PosModel.extend({
|
||||
this._get_from_server(table.id).then(function (server_orders) {
|
||||
var orders = self.get_order_list();
|
||||
orders.forEach(function(order){
|
||||
if (order.server_id){
|
||||
const screen = order.get_screen_data();
|
||||
// Only remove the 'Receipt' status order when 'Next Order'
|
||||
// from the receipt screen is clicked. We are bypassing the
|
||||
// removal of paid order here to make sure those orders are
|
||||
// still in the tickets list.
|
||||
if (order.server_id && screen.name !== 'ReceiptScreen'){
|
||||
self.get("orders").remove(order);
|
||||
order.destroy();
|
||||
}
|
||||
@@ -257,6 +264,7 @@ models.PosModel = models.PosModel.extend({
|
||||
if(!table){
|
||||
this.sync_from_server(table, this.get_order_list(), this.get_order_with_uid());
|
||||
this.set_order(null);
|
||||
this.table = null;
|
||||
} else if (this.order_to_transfer_to_different_table) {
|
||||
var order_ids = this.get_order_with_uid();
|
||||
|
||||
@@ -269,6 +277,7 @@ models.PosModel = models.PosModel.extend({
|
||||
this.table = table;
|
||||
this.sync_to_server(table, order);
|
||||
}
|
||||
posbus.trigger('table-set');
|
||||
},
|
||||
|
||||
// if we have tables, we do not load a default order, as the default order will be
|
||||
@@ -284,9 +293,12 @@ models.PosModel = models.PosModel.extend({
|
||||
add_new_order: function() {
|
||||
if (this.config.iface_floorplan) {
|
||||
if (this.table) {
|
||||
return _super_posmodel.add_new_order.call(this);
|
||||
return _super_posmodel.add_new_order.apply(this, arguments);
|
||||
} else {
|
||||
console.warn("WARNING: orders cannot be created when there is no active table in restaurant mode");
|
||||
Gui.showPopup('ConfirmPopup', {
|
||||
title: 'Unable to create order',
|
||||
body: 'Orders cannot be created when there is no active table in restaurant mode',
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
@@ -298,7 +310,7 @@ models.PosModel = models.PosModel.extend({
|
||||
// get the list of unpaid orders (associated to the current table)
|
||||
get_order_list: function() {
|
||||
var orders = _super_posmodel.get_order_list.call(this);
|
||||
if (!this.config.iface_floorplan) {
|
||||
if (!(this.config && this.config.iface_floorplan)) {
|
||||
return orders;
|
||||
} else if (!this.table) {
|
||||
return [];
|
||||
@@ -345,7 +357,7 @@ models.PosModel = models.PosModel.extend({
|
||||
this.db.set_order_to_remove_from_server(removed_order);
|
||||
}
|
||||
if( (reason === 'abandon' || removed_order.temporary) && order_list.length > 0){
|
||||
this.set_order(order_list[index] || order_list[order_list.length -1]);
|
||||
this.set_order(order_list[index] || order_list[order_list.length - 1], { silent: true });
|
||||
} else if (order_list.length === 0) {
|
||||
this.set_order(null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="Chrome" t-inherit="point_of_sale.Chrome" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('search-bar-portal')]" position="before">
|
||||
<BackToFloorButton />
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="BackToFloorButton" owl="1">
|
||||
<span class="order-button floor-button">
|
||||
<span t-if="hasTable" class="order-button floor-button" t-on-click="backToFloorScreen">
|
||||
<i class="fa fa-angle-double-left" role="img" aria-label="Back to floor" title="Back to floor" />
|
||||
<span> </span>
|
||||
<span t-esc="floor.name" />
|
||||
@@ -13,6 +13,7 @@
|
||||
<span> )</span>
|
||||
</span>
|
||||
</span>
|
||||
<span t-else=""></span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="OrderSelector" t-inherit="point_of_sale.OrderSelector" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('order-selector')]//span[hasclass('orders')]//t" position="before">
|
||||
<BackToFloorButton t-if="backToFloorButtonIsShown" table="currentTable" />
|
||||
</xpath>
|
||||
<xpath expr="//span[hasclass('order-sequence')]" position="replace">
|
||||
<span class="order-sequence">
|
||||
<t t-if="order.server_id and env.pos.pos_session.login_number != order.uid.split('-')[1]">
|
||||
<t t-esc="order.uid.substring(order.uid.indexOf('-') +1)" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="order.sequence_number" />
|
||||
</t>
|
||||
</span>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="TicketScreen" t-inherit="point_of_sale.TicketScreen" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('header-row')]//div[@name='delete']" position="before">
|
||||
<div t-if="env.pos.config.iface_floorplan" class="col start" name="table">Table</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('order-row')]//div[@name='delete']" position="before">
|
||||
<div t-if="env.pos.config.iface_floorplan" class="col start" name="table">
|
||||
<t t-esc="getTable(order)"></t>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -11,6 +11,7 @@ odoo.define('pos_restaurant.tour.OrderManagementScreen', function (require) {
|
||||
} = require('point_of_sale.tour.OrderManagementScreenTourMethods');
|
||||
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
|
||||
const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
@@ -36,7 +37,8 @@ odoo.define('pos_restaurant.tour.OrderManagementScreen', function (require) {
|
||||
['Water', '3', '4'],
|
||||
['Minute Maid', '5', '6']
|
||||
);
|
||||
Chrome.do.newOrder();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addMultiOrderlines(['Coca-Cola', '1', '2'], ['Minute Maid', '5', '6']);
|
||||
Chrome.do.backToFloor();
|
||||
|
||||
@@ -47,7 +49,8 @@ odoo.define('pos_restaurant.tour.OrderManagementScreen', function (require) {
|
||||
['Water', '9', '10'],
|
||||
['Minute Maid', '11', '12']
|
||||
);
|
||||
Chrome.do.newOrder();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addMultiOrderlines(['Coca-Cola', '13', '14'], ['Minute Maid', '15', '16']);
|
||||
Chrome.do.backToFloor();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ odoo.define('pos_restaurant.tour.SplitBillScreen', function (require) {
|
||||
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
|
||||
const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');
|
||||
const { SplitBillScreen } = require('pos_restaurant.tour.SplitBillScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
@@ -42,7 +43,8 @@ odoo.define('pos_restaurant.tour.SplitBillScreen', function (require) {
|
||||
ProductScreen.do.clickOrderline('Coca-Cola', '1.0')
|
||||
|
||||
// go back to the original order and see if the order is changed
|
||||
Chrome.do.selectOrder('1');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectOrder('-0001');
|
||||
ProductScreen.do.clickOrderline('Water', '2.0')
|
||||
ProductScreen.do.clickOrderline('Minute Maid', '3.0')
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
odoo.define('pos_restaurant.tour.TicketScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
// New Ticket button should not be in the ticket screen if no table is selected.
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.noNewTicketButton();
|
||||
TicketScreen.do.clickDiscard();
|
||||
|
||||
// Deleting the last order in the table brings back to floorscreen
|
||||
FloorScreen.do.clickTable('T4');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.nthRowContains(2, '-0001');
|
||||
TicketScreen.do.deleteOrder('-0001');
|
||||
FloorScreen.check.isShown();
|
||||
|
||||
// Create 2 items in a table. From floorscreen, delete 1 item. Then select the other item.
|
||||
// Correct order and screen should be displayed and the BackToFloorButton is shown.
|
||||
FloorScreen.do.clickTable('T2');
|
||||
ProductScreen.exec.addOrderline('Minute Maid', '1', '2');
|
||||
ProductScreen.check.totalAmountIs('2.0');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Coca-Cola', '2', '2');
|
||||
ProductScreen.check.totalAmountIs('4.0');
|
||||
Chrome.do.backToFloor();
|
||||
FloorScreen.check.orderCountSyncedInTableIs('T2', '2');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.deleteOrder('-0003');
|
||||
Chrome.do.confirmPopup();
|
||||
TicketScreen.do.selectOrder('-0002');
|
||||
ProductScreen.check.totalAmountIs('2.0');
|
||||
Chrome.check.backToFloorTextIs('Main Floor', 'T2');
|
||||
|
||||
Tour.register('PosResTicketScreenTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
});
|
||||
@@ -72,9 +72,7 @@ odoo.define('pos_restaurant.tour.FloorScreenTourMethods', function (require) {
|
||||
return [
|
||||
{
|
||||
content: `change shape to '${shape}'`,
|
||||
trigger: `.edit-button .button-option${
|
||||
shape === 'round' ? '.square' : '.round'
|
||||
}`,
|
||||
trigger: `.edit-button .button-option${shape === 'round' ? '.square' : '.round'}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -122,6 +120,23 @@ odoo.define('pos_restaurant.tour.FloorScreenTourMethods', function (require) {
|
||||
{
|
||||
content: `number of seats in table '${table}' is '${val}'`,
|
||||
trigger: `.floor-map .tables .table .label:contains("${table}") ~ .table-seats:contains("${val}")`,
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
}
|
||||
orderCountSyncedInTableIs(table, count) {
|
||||
return [
|
||||
{
|
||||
trigger: `.floor-map .table .order-count:contains("${count}") ~ .label:contains("${table}")`,
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
}
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
trigger: '.floor-map',
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,252 +1,108 @@
|
||||
odoo.define('pos_reataurant.tour.synchronized_table_management', function (require) {
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
var Tour = require("web_tour.tour");
|
||||
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
|
||||
const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');
|
||||
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
|
||||
const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
const Tour = require('web_tour.tour');
|
||||
|
||||
function verify_order_total(total_str) {
|
||||
return [{
|
||||
content: 'order total contains ' + total_str,
|
||||
trigger: '.order .total .value:contains("' + total_str + '")',
|
||||
run: function () {}, // it's a check
|
||||
}];
|
||||
}
|
||||
startSteps();
|
||||
|
||||
function verify_orders_synced(order_count) {
|
||||
return [{
|
||||
content: "check synced",
|
||||
trigger: ".order-sequence",
|
||||
run: function() {
|
||||
var orders = $('.order-sequence');
|
||||
if (orders.length === order_count) {
|
||||
return
|
||||
} else {
|
||||
throw `sync failed 'there should be ${order_count} orders, however, ${orders.length} orders are displayed' `;
|
||||
}
|
||||
},
|
||||
}];
|
||||
}
|
||||
FloorScreen.do.clickTable('T5');
|
||||
|
||||
function add_product_to_order(product_name) {
|
||||
return [{
|
||||
content: 'buy ' + product_name,
|
||||
trigger: '.product-list .product-name:contains("' + product_name + '")',
|
||||
}, {
|
||||
content: 'the ' + product_name + ' have been added to the order',
|
||||
trigger: '.order .product-name:contains("' + product_name + '")',
|
||||
run: function () {}, // it's a check
|
||||
}];
|
||||
}
|
||||
// Create first order
|
||||
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
|
||||
ProductScreen.check.selectedOrderlineHas('Coca-Cola');
|
||||
ProductScreen.do.clickDisplayedProduct('Water');
|
||||
ProductScreen.check.selectedOrderlineHas('Water');
|
||||
ProductScreen.check.totalAmountIs('4.40');
|
||||
|
||||
function generate_keypad_steps(amount_str, keypad_selector) {
|
||||
var i, steps = [], current_char;
|
||||
for (i = 0; i < amount_str.length; ++i) {
|
||||
current_char = amount_str[i];
|
||||
steps.push({
|
||||
content: 'press ' + current_char + ' on payment keypad',
|
||||
trigger: keypad_selector + ' .input-button:contains("' + current_char + '"):visible'
|
||||
});
|
||||
}
|
||||
// Create 2nd order (paid)
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
|
||||
ProductScreen.check.selectedOrderlineHas('Coca-Cola');
|
||||
ProductScreen.do.clickDisplayedProduct('Minute Maid');
|
||||
ProductScreen.check.selectedOrderlineHas('Minute Maid');
|
||||
ProductScreen.check.totalAmountIs('4.40');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
|
||||
return steps;
|
||||
}
|
||||
// After clicking next order, floor screen is shown.
|
||||
// It should have 1 as number of draft synced order.
|
||||
FloorScreen.check.orderCountSyncedInTableIs('T5', '1');
|
||||
FloorScreen.do.clickTable('T5');
|
||||
ProductScreen.check.totalAmountIs('4.40');
|
||||
|
||||
function generate_payment_screen_keypad_steps(amount_str) {
|
||||
return generate_keypad_steps(amount_str, '.payment-numpad');
|
||||
}
|
||||
// Create another draft order and go back to floor
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
|
||||
ProductScreen.check.selectedOrderlineHas('Coca-Cola');
|
||||
ProductScreen.do.clickDisplayedProduct('Minute Maid');
|
||||
ProductScreen.check.selectedOrderlineHas('Minute Maid');
|
||||
Chrome.do.backToFloor();
|
||||
|
||||
function generate_product_screen_keypad_steps(amount_str) {
|
||||
return generate_keypad_steps(amount_str, '.numpad');
|
||||
}
|
||||
// At floor screen, there should be 2 synced draft orders
|
||||
FloorScreen.check.orderCountSyncedInTableIs('T5', '2');
|
||||
|
||||
function goto_payment_screen_and_select_payment_method() {
|
||||
return [{
|
||||
content: "go to payment screen",
|
||||
trigger: '.button.pay',
|
||||
}, {
|
||||
content: "pay with cash",
|
||||
trigger: '.paymentmethod:contains("Cash")',
|
||||
}];
|
||||
}
|
||||
// Delete the first order then go back to floor
|
||||
FloorScreen.do.clickTable('T5');
|
||||
ProductScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.deleteOrder('-0001');
|
||||
Chrome.do.confirmPopup();
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
Chrome.do.backToFloor();
|
||||
|
||||
function open_table(table_id, order_count) {
|
||||
order_count = order_count || null;
|
||||
var steps = [{
|
||||
content: 'open table ' + table_id,
|
||||
trigger: '.label:contains(' + table_id +')',
|
||||
run: 'click',
|
||||
}];
|
||||
if (order_count !== null){
|
||||
steps = steps.concat(verify_orders_synced(order_count));
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
// There should be 1 synced draft order.
|
||||
FloorScreen.check.orderCountSyncedInTableIs('T5', '1');
|
||||
|
||||
function transfer_order_to_table(table_id, order_uid) {
|
||||
return [{
|
||||
content: 'Click transfer button',
|
||||
trigger: '.control-button:contains("Transfer")',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'Transfer order to table ' + table_id,
|
||||
trigger: '.label:contains(' + table_id +')',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'Check if order ' + order_uid + ' is open after transfer',
|
||||
trigger: '.order-button.selected .order-sequence:contains("' + order_uid + '")',
|
||||
run: function(){} // Check
|
||||
}];
|
||||
}
|
||||
|
||||
function finish_order() {
|
||||
var steps = [{
|
||||
content: "validate the order",
|
||||
trigger: '.payment-screen .button.next:visible',
|
||||
}];
|
||||
steps = steps.concat([{
|
||||
content: "next order",
|
||||
trigger: '.receipt-screen .button.next:visible',
|
||||
}]);
|
||||
return steps;
|
||||
}
|
||||
|
||||
/* pos_restaurant_sync
|
||||
*
|
||||
* Run on new session.
|
||||
*/
|
||||
var steps = [{
|
||||
content: 'waiting for loading to finish',
|
||||
trigger: 'body:not(:has(.loader))',
|
||||
run: function () {},
|
||||
}]
|
||||
|
||||
|
||||
steps = steps.concat(open_table('T5'));
|
||||
|
||||
steps = steps.concat(add_product_to_order('Coca-Cola'));
|
||||
steps = steps.concat(add_product_to_order('Water'));
|
||||
steps = steps.concat(verify_order_total('4.40'));
|
||||
steps = steps.concat([{
|
||||
content: 'start new order',
|
||||
trigger: '.neworder-button',
|
||||
run: 'click',
|
||||
}]);
|
||||
steps = steps.concat(add_product_to_order('Coca-Cola'));
|
||||
steps = steps.concat(add_product_to_order('Minute Maid'));
|
||||
steps = steps.concat(verify_order_total('4.40'));
|
||||
steps = steps.concat(goto_payment_screen_and_select_payment_method());
|
||||
steps = steps.concat(generate_payment_screen_keypad_steps('6.05'));
|
||||
steps = steps.concat(finish_order());
|
||||
steps = steps.concat(open_table('T5', 1));
|
||||
steps = steps.concat(verify_order_total('4.40'));
|
||||
steps = steps.concat([{
|
||||
content: 'start new order',
|
||||
trigger: '.neworder-button',
|
||||
run: 'click',
|
||||
}]);
|
||||
steps = steps.concat(add_product_to_order('Coca-Cola'));
|
||||
steps = steps.concat(add_product_to_order('Minute Maid'));
|
||||
steps = steps.concat([{
|
||||
content: 'back to floor',
|
||||
trigger: '.floor-button',
|
||||
run: 'click',
|
||||
}]);
|
||||
steps = steps.concat(open_table('T5', 2));
|
||||
steps = steps.concat([{
|
||||
content: 'delete order',
|
||||
trigger: '.deleteorder-button',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'confirm delete',
|
||||
trigger: '.button.confirm',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'back to floor',
|
||||
trigger: '.floor-button',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'back to floor',
|
||||
trigger: '.oe_status .js_connected',
|
||||
run: function() {},
|
||||
}]);
|
||||
steps = steps.concat(open_table('T5', 1));
|
||||
|
||||
Tour.register('pos_restaurant_sync', { test: true, url: '/pos/web' }, steps);
|
||||
Tour.register('pos_restaurant_sync', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
/* pos_restaurant_sync_second_login
|
||||
*
|
||||
* This tour should be run after the first tour is done.
|
||||
*/
|
||||
var steps = [{
|
||||
content: 'waiting for loading to finish',
|
||||
trigger: 'body:not(:has(.loader))',
|
||||
run: function () {},
|
||||
}];
|
||||
steps = steps.concat(open_table('T5', 1));
|
||||
steps = steps.concat(verify_order_total('4.40'));
|
||||
|
||||
// There is one draft synced order from the previous tour
|
||||
FloorScreen.check.orderCountSyncedInTableIs('T5', '1');
|
||||
FloorScreen.do.clickTable('T5');
|
||||
ProductScreen.check.totalAmountIs('4.40');
|
||||
|
||||
// Test transfering an order
|
||||
steps = steps.concat(transfer_order_to_table('T4', '002-0001'));
|
||||
ProductScreen.do.clickTransferButton();
|
||||
FloorScreen.do.clickTable('T4');
|
||||
|
||||
// Test if products still get merged after transfering the order
|
||||
steps = steps.concat(add_product_to_order('Coca-Cola'));
|
||||
steps = steps.concat({
|
||||
content: 'check the order-line for Coca-Cola has 2 Units',
|
||||
trigger: '.orderlines:has(.orderline .product-name:contains("Coca-Cola")) .info-list:contains("2.000")',
|
||||
run: function () {},
|
||||
})
|
||||
steps = steps.concat(generate_product_screen_keypad_steps('1'));
|
||||
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
|
||||
ProductScreen.check.selectedOrderlineHas('Coca-Cola', '2.0');
|
||||
ProductScreen.check.totalAmountIs('6.60');
|
||||
ProductScreen.do.pressNumpad('1');
|
||||
ProductScreen.check.totalAmountIs('4.40');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
// At this point, there are no draft orders.
|
||||
|
||||
steps = steps.concat(goto_payment_screen_and_select_payment_method());
|
||||
steps = steps.concat(generate_payment_screen_keypad_steps('4.4'));
|
||||
steps = steps.concat(finish_order());
|
||||
steps = steps.concat(open_table('T2'));
|
||||
|
||||
// Test transfering an empty order
|
||||
steps = steps.concat(transfer_order_to_table('T4', '2'));
|
||||
|
||||
steps = steps.concat(add_product_to_order('Coca-Cola'));
|
||||
steps = steps.concat(verify_order_total('2.20'));
|
||||
|
||||
// Take a synced order with products, remove the products
|
||||
// and check if the order is still available in the front-end
|
||||
steps = steps.concat([{
|
||||
content: 'back to floor',
|
||||
trigger: '.floor-button',
|
||||
run: 'click',
|
||||
}]);
|
||||
steps = steps.concat(open_table('T4', 1));
|
||||
steps = steps.concat([{
|
||||
content: 'click backspace to set quantity to 0',
|
||||
trigger: '.numpad-backspace',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'click backspace to remove line',
|
||||
trigger: '.numpad-backspace',
|
||||
run: 'click',
|
||||
}]);
|
||||
steps = steps.concat([{
|
||||
content: 'back to floor',
|
||||
trigger: '.floor-button',
|
||||
run: 'click',
|
||||
}]);
|
||||
steps = steps.concat(open_table('T4', 1));
|
||||
steps = steps.concat(add_product_to_order('Coca-Cola'));
|
||||
steps = steps.concat(verify_order_total('2.20'));
|
||||
steps = steps.concat([{
|
||||
content: 'back to floor',
|
||||
trigger: '.floor-button',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'check if in floor screen',
|
||||
trigger: '.pos-content .floor-screen',
|
||||
run: function() {},
|
||||
}, {
|
||||
content: 'wait for sync to finish',
|
||||
trigger: '.oe_status .js_connected',
|
||||
run: function() {},
|
||||
}]);
|
||||
|
||||
Tour.register('pos_restaurant_sync_second_login', { test: true, url: '/pos/web' }, steps);
|
||||
FloorScreen.do.clickTable('T2');
|
||||
ProductScreen.check.isShown();
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
ProductScreen.do.clickTransferButton();
|
||||
FloorScreen.do.clickTable('T4');
|
||||
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
|
||||
ProductScreen.check.totalAmountIs('2.20');
|
||||
Chrome.do.backToFloor();
|
||||
FloorScreen.check.orderCountSyncedInTableIs('T4', '1');
|
||||
|
||||
Tour.register('pos_restaurant_sync_second_login', { test: true, url: '/pos/web' }, getSteps());
|
||||
});
|
||||
|
||||
@@ -179,3 +179,7 @@ class TestFrontend(odoo.tests.HttpCase):
|
||||
self.pos_config.write({'manage_orders': True})
|
||||
self.pos_config.with_user(self.env.ref('base.user_admin')).open_session_cb(check_coa=False)
|
||||
self.start_tour("/pos/web?config_id=%d" % self.pos_config.id, 'PosResOrderManagementScreenTour', login="admin", step_delay=50)
|
||||
|
||||
def test_04_ticket_screen(self):
|
||||
self.pos_config.with_user(self.env.ref('base.user_admin')).open_session_cb(check_coa=False)
|
||||
self.start_tour("/pos/web?config_id=%d" % self.pos_config.id, 'PosResTicketScreenTour', login="admin", step_delay=50)
|
||||
|
||||
@@ -24,8 +24,9 @@
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/Screens/OrderManagementScreen/OrderManagementScreen.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/Screens/OrderManagementScreen/OrderRow.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/ChromeWidgets/OrderSelector.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/Screens/TicketScreen.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/ChromeWidgets/BackToFloorButton.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/Chrome.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js"></script>
|
||||
</xpath>
|
||||
@@ -54,6 +55,7 @@
|
||||
<script type="text/javascript" src="/pos_restaurant/static/tests/tours/ControlButtons.tour.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/tests/tours/FloorScreen.tour.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/tests/tours/OrderManagementScreen.tour.js"></script>
|
||||
<script type="text/javascript" src="/pos_restaurant/static/tests/tours/TicketScreen.tour.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user