diff --git a/addons/point_of_sale/static/src/app/components/product_card/product_card.js b/addons/point_of_sale/static/src/app/components/product_card/product_card.js
index 2fe1f1e35effd..49f54760cb148 100644
--- a/addons/point_of_sale/static/src/app/components/product_card/product_card.js
+++ b/addons/point_of_sale/static/src/app/components/product_card/product_card.js
@@ -14,6 +14,7 @@ export class ProductCard extends Component {
onClick: { type: Function, optional: true },
onProductInfoClick: { type: Function, optional: true },
showWarning: { type: Boolean, optional: true },
+ productCartQty: { type: [Number, undefined], optional: true },
};
static defaultProps = {
onClick: () => {},
diff --git a/addons/point_of_sale/static/src/app/components/product_card/product_card.xml b/addons/point_of_sale/static/src/app/components/product_card/product_card.xml
index 54f445b6f7948..07b0c31406783 100644
--- a/addons/point_of_sale/static/src/app/components/product_card/product_card.xml
+++ b/addons/point_of_sale/static/src/app/components/product_card/product_card.xml
@@ -14,11 +14,14 @@
-
-
diff --git a/addons/point_of_sale/static/src/app/models/pos_order.js b/addons/point_of_sale/static/src/app/models/pos_order.js
index a132793b13b88..0641d36a3c116 100644
--- a/addons/point_of_sale/static/src/app/models/pos_order.js
+++ b/addons/point_of_sale/static/src/app/models/pos_order.js
@@ -85,6 +85,10 @@ export class PosOrder extends Base {
return this.state !== "draft";
}
+ get totalQuantity() {
+ return this.lines.reduce((sum, line) => sum + line.getQuantity(), 0);
+ }
+
get isUnsyncedPaid() {
return this.finalized && typeof this.id === "string";
}
diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.js b/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.js
index 0447289835c05..d5e8e50cf0bef 100644
--- a/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.js
+++ b/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.js
@@ -3,7 +3,7 @@ import { useService } from "@web/core/utils/hooks";
import { useBarcodeReader } from "@point_of_sale/app/hooks/barcode_reader_hook";
import { _t } from "@web/core/l10n/translation";
import { usePos } from "@point_of_sale/app/hooks/pos_hook";
-import { Component, onMounted, useState, reactive, onWillRender } from "@odoo/owl";
+import { Component, onMounted, useEffect, useState, reactive, onWillRender } from "@odoo/owl";
import { CategorySelector } from "@point_of_sale/app/components/category_selector/category_selector";
import { Input } from "@point_of_sale/app/components/inputs/input/input";
import {
@@ -52,6 +52,7 @@ export class ProductScreen extends Component {
this.state = useState({
previousSearchWord: "",
currentOffset: 0,
+ quantityByProductTmplId: {},
});
onMounted(() => {
this.pos.openOpeningControl();
@@ -84,6 +85,18 @@ export class ProductScreen extends Component {
this.numberBuffer.use({
useWithBarcode: true,
});
+
+ useEffect(
+ () => {
+ this.state.quantityByProductTmplId = this.currentOrder?.lines?.reduce((acc, ol) => {
+ acc[ol.product_id.product_tmpl_id.id]
+ ? (acc[ol.product_id.product_tmpl_id.id] += ol.qty)
+ : (acc[ol.product_id.product_tmpl_id.id] = ol.qty);
+ return acc;
+ }, {});
+ },
+ () => [this.currentOrder.totalQuantity]
+ );
}
getNumpadButtons() {
diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.xml b/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.xml
index 727d3b69cd20c..85afa17efbfe5 100644
--- a/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.xml
+++ b/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.xml
@@ -37,6 +37,7 @@
imageUrl="pos.config.show_product_images and this.getProductImage(product)"
onClick.bind="() => this.addProductToOrder(product)"
productInfo="true"
+ productCartQty="this.state.quantityByProductTmplId[product.id]"
onProductInfoClick.bind="() => this.onProductInfoClick(product)" />
diff --git a/addons/point_of_sale/static/tests/pos/tours/product_screen_tour.js b/addons/point_of_sale/static/tests/pos/tours/product_screen_tour.js
index 0f51fceeabce1..65ffb90fab962 100644
--- a/addons/point_of_sale/static/tests/pos/tours/product_screen_tour.js
+++ b/addons/point_of_sale/static/tests/pos/tours/product_screen_tour.js
@@ -45,6 +45,12 @@ registry.category("web_tour.tours").add("ProductScreenTour", {
...ProductScreen.selectedOrderlineHasDirect("Desk Organizer", "123.0", "627.3"),
...[".", "5"].map(Numpad.click),
...ProductScreen.selectedOrderlineHasDirect("Desk Organizer", "123.5", "629.85"),
+ ]),
+ // Check effects of numpad on product card quantity
+ ProductScreen.productCardQtyIs("Desk Organizer", "123.5"),
+ inLeftSide([
+ // Re-select the order line after switching to the product screen
+ { ...ProductScreen.clickLine("Desk Organizer", "123.5")[0], isActive: ["mobile"] },
Numpad.click("Price"),
Numpad.isActive("Price"),
Numpad.click("1"),
diff --git a/addons/point_of_sale/static/tests/pos/tours/utils/product_screen_util.js b/addons/point_of_sale/static/tests/pos/tours/utils/product_screen_util.js
index a368d7739c1a3..3f9f19b5a0e3a 100644
--- a/addons/point_of_sale/static/tests/pos/tours/utils/product_screen_util.js
+++ b/addons/point_of_sale/static/tests/pos/tours/utils/product_screen_util.js
@@ -87,6 +87,9 @@ export function clickDisplayedProduct(
if (isCheckNeed) {
step.push(...selectedOrderlineHas(name, nextQuantity, nextPrice));
}
+ if (isCheckNeed && nextQuantity) {
+ step.push(...productCardQtyIs(name, nextQuantity));
+ }
return step;
}
@@ -461,6 +464,16 @@ export function cashDifferenceIs(val) {
},
];
}
+export function productCardQtyIs(productName, qty) {
+ qty = `${Number.parseFloat(Number.parseFloat(qty).toFixed(2))}`;
+ return [
+ {
+ content: `'${productName}' should have '${qty}' quantity`,
+ trigger: `article.product .product-content:has(.product-name:contains("${productName}")):has(.product-cart-qty:contains("${qty}"))`,
+ },
+ ];
+}
+
// Temporarily put it here. It should be in the utility methods for the backend views.
export function lastClosingCashIs(val) {
return [