diff --git a/pos_barcode_block_on_error/README.rst b/pos_barcode_block_on_error/README.rst
new file mode 100644
index 0000000..c493187
--- /dev/null
+++ b/pos_barcode_block_on_error/README.rst
@@ -0,0 +1,35 @@
+POS: Block barcode scanning on error popup
+=========================================
+
+Summary
+-------
+This Point of Sale extension prevents the POS from processing further barcode
+scans while the "barcode not found" error popup is visible. It also avoids the
+scanner's Enter/Escape keystrokes from closing the popup, so the cashier must
+explicitly dismiss it (click/tap) before continuing.
+
+Features
+--------
+- Takes exclusive control of the barcode reader while the error popup is shown.
+- Prevents scanner-triggered keyboard "clicks" from closing the popup.
+- Keeps the initial error message visible; subsequent scans are ignored.
+- Includes CSS to highlight the error popup for better visibility.
+
+Usage
+-----
+- Scan a non-existent barcode in the POS.
+ - The error popup will show.
+ - Further scans will be ignored while the popup is visible.
+ - Close the popup via mouse/touch to resume scanning.
+
+Limitations
+-----------
+- While the popup is open, all barcode actions are blocked (product, client,
+ weight, price, discount, GS1, etc.). Customize the hook if you need
+ exceptions.
+
+
+Credits
+-------
+- Author: Criptomart
+- License: AGPL-3
diff --git a/pos_barcode_block_on_error/__manifest__.py b/pos_barcode_block_on_error/__manifest__.py
new file mode 100644
index 0000000..1af2032
--- /dev/null
+++ b/pos_barcode_block_on_error/__manifest__.py
@@ -0,0 +1,16 @@
+{
+ "name": "POS: Block barcode scanning on error popup",
+ "version": "16.0.1.0.0",
+ "summary": "Stops processing new barcodes while ErrorBarcodePopup is shown.",
+ "category": "Point of Sale",
+ "license": "AGPL-3",
+ "author": "Criptomart",
+ "depends": ["point_of_sale"],
+ "assets": {
+ "point_of_sale.assets": [
+ "pos_barcode_block_on_error/static/src/js/error_barcode_popup_block.js",
+ "pos_barcode_block_on_error/static/src/css/error_popup.css",
+ ],
+ },
+ "installable": True,
+}
diff --git a/pos_barcode_block_on_error/static/src/css/error_popup.css b/pos_barcode_block_on_error/static/src/css/error_popup.css
new file mode 100644
index 0000000..817a99b
--- /dev/null
+++ b/pos_barcode_block_on_error/static/src/css/error_popup.css
@@ -0,0 +1,16 @@
+/* Emphasize the error popup for unknown barcode */
+/* Make the whole popup background red and invert text for visibility */
+.popup.popup-barcode {
+ background-color: #c62828 !important; /* strong red background */
+ color: #fff !important; /* default text to white */
+}
+.popup.popup-barcode .title,
+.popup.popup-barcode .body {
+ color: #fff !important;
+ font-weight: 600;
+}
+.popup.popup-barcode .footer .button.cancel {
+ background: #fff !important; /* white button on red bg */
+ color: #c62828 !important; /* red text */
+ border-color: #fff !important;
+}
diff --git a/pos_barcode_block_on_error/static/src/js/error_barcode_popup_block.js b/pos_barcode_block_on_error/static/src/js/error_barcode_popup_block.js
new file mode 100644
index 0000000..9024895
--- /dev/null
+++ b/pos_barcode_block_on_error/static/src/js/error_barcode_popup_block.js
@@ -0,0 +1,67 @@
+/** @odoo-module */
+/**
+ * Block barcode processing while ErrorBarcodePopup is visible by taking
+ * exclusive control of the barcode reader, and release it when closing.
+ */
+
+import Registries from "point_of_sale.Registries";
+import ErrorBarcodePopup from "point_of_sale.ErrorBarcodePopup";
+import { useBarcodeReader } from "point_of_sale.custom_hooks";
+
+const ErrorBarcodePopupBlock = (ErrorPopup) =>
+ class extends ErrorPopup {
+ setup() {
+ super.setup(...arguments);
+ // Prevent keyboard-triggered clicks (Enter on focused button)
+ owl.onMounted(() => {
+ this.__clickHandler = (ev) => {
+ // Keyboard-triggered clicks usually have detail === 0
+ if (ev && ev.detail === 0) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+ };
+ if (this.el) {
+ this.el.addEventListener('click', this.__clickHandler, true);
+ }
+ });
+
+
+ // Take exclusive control using POS hook so subsequent scans are ignored
+ useBarcodeReader({
+ product: () => {},
+ quantity: () => {},
+ weight: () => {},
+ price: () => {},
+ client: () => {},
+ discount: () => {},
+ cashier: () => {},
+ error: () => {},
+ gs1: () => {},
+ }, true);
+ }
+
+ willUnmount() {
+ // useBarcodeReader cleans up automatically on unmount
+ if (this.el && this.__clickHandler) {
+ this.el.removeEventListener('click', this.__clickHandler, true);
+ }
+ super.willUnmount(...arguments);
+ }
+
+ // Only accept real pointer clicks to close the popup; ignore keyboard/programmatic events
+ async confirm(ev) {
+ if (ev && ev.type === 'click' && ev.isTrusted && ev.detail > 0) {
+ return super.confirm();
+ }
+ }
+ cancel(ev) {
+ if (ev && ev.type === 'click' && ev.isTrusted && ev.detail > 0) {
+ return super.cancel();
+ }
+ }
+ };
+
+Registries.Component.extend(ErrorBarcodePopup, ErrorBarcodePopupBlock);
+
+export default ErrorBarcodePopup;
diff --git a/product_sale_price_from_pricelist/models/product_template.py b/product_sale_price_from_pricelist/models/product_template.py
index 4bb23a4..894de69 100644
--- a/product_sale_price_from_pricelist/models/product_template.py
+++ b/product_sale_price_from_pricelist/models/product_template.py
@@ -46,6 +46,18 @@ class ProductTemplate(models.Model):
company_dependent=True,
)
+ price_differs = fields.Boolean(
+ string="Price Differs", compute="_compute_price_differs", store=True
+ )
+
+ @api.depends("list_price", "list_price_theoritical")
+ def _compute_price_differs(self):
+ for product in self:
+ product.price_differs = (
+ product.list_price != product.list_price_theoritical
+ and product.last_purchase_price_compute_type != "manual_update"
+ )
+
def _compute_theoritical_price(self):
pricelist_obj = self.env["product.pricelist"]
pricelist_id = (
@@ -89,7 +101,9 @@ class ProductTemplate(models.Model):
template.write(
{
"list_price_theoritical": price_with_taxes,
- "last_purchase_price_updated": True,
+ "last_purchase_price_updated": (
+ price_with_taxes != template.list_price
+ ),
}
)
else:
diff --git a/product_sale_price_from_pricelist/views/product_view.xml b/product_sale_price_from_pricelist/views/product_view.xml
index 1d1a277..2932212 100644
--- a/product_sale_price_from_pricelist/views/product_view.xml
+++ b/product_sale_price_from_pricelist/views/product_view.xml
@@ -60,8 +60,10 @@
+
diff --git a/product_update_price_last_purchase/views/product_view.xml b/product_update_price_last_purchase/views/product_view.xml
index 9a189c9..b22d253 100644
--- a/product_update_price_last_purchase/views/product_view.xml
+++ b/product_update_price_last_purchase/views/product_view.xml
@@ -47,5 +47,17 @@
+
+ product.product.tree.inherit.margin.classification
+ product.product
+
+
+
+
+
+
+
+
+