Compare commits

...

2 Commits

Author SHA1 Message Date
default 7e5a259319 [REM] rem cert hash 3 years ago
default 287e500f35 [NEW] initial merge between OCA and Vracoop 4 years ago
  1. 24
      hw_customer_display/__init__.py
  2. 86
      hw_customer_display/__manifest__.py
  3. 24
      hw_customer_display/controllers/__init__.py
  4. 192
      hw_customer_display/controllers/main.py
  5. 14
      hw_customer_display/i18n/hw_customer_display.pot
  6. 80
      hw_customer_display/test-scripts/customer-display-test.py
  7. 4
      hw_customer_display_currency/__init__.py
  8. 19
      hw_customer_display_currency/__manifest__.py
  9. 2
      hw_customer_display_currency/controllers/__init__.py
  10. 133
      hw_customer_display_currency/controllers/main.py
  11. 102
      hw_customer_display_currency/test-scripts/customer_display_currency_test.py
  12. 81
      pos_container/README.rst
  13. 2
      pos_container/__init__.py
  14. 36
      pos_container/__manifest__.py
  15. 20
      pos_container/data/product.xml
  16. 19
      pos_container/demo/demo.xml
  17. 384
      pos_container/i18n/fr.po
  18. 377
      pos_container/i18n/pos_container.pot
  19. 3
      pos_container/models/__init__.py
  20. 13
      pos_container/models/barcode.py
  21. 45
      pos_container/models/container.py
  22. 15
      pos_container/models/pos_order_line.py
  23. 2
      pos_container/readme/CONTRIBUTORS.rst
  24. 5
      pos_container/readme/DESCRIPTION.rst
  25. 2
      pos_container/readme/USAGE.rst
  26. 3
      pos_container/security/ir.model.access.csv
  27. 431
      pos_container/static/description/index.html
  28. 191
      pos_container/static/src/css/container.css
  29. 431
      pos_container/static/src/js/container.js
  30. 619
      pos_container/static/src/js/models_and_db.js
  31. 244
      pos_container/static/src/js/tests.js
  32. 442
      pos_container/static/src/xml/pos.xml
  33. 17
      pos_container/templates/templates.xml
  34. 1
      pos_container/tests/__init__.py
  35. 14
      pos_container/tests/test_pos_container.py
  36. 43
      pos_container/views/container.xml
  37. 3
      pos_customer_display_currency/__init__.py
  38. 33
      pos_customer_display_currency/__manifest__.py
  39. 3
      pos_customer_display_currency/models/__init__.py
  40. 30
      pos_customer_display_currency/models/pos_customer_display_currency.py
  41. 310
      pos_customer_display_currency/static/src/js/pos_customer_display_currency.js
  42. 10
      pos_customer_display_currency/templates/templates.xml
  43. 17
      pos_customer_display_currency/views/pos_config_view.xml
  44. 105
      pos_loyalty/README.rst
  45. 3
      pos_loyalty/__init__.py
  46. 36
      pos_loyalty/__manifest__.py
  47. 8
      pos_loyalty/demo/templates.xml
  48. 523
      pos_loyalty/i18n/es.po
  49. 504
      pos_loyalty/i18n/fr.po
  50. 498
      pos_loyalty/i18n/hr_HR.po
  51. 497
      pos_loyalty/i18n/it.po
  52. 498
      pos_loyalty/i18n/nl_NL.po
  53. 483
      pos_loyalty/i18n/pos_loyalty.pot
  54. 9
      pos_loyalty/models/__init__.py
  55. 30
      pos_loyalty/models/loyalty_program.py
  56. 73
      pos_loyalty/models/loyalty_reward.py
  57. 34
      pos_loyalty/models/loyalty_rule.py
  58. 14
      pos_loyalty/models/pos_config.py
  59. 30
      pos_loyalty/models/pos_order.py
  60. 20
      pos_loyalty/models/pos_order_line.py
  61. 13
      pos_loyalty/models/res_partner.py
  62. 4
      pos_loyalty/readme/CONFIGURE.rst
  63. 3
      pos_loyalty/readme/CONTRIBUTORS.rst
  64. 6
      pos_loyalty/readme/DESCRIPTION.rst
  65. 11
      pos_loyalty/readme/USAGE.rst
  66. 7
      pos_loyalty/security/ir.model.access.csv
  67. BIN
      pos_loyalty/static/description/icon.png
  68. 449
      pos_loyalty/static/description/index.html
  69. 21
      pos_loyalty/static/src/css/pos.css
  70. 477
      pos_loyalty/static/src/js/pos.js
  71. 170
      pos_loyalty/static/src/js/tests.js
  72. 85
      pos_loyalty/static/src/xml/pos.xml
  73. 2
      pos_loyalty/tests/__init__.py
  74. 74
      pos_loyalty/tests/test_pos_loyalty.py
  75. 83
      pos_loyalty/views/loyalty_program_view.xml
  76. 37
      pos_loyalty/views/loyalty_reward_view.xml
  77. 31
      pos_loyalty/views/loyalty_rule_view.xml
  78. 23
      pos_loyalty/views/pos_config_view.xml
  79. 15
      pos_loyalty/views/pos_order_view.xml
  80. 18
      pos_loyalty/views/res_partner_view.xml
  81. 9
      pos_loyalty/views/templates.xml
  82. 86
      pos_mail_receipt/README.rst
  83. 1
      pos_mail_receipt/__init__.py
  84. 14
      pos_mail_receipt/__manifest__.py
  85. 19
      pos_mail_receipt/data/email.xml
  86. 131
      pos_mail_receipt/i18n/fr.po
  87. 122
      pos_mail_receipt/i18n/pos_mail_receipt.pot
  88. 1
      pos_mail_receipt/models/__init__.py
  89. 86
      pos_mail_receipt/models/pos_order.py
  90. 3
      pos_mail_receipt/readme/CONTRIBUTORS.rst
  91. 5
      pos_mail_receipt/readme/DESCRIPTION.rst
  92. 3
      pos_mail_receipt/readme/USAGE.rst
  93. BIN
      pos_mail_receipt/static/description/icon.png
  94. 434
      pos_mail_receipt/static/description/index.html
  95. 3
      pos_mail_receipt/static/src/css/pos.css
  96. 129
      pos_mail_receipt/static/src/css/print.css
  97. 102
      pos_mail_receipt/static/src/js/screens.js
  98. 13
      pos_mail_receipt/static/src/xml/pos.xml
  99. 20
      pos_mail_receipt/templates/assets.xml
  100. 1
      pos_mail_receipt/tests/__init__.py

24
hw_customer_display/__init__.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Hardware Customer Display module for Odoo
# Copyright (C) 2014 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import controllers

86
hw_customer_display/__manifest__.py

@ -0,0 +1,86 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Hardware Customer Display module for Odoo
# Copyright (C) 2014 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Hardware Customer Display',
'version': '12.0.0.1.0',
'category': 'Hardware Drivers',
'license': 'AGPL-3',
'summary': 'Adds support for Customer Display in the Point of Sale',
'description': """
Hardware Customer Display
=========================
This module adds support for Customer Display in the Point of Sale.
This module is designed to be installed on the *POSbox* (i.e. the
proxy on which the USB devices are connected) and not on the main
Odoo server. On the main Odoo server, you should install the module
*pos_customer_display*.
The configuration of the hardware is done in the configuration file of
the Odoo server of the POSbox. You should add the following entries in
the configuration file:
* customer_display_device_name (default = /dev/ttyUSB0)
* customer_display_device_rate (default = 9600)
* customer_display_device_timeout (default = 2 seconds)
The number of cols of the Customer Display (usually 20) should be
configured on the main Odoo server, in the menu Point of Sale >
Configuration > Point of Sales. The number of rows is supposed to be 2.
It should support most serial and USB-serial LCD displays out-of-the-box
or with inheritance of a few functions.
It has been tested with:
* Bixolon BCD-1100 (Datasheet :
http://www.bixolon.com/html/en/product/product_detail.xhtml?prod_id=61)
* Bixolon BCD-1000
To setup the BCD-1100 on Linux, you will find some technical instructions
on this page:
http://techtuxwords.blogspot.fr/2012/12/linux-and-bixolon-bcd-1100.html
If you have a kernel >= 3.12, you should also read this:
http://www.leniwiec.org/en/2014/06/25/ubuntu-14-04lts-how-to-pass-id-ven
dor-and-id-product-to-ftdi_sio-driver/
This module has been developped during a POS code sprint at Akretion
France from July 7th to July 10th 2014. This module is part of the POS
project of the Odoo Community Association http://odoo-community.org/.
You are invited to become a member and/or get involved in the
Association !
This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
'author': "Akretion,Odoo Community Association (OCA)",
'website': 'http://www.akretion.com',
'depends': ['hw_proxy'],
'external_dependencies': {
'python': ['serial', 'unidecode'],
},
'data': [],
'installable':'False'
}

24
hw_customer_display/controllers/__init__.py

@ -0,0 +1,24 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Hardware Customer Display module for Odoo
# Copyright (C) 2014 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import main

192
hw_customer_display/controllers/main.py

@ -0,0 +1,192 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Hardware Customer Display module for Odoo
# Copyright (C) 2014 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
import json as simplejson
import time
from threading import Thread, Lock
from queue import Queue
import odoo.addons.hw_proxy.controllers.main as hw_proxy
from odoo import http
from odoo.tools.config import config
logger = logging.getLogger(__name__)
CLEAR_DISPLAY = b'\x0C'
MOVE_CURSOR_TO = b'\x1B\x6C'
CURSOR_OFF = b'\x1F\x43\x00'
try:
from serial import Serial
from unidecode import unidecode
except (ImportError, IOError) as err:
logger.debug(err)
class CustomerDisplayDriver(Thread):
def __init__(self):
Thread.__init__(self)
self.queue = Queue()
self.lock = Lock()
self.status = {'status': 'connecting', 'messages': []}
self.device_name = config.get(
'customer_display_device_name', '/dev/ttyUSB0')
self.device_rate = int(config.get(
'customer_display_device_rate', 9600))
self.device_timeout = int(config.get(
'customer_display_device_timeout', 2))
self.serial = False
def get_status(self):
self.push_task('status')
return self.status
def set_status(self, status, message=None):
if status == self.status['status']:
if message is not None and message != self.status['messages'][-1]:
self.status['messages'].append(message)
else:
self.status['status'] = status
if message:
self.status['messages'] = [message]
else:
self.status['messages'] = []
if status == 'error' and message:
logger.error('Display Error: ' + message)
elif status == 'disconnected' and message:
logger.warning('Disconnected Display: ' + message)
def lockedstart(self):
with self.lock:
if not self.isAlive():
self.daemon = True
self.start()
def push_task(self, task, data=None):
self.lockedstart()
self.queue.put((time.time(), task, data))
def move_cursor(self, col, row):
# Bixolon spec : 11. "Move Cursor to Specified Position"
position = MOVE_CURSOR_TO + chr(col).encode('ascii') + chr(row).encode('ascii')
self.cmd_serial_write(position)
def display_text(self, lines):
logger.debug(
"Preparing to send the following lines to LCD: %s" % lines)
# We don't check the number of rows/cols here, because it has already
# been checked in the POS client in the JS code
for row, line in enumerate(lines, start=1):
self.move_cursor(1, row)
self.serial_write(unidecode(line).encode('ascii'))
def setup_customer_display(self):
"""Set LCD cursor to off
If your LCD has different setup instruction(s), you should
inherit this function"""
# Bixolon spec : 35. "Set Cursor On/Off"
self.cmd_serial_write(CURSOR_OFF)
logger.debug('LCD cursor set to off')
def clear_customer_display(self):
"""If your LCD has different clearing instruction, you should inherit
this function"""
# Bixolon spec : 12. "Clear Display Screen and Clear String Mode"
self.cmd_serial_write(CLEAR_DISPLAY)
logger.debug('Customer display cleared')
def cmd_serial_write(self, command):
"""If your LCD requires a prefix and/or suffix on all commands,
you should inherit this function"""
assert isinstance(command, bytes), 'command must be a bytes string'
self.serial_write(command)
def serial_write(self, text):
assert isinstance(text, bytes), 'text must be a bytes string'
self.serial.write(text)
def send_text_customer_display(self, text_to_display):
"""This function sends the data to the serial/usb port.
We open and close the serial connection on every message display.
Why ?
1. Because it is not a problem for the customer display
2. Because it is not a problem for performance, according to my tests
3. Because it allows recovery on errors : you can unplug/replug the
customer display and it will work again on the next message without
problem
"""
lines = simplejson.loads(text_to_display)
assert isinstance(lines, list), 'lines_list should be a list'
try:
logger.debug(
'Opening serial port %s for customer display with baudrate %d'
% (self.device_name, self.device_rate))
self.serial = Serial(
self.device_name, self.device_rate,
timeout=self.device_timeout)
logger.debug('serial.is_open = %s' % self.serial.isOpen())
self.setup_customer_display()
self.clear_customer_display()
self.display_text(lines)
except Exception as e:
logger.error('Exception in serial connection: %s' % str(e))
finally:
if self.serial:
logger.debug('Closing serial port for customer display')
self.serial.close()
def run(self):
while True:
try:
timestamp, task, data = self.queue.get(True)
if task == 'display':
self.send_text_customer_display(data)
elif task == 'status':
serial = Serial(
self.device_name, self.device_rate,
timeout=self.device_timeout)
if serial.isOpen():
self.set_status(
'connected',
'Connected to %s' % self.device_name
)
self.serial = serial
except Exception as e:
self.set_status('error', str(e))
driver = CustomerDisplayDriver()
hw_proxy.drivers['customer_display'] = driver
class CustomerDisplayProxy(hw_proxy.Proxy):
@http.route(
'/hw_proxy/send_text_customer_display', type='json', auth='none',
cors='*')
def send_text_customer_display(self, text_to_display):
logger.debug(
'LCD: Call send_text_customer_display with text=%s',
text_to_display)
driver.push_task('display', text_to_display)

14
hw_customer_display/i18n/hw_customer_display.pot

@ -0,0 +1,14 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

80
hw_customer_display/test-scripts/customer-display-test.py

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Author : Alexis de Lattre <alexis.delattre@akretion.com>
# The licence is in the file __manifest__.py
# This is a test script, that you can use if you want to test/play
# with the customer display independantly from the Odoo server
# It has been tested with a Bixolon BCD-1100
import sys
import logging
import time
_logger = logging.getLogger(__name__)
try:
from serial import Serial
from unidecode import unidecode
except (ImportError, IOError) as err:
_logger.debug(err)
DEVICE = '/dev/bixolon'
DEVICE_RATE = 9600
DEVICE_COLS = 20
CLEAR_DISPLAY = b'\x0C'
MOVE_CURSOR_TO = b'\x1B\x6C'
CURSOR_OFF = b'\x1F\x43\x00'
def display_text(ser, line1, line2):
print(("set lines to the right length (%s)" % DEVICE_COLS))
for line in [line1, line2]:
if len(line) < DEVICE_COLS:
line += ' ' * (DEVICE_COLS - len(line))
elif len(line) > DEVICE_COLS:
line = line[0:DEVICE_COLS]
assert len(line) == DEVICE_COLS, 'Wrong length'
print("try to clear display")
ser.write(CLEAR_DISPLAY)
print("clear done")
print("try to position at start of 1st line")
l1 = MOVE_CURSOR_TO + chr(1).encode('ascii') + chr(1).encode('ascii')
ser.write(l1)
print("position done")
print("try to write 1st line")
ser.write(unidecode(line1).encode('ascii'))
print("write 1st line done")
time.sleep(1)
print("try to position at start of 2nd line")
l2 = MOVE_CURSOR_TO + chr(1).encode('ascii') + chr(2).encode('ascii')
ser.write(l2)
print("position done")
print("try to write 2nd line")
ser.write(unidecode(line2).encode('ascii'))
print("write done")
def open_close_display(line1, line2):
ser = False
try:
print("open serial port")
ser = Serial(DEVICE, DEVICE_RATE, timeout=2)
print(("serial port open =", ser.isOpen()))
print(("serial name =", ser.name))
print("try to set cursor to off")
ser.write(CURSOR_OFF)
print("cursor set to off")
display_text(ser, line1, line2)
except Exception as e:
print(('EXCEPTION e={}'.format(e)))
sys.exit(1)
finally:
if ser:
print("close serial port")
ser.close()
if __name__ == '__main__':
line1 = 'Coop IT Easy'
line2 = 'Migration to 12.0'
open_close_display(line1, line2)

4
hw_customer_display_currency/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import controllers

19
hw_customer_display_currency/__manifest__.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Coop IT Easy SCRLfs
# Vincent Van Rossem <vvrossem@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).{
{
'name': "Currency Management Extension for the Hardware Customer Display ",
'version': '12.0.1.0.0',
'summary': """
Adds currency management to the Hardware Customer Display""",
"author": "Coop IT Easy SCRLfs, "
"Odoo Community Association (OCA)",
'license': "AGPL-3",
'website': "https://github.com/OCA/pos/",
'category': 'Hardware Drivers',
'depends': ['hw_customer_display'],
'data': [],
'installable': False,
}

2
hw_customer_display_currency/controllers/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import main

133
hw_customer_display_currency/controllers/main.py

@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Coop IT Easy SCRLfs
# Vincent Van Rossem <vvrossem@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
import json as simplejson
import odoo.addons.hw_proxy.controllers.main as hw_proxy
from odoo.addons.hw_customer_display.controllers.main import CustomerDisplayDriver
from odoo import http
logger = logging.getLogger(__name__)
SELECT_USER_DEFINED_CHAR = b'\x1B\x25\x01'
DEFINE_USER_DEFINED_CHAR = b'\x1B\x26\x01'
EURO_SYMBOL_DRAWING = b'\x05\x14\x3E\x55\x41\x22'
INIT_DISPLAY = b'\x1B\x40'
try:
from serial import Serial
from unidecode import unidecode
except (ImportError, IOError) as err:
logger.debug(err)
class CustomerDisplayCurrencyDriver(CustomerDisplayDriver):
def __init__(self):
super().__init__()
self.currency_char_code = None
self.currency_code = None
def init_customer_display(self):
self.serial.write(INIT_DISPLAY)
logger.debug('Init display')
def set_currency_code(self, currency_code):
self.currency_code = currency_code
def set_currency_char_code(self, currency_char_code):
self.currency_char_code = currency_char_code.encode('ascii')
def send_currency_data_customer_display(self, currency_data):
currency_data = simplejson.loads(currency_data)
self.set_currency_code(currency_data['currency_code'])
self.set_currency_char_code(currency_data['currency_char'])
def draw_euro_symbol(self):
cmd = DEFINE_USER_DEFINED_CHAR + self.currency_char_code + self.currency_char_code + EURO_SYMBOL_DRAWING
self.serial_write(cmd)
self.serial_write(SELECT_USER_DEFINED_CHAR)
logger.debug('Draw euro symbol')
def send_text_customer_display(self, text_to_display):
"""This function sends the data to the serial/usb port.
We open and close the serial connection on every message display.
Why ?
1. Because it is not a problem for the customer display
2. Because it is not a problem for performance, according to my tests
3. Because it allows recovery on errors : you can unplug/replug the
customer display and it will work again on the next message without
problem
"""
lines = simplejson.loads(text_to_display)
assert isinstance(lines, list), 'lines_list should be a list'
try:
logger.debug(
'Opening serial port %s for customer display with baudrate %d'
% (self.device_name, self.device_rate))
self.serial = Serial(
self.device_name, self.device_rate,
timeout=self.device_timeout)
logger.debug('serial.is_open = %s' % self.serial.isOpen())
self.init_customer_display()
self.setup_customer_display() # set cursor off
if self.currency_code == 'EUR':
self.draw_euro_symbol()
self.display_text(lines)
except Exception as e:
logger.error('Exception in serial connection: %s' % str(e))
finally:
if self.serial:
logger.debug('Closing serial port for customer display')
self.serial.close()
def run(self):
while True:
try:
timestamp, task, data = self.queue.get(True)
if task == 'display':
self.send_text_customer_display(data)
elif task == 'currency':
self.send_currency_data_customer_display(data)
elif task == 'status':
serial = Serial(
self.device_name, self.device_rate,
timeout=self.device_timeout)
if serial.isOpen():
self.set_status(
'connected',
'Connected to %s' % self.device_name
)
self.serial = serial
except Exception as e:
self.set_status('error', str(e))
driver = CustomerDisplayCurrencyDriver()
hw_proxy.drivers['customer_display'] = driver
class CustomerDisplayCurrencyProxy(hw_proxy.Proxy):
@http.route(
'/hw_proxy/send_text_customer_display', type='json', auth='none',
cors='*')
def send_text_customer_display(self, text_to_display):
logger.debug(
'LCD: Call send_text_customer_display with text=%s',
text_to_display)
driver.push_task('display', text_to_display)
@http.route(
'/hw_proxy/send_currency_data_customer_display', type='json', auth='none',
cors='*')
def send_currency_data_customer_display(self, currency_data):
logger.debug(
'LCD: Call send_currency_data_customer_display with json=%s',
currency_data)
driver.push_task('currency', currency_data)

102
hw_customer_display_currency/test-scripts/customer_display_currency_test.py

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Coop IT Easy SCRLfs
# Vincent Van Rossem <vvrossem@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import sys
import logging
import time
_logger = logging.getLogger(__name__)
try:
from serial import Serial
from unidecode import unidecode
except (ImportError, IOError) as err:
_logger.debug(err)
DEVICE = '/dev/bixolon'
DEVICE_RATE = 9600
DEVICE_COLS = 20
# Bixolon BCD-1100 COMMANDS
INIT_DISPLAY = b'\x1B\x40'
MOVE_CURSOR_TO = b'\x1B\x6C'
SET_CURSOR_OFF = b'\x1F\x43\x00'
SELECT_USER_DEFINED_CHAR = b'\x1B\x25\x01'
DEFINE_USER_DEFINED_CHAR = b'\x1B\x26\x01'
EURO_SYMBOL_DRAWING = b'\x05\x14\x3E\x55\x41\x22'
def draw_euro_symbol(ser, char_code):
char_code = char_code.encode('ascii')
print('char_code encoded:', char_code)
cmd = DEFINE_USER_DEFINED_CHAR + char_code + char_code + EURO_SYMBOL_DRAWING
ser.write(cmd)
ser.write(SELECT_USER_DEFINED_CHAR)
def display_text(ser, line1, line2):
print("\nset lines to the right length (%s)" % DEVICE_COLS)
for line in [line1, line2]:
if len(line) < DEVICE_COLS:
line += ' ' * (DEVICE_COLS - len(line))
elif len(line) > DEVICE_COLS:
line = line[0:DEVICE_COLS]
assert len(line) == DEVICE_COLS, 'Wrong length'
print("\ntry to draw euro symbol")
draw_euro_symbol(ser, '~')
print('\tdraw euro symbol done')
print("\ntry to position at start of 1st line")
l1 = MOVE_CURSOR_TO + chr(1).encode('ascii') + chr(1).encode('ascii')
ser.write(l1)
print("\tposition done")
print("\ntry to write 1st line")
ser.write(unidecode(line1).encode('ascii'))
print("\twrite 1st line done")
time.sleep(1)
print("\ntry to position at start of 2nd line")
l2 = MOVE_CURSOR_TO + chr(1).encode('ascii') + chr(2).encode('ascii')
ser.write(l2)
print("\tposition done")
print("\ntry to write 2nd line")
ser.write(unidecode(line2).encode('ascii'))
print("\twrite 2nd line done")
def open_close_display(line1, line2):
ser = False
try:
print("open serial port")
ser = Serial(DEVICE, DEVICE_RATE, timeout=2)
print("serial port open =", ser.isOpen())
print("serial name =", ser.name)
print("\ntry to (re)initialize display")
ser.write(INIT_DISPLAY)
print("\t(re)initialize display done")
print("\ntry to set cursor to off")
ser.write(SET_CURSOR_OFF)
print("\tcursor set to off")
display_text(ser, line1, line2)
except Exception as e:
print('EXCEPTION e={}'.format(e))
sys.exit(1)
finally:
if ser:
print("\nclose serial port")
ser.close()
if __name__ == '__main__':
line1 = 'Coop IT Easy'
line2 = 'Draw € symbol: ~'
open_close_display(line1, line2)

81
pos_container/README.rst

@ -0,0 +1,81 @@
=============
POS Container
=============
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! 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/github-OCA%2Fpos-lightgray.png?logo=github
:target: https://github.com/OCA/pos/tree/12.0/pos_container
:alt: OCA/pos
.. |badge3| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/pos-12-0/pos-12-0-pos_container
:alt: Translate me on Weblate
.. |badge4| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/184/12.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4|
This module allows to handle use of reusable containers in POS,
this is useful to handle selling product in bulk without having to calculate
the tare of the container.
Each container is identified by a barcode, the weight is stored in Odoo.
**Table of contents**
.. contents::
:local:
Usage
=====
You have to create a Barcode Nomenclature to handle containers before using the
module.
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_container%0Aversion:%2012.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
~~~~~~~
* Coop IT Easy SCRLfs
Contributors
~~~~~~~~~~~~
* Pierrick Brun <pierrick.brun@akretion.com>
* Robin Keunen <robin.keunen@coopiteasy.be>
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/12.0/pos_container>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

2
pos_container/__init__.py

@ -0,0 +1,2 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

36
pos_container/__manifest__.py

@ -0,0 +1,36 @@
# Copyright 2019 Coop IT Easy SCRLfs
# Robin Keunen <robin@coopiteasy.be>
# Pierrick Brun <pierrick.brun@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).{
{
'name': "POS Container",
'version': '12.0.1.0.0',
'summary': """
Allows managing pre-weighted containers for bulk shop""",
"author": "Coop IT Easy SCRLfs, "
"Odoo Community Association (OCA)",
'license': "AGPL-3",
'website': "https://github.com/OCA/pos/",
'category': 'Point of Sale',
'depends': ['point_of_sale'],
'data': [
'data/product.xml',
'views/container.xml',
'templates/templates.xml',
'security/ir.model.access.csv',
],
'demo': [
'demo/demo.xml',
],
'qweb': [
'static/src/xml/pos.xml',
],
'installable': True,
}

20
pos_container/data/product.xml

@ -0,0 +1,20 @@
<odoo>
<record id="temporary_container_product" model="product.product">
<field name="name">Container without product</field>
<!-- The barcode is used to find it from the POS -->
<field name="barcode">CONTAINER</field>
<field name="uom_id" ref="uom.product_uom_kgm"/>
<field name="uom_po_id" ref="uom.product_uom_kgm"/>
<field name="purchase_ok" eval="False"/>
<field name="description">
This product is used to describe POS order lines having a container but no product yet
</field>
<field name="list_price">0</field>
<field name="available_in_pos" eval="True"/>
<field name="to_weight" eval="True"/>
<field name="active" eval="False"/>
</record>
<record id="temporary_container_product_product_template" model="product.template">
<field name="active" eval="False"/>
</record>
</odoo>

19
pos_container/demo/demo.xml

@ -0,0 +1,19 @@
<odoo>
<record id="container_1" model="pos.container">
<field name="name">Container 1</field>
<field name="barcode">0498765456789</field>
<field name="weight">0.123</field>
</record>
<record id="container_2" model="pos.container">
<field name="name">Container 2</field>
<field name="barcode">0490987654356</field>
<field name="weight">0.234</field>
</record>
<record id="container_3" model="pos.container">
<field name="name">Container 3</field>
<field name="barcode">0490987654398</field>
<field name="weight">0.567</field>
</record>
</odoo>

384
pos_container/i18n/fr.po

@ -0,0 +1,384 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_container
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19 09:34+0000\n"
"PO-Revision-Date: 2019-11-19 10:42+0100\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"Language: fr\n"
"X-Generator: Poedit 2.2.4\n"
#. module: pos_container
#: model:product.product,description:pos_container.temporary_container_product
#: model:product.template,description:pos_container.temporary_container_product_product_template
msgid ""
"\n"
" This product is used to describe POS order lines having a container but no product yet\n"
" "
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:161
#: code:addons/pos_container/static/src/xml/pos.xml:202
#: code:addons/pos_container/static/src/xml/pos.xml:252
#, python-format
msgid "% discount"
msgstr "% de réduction"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:127
#, python-format
msgid "Add a container"
msgstr "Ajouter un contenant"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Alias"
msgstr "Alias"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:184
#: code:addons/pos_container/static/src/xml/pos.xml:318
#, python-format
msgid "Automatic Weighing"
msgstr "Pesée automatique"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:124
#, python-format
msgid "Back"
msgstr "Retour"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:48
#: model:ir.model.fields,field_description:pos_container.field_pos_container__barcode
#, python-format
msgid "Barcode"
msgstr "Code Barre"
#. module: pos_container
#: model:ir.model,name:pos_container.model_barcode_rule
msgid "Barcode Rule"
msgstr "Règle de code barre"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:22
#, python-format
msgid "Cancel"
msgstr "Annuler"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Cashier"
msgstr "Caissier"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Client"
msgstr "Client"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:374
#: code:addons/pos_container/static/src/xml/pos.xml:6
#: code:addons/pos_container/static/src/xml/pos.xml:135
#: model:ir.model.fields,field_description:pos_container.field_pos_order_line__container_id
#, python-format
msgid "Container"
msgstr "Contenants"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_order_line__container_weight
msgid "Container Weight"
msgstr "Poids du contenant"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:80
#, python-format
msgid "Container deletion"
msgstr "Suppression de contenant"
#. module: pos_container
#: model:ir.model,name:pos_container.model_pos_container
msgid "Container for bulk items"
msgstr "Contenant pour produits en vrac"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:134
#, python-format
msgid "Container name:"
msgstr "Nom du contenant:"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Container unit"
msgstr "Unité de contenant"
#. module: pos_container
#: model:product.product,name:pos_container.temporary_container_product
#: model:product.template,name:pos_container.temporary_container_product_product_template
msgid "Container without product"
msgstr "Contenant sans produit"
#. module: pos_container
#: model:ir.actions.act_window,name:pos_container.pos_container_action_window
#: model:ir.ui.menu,name:pos_container.pos_container_menu
msgid "Containers"
msgstr "Contenants"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__create_date
msgid "Created on"
msgstr "Créé le"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:30
#, python-format
msgid "Delete container"
msgstr "Supprimer le contenant"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:295
#: code:addons/pos_container/static/src/xml/pos.xml:335
#: code:addons/pos_container/static/src/xml/pos.xml:383
#, python-format
msgid "Discount:"
msgstr "Réduction:"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Discounted Product"
msgstr "Article en promotion"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:81
#, python-format
msgid "Do you want to delete this container ?\n"
msgstr "Voulez-vous supprimer ce contenant ?\n"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:144
#, python-format
msgid "Error: Could not Save Changes"
msgstr "Erreur: Impossible de sauvegarder les changements"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:84
#: code:addons/pos_container/static/src/xml/pos.xml:101
#, python-format
msgid "Gross :"
msgstr "Poids Brut :"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__id
msgid "ID"
msgstr "ID"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Location"
msgstr "Lieu"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Lot"
msgstr "Lot"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:234
#: code:addons/pos_container/static/src/xml/pos.xml:366
#, python-format
msgid "Manual Input"
msgstr "Saisie manuelle"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:47
#: model:ir.model.fields,field_description:pos_container.field_pos_container__name
#, python-format
msgid "Name"
msgstr "Nom"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__owner_id
msgid "Owner"
msgstr "Propriétaire"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Package"
msgstr "Colis"
#. module: pos_container
#: model:ir.model,name:pos_container.model_pos_order_line
msgid "Point of Sale Order Lines"
msgstr "Lignes de Commande en Point de Vente"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Priced Product"
msgstr "Article à prix fixe"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:137
#, python-format
msgid "Save"
msgstr "Sauvegarder"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:26
#, python-format
msgid "Search Containers"
msgstr "Recherche contenant"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:34
#, python-format
msgid "Select a container"
msgstr "Sélection contenant"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:12
#: model:ir.model.fields,field_description:pos_container.field_pos_order_line__tare
#, python-format
msgid "Tare"
msgstr "Tare"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:215
#: code:addons/pos_container/static/src/xml/pos.xml:266
#: code:addons/pos_container/static/src/xml/pos.xml:356
#: code:addons/pos_container/static/src/xml/pos.xml:404
#, python-format
msgid "Tare :"
msgstr "PT :"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_barcode_rule__type
msgid "Type"
msgstr "Type"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Unit Product"
msgstr "Unité de produit"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:49
#, python-format
msgid "Weight"
msgstr "Poids"
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__weight
msgid "Weight (g)"
msgstr "Poids (kg)"
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Weighted Product"
msgstr "Article pesé"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:160
#: code:addons/pos_container/static/src/xml/pos.xml:201
#: code:addons/pos_container/static/src/xml/pos.xml:251
#, python-format
msgid "With a"
msgstr "Avec un"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:138
#, python-format
msgid "Your Internet connection is probably down."
msgstr "Votre connexion internet est probablement coupée."
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:85
#: code:addons/pos_container/static/src/xml/pos.xml:102
#: model:product.product,uom_name:pos_container.temporary_container_product
#: model:product.product,weight_uom_name:pos_container.temporary_container_product
#: model:product.template,uom_name:pos_container.temporary_container_product_product_template
#: model:product.template,weight_uom_name:pos_container.temporary_container_product_product_template
#, python-format
msgid "kg"
msgstr "kg"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:90
#, python-format
msgid ""
"kg \n"
" -"
msgstr ""
"kg\n"
" -"
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:107
#, python-format
msgid ""
"kg \n"
" - Manual tare"
msgstr ""
"kg \n"
" - Tare manuelle"

377
pos_container/i18n/pos_container.pot

@ -0,0 +1,377 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_container
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19 09:33+0000\n"
"PO-Revision-Date: 2019-11-19 09:33+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_container
#: model:product.product,description:pos_container.temporary_container_product
#: model:product.template,description:pos_container.temporary_container_product_product_template
msgid "\n"
" This product is used to describe POS order lines having a container but no product yet\n"
" "
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:161
#: code:addons/pos_container/static/src/xml/pos.xml:202
#: code:addons/pos_container/static/src/xml/pos.xml:252
#, python-format
msgid "% discount"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:127
#, python-format
msgid "Add a container"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Alias"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:184
#: code:addons/pos_container/static/src/xml/pos.xml:318
#, python-format
msgid "Automatic Weighing"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:124
#, python-format
msgid "Back"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:48
#: model:ir.model.fields,field_description:pos_container.field_pos_container__barcode
#, python-format
msgid "Barcode"
msgstr ""
#. module: pos_container
#: model:ir.model,name:pos_container.model_barcode_rule
msgid "Barcode Rule"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:22
#, python-format
msgid "Cancel"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Cashier"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Client"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:374
#: code:addons/pos_container/static/src/xml/pos.xml:6
#: code:addons/pos_container/static/src/xml/pos.xml:135
#: model:ir.model.fields,field_description:pos_container.field_pos_order_line__container_id
#, python-format
msgid "Container"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_order_line__container_weight
msgid "Container Weight"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:80
#, python-format
msgid "Container deletion"
msgstr ""
#. module: pos_container
#: model:ir.model,name:pos_container.model_pos_container
msgid "Container for bulk items"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:134
#, python-format
msgid "Container name:"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Container unit"
msgstr ""
#. module: pos_container
#: model:product.product,name:pos_container.temporary_container_product
#: model:product.template,name:pos_container.temporary_container_product_product_template
msgid "Container without product"
msgstr ""
#. module: pos_container
#: model:ir.actions.act_window,name:pos_container.pos_container_action_window
#: model:ir.ui.menu,name:pos_container.pos_container_menu
msgid "Containers"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__create_uid
msgid "Created by"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__create_date
msgid "Created on"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:30
#, python-format
msgid "Delete container"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:295
#: code:addons/pos_container/static/src/xml/pos.xml:335
#: code:addons/pos_container/static/src/xml/pos.xml:383
#, python-format
msgid "Discount:"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Discounted Product"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__display_name
msgid "Display Name"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:81
#, python-format
msgid "Do you want to delete this container ?\n"
""
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:144
#, python-format
msgid "Error: Could not Save Changes"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:84
#: code:addons/pos_container/static/src/xml/pos.xml:101
#, python-format
msgid "Gross :"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__id
msgid "ID"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container____last_update
msgid "Last Modified on"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__write_uid
msgid "Last Updated by"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__write_date
msgid "Last Updated on"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Location"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Lot"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:234
#: code:addons/pos_container/static/src/xml/pos.xml:366
#, python-format
msgid "Manual Input"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:47
#: model:ir.model.fields,field_description:pos_container.field_pos_container__name
#, python-format
msgid "Name"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__owner_id
msgid "Owner"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Package"
msgstr ""
#. module: pos_container
#: model:ir.model,name:pos_container.model_pos_order_line
msgid "Point of Sale Order Lines"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Priced Product"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:137
#, python-format
msgid "Save"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:26
#, python-format
msgid "Search Containers"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:34
#, python-format
msgid "Select a container"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:12
#: model:ir.model.fields,field_description:pos_container.field_pos_order_line__tare
#, python-format
msgid "Tare"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:215
#: code:addons/pos_container/static/src/xml/pos.xml:266
#: code:addons/pos_container/static/src/xml/pos.xml:356
#: code:addons/pos_container/static/src/xml/pos.xml:404
#, python-format
msgid "Tare :"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_barcode_rule__type
msgid "Type"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Unit Product"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:49
#, python-format
msgid "Weight"
msgstr ""
#. module: pos_container
#: model:ir.model.fields,field_description:pos_container.field_pos_container__weight
msgid "Weight (g)"
msgstr ""
#. module: pos_container
#: selection:barcode.rule,type:0
msgid "Weighted Product"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:160
#: code:addons/pos_container/static/src/xml/pos.xml:201
#: code:addons/pos_container/static/src/xml/pos.xml:251
#, python-format
msgid "With a"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/js/container.js:138
#, python-format
msgid "Your Internet connection is probably down."
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:85
#: code:addons/pos_container/static/src/xml/pos.xml:102
#: model:product.product,uom_name:pos_container.temporary_container_product
#: model:product.product,weight_uom_name:pos_container.temporary_container_product
#: model:product.template,uom_name:pos_container.temporary_container_product_product_template
#: model:product.template,weight_uom_name:pos_container.temporary_container_product_product_template
#, python-format
msgid "kg"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:90
#, python-format
msgid "kg \n"
" -"
msgstr ""
#. module: pos_container
#. openerp-web
#: code:addons/pos_container/static/src/xml/pos.xml:107
#, python-format
msgid "kg \n"
" - Manual tare"
msgstr ""

3
pos_container/models/__init__.py

@ -0,0 +1,3 @@
from . import container
from . import barcode
from . import pos_order_line

13
pos_container/models/barcode.py

@ -0,0 +1,13 @@
# Copyright 2019 Coop IT Easy SCRLfs
# Pierrick Brun <pierrick.brun@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
class BarcodeRule(models.Model):
_inherit = 'barcode.rule'
type = fields.Selection(
selection_add=[('container', 'Container unit')],
)

45
pos_container/models/container.py

@ -0,0 +1,45 @@
# Copyright 2019 Coop IT Easy SCRLfs
# Robin Keunen <robin@coopiteasy.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
class Container(models.Model):
_name = 'pos.container'
_description = 'Container for bulk items'
name = fields.Char(
string='Name',
)
barcode = fields.Char(
'Barcode',
size=13,
)
weight = fields.Float(
string='Weight (kg)',
)
owner_id = fields.Many2one(
comodel_name='res.partner',
inverse_name='container_ids',
string='Owner',
)
_sql_constraints = [
('barcode_uniq',
'unique(barcode)',
"A barcode can only be assigned to one container !"),
]
@api.model
def create_from_ui(self, containers):
# retourne la liste des ids dans le même ordre que la liste fournie
container_ids = []
for container in containers:
container_id = container.pop('id', False)
if container_id: # Modifying existing container
self.browse(container_id).write(container)
else:
container_id = self.create(container).id
container_ids.append(container_id)
return container_ids

15
pos_container/models/pos_order_line.py

@ -0,0 +1,15 @@
# Copyright 2019 Coop IT Easy SCRLfs
# @author Pierrick Brun <pierrick.brun@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class PosOrderLine(models.Model):
_inherit = 'pos.order.line'
tare = fields.Float('Tare')
container_weight = fields.Float('Container Weight')
container_id = fields.Many2one(
'pos.container',
'Container')

2
pos_container/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Pierrick Brun <pierrick.brun@akretion.com>
* Robin Keunen <robin.keunen@coopiteasy.be>

5
pos_container/readme/DESCRIPTION.rst

@ -0,0 +1,5 @@
This module allows to handle use of reusable containers in POS,
this is useful to handle selling product in bulk without having to calculate
the tare of the container.
Each container is identified by a barcode, the weight is stored in Odoo.

2
pos_container/readme/USAGE.rst

@ -0,0 +1,2 @@
You have to create a Barcode Nomenclature to handle containers before using the
module.

3
pos_container/security/ir.model.access.csv

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_pos_container_pos_user,access_pos_container_pos_user,model_pos_container,point_of_sale.group_pos_user,1,1,1,1
access_pos_container_pos_manager,access_pos_container_pos_manager,model_pos_container,point_of_sale.group_pos_manager,1,1,1,1

431
pos_container/static/description/index.html

@ -0,0 +1,431 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>POS Container</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="pos-container">
<h1 class="title">POS Container</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="https://github.com/OCA/pos/tree/12.0/pos_container"><img alt="OCA/pos" src="https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/pos-12-0/pos-12-0-pos_container"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/184/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module allows to handle use of reusable containers in POS,
this is useful to handle selling product in bulk without having to calculate
the tare of the container.</p>
<p>Each container is identified by a barcode, the weight is stored in Odoo.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="id1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id1">Usage</a></h1>
<p>You have to create a Barcode Nomenclature to handle containers before using the
module.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/pos/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/pos/issues/new?body=module:%20pos_container%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id4">Authors</a></h2>
<ul class="simple">
<li>Coop IT Easy SCRLfs</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id5">Contributors</a></h2>
<blockquote>
<ul class="simple">
<li>Pierrick Brun &lt;<a class="reference external" href="mailto:pierrick.brun&#64;akretion.com">pierrick.brun&#64;akretion.com</a>&gt;</li>
<li>Robin Keunen &lt;<a class="reference external" href="mailto:robin.keunen&#64;coopiteasy.be">robin.keunen&#64;coopiteasy.be</a>&gt;</li>
</ul>
</blockquote>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/pos/tree/12.0/pos_container">OCA/pos</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

191
pos_container/static/src/css/container.css

@ -0,0 +1,191 @@
/* firefox seems to ignore the relative positionning of the subwindow-container
* putting this inside subwindow-container fixes it.
*/
.pos .containerlist-screen .window,
.pos .containerlist-screen .full-content .subwindow{
display: block;
}
.pos .containerlist-screen .full-content .subwindow-container{
display: block;
height: 100%;
}
.pos .containerlist-screen .full-content .subwindow.collapsed,
.pos .containerlist-screen .full-content .subwindow-container.collapsed{
height: auto;
}
/* The Scale Container Screen */
.pos .scale-screen .add-container{
text-align: center;
font-size: 32px;
background: rgb(110,200,155);
color: white;
border-radius: 3px;
padding: 16px;
margin: 16px;
cursor: pointer;
}
.pos .scale-screen .container-name,.container-barcode{
text-align: center;
font-size: 25px;
border-radius: 3px;
padding-top: 10px;
padding-bottom:10px;
margin-top: 5px;
}
/* e) The Container List Screen */
.pos .containerlist-screen .container-list{
font-size: 16px;
width: 100%;
line-height: 40px;
}
.pos .containerlist-screen .container-list th,
.pos .containerlist-screen .container-list td {
padding: 0px 8px;
}
.pos .containerlist-screen .container-list tr{
transition: all 150ms linear;
background: rgb(230,230,230);
}
.pos .containerlist-screen .container-list thead > tr,
.pos .containerlist-screen .container-list tr:nth-child(even) {
background: rgb(247,247,247);
}
.pos .containerlist-screen .container-list tr.highlight{
transition: all 150ms linear;
background: rgb(110,200,155) !important;
color: white;
}
.pos .containerlist-screen .container-list tr.lowlight{
transition: all 150ms linear;
background: rgb(216, 238, 227);
}
.pos .containerlist-screen .container-list tr.lowlight:nth-child(even){
transition: all 150ms linear;
background: rgb(227, 246, 237);
}
.pos .containerlist-screen .container-details{
padding: 16px;
border-bottom: solid 5px rgb(110,200,155);
}
.pos .containerlist-screen .container-picture{
height: 64px;
width: 64px;
border-radius: 32px;
overflow: hidden;
text-align: center;
float: left;
margin-right: 16px;
background: white;
position: relative;
}
.pos .containerlist-screen .container-picture > img {
position: absolute;
top: -9999px;
bottom: -9999px;
right: -9999px;
left: -9999px;
max-height: 64px;
margin: auto;
}
.pos .containerlist-screen .container-picture > .fa {
line-height: 64px;
font-size: 32px;
}
.pos .containerlist-screen .container-picture .image-uploader {
position: absolute;
z-index: 1000;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
cursor: pointer;
}
.pos .containerlist-screen .container-name {
font-size: 32px;
line-height: 64px;
margin-bottom:16px;
}
.pos .containerlist-screen .edit-buttons {
position: absolute;
right: 16px;
top: 10px;
}
.pos .containerlist-screen .edit-buttons .button{
display: inline-block;
margin-left: 16px;
color: rgb(128,128,128);
cursor: pointer;
font-size: 36px;
}
.pos .containerlist-screen .container-details-box{
position: relative;
font-size: 16px;
}
.pos .containerlist-screen .container-details-left{
width: 50%;
float: left;
}
.pos .containerlist-screen .container-details-right{
width: 50%;
float: left;
}
.pos .containerlist-screen .container-detail{
line-height: 24px;
}
.pos .containerlist-screen .container-detail > .label{
font-weight: bold;
display: inline-block;
width: 75px;
text-align: right;
margin-right: 8px;
}
.pos .containerlist-screen .container-details input,
.pos .containerlist-screen .container-details select
{
padding: 4px;
border-radius: 3px;
border: solid 1px #cecbcb;
margin-bottom: 4px;
background: white;
font-family: "Lato","Lucida Grande", Helvetica, Verdana, Arial;
color: #555555;
width: 340px;
font-size: 14px;
box-sizing: border-box;
}
.pos .containerlist-screen .container-details input.container-name {
font-size: 24px;
line-height: 24px;
margin: 18px 6px;
width: 340px;
}
.pos .containerlist-screen .container-detail > .empty{
opacity: 0.3;
}
.pos .containerlist-screen .searchbox{
right: auto;
margin-left: -90px;
margin-top:8px;
left: 45%;
}
.pos .containerlist-screen .searchbox input{
width: 120px;
}
.pos .containerlist-screen .button.delete-container {
left: 50%;
margin-left: 120px;
}
/* Container Action buttons */
.pos .control-button.main {
width: 75%;
}
.pos .control-button.second {
width: 15%;
flex-grow: 0;
}

431
pos_container/static/src/js/container.js

@ -0,0 +1,431 @@
/*
Copyright 2019 Coop IT Easy SCRLfs
Robin Keunen <robin@coopiteasy.be>
Pierrick Brun <pierrick.brun@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
odoo.define('pos_container.container', function (require) {
"use strict";
var models_and_db = require('pos_container.models_and_db');
var screens = require('point_of_sale.screens');
var gui = require('point_of_sale.gui');
var models = require('point_of_sale.models');
var core = require('web.core');
var rpc = require('web.rpc');
var QWeb = core.qweb;
var _t = core._t;
var TareButton = screens.ActionButtonWidget.extend({
template: 'TareButton',
});
screens.define_action_button({
'name': 'tare',
'widget': TareButton,
});
screens.NumpadWidget.include({
// to put selected-mode on tare button outside the numpadwidget
changedMode: function() {
this._super();
if (this.state.get('mode') === 'tare'){
$('.mode-button[data-mode="tare"]').addClass('selected-mode');
}
},
});
var ContainerButton = screens.ActionButtonWidget.extend({
template: 'ContainerButton',
button_click: function(){
this.gui.show_screen('containerlist');
}
});
screens.define_action_button({
'name': 'container',
'widget': ContainerButton,
});
var ContainerListScreenWidget = screens.ScreenWidget.extend({
template: 'ContainerListScreenWidget',
init: function(parent, options){
this._super(parent, options);
this.container_cache = new screens.DomCache();
},
show: function(){
var self = this;
this._super();
this.renderElement();
this.$('.back').click(function(){
self.gui.back();
});
this.$('.next').click(function(){
self.save_changes();
self.gui.show_screen('products');
});
this.$('.delete-container').click(function(){
if (self.container){
self.gui.show_popup('confirm', {
'title': _t('Container deletion'),
'body': _t(
'Do you want to delete this container ?\n').concat(
self.container.name),
confirm: function(){
self.delete_selected_container();
},
});
}
});
var containers = this.pos.db.get_containers_sorted(1000);
this.render_list(containers);
this.reload_containers();
this.$('.container-list-contents').delegate('.container-line',
'click', function(event){
self.line_select(event,$(this),$(this).data('id'));
});
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();
});
},
delete_selected_container: function(){
var self = this;
if (!self.container.id){
self.deleted_container(self.container.barcode)
}
else {
rpc.query({
model: 'pos.container',
method: 'unlink',
args: [self.container.id],
}).then(function(){
self.deleted_container(self.container.barcode);
},function(err,ev){
ev.preventDefault();
var error_body = _t('Your Internet connection is probably down.');
if (err.data) {
var except = err.data;
error_body = except.arguments && except.arguments[0] || except.message || error_body;
}
self.gui.show_popup('error',{
'title': _t('Error: Could not Save Changes'),
'body': error_body,
});
}
);
}
},
deleted_container: function(barcode){
var self = this;
this.pos.db.remove_containers([barcode]);
this.$('.container-list .highlight').remove();
this.container = null;
},
perform_search: function(query, associate_result){
if(query){
var containers = this.pos.db.search_container(query);
if ( associate_result && containers.length === 1){
this.container = containers[0];
this.save_changes();
this.gui.back();
}
this.render_list(containers);
}else{
var containers = this.pos.db.get_containers_sorted();
this.render_list(containers);
}
},
clear_search: function(){
var containers = this.pos.db.get_containers_sorted(1000);
this.render_list(containers);
this.$('.searchbox input')[0].value = '';
this.$('.searchbox input').focus();
},
render_list: function(containers) {
var contents = this.$el[0].querySelector('.container-list-contents');
contents.innerHTML = "";
for(var i = 0, len = Math.min(containers.length,1000); i < len; i++){
var container = containers[i];
var containerline_html = QWeb.render('ContainerLine',{widget: this, container:containers[i]});
var containerline = document.createElement('tbody');
containerline.innerHTML = containerline_html;
containerline = containerline.childNodes[1];
if(containers === this.container) {
containerline.classList.add('highlight');
} else {
containerline.classList.remove('highlight');
}
contents.appendChild(containerline);
}
},
save_changes: function(){
this.pos.get_order().add_container(this.container);
},
toggle_delete_button: function(){
var $button = this.$('.button.delete-container');
$button.toggleClass('oe_hidden', !this.container);
},
toggle_save_button: function(){
var $button = this.$('.button.next');
if (this.container) {
$button.text('Set Container');
}
$button.toggleClass('oe_hidden', !this.container);
},
line_select: function(event,$line,barcode){
var container = this.pos.db.get_container_by_barcode(barcode);
this.$('.container-list .lowlight').removeClass('lowlight');
if ( $line.hasClass('highlight') ){
$line.removeClass('highlight');
$line.addClass('lowlight');
this.toggle_delete_button();
this.toggle_save_button();
}else{
this.$('.container-list .highlight').removeClass('highlight');
$line.addClass('highlight');
var y = event.pageY - $line.parent().offset().top;
this.container = container;
this.toggle_delete_button();
this.toggle_save_button();
}
},
// This fetches container changes on the server, and in case of changes,
// rerenders the affected views
reload_containers: function(){
var self = this;
return this.pos.load_new_containers().then(function(){
// containers may have changed in the backend
self.container_cache = new screens.DomCache();
self.render_list(self.pos.db.get_containers_sorted(1000));
var last_orderline = self.pos.get_order().get_last_orderline();
if(last_orderline) {
// update the currently assigned container if it has been changed in db.
var curr_container = last_orderline.get_container();
}
if (curr_container) {
last_orderline.set_container(
self.pos.db.get_container_by_barcode(curr_container.barcode));
}
});
},
close: function(){
this._super();
if (this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard) {
this.chrome.widget.keyboard.hide();
}
},
auto_back: true,
});
gui.define_screen({
name:'containerlist',
widget: ContainerListScreenWidget
});
// add container barcode scan
screens.ScreenWidget.include({
barcode_container_action: function(code){
var self = this;
if (self.pos.scan_container(code)) {
// nothing to do now, the container is added
// as an orderline if found.
} else {
self.gui.show_screen('containerscale', {barcode: code.base_code});
}
},
show: function(){
var self = this;
this._super();
this.pos.barcode_reader.set_action_callback({
'container': _.bind(self.barcode_container_action, self),
});
},
});
screens.ProductScreenWidget.include({
// to use Tare Button from outside the NumpadWidget
start: function(){
this._super();
var tare_button = $('.mode-button[data-mode="tare"]');
tare_button.click(_.bind(this.numpad.clickChangeMode, this.numpad));
},
// to add a product to a container orderline
click_product: function(product) {
var order = this.pos.get_order();
var selected_orderline = order.get_selected_orderline();
if (product.to_weight && selected_orderline &&
selected_orderline.product === this.pos.get_container_product()){
var container = selected_orderline.get_container();
this.gui.show_screen(
'scale',
{product: product,
container: container,
old_orderline: selected_orderline});
} else {
this._super(product);
}
},
});
screens.ScaleScreenWidget.include({
order_product: function(){
// Replace the orderline if the product is the placeholder
// container product.
var container = this.gui.get_current_screen_param('container');
if (container){
var order = this.pos.get_order();
order.add_product(this.get_product(),{ quantity: this.weight, price: this.price });
var orderline = order.get_selected_orderline();
orderline.set_container(container);
var old_orderline = this.gui.get_current_screen_param(
'old_orderline');
if (old_orderline){
order.remove_orderline(old_orderline);
}
orderline.set_quantity(this.weight);
orderline.set_gross_weight(this.weight + container.weight);
orderline.set_tare_mode('AUTO');
orderline.trigger('change', orderline);
} else {
this._super();
var orderline = this.pos.get_order().get_selected_orderline();
orderline.set_tare_mode('AUTO');
}
},
});
var ContainerScaleScreenWidget = screens.ScaleScreenWidget.extend({
template: 'ContainerScaleScreenWidget',
next_screen: 'products',
previous_screen: 'products',
init: function(parent, options){
this._super(parent, options);
},
show: function(){
this._super();
var self = this;
this.$('.next,.add-container').click(function(){
self.create_container();
});
if(this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard){
this.chrome.widget.keyboard.connect($(this.el.querySelector('.container-name input')));
}
},
get_product: function(){
return this.pos.get_container_product();
},
create_container: function(){
var self = this;
var fields = {};
fields['weight'] = this.weight;
this.$('.container-name .detail').each(function(idx,el){
fields['name'] = el.value;
});
fields.barcode = this.gui.get_current_screen_param('barcode') || false;
fields.name = fields.name || _t('Container');
this.pos.push_container(fields).then(
this.pushed_container(fields["barcode"])
);
},
pushed_container: function(barcode){
var self = this;
self.gui.show_screen(self.next_screen);
},
close: function(){
this._super();
if (this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard) {
this.chrome.widget.keyboard.hide();
}
},
});
gui.define_screen({
name:'containerscale',
widget: ContainerScaleScreenWidget,
});
screens.ProductListWidget.include({
render_product: function(product){
if(product.barcode != 'CONTAINER'){
return this._super(product);
} else {
return document.createElement('div');
}
}
});
screens.OrderWidget.include({
set_value: function(val) {
this._super(val);
var order = this.pos.get_order();
if (order.get_selected_orderline()) {
var oline = order.get_selected_orderline();
var mode = this.numpad_state.get('mode');
if( mode === 'tare'){
oline.set_tare(val);
}
if( mode === 'quantity' && oline.container) {
oline.set_gross_weight(parseFloat(val) + oline.container.weight);
}
oline.set_tare_mode('MAN');
}
},
});
return {
ContainerScaleScreenWidget: ContainerScaleScreenWidget,
};
});

619
pos_container/static/src/js/models_and_db.js

@ -0,0 +1,619 @@
/*
Copyright 2019 Coop IT Easy SCRLfs
Pierrick Brun <pierrick.brun@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
odoo.define('pos_container.models_and_db', function (require) {
"use strict";
var PosDB = require('point_of_sale.DB');
var models = require('point_of_sale.models');
var rpc = require('web.rpc');
var config = require('web.config');
var core = require('web.core');
var QWeb = core.qweb;
var utils = require('web.utils');
var field_utils = require('web.field_utils');
var round_di = utils.round_decimals;
var round_pr = utils.round_precision;
// include not available => extend
models.PosModel = models.PosModel.extend({
get_container_product: function(){
// assign value if not already assigned.
// Avoids rewriting init function
if (!this.container_product){
this.container_product = this.db.get_product_by_barcode(
'CONTAINER');
}
return this.container_product
},
scan_container: function(parsed_code){
var selected_order = this.get_order();
var container = this.db.get_container_by_barcode(
parsed_code.base_code);
if(!container){
return false;
}
selected_order.add_container(container);
return true;
},
// reload the list of container, returns as a deferred that resolves if there were
// updated containers, and fails if not
load_new_containers: function(){
var self = this;
var def = new $.Deferred();
var fields = _.find(this.models,function(model){
return model.model === 'pos.container';
}).fields;
var domain = [];
rpc.query({
model: 'pos.container',
method: 'search_read',
args: [domain, fields],
}, {
timeout: 3000,
shadow: true,
})
.then(function(containers){
if (self.db.add_containers(containers)) {
// check if the partners we got were real updates
def.resolve();
} else {
def.reject();
}
}, function(type,err){ def.reject(); });
return def;
},
// load placeholder product for containers.
// it is done here to load it even if inactivated.
load_placeholder_product: function(){
var self = this;
var fields = _.find(this.models,function(model){
return model.model === 'product.product';
}).fields;
var domain = [['barcode', '=', 'CONTAINER'], ['active', '=', false]];
// no need to load it when active because it is already done in standard
return rpc.query({
model: 'product.product',
method: 'search_read',
args: [domain, fields],
}).then(function(products){
self.db.add_products(_.map(products, function (product) {
return new models.Product({}, product);
}));
});
},
// saves the container locally and try to send it to the backend.
// it returns a deferred that succeeds after having tried to send the container and all the other pending containers.
push_container: function(container, opts) {
opts = opts || {};
var self = this;
if(container){
this.db.add_containers([container]);
}
var pushed = new $.Deferred();
this.flush_mutex.exec(function(){
var flushed = self._save_containers_to_server(self.db.get_containers_sorted(), opts);
flushed.always(function(ids){
pushed.resolve();
});
return flushed;
});
return pushed;
},
// send an array of containers to the server
// available options:
// - timeout: timeout for the rpc call in ms
// returns a deferred that resolves with the list of
// server generated ids for the sent containers
_save_containers_to_server: function (containers, options) {
var self = this;
var containers= containers.filter(container => !( "id" in container))
if (!containers || !containers.length) {
var result = $.Deferred();
result.resolve([]);
return result;
}
options = options || {};
var timeout = typeof options.timeout === 'number' ? options.timeout : 7500 * containers.length;
return rpc.query({
model: 'pos.container',
method: 'create_from_ui',
args: containers,
}, {
timeout: timeout,
})
.then(function (server_ids) {
//self.db.remove_containers(containers);
_.each(containers, function(container, key){
container["id"] = server_ids[key]
});
//self.db.add_containers(containers)
self.set('failed',false);
return server_ids;
}).fail(function (type, error){
if(error.code === 200 ){ // Business Logic Error, not a connection problem
//if warning do not need to display traceback!!
if (error.data.exception_type == 'warning') {
delete error.data.debug;
}
// Hide error if already shown before ...
if ((!self.get('failed') || options.show_error) && !options.to_invoice) {
self.gui.show_popup('error-traceback',{
'title': error.data.message,
'body': error.data.debug
});
}
self.set('failed',error);
}
console.error('Failed to send containers:', containers);
});
},
// wrapper around the _save_to_server that updates the synch status widget
// it is modified to send containers before orders
_flush_orders: function(orders, options) {
var self = this;
this.set('synch',{ state: 'connecting', pending: orders.length});
return self._save_containers_to_server(self.db.get_containers_sorted())
.then(function(container_ids) {
for (var i=0; i < orders.length; i++){
if (orders[i].data.lines) {
for (var j=0; j < orders[i].data.lines[0].length; j++){
var orderline = orders[i].data.lines[0][j]
if ( !orderline.container_id && orderline.container_barcode) {
orderline.container_id = self.db.get_container_by_barcode(orderline.container_barcode).id;
delete orderline["container_barcode"]
}
}
}
}
return self._save_to_server(orders, options)
}).done(function (server_ids) {
var pending = self.db.get_orders().length;
self.set('synch', {
state: pending ? 'connecting' : 'connected',
pending: pending
});
return server_ids;
}).fail(function(error, event){
var pending = self.db.get_orders().length;
if (self.get('failed')) {
self.set('synch', { state: 'error', pending: pending });
} else {
self.set('synch', { state: 'disconnected', pending: pending });
}
});
},
});
models.Order = models.Order.extend({
add_container: function(container, options){
if(this._printed){
this.destroy();
return this.pos.get_order().add_container(container, options);
}
options = options || {};
var attr = JSON.parse(JSON.stringify(container));
attr.pos = this.pos;
attr.order = this;
var product = this.pos.get_container_product();
var line = new models.Orderline({}, {
pos: this.pos, order: this, product: product});
line.set_container(container);
line.set_quantity(0);
this.orderlines.add(line);
this.select_orderline(this.get_last_orderline());
},
has_tare_line: function(mode){
var orderlines = this.orderlines.models
for(var i=0; i < orderlines.length; i++){
var line = orderlines[i];
if(line && line.get_tare_mode() === mode){
return true;
}
}
return false;
},
export_for_printing: function(){
var orderlines = [];
var self = this;
this.orderlines.each(function(orderline){
orderlines.push(orderline.export_for_printing());
});
var paymentlines = [];
this.paymentlines.each(function(paymentline){
paymentlines.push(paymentline.export_for_printing());
});
var client = this.get('client');
var cashier = this.pos.get_cashier();
var company = this.pos.company;
var shop = this.pos.shop;
var date = new Date();
function is_xml(subreceipt){
return subreceipt ? (subreceipt.split('\n')[0].indexOf('<!DOCTYPE QWEB') >= 0) : false;
}
function render_xml(subreceipt){
if (!is_xml(subreceipt)) {
return subreceipt;
} else {
subreceipt = subreceipt.split('\n').slice(1).join('\n');
var qweb = new QWeb2.Engine();
qweb.debug = config.debug;
qweb.default_dict = _.clone(QWeb.default_dict);
qweb.add_template('<templates><t t-name="subreceipt">'+subreceipt+'</t></templates>');
return qweb.render('subreceipt',{'pos':self.pos,'widget':self.pos.chrome,'order':self, 'receipt': receipt}) ;
}
}
var receipt = {
orderlines: orderlines,
paymentlines: paymentlines,
subtotal: this.get_subtotal(),
total_with_tax: this.get_total_with_tax(),
total_without_tax: this.get_total_without_tax(),
total_tax: this.get_total_tax(),
total_paid: this.get_total_paid(),
total_discount: this.get_total_discount(),
tax_details: this.get_tax_details(),
change: this.get_change(),
name : this.get_name(),
client: client ? client.name : null ,
invoice_id: null, //TODO
cashier: cashier ? cashier.name : null,
precision: {
price: 2,
money: 2,
quantity: 3,
},
date: {
year: date.getFullYear(),
month: date.getMonth(),
date: date.getDate(), // day of the month
day: date.getDay(), // day of the week
hour: date.getHours(),
minute: date.getMinutes() ,
isostring: date.toISOString(),
localestring: date.toLocaleString(),
},
company:{
email: company.email,
website: company.website,
company_registry: company.company_registry,
contact_address: company.partner_id[1],
vat: company.vat,
vat_label: company.country && company.country.vat_label || '',
name: company.name,
phone: company.phone,
logo: this.pos.company_logo_base64,
},
shop:{
name: shop.name,
},
currency: this.pos.currency,
//custom here
has_tare_mode: {
auto: this.has_tare_line('AUTO'),
manual: this.has_tare_line('MAN'),
}
//custom end
};
if (is_xml(this.pos.config.receipt_header)){
receipt.header = '';
receipt.header_xml = render_xml(this.pos.config.receipt_header);
} else {
receipt.header = this.pos.config.receipt_header || '';
}
if (is_xml(this.pos.config.receipt_footer)){
receipt.footer = '';
receipt.footer_xml = render_xml(this.pos.config.receipt_footer);
} else {
receipt.footer = this.pos.config.receipt_footer || '';
}
return receipt;
},
});
// Add container to order line
models.Orderline = models.Orderline.extend({
get_container: function(){
return this.container;
},
set_container: function(container){
this.container = container;
},
set_tare_mode: function(mode){
if (['MAN', 'AUTO'].indexOf(mode) != -1){
this.tare_mode = mode;
this.trigger('change', this);
}
},
get_tare_mode: function() {
return this.tare_mode;
},
set_tare: function(tare){
this.tare = this.get_value_rounded(tare).toFixed(3);
this.container = null;
if (this.gross_weight && this.gross_weight != 'NaN'){
this.set_quantity(this.gross_weight - parseFloat(this.tare));
}
else{
this.set_gross_weight(this.quantity);
this.set_quantity(this.quantity - parseFloat(this.tare));
}
this.trigger('change', this);
},
get_tare: function(){
return this.tare || 0;
},
get_gross_weight: function(){
return this.gross_weight;
},
set_gross_weight: function(weight){
this.gross_weight = this.get_value_rounded(weight).toFixed(3);
this.trigger('change', this);
},
set_quantity: function(quantity, keep_price){
// copied from odoo core
this.order.assert_editable();
if(quantity === 'remove'){
this.order.remove_orderline(this);
return;
}else{
var quant = parseFloat(quantity) || 0;
var unit = this.get_unit();
if(unit){
if (unit.rounding) {
this.quantity = round_pr(quant, unit.rounding);
var decimals = this.pos.dp['Product Unit of Measure'];
this.quantity = round_di(this.quantity, decimals)
this.quantityStr = field_utils.format.float(this.quantity, {digits: [69, decimals]});
} else {
this.quantity = round_pr(quant, 1);
this.quantityStr = this.quantity.toFixed(0);
}
}else{
this.quantity = quant;
this.quantityStr = '' + this.quantity;
}
}
// just like in sale.order changing the quantity will recompute the unit price
if(! keep_price && ! this.price_manually_set){
this.set_unit_price(this.product.get_price(this.order.pricelist, this.get_quantity()));
this.order.fix_tax_included_price(this);
}
// surcharge starts here
if (this.tare){
this.set_gross_weight(this.quantity + parseFloat(this.tare));
}
this.trigger('change', this);
},
get_value_rounded: function(value){
var value = parseFloat(value) || 0;
var unit = this.get_unit();
if(unit){
if (unit.rounding) {
value = round_pr(value, unit.rounding);
var decimals = this.pos.dp['Product Unit of Measure'];
value = round_di(value, decimals)
} else {
value = round_pr(value, 1);
}
}
return value;
},
export_as_JSON: function(){
var pack_lot_ids = [];
if (this.has_product_lot){
this.pack_lot_lines.each(_.bind( function(item) {
return pack_lot_ids.push([0, 0, item.export_as_JSON()]);
}, this));
}
return {
qty: this.get_quantity(),
price_unit: this.get_unit_price(),
price_subtotal: this.get_price_without_tax(),
price_subtotal_incl: this.get_price_with_tax(),
discount: this.get_discount(),
product_id: this.get_product().id,
tax_ids: [[6, false, _.map(this.get_applicable_taxes(), function(tax){ return tax.id; })]],
id: this.id,
pack_lot_ids: pack_lot_ids,
//custom starts here
tare: this.get_tare() ? this.get_tare() : null,
container_id: this.get_container() ? this.get_container().id : null,
container_barcode: this.get_container() ? this.get_container().barcode : null,
container_weight: this.get_container() ? this.get_container().weight : null,
};
},
//used to create a json of the ticket, to be sent to the printer
export_for_printing: function(){
return {
quantity: this.get_quantity(),
unit_name: this.get_unit().name,
price: this.get_unit_price(),
discount: this.get_discount(),
product_name: this.get_product().display_name,
product_name_wrapped: this.generate_wrapped_product_name(),
price_display : this.get_display_price(),
price_with_tax : this.get_price_with_tax(),
price_without_tax: this.get_price_without_tax(),
tax: this.get_tax(),
product_description: this.get_product().description,
product_description_sale: this.get_product().description_sale,
// extension starts here
container: this.get_container(),
tare: this.get_tare(),
tare_mode: this.get_tare_mode(),
gross_weight: this.get_gross_weight(),
product_barcode: this.get_product().barcode,
};
},
});
PosDB.include({
init: function(parent, options) {
this._super(parent, options);
this.container_sorted = [];
this.container_by_id = {};
this.container_by_barcode = {};
this.container_search_string = "";
this.container_write_date = null;
},
_container_search_string: function(container){
var str = '';
if(container.barcode){
str += '|' + container.barcode;
}
if(container.name) {
str += '|' + container.name;
}
var id = container.id || 0;
str = '' + id + ':' + str.replace(':','') + '\n';
return str;
},
add_containers: function(containers) {
var updated_count = 0;
var new_write_date = '';
for(var i = 0, len = containers.length; i < len; i++) {
var container = containers[i];
if (this.container_write_date &&
this.container_by_barcode[container.barcode] &&
new Date(this.container_write_date).getTime() + 1000 >=
new Date(container.write_date).getTime() ) {
// FIXME: The write_date is stored with milisec precision in the database
// but the dates we get back are only precise to the second. This means when
// you read containers modified strictly after time X, you get back containers that were
// modified X - 1 sec ago.
continue;
} else if ( new_write_date < container.write_date ) {
new_write_date = container.write_date;
}
if (!this.container_by_barcode[container.barcode]) {
this.container_sorted.push(container.barcode);
}
this.container_by_barcode[container.barcode] = container;
updated_count += 1;
}
this.container_write_date = new_write_date || this.container_write_date;
if (updated_count) {
// If there were updates, we need to completely
// rebuild the search string and the id indexing
this.container_search_string = "";
this.container_by_id = {};
for (var barcode in this.container_by_barcode) {
var container = this.container_by_barcode[barcode];
if(container.id){
this.container_by_id[container.id] = container;
}
this.container_search_string += this._container_search_string(container);
}
}
return updated_count;
},
remove_containers: function(barcodes){
for(var i = 0; i < barcodes.length; i++) {
var container = this.container_by_barcode[barcodes[i]];
if (container){
var index_s = this.container_sorted.indexOf(container.barcode);
this.container_sorted.splice(index_s, 1);
delete this.container_by_id[container.id];
delete this.container_by_barcode[container.barcode];
}
}
},
get_container_write_date: function(){
return this.container_write_date;
},
get_container_by_id: function(id){
return this.container_by_id[id];
},
get_container_by_barcode: function(barcode){
return this.container_by_barcode[barcode];
},
get_containers_sorted: function(max_count){
max_count = max_count ? Math.min(this.container_sorted.length, max_count) : this.container_sorted.length;
var containers = [];
for (var i = 0; i < max_count; i++) {
containers.push(this.container_by_barcode[this.container_sorted[i]]);
}
return containers;
},
search_container: function(query) {
try {
query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.');
query = query.replace(' ','.+');
var re = RegExp("([0-9]+):\\|([0-9]*"+query+"[0-9]*\\|\|[0-9]*\\|.*?"+query+")","gi");
} catch(e) {
return [];
}
var results = [];
for(var i = 0; i < this.limit; i++) {
var r = re.exec(this.container_search_string);
if(r) {
// r[1] = id, r[2] = barcode
var barcode = r[2].substring(0, r[2].indexOf("\|"));
results.push(this.get_container_by_barcode(barcode));
} else {
break;
}
}
return results;
},
});
models.load_models({
model: 'pos.container',
fields: ['name','barcode', 'weight'],
loaded: function(self, containers){
self.db.add_containers(containers);
return self.load_placeholder_product();
},
});
});

244
pos_container/static/src/js/tests.js

@ -0,0 +1,244 @@
odoo.define('pos_container.tour.tare', function (require) {
"use strict";
var Tour = require('web_tour.tour');
function click_numpad(num) {
return {
content: "click on numpad button '" + num + "'",
trigger: ".input-button.number-char:contains('"+num+"')"
}
}
function scan(barcode) {
return {
content: "Scanning barcode " + barcode,
trigger: "input.ean",
run: "text " + barcode
}
}
function confirm_scan() {
return {
content: "Confirm barcode",
trigger: ".button.barcode",
}
}
function set_weight(weight) {
return {
content: "Setting weight " + weight,
trigger: "input.weight",
run: "text " + weight
}
}
function confirm_weight() {
return {
content: "Confirm weight",
trigger: ".button.set_weight",
}
}
function check_selected_orderline(message, check) {
return {
content: message,
trigger: ".orderline.selected " + check,
run: function () {}, // it's a check
}
}
var steps = [{
content: 'waiting for loading to finish',
trigger: '.o_main_content:has(.loader:hidden)',
run: function () {},
},
scan('0499999999998'),
confirm_scan(),
set_weight(0.1),
confirm_weight(),
{
content: "Click on save",
trigger: ".add-container",
},
// Test a second time with a custom name
scan('0499999999981'),
confirm_scan(),
set_weight(0.2),
confirm_weight(),
{
content: "Set a custom name",
trigger:"input.container-name",
run:"text TOTO",
}, {
content: "Click on save",
trigger: ".add-container",
},
// Scan du premier contenant
scan('0499999999998'),
confirm_scan(),
check_selected_orderline("Check: empty container in the orderline", ".product-name:contains('Container without product')"),
check_selected_orderline("Check: the name is 'Container'", ".info:contains('Container')"),
check_selected_orderline("Check: the quantity is 0", ".info em:contains('0.000')"),
{
content: "select product",
trigger: ".product:contains('Whiteboard Pen')", //UoM = kg
},
set_weight(0.2),
confirm_weight(),
{
content: "validate weight",
trigger: ".buy-product",
},
check_selected_orderline("Check: the name is 'Container'", ".info:contains('Container')"),
check_selected_orderline("Check: orderline in AUTO tare mode", ".pos-right-align:contains('AUTO')"),
check_selected_orderline("Check: orderline's product is the Pen", ".product-name:contains('Whiteboard Pen')"),
check_selected_orderline("Check: the quantity is the tared weight", ".info:contains('0.200')"),
{
content: "click container button",
trigger: ".control-button.o_container_button",
}, {
content: "Search Container TOTO",
trigger: ".searchbox input",
run: "text TOTO",
}, {
content: "Select container TOTO",
trigger: ".container-line:contains('TOTO')",
}, {
content: "Click delete",
trigger: ".button.delete-container",
}, {
content: "Click cancel",
trigger: ".button.cancel",
}, {
content: "Click delete",
trigger: ".button.delete-container",
}, {
content: "Click confirm",
trigger: ".button.confirm",
}, {
content: "Search by barcode",
trigger: ".searchbox input",
run: "text 0499999999998",
}, {
content: "select the searched container",
trigger: ".container-line:contains('Container')",
}, {
content: "confirm selection",
trigger: ".containerlist-screen .next",
}, {
content: "remove orderline quantity",
trigger: ".input-button.numpad-backspace",
}, {
content: "delete orderline",
trigger: ".input-button.numpad-backspace",
}, {
content: "select another product",
trigger: ".product:contains('Desk Organizer')", //UoM = kg
},
set_weight(0.5),
confirm_weight(),
{
content: "confirm purchase",
trigger: ".buy-product",
}, {
content: "switch numpad to tare mode",
trigger: ".control-button.o_tare_button",
},
click_numpad(0),
click_numpad('.'),
click_numpad(2),
check_selected_orderline("Check: orderline in MAN tare mode", ".pos-right-align:contains('MAN')"),
check_selected_orderline("Check: orderline's product is the Organizer", ".product-name:contains('Desk Organizer')"),
check_selected_orderline("Check: the quantity is the tared weight", ".info:contains('0.300')"),
{
content: "click orderline auto",
trigger: ".orderline .pos-right-align:contains('AUTO')",
}, {
content: "switch numpad to tare mode",
trigger: ".control-button.o_tare_button",
},
click_numpad(0),
click_numpad('.'),
click_numpad(2),
check_selected_orderline("Check: orderline in MAN tare mode", ".pos-right-align:contains('MAN')"),
check_selected_orderline("Check: orderline's product is the Pen", ".product-name:contains('Whiteboard Pen')"),
check_selected_orderline("Check: the quantity is the tared weight", ".info em:contains('0.100')"),
{
content: "switch numpad to quantity mode",
trigger: ".mode-button[data-mode='quantity']",
},
click_numpad(0),
click_numpad('.'),
click_numpad(6),
check_selected_orderline("Check: orderline in MAN tare mode", ".pos-right-align:contains('MAN')"),
check_selected_orderline("Check: orderline's product is the Pen", ".product-name:contains('Whiteboard Pen')"),
check_selected_orderline("Check: the quantity is 0.6", ".info em:contains('0.600')"),
check_selected_orderline("Check: the tare is unchanged", ".info:contains('0.2')"),
check_selected_orderline("Check: the gross weight is the tare + the quantity", ".info:contains('Gross : 0.8 kg')"),
{
content: "Add a unit product",
trigger: ".product:contains('Large Cabinet')",
}, {
content: "click discount",
trigger: ".mode-button[data-mode='discount']",
},
click_numpad(1),
click_numpad(0),
check_selected_orderline("Check: orderline in MAN tare mode", ".pos-right-align:contains('MAN')"),
check_selected_orderline("Check: the undiscounted price is still 320", ".info:contains('320.00')"),
{
content: "Add a unit product",
trigger: ".product:contains('Large Cabinet')",
}, {
content: "click price change",
trigger: ".mode-button[data-mode='price']",
},
click_numpad(2),
click_numpad(0),
click_numpad(0),
check_selected_orderline("Check: orderline in MAN tare mode", ".pos-right-align:contains('MAN')"),
// Ajouter une ligne en AUTO
scan('0499999999998'),
confirm_scan(),
{
content: "select product",
trigger: ".product:contains('Whiteboard Pen')", //UoM = kg
},
set_weight(0.3),
confirm_weight(),
{
content: "validate weight",
trigger: ".buy-product",
},
{
content: "pay",
trigger: ".button.pay",
},
click_numpad(2),
click_numpad(0),
click_numpad(0),
click_numpad(0),
{
content: "validate",
trigger: ".button.next",
}];
var autre = [{
content: "relancer POS",
trigger: "",
}, {
content: "click commandes",
trigger: "",
}, {
content: "sélectionner dernière commande",
trigger: "",
}, {
content: "click reprint",
trigger: "",
}];
Tour.register('pos_container', { test: true, url: '/pos/web' }, steps);
});

442
pos_container/static/src/xml/pos.xml

@ -0,0 +1,442 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="ContainerButton">
<button class="control-button main o_container_button">
<i class="fa fa-beer" role="img" aria-label="Container" title="Container"/>
Container
</button>
</t>
<t t-name="TareButton">
<button class="control-button second mode-button o_tare_button" data-mode="tare">
Tare
</button>
</t>
<t t-name="ContainerListScreenWidget">
<div class="containerlist-screen screen">
<div class="screen-content">
<section class="top-content">
<span class="button back">
<i class="fa fa-angle-double-left"/>
Cancel
</span>
<span class="searchbox">
<input placeholder="Search Containers"/>
<span class="search-clear"/>
</span>
<span class="searchbox"/>
<span class="button delete-container oe_hidden" role="img" aria-label="Delete container" title="Delete container">
<i class="fa fa-beer"/>
<i class="fa fa-trash"/>
</span>
<span class="button next oe_hidden highlight">
Select a container
<i class="fa fa-angle-double-right"/>
</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="container-list">
<thead>
<tr>
<th>Name</th>
<th>Barcode</th>
<th>Weight</th>
</tr>
</thead>
<tbody class="container-list-contents">
</tbody>
</table>
</div>
</div>
</section>
</div>
</section>
</div>
</div>
</t>
<t t-name="ContainerLine">
<tr class="container-line" t-att-data-id="container.barcode">
<td>
<t t-esc="container.name"/>
</td>
<td>
<t t-esc="container.barcode"/>
</td>
<td>
<t t-esc="container.weight"/>
</td>
</tr>
</t>
<t t-extend='Orderline'>
<t t-jquery='.info-list:last-child' t-operation='prepend'>
<t t-if="line.get_container()">
<ul class="info-list">
<ul class="info-list">
<i><t t-if="line.get_gross_weight()">
<li class="info">
Gross : <t t-esc="line.get_gross_weight()"/> kg
</li>
</t>
<li class="info">
<i class='fa fa-beer'/> :
<t t-esc="line.get_container().weight"/> kg
-
<t t-esc="line.get_container().name"/>
</li></i>
</ul>
</ul>
</t>
<t t-if="line.get_tare()">
<ul class="info-list">
<ul class="info-list">
<i><t t-if="line.get_gross_weight()">
<li class="info">
Gross : <t t-esc="line.get_gross_weight()"/> kg
</li>
</t>
<li class="info">
<i class='fa fa-beer'/> :
<t t-esc="line.get_tare()"/> kg
- Manual tare
</li></i>
</ul>
</ul>
</t>
<div class="pos-right-align">
<t t-esc="line.get_tare_mode()"/>
</div>
</t>
</t>
<t t-name="ContainerScaleScreenWidget">
<div class="scale-screen screen">
<div class="screen-content">
<div class="top-content">
<span class='button back'>
<i class='fa fa-angle-double-left'></i>
Back
</span>
<h1 class='product-name'>Add a container</h1>
</div>
<div class="centered-content">
<div class='weight js-weight'>
<t t-esc="widget.get_product_weight_string()" />
</div>
<div class='container-name'>
<span class='label'>Container name: </span><br/>
<input class='detail container-name' name='container_name' placeholder="Container"></input>
</div>
<div class='add-container'>
Save
<i class='fa fa-angle-double-right'></i>
</div>
</div>
</div>
</div>
</t>
<t t-extend="PosTicket">
<t t-jquery='.receipt-orderlines' t-operation='inner'>
<table>
<colgroup>
<col width='50%' />
<col width='25%' />
<col width='25%' />
</colgroup>
<tr t-foreach="orderlines" t-as="orderline">
<t t-if="orderline.get_tare_mode() == undefined
and orderline.product.barcode != 'CONTAINER'">
<td>
<t t-esc="orderline.get_product().display_name"/>
<t t-if="orderline.get_discount() > 0">
<div class="pos-disc-font">
With a <t t-esc="orderline.get_discount()"/>% discount
</div>
</t>
</td>
<td class="pos-right-align">
<t t-esc="orderline.get_quantity_str_with_unit()"/>
</td>
<td class="pos-right-align">
<t t-esc="widget.format_currency(orderline.get_display_price())"/>
</td>
</t>
</tr>
</table>
<t t-if="order.has_tare_line('AUTO')">
<table>
<colgroup>
<col width='20%' />
<col width='60%' />
<col width='20%' />
</colgroup>
<td></td>
<td>
<center>------------------------</center>
<center>Automatic Weighing</center>
<center>------------------------</center>
</td>
</table>
</t>
<table>
<colgroup>
<col width='50%' />
<col width='25%' />
<col width='25%' />
</colgroup>
<tr t-foreach="orderlines" t-as="orderline">
<t t-if="orderline.get_tare_mode() == 'AUTO'">
<tr>
<td>
<t t-esc="orderline.get_product().display_name"/>
<t t-if="orderline.get_discount() > 0">
<div class="pos-disc-font">
With a <t t-esc="orderline.get_discount()"/>% discount
</div>
</t>
</td>
<td/>
<td class="pos-right-align">
<t t-esc="widget.format_currency(orderline.get_display_price())"/>
</td>
</tr>
<tr>
<td>
<div style="margin-left:5%">
<t t-esc="orderline.get_quantity_str_with_unit()"/>
x
<t t-esc="widget.format_currency(orderline.get_unit_display_price())"/>/<t t-esc="orderline.get_unit().name"/>
</div>
</td>
</tr>
<t t-if="orderline.get_container()">
<tr>
<td>
<div style="margin-left:5%">
Tare : <t t-esc="orderline.get_container().weight"/>
<t t-esc="orderline.get_unit().name"/>
</div>
</td>
</tr>
</t>
<t t-if="orderline.get_tare()">
<tr>
<td>
<div style="margin-left:5%">
Tare : <t t-esc="orderline.get_tare()"/>
<t t-esc="orderline.get_unit().name"/>
</div>
</td>
</tr>
</t>
</t>
</tr>
</table>
<t t-if="order.has_tare_line('MAN')">
<table>
<colgroup>
<col width='20%' />
<col width='60%' />
<col width='20%' />
</colgroup>
<td></td>
<td>
<center>------------------------</center>
<center>Manual Input</center>
<center>------------------------</center>
</td>
</table>
</t>
<table>
<colgroup>
<col width='50%' />
<col width='25%' />
<col width='25%' />
</colgroup>
<tr t-foreach="orderlines" t-as="orderline">
<t t-if="orderline.get_tare_mode() == 'MAN'">
<tr>
<td>
<t t-esc="orderline.get_product().display_name"/>
<t t-if="orderline.get_discount() > 0">
<div class="pos-disc-font">
With a <t t-esc="orderline.get_discount()"/>% discount
</div>
</t>
</td>
<td class="pos-right-align">
<t t-esc="orderline.get_quantity_str_with_unit()"/>
</td>
<td class="pos-right-align">
<t t-esc="widget.format_currency(orderline.get_display_price())"/>
</td>
</tr>
<t t-if="orderline.get_tare()">
<tr>
<td>
<div style="margin-left:5%">
Tare : <t t-esc="orderline.tare"/>
<t t-esc="orderline.get_unit().name"/>
</div>
</td>
</tr>
</t>
</t>
</tr>
</table>
</t>
</t>
<t t-extend="XmlReceipt">
<t t-jquery='.orderlines' t-operation='inner'>
<t t-foreach='receipt.orderlines' t-as='line'>
<t t-if="line.tare_mode == undefined and line.product_barcode != 'CONTAINER'">
<t t-set='simple' t-value='line.discount === 0 and line.unit_name === "Unit(s)" and line.quantity === 1' />
<t t-if='simple'>
<line>
<left><t t-esc='line.product_name_wrapped[0]' /></left>
<right><value t-att-value-decimals='pos.currency.decimals'><t t-esc='line.price_display' /></value></right>
</line>
<t t-call="XmlReceiptWrappedProductNameLines"/>
</t>
<t t-if='!simple'>
<line><left><t t-esc='line.product_name_wrapped[0]' /></left></line>
<t t-call="XmlReceiptWrappedProductNameLines"/>
<t t-if='line.discount !== 0'>
<line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line>
</t>
<line indent='1'>
<left>
<value t-att-value-decimals='pos.dp["Product Unit of Measure"]' value-autoint='on'>
<t t-esc='line.quantity' />
</value>
<t t-if='line.unit_name !== "Unit(s)"'>
<t t-esc='line.unit_name' />
</t>
x
<value t-att-value-decimals='pos.dp["Product Price"]'>
<t t-esc='line.price' />
</value>
</left>
<right>
<value t-att-value-decimals='pos.currency.decimals'><t t-esc='line.price_display' /></value>
</right>
</line>
</t>
</t>
</t>
<div>------------------</div>
<t t-if="receipt.has_tare_mode.auto">
Automatic Weighing
<div>------------------</div>
<t t-foreach='receipt.orderlines' t-as='line'>
<t t-if="line.tare_mode == 'AUTO'">
<t t-set='simple' t-value='line.discount === 0 and line.unit_name === "Unit(s)" and line.quantity === 1' />
<t t-if='simple'>
<line>
<left><t t-esc='line.product_name_wrapped[0]' /></left>
<right><value t-att-value-decimals='pos.currency.decimals'><t t-esc='line.price_display' /></value></right>
</line>
<t t-call="XmlReceiptWrappedProductNameLines"/>
</t>
<t t-if='!simple'>
<line><left><t t-esc='line.product_name_wrapped[0]' /></left></line>
<t t-call="XmlReceiptWrappedProductNameLines"/>
<t t-if='line.discount !== 0'>
<line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line>
</t>
<line indent='1'>
<left>
<value t-att-value-decimals='pos.dp["Product Unit of Measure"]' value-autoint='on'>
<t t-esc='line.quantity' />
</value>
<t t-if='line.unit_name !== "Unit(s)"'>
<t t-esc='line.unit_name' />
</t>
x
<value t-att-value-decimals='pos.dp["Product Price"]'>
<t t-esc='line.price' />
</value>
</left>
<right>
<value t-att-value-decimals='pos.currency.decimals'><t t-esc='line.price_display' /></value>
</right>
</line>
<t t-if='line.container'>
<line indent='1'>
<left>
Tare : <t t-esc='line.container.weight' /><t t-esc='line.unit_name' />
</left>
</line>
</t>
<t t-if='line.tare'>
<line indent='1'>
<left>
Tare : <t t-esc='line.tare' /><t t-esc='line.unit_name' />
</left>
</line>
</t>
</t>
</t>
</t>
<div>------------------</div>
</t>
<t t-if="receipt.has_tare_mode.manual">
Manual Input
<div>------------------</div>
<t t-foreach='receipt.orderlines' t-as='line'>
<t t-if="line.tare_mode == 'MAN'">
<t t-set='simple' t-value='line.discount === 0 and line.unit_name === "Unit(s)" and line.quantity === 1' />
<t t-if='simple'>
<line>
<left><t t-esc='line.product_name_wrapped[0]' /></left>
<right><value t-att-value-decimals='pos.currency.decimals'><t t-esc='line.price_display' /></value></right>
</line>
<t t-call="XmlReceiptWrappedProductNameLines"/>
</t>
<t t-if='!simple'>
<line><left><t t-esc='line.product_name_wrapped[0]' /></left></line>
<t t-call="XmlReceiptWrappedProductNameLines"/>
<t t-if='line.discount !== 0'>
<line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line>
</t>
<line indent='1'>
<left>
<value t-att-value-decimals='pos.dp["Product Unit of Measure"]' value-autoint='on'>
<t t-esc='line.quantity' />
</value>
<t t-if='line.unit_name !== "Unit(s)"'>
<t t-esc='line.unit_name' />
</t>
x
<value t-att-value-decimals='pos.dp["Product Price"]'>
<t t-esc='line.price' />
</value>
</left>
<right>
<value t-att-value-decimals='pos.currency.decimals'><t t-esc='line.price_display' /></value>
</right>
</line>
<t t-if='line.tare'>
<line indent='1'>
<left>
Tare : <t t-esc='line.tare' /><t t-esc='line.unit_name' />
</left>
</line>
</t>
</t>
</t>
</t>
</t>
</t>
</t>
</templates>

17
pos_container/templates/templates.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Coop IT Easy SCRLfs
Robin Keunen <robin@coopiteasy.be>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<template id="assets_frontend" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<link href="/pos_container/static/src/css/container.css" rel="stylesheet"/>
<script type="text/javascript" src="/pos_container/static/src/js/models_and_db.js"/>
<script type="text/javascript" src="/pos_container/static/src/js/container.js"/>
<script type="text/javascript" src="/pos_container/static/src/js/tests.js"/>
</xpath>
</template>
</odoo>

1
pos_container/tests/__init__.py

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

14
pos_container/tests/test_pos_container.py

@ -0,0 +1,14 @@
import odoo.tests
@odoo.tests.tagged('pos_install', '-at-install')
class TestUi(odoo.tests.HttpCase):
def test_01_pos_container_tour(self):
self.phantom_js(
"/web",
"odoo.__DEBUG__.services['web_tour.tour']" +
".run('pos_container_tour')",
"odoo.__DEBUG__.services['web_tour.tour']" +
".tours.pos_container_tour.ready",
login="admin")

43
pos_container/views/container.xml

@ -0,0 +1,43 @@
<odoo>
<record id="pos_container_tree_view" model="ir.ui.view">
<field name="name">pos.container.tree</field>
<field name="model">pos.container</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="barcode"/>
<field name="weight"/>
</tree>
</field>
</record>
<record id="pos_container_form_view" model="ir.ui.view">
<field name="name">pos.container.form</field>
<field name="model">pos.container</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="barcode"/>
<field name="weight"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="pos_container_action_window" model="ir.actions.act_window" >
<field name="name">Containers</field>
<field name="res_model">pos.container</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="pos_container_menu"
name="Containers"
parent="point_of_sale.pos_config_menu_catalog"
sequence="13"
action="pos_container_action_window"
groups="point_of_sale.group_pos_manager,point_of_sale.group_pos_user"
/>
</odoo>

3
pos_customer_display_currency/__init__.py

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

33
pos_customer_display_currency/__manifest__.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Coop IT Easy SCRLfs
# Vincent Van Rossem <vvrossem@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Currency Management Extension to POS Customer Display',
'version': '12.0.1.0.0',
'category': 'Point Of Sale',
'summary': """
Add Currency Management to POS Customer Display device""",
"author": "Coop IT Easy SCRLfs, "
"Odoo Community Association (OCA)",
'license': 'AGPL-3',
'depends': [
'pos_customer_display'
],
'data': [
'templates/templates.xml',
'views/pos_config_view.xml',
],
'demo': [],
'installable': True,
}

3
pos_customer_display_currency/models/__init__.py

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

30
pos_customer_display_currency/models/pos_customer_display_currency.py

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Coop IT Easy SCRLfs
# Vincent Van Rossem <vvrossem@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class PosConfig(models.Model):
_inherit = 'pos.config'
# TODO(Vincent) add field customer_display_currency_code or use/fetch Odoo's default currency
customer_display_currency_char = fields.Char(
size=1,
required=True,
string="User-defined Currency Character", default="$",
help="The ASCII character to use for drawing the default currency symbol on the device.")
@api.constrains('customer_display_currency_char')
def _check_currency_char(self):
"""
char_code can only be defined between character hex codes \x20 and \xFF
(or decimal codes 32 and 255)
"""
self.ensure_one()
if not 32 <= ord(self.customer_display_currency_char) <= 255:
raise ValidationError("User-defined Currency Character must be defined between "
"character codes 20h (32) and FFh (255)")

310
pos_customer_display_currency/static/src/js/pos_customer_display_currency.js

@ -0,0 +1,310 @@
/*
Copyright 2019 Coop IT Easy SCRLfs
Vincent Van Rossem <vvrossem@gmail.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
odoo.define('pos_customer_display_currency.pos_customer_display_currency', function (require) {
"use strict";
var chrome = require('point_of_sale.chrome');
var core = require('web.core');
var utils = require('web.utils')
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 timerID_total;
var timerID_updated;
var round_pr = utils.round_precision;
models.PosModel = models.PosModel.extend({
/**
* Prepare and send the currency data to the customer display device
*/
prepare_currency_data_customer_display: function () {
var currency_data = {
'currency_code': this.currency.name || this.company_currency.name,
'currency_char': this.config.customer_display_currency_char
};
this.proxy.send_currency_data_customer_display(currency_data);
},
/**
* Prepare and send a text to the customer display device according to param type
* @override prepare_text_customer_display from customer_display.js (module pos_customer_display)
* @param {string} type
* @param data
*/
prepare_text_customer_display: function (type, data) {
if (!this.config.iface_customer_display)
return;
var line_length = this.config.customer_display_line_length || 20;
var currency_rounding = this.currency.decimals;
var currency_char = this.config.customer_display_currency_char;
var previous_lines_to_send, updated_lines_to_send, lines_to_send, total_lines = [];
var line, product, container = {};
var l21 = "";
var l22 = "";
var total = "";
var mode = $('.selected-mode').attr('data-mode'); // numpad selected mode
if (timerID_updated){
clearTimeout(timerID_updated);
}
if (timerID_total){
clearTimeout(timerID_total);
}
switch(type) {
case 'add_update_line':
line = data['line'];
if ((line.qty_manually_set && mode === 'quantity') || mode === 'tare' || mode === 'price') {
// first display "Manual Entry"
l21 = line.get_quantity_str_with_uom()
+ 'x'
+ line.get_unit_price_with_uom_currency(currency_char, currency_rounding);
previous_lines_to_send = [
this.proxy.align_left(_t('Manual Entry'), line_length),
this.proxy.align_right(l21, line_length)
];
this.proxy.send_text_customer_display(previous_lines_to_send, line_length);
// then display updated product after 3s
l22 = ' ' + line.get_display_price().toFixed(currency_rounding) + currency_char;
updated_lines_to_send = [
this.proxy.align_left(line.get_product().display_name, line_length - l22.length) + l22,
this.proxy.align_left(l21, line_length)
];
timerID_updated = setTimeout(function () {
this.proxy.send_text_customer_display(updated_lines_to_send, line_length);
}.bind(this),3000);
} else if (!line.qty_manually_set && mode === 'quantity') {
// first display added product
l21 = line.get_quantity_str_with_uom()
+ 'x'
+ line.get_unit_price_with_uom_currency(currency_char, currency_rounding);
l22 = ' ' + line.get_display_price().toFixed(currency_rounding) + currency_char;
previous_lines_to_send = [
this.proxy.align_left(line.get_product().display_name, line_length - l22.length) + l22,
this.proxy.align_left(l21, line_length)
];
this.proxy.send_text_customer_display(previous_lines_to_send, line_length);
} else if (mode === 'discount') {
// first display discount information
var discount = line.get_discount();
l22 = ' ' + line.get_base_price().toFixed(currency_rounding) + currency_char;
previous_lines_to_send = [
this.proxy.align_left(line.get_product().display_name, line_length - l22.length) + l22,
this.proxy.align_left(_t("Discount:") + discount + '%', line_length)
];
this.proxy.send_text_customer_display(previous_lines_to_send, line_length);
// then display updated product after 3s
l21 = line.get_quantity_str_with_uom()
+ 'x'
+ line.get_base_price().toFixed(currency_rounding) + currency_char;
updated_lines_to_send = [
this.proxy.align_left(line.get_product().display_name, line_length - l22.length) + l22,
this.proxy.align_left(l21, line_length)
];
timerID_updated = setTimeout(function () {
this.proxy.send_text_customer_display(updated_lines_to_send, line_length);
}.bind(this),3000);
}
break;
case 'add_container':
line = data['line'];
container = line.get_container();
previous_lines_to_send = [
this.proxy.align_left(container.name, line_length),
this.proxy.align_right(container.weight.toString() + ' kg', line_length)
];
this.proxy.send_text_customer_display(previous_lines_to_send, line_length);
break;
case 'remove_orderline':
// first click on the backspace button set the amount to 0
// => we can't precise the deleted quantity and price
line = data['line'];
product = line.get_product();
if (line.container) {
lines_to_send = [
this.proxy.align_left(_t("Delete Container"), line_length),
this.proxy.align_right(line.container.name, line_length)
];
} else if (product) {
lines_to_send = [
this.proxy.align_left(_t("Delete Item"), line_length),
this.proxy.align_right(product.display_name, line_length)
];
}
this.proxy.send_text_customer_display(lines_to_send, line_length);
break;
case 'add_paymentline':
total = this.get('selectedOrder').get_total_with_tax().toFixed(currency_rounding) + currency_char;
lines_to_send = [
this.proxy.align_left(_t("TOTAL:"), line_length),
this.proxy.align_right(total, line_length)
];
this.proxy.send_text_customer_display(lines_to_send, line_length);
break;
case 'remove_paymentline':
line = data['line'];
var amount = line.get_amount().toFixed(currency_rounding) + currency_char;
lines_to_send = [
this.proxy.align_left(_t("Cancel Payment"), line_length),
this.proxy.align_right(line.cashregister.journal_id[1], line_length - 1 - amount.length) + ' ' + amount
];
this.proxy.send_text_customer_display(lines_to_send, line_length);
break;
case 'update_payment':
var change = data['change'] + currency_char;
lines_to_send = [
this.proxy.align_left(_t("Your Change:"), line_length),
this.proxy.align_right(change, line_length)
];
this.proxy.send_text_customer_display(lines_to_send, line_length);
break;
// same display for both types
case 'push_order':
case 'openPOS':
lines_to_send = [
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)
];
this.proxy.send_text_customer_display(lines_to_send, line_length);
break;
case 'closePOS':
lines_to_send = [
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)
];
this.proxy.send_text_customer_display(lines_to_send, line_length);
break;
default:
console.warn('Unknown message type');
return;
}
if ((type === 'add_update_line' && mode === 'quantity' && !line.qty_manually_set)
|| type === 'add_container' || type === 'remove_orderline') {
// display total after 3s
var order = this.get('selectedOrder');
if (order){
total = order.get_total_with_tax().toFixed(currency_rounding) + currency_char;
total_lines = [
this.proxy.align_left(_t("TOTAL:"), line_length),
this.proxy.align_right(total, line_length)
];
timerID_total = setTimeout(function() {this.proxy.send_text_customer_display(total_lines, line_length); }.bind(this), 3000);
}
} else if ((type === 'add_update_line' && mode === 'quantity' && line.qty_manually_set)
|| mode === 'price' || mode === 'discount' || mode === 'tare') {
// then display total after 6s
total = this.get('selectedOrder').get_total_with_tax().toFixed(currency_rounding) + currency_char;
total_lines = [
this.proxy.align_left(_t("TOTAL:"), line_length),
this.proxy.align_right(total, line_length)
];
timerID_total = setTimeout(function() {this.proxy.send_text_customer_display(total_lines, line_length); }.bind(this), 6000);
}
},
});
devices.ProxyDevice = devices.ProxyDevice.extend({
/**
* Send currency data to the customer display device
* @param currency_data
* @returns {*}
*/
send_currency_data_customer_display: function (currency_data) {
return this.message('send_currency_data_customer_display',
{
'currency_data': JSON.stringify(currency_data)
});
},
});
models.Orderline = models.Orderline.extend({
/**
* This function is for the Bixolon only
* Returns quantityStr with or without the name of the unit of measure (uom)
*/
get_quantity_str_with_uom: function(){
var unit = this.get_unit();
if(unit && !unit.is_pos_groupable){
return this.quantityStr + '' + unit.name;
}else{
return round_pr(this.quantity, 0.1);
}
},
/**
* This function is for the Bixolon display device only.
* Returns the unit price with the user-defined currency symbol (default: '$')
*/
get_unit_price_with_uom_currency: function(currency_char, currency_rounding){
var unit = this.get_unit();
if(unit && !unit.is_pos_groupable){
return this.get_unit_price().toFixed(currency_rounding) + currency_char + '/' + unit.name;
}else{
return this.get_unit_price().toFixed(currency_rounding) + currency_char;
}
},
});
var OrderSuper = models.Order;
models.Order = models.Order.extend({
/**
* @extends add_container from models_and_db.js (module: pos_container)
*/
add_container: function(container, options){
// invoke parent object's implementation
var res = OrderSuper.prototype.add_container.call(this, container, options);
if (container){
// parent ends with this.select_orderline(this.get_last_orderline());
var line = this.get_last_orderline();
this.pos.prepare_text_customer_display('add_container', {'line': line });
}
return res;
},
});
/**
* Fetch and set currency data when the ProxyStatusWidget starts
*/
chrome.ProxyStatusWidget.include({
start: function () {
this._super();
this.pos.prepare_currency_data_customer_display();
},
});
screens.OrderWidget.include({
set_value: function(val) {
var order = this.pos.get_order();
var selected_orderline = order.get_selected_orderline();
if (selected_orderline) {
var mode = this.numpad_state.get('mode');
if( mode === 'quantity' ) {
selected_orderline.qty_manually_set = true;
}
}
this._super(val);
},
});
});

10
pos_customer_display_currency/templates/templates.xml

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

17
pos_customer_display_currency/views/pos_config_view.xml

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<odoo>
<record id="view_pos_config_form" model="ir.ui.view">
<field name="name">pos_customer_display_currency.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="//field[@name='customer_display_line_length']/.." position="before">
<div class="row">
<label for="customer_display_currency_char" class="col-lg-3 o_light_label"/>
<field name="customer_display_currency_char"/>
</div>
</xpath>
</field>
</record>
</odoo>

105
pos_loyalty/README.rst

@ -0,0 +1,105 @@
===============
Loyalty Program
===============
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! 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_loyalty
: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_loyalty
: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 allows you to define a loyalty program in the point of sale,
where the customers earn loyalty points and get rewards.
This module is a forward-port to v10 of the pos_loyalty module from Odoo's
saas-6 branch.
The functionality was moved to the Enterprise edition in later versions.
**Table of contents**
.. contents::
:local:
Configuration
=============
To use this module, you need to:
* Go to *Point of Sale > Configuration > Loyalty Programs* and define a new loyalty program with specific rules and rewards.
* Assign the loyalty program to the desired Point of Sale.
Usage
=====
The Loyalty Program defines rules for acquiring points and rewards on which they can be spent.
Rules can be defined globally for all products (fields on loyalty.program) and / or rules that are applied only on specific product or PoS category (loyalty.rule records) on a *points per product sold* or *points per currency spent* basis. The specific rules (loyalty.rule) can be defined as cumulative, which means that they will be aggregated with other matching rules (loyalty.rule records and loyalty.program fields). In the case of non-cumulative rules only the points from that one matching rule are used. Additionally, *fixed points per order* can be added which are applied regardless of whether or not cumulative or non-cumulative rules were applied also.
Rewards can be of three types:
* *Gift* - give a single unit of product for free
* *Discount* - give a discount to the whole order. It should be added at the end of the order so that the correct total price is used.
* *Resale* - allow for customer to sell back his earned points. These are calculated by setting the price on the Resale product (*resale_product.list_price* * *customer.loyalty_points*)
All rewards can define how many points they cost (point_cost) and how many are needed so that the customer can become eligable for the reward (minimum_points). for Gift and Discount rewards minimum_points are considered only if they are greater then the point_cost for that reward (minimum_points > point_cost). For Resale products only minimum_points can be used.
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_loyalty%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
~~~~~~~
* OpenERP SA
* RGB Consulting SL
* Lambda IS
Contributors
~~~~~~~~~~~~
* RGB Consulting SL (http://www.rgbconsulting.com)
* Forward-port from Odoo SA saas-6 branch
* Kiril Vangelovski <kiril@lambda-is.com>
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_loyalty>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

3
pos_loyalty/__init__.py

@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

36
pos_loyalty/__manifest__.py

@ -0,0 +1,36 @@
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Loyalty Program',
'version': '12.0.1.0.0',
'category': 'Point of Sale',
'license': 'AGPL-3',
'author': "OpenERP SA, "
"RGB Consulting SL, "
"Lambda IS, "
"Odoo Community Association (OCA)",
'website': "https://odoo-community.org/",
'depends': ['point_of_sale'],
'demo': [
'demo/templates.xml',
],
'data': [
'security/ir.model.access.csv',
'views/templates.xml',
'views/loyalty_program_view.xml',
'views/loyalty_reward_view.xml',
'views/loyalty_rule_view.xml',
'views/pos_config_view.xml',
'views/pos_order_view.xml',
'views/res_partner_view.xml',
],
'qweb': [
'static/src/xml/pos.xml',
],
'installable': True,
}

8
pos_loyalty/demo/templates.xml

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

523
pos_loyalty/i18n/es.po

@ -0,0 +1,523 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_loyalty
#
# Translators:
# OCA Transbot <transbot@odoo-community.org>, 2017
# enjolras <yo@miguelrevilla.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-01 02:01+0000\n"
"PO-Revision-Date: 2018-03-01 02:01+0000\n"
"Last-Translator: enjolras <yo@miguelrevilla.com>, 2017\n"
"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_order
msgid ""
"Amount of loyalty points given to the customer for each point of sale order"
msgstr ""
"Cantidad de puntos de fidelidad añadidos al cliente por cada pedido del "
"punto de venta"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_product
msgid "Amount of loyalty points given to the customer per product sold"
msgstr ""
"Cantidad de puntos de fidelidad añadidos al cliente por producto vendido"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_currency
msgid "Amount of loyalty points given to the customer per sold currency"
msgstr "Cantidad de puntos de fidelidad añadidos al cliente por moneda vendida"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Amount of points earned per currency"
msgstr "Cantidad de puntos ganados por moneda"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_product
msgid "Amount of points earned per product"
msgstr "Cantidad de puntos ganados por producto"
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Category"
msgstr "Categoría"
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid "Click create to define a Loyalty Program."
msgstr "Haga clic en crear para definir un Programa de Fidelización"
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_res_partner
msgid "Contact"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_cost
msgid "Cost of the reward per monetary unit discounted"
msgstr "Coste de la recompensa por unidad monetaria descontada"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_uid
msgid "Created by"
msgstr "Creado por"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_date
msgid "Created on"
msgstr "Creado el"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_cumulative
msgid "Cumulative"
msgstr "Acumulativo"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount
#: selection:loyalty.reward,type:0
msgid "Discount"
msgstr "Descuento"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "Discount Product"
msgstr "Producto de descuento"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_max
msgid "Discount limit"
msgstr "Límite de descuento"
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:64
#, python-format
msgid "Discount product field is mandatory for discount rewards"
msgstr ""
"El campo Producto de descuento es obligatorio para las recompensas de tipo "
"descuento."
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_display_name
msgid "Display Name"
msgstr "Nombre a mostrar"
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Gift"
msgstr "Regalo"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "Gift Product"
msgstr "Producto de regalo"
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:57
#, python-format
msgid "Gift product field is mandatory for gift rewards"
msgstr ""
"El campo Producto de regalo es obligatorio para las recompensas de tipo "
"regalo"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_id
msgid "ID"
msgstr "ID"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule___last_update
msgid "Last Modified on"
msgstr "Última modificación el"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_uid
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_date
msgid "Last Updated on"
msgstr "Última actualización el"
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order_line
#, fuzzy
msgid "Lines of Point of Sale Orders"
msgstr "Pedidos del TPV"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_order_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_users_loyalty_points
msgid "Loyalty Points"
msgstr "Puntos de fidelidad"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_config_loyalty_id
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Loyalty Program"
msgstr "Programa de fidelización"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
#, fuzzy
msgid "Loyalty Program (OCA)"
msgstr "Programa de fidelización"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_name
msgid "Loyalty Program Name"
msgstr "Nombre programa de fidelización"
#. module: pos_loyalty
#: model:ir.actions.act_window,name:pos_loyalty.loyalty_program_action
#: model:ir.ui.menu,name:pos_loyalty.loyalty_program_menu
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_tree_view
#: model:ir.ui.view,arch_db:pos_loyalty.partner_property_form_view
#: model:ir.ui.view,arch_db:pos_loyalty.pos_order_form_view
msgid "Loyalty Programs"
msgstr "Programas de fidelización"
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid ""
"Loyalty Programs allow you customers to earn points\n"
" and rewards when purchasing from your shops."
msgstr ""
"Los programas de fidelización le permiten a los clientes ganar puntos\n"
" y recompensas al comprar en tus tiendas."
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_reward_form_view
msgid "Loyalty Reward"
msgstr "Recompensa de fidelización"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_rule_form_view
msgid "Loyalty Rule"
msgstr "Regla de fidelización"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_rounding
msgid "Loyalty point amounts will be rounded to multiples of this value"
msgstr "Los puntos de fidelidad se redondearán a múltiplos de este valor"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
#, fuzzy
msgid "Loyalty program that will be available in this PoS"
msgstr "El programa de fidelización al que pertenece esta regla"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_max
msgid "Maximum discounted amount allowed forthis discount reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_minimum_points
msgid "Minimum Points"
msgstr "Puntos mínimos"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_minimum_points
msgid ""
"Minimum amount of points the customer must have to qualify for this reward"
msgstr "Cantidad mínima de puntos de cliente para acceder a esta recompensa"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:379
#, python-format
msgid "No Rewards Available"
msgstr "No hay recompensas disponibles"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:393
#, python-format
msgid "Please select a reward"
msgstr "Selecciona una recompensa"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_cost
msgid "Point Cost"
msgstr "Coste Puntos"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Point Product"
msgstr "Producto Punto"
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order
msgid "Point of Sale Orders"
msgstr "Pedidos del TPV"
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:72
#, python-format
msgid "Point product field is mandatory for point resale rewards"
msgstr ""
"El campo Producto Punto es obligatorio para recompensas de reventa de puntos"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:6
#: code:addons/pos_loyalty/static/src/xml/pos.xml:71
#: code:addons/pos_loyalty/static/src/xml/pos.xml:81
#, python-format
msgid "Points"
msgstr "Puntos"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rounding
msgid "Points Rounding"
msgstr "Redondeo Puntos"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:57
#, python-format
msgid "Points Spent"
msgstr "Puntos Gastados"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:54
#, python-format
msgid "Points Won"
msgstr "Puntos Ganados"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_currency
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Points per currency"
msgstr "Puntos por moneda"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_order
msgid "Points per order"
msgstr "Puntos por venta"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_product
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_product
msgid "Points per product"
msgstr "Puntos por producto"
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Product"
msgstr "Producto"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Product that represents a point that is sold by the customer"
msgstr "Producto que representa un punto vendido por el cliente"
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Resale"
msgstr "Reventa"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_name
msgid "Reward Name"
msgstr "Nombre recompensa"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Reward the customer with gifts or discounts for loyalty points"
msgstr ""
"Recompensar al cliente con regalos o descuentos por puntos de fidelidad"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:25
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_reward_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
#, python-format
msgid "Rewards"
msgstr "Recompensas"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_name
msgid "Rule Name"
msgstr "Nombre de regla"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rule_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Rules"
msgstr "Reglas"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid ""
"Rules define how loyalty points are earned for specific products or "
"categories"
msgstr ""
"Las reglas definen cómo se obtienen puntos de fidelidad para productos o "
"categorías específicos"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_category_id
msgid "Target Category"
msgstr "Categoría afectada"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_product_id
msgid "Target Product"
msgstr "Producto afectado"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_loyalty_program_id
msgid "The Loyalty Program this reward belongs to"
msgstr "El programa de fidelización al que pertenece esta recompensa"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_loyalty_program_id
msgid "The Loyalty Program this rule belongs to"
msgstr "El programa de fidelización al que pertenece esta regla"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_order_loyalty_points
msgid "The amount of Loyalty points awarded to the customer with this order"
msgstr ""
"La cantidad de puntos de fidelidad que el cliente obtiene con este pedido"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_category_id
msgid "The category affected by this rule"
msgstr "La categoría afectada por esta regla"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_type
msgid "The concept this rule applies to"
msgstr "El concepto al que aplica esta regla"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount
msgid "The discount percentage"
msgstr "El porcentaje de descuento"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,help:pos_loyalty.field_res_users_loyalty_points
msgid "The loyalty points the user won as part of a Loyalty Program"
msgstr ""
"Los puntos de fidelidad que el usuario ganó como parte de un Programa de "
"Fidelización"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_config_loyalty_id
msgid "The loyalty program used by this Point of Sale"
msgstr "El programa de fidelización utilizado en este Punto de Venta"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_cumulative
msgid ""
"The points from this rule will be added to points won from other rules with "
"the same concept"
msgstr ""
"Los puntos de esta regla se añadirán a los puntos ganados en otras reglas "
"con el mismo concepto"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_product_id
msgid "The product affected by this rule"
msgstr "El producto afectado por esta regla"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "The product given as a reward"
msgstr "El producto utilizado como recompensa"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "The product used to apply discounts"
msgstr "El producto utilizado para aplicar descuentos"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:380
#, python-format
msgid ""
"There are no rewards available for this customer as part of the loyalty "
"program"
msgstr ""
"No hay recompensas disponibles para este cliente como parte de este programa "
"de fidelización"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:60
#, python-format
msgid "Total Points"
msgstr "Total Puntos"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_type
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_type
msgid "Type"
msgstr "Tipo"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_type
msgid "Type of the reward"
msgstr "Tipo de recompensa"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "abc"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_program
msgid "loyalty.program"
msgstr "loyalty.program"
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_reward
msgid "loyalty.reward"
msgstr "loyalty.reward"
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_rule
msgid "loyalty.rule"
msgstr "loyalty.rule"
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_config
msgid "pos.config"
msgstr "pos.config"
#~ msgid "Partner"
#~ msgstr "Empresa"

504
pos_loyalty/i18n/fr.po

@ -0,0 +1,504 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_loyalty
#
# Translators:
# leemannd <denis.leemann@camptocamp.com>, 2017
# OCA Transbot <transbot@odoo-community.org>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-01 02:01+0000\n"
"PO-Revision-Date: 2018-03-01 02:01+0000\n"
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_order
msgid ""
"Amount of loyalty points given to the customer for each point of sale order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_product
msgid "Amount of loyalty points given to the customer per product sold"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_currency
msgid "Amount of loyalty points given to the customer per sold currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Amount of points earned per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_product
msgid "Amount of points earned per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Category"
msgstr "Catégorie"
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid "Click create to define a Loyalty Program."
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_res_partner
msgid "Contact"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_cost
msgid "Cost of the reward per monetary unit discounted"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_uid
msgid "Created by"
msgstr "Créé par"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_date
msgid "Created on"
msgstr "Créé le"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_cumulative
msgid "Cumulative"
msgstr "Cumulatif"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount
#: selection:loyalty.reward,type:0
msgid "Discount"
msgstr "Rabaias"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "Discount Product"
msgstr "Produit Soldé"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_max
msgid "Discount limit"
msgstr "Limite de Rabais"
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:64
#, python-format
msgid "Discount product field is mandatory for discount rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_display_name
msgid "Display Name"
msgstr "Nom Affiché"
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Gift"
msgstr "Cadeau"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "Gift Product"
msgstr "Produit Cadeau"
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:57
#, python-format
msgid "Gift product field is mandatory for gift rewards"
msgstr ""
"Le champs 'produit cadeau' est nécessaire pour les cadeau de récompense"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_id
msgid "ID"
msgstr "ID"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule___last_update
msgid "Last Modified on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_uid
msgid "Last Updated by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_date
msgid "Last Updated on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order_line
msgid "Lines of Point of Sale Orders"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_order_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_users_loyalty_points
msgid "Loyalty Points"
msgstr "Points de fidélité"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_config_loyalty_id
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Loyalty Program"
msgstr "Programme de Fidélité"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
#, fuzzy
msgid "Loyalty Program (OCA)"
msgstr "Programme de Fidélité"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_name
msgid "Loyalty Program Name"
msgstr "Nom du Programme de Fidélité"
#. module: pos_loyalty
#: model:ir.actions.act_window,name:pos_loyalty.loyalty_program_action
#: model:ir.ui.menu,name:pos_loyalty.loyalty_program_menu
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_tree_view
#: model:ir.ui.view,arch_db:pos_loyalty.partner_property_form_view
#: model:ir.ui.view,arch_db:pos_loyalty.pos_order_form_view
msgid "Loyalty Programs"
msgstr "Progammes de Fidélité"
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid ""
"Loyalty Programs allow you customers to earn points\n"
" and rewards when purchasing from your shops."
msgstr ""
"Les programmes de fidélité permettent aux clients de gagner des points\n"
"et des cadeaux lors de leurs achats sur vos sites internet."
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_reward_form_view
msgid "Loyalty Reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_rule_form_view
msgid "Loyalty Rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_rounding
msgid "Loyalty point amounts will be rounded to multiples of this value"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty program that will be available in this PoS"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_max
msgid "Maximum discounted amount allowed forthis discount reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_minimum_points
msgid "Minimum Points"
msgstr "Points Minimum"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_minimum_points
msgid ""
"Minimum amount of points the customer must have to qualify for this reward"
msgstr "Le minimum de points requis pour avoir le droit à cette récompense"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:379
#, python-format
msgid "No Rewards Available"
msgstr "Pas de récompenses disponible"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:393
#, python-format
msgid "Please select a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_cost
msgid "Point Cost"
msgstr "Coût en points"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Point Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order
msgid "Point of Sale Orders"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:72
#, python-format
msgid "Point product field is mandatory for point resale rewards"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:6
#: code:addons/pos_loyalty/static/src/xml/pos.xml:71
#: code:addons/pos_loyalty/static/src/xml/pos.xml:81
#, python-format
msgid "Points"
msgstr "Points"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rounding
msgid "Points Rounding"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:57
#, python-format
msgid "Points Spent"
msgstr "Points dépensés"
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:54
#, python-format
msgid "Points Won"
msgstr "Points Gagnés"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_currency
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Points per currency"
msgstr "Points par devise"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_order
msgid "Points per order"
msgstr "Points par commande"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_product
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_product
msgid "Points per product"
msgstr "Points par produit"
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Product"
msgstr "Produit"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Product that represents a point that is sold by the customer"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Resale"
msgstr "Revente"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_name
msgid "Reward Name"
msgstr "Nom de Récompense"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Reward the customer with gifts or discounts for loyalty points"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:25
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_reward_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
#, python-format
msgid "Rewards"
msgstr "Récompenses"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_name
msgid "Rule Name"
msgstr "Nom de Règle"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rule_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Rules"
msgstr "Règles"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid ""
"Rules define how loyalty points are earned for specific products or "
"categories"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_category_id
msgid "Target Category"
msgstr "Catégorie cible"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_product_id
msgid "Target Product"
msgstr "Produit Cible"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_loyalty_program_id
msgid "The Loyalty Program this reward belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_loyalty_program_id
msgid "The Loyalty Program this rule belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_order_loyalty_points
msgid "The amount of Loyalty points awarded to the customer with this order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_category_id
msgid "The category affected by this rule"
msgstr "La catégorie affectée par la règle"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_type
msgid "The concept this rule applies to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount
msgid "The discount percentage"
msgstr "Le pourcentage de rabais"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,help:pos_loyalty.field_res_users_loyalty_points
msgid "The loyalty points the user won as part of a Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_config_loyalty_id
msgid "The loyalty program used by this Point of Sale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_cumulative
msgid ""
"The points from this rule will be added to points won from other rules with "
"the same concept"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_product_id
msgid "The product affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "The product given as a reward"
msgstr "Le produit est donné en guise de récompense"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "The product used to apply discounts"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:380
#, python-format
msgid ""
"There are no rewards available for this customer as part of the loyalty "
"program"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:60
#, python-format
msgid "Total Points"
msgstr "Points Totaux"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_type
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_type
msgid "Type"
msgstr "Type"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_type
msgid "Type of the reward"
msgstr "Type de récompense"
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "abc"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_program
msgid "loyalty.program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_reward
msgid "loyalty.reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_rule
msgid "loyalty.rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_config
msgid "pos.config"
msgstr "pos.config"
#~ msgid "Partner"
#~ msgstr "Partner"

498
pos_loyalty/i18n/hr_HR.po

@ -0,0 +1,498 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_loyalty
#
# Translators:
# Bole <bole@dajmi5.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-12 02:44+0000\n"
"PO-Revision-Date: 2017-07-12 02:44+0000\n"
"Last-Translator: Bole <bole@dajmi5.com>, 2017\n"
"Language-Team: Croatian (Croatia) (https://www.transifex.com/oca/teams/23907/"
"hr_HR/)\n"
"Language: hr_HR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_order
msgid ""
"Amount of loyalty points given to the customer for each point of sale order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_product
msgid "Amount of loyalty points given to the customer per product sold"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_currency
msgid "Amount of loyalty points given to the customer per sold currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Amount of points earned per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_product
msgid "Amount of points earned per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Category"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid "Click create to define a Loyalty Program."
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_res_partner
msgid "Contact"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_cost
msgid "Cost of the reward per monetary unit discounted"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_uid
msgid "Created by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_date
msgid "Created on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_cumulative
msgid "Cumulative"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount
#: selection:loyalty.reward,type:0
msgid "Discount"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "Discount Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_max
msgid "Discount limit"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:64
#, python-format
msgid "Discount product field is mandatory for discount rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_display_name
msgid "Display Name"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Gift"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "Gift Product"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:57
#, python-format
msgid "Gift product field is mandatory for gift rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_id
msgid "ID"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule___last_update
msgid "Last Modified on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_uid
msgid "Last Updated by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_date
msgid "Last Updated on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order_line
msgid "Lines of Point of Sale Orders"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_order_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_users_loyalty_points
msgid "Loyalty Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_config_loyalty_id
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty Program (OCA)"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_name
msgid "Loyalty Program Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,name:pos_loyalty.loyalty_program_action
#: model:ir.ui.menu,name:pos_loyalty.loyalty_program_menu
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_tree_view
#: model:ir.ui.view,arch_db:pos_loyalty.partner_property_form_view
#: model:ir.ui.view,arch_db:pos_loyalty.pos_order_form_view
msgid "Loyalty Programs"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid ""
"Loyalty Programs allow you customers to earn points\n"
" and rewards when purchasing from your shops."
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_reward_form_view
msgid "Loyalty Reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_rule_form_view
msgid "Loyalty Rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_rounding
msgid "Loyalty point amounts will be rounded to multiples of this value"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty program that will be available in this PoS"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_max
msgid "Maximum discounted amount allowed forthis discount reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_minimum_points
msgid "Minimum Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_minimum_points
msgid ""
"Minimum amount of points the customer must have to qualify for this reward"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:379
#, python-format
msgid "No Rewards Available"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:393
#, python-format
msgid "Please select a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_cost
msgid "Point Cost"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Point Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order
msgid "Point of Sale Orders"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:72
#, python-format
msgid "Point product field is mandatory for point resale rewards"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:6
#: code:addons/pos_loyalty/static/src/xml/pos.xml:71
#: code:addons/pos_loyalty/static/src/xml/pos.xml:81
#, python-format
msgid "Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rounding
msgid "Points Rounding"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:57
#, python-format
msgid "Points Spent"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:54
#, python-format
msgid "Points Won"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_currency
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Points per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_order
msgid "Points per order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_product
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_product
msgid "Points per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Product that represents a point that is sold by the customer"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Resale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_name
msgid "Reward Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Reward the customer with gifts or discounts for loyalty points"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:25
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_reward_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
#, python-format
msgid "Rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_name
msgid "Rule Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rule_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Rules"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid ""
"Rules define how loyalty points are earned for specific products or "
"categories"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_category_id
msgid "Target Category"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_product_id
msgid "Target Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_loyalty_program_id
msgid "The Loyalty Program this reward belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_loyalty_program_id
msgid "The Loyalty Program this rule belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_order_loyalty_points
msgid "The amount of Loyalty points awarded to the customer with this order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_category_id
msgid "The category affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_type
msgid "The concept this rule applies to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount
msgid "The discount percentage"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,help:pos_loyalty.field_res_users_loyalty_points
msgid "The loyalty points the user won as part of a Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_config_loyalty_id
msgid "The loyalty program used by this Point of Sale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_cumulative
msgid ""
"The points from this rule will be added to points won from other rules with "
"the same concept"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_product_id
msgid "The product affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "The product given as a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "The product used to apply discounts"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:380
#, python-format
msgid ""
"There are no rewards available for this customer as part of the loyalty "
"program"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:60
#, python-format
msgid "Total Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_type
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_type
msgid "Type"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_type
msgid "Type of the reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "abc"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_program
msgid "loyalty.program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_reward
msgid "loyalty.reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_rule
msgid "loyalty.rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_config
msgid "pos.config"
msgstr "pos.config"

497
pos_loyalty/i18n/it.po

@ -0,0 +1,497 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_loyalty
#
# Translators:
# Francesco Fresta <franco.fresta@gmail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-01 02:01+0000\n"
"PO-Revision-Date: 2018-03-01 02:01+0000\n"
"Last-Translator: Francesco Fresta <franco.fresta@gmail.com>, 2018\n"
"Language-Team: Italian (https://www.transifex.com/oca/teams/23907/it/)\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_order
msgid ""
"Amount of loyalty points given to the customer for each point of sale order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_product
msgid "Amount of loyalty points given to the customer per product sold"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_currency
msgid "Amount of loyalty points given to the customer per sold currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Amount of points earned per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_product
msgid "Amount of points earned per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Category"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid "Click create to define a Loyalty Program."
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_res_partner
msgid "Contact"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_cost
msgid "Cost of the reward per monetary unit discounted"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_uid
msgid "Created by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_date
msgid "Created on"
msgstr "Creato il"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_cumulative
msgid "Cumulative"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount
#: selection:loyalty.reward,type:0
msgid "Discount"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "Discount Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_max
msgid "Discount limit"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:64
#, python-format
msgid "Discount product field is mandatory for discount rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_display_name
msgid "Display Name"
msgstr "Mostra il nome"
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Gift"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "Gift Product"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:57
#, python-format
msgid "Gift product field is mandatory for gift rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_id
msgid "ID"
msgstr "ID"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule___last_update
msgid "Last Modified on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_uid
msgid "Last Updated by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order_line
#, fuzzy
msgid "Lines of Point of Sale Orders"
msgstr "Punto di riordino"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_order_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_users_loyalty_points
msgid "Loyalty Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_config_loyalty_id
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty Program (OCA)"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_name
msgid "Loyalty Program Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,name:pos_loyalty.loyalty_program_action
#: model:ir.ui.menu,name:pos_loyalty.loyalty_program_menu
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_tree_view
#: model:ir.ui.view,arch_db:pos_loyalty.partner_property_form_view
#: model:ir.ui.view,arch_db:pos_loyalty.pos_order_form_view
msgid "Loyalty Programs"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid ""
"Loyalty Programs allow you customers to earn points\n"
" and rewards when purchasing from your shops."
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_reward_form_view
msgid "Loyalty Reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_rule_form_view
msgid "Loyalty Rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_rounding
msgid "Loyalty point amounts will be rounded to multiples of this value"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty program that will be available in this PoS"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_max
msgid "Maximum discounted amount allowed forthis discount reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_minimum_points
msgid "Minimum Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_minimum_points
msgid ""
"Minimum amount of points the customer must have to qualify for this reward"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:379
#, python-format
msgid "No Rewards Available"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:393
#, python-format
msgid "Please select a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_cost
msgid "Point Cost"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Point Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order
msgid "Point of Sale Orders"
msgstr "Punto di riordino"
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:72
#, python-format
msgid "Point product field is mandatory for point resale rewards"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:6
#: code:addons/pos_loyalty/static/src/xml/pos.xml:71
#: code:addons/pos_loyalty/static/src/xml/pos.xml:81
#, python-format
msgid "Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rounding
msgid "Points Rounding"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:57
#, python-format
msgid "Points Spent"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:54
#, python-format
msgid "Points Won"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_currency
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Points per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_order
msgid "Points per order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_product
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_product
msgid "Points per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Product that represents a point that is sold by the customer"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Resale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_name
msgid "Reward Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Reward the customer with gifts or discounts for loyalty points"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:25
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_reward_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
#, python-format
msgid "Rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_name
msgid "Rule Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rule_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Rules"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid ""
"Rules define how loyalty points are earned for specific products or "
"categories"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_category_id
msgid "Target Category"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_product_id
msgid "Target Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_loyalty_program_id
msgid "The Loyalty Program this reward belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_loyalty_program_id
msgid "The Loyalty Program this rule belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_order_loyalty_points
msgid "The amount of Loyalty points awarded to the customer with this order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_category_id
msgid "The category affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_type
msgid "The concept this rule applies to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount
msgid "The discount percentage"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,help:pos_loyalty.field_res_users_loyalty_points
msgid "The loyalty points the user won as part of a Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_config_loyalty_id
msgid "The loyalty program used by this Point of Sale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_cumulative
msgid ""
"The points from this rule will be added to points won from other rules with "
"the same concept"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_product_id
msgid "The product affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "The product given as a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "The product used to apply discounts"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:380
#, python-format
msgid ""
"There are no rewards available for this customer as part of the loyalty "
"program"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:60
#, python-format
msgid "Total Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_type
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_type
msgid "Type"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_type
msgid "Type of the reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "abc"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_program
msgid "loyalty.program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_reward
msgid "loyalty.reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_rule
msgid "loyalty.rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_config
msgid "pos.config"
msgstr "pos.config"

498
pos_loyalty/i18n/nl_NL.po

@ -0,0 +1,498 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_loyalty
#
# Translators:
# Peter Hageman <hageman.p@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-12 02:44+0000\n"
"PO-Revision-Date: 2017-07-12 02:44+0000\n"
"Last-Translator: Peter Hageman <hageman.p@gmail.com>, 2017\n"
"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/"
"teams/23907/nl_NL/)\n"
"Language: nl_NL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_order
msgid ""
"Amount of loyalty points given to the customer for each point of sale order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_product
msgid "Amount of loyalty points given to the customer per product sold"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_currency
msgid "Amount of loyalty points given to the customer per sold currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Amount of points earned per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_product
msgid "Amount of points earned per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Category"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid "Click create to define a Loyalty Program."
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_res_partner
msgid "Contact"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_cost
msgid "Cost of the reward per monetary unit discounted"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_uid
msgid "Created by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_date
msgid "Created on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_cumulative
msgid "Cumulative"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount
#: selection:loyalty.reward,type:0
msgid "Discount"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "Discount Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_max
msgid "Discount limit"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:64
#, python-format
msgid "Discount product field is mandatory for discount rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_display_name
msgid "Display Name"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Gift"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "Gift Product"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:57
#, python-format
msgid "Gift product field is mandatory for gift rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_id
msgid "ID"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule___last_update
msgid "Last Modified on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_uid
msgid "Last Updated by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_date
msgid "Last Updated on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order_line
#, fuzzy
msgid "Lines of Point of Sale Orders"
msgstr "Kassaorders"
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_order_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_users_loyalty_points
msgid "Loyalty Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_config_loyalty_id
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty Program (OCA)"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_name
msgid "Loyalty Program Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,name:pos_loyalty.loyalty_program_action
#: model:ir.ui.menu,name:pos_loyalty.loyalty_program_menu
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_tree_view
#: model:ir.ui.view,arch_db:pos_loyalty.partner_property_form_view
#: model:ir.ui.view,arch_db:pos_loyalty.pos_order_form_view
msgid "Loyalty Programs"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid ""
"Loyalty Programs allow you customers to earn points\n"
" and rewards when purchasing from your shops."
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_reward_form_view
msgid "Loyalty Reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_rule_form_view
msgid "Loyalty Rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_rounding
msgid "Loyalty point amounts will be rounded to multiples of this value"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty program that will be available in this PoS"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_max
msgid "Maximum discounted amount allowed forthis discount reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_minimum_points
msgid "Minimum Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_minimum_points
msgid ""
"Minimum amount of points the customer must have to qualify for this reward"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:379
#, python-format
msgid "No Rewards Available"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:393
#, python-format
msgid "Please select a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_cost
msgid "Point Cost"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Point Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order
msgid "Point of Sale Orders"
msgstr "Kassaorders"
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:72
#, python-format
msgid "Point product field is mandatory for point resale rewards"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:6
#: code:addons/pos_loyalty/static/src/xml/pos.xml:71
#: code:addons/pos_loyalty/static/src/xml/pos.xml:81
#, python-format
msgid "Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rounding
msgid "Points Rounding"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:57
#, python-format
msgid "Points Spent"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:54
#, python-format
msgid "Points Won"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_currency
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Points per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_order
msgid "Points per order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_product
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_product
msgid "Points per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Product that represents a point that is sold by the customer"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Resale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_name
msgid "Reward Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Reward the customer with gifts or discounts for loyalty points"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:25
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_reward_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
#, python-format
msgid "Rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_name
msgid "Rule Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rule_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Rules"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid ""
"Rules define how loyalty points are earned for specific products or "
"categories"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_category_id
msgid "Target Category"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_product_id
msgid "Target Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_loyalty_program_id
msgid "The Loyalty Program this reward belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_loyalty_program_id
msgid "The Loyalty Program this rule belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_order_loyalty_points
msgid "The amount of Loyalty points awarded to the customer with this order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_category_id
msgid "The category affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_type
msgid "The concept this rule applies to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount
msgid "The discount percentage"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,help:pos_loyalty.field_res_users_loyalty_points
msgid "The loyalty points the user won as part of a Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_config_loyalty_id
msgid "The loyalty program used by this Point of Sale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_cumulative
msgid ""
"The points from this rule will be added to points won from other rules with "
"the same concept"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_product_id
msgid "The product affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "The product given as a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "The product used to apply discounts"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:380
#, python-format
msgid ""
"There are no rewards available for this customer as part of the loyalty "
"program"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:60
#, python-format
msgid "Total Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_type
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_type
msgid "Type"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_type
msgid "Type of the reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "abc"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_program
msgid "loyalty.program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_reward
msgid "loyalty.reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_rule
msgid "loyalty.rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_config
msgid "pos.config"
msgstr "pos.config"

483
pos_loyalty/i18n/pos_loyalty.pot

@ -0,0 +1,483 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_loyalty
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 11.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_order
msgid "Amount of loyalty points given to the customer for each point of sale order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_product
msgid "Amount of loyalty points given to the customer per product sold"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_pp_currency
msgid "Amount of loyalty points given to the customer per sold currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Amount of points earned per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_pp_product
msgid "Amount of points earned per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Category"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid "Click create to define a Loyalty Program."
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_res_partner
msgid "Contact"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_cost
msgid "Cost of the reward per monetary unit discounted"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_uid
msgid "Created by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_create_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_create_date
msgid "Created on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_cumulative
msgid "Cumulative"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount
#: selection:loyalty.reward,type:0
msgid "Discount"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "Discount Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_discount_max
msgid "Discount limit"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:64
#, python-format
msgid "Discount product field is mandatory for discount rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_display_name
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_display_name
msgid "Display Name"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Gift"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "Gift Product"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:57
#, python-format
msgid "Gift product field is mandatory for gift rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_id
msgid "ID"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward___last_update
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule___last_update
msgid "Last Modified on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_uid
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_uid
msgid "Last Updated by"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_write_date
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_write_date
msgid "Last Updated on"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order_line
msgid "Lines of Point of Sale Orders"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_order_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,field_description:pos_loyalty.field_res_users_loyalty_points
msgid "Loyalty Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_loyalty_program_id
#: model:ir.model.fields,field_description:pos_loyalty.field_pos_config_loyalty_id
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty Program (OCA)"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_name
msgid "Loyalty Program Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,name:pos_loyalty.loyalty_program_action
#: model:ir.ui.menu,name:pos_loyalty.loyalty_program_menu
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_tree_view
#: model:ir.ui.view,arch_db:pos_loyalty.partner_property_form_view
#: model:ir.ui.view,arch_db:pos_loyalty.pos_order_form_view
msgid "Loyalty Programs"
msgstr ""
#. module: pos_loyalty
#: model:ir.actions.act_window,help:pos_loyalty.loyalty_program_action
msgid "Loyalty Programs allow you customers to earn points\n"
" and rewards when purchasing from your shops."
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_reward_form_view
msgid "Loyalty Reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_rule_form_view
msgid "Loyalty Rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_program_rounding
msgid "Loyalty point amounts will be rounded to multiples of this value"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "Loyalty program that will be available in this PoS"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_max
msgid "Maximum discounted amount allowed forthis discount reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_minimum_points
msgid "Minimum Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_minimum_points
msgid "Minimum amount of points the customer must have to qualify for this reward"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:379
#, python-format
msgid "No Rewards Available"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:393
#, python-format
msgid "Please select a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_cost
msgid "Point Cost"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Point Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_order
msgid "Point of Sale Orders"
msgstr ""
#. module: pos_loyalty
#: code:addons/pos_loyalty/models/loyalty_reward.py:72
#, python-format
msgid "Point product field is mandatory for point resale rewards"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:6
#: code:addons/pos_loyalty/static/src/xml/pos.xml:71
#: code:addons/pos_loyalty/static/src/xml/pos.xml:81
#, python-format
msgid "Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rounding
msgid "Points Rounding"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:57
#, python-format
msgid "Points Spent"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:54
#, python-format
msgid "Points Won"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_currency
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_currency
msgid "Points per currency"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_order
msgid "Points per order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_pp_product
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_pp_product
msgid "Points per product"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.rule,type:0
msgid "Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_point_product_id
msgid "Product that represents a point that is sold by the customer"
msgstr ""
#. module: pos_loyalty
#: selection:loyalty.reward,type:0
msgid "Resale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_name
msgid "Reward Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Reward the customer with gifts or discounts for loyalty points"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:25
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_reward_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
#, python-format
msgid "Rewards"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_name
msgid "Rule Name"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_program_rule_ids
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Rules"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.loyalty_program_form_view
msgid "Rules define how loyalty points are earned for specific products or categories"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_category_id
msgid "Target Category"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_product_id
msgid "Target Product"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_loyalty_program_id
msgid "The Loyalty Program this reward belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_loyalty_program_id
msgid "The Loyalty Program this rule belongs to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_order_loyalty_points
msgid "The amount of Loyalty points awarded to the customer with this order"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_category_id
msgid "The category affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_type
msgid "The concept this rule applies to"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount
msgid "The discount percentage"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_res_partner_loyalty_points
#: model:ir.model.fields,help:pos_loyalty.field_res_users_loyalty_points
msgid "The loyalty points the user won as part of a Loyalty Program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_pos_config_loyalty_id
msgid "The loyalty program used by this Point of Sale"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_cumulative
msgid "The points from this rule will be added to points won from other rules with the same concept"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_rule_product_id
msgid "The product affected by this rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_gift_product_id
msgid "The product given as a reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_discount_product_id
msgid "The product used to apply discounts"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/js/pos.js:380
#, python-format
msgid "There are no rewards available for this customer as part of the loyalty program"
msgstr ""
#. module: pos_loyalty
#. openerp-web
#: code:addons/pos_loyalty/static/src/xml/pos.xml:60
#, python-format
msgid "Total Points"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_reward_type
#: model:ir.model.fields,field_description:pos_loyalty.field_loyalty_rule_type
msgid "Type"
msgstr ""
#. module: pos_loyalty
#: model:ir.model.fields,help:pos_loyalty.field_loyalty_reward_type
msgid "Type of the reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.ui.view,arch_db:pos_loyalty.pos_config_view_form
msgid "abc"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_program
msgid "loyalty.program"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_reward
msgid "loyalty.reward"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_loyalty_rule
msgid "loyalty.rule"
msgstr ""
#. module: pos_loyalty
#: model:ir.model,name:pos_loyalty.model_pos_config
msgid "pos.config"
msgstr ""

9
pos_loyalty/models/__init__.py

@ -0,0 +1,9 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import loyalty_program
from . import loyalty_reward
from . import loyalty_rule
from . import pos_config
from . import pos_order
from . import pos_order_line
from . import res_partner

30
pos_loyalty/models/loyalty_program.py

@ -0,0 +1,30 @@
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class LoyaltyProgram(models.Model):
_name = 'loyalty.program'
name = fields.Char(string='Loyalty Program Name', size=32, index=True,
required=True)
pp_currency = fields.Float(string='Points per currency',
help='Amount of loyalty points given to the '
'customer per sold currency')
pp_product = fields.Float(string='Points per product',
help='Amount of loyalty points given to the '
'customer per product sold')
pp_order = fields.Float(string='Points per order',
help='Amount of loyalty points given to the '
'customer for each point of sale order')
rounding = fields.Float(string='Points Rounding', default=1,
help='Loyalty point amounts will be rounded to '
'multiples of this value')
rule_ids = fields.One2many(comodel_name='loyalty.rule',
inverse_name='loyalty_program_id',
string='Rules')
reward_ids = fields.One2many(comodel_name='loyalty.reward',
inverse_name='loyalty_program_id',
string='Rewards')

73
pos_loyalty/models/loyalty_reward.py

@ -0,0 +1,73 @@
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, api, _
from odoo.exceptions import ValidationError
class LoyaltyReward(models.Model):
_name = 'loyalty.reward'
name = fields.Char(string='Reward Name', size=32, index=True,
required=True)
type = fields.Selection(selection=[('gift', 'Gift'),
('discount', 'Discount'),
('resale', 'Resale')],
string='Type', required=True,
help='Type of the reward')
minimum_points = fields.Float(string='Minimum Points',
help='Minimum amount of points the customer'
' must have to qualify for this reward')
point_cost = fields.Float(string='Point Cost',
help='Cost of the reward per monetary unit '
'discounted')
discount = fields.Float(help='The discount percentage')
discount_max = fields.Float(string='Discount limit',
help='Maximum discounted amount allowed for'
'this discount reward')
loyalty_program_id = fields.Many2one(comodel_name='loyalty.program',
string='Loyalty Program',
help='The Loyalty Program this reward'
' belongs to')
gift_product_id = fields.Many2one(comodel_name='product.product',
domain=[('available_in_pos', '=', True)],
string='Gift Product',
help='The product given as a reward')
discount_product_id = fields.Many2one(comodel_name='product.product',
domain=[
('available_in_pos', '=', True)],
string='Discount Product',
help='The product used to apply '
'discounts')
point_product_id = fields.Many2one(comodel_name='product.product',
domain=[
('available_in_pos', '=', True)],
string='Point Product',
help='Product that represents a point '
'that is sold by the customer')
@api.multi
@api.constrains('type', 'gift_product_id')
def _check_gift_product(self):
for reward in self:
if reward.type == 'gift' and not reward.gift_product_id:
raise ValidationError(
_('Gift product field is mandatory for gift rewards'))
@api.multi
@api.constrains('type', 'discount_product_id')
def _check_discount_product(self):
for reward in self:
if reward.type == 'discount' and not reward.discount_product_id:
raise ValidationError(_('Discount product field is '
'mandatory for discount rewards'))
@api.multi
@api.constrains('type', 'point_product_id')
def _check_point_product(self):
for reward in self:
if reward.type == 'resale' and not reward.point_product_id:
raise ValidationError(_('Point product field is '
'mandatory for point resale rewards'))

34
pos_loyalty/models/loyalty_rule.py

@ -0,0 +1,34 @@
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class LoyaltyRule(models.Model):
_name = 'loyalty.rule'
name = fields.Char(string='Rule Name', size=32, index=True, required=True)
type = fields.Selection(selection=[('product', 'Product'),
('category', 'Category')],
string='Type', required=True, default='product',
help='The concept this rule applies to')
cumulative = fields.Boolean(help='The points from this rule will be added '
'to points won from other rules with '
'the same concept')
pp_product = fields.Float(string='Points per product',
help='Amount of points earned per product')
pp_currency = fields.Float(string='Points per currency',
help='Amount of points earned per currency')
loyalty_program_id = fields.Many2one(comodel_name='loyalty.program',
string='Loyalty Program',
help='The Loyalty Program this rule '
'belongs to')
product_id = fields.Many2one(comodel_name='product.product',
domain=[('available_in_pos', '=', True)],
string='Target Product',
help='The product affected by this rule')
category_id = fields.Many2one(comodel_name='pos.category',
string='Target Category',
help='The category affected by this rule')

14
pos_loyalty/models/pos_config.py

@ -0,0 +1,14 @@
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class PosConfig(models.Model):
_inherit = 'pos.config'
loyalty_id = fields.Many2one(comodel_name='loyalty.program',
string='Loyalty Program',
help='The loyalty program used by this '
'Point of Sale')

30
pos_loyalty/models/pos_order.py

@ -0,0 +1,30 @@
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, api
class PosOrder(models.Model):
_inherit = 'pos.order'
loyalty_points = fields.Float(string='Loyalty Points',
help='The amount of Loyalty points awarded '
'to the customer with this order')
@api.model
def _order_fields(self, ui_order):
res = super(PosOrder, self)._order_fields(ui_order)
res['loyalty_points'] = ui_order.get('loyalty_points', 0)
return res
@api.model
def create_from_ui(self, orders):
res = super(PosOrder, self).create_from_ui(orders)
for order in orders:
order_partner = order['data']['partner_id']
order_points = order['data'].get('loyalty_points', 0)
if order_points != 0 and order_partner:
partner = self.env['res.partner'].browse(order_partner)
partner.loyalty_points += order_points
return res

20
pos_loyalty/models/pos_order_line.py

@ -0,0 +1,20 @@
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
class PosOrderLine(models.Model):
_inherit = 'pos.order.line'
@api.model
def _order_line_fields(self, line, session_id=None):
line = super(PosOrderLine, self)._order_line_fields(
line, session_id=session_id)
if line and 'reward_id' in line[2]:
# Delete the key since field doesn't exist
# and raises a warning in the logs.
# TODO: add field and remove this if data will be
# used on server, example in report / widget.
del line[2]['reward_id']
return line

13
pos_loyalty/models/res_partner.py

@ -0,0 +1,13 @@
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
loyalty_points = fields.Float(string='Loyalty Points',
help='The loyalty points the user won as '
'part of a Loyalty Program')

4
pos_loyalty/readme/CONFIGURE.rst

@ -0,0 +1,4 @@
To use this module, you need to:
* Go to *Point of Sale > Configuration > Loyalty Programs* and define a new loyalty program with specific rules and rewards.
* Assign the loyalty program to the desired Point of Sale.

3
pos_loyalty/readme/CONTRIBUTORS.rst

@ -0,0 +1,3 @@
* RGB Consulting SL (http://www.rgbconsulting.com)
* Forward-port from Odoo SA saas-6 branch
* Kiril Vangelovski <kiril@lambda-is.com>

6
pos_loyalty/readme/DESCRIPTION.rst

@ -0,0 +1,6 @@
This module allows you to define a loyalty program in the point of sale,
where the customers earn loyalty points and get rewards.
This module is a forward-port to v12 of the pos_loyalty module from Odoo's
saas-6 branch.
The functionality was moved to the Enterprise edition in later versions.

11
pos_loyalty/readme/USAGE.rst

@ -0,0 +1,11 @@
The Loyalty Program defines rules for acquiring points and rewards on which they can be spent.
Rules can be defined globally for all products (fields on loyalty.program) and / or rules that are applied only on specific product or PoS category (loyalty.rule records) on a *points per product sold* or *points per currency spent* basis. The specific rules (loyalty.rule) can be defined as cumulative, which means that they will be aggregated with other matching rules (loyalty.rule records and loyalty.program fields). In the case of non-cumulative rules only the points from that one matching rule are used. Additionally, *fixed points per order* can be added which are applied regardless of whether or not cumulative or non-cumulative rules were applied also.
Rewards can be of three types:
* *Gift* - give a single unit of product for free
* *Discount* - give a discount to the whole order. It should be added at the end of the order so that the correct total price is used.
* *Resale* - allow for customer to sell back his earned points. These are calculated by setting the price on the Resale product (*resale_product.list_price* * *customer.loyalty_points*)
All rewards can define how many points they cost (point_cost) and how many are needed so that the customer can become eligable for the reward (minimum_points). for Gift and Discount rewards minimum_points are considered only if they are greater then the point_cost for that reward (minimum_points > point_cost). For Resale products only minimum_points can be used.

7
pos_loyalty/security/ir.model.access.csv

@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_loyalty_program,loyalty.program.user,model_loyalty_program,point_of_sale.group_pos_user,1,0,0,0
access_loyalty_program_manager,loyalty.program.manager,model_loyalty_program,point_of_sale.group_pos_manager,1,1,1,1
access_loyalty_rule,loyalty.rule.user,model_loyalty_rule,point_of_sale.group_pos_user,1,0,0,0
access_loyalty_rule_manager,loyalty.rule.manager,model_loyalty_rule,point_of_sale.group_pos_manager,1,1,1,1
access_loyalty_reward,loyalty.reward.user,model_loyalty_reward,point_of_sale.group_pos_user,1,0,0,0
access_loyalty_reward_manager,loyalty.reward.manager,model_loyalty_reward,point_of_sale.group_pos_manager,1,1,1,1

BIN
pos_loyalty/static/description/icon.png

After

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

449
pos_loyalty/static/description/index.html

@ -0,0 +1,449 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>Loyalty Program</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="loyalty-program">
<h1 class="title">Loyalty Program</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/pos/tree/11.0/pos_loyalty"><img alt="OCA/pos" src="https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/pos-11-0/pos-11-0-pos_loyalty"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/184/11.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module allows you to define a loyalty program in the point of sale,
where the customers earn loyalty points and get rewards.</p>
<p>This module is a forward-port to v10 of the pos_loyalty module from Odoo’s
saas-6 branch.
The functionality was moved to the Enterprise edition in later versions.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>To use this module, you need to:</p>
<ul class="simple">
<li>Go to <em>Point of Sale &gt; Configuration &gt; Loyalty Programs</em> and define a new loyalty program with specific rules and rewards.</li>
<li>Assign the loyalty program to the desired Point of Sale.</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p>The Loyalty Program defines rules for acquiring points and rewards on which they can be spent.</p>
<p>Rules can be defined globally for all products (fields on loyalty.program) and / or rules that are applied only on specific product or PoS category (loyalty.rule records) on a <em>points per product sold</em> or <em>points per currency spent</em> basis. The specific rules (loyalty.rule) can be defined as cumulative, which means that they will be aggregated with other matching rules (loyalty.rule records and loyalty.program fields). In the case of non-cumulative rules only the points from that one matching rule are used. Additionally, <em>fixed points per order</em> can be added which are applied regardless of whether or not cumulative or non-cumulative rules were applied also.</p>
<p>Rewards can be of three types:</p>
<ul class="simple">
<li><em>Gift</em> - give a single unit of product for free</li>
<li><em>Discount</em> - give a discount to the whole order. It should be added at the end of the order so that the correct total price is used.</li>
<li><em>Resale</em> - allow for customer to sell back his earned points. These are calculated by setting the price on the Resale product (<em>resale_product.list_price</em> * <em>customer.loyalty_points</em>)</li>
</ul>
<p>All rewards can define how many points they cost (point_cost) and how many are needed so that the customer can become eligable for the reward (minimum_points). for Gift and Discount rewards minimum_points are considered only if they are greater then the point_cost for that reward (minimum_points &gt; point_cost). For Resale products only minimum_points can be used.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/pos/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/pos/issues/new?body=module:%20pos_loyalty%0Aversion:%2011.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
<ul class="simple">
<li>OpenERP SA</li>
<li>RGB Consulting SL</li>
<li>Lambda IS</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id6">Contributors</a></h2>
<ul class="simple">
<li>RGB Consulting SL (<a class="reference external" href="http://www.rgbconsulting.com">http://www.rgbconsulting.com</a>)</li>
<li>Forward-port from Odoo SA saas-6 branch</li>
<li>Kiril Vangelovski &lt;<a class="reference external" href="mailto:kiril&#64;lambda-is.com">kiril&#64;lambda-is.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/pos/tree/11.0/pos_loyalty">OCA/pos</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

21
pos_loyalty/static/src/css/pos.css

@ -0,0 +1,21 @@
.pos .order .summary .loyalty-points{
margin-left: 20px;
float: left;
padding: 10px;
max-width: 216px;
text-align: left;
color: #6EC89B;
background: rgba(110, 200, 155, 0.17);
border-radius: 3px;
}
.pos .order .summary .loyalty-points.negative{
color: #C86E6E;
background: rgba(200, 110, 110, 0.17);
}
.pos .order .summary .loyalty-points-total {
border-top: solid 2px;
text-align: center;
padding-top: 4px;
margin-top: 4px;
}

477
pos_loyalty/static/src/js/pos.js

@ -0,0 +1,477 @@
/* Copyright 2004-2010 OpenERP SA
* Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
odoo.define('pos_loyalty.loyalty_program', function(require) {
"use strict"
var models = require('point_of_sale.models');
var screens = require('point_of_sale.screens');
var utils = require('web.utils');
var round_pr = utils.round_precision;
var core = require('web.core');
var QWeb = core.qweb;
var _t = core._t;
models.load_fields('res.partner', 'loyalty_points');
models.load_models([{
model: 'loyalty.program',
condition: function(self) {
return !!self.config.loyalty_id[0];
},
fields: ['name', 'pp_currency', 'pp_product', 'pp_order', 'rounding'],
domain: function(self) {
return [
['id', '=', self.config.loyalty_id[0]]
];
},
loaded: function(self, loyalties) {
self.loyalty = loyalties[0];
},
}, {
model: 'loyalty.rule',
condition: function(self) {
return !!self.loyalty;
},
fields: ['name', 'type', 'product_id', 'category_id', 'cumulative', 'pp_product', 'pp_currency'],
domain: function(self) {
return [
['loyalty_program_id', '=', self.loyalty.id]
];
},
loaded: function(self, rules) {
self.loyalty.rules = rules;
self.loyalty.rules_by_product_id = {};
self.loyalty.rules_by_category_id = {};
function update_rules(rules, rule, id) {
if (!rules[id]) {
rules[id] = [rule];
} else if (rule.cumulative) {
rules[id].unshift(rule);
} else {
rules[id].push(rule);
}
}
_.each(rules, function(rule) {
if (rule.type === 'product')
update_rules(self.loyalty.rules_by_product_id, rule, rule.product_id[0])
else if (rule.type === 'category')
update_rules(self.loyalty.rules_by_category_id, rule, rule.category_id[0]);
});
},
}, {
model: 'loyalty.reward',
condition: function(self) {
return !!self.loyalty;
},
fields: ['name', 'type', 'minimum_points', 'gift_product_id', 'point_cost', 'discount_product_id', 'discount', 'discount_max', 'point_product_id'],
domain: function(self) {
return [
['loyalty_program_id', '=', self.loyalty.id]
];
},
loaded: function(self, rewards) {
self.loyalty.rewards = rewards;
self.loyalty.rewards_by_id = {};
for (var i = 0; i < rewards.length; i++) {
self.loyalty.rewards_by_id[rewards[i].id] = rewards[i];
}
},
}, ], {
'after': 'product.product'
});
var _orderline_super = models.Orderline.prototype;
models.Orderline = models.Orderline.extend({
get_reward: function() {
return this.pos.loyalty.rewards_by_id[this.reward_id];
},
set_reward: function(reward) {
this.reward_id = reward.id;
},
export_as_JSON: function() {
var json = _orderline_super.export_as_JSON.apply(this, arguments);
json.reward_id = this.reward_id;
return json;
},
init_from_JSON: function(json) {
_orderline_super.init_from_JSON.apply(this, arguments);
this.reward_id = json.reward_id;
},
});
var _order_super = models.Order.prototype;
models.Order = models.Order.extend({
/* The total of points won, excluding the points spent on rewards */
get_won_points: function() {
if (!this.pos.loyalty || !this.get_client()) {
return 0;
}
var orderLines = this.get_orderlines();
var rounding = this.pos.loyalty.rounding;
var product_sold = 0;
var total_sold = 0;
var total_points = 0;
for (var i = 0; i < orderLines.length; i++) {
var line = orderLines[i];
var product = line.get_product();
var rules = this.pos.loyalty.rules_by_product_id[product.id] || [];
var overriden = false;
if (line.get_reward()) { // Reward products are ignored
continue;
}
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
total_points += round_pr(line.get_quantity() * rule.pp_product, rounding);
total_points += round_pr(line.get_price_with_tax() * rule.pp_currency, rounding);
// if affected by a non cumulative rule, skip the others. (non cumulative rules are put
// at the beginning of the list when they are loaded )
if (!rule.cumulative) {
overriden = true;
break;
}
}
// Test the category rules
if (product.pos_categ_id) {
var category = this.pos.db.get_category_by_id(product.pos_categ_id[0]);
while (category && !overriden) {
var rules = this.pos.loyalty.rules_by_category_id[category.id] || [];
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
total_points += round_pr(line.get_quantity() * rule.pp_product, rounding);
total_points += round_pr(line.get_price_with_tax() * rule.pp_currency, rounding);
if (!rule.cumulative) {
overriden = true;
break;
}
}
var _category = category;
category = this.pos.db.get_category_by_id(this.pos.db.get_category_parent_id(category.id));
if (_category === category) {
break;
}
}
}
if (!overriden) {
product_sold += line.get_quantity();
total_sold += line.get_price_with_tax();
}
}
total_points += round_pr(total_sold * this.pos.loyalty.pp_currency, rounding);
total_points += round_pr(product_sold * this.pos.loyalty.pp_product, rounding);
total_points += round_pr(this.pos.loyalty.pp_order, rounding);
return total_points;
},
/* The total number of points spent on rewards */
get_spent_points: function() {
if (!this.pos.loyalty || !this.get_client()) {
return 0;
} else {
var lines = this.get_orderlines();
var rounding = this.pos.loyalty.rounding;
var points = 0;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var reward = line.get_reward();
if (reward) {
if (reward.type === 'gift') {
points += round_pr(line.get_quantity() * reward.point_cost, rounding);
} else if (reward.type === 'discount') {
points += reward.point_cost;
} else if (reward.type === 'resale') {
points += (-line.get_quantity());
}
}
}
return points;
}
},
/* The total number of points lost or won after the order is validated */
get_new_points: function() {
if (!this.pos.loyalty || !this.get_client()) {
return 0;
} else {
return round_pr(this.get_won_points() - this.get_spent_points(), this.pos.loyalty.rounding);
}
},
/* The total number of points that the customer will have after this order is validated */
get_new_total_points: function() {
if (!this.pos.loyalty || !this.get_client()) {
return 0;
} else {
return round_pr(this.get_client().loyalty_points + this.get_new_points(), this.pos.loyalty.rounding);
}
},
/* The number of loyalty points currently owned by the customer */
get_current_points: function() {
return this.get_client() ? this.get_client().loyalty_points : 0;
},
/* The total number of points spendable on rewards */
get_spendable_points: function() {
if (!this.pos.loyalty || !this.get_client()) {
return 0;
} else {
return round_pr(this.get_client().loyalty_points - this.get_spent_points(), this.pos.loyalty.rounding);
}
},
has_discount_reward: function() {
var res = false;
var lines = this.get_orderlines();
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var reward = line.get_reward();
if (reward && reward.type === 'discount') {
res = true;
break;
}
}
return res;
},
/* The list of rewards that the current customer can get */
get_available_rewards: function() {
var client = this.get_client();
if (!client) {
return [];
}
var rewards = [];
var discount_reward_set = this.has_discount_reward();
for (var i = 0; i < this.pos.loyalty.rewards.length; i++) {
var reward = this.pos.loyalty.rewards[i];
if (reward.minimum_points > this.get_spendable_points()) {
continue;
} else if (reward.type === 'gift' &&
reward.point_cost > this.get_spendable_points()) {
continue;
} else if (reward.type === 'discount' &&
(discount_reward_set || reward.point_cost > this.get_spendable_points())) {
continue;
}
rewards.push(reward);
}
return rewards;
},
apply_reward: function(reward) {
var client = this.get_client();
if (!client) {
return;
} else if (reward.type === 'gift') {
var product = this.pos.db.get_product_by_id(reward.gift_product_id[0]);
if (!product) {
return;
}
this.add_product(product, {
price: 0,
quantity: 1,
merge: false,
extras: {
reward_id: reward.id
},
});
} else if (reward.type === 'discount') {
var crounding = this.pos.currency.rounding;
var order_total = this.get_total_with_tax();
var discount = round_pr(order_total * reward.discount, crounding);
var discount_max = reward.discount_max
if (discount_max && discount > discount_max) {
discount = discount_max;
}
var product = this.pos.db.get_product_by_id(reward.discount_product_id[0]);
if (!product) {
return;
}
this.add_product(product, {
price: -discount,
quantity: 1,
merge: false,
extras: {
reward_id: reward.id
},
});
} else if (reward.type === 'resale') {
var lrounding = this.pos.loyalty.rounding;
var crounding = this.pos.currency.rounding;
var spendable = this.get_spendable_points();
var order_total = this.get_total_with_tax();
var product = this.pos.db.get_product_by_id(reward.point_product_id[0]);
if (!product) {
return;
}
if (round_pr(spendable * product.price, crounding) > order_total) {
spendable = round_pr(Math.floor(order_total / product.price), lrounding);
}
if (spendable < 0.00001) {
return;
}
this.add_product(product, {
quantity: -spendable,
merge: false,
extras: {
reward_id: reward.id
},
});
}
},
finalize: function() {
var client = this.get_client();
if (client) {
client.loyalty_points = this.get_new_total_points();
this.pos.gui.screen_instances.clientlist.partner_cache.clear_node(client.id);
}
_order_super.finalize.apply(this, arguments);
},
export_for_printing: function() {
var json = _order_super.export_for_printing.apply(this, arguments);
if (this.pos.loyalty && this.get_client()) {
json.loyalty = {
rounding: this.pos.loyalty.rounding || 1,
name: this.pos.loyalty.name,
client: this.get_client().name,
points_won: this.get_won_points(),
points_spent: this.get_spent_points(),
points_total: this.get_new_total_points(),
};
}
return json;
},
export_as_JSON: function() {
var json = _order_super.export_as_JSON.apply(this, arguments);
json.loyalty_points = this.get_new_points();
return json;
},
});
var LoyaltyButton = screens.ActionButtonWidget.extend({
template: 'LoyaltyButton',
button_click: function() {
var self = this;
var order = this.pos.get_order();
var client = order.get_client();
if (!client) {
this.gui.show_screen('clientlist');
return;
}
var rewards = order.get_available_rewards();
if (rewards.length === 0) {
this.gui.show_popup('error', {
'title': _t('No Rewards Available'),
'body': _t('There are no rewards available for this customer as part of the loyalty program'),
});
} else if (rewards.length === 1 && this.pos.loyalty.rewards.length === 1) {
order.apply_reward(rewards[0]);
} else {
var list = [];
for (var i = 0; i < rewards.length; i++) {
list.push({
label: rewards[i].name,
item: rewards[i],
});
}
this.gui.show_popup('selection', {
'title': _t('Please select a reward'),
'list': list,
'confirm': function(reward) {
order.apply_reward(reward);
},
});
}
},
});
screens.define_action_button({
'name': 'loyalty',
'widget': LoyaltyButton,
'condition': function() {
return this.pos.loyalty && this.pos.loyalty.rewards.length;
},
});
screens.OrderWidget.include({
update_summary: function() {
this._super();
var order = this.pos.get_order();
var $loypoints = $(this.el).find('.summary .loyalty-points');
if (this.pos.loyalty && order.get_client()) {
var points_won = order.get_won_points();
var points_spent = order.get_spent_points();
var points_total = order.get_new_total_points();
$loypoints.replaceWith($(QWeb.render('LoyaltyPoints', {
widget: this,
rounding: this.pos.loyalty.rounding,
points_won: points_won,
points_spent: points_spent,
points_total: points_total,
})));
$loypoints = $(this.el).find('.summary .loyalty-points');
$loypoints.removeClass('oe_hidden');
if (points_total < 0) {
$loypoints.addClass('negative');
} else {
$loypoints.removeClass('negative');
}
} else {
$loypoints.empty();
$loypoints.addClass('oe_hidden');
}
if (this.pos.loyalty &&
order.get_client() &&
this.getParent().action_buttons &&
this.getParent().action_buttons.loyalty) {
var rewards = order.get_available_rewards();
this.getParent().action_buttons.loyalty.highlight(!!rewards.length);
}
},
});
});

170
pos_loyalty/static/src/js/tests.js

@ -0,0 +1,170 @@
// Copyright 2004-2018 Odoo SA
// Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
odoo.define('pos_loyalty.tour.test_pos_loyalty', function(require) {
"use strict";
// Some of the steps are taken from the pos_basic_order tour in point_of_sale.
// Added additional ones necessary for testing the rewards.
var Tour = require("web_tour.tour");
function add_customer(customer_name) {
return [{
content: 'open customer screen',
trigger: '.button.set-customer',
}, {
content: 'choose customer ' + customer_name,
trigger: 'table.client-list tbody.client-list-contents tr.client-line td:contains("' + customer_name + '")',
}, {
content: 'select customer ' + customer_name,
trigger: '.button.next:contains("Set Customer")',
}, {
content: 'Check if customer ' + customer_name + ' is added',
trigger: '.button.set-customer:contains("' + customer_name + '")',
run: function() {}, // it's a check
}];
}
function add_reward(reward_name) {
return [{
content: 'open rewards screen',
trigger: '.control-button:contains("Rewards")',
}, {
content: 'choose reward',
trigger: '.selection-item:contains("' + reward_name + '")',
}]
}
function add_product_to_order(product_name) {
return [{
content: 'buy ' + product_name,
trigger: '.product-list .product-name:contains("' + product_name + '")',
}, {
content: 'the ' + product_name + ' have been added to the order',
trigger: '.order .product-name:contains("' + product_name + '")',
run: function() {}, // it's a check
}];
}
function verify_order_product(product_name) {
return [{
content: 'check if ' + product_name + ' is in order',
trigger: '.orderline .product-name:contains("' + product_name + '")',
run: function() {}, // it's a check
}]
}
function generate_keypad_steps(amount_str, keypad_selector) {
var i, steps = [],
current_char;
for (i = 0; i < amount_str.length; ++i) {
current_char = amount_str[i];
steps.push({
content: 'press ' + current_char + ' on payment keypad',
trigger: keypad_selector + ' .input-button:contains("' + current_char + '"):visible'
});
}
return steps;
}
function generate_payment_screen_keypad_steps(amount_str) {
return generate_keypad_steps(amount_str, '.payment-numpad');
}
function generate_product_screen_keypad_steps(amount_str) {
return generate_keypad_steps(amount_str, '.numpad');
}
function verify_order_total(total_str) {
return [{
content: 'order total contains ' + total_str,
trigger: '.order .total .value:contains("' + total_str + '")',
run: function() {}, // it's a check
}];
}
function goto_payment_screen_and_select_payment_method() {
return [{
content: "go to payment screen",
trigger: '.button.pay',
}, {
content: "pay with cash",
trigger: '.paymentmethod:contains("Cash")',
}];
}
function finish_order() {
return [{
content: "validate the order",
trigger: '.button.next:visible',
}, {
content: "verify that the order is being sent to the backend",
trigger: ".js_connecting:visible",
run: function() {}, // it's a check
}, {
content: "verify that the order has been succesfully sent to the backend",
trigger: ".js_connected:visible",
run: function() {}, // it's a check
}, {
content: "next order",
trigger: '.button.next:visible',
}];
}
var steps = [{
content: 'waiting for loading to finish',
trigger: '.o_main_content:has(.loader:hidden)',
run: function() {}, // it's a check
}];
steps = steps.concat(add_customer('Agrolait'));
steps = steps.concat(add_product_to_order('Peaches'));
steps = steps.concat(verify_order_total('5.10'));
steps = steps.concat(add_product_to_order('Peaches')); // buy another kg of peaches
steps = steps.concat(verify_order_total('10.20'));
steps = steps.concat(goto_payment_screen_and_select_payment_method());
steps = steps.concat(generate_payment_screen_keypad_steps("12.20"));
steps = steps.concat([{
content: "verify tendered",
trigger: '.col-tendered:contains("12.20")',
run: function() {}, // it's a check
}, {
content: "verify change",
trigger: '.col-change:contains("2.00")',
run: function() {}, // it's a check
}]);
steps = steps.concat(finish_order());
Tour.register('test_pos_loyalty_acquire_points', {
test: true,
url: '/pos/web'
}, steps);
steps = [{
content: 'waiting for loading to finish',
trigger: '.o_main_content:has(.loader:hidden)',
run: function() {}, // it's a check
}];
steps = steps.concat(add_customer('Agrolait'));
steps = steps.concat(add_reward('Free Peaches'));
steps = steps.concat(verify_order_product('Peaches'));
steps = steps.concat(verify_order_total('0.00'));
steps = steps.concat(goto_payment_screen_and_select_payment_method());
steps = steps.concat([{
content: "verify tendered",
trigger: '.col-tendered:contains("0.00")',
run: function() {}, // it's a check
}]);
steps = steps.concat(finish_order());
Tour.register('test_pos_loyalty_spend_points', {
test: true,
url: '/pos/web'
}, steps);
})

85
pos_loyalty/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="LoyaltyPoints">
<div class='loyalty-points'>
<div class='loyalty-points-title'>Points</div>
<t t-if='points_won'>
<div class="loyalty-points-won">
<span class='value'>+<t t-esc='widget.format_pr(points_won,rounding)'/></span>
</div>
</t>
<t t-if='points_spent'>
<div class="loyalty-points-spent">
<span class='value'>-<t t-esc='widget.format_pr(points_spent,rounding)'/></span>
</div>
</t>
<div class='loyalty-points-total'>
<span class='value'><t t-esc='widget.format_pr(points_total,rounding)' /></span>
</div>
</div>
</t>
<t t-name="LoyaltyButton">
<div class='control-button'>
<i class='fa fa-star' /> Rewards
</div>
</t>
<t t-extend='OrderWidget'>
<t t-jquery='.summary' t-operation='prepend'>
<div class='loyalty-points oe_hidden'>.</div>
</t>
</t>
<t t-extend='ClientLine'>
<t t-jquery='.client-line' t-operation='append'>
<td>
<t t-esc='widget.format_pr(partner.loyalty_points || 0, widget.pos.loyalty ? widget.pos.loyalty.rounding : 1.0)' />
</td>
</t>
</t>
<t t-extend="XmlReceipt">
<t t-jquery='.before-footer' t-operation='append'>
<t t-if='receipt.loyalty'>
<div class='loyalty' value-decimals='2' value-autoint='on'>
<div>--------------------------------</div>
<br/>
<div size='double-height'><t t-esc='receipt.loyalty.name'/></div>
<br />
<div><t t-esc='receipt.loyalty.client' /></div>
<br/>
<t t-if='receipt.loyalty.points_won'>
<line><left>Points Won</left><right><value><t t-esc='receipt.loyalty.points_won' /></value></right></line>
</t>
<t t-if='receipt.loyalty.points_spent'>
<line><left>Points Spent</left><right><value><t t-esc='receipt.loyalty.points_spent' /></value></right></line>
</t>
<t t-if='receipt.loyalty.points_total'>
<line><left>Total Points</left><right><value><t t-esc='receipt.loyalty.points_total' /></value></right></line>
</t>
<br />
</div>
</t>
</t>
</t>
<t t-extend='ClientDetails'>
<t t-jquery='.client-details-right' t-operation='prepend'>
<div class='client-detail'>
<span class='label'>Points</span>
<span class='detail client-points'>
<t t-esc='widget.format_pr(partner.loyalty_points || 0, widget.pos.loyalty ? widget.pos.loyalty.rounding : 1.0)' />
</span>
</div>
</t>
</t>
<t t-extend='ClientListScreenWidget'>
<t t-jquery='.client-list thead tr' t-operation='append'>
<th>Points</th>
</t>
</t>
</templates>

2
pos_loyalty/tests/__init__.py

@ -0,0 +1,2 @@
from . import test_pos_loyalty

74
pos_loyalty/tests/test_pos_loyalty.py

@ -0,0 +1,74 @@
# Copyright 2004-2018 Odoo SA
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.api import Environment
from odoo.tests import HttpCase
class TestPOSLoyalty(HttpCase):
def test_pos_loyalty(self):
cr = self.registry.cursor()
assert cr == self.registry.test_cr
env = Environment(cr, self.uid, {})
main_pos_config = env.ref('point_of_sale.pos_config_main')
target_product = env.ref('point_of_sale.peche')
free_product = env.ref('point_of_sale.Onions')
customer = env.ref('base.res_partner_2')
loyalty_program = env['loyalty.program'].create({
'name': 'foo',
'rule_ids': [(0, 0, {
'name': 'Peaches',
'type': 'product',
'product_id': target_product.id,
'pp_product': 10,
})],
'reward_ids': [(0, 0, {
'name': 'Free Peaches',
'type': 'gift',
'gift_product_id': target_product.id,
'point_cost': 20,
'minimum_points': 20,
}), (0, 0, {
'name': 'Free Onions',
'type': 'gift',
'gift_product_id': free_product.id,
'point_cost': 20,
'minimum_points': 20,
})]
})
main_pos_config.write({'loyalty_id': loyalty_program.id})
main_pos_config.open_session_cb()
# needed because tests are run before the module is marked as
# installed. In js web will only load qweb coming from modules
# that are returned by the backend in module_boot. Without
# this you end up with js, css but no qweb.
env['ir.module.module'].search(
[('name', '=', 'pos_loyalty')], limit=1).state = 'installed'
cr.release()
# Process an order with 2kg of Peaches which should
# add 20 loyalty points
self.phantom_js("/pos/web",
"odoo.__DEBUG__.services['web_tour.tour'].run("
"'test_pos_loyalty_acquire_points')",
"odoo.__DEBUG__.services['web_tour.tour'].tours"
".test_pos_loyalty_acquire_points.ready",
login="admin")
self.assertEqual(customer.loyalty_points, 20)
# Spend 20 loyalty points on "Free Peaches" reward
self.phantom_js("/pos/web",
"odoo.__DEBUG__.services['web_tour.tour'].run("
"'test_pos_loyalty_spend_points')",
"odoo.__DEBUG__.services['web_tour.tour'].tours"
".test_pos_loyalty_spend_points.ready",
login="admin")
customer_points = customer.read(
['loyalty_points'])[0]['loyalty_points']
self.assertEqual(customer_points, 0)

83
pos_loyalty/views/loyalty_program_view.xml

@ -0,0 +1,83 @@
<?xml version="1.0"?>
<odoo>
<record id="loyalty_program_form_view" model="ir.ui.view">
<field name="name">loyalty.program.form</field>
<field name="model">loyalty.program</field>
<field name="arch" type="xml">
<form string="Loyalty Program">
<sheet>
<div class="oe_title">
<label class="oe_edit_only" for="name"/>
<h1>
<field name="name" class="oe_inline"/>
</h1>
</div>
<group>
<group>
<field name="pp_product"/>
<field name="pp_currency"/>
</group>
<group>
<field name="pp_order"/>
<field name="rounding"/>
</group>
</group>
<separator string="Rules" colspan="4"/>
<p>Rules define how loyalty points are earned for specific products or categories</p>
<field name="rule_ids" colspan="4" nolabel="1">
<tree string="Rules">
<field name="name"/>
<field name="pp_product"/>
<field name="pp_currency"/>
<field name="cumulative"/>
</tree>
</field>
<separator string="Rewards" colspan="4"/>
<p>Reward the customer with gifts or discounts for loyalty points</p>
<field name="reward_ids" colspan="4" nolabel="1">
<tree string="Rewards">
<field name="name"/>
<field name="type"/>
</tree>
</field>
</sheet>
</form>
</field>
</record>
<record id="loyalty_program_tree_view" model="ir.ui.view">
<field name="name">loyalty.program.tree</field>
<field name="model">loyalty.program</field>
<field name="arch" type="xml">
<tree string="Loyalty Programs">
<field name="name"/>
</tree>
</field>
</record>
<record id="loyalty_program_action" model="ir.actions.act_window">
<field name="name">Loyalty Programs</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">loyalty.program</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click create to define a Loyalty Program.
</p>
<p>
Loyalty Programs allow you customers to earn points
and rewards when purchasing from your shops.
</p>
</field>
</record>
<menuitem parent="point_of_sale.menu_point_config_product"
action="loyalty_program_action"
id="loyalty_program_menu"
sequence="30"
groups="point_of_sale.group_pos_manager"/>
</odoo>

37
pos_loyalty/views/loyalty_reward_view.xml

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<odoo>
<record id="loyalty_reward_form_view" model="ir.ui.view">
<field name="name">loyalty.reward.form</field>
<field name="model">loyalty.reward</field>
<field name="arch" type="xml">
<form string="Loyalty Reward">
<div class="oe_title">
<label class="oe_edit_only" for="name"/>
<h1>
<field name="name" class="oe_inline"/>
</h1>
</div>
<group>
<field name="type"/>
</group>
<group >
<field name="gift_product_id"
attrs="{'invisible':[('type','!=','gift')], 'required':[('type','=','gift')]}"/>
<field name="discount"
attrs="{'invisible':[('type','!=','discount')]}"/>
<field name="discount_max"
attrs="{'invisible':[('type','!=','discount')]}"/>
<field name="discount_product_id"
attrs="{'invisible':[('type','!=','discount')], 'required':[('type','=','discount')]}"/>
<field name="point_product_id"
attrs="{'invisible': [('type','!=','resale')], 'required': [('type','=','resale')]}"/>
</group>
<group>
<field name="point_cost"
attrs="{'invisible':[('type','=','resale')]}"/>
<field name="minimum_points"/>
</group>
</form>
</field>
</record>
</odoo>

31
pos_loyalty/views/loyalty_rule_view.xml

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<odoo>
<record id="loyalty_rule_form_view" model="ir.ui.view">
<field name="name">loyalty.rule.form</field>
<field name="model">loyalty.rule</field>
<field name="arch" type="xml">
<form string="Loyalty Rule">
<div class="oe_title">
<label class="oe_edit_only" for="name"/>
<h1>
<field name="name" class="oe_inline"/>
</h1>
</div>
<group>
<group>
<field name="type"/>
</group>
<group>
<field name="product_id" attrs="{'invisible': [('type','!=','product')], 'required': [('type','=','product')]}"/>
<field name="category_id" attrs="{'invisible': [('type','!=','category')], 'required': [('type','=','category')]}"/>
</group>
</group>
<group col="6">
<field name="pp_product"/>
<field name="pp_currency"/>
<field name="cumulative"/>
</group>
</form>
</field>
</record>
</odoo>

23
pos_loyalty/views/pos_config_view.xml

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<odoo>
<record id="pos_config_view_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='pricelist_setting']" position="after">
<div class="col-xs-12 col-md-6 o_setting_box" title="abc">
<div class="o_setting_right_pane">
<label for="loyalty_id" string="Loyalty Program (OCA)"/>
<div class="text-muted">
Loyalty program that will be available in this PoS
</div>
<div class="content-group mt16">
<field name="loyalty_id"/>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

15
pos_loyalty/views/pos_order_view.xml

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<odoo>
<record id="pos_order_form_view" model="ir.ui.view">
<field name="name">pos.order.form</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='extra']" position="inside">
<group name="loyalty" string="Loyalty Programs">
<field name="loyalty_points"/>
</group>
</xpath>
</field>
</record>
</odoo>

18
pos_loyalty/views/res_partner_view.xml

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<odoo>
<record id="partner_property_form_view" model="ir.ui.view">
<field name="name">partner.property.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="point_of_sale.view_partner_property_form"/>
<field name="arch" type="xml">
<button name="%(point_of_sale.action_pos_pos_form)d" position="before">
<button class="oe_stat_button" type="action" name="%(point_of_sale.action_pos_pos_form)d"
context="{'search_default_partner_id': active_id,'default_partner_id': active_id}"
attrs="{'invisible': ['|', ('customer', '=', False), ('pos_order_count', '=', 0)]}"
icon="fa-star">
<field name="loyalty_points" widget="statinfo"/>
</button>
</button>
</field>
</record>
</odoo>

9
pos_loyalty/views/templates.xml

@ -0,0 +1,9 @@
<?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_loyalty/static/src/js/pos.js"></script>
<link rel="stylesheet" href="/pos_loyalty/static/src/css/pos.css"/>
</xpath>
</template>
</odoo>

86
pos_mail_receipt/README.rst

@ -0,0 +1,86 @@
================
Pos Mail Receipt
================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! 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/12.0/pos_mail_receipt
:alt: OCA/pos
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/pos-12-0/pos-12-0-pos_mail_receipt
: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/12.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This modules allows you to send the PoS receipt by e-mail.
Instead of printing the ticket you can choose to send it be e-mail.
If you know the customer's e-mail, it will use it.
If not you will be prompted for it.
**Table of contents**
.. contents::
:local:
Usage
=====
#. Open a new PoS session.
#. Make an order and validate it.
#. You should see the company logo in the receipt preview.
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_mail_receipt%0Aversion:%2012.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
~~~~~~~
* Coop IT Easy SCRLfs
Contributors
~~~~~~~~~~~~
* `Coop IT Easy SCRLfs <https://coopiteasy.be>`_:
* Pierrick Brun
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/12.0/pos_mail_receipt>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

1
pos_mail_receipt/__init__.py

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

14
pos_mail_receipt/__manifest__.py

@ -0,0 +1,14 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Pos Mail Receipt",
"category": "Point Of Sale",
"version": "12.0.1.0.0",
"author": "Coop IT Easy SCRLfs, " "Odoo Community Association (OCA)",
"website": "https://github.com/OCA/pos",
"license": "AGPL-3",
"depends": ["point_of_sale"],
"data": ["templates/assets.xml", "data/email.xml"],
"qweb": ["static/src/xml/pos.xml"],
"installable": True,
}

19
pos_mail_receipt/data/email.xml

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!--Email template -->
<record id="email_send_ticket" model="mail.template">
<field name="name">Send Receipt</field>
<field name="email_from">${(object.user_id.email and '%s &lt;%s&gt;' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
<field name="subject">${object.pos_reference}</field>
<field name="model_id" ref="point_of_sale.model_pos_order" />
<field name="auto_delete" eval="True" />
<field name="lang">${object.partner_id.lang}</field>
<field name="body_html"><![CDATA[
Your Ticket ${object.pos_reference}
]]></field>
</record>
</data>
</odoo>

131
pos_mail_receipt/i18n/fr.po

@ -0,0 +1,131 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_mail_receipt
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-17 14:32+0000\n"
"PO-Revision-Date: 2019-12-17 15:38+0100\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 2.2.4\n"
"Last-Translator: \n"
"Language: fr\n"
#. module: pos_mail_receipt
#: model:mail.template,subject:pos_mail_receipt.email_send_ticket
msgid "${object.pos_reference}"
msgstr ""
#. module: pos_mail_receipt
#: model:mail.template,body_html:pos_mail_receipt.email_send_ticket
msgid ""
"<p style=\"margin:0px 0 1rem 0;font-size:13px;font-family:&quot;Lucida "
"Grande&quot;, Helvetica, Verdana, Arial, sans-serif;\">\n"
"Your Ticket ${object.pos_reference}\n"
" </p>"
msgstr ""
"<p style=\"margin:0px 0 1rem 0;font-size:13px;font-family:&quot;Lucida "
"Grande&quot;, Helvetica, Verdana, Arial, sans-serif;\">\n"
"Votre Ticket ${object.pos_reference}\n"
" </p>"
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/xml/pos.xml:25
#, python-format
msgid "Cancel"
msgstr "Annuler"
#. module: pos_mail_receipt
#: code:addons/pos_mail_receipt/models/pos_order.py:25
#, python-format
msgid "Cannot send the ticket, no email address found for the client"
msgstr ""
"Impossible d'envoyer le ticket, pas d'adresse mail trouvée pour le "
"client"
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/js/screens.js:82
#, python-format
msgid "Check your internet connection and try again."
msgstr "Vérifiez votre connexion internet et essayez à nouveau."
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/xml/pos.xml:22
#, python-format
msgid "Confirm"
msgstr "Confirmer"
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/js/screens.js:28
#, python-format
msgid "E-mail address to use"
msgstr "Adresse mail à utiliser"
#. module: pos_mail_receipt
#: code:addons/pos_mail_receipt/models/pos_order.py:23
#, python-format
msgid "E-mail already sent"
msgstr "E-mail déjà envoyé"
#. module: pos_mail_receipt
#: model:ir.model.fields,field_description:pos_mail_receipt.field_pos_order__email_receipt_sent
msgid "Email Receipt Sent"
msgstr "Reçu envoyé"
#. module: pos_mail_receipt
#: code:addons/pos_mail_receipt/models/pos_order.py:21
#, python-format
msgid "Error: no order found"
msgstr "Erreur: pas de commande trouvée"
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/xml/pos.xml:19
#, python-format
msgid "Mail"
msgstr "Mail"
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/xml/pos.xml:8
#, python-format
msgid "Mail Receipt"
msgstr "Reçu"
#. module: pos_mail_receipt
#: model:ir.model,name:pos_mail_receipt.model_pos_order
msgid "Point of Sale Orders"
msgstr "Commandes du point de vente"
#. module: pos_mail_receipt
#: code:addons/pos_mail_receipt/models/pos_order.py:44
#, python-format
msgid "Receipt_{}.pdf"
msgstr "Recu_{}.pdf"
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/js/screens.js:81
#, python-format
msgid "The e-mail could not be sent"
msgstr "L'e-mail n'a pas pu être envoyé"
#. module: pos_mail_receipt
#: model:mail.template,report_name:pos_mail_receipt.email_send_ticket
msgid "Ticket ${object.pos_reference}"
msgstr "Ticket ${object.pos_reference}"
#. module: pos_mail_receipt
#: model:ir.model,name:pos_mail_receipt.model_report_pos_mail_receipt_pos_receipt_report
msgid "report.pos_mail_receipt.pos_receipt_report"
msgstr ""

122
pos_mail_receipt/i18n/pos_mail_receipt.pot

@ -0,0 +1,122 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_mail_receipt
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-17 14:32+0000\n"
"PO-Revision-Date: 2019-12-17 14:32+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_mail_receipt
#: model:mail.template,subject:pos_mail_receipt.email_send_ticket
msgid "${object.pos_reference}"
msgstr ""
#. module: pos_mail_receipt
#: model:mail.template,body_html:pos_mail_receipt.email_send_ticket
msgid "<p style=\"margin:0px 0 1rem 0;font-size:13px;font-family:&quot;Lucida Grande&quot;, Helvetica, Verdana, Arial, sans-serif;\">\n"
"Your Ticket ${object.pos_reference}\n"
" </p>"
msgstr ""
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/xml/pos.xml:25
#, python-format
msgid "Cancel"
msgstr ""
#. module: pos_mail_receipt
#: code:addons/pos_mail_receipt/models/pos_order.py:25
#, python-format
msgid "Cannot send the ticket, no email address found for the client"
msgstr ""
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/js/screens.js:82
#, python-format
msgid "Check your internet connection and try again."
msgstr ""
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/xml/pos.xml:22
#, python-format
msgid "Confirm"
msgstr ""
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/js/screens.js:28
#, python-format
msgid "E-mail address to use"
msgstr ""
#. module: pos_mail_receipt
#: code:addons/pos_mail_receipt/models/pos_order.py:23
#, python-format
msgid "E-mail already sent"
msgstr ""
#. module: pos_mail_receipt
#: model:ir.model.fields,field_description:pos_mail_receipt.field_pos_order__email_receipt_sent
msgid "Email Receipt Sent"
msgstr ""
#. module: pos_mail_receipt
#: code:addons/pos_mail_receipt/models/pos_order.py:21
#, python-format
msgid "Error: no order found"
msgstr ""
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/xml/pos.xml:19
#, python-format
msgid "Mail"
msgstr ""
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/xml/pos.xml:8
#, python-format
msgid "Mail Receipt"
msgstr ""
#. module: pos_mail_receipt
#: model:ir.model,name:pos_mail_receipt.model_pos_order
msgid "Point of Sale Orders"
msgstr ""
#. module: pos_mail_receipt
#: code:addons/pos_mail_receipt/models/pos_order.py:44
#, python-format
msgid "Receipt_{}.pdf"
msgstr ""
#. module: pos_mail_receipt
#. openerp-web
#: code:addons/pos_mail_receipt/static/src/js/screens.js:81
#, python-format
msgid "The e-mail could not be sent"
msgstr ""
#. module: pos_mail_receipt
#: model:mail.template,report_name:pos_mail_receipt.email_send_ticket
msgid "Ticket ${object.pos_reference}"
msgstr ""
#. module: pos_mail_receipt
#: model:ir.model,name:pos_mail_receipt.model_report_pos_mail_receipt_pos_receipt_report
msgid "report.pos_mail_receipt.pos_receipt_report"
msgstr ""

1
pos_mail_receipt/models/__init__.py

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

86
pos_mail_receipt/models/pos_order.py

@ -0,0 +1,86 @@
# Copyright 2019 Coop IT Easy SCRLfs
# @author Pierrick Brun <pierrick.brun@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
import base64
from odoo import fields, models, api, _
_logger = logging.getLogger(__name__)
class PosOrder(models.Model):
_inherit = "pos.order"
email_receipt_sent = fields.Boolean()
@api.model
def send_mail_receipt(
self, pos_reference, email, body_from_ui, force=True
):
order = self.search([("pos_reference", "=", pos_reference)])
if len(order) < 1:
_logger.error(_("Error: no order found"))
return
if order.email_receipt_sent:
_logger.info(_("E-mail already sent"))
return
if not email and not order.partner_id and not order.partner_id.email:
_logger.error(
_(
"Cannot send the ticket, "
"no email address found for the client"
)
)
email_values = {}
if email:
email_values["email_to"] = email
else:
email_values["email_to"] = order.partner_id.email
receipt = (
"<main><div class='article'><div class='pos'>"
"<div class='pos-receipt-container'>"
"{}</div></div></div></main>".format(body_from_ui)
)
bodies, html_ids, header, footer, specific_paperformat_args = self.env[
"ir.actions.report"
]._prepare_html(receipt)
base64_pdf = self.env["ir.actions.report"]._run_wkhtmltopdf(
bodies,
landscape=False,
specific_paperformat_args=specific_paperformat_args,
)
attachment = self.env["ir.attachment"].create(
{
"name": pos_reference,
"datas_fname": _("Receipt_{}.pdf".format(pos_reference)),
"type": "binary",
"mimetype": "application/x-pdf",
"db_datas": base64.encodestring(base64_pdf),
"res_model": "pos.order",
"res_id": order.id,
}
)
email_values["attachment_ids"] = [attachment.id]
mail_template = self.env.ref("pos_mail_receipt.email_send_ticket")
mail_template.send_mail(
order.id, force_send=force, email_values=email_values,
)
order.email_receipt_sent = True
@api.model
def create_from_ui(self, orders):
res = super(PosOrder, self).create_from_ui(orders)
for order in orders:
if "email" in order["data"]:
self.send_mail_receipt(
order["data"]["name"],
order["data"]["email"],
order["data"]["body_from_ui"],
force=False,
)
return res

3
pos_mail_receipt/readme/CONTRIBUTORS.rst

@ -0,0 +1,3 @@
* `Coop IT Easy SCRLfs <https://coopiteasy.be>`_:
* Pierrick Brun

5
pos_mail_receipt/readme/DESCRIPTION.rst

@ -0,0 +1,5 @@
This modules allows you to send the PoS receipt by e-mail.
Instead of printing the ticket you can choose to send it be e-mail.
If you know the customer's e-mail, it will use it.
If not you will be prompted for it.

3
pos_mail_receipt/readme/USAGE.rst

@ -0,0 +1,3 @@
#. Open a new PoS session.
#. Make an order and validate it.
#. You should see the company logo in the receipt preview.

BIN
pos_mail_receipt/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.3 KiB

434
pos_mail_receipt/static/description/index.html

@ -0,0 +1,434 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Pos Mail Receipt</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="pos-mail-receipt">
<h1 class="title">Pos Mail Receipt</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/pos/tree/12.0/pos_mail_receipt"><img alt="OCA/pos" src="https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/pos-12-0/pos-12-0-pos_mail_receipt"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/184/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This modules allows you to send the PoS receipt by e-mail.</p>
<p>Instead of printing the ticket you can choose to send it be e-mail.
If you know the customer’s e-mail, it will use it.
If not you will be prompted for it.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="id1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id1">Usage</a></h1>
<ol class="arabic simple">
<li>Open a new PoS session.</li>
<li>Make an order and validate it.</li>
<li>You should see the company logo in the receipt preview.</li>
</ol>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/pos/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/pos/issues/new?body=module:%20pos_mail_receipt%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id4">Authors</a></h2>
<ul class="simple">
<li>Coop IT Easy SCRLfs</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id5">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://coopiteasy.be">Coop IT Easy SCRLfs</a>:<ul>
<li>Pierrick Brun</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/pos/tree/12.0/pos_mail_receipt">OCA/pos</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

3
pos_mail_receipt/static/src/css/pos.css

@ -0,0 +1,3 @@
.pos .modal-dialog .popup-textinput input{
margin-left: 10%
}

129
pos_mail_receipt/static/src/css/print.css

@ -0,0 +1,129 @@
/* This is a copy of the pos receipt screen CSS
* This allows to use it in the backend */
.pos .receipt-screen .centered-content .button {
line-height: 40px;
padding: 3px 13px;
font-size: 20px;
text-align: center;
background: rgb(230, 230, 230);
margin: 16px;
margin-bottom: 0px;
border-radius: 3px;
border: solid 1px rgb(209, 209, 209);
cursor: pointer;
}
.pos .pos-receipt-container {
font-size: 0.75em;
text-align: center;
direction: ltr;
}
.pos .pos-sale-ticket {
text-align: left;
width: 300px;
background-color: white;
margin: 20px;
padding: 15px;
font-size: 14px;
padding-bottom:30px;
display: inline-block;
font-family: "Inconsolata";
border: solid 1px rgb(220,220,220);
border-radius: 3px;
overflow: hidden;
}
.pos .pos-sale-ticket pre{
font-family: "Inconsolata";
}
.pos .pos-sale-ticket .emph{
font-size: 20px;
margin:5px;
}
.pos .pos-sale-ticket table {
width: 100%;
border: 0;
table-layout: fixed;
}
.pos .pos-sale-ticket table td {
border: 0;
word-wrap: break-word;
}
@page {
margin: 0;
}
@media print {
* {
color: black !important;
}
body {
margin: 0;
color: white !important;
/* avoid black background if backgrounds are printed */
background: initial;
}
.oe_leftbar,
.oe_loading,
.pos .pos-topheader,
.pos .pos-leftpane,
.pos .keyboard_frame,
.pos .receipt-screen header,
.pos .receipt-screen .top-content,
.pos .receipt-screen .centered-content .button {
display: none !important;
}
.pos,
.pos .pos-content,
.pos .rightpane,
.pos .screen,
.pos .window,
.pos .window .subwindow,
.pos .subwindow .subwindow-container{
display: block;
position: static;
height: auto;
}
.pos{
background: white !important;
}
.pos .rightpane {
left: 0px !important;
background-color: white;
}
.pos .receipt-screen {
text-align: left;
}
.pos .receipt-screen .centered-content{
position: static;
border: none;
}
.pos .pos-receipt-container {
text-align: left;
}
.pos-actionbar {
display: none !important;
}
.debug-widget{
display: none !important;
}
.pos *{
text-shadow: none !important;
box-shadow: none !important;
background: transparent !important;
}
.pos .pos-sale-ticket{
margin: 0;
margin-left: auto !important;
margin-right: auto !important;
border: none !important;
font-size: 13px !important;
width: 266px !important;
}
.o_debug_manager {
display: none !important;
}
.o_chat_window {
display: none !important;
}
}

102
pos_mail_receipt/static/src/js/screens.js

@ -0,0 +1,102 @@
/* License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */
odoo.define("pos_mail_receipt.screens", function (require) {
"use strict";
var screens = require('point_of_sale.screens');
var rpc = require('web.rpc');
var core = require('web.core');
var _t = core._t;
var ReceiptScreenWidget = screens.ReceiptScreenWidget.include({
renderElement: function() {
this._super();
var self = this;
this.$('.button.email').click(function(){
if (!self._locked) {
self.email();
}
});
},
click_next: function() {
this._super();
this.$('.button.email').removeClass("highlight");
},
email: function() {
var self = this;
var email = false;
var body_from_ui = this.$('.pos-receipt-container').html()
if( this.pos.get_order().get_client() && this.pos.get_order().get_client().email ) {
self._send_email_server(this.pos.get_order().name, {"email": this.pos.get_order().get_client().email, "body_from_ui": body_from_ui});
} else {
this.gui.show_popup('textinput', {
'title':_t('E-mail address to use'),
'value': '',
'confirm': function(value) {
self._send_email_server(self.pos.get_order().name, {"email": value, "body_from_ui": body_from_ui});
}
});
}
},
// ask the server to send the ticket as e-mail
// available options:
// - timeout: timeout for the rpc call in ms
// - email: email to use instead of the partner's
// returns a deferred
_send_email_server: async function (order, options) {
var self = this;
options = options || {};
var timeout = typeof options.timeout === 'number' ? options.timeout : 7500;
this.$('.button.email').addClass("highlight");
while (self.pos.get("synch").state == "connecting") {
await sleep(1000);
}
return rpc.query({
model: 'pos.order',
method: 'send_mail_receipt',
args: [order, options["email"], options["body_from_ui"]],
}, {
timeout: timeout,
})
.then(function (result) {
return true
}).fail(function (type, error){
var connection_problem = true;
for (var i = 0; i < self.pos.db.get_orders().length; i++) {
if (order == self.pos.db.get_orders()[i].data.name) {
self.pos.db.get_orders()[i].data["email"] = options["email"] || false;
self.pos.db.get_orders()[i].data["body_from_ui"] = options["body_from_ui"] || false;
connection_problem = false;
}
}
if(error.code === 200 ){
// Business Logic Error, not a connection problem
//if warning do not need to display traceback!!
if (error.data.exception_type == 'warning') {
delete error.data.debug;
}
self.gui.show_popup('error-traceback',{
'title': error.data.message,
'body': error.data.debug
});
self.$('.button.email').removeClass("highlight");
}
if(connection_problem){
self.gui.show_popup('error',{
'title': _t('The e-mail could not be sent'),
'body': _t('The e-mail could not be sent to ') + options["email"] + _t('. Check your internet connection and try again.'),
});
self.$('.button.email').removeClass("highlight");
}
});
},
});
});
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

13
pos_mail_receipt/static/src/xml/pos.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-extend="ReceiptScreenWidget">
<t t-jquery=".button.print" t-operation="after">
<div class="button email">
<i class='fa fa-at'></i> Mail Receipt
</div>
</t>
</t>
</templates>

20
pos_mail_receipt/templates/assets.xml

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets" inherit_id="point_of_sale.assets">
<xpath expr=".">
<link href="/pos_mail_receipt/static/src/css/pos.css" rel="stylesheet"/>
<script
type="text/javascript"
src="/pos_mail_receipt/static/src/js/screens.js"/>
</xpath>
</template>
<template id="report_assets_pdf" inherit_id="web.report_assets_pdf">
<xpath expr=".">
<link href="/pos_mail_receipt/static/src/css/print.css" rel="stylesheet"/>
</xpath>
</template>
</odoo>

1
pos_mail_receipt/tests/__init__.py

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save