Compare commits

...

3 commits

7 changed files with 165 additions and 3 deletions

View file

@ -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

View file

@ -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,
}

View file

@ -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;
}

View file

@ -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;

View file

@ -46,6 +46,18 @@ class ProductTemplate(models.Model):
company_dependent=True, 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): def _compute_theoritical_price(self):
pricelist_obj = self.env["product.pricelist"] pricelist_obj = self.env["product.pricelist"]
pricelist_id = ( pricelist_id = (
@ -89,7 +101,9 @@ class ProductTemplate(models.Model):
template.write( template.write(
{ {
"list_price_theoritical": price_with_taxes, "list_price_theoritical": price_with_taxes,
"last_purchase_price_updated": True, "last_purchase_price_updated": (
price_with_taxes != template.list_price
),
} }
) )
else: else:

View file

@ -60,8 +60,10 @@
<filter string="To update sales price" <filter string="To update sales price"
name="products_updated_filter" name="products_updated_filter"
domain="[('last_purchase_price_updated', '=', True), ('last_purchase_price_compute_type', '!=', 'manual_update')]" domain="[('last_purchase_price_updated', '=', True), ('last_purchase_price_compute_type', '!=', 'manual_update')]"
help="Products that have recently changed their cost price and have not updated their selling price." />
icon="terp-project" <filter string="Different Theoritical Price"
name="products_diff_theoritical_filter"
domain="[('price_differs', '=', True)]"
/> />
</xpath> </xpath>
</field> </field>

View file

@ -47,5 +47,17 @@
</field> </field>
</record> </record>
<record id="product_product_tree_view_inherit_margin_classification" model="ir.ui.view">
<field name="name">product.product.tree.inherit.margin.classification</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product_margin_classification.view_product_product_tree" />
<field name="arch" type="xml">
<field name="name" position="after">
<field name="qty_available" readonly="1" optional="show"/>
<field name="virtual_available" readonly="1" optional="hide"/>
</field>
</field>
</record>
</data> </data>
</odoo> </odoo>