diff --git a/hw_customer_display/__init__.py b/hw_customer_display/__init__.py new file mode 100644 index 00000000..ca8d6d66 --- /dev/null +++ b/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 +# +# 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 . +# +############################################################################## + + +from . import controllers diff --git a/hw_customer_display/__manifest__.py b/hw_customer_display/__manifest__.py new file mode 100644 index 00000000..60774e24 --- /dev/null +++ b/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 +# +# 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 . +# +############################################################################## + + +{ + '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 +. + """, + 'author': "Akretion,Odoo Community Association (OCA)", + 'website': 'http://www.akretion.com', + 'depends': ['hw_proxy'], + 'external_dependencies': { + 'python': ['serial', 'unidecode'], + }, + 'data': [], + 'installable':'False' +} diff --git a/hw_customer_display/controllers/__init__.py b/hw_customer_display/controllers/__init__.py new file mode 100644 index 00000000..7ae94c9e --- /dev/null +++ b/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 +# +# 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 . +# +############################################################################## + + +from . import main diff --git a/hw_customer_display/controllers/main.py b/hw_customer_display/controllers/main.py new file mode 100644 index 00000000..5cb6f82d --- /dev/null +++ b/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 +# +# 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 . +# +############################################################################## + + +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) diff --git a/hw_customer_display/i18n/hw_customer_display.pot b/hw_customer_display/i18n/hw_customer_display.pot new file mode 100644 index 00000000..386b2558 --- /dev/null +++ b/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" + diff --git a/hw_customer_display/test-scripts/customer-display-test.py b/hw_customer_display/test-scripts/customer-display-test.py new file mode 100755 index 00000000..46df8ff6 --- /dev/null +++ b/hw_customer_display/test-scripts/customer-display-test.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# Author : Alexis de Lattre +# 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) diff --git a/hw_customer_display_currency/__init__.py b/hw_customer_display_currency/__init__.py new file mode 100644 index 00000000..2fb4279b --- /dev/null +++ b/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 diff --git a/hw_customer_display_currency/__manifest__.py b/hw_customer_display_currency/__manifest__.py new file mode 100644 index 00000000..747cb177 --- /dev/null +++ b/hw_customer_display_currency/__manifest__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Coop IT Easy SCRLfs +# Vincent Van Rossem +# 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, +} diff --git a/hw_customer_display_currency/controllers/__init__.py b/hw_customer_display_currency/controllers/__init__.py new file mode 100644 index 00000000..757b12a1 --- /dev/null +++ b/hw_customer_display_currency/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import main diff --git a/hw_customer_display_currency/controllers/main.py b/hw_customer_display_currency/controllers/main.py new file mode 100644 index 00000000..9ddec9c1 --- /dev/null +++ b/hw_customer_display_currency/controllers/main.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Coop IT Easy SCRLfs +# Vincent Van Rossem +# 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) diff --git a/hw_customer_display_currency/test-scripts/customer_display_currency_test.py b/hw_customer_display_currency/test-scripts/customer_display_currency_test.py new file mode 100644 index 00000000..31fe39b1 --- /dev/null +++ b/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 +# 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) diff --git a/pos_container/README.rst b/pos_container/README.rst new file mode 100644 index 00000000..28f80f30 --- /dev/null +++ b/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 `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Coop IT Easy SCRLfs + +Contributors +~~~~~~~~~~~~ + + * Pierrick Brun + * Robin Keunen + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_container/__init__.py b/pos_container/__init__.py new file mode 100644 index 00000000..02179fb0 --- /dev/null +++ b/pos_container/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import models diff --git a/pos_container/__manifest__.py b/pos_container/__manifest__.py new file mode 100644 index 00000000..e0c9c6a3 --- /dev/null +++ b/pos_container/__manifest__.py @@ -0,0 +1,36 @@ +# Copyright 2019 Coop IT Easy SCRLfs +# Robin Keunen +# Pierrick Brun +# 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, +} diff --git a/pos_container/data/product.xml b/pos_container/data/product.xml new file mode 100644 index 00000000..d09932ee --- /dev/null +++ b/pos_container/data/product.xml @@ -0,0 +1,20 @@ + + + Container without product + + CONTAINER + + + + + This product is used to describe POS order lines having a container but no product yet + + 0 + + + + + + + + diff --git a/pos_container/demo/demo.xml b/pos_container/demo/demo.xml new file mode 100644 index 00000000..ed417ae9 --- /dev/null +++ b/pos_container/demo/demo.xml @@ -0,0 +1,19 @@ + + + Container 1 + 0498765456789 + 0.123 + + + + Container 2 + 0490987654356 + 0.234 + + + + Container 3 + 0490987654398 + 0.567 + + diff --git a/pos_container/i18n/fr.po b/pos_container/i18n/fr.po new file mode 100644 index 00000000..4368977e --- /dev/null +++ b/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" diff --git a/pos_container/i18n/pos_container.pot b/pos_container/i18n/pos_container.pot new file mode 100644 index 00000000..b863edce --- /dev/null +++ b/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 "" + diff --git a/pos_container/models/__init__.py b/pos_container/models/__init__.py new file mode 100644 index 00000000..b662490e --- /dev/null +++ b/pos_container/models/__init__.py @@ -0,0 +1,3 @@ +from . import container +from . import barcode +from . import pos_order_line diff --git a/pos_container/models/barcode.py b/pos_container/models/barcode.py new file mode 100644 index 00000000..78e1ab03 --- /dev/null +++ b/pos_container/models/barcode.py @@ -0,0 +1,13 @@ +# Copyright 2019 Coop IT Easy SCRLfs +# Pierrick Brun +# 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')], + ) diff --git a/pos_container/models/container.py b/pos_container/models/container.py new file mode 100644 index 00000000..79abd6dc --- /dev/null +++ b/pos_container/models/container.py @@ -0,0 +1,45 @@ +# Copyright 2019 Coop IT Easy SCRLfs +# Robin Keunen +# 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 diff --git a/pos_container/models/pos_order_line.py b/pos_container/models/pos_order_line.py new file mode 100644 index 00000000..1a0899ca --- /dev/null +++ b/pos_container/models/pos_order_line.py @@ -0,0 +1,15 @@ +# Copyright 2019 Coop IT Easy SCRLfs +# @author Pierrick Brun +# 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') diff --git a/pos_container/readme/CONTRIBUTORS.rst b/pos_container/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..872a7eaf --- /dev/null +++ b/pos_container/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ + * Pierrick Brun + * Robin Keunen diff --git a/pos_container/readme/DESCRIPTION.rst b/pos_container/readme/DESCRIPTION.rst new file mode 100644 index 00000000..1bba872c --- /dev/null +++ b/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. diff --git a/pos_container/readme/USAGE.rst b/pos_container/readme/USAGE.rst new file mode 100644 index 00000000..f5a5c7ae --- /dev/null +++ b/pos_container/readme/USAGE.rst @@ -0,0 +1,2 @@ +You have to create a Barcode Nomenclature to handle containers before using the +module. diff --git a/pos_container/security/ir.model.access.csv b/pos_container/security/ir.model.access.csv new file mode 100644 index 00000000..9bac7aa8 --- /dev/null +++ b/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 diff --git a/pos_container/static/description/index.html b/pos_container/static/description/index.html new file mode 100644 index 00000000..a141c557 --- /dev/null +++ b/pos_container/static/description/index.html @@ -0,0 +1,431 @@ + + + + + + +POS Container + + + +
+

POS Container

+ + +

Beta OCA/pos Translate me on Weblate Try me on Runbot

+

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

+ +
+

Usage

+

You have to create a Barcode Nomenclature to handle containers before using the +module.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Coop IT Easy SCRLfs
  • +
+
+
+

Contributors

+
+ +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_container/static/src/css/container.css b/pos_container/static/src/css/container.css new file mode 100644 index 00000000..17260fd2 --- /dev/null +++ b/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; +} diff --git a/pos_container/static/src/js/container.js b/pos_container/static/src/js/container.js new file mode 100644 index 00000000..9f55da23 --- /dev/null +++ b/pos_container/static/src/js/container.js @@ -0,0 +1,431 @@ +/* + Copyright 2019 Coop IT Easy SCRLfs + Robin Keunen + Pierrick Brun + 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, + }; + +}); diff --git a/pos_container/static/src/js/models_and_db.js b/pos_container/static/src/js/models_and_db.js new file mode 100644 index 00000000..883e97a7 --- /dev/null +++ b/pos_container/static/src/js/models_and_db.js @@ -0,0 +1,619 @@ +/* + Copyright 2019 Coop IT Easy SCRLfs + Pierrick Brun + 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('= 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(''+subreceipt+''); + + 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(); + }, + }); + +}); diff --git a/pos_container/static/src/js/tests.js b/pos_container/static/src/js/tests.js new file mode 100644 index 00000000..ec07e100 --- /dev/null +++ b/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); +}); diff --git a/pos_container/static/src/xml/pos.xml b/pos_container/static/src/xml/pos.xml new file mode 100644 index 00000000..fcd142a2 --- /dev/null +++ b/pos_container/static/src/xml/pos.xml @@ -0,0 +1,442 @@ + + + + + + + + + + + + +
+
+
+ + + Cancel + + + + + + + + + + + + Select a container + + +
+
+
+
+
+
+ + + + + + + + + + +
NameBarcodeWeight
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
    +
      + +
    • + Gross : kg +
    • +
      +
    • + : + kg + - + +
    • +
    +
+
+ +
    +
      + +
    • + Gross : kg +
    • +
      +
    • + : + kg + - Manual tare +
    • +
    +
+
+
+ +
+
+
+ + +
+
+
+ + + Back + +

Add a container

+
+
+
+ +
+
+ Container name:
+ +
+
+ Save + +
+
+
+
+
+ + + + + + + + + + + + + + + + +
+ + +
+ With a % discount +
+
+
+ + + +
+ + + + + + + + + +
+
------------------------
+
Automatic Weighing
+
------------------------
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ With a % discount +
+
+
+ + +
+
+ + x + / +
+
+
+ Tare : + +
+
+
+ Tare : + +
+
+ + + + + + + + + +
+
------------------------
+
Manual Input
+
------------------------
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ + +
+ With a % discount +
+
+
+ + + +
+
+ Tare : + +
+
+
+
+ + + + + + + + + + + + + + + + + + Discount: % + + + + + + + + + + x + + + + + + + + + + + +
------------------
+ + Automatic Weighing +
------------------
+ + + + + + + + + + + + + + + Discount: % + + + + + + + + + + x + + + + + + + + + + + + Tare : + + + + + + + Tare : + + + + + + +
------------------
+
+ + Manual Input +
------------------
+ + + + + + + + + + + + + + + Discount: % + + + + + + + + + + x + + + + + + + + + + + + Tare : + + + + + + + + + + +
diff --git a/pos_container/templates/templates.xml b/pos_container/templates/templates.xml new file mode 100644 index 00000000..a7828db8 --- /dev/null +++ b/pos_container/templates/templates.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/pos_customer_display_currency/views/pos_config_view.xml b/pos_customer_display_currency/views/pos_config_view.xml new file mode 100644 index 00000000..986280be --- /dev/null +++ b/pos_customer_display_currency/views/pos_config_view.xml @@ -0,0 +1,17 @@ + + + + pos_customer_display_currency.pos.config.form + pos.config + + + +
+
+
+
+
+ +
diff --git a/pos_hash_cert/__init__.py b/pos_hash_cert/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/pos_hash_cert/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_hash_cert/__manifest__.py b/pos_hash_cert/__manifest__.py new file mode 100644 index 00000000..7c5e8db4 --- /dev/null +++ b/pos_hash_cert/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "POS Hash Certification", + "summary": "Module de certification ", + "version": "12.0.1.0.1", + "category": "POS", + "website": "https://vracoop.fr/", + "author": " Coop IT Easy SCRLfs", + "license": "AGPL-3", + "application": False, + "installable": True, + "external_dependencies": {"python": ["checksumdir"]}, + "depends": [], + "data": ["views/ir_module.xml"], +} diff --git a/pos_hash_cert/models/__init__.py b/pos_hash_cert/models/__init__.py new file mode 100644 index 00000000..eb0aa988 --- /dev/null +++ b/pos_hash_cert/models/__init__.py @@ -0,0 +1 @@ +from . import pos_hash_cert diff --git a/pos_hash_cert/models/pos_hash_cert.py b/pos_hash_cert/models/pos_hash_cert.py new file mode 100644 index 00000000..5a92c92a --- /dev/null +++ b/pos_hash_cert/models/pos_hash_cert.py @@ -0,0 +1,63 @@ +import logging +import os + +from odoo import api, fields, models +from odoo.tools.config import config + +from checksumdir import dirhash + +_logger = logging.getLogger(__name__) + +CERT_DIR = config.get('certified_modules_directory', 'pos_certified_modules') +USER_DIR = os.path.expanduser("~") + + +class ModuleHash(models.Model): + _inherit = 'ir.module.module' + + hash = fields.Char(compute='_compute_hash', + help='Module hash') + + @api.multi + def _compute_hash(self): + + _logger.debug("[pos_hash_cert] USER_DIR = %s" % USER_DIR) + start_dir = os.path.dirname(os.path.realpath(__file__)) + _logger.debug("[pos_hash_cert] start_DIR = %s" % start_dir) + + last_root = start_dir + current_root = start_dir + found_cert_dir = None + + while found_cert_dir is None and current_root != os.path.dirname(USER_DIR): + pruned = False + for root, dirs, files in os.walk(current_root): + if not pruned: + try: + # Remove the part of the tree we already searched + del dirs[dirs.index(os.path.basename(last_root))] + pruned = True + except ValueError: + pass + if CERT_DIR in dirs: + # found the directory, stop + found_cert_dir = os.path.join(root, CERT_DIR) + break + # Otherwise, pop up a level, search again + last_root = current_root + current_root = os.path.dirname(last_root) + + if found_cert_dir: + _logger.debug("[pos_hash_cert] found_cert_dir = %s" % found_cert_dir) + certified_modules = [ + name + for name in os.listdir(found_cert_dir) + if os.path.isdir(os.path.join(found_cert_dir, name)) + ] + + for record in self: + if record.name in certified_modules: + record.hash = dirhash(found_cert_dir, 'sha256', excluded_extensions=['pyc']) + else: + _logger.debug("[pos_hash_cert] no certified modules directory found") + pass diff --git a/pos_hash_cert/views/ir_module.xml b/pos_hash_cert/views/ir_module.xml new file mode 100644 index 00000000..db01e303 --- /dev/null +++ b/pos_hash_cert/views/ir_module.xml @@ -0,0 +1,15 @@ + + + + + ir.module.module.form.hashview + ir.module.module + + + + + + + + + diff --git a/pos_loyalty/README.rst b/pos_loyalty/README.rst new file mode 100644 index 00000000..f3441df6 --- /dev/null +++ b/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 `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* 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 + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_loyalty/__init__.py b/pos_loyalty/__init__.py new file mode 100644 index 00000000..69f7babd --- /dev/null +++ b/pos_loyalty/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/pos_loyalty/__manifest__.py b/pos_loyalty/__manifest__.py new file mode 100644 index 00000000..b7e7f518 --- /dev/null +++ b/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 +# 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, +} diff --git a/pos_loyalty/demo/templates.xml b/pos_loyalty/demo/templates.xml new file mode 100644 index 00000000..81f834eb --- /dev/null +++ b/pos_loyalty/demo/templates.xml @@ -0,0 +1,8 @@ + + + + diff --git a/pos_loyalty/i18n/es.po b/pos_loyalty/i18n/es.po new file mode 100644 index 00000000..01f984fe --- /dev/null +++ b/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 , 2017 +# enjolras , 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 , 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" diff --git a/pos_loyalty/i18n/fr.po b/pos_loyalty/i18n/fr.po new file mode 100644 index 00000000..40565741 --- /dev/null +++ b/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 , 2017 +# OCA Transbot , 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 , 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" diff --git a/pos_loyalty/i18n/hr_HR.po b/pos_loyalty/i18n/hr_HR.po new file mode 100644 index 00000000..145b5dec --- /dev/null +++ b/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 , 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 , 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" diff --git a/pos_loyalty/i18n/it.po b/pos_loyalty/i18n/it.po new file mode 100644 index 00000000..62929dfc --- /dev/null +++ b/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 , 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 , 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" diff --git a/pos_loyalty/i18n/nl_NL.po b/pos_loyalty/i18n/nl_NL.po new file mode 100644 index 00000000..f63a6572 --- /dev/null +++ b/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 , 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 , 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" diff --git a/pos_loyalty/i18n/pos_loyalty.pot b/pos_loyalty/i18n/pos_loyalty.pot new file mode 100644 index 00000000..ae7a17b4 --- /dev/null +++ b/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 "" + diff --git a/pos_loyalty/models/__init__.py b/pos_loyalty/models/__init__.py new file mode 100644 index 00000000..d6885ec8 --- /dev/null +++ b/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 diff --git a/pos_loyalty/models/loyalty_program.py b/pos_loyalty/models/loyalty_program.py new file mode 100644 index 00000000..a3493beb --- /dev/null +++ b/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') diff --git a/pos_loyalty/models/loyalty_reward.py b/pos_loyalty/models/loyalty_reward.py new file mode 100644 index 00000000..ac3d97eb --- /dev/null +++ b/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 +# 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')) diff --git a/pos_loyalty/models/loyalty_rule.py b/pos_loyalty/models/loyalty_rule.py new file mode 100644 index 00000000..85caafe6 --- /dev/null +++ b/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 +# 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') diff --git a/pos_loyalty/models/pos_config.py b/pos_loyalty/models/pos_config.py new file mode 100644 index 00000000..b39e549d --- /dev/null +++ b/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') diff --git a/pos_loyalty/models/pos_order.py b/pos_loyalty/models/pos_order.py new file mode 100644 index 00000000..bc644065 --- /dev/null +++ b/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 diff --git a/pos_loyalty/models/pos_order_line.py b/pos_loyalty/models/pos_order_line.py new file mode 100644 index 00000000..fe9182f5 --- /dev/null +++ b/pos_loyalty/models/pos_order_line.py @@ -0,0 +1,20 @@ +# Copyright 2018 Lambda IS DOOEL +# 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 diff --git a/pos_loyalty/models/res_partner.py b/pos_loyalty/models/res_partner.py new file mode 100644 index 00000000..60350f04 --- /dev/null +++ b/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') diff --git a/pos_loyalty/readme/CONFIGURE.rst b/pos_loyalty/readme/CONFIGURE.rst new file mode 100644 index 00000000..7c7dac48 --- /dev/null +++ b/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. diff --git a/pos_loyalty/readme/CONTRIBUTORS.rst b/pos_loyalty/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..4ec3dbcc --- /dev/null +++ b/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 diff --git a/pos_loyalty/readme/DESCRIPTION.rst b/pos_loyalty/readme/DESCRIPTION.rst new file mode 100644 index 00000000..66ab8c12 --- /dev/null +++ b/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. diff --git a/pos_loyalty/readme/USAGE.rst b/pos_loyalty/readme/USAGE.rst new file mode 100644 index 00000000..741eecfc --- /dev/null +++ b/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. diff --git a/pos_loyalty/security/ir.model.access.csv b/pos_loyalty/security/ir.model.access.csv new file mode 100644 index 00000000..8c13e017 --- /dev/null +++ b/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 diff --git a/pos_loyalty/static/description/icon.png b/pos_loyalty/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/pos_loyalty/static/description/icon.png differ diff --git a/pos_loyalty/static/description/index.html b/pos_loyalty/static/description/index.html new file mode 100644 index 00000000..55bd7ebb --- /dev/null +++ b/pos_loyalty/static/description/index.html @@ -0,0 +1,449 @@ + + + + + + +Loyalty Program + + + +
+

Loyalty Program

+ + +

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runbot

+

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

+ +
+

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. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • OpenERP SA
  • +
  • RGB Consulting SL
  • +
  • Lambda IS
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_loyalty/static/src/css/pos.css b/pos_loyalty/static/src/css/pos.css new file mode 100644 index 00000000..4d141918 --- /dev/null +++ b/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; +} diff --git a/pos_loyalty/static/src/js/pos.js b/pos_loyalty/static/src/js/pos.js new file mode 100644 index 00000000..91f07f86 --- /dev/null +++ b/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); + } + }, + }); +}); \ No newline at end of file diff --git a/pos_loyalty/static/src/js/tests.js b/pos_loyalty/static/src/js/tests.js new file mode 100644 index 00000000..eff9f481 --- /dev/null +++ b/pos_loyalty/static/src/js/tests.js @@ -0,0 +1,170 @@ +// Copyright 2004-2018 Odoo SA +// Copyright 2018 Lambda IS DOOEL +// 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); +}) \ No newline at end of file diff --git a/pos_loyalty/static/src/xml/pos.xml b/pos_loyalty/static/src/xml/pos.xml new file mode 100644 index 00000000..988096db --- /dev/null +++ b/pos_loyalty/static/src/xml/pos.xml @@ -0,0 +1,85 @@ + + + + +
+
Points
+ +
+ + +
+
+ +
+ - +
+
+
+ +
+
+
+ + +
+ Rewards +
+
+ + + +
.
+
+
+ + + + + + + + + + + + +
+
--------------------------------
+
+
+
+
+
+ + Points Won + + + Points Spent + + + Total Points + +
+
+
+
+
+ + + +
+ Points + + + +
+
+
+ + + + Points + + + +
diff --git a/pos_loyalty/tests/__init__.py b/pos_loyalty/tests/__init__.py new file mode 100644 index 00000000..09e18a01 --- /dev/null +++ b/pos_loyalty/tests/__init__.py @@ -0,0 +1,2 @@ + +from . import test_pos_loyalty diff --git a/pos_loyalty/tests/test_pos_loyalty.py b/pos_loyalty/tests/test_pos_loyalty.py new file mode 100644 index 00000000..1a50dd1b --- /dev/null +++ b/pos_loyalty/tests/test_pos_loyalty.py @@ -0,0 +1,74 @@ +# Copyright 2004-2018 Odoo SA +# Copyright 2018 Lambda IS DOOEL +# 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) diff --git a/pos_loyalty/views/loyalty_program_view.xml b/pos_loyalty/views/loyalty_program_view.xml new file mode 100644 index 00000000..65f3d247 --- /dev/null +++ b/pos_loyalty/views/loyalty_program_view.xml @@ -0,0 +1,83 @@ + + + + loyalty.program.form + loyalty.program + +
+ +
+
+ + + + + + + + + + + + + +

Rules define how loyalty points are earned for specific products or categories

+ + + + + + + + + + +

Reward the customer with gifts or discounts for loyalty points

+ + + + + + +
+
+
+
+ + + loyalty.program.tree + loyalty.program + + + + + + + + + Loyalty Programs + ir.actions.act_window + loyalty.program + form + tree,form + +

+ Click create to define a Loyalty Program. +

+

+ Loyalty Programs allow you customers to earn points + and rewards when purchasing from your shops. +

+
+
+ + +
diff --git a/pos_loyalty/views/loyalty_reward_view.xml b/pos_loyalty/views/loyalty_reward_view.xml new file mode 100644 index 00000000..f194ccac --- /dev/null +++ b/pos_loyalty/views/loyalty_reward_view.xml @@ -0,0 +1,37 @@ + + + + loyalty.reward.form + loyalty.reward + +
+
+
+ + + + + + + + + + + + + + +
+
+
+
diff --git a/pos_loyalty/views/loyalty_rule_view.xml b/pos_loyalty/views/loyalty_rule_view.xml new file mode 100644 index 00000000..bf1152d9 --- /dev/null +++ b/pos_loyalty/views/loyalty_rule_view.xml @@ -0,0 +1,31 @@ + + + + loyalty.rule.form + loyalty.rule + +
+
+
+ + + + + + + + + + + + + + +
+
+
+
diff --git a/pos_loyalty/views/pos_config_view.xml b/pos_loyalty/views/pos_config_view.xml new file mode 100644 index 00000000..8988a056 --- /dev/null +++ b/pos_loyalty/views/pos_config_view.xml @@ -0,0 +1,23 @@ + + + + pos.config.form + pos.config + + + +
+
+
+
+
+
+
+
diff --git a/pos_loyalty/views/pos_order_view.xml b/pos_loyalty/views/pos_order_view.xml new file mode 100644 index 00000000..225f610a --- /dev/null +++ b/pos_loyalty/views/pos_order_view.xml @@ -0,0 +1,15 @@ + + + + pos.order.form + pos.order + + + + + + + + + + diff --git a/pos_loyalty/views/res_partner_view.xml b/pos_loyalty/views/res_partner_view.xml new file mode 100644 index 00000000..c183f50d --- /dev/null +++ b/pos_loyalty/views/res_partner_view.xml @@ -0,0 +1,18 @@ + + + + partner.property.form + res.partner + + + + + + + diff --git a/pos_loyalty/views/templates.xml b/pos_loyalty/views/templates.xml new file mode 100644 index 00000000..d05d64a8 --- /dev/null +++ b/pos_loyalty/views/templates.xml @@ -0,0 +1,9 @@ + + + + diff --git a/pos_mail_receipt/README.rst b/pos_mail_receipt/README.rst new file mode 100644 index 00000000..7cc432ee --- /dev/null +++ b/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 `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Coop IT Easy SCRLfs + +Contributors +~~~~~~~~~~~~ + +* `Coop IT Easy SCRLfs `_: + + * 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 `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_mail_receipt/__init__.py b/pos_mail_receipt/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/pos_mail_receipt/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_mail_receipt/__manifest__.py b/pos_mail_receipt/__manifest__.py new file mode 100644 index 00000000..150d1b0e --- /dev/null +++ b/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, +} diff --git a/pos_mail_receipt/data/email.xml b/pos_mail_receipt/data/email.xml new file mode 100644 index 00000000..aff3af80 --- /dev/null +++ b/pos_mail_receipt/data/email.xml @@ -0,0 +1,19 @@ + + + + + + + Send Receipt + ${(object.user_id.email and '%s <%s>' % (object.user_id.name, object.user_id.email) or '')|safe} + ${object.pos_reference} + + + ${object.partner_id.lang} + + + + + diff --git a/pos_mail_receipt/i18n/fr.po b/pos_mail_receipt/i18n/fr.po new file mode 100644 index 00000000..094ec1a2 --- /dev/null +++ b/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 "" +"

\n" +"Your Ticket ${object.pos_reference}\n" +"

" +msgstr "" +"

\n" +"Votre Ticket ${object.pos_reference}\n" +"

" + +#. 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 "" diff --git a/pos_mail_receipt/i18n/pos_mail_receipt.pot b/pos_mail_receipt/i18n/pos_mail_receipt.pot new file mode 100644 index 00000000..3641c716 --- /dev/null +++ b/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 "

\n" +"Your Ticket ${object.pos_reference}\n" +"

" +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 "" + diff --git a/pos_mail_receipt/models/__init__.py b/pos_mail_receipt/models/__init__.py new file mode 100644 index 00000000..e9ab911d --- /dev/null +++ b/pos_mail_receipt/models/__init__.py @@ -0,0 +1 @@ +from . import pos_order diff --git a/pos_mail_receipt/models/pos_order.py b/pos_mail_receipt/models/pos_order.py new file mode 100644 index 00000000..a3884455 --- /dev/null +++ b/pos_mail_receipt/models/pos_order.py @@ -0,0 +1,86 @@ +# Copyright 2019 Coop IT Easy SCRLfs +# @author Pierrick Brun +# 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 = ( + "
" + "
" + "{}
".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 diff --git a/pos_mail_receipt/readme/CONTRIBUTORS.rst b/pos_mail_receipt/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..0316142d --- /dev/null +++ b/pos_mail_receipt/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Coop IT Easy SCRLfs `_: + + * Pierrick Brun diff --git a/pos_mail_receipt/readme/DESCRIPTION.rst b/pos_mail_receipt/readme/DESCRIPTION.rst new file mode 100644 index 00000000..59b716b9 --- /dev/null +++ b/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. diff --git a/pos_mail_receipt/readme/USAGE.rst b/pos_mail_receipt/readme/USAGE.rst new file mode 100644 index 00000000..8bf546e6 --- /dev/null +++ b/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. diff --git a/pos_mail_receipt/static/description/icon.png b/pos_mail_receipt/static/description/icon.png new file mode 100644 index 00000000..e060fb4a Binary files /dev/null and b/pos_mail_receipt/static/description/icon.png differ diff --git a/pos_mail_receipt/static/description/index.html b/pos_mail_receipt/static/description/index.html new file mode 100644 index 00000000..f8193fbf --- /dev/null +++ b/pos_mail_receipt/static/description/index.html @@ -0,0 +1,434 @@ + + + + + + +Pos Mail Receipt + + + +
+

Pos Mail Receipt

+ + +

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runbot

+

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

+ +
+

Usage

+
    +
  1. Open a new PoS session.
  2. +
  3. Make an order and validate it.
  4. +
  5. You should see the company logo in the receipt preview.
  6. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Coop IT Easy SCRLfs
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_mail_receipt/static/src/css/pos.css b/pos_mail_receipt/static/src/css/pos.css new file mode 100644 index 00000000..a6481084 --- /dev/null +++ b/pos_mail_receipt/static/src/css/pos.css @@ -0,0 +1,3 @@ +.pos .modal-dialog .popup-textinput input{ + margin-left: 10% +} diff --git a/pos_mail_receipt/static/src/css/print.css b/pos_mail_receipt/static/src/css/print.css new file mode 100644 index 00000000..edb0b670 --- /dev/null +++ b/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; + } +} diff --git a/pos_mail_receipt/static/src/js/screens.js b/pos_mail_receipt/static/src/js/screens.js new file mode 100644 index 00000000..7d67d17c --- /dev/null +++ b/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)); +} diff --git a/pos_mail_receipt/static/src/xml/pos.xml b/pos_mail_receipt/static/src/xml/pos.xml new file mode 100644 index 00000000..672e7129 --- /dev/null +++ b/pos_mail_receipt/static/src/xml/pos.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/pos_mail_receipt/templates/assets.xml b/pos_mail_receipt/templates/assets.xml new file mode 100644 index 00000000..99be6065 --- /dev/null +++ b/pos_mail_receipt/templates/assets.xml @@ -0,0 +1,20 @@ + + + + +