diff --git a/pos_lot_barcode/README.rst b/pos_lot_barcode/README.rst new file mode 100644 index 0000000000..eff3e97d67 --- /dev/null +++ b/pos_lot_barcode/README.rst @@ -0,0 +1,79 @@ +=============== +POS Lot Barcode +=============== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github + :target: https://github.com/OCA/pos/tree/16.0/pos_lot_barcode + :alt: OCA/pos +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pos-16-0/pos-16-0-pos_lot_barcode + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/184/16.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the point of sale functionalities to allow scanning a lot/serial number +using a barcode reader instead of having to enter it manually. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Akim Juillerat + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_lot_barcode/__init__.py b/pos_lot_barcode/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pos_lot_barcode/__manifest__.py b/pos_lot_barcode/__manifest__.py new file mode 100644 index 0000000000..c2b91ef112 --- /dev/null +++ b/pos_lot_barcode/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +{ + "name": "POS Lot Barcode", + "summary": "Scan barcode to enter lot/serial numbers", + "version": "16.0.1.0.0", + "development_status": "Alpha", + "category": "Sales/Point of Sale", + "website": "https://github.com/OCA/pos", + "author": "Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "point_of_sale", + ], + "assets": { + "point_of_sale.assets": [ + "pos_lot_barcode/static/src/js/**/*.js", + "pos_lot_barcode/static/src/xml/**/*.xml", + ], + }, +} diff --git a/pos_lot_barcode/i18n/pos_lot_barcode.pot b/pos_lot_barcode/i18n/pos_lot_barcode.pot new file mode 100644 index 0000000000..0fcd87bdf9 --- /dev/null +++ b/pos_lot_barcode/i18n/pos_lot_barcode.pot @@ -0,0 +1,84 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_lot_barcode +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/js/Popups/ErrorMultiLotBarcodePopup.js:0 +#, python-format +msgid "Cancel" +msgstr "" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/js/Popups/ErrorMultiLotBarcodePopup.js:0 +#, python-format +msgid "Error" +msgstr "" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/js/Screens/ProductScreen.js:0 +#, python-format +msgid "" +"Lot is not loaded. Tried loading the lot from the server but there is a " +"network error." +msgstr "" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/js/Popups/EditListPopup.js:0 +#, python-format +msgid "Lot/Serial Number(s) Required" +msgstr "" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/xml/Popups/ErrorMultiLotBarcodePopup.xml:0 +#, python-format +msgid "Multiple Products Matching Barcode" +msgstr "" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/js/Screens/ProductScreen.js:0 +#, python-format +msgid "Network Error" +msgstr "" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/js/Popups/ErrorMultiLotBarcodePopup.js:0 +#: code:addons/pos_lot_barcode/static/src/xml/Popups/ErrorMultiLotBarcodePopup.xml:0 +#, python-format +msgid "Ok" +msgstr "" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/js/Popups/ErrorMultiLotBarcodePopup.js:0 +#, python-format +msgid "" +"The Point of Sale can not process the scanned barcode, as it matches " +"multiple products:" +msgstr "" + +#. module: pos_lot_barcode +#. openerp-web +#: code:addons/pos_lot_barcode/static/src/js/Screens/ProductScreen.js:0 +#, python-format +msgid "" +"The Point of Sale could not find any product, lot, client, employee or " +"action associated with the scanned barcode." +msgstr "" diff --git a/pos_lot_barcode/readme/CONFIGURATION.rst b/pos_lot_barcode/readme/CONFIGURATION.rst new file mode 100644 index 0000000000..f5c535087e --- /dev/null +++ b/pos_lot_barcode/readme/CONFIGURATION.rst @@ -0,0 +1,2 @@ +In the Point of Sale configuration, you should make sure you have a barcode rule of type 'Lot' + defined in the Barcode Nomenclature. diff --git a/pos_lot_barcode/readme/CONTRIBUTORS.rst b/pos_lot_barcode/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..e31e2f0c4f --- /dev/null +++ b/pos_lot_barcode/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Akim Juillerat diff --git a/pos_lot_barcode/readme/DESCRIPTION.rst b/pos_lot_barcode/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..22aab3c70f --- /dev/null +++ b/pos_lot_barcode/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module extends the point of sale functionalities to allow scanning a lot/serial number +using a barcode reader instead of having to enter it manually. diff --git a/pos_lot_barcode/static/description/icon.png b/pos_lot_barcode/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/pos_lot_barcode/static/description/icon.png differ diff --git a/pos_lot_barcode/static/description/index.html b/pos_lot_barcode/static/description/index.html new file mode 100644 index 0000000000..fbd33fe7f2 --- /dev/null +++ b/pos_lot_barcode/static/description/index.html @@ -0,0 +1,426 @@ + + + + + + +POS Lot Barcode + + + +
+

POS Lot Barcode

+ + +

Alpha License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runbot

+

This module extends the point of sale functionalities to allow scanning a lot/serial number +using a barcode reader instead of having to enter it manually.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_lot_barcode/static/src/js/Popups/EditListPopup.js b/pos_lot_barcode/static/src/js/Popups/EditListPopup.js new file mode 100644 index 0000000000..4dfd8d4e4f --- /dev/null +++ b/pos_lot_barcode/static/src/js/Popups/EditListPopup.js @@ -0,0 +1,30 @@ +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ +odoo.define("pos_lot_barcode.EditListPopup", function (require) { + "use strict"; + + const EditListPopup = require("point_of_sale.EditListPopup"); + const Registries = require("point_of_sale.Registries"); + const {useBarcodeReader} = require("point_of_sale.custom_hooks"); + + const PosLotBarcodeEditListPopup = (EditListPopup) => + class extends EditListPopup { + setup() { + super.setup(); + useBarcodeReader({ + lot: this._lotScanned, + }); + } + _lotScanned(code) { + // Check we are on lot/SN selection popup + if (this.props.title === this.env._t("Lot/Serial Number(s) Required")) { + this.state.array.push({text: code.code, _id: this._nextId()}); + this.confirm(); + } + } + }; + Registries.Component.extend(EditListPopup, PosLotBarcodeEditListPopup); + return EditListPopup; +}); diff --git a/pos_lot_barcode/static/src/js/Popups/ErrorMultiLotBarcodePopup.js b/pos_lot_barcode/static/src/js/Popups/ErrorMultiLotBarcodePopup.js new file mode 100644 index 0000000000..3b737db0c1 --- /dev/null +++ b/pos_lot_barcode/static/src/js/Popups/ErrorMultiLotBarcodePopup.js @@ -0,0 +1,31 @@ +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ +odoo.define("pos_lot_barcode.ErrorMultiLotBarcodePopup", function (require) { + "use strict"; + + const ErrorBarcodePopup = require("point_of_sale.ErrorBarcodePopup"); + const Registries = require("point_of_sale.Registries"); + const {_lt} = require("@web/core/l10n/translation"); + + class ErrorMultiLotBarcodePopup extends ErrorBarcodePopup { + get translatedMessage() { + return this.env._t(this.props.message); + } + } + ErrorMultiLotBarcodePopup.template = "ErrorMultiLotBarcodePopup"; + ErrorMultiLotBarcodePopup.defaultProps = { + confirmText: _lt("Ok"), + cancelText: _lt("Cancel"), + title: _lt("Error"), + body: "", + message: _lt( + "The Point of Sale can not process the scanned barcode, as it matches multiple products:" + ), + }; + + Registries.Component.add(ErrorMultiLotBarcodePopup); + + return ErrorMultiLotBarcodePopup; +}); diff --git a/pos_lot_barcode/static/src/js/Screens/ProductScreen.js b/pos_lot_barcode/static/src/js/Screens/ProductScreen.js new file mode 100644 index 0000000000..8baefa9988 --- /dev/null +++ b/pos_lot_barcode/static/src/js/Screens/ProductScreen.js @@ -0,0 +1,207 @@ +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ +odoo.define("pos_lot_barcode.ProductScreen", function (require) { + "use strict"; + + const ProductScreen = require("point_of_sale.ProductScreen"); + const Registries = require("point_of_sale.Registries"); + const {useBarcodeReader} = require("point_of_sale.custom_hooks"); + const {isConnectionError} = require("point_of_sale.utils"); + const {_lt} = require("@web/core/l10n/translation"); + + const PosLotBarcodeProductScreen = (ProductScreen) => + class extends ProductScreen { + setup() { + super.setup(); + this.scan_lots_active = true; + useBarcodeReader({ + lot: this._barcodeLotAction, + }); + } + async _barcodeLotAction(code) { + // Do not do anything if lot scanning is not active + if (!this.scan_lots_active) return; + // Get the product according to lot barcode + const product = await this._getProductByLotBarcode(code); + // If we didn't get a product it must display a popup + if (!product) return; + // Get possible options not linked to lot selection + const options = await this._getAddLotProductOptions(product, code); + // Do not proceed on adding the product when no options is returned. + // This is consistent with _clickProduct. + if (!options) return; + this.currentOrder.add_product(product, options); + } + async _getProductByLotBarcode(base_code) { + const foundLotIds = await this._searchLotProduct(base_code.code); + if (foundLotIds.length === 1) { + let product = this.env.pos.db.get_product_by_id( + foundLotIds[0].product_id[0] + ); + if (!product) { + // If product is not loaded in POS, load it + await this.env.pos._addProducts(foundLotIds[0].product_id[0]); + // Assume that the result is unique. + product = this.env.pos.db.get_product_by_id( + foundLotIds[0].product_id[0] + ); + } + return product; + } else if (foundLotIds.length > 1) { + // If we found more than a single lot in backend, raise error + this._barcodeMultiLotErrorAction( + base_code, + _.map(foundLotIds, (lot) => lot.product_id[1]) + ); + return false; + } + this._barcodeLotErrorAction(base_code); + return false; + } + async _searchLotProduct(code) { + let foundLotIds = []; + try { + foundLotIds = await this.rpc({ + model: "stock.lot", + method: "search_read", + domain: [["name", "=", code]], + fields: ["id", "product_id"], + context: this.env.session.user_context, + }); + } catch (error) { + if (isConnectionError(error)) { + return this.showPopup("OfflineErrorPopup", { + title: this.env._t("Network Error"), + body: this.env._t( + "Lot is not loaded. Tried loading the lot from the server but there is a network error." + ), + }); + } + throw error; + } + return foundLotIds; + } + async _getAddProductOptions() { + // Deactivate lot scanning if lot selection popup must be opened + this.scan_lots_active = false; + const options = await super._getAddProductOptions(...arguments); + this.scan_lots_active = true; + return options; + } + async _getAddLotProductOptions(product, base_code) { + // Copy and reimplement _getAddProductOptions with lot taken from base_code parameter + let price_extra = 0.0; + let weight = 0.0; + let description = ""; + let draftPackLotLines = []; + let packLotLinesToEdit = []; + let existingPackLotLines = []; + let newPackLotLines = []; + // Keep opening the product configurator if needed (copied from _getAddProductOptions) + if ( + this.env.pos.config.product_configurator && + _.some( + product.attribute_line_ids, + (id) => id in this.env.pos.attributes_by_ptal_id + ) + ) { + const attributes = _.map( + product.attribute_line_ids, + (id) => this.env.pos.attributes_by_ptal_id[id] + ).filter((attr) => attr !== undefined); + const {confirmed, payload} = await this.showPopup( + "ProductConfiguratorPopup", + { + product: product, + attributes: attributes, + } + ); + + if (confirmed) { + description = payload.selected_attributes.join(", "); + price_extra += payload.price_extra; + } else { + return; + } + } + // Set lot information, check still needed for picking_type (copied from _getAddProductOptions) + if ( + this.env.pos.picking_type.use_create_lots || + this.env.pos.picking_type.use_existing_lots + ) { + // Build packLotLinesToEdit (copied from _getAddProductOptions) + const isAllowOnlyOneLot = product.isAllowOnlyOneLot(); + if (isAllowOnlyOneLot) { + packLotLinesToEdit = []; + } else { + const orderline = this.currentOrder + .get_orderlines() + .filter((line) => !line.get_discount()) + .find((line) => line.product.id === product.id); + if (orderline) { + packLotLinesToEdit = orderline.getPackLotLinesToEdit(); + } else { + packLotLinesToEdit = []; + } + } + // Remove new Id from packLotLinesToEdit if we have any + existingPackLotLines = {}; + if (packLotLinesToEdit.length) { + existingPackLotLines = Object.fromEntries( + packLotLinesToEdit + .filter((item) => item.id) + .map((item) => [item.id, item.text]) + ); + } + // Define new lot using scanned barcode + newPackLotLines = [{lot_name: base_code.code}]; + draftPackLotLines = { + modifiedPackLotLines: existingPackLotLines, + newPackLotLines, + }; + } + + // Take the weight if necessary. (copied from _getAddProductOptions) + if (product.to_weight && this.env.pos.config.iface_electronic_scale) { + // Show the ScaleScreen to weigh the product. + if (this.isScaleAvailable) { + const {confirmed, payload} = await this.showTempScreen( + "ScaleScreen", + { + product, + } + ); + if (confirmed) { + weight = payload.weight; + } else { + // Do not add the product; + return; + } + } else { + await this._onScaleNotAvailable(); + } + } + return {draftPackLotLines, quantity: weight, description, price_extra}; + } + _barcodeLotErrorAction(code) { + return this.showPopup("ErrorBarcodePopup", { + code: this._codeRepr(code), + message: _lt( + "The Point of Sale could not find any product, lot, client, employee or action associated with the scanned barcode." + ), + }); + } + _barcodeMultiLotErrorAction(code, product_names) { + return this.showPopup("ErrorMultiLotBarcodePopup", { + code: this._codeRepr(code), + products: product_names, + }); + } + }; + + Registries.Component.extend(ProductScreen, PosLotBarcodeProductScreen); + + return ProductScreen; +}); diff --git a/pos_lot_barcode/static/src/xml/Popups/ErrorMultiLotBarcodePopup.xml b/pos_lot_barcode/static/src/xml/Popups/ErrorMultiLotBarcodePopup.xml new file mode 100644 index 0000000000..b334cfdd85 --- /dev/null +++ b/pos_lot_barcode/static/src/xml/Popups/ErrorMultiLotBarcodePopup.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/setup/pos_lot_barcode/odoo/addons/pos_lot_barcode b/setup/pos_lot_barcode/odoo/addons/pos_lot_barcode new file mode 120000 index 0000000000..54240e2272 --- /dev/null +++ b/setup/pos_lot_barcode/odoo/addons/pos_lot_barcode @@ -0,0 +1 @@ +../../../../pos_lot_barcode \ No newline at end of file diff --git a/setup/pos_lot_barcode/setup.py b/setup/pos_lot_barcode/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/pos_lot_barcode/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)