Skip to content

Commit 1f92caa

Browse files
committed
[ADD] sales_barcodescan: hit the add product button for barcode scan
- Allows to add quantity through barcode search - If product found on scan -> Hit the add button with qty 1 on every unique barcode else -> raise toster - no products founds with this barcoe - scaling the same product increase the qty with same order line - all scaled product at first page
1 parent fbf9ee9 commit 1f92caa

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed

sales_barcodescan/__manifest__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "SO/PO barcode scan",
3+
"version": "1.0",
4+
"summary": "Add products to PO/SO from catalog via barcode scanning.",
5+
"author": "prbo",
6+
"depends": ["sale_management", "product"],
7+
"license": "LGPL-3",
8+
"assets": {
9+
"web.assets_backend": [
10+
"sales_barcodescan/static/src/**/*",
11+
],
12+
},
13+
"installable": True,
14+
"application": True,
15+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { patch } from "@web/core/utils/patch";
2+
import { ProductCatalogKanbanController } from "@product/product_catalog/kanban_controller";
3+
import { rpc } from "@web/core/network/rpc";
4+
import { useService } from "@web/core/utils/hooks";
5+
import { onMounted, onWillUnmount } from "@odoo/owl";
6+
7+
patch(ProductCatalogKanbanController.prototype, {
8+
setup(){
9+
super.setup();
10+
this.orm = useService("orm");
11+
this.notification = useService("notification");
12+
this.orderId = this.props.context.order_id;
13+
this.orderResModel = this.props.context.product_catalog_order_model;
14+
this.lastInputTime = 0;
15+
this.barcodeBuffer = "";
16+
this._onKeyDown = this._onKeyDown.bind(this);
17+
onMounted(() => {
18+
document.addEventListener("keydown", this._onKeyDown);
19+
});
20+
onWillUnmount(() => {
21+
document.removeEventListener("keydown", this._onKeyDown);
22+
});
23+
},
24+
25+
async _onKeyDown(res) {
26+
const targetfield = res.target.tagName;
27+
if (targetfield === "INPUT" || targetfield === "TEXTAREA" || targetfield === "SELECT") {
28+
return;
29+
}
30+
const currentTime = new Date().getTime();
31+
if (currentTime - this.lastInputTime > 800) {
32+
this.barcodeBuffer = "";
33+
}
34+
if (res.key === "Enter") {
35+
if (this.barcodeBuffer.length > 1) {
36+
this._processBarcode(this.barcodeBuffer);
37+
this.barcodeBuffer = "";
38+
}
39+
} else if (res.key.length === 1) {
40+
this.barcodeBuffer += res.key;
41+
}
42+
this.lastInputTime = currentTime;
43+
},
44+
45+
async _processBarcode(scannedBarcode) {
46+
if (!this.orderId) {
47+
this.notification.add("Please select an order first.", {
48+
type: "warning",
49+
});
50+
return;
51+
}
52+
try {
53+
const products = await this.orm.searchRead(
54+
"product.product",
55+
[["barcode", "=", scannedBarcode]],
56+
["id", "name"]
57+
);
58+
59+
if (!products.length) {
60+
this.notification.add("No product found for this barcode.", {
61+
type: "warning",
62+
});
63+
return;
64+
}
65+
66+
const product = products[0];
67+
68+
let orderLineModel, quantityField;
69+
if (this.orderResModel === "sale.order") {
70+
orderLineModel = "sale.order.line";
71+
quantityField = "product_uom_qty";
72+
} else if (this.orderResModel === "purchase.order") {
73+
orderLineModel = "purchase.order.line";
74+
quantityField = "product_qty";
75+
} else {
76+
console.error(
77+
"Unsupported order model for barcode scanning:",
78+
this.orderResModel
79+
);
80+
this.notification.add(
81+
"Barcode scanning is not supported for this type of model.",
82+
{ type: "danger" }
83+
);
84+
return;
85+
}
86+
87+
const existingOrderLines = await this.orm.searchRead(
88+
orderLineModel,
89+
[
90+
["order_id", "=", this.orderId],
91+
["product_id", "=", product.id],
92+
],
93+
["id", quantityField]
94+
);
95+
96+
const updatedQuantity = existingOrderLines.length ? existingOrderLines[0][quantityField] + 1 : 1;
97+
98+
const response = await rpc("/product/catalog/update_order_line_info", {
99+
res_model: this.orderResModel,
100+
order_id: this.orderId,
101+
product_id: product.id,
102+
quantity: updatedQuantity,
103+
});
104+
105+
if (response && response.success) {
106+
this.notification.add(
107+
`successfully ${existingOrderLines ? "Updated" : "Added"} ${product.name} (Qty: ${updatedQuantity})`,
108+
{ type: "success" }
109+
);
110+
this.model.load();
111+
} else {
112+
this.notification.add(
113+
`failed to ${existingOrderLines ? "update" : "add"} ${product.name}.`,
114+
{ type: "danger" }
115+
);
116+
this.model.load();
117+
}
118+
this.model.load();
119+
} catch (error) {
120+
console.error("Error processing barcode scan:", error);
121+
this.notification.add("An error occurred while processing the barcode.", {
122+
type: "danger",
123+
});
124+
}
125+
},
126+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { rpc } from "@web/core/network/rpc";
2+
import { ProductCatalogKanbanModel } from "@product/product_catalog/kanban_model";
3+
import { getFieldsSpec } from "@web/model/relational_model/utils";
4+
5+
export class BarcodeProductCatalogKanbanModel extends ProductCatalogKanbanModel {
6+
async _loadUngroupedList(config) {
7+
const allProducts = await this.orm.search(config.resModel, config.domain);
8+
9+
if (!allProducts.length) {
10+
return { records: [], length: 0 };
11+
}
12+
13+
let orderLines = {};
14+
const scanned = [], unscanned = [];
15+
16+
if (config.context.order_id && config.context.product_catalog_order_model) {
17+
orderLines = await rpc("/product/catalog/order_lines_info", {
18+
order_id: config.context.order_id,
19+
product_ids: allProducts,
20+
res_model: config.context.product_catalog_order_model,
21+
});
22+
23+
for (const id of allProducts) {
24+
const qty = (orderLines[id]?.quantity) || 0;
25+
if (qty > 0) scanned.push(id);
26+
else unscanned.push(id);
27+
}
28+
29+
scanned.sort((a, b) =>
30+
(orderLines[b]?.quantity || 0) - (orderLines[a]?.quantity || 0)
31+
);
32+
} else {
33+
unscanned.push(...allProducts);
34+
}
35+
36+
const sortedIds = [...scanned, ...unscanned];
37+
const paginatedIds = sortedIds.slice(config.offset, config.offset + config.limit);
38+
39+
const kwargs = {
40+
specification: getFieldsSpec(config.activeFields, config.fields, config.context), //passed through webserach() in orm
41+
};
42+
43+
const result = await this.orm.webSearchRead(config.resModel, [["id", "in", paginatedIds]], kwargs);
44+
45+
result.records.sort((a, b) => {
46+
const qtyA = orderLines[a.id]?.quantity || 0;
47+
const qtyB = orderLines[b.id]?.quantity || 0;
48+
return qtyB - qtyA || a.id - b.id;
49+
});
50+
51+
return {
52+
length: allProducts.length,
53+
records: result.records,
54+
};
55+
}
56+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { registry } from "@web/core/registry";
2+
import { productCatalogKanbanView } from "@product/product_catalog/kanban_view";
3+
import { BarcodeProductCatalogKanbanModel } from "./barcode_kanban_model";
4+
5+
export const BarcodeProductCatalogKanbanView = {
6+
...productCatalogKanbanView,
7+
Model: BarcodeProductCatalogKanbanModel,
8+
};
9+
10+
registry.category("views").remove("product_kanban_catalog");
11+
registry.category("views").add("product_kanban_catalog", BarcodeProductCatalogKanbanView);

0 commit comments

Comments
 (0)