Browse Source

[11.0][ADD] pos_order_mgmt: New module

pull/297/head
David Vidal 6 years ago
committed by David
parent
commit
e9e928fa67
  1. 121
      pos_order_mgmt/README.rst
  2. 1
      pos_order_mgmt/__init__.py
  3. 27
      pos_order_mgmt/__manifest__.py
  4. 149
      pos_order_mgmt/i18n/es.po
  5. 146
      pos_order_mgmt/i18n/pos_order_mgmt.pot
  6. 2
      pos_order_mgmt/models/__init__.py
  7. 24
      pos_order_mgmt/models/pos_config.py
  8. 116
      pos_order_mgmt/models/pos_order.py
  9. 10
      pos_order_mgmt/readme/CONFIGURE.rst
  10. 2
      pos_order_mgmt/readme/CONTRIBUTORS.rst
  11. 3
      pos_order_mgmt/readme/DESCRIPTION.rst
  12. 3
      pos_order_mgmt/readme/ROADMAP.rst
  13. 19
      pos_order_mgmt/readme/USAGE.rst
  14. BIN
      pos_order_mgmt/static/description/icon.png
  15. BIN
      pos_order_mgmt/static/description/order-mgmt-icon.png
  16. BIN
      pos_order_mgmt/static/description/order-mgmt-list.png
  17. 34
      pos_order_mgmt/static/src/css/pos.css
  18. 46
      pos_order_mgmt/static/src/js/models.js
  19. 389
      pos_order_mgmt/static/src/js/widgets.js
  20. 85
      pos_order_mgmt/static/src/xml/pos.xml
  21. 13
      pos_order_mgmt/views/assets.xml
  22. 41
      pos_order_mgmt/views/view_pos_config.xml

121
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 <https://github.com/OCA/pos/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 <https://github.com/OCA/pos/issues/new?body=module:%20pos_order_mgmt%0Aversion:%2011.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* GRAP
* Tecnativa
Contributors
~~~~~~~~~~~~
* David Vidal <david.vidal@tecnativa.com>
* 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 <https://github.com/OCA/pos/tree/11.0/pos_order_mgmt>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

1
pos_order_mgmt/__init__.py

@ -0,0 +1 @@
from . import models

27
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,
}

149
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 ""

146
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 ""

2
pos_order_mgmt/models/__init__.py

@ -0,0 +1,2 @@
from . import pos_config
from . import pos_order

24
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).',
)

116
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

10
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.

2
pos_order_mgmt/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* David Vidal <david.vidal@tecnativa.com>
* Sylvain LE GAL (https://twitter.com/legalsylvain)

3
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.

3
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.

19
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.

BIN
pos_order_mgmt/static/description/icon.png

After

Width: 560  |  Height: 560  |  Size: 15 KiB

BIN
pos_order_mgmt/static/description/order-mgmt-icon.png

After

Width: 678  |  Height: 271  |  Size: 26 KiB

BIN
pos_order_mgmt/static/description/order-mgmt-list.png

After

Width: 825  |  Height: 279  |  Size: 38 KiB

34
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;
}

46
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;
},
});
});

389
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,
};
});

85
pos_order_mgmt/static/src/xml/pos.xml

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="ListOrderButtonWidget">
<t t-if="widget.pos.config.iface_load_done_order">
<div class="header-button order-list-button">
<i class='fa fa-fw fa-shopping-cart'/>
</div>
</t>
</t>
<t t-name="OrderListScreenWidget">
<div class="clientlist-screen screen">
<div class="screen-content">
<section class="top-content">
<span class='button back'>
<i class='fa fa-angle-double-left'></i>
Back
</span>
<span class='searchbox'>
<input placeholder='Search Order' />
<span class='search-clear'></span>
</span>
<span class='searchbox'></span>
</section>
<section class="full-content">
<div class="window">
<section class="subwindow">
<div class="subwindow-container">
<div class="subwindow-container-fix touch-scrollable scrollable-y">
<table class="client-list">
<thead>
<th name="th_ol_ref">Ref.</th>
<th name="th_ol_customer">Customer</th>
<th name="th_ol_date">Date</th>
<th name="th_ol_amount_total">Amount Total</th>
<th name="th_ol_reprint"/>
</thead>
<tbody class="order-list-contents">
</tbody>
</table>
</div>
</div>
</section>
</div>
</section>
</div>
</div>
</t>
<t t-name="OrderLine">
<tr class='order-line' t-att-data-id='order.id' t-att-data-Uid='order.uid'>
<td name="td_ol_name"><t t-esc='order.pos_reference or order.name' /></td>
<td name="td_ol_customer"><t t-esc='order.partner_id[1]' /></td>
<td name="td_ol_date"><t t-esc='order.date_order' /></td>
<td name="td_ol_amount_total"><t t-esc='widget.format_currency(order.amount_total)' /></td>
<td name="td_ol_reprint" t-att-data-order-id="order.id" t-att-data-Uid='order.uid'>
<span class="button order-list-reprint" t-att-data-order-id="order.id" t-att-data-Uid='order.uid'>
<i class='fa fa-fw fa-print'/>
</span>
<span t-if="order.amount_total >= 0" class="button order-list-return" t-att-data-order-id="order.id" t-att-data-Uid='order.uid'>
<i class='fa fa-fw fa-undo'/>
</span>
</td>
</tr>
</t>
<t t-extend="OrderWidget">
<t t-jquery=".order-scroller.touch-scrollable" t-operation="before">
<div class="order-returned-warning" t-if="order.return">
<span>Returned order: </span><t name="order-return-uid" t-esc="order.origin_name"></t>
</div>
</t>
</t>
<t t-extend="PosTicket">
<t t-jquery="t[t-esc='order.name']" t-operation="after">
<t t-if="order.origin_name">
<br/>
<span name="order-origin-name">Rectifies: </span><t t-esc="order.origin_name"/>
</t>
</t>
</t>
</templates>

13
pos_order_mgmt/views/assets.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<script type="text/javascript" src="/pos_order_mgmt/static/src/js/widgets.js"/>
<script type="text/javascript" src="/pos_order_mgmt/static/src/js/models.js"/>
<link rel="stylesheet" href="/pos_order_mgmt/static/src/css/pos.css"/>
</xpath>
</template>
</odoo>

41
pos_order_mgmt/views/view_pos_config.xml

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2018 GRAP - Sylvain LE GAL
Copyright 2018 Tecnativa - David Vidal
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="view_pos_config_form" model="ir.ui.view">
<field name="name">pos.config.form</field>
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.pos_config_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='receipt']" position="inside">
<div class="col-xs-12 col-md-6 o_setting_box" id="load_done_order">
<div class="o_setting_left_pane">
<field name="iface_load_done_order"/>
</div>
<div class="o_setting_right_pane">
<label string="Load Done Orders"/>
<div class="text-muted">
Allow to load done orders in this POS
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box" id="load_done_order_max_qty"
attrs="{'invisible': [('iface_load_done_order', '=', False)]}">
<div class="o_setting_right_pane">
<label string="Load Done Order Max Qty."/>
<div class="text-muted">
Maximum number orders to load
</div>
<div class="content-group mt16">
<field name="iface_load_done_order_max_qty" class="oe_inline"/>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>
Loading…
Cancel
Save