Browse Source

[MIG] pos_customer_display: Migration to 12.0

[REF] OCA Convention
- rename files.
- split js file into many files. (models, screens, etc.)
[FIX] make default "welcome" and "good bye" message translatable
[REF] add the possibility to have other format of LCD Customer display.

fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! [MIG] pos_customer_display: Migration to 12.0 [REF] OCA Convention. rename files.

fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! [MIG] pos_customer_display: Migration to 12.0 [REF] OCA Convention. rename files.

fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! [MIG] pos_customer_display: Migration to 12.0 [REF] OCA Convention. rename files.

fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! [MIG] pos_customer_display: Migration to 12.0 [REF] OCA Convention. rename files.

fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! [MIG] pos_customer_display: Migration to 12.0 [REF] OCA Convention. rename files.

fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! [MIG] pos_customer_display: Migration to 12.0 [REF] OCA Convention. rename files.
pull/437/head
Sylvain LE GAL 4 years ago
parent
commit
07737aba38
  1. 98
      pos_customer_display/README.rst
  2. 2
      pos_customer_display/__init__.py
  3. 15
      pos_customer_display/__manifest__.py
  4. 9
      pos_customer_display/demo/pos_config.xml
  5. 11
      pos_customer_display/demo/pos_customer_display_demo.xml
  6. 136
      pos_customer_display/i18n/pos_customer_display.pot
  7. 4
      pos_customer_display/models/__init__.py
  8. 102
      pos_customer_display/models/pos_config.py
  9. 80
      pos_customer_display/models/pos_customer_display.py
  10. 8
      pos_customer_display/readme/CONFIGURE.rst
  11. 5
      pos_customer_display/readme/CONTRIBUTORS.rst
  12. 10
      pos_customer_display/readme/DESCRIPTION.rst
  13. 7
      pos_customer_display/readme/INSTALL.rst
  14. 0
      pos_customer_display/readme/ROADMAP.rst
  15. 9
      pos_customer_display/readme/USAGE.rst
  16. BIN
      pos_customer_display/static/description/icon.png
  17. 309
      pos_customer_display/static/src/js/customer_display.js
  18. 111
      pos_customer_display/static/src/js/customer_display_2_20.js
  19. 90
      pos_customer_display/static/src/js/devices.js
  20. 24
      pos_customer_display/static/src/js/gui.js
  21. 128
      pos_customer_display/static/src/js/models.js
  22. 51
      pos_customer_display/static/src/js/screens.js
  23. 14
      pos_customer_display/views/assets.xml
  24. 26
      pos_customer_display/views/customer_display_view.xml
  25. 10
      pos_customer_display/views/pos_customer_display.xml
  26. 41
      pos_customer_display/views/view_pos_config.xml

98
pos_customer_display/README.rst

@ -1,90 +1,8 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
====================
POS Customer Display
====================
This module adds support for Customer Display in the Point of Sale. As incredible as it may seem, the Odoo POS doesn't have native support for a customer display, even on Odoo 10. So, if the customer cannot see the screen of the cashier, he will not be able to check the price of each product scanned by the cashier, he won't be able to see the total amount, etc. This module provides a solution to this problem by adding support for good old POS LCDs, which are often made of 2 lines of 20 caracters.
This module is designed to be installed on the *main Odoo server*. On the
*POSbox*, you should install the module *hw_customer_display*. But you will certainly prefer to use `pywebdriver <https://github.com/akretion/pywebdriver>`__ instead of the POSbox. Compared to the POSbox, Pywebdriver has several advantages:
* smaller footprint: no need to have a full-blown Odoo with PostgreSQL on the computer of the cashier (or his small Linux-based PC connected to the hardware, like the RaspberryPi for the POSbox),
* availability of an Ubuntu package, for easier deployment,
* native support for the customer display, payment terminal, etc.
* nice test/diagnosis Web interface.
It has been tested with a Bixolon BCD-1100
(http://www.bixolon.com/html/en/product/product_detail.xhtml?prod_id=61),
but should support most serial and USB-serial LCD displays
out-of-the-box or with minor adaptations in the source code:
* of the module *hw_customer_display* if you use the POSbox,
* or of the Python lib `pyposdisplay <https://github.com/akretion/pyposdisplay>`__ if you use `pywebdriver <https://github.com/akretion/pywebdriver>`__.
This module has been developped during a POS code sprint at
`Akretion France <http://www.akretion.com/>`_ from July 7th to July 10th 2014.
Configuration
=============
To configure this module, go to the menu *Point of Sale > Configuration > Point
of Sale* and edit the point of sale for which you want to enable the LCD:
* make sure you have configured the *IP address* and port of the POSbox or pywebdriver in the section *Hardware Proxy / PosBox*,
* activate the option *Customer Display*,
* configure the number of caracters on each line of your LCD (20 by default).
At the end of the page, in the *Customer Display* section, you can customize the *Next customer* message and the *POS closed* message.
Usage
=====
Once everything is configured, just start the POS as usual. You will see messages on the LCD when:
* you start the POS,
* you add or remove a product,
* you press the Payment button: the LCD will display the total amount,
* you enter the amount of cash you receive: the LCD will display the amount of the change to give back,
* you validate an order and go to the next customer,
* you close the POS.
.. 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
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.
Credits
=======
Contributors
------------
* Aurélien Dumaine
* Alexis de Lattre <alexis.delattre@akretion.com>
* Father Odilon (`Barroux Abbey <http://www.barroux.org/>`_)
* Daniel Kraft
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.
====================================
Point of Sale - LCD Customer Display
====================================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

2
pos_customer_display/__init__.py

@ -1,3 +1 @@
# -*- coding: utf-8 -*-
from . import models

15
pos_customer_display/__manifest__.py

@ -1,20 +1,19 @@
# -*- coding: utf-8 -*-
# © 2014-2016 Aurélien DUMAINE
# © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "POS Customer Display",
"version": "10.0.1.0.1",
"name": "Point of Sale - LCD Customer Display",
"version": "12.0.1.0.0",
"category": "Point Of Sale",
"summary": "Manage Customer Display device from POS front end",
"author": "Aurélien DUMAINE,Akretion,Odoo Community Association (OCA)",
"summary": "Manage LCD Customer Display device from POS front end",
"author": "Aurélien DUMAINE,GRAP,Akretion,Odoo Community Association (OCA)",
"license": "AGPL-3",
"depends": ["point_of_sale"],
"data": [
"views/pos_customer_display.xml",
"views/customer_display_view.xml",
"views/assets.xml",
"views/view_pos_config.xml",
],
"demo": ["demo/pos_customer_display_demo.xml"],
"demo": ["demo/pos_config.xml"],
"installable": True,
}

9
pos_customer_display/demo/pos_config.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="point_of_sale.pos_config_main" model="pos.config">
<field name="iface_customer_display" eval="True"/>
</record>
</odoo>

11
pos_customer_display/demo/pos_customer_display_demo.xml

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="point_of_sale.pos_config_main" model="pos.config">
<field name="iface_customer_display" eval="True"/>
</record>
</odoo>

136
pos_customer_display/i18n/pos_customer_display.pot

@ -4,8 +4,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-11 13:43+0000\n"
"PO-Revision-Date: 2020-01-11 13:43+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@ -14,130 +16,158 @@ msgstr ""
"Plural-Forms: \n"
#. module: pos_customer_display
#: model:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "Bottom Line"
#: selection:pos.config,customer_display_format:0
msgid "2 Lines of 20 Characters"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,help:pos_customer_display.field_pos_config_customer_display_msg_next_l2
msgid "Bottom line of the message on the customer display which is displayed after starting POS and also after validation of an order"
#. openerp-web
#: code:addons/pos_customer_display/static/src/js/customer_display_2_20.js:93
#, python-format
msgid "Customer Account"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,help:pos_customer_display.field_pos_config_customer_display_msg_closed_l2
msgid "Bottom line of the message on the customer display which is displayed when POS is closed"
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config__customer_display_format
msgid "Customer Display Size"
msgstr ""
#. module: pos_customer_display
#. openerp-web
#: code:addons/pos_customer_display/static/src/js/customer_display.js:74
#: code:addons/pos_customer_display/static/src/js/customer_display_2_20.js:60
#, python-format
msgid "Cancel Payment"
msgid "Deleting Line ..."
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config_iface_customer_display
#: model:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "Customer Display"
#: model:ir.model.fields,help:pos_customer_display.field_pos_config__iface_customer_display
msgid "Display data on the customer display"
msgstr ""
#. module: pos_customer_display
#. openerp-web
#: code:addons/pos_customer_display/static/src/js/customer_display.js:59
#, python-format
msgid "Delete Item"
#: model:ir.model.fields,help:pos_customer_display.field_pos_config__customer_display_msg_next_l1
msgid "First line of the message on the customer display which is displayed after starting POS and also after validation of an order"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,help:pos_customer_display.field_pos_config_iface_customer_display
msgid "Display data on the customer display"
#: model:ir.model.fields,help:pos_customer_display.field_pos_config__customer_display_msg_closed_l1
msgid "First line of the message on the customer display which is displayed when POS is closed"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config__iface_customer_display
#: model_terms:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "LED Customer Display"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,help:pos_customer_display.field_pos_config_customer_display_line_length
#: model:ir.model.fields,help:pos_customer_display.field_pos_config__customer_display_line_length
msgid "Length of the LEDs lines of the customer display"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config_customer_display_line_length
msgid "Line Length of the Customer Display"
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config__customer_display_line_length
#: model_terms:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "Line Length"
msgstr ""
#. module: pos_customer_display
#: code:addons/pos_customer_display/models/pos_customer_display.py:46
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config_customer_display_msg_next_l2
#, python-format
msgid "Next Customer (bottom line)"
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config__customer_display_msg_next_l1
#: model_terms:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "Next Customer (Line 1)"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config__customer_display_msg_next_l2
#: model_terms:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "Next Customer (Line 2)"
msgstr ""
#. module: pos_customer_display
#: code:addons/pos_customer_display/models/pos_customer_display.py:44
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config_customer_display_msg_next_l1
#. openerp-web
#: code:addons/pos_customer_display/static/src/js/customer_display_2_20.js:98
#, python-format
msgid "Next Customer (top line)"
msgid "No Customer Account"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config__customer_display_msg_closed_l1
#: model_terms:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "PoS Closed (Line 1)"
msgstr ""
#. module: pos_customer_display
#: model:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "Next Customer Message"
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config__customer_display_msg_closed_l2
msgid "PoS Closed (Line 2)"
msgstr ""
#. module: pos_customer_display
#: code:addons/pos_customer_display/models/pos_customer_display.py:50
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config_customer_display_msg_closed_l2
#: code:addons/pos_customer_display/models/pos_config.py:64
#, python-format
msgid "POS Closed (bottom line)"
msgid "Point of Sale Closed"
msgstr ""
#. module: pos_customer_display
#: model:ir.model,name:pos_customer_display.model_pos_config
msgid "Point of Sale Configuration"
msgstr ""
#. module: pos_customer_display
#: code:addons/pos_customer_display/models/pos_customer_display.py:48
#: model:ir.model.fields,field_description:pos_customer_display.field_pos_config_customer_display_msg_closed_l1
#: code:addons/pos_customer_display/models/pos_config.py:60
#, python-format
msgid "POS Closed (top line)"
msgid "Point of Sale Open"
msgstr ""
#. module: pos_customer_display
#: model:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "POS Closed Message"
#: model_terms:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "Pos Closed (Line 2)"
msgstr ""
#. module: pos_customer_display
#. openerp-web
#: code:addons/pos_customer_display/static/src/js/customer_display.js:66
#: code:addons/pos_customer_display/static/src/js/customer_display_2_20.js:80
#, python-format
msgid "TOTAL: "
msgid "Returned: "
msgstr ""
#. module: pos_customer_display
#: code:addons/pos_customer_display/models/pos_customer_display.py:55
#, python-format
msgid "The message for customer display '%s' is too long: it has %d chars whereas the maximum is %d chars."
#: model:ir.model.fields,help:pos_customer_display.field_pos_config__customer_display_msg_next_l2
msgid "Second line of the message on the customer display which is displayed after starting POS and also after validation of an order"
msgstr ""
#. module: pos_customer_display
#: model:ir.ui.view,arch_db:pos_customer_display.view_pos_config_form
msgid "Top Line"
#: model:ir.model.fields,help:pos_customer_display.field_pos_config__customer_display_msg_closed_l2
msgid "Second line of the message on the customer display which is displayed when POS is closed"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,help:pos_customer_display.field_pos_config_customer_display_msg_next_l1
msgid "Top line of the message on the customer display which is displayed after starting POS and also after validation of an order"
#: code:addons/pos_customer_display/models/pos_config.py:66
#, python-format
msgid "See you soon!"
msgstr ""
#. module: pos_customer_display
#: model:ir.model.fields,help:pos_customer_display.field_pos_config_customer_display_msg_closed_l1
msgid "Top line of the message on the customer display which is displayed when POS is closed"
#: code:addons/pos_customer_display/models/pos_config.py:93
#, python-format
msgid "The message for customer display '%s' is too long: it has %d chars whereas the maximum is %d chars."
msgstr ""
#. module: pos_customer_display
#. openerp-web
#: code:addons/pos_customer_display/static/src/js/customer_display.js:81
#: code:addons/pos_customer_display/static/src/js/customer_display_2_20.js:78
#, python-format
msgid "Your Change:"
msgid "To Pay: "
msgstr ""
#. module: pos_customer_display
#: model:ir.model,name:pos_customer_display.model_pos_config
msgid "pos.config"
#. openerp-web
#: code:addons/pos_customer_display/static/src/js/customer_display_2_20.js:85
#, python-format
msgid "Total"
msgstr ""
#. module: pos_customer_display
#: code:addons/pos_customer_display/models/pos_config.py:62
#, python-format
msgid "Welcome!"
msgstr ""

4
pos_customer_display/models/__init__.py

@ -1,3 +1 @@
# -*- coding: utf-8 -*-
from . import pos_customer_display
from . import pos_config

102
pos_customer_display/models/pos_config.py

@ -0,0 +1,102 @@
# © 2014-2016 Aurélien DUMAINE
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PosConfig(models.Model):
_inherit = "pos.config"
_CUSTOMER_DISPLAY_FORMAT_SELECTION = [
("2_20", "2 Lines of 20 Characters"),
]
iface_customer_display = fields.Boolean(
string="LED Customer Display",
help="Display data on the customer display"
)
customer_display_format = fields.Selection(
selection=_CUSTOMER_DISPLAY_FORMAT_SELECTION,
string="Customer Display Format",
default="2_20", required=True)
customer_display_line_length = fields.Integer(
string="Line Length",
compute="_customer_display_line_length",
store=True,
help="Length of the LEDs lines of the customer display",
)
customer_display_msg_next_l1 = fields.Char(
string="Next Customer (Line 1)",
default=lambda x: x._default_customer_display_msg("next_l1"),
help="First line of the message on the customer display which is "
"displayed after starting POS and also after validation of an order",
)
customer_display_msg_next_l2 = fields.Char(
string="Next Customer (Line 2)",
default=lambda x: x._default_customer_display_msg("next_l2"),
help="Second line of the message on the customer display which is "
"displayed after starting POS and also after validation of an order",
)
customer_display_msg_closed_l1 = fields.Char(
string="PoS Closed (Line 1)",
default=lambda x: x._default_customer_display_msg("closed_l1"),
help="First line of the message on the customer display which "
"is displayed when POS is closed",
)
customer_display_msg_closed_l2 = fields.Char(
string="PoS Closed (Line 2)",
default=lambda x: x._default_customer_display_msg("closed_l1"),
help="Second line of the message on the customer display which "
"is displayed when POS is closed",
)
@api.model
def _default_customer_display_msg(self, line):
if line == "next_l1":
return _("Point of Sale Open")
elif line == "next_l2":
return _("Welcome!")
elif line == "closed_l1":
return _("Point of Sale Closed")
elif line == "closed_l2":
return _("See you soon!")
@api.depends("customer_display_format")
def _compute_customer_display_line_length(self):
for config in self:
config.customer_display_line_length = int(
config.customer_display_format.split("_")[1])
@api.constrains(
"iface_customer_display",
"customer_display_format",
"customer_display_msg_next_l1",
"customer_display_msg_next_l2",
"customer_display_msg_closed_l1",
"customer_display_msg_closed_l2",
)
def _check_customer_display_length(self):
for config in self.filtered(lambda x: x.customer_display_line_length):
maxsize = config.customer_display_line_length
fields_to_check = [
x for x in self._fields.keys()
if 'customer_display_msg_' in x
]
for field_name in fields_to_check:
value = getattr(config, field_name)
if value and len(value) > maxsize:
raise ValidationError(
_(
"The message for customer display '%s' is too "
"long: it has %d chars whereas the maximum "
"is %d chars."
)
% (
self._fields[field_name].string,
len(value),
maxsize)
)

80
pos_customer_display/models/pos_customer_display.py

@ -1,80 +0,0 @@
# -*- coding: utf-8 -*-
# © 2014-2016 Aurélien DUMAINE
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class PosConfig(models.Model):
_inherit = "pos.config"
iface_customer_display = fields.Boolean(
string="Customer Display", help="Display data on the customer display"
)
customer_display_line_length = fields.Integer(
string="Line Length of the Customer Display",
default=20,
help="Length of the LEDs lines of the customer display",
)
customer_display_msg_next_l1 = fields.Char(
string="Next Customer (top line)",
default="Welcome!",
help="Top line of the message on the customer display which is "
"displayed after starting POS and also after validation of an order",
)
customer_display_msg_next_l2 = fields.Char(
string="Next Customer (bottom line)",
default="Point of Sale Open",
help="Bottom line of the message on the customer display which is "
"displayed after starting POS and also after validation of an order",
)
customer_display_msg_closed_l1 = fields.Char(
string="POS Closed (top line)",
default="Point of Sale Closed",
help="Top line of the message on the customer display which "
"is displayed when POS is closed",
)
customer_display_msg_closed_l2 = fields.Char(
string="POS Closed (bottom line)",
default="See you soon!",
help="Bottom line of the message on the customer display which "
"is displayed when POS is closed",
)
@api.constrains(
"customer_display_line_length",
"customer_display_msg_next_l1",
"customer_display_msg_next_l2",
"customer_display_msg_closed_l1",
"customer_display_msg_closed_l2",
)
def _check_customer_display_length(self):
self.ensure_one()
if self.customer_display_line_length:
maxsize = self.customer_display_line_length
to_check = {
_(
"Next Customer (top line)"
): self.customer_display_msg_next_l1,
_(
"Next Customer (bottom line)"
): self.customer_display_msg_next_l2,
_(
"POS Closed (top line)"
): self.customer_display_msg_closed_l1,
_(
"POS Closed (bottom line)"
): self.customer_display_msg_closed_l2,
}
for field, msg in to_check.iteritems():
if msg and len(msg) > maxsize:
raise ValidationError(
_(
"The message for customer display '%s' is too "
"long: it has %d chars whereas the maximum "
"is %d chars."
)
% (field, len(msg), maxsize)
)

8
pos_customer_display/readme/CONFIGURE.rst

@ -0,0 +1,8 @@
To configure this module,
* go to the menu Point of Sale > Configuration > Point of Sale
* edit the point of sale for which you want to enable the LCD:
* In the IotBox section, activate the option *LCD Customer Display*,
* configure the format of your LCD screen. (2 lines of 20 characters, by default)
* optionaly, you can customize the *Next customer* message and the *POS closed* message

5
pos_customer_display/readme/CONTRIBUTORS.rst

@ -0,0 +1,5 @@
* Aurélien Dumaine
* Alexis de Lattre <alexis.delattre@akretion.com>
* Father Odilon (`Barroux Abbey <http://www.barroux.org/>`_)
* Daniel Kraft
* Sylvain LE GAL ((https://twitter.com/legalsylvain))

10
pos_customer_display/readme/DESCRIPTION.rst

@ -0,0 +1,10 @@
This module adds support for LCD Customer Display in the Point of Sale.
It has been tested with
* Bixolon BCD-1100 (http://www.bixolon.com/html/en/product/product_detail.xhtml?prod_id=61),
* Aures OCD 300 (https://www.aures.com/point-de-vente-equipment-solutions-systemes/ecrans-tactiles-afficheurs/ocd-300-350-afficheur-client-graphique)
But it should support most serial and USB-serial LCD displays out-of-the-box or with minor adaptations in the source code
* of the module *hw_customer_display* if you use the POSbox,
* or of the Python lib `pyposdisplay <https://github.com/akretion/pyposdisplay>`__ if you use `pywebdriver <https://github.com/akretion/pywebdriver>`__.

7
pos_customer_display/readme/INSTALL.rst

@ -0,0 +1,7 @@
This module is designed to be installed on the *main Odoo server*. On the
*POSbox*, you should install the module *hw_customer_display*. But you will certainly prefer to use `pywebdriver <https://github.com/akretion/pywebdriver>`__ instead of the POSbox. Compared to the POSbox, Pywebdriver has several advantages:
* smaller footprint: no need to have a full-blown Odoo with PostgreSQL on the computer of the cashier (or his small Linux-based PC connected to the hardware, like the RaspberryPi for the POSbox),
* availability of an Ubuntu package, for easier deployment,
* native support for the customer display, payment terminal, etc.
* nice test/diagnosis Web interface.

0
pos_customer_display/readme/ROADMAP.rst

9
pos_customer_display/readme/USAGE.rst

@ -0,0 +1,9 @@
Once everything is configured, just start the POS as usual. You will see messages on the LCD when:
* you start the POS,
* you add or remove a product,
* you select or deselect a customer,
* you press the Payment button: the LCD will display the total amount,
* you enter the amount of cash you receive: the LCD will display the amount of the change to give back,
* you validate an order and go to the next customer,
* you close the POS.

BIN
pos_customer_display/static/description/icon.png

Before

Width: 128  |  Height: 128  |  Size: 9.2 KiB

After

Width: 200  |  Height: 200  |  Size: 18 KiB

309
pos_customer_display/static/src/js/customer_display.js

@ -1,309 +0,0 @@
/*
© 2014-2016 Aurélien DUMAINE
© 2014-2016 Barroux Abbey (www.barroux.org)
© 2014-2016 Akretion (www.akretion.com)
@author: Aurélien DUMAINE
@author: Alexis de Lattre <alexis.delattre@akretion.com>
@author: Father Odilon (Barroux Abbey)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
*/
odoo.define('pos_customer_display', function(require) {
"use strict";
var chrome = require('point_of_sale.chrome');
var core = require('web.core');
var devices = require('point_of_sale.devices');
var gui = require('point_of_sale.gui');
var models = require('point_of_sale.models');
var screens = require('point_of_sale.screens');
var _t = core._t;
var PosModelSuper = models.PosModel;
models.PosModel = models.PosModel.extend({
prepare_text_customer_display: function(type, data){
if (this.config.iface_customer_display != true)
return;
var line_length = this.config.customer_display_line_length || 20;
var currency_rounding = this.currency.decimals;
if (type == 'add_update_line'){
var line = data['line'];
var price_unit = line.get_unit_price();
var discount = line.get_discount();
if (discount) {
price_unit = price_unit * (1.0 - (discount / 100.0));
}
price_unit = price_unit.toFixed(currency_rounding);
var qty = line.get_quantity();
// only display decimals when qty is not an integer
if (qty.toFixed(0) == qty) {
qty = qty.toFixed(0);
}
// only display unit when != Unit(s)
var unit = line.get_unit();
var unit_display = '';
if (unit && !unit.is_unit) {
unit_display = unit.name;
}
var l21 = qty + unit_display + ' x ' + price_unit;
var l22 = ' ' + line.get_display_price().toFixed(currency_rounding);
var lines_to_send = new Array(
this.proxy.align_left(line.get_product().display_name, line_length),
this.proxy.align_left(l21, line_length - l22.length) + l22
);
} else if (type == 'remove_orderline') {
// first click on the backspace button set the amount to 0 => we can't precise the deleted qunatity and price
var line = data['line'];
var lines_to_send = new Array(
this.proxy.align_left(_t("Delete Item"), line_length),
this.proxy.align_right(line.get_product().display_name, line_length)
);
} else if (type == 'add_paymentline') {
var total = this.get('selectedOrder').get_total_with_tax().toFixed(currency_rounding);
var lines_to_send = new Array(
this.proxy.align_left(_t("TOTAL: "), line_length),
this.proxy.align_right(total, line_length)
);
} else if (type == 'remove_paymentline') {
var line = data['line'];
var amount = line.get_amount().toFixed(currency_rounding);
var lines_to_send = new Array(
this.proxy.align_left(_t("Cancel Payment"), line_length),
this.proxy.align_right(line.cashregister.journal_id[1] , line_length - 1 - amount.length) + ' ' + amount
);
} else if (type == 'update_payment') {
var change = data['change'];
var lines_to_send = new Array(
this.proxy.align_left(_t("Your Change:"), line_length),
this.proxy.align_right(change, line_length)
);
} else if (type == 'push_order') {
var lines_to_send = new Array(
this.proxy.align_center(this.config.customer_display_msg_next_l1, line_length),
this.proxy.align_center(this.config.customer_display_msg_next_l2, line_length)
);
} else if (type == 'openPOS') {
var lines_to_send = new Array(
this.proxy.align_center(this.config.customer_display_msg_next_l1, line_length),
this.proxy.align_center(this.config.customer_display_msg_next_l2, line_length)
);
} else if (type = 'closePOS') {
var lines_to_send = new Array(
this.proxy.align_center(this.config.customer_display_msg_closed_l1, line_length),
this.proxy.align_center(this.config.customer_display_msg_closed_l2, line_length)
);
} else {
console.warn('Unknown message type');
return;
}
this.proxy.send_text_customer_display(lines_to_send, line_length);
},
push_order: function(order){
var res = PosModelSuper.prototype.push_order.call(this, order);
if (order) {
this.prepare_text_customer_display('push_order', {'order' : order});
}
return res;
},
});
devices.ProxyDevice = devices.ProxyDevice.extend({
send_text_customer_display: function(data, line_length){
//FIXME : this function is call twice. The first time, it is not called by prepare_text_customer_display : WHY ?
if (_.isEmpty(data) || data.length != 2 || data[0].length != line_length || data[1].length != line_length){
console.warn("send_text_customer_display: Bad Data argument. Data=" + data + ' line_length=' + line_length);
} else {
// alert(JSON.stringify(data));
return this.message('send_text_customer_display', {'text_to_display' : JSON.stringify(data)});
}
},
align_left: function(string, length){
if (string) {
if (string.length > length)
{
string = string.substring(0,length);
}
else if (string.length < length)
{
while(string.length < length)
string = string + ' ';
}
}
else {
string = ' '
while(string.length < length)
string = ' ' + string;
}
return string;
},
align_right: function(string, length){
if (string) {
if (string.length > length)
{
string = string.substring(0,length);
}
else if (string.length < length)
{
while(string.length < length)
string = ' ' + string;
}
}
else {
string = ' '
while(string.length < length)
string = ' ' + string;
}
return string;
},
align_center: function(string, length){
if (string) {
if (string.length > length)
{
string = string.substring(0, length);
}
else if (string.length < length)
{
var ini = (length - string.length) / 2;
while(string.length < length - ini)
string = ' ' + string;
while(string.length < length)
string = string + ' ';
}
}
else {
string = ' '
while(string.length < length)
string = ' ' + string;
}
return string;
},
});
var OrderlineSuper = models.Orderline;
models.Orderline = models.Orderline.extend({
/* set_quantity() is called when you force the qty via the dedicated button
AND when you create a new order line via add_product().
So, when you add a product, we call prepare_text_customer_display() twice...
but I haven't found any good solution to avoid this -- Alexis */
set_quantity: function(quantity){
var res = OrderlineSuper.prototype.set_quantity.call(this, quantity);
if (quantity != 'remove') {
var line = this;
if(this.selected){
this.pos.prepare_text_customer_display('add_update_line', {'line': line});
}
}
return res;
},
set_discount: function(discount){
var res = OrderlineSuper.prototype.set_discount.call(this, discount);
if (discount) {
var line = this;
if(this.selected){
this.pos.prepare_text_customer_display('add_update_line', {'line': line});
}
}
return res;
},
set_unit_price: function(price){
var res = OrderlineSuper.prototype.set_unit_price.call(this, price);
var line = this;
if(this.selected){
this.pos.prepare_text_customer_display('add_update_line', {'line': line});
}
return res;
},
});
var OrderSuper = models.Order;
models.Order = models.Order.extend({
add_product: function(product, options){
var res = OrderSuper.prototype.add_product.call(this, product, options);
if (product) {
var line = this.get_last_orderline();
this.pos.prepare_text_customer_display('add_update_line', {'line' : line});
}
return res;
},
remove_orderline: function(line){
if (line) {
this.pos.prepare_text_customer_display('remove_orderline', {'line' : line});
}
return OrderSuper.prototype.remove_orderline.call(this, line);
},
remove_paymentline: function(line){
if (line) {
this.pos.prepare_text_customer_display('remove_paymentline', {'line' : line});
}
return OrderSuper.prototype.remove_paymentline.call(this, line);
},
add_paymentline: function(cashregister){
var res = OrderSuper.prototype.add_paymentline.call(this, cashregister);
if (cashregister) {
this.pos.prepare_text_customer_display('add_paymentline', {'cashregister' : cashregister});
}
return res;
},
});
screens.PaymentScreenWidget.include({
render_paymentlines: function(){
var res = this._super();
var currentOrder = this.pos.get_order();
if (currentOrder) {
var paidTotal = currentOrder.get_total_paid();
var dueTotal = currentOrder.get_total_with_tax();
var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
if (change) {
var change_rounded = change.toFixed(2);
this.pos.prepare_text_customer_display('update_payment', {'change': change_rounded});
}
}
return res;
},
});
gui.Gui.include({
close: function(){
this._super();
this.pos.prepare_text_customer_display('closePOS', {});
},
});
chrome.ProxyStatusWidget.include({
start: function(){
this._super();
this.pos.prepare_text_customer_display('openPOS', {});
},
});
screens.PaymentScreenWidget.include({
show: function(){
this._super();
this.pos.prepare_text_customer_display('add_paymentline', {});
},
});
});

111
pos_customer_display/static/src/js/customer_display_2_20.js

@ -0,0 +1,111 @@
/*
Copyright (C) 2015-Today GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
odoo.define('pos_customer_display.customer_display_2_20', function (require) {
"use strict";
var core = require('web.core');
var _t = core._t;
var CustomerDisplay_2_20 = function(proxy_device){
this.pos = proxy_device.pos;
this._prepare_line = proxy_device._prepare_line;
this._prepare_message_welcome = function(){
return new Array(
this._prepare_line(this.pos.config.customer_display_msg_next_l1, ""),
this._prepare_line(this.pos.config.customer_display_msg_next_l2, ""),
);
};
this._prepare_message_close = function(){
return new Array(
this._prepare_line(this.pos.config.customer_display_msg_closed_l1, ""),
this._prepare_line(this.pos.config.customer_display_msg_closed_l2, ""),
);
};
this._prepare_message_orderline = function(order_line, action){
var currency_rounding = this.pos.currency.decimals;
var product_name = order_line.product.display_name;
var unit_price_str = order_line.get_unit_price().toFixed(currency_rounding);
var total_amount_str = order_line.get_display_price().toFixed(currency_rounding);
var qty = order_line.get_quantity();
// only display decimals when qty is not an integer
if (qty.toFixed(0) == qty) {
qty = qty.toFixed(0);
}
var discount = order_line.get_discount();
var discount_str = "";
if ([
'add_line',
'update_quantity',
'update_unit_price',
'update_discount',
].indexOf(action) !== -1){
var second_line = String(qty) + " * " + unit_price_str;
if (discount){
discount_str = " -" + String(discount) + "%";
}
return new Array(
this._prepare_line(product_name, discount_str),
this._prepare_line(second_line, total_amount_str),
);
} else if (action === 'delete_line'){
return new Array(
this._prepare_line(_t("Deleting Line ..."), ""),
this._prepare_line(order_line.product.display_name, ""),
);
}
};
this._prepare_message_payment = function(action){
var currency_rounding = this.pos.currency.decimals;
var order = this.pos.get_order()
var total = order.get_total_with_tax().toFixed(currency_rounding);
var total_paid = order.get_total_paid().toFixed(currency_rounding);
var total_change = order.get_due().toFixed(currency_rounding);
var total_to_pay = (total - total_paid).toFixed(currency_rounding);
var remaining_operation_str = "";
if (total_paid != 0) {
if (total_to_pay > 0) {
remaining_operation_str = _t("To Pay: ") + String(total_to_pay);
} else if (total_change < 0) {
remaining_operation_str = _t("Returned: ") + String(- total_change);
}
}
return new Array(
this._prepare_line(_t("Total"), String(total)),
this._prepare_line(remaining_operation_str, ""),
);
};
this._prepare_message_client = function(client){
if ( client ) {
return new Array(
this._prepare_line(_t("Customer Account"), ""),
this._prepare_line(client.name, ""),
);
} else {
return new Array(
this._prepare_line(_t("No Customer Account"), ""),
this._prepare_line("", ""),
);
}
};
};
return {
CustomerDisplay_2_20: CustomerDisplay_2_20,
};
});

90
pos_customer_display/static/src/js/devices.js

@ -0,0 +1,90 @@
/*
Copyright (C) 2015-Today GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
odoo.define('pos_customer_display.devices', function (require) {
"use strict";
var devices = require('point_of_sale.devices');
var customer_display_2_20 = require('pos_customer_display.customer_display_2_20');
var ProxyDeviceSuper = devices.ProxyDevice;
devices.ProxyDevice = devices.ProxyDevice.extend({
init: function(parent, options){
var res = ProxyDeviceSuper.prototype.init.call(this, parent, options);
this.customer_display_proxy = false;
return res;
},
load_customer_display_format_file: function(){
// console.log(this.config.customer_display_format);
if (this.pos.config.customer_display_format == "2_20") {
this.customer_display_proxy = new customer_display_2_20.CustomerDisplay_2_20(this);
} else {
console.warn("No Javascript file found for the Customer Display format" + this.config.customer_display_format);
}
},
send_text_customer_display: function(data){
console.log(data);
if (this.customer_display_proxy) {
return this.message(
'send_text_customer_display',
{'text_to_display' : JSON.stringify(data)}
);
}
},
_prepare_line: function(left_part, right_part){
var line_length = this.pos.config.customer_display_line_length;
var max_left_length = line_length;
if (right_part.length !== 0) {
max_left_length -= right_part.length;
}
var result = left_part.substring(0, max_left_length - 1);
result = result.padEnd(max_left_length);
if (right_part.length !== 0) {
result += right_part.padStart(line_length - result.length);
}
return result;
},
prepare_message_orderline: function(order_line, action){
if (this.customer_display_proxy) {
return this.customer_display_proxy._prepare_message_orderline(order_line, action);
}
},
prepare_message_payment: function(action){
if (this.customer_display_proxy) {
return this.customer_display_proxy._prepare_message_payment(action);
}
},
prepare_message_welcome: function(){
if (this.customer_display_proxy) {
return this.customer_display_proxy._prepare_message_welcome();
}
},
prepare_message_close: function(){
if (this.customer_display_proxy) {
return this.customer_display_proxy._prepare_message_close();
}
},
prepare_message_client: function(client){
if (this.customer_display_proxy) {
return this.customer_display_proxy._prepare_message_client(client);
}
}
});
});

24
pos_customer_display/static/src/js/gui.js

@ -0,0 +1,24 @@
/*
Copyright (C) 2020-Today GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
odoo.define('pos_customer_display.gui', function (require) {
"use strict";
var gui = require('point_of_sale.gui');
gui.Gui.include({
close: function(){
this.pos.proxy.send_text_customer_display(
this.pos.proxy.prepare_message_closed()
);
return this._super();
},
});
});

128
pos_customer_display/static/src/js/models.js

@ -0,0 +1,128 @@
/*
Copyright (C) 2015-Today GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
odoo.define('pos_customer_display.models', function (require) {
"use strict";
var models = require('point_of_sale.models');
var OrderSuper = models.Order;
var OrderlineSuper = models.Orderline;
var PosModelSuper = models.PosModel;
// //////////////////////////////////
// Overload models.PosModel
// //////////////////////////////////
models.PosModel = models.PosModel.extend({
initialize: function(session, attributes) {
var res = PosModelSuper.prototype.initialize.call(this, session, attributes);
// send_message_customer_display is a variable that allow
// to disable the send of the message to the customer display
// This desing is due to the current design of Odoo PoS.
// 1) during the call_of_init_from_JSON, a call to
// add_product and remove_orderline is done.
// 2) during the call of add_product, a call
// to set_price, set_discount, set_quantity is done.
// To avoid multiple useless messages sends.
// it fixed this @via-alexis comment:
// https://github.com/OCA/pos/blob/10.0/pos_customer_display/
// static/src/js/customer_display.js#L198
this.send_message_customer_display = true;
return res;
},
after_load_server_data: function(){
this.proxy.load_customer_display_format_file();
return PosModelSuper.prototype.after_load_server_data.call(this);
},
});
// //////////////////////////////////
// Overload models.Order
// //////////////////////////////////
models.Order = models.Order.extend({
init_from_JSON: function(json) {
this.pos.send_message_customer_display = false;
var res = OrderSuper.prototype.init_from_JSON.call(this, json);
this.pos.send_message_customer_display = true;
return res;
},
add_product: function(product, options){
var send_message = this.pos.send_message_customer_display;
this.pos.send_message_customer_display = false;
var res = OrderSuper.prototype.add_product.call(this, product, options);
if (send_message){
this.pos.proxy.send_text_customer_display(
this.pos.proxy.prepare_message_orderline(
this.get_last_orderline(), 'add_line'));
this.pos.send_message_customer_display = true;
}
return res;
},
});
//////////////////////////////////
// Overload models.Orderline
//////////////////////////////////
models.Orderline = models.Orderline.extend({
set_quantity: function(quantity, keep_price){
var send_message = this.pos.send_message_customer_display;
var message;
this.pos.send_message_customer_display = false;
// In the current Odoo design, set_quantity is call to remove line
// so we prepare the message before because after the call
// of super, the line is delete.
if (send_message){
if (quantity === 'remove') {
message = this.pos.proxy.prepare_message_orderline(this, 'delete_line');
}
}
var res = OrderlineSuper.prototype.set_quantity.call(this, quantity, keep_price);
if (send_message) {
if (quantity !== 'remove'){
message = this.pos.proxy.prepare_message_orderline(this, 'update_quantity');
}
this.pos.send_message_customer_display = true;
this.pos.proxy.send_text_customer_display(message);
}
return res;
},
set_discount: function(discount){
var send_message = this.pos.send_message_customer_display;
this.pos.send_message_customer_display = false;
var res = OrderlineSuper.prototype.set_discount.call(this, discount);
if (send_message) {
this.pos.send_message_customer_display = true;
this.pos.proxy.send_text_customer_display(
this.pos.proxy.prepare_message_orderline(this, 'update_discount'));
}
return res;
},
set_unit_price: function(price){
var send_message = this.pos.send_message_customer_display;
this.pos.send_message_customer_display = false;
var res = OrderlineSuper.prototype.set_unit_price.call(this, price);
if (send_message) {
this.pos.send_message_customer_display = true;
this.pos.proxy.send_text_customer_display(
this.pos.proxy.prepare_message_orderline(this, 'update_unit_price'));
}
return res;
},
});
});

51
pos_customer_display/static/src/js/screens.js

@ -0,0 +1,51 @@
/*
Copyright (C) 2015-Today GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
odoo.define('pos_customer_display.screens', function (require) {
"use strict";
var screens = require('point_of_sale.screens');
screens.PaymentScreenWidget.include({
render_paymentlines: function() {
if (this.pos.get_order().get_total_with_tax() === 0) {
// Render payment is called each time a new order is created
// (and so when lauching the PoS)
// in that case, we display the welcome message
this.pos.proxy.send_text_customer_display(
this.pos.proxy.prepare_message_welcome()
);
} else {
this.pos.proxy.send_text_customer_display(
this.pos.proxy.prepare_message_payment()
);
}
return this._super();
},
});
screens.ClientListScreenWidget.include({
save_changes: function(){
if(this.has_client_changed()){
this.pos.proxy.send_text_customer_display(
this.pos.proxy.prepare_message_client(this.new_client)
);
}
// we disable the send of message, during the call of _super()
// because when selecting customer, all lines are recomputed
// and so a message is sent for each lines
// causing useless flashes
this.pos.send_message_customer_display = false;
var res = this._super();
this.pos.send_message_customer_display = true;
return res;
},
});
});

14
pos_customer_display/views/assets.xml

@ -0,0 +1,14 @@
<?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_customer_display/static/src/js/models.js" />
<script type="text/javascript" src="/pos_customer_display/static/src/js/screens.js" />
<script type="text/javascript" src="/pos_customer_display/static/src/js/gui.js" />
<script type="text/javascript" src="/pos_customer_display/static/src/js/devices.js" />
<script type="text/javascript" src="/pos_customer_display/static/src/js/customer_display_2_20.js" />
</xpath>
</template>
</odoo>

26
pos_customer_display/views/customer_display_view.xml

@ -1,26 +0,0 @@
<?xml version="1.0"?>
<odoo>
<record id="view_pos_config_form" model="ir.ui.view">
<field name="name">pos_customer_display.pos.config.form</field>
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.view_pos_config_form"/>
<field name="arch" type="xml">
<field name="iface_cashdrawer" position="after">
<field name="iface_customer_display"/>
<field name="customer_display_line_length"/>
</field>
<group name="receipt" position="after">
<group name="customer_display" string="Customer Display">
<label string="Next Customer Message" name="next_customer" colspan="2"/>
<field name="customer_display_msg_next_l1" string="Top Line"/>
<field name="customer_display_msg_next_l2" string="Bottom Line"/>
<label string="POS Closed Message" name="pos_closed" colspan="2"/>
<field name="customer_display_msg_closed_l1" string="Top Line"/>
<field name="customer_display_msg_closed_l2" string="Bottom Line"/>
</group>
</group>
</field>
</record>
</odoo>

10
pos_customer_display/views/pos_customer_display.xml

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets" name="pos_customer_display assets" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<script type="text/javascript" src="/pos_customer_display/static/src/js/customer_display.js"></script>
</xpath>
</template>
</odoo>

41
pos_customer_display/views/view_pos_config.xml

@ -0,0 +1,41 @@
<?xml version="1.0"?>
<odoo>
<record id="view_pos_config_form" model="ir.ui.view">
<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="//field[@name='iface_cashdrawer']/.." position="after">
<div class="row">
<label string="LED Customer Display" for="iface_customer_display" class="col-lg-3 o_light_label"/>
<field name="iface_customer_display"/>
</div>
<div class="row" attrs="{'invisible': [('iface_customer_display', '=', False)]}">
<label string="Line Length" for="customer_display_line_length" class="col-lg-3 o_light_label"/>
<field name="customer_display_line_length"/>
</div>
<div class="row" attrs="{'invisible': [('iface_customer_display', '=', False)]}">
<label string="Next Customer (Line 1)" for="customer_display_msg_next_l1" class="col-lg-3 o_light_label"/>
<field name="customer_display_msg_next_l1"/>
</div>
<div class="row" attrs="{'invisible': [('iface_customer_display', '=', False)]}">
<label string="Next Customer (Line 2)" for="customer_display_msg_next_l2" class="col-lg-3 o_light_label"/>
<field name="customer_display_msg_next_l2"/>
</div>
<div class="row" attrs="{'invisible': [('iface_customer_display', '=', False)]}">
<label string="PoS Closed (Line 1)" for="customer_display_msg_closed_l1" class="col-lg-3 o_light_label"/>
<field name="customer_display_msg_closed_l1"/>
</div>
<div class="row" attrs="{'invisible': [('iface_customer_display', '=', False)]}">
<label string="Pos Closed (Line 2)" for="customer_display_msg_closed_l2" class="col-lg-3 o_light_label"/>
<field name="customer_display_msg_closed_l2"/>
</div>
</xpath>
</field>
</record>
</odoo>
Loading…
Cancel
Save