From 8229c466bbb469d77777aaae36b24321a63de5c9 Mon Sep 17 00:00:00 2001 From: kdes-odoo Date: Tue, 29 Jul 2025 18:22:48 +0530 Subject: [PATCH] [ADD] sales_purchase_barcode: implement barcode scanning in product catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- sales_purchase_barcode/__manifest__.py | 16 ++++ .../static/src/barcodescanner_controller.js | 92 +++++++++++++++++++ .../static/src/kanban_model.js | 61 ++++++++++++ .../static/src/kanban_view.js | 11 +++ 4 files changed, 180 insertions(+) create mode 100644 sales_purchase_barcode/__manifest__.py create mode 100644 sales_purchase_barcode/static/src/barcodescanner_controller.js create mode 100644 sales_purchase_barcode/static/src/kanban_model.js create mode 100644 sales_purchase_barcode/static/src/kanban_view.js diff --git a/sales_purchase_barcode/__manifest__.py b/sales_purchase_barcode/__manifest__.py new file mode 100644 index 00000000000..36423271047 --- /dev/null +++ b/sales_purchase_barcode/__manifest__.py @@ -0,0 +1,16 @@ +{ + "name": "SO/PO Barcode Scanning", + "version": "1.0", + "category": "Sales", + "summary": "Adds products to SO from catalog via barcode scanning.", + "author": "Kalpan Desai", + "depends": ["sale_management", "web", "product", "purchase", "barcodes"], + "license": "LGPL-3", + "assets": { + "web.assets_backend": [ + "sales_purchase_barcode/static/src/**/*" + ] + }, + "installable": True, + "application": True, +} diff --git a/sales_purchase_barcode/static/src/barcodescanner_controller.js b/sales_purchase_barcode/static/src/barcodescanner_controller.js new file mode 100644 index 00000000000..b3bf15bdba0 --- /dev/null +++ b/sales_purchase_barcode/static/src/barcodescanner_controller.js @@ -0,0 +1,92 @@ +import { patch } from "@web/core/utils/patch"; +import { ProductCatalogKanbanController } from "@product/product_catalog/kanban_controller"; +import { rpc } from "@web/core/network/rpc"; +import { useService, useBus } from "@web/core/utils/hooks"; + +patch(ProductCatalogKanbanController.prototype, { + /** + * @override + */ + setup() { + super.setup(); + this.orm = useService("orm"); + this.notification = useService("notification"); + this.barcodeService = useService('barcode'); + useBus(this.barcodeService.bus, 'barcode_scanned', (ev) => this._processBarcode(ev.detail.barcode)); + }, + + /** + * Processes the scanned barcode to find the corresponding product and update the order. + * + * @param {string} scannedBarcode The barcode string to process. + */ + async _processBarcode(scannedBarcode) { + // An order must be selected to add products. + if (!this.orderId) { + this.notification.add("Please select an order first.", { type: "warning" }); + return; + } + + try { + // Search for a product with the scanned barcode. + const products = await this.orm.searchRead( + "product.product", + [["barcode", "=", scannedBarcode]], + ["id", "name"] + ); + + if (!products.length) { + this.notification.add("No product found for this barcode.", { type: "warning" }); + return; + } + + const product = products[0]; + + let orderLineModel, quantityField; + // Determine the correct model and field names based on the order type. + if (this.orderResModel === "sale.order") { + orderLineModel = "sale.order.line"; + quantityField = "product_uom_qty"; + } else if (this.orderResModel === "purchase.order") { + orderLineModel = "purchase.order.line"; + quantityField = "product_qty"; + } else { + // Log an error if the order model is not supported. + console.error("Unsupported order model for barcode scanning:", this.orderResModel); + this.notification.add("Barcode scanning is not supported for this document type.", { type: "danger" }); + return; + } + + // Check if there is an existing order line for this product. + const existingOrderLines = await this.orm.searchRead( + orderLineModel, + [["order_id", "=", this.orderId], ["product_id", "=", product.id]], + ["id", quantityField] + ); + + // If a line exists, increment its quantity; otherwise, set quantity to 1. + const updatedQuantity = existingOrderLines.length ? existingOrderLines[0][quantityField] + 1 : 1; + + // Call the backend to create or update the order line. + await rpc("/product/catalog/update_order_line_info", { + res_model: this.orderResModel, + order_id: this.orderId, + product_id: product.id, + quantity: updatedQuantity, + }); + + // Notify the user of the successful addition. + this.notification.add( + `Added ${product.name} (Qty: ${updatedQuantity})`, + { type: "success" } + ); + + // Reload the view to show the updated order line information. + this.model.load(); + + } catch (error) { + console.error("Error processing barcode scan:", error); + this.notification.add("An error occurred while processing the barcode.", { type: "danger" }); + } + }, +}); diff --git a/sales_purchase_barcode/static/src/kanban_model.js b/sales_purchase_barcode/static/src/kanban_model.js new file mode 100644 index 00000000000..6f633ce8635 --- /dev/null +++ b/sales_purchase_barcode/static/src/kanban_model.js @@ -0,0 +1,61 @@ +/** @odoo-module **/ + +import { rpc } from "@web/core/network/rpc"; +import { ProductCatalogKanbanModel } from "@product/product_catalog/kanban_model"; +import { getFieldsSpec } from "@web/model/relational_model/utils"; + +export class BarcodeProductCatalogKanbanModel extends ProductCatalogKanbanModel { + + async _loadUngroupedList(config) { + const allProducts = await this.orm.search(config.resModel, config.domain); + + if (!allProducts.length) { + return { records: [], length: 0 }; + } + + let orderLines = {}; + const scanned = [], unscanned = []; + + if (config.context.order_id && config.context.product_catalog_order_model) { + orderLines = await rpc("/product/catalog/order_lines_info", { + order_id: config.context.order_id, + product_ids: allProducts, + res_model: config.context.product_catalog_order_model, + }); + + for (const id of allProducts) { + const qty = (orderLines[id]?.quantity) || 0; + if (qty > 0) scanned.push(id); + else unscanned.push(id); + } + + + scanned.sort((a, b) => + (orderLines[b]?.quantity || 0) - (orderLines[a]?.quantity || 0) + ); + } else { + unscanned.push(...allProducts); + } + + const sortedProductIds = [...scanned, ...unscanned]; + const paginatedProductIds = sortedProductIds.slice(config.offset, config.offset + config.limit); + + const kwargs = { + specification: getFieldsSpec(config.activeFields, config.fields, config.context), + }; + + const result = await this.orm.webSearchRead(config.resModel, [["id", "in", paginatedProductIds]], kwargs); + + result.records.sort((a, b) => { + const qtyA = orderLines[a.id]?.quantity || 0; + const qtyB = orderLines[b.id]?.quantity || 0; + return qtyB - qtyA || a.id - b.id; + }); + + return { + length: allProducts.length, + records: result.records, + }; + } + +} \ No newline at end of file diff --git a/sales_purchase_barcode/static/src/kanban_view.js b/sales_purchase_barcode/static/src/kanban_view.js new file mode 100644 index 00000000000..625ef68855d --- /dev/null +++ b/sales_purchase_barcode/static/src/kanban_view.js @@ -0,0 +1,11 @@ +import { registry } from "@web/core/registry"; +import { productCatalogKanbanView } from "@product/product_catalog/kanban_view"; +import { BarcodeProductCatalogKanbanModel } from "./kanban_model"; + +export const BarcodeProductCatalogKanbanView = { + ...productCatalogKanbanView, + Model: BarcodeProductCatalogKanbanModel, +}; + +registry.category("views").remove("product_kanban_catalog"); +registry.category("views").add("product_kanban_catalog", BarcodeProductCatalogKanbanView); \ No newline at end of file