[IMP] point_of_sale,pos_restaurant: add bus_service
Before a longpolling every 5 seconds was set up to sync the number of orders per table in the pos_restaurant, which was not very userfriendly / efficient. Now, the longpolling has been replaced by a bus (websocket). As soon as an order is sent to the server by another PoS, the server dispatches the information to all other PoS using the corresponding floor plan. The bus_service was initialized in the pos_store of the point_of_sale, but is now used only in the pos_restaurant via override. This was done for obvious future use. closes odoo/odoo#121875 Signed-off-by: Heinz Robin (rhe) <rhe@odoo.com>
This commit is contained in:
@@ -103,6 +103,11 @@
|
||||
'web/static/src/webclient/barcode/barcode_scanner.js',
|
||||
'web/static/src/webclient/barcode/ZXingBarcodeDetector.js',
|
||||
'web/static/src/webclient/barcode/crop_overlay.js',
|
||||
# bus service
|
||||
'bus/static/src/services/bus_service.js',
|
||||
'bus/static/src/bus_parameters_service.js',
|
||||
'bus/static/src/multi_tab_service.js',
|
||||
'bus/static/src/workers/*',
|
||||
# report download utils
|
||||
'web/static/src/webclient/actions/reports/utils.js',
|
||||
# libs
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export class PosBus {
|
||||
static serviceDependencies = ["pos", "orm", "bus_service"];
|
||||
|
||||
constructor(...args) {
|
||||
this.setup(...args);
|
||||
}
|
||||
|
||||
setup(env, { pos, orm, bus_service }) {
|
||||
this.pos = pos;
|
||||
this.orm = orm;
|
||||
|
||||
// initialize bus_service and listen on pos_config-`id` channel
|
||||
bus_service.addChannel(`pos_config-${pos.globalState.config.id}`);
|
||||
bus_service.addEventListener("notification", ({ detail }) => {
|
||||
for (const message of detail) {
|
||||
this.dispatch(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(message) {}
|
||||
}
|
||||
|
||||
export const posBusService = {
|
||||
dependencies: PosBus.serviceDependencies,
|
||||
async start(env, deps) {
|
||||
return new PosBus(env, deps);
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("pos_bus", posBusService);
|
||||
@@ -21,6 +21,11 @@ QUnit.module("Chrome", {
|
||||
start() {
|
||||
return { bus: new EventBus() };
|
||||
},
|
||||
})
|
||||
.add("bus_service", {
|
||||
start() {
|
||||
return { addChannel: () => {}, addEventListener: () => {} };
|
||||
},
|
||||
});
|
||||
|
||||
for (const service of [
|
||||
|
||||
@@ -28,6 +28,12 @@ class PosConfig(models.Model):
|
||||
skip_changes_map = defaultdict(lambda: 0)
|
||||
|
||||
for line in linked_orderlines:
|
||||
# For the moment, as this feature is not compatible with pos_self_order,
|
||||
# we ignore last_order_preparation_change when it is set to false.
|
||||
# In future, pos_self_order will send the various changes to the order.
|
||||
if not line.order_id.last_order_preparation_change:
|
||||
continue
|
||||
|
||||
last_order_preparation_change = json.loads(line.order_id.last_order_preparation_change)
|
||||
prep_change = {}
|
||||
for line_uuid in last_order_preparation_change:
|
||||
|
||||
@@ -54,6 +54,27 @@ class PosOrder(models.Model):
|
||||
"""
|
||||
return [('state', '=', 'draft'), ('table_id', 'in', table_ids)]
|
||||
|
||||
@api.model
|
||||
def remove_from_ui(self, server_ids):
|
||||
tables = self.env['pos.order'].search([('id', 'in', server_ids)]).table_id
|
||||
order_ids = super().remove_from_ui(server_ids)
|
||||
self.send_table_count_notification(tables)
|
||||
return order_ids
|
||||
|
||||
@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_table_count_notification(order_ids.table_id)
|
||||
return orders
|
||||
|
||||
def send_table_count_notification(self, table_ids):
|
||||
messages = []
|
||||
for config in self.env['pos.config'].search([('floor_ids', 'in', table_ids.floor_id.ids)]):
|
||||
order_count = config.get_tables_order_count_and_printing_changes()
|
||||
messages.append((f'pos_config-{config.id}', 'TABLE_ORDER_COUNT', order_count))
|
||||
self.env['bus.bus']._sendmany(messages)
|
||||
|
||||
@api.model
|
||||
def get_table_draft_orders(self, table_ids):
|
||||
"""Generate an object of all draft orders for the given table.
|
||||
|
||||
@@ -65,8 +65,27 @@ export class FloorScreen extends Component {
|
||||
this.state.floorMapScrollTop = this.floorMapRef.el.getBoundingClientRect().top;
|
||||
}
|
||||
async onWillStart() {
|
||||
const table = this.pos.globalState.table;
|
||||
if (table) {
|
||||
const orders = this.pos.globalState.get_order_list();
|
||||
const tableOrders = orders.filter(
|
||||
(order) => order.tableId === table.id && !order.finalized
|
||||
);
|
||||
const qtyChange = tableOrders.reduce(
|
||||
(acc, order) => {
|
||||
const quantityChange = order.getOrderChanges();
|
||||
const quantitySkipped = order.getOrderChanges(true);
|
||||
acc.changed += quantityChange.count;
|
||||
acc.skipped += quantitySkipped.count;
|
||||
return acc;
|
||||
},
|
||||
{ changed: 0, skipped: 0 }
|
||||
);
|
||||
|
||||
table.changes_count = qtyChange.changed;
|
||||
table.skip_changes = qtyChange.skipped;
|
||||
}
|
||||
await this.pos.globalState.unsetTable();
|
||||
await this._tableLongpolling();
|
||||
}
|
||||
onMounted() {
|
||||
this.pos.openCashControl();
|
||||
@@ -78,9 +97,6 @@ export class FloorScreen extends Component {
|
||||
this.addFloorRef.el.style.display = "initial";
|
||||
}
|
||||
this.state.floorMapScrollTop = this.floorMapRef.el.getBoundingClientRect().top;
|
||||
// call _tableLongpolling once then set interval of 5sec.
|
||||
|
||||
this.tableLongpolling = setInterval(this._tableLongpolling.bind(this), 5000);
|
||||
}
|
||||
onWillUnmount() {
|
||||
clearInterval(this.tableLongpolling);
|
||||
@@ -262,43 +278,6 @@ export class FloorScreen extends Component {
|
||||
async _renameFloor(floorId, newName) {
|
||||
await this.orm.call("restaurant.floor", "rename_floor", [floorId, newName]);
|
||||
}
|
||||
async _tableLongpolling() {
|
||||
const { globalState } = this.pos;
|
||||
if (globalState.isEditMode) {
|
||||
return;
|
||||
}
|
||||
const result = await this.orm.call(
|
||||
"pos.config",
|
||||
"get_tables_order_count_and_printing_changes",
|
||||
[globalState.config.id]
|
||||
);
|
||||
for (const table of result) {
|
||||
if (!(table.id in globalState.tables_by_id)) {
|
||||
//We enter this condition if, after loading our PoS, a table is deleted on a PoS and not on another.
|
||||
//This will lead in receiving information on orders from a table that we do not have.
|
||||
//If we do not have a table in the PoS but still receive information about it, we reload the tables
|
||||
//from the server.
|
||||
const result = await this.orm.call("pos.session", "get_pos_ui_restaurant_floor", [
|
||||
[odoo.pos_session_id],
|
||||
]);
|
||||
if (globalState.config.module_pos_restaurant) {
|
||||
globalState.floors = result;
|
||||
globalState.loadRestaurantFloor();
|
||||
}
|
||||
}
|
||||
const table_obj = globalState.tables_by_id[table.id];
|
||||
const unsynced_orders = globalState.getTableOrders(table_obj.id).filter(
|
||||
(o) =>
|
||||
o.server_id === undefined &&
|
||||
(o.orderlines.length !== 0 || o.paymentlines.length !== 0) &&
|
||||
// do not count the orders that are already finalized
|
||||
!o.finalized
|
||||
);
|
||||
table_obj.order_count = table.orders + unsynced_orders.length;
|
||||
table_obj.changes_count = table.changes;
|
||||
table_obj.skip_changes = table.skip_changes;
|
||||
}
|
||||
}
|
||||
get activeFloor() {
|
||||
return this.state.selectedFloorId
|
||||
? this.pos.globalState.floors_by_id[this.state.selectedFloorId]
|
||||
|
||||
@@ -35,10 +35,12 @@ export class Table extends Component {
|
||||
let boxShadow = "0px 3px rgba(0,0,0,0.07)";
|
||||
if (!this.isOccupied()) {
|
||||
background = "transparent";
|
||||
const rgb = table.floor.background_color.substring(4, table.floor.background_color.length-1)
|
||||
.replace(/ /g, '')
|
||||
.split(',');
|
||||
textColor = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2])/255 > 0.5 ? "black" : "white";
|
||||
const rgb = table.floor.background_color
|
||||
.substring(4, table.floor.background_color.length - 1)
|
||||
.replace(/ /g, "")
|
||||
.split(",");
|
||||
textColor =
|
||||
(0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255 > 0.5 ? "black" : "white";
|
||||
border = "3px solid " + table.color;
|
||||
boxShadow = "none";
|
||||
}
|
||||
@@ -90,13 +92,19 @@ export class Table extends Component {
|
||||
}
|
||||
get orderCount() {
|
||||
const table = this.props.table;
|
||||
|
||||
const unsynced_orders = this.pos.globalState.getTableOrders(table.id).filter(
|
||||
(o) =>
|
||||
o.server_id === undefined &&
|
||||
(o.orderlines.length !== 0 || o.paymentlines.length !== 0) &&
|
||||
// do not count the orders that are already finalized
|
||||
!o.finalized
|
||||
);
|
||||
if (table.changes_count > 0) {
|
||||
return table.changes_count;
|
||||
} else if (table.skip_changes > 0) {
|
||||
return table.skip_changes;
|
||||
}
|
||||
return table.order_count;
|
||||
return table.order_count + unsynced_orders.length;
|
||||
}
|
||||
get orderCountClass() {
|
||||
const countClass = { "order-count": true };
|
||||
@@ -121,6 +129,9 @@ export class Table extends Component {
|
||||
return hasChangesCount ? { printing: true } : hasSkippedCount ? { skipped: true } : {};
|
||||
}
|
||||
isOccupied() {
|
||||
return this.pos.globalState.getCustomerCount(this.props.table.id) > 0 || this.props.table.order_count > 0;
|
||||
return (
|
||||
this.pos.globalState.getCustomerCount(this.props.table.id) > 0 ||
|
||||
this.props.table.order_count > 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import { TipScreen } from "@pos_restaurant/js/Screens/TipScreen";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(BackButton.prototype, "pos_restaurant.BackButton", {
|
||||
get table() {
|
||||
return this.pos.globalState.table;
|
||||
},
|
||||
get floor() {
|
||||
return this.table?.floor;
|
||||
},
|
||||
@@ -27,24 +24,6 @@ patch(BackButton.prototype, "pos_restaurant.BackButton", {
|
||||
this.pos.globalState.mobile_pane == "right") ||
|
||||
this.pos.mainScreen.component === TipScreen
|
||||
) {
|
||||
if (this.table) {
|
||||
const orders = this.pos.globalState.get_order_list();
|
||||
const tableOrders = orders.filter((order) => order.tableId === this.table.id);
|
||||
const qtyChange = tableOrders.reduce(
|
||||
(acc, order) => {
|
||||
const quantityChange = order.getOrderChanges();
|
||||
const quantitySkipped = order.getOrderChanges(true);
|
||||
acc.changed += quantityChange.count;
|
||||
acc.skipped += quantitySkipped.count;
|
||||
return acc;
|
||||
},
|
||||
{ changed: 0, skipped: 0 }
|
||||
);
|
||||
|
||||
this.table.changes_count = qtyChange.changed;
|
||||
this.table.skip_changes = qtyChange.skipped;
|
||||
}
|
||||
|
||||
this.pos.showScreen("FloorScreen", { floor: this.floor });
|
||||
} else {
|
||||
this.pos.globalState.mobile_pane = "right";
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { PosBus } from "@point_of_sale/app/pos_bus";
|
||||
|
||||
patch(PosBus.prototype, "pos_restaurant.PosBus", {
|
||||
// Override
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (this.pos.globalState.config.module_pos_restaurant) {
|
||||
this.initTableOrderCount();
|
||||
}
|
||||
},
|
||||
|
||||
async initTableOrderCount() {
|
||||
const result = await this.orm.call(
|
||||
"pos.config",
|
||||
"get_tables_order_count_and_printing_changes",
|
||||
[this.pos.globalState.config.id]
|
||||
);
|
||||
|
||||
this.ws_syncTableCount(result);
|
||||
},
|
||||
|
||||
// Override
|
||||
dispatch(message) {
|
||||
this._super(...arguments);
|
||||
|
||||
if (message.type === "TABLE_ORDER_COUNT") {
|
||||
this.ws_syncTableCount(message.payload);
|
||||
}
|
||||
},
|
||||
|
||||
// Sync the number of orders on each table with other PoS
|
||||
// using the same floorplan.
|
||||
async ws_syncTableCount(data) {
|
||||
const missingTable = data.find((table) => !(table.id in this.pos.globalState.tables_by_id));
|
||||
|
||||
if (missingTable) {
|
||||
const result = await this.orm.call("pos.session", "get_pos_ui_restaurant_floor", [
|
||||
[odoo.pos_session_id],
|
||||
]);
|
||||
|
||||
if (this.pos.globalState.config.module_pos_restaurant) {
|
||||
this.pos.globalState.floors = result;
|
||||
this.pos.globalState.loadRestaurantFloor();
|
||||
}
|
||||
}
|
||||
|
||||
for (const table of data) {
|
||||
const table_obj = this.pos.globalState.tables_by_id[table.id];
|
||||
if (table_obj) {
|
||||
table_obj.order_count = table.orders;
|
||||
table_obj.changes_count = table.changes;
|
||||
table_obj.skip_changes = table.skip_changes;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user