[IMP] pos_self_order: add bus notification for self order

Previously, when an order was paid for at the restaurant counter,
the self-orderer received no update that the order had
been paid for.

Now, if the customer self-order page is still open on their phone,
it will receive a notification via websocket that the order has been
paid for from the restaurant counter.The order will no longer be
editable.

In the event that they have already closed the self-order when paying
at the counter, the next time they return to this page. An RPC request
will be made with the access_token of the old order and the
states will be aligned.

closes odoo/odoo#125921

Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
This commit is contained in:
David Monnom (moda)
2023-06-27 16:26:53 +02:00
parent 3ed674645d
commit 2222b6911e
6 changed files with 114 additions and 21 deletions
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from datetime import timedelta
import uuid
from odoo import http, fields, Command
@@ -137,6 +138,7 @@ class PosSelfOrderController(http.Controller):
def get_orders_by_access_token(self, access_tokens):
orders_sudo = request.env["pos.order"].sudo().search([
("access_token", "in", access_tokens),
("date_order", ">=", fields.Datetime.now() - timedelta(days=7)),
])
if not orders_sudo:
+26 -4
View File
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from typing import Dict, Union
from typing import Dict
from odoo import models, fields
from odoo import models, fields, api
class PosOrderLine(models.Model):
@@ -25,6 +24,29 @@ class PosOrderLine(models.Model):
class PosOrder(models.Model):
_inherit = "pos.order"
@api.model
def create_from_ui(self, orders, draft=False):
orders = super().create_from_ui(orders, draft)
order_ids = self.env['pos.order'].browse([order['id'] for order in orders])
self._send_notification(order_ids)
return orders
@api.model
def remove_from_ui(self, server_ids):
order_ids = self.env['pos.order'].browse(server_ids)
order_ids.state = 'cancel'
self._send_notification(order_ids)
return super().remove_from_ui(server_ids)
def _send_notification(self, order_ids):
for order in order_ids:
if order.access_token and order.state != 'draft':
self.env['bus.bus']._sendone(f'self_order-{order.access_token}', 'ORDER_STATE_CHANGED', {
'access_token': order.access_token,
'state': order.state
})
def _export_for_self_order(self) -> Dict:
self.ensure_one()
@@ -34,7 +56,7 @@ class PosOrder(models.Model):
"pos_reference": self.pos_reference,
"access_token": self.access_token,
"state": self.state,
"date": str(self.date_order),
"date_order": str(self.date_order),
"amount_total": self.amount_total,
"amount_tax": self.amount_tax,
"lines": [
@@ -2,6 +2,7 @@
import { registry } from "@web/core/registry";
import { effect } from "@web/core/utils/reactive";
import { batched } from "@point_of_sale/utils";
export class SelfOrderBus {
static serviceDependencies = ["self_order", "orm", "bus_service"];
@@ -16,13 +17,13 @@ export class SelfOrderBus {
this.bus = bus_service;
effect(
(orders) => {
batched((orders) => {
for (const order of orders) {
if (order.access_token) {
this.bus.addChannel(`self_order-${order.access_token}`);
}
}
},
}),
[this.selfOrder.orders]
);
@@ -34,10 +35,14 @@ export class SelfOrderBus {
}
dispatch(message) {
if (message.type === "ORDER_PAID") {
console.log("ORDER_PAID", message.payload);
if (message.type === "ORDER_STATE_CHANGED") {
this.ws_changeOrderState(message.payload.access_token, message.payload.state);
}
}
ws_changeOrderState(access_token, state) {
this.selfOrder.changeOrderState(access_token, state);
}
}
export const SelfOrderBusService = {
@@ -9,12 +9,14 @@ import { effect } from "@web/core/utils/reactive";
import { Order } from "./models/order";
import { Product } from "./models/product";
import { ConnectionLostError, RPCError } from "@web/core/network/rpc_service";
import { batched } from "@point_of_sale/utils";
export class SelfOrder {
constructor(...args) {
this.setup(...args);
this.ready = this.setup(...args).then(() => this);
}
setup(env, rpc, notification, router) {
async setup(env, rpc, notification, router) {
Object.assign(this, {
...session.pos_self_order_data,
});
@@ -50,6 +52,14 @@ export class SelfOrder {
{ type: "warning" }
);
}
if (this.self_order_mode !== "qr_code") {
await this.getOrdersFromServer();
effect(
batched((state) => this.saveOrderToLocalStorage(state.orders)),
[this]
);
}
}
initData() {
@@ -143,25 +153,39 @@ export class SelfOrder {
}
async getOrdersFromServer() {
const accessTokens = this.orders.map((order) => order.access_token);
const accessTokens = this.orders.map((order) => order.access_token).filter(Boolean);
if (accessTokens.length === 0) {
return;
}
try {
const orderChanges = {};
const orders = await this.rpc(`/pos-self-order/get-orders/`, {
access_tokens: accessTokens,
});
this.orders = this.orders.filter((order) => {
orderChanges[order.access_token] = order.lastChangesSent;
return !order.access_token;
});
const ordersToRecreate = {};
for (const order of this.orders.filter((o) => o.access_token)) {
ordersToRecreate[order.access_token] = {
lastChangesSent: order.lastChangesSent,
lines: order.lines.filter((l) => !l.id),
};
}
for (const index in this.orders) {
if (this.orders[index].access_token) {
this.orders.splice(index, 1);
}
}
this.orders.push(
...orders.map((o) => {
const data = ordersToRecreate[o.access_token] || {};
const newOrder = new Order({
...o,
lastChangesSent: orderChanges[o.access_token],
lastChangesSent: data.lastChangesSent ?? {},
});
newOrder.lines.push(...(data.lines ?? []));
return newOrder;
})
);
@@ -179,12 +203,16 @@ export class SelfOrder {
this.priceLoading = true;
try {
if (!this.currentOrder) {
return;
}
const taxes = await this.rpc(`/pos-self-order/get-orders-taxes/`, {
order: this.currentOrder,
pos_config_id: this.pos_config_id,
});
for (const line of this.editedOrder.lines) {
for (const line of this.currentOrder.lines) {
const lineTaxes = taxes.lines.find((ol) => ol.uuid === line.uuid);
line.price_subtotal = lineTaxes.price_subtotal;
line.price_subtotal_incl = lineTaxes.price_subtotal_incl;
@@ -218,15 +246,44 @@ export class SelfOrder {
if (accessToken) {
this.editedOrder = null;
this.orders = this.orders.filter((o) => !accessToken.includes(o.access_token));
for (const index in this.orders) {
if (accessToken.includes(this.orders[index].access_token)) {
this.orders.splice(index, 1);
}
}
}
}
changeOrderState(access_token, state) {
const order = this.orders.filter((o) => o.access_token === access_token);
let message = _t("Your order status has been changed");
if (order.length !== 1) {
throw new Error("Warning, two orders with the same access_token");
} else {
order[0].state = state;
}
if (state === "paid") {
this.editedOrder = null;
message = _t("Your order has been paid");
} else if (state === "cancel") {
this.editedOrder = null;
message = _t("Your order has been canceled");
}
this.notification.add(message, {
type: "success",
});
this.router.navigate("default");
}
}
export const selfOrderService = {
dependencies: ["rpc", "notification", "router"],
async start(env, { rpc, notification, router }) {
return new SelfOrder(env, rpc, notification, router);
return new SelfOrder(env, rpc, notification, router).ready;
},
};
@@ -10,7 +10,7 @@ registry.category("web_tour.tours").add("self_order_after_each_cart_tour", {
PosSelf.check.isNotNotification(),
PosSelf.check.isPrimaryBtn("My Orders"),
PosSelf.action.clickPrimaryBtn("My Orders"),
PosSelf.check.isNotification("found on the server"),
PosSelf.check.isNoOrderInHistory(),
PosSelf.action.clickBack(),
PosSelf.action.clickPrimaryBtn("View Menu"),
@@ -71,6 +71,13 @@ export const PosSelf = {
},
];
},
isNoOrderInHistory: () => {
return {
content: `Verify if there is no order in history`,
trigger: `.justify-content-center:contains('No order found')`,
run: () => {},
};
},
},
action: {
clickBack: () => {