1
0
Fork 0

initial work on odoo 12

This commit is contained in:
Tobias Brunner 2019-06-26 22:28:31 +02:00
parent 5f8ef72c8b
commit 41cb41be5f
12 changed files with 151 additions and 519 deletions

View File

@ -5,18 +5,6 @@ Odoo PosBox in Docker. It additionally contains
[pos-addons](https://github.com/it-projects-llc/pos-addons)
for using receipt printers over the network.
## Running on Raspberry Pi
The official Docker images of Odoo are not multi-arch
compatible, the image needs to be rebuilt on arm.
1. Change the `FROM` in the [Dockerfile](https://github.com/odoo/docker/blob/master/11.0/Dockerfile) to
`FROM armhf/debian:stretch` and build it as `local/odoo:11`.
1. Change the `FROM` in [odoo/Dockerfile](odoo/Dockerfile) to
`local/odoo:11`.
Corresponding issue on GitHub: [Support for ARM64v8 #195](https://github.com/odoo/docker/issues/195).
## Backup configuration
Example contents of `backup.env`:

View File

@ -8,12 +8,16 @@ services:
- 80:8069
volumes:
- odoo-web-data:/var/lib/odoo
environment:
- HOST=db
- USER=odoo
- PASSWORD=odoo
networks:
localnet:
ipv4_address: 10.5.0.2
posbox:
build: ./posbox
command: -- --load=web,hw_proxy,hw_posbox_homepage,hw_escpos,hw_screen,hw_printer_network
iotbox:
build: ./iotbox
command: -- --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos,hw_blackbox_be,hw_screen,hw_drivers,hw_printer_network
ports:
- 8070:8069
- 8072:8072
@ -23,8 +27,9 @@ services:
localnet:
ipv4_address: 10.5.0.3
db:
image: postgres:9.6
image: postgres:10
environment:
- POSTGRES_DB=postgres
- POSTGRES_PASSWORD=odoo
- POSTGRES_USER=odoo
- PGDATA=/var/lib/postgresql/data/pgdata
@ -33,28 +38,28 @@ services:
networks:
localnet:
ipv4_address: 10.5.0.4
backup:
build: ./backup
environment:
- PGHOST=db
- PGUSER=odoo
- PGPASSWORD=odoo
- BACKUP_SCHEDULE=0 19 * * *
env_file: backup.env
volumes:
- odoo-db-data:/data/pg_raw:ro
- /home/pi:/data/home-pi:ro
networks:
localnet:
ipv4_address: 10.5.0.5
monitoring:
build: ./monitoring
command: checkup every 10m
volumes:
- ./checkup.json:/opt/checkup/checkup.json:ro
networks:
localnet:
ipv4_address: 10.5.0.6
# backup:
# build: ./backup
# environment:
# - PGHOST=db
# - PGUSER=odoo
# - PGPASSWORD=odoo
# - BACKUP_SCHEDULE=0 19 * * *
# env_file: backup.env
# volumes:
# - odoo-db-data:/data/pg_raw:ro
# - /home/pi:/data/home-pi:ro
# networks:
# localnet:
# ipv4_address: 10.5.0.5
# monitoring:
# build: ./monitoring
# command: checkup every 10m
# volumes:
# - ./checkup.json:/opt/checkup/checkup.json:ro
# networks:
# localnet:
# ipv4_address: 10.5.0.6
volumes:
odoo-web-data:
odoo-db-data:

78
iotbox/Dockerfile Normal file
View File

@ -0,0 +1,78 @@
FROM odoo:12
USER root
## Dependencies for iotbox
## See also https://github.com/odoo/odoo/blob/master/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
RUN set -x; apt-get update \
&& apt-get -y install --no-install-recommends \
bluez \
cups \
cups-ipp-utils \
dbus \
gcc \
git \
libcups2-dev \
python3-babel \
python3-dateutil \
python3-dbus \
python3-decorator \
python3-dev \
python3-docutils \
python3-feedparser \
python3-gi \
python3-html2text \
python3-jinja2 \
python3-ldap3 \
python3-libsass \
python3-lxml \
python3-mako \
python3-mock \
python3-netifaces \
python3-openid \
python3-passlib \
python3-pil \
python3-pip \
python3-psutil \
python3-psycopg2 \
python3-pydot \
python3-pyparsing \
python3-pypdf2 \
python3-pyscard \
python3-qrcode \
python3-reportlab \
python3-requests \
python3-serial \
python3-simplejson \
python3-simplejson \
python3-tz \
python3-unittest2 \
python3-urllib3 \
python3-vatnumber \
python3-werkzeug \
python3-wheel \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /opt/posbox/addons \
&& chown -R odoo.odoo /opt/posbox \
&& mkdir /usr/lib/python3/dist-packages/odoo/addons/hw_drivers/drivers
RUN pip3 install \
pyusb==1.0.0b1 \
evdev \
gatt \
v4l2 \
polib \
pycups
# See https://bugs.launchpad.net/python-v4l2/+bug/1664158
COPY python-v4l2-1664158-fix.patch /tmp
RUN patch -p0 /usr/local/lib/python3.5/dist-packages/v4l2.py < /tmp/python-v4l2-1664158-fix.patch
#USER odoo
## Get pos-addons for pos_printer_network
RUN git clone --depth=1 -b 12.0 https://github.com/it-projects-llc/pos-addons.git \
/opt/posbox/addons
COPY odoo.conf /etc/odoo/odoo.conf
COPY entrypoint.sh /

6
iotbox/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,7 @@ DB_ARGS=()
function check_config() {
param="$1"
value="$2"
if ! grep -q -E "^\s*\b${param}\b\s*=" "$OPENERP_SERVER" ; then
if ! grep -q -E "^\s*\b${param}\b\s*=" "$ODOO_RC" ; then
DB_ARGS+=("--${param}")
DB_ARGS+=("${value}")
fi;
@ -24,16 +24,18 @@ check_config "db_user" "$USER"
check_config "db_password" "$PASSWORD"
case "$1" in
-- | openerp-server)
-- | odoo)
shift
/etc/init.d/cups start
/etc/init.d/dbus start
if [[ "$1" == "scaffold" ]] ; then
exec openerp-server "$@"
exec odoo "$@"
else
exec openerp-server "$@" "${DB_ARGS[@]}"
exec odoo "$@" "${DB_ARGS[@]}"
fi
;;
-*)
exec openerp-server "$@" "${DB_ARGS[@]}"
exec odoo "$@" "${DB_ARGS[@]}"
;;
*)
exec "$@"

4
iotbox/odoo.conf Normal file
View File

@ -0,0 +1,4 @@
[options]
addons_path = /mnt/extra-addons,/opt/posbox/addons
data_dir = /var/lib/odoo
admin_passwd = S3Cur3Passw0rd

View File

@ -0,0 +1,22 @@
=== modified file 'v4l2.py'
--- v4l2.py 2010-07-22 01:07:58 +0000
+++ v4l2.py 2018-03-03 01:59:16 +0000
@@ -194,7 +194,7 @@
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT,
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY,
V4L2_BUF_TYPE_PRIVATE,
-) = range(1, 9) + [0x80]
+) = list(range(1, 9)) + [0x80]
v4l2_ctrl_type = enum
@@ -245,7 +245,7 @@
V4L2_PRIORITY_INTERACTIVE,
V4L2_PRIORITY_RECORD,
V4L2_PRIORITY_DEFAULT,
-) = range(0, 4) + [2]
+) = list(range(0, 4)) + [2]
class v4l2_rect(ctypes.Structure):

View File

@ -1,4 +1,4 @@
FROM local/odoo:11
FROM odoo:12
USER root
@ -11,8 +11,8 @@ RUN apt-get update \
USER odoo
## Get pos-addons for pos_printer_network
RUN git clone --depth=1 -b 11.0 https://github.com/it-projects-llc/pos-addons.git \
RUN git clone --depth=1 -b 12.0 https://github.com/it-projects-llc/pos-addons.git \
/opt/posbox/addons
COPY odoo.conf /etc/odoo/odoo.conf
COPY bootstrap.min.js /usr/lib/python3/dist-packages/odoo/addons/web/static/lib/bootstrap/js/
#COPY bootstrap.min.js /usr/lib/python3/dist-packages/odoo/addons/web/static/lib/bootstrap/js/

View File

@ -1,66 +0,0 @@
FROM debian:jessie
MAINTAINER Odoo S.A. <info@odoo.com>
# Install some deps, lessc and less-plugin-clean-css, and wkhtmltopdf
RUN set -x; \
apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
node-clean-css \
node-less \
python-gevent \
python-netifaces \
python-pip \
python-pyinotify \
python-renderpm \
python-support \
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false -o APT::AutoRemove::SuggestsImportant=false npm \
&& rm -rf /var/lib/apt/lists/* wkhtmltox.deb \
&& pip install psycogreen==1.0
# Install Odoo
ENV ODOO_VERSION 8.0
ENV ODOO_RELEASE 20170815
RUN set -x; \
curl -o odoo.deb -SL http://nightly.odoo.com/${ODOO_VERSION}/nightly/deb/odoo_${ODOO_VERSION}.${ODOO_RELEASE}_all.deb \
&& echo '5835e966a07e5684b4f7bcc39585276b0bb68254 odoo.deb' | sha1sum -c - \
&& dpkg --force-depends -i odoo.deb \
&& apt-get update \
&& apt-get -y install -f --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* odoo.deb
# Copy entrypoint script and Odoo configuration file
COPY ./entrypoint.sh /
COPY ./openerp-server.conf /etc/odoo/
RUN chown odoo /etc/odoo/openerp-server.conf
# Mount /var/lib/odoo to allow restoring filestore and /mnt/extra-addons for users addons
RUN mkdir -p /mnt/extra-addons \
&& chown -R odoo /mnt/extra-addons
VOLUME ["/var/lib/odoo", "/mnt/extra-addons"]
# Expose Odoo services
EXPOSE 8069 8071
# Set the default config file
ENV OPENERP_SERVER /etc/odoo/openerp-server.conf
## Prepare for pos-addons / hw_printer_network
RUN pip install -U pip setuptools \
&& pip install python-escpos
# hw_printer_network is located in 11.0 branch, but works with earler versions too
RUN git clone --depth=1 -b 11.0 https://github.com/it-projects-llc/pos-addons.git \
/opt/posbox/addons
COPY hw_screen_main.py /usr/lib/python3/dist-packages/odoo/addons/hw_screen/controllers/main.py
COPY hw_screen_main.py /usr/lib/python2.7/dist-packages/openerp/addons/hw_screen/controllers/main.py
COPY hw_printer_network_controller.py /opt/posbox/addons/hw_printer_network/controllers/hw_printer_network_controller.py
# Set default user when running the container
USER odoo
ENTRYPOINT ["/entrypoint.sh"]
CMD ["openerp-server"]

View File

@ -1,209 +0,0 @@
# Copyright 2017-2018 Dinar Gabbasov <https://it-projects.info/team/GabbasovDinar>
# Copyright 2018 Tom Blauwendraat <tom@sunflowerweb.nl>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from openerp import http
import logging
import time
import socket
import subprocess
import threading
import traceback
_logger = logging.getLogger(__name__)
try:
from openerp.addons.hw_escpos.escpos import escpos
from openerp.addons.hw_escpos.controllers.main import EscposProxy
from openerp.addons.hw_escpos.controllers.main import EscposDriver
from openerp.addons.hw_escpos.escpos.printer import Network
import openerp.addons.hw_proxy.controllers.main as hw_proxy
except ImportError:
EscposProxy = object
EscposDriver = object
class PingProcess(threading.Thread):
def __init__(self, ip):
self.stdout = None
self.stderr = None
threading.Thread.__init__(self)
self.status = 'offline'
self.ip = ip
self.stop = False
def run(self):
while not self.stop:
child = subprocess.Popen(
["ping", "-c1", "-w5", self.ip],
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
child.communicate()
self.status = 'offline' if child.returncode else 'online'
time.sleep(1)
def get_status(self):
return self.status
def __del__(self):
self.stop = True
class EscposNetworkDriver(EscposDriver):
def __init__(self):
self.network_printers = []
self.ping_processes = {}
self.printer_objects = {}
super(EscposNetworkDriver, self).__init__()
def get_network_printer(self, ip, name=None):
found_printer = False
for printer in self.network_printers:
if printer['ip'] == ip:
found_printer = True
if name:
printer['name'] = name
if printer['status'] == 'online':
printer_object = self.printer_objects.get(ip, None)
if not printer_object:
try:
printer_object = Network(ip)
self.printer_objects[ip] = printer_object
except socket.error:
pass
return printer
if not found_printer:
self.add_network_printer(ip, name)
return None
def add_network_printer(self, ip, name=None):
printer = dict(
ip=ip,
status='offline',
name=name or 'Unnamed printer'
)
self.network_printers.append(printer) # dont return because offline
self.start_pinging(ip)
def start_pinging(self, ip):
pinger = PingProcess(ip)
self.ping_processes[ip] = pinger
pinger.start()
def update_driver_status(self):
count = len([p for p in self.network_printers if p.get('status', None) == 'online'])
if count:
self.set_status('connected', '{} printer(s) Connected'.format(count))
else:
self.set_status('disconnected', 'Disconnected')
def run(self):
if not escpos:
_logger.error('ESC/POS cannot initialize, please verify system dependencies.')
return
while True:
try:
error = True
timestamp, task, data = self.queue.get(True)
if task == 'xml_receipt':
error = False
if timestamp >= (time.time() - 1 * 60 * 60):
receipt, network_printer_ip = data
printer_info = self.get_network_printer(network_printer_ip)
printer = self.printer_objects.get(network_printer_ip, None)
if printer_info and printer_info['status'] == 'online' and printer:
_logger.info('Printing XML receipt on printer %s...', network_printer_ip)
try:
printer.receipt(receipt)
except socket.error:
printer.open()
printer.receipt(receipt)
_logger.info('Done printing XML receipt on printer %s', network_printer_ip)
else:
_logger.error('xml_receipt: printer offline!')
# add a missed order to queue
time.sleep(3)
self.queue.put((timestamp, task, data))
elif task == 'cashbox':
printer_info = self.get_network_printer(data)
printer = self.printer_objects.get(data, None)
if printer_info and printer_info['status'] == 'online' and printer:
_logger.info('Opening cashbox on printer %s...', data)
try:
printer.cashdraw(2)
printer.cashdraw(5)
except socket.error:
printer.open()
printer.cashdraw(2)
printer.cashdraw(5)
_logger.info('Done opening cashbox on printer %s', data)
else:
_logger.error('cashbox: printer offline!')
elif task == 'printstatus':
pass
elif task == 'status':
error = False
for printer in self.network_printers:
ip = printer['ip']
pinger = self.ping_processes.get(ip, None)
if pinger and pinger.isAlive():
status = pinger.get_status()
if status != printer['status']:
# todo: use a lock?
printer['status'] = status
self.update_driver_status()
else:
self.start_pinging(ip)
error = False
except Exception as e:
self.set_status('error', str(e))
errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n'
_logger.error(errmsg)
finally:
if error:
self.queue.put((timestamp, task, data))
# Separate instance, mainloop and queue for network printers
# original driver runs in parallel and deals with USB printers
network_driver = EscposNetworkDriver()
hw_proxy.drivers['escpos_network'] = network_driver
# this will also start the message handling loop
network_driver.push_task('printstatus')
class UpdatedEscposProxy(EscposProxy):
@http.route('/hw_proxy/print_xml_receipt', type='json', auth='none', cors='*')
def print_xml_receipt(self, receipt, proxy=None):
if proxy:
_logger.info('print_xml_receipt proxy %s', proxy)
network_driver.push_task('xml_receipt', (receipt, proxy))
else:
super(UpdatedEscposProxy, self).print_xml_receipt(receipt)
@http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*')
def open_cashbox(self, proxy=None):
_logger.info('Open cashbox via network printer')
network_driver.push_task('cashbox', '192.168.233.3')
@http.route('/hw_proxy/network_printers', type='json', auth='none', cors='*')
def network_printers(self, network_printers=None):
for printer in network_printers:
network_driver.get_network_printer(printer['ip'], name=printer['name'])
@http.route('/hw_proxy/status_network_printers', type='json', auth='none', cors='*')
def network_printers_status(self):
return network_driver.network_printers
@http.route('/hw_proxy/without_usb', type='http', auth='none', cors='*')
def without_usb(self):
""" Old pos_printer_network module expects this to work """
return "ping"

View File

@ -1,195 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2015 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import http
from openerp.tools import config
from openerp.addons.web.controllers import main as web
from openerp.addons.hw_posbox_homepage.controllers import main as homepage
import logging
import netifaces as ni
import os
from subprocess import call
import time
import threading
self_port = str(config['xmlrpc_port'] or 8069)
_logger = logging.getLogger(__name__)
class Homepage(homepage.PosboxHomepage):
def get_hw_screen_message(self):
return """
<p>
If you need to display the current customer basket on another device, you can do it <a href='/point_of_sale/display'>here</a>.
</p>
"""
class HardwareScreen(web.Home):
event_data = threading.Event()
pos_client_data = {'rendered_html': False,
'ip_from': False}
display_in_use = ''
failure_count = {}
def _call_xdotools(self, keystroke):
os.environ['DISPLAY'] = ":0.0"
os.environ['XAUTHORITY'] = "/run/lightdm/pi/xauthority"
try:
call(['xdotool', 'key', keystroke])
return "xdotool succeeded in stroking " + keystroke
except:
return "xdotool threw an error, maybe it is not installed on the posbox"
@http.route('/hw_proxy/display_refresh', type='json', auth='none', cors='*')
def display_refresh(self):
return self._call_xdotools('F5')
# POS CASHIER'S ROUTES
@http.route('/hw_proxy/customer_facing_display', type='json', auth='none', cors='*')
def update_user_facing_display(self, html=None):
request_ip = http.request.httprequest.remote_addr
if request_ip == HardwareScreen.pos_client_data.get('ip_from', ''):
HardwareScreen.pos_client_data['rendered_html'] = html
HardwareScreen.event_data.set()
return {'status': 'updated'}
else:
return {'status': 'failed'}
@http.route('/hw_proxy/take_control', type='json', auth='none', cors='*')
def take_control(self, html=None):
# ALLOW A CASHIER TO TAKE CONTROL OVER THE POSBOX, IN CASE OF MULTIPLE CASHIER PER POSBOX
HardwareScreen.pos_client_data['rendered_html'] = html
HardwareScreen.pos_client_data['ip_from'] = http.request.httprequest.remote_addr
HardwareScreen.event_data.set()
return {'status': 'success',
'message': 'You now have access to the display'}
@http.route('/hw_proxy/test_ownership', type='json', auth='none', cors='*')
def test_ownership(self):
if HardwareScreen.pos_client_data.get('ip_from') == http.request.httprequest.remote_addr:
return {'status': 'OWNER'}
else:
return {'status': 'NOWNER'}
# POSBOX ROUTES (SELF)
@http.route('/point_of_sale/display', type='http', auth='none')
def render_main_display(self):
return self._get_html()
@http.route('/point_of_sale/get_serialized_order', type='json', auth='none')
def get_serialized_order(self):
request_addr = http.request.httprequest.remote_addr
result = HardwareScreen.pos_client_data
if HardwareScreen.display_in_use and request_addr != HardwareScreen.display_in_use:
if not HardwareScreen.failure_count.get(request_addr):
HardwareScreen.failure_count[request_addr] = 0
if HardwareScreen.failure_count[request_addr] > 0:
time.sleep(10)
HardwareScreen.failure_count[request_addr] += 1
return {'rendered_html': """<div class="pos-customer_facing_display"><p>Not Authorized. Another browser is in use to display for the client. Please refresh.</p></div> """,
'stop_longpolling': True,
'ip_from': request_addr}
# IMPLEMENTATION OF LONGPOLLING
# Times out 2 seconds before the JS request does
if HardwareScreen.event_data.wait(28):
HardwareScreen.event_data.clear()
HardwareScreen.failure_count[request_addr] = 0
return result
return {'rendered_html': False,
'ip_from': HardwareScreen.pos_client_data['ip_from']}
def _get_html(self):
cust_js = None
interfaces = ni.interfaces()
my_ip = '127.0.0.1'
HardwareScreen.display_in_use = http.request.httprequest.remote_addr
with open(os.path.join(os.path.dirname(__file__), "../static/src/js/worker.js")) as js:
cust_js = js.read()
with open(os.path.join(os.path.dirname(__file__), "../static/src/css/cust_css.css")) as css:
cust_css = css.read()
display_ifaces = ""
for iface_id in interfaces:
iface_obj = ni.ifaddresses(iface_id)
ifconfigs = iface_obj.get(ni.AF_INET, [])
for conf in ifconfigs:
if conf.get('addr'):
display_ifaces += "<tr><td>" + iface_id + "</td>"
display_ifaces += "<td>" + conf.get('addr') + "</td>"
display_ifaces += "<td>" + conf.get('netmask') + "</td></tr>"
# What is my external IP ?
if iface_id != 'lo':
my_ip = conf.get('addr')
my_ip_port = my_ip + ":" + self_port
html = """
<!DOCTYPE html>
<html>
<head>
<title class="origin">Odoo -- Point of Sale</title>
<script type="text/javascript" class="origin" src="http://192.168.25.132:8069/web/static/lib/jquery/jquery.js" >
</script>
<script type="text/javascript" class="origin">
""" + cust_js + """
</script>
<link rel="stylesheet" class="origin" href="http://192.168.25.132:8069/web/static/lib/bootstrap/css/bootstrap.css" >
</link>
<script class="origin" src="http://192.168.25.132:8069/web/static/lib/bootstrap/js/bootstrap.min.js"></script>
<style class="origin">
""" + cust_css + """
</style>
</head>
<head>
<body class="original_body">
<div hidden class="shadow"></div>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<h1>Odoo Point of Sale</h1>
<h2>POSBox Client display</h2>
<h3>My IPs</h3>
<table id="table_ip" class="table table-condensed">
<tr>
<th>Interface</th>
<th>IP</th>
<th>Netmask</th>
</tr>
""" + display_ifaces + """
</table>
<p>The customer cart will be displayed here once a Point of Sale session is started.</p>
<p>Odoo version 11 or above is required.</p>
</div>
</div>
</div>
</body>
</html>
"""
return html

View File

@ -1,3 +0,0 @@
[options]
addons_path = /opt/posbox/addons
data_dir = /var/lib/odoo