diff --git a/pos_order_mgmt/README.rst b/pos_order_mgmt/README.rst new file mode 100644 index 00000000..6ad8fb7f --- /dev/null +++ b/pos_order_mgmt/README.rst @@ -0,0 +1,121 @@ +============================== +POS Frontend Orders Management +============================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |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/11.0/pos_order_mgmt + :alt: OCA/pos +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pos-11-0/pos-11-0-pos_order_mgmt + :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/11.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of the PoS frontend allowing to load +already done PoS Orders in order to be able to operate over them, being able to +reprint past tickets or return them. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +#. Go to *Point of Sale > Configuration > Point of Sale* and select one of + them. +#. Set *Load Done Orders* on if you want to be able to load past orders in that + PoS. +#. Change *Max Done Orders Quantity To Load* to your desired amount (10 by + default). Please note that the more you load, the more it will take to load + them in the session opening. You can also set it to 0 and you'll just be + able to load them from the order list screen. + +Usage +===== + +Once the PoS is loaded, you'll find a shopping trolley icon (🛒) in the top +bar that grants access to the order list screen. + +.. image:: /pos_order_mgmt/static/description/order-mgmt-icon.png + +There you can find the number of past orders loaded according to your +configuration (see Configuration) as well as the orders you checked out in +the current session: + +.. image:: /pos_order_mgmt/static/description/order-mgmt-list.png + +#. You can see their totals as well as their custumers if registered. +#. You can reprint their tickets clicking on the printer icon (⎙). +#. You can return them pressing on the arrow icon (↶). +#. You have a search input as well that lets you find past tickets by its + reference number. + +NOTE: You'll need your PoS to be online to be able to search or return a past +ticket. + +Known issues / Roadmap +====================== + +* It's possible to return the same order over and over. To avoid so, we should + load and control if there's a returned line id associated with the original + order. That would be a great improvement for future revisions. + +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 +~~~~~~~ + +* GRAP +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* David Vidal +* Sylvain LE GAL (https://twitter.com/legalsylvain) + +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_order_mgmt/__init__.py b/pos_order_mgmt/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/pos_order_mgmt/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_order_mgmt/__manifest__.py b/pos_order_mgmt/__manifest__.py new file mode 100644 index 00000000..8ee74902 --- /dev/null +++ b/pos_order_mgmt/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright 2018 GRAP - Sylvain LE GAL +# Copyright 2018 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'POS Frontend Orders Management', + 'summary': 'Manage old POS Orders from the frontend', + 'version': '11.0.1.0.0', + 'category': 'Point of Sale', + 'author': 'GRAP, ' + 'Tecnativa, ' + 'Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/pos', + 'license': 'AGPL-3', + 'depends': [ + 'pos_order_return', + ], + 'data': [ + 'views/assets.xml', + 'views/view_pos_config.xml', + ], + 'qweb': [ + 'static/src/xml/pos.xml' + ], + 'application': False, + 'installable': True, +} diff --git a/pos_order_mgmt/i18n/es.po b/pos_order_mgmt/i18n/es.po new file mode 100644 index 00000000..6e429edd --- /dev/null +++ b/pos_order_mgmt/i18n/es.po @@ -0,0 +1,149 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_order_mgmt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-10-18 11:15+0000\n" +"PO-Revision-Date: 2018-10-18 11:15+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_order_mgmt +#: model:ir.ui.view,arch_db:pos_order_mgmt.view_pos_config_form +msgid "Allow to load done orders in this POS" +msgstr "Permitir cargar pedidos en este PdV" + +#. module: pos_order_mgmt +#: model:ir.model.fields,help:pos_order_mgmt.field_pos_config_iface_load_done_order +msgid "Allows to load already done orders in the frontend to operate over them, allowing reprint the tickets, return items, etc." +msgstr "Permite cargar pedidos ya realizados desde el PdV para operar sobre ellos: reimprimir tickes, devoluciónes, etc." + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:36 +#, python-format +msgid "Amount Total" +msgstr "Importe Total" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:17 +#, python-format +msgid "Back" +msgstr "Volver" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:230 +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:258 +#, python-format +msgid "Can not execute this action because the POS is currently offline" +msgstr "No se puede ejecutar esta acción porque el PdV está sin línea en este momento" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:229 +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:257 +#, python-format +msgid "Connection error" +msgstr "Error de conexión" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:34 +#, python-format +msgid "Customer" +msgstr "Cliente" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:35 +#, python-format +msgid "Date" +msgstr "Fecha" + +#. module: pos_order_mgmt +#: model:ir.ui.view,arch_db:pos_order_mgmt.view_pos_config_form +msgid "Load Done Order Max Qty." +msgstr "Nº Máximo de Ventas a Cargar" + +#. module: pos_order_mgmt +#: model:ir.model.fields,field_description:pos_order_mgmt.field_pos_config_iface_load_done_order +#: model:ir.ui.view,arch_db:pos_order_mgmt.view_pos_config_form +msgid "Load Done Orders" +msgstr "Cargar Ventas Realizadas" + +#. module: pos_order_mgmt +#: model:ir.model.fields,field_description:pos_order_mgmt.field_pos_config_iface_load_done_order_max_qty +msgid "Max. Done Orders Quantity To Load" +msgstr "Nº Máximo de Ventas Realizadas a Cargar" + +#. module: pos_order_mgmt +#: model:ir.model.fields,help:pos_order_mgmt.field_pos_config_iface_load_done_order_max_qty +msgid "Maximum number of orders to load on the PoS at its init. Set it to 0 to load none (it's still posible to load them by ticket code)." +msgstr "Número máximo de ventas a cargar en el PdV cuando este se inicia. Establézcalo a 0 para no cargar ninguna (es posible cargarlas por referencia del ticket)." + +#. module: pos_order_mgmt +#: model:ir.ui.view,arch_db:pos_order_mgmt.view_pos_config_form +msgid "Maximum number orders to load" +msgstr "Ventas máximas a cargar" + +#. module: pos_order_mgmt +#: model:ir.model,name:pos_order_mgmt.model_pos_order +msgid "Point of Sale Orders" +msgstr "Ventas del Punto de Venta" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:33 +#, python-format +msgid "Ref." +msgstr "Ref." + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:71 +#, python-format +msgid "Returned order:" +msgstr "Devolución de venta:" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:21 +#, python-format +msgid "Search Order" +msgstr "Buscar Venta" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:190 +#, python-format +msgid "Unable to load some order lines because the products are not available in the POS cache.\n" +"\n" +"Please check that lines :\n" +"\n" +" * " +msgstr "No fue posible cargar algunas líneas porque los pedidos no están disponibles en la caché del PdV.\n" +"\n" +"Por favor, compruebe estas líneas :\n" +"\n" +" * " + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:189 +#, python-format +msgid "Unknown Products" +msgstr "Productos desconocidos" + +#. module: pos_order_mgmt +#: model:ir.model,name:pos_order_mgmt.model_pos_config +msgid "pos.config" +msgstr "" diff --git a/pos_order_mgmt/i18n/pos_order_mgmt.pot b/pos_order_mgmt/i18n/pos_order_mgmt.pot new file mode 100644 index 00000000..1e06ed6e --- /dev/null +++ b/pos_order_mgmt/i18n/pos_order_mgmt.pot @@ -0,0 +1,146 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_order_mgmt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-10-18 11:15+0000\n" +"PO-Revision-Date: 2018-10-18 11:15+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_order_mgmt +#: model:ir.ui.view,arch_db:pos_order_mgmt.view_pos_config_form +msgid "Allow to load done orders in this POS" +msgstr "" + +#. module: pos_order_mgmt +#: model:ir.model.fields,help:pos_order_mgmt.field_pos_config_iface_load_done_order +msgid "Allows to load already done orders in the frontend to operate over them, allowing reprint the tickets, return items, etc." +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:36 +#, python-format +msgid "Amount Total" +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:17 +#, python-format +msgid "Back" +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:230 +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:258 +#, python-format +msgid "Can not execute this action because the POS is currently offline" +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:229 +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:257 +#, python-format +msgid "Connection error" +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:34 +#, python-format +msgid "Customer" +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:35 +#, python-format +msgid "Date" +msgstr "" + +#. module: pos_order_mgmt +#: model:ir.ui.view,arch_db:pos_order_mgmt.view_pos_config_form +msgid "Load Done Order Max Qty." +msgstr "" + +#. module: pos_order_mgmt +#: model:ir.model.fields,field_description:pos_order_mgmt.field_pos_config_iface_load_done_order +#: model:ir.ui.view,arch_db:pos_order_mgmt.view_pos_config_form +msgid "Load Done Orders" +msgstr "" + +#. module: pos_order_mgmt +#: model:ir.model.fields,field_description:pos_order_mgmt.field_pos_config_iface_load_done_order_max_qty +msgid "Max. Done Orders Quantity To Load" +msgstr "" + +#. module: pos_order_mgmt +#: model:ir.model.fields,help:pos_order_mgmt.field_pos_config_iface_load_done_order_max_qty +msgid "Maximum number of orders to load on the PoS at its init. Set it to 0 to load none (it's still posible to load them by ticket code)." +msgstr "" + +#. module: pos_order_mgmt +#: model:ir.ui.view,arch_db:pos_order_mgmt.view_pos_config_form +msgid "Maximum number orders to load" +msgstr "" + +#. module: pos_order_mgmt +#: model:ir.model,name:pos_order_mgmt.model_pos_order +msgid "Point of Sale Orders" +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:33 +#, python-format +msgid "Ref." +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:71 +#, python-format +msgid "Returned order:" +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/xml/pos.xml:21 +#, python-format +msgid "Search Order" +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:190 +#, python-format +msgid "Unable to load some order lines because the products are not available in the POS cache.\n" +"\n" +"Please check that lines :\n" +"\n" +" * " +msgstr "" + +#. module: pos_order_mgmt +#. openerp-web +#: code:addons/pos_order_mgmt/static/src/js/widgets.js:189 +#, python-format +msgid "Unknown Products" +msgstr "" + +#. module: pos_order_mgmt +#: model:ir.model,name:pos_order_mgmt.model_pos_config +msgid "pos.config" +msgstr "" + diff --git a/pos_order_mgmt/models/__init__.py b/pos_order_mgmt/models/__init__.py new file mode 100644 index 00000000..234b311e --- /dev/null +++ b/pos_order_mgmt/models/__init__.py @@ -0,0 +1,2 @@ +from . import pos_config +from . import pos_order diff --git a/pos_order_mgmt/models/pos_config.py b/pos_order_mgmt/models/pos_config.py new file mode 100644 index 00000000..4e82aa6f --- /dev/null +++ b/pos_order_mgmt/models/pos_config.py @@ -0,0 +1,24 @@ +# Copyright 2018 GRAP - Sylvain LE GAL +# Copyright 2018 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class PosConfig(models.Model): + _inherit = 'pos.config' + + iface_load_done_order = fields.Boolean( + string='Load Done Orders', + default=True, + help='Allows to load already done orders in the frontend to operate ' + 'over them, allowing reprint the tickets, return items, etc.', + ) + iface_load_done_order_max_qty = fields.Integer( + string='Max. Done Orders Quantity To Load', + default=10, + required=True, + help='Maximum number of orders to load on the PoS at its init. ' + 'Set it to 0 to load none (it\'s still posible to load them by ' + 'ticket code).', + ) diff --git a/pos_order_mgmt/models/pos_order.py b/pos_order_mgmt/models/pos_order.py new file mode 100644 index 00000000..c2ae6382 --- /dev/null +++ b/pos_order_mgmt/models/pos_order.py @@ -0,0 +1,116 @@ +# Copyright 2018 GRAP - Sylvain LE GAL +# Copyright 2018 Tecnativa S.L. - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class PosOrder(models.Model): + _inherit = 'pos.order' + + @api.model + def _prepare_filter_for_pos(self, pos_session_id): + return [ + ('state', 'in', ['paid', 'done', 'invoiced']), + ] + + @api.model + def _prepare_filter_query_for_pos(self, pos_session_id, query): + return [ + '|', + ('name', 'ilike', query), + ('pos_reference', 'ilike', query), + ] + + @api.model + def _prepare_fields_for_pos_list(self): + return [ + 'name', 'pos_reference', 'partner_id', 'date_order', + 'amount_total', 'amount_paid', 'amount_return', 'session_id', + 'amount_tax', 'statement_ids', 'lines', 'invoice_id', + 'returned_order_id', 'fiscal_position_id' + ] + + @api.model + def search_done_orders_for_pos(self, query, pos_session_id): + session_obj = self.env['pos.session'] + config = session_obj.browse(pos_session_id).config_id + condition = self._prepare_filter_for_pos(pos_session_id) + if not query: + # Search only this POS orders + condition += [('config_id', '=', config.id)] + else: + # Search globally by criteria + condition += self._prepare_filter_query_for_pos(pos_session_id, + query) + fields = self._prepare_fields_for_pos_list() + return self.search_read( + condition, fields, limit=config.iface_load_done_order_max_qty) + + @api.multi + def _prepare_done_order_for_pos(self): + self.ensure_one() + order_lines = [] + payment_lines = [] + for order_line in self.lines: + order_line = self._prepare_done_order_line_for_pos(order_line) + order_lines.append(order_line) + for payment_line in self.statement_ids: + payment_line = self._prepare_done_order_payment_for_pos( + payment_line) + payment_lines.append(payment_line) + return { + 'id': self.id, + 'date_order': self.date_order, + 'pos_reference': self.pos_reference, + 'name': self.name, + 'partner_id': self.partner_id.id, + 'fiscal_position': self.fiscal_position_id.id, + 'line_ids': order_lines, + 'statement_ids': payment_lines, + 'origin_invoice_id': bool(self.invoice_id), + 'returned_order_id': (self.returned_order_id and + self.returned_order_id.pos_reference or + False), + } + + @api.multi + def _prepare_done_order_line_for_pos(self, order_line): + self.ensure_one() + return { + 'product_id': order_line.product_id.id, + 'qty': order_line.qty, + 'price_unit': order_line.price_unit, + 'discount': order_line.discount, + } + + @api.multi + def _prepare_done_order_payment_for_pos(self, payment_line): + self.ensure_one() + return { + 'statement_id': payment_line.statement_id.id, + 'amount': payment_line.amount, + } + + @api.multi + def load_done_order_for_pos(self): + self.ensure_one() + return self._prepare_done_order_for_pos() + + @api.model + def _process_order(self, pos_order): + if (not pos_order.get('return') or + not pos_order.get('returned_order_id')): + return super()._process_order(pos_order) + order = super(PosOrder, + self.with_context(do_not_check_negative_qty=True) + )._process_order(pos_order) + returned_order_id = pos_order.get('returned_order_id') + if isinstance(returned_order_id, int): + order.returned_order_id = self.browse(returned_order_id) + # Only if the order is returned from the browser saved orders. + else: + order.returned_order_id = self.search([ + ('pos_reference', '=', returned_order_id)]) + order.returned_order_id.refund_order_ids |= order + return order diff --git a/pos_order_mgmt/readme/CONFIGURE.rst b/pos_order_mgmt/readme/CONFIGURE.rst new file mode 100644 index 00000000..a62d47bf --- /dev/null +++ b/pos_order_mgmt/readme/CONFIGURE.rst @@ -0,0 +1,10 @@ +To configure this module, you need to: + +#. Go to *Point of Sale > Configuration > Point of Sale* and select one of + them. +#. Set *Load Done Orders* on if you want to be able to load past orders in that + PoS. +#. Change *Max Done Orders Quantity To Load* to your desired amount (10 by + default). Please note that the more you load, the more it will take to load + them in the session opening. You can also set it to 0 and you'll just be + able to load them from the order list screen. diff --git a/pos_order_mgmt/readme/CONTRIBUTORS.rst b/pos_order_mgmt/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..9b6ff1a8 --- /dev/null +++ b/pos_order_mgmt/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* David Vidal +* Sylvain LE GAL (https://twitter.com/legalsylvain) diff --git a/pos_order_mgmt/readme/DESCRIPTION.rst b/pos_order_mgmt/readme/DESCRIPTION.rst new file mode 100644 index 00000000..0b3deafb --- /dev/null +++ b/pos_order_mgmt/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module extends the functionality of the PoS frontend allowing to load +already done PoS Orders in order to be able to operate over them, being able to +reprint past tickets or return them. diff --git a/pos_order_mgmt/readme/ROADMAP.rst b/pos_order_mgmt/readme/ROADMAP.rst new file mode 100644 index 00000000..7fe4e24b --- /dev/null +++ b/pos_order_mgmt/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +* It's possible to return the same order over and over. To avoid so, we should + load and control if there's a returned line id associated with the original + order. That would be a great improvement for future revisions. diff --git a/pos_order_mgmt/readme/USAGE.rst b/pos_order_mgmt/readme/USAGE.rst new file mode 100644 index 00000000..5811a79c --- /dev/null +++ b/pos_order_mgmt/readme/USAGE.rst @@ -0,0 +1,19 @@ +Once the PoS is loaded, you'll find a shopping trolley icon (🛒) in the top +bar that grants access to the order list screen. + +.. image:: /pos_order_mgmt/static/description/order-mgmt-icon.png + +There you can find the number of past orders loaded according to your +configuration (see Configuration) as well as the orders you checked out in +the current session: + +.. image:: /pos_order_mgmt/static/description/order-mgmt-list.png + +#. You can see their totals as well as their custumers if registered. +#. You can reprint their tickets clicking on the printer icon (⎙). +#. You can return them pressing on the arrow icon (↶). +#. You have a search input as well that lets you find past tickets by its + reference number. + +NOTE: You'll need your PoS to be online to be able to search or return a past +ticket. diff --git a/pos_order_mgmt/static/description/icon.png b/pos_order_mgmt/static/description/icon.png new file mode 100644 index 00000000..cafb0ac0 Binary files /dev/null and b/pos_order_mgmt/static/description/icon.png differ diff --git a/pos_order_mgmt/static/description/order-mgmt-icon.png b/pos_order_mgmt/static/description/order-mgmt-icon.png new file mode 100644 index 00000000..e4723efb Binary files /dev/null and b/pos_order_mgmt/static/description/order-mgmt-icon.png differ diff --git a/pos_order_mgmt/static/description/order-mgmt-list.png b/pos_order_mgmt/static/description/order-mgmt-list.png new file mode 100644 index 00000000..4e232586 Binary files /dev/null and b/pos_order_mgmt/static/description/order-mgmt-list.png differ diff --git a/pos_order_mgmt/static/src/css/pos.css b/pos_order_mgmt/static/src/css/pos.css new file mode 100644 index 00000000..7ebf180e --- /dev/null +++ b/pos_order_mgmt/static/src/css/pos.css @@ -0,0 +1,34 @@ +/* Copyright 2018 Tecnativa - David Vidal + License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +*/ + +.order-list-button { + font-size: 20px; +} + +.order-line .button { + cursor: pointer; + top: 0px; + line-height: 32px; + padding: 3px 13px; + font-size: 20px; + background: rgb(230, 230, 230); + margin-left: 12px; + border-radius: 3px; + border: solid 1px rgb(209, 209, 209); + transition: all 150ms linear; +} + +.order-line .button:active { + background: black; + border-color: black; + color: white; +} + +.order-returned-warning { + font-size: 16px; + font-style: italic; + padding: 8px 0; + background-color: rgb(239, 153, 65); + color: white; +} diff --git a/pos_order_mgmt/static/src/js/models.js b/pos_order_mgmt/static/src/js/models.js new file mode 100644 index 00000000..b76d7740 --- /dev/null +++ b/pos_order_mgmt/static/src/js/models.js @@ -0,0 +1,46 @@ +/* Copyright 2018 Tecnativa - David Vidal + License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */ + +odoo.define('pos_order_mgmt.models', function (require) { + 'use strict'; + + var models = require('point_of_sale.models'); + + var _PosModel_push_order = models.PosModel.prototype.push_order; + models.PosModel.prototype.push_order = function () { + var res = _PosModel_push_order.apply(this, arguments); + if (arguments.length && arguments[0] && arguments[0].uid) { + var order = this.db.get_order(arguments[0].uid); + if (order && order.data) { + var data = Object.assign({}, order.data); + var partner = this.db.get_partner_by_id(data.partner_id); + if (partner && partner.id && partner.name) { + data.partner_id = [partner.id, partner.name]; + } + data.date_order = moment(order.data.creation_date) + .format('YYYY-MM-DD HH:mm:ss'); + this.gui.screen_instances.orderlist.orders.unshift(data); + } + } + return res; + }; + + var order_super = models.Order.prototype; + models.Order = models.Order.extend({ + init_from_JSON: function (json) { + order_super.init_from_JSON.apply(this, arguments); + this.return = json.return; + this.returned_order_id = json.returned_order_id; + this.origin_name = json.origin_name; + }, + export_as_JSON: function () { + var res = order_super.export_as_JSON.apply(this, arguments); + if (this.return) { + res.origin_name = this.origin_name; + res.returned_order_id = this.returned_order_id; + res.return = this.return; + } + return res; + }, + }); +}); diff --git a/pos_order_mgmt/static/src/js/widgets.js b/pos_order_mgmt/static/src/js/widgets.js new file mode 100644 index 00000000..0c55b24b --- /dev/null +++ b/pos_order_mgmt/static/src/js/widgets.js @@ -0,0 +1,389 @@ +/* Copyright 2018 GRAP - Sylvain LE GAL + Copyright 2018 Tecnativa - David Vidal + License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */ +odoo.define('pos_order_mgmt.widgets', function (require) { + "use strict"; + + var core = require('web.core'); + var _t = core._t; + var PosBaseWidget = require('point_of_sale.BaseWidget'); + var screens = require('point_of_sale.screens'); + var gui = require('point_of_sale.gui'); + var chrome = require('point_of_sale.chrome'); + var pos = require('point_of_sale.models'); + + var QWeb = core.qweb; + var ScreenWidget = screens.ScreenWidget; + var DomCache = screens.DomCache; + + screens.ReceiptScreenWidget.include({ + render_receipt: function () { + if (!this.pos.reloaded_order) { + return this._super(); + } + var order = this.pos.reloaded_order; + this.$('.pos-receipt-container').html(QWeb.render('PosTicket', { + widget: this, + pos: this.pos, + order: order, + receipt: order.export_for_printing(), + orderlines: order.get_orderlines(), + paymentlines: order.get_paymentlines(), + })); + this.pos.from_loaded_order = true; + }, + click_next: function () { + if (!this.pos.from_loaded_order) { + return this._super(); + } + this.pos.from_loaded_order = false; + return this.gui.show_screen(this.gui.startup_screen); + }, + }); + + var OrderListScreenWidget = ScreenWidget.extend({ + template: 'OrderListScreenWidget', + + init: function (parent, options) { + this._super(parent, options); + this.order_cache = new DomCache(); + this.orders = []; + this.unknown_products = []; + this.search_done_orders(); + }, + + auto_back: true, + + show: function () { + var self = this; + var previous_screen = this.pos.get_order().get_screen_data('previous-screen'); + if (previous_screen === 'receipt') { + this.gui.screen_instances.receipt.click_next(); + this.gui.show_screen('orderlist'); + } + this._super(); + this.renderElement(); + this.old_order = this.pos.get_order(); + this.$('.back').click(function () { + return self.gui.show_screen(self.gui.startup_screen); + }); + if (self.orders.length === 0) { + this.search_done_orders(); + } + this.render_list(); + var search_timeout = null; + if (this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard) { + this.chrome.widget.keyboard.connect(this.$('.searchbox input')); + } + this.$('.searchbox input').on('keyup', function (event) { + clearTimeout(search_timeout); + var query = this.value; + search_timeout = setTimeout(function () { + self.perform_search(query, event.which === 13); + }, 70); + }); + this.$('.searchbox .search-clear').click(function () { + self.clear_search(); + }); + }, + + render_list: function () { + var self = this; + var orders = this.orders; + var contents = this.$el[0].querySelector('.order-list-contents'); + contents.innerHTML = ""; + for (var i = 0, len = Math.min(orders.length, 1000); i < len; i++) { + var order = orders[i]; + var orderline = this.order_cache.get_node(order.id || order.uid); + if (!orderline) { + var orderline_html = QWeb.render('OrderLine', { + widget: this, + order: order, + }); + orderline = document.createElement('tbody'); + orderline.innerHTML = orderline_html; + orderline = orderline.childNodes[1]; + this.order_cache.cache_node(order.id || order.uid, orderline); + } + if (order === this.old_order) { + orderline.classList.add('highlight'); + } else { + orderline.classList.remove('highlight'); + } + contents.appendChild(orderline); + } + // FIXME: Everytime the list is rendered we need to reassing the + // button events. + this.$('.order-list-return').off('click'); + this.$('.order-list-reprint').off('click'); + this.$('.order-list-return').click(function (event) { + self.order_list_actions(event, 'return'); + }); + this.$('.order-list-reprint').click(function (event) { + self.order_list_actions(event, 'print'); + }); + }, + + order_list_actions: function (event, action) { + var dataset = event.target.parentNode.dataset; + var self = this; + if (dataset.orderId) { + this.load_order(parseInt(dataset.orderId, 10), action); + } else { + var local_order = ''; + _.each(this.orders, function (order) { + if (order.uid === dataset.uid) { + order.return = action === 'return'; + local_order = self._prepare_order_from_order_data(order); + } + }); + if (local_order) { + this['action_' + action](local_order); + } + } + }, + + action_print: function (order) { + var receipt = order.export_for_printing(); + if (this.pos.config.iface_print_via_proxy) { + this.pos.proxy.print_receipt(QWeb.render( + 'XmlReceipt', { + receipt: receipt, + widget: this, + pos: this.pos, + order: order, + orderlines: order.get_orderlines(), + paymentlines: order.get_paymentlines(), + })); + } else { + this.pos.reloaded_order = order; + this.gui.show_screen('receipt'); + this.pos.reloaded_order = false; + } + }, + + action_return: function (order) { + this.pos.get('orders').add(order); + this.pos.set('selectedOrder', order); + return order; + }, + + _prepare_order_from_order_data: function (order_data) { + var self = this; + var order = new pos.Order({}, { + pos: this.pos, + temporary: !order_data.return, + }); + // Set Generic Info + if (!order_data.return) { + order.name = order_data.pos_reference || order_data.name; + } + if (order_data.partner_id.length) { + order_data.partner_id = order_data.partner_id[0]; + } + order.set_client(this.pos.db.get_partner_by_id(order_data.partner_id)); + // Set order lines + var orderLines = order_data.line_ids || order_data.lines || []; + _.each(orderLines, function (orderLine) { + var line = orderLine; + // In case of local data + if (line.length === 3) { + line = line[2]; + } + var product = self.pos.db.get_product_by_id(line.product_id); + // Check if product are available in pos + if (_.isUndefined(product)) { + self.unknown_products.push(String(line.product_id)); + } else { + // Create a new order line + order.add_product(product, { + price: line.price_unit, + quantity: order_data.return ? line.qty * -1 : line.qty, + discount: line.discount, + merge: false, + }); + } + }); + // Set fiscal position + if (order_data.fiscal_position && this.pos.fiscal_positions) { + var fiscal_positions = this.pos.fiscal_positions; + order.fiscal_position = fiscal_positions.filter(function (p) { + return p.id === order_data.fiscal_position; + })[0]; + _.each(order.orderlines.models, function (line) { + line.set_quantity(line.quantity); + }); + order.trigger('change'); + } + if (order_data.return) { + order.return = true; + // A credit note should be emited if there was an invoice + order.set_to_invoice(order_data.origin_invoice_id); + // We'll refunded orders once they are synced + order.returned_order_id = order_data.id || order_data.name; + order.origin_name = order_data.pos_reference || order.returned_order_id; + return order; + } + if (order_data.returned_order_id) { + order.origin_name = order_data.returned_order_id; + } + order.formatted_validation_date = moment(order_data.date_order).format('YYYY-MM-DD HH:mm:ss'); + // Set Payment lines + var paymentLines = order_data.statement_ids || []; + _.each(paymentLines, function (paymentLine) { + var line = paymentLine; + // In case of local data + if (line.length === 3) { + line = line[2]; + } + _.each(self.pos.cashregisters, function (cashregister) { + if (cashregister.id === line.statement_id) { + if (line.amount > 0) { + // If it is not change + order.add_paymentline(cashregister); + order.selected_paymentline.set_amount(line.amount); + } + } + }); + }); + return order; + }, + + load_order: function (order_id, action) { + this.unknown_products = []; + var self = this; + return this._rpc({ + model: 'pos.order', + method: 'load_done_order_for_pos', + args: [order_id], + }).then(function (order_data) { + self.gui.back(); + var correct_order_print = true; + if (action === 'return') { + order_data.return = true; + } + var order = self._prepare_order_from_order_data(order_data); + // Forbid POS Order loading if some products are unknown + if (self.unknown_products.length > 0) { + self.gui.show_popup('error-traceback', { + 'title': _t('Unknown Products'), + 'body': _t('Unable to load some order lines because the ' + + 'products are not available in the POS cache.\n\n' + + 'Please check that lines :\n\n * ') + self.unknown_products.join("; \n *"), + }); + correct_order_print = false; + } + if (correct_order_print && action === 'print') { + self.action_print(order); + } + if (correct_order_print && action === 'return') { + self.action_return(order); + } + }).fail(function (error, event) { + if (parseInt(error.code, 10) === 200) { + // Business Logic Error, not a connection problem + self.gui.show_popup( + 'error-traceback', { + 'title': error.data.message, + 'body': error.data.debug, + }); + } else { + self.gui.show_popup('error', { + 'title': _t('Connection error'), + 'body': _t('Can not execute this action because the POS is currently offline'), + }); + } + event.preventDefault(); + }); + }, + + // Search Part + search_done_orders: function (query) { + var self = this; + return this._rpc({ + model: 'pos.order', + method: 'search_done_orders_for_pos', + args: [query || '', this.pos.pos_session.id], + }).then(function (result) { + self.orders = result; + }).fail(function (error, event) { + if (parseInt(error.code, 10) === 200) { + // Business Logic Error, not a connection problem + self.gui.show_popup( + 'error-traceback', { + 'title': error.data.message, + 'body': error.data.debug, + } + ); + } else { + self.gui.show_popup('error', { + 'title': _t('Connection error'), + 'body': _t('Can not execute this action because the POS is currently offline'), + }); + } + event.preventDefault(); + }); + }, + + perform_search: function (query) { + var self = this; + this.search_done_orders(query) + .done(function () { + self.render_list(); + }); + }, + + clear_search: function () { + var self = this; + this.search_done_orders() + .done(function () { + self.$('.searchbox input')[0].value = ''; + self.$('.searchbox input').focus(); + self.render_list(); + }); + }, + }); + + gui.define_screen({ + name: 'orderlist', + widget: OrderListScreenWidget, + }); + + var ListOrderButtonWidget = PosBaseWidget.extend({ + template: 'ListOrderButtonWidget', + init: function (parent, options) { + var opts = options || {}; + this._super(parent, opts); + this.action = opts.action; + this.label = opts.label; + }, + + button_click: function () { + this.gui.show_screen('orderlist'); + }, + + renderElement: function () { + var self = this; + this._super(); + this.$el.click(function () { + self.button_click(); + }); + }, + }); + + var widgets = chrome.Chrome.prototype.widgets; + widgets.push({ + 'name': 'list_orders', + 'widget': ListOrderButtonWidget, + 'prepend': '.pos-rightheader', + 'args': { + 'label': 'All Orders', + }, + }); + + return { + ListOrderButtonWidget: ListOrderButtonWidget, + OrderListScreenWidget: OrderListScreenWidget, + }; + +}); diff --git a/pos_order_mgmt/static/src/xml/pos.xml b/pos_order_mgmt/static/src/xml/pos.xml new file mode 100644 index 00000000..f3b7b55c --- /dev/null +++ b/pos_order_mgmt/static/src/xml/pos.xml @@ -0,0 +1,85 @@ + + + + + +
+ +
+
+
+ + +
+
+
+ + + Back + + + + + + +
+
+
+
+
+
+ + + + + + + + + +
Ref.CustomerDateAmount Total +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ Returned order: +
+
+
+ + + + +
+ Rectifies: + +
+
+ +
diff --git a/pos_order_mgmt/views/assets.xml b/pos_order_mgmt/views/assets.xml new file mode 100644 index 00000000..126e81c3 --- /dev/null +++ b/pos_order_mgmt/views/assets.xml @@ -0,0 +1,13 @@ + + + +