Hi everyone,
I am implementing a custom barcode search feature in POS.
Current behavior:
Online mode
- Server barcode search works
- Real-time stock works
- allow_negative_pos_sale works
Offline mode
I want:
- Local barcode search
- Cached stock check
- No crash
❌ Problem:
When POS is offline, barcode search is not working properly (product not found or stock shows 0 even if available).
My Code (JS):
/** @odoo-module **/My Backend Code:
import { patch } from "@web/core/utils/patch";
import { Navbar } from "@point_of_sale/app/navbar/navbar";
import { useRef } from "@odoo/owl";
patch(Navbar.prototype, {
patchName: "pos_barcode_search.Navbar",
setup() {
super.setup();
this.barcodeInput = useRef("barcodeInput");
},
async barcodeKeyHandler(ev) {
if (ev.key === "Enter") {
console.log("Barcode handler triggered!");
const barcode = this.barcodeInput.el.value.trim();
console.log("barcode", barcode);
console.log("this.pos", this.pos);
if (barcode && this.pos) {
// First try to find the product in local models
let product = this.pos.models["product.product"].getBy("barcode", barcode);
// If not found locally, try product packaging
if (!product) {
const productPackaging = this.pos.models["product.packaging"].getBy("barcode", barcode);
product = productPackaging && productPackaging.product_id;
}
// If still not found, search on the server
if (!product) {
try {
const records = await this.pos.data.callRelated(
"pos.session",
"find_product_by_barcode",
[this.pos.session.id, barcode, this.pos.config.id]
);
await this.pos.processProductAttributes();
if (records && records["product.product"].length > 0) {
product = records["product.product"][0];
await this.pos._loadMissingPricelistItems([product]);
}
} catch (error) {
console.error("Error searching for product by barcode:", error);
}
}
if (product) {
// Ensure we have the 'type' field on the product
let productType = product.type;
if (productType === undefined) {
const orm = this.env.services.orm;
const typeData = await orm.read('product.product', [product.id], ['type']);
productType = typeData[0]?.type;
}
if (productType === 'product' || productType === 'consu') { // Both storable and consumable products need stock check
// Check if product allows negative POS sales
const allowNegativeSale = product.allow_negative_pos_sale;
console.log("[BARCODE SEARCH] Product:", product.name, "allow_negative_pos_sale:", allowNegativeSale);
console.log("[BARCODE SEARCH] Full product data:", product);
console.log("[BARCODE SEARCH] Product ID:", product.id);
console.log("[BARCODE SEARCH] All product keys:", Object.keys(product));
// If field is not available, try to fetch it from server
let finalAllowNegativeSale = allowNegativeSale;
if (allowNegativeSale === undefined || allowNegativeSale === null) {
console.log("[BARCODE SEARCH] Field not available in product data, fetching from server...");
try {
const orm = this.env.services.orm;
const fieldData = await orm.read('product.product', [product.id], ['allow_negative_pos_sale']);
finalAllowNegativeSale = fieldData[0]?.allow_negative_pos_sale || false;
console.log("[BARCODE SEARCH] Fetched from server:", finalAllowNegativeSale);
} catch (error) {
console.error("[BARCODE SEARCH] Error fetching field from server:", error);
finalAllowNegativeSale = false;
}
}
// CONDITION: If product has exemption enabled, skip stock restriction
if (finalAllowNegativeSale) {
console.log("[BARCODE SEARCH] Product has exemption enabled - skipping stock restriction");
// Proceed with adding product without stock check
} else {
console.log("[BARCODE SEARCH] Product has no exemption - applying stock restriction");
// Stock check logic from pos_stock_restriction.js
const orm = this.env.services.orm;
const stockData = await orm.read('product.product', [product.id], ['qty_available']);
const qty_available = stockData[0]?.qty_available ?? 0;
console.log("Qty In hand", qty_available);
// Get the current order and count how many of this product are already in the order
const order = this.pos.get_order();
let qty_in_order = 0;
if (order) {
const lines = order.get_orderlines();
console.log("Order lines:", lines);
lines.forEach(line => {
const lineProduct = line.get_product ? line.get_product() : line.product;
// Use get_quantity() if available, otherwise fallback to line.quantity
const lineQty = line.get_quantity ? line.get_quantity() : line.quantity;
console.log("Line product:", lineProduct, "Line product id:", lineProduct && lineProduct.id, "Target product id:", product.id, "Line qty:", lineQty);
if (lineProduct && lineProduct.id === product.id) {
qty_in_order += lineQty;
console.log("Qty in order (matched):", qty_in_order);
}
});
}
// The quantity to add per scan
const qty_needed = 1;
if (qty_available < qty_in_order + qty_needed) {
this.env.services.notification.add(
`Out of stock: ${product.display_name || product.name}`,
{ type: "danger" }
);
return;
}
}
await this.pos.addLineToCurrentOrder({ product_id: product }, {}, false);
this.barcodeInput.el.value = "";
} else {
// Not a storable product (e.g., service or consumable), add without stock check
await this.pos.addLineToCurrentOrder({ product_id: product }, {}, false);
this.barcodeInput.el.value = "";
}
} else {
this.env.services.notification.add("Product not found for barcode: " + barcode, { type: "danger" });
}
}
}
},
});
from odoo import models
class PosConfig(models.Model):
_inherit = 'pos.config'
def _get_pos_ui_product_fields(self):
fields = super()._get_pos_ui_product_fields()
if 'allow_negative_pos_sale' not in fields:
fields.append('allow_negative_pos_sale')
print(f"[DEBUG] Barcode Search - POS UI Product fields: {fields}")
# Force ensure the field is loaded
if 'allow_negative_pos_sale' not in fields:
fields.append('allow_negative_pos_sale')
print(f"[DEBUG] Barcode Search - Force added allow_negative_pos_sale to POS UI fields")
return fields
def _loader_params_product_product(self):
res = super()._loader_params_product_product()
if 'allow_negative_pos_sale' not in res['search_params']['fields']:
res['search_params']['fields'].append('allow_negative_pos_sale')
print(f"[DEBUG] Barcode Search - Product fields being loaded: {res['search_params']['fields']}")
# Force ensure the field is loaded
if 'allow_negative_pos_sale' not in res['search_params']['fields']:
res['search_params']['fields'].append('allow_negative_pos_sale')
print(f"[DEBUG] Barcode Search - Force added allow_negative_pos_sale field")
# Also ensure we have the basic fields
required_fields = ['id', 'name', 'barcode', 'type', 'allow_negative_pos_sale']
for field in required_fields:
if field not in res['search_params']['fields']:
res['search_params']['fields'].append(field)
print(f"[DEBUG] Barcode Search - Force added required field: {field}")
# Also ensure we have the search domain
if 'search_params' not in res:
res['search_params'] = {}
if 'domain' not in res['search_params']:
res['search_params']['domain'] = []
return res
def _loader_params_product_template(self):
res = super()._loader_params_product_template()
if 'allow_negative_pos_sale' not in res['search_params']['fields']:
res['search_params']['fields'].append('allow_negative_pos_sale')
print(f"[DEBUG] Barcode Search - Product Template fields being loaded: {res['search_params']['fields']}")
# Force ensure the field is loaded
if 'allow_negative_pos_sale' not in res['search_params']['fields']:
res['search_params']['fields'].append('allow_negative_pos_sale')
print(f"[DEBUG] Barcode Search - Force added allow_negative_pos_sale to Product Template fields")
# Also ensure we have the basic fields
required_fields = ['id', 'name', 'type', 'allow_negative_pos_sale']
for field in required_fields:
if field not in res['search_params']['fields']:
res['search_params']['fields'].append(field)
print(f"[DEBUG] Barcode Search - Force added required field to Product Template: {field}")
return res
def _pos_ui_models_to_load(self):
res = super()._pos_ui_models_to_load()
if 'product.product' not in res:
res.append('product.product')
print(f"[DEBUG] Barcode Search - POS UI Models to load: {res}")
# Force ensure product.product is loaded
if 'product.product' not in res:
res.append('product.product')
print(f"[DEBUG] Barcode Search - Force added product.product to POS UI Models")
# Also ensure we have the basic models
required_models = ['product.product', 'product.template']
for model in required_models:
if model not in res:
res.append(model)
print(f"[DEBUG] Barcode Search - Force added required model: {model}")
return res
My Requirement:
I need a best hybrid logic:
- If online → use server (dynamic data)
- If offline → use local POS data (no RPC calls)
- Barcode search should work in both cases
Can anyone help me modify this code to:
- Work correctly in offline mode
- Keep my existing online functionality
- Ensure stock and barcode search works properly offline
Thanks in advance!