[IMP] pos_self_order, pos_online_payment_self_order: settings behavior

Previously, mobile self-order was not available when restaurant mode was
deactivated.

Now it's possible to activate self-order mode without the restaurant
option activated. Self-order will then be forced to "pickup zone" mode
and pay after "each order".

---

Previously, when the self-order was closed, a popup indicating this
appeared in full screen.

Now, this popup has been replaced by a banner at the top of the screen,
so that the self-order can still be used in read-only mode.

---

Previously, when the kiosk was loaded and there was no product available
in the pos_config, a traceback was raised. Now, when we are in this
case, we display a message indicating to first add products in full
screen.

---

Fixed a traceback when trying to start the POS by clicking on the image
of the landing page. The function start() had been removed. It is now
restored.

closes odoo/odoo#137671

Signed-off-by: Adrien Guilliams (adgu) <adgu@odoo.com>
This commit is contained in:
David
2023-10-10 08:19:32 +00:00
committed by “moda”
parent bb9677d673
commit 37eb4cc268
17 changed files with 122 additions and 106 deletions
@@ -9,22 +9,22 @@ patch(CartPage.prototype, {
return super.pay(...arguments);
}
if (this.sendInProgress) {
return;
}
const order = this.selfOrder.currentOrder;
const mode = this.selfOrder.config.self_ordering_pay_after;
const isOnlinePayment = this.selfOrder.pos_payment_methods.find((p) => p.is_online_payment);
const service = this.selfOrder.config.self_ordering_service_mode;
const takeAway = this.selfOrder.currentOrder.take_away;
if (this.sendInProgress) {
return;
}
if (!this.selfOrder.table && service === "table" && !takeAway) {
this.state.selectTable = true;
return;
}
if (mode === "meal" && isOnlinePayment) {
const order = this.selfOrder.currentOrder;
if (!order) {
this.selfOrder.notification.add(_t("The current order is invalid."), {
type: "danger",
@@ -39,17 +39,28 @@ patch(CartPage.prototype, {
this.checkAndOpenPaymentPage(order);
}
} else if (mode === "each") {
this.sendInProgress = true;
const order = await this.selfOrder.sendDraftOrderToServer();
this.sendInProgress = false;
this.checkAndOpenPaymentPage(order);
} else {
return super.pay(...arguments);
}
},
checkAndOpenPaymentPage(order) {
async checkAndOpenPaymentPage(order) {
const isOnlinePayment = this.selfOrder.pos_payment_methods.find((p) => p.is_online_payment);
if (order) {
if (order.state === "draft") {
if (!isOnlinePayment) {
this.selfOrder.notification.add(
_t("The current order cannot be paid (no online payment method)."),
{ type: "danger" }
);
return;
}
if (!order.isSavedOnServer) {
await this.selfOrder.sendDraftOrderToServer();
}
this.selfOrder.openOnlinePaymentPage(order);
} else {
this.selfOrder.notification.add(
+4 -1
View File
@@ -150,6 +150,9 @@ class PosConfig(models.Model):
if vals.get('self_ordering_mode') == 'kiosk' or record.self_ordering_mode == 'kiosk':
vals['self_ordering_pay_after'] = 'each'
if (not vals.get('module_pos_restaurant') and not record.module_pos_restaurant) and vals.get('self_ordering_mode') == 'mobile':
vals['self_ordering_pay_after'] = 'each'
return super().write(vals)
@api.depends("module_pos_restaurant")
@@ -175,7 +178,7 @@ class PosConfig(models.Model):
self.ensure_one()
table_qr_code = []
if self.self_ordering_mode == 'mobile':
if self.self_ordering_mode == 'mobile' and self.module_pos_restaurant and self.self_ordering_service_mode == 'table':
table_qr_code.extend([{
'name': floor.name,
'type': 'table',
@@ -16,13 +16,19 @@ class ProductTemplate(models.Model):
default=True,
)
@api.constrains('available_in_pos')
def _check_combo_inclusions(self):
super()._check_combo_inclusions()
self.self_order_available = False
@api.onchange('available_in_pos')
def _on_change_available_in_pos(self):
for record in self:
if not record.available_in_pos:
record.self_order_available = False
def write(self, vals_list):
if 'available_in_pos' in vals_list:
if not vals_list['available_in_pos']:
vals_list['self_order_available'] = False
res = super().write(vals_list)
if 'self_order_available' in vals_list:
for record in self:
for product in record.product_variant_ids:
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.exceptions import ValidationError
from odoo.tools.misc import split_every
from werkzeug.urls import url_unquote
@@ -28,7 +28,12 @@ class ResConfigSettings(models.TransientModel):
user_id = self.pos_self_ordering_default_user_id
if not user_id.has_group("point_of_sale.group_pos_user") and not user_id.has_group("point_of_sale.group_pos_manager"):
raise UserError(_("The user must be a POS user"))
raise ValidationError(_("The user must be a POS user"))
@api.onchange("pos_self_ordering_service_mode")
def _onchange_pos_self_order_service_mode(self):
if self.pos_self_ordering_service_mode == 'counter':
self.pos_self_ordering_pay_after = "each"
@api.onchange("pos_self_ordering_default_language_id", "pos_self_ordering_available_language_ids")
def _onchange_pos_self_order_kiosk_default_language(self):
@@ -37,45 +42,47 @@ class ResConfigSettings(models.TransientModel):
if not self.pos_self_ordering_default_language_id and self.pos_self_ordering_available_language_ids:
self.pos_self_ordering_default_language_id = self.pos_self_ordering_available_language_ids[0]
@api.onchange("pos_self_ordering_mode")
@api.onchange("pos_self_ordering_mode", "pos_module_pos_restaurant")
def _onchange_pos_self_order_kiosk(self):
if self.pos_self_ordering_mode == 'kiosk':
self.is_kiosk_mode = True
self.pos_module_pos_restaurant = False
self.pos_self_ordering_pay_after = "each"
elif self.pos_self_ordering_mode == 'mobile' and not self.pos_module_pos_restaurant:
raise UserError(_("In Self-Order mode, you must have the Restaurant module"))
else:
self.is_kiosk_mode = False
@api.onchange("pos_self_ordering_pay_after", "pos_self_ordering_mode")
def _onchange_service_and_ordering(self):
if self.pos_self_ordering_pay_after == "meal" and self.pos_self_ordering_mode == 'mobile':
self.pos_self_ordering_service_mode = 'table'
if not self.pos_module_pos_restaurant:
self.pos_self_ordering_service_mode = 'counter'
@api.onchange("pos_self_ordering_pay_after", "pos_self_ordering_mode")
def _onchange_pos_self_order_pay_after(self):
if self.pos_self_ordering_pay_after == "meal" and self.pos_self_ordering_mode == 'kiosk':
raise UserError(_("Only pay after each is available with kiosk mode."))
raise ValidationError(_("Only pay after each is available with kiosk mode."))
if self.pos_self_ordering_service_mode == 'counter' and self.pos_self_ordering_mode == 'mobile':
self.pos_self_ordering_pay_after = "each"
if self.pos_self_ordering_pay_after == "each" and not self.module_pos_preparation_display:
self.module_pos_preparation_display = True
@api.onchange("pos_self_ordering_service_mode")
def _onchange_pos_self_ordering_service_mode(self):
table_ids = self.pos_floor_ids.table_ids
if self.pos_self_ordering_service_mode == 'table' and self.pos_self_ordering_mode == 'mobile' and not table_ids:
raise UserError(_("In Self-Order mode, you must have at least one table to use the table service mode"))
def custom_link_action(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"res_model": "pos_self_order.custom_link",
"views": [[False, "tree"]],
"domain": [['pos_config_ids', 'in', self.pos_config_id.id]],
}
def generate_qr_codes_page(self):
"""
Generate the data needed to print the QR codes page
"""
if self.pos_self_ordering_mode == 'mobile':
if self.pos_self_ordering_mode == 'mobile' and self.pos_module_pos_restaurant:
table_ids = self.pos_config_id.floor_ids.table_ids
if not table_ids:
raise UserError(_("In Self-Order mode, you must have at least one table to generate QR codes"))
raise ValidationError(_("In Self-Order mode, you must have at least one table to generate QR codes"))
url = url_unquote(self.pos_config_id._get_self_order_url(table_ids[0].id))
name = table_ids[0].name
@@ -94,7 +101,8 @@ class ResConfigSettings(models.TransientModel):
}
for floor in self.pos_config_id._get_qr_code_data()
],
'table_mode': self.pos_self_ordering_mode,
'table_mode': self.pos_self_ordering_mode and self.pos_module_pos_restaurant and self.pos_self_ordering_service_mode == 'table',
'self_order': self.pos_self_ordering_mode == 'mobile',
'table_example': {
'name': name,
'decoded_url': url or "",
@@ -40,19 +40,20 @@ export class OrderWidget extends Component {
const currentPage = this.router.activeSlot;
const payAfter = this.selfOrder.config.self_ordering_pay_after;
const kioskPayment = this.selfOrder.pos_payment_methods.find((p) => !p.is_online_payment); // cannot be online payment in kiosk for instance
const isLine = this.selfOrder.currentOrder.lines.length !== 0;
const isNoLine = this.selfOrder.currentOrder.lines.length === 0;
let label = "";
let disabled = !isLine;
let disabled = false;
if (currentPage === "product_list") {
label = _t("Order");
disabled = isNoLine;
} else if (payAfter === "meal" && !this.selfOrder.currentOrder.isSavedOnServer) {
label = _t("Order");
disabled = disabled || !kioskPayment;
disabled = isNoLine;
} else {
label = kioskPayment ? _t("Pay") : _t("Pay at cashier");
disabled = disabled || kioskPayment;
disabled = false;
}
return { label, disabled };
@@ -15,7 +15,7 @@ export class CartPage extends Component {
setup() {
this.selfOrder = useSelfOrder();
this.router = useService("router");
this.sendInProgress = false;
this.state = useState({
selectTable: false,
cancelConfirmation: false,
@@ -49,6 +49,10 @@ export class CartPage extends Component {
}
async pay() {
if (this.sendInProgress) {
return;
}
const orderingMode = this.selfOrder.config.self_ordering_service_mode;
const type = this.selfOrder.config.self_ordering_mode;
const takeAway = this.selfOrder.currentOrder.take_away;
@@ -1,7 +0,0 @@
/** @odoo-module */
import { Component } from "@odoo/owl";
export class ClosedPage extends Component {
static template = "pos_self_order.ClosedPage";
}
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.ClosedPage">
<div class="d-flex gap-3 h-100 w-100 align-items-center justify-content-center text-bg-800">
<span class="fs-1">The kiosk is closed</span>
</div>
</t>
</templates>
@@ -68,6 +68,10 @@ export class LandingPage extends Component {
return;
}
this.start();
}
start() {
if (
this.selfOrder.config.self_ordering_takeaway &&
this.selfOrder.currentOrder.take_away === null
@@ -23,24 +23,18 @@ export class PaymentPage extends Component {
});
onWillStart(async () => {
if (this.selfOrder.currentOrder.lines.length === 0) {
this.router.navigate("default");
return;
}
const type = this.selfOrder.config.self_ordering_mode;
const paymentMethods = this.selfOrder.pos_payment_methods.filter(
(p) => !p.is_online_payment
);
if (paymentMethods.length === 0 && type === "kiosk") {
try {
const order = await this.rpc("/pos-self-order/process-new-order/kiosk", {
order: this.selfOrder.currentOrder,
access_token: this.selfOrder.access_token,
table_identifier: null,
});
this.selfOrder.updateOrderFromServer(order);
} catch (error) {
this.selfOrder.handleErrorNotification(error);
}
this.selfOrder.sendDraftOrderToServer();
this.router.navigate("payment_success");
} else if (paymentMethods.length === 1 && type === "kiosk") {
this.selectMethod(this.selfOrder.pos_payment_methods[0].id);
@@ -74,13 +74,12 @@ export class SelfOrderBus {
if (payload.status === "closed") {
this.selfOrder.pos_session = [];
this.selfOrder.ordering = false;
} else {
// reload to get potential new settings
// more easier than RPC for now
window.location.reload();
}
this.selfOrder.isSession();
}
ws_productChanged(message) {
@@ -1,5 +1,5 @@
/** @odoo-module */
import { Component, whenReady, App, onMounted } from "@odoo/owl";
import { Component, whenReady, App } from "@odoo/owl";
import { makeEnv, startServices } from "@web/env";
import { templates } from "@web/core/assets";
import { _t } from "@web/core/l10n/translation";
@@ -15,7 +15,6 @@ import { PaymentPage } from "@pos_self_order/app/pages/payment_page/payment_page
import { PaymentSuccessPage } from "@pos_self_order/app/pages/payment_success_page/payment_success_page";
import { EatingLocationPage } from "@pos_self_order/app/pages/eating_location_page/eating_location_page";
import { StandNumberPage } from "@pos_self_order/app/pages/stand_number_page/stand_number_page";
import { ClosedPage } from "@pos_self_order/app/pages/closed_page/closed_page";
import { OrdersHistoryPage } from "@pos_self_order/app/pages/order_history_page/order_history_page";
class selfOrderIndex extends Component {
@@ -25,7 +24,6 @@ class selfOrderIndex extends Component {
Router,
CartPage,
ProductPage,
ClosedPage,
OrdersHistoryPage,
ComboPage,
PaymentPage,
@@ -39,10 +37,9 @@ class selfOrderIndex extends Component {
setup() {
this.selfOrder = useSelfOrder();
onMounted(() => {
this.selfOrder.isSession();
});
}
get selfIsReady() {
return Object.values(this.selfOrder.productByIds).length > 0;
}
}
@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.selfOrderIndex">
<Router pos_config_id="selfOrder.pos_config_id">
<div t-if="!selfOrder.ordering" class="w-100 m-0 text-center bg-black text-white">
<p>We're currently closed.</p>
</div>
<Router t-if="selfIsReady" pos_config_id="selfOrder.pos_config_id">
<t t-set-slot="default" route="`/pos-self/${selfOrder.pos_config_id}`">
<LandingPage />
</t>
@@ -29,13 +32,13 @@
<t t-set-slot="stand_number" route="`/pos-self/${selfOrder.pos_config_id}/stand_number`">
<StandNumberPage />
</t>
<t t-set-slot="closed" route="`/pos-self/${selfOrder.pos_config_id}/closed`">
<ClosedPage />
</t>
<t t-set-slot="orderHistory" route="`/pos-self/${selfOrder.pos_config_id}/orders`">
<OrdersHistoryPage />
</t>
</Router>
<div t-else="" class="h-100 w-100 d-flex align-items-center justify-content-center text-center">
Hey, looks like you forgot to create products or add them to pos_config. Please add them before using the Self Order
</div>
<MainComponentsContainer />
</t>
</templates>
@@ -106,6 +106,12 @@ export class SelfOrder extends Reactive {
return product;
});
this.combos = this.combos.map((c) => {
const combo = new Combo(c);
this.comboByIds[combo.id] = combo;
return combo;
});
this.productsGroupedByCategory = this.products.reduce((acc, product) => {
product.pos_categ_ids.map((pos_categ_ids) => {
acc[pos_categ_ids] = acc[pos_categ_ids] || [];
@@ -121,11 +127,14 @@ export class SelfOrder extends Reactive {
.sort((a, b) => categorySorter(a, b, this.config.iface_start_categ_id))
);
this.combos = this.combos.map((c) => {
const combo = new Combo(c);
this.comboByIds[combo.id] = combo;
return combo;
});
if (this.categoryList.size === 0) {
this.categoryList.add({
has_image: false,
id: 0,
name: _t("Other"),
sequence: -1,
});
}
this.currentCategory = this.pos_category.length > 0 ? [...this.categoryList][0] : null;
}
@@ -197,6 +206,10 @@ export class SelfOrder extends Reactive {
}
async sendDraftOrderToServer() {
if (this.currentOrder.isSavedOnServer || this.currentOrder.lines.length === 0) {
return true;
}
try {
const rpcUrl = this.currentOrder.isAlreadySent
? "/pos-self-order/update-existing-order"
@@ -308,18 +321,6 @@ export class SelfOrder extends Reactive {
this.currentOrder.updateDataFromServer(order);
}
isSession() {
if (this.config.self_ordering_mode !== "kiosk") {
return;
}
if (!this.pos_session || !this.pos_session.id) {
this.router.navigate("closed");
} else if (this.router.activeSlot === "closed") {
this.router.navigate("default");
}
}
isOrder() {
if (!this.currentOrder || !this.currentOrder.lines.length) {
this.router.navigate("default");
@@ -1,10 +1,5 @@
<?xml version="1.0"?>
<odoo>
<record id="custom_link_action" model="ir.actions.act_window">
<field name="name">Custom Links</field>
<field name="res_model">pos_self_order.custom_link</field>
<field name="view_mode">tree</field>
</record>
<record model="ir.ui.view" id="custom_link_tree">
<field name="name">custom.link.tree</field>
<field name="model">pos_self_order.custom_link</field>
+3 -3
View File
@@ -25,10 +25,10 @@
<t t-set="qr_code_size" t-value="190"/>
<t t-call="web.basic_layout">
<div class="w-100" style="text-align: right">
<span class="fs-4">Point of sale: <t t-esc="pos_name" /></span><span class="fs-4" style="margin-left:20px">Self Order: <t t-if="table_mode == 'mobile'">Yes</t><t t-else="">No</t></span>
<span class="fs-4">Point of sale: <t t-esc="pos_name" /></span><span class="fs-4" style="margin-left:20px">Self Order: <t t-if="self_order">Yes</t><t t-else="">No</t></span>
</div>
<div class="mb-5" style="border-bottom: 1px solid black;">
<h2 t-if="table_mode == 'mobile'">Make it easy for your customers to explore your menu
<h2 t-if="table_mode">Make it easy for your customers to explore your menu
online or order with the QR codes on your tables</h2>
<h2 t-else="">Make it easy for your customers to explore your menu
online with the QR codes on your tables</h2>
@@ -36,7 +36,7 @@
<div>
<h3>How to use</h3>
<t t-if="table_mode == 'mobile'">
<t t-if="table_mode">
<p>Each table in your floor plan is assigned a unique QR code based on your configuration. For security reasons,
both the point of sale and table names are encrypted in the generated URL, as shown in the example below:.</p>
<p class="mt-2">Table: <span t-if="table_example" t-esc="table_example['name']" /><br/>
@@ -16,7 +16,7 @@
<field name="pos_self_ordering_mode" />
<div class="d-flex flex-column align-items-start w-50" invisible="pos_self_ordering_mode == 'nothing'">
<button class="btn-link p-0" icon="oi-arrow-right" name="preview_self_order_app" type="object" string="Preview Web interface"/>
<button class="btn-link p-0" icon="oi-arrow-right" name="pos_self_order.custom_link_action" type="action" string="Home buttons"/>
<button class="btn-link p-0" icon="oi-arrow-right" name="custom_link_action" type="object" string="Home buttons"/>
<button class="btn-link p-0" icon="oi-arrow-right" name="generate_qr_codes_page" type="object" string="Print QR Codes" invisible="not pos_self_ordering_mode in ['consultation', 'mobile']"/>
<button groups="base.group_no_one" class="btn-link p-0" icon="oi-arrow-right" name="update_access_tokens" type="object" string="Reset QR Codes" invisible="not pos_self_ordering_mode in ['consultation', 'mobile']"/>
</div>
@@ -46,7 +46,7 @@
<setting string="Options" help="All options for your Self devices" invisible="pos_self_ordering_mode in ['nothing', 'consultation']">
<div class="content-group row" invisible="not pos_self_ordering_mode in ['kiosk', 'mobile']">
<label for="pos_self_ordering_service_mode" class="col-lg-4" string="Service at" />
<field name="pos_self_ordering_service_mode" readonly="pos_self_ordering_pay_after == 'meal' and pos_self_ordering_mode == 'mobile'" />
<field name="pos_self_ordering_service_mode" readonly="not pos_module_pos_restaurant and pos_self_ordering_mode != 'kiosk'" />
</div>
<div class="content-group row" invisible="not pos_self_ordering_mode in ['kiosk', 'mobile']">
<label for="pos_self_ordering_default_user_id" class="col-lg-4" string="Default User"/>
@@ -54,7 +54,7 @@
</div>
<div id="self-payment-after" class="content-group row" invisible="not pos_self_ordering_mode in ['kiosk', 'mobile']">
<label string="Pay after" for="pos_self_ordering_pay_after" class="col-lg-4"/>
<field name="pos_self_ordering_pay_after" readonly="pos_self_ordering_mode == 'kiosk'" widget="upgrade_selection"/>
<field name="pos_self_ordering_pay_after" readonly="pos_self_ordering_mode == 'kiosk' or pos_self_ordering_service_mode == 'counter' and pos_self_ordering_mode == 'mobile'" widget="upgrade_selection"/>
</div>
</setting>
<setting string="Eat in / Take out" help="Adjust the tax rate based on whether customers are dining in or opting for takeout." invisible="not pos_self_ordering_mode in ['mobile', 'kiosk']">
@@ -69,6 +69,11 @@
</setting>
</block>
</block>
<block id="restaurant_section" position="attributes">
<attribute name="invisible">is_kiosk_mode</attribute>
</block>
<setting id="customer_display" position="attributes">
<attribute name="invisible">is_kiosk_mode</attribute>
</setting>