diff --git a/barcode_scan/__manifest__.py b/barcode_scan/__manifest__.py new file mode 100644 index 00000000000..6298ee5a307 --- /dev/null +++ b/barcode_scan/__manifest__.py @@ -0,0 +1,14 @@ +{ + 'name': "Barcode Scanning In SO/PO", + 'version': '0.1', + 'summary': 'Adds barcode scanning capabilities to product catalog', + 'depends': ['sale_management', 'stock_barcode', 'purchase'], + 'application': True, + 'installable': True, + "assets": { + 'web.assets_backend': [ + 'barcode_scan/static/src/**/*', + ], + }, + 'license': 'AGPL-3' +} diff --git a/barcode_scan/static/src/product_catalog/kanban_controller.js b/barcode_scan/static/src/product_catalog/kanban_controller.js new file mode 100644 index 00000000000..33a222e5f5e --- /dev/null +++ b/barcode_scan/static/src/product_catalog/kanban_controller.js @@ -0,0 +1,55 @@ +import { patch } from "@web/core/utils/patch"; +import { rpc } from "@web/core/network/rpc"; +import { useService, useBus } from "@web/core/utils/hooks"; +import { ProductCatalogKanbanController } from "@product/product_catalog/kanban_controller"; + +patch(ProductCatalogKanbanController.prototype, { + setup(){ + super.setup(); + this.orm = useService("orm"); + this.barcodeService = useService("barcode"); + useBus(this.barcodeService.bus, "barcode_scanned", (ev) => this.onBarcodeScannedHandler(ev.detail.barcode)); + this.resModel = this.props.context.product_catalog_order_model; + this.notification = useService("notification"); + }, + + async onBarcodeScannedHandler(barcode) { + const [product] = await this.orm.searchRead( + "product.product", + [["barcode", "=", barcode]], + ["id"], + { limit: 1 } + ); + if (!product) { + this.notification.add(("No product found with barcode: ") + barcode, { type: "danger" }); + return; + } + + const quantityFieldMap = { + "sale.order": "product_uom_qty", + "purchase.order": "product_qty", + }; + const productQuantity = quantityFieldMap[this.resModel]; + if (!productQuantity) { + this.notification.add("Unsupported model: " + this.resModel, { type: "danger" }); + } + + const orderLines = await this.orm.searchRead( + this.props.context.active_model, + [["order_id", "=", this.props.context.order_id], + ["product_id", "=", product.id]], + ["id", productQuantity, "product_id"] + ); + + const existingQty = orderLines[0]?.[productQuantity] || 0; + const finallyQuantity = existingQty + 1; + await rpc("/product/catalog/update_order_line_info", { + order_id: this.props.context.order_id, + product_id: product.id, + quantity: finallyQuantity, + res_model: this.resModel, + }); + + this.model.load(); + } +}); diff --git a/barcode_scan/static/src/product_catalog/kanban_model.js b/barcode_scan/static/src/product_catalog/kanban_model.js new file mode 100644 index 00000000000..407034787a9 --- /dev/null +++ b/barcode_scan/static/src/product_catalog/kanban_model.js @@ -0,0 +1,36 @@ +import { ProductCatalogKanbanModel } from "@product/product_catalog/kanban_model"; +import { getFieldsSpec } from "@web/model/relational_model/utils"; +import { rpc } from "@web/core/network/rpc"; + +export class CustomProductCatalogKanbanModel extends ProductCatalogKanbanModel { + async _loadUngroupedList(config) { + const ProductIds = await this.orm.search(config.resModel, config.domain); + if (!ProductIds.length) { + return { records: [], length: 0 }; + } + + let orderLinesInfo = {}; + if (config.context.order_id && config.context.product_catalog_order_model) { + orderLinesInfo = await rpc("/product/catalog/order_lines_info", { + order_id: config.context.order_id, + product_ids: ProductIds, + res_model: config.context.product_catalog_order_model, + }); + ProductIds.sort((a, b) => (orderLinesInfo[b].quantity || 0) - (orderLinesInfo[a].quantity || 0)); + } + + const catalogPage = ProductIds.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", catalogPage]], kwargs); + + result.records.sort((a, b) => (orderLinesInfo[b.id].quantity || 0) - (orderLinesInfo[a.id].quantity || 0)); + return { + length: ProductIds.length, + records: result.records, + }; + } +} diff --git a/barcode_scan/static/src/product_catalog/kanban_view.js b/barcode_scan/static/src/product_catalog/kanban_view.js new file mode 100644 index 00000000000..274a7801a0b --- /dev/null +++ b/barcode_scan/static/src/product_catalog/kanban_view.js @@ -0,0 +1,11 @@ +import { registry } from "@web/core/registry"; +import { productCatalogKanbanView } from "@product/product_catalog/kanban_view"; +import { CustomProductCatalogKanbanModel } from "./kanban_model"; + +export const customProductCatalogKanbanView = { + ...productCatalogKanbanView, + Model: CustomProductCatalogKanbanModel, +}; + +registry.category("views").remove("product_kanban_catalog"); +registry.category("views").add("product_kanban_catalog", customProductCatalogKanbanView);