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