Compare commits
3 commits
190d1f09c2
...
93ccb20a17
| Author | SHA1 | Date | |
|---|---|---|---|
| 93ccb20a17 | |||
| a24b4392d6 | |||
| f4bc2ee66b |
7 changed files with 165 additions and 3 deletions
35
pos_barcode_block_on_error/README.rst
Normal file
35
pos_barcode_block_on_error/README.rst
Normal 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
|
||||||
16
pos_barcode_block_on_error/__manifest__.py
Normal file
16
pos_barcode_block_on_error/__manifest__.py
Normal 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,
|
||||||
|
}
|
||||||
16
pos_barcode_block_on_error/static/src/css/error_popup.css
Normal file
16
pos_barcode_block_on_error/static/src/css/error_popup.css
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue