From 1fe6e52314cbcb2fa81a1a94d276f4533fa4cec5 Mon Sep 17 00:00:00 2001 From: David Vidal Date: Wed, 14 Mar 2018 15:14:16 +0100 Subject: [PATCH 1/4] [10.0][ADD] pos_lot_selection: New module (#256) --- pos_lot_selection/README.rst | 82 ++++++++++++++++++ pos_lot_selection/__init__.py | 1 + pos_lot_selection/__manifest__.py | 24 +++++ pos_lot_selection/i18n/es.po | 28 ++++++ pos_lot_selection/static/description/icon.png | Bin 0 -> 4028 bytes pos_lot_selection/static/src/css/pos.css | 29 +++++++ pos_lot_selection/static/src/js/chrome.js | 62 +++++++++++++ pos_lot_selection/static/src/js/models.js | 56 ++++++++++++ pos_lot_selection/static/src/xml/pos.xml | 27 ++++++ pos_lot_selection/templates/assets.xml | 15 ++++ 10 files changed, 324 insertions(+) create mode 100644 pos_lot_selection/README.rst create mode 100644 pos_lot_selection/__init__.py create mode 100644 pos_lot_selection/__manifest__.py create mode 100644 pos_lot_selection/i18n/es.po create mode 100644 pos_lot_selection/static/description/icon.png create mode 100644 pos_lot_selection/static/src/css/pos.css create mode 100644 pos_lot_selection/static/src/js/chrome.js create mode 100644 pos_lot_selection/static/src/js/models.js create mode 100644 pos_lot_selection/static/src/xml/pos.xml create mode 100644 pos_lot_selection/templates/assets.xml diff --git a/pos_lot_selection/README.rst b/pos_lot_selection/README.rst new file mode 100644 index 00000000..ef8a32c8 --- /dev/null +++ b/pos_lot_selection/README.rst @@ -0,0 +1,82 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +================= +POS Lot Selection +================= + +This module allows to pick between existing lots in POS frontend. + +Configuration +============= + +1. Go to *Inventory > Settings* and set the option *Track lots or serial + numbers* +2. Chose a product that is stockable, go to its *Inventory* + tab, and set *Tracking* to *By Lots*. +3. Go to its *Sales* tab and set it as *Available in the Point of Sale*. +4. Click on *Update Qty On Hand*, chose the same location configured in the + POS you want the lot available in; write a quantity; unfold the *Lot/Serial + Number* field and pick create one if none is available yet. +5. Create a new lot with the serial number of your choice. + +Usage +===== + +* Open a POS Session. +* Choose a product with required lot. +* There is a new select field with the lots available for that product. +* If lot quantity can't cover the order line demand it won't be available to + pick. +* If there were many lots to fill the value of one of them can be propagated to + the rest with the new clone control on the right of the lot field. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/184/10.0 + +Known issues / Roadmap +====================== + +* Lot selection is only available when the POS is online. In offline mode the + select isn't loaded and the lot has to be manually set. + +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 smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* David + * (https://www.tecnativa.com) + +Do not contact contributors directly about support or help with technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/pos_lot_selection/__init__.py b/pos_lot_selection/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/pos_lot_selection/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pos_lot_selection/__manifest__.py b/pos_lot_selection/__manifest__.py new file mode 100644 index 00000000..c03d0b75 --- /dev/null +++ b/pos_lot_selection/__manifest__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'POS Lot Selection', + 'version': '10.0.1.0.0', + 'category': 'Point of Sale', + 'author': 'Tecnativa,' + 'Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/pos', + 'license': 'AGPL-3', + 'depends': [ + 'point_of_sale', + ], + 'data': [ + 'templates/assets.xml', + ], + 'qweb': [ + 'static/src/xml/pos.xml' + ], + 'application': False, + 'installable': True, +} diff --git a/pos_lot_selection/i18n/es.po b/pos_lot_selection/i18n/es.po new file mode 100644 index 00000000..d107fc5f --- /dev/null +++ b/pos_lot_selection/i18n/es.po @@ -0,0 +1,28 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_lot_selection +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-13 09:16+0000\n" +"PO-Revision-Date: 2018-03-13 09:16+0000\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_selection +#: model:ir.model,name:pos_lot_selection.model_stock_quant +msgid "Quants" +msgstr "Quants" + +#. module: pos_lot_selection +#. openerp-web +#: code:addons/pos_lot_selection/static/src/xml/pos.xml:12 +#, python-format +msgid "Select a Serial/Lot Number" +msgstr "Escoja un nº de Serie / de Lote" diff --git a/pos_lot_selection/static/description/icon.png b/pos_lot_selection/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ca2e8601cc3a3295f625601d77fb7ae70481b957 GIT binary patch literal 4028 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 literal 0 HcmV?d00001 diff --git a/pos_lot_selection/static/src/css/pos.css b/pos_lot_selection/static/src/css/pos.css new file mode 100644 index 00000000..de079d71 --- /dev/null +++ b/pos_lot_selection/static/src/css/pos.css @@ -0,0 +1,29 @@ +/* Copyright 2018 Tecnativa - David Vidal + License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +*/ + +.pos .popup .packlot-select { + border-bottom: solid 1px rgba(60,60,60,0.1); + margin-top: 3px; +} + +.pos .popup select { + text-align: left; + padding: 10px; + display: inline-block; + border-radius: 3px; + border: solid 1px #cecbcb; + margin-bottom: 4px; + background: white; + font-family: "Lato","Lucida Grande", Helvetica, Verdana, Arial; + color: #444; + width: 80%; + min-height: 44px; + font-size: 20px; + box-sizing: border-box; +} + +.pos .popup select:focus, .pos .popup select:active { + outline: none; + box-shadow: 0px 0px 0px 3px #6EC89B; +} diff --git a/pos_lot_selection/static/src/js/chrome.js b/pos_lot_selection/static/src/js/chrome.js new file mode 100644 index 00000000..addbe32b --- /dev/null +++ b/pos_lot_selection/static/src/js/chrome.js @@ -0,0 +1,62 @@ +/* Copyright 2018 Tecnativa - David Vidal + License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */ + +odoo.define("pos_lot_selection.chrome", function (require) { + "use strict"; + + var chrome = require("point_of_sale.chrome"); + + chrome.Chrome.include({ + build_widgets: function () { + var res = this._super.apply(this, arguments); + var packlotline = this.gui.popup_instances.packlotline; + // Add events over instanced popup + var events = { + "change .packlot-line-select": "lot_to_input", + "click .lot-clone": "clone_input", + }; + packlotline.events = Object.assign( + packlotline.events, events + ); + // Add methods over instanced popup + // Write the value in the corresponding input + packlotline.lot_to_input = function (event) { + var $select = this.$("select.packlot-line-select"); + var $option = this.$("select.packlot-line-select option"); + var $input = this.$el.find("input[cid='" + this.active_cid + "']"); + if ($input.length) { + $input[0].value = $select[0].value; + $input.focus(); + } + $option.prop('selected', function () { + return this.defaultSelected; + }); + }; + // Tracks the last selected input + packlotline.lose_input_focus = function (event) { + var $input = $(event.target), + cid = $input.attr('cid'); + this.active_cid = cid; + var lot_model = this.options.pack_lot_lines.get({cid: cid}); + lot_model.set_lot_name($input.val()); + }; + // Clones content of input to all the others + packlotline.clone_input = function (event) { + var $input = $(event.target).prev().prev(), + cid = $input.attr('cid'); + var $clone_input = this.$el.find("input"); + if ($clone_input.length > 1) { + for (var i = 0; i < $clone_input.length; i++) { + if ($clone_input[i].getAttribute('cid') != cid) { + $clone_input[i].value = $input.val(); + $clone_input[i].blur(); + } + } + } + } + this.gui.popup_instances.packlotline = packlotline; + return res; + }, + }); + +}); diff --git a/pos_lot_selection/static/src/js/models.js b/pos_lot_selection/static/src/js/models.js new file mode 100644 index 00000000..a7d72222 --- /dev/null +++ b/pos_lot_selection/static/src/js/models.js @@ -0,0 +1,56 @@ +/* Copyright 2018 Tecnativa - David Vidal + License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */ + +odoo.define("pos_lot_selection.models", function (require) { + "use strict"; + + var models = require("point_of_sale.models"); + var Model = require("web.DataModel"); + var session = require("web.session"); + + models.PosModel = models.PosModel.extend({ + get_lot: function(product, location_id) { + var done = new $.Deferred(); + session.rpc("/web/dataset/search_read", { + "model": "stock.quant", + "domain": [ + ["location_id", "=", location_id], + ["product_id", "=", product], + ["lot_id", "!=", false]], + }, {'async': false}).then(function (result) { + var product_lot = []; + if (result.length) { + for (var i = 0; i < result.length; i++) { + product_lot.push({ + 'lot_name': result.records[i].lot_id[1], + 'qty': result.records[i].qty, + }); + } + } + done.resolve(product_lot); + }); + return done; + }, + }); + + var _orderline_super = models.Orderline.prototype; + models.Orderline = models.Orderline.extend({ + compute_lot_lines: function(){ + var done = new $.Deferred(); + var compute_lot_lines = _orderline_super.compute_lot_lines.apply(this, arguments); + this.pos.get_lot(this.product.id, this.pos.config.stock_location_id[0]) + .then(function (product_lot) { + var lot_name = []; + for (var i = 0; i < product_lot.length; i++) { + if (product_lot[i].qty >= compute_lot_lines.order_line.quantity) { + lot_name.push(product_lot[i].lot_name); + } + } + compute_lot_lines.lot_name = lot_name; + done.resolve(compute_lot_lines); + }); + return compute_lot_lines; + }, + }); + +}); diff --git a/pos_lot_selection/static/src/xml/pos.xml b/pos_lot_selection/static/src/xml/pos.xml new file mode 100644 index 00000000..13e1ac12 --- /dev/null +++ b/pos_lot_selection/static/src/xml/pos.xml @@ -0,0 +1,27 @@ + + + + + + + +
+ + + +
+
+ + + +
+ +
diff --git a/pos_lot_selection/templates/assets.xml b/pos_lot_selection/templates/assets.xml new file mode 100644 index 00000000..e5552f52 --- /dev/null +++ b/pos_lot_selection/templates/assets.xml @@ -0,0 +1,15 @@ + + + + + +