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,
|
||||
)
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -60,8 +60,10 @@
|
|||
<filter string="To update sales price"
|
||||
name="products_updated_filter"
|
||||
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>
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -47,5 +47,17 @@
|
|||
</field>
|
||||
</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>
|
||||
</odoo>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue