[IMP] pos_self_order: add barcode scanning
In this commit we implement barcode scanning functionality in the `pos_self_order` module. This is useful for kiosk devices in shops. closes odoo/odoo#148480 Task: 3637834 Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
This commit is contained in:
@@ -55,6 +55,7 @@
|
||||
'bus/static/src/bus_parameters_service.js',
|
||||
'bus/static/src/multi_tab_service.js',
|
||||
'bus/static/src/workers/*',
|
||||
'barcodes/static/src/barcode_service.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/data.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/event-handler.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/manipulator.js',
|
||||
|
||||
@@ -196,6 +196,7 @@ class ProductProduct(models.Model):
|
||||
"is_pos_groupable": self.uom_id.is_pos_groupable,
|
||||
"write_date": self.write_date.timestamp(),
|
||||
"self_order_available": self.self_order_available,
|
||||
"barcode": self.barcode,
|
||||
}
|
||||
|
||||
def _get_self_order_data(self, pos_config: PosConfig) -> List[Dict]:
|
||||
|
||||
@@ -35,6 +35,7 @@ export class Product extends Reactive {
|
||||
this.is_pos_groupable = product.is_pos_groupable;
|
||||
this.write_date = product.write_date;
|
||||
this.self_order_available = product.self_order_available;
|
||||
this.barcode = product.barcode;
|
||||
|
||||
// data
|
||||
this.showPriceTaxIncluded = showPriceTaxIncluded;
|
||||
|
||||
@@ -27,7 +27,7 @@ export class ComboPage extends Component {
|
||||
});
|
||||
|
||||
if (!this.props.product) {
|
||||
this.router.navigate("productList");
|
||||
this.router.navigate("product_list");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
import { Component, onWillUnmount, useState, useSubEnv } from "@odoo/owl";
|
||||
import { useSelfOrder } from "@pos_self_order/app/self_order_service";
|
||||
import { AttributeSelection } from "@pos_self_order/app/components/attribute_selection/attribute_selection";
|
||||
import { Line } from "@pos_self_order/app/models/line";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { attributeFlatter } from "@pos_self_order/app/utils";
|
||||
import { constructFullProductName } from "@point_of_sale/utils";
|
||||
|
||||
export class ProductPage extends Component {
|
||||
static template = "pos_self_order.ProductPage";
|
||||
@@ -19,7 +16,7 @@ export class ProductPage extends Component {
|
||||
useSubEnv({ selectedValues: {}, customValues: {}, editable: this.editableProductLine });
|
||||
|
||||
if (!this.props.product) {
|
||||
this.router.navigate("productList");
|
||||
this.router.navigate("product_list");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,75 +77,19 @@ export class ProductPage extends Component {
|
||||
return increase ? this.state.qty++ : this.state.qty--;
|
||||
}
|
||||
|
||||
orderlineCanBeMerged() {
|
||||
if (this.props.product.pos_combo_ids.length) {
|
||||
return false;
|
||||
}
|
||||
const editedLine = this.selfOrder.editedLine;
|
||||
|
||||
if (editedLine) {
|
||||
return editedLine;
|
||||
}
|
||||
|
||||
const line = this.selfOrder.currentOrder.lines.find(
|
||||
(l) =>
|
||||
JSON.stringify(l.attribute_value_ids.sort()) ===
|
||||
JSON.stringify(attributeFlatter(this.env.selectedValues).sort()) &&
|
||||
l.customer_note === this.state.customer_note &&
|
||||
l.product_id === this.product.id
|
||||
);
|
||||
|
||||
return line || false;
|
||||
}
|
||||
|
||||
async addToCart() {
|
||||
const lines = this.selfOrder.currentOrder.lines;
|
||||
const lineToMerge = this.orderlineCanBeMerged();
|
||||
|
||||
if (lineToMerge) {
|
||||
lineToMerge.attribute_value_ids = attributeFlatter(this.env.selectedValues);
|
||||
lineToMerge.customer_note = this.state.customer_note;
|
||||
lineToMerge.full_product_name = this.product.name;
|
||||
|
||||
if (this.selfOrder.editedLine) {
|
||||
lineToMerge.qty = this.state.qty;
|
||||
} else {
|
||||
lineToMerge.qty += this.state.qty;
|
||||
}
|
||||
} else {
|
||||
const mainLine = new Line({
|
||||
id: lineToMerge ? lineToMerge.id : null,
|
||||
uuid: lineToMerge ? lineToMerge.uuid : null,
|
||||
qty: this.state.qty,
|
||||
product_id: this.product.id,
|
||||
customer_note: this.state.customer_note,
|
||||
custom_attribute_value_ids: Object.values(this.env.customValues),
|
||||
attribute_value_ids: attributeFlatter(this.env.selectedValues),
|
||||
});
|
||||
|
||||
mainLine.full_product_name = constructFullProductName(
|
||||
mainLine,
|
||||
this.selfOrder.attributeValueById,
|
||||
this.product.name
|
||||
);
|
||||
|
||||
lines.push(mainLine);
|
||||
}
|
||||
|
||||
// If a command line does not have a quantity greater than 0, we consider it deleted
|
||||
await this.selfOrder.getPricesFromServer();
|
||||
this.selfOrder.currentOrder.lines = lines.filter((o) => o.qty > 0);
|
||||
|
||||
if (this.selfOrder.currentOrder.lines.length === 0) {
|
||||
this.router.navigate("productList");
|
||||
} else {
|
||||
this.router.back();
|
||||
}
|
||||
}
|
||||
|
||||
get showQtyButtons() {
|
||||
return this.props.product.self_order_available;
|
||||
}
|
||||
addToCart() {
|
||||
this.selfOrder.addToCart(
|
||||
this.props.product,
|
||||
this.state.qty,
|
||||
this.state.customer_note,
|
||||
this.env.selectedValues,
|
||||
this.env.customValues
|
||||
);
|
||||
this.router.back();
|
||||
}
|
||||
|
||||
isEveryValueSelected() {
|
||||
return Object.values(this.state.selectedValues).find((value) => !value) == false;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Product } from "@pos_self_order/app/models/product";
|
||||
import { Combo } from "@pos_self_order/app/models/combo";
|
||||
import { session } from "@web/session";
|
||||
import { getColor } from "@web/core/colors/colors";
|
||||
import { categorySorter, attributeFormatter } from "@pos_self_order/app/utils";
|
||||
import { categorySorter, attributeFormatter, attributeFlatter } from "@pos_self_order/app/utils";
|
||||
import { Order } from "@pos_self_order/app/models/order";
|
||||
import { batched } from "@web/core/utils/timing";
|
||||
import { useState, markup } from "@odoo/owl";
|
||||
@@ -16,7 +16,8 @@ import { registry } from "@web/core/registry";
|
||||
import { cookie } from "@web/core/browser/cookie";
|
||||
import { formatDateTime } from "@web/core/l10n/dates";
|
||||
import { printerService } from "@point_of_sale/app/printer/printer_service";
|
||||
import { qrCodeSrc } from "@point_of_sale/utils";
|
||||
import { constructFullProductName, qrCodeSrc } from "@point_of_sale/utils";
|
||||
import { Line } from "./models/line";
|
||||
|
||||
export class SelfOrder extends Reactive {
|
||||
constructor(...args) {
|
||||
@@ -24,13 +25,14 @@ export class SelfOrder extends Reactive {
|
||||
this.ready = this.setup(...args).then(() => this);
|
||||
}
|
||||
|
||||
async setup(env, { rpc, notification, router, printer }) {
|
||||
async setup(env, { rpc, notification, router, printer, barcode }) {
|
||||
// services
|
||||
this.notification = notification;
|
||||
this.router = router;
|
||||
this.env = env;
|
||||
this.rpc = rpc;
|
||||
this.printer = printer;
|
||||
this.barcode = barcode;
|
||||
|
||||
// data
|
||||
Object.assign(this, {
|
||||
@@ -60,6 +62,75 @@ export class SelfOrder extends Reactive {
|
||||
} else {
|
||||
await this.initMobileData();
|
||||
}
|
||||
barcode.bus.addEventListener("barcode_scanned", (ev) => {
|
||||
if (!this.ordering) {
|
||||
this.notification.add(_t("We're currently closed"), {
|
||||
type: "danger",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const product = Object.values(this.productByIds).filter(
|
||||
(p) => p.barcode === ev.detail.barcode
|
||||
)?.[0];
|
||||
if (!product) {
|
||||
this.notification.add(_t("Product not found"), {
|
||||
type: "danger",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!product.self_order_available) {
|
||||
this.notification.add(_t("Product is not available"), {
|
||||
type: "danger",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (product.attributes.length) {
|
||||
this.router.navigate("product", { id: product.id });
|
||||
return;
|
||||
}
|
||||
this.addToCart(product, 1, "", {}, {});
|
||||
this.router.navigate("cart");
|
||||
});
|
||||
}
|
||||
|
||||
async addToCart(product, qty, customer_note, selectedValues, customValues) {
|
||||
const lineToMerge =
|
||||
!product.pos_combo_ids.length &&
|
||||
(this.editedLine ||
|
||||
this.currentOrder.lines.find(
|
||||
(l) =>
|
||||
JSON.stringify(l.attribute_value_ids.sort()) ===
|
||||
JSON.stringify(attributeFlatter(selectedValues).sort()) &&
|
||||
l.customer_note === customer_note &&
|
||||
l.product_id === product.id
|
||||
));
|
||||
if (lineToMerge) {
|
||||
lineToMerge.attribute_value_ids = attributeFlatter(selectedValues);
|
||||
lineToMerge.customer_note = customer_note;
|
||||
lineToMerge.full_product_name = product.name;
|
||||
if (this.editedLine) {
|
||||
lineToMerge.qty = qty;
|
||||
} else {
|
||||
lineToMerge.qty += qty;
|
||||
}
|
||||
} else {
|
||||
const mainLine = new Line({
|
||||
qty,
|
||||
product_id: product.id,
|
||||
customer_note,
|
||||
custom_attribute_value_ids: Object.values(customValues),
|
||||
attribute_value_ids: attributeFlatter(selectedValues),
|
||||
});
|
||||
mainLine.full_product_name = constructFullProductName(
|
||||
mainLine,
|
||||
this.attributeValueById,
|
||||
product.name
|
||||
);
|
||||
this.currentOrder.lines.push(mainLine);
|
||||
}
|
||||
await this.getPricesFromServer();
|
||||
// If a command line does not have a quantity greater than 0, we consider it deleted
|
||||
this.currentOrder.lines = this.currentOrder.lines.filter((o) => o.qty > 0);
|
||||
}
|
||||
|
||||
async confirmOrder() {
|
||||
@@ -587,9 +658,9 @@ export class SelfOrder extends Reactive {
|
||||
}
|
||||
|
||||
export const selfOrderService = {
|
||||
dependencies: ["rpc", "notification", "router", "printer"],
|
||||
async start(env, { rpc, notification, router, printer }) {
|
||||
return new SelfOrder(env, { rpc, notification, router, printer }).ready;
|
||||
dependencies: ["rpc", "notification", "router", "printer", "barcode"],
|
||||
async start(env, { rpc, notification, router, printer, barcode }) {
|
||||
return new SelfOrder(env, { rpc, notification, router, printer, barcode }).ready;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user