Skip to content

Commit 8229c46

Browse files
committed
[ADD] sales_purchase_barcode: implement barcode scanning in product catalog
Before this commit: Users had to manually enter the barcode into the search bar to find products in the catalog view. After this commit: Scanning a barcode automatically adds a quantity of 1 to the order without manual intervention. Displays a toaster message saying “No product found with this barcode number” if no matching product is found. If the same product is scanned multiple times, its quantity is incremented on the existing order line. All scanned products are sorted and displayed on the first page for easy access.
1 parent fbf9ee9 commit 8229c46

File tree

4 files changed

+180
-0
lines changed

4 files changed

+180
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "SO/PO Barcode Scanning",
3+
"version": "1.0",
4+
"category": "Sales",
5+
"summary": "Adds products to SO from catalog via barcode scanning.",
6+
"author": "Kalpan Desai",
7+
"depends": ["sale_management", "web", "product", "purchase", "barcodes"],
8+
"license": "LGPL-3",
9+
"assets": {
10+
"web.assets_backend": [
11+
"sales_purchase_barcode/static/src/**/*"
12+
]
13+
},
14+
"installable": True,
15+
"application": True,
16+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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, useBus } from "@web/core/utils/hooks";
5+
6+
patch(ProductCatalogKanbanController.prototype, {
7+
/**
8+
* @override
9+
*/
10+
setup() {
11+
super.setup();
12+
this.orm = useService("orm");
13+
this.notification = useService("notification");
14+
this.barcodeService = useService('barcode');
15+
useBus(this.barcodeService.bus, 'barcode_scanned', (ev) => this._processBarcode(ev.detail.barcode));
16+
},
17+
18+
/**
19+
* Processes the scanned barcode to find the corresponding product and update the order.
20+
*
21+
* @param {string} scannedBarcode The barcode string to process.
22+
*/
23+
async _processBarcode(scannedBarcode) {
24+
// An order must be selected to add products.
25+
if (!this.orderId) {
26+
this.notification.add("Please select an order first.", { type: "warning" });
27+
return;
28+
}
29+
30+
try {
31+
// Search for a product with the scanned barcode.
32+
const products = await this.orm.searchRead(
33+
"product.product",
34+
[["barcode", "=", scannedBarcode]],
35+
["id", "name"]
36+
);
37+
38+
if (!products.length) {
39+
this.notification.add("No product found for this barcode.", { type: "warning" });
40+
return;
41+
}
42+
43+
const product = products[0];
44+
45+
let orderLineModel, quantityField;
46+
// Determine the correct model and field names based on the order type.
47+
if (this.orderResModel === "sale.order") {
48+
orderLineModel = "sale.order.line";
49+
quantityField = "product_uom_qty";
50+
} else if (this.orderResModel === "purchase.order") {
51+
orderLineModel = "purchase.order.line";
52+
quantityField = "product_qty";
53+
} else {
54+
// Log an error if the order model is not supported.
55+
console.error("Unsupported order model for barcode scanning:", this.orderResModel);
56+
this.notification.add("Barcode scanning is not supported for this document type.", { type: "danger" });
57+
return;
58+
}
59+
60+
// Check if there is an existing order line for this product.
61+
const existingOrderLines = await this.orm.searchRead(
62+
orderLineModel,
63+
[["order_id", "=", this.orderId], ["product_id", "=", product.id]],
64+
["id", quantityField]
65+
);
66+
67+
// If a line exists, increment its quantity; otherwise, set quantity to 1.
68+
const updatedQuantity = existingOrderLines.length ? existingOrderLines[0][quantityField] + 1 : 1;
69+
70+
// Call the backend to create or update the order line.
71+
await rpc("/product/catalog/update_order_line_info", {
72+
res_model: this.orderResModel,
73+
order_id: this.orderId,
74+
product_id: product.id,
75+
quantity: updatedQuantity,
76+
});
77+
78+
// Notify the user of the successful addition.
79+
this.notification.add(
80+
`Added ${product.name} (Qty: ${updatedQuantity})`,
81+
{ type: "success" }
82+
);
83+
84+
// Reload the view to show the updated order line information.
85+
this.model.load();
86+
87+
} catch (error) {
88+
console.error("Error processing barcode scan:", error);
89+
this.notification.add("An error occurred while processing the barcode.", { type: "danger" });
90+
}
91+
},
92+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/** @odoo-module **/
2+
3+
import { rpc } from "@web/core/network/rpc";
4+
import { ProductCatalogKanbanModel } from "@product/product_catalog/kanban_model";
5+
import { getFieldsSpec } from "@web/model/relational_model/utils";
6+
7+
export class BarcodeProductCatalogKanbanModel extends ProductCatalogKanbanModel {
8+
9+
async _loadUngroupedList(config) {
10+
const allProducts = await this.orm.search(config.resModel, config.domain);
11+
12+
if (!allProducts.length) {
13+
return { records: [], length: 0 };
14+
}
15+
16+
let orderLines = {};
17+
const scanned = [], unscanned = [];
18+
19+
if (config.context.order_id && config.context.product_catalog_order_model) {
20+
orderLines = await rpc("/product/catalog/order_lines_info", {
21+
order_id: config.context.order_id,
22+
product_ids: allProducts,
23+
res_model: config.context.product_catalog_order_model,
24+
});
25+
26+
for (const id of allProducts) {
27+
const qty = (orderLines[id]?.quantity) || 0;
28+
if (qty > 0) scanned.push(id);
29+
else unscanned.push(id);
30+
}
31+
32+
33+
scanned.sort((a, b) =>
34+
(orderLines[b]?.quantity || 0) - (orderLines[a]?.quantity || 0)
35+
);
36+
} else {
37+
unscanned.push(...allProducts);
38+
}
39+
40+
const sortedProductIds = [...scanned, ...unscanned];
41+
const paginatedProductIds = sortedProductIds.slice(config.offset, config.offset + config.limit);
42+
43+
const kwargs = {
44+
specification: getFieldsSpec(config.activeFields, config.fields, config.context),
45+
};
46+
47+
const result = await this.orm.webSearchRead(config.resModel, [["id", "in", paginatedProductIds]], kwargs);
48+
49+
result.records.sort((a, b) => {
50+
const qtyA = orderLines[a.id]?.quantity || 0;
51+
const qtyB = orderLines[b.id]?.quantity || 0;
52+
return qtyB - qtyA || a.id - b.id;
53+
});
54+
55+
return {
56+
length: allProducts.length,
57+
records: result.records,
58+
};
59+
}
60+
61+
}
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 "./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)