From e9d910c23a25cc8b3813a16b09728e3213e535d5 Mon Sep 17 00:00:00 2001 From: Oscar Alvarez Date: Sun, 19 Apr 2020 10:54:08 -0500 Subject: [PATCH] Migrated from hg --- COPYRIGHT | 16 + INSTALL | 56 + INSTALL_es | 352 +++ LICENSE | 674 +++++ README | 46 + TODO | 5 + app/__init__.py | 3 + app/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 198 bytes app/__pycache__/buttonpad.cpython-37.pyc | Bin 0 -> 5143 bytes app/__pycache__/common.cpython-37.pyc | Bin 0 -> 762 bytes app/__pycache__/constants.cpython-37.pyc | Bin 0 -> 1258 bytes app/__pycache__/localdb.cpython-37.pyc | Bin 0 -> 5225 bytes ...montero@gmail.com 2020-03-11-23-57-24).pyc | Bin 0 -> 70492 bytes ...montero@gmail.com 2020-03-24-19-22-37).pyc | Bin 0 -> 70497 bytes app/__pycache__/mainwindow.cpython-37.pyc | Bin 0 -> 69762 bytes app/__pycache__/manage_tables.cpython-37.pyc | Bin 0 -> 2462 bytes ...montero@gmail.com 2020-03-11-21-32-44).pyc | Bin 0 -> 2784 bytes ...montero@gmail.com 2020-03-12-19-17-24).pyc | Bin 0 -> 2788 bytes ...montero@gmail.com 2020-03-24-19-22-37).pyc | Bin 0 -> 2784 bytes app/__pycache__/proxy.cpython-37.pyc | Bin 0 -> 2780 bytes ...montero@gmail.com 2020-03-11-21-14-37).pyc | Bin 0 -> 15296 bytes app/__pycache__/reporting.cpython-37.pyc | Bin 0 -> 15300 bytes app/__pycache__/states.cpython-37.pyc | Bin 0 -> 549 bytes app/app_tablet.css | 164 + app/buttonpad.py | 178 ++ app/common.py | 20 + app/constants.py | 52 + app/electronic_scale.py | 81 + app/help.py | 28 + app/large_screen.css | 137 + app/localdb.py | 143 + app/mainwindow.py | 2637 +++++++++++++++++ app/manage_tables.py | 64 + app/medium_screen.css | 137 + app/proxy.py | 103 + app/reporting.py | 648 ++++ app/share/accept.svg | 52 + app/share/beer.svg | 1 + app/share/breakfast.svg | 1 + app/share/burger.svg | 1 + app/share/calendar.svg | 54 + app/share/cancel.svg | 97 + app/share/cash.svg | 120 + app/share/cheese.svg | 1 + app/share/chicken-leg.svg | 1 + app/share/chinese-food.svg | 1 + app/share/coffee.svg | 1 + app/share/comment.svg | 85 + app/share/croissant.svg | 1 + app/share/delete_line.svg | 89 + app/share/donut.svg | 1 + app/share/draft.svg | 60 + app/share/fork.svg | 1 + app/share/global_discount.svg | 1 + app/share/hot-dog.svg | 1 + app/share/ice-cream.svg | 1 + app/share/kebab.svg | 1 + app/share/menu.svg | 1 + app/share/menu_section.svg | 204 ++ app/share/milkshake.svg | 1 + app/share/new_sale.svg | 81 + app/share/party.svg | 77 + app/share/payment.svg | 91 + app/share/payment_term.svg | 1 + app/share/pizza-slice.svg | 1 + app/share/plus.svg | 61 + app/share/pos-icon.ico | Bin 0 -> 31982 bytes app/share/pos-icon.svg | 280 ++ app/share/pos_banner.png | Bin 0 -> 29661 bytes app/share/position.svg | 81 + app/share/print_order.svg | 77 + app/share/print_sale.svg | 89 + app/share/reservations.svg | 54 + app/share/salesman.svg | 78 + app/share/search_product.svg | 70 + app/share/search_sale.svg | 81 + app/share/table.svg | 1 + app/share/tables.svg | 152 + app/share/taco.svg | 1 + app/share/tea.svg | 1 + app/share/tip.svg | 1 + app/share/waffle.svg | 1 + app/share/waiter.svg | 1 + app/small_screen.css | 164 + app/states.py | 35 + app/translations/i18n_es.qm | Bin 0 -> 13013 bytes app/translations/i18n_es.ts | 823 +++++ config_pos.ini | 59 + doc/index.rst | 33 + doc/translation_guide.rst | 19 + pospro | 50 + pospro.pyw | 53 + posproc | Bin 0 -> 640 bytes project.pro | 3 + setup.py | 59 + update_new_version.sh | 4 + 96 files changed, 8903 insertions(+) create mode 100644 COPYRIGHT create mode 100644 INSTALL create mode 100644 INSTALL_es create mode 100644 LICENSE create mode 100644 README create mode 100644 TODO create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-37.pyc create mode 100644 app/__pycache__/buttonpad.cpython-37.pyc create mode 100644 app/__pycache__/common.cpython-37.pyc create mode 100644 app/__pycache__/constants.cpython-37.pyc create mode 100644 app/__pycache__/localdb.cpython-37.pyc create mode 100644 app/__pycache__/mainwindow.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-23-57-24).pyc create mode 100644 app/__pycache__/mainwindow.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-24-19-22-37).pyc create mode 100644 app/__pycache__/mainwindow.cpython-37.pyc create mode 100644 app/__pycache__/manage_tables.cpython-37.pyc create mode 100644 app/__pycache__/proxy.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-21-32-44).pyc create mode 100644 app/__pycache__/proxy.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-12-19-17-24).pyc create mode 100644 app/__pycache__/proxy.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-24-19-22-37).pyc create mode 100644 app/__pycache__/proxy.cpython-37.pyc create mode 100644 app/__pycache__/reporting.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-21-14-37).pyc create mode 100644 app/__pycache__/reporting.cpython-37.pyc create mode 100644 app/__pycache__/states.cpython-37.pyc create mode 100644 app/app_tablet.css create mode 100644 app/buttonpad.py create mode 100644 app/common.py create mode 100644 app/constants.py create mode 100755 app/electronic_scale.py create mode 100644 app/help.py create mode 100644 app/large_screen.css create mode 100644 app/localdb.py create mode 100644 app/mainwindow.py create mode 100644 app/manage_tables.py create mode 100644 app/medium_screen.css create mode 100644 app/proxy.py create mode 100755 app/reporting.py create mode 100644 app/share/accept.svg create mode 100644 app/share/beer.svg create mode 100644 app/share/breakfast.svg create mode 100644 app/share/burger.svg create mode 100644 app/share/calendar.svg create mode 100644 app/share/cancel.svg create mode 100644 app/share/cash.svg create mode 100644 app/share/cheese.svg create mode 100644 app/share/chicken-leg.svg create mode 100644 app/share/chinese-food.svg create mode 100644 app/share/coffee.svg create mode 100644 app/share/comment.svg create mode 100644 app/share/croissant.svg create mode 100644 app/share/delete_line.svg create mode 100644 app/share/donut.svg create mode 100644 app/share/draft.svg create mode 100644 app/share/fork.svg create mode 100644 app/share/global_discount.svg create mode 100644 app/share/hot-dog.svg create mode 100644 app/share/ice-cream.svg create mode 100644 app/share/kebab.svg create mode 100644 app/share/menu.svg create mode 100644 app/share/menu_section.svg create mode 100644 app/share/milkshake.svg create mode 100644 app/share/new_sale.svg create mode 100644 app/share/party.svg create mode 100644 app/share/payment.svg create mode 100644 app/share/payment_term.svg create mode 100644 app/share/pizza-slice.svg create mode 100644 app/share/plus.svg create mode 100644 app/share/pos-icon.ico create mode 100644 app/share/pos-icon.svg create mode 100644 app/share/pos_banner.png create mode 100644 app/share/position.svg create mode 100644 app/share/print_order.svg create mode 100644 app/share/print_sale.svg create mode 100644 app/share/reservations.svg create mode 100644 app/share/salesman.svg create mode 100644 app/share/search_product.svg create mode 100644 app/share/search_sale.svg create mode 100644 app/share/table.svg create mode 100644 app/share/tables.svg create mode 100644 app/share/taco.svg create mode 100644 app/share/tea.svg create mode 100644 app/share/tip.svg create mode 100644 app/share/waffle.svg create mode 100644 app/share/waiter.svg create mode 100644 app/small_screen.css create mode 100644 app/states.py create mode 100644 app/translations/i18n_es.qm create mode 100644 app/translations/i18n_es.ts create mode 100644 config_pos.ini create mode 100644 doc/index.rst create mode 100644 doc/translation_guide.rst create mode 100755 pospro create mode 100755 pospro.pyw create mode 100644 posproc create mode 100644 project.pro create mode 100644 setup.py create mode 100644 update_new_version.sh diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..bce7cc7 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,16 @@ +Copyright (C) 2012-2015 Oscar Alvarez. +Copyright (C) 2008-2012 Cédric Krier. +Copyright (C) 2008-2012 B2CK SPRL. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..621632e --- /dev/null +++ b/INSTALL @@ -0,0 +1,56 @@ +Installing Tryton POS Client +============================ + +Prerequisites Client +-------------------- + + * Python 3.5 or later (http://www.python.org/) + * python-setuptools + * python3-pyqt5 + * python3-pyqt5.qtsvg + * python3-dateutil + * python-pip + * libusb-1.0-0 + * libusb-1.0-0-dev + * python3-dev + * python3-pil + + * neo (https://bitbucket.org/presik/neo) + + * Optional, Pip packages: + pip3 install pyusb + pip3 install pillow + pip3 install qrcode + pip3 install paramiko + pip3 install pycups + +Prerequisites Server Modules +---------------------------- + + * trytond_account_invoice (http://www.tryton.org/) + * trytond_sale (http://www.tryton.org/) + * trytond_sale_invoice_grouping (http://www.tryton.org/) + * trytond_sale_payment (http://www.bitbucket.org/) + * trytond_sale_shop (http://www.bitbucket.org/) + * trytond_sale_pos (http://www.bitbucket.org/) + * trytonpsk_sale_salesman (http://www.bitbucket.org/) + * trytonpsk_sale_w_tax (http://www.bitbucket.org/) + * trytonpsk_sale_pos_frontend (https://bitbucket.org/presik/trytonpsk_sale_pos_frontend) + * trytonpsk_product_onebarcode (http://www.bitbucket.org/) + + +pip3 install pyserial +sudo adduser myuser dialout + + +Installation +------------ + +Once you've downloaded and unpacked the pos_client_qt5 source release, enter the +directory where the archive was unpacked, and run: + + python setup.py install + +Note that you may need administrator/root privileges for this step, as +this command will by default attempt to install module to the Python +site-packages directory on your system. diff --git a/INSTALL_es b/INSTALL_es new file mode 100644 index 0000000..a95394f --- /dev/null +++ b/INSTALL_es @@ -0,0 +1,352 @@ +Instalación de Tryton POS Client Qt5 +==================================== + +Esta versión solo es compatible con Tryton 4.0+, se asume que el usuario tiene +conocimientos básicos previos sobre la instalación y configuración de Tryton, +especialmente los modulos oficiales relacionados con contabilidad y ventas, no +es el objeto de esta guia abordar temas de configuración básica. + +Para poder instalar Tryton POS Client Qt5 se requieren los siguientes +paquetes instalados en el sistema: + +En Debian, Ubuntu y Derivados, se recomienda usar: $ apt install paquete + + * Python 3.5 or later (http://www.python.org/) + * python-setuptools + * python3-pyqt5 + * python3-pyqt5.qtsvg + * python3-dateutil + * python3-pip + * libusb-1.0-0 + * libusb-1.0-0-dev + * libcups2-dev + * gcc + * python-dev + * python3-dev + * python3-pil + + * neo (https://bitbucket.org/presik/neox) + +Los siguientes paquetes se deben instalar usando PIP + + pip3 install pyserial + pip3 install pyusb + pip3 install pillow + pip3 install qrcode + pip3 install paramiko + pip3 install pycups + + +Tener en cuenta que algunos paquetes se deben instalar con pip para python3. + +Nota: el Cliente POS de momento ha sido testeado en Windows parcialmente, asi que +no hay garantia de que funcione al 100% en este OS. + +Se recomienda instalar Tryton 4.0 creando un ambiente virtual con +virtualenv. + +Los siguientes módulos se deben instalar en la base de datos Tryton +y deben estar configurados al 100% (se ampliara explicación de la configuración +más adelante). + +Los modulos deberian instalarse aproximadamente en el orden en que estan +listados de arriba a abajo, empezando por Oficiales, y luego los modulos +Presik. + + +Modulos Oficiales +---------------------------------------------------------------------------- + * trytond_account (http://www.tryton.org/) + * trytond_account_invoice (http://www.tryton.org/) + * trytond_stock (http://www.tryton.org/) + * trytond_sale (http://www.tryton.org/) + * trytond_sale_price_list (http://www.tryton.org/) + * trytond_sale_invoice_grouping (http://www.tryton.org/) + * trytond_account_statement (http://www.tryton.org/) + +Sugerencia, instalar en el ambiente virtual usando: + +$ pip3 install trytond_module + + +Ingresar al directorio e instalar (dentro del ambiente virtual +anteriormente creado): + + $ python3 setup.py install + + +Modulos No Oficiales (Presik) +---------------------------------------------------------------------------- + * trytonpsk_sale_payment (https://bitbucket.org/presik/trytonpsk_sale_payment) + * trytonpsk_sale_w_tax (https://bitbucket.org/presik/trytonpsk_sale_w_tax) + * trytonpsk_sale_shop (https://bitbucket.org/presik/trytonpsk_sale_shop) + * trytonpsk_sale_pos (https://bitbucket.org/presik/trytonpsk_sale_pos) + * trytonpsk_product_onebarcode (https://bitbucket.org/presik/trytonpsk_product_onebarcode) + * trytonpsk_sale_salesman (https://bitbucket.org/presik/trytonpsk_sale_salesman) + * trytonpsk_sale_discount (https://bitbucket.org/presik/trytonpsk_sale_discount) + * trytonpsk_sale_pos_frontend (https://bitbucket.org/presik/trytonpsk_sale_pos_frontend) + +Ingresar al directorio e instalar (dentro del ambiente virtual +anteriormente creado): + +Sugerencia, descargar los paquetes desde bitbucket usando en el terminal: + + $ hg clone paquete + +Luego instalar: + + $ python3 setup.py install + + +Nota: Verificar todos los modulos anteriores esten en la versión correcta, +si alguno tiene una versión distinta seguramente el POS no funcionará. + + + +CREACION DE BASE DE DATOS Y CONFIGURACIÓN DE VENTAS +---------------------------------------------------------------------------- + +Se asume que se tienen conocimientos previos sobre Tryton por lo cual se +resumiran los pasos: + +- Crear una base de datos + +- Instalar los modulos oficiales en la base de datos: + * Administración > Modulos > Modulos + +- Instalar los modulos Presik: + * Administración > Modulos > Modulos + +- Crear la Compañia, Año Fiscal, Plan de Cuentas, Formas de Pago, etc. + + +Configuración del Modulo de Ventas (especialmente sale_shop, sale_pos) + +- Crear un Libro de Estado de Cuenta: + + Contabilidad > Configuración > Estados de Cuenta > Libros de Estado de Cuenta + + +En el módulo de Ventas > Configuración: + +- Crear una "Tienda" (Shop) + +- Crear un "Terminal de Venta" y asignarle al menos un Libro Contable. + Ej: Efectivo + + +En Terceros: + +- Crear el "Tercero" que sera usado en el POS y asignarlo a las tiendas por defecto. + +- Crear al menos un "Empleado" (que será usado como un vendedor cuando la venta +lo requiera) + + +En el módulo Administración: + +- Crear un usuario POS, en: + "Administración > Usuarios > Preferencias" + +Asignarle la tienda, y el terminal de venta creado anteriormente, +asi mismo verificar que este usuario tenga "Permisos de Acceso" para el modulo +de Ventas. + +Tambien en la pestaña "Permisos de Acceso" marcar los campos: + +Usuario POS Frontend [X] + +Usuario Borra Ventas POS [X] + +Este último es opcional si es aplica. + + + + +INSTALACION Y CONFIGURACION DEL CLIENTE POS +---------------------------------------------------------------------------- + +Requisitos del Cliente POS: + + * libjpeg8 + * libjpeg62-dev + * libfreetype6 + * libfreetype6-dev + + +Instalelos con: apt-get install paquete + +Ahora se debe descargar e instalar el modulo python_escpos, el cual +es requerido para que funcionen las impresoras POS: + +$ hg clone https://bitbucket.org/presik/python_escpos + +$ cd python_escpos + +$ python setup.py install + + +Descargar el Cliente POS, usando el comando hg clone: + +https://bitbucket.org/presik/presik_pos + +Con esto obtendrá la ultima version de desarrollo. + +Descargue el microframework NEO, desarrollado por presik: + +https://bitbucket.org/presik/neo + +Instalelo dentro de la carpeta principal de presik_pos, +los directorios deben quedar así: + +|__presik_pos + |__app + |__doc + |__neo + +Poner la carpeta descargada en algun lugar de su directorio /home/usuario/miaplicacion + +Crear el directorio ".tryton" en el directorio HOME del usuario, ejemplo +si mi usuario se llama "pedro", la ruta debe quedar así: + + /home/pedro/.tryton + +Copiar el archivo "config_pos.ini" del paquete descargado al directorio +creado ".tryton" + +Al final del proceso debe quedar así: + + /home/myuser/.tryton/config_pos.ini + +El archivo config_pos.ini define la información de la configuración del +terminal de venta, y los parametros de conexión, si el archivo queda +mal o con algún error es probable que el cliente NO funcione o genere errores. + + +Archivo de Configuración del POS +-------------------------------- +A continuación se explicaran los principales campos en este archivo de +configuración: + + #Valid protocols: xml, json, local + protocol=xml + +El protocolo debe ser 'xml' tal como se ve en el archivo por defecto, +'json' presenta un bug que no se ha resuelto. + + server=192.168.X.XX + +El servidor al que se va a conectar el terminal. + + port=8000 + +El puerto de conexión + + database=DEMO40 + +El nombre de la base de datos creada + + + user=admin + +El usuario por defecto para el terminal de venta, este debe tener permisos para +ingresar al terminal, por defecto es admin. + + +Para configurar la impresora se maneja el siguiente formato: + + printer_sale_name=interface,rutadelaimpresora + +Las interfaces válidas son tres: usb, cups, network y ssh (en este caso instalar openssh-server) + + Ejemplos + + printer_sale_name=usb,/dev/usb/lp0 + + printer_sale_name=cups,EPSON-TM-T20 + + printer_sale_name=ssh,usuario@contraseña@ipdelequiporemoto@puerto@/dev/usb/lpX + + + + +Id de dispositivo: + + device_id=CAJA-10 + +El numero Id de la caja a conectarse tal como fue creada en Tryton, en +Terminales de Venta, para hallarla se debe usar el boton de herramientas, +y click en la opción "Ver Registro..", en el formulario de la Tienda. + + + +Haga una venta desde Tryton sin POS +----------------------------------- + +Asi que en este punto sin necesidad del Cliente POS usted debería ser +capaz a través del cliente Tryton 4.0, de hacer ventas: + + >> Ventas > Ventas POS + +En caso de no poder hacer ventas a través de este módulo probablemente +tampoco las podra hacer por el Cliente POS. + +Recuerde que para iniciar a hacer ventas debe abrir primero los "Estados +de Cuenta" a través del Wizard en el Módulo de Ventas: + >> Ventas + > Estados de Cuenta + > "Abrir Estados de Cuenta" + +Y al finalizar el día/turno del cajero debería ir a la misma ruta y +"Cerrar los Estados de Cuenta", con el fin de que Tryton contabilice +los pagos y marque las facturas como pagadas. + + +Ejecutar el Cliente Presik POS +------------------------------ + +Antes de ejecutar el cliente es muy importante verificar que Tryton +esta perfectamente configurado y permite hacer ventas por el usuario creado. + +Antes de ejecutar el cliente debe asegurarse de que el servidor este +ejecutandose. Para ejecutar el cliente ingrese al directorio +"presik_pos" y ejecute: + + $ python3 pospro + + + +Manejo Básico del Cliente POS +----------------------------- + +El cliente POS funciona con los códigos de producto, asi que los +productos que no tengan código, no podrán ingresarse. + +Para testear el sistema debe crear un producto tipo "Articulo" +vendible, con código. + + +Oprima la tecla F1 para ver los principales atajos de teclado. + +* Ingresar productos: +Para ingresar un producto, digite el código del producto, y oprima la tecla [+] + + +* Terminar la venta: +Oprima dos veces la tecla [intro], lo cual hará que el sistema pida el valor a +pagar. En la barra superior en el campo de información. + + +* Ingresar el pago: +Ingrese el valor en billetes, monedas de pago del cliente, y oprima la tecla [+], +para confirmar el pago. + + +* Impresion de factura: +Si la impresora esta bien configurada, la impresión de la factura es automática, +en caso de no haber impresora el sistema permite continuar a la siguiente venta, +si desea reimprimir la factura utilice la tecla [F7]. + + +Cualquier bug o error, puede reportarse en bitbucket. Para asesoria +o soporte adicional, puede contactarme: oscar.alvarez.montero@gmail.com diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README b/README new file mode 100644 index 0000000..59b1fd3 --- /dev/null +++ b/README @@ -0,0 +1,46 @@ +Presik POS Client for Tryton +========================================= + +The Point of Sale Client for Tryton application platform development +in Qt5 and Python3. + +Installing +---------- + +See INSTALL + +Support +------- + +If you encounter any problems with Tryton, please don't hesitate to ask +questions on the Tryton bug tracker, mailing list, wiki or IRC channel: + + https://bitbucket.org/presik/presik_pos + https://www.presik.com + http://bugs.tryton.org/ + http://groups.tryton.org/ + http://wiki.tryton.org/ + irc://irc.freenode.net/tryton + +License +------- + +See LICENSE + + +Update Translation +------------------ +Modify your i18 file and execute in terminal: + +$lrelease i18n_es.ts + + +Copyright +--------- + +See COPYRIGHT + + +For more information please visit the Tryton web site: + + http://www.bitbucket.org/ diff --git a/TODO b/TODO new file mode 100644 index 0000000..87e4fab --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ +TODO + +- Adicionar funcionamiento del POS modo standlone + +- Usar voz para informar al usuario diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..8e392fe --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,3 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +__version__ = "5.0.0" diff --git a/app/__pycache__/__init__.cpython-37.pyc b/app/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6c1250471ad89f6cd94b62b1a5f569979dab933 GIT binary patch literal 198 zcmZ?b<>g`k0^P#IxMe{4F^B^Lj6jA15EpX*i4=w?h7`tN22G|aR#QC#Jp(^Y##`L+ z@nxw+#hLke@$oAeikN`vz{Ia8{fzwFRQ;6HvecaXg481Y;MByl%$!tx7uPUXAO8Sf zSHBSbkf6vAe?KUt3p7;UEy&+5#MRFwSihhswKy|7z97F?Ke3=dKR!M)FS8^*Uaz3? V7Kcr4eoARhsvR@Xy3at&007YGGZ6p) literal 0 HcmV?d00001 diff --git a/app/__pycache__/buttonpad.cpython-37.pyc b/app/__pycache__/buttonpad.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53db8fb2f47cacd95f7dd9cebef703bbb5cffdaa GIT binary patch literal 5143 zcmbtYTW{RP73OWZT<+D9EXi(~x^9|w+eC8H+~YXWWo@gqSC>`dhG|03JCsC;yIga) zwk5290CJJ$Aw~L7^evDcgT5901^pGpJQe7ZU-|>`)b9+*wUkQ(v|R9T&Y3eaXU?44 z>|(WQD|nv2=Y9CajH3LDko=*ZxAF4712BcDq2kb23)PCBJZ_J5#{x%m!}zCh+sX8>|exEc>T{pJNliCwv3_GfeF$ z&B@2uhth7ULR(LohA6Jz3D~xui1PZ4IAFKD{b)$Y&Bf?phSb(OiPzopS;~o_m8Bt1 zqTb>#NuvH^6%&GJVHP*&M*RbAZcY^Vju-o)p7QYP!+tjjqQ0nQgltMt&G7cHch`@* zBwYT`yi0hw0l*XpueQHX;u^3g;LG>tccPv@&;0v-7!CY*zTk+F45sT{ zod)0`tb&(tK8F<&B~eGpk#?+|;M_;bvCd!_vSp+#rXA``|3qO1dJC*@qB3(^-^M7{ zl$7F$dT21~urN}0i>$P##Z1y|NiT!0?3%|Gt4L}U<7?YGt4pi_>>RKOiA@5V0(M?v z)4*o7^-*DDuvt(qNa{sUzX$9oiG3f~(~t%-KxTEf#D1`+$N!cq+U%0#`5|~NZ|h@@ zAMNR%sqxDh&oh$e$KZK(%u{4P+0#z({8aKZ!Sl0IJlH9CVx~hsKmP=}le>IQ@;(pV zIY>D#u`9rS0V%ai2B{^C{aZHX1Y@4Y!=y68iR_w3+OqON`?uO*@z5Gs?9B@byT+8?X&02kQc@kMDJ`ju zN=MXIFDQU-f$MFy@TqxdV~+aB-cm=!&$U0mDn3*1{o_vR7K`oJg6TXa8+=Sg46s{e(Y2-bl}CwzOa_w>1?jvZf%H4wpdEf2X(!E*AE?YX`#K; zx+SU^Q%0W1*H7i=5aXcOnY%;+iixw*Q>Sw-Upx-_i7QEkwT+ebrZ|@`KkgCre1dFu z&U7|f*w!OUo;rrPX&Z*>pDYD~wJq??5gR#mtXIVRd5} z+s~yv-Bs**d?$d;ay|F`2U6$*tKGVj?lKoL?-B($ty9Z)>h1S@$Y`xCym#sL>T*le z@|~tV!i@dI3qoh|>H`QRXAhdu9 zgYvB~n9sYy?D@$~#Dv}VdcMn({m^eVkcWpziZbB{D_>AdaX+~k#lb<;PrR@*@VY^N zTTFoZt{*1>gvn`I5)1Rb7Y=>k^zNY^$@%v0vll_5D$u78)wC(JQ|>8!TAfxKs)nDL{VeTZ_S>wW zsb0sa;*6y5uCQHF$1wC=S5#d0-p~uv9x1Y@X4^p&4PEz1L4?h%VI%40sIoYZSDK)u zchMX_H^@<)-bmv&sr}rY-U?=5@RN0FIJ=onq#|BG1h!9l6=(+-E2XUx7a3 z6#}mkc!R*}0HU1QuuEA(7!u}DkdSkPsn7lwrSI)vnWUl*06l=2EIchhl7H!YewkGx}y z!-I<+PEJMP!P3Z~$)z;}ic@X9k%kD(mi;+L<1~*kJswM=4`#ApkllvH838I@$8zxo zferx*Q}HH&@%<+kR?11r`2cU~>DvG7=~c{e#?#5mrKdlO##v8?{eS&pLiaQ+El9Bo zd2lpmx`AuddJEvYmHuaNaXM&4qnLa!ovA9lDE=RsB5D3alT#eCzC}~dpzoVvA_X$s z$Z@a3U9LcPIZ;Xl!;SHq^+r~BR&R)limL}%lpLvuD|BtcMJ;|YK^&BLDY3H_BG+++ zE`l<`Xl&`hE>i?}p_tt=UWEGH>NX`6gyLPg(ONT5>TZpoThG)b??%l=OcDr# z3aqp`Dg4^scdrQfc}-Mk^KPb?l(>+79AV2oFC6T6qC%qo-BOG5~Z!D)+}R<^owzvd{UVU>uOq3U85|? bF9KMo;7F$xZ3@+!ty_kvH*PjwZIu247ce{H literal 0 HcmV?d00001 diff --git a/app/__pycache__/common.cpython-37.pyc b/app/__pycache__/common.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..465aba95accbdba0c40088d6a9eff232f81d8b81 GIT binary patch literal 762 zcmah{&5qMB5Vjqsb^rKBJVPb6;j*_CLa+rPLAu>m2y!3_vD>Db#KCb^T9jT`9%T2( zEBVT4Ux5>2FN+WtEX~Yk*)yN#8$Tb7`UuwE=j7v8jL;9exmg~7XJGXKIF2~ZQDh^@ zaX})2kw9DTf(ri0?(+^r#=D$B>~Xw6#=W$tPQOay)kqvv(gOGMk!J1m}g26U>Q?G%cKxdZNZd>6hWHGsQ6 z-v=96h=7Yzk;_V`U?CEl7939BP2bFp-cG}1usr^-oQ1!`-u~oC@bY*TE~nw)BB+$8 zvy-@zb&yn5kjkQv<)k_@Y%R2-Vzzzj5FX+Om|-;n{6{6LmQ~8BbPIq+8n-ZzbI<~G zAmNqkiWslf%J@n)B?qxPWm9|-%C&LrhLCLfTE=i9RF>X`eQk3QR`>rB`GvNB*@I9q!$UIkSeMaXpT(yK literal 0 HcmV?d00001 diff --git a/app/__pycache__/constants.cpython-37.pyc b/app/__pycache__/constants.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7da5bfb768b0092056cf068274932f83aa5a3e48 GIT binary patch literal 1258 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 aD?lTfh~k%tiqQa6u}l`Fv#|!x8u>5YUiPH` literal 0 HcmV?d00001 diff --git a/app/__pycache__/mainwindow.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-23-57-24).pyc b/app/__pycache__/mainwindow.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-23-57-24).pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e0506c1ace4a13f71033b50611451072faacc54 GIT binary patch literal 70492 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
vjr0zbm1dOt4Y^2y;ZV6nmS6I#S-JrvR; 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%0D#Vd{C*m)P4f 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 HU;qCDw3F5^ literal 0 HcmV?d00001 diff --git a/app/__pycache__/mainwindow.cpython-37.pyc b/app/__pycache__/mainwindow.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..552c40fe9088f05b6aa6334b7328211f70b4fb07 GIT binary patch literal 69762 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 literal 0 HcmV?d00001 diff --git a/app/__pycache__/manage_tables.cpython-37.pyc b/app/__pycache__/manage_tables.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b015e54f3ee4495b3c7df12c831df9fc453f71d GIT binary patch literal 2462 zcmaJ?TW=Fb6rP#AcRTVGDtYW*n%6$@7kKJ9V>_3s>a6BId*)oe^UZ@wr9`0I*ztdG3xxcQ zlWD=7dr;FpG@Ni6krwtWqCM7PJ)>n%B1qRfv~+9hwjdmCz-JzrNA!rcirnhZR*BoZ zz#U%v!)VPsCcMOF4hWwS#=g-i!@SHbm|KFuyuxXNxRrNsE2+Co&AfOlL;l3yO9mNC zo)6Nk2ZJn2;vuV6m6dM!QmFD%KlVGK>9-@14ll}1`^%fRmlhYMjXS)6Yqyq{7Z+~b zgpIG3+~IkBY+2717nWStP(^>&4C^Rq(wPt zu{UIsa?06()6!lzAT5(y+=kwILrl`L!M1`bJ@lh!Y(+4YgS`nA`?L*BMlza{eRjxl zkU39|C?z>LG&s{Ey_OTtgk57Ec03D4rXJZjgLwf?JL9$Szun?tDJR_-oN)r0SaoH_ zeor(N3pw~Q^E2U=VKC?mDT|o3=zJbV6hz22-fH@<5=t53Qc%hjdY zqm5_v=6ZdtQR_nt!)>phq&2_auk|!syiDVz+TWAs;L=M_Q@owfIa->0Ha$m&mrmkU zo%$o7coGT*%!c|9YI+YEa51FIHj}fQjc9CgI)XqRLhV9Wx(4p&^vE2soTaxg5U>kj zJPwOdmncv|N{OU?oxRb_NDeRrU{_b&0$4jP=uIpsdh-9+LbHU0xhc{60a5}Myyci4 zeuv2kJsgn=%+*8IZv~eM_#9eV~l1WMqa&76naJK-33L=H%u+U4#9R z_0vjL%q{qXt&!K&@7c&66|z!Z$c=qAa&iZ-TgaX6%nG8soI9H|xBq0nf(AS7S5s1X zhpF)nLvkib&>o#**J02B0k1zC(Tu!>i{Y{|Ad2Q}ll^Aw%Ea}G`0W86) zONxeyc3}~yBpUSM3FI*j?@KFDm}WuJ>#yF1d5S=`$KFPy&$c}HY!Q75mm`|y6jzxJ4mbe=0yCO@XC2y7K*VG^gxnGWR@lxmOf zn7|M^7xmU?#8kftHN}Gj$P0RcaFXUtD`Wg>tR?-cssmg;shsS2jz^ky5YM^8O&Y#il z^f}j-pQ5YsYJv` z%v5Fe{cKAW{C27bP7H*kM`kw(W26#%eSkiSYu|2ojc@L6tgC|O!K)55aSg>_zK)S} hV)=>r&dQj8Rxwl3i_kc1mf7?pW*dw$>X@^i`~%@eJ9q#9 literal 0 HcmV?d00001 diff --git a/app/__pycache__/proxy.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-21-32-44).pyc b/app/__pycache__/proxy.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-21-32-44).pyc new file mode 100644 index 0000000000000000000000000000000000000000..48f2a86cb3556a65f1aa7123e61c901f74c79456 GIT binary patch literal 2784 zcmbVO%W@pI6>Xr=j~Nb!loVT09xmopNv9%?;zY6Iq+E7P$|+YgOp&ZSdXcWy02F)N z^DuyM#HnT$p|<%0F34r}`5J7q(hp?emCr?!BU`e5XJMYG zR3;yDy_Bi!$nH7MJ916-DwcOuqB^R3#`86~p7&&5ZlHBttzGn0?$mUHL8#IAsXI#G_Srd#oa)>M>KNkmqjcnWm zXH157VgJcwfVss`b9|Wz*C~ooT^dsqPMoMxyJ#|-+G7{W@??2$t1?^Dnce#6U~jl( zl&;FUT9!|0l_joQnQCfmIUOlKnI`Zw?M%lou5)-YSG!uzv=ds*H4YNao{aw;=rweI zb}*PIS(SsG_YdD69H{cBs?}g;_fNZj*!$z--QnTjaR0A|d&8^Z{=@ADgGc*&!^7R- z&cR@=m8m{1<}))W=X1Qyo}FyZPh6s=qnT7X!3w4a6YwtQOyKEqy^i-0HD0F>mG_X% z?_pGs1Du>oCON1LuP7GsmP};&oaGTwik_G#MVZKT*+-em+j2v0Vnjzq0QZjTJSvTS zjE&SI0)-Nl1dC_wZB!a8bPvoGRO7zJ8t_(d8D2nL4U-XCnT+oRPs2+vTL{lxGIU~E zPLzv}DpgDK85=C)V{7N{3|vyqt74&RmqIe-lhUXxB1W+jy@^F#II_>27|*Jy3o#Y~ z5JuHUE}oaVoEZHL4AQTWA(C1{IJ&ebs%d45f;vqK6R<9xm@RLwPP=_QXhiCwLJ#pp2;lK-^53H1ICF`KQ*uJ zYpp1rDoYa-#kXk@4>-rRUc0%i)tKQ6iBThxDWwkbik5x6G(vB}d4dbph)cL)6I_6( zOLiK0w_*(^lEw%L=h10w#W-qW8_V!t{4}x2IBlXPkpgdNlU|5RI%hO=v4Lxij|!~al%OD*X^7QDbv3E1OGfZK^~~b82aaxs}3FLRe_^~pjRYg~x2_}=5Vg%j%Uk@-Fu;?hNa9v2b1h5ivWg>bJ|ffh&A zRBoZyybcqdT zKNqLMhGWqP8+j-Z!Nv(-k7NX3<0ifcJ+Lu6!IYmWO(8K8Efl z^bgyD&X=Y7$0%-y{U`LAk)ar&e+uKm`NC$2$IXS6G%ohyH2P;W&mJDdJMT8Tm?wQK~_JoIs zH=+fsiLM|b?ZNq#Rd6Nm+`qu2Ul$Wi*G(3;TJjTpH5U3;_~tjrrVrC1vBF{VB@XQ_ zS3r<*C~$ZnZli*OLos&|K<);oyy0*xKGe6+19Ibd&Al0|?Ym33VdA7LYg z5D%bP^6xJ10;xVrUkUZ?@GKnaH!v7KD#NTx_XJ%|_?_q$1x~Z5sopD!PZnj}_ULb6 zxc)8~@(}%y45_5`O1d0x_>*Ult{_eNxr6M5l=3uAqwYr9OV?82`)F+nt3|Qw*E8(y z7%;r^@WERT@_1+W@!tMU#x)&G(<~m%Wyh4Y+9vbjKWNN5S{*5kzX-KV_ddA);LW$+ z`ek;j4G46nDqBt3-yPRA)nr~PioDvhK%v9M_{C8p>o=r-P3zpdUM%lg1Y`mQxAD)Z u)sx|mPeH^*b6rjCmz~|EziqL6mwdudEKd=C(=hFA?xwhgDGQ#4efBvmQcxTK literal 0 HcmV?d00001 diff --git a/app/__pycache__/proxy.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-12-19-17-24).pyc b/app/__pycache__/proxy.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-12-19-17-24).pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ad0d56ab13a68ea374bb65923f53b89b6fb2731 GIT binary patch literal 2788 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 literal 0 HcmV?d00001 diff --git a/app/__pycache__/proxy.cpython-37.pyc b/app/__pycache__/proxy.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c202b4cae341f2d7b5c8bd5278e534738a32659 GIT binary patch literal 2780 zcmbVOO>Z2x86I*tv$LbsO16`@Lcf%@Xgg@Eb&VJff+BEg$tlu!6<9)n4uTBEOUfE$ z_9G-WwHBiuDqs5#bdZj{_mA|-Yft_QIrVut(#kPX6zDAYk{oh4Rep{jfW`auLv@btPXOjdCkT==d8UK=0b*2ybSW_lR!o?ei_JE#iwE3 zQHkt)#q~lavMbXwo_FPn>{TpJRY!GIddl+^xtjN6U#{Wqs#-bktKM^_RxfzIrqE7;e z(T+ycr<_&AT-PpvM#^WUQQ#C5;_WUR*%wY6O{<9uF)a(7FseS#x3CNS z76l@r4Fs1eP<>^RhZ1K)h zW#@XbSz4>BXLGAIzbWgvf*RtnvWHWdh59xga$-`Bl@q0YZrMylmR2Z=pVC`M zY5{h(c>DEzE$0khNsNz3i*$8SmhgXpP9yX>G9)->jkrL*H32AK7wjbRxx+vVWf1cI zEINs;IEtFs#xndnKk3-cQPM33)Y+D6wc5j=i-8n91mS=kV&J%Eb>^pSWd?! zScs+?@^n#MjVtRqBP5!7VSz+Ps)KS~QB zW{O4Sd^|H=-Y(JV>$x)4xDaz)G}G0@W?hf3CgAiYg?A~C09@pka}j}F=wHyS5U+j{ ze{oPvB#oLMAOsLZ4=E)W_rH|)S)ZkB(Z42x9$3ns5_0KS+vosqcpUI1lv#tEBiW-3 z>7R)cVZ$TQ2pf5f;a^7`@Da%fh{sKQ9$w)rO!4K2dKchjW zf8JJfhAh#)MDx&283cl^IsjxY*0q==W&K9SXFV-lFhE9z}oA zl~wO!#H=F(9;+BwwMZ@2u5t1+mzhK&6)4^(K0;#&6oCVvJVg8~JPAESAs`}A3J(!) zLx;Lp*2t2rUvz9M-RKXm_~; z;>uDy5I6B44i3fKhPB)dPGFYEGd|R77y-HOP<81dF*t|zTbjPhi>LjMnA_S1G`qNU zy@O?PEjK}IEM@x`9ooceJTOQVyqt%Ula#$p@JpKz-X_A=7=byQK-AcHG`v)+-oT>z zhZIPrdC&WHacPU+6>YcW&p+Y4+iUpylq>LIv{y9w#l2cRSN~PAu>Mh5*F~|5jTk~a zKz>t(r#uBxeU`lO5oo7p;ZXk=lkqoYn5FauAs5G=iL@wint4s_UQv8KFY9(hlZEIX zQ5aC5H1R5Fv+@CTuX*z=`Z#IQ4@uYyDd9<)MCp3cOI8x$$LQVUvPH4z*Hi575HNgr z|L%jkdAz;zWcTTI#`O(aiB9LyTy#xYt1Swz{)wA;SE~c1@i#&(lHEsl?%w<0!TZ?_ zZwUSat86uH|LnNbRO4B#$bz+Jfvm&D_=BTH)^A9EK<^;~@Gr|#3uh)!RIPtYcRd;Y o_})Z*L-)St8*d7z;~$#o`ik&ABX@<;{X5v literal 0 HcmV?d00001 diff --git a/app/__pycache__/reporting.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-21-14-37).pyc b/app/__pycache__/reporting.cpython-37 (SFConflict oscar.alvarez.montero@gmail.com 2020-03-11-21-14-37).pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc316c3d4b353256f3d4ffc575feae2fe508d40c GIT binary patch literal 15296 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 B`m_K5 literal 0 HcmV?d00001 diff --git a/app/__pycache__/states.cpython-37.pyc b/app/__pycache__/states.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1dc1220725467c46d8b2e6c0c6aad22a3bca5f62 GIT binary patch literal 549 zcmaiwOH0E*5XX0uHjTB#f_U-VL(|%95j+Uui$)PNl_rSx$(mg;nl@{a3XSp9ui+Q) zEA7>jH_x8jJ_JF*f%(sGXPKGZIn3v?Kv?ww_dX;5-lm6dQw8h`5ATE!K%fT(umvF? z5`{#bK$3cqNLsM6;EZ7P2QU4@*%zfHk!4Ypn?M(~WRkIfmVX!XfChQS@dwQHD9>My zqR1civExt{#l>E!Q*PI)+ud?93l*)HR1=hZXWM$WtVvv%mvJeee0kBr!QpMJ_U2_p zGhSO@X=9dp+Sq*7_KNZ9A0sq{%a{;v1dc&2Iet&?N(6$FLp 1: + _clause = ' AND '.join(clauses) + else: + _clause = clauses[0] + query = "SELECT * FROM product WHERE {}".format(_clause) + + cur.execute(query) + rows = cur.fetchall() + return rows + + # def do_query(self): + # c = self.conn.cursor() + # c.execute(query) + # rows = c.fetchall() + # self.conn.commit() + # self.conn.close() + # for row in rows: + # print(row) + + +if __name__ == "__main__": + store = LocalStore() + store.create_table() + store.get_local_products() diff --git a/app/mainwindow.py b/app/mainwindow.py new file mode 100644 index 0000000..fc58c96 --- /dev/null +++ b/app/mainwindow.py @@ -0,0 +1,2637 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +import sys +import os +import logging +from decimal import Decimal + +from datetime import datetime, timedelta, date +from collections import OrderedDict +from PyQt5.QtCore import Qt, QThread, pyqtSignal +from PyQt5.QtGui import QTouchEvent +from PyQt5.QtWidgets import (QLabel, QTextEdit, QHBoxLayout, QVBoxLayout, + QWidget, QGridLayout, QLineEdit, QDoubleSpinBox) + +from neox.commons.action import Action +from neox.commons.forms import GridForm, FieldMoney, ComboBox +from neox.commons.messages import MessageBar +from neox.commons.image import Image +from neox.commons.dialogs import QuickDialog +from neox.commons.table import TableView +from neox.commons.model import TableModel, Modules +from neox.commons.search_window import SearchWindow +from neox.commons.frontwindow import FrontWindow +from neox.commons.menu_buttons import MenuDash + +from .proxy import FastModel +from .localdb import LocalStore +from .reporting import Receipt +from .buttonpad import Buttonpad +from .manage_tables import ManageTables +from .states import STATES, RE_SIGN +from .common import get_icon, to_float, to_numeric +from .constants import (PATH_PRINTERS, DELTA_LOCALE, STRETCH, alignRight, + alignLeft, alignCenter, alignHCenter, alignVCenter, DIALOG_REPLY_NO, + DIALOG_REPLY_YES, ZERO, FRACTIONS, RATE_CREDIT_LIMIT, SCREENS, FILE_BANNER, + CONVERSION_DIGITS) + + +class MainWindow(FrontWindow): + + def __init__(self, connection, params): + title = "PRESIK | SMART POS" + global CONNECTION + self.conn = connection + CONNECTION = connection + super(MainWindow, self).__init__(connection, params, title) + print('Screen Size: > ', self.screen_size) + self.set_style(SCREENS[self.screen_size]) + + self.is_clear_right_panel = True + self.payment_ctx = {} + self.set_keys() + self.stock_context = None + + self.ctx = self._context + self.ctx['params'] = params + # self.ctx['params'] = params + response = self.load_modules() + if response is not True: + d = self.dialog(response) + d.exec_() + super(MainWindow, self).close() + return + self.setup_sale_line() + self.setup_payment() + self.set_domains() + self.create_gui() + self.message_bar.load_stack(self.stack_msg) + + if not hasattr(self, 'auto_print_commission'): + self.auto_print_commission = False + + self.active_usb_printers = [] + + if os.name == 'posix' and os.path.exists(PATH_PRINTERS): + self.set_printers_usb(PATH_PRINTERS) + + self.set_printing_context() + if not self.tablet_mode: + self.create_statusbar() + + self.window().showMaximized() + self.create_dialog_search_products() + + if not self.tablet_mode: + self.grabKeyboard() + self.reader_thread = None + self._current_line_id = None + self._amount_text = '' + self._sign = None + self.create_dialogs() + self.createNewSale() + if not hasattr(self, 'active_weighing'): + self.active_weighing = False + elif self.active_weighing is True: + from .electronic_scale import ScaleReader + self.reader_thread = ScaleReader() + self.reader_thread.sigSetWeight.connect(self.set_weight_readed) + + self.do_invoice = DoInvoice(self, self._context) + self.do_invoice.sigDoInvoice.connect(self.__do_invoice_thread) + self.set_cache_company() + self.set_cache_products() + + def set_domains(self): + self.domain_search_product = [ + ('code', '!=', None), + ('active', '=', True), + ('template.salable', '=', True), + ('template.account_category', '!=', None), + ] + + if self.shop['product_categories']: + self.domain_search_product.append( + ('account_category', 'in', self.shop['product_categories']) + ) + + def filter_cache(self, data, filter, target): + res = [] + for d in data: + for t in target: + if t in d[filter]: + res.append(d) + return res + + def set_cache_company(self): + self.store = LocalStore() + self.store.create_table_config() + self._local_config = self.store.get_config() + if not self._local_config: + company_id = self.device['shop.company'] + self._local_config = self.store.set_config([company_id]) + + def set_cache_products(self): + self.store.create_table_product() + local_products = self.store.get_local_products() + + config = self.store.get_config() + _sync_date = config[1] + products = self.Product.sync_get_products({'write_date': _sync_date, 'shop_id': self.ctx['shop']}) + # print('products------->', products) + self.store.update_products(products, local_products) + now = datetime.now() + self.store.set_config_sync(str(now)) + + def event(self, evento): + event_type = super(MainWindow, self).event(evento) + touch = QTouchEvent(event_type) + # logging.warning('Device:', touch.device()) + return event_type + + def set_printers_usb(self, PATH_PRINTERS): + for usb_dev in os.listdir(PATH_PRINTERS): + if 'lp' not in usb_dev: + continue + path_device = os.path.join(PATH_PRINTERS, usb_dev) + self.active_usb_printers.append(['usb', path_device]) + + def get_current_sale(self): + if hasattr(self, '_sale') and self._sale['id']: + sales = self.ModSale.find([ + ('id', '=', self._sale['id']) + ]) + if not sales: + return + return sales[0] + + def check_empty_sale(self): + sale = self.get_current_sale() + if sale and self.model_sale_lines.rowCount() == 0 \ + and sale['state'] == 'draft' and not sale['number']: + self.delete_current_sale() + + def close(self): + dialog = self.dialog('confirm_exit', response=True) + response = dialog.exec_() + if response == DIALOG_REPLY_YES: + self.check_empty_sale() + if self.active_weighing and self.reader_thread: + self.reader_thread.onClose() + super(MainWindow, self).close() + + def delete_current_sale(self): + if self._sale['id']: + self._PosSale.cancel_sale(self._sale['id'], self._context) + + def resize_window_tablet_dev(self): + self.resize(690, self.get_geometry()[1]) + + def set_stack_messages(self): + super(MainWindow, self).set_stack_messages() + self.stack_msg.update({ + 'system_ready': ('info', self.tr('SYSTEM READY...')), + 'confirm_exit': ('warning', self.tr('DO YOU WANT TO EXIT?')), + 'confirm_credit': ('question', self.tr('PLEASE CONFIRM YOUR PAYMENT TERM AS CREDIT?')), + 'sale_number_not_found': ('warning', self.tr('SALE ORDER / INVOICE NUMBER NOT FOUND!')), + 'sale_closed': ('error', self.tr('THIS SALE IS CLOSED, YOU CAN NOT TO MODIFY!')), + 'discount_not_valid': ('warning', self.tr('DISCOUNT VALUE IS NOT VALID!')), + 'add_payment_sale_draft': ('info', self.tr('YOU CAN NOT ADD PAYMENTS TO SALE ON DRAFT STATE!')), + 'enter_quantity': ('question', self.tr('ENTER QUANTITY...')), + 'enter_discount': ('question', self.tr('ENTER DISCOUNT...')), + 'enter_payment': ('question', self.tr('ENTER PAYMENT AMOUNT BY: %s')), + 'enter_new_price': ('question', self.tr('ENTER NEW PRICE...')), + 'order_successfully': ('info', self.tr('ORDER SUCCESUFULLY SENT.')), + 'order_failed': ('warning', self.tr('FAILED SEND ORDER!')), + 'missing_agent': ('warning', self.tr('MISSING AGENT!')), + 'missing_salesman': ('warning', + self.tr('THERE IS NOT SALESMAN FOR THE SALE!')), + 'sale_without_products': ('warning', self.tr('YOU CAN NOT CONFIRM A SALE WITHOUT PRODUCTS!')), + 'user_without_permission': ('error', self.tr('USER WITHOUT PERMISSION FOR SALE POS!')), + 'quantity_not_valid': ('error', self.tr('THE QUANTITY IS NOT VALID...!')), + 'user_not_permissions_device': ('error', self.tr('THE USER HAVE NOT PERMISSIONS FOR ACCESS' \ + ' TO DEVICE!')), + 'missing_party_configuration': ('warning', + self.tr('MISSING THE DEFAULT PARTY ON SHOP CONFIGURATION!')), + 'missing_journal_device': ('error', self.tr('MISSING SET THE JOURNAL ON DEVICE!')), + 'statement_closed': ('error', self.tr('THERE IS NOT A STATEMENT OPEN FOR THIS DEVICE!')), + 'product_not_found': ('warning', self.tr('PRODUCT NOT FOUND!')), + 'must_load_or_create_sale': ('warning', self.tr('FIRST YOU MUST CREATE/LOAD A SALE!')), + 'new_sale': ('warning', self.tr('DO YOU WANT CREATE NEW SALE?')), + 'cancel_sale': ('question', self.tr('ARE YOU WANT TO CANCEL SALE?')), + 'not_permission_delete_sale': ('info', self.tr('YOU HAVE NOT PERMISSIONS FOR DELETE THIS SALE!')), + 'not_permission_for_cancel': ('info', self.tr('YOU HAVE NOT PERMISSIONS FOR CANCEL THIS SALE!')), + 'customer_not_credit': ('info', self.tr('THE CUSTOMER HAS NOT CREDIT!')), + 'agent_not_found': ('warning', self.tr('AGENT NOT FOUND!')), + 'invalid_commission': ('warning', self.tr('COMMISSION NOT VALID!')), + 'credit_limit_exceed': ('info', self.tr('CREDIT LIMIT FOR CUSTOMER EXCEED!')), + 'credit_limit_capacity': ('info', self.tr('THE CUSTOMER CREDIT CAPACITY IS ABOVE 80%')), + 'not_can_force_assign': ('warning', self.tr('YOU CAN NOT FORCE ASSIGN!')), + }) + + def load_modules(self): + modules = Modules(self, self.conn) + self._sale_pos_restaurant = None + self.Module = FastModel('ir.module', self.ctx) + self.Config = FastModel('sale.configuration', self.ctx) + self._config, = self.Config.find([('id', '=', 1)]) + + self.discount_method = self._config.get('discount_pos_method') + + self._commission_activated = self.Module.find([ + ('name', '=', 'commission'), + ('state', '=', 'activated'), + ]) + self._credit_limit_activated = self.Module.find([ + ('name', '=', 'account_credit_limit'), + ('state', '=', 'activated'), + ]) + + _product = { + 'name': 'product.product', + 'fields': [ + 'template.name', 'code', 'barcode', 'write_date', + 'description', 'template.sale_price_w_tax', + 'template.account_category' + ] + } + self.cache_local = self._config.get('cache_products_local') + + if self._config['show_location_pos']: + _product['fields'].append('location_') + + if self._config['show_stock_pos'] in ('value', 'icon'): + if self._config['show_stock_pos'] == 'value': + _product['fields'].append('quantity') + if self._config['show_brand']: + _product['fields'].append('brand.name') + + if self._config['encoded_sale_price']: + _product['fields'].extend(['image', 'image_icon', 'encoded_sale_price']) + + _PosSale = { + 'name': '_PosSale', + 'model': 'sale.sale', + 'fields': ['number', 'party', 'party.name', 'salesman', 'lines', + 'position', 'total_amount_cache', 'salesman.party.name', + 'payment_term', 'payment_term.name', 'invoices', + 'payments', 'untaxed_amount', 'state', 'tax_amount', + 'total_amount', 'residual_amount', 'paid_amount', 'invoice', + 'invoice.state', 'invoice_number', 'invoice.number', 'invoices', + 'delivery_charge', 'sale_date', 'invoice_type'], + 'methods': ( + 'get_printing_context', 'cancel_sale', 'get_amounts', + 'get_discount_total', 'process_sale', 'reconcile_invoice', + 'post_invoice', 'get_data', 'add_value', 'faster_add_product', + 'get_product_prices', 'add_payment', 'get_order2print', + 'get_sale_from_invoice', 'add_tax', 'check_state', 'to_quote', + 'to_draft', 'new_sale', 'on_change', 'get_salesman_in_party', + ) + } + + _Agent = { + 'name': '_Agent', + 'model': 'commission.agent', + 'fields': ('id', 'party.name', 'party.id_number', 'plan.percentage', + 'active'), + } + _Commission = { + 'name': '_Commission', + 'model': 'commission', + 'fields': ('id', 'origin', 'invoice_line', 'invoice_line.invoice'), + } + + _Tables = self._Tables = None + if self.enviroment == 'restaurant': + self._sale_pos_restaurant = self.Module.find([ + ('name', '=', 'sale_pos_frontend_rest'), + ('state', '=', 'activated'), + ]) + if self._sale_pos_restaurant: + _Tables = { + 'name': '_Tables', + 'model': 'sale.shop.table', + 'fields': ('name', 'shop', 'capacity', 'state') + } + _PosSale['fields'].extend(['table_assigned', + 'table_assigned.name', 'table_assigned.state']) + + if self._commission_activated: + _PosSale['fields'].extend(['agent', 'agent.party.name', 'commission']) + modules.set_models([_Agent, _Commission]) + + self.User = FastModel('res.user', self.ctx) + self._user, = self.User.find([('login', '=', self.user)]) + + if not self._user['sale_device']: + return 'user_not_permissions_device' + + self.ctx['user'] = self._user['id'] + + self.ModSale = FastModel('sale.sale', self.ctx) + self.ModSaleLine = FastModel('sale.line', self.ctx) + self.Product = FastModel('product.product', self.ctx) + self.Journal = FastModel('account.statement.journal', self.ctx) + self.Employee = FastModel('company.employee', self.ctx) + self.Device = FastModel('sale.device', self.ctx) + self.Category = FastModel('product.category', self.ctx) + self.PaymentTerm = FastModel('account.invoice.payment_term', self.ctx) + self.Party = FastModel('party.party', self.ctx) + self.Taxes = FastModel('account.tax', self.ctx) + self.ActionReport = FastModel('ir.action.report', self.ctx) + + models_to_work = [_PosSale] + + if _Tables: + models_to_work.append(_Tables) + + modules.set_models(models_to_work) + self.device, = self.Device.find([ + ('id', '=', self._user['sale_device']['id']), + ]) + + self.shop = self.device['shop'] + self.company = self.shop['company'] + + self.shop_taxes = self.shop['taxes'] + self._journals = dict([(j['id'], j) for j in self.device['journals']]) + + self.employees = self.Employee.find([ + ('company', '=', self.company['id']), + ]) + self._payment_terms = self.PaymentTerm.get_payment_term_pos() + + self.type_pos_user = self._context.get('type_pos_user') + + if not self.type_pos_user: + return 'user_without_permission' + self.user_can_delete = self.type_pos_user in ('frontend_admin', 'cashier') + + self.product_categories = self.device['shop']['product_categories'] + self.salesman_required = self.device['shop']['salesman_pos_required'] + + self.default_party = self.shop['party'] + if not self.default_party: + return 'missing_party_configuration' + + self.default_journal = self.device['journal'] + if not self.default_journal: + return 'missing_journal_device' + + self.default_payment_term = self.shop['payment_term'] + self._password_admin = self._config.get('password_admin_pos') + + self._action_report, = self.ActionReport.find([ + ('report_name', '=', 'account.invoice'), + ]) + + if self._config['show_stock_pos'] in ('value', 'icon'): + self.stock_context = { + 'stock_date_end': date.today(), + 'locations': [self.shop['warehouse']], + } + return True + + def create_dialogs(self): + self.create_dialog_position() + self.create_dialog_comment() + self.create_dialog_search_party() + self.create_dialog_payment() + self.create_dialog_salesman() + self.create_dialog_voucher() + self.create_dialog_print_invoice() + self.create_dialog_global_discount() + self.create_dialog_payment_term() + self.create_dialog_search_sales() + self.create_wizard_new_sale() + self.create_dialog_stock() + self.create_dialog_order() + self.create_dialog_force_assign() + self.create_dialog_taxes() + self.create_dialog_cancel_invoice() + self.create_dialog_sale_line() + if self._commission_activated: + self.create_dialog_agent() + if self.enviroment == 'restaurant' and self._sale_pos_restaurant: + self.create_dialog_manage_tables() + + def set_printing_context(self): + # Printing invoice context + if self.printer_sale_name: + if ">" in self.printer_sale_name: + self.printer_sale_name = str(str("\\") + self.printer_sale_name.replace('>', str('\\'))) + + ctx_printing = self._PosSale.get_printing_context( + [self.device['id']], self.user, self._context) + ctx_printing['row_characters'] = self.row_characters + ctx_printing['delta_locale'] = DELTA_LOCALE + + self.receipt_sale = Receipt(ctx_printing) + + # Printing order context + if self.print_order: + self.receipt_order = Receipt(ctx_printing) + self.set_default_printer() + + def set_default_printer(self, printer=None): + if self.active_usb_printers: + self.printer_sale_name = self.active_usb_printers[0] + if not printer and self.printer_sale_name: + printer = { + 'interface': self.printer_sale_name[0], + 'device': self.printer_sale_name[1], + } + if printer: + self.receipt_sale.set_printer(printer) + + def button_new_sale_pressed(self): + self.createNewSale() + + def button_send_to_pay_pressed(self): + # Return sale to draft state + self._PosSale.to_quote(self._sale['id'], self._context) + if self.model_sale_lines.rowCount() > 0: + if self.check_salesman(): + self.state_disabled() + + def button_to_draft_pressed(self): + # Return sale to draft state + if hasattr(self, '_sale'): + self._PosSale.to_draft(self._sale['id'], self._context) + self.state_disabled() + + def create_gui(self): + panels = QHBoxLayout() + panel_left = QVBoxLayout() + panel_right = QVBoxLayout() + + left_head = QHBoxLayout() + left_table = None + left_bottom = QHBoxLayout() + + self.message_bar = MessageBar() + self.label_input = QLabel() + self.label_input.setFocus() + self.label_input.setObjectName('label_input') + + if self.enviroment == 'restaurant': + values = self.get_product_by_categories() + menu_dash = MenuDash(self, values, 'on_selected_item') + + if not self.tablet_mode: + _label_invoice = QLabel(self.tr('INVOICE:')) + _label_invoice.setObjectName('label_invoice') + _label_invoice.setAlignment(alignRight | alignVCenter) + + self.field_invoice = QLineEdit() + self.field_invoice.setReadOnly(True) + self.field_invoice.setObjectName('field_invoice') + if self.tablet_mode: + self.field_invoice.setPlaceholderText(self.tr('INVOICE')) + + self.field_amount = FieldMoney(self, 'amount', {}) + self.field_amount.setObjectName('field_amount') + self.field_sign = QLabel(' ') + self.field_sign.setObjectName('field_sign') + + layout_message = QGridLayout() + layout_message.addLayout(self.message_bar, 1, 0, 1, 4) + + if not self.tablet_mode: + layout_message.addWidget(self.label_input, 2, 0, 1, 2) + layout_message.addWidget(_label_invoice, 2, 2) + layout_message.addWidget(self.field_invoice, 2, 3) + else: + layout_message.addWidget(self.label_input, 2, 0, 1, 2) + layout_message.addWidget(self.field_invoice, 2, 3) + + left_head.addLayout(layout_message, 0) + left_head.addWidget(self.field_sign, 0) + left_head.addWidget(self.field_amount, 0) + + info_fields = [ + ('party', { + 'name': self.tr('CUSTOMER'), + 'readonly': True, + 'placeholder': False, + 'size': self.screen_size, + 'color': 'gray' + }), + ('date', { + 'name': self.tr('DATE'), + 'readonly': True, + 'placeholder': False, + 'size': self.screen_size, + 'color': 'gray' + }), + ('salesman', { + 'name': self.tr('SALESMAN'), + 'readonly': True, + 'placeholder': False, + 'size': self.screen_size, + 'color': 'gray' + }), + ('payment_term', { + 'name': self.tr('PAYMENT TERM'), + 'readonly': True, + 'invisible': self.tablet_mode, + 'placeholder': False, + 'size': self.screen_size, + 'color': 'gray' + }), + ('order_number', { + 'name': self.tr('No ORDER'), + 'placeholder': False, + 'readonly': True, + 'size': self.screen_size, + 'color': 'gray' + }), + ('invoice_type', { + 'name': self.tr('INVOICE TYPE'), + 'placeholder': False, + 'type': 'selection', + 'on_change': 'action_invoice_type_selection_changed', + 'values': [ + ('', ''), + ('C', self.tr('COMPUTADOR')), + ('M', self.tr('MANUAL')), + ('P', self.tr('POS')), + ('1', self.tr('VENTA ELECTRONICA')), + ('2', self.tr('VENTA DE EXPORTACION')), + ('3', self.tr('FACTURA POR CONTINGENCIA FACTURADOR')), + ('4', self.tr('FACTURA POR CONTINGENCIA DIAN')), + ('91', self.tr('NOTA CREDITO ELECTRONICA')), + ('92', self.tr('NOTA DEBITO ELECTRONICA')), + ], + 'size': self.screen_size, + 'color': 'gray' + }), + ] + self.field_invoice_type = None + + if self.tablet_mode or self._config['show_position_pos']: + info_fields.append(('position', { + 'name': self.tr('POSITION'), + 'readonly': True, + 'placeholder': False, + 'size': self.screen_size, + 'color': 'gray' + })) + if self._commission_activated and not self.tablet_mode \ + and self._config['show_agent_pos']: + info_fields.append(('agent', { + 'name': self.tr('AGENT'), + 'placeholder': self.tablet_mode, + 'readonly': True, + 'size': self.screen_size, + 'color': 'gray' + })) + + _cols = 2 + if self.enviroment == 'restaurant': + _cols = 1 + + self.field_delivery_charge = None + if self._config['show_delivery_charge']: + info_fields.append( + ('delivery_charge', { + 'name': self.tr('DELIVERY CHARGE'), + 'placeholder': False, + 'type': 'selection', + 'on_change': 'action_delivery_charge_selection_changed', + 'values': [ + ('', ''), + ('customer', self.tr('CUSTOMER')), + ('company', self.tr('COMPANY')), + ], + 'size': self.screen_size, + 'color': 'gray' + })) + + self.field_table_assigned = None + if self.enviroment == 'restaurant' and self._sale_pos_restaurant: + info_fields.append( + ('table_assigned', { + 'name': self.tr('ASSIGNED TABLE'), + 'placeholder': False, + 'size': self.screen_size, + 'color': 'gray' + })) + + self.grid_info = GridForm(self, OrderedDict(info_fields), col=_cols) + + col_sizes_tlines = [field['width'] for field in self.fields_sale_line] + left_table = TableView('model_sale_lines', self.model_sale_lines, + col_sizes_tlines, method_selected_row=self.sale_line_selected) + self.table_sale_lines = left_table + + for i, f in enumerate(self.model_sale_lines._fields, 0): + if f.get('invisible'): + self.table_sale_lines.hideColumn(i) + + _fields_amounts = [ + ('untaxed_amount', { + 'name': self.tr('SUBTOTAL'), + 'readonly': True, + 'type': 'money', + 'size': self.screen_size, + 'color': 'gray' + + }), + ('taxes_amount', { + 'name': self.tr('TAXES'), + 'readonly': True, + 'type': 'money', + 'size': self.screen_size, + 'color': 'gray' + }), + ('discount', { + 'name': self.tr('DISCOUNT'), + 'readonly': True, + 'type': 'money', + 'size': self.screen_size, + 'color': 'gray' + }), + ('total_amount', { + 'name': self.tr('TOTAL'), + 'readonly': True, + 'type': 'money', + 'size': self.screen_size, + 'color': 'blue' + }), + ('paid', { + 'name': self.tr('PAID'), + 'readonly': True, + 'type': 'money', + 'size': self.screen_size, + 'color': 'gray' + }), + ('pending', { + 'name': self.tr('PENDIENTE'), + 'readonly': True, + 'type': 'money', + 'size': self.screen_size, + 'color': 'blue' + }), + ('change', { + 'name': self.tr('CHANGE'), + 'readonly': True, + 'type': 'money', + 'size': self.screen_size, + 'color': 'orange' + }) + ] + + fields_amounts = OrderedDict(_fields_amounts) + self.grid_amounts = GridForm(self, fields_amounts, col=1) + + self.buttonpad = Buttonpad(self) + self.pixmap_pos = Image(self, 'pixmap_pos', FILE_BANNER) + self.table_payment = TableView('table_payment', self.table_payment_lines, + [250, STRETCH]) + + panel_left.addLayout(left_head, 0) + panel_left.addWidget(left_table, 1) + panel_right.addWidget(self.pixmap_pos, 0) + + left_bottom.addLayout(self.grid_info, 1) + if self.enviroment == 'restaurant': + panel_left.addLayout(self.buttonpad.functions, 1) + left_bottom.addLayout(self.grid_amounts, 0) + panel_right.addLayout(menu_dash, 1) + panel_right.addLayout(self.buttonpad.stacked, 0) + else: + panel_right.addLayout(self.grid_amounts, 1) + panel_right.addLayout(self.buttonpad.functions, 1) + panel_right.addLayout(self.buttonpad.stacked, 0) + panel_right.addWidget(self.table_payment) + + panel_left.addLayout(left_bottom, 0) + + panels.addLayout(panel_left, 1) + panels.addLayout(panel_right, 0) + + widget = QWidget() + widget.setLayout(panels) + self.setCentralWidget(widget) + + def create_statusbar(self): + values = OrderedDict([ + ('stb_shop', {'name': self.tr('SHOP'), 'value': self.shop['name']}), + ('stb_device', {'name': self.tr('DEVICE'), 'value': self.device['name']}), + ('stb_database', {'name': self.tr('DATABASE'), 'value': self.database}), + ('stb_user', {'name': self.tr('USER'), 'value': self.user}), + ('stb_printer', {'name': self.tr('PRINTER'), 'value': self.printer_sale_name}) + ]) + self.set_statusbar(values) + + def button_plus_pressed(self): + error = False + if self._input_text == '' and self._amount_text == '0': + return + if self._state in ('paid', 'disabled'): + return + if self._sign in ('*', '-', '/'): + if hasattr(self, '_sale_line') and self._sale_line \ + and self._sale_line.get('type') and self._state == 'add' \ + and self.model_sale_lines.rowCount() > 0: + if self._sign == '*': + self._process_quantity(self._amount_text) + else: + error = not(self._process_price(self._amount_text)) + elif self._state in ['add', 'cancel', 'accept']: + self.clear_right_panel() + self.add_product(code=self._input_text) + elif self._state == 'cash': + is_paid = self._process_pay(self.field_amount.text()) + if not is_paid: + self.clear_input_text() + self.clear_amount_text() + return + else: + logging.warning('Unknown command/text') + self._clear_context(error) + + def action_read_weight(self): + self.reader_thread.start() + + def set_weight_readed(self): + if not self.reader_thread or not self.reader_thread.best_weight: + return + + if self.reader_thread.best_weight: + self.amount_text_changed(self.reader_thread.best_weight) + self._process_quantity(self._amount_text) + self._clear_context(False) + self.reader_thread.fZERO() + + def _clear_context(self, error=False): + self.clear_input_text() + self.clear_amount_text() + self.clear_sign() + + if self._state not in ('warning', 'cash') and not error: + self.message_bar.set('system_ready') + else: + self.set_state('add') + + def _process_quantity(self, text): + eval_value = text.replace(',', '.') + rec = {} + try: + quantity = Decimal(eval_value) + if self._sale_line['product'].get('quantity'): + if not self._check_stock_quantity(self._sale_line['product'], quantity): + return + if self._current_line_id: + rec = self.ModSaleLine.faster_set_quantity({ + 'id': self._current_line_id, + 'quantity': float(quantity) + }) + except: + return self.message_bar.set('quantity_not_valid') + + self.message_bar.set('system_ready') + self.model_sale_lines.update_record(rec) + self.update_total_amount() + + def _process_price(self, text): + discount_valid = True + eval_value = text.replace(',', '') + + value = float(eval_value) + if self._sign == '-': + if self.discount_method == 'percentage' and value > 90: + discount_valid = False + # Do discount + else: + discount_valid = self.set_discount(eval_value) + elif self._sign == '/': + if value <= 0: + return + sale_line, = self.ModSaleLine.find([ + ('id', '=', self._current_line_id) + ]) + + price_w_tax = sale_line['product.template.sale_price_w_tax'] + if price_w_tax <= Decimal(value): + # Change unit price + discount_valid = self.set_unit_price(value) + else: + eval_value = (1 - (value / price_w_tax)) * 100 + if self.discount_method == 'fixed': + eval_value = price_w_tax - value + discount_valid = self.set_discount(eval_value) + if not discount_valid: + self.message_bar.set('discount_not_valid') + return False + + self.message_bar.set('system_ready') + self.update_total_amount() + return True + + def _process_pay(self, text): + if not self.validate_done_sale(): + return + val = Decimal(text.replace(',', '')) + + if self._commission_activated: + if self._journals[self.field_journal_id]['kind'] == 'payment': + agent_id = None + if self.field_agent_id: + agent_id = self.field_agent_id + else: + agent_id = self.field_agent_ask.get_id() + + if not agent_id: + self.message_bar.set('missing_agent') + return False + + cash_received = Decimal(val) + self.set_amounts() + residual_amount = self._sale['residual_amount'] + if residual_amount < 0: + # The sale is paid + self._done_sale() + return True + + change = cash_received - residual_amount + + if residual_amount >= cash_received: + amount_to_add = cash_received + else: + amount_to_add = residual_amount + + all_money = cash_received + self._sale['paid_amount'] + # self.field_change.setText(change) + if change < ZERO: + self.field_pending.setText(abs(change)) + else: + self.field_pending.setText(str(ZERO)) + + self.set_amount_received(all_money) + res = self.add_payment(amount_to_add, all_money, change) + if res['residual_amount'] < 0: + # The sale is paid + self._done_sale() + return True + + self.field_journal_id = self._default_journal_id + + if res['msg'] == 'missing_money': + self.message_bar.set('enter_payment', self.default_journal['name']) + return False + + if change < ZERO: + self.message_bar.set('enter_payment', self.default_journal['name']) + + self.field_change.setText(change) + # self.set_amount_received(all_money) + + self._sale.update({ + 'residual_amount': res['residual_amount'] + }) + residual_amount = self._sale['residual_amount'] + + if self._sale['residual_amount'] <= 0: + # The sale is paid + self._done_sale() + return True + + def validate_done_sale(self): + if self.model_sale_lines.rowCount() == 0: + self.dialog('sale_without_products') + self.set_state('add') + self.message_bar.set('system_ready') + return + return True + + def _get_total_amount(self): + return self.model_sale_lines.get_sum('amount_w_tax') + + def set_discount_amount(self): + res = 0 + if self._sale['id']: + res = self.ModSale.get_discount_total({ + 'sale_id': self._sale['id'] + }) + self.field_discount.setText(res) + + def amount_text_changed(self, text=None): + if text: + self._amount_text += text + self.field_amount.setText(self._amount_text) + + def input_text_changed(self, text=None): + if text: + self._input_text += text + elif text == '': + self._input_text = '' + self.label_input.setText(self._input_text) + + def __do_invoice_thread(self): + self.ModSale.faster_post_invoice({ + 'sale_id': self.sale_to_post['id'] + }) + if self.sale_to_post['is_credit']: + return + + self.ModSale.reconcile_invoice({ + 'sale_id': self.sale_to_post['id'] + }) + + def _done_sale(self, is_credit=False): + self._sale['is_credit'] = is_credit + self.sale_to_post = self._sale + self.do_invoice.start() + if self._Tables and self._sale.get('table_assigned'): + self._Tables.write([self._sale['table_assigned']], + {'state': 'available'} + ) + + try: + if self.print_receipt == 'automatic': + _copies = self.device['shop.invoice_copies'] + if not is_credit: + self.print_invoice(copies=_copies) + if self.print_order and self.print_auto_order: + self.action_print_order() + except: + logging.error(sys.exc_info()[0]) + + if not is_credit and self._commission_activated: + agent_id = self.field_agent_ask.get_id() + if self.auto_print_commission and agent_id: + self.print_equivalent_invoice(self._sale['id']) + + if self.type_pos_user not in ('cashier', 'order') \ + and self._config.get('new_sale_automatic'): + self.createNewSale() + else: + self.state_disabled() + return True + + def print_invoice(self, sale_id=None, copies=1): + if not sale_id: + sale_id = self._sale['id'] + data = self._PosSale.get_data(sale_id, self._context) + for i in range(copies): + self.receipt_sale.print_sale(data) + + def button_accept_pressed(self): + if not self._sale['id'] or not self.model_sale_lines.rowCount() > 0: + return + self.set_state('accept') + + def button_cash_pressed(self): + if not self.check_salesman(): + return + if not self.validate_payment_term(): + return + sale_id = self._sale['id'] + res, msg = self.ModSale.faster_process({'sale_id': sale_id}) + # Remove deprecation res['res'] + if msg: + # msg = Mesagge error + self.message_bar.set(msg) + return + + self.set_amounts(res) + + self.field_invoice.setText(res['invoice_number']) + self.field_amount.zero() + if self.type_pos_user == 'salesman': + self.print_invoice(sale_id) + res = self._print_order(sale_id, 'delivery') + self.createNewSale() + return + + self.message_bar.set('enter_payment', self.default_journal['name']) + self.set_state('cash') + self.buttonpad.setFocus() + + def action_reservations(self): + logging.info('Buscando reservas.....') + + def action_tables(self): + self.dialog_manage_tables.exec_() + + def action_table_assigned(self, id, name, prev_state, new_state): + table_assigned = id + + if self._sale.get('table_assigned') != id and prev_state == 'occupied': + return False + + if self._sale.get('table_assigned') == id and new_state == 'available': + name = '' + table_assigned = None + + self._sale['table_assigned'] = table_assigned + self._sale['table_assigned.state'] = new_state + + self._Tables.write([id], {'state': new_state}) + + self._PosSale.write([self._sale['id']], {'table_assigned': table_assigned}) + self.field_table_assigned.setText(name) + return True + + def action_tip(self): + if self._config['tip_product.code'] and self._config['tip_rate']: + total_amount = int(self._get_total_amount()) + self.add_product(code=self._config['tip_product.code']) + self.button_plus_pressed() + eval_value = int((self._config['tip_rate'] / 100) * total_amount) + self.ModSaleLine.write( + [self._current_line_id], {'unit_price': Decimal(eval_value)} + ) + self._PosSale.write([self._sale['id']], {'tip': Decimal(eval_value)}) + self.update_total_amount() + + def action_salesman(self): + self.dialog_salesman.exec_() + + def action_tax(self): + self.dialog_tax.exec_() + + def action_payment(self): + if self._state != 'cash': + self.dialog('add_payment_sale_draft') + return + self.dialog_payment.exec_() + + def _check_credit_capacity(self, party): + if party['credit_limit_amount']: + if (party['credit_limit_amount'] * Decimal(RATE_CREDIT_LIMIT)) < (party['credit_amount'] + self._get_total_amount()): + self.dialog('credit_limit_capacity') + return True + + def validate_payment_term(self): + is_credit = self._payment_terms[str(self.field_payment_term_id)]['is_credit'] + party = self.Party.find([('id', '=', self.party_id)])[0] + + if is_credit: + if self.party_id == self.default_party['id']: + self.dialog('customer_not_credit') + return False + if self._credit_limit_activated: + if is_credit and not party['credit_limit_amount']: + self.dialog('customer_not_credit') + return False + + self._credit_amount = self.ModSale.get_credit_amount_party({'party_id': self.party_id}) + self._credit_limit_amount = party['credit_limit_amount'] + + if is_credit and self._credit_limit_amount and \ + self._credit_limit_amount < (self._credit_amount + self._get_total_amount()): + self.dialog('credit_limit_exceed') + return False + return True + + def action_payment_term_selection_changed(self): + is_credit = self._payment_terms[str(self.field_payment_term_id)]['is_credit'] + self._PosSale.write([self._sale['id']], {'payment_term': self.field_payment_term_id}) + if is_credit and self.type_pos_user != 'salesman': + if self.validate_credit_limit(): + self._done_sale(is_credit=True) + + def validate_credit_limit(self): + if self._credit_limit_amount and self._credit_limit_amount < (self._credit_amount + self._get_total_amount()): + self.dialog('credit_limit_exceed') + return False + else: + return True + + def action_journal_selection_changed(self): + self.message_bar.set('enter_payment', self.field_journal_name) + + def action_salesman_selection_changed(self): + self._PosSale.write([self._sale['id']], {'salesman': self.field_salesman_id}) + + def action_delivery_charge_selection_changed(self, index): + val = self.field_delivery_charge.get_id() + if val: + self._PosSale.write([self._sale['id']], {'delivery_charge': val}) + + def action_invoice_type_selection_changed(self, index): + val = self.field_invoice_type.get_id() + if val: + self._PosSale.write([self._sale['id']], {'invoice_type': val}) + + def action_tax_selection_changed(self): + res = self._PosSale.add_tax(self._sale['id'], self.field_tax_id, self._context) + self._sale.update(res) + self.set_amounts() + + def action_print_sale(self): + number = self.field_invoice.text() + if not number: + number = self.field_order_number.text() + if number: + self.field_invoice_number_ask.setText(number) + res = self.dialog_print_invoice.exec_() + if res == DIALOG_REPLY_NO: + return + number = self.field_invoice_number_ask.text() + printer_id = self.field_printer_ask.get_id() + type_doc = self.field_type_ask.get_id() + sale = {} + if number: + if type_doc == 'order': + sales = self.ModSale.find([('number', '=', number)]) + if sales: + sale = sales[0] + else: + sale = self._PosSale.get_sale_from_invoice([1], number, self._context) + + if not sale: + return self.message_bar.set('sale_number_not_found') + sale_id = sale['id'] + else: + sale_id = self._sale['id'] + + if printer_id == '1': + self.print_invoice(sale_id) + else: + self.print_odt_invoice(sale) + + def print_odt_invoice(self, sale): + if not sale.get('invoices'): + return + invoice_id = sale['invoices'][0] + model = u'account.invoice' + data = { + 'model': model, + 'action_id': self._action_report['id'], + 'id': invoice_id, + 'ids': [invoice_id], + } + ctx = {'date_format': u'%d/%m/%Y'} + ctx.update(self._context) + Action.exec_report(self.conn, u'account.invoice', + data, direct_print=True, context=ctx) + + def print_equivalent_invoice(self, sale_id): + sale, = self.ModSale.find([('id', '=', sale_id)]) + + # if not sale['invoices']: + # return + # + # invoice_id = sale['invoices'][0] + # commissions = self._Commission.find([ + # ('origin', '=', 'account.invoice,' + str(invoice_id)) + # ]) + # + # # if not commissions: + # # return + # # commission = commissions[0] + # # if not commission['invoice_line.invoice']: + # # return + # # + # # model = u'account.invoice' + # # data = { + # # 'model': model, + # # 'action_id': self._action_report_equivalent['id'], + # # 'id': commission['invoice_line.invoice'], + # # 'ids': [commission['invoice_line.invoice']], + # # } + # # ctx = {'date_format': u'%d/%m/%Y'} + # # ctx.update(self._context) + # # Action.exec_report(self.conn, u'account.invoice', + # # data, direct_print=True, context=ctx) + + def action_comment(self): + self.dialog_comment.exec_() + comment = self.field_comment_ask.text() + if comment: + self._PosSale.write([self._sale['id']], {'comment': comment}) + + def action_position(self): + self.dialog_position.exec_() + position = self.field_position_ask.text() + if hasattr(self, 'field_position') and position: + self.field_position.setText(position) + self._PosSale.write([self._sale['id']], {'position': position}) + + def action_agent(self): + self.dialog_agent.exec_() + res = self.field_commission_ask.text() + if not res: + return + commission = float(res) + sale, = self.ModSale.find([ + ('id', '=', self._sale['id']), + ]) + self.field_agent_id = self.field_agent_ask.get_id() + + if self.field_agent_id and commission: + agent, = self._Agent.find([ + ('id', '=', self.field_agent_id), + ]) + if commission <= agent['plan.percentage']: + self._PosSale.write([self._sale['id']], { + 'agent': self.field_agent_id, + 'commission': int(commission), + }) + else: + self.message_bar.set('invalid_commission') + return + self.message_bar.set('system_ready') + comm_string = str('[' + str(commission) + ']' + ' ') + (str(self.field_agent_ask.text())) + self.field_agent.setText(comm_string) + self._set_commission_amount(sale['untaxed_amount'], commission) + + def _set_commission_amount(self, untaxed_amount, commission): + untaxed_amount = int(untaxed_amount) + commission = int(commission) + total = ((untaxed_amount * commission) / 100) + self.field_commission_amount.setText(str(total)) + + def action_party(self): + self.dialog_search_parties.clear_rows() + self.dialog_search_parties.execute() + + def action_global_discount(self, sale_id=None): + self.dialog_global_discount.exec_() + discount = self.field_global_discount_ask.text() + if discount and discount.isdigit(): + if self.model_sale_lines.rowCount() > 0: + lines = [line['id'] for line in self.model_sale_lines._data] + self.set_discount(int(discount), lines) + + def _print_order(self, sale_id, kind, reversion=False): + result = False + try: + orders = self._PosSale.get_order2print(sale_id, reversion, False, self._context) + result = self.receipt_order.print_orders(orders, reversion, kind) + except: + logging.error('Printing order fail!') + return result + + def action_print_order(self, sale_id=None, reversion=False): + res = self.dialog_order.exec_() + if res == DIALOG_REPLY_NO: + return + # order_number = self.field_order_ask.text() + kind = 'delivery' + if self.enviroment == 'restaurant': + kind = 'command' + if not self.print_order: + return + if not sale_id and self._sale['id']: + sale_id = self._sale['id'] + + result = self._print_order(sale_id, kind) + # try: + # orders = self._PosSale.get_order2print(sale_id, reversion, + # False, self._context) + # result = self.receipt_order.print_orders(orders, reversion, kind) + # print(result, orders) + # except: + # print('Error: printing order fail!') + if result: + # Show dialog if send sale order was successful + self.dialog('order_successfully') + else: + self.dialog('order_failed') + + def action_payment_term(self): + if self._state == 'cash' or self.type_pos_user == 'salesman': + self.dialog_payment_term.exec_() + + def action_new_sale(self): + if not self._sale['id']: + return + if self._ask_new_sale(): + self.createNewSale() + if self.enviroment == 'restaurant': + self.wizard_new_sale() + + def wizard_new_sale(self): + # self.action_position() + # self.action_salesman() + pass + + def numpad_price_clicked(self): + code = self.label_input.text() + product = self._search_product(code) + if not product: + return + + def _ask_new_sale(self): + dialog = self.dialog('new_sale', response=True) + res = dialog.exec_() + if res == DIALOG_REPLY_NO: + return False + return True + + def action_cancel(self): + if self.type_pos_user == 'cashier': + self.dialog_cancel_invoice.exec_() + password = self.field_password_for_cancel_ask.text() + if password != self._password_admin: + return self.dialog('not_permission_for_cancel') + + if not self._sale['id']: + return + if self._state == 'cash' and not self.user_can_delete: + return self.dialog('not_permission_delete_sale') + if self.type_pos_user in ('order', 'salesman') and \ + self._sale.get('invoice_number'): + return self.dialog('not_permission_delete_sale') + dialog = self.dialog('cancel_sale', response=True) + response = dialog.exec_() + if response == DIALOG_REPLY_NO: + return + self._PosSale.cancel_sale(self._sale['id'], self._context) + self.field_password_for_cancel_ask.setText('') + self.set_state('cancel') + self.clear_right_panel() + self.createNewSale() + + def action_search_product(self): + if self._state == 'cash': + return + self.dialog_search_products.show() + + def action_search_sale(self): + delta = str(datetime.now() - timedelta(4)) + if self.type_pos_user == 'cashier': + dom = ['OR', [ + ('create_date', '>=', delta), + ('state', 'in', ['quotation', 'confirmed']), + ], [ + ('state', '=', 'processing'), + ('invoice.state', '=', 'draft'), + ('invoice.type', '=', 'out'), + ]] + elif self.type_pos_user in ('order', 'salesman'): + dom = [ + ('state', '=', 'draft'), + ('create_date', '>=', delta), + ] + else: + dom = [ + ('state', 'in', ['draft', 'quotation', 'confirmed']), + ('create_date', '>=', delta), + ] + + sales = self.ModSale.find(dom, order=[('id', 'DESC')]) + self.dialog_search_sales.set_from_values(sales) + + if self.enviroment == 'retail': + dom_draft = [ + ('create_date', '>=', delta), + ('state', '=', 'draft'), + ('invoice_number', '!=', None), + ] + sales_draft = self.ModSale.find(dom_draft) + self.dialog_search_sales.set_counter_control(sales_draft) + response = self.dialog_search_sales.execute() + if response == DIALOG_REPLY_NO: + return + + def on_selected_sale(self): + sale_id = self.dialog_search_sales.get_id() + if not sale_id: + return + self.load_sale(sale_id) + self.setFocus() + self.label_input.setFocus() + self.grabKeyboard() + + def on_selected_party(self): + party_id = self.dialog_search_parties.get_id() + party, = self.Party.find([ + ('id', '=', party_id) + ]) + if not party_id: + return + + values = { + 'party': party_id, + 'invoice_address': party['addresses'][0]['id'], + 'shipment_address': party['addresses'][0]['id'], + } + + if party.get('customer_payment_term'): + values['payment_term'] = party.get('customer_payment_term') + self.field_payment_term.setText(str(party['customer_payment_term.name'])) + self.field_payment_term_id = party.get('customer_payment_term') + else: + values['payment_term'] = self.default_payment_term['id'] + self.field_payment_term_id = self.default_payment_term['id'] + self.field_payment_term.setText(self.default_payment_term['name']) + + self._PosSale.write([self._sale['id']], values) + + self.party_id = party_id + self.field_party.setText(party['name']) + + res = self.ModSale.get_salesman_in_party({ + 'party_id': party_id, + }) + if res: + self.field_salesman_id = res['id'] + self._PosSale.write([self._sale['id']], {'salesman': self.field_salesman_id}) + self.field_salesman.setText(res['name']) + + # if self._credit_limit_activated: + # self._check_credit_capacity(party) + + self.message_bar.set('system_ready') + self.setFocus() + self.label_input.setFocus() + self.grabKeyboard() + + def load_sale(self, sale_id): + # loads only draft sales + self.is_clear_right_panel = True + self.clear_data() + self.clear_left_panel() + self.clear_right_panel() + sale, = self.ModSale.find([('id', '=', sale_id)]) + self._sale.update(sale) + self.table_payment_lines.reset() + self.field_order_number.setText(sale['number'] or '') + self._set_sale_date() + if hasattr(self, 'field_position'): + self.field_position.setText(sale['position'] or '') + if sale.get('payment_term.name'): + self.field_payment_term_id = sale['payment_term'] + self.field_payment_term.setText(sale['payment_term.name'] or '') + if sale.get('salesman'): + self.field_salesman.setText(sale['salesman.rec_name'] or '') + self.field_salesman_id = sale['salesman'] + + res = self.ModSale.get_invoice_type({'sale_id': sale_id}) + if res: + self.field_invoice_type.set_from_id(res['invoice_type']) + + if sale.get('invoice_number'): + self.field_invoice.setText(sale['invoice_number'] or '') + else: + self.field_invoice.setText('') + if self.field_delivery_charge: + self.field_delivery_charge.set_enabled(True) + if self._sale.get('delivery_charge'): + self.field_delivery_charge.set_from_id(self._sale['delivery_charge']) + if sale.get('table_assigned'): + self.field_table_assigned.setText(sale['table_assigned.name'] or '') + + self.field_change.zero() + if self._commission_activated: + if hasattr(self, 'field_agent') and sale.get('agent.party.name') \ + and sale.get('commission'): + commission = sale.get('commission') + self.field_agent.setText('[' + str(int(commission)) + ']' + ' ' + sale.get('agent.party.name')) + self.field_agent_id = sale.get('agent') + self.field_agent_ask.setText(sale.get('agent.party.name')) + self.field_commission_ask.setText(str(commission)) + self._set_commission_amount(sale['untaxed_amount'], commission) + + if sale.get('lines'): + lines = self.ModSaleLine.find([ + ('id', 'in', sale.get('lines')) + ]) + sale['lines'] = lines + for line in lines: + self.add_sale_line(line) + + if sale.get('payments'): + for payment in sale['payments']: + self.table_payment_lines.record_add(payment) + + self.set_state('add') + self.party_id = sale['party'] + self.field_party.setText(sale['party.name']) + self.set_amounts(sale) + self.set_amount_received() + self.field_amount.zero() + if self.type_pos_user in ('cashier', 'order', 'salesman'): + self.table_sale_lines.setEnabled(True) + + def set_change_amount(self): + amount_paid = self.table_payment_lines.get_sum('amount') + res = amount_paid - self._get_total_amount() + self.field_change.setText(res) + + def set_amount_received(self, cash_received=ZERO): + residual_amount = self._sale['residual_amount'] + if cash_received: + amount = cash_received + else: + amount = self._sale['paid_amount'] + self.field_pending.setText(residual_amount) + self.field_paid.setText(amount) + + def update_total_amount(self): + self.set_amounts() + # res = self.model_sale_lines.get_sum('amount_w_tax') + # self.field_total_amount.setText(res) + + def set_amounts(self, res=None): + if not res: + res = self._PosSale.get_amounts([self._sale['id']], self._context) + + self._sale.update(res) + self.field_untaxed_amount.setText(res['untaxed_amount']) + self.field_taxes_amount.setText(res['tax_amount']) + self.field_total_amount.setText(res['total_amount']) + self.set_discount_amount() + + def _get_products_by_category(self, cat_id): + records = self.Product.find([ + ('code', '!=', None), + ('template.salable', '=', True), + ('template.categories', '=', cat_id), + ]) + return [ + [r['id'], r['code'], r['template.name'], r['template.sale_price_w_tax']] + for r in records] + + def get_product_by_categories(self): + domain = [ + ('parent', '=', None), + ('accounting', '=', False), + ('id', 'in', self.product_categories) + ] + self.allow_categories = self.Category.find(domain) + + for cat in self.allow_categories: + cat['icon'] = get_icon(cat['name_icon']) + cat['items'] = self._get_products_by_category(cat['id']) + return self.allow_categories + + def _get_childs(self, parent_cat): + res = {} + for cat_id in parent_cat['childs']: + sub_categories = self.Category.find([ + ('parent', '=', parent_cat['id']) + ]) + + for sub_cat in sub_categories: + res.update(self._get_childs(sub_cat)) + res = { + 'id': parent_cat['id'], + 'name': parent_cat['name'], + 'childs': parent_cat['childs'], + 'records': [], + 'obj': parent_cat, + } + return res + + def get_category_items(self, records): + records_by_category = {} + + def _get_tree_categories(cat): + sub_categories = {} + if not cat['childs']: + sub_categories[cat['name']] = records_by_category.get(cat['id']) or [] + else: + for child in cat['childs']: + sub_categories.update(_get_tree_categories( + self.target_categories[child]['obj'])) + return sub_categories + + for record in records: + cat_id = record.get('template.account_category') + if cat_id not in records_by_category.keys(): + records_by_category[cat_id] = [] + + records_by_category[cat_id].append(record) + + res = {} + for ac in self.allow_categories: + res[ac['name']] = _get_tree_categories(ac) + return res + + def on_selected_product(self): + if self.dialog_search_products.current_row: + self._current_line_id = None + self.clear_right_panel() + self.add_product(product=self.dialog_search_products.current_row) + + def on_selected_icon_product(self): + if self.dialog_search_products.current_row: + code = self.dialog_search_products.current_row['code'] + products = self.Product.find([ + ('code', '=', code) + ]) + if not products: + return + + product = products[0] + image = Image(name='product_icon') + if not product['image']: + return + + b64image = product['image'].encode() + image.set_image(b64image, kind='bytes') + image.activate() + + def on_selected_stock_product(self): + if self.dialog_search_products.current_row: + code = self.dialog_search_products.current_row['code'] + res = self.Product.get_stock_by_locations({'code': code}) + self.dialog_product_stock.update_values(res) + self.dialog_product_stock.show() + + def on_selected_item(self, product_id): + if product_id: + self.clear_right_panel() + self.add_product(id=product_id) + + def create_dialog_manage_tables(self): + if not self._Tables: + return + tables = self._Tables.find([ + ('shop', '=', self.shop['id']) + ]) + self.tables = ManageTables(self, tables, self.action_table_assigned) + self.dialog_manage_tables = QuickDialog(self, 'action', widgets=[self.tables]) + + def create_dialog_search_sales(self): + headers = [ + ('id', self.tr('ID')), + ('number', self.tr('NUMBER')), + ('invoice_number', self.tr('INVOICE')), + ('sale_date', self.tr('DATE')), + ('salesman.party.name', self.tr('SALESMAN')), + ('position', self.tr('POSITION')), + ('total_amount_cache', self.tr('TOTAL AMOUNT')), + ] + widths = [20, 100, 100, 90, 150, 100, 100] + if self.enviroment == 'retail': + headers.insert(2, ('party.name', self.tr('PARTY'))) + widths.insert(2, 150) + title = self.tr('SEARCH SALES...') + methods = { + 'on_selected_method': 'on_selected_sale', + 'on_return_method': 'on_selected_sale' + } + self.dialog_search_sales = SearchWindow(self, headers, None, methods, + filter_column=[1, 2, 3, 4], cols_width=widths, title=title, fill=True) + + self.dialog_search_sales.activate_counter() + + def create_dialog_search_products(self): + _cols_width = [10, 80] + headers = [ + ('id', self.tr('ID')), + ('code', self.tr('CODE')), + ] + if self._config.get('show_stock_pos') in ['icon', 'value']: + if self._config['show_stock_pos'] == 'icon': + headers.append(('icon_stock', self.tr('STOCK'))) + else: + headers.append(('quantity', self.tr('STOCK'))) + _cols_width.append(80) + + headers.append( + ('template.name', self.tr('NAME')), + ) + _cols_width.append(350) + + if self._config.get('show_description_pos'): + headers.append(('description', self.tr('DESCRIPTION'))) + _cols_width.append(300) + + if self._config.get('show_brand'): + headers.append(('brand.name', self.tr('BRAND'))) + _cols_width.append(100) + + if not self._config.get('encoded_sale_price'): + sale_price = ('template.sale_price_w_tax', self.tr('PRICE')) + else: + sale_price = ('encoded_sale_price', self.tr('PRICE')) + headers.append(sale_price) + _cols_width.append(90) + + if self._config.get('show_location_pos'): + headers.append(('location.name', self.tr('LOCATION'))) + _cols_width.append(100) + if self._config['show_product_image']: + headers.append(('icon_camera', self.tr('IMAGE'))) + _cols_width.append(30) + + methods = { + 'on_selected_method': 'on_selected_product', + 'on_return_method': 'on_search_product', + 'icon_camera': self.on_selected_icon_product, + 'icon_stock': self.on_selected_stock_product, + 'quantity': self.on_selected_stock_product + } + + self.dialog_search_products = SearchWindow(self, headers, None, + methods, cols_width=_cols_width, fill=True) + + def create_dialog_search_party(self): + headers = [ + ('id', self.tr('ID')), + ('id_number', self.tr('ID NUMBER')), + ('name', self.tr('NAME')), + ('street', self.tr('ADDRESS')), + ('phone', self.tr('PHONE')), + ] + title = self.tr('SEARCH CUSTOMER') + methods = { + 'on_selected_method': 'on_selected_party', + 'on_return_method': 'on_search_party', + } + self.dialog_search_parties = SearchWindow(self, headers, None, + methods, filter_column=[], cols_width=[60, 120, 270, 190, 90], + title=title, fill=True) + + def create_dialog_payment(self): + data = { + 'name': 'journal', + 'values': sorted([(str(j), self._journals[j]['name']) + for j in self._journals]), + 'heads': [self.tr('ID'), self.tr('PAYMENT MODE:')], + } + string = self.tr('SELECT PAYMENT MODE:') + self.dialog_payment = QuickDialog(self, 'selection', string, data) + + def create_dialog_stock(self): + data = { + 'name': 'stock', + 'values': [], + 'heads': [self.tr('WAREHOUSE'), self.tr('QUANTITY')], + } + label = self.tr('STOCK BY PRODUCT:') + self.dialog_product_stock = QuickDialog(self.dialog_search_products, + 'selection', label, data, readonly=True) + + def create_dialog_salesman(self): + print(self.employees) + data = { + 'name': 'salesman', + 'values': [(str(e['id']), e['party']['name']) + for e in self.employees], + 'heads': [self.tr('Id'), self.tr('Salesman')], + } + string = self.tr('CHOOSE SALESMAN') + self.dialog_salesman = QuickDialog(self, 'selection', string, data) + + def create_dialog_taxes(self): + if self.shop_taxes: + taxes = [(str(e['id']), e['name']) for e in self.shop_taxes] + else: + taxes = [] + data = { + 'name': 'tax', + 'values': taxes, + 'heads': [self.tr('Id'), self.tr('Salesman')], + } + string = self.tr('CHOOSE TAX') + self.dialog_tax = QuickDialog(self, 'selection', string, data) + + def create_dialog_payment_term(self): + data = { + 'name': 'payment_term', + 'values': [(p_id, self._payment_terms[p_id]['name']) + for p_id in self._payment_terms], + 'heads': [self.tr('ID'), self.tr('PAYMENT TERM')], + } + string = self.tr('SELECT PAYMENT TERM') + self.dialog_payment_term = QuickDialog(self, 'selection', string, data) + + def on_search_product(self): + target = self.dialog_search_products.filter_field.text() + if not target: + return + target_words = target.split(' ') + domain = [] + + for tw in target_words: + if len(tw) <= 1: + continue + clause = ['OR', + ('template.name', 'ilike', '%{:}%'.format(tw)), + ('description', 'ilike', '%{:}%'.format(tw)), + ] + domain.append(clause) + + if not domain: + return + if self.cache_local: + products = self.store.find_product_elastic(domain, limit=100) + else: + products = self.Product.find(domain, limit=100, ctx=self.stock_context) + self.dialog_search_products.set_from_values(products) + + def on_search_party(self): + target = self.dialog_search_parties.filter_field.text() + if not target: + return + target_words = target.split(' ') + domain = [('id_number', '!=', None)] + for tw in target_words: + if len(tw) <= 2: + continue + or_clause = ['OR', + ('name', 'ilike', '%' + tw + '%'), + ('contact_mechanisms.value', 'like', tw + '%'), + ('id_number', 'like', tw + '%'), + ] + domain.append(or_clause) + + parties = self.Party.find(domain) + self.dialog_search_parties.set_from_values(parties) + + def create_dialog_print_invoice(self): + view = [ + ('invoice_number_ask', {'name': self.tr('INVOICE NUMBER')}), + ('printer_ask', { + 'name': self.tr('PRINTER'), + 'type': 'selection', + 'values': [ + (1, 'POS'), + (2, 'LASER') + ], + }), + ('type_ask', { + 'name': self.tr('TYPE'), + 'type': 'selection', + 'values': [ + ('invoice', self.tr('INVOICE')), + ('order', self.tr('ORDER')) + ], + }), + ] + self.dialog_print_invoice = QuickDialog(self, 'action', data=view) + + def create_dialog_cancel_invoice(self): + view = [ + ('password_for_cancel_ask', { + 'name': self.tr('INSERT PASSWORD FOR CANCEL'), + 'password': True + }), + ] + self.dialog_cancel_invoice = QuickDialog(self, 'action', data=view) + + def create_dialog_global_discount(self): + field = 'global_discount_ask' + data = {'name': self.tr('GLOBAL DISCOUNT')} + self.dialog_global_discount = QuickDialog(self, 'action', data=[(field, data)]) + + def create_dialog_force_assign(self): + field = 'password_force_assign_ask' + data = {'name': self.tr('PASSWORD FORCE ASSIGN')} + self.dialog_force_assign = QuickDialog(self, 'action', data=[(field, data)]) + self.field_password_force_assign_ask.setEchoMode(QLineEdit.Password) + + def create_dialog_voucher(self): + field = 'voucher_ask' + data = {'name': self.tr('VOUCHER NUMBER')} + self.dialog_voucher = QuickDialog(self, 'action', data=[(field, data)]) + + def create_dialog_order(self): + # field = 'Send Order' + # data = {'name': self.tr('COPY NUMBER')} + string = self.tr('DO YOU WANT TO CONFIRM THE SEND ORDER?') + self.dialog_order = QuickDialog(self, 'action', string, data=[]) + + def create_dialog_position(self): + field = 'position_ask' + data = {'name': self.tr('POSITION')} + self.dialog_position = QuickDialog(self, 'action', data=[(field, data)]) + + def create_dialog_agent(self): + view = [ + ('agent_ask', { + 'name': self.tr('AGENT'), + 'type': 'relation', + 'model': self._Agent, + 'domain': [('active', '=', True)], + 'fields': [ + ('id', self.tr('ID')), + ('party.name', self.tr('NAME')), + ('party.id_number', self.tr('ID NUMBER')), + ]}), + ('commission_ask', {'name': self.tr('COMMISSION')}), + ('commission_amount', {'name': self.tr('AMOUNT'), + 'readonly': True}), + ] + self.dialog_agent = QuickDialog(self, 'action', data=view, size=(600, 400)) + + def create_wizard_new_sale(self): + pass + + def create_dialog_comment(self): + field = 'comment_ask' + data = {'name': self.tr('COMMENTS'), 'widget': 'text'} + self.dialog_comment = QuickDialog(self, 'action', data=[(field, data)]) + + def clear_data(self): + self._sale = { + 'total_amount': 0 + } + self.party_name = None + self._sale_line = {'id': None} + self._total_amount = {} + self._sale_lines_taxes = {} + self.field_journal_id = self.default_journal['id'] + + def clear_left_panel(self): + self.message_bar.set('system_ready') + self.field_party.setText('') + self.field_salesman.setText('') + self.field_salesman_id = None + self.field_invoice_type.set_from_id('') + self.field_party_id = None + self.field_agent_id = None + self.field_payment_term_id = self.default_payment_term['id'] + self.field_payment_term.setText(self.default_payment_term['name']) + self.field_date.setText('') + self.field_global_discount_ask.setText('') + self.field_amount.zero() + self.field_order_number.setText('') + self.current_comment = '' + if self.field_delivery_charge: + self.field_delivery_charge.set_from_id('') + if self.field_table_assigned: + self.field_table_assigned.setText('') + if hasattr(self, 'field_comment_ask'): + # self.field_comment_ask.document().clear() + pass + if hasattr(self, 'field_points'): + self.field_points.setText('') + if self._commission_activated and hasattr(self, 'field_agent'): + self.field_agent.setText('') + self.field_agent_ask.setText('') + self.field_commission_ask.setText('') + self.field_commission_amount.setText('') + if hasattr(self, 'field_position'): + self.field_position.setText('') + self.field_position_ask.setText('') + self.model_sale_lines.reset() + self.clear_input_text() + self.clear_amount_text() + + def clear_right_panel(self): + if self.is_clear_right_panel: + return + self.field_invoice.setText('') + self.field_untaxed_amount.zero() + self.field_taxes_amount.zero() + self.field_total_amount.zero() + self.field_change.zero() + self.field_paid.zero() + self.field_discount.zero() + self.table_payment_lines.reset() + self.is_clear_right_panel = True + + def state_enabled(self): + pass + + def state_disabled(self): + self.payment_ctx = {} + self.clear_left_panel() + self.table_sale_lines.setDisabled(True) + self.set_state('disabled') + if self.field_delivery_charge: + self.field_delivery_charge.set_enabled(False) + + def createNewSale(self): + self.check_empty_sale() + if self.type_pos_user == 'cashier': + self.state_disabled() + return + self.set_state('add') + self.input_text_changed('') + self.amount_text_changed('0') + self.clear_sign() + self.global_timer = 0 + self.payment_ctx = {} + self.is_clear_right_panel = False + self.table_sale_lines.setEnabled(True) + self.clear_data() + self.clear_left_panel() + self._sale = self._PosSale.new_sale([], { + 'shop': self.shop['id'], + 'invoice_type': 'P', + 'company': self.company['id'], + 'party': self.default_party['id'], + 'sale_device': self.device['id'], + 'payment_term': self.default_payment_term['id'] + }, self._context) + + self.field_invoice_type.set_from_id(self._sale['invoice_type']) + # FIXME ADD MORE + self._sale.update({ + 'total_amount': 0 + }) + self.party_id = self.default_party['id'] + if self._sale.get('id'): + self._set_sale_date() + self.field_order_number.setText(self._sale['number']) + if self.field_delivery_charge: + self.field_delivery_charge.set_enabled(True) + + def _set_sale_date(self): + if self._sale.get('sale_date'): + local_date = self._sale['sale_date'] + if isinstance(local_date, date): + local_date = local_date.isoformat() + self.field_date.setText(local_date) + + def _search_product(self, code): + domain = [ + ('template.salable', '=', True), + ('template.account_category', '!=', None), + ] + domain.append(['OR', + ('barcode', '=', code), + ('code', '=', code), + ]) + products = self.Product.find(domain) + if not products or len(products) > 1: + self.message_bar.set('product_not_found') + return False + else: + product = products[0] + return product + + def check_salesman(self): + if self.salesman_required and not self.field_salesman_id: + dialog = self.dialog('missing_salesman') + dialog.exec_() + return False + return True + + def add_product(self, id=None, code=None, product=None): + if self._state == 'disabled': + self.message_bar.set('must_load_or_create_sale') + return + + product_id = None + if id: + product_id = id + elif code: + # REMOVE ME + product = self._search_product(code) + if product: + product_id = product['id'] + elif product: + product_id = product['id'] + + if not product_id: + self._state = 'warning' + return + + data = { + 'sale_id': self._sale['id'], + 'product_id': product_id, + 'qty': 1 + } + + res = self.ModSale.faster_add_product(data) + self._sale_line = res + self._current_line_id = res['id'] + self.add_sale_line(res) + self._sale_line['product'] = product + + self.update_total_amount() + self.set_state('add') + + def _check_stock_quantity(self, product, request_qty): + if self._password_admin and product['quantity'] < request_qty: + self.dialog_force_assign.exec_() + password = self.field_password_force_assign_ask.text() + self.field_password_force_assign_ask.setText('') + if password != self._password_admin: + self.message_bar.set('not_can_force_assign') + return False + return True + + def add_sale_line(self, record): + rec = self.model_sale_lines.add_record(record) + self.field_amount.setText(rec['amount_w_tax']) + + def sale_line_selected(self, product): + if self._state == 'cash': + return + self._current_line_id = product['id'] + self.label_product.setText(product['product.template.name']) + self.row_field_description.setText(product['description']) + self.row_field_qty.setValue(float(product['quantity'])) + self.row_field_price.setText(str(product['unit_price_w_tax'])) + # self.row_field_note.setText(str(product['note'])) + + self.dialog_product_edit.show() + self.row_field_note.setFocus() + + def create_dialog_sale_line(self): + self.state_line = {} + + vbox_product = QVBoxLayout() + grid = QGridLayout() + qty = 2 + + self.label_product = QLabel() + self.label_product.setAlignment(alignCenter) + self.label_product.setObjectName('label_product') + vbox_product.addWidget(self.label_product) + self.row_field_description = QLineEdit() + self.row_field_description.setObjectName('row_field_description') + self.row_field_description.textChanged.connect( + lambda: self.update_sale_line('description') + ) + grid.addWidget(self.row_field_description, 1, 1, 1, 2) + + if self._config.get('show_fractions'): + label_fraction = QLabel(self.tr('FRACTION:')) + label_fraction.setObjectName('label_fraction') + grid.addWidget(label_fraction, 2, 1) + self.field_combobox_fraction = ComboBox(self, 'fraction', + {'values': FRACTIONS}) + grid.addWidget(self.field_combobox_fraction, 2, 2) + self.field_combobox_fraction.currentIndexChanged.connect( + lambda: self.update_sale_line('qty_fraction') + ) + + label_qty = QLabel(self.tr('QUANTITY:')) + label_qty.setObjectName('label_qty') + grid.addWidget(label_qty, 3, 1) + self.row_field_qty = QDoubleSpinBox() + self.row_field_qty.setObjectName('row_field_qty') + self.row_field_qty.setMinimum(0) + self.row_field_qty.setMaximum(100000) + if self._config.get('decimals_digits_quantity'): + qty = self._config['decimals_digits_quantity'] + + self.row_field_qty.setDecimals(qty) + self.row_field_qty.setAlignment(alignCenter) + grid.addWidget(self.row_field_qty, 3, 2) + self.row_field_qty.valueChanged.connect( + lambda: self.update_sale_line('quantity') + ) + + label_price = QLabel(self.tr('UNIT PRICE:')) + label_price.setObjectName('label_price') + grid.addWidget(label_price, 4, 1) + self.row_field_price = FieldMoney(self, 'row_field_price', {}, readonly=False) + self.row_field_price.setObjectName('row_field_price') + grid.addWidget(self.row_field_price, 4, 2) + self.row_field_price.textChanged.connect( + lambda: self.update_sale_line('unit_price') + ) + + self.row_field_note = QTextEdit('') + self.row_field_note.setObjectName('row_field_note') + grid.addWidget(self.row_field_note, 5, 1, 5, 2) + self.row_field_note.textChanged.connect( + lambda: self.update_sale_line('note') + ) + + vbox_product.addLayout(grid) + self.dialog_product_edit = QuickDialog(self, 'action', widgets=[vbox_product]) + self.dialog_product_edit.accepted.connect(self.dialog_product_edit_accepted) + + def update_sale_line(self, field): + value = None + self.state_line['id'] = self._current_line_id + if field == 'quantity': + value = Decimal(self.row_field_qty.value()) + if field == 'unit_price': + value = self.row_field_price.text() + if field == 'qty_fraction': + qty = self.field_combobox_fraction.get_id() + self.row_field_qty.setValue(float(qty)) + value = self.field_combobox_fraction.get_label() + self.state_line['quantity'] = qty + + price_ = self.ModSale.get_product_prices({ + 'ids': [self._sale_line['product']['id']], + 'quantity': float(qty), + 'sale_id': self._sale['id'], + }) + if price_ and price_.get('unit_price_w_tax'): + price_list = str(price_['unit_price_w_tax']) + self.row_field_price.setText(price_list) + self.state_line['unit_price'] = price_list + + if field == 'description': + value = self.row_field_description.text() + if field == 'note': + value = self.row_field_note.toPlainText() + + if value: + self.state_line[field] = value + + + def dialog_product_edit_accepted(self): + if not self.state_line: + return + + _record = None + + if self.state_line.get('quantity'): + quantity = self.state_line.pop('quantity') + _record = self.ModSaleLine.faster_set_quantity({ + 'id': self._current_line_id, + 'quantity': to_float(quantity, 2) + }) + + if self.state_line.get('unit_price'): + unit_price = self.state_line.pop('unit_price') + self._sign = '/' + self._process_price(unit_price) + self._sign = None + _record = self.ModSaleLine.write([self._current_line_id], {}) + + if self.state_line.get('description'): + _record = self.ModSaleLine.write([self._current_line_id], { + 'description': self.state_line['description'] + }) + + if self.state_line.get('note'): + _record = self.ModSaleLine.write([self._current_line_id], { + 'note': self.state_line['note'] + }) + + if _record: + _record['product.template.name'] = _record['product']['name'] + _record['product.code'] = _record['product']['code'] + self.model_sale_lines.update_record(_record) + + self.update_total_amount() + self.state_line = {} + # self.field_combobox_fraction.set_from_id(1) + + def setup_sale_line(self): + product_code = { + 'name': 'product.code', + 'align': alignRight, + 'description': self.tr('COD'), + 'width': 80 + } + product = { + 'name': 'product.template.name', + 'align': alignLeft, + 'description': self.tr('NAME'), + 'width': STRETCH + } + description = { + 'name': 'description', + 'align': alignLeft, + 'description': self.tr('DESCRIPTION'), + 'width': 180 + } + uom = { + 'name': 'unit.symbol', + 'align': alignHCenter, + 'description': self.tr('UNIT'), + 'width': 50 + } + qty = { + 'name': 'quantity', + 'format': '{:3,.%sf}', + 'align': alignRight, + 'description': self.tr('QTY'), + 'digits': ('unit.symbol', CONVERSION_DIGITS), + 'width': 80 + } + discount = { + 'name': 'discount', + 'format': '{0:.0%}', + 'align': alignRight, + 'description': self.tr('DISC'), + 'width': 50 + } + amount = { + 'name': 'amount_w_tax', + 'format': '{:5,.1f}', + 'align': alignRight, + 'description': self.tr('SUBTOTAL'), + 'width': 100 + } + note = { + 'name': 'note', + 'align': alignLeft, + 'description': self.tr('NOTE'), + 'invisible': True, + 'width': 100 + } + unit_price = { + 'name': 'unit_price_w_tax', + 'format': '{:5,.1f}', + 'align': alignLeft, + 'description': self.tr('UNIT PRICE W TAX'), + 'invisible': True, + 'width': 100 + } + + qty_fraction = { + 'name': 'qty_fraction', + 'align': alignHCenter, + 'description': self.tr('FRAC'), + 'width': 50 + } + + self.fields_sale_line = [product, uom, qty, discount, + amount, note, unit_price] + + if self.enviroment == 'retail': + self.fields_sale_line.insert(0, product_code) + + if self._config.get('show_description_pos'): + self.fields_sale_line.insert(2, description) + + if self._config.get('show_fractions'): + self.fields_sale_line.insert(4, qty_fraction) + + self.model_sale_lines = TableModel('sale.line', self.fields_sale_line) + + def setup_payment(self): + pay_fields = [{ + 'name': 'statement', + 'align': alignLeft, + 'description': self.tr('STATEMENT JOURNAL'), + }, { + 'name': 'amount', + 'align': alignRight, + 'format': '{:5,.1f}', + 'description': self.tr('AMOUNT'), + }, { + 'name': 'voucher', + 'align': alignCenter, + 'description': self.tr('VOUCHER'), + }] + self.table_payment_lines = TableModel('account.statement.line', pay_fields) + + def action_table(self): + self.table_sale_lines.setFocus() + + def on_change_line_selected(self, key): + self.table_sale_lines.moved_selection(key) + + def action_delete_line(self): + if self.model_sale_lines.rowCount() <= 0 or self._state == 'cash': + return + + self.table_sale_lines.setFocus() + + removed_item = self.table_sale_lines.delete_item() + self.ModSaleLine.delete([removed_item['id']]) + self.set_amounts() + self.update_total_amount() + # self.clear_right_panel() + + self._current_line_id = None + self.setFocus() + self.label_input.setFocus() + + if self.enviroment == 'restaurant': + if removed_item and self.print_order: + self.action_print_order(self._sale['id'], removed_item) + if self._config['tip_product.code'] == removed_item['product.code']: + self._PosSale.write([self._sale['id']], {'tip': None}) + + def set_discount(self, eval_value, lines_ids=[]): + res = False + try: + value = round(float(str(eval_value)), 6) + except ValueError: + logging.warning('ValueError > ', ValueError) + return + + if float(value) <= 0: + return + + if not lines_ids: + target_lines = [self._current_line_id] + else: + target_lines = lines_ids + + records = self.ModSaleLine.faster_set_discount({ + 'line_ids': target_lines, + 'value': value + }) + + for rec in records: + self.model_sale_lines.update_record(rec) + + if records: + res = True + self.set_amounts() + return res + + def set_unit_price(self, value): + rec = self.ModSaleLine.set_faster_unit_price({ + 'id': self._current_line_id, + 'value': value, + }) + + if rec: + self.model_sale_lines.update_record(rec) + self.update_total_amount() + return True + return False + + def add_payment(self, amount, cash_received=0, change=0): + voucher_number = None + if self._journals[self.field_journal_id]['require_voucher']: + self.dialog_voucher.exec_() + voucher_number = self.field_voucher_ask.text() + if voucher_number is None or voucher_number == '': + return self.add_payment(amount) + + res = self.ModSale.faster_add_payment({ + 'sale_id': self._sale['id'], + 'journal_id': self.field_journal_id, + 'amount': to_numeric(amount), + 'voucher_number': voucher_number, + 'cash_received': to_numeric(cash_received), + 'change': to_numeric(change) + }) + + if res.get('msg') not in ('missing_money', 'ok'): + self.dialog(res['msg']) + return res + + self.table_payment_lines.add_record(res) + return res + + def create_dialog_help(self): + from .help import Help + help = Help(self) + help.show() + + def set_keys(self): + self.keys_numbers = list(range(Qt.Key_0, Qt.Key_9 + 1)) + self.keys_alpha = list(range(Qt.Key_A, Qt.Key_Z + 1)) + self.keys_period = [Qt.Key_Period] + self.show_keys = self.keys_numbers + self.keys_alpha + self.keys_period + + self.keys_special = [Qt.Key_Asterisk, Qt.Key_Comma, + Qt.Key_Minus, Qt.Key_Slash] + self.keys_input = [Qt.Key_Backspace] + self.keys_input.extend(self.keys_special) + self.keys_input.extend(self.show_keys) + self.keys_input.extend(self.keys_numbers) + self.keys_input.extend([Qt.Key_Return, Qt.Key_Plus]) + + def set_state(self, state='add'): + self._state = state + state = STATES[state] + self._re = state['re'] + if not self.type_pos_user == 'order': + if not self.buttonpad.stacked.stacked.currentWidget(): + return + if state['button']: + self.buttonpad.stacked.stacked.setCurrentWidget( + getattr(self.buttonpad.stacked, state['button']) + ) + if not self.tablet_mode: + self.buttonpad.stacked.stacked.currentWidget().setVisible(True) + else: + self.buttonpad.stacked.stacked.currentWidget().setVisible(False) + + def key_pressed(self, text): + if not self._sign and self._state != 'cash': + if self._re.match(self._input_text + text): + self.input_text_changed(text) + else: + if RE_SIGN['quantity'].match(self._amount_text + text): + self.amount_text_changed(text) + + def clear_sign(self): + self._sign = None + self.field_sign.setText(' {0} '.format(' ')) + + def sign_text_changed(self, sign): + self._sign = sign + self.field_sign.setText(' {0} '.format(sign)) + if hasattr(self, '_sale_line') and self._sale_line: + if sign == '-': + self.message_bar.set('enter_discount') + elif sign == '/': + self.message_bar.set('enter_new_price') + elif sign == '*': + self.message_bar.set('enter_quantity') + if self.active_weighing and self._sale_line['unit_symbol'] != 'u': + self.action_read_weight() + + def key_special_pressed(self, value): + self.clear_amount_text() + self.clear_input_text() + if value not in ['-', '/', '*']: + return + self.sign_text_changed(value) + + def key_backspace_pressed(self): + if self._sign or self._state == 'cash': + self._amount_text = self._amount_text[:-1] + self.amount_text_changed() + else: + self._input_text = self._input_text[:-1] + self.input_text_changed() + + def set_text(self, text): + if not self._state == 'cash': + self.input_text_changed(text) + else: + self.amount_text_changed(text) + + def clear_input_text(self): + self.input_text_changed('') + + def clear_amount_text(self): + self._amount_text = '0' + self.amount_text_changed() + + def keyPressEvent(self, event): + self._keyStates[event.key()] = True + key = event.key() + + if self._state == 'add' and key not in self.keys_input and \ + key not in (Qt.Key_Enter, Qt.Key_End): + # Clear ui context when keys function are pressed + self._clear_context() + + if key in (Qt.Key_Return, Qt.Key_Plus): + self.button_plus_pressed() + elif key in self.show_keys: + # No allow change quantity o discount in state == cash + if self._state == 'cash' and key not in self.keys_numbers: + return + self.key_pressed(event.text()) + elif key in self.keys_special: + if self._state == 'cash' or not self._current_line_id: + return + self.key_special_pressed(event.text()) + elif key == Qt.Key_Backspace: + self.key_backspace_pressed() + elif key == Qt.Key_Escape: + self.close() + elif key == Qt.Key_F1: + self.create_dialog_help() + elif key == Qt.Key_F9: + self.action_search_sale() + elif key == Qt.Key_F11: + self.action_new_sale() + elif key == Qt.Key_F7: + self.action_print_sale() + elif self._state == 'disabled': + self.message_bar.set('must_load_or_create_sale') + return + elif key in (Qt.Key_Enter, Qt.Key_End): + if self.type_pos_user in ['order', 'salesman']: + return + if self._state == 'add': + self.button_accept_pressed() + elif self._state in ['accept', 'cash']: + self.button_cash_pressed() + elif key == Qt.Key_F2: + self.action_search_product() + elif key == Qt.Key_F3: + self.action_payment() + elif key == Qt.Key_F4: + self.action_party() + elif key == Qt.Key_F5: + self.action_global_discount() + elif key == Qt.Key_F6: + self.action_print_order() + elif key == Qt.Key_F8: + self.action_payment_term() + elif key == Qt.Key_F10: + self.action_table() + elif key == Qt.Key_F12: + self.action_cancel() + elif key == Qt.Key_Home: + self.action_salesman() + elif key == Qt.Key_Down or key == Qt.Key_Up: + self.on_change_line_selected(key) + elif key == Qt.Key_Delete: + self.action_delete_line() + elif key == Qt.Key_Insert: + self.action_position() + elif key == Qt.Key_Semicolon and self._commission_activated: + sale = self.get_current_sale() + if sale['state'] == 'draft' and self._state not in ['accept', 'cash']: + self.action_agent() + elif key == Qt.Key_QuoteDbl: + self.action_comment() + elif key == Qt.Key_Question: + self.action_tax() + else: + pass + + @property + def state(self): + return self._state + + +class DoInvoice(QThread): + """ + Process invoices using a thread + """ + sigDoInvoice = pyqtSignal() + + def __init__(self, main, context): + QThread.__init__(self) + + def run(self): + self.sigDoInvoice.emit() diff --git a/app/manage_tables.py b/app/manage_tables.py new file mode 100644 index 0000000..560659d --- /dev/null +++ b/app/manage_tables.py @@ -0,0 +1,64 @@ +import os + +from PyQt5.QtWidgets import QGridLayout, QPushButton + +DIR_SHARE = os.path.abspath( + os.path.normpath(os.path.join(__file__, '..', '..', 'share'))) + +__all__ = ['ManageTables'] + +STATES = { + 'available': 'rgb(180, 180, 180)', + 'occupied': 'rgb(255, 210, 30)', + 'reserved': 'rgb(150, 30, 0)' +} + + +class CallButton(QPushButton): + + def __init__(self, value, method): + super(CallButton, self).__init__(value['name']) + self.setAutoFillBackground(True) + self.name = value['name'] + self.id = value['id'] + self.state = value['state'] + self.method = method + self.set_state(value['state']) + self.clicked.connect(self.handle_click) + + def handle_click(self): + if self.state == 'available': + state = 'occupied' + else: + state = 'available' + + res = self.method(self.id, self.name, self.state, state) + if not res: + return + + self.set_state(state) + + def set_state(self, state): + self.state = state + color = STATES[self.state] + self.setStyleSheet('background-color: {}; border:none;'.format(color)) + + +class ManageTables(QGridLayout): + + def __init__(self, parent, tables, method): + super(ManageTables, self).__init__() + self.setHorizontalSpacing(1) + self.setVerticalSpacing(1) + columns = 6 + rows = int(len(tables) / columns) + 1 + self.buttons = {} + positions = [(i, j) for i in range(rows) for j in range(columns)] + for position, value in zip(positions, tables): + button = CallButton(value, method) + self.buttons[button.id] = button + self.addWidget(button, *position) + + def update_table(self, button_id, state): + button = self.buttons[button_id] + button.set_state(state) diff --git a/app/medium_screen.css b/app/medium_screen.css new file mode 100644 index 0000000..00184bd --- /dev/null +++ b/app/medium_screen.css @@ -0,0 +1,137 @@ + +QAbstractButton { + font-family: "DejaVu Sans"; + border-style: groove; + font: 20pt; + color: rgb(102, 102, 102); + background-color: rgb(242, 242, 242); + min-height: 45px; + min-width : 100px; + border-color: rgb(208, 208, 208); + border-width: 0.5px; +} + +#label_input, +#field_default, +#field_medium_gray, +#field_medium_blue, +#field_medium_orange { + background-color : white; + border-style : groove; + border-width : 0.5px; + border-color : rgb(208, 208, 208); +} + +#field_default { + font : 22px; +} + +#field_medium_gray, #label_medium_gray { + font : 22px; + color : rgb(54, 54, 54); +} + +#field_medium_blue, #label_medium_blue { + font : 22px; + color : rgb(0, 30, 80); +} + +#field_medium_orange, #label_medium_orange { + font : 22px; + color : rgb(235, 160, 15); +} + +#label_qty, #spin_box_qty, #row_field_price, +#label_price, #row_field_note, #label_fraction, #row_field_qty, +#field_fraction { + font : 22px; + alignment : center; +} + +#row_field_description { + font : 22px; + color : #242424; +} + +#label_product { + font : bold 24px; + alignment : center; + color : #242424; +} + +QSpinBox { + padding-right: 15px; + border-width: 3; + height: 40px; +} + +QSpinBox::up-button { + subcontrol-position: right; + height: 40px; + width: 40px; +} + +QSpinBox::down-button { + subcontrol-position: left; + height: 40px; + width: 40px; +} + +#field_invoice { + min-width : 120px; + max-width : 140px; +} + +#label_position, #label_salesman, #label_payment_term, + #label_party, #label_global_discount, #field_invoice, #label_invoice, + #label_agent, #label_date, #label_order_number, #label_default { + font : 20px; + color : rgb(150, 150, 150); + min-height : 10px; + min-width : 10px; +} + +#field_amount, #field_sign { + font : bold 42px; + color : rgb(43, 60, 77); + min-height : 40px; + max-height : 100px; +} + +#field_amount { + max-width : 260px; + min-width : 180px; +} + +#field_total_amount, #field_change, #label_total_amount, #label_change { + color : rgb(50, 65, 75); + min-width : 200px; +} + +#field_paid, #label_paid { + color : rgb(80, 190, 220); +} + +#table_payment { + color : rgb(70, 70, 70); + font : 14pt; + max-height : 80px; +} + +#table_sale_lines { + color : rgb(70, 70, 70); + font : 14pt; +} + +#label_paid, #label_change, #label_discount, #label_total_amount, +#label_discount, #label_change, #label_paid, #label_amount, #label_untaxed_amount, #label_taxes_amount, +#field_total_amount, #field_change, #field_discount, #field_paid, +#field_untaxed_amount, #field_taxes_amount { + min-height : 10px; + font: bold 20pt; +} + +#img_pixmap_pos { + min-width: 200pt; + min-height: 100pt; +} diff --git a/app/proxy.py b/app/proxy.py new file mode 100644 index 0000000..42017cd --- /dev/null +++ b/app/proxy.py @@ -0,0 +1,103 @@ + +import requests +from datetime import date +import simplejson as json + + +def encoder(obj): + # FIXME: add datetime, buffer, bytes + if isinstance(obj, date): + return { + '__class__': 'date', + 'year': obj.year, + 'month': obj.month, + 'day': obj.day, + } + raise TypeError(repr(obj) + " is not JSON serializable") + + +class FastModel(object): + + def __init__(self, model, ctx): + # params = parent.params + # self.model = model['name'] + self.model = model + # self.fields = model['fields'] + self.ctx = ctx + self.api = '/'.join( + ['http:/', ctx['params']['api_url'], ctx['params']['database']] + ) + + def __getattr__(self, name, *args): + 'Return attribute value' + self.method = name + return self + + def find(self, domain, order=None, limit=100, ctx=None): + if ctx: + self.ctx.update(ctx) + route = self.get_route('search') + args_ = { + 'model': self.model, + 'domain': domain, + 'order': order, + 'limit': limit, + 'context': self.ctx, + } + data = json.dumps(args_, default=encoder) + res = requests.get(route, data=data) + print('res ', res) + return res.json() + + def write(self, ids, values): + route = self.get_route('save') + args_ = { + 'model': self.model, + 'id': ids[0], + 'record_data': values, + 'context': self.ctx, + } + data = json.dumps(args_, default=encoder) + res = requests.put(route, data=data) + return res.json() + + def delete(self, ids): + route = self.get_route('delete') + args_ = { + 'model': self.model, + 'ids': ids, + 'context': self.ctx, + } + data = json.dumps(args_, default=encoder) + res = requests.delete(route, data=data) + return res.json() + + def get_route(self, target): + route = self.api + '/' + target + return route + + def __call__(self, args=None): + args_ = { + 'model': self.model, + 'method': self.method, + 'args': args, + 'context': self.ctx, + } + route = self.get_route('model_method') + data = json.dumps(args_, default=encoder) + res = requests.post(route, data=data) + data = res.json() + return data + + +if __name__ == "__main__": + params = {'api_url': 'localhost:5070', 'database': 'DEMORD'} + model = {'model': 'sale.sale'} + test_model = FastModel(params, model) + id = 180 + data = { + 'reference': 'OC-02874' + } + # res = test_model.find(dom) + res = test_model.write([id], data) + print(res) diff --git a/app/reporting.py b/app/reporting.py new file mode 100755 index 0000000..01273fe --- /dev/null +++ b/app/reporting.py @@ -0,0 +1,648 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. + +import os +from io import StringIO +import logging +from datetime import datetime +from decimal import Decimal + +pyudev = None + +try: + import pyudev +except: + logging.warning("Pyudev module not found!") + +try: + from escpos import printer +except: + logging.warning("Escpos module not found!") + +try: + import cups +except: + logging.warning("Cups module not found!") + +__all__ = ['Receipt'] + +_ROW_CHARACTERS = 48 + +_DIGITS = 9 +_PRINT_TAX_ID = False +_DIGITS_CODE_RECEIPT = 4 + +# ------------------- Type Font Escpos ----------------- +_FONT_A = 'a' # Normal Font +_FONT_B = 'b' # Condensed Font +# ------------------------------------------------------ + +if os.name == 'posix': + homex = 'HOME' + dirconfig = '.tryton/temp' +elif os.name == 'nt': + homex = 'USERPROFILE' + dirconfig = 'AppData/Local/tryton/temp' + +HOME_DIR = os.getenv(homex) +directory = os.path.join(HOME_DIR, dirconfig) + + +if not os.path.exists(directory): + os.makedirs(directory) + +TEMP_INVOICE_FILE = os.path.join(directory, 'invoice.txt') +SSH_PORT = 23 + + +def money(value): + if type(value) != int: + value = int(value) + return '{:,}'.format(value) + + +dev_printers = {} +if pyudev: + context = pyudev.Context() +# for device in context.list_devices(): +# if device.subsystem == 'usbmisc': +# print(device.subsystem, device.sys_path.split('2-1/')[1][0:5], device.device_node) +# dev_printers[str(device.sys_path.split('2-1/')[1][0:5])] = device.device_node + + +class Receipt(object): + __name__ = 'frontend_pos.ticket' + + def __init__(self, context, row_characters=None, logo=None, environment='retail'): + self.logger = logging.getLogger('reporting') + self._company = context.get('company') + self._sale_device = context.get('sale_device') + self._shop = context.get('shop') + self._street = context.get('street') + self._city = context.get('city') + self._phone = context.get('phone') + self._id_number = context.get('id_number') + self._regime_tax = context.get('regime_tax') + self._gta_info = context.get('gta_info') + self._user = context.get('user') + self._footer = context.get('footer') + self._header = context.get('header') + self._printing_taxes = context.get('printing_taxes') + self._delta_locale = context.get('delta_locale') + self._environment = environment + + self._row_characters = _ROW_CHARACTERS + if context.get('row_characters'): + self._row_characters = int(context.get('row_characters')) + + self.taxes_col_width = int(self._row_characters / 3) + + order_col_width = int(self._row_characters / 3) + self.order_col_1 = order_col_width - 6 + self.order_col_2 = order_col_width + 11 + self.order_col_3 = order_col_width - 5 + + self._show_position = context.get('show_position') + self._show_discount = context.get('show_discount') + self._img_logo = None + + if logo: + self._img_logo = StringIO(logo) + + def printer_found(self): + return self._printer + + def printing(f): + def p(self, *p, **kw): + self._open_device() + try: + res = f(self, *p, **kw) + finally: + pass + return res + return p + + def test_printer(self): + if self._interface == 'usb': + if os.name == 'posix': + self._printer = printer.File(self._device) + elif os.name == 'nt': + self._printer = printer.UsbWin(self._device) + self._printer.open() + elif self._interface == 'network': + self._printer = printer.Network(self._device) + elif self._interface == 'ssh': + self._printer = printer.FileSSH(*self._device.split('@')) + self._printer.open() + if not self._printer: + return + if self._img_logo: + self.print_logo() + self.print_enter() + self.print_enter() + self.print_header() + self.print_enter() + self.print_enter() + self._printer.cut() + self._printer.cashdraw(2) + self._printer.close() + + def set_printer(self, printer): + if dev_printers.get(printer['device']): + device = dev_printers[printer['device']] + else: + device = printer['device'] + + self._interface = printer['interface'] + self._device = device + + def print_sale(self, sale): + try: + if self._interface == 'usb': + if os.name == 'posix': + self._printer = printer.File(self._device) + elif os.name == 'nt': + self._printer = printer.UsbWin(self._device) + self._printer.open() + elif self._interface == 'network': + self._printer = printer.Network(self._device) + elif self._interface == 'ssh': + self._printer = printer.FileSSH(*self._device.split('@')) + self._printer.open() + elif self._interface == 'cups': + self.conn = cups.Connection() + self._file = open(TEMP_INVOICE_FILE, 'w') + self._printer = CupsPrinter(self._file, self._row_characters) + if not self._printer: + self.logger.info("Warning: Can not found Printer!") + return + self.logger.info("Info: Printer is OK!") + self._print_sale(sale) + except: + self.logger.info("Warning: Printer error or device not found!") + + def _print_sale(self, sale): + self.print_header() + self.print_body(sale) + self.print_footer() + # self.print_extra_info(sale) + if self._interface in ['usb', 'ssh', 'network']: + self._printer.close() + elif self._interface == 'cups': + self._file.close() + self.conn.printFile(self._printer_name, TEMP_INVOICE_FILE, + 'POS Invoice', {}) + + def print_logo(self): + self._printer.set(align='center') + self._printer.image(self._img_logo) + self.print_enter() + + def print_header(self): + if self._img_logo: + self.print_logo() + self._printer.set(align='center') + if self._header != '' and self._header is not None: + self._printer.text(self._header) + self.print_enter() + self._printer.text(self._company) + self.print_enter() + self._printer.text(self._shop) + self.print_enter() + if self._id_number: + self._printer.text('NIT:' + self._id_number) + if self._regime_tax: + self._printer.text(' ' + self._regime_tax) + self.print_enter() + if self._street: + self._printer.text(self._street) + self.print_enter() + if self._city: + self._printer.text(self._city) + if self._phone: + if self._city: + self._printer.text(' ') + self._printer.text('Telefono:' + self._phone) + if self._city or self._phone: + self.print_enter() + self.print_enter() + self.print_enter() + + def print_horinzontal_line(self): + self._printer.text('-' * self._row_characters) + + def print_horinzontal_double_line(self): + self._printer.text('=' * self._row_characters) + + def print_enter(self): + self._printer.text('\n') + + def print_split(self, left, right): + len_left = self._row_characters - len(right) - 1 + left = left[:len_left] + if type(left) == bytes: + left = left.decode("utf-8") + if type(right) == bytes: + right = right.decode("utf-8") + left += (len_left - len(left) + 1) * ' ' + self._printer.text(left) + self._printer.text(right + '\n') + + def print_body(self, sale): + self._cashdraw = True + self._printer.set(font=_FONT_B) + self._printer.set(align='left') + if sale['number'] and sale['state'] in ['processing', 'done', 'cancel']: + if sale['total_amount'] >= 0: + self._printer.text('FACTURA DE VENTA No. ' + sale['number']) + else: + self._printer.text('NOTA CREDITO No. ' + sale['number']) + else: + self._cashdraw = False + self._printer.text('Pedido: ' + sale['order']) + self.print_enter() + #mod_hours = sale["create_date"] + timedelta(hours=self._delta_locale) + #time_ = mod_hours.strftime('%I:%M %p') + self._printer.text('Fecha:%s' % sale['date']) + if sale.get('turn') and sale['turn'] != 0: + self._printer.text('Turno: %s - ' % str(sale['turn'])) + self.print_enter() + self.print_horinzontal_line() + party_name = 'Cliente: %s ' % sale['party'] + party_id_number = 'Id: %s' % sale.get('party_id_number', '') + if len(party_name + party_id_number) > self._row_characters: + self._printer.text(party_name) + self.print_enter() + self._printer.text(party_id_number) + else: + self._printer.text(party_name + party_id_number) + if sale.get('party_address'): + self.print_enter() + self._printer.text('Direccion: %s' % sale['party_address']) + if sale.get('party_phone'): + self.print_enter() + self._printer.text('Telefono: %s' % sale['party_phone']) + + self.print_enter() + self.print_horinzontal_line() + self.print_split(' Articulo ', 'Subtotal ') + self.print_horinzontal_line() + + len_row = self._row_characters - (_DIGITS_CODE_RECEIPT + 1) - (_DIGITS + 1) + for line in sale['lines']: + if line['taxes'] and _PRINT_TAX_ID: + tax_id = ' ' + str(line['taxes'][0].id) + else: + tax_id = '' + line_total = money(line['amount_w_tax']) + tax_id + + if line['quantity'] != 1: + length_name = self._row_characters - 11 + first_line = line['code'] + ' ' + line['name'][:length_name] + + if type(first_line) == bytes: + first_line = first_line.decode('utf-8') + + self._printer.text(first_line + '\n') + + unit_price_w_tax = str(round(line['unit_price_w_tax'], 2)) + + second_line = ' %s x %s' % (line['quantity'], unit_price_w_tax) + second_line = second_line.encode('utf-8') + self.print_split(second_line, line_total) + else: + if self._environment == 'retail': + line_pt = line['code'] + ' ' + line['name'][:len_row] + else: + line_pt = line['name'][:len_row] + + self.print_split(line_pt, line_total) + + untaxed_amount = sale['untaxed_amount'] + total_amount = sale['total_amount'] + total_string = 'Total:' + + tip = None + if sale.get('tip') and sale['tip'] > 0: + untaxed_amount = untaxed_amount - sale['tip'] + total_amount = untaxed_amount + sale['tax_amount'] + tip = sale['tip'] + total_string = 'Total Sin Propina:' + + self.print_split('', '----------------') + self.print_split('Subtotal Base:', money(untaxed_amount)) + self.print_split('Impuesto:', money(sale['tax_amount'])) + self.print_split('', '----------------') + self.print_split(total_string, money(total_amount)) + self.print_enter() + + if tip: + self.print_split('Propina:', money(tip)) + self.print_split('Total con Propina:', money(sale['total_amount'])) + + if self._show_discount: + self.print_split('Descuento:', money(sale['discount'])) + self.print_enter() + if sale['cash_received']: + self.print_split('Recibido:', money(sale['cash_received'])) + else: + self.print_split('Recibido:', money(sale['paid_amount'])) + self.print_split('Saldo Pendiente:', money(sale['total_amount'] - sale['paid_amount'])) + self.print_split('Cambio:', money(sale['change'])) + self.print_horinzontal_line() + self.print_enter() + if self._printing_taxes: + self.print_col('Tipo', self.taxes_col_width + 2) + self.print_col('Base', self.taxes_col_width) + self.print_col('Imp.', self.taxes_col_width) + taxes = sale['taxes'] + for tax in taxes: + self.print_col(str(taxes[tax]['name']) + ' ', self.taxes_col_width) + self.print_col(str(int(taxes[tax]['base'])), self.taxes_col_width) + self.print_col(str(int(taxes[tax]['tax'])), self.taxes_col_width) + self.print_enter() + + self.print_horinzontal_line() + self.print_enter() + no_products = 'No Items: %s' % str(sale['num_products']) + self._printer.text(no_products) + self.print_enter() + + if self._gta_info and sale['state'] not in ['draft']: + self._printer.text(self._gta_info) + self.print_enter() + if sale['state'] in ['processing', 'done']: + self._printer.text('Pedido: ' + sale['order']) + self.print_enter() + + register = 'Caja No. %s' % self._sale_device + self._printer.text(register) + self.print_enter() + + self._printer.text('Cajero: %s' % self._user) + self.print_enter() + if sale.get('salesman'): + self._printer.text('Vendedor: %s' % sale['salesman']) + self.print_enter() + if sale.get('comment'): + self._printer.text('Notas: %s' % sale['comment']) + self.print_enter() + + if self._show_position: + self._printer.text('Posicion: %s' % str(sale['position'])) + self.print_enter() + + self._printer.set(align='center') + #self.print_split('Puntos Acumulados:', sale['points']) + #self.print_enter() + #printer.barcode(sale.receipt_code, 'CODE128B', 3, 50,'','') + self.print_enter() + self.print_enter() + + def print_extra_info(self, sale): + if sale.get('pos_notes'): + self.print_enter() + self.print_header() + self.print_horinzontal_line() + self.print_enter() + party_name = 'Cliente: %s ' % sale['party'] + self._printer.text(party_name) + self.print_enter() + if self._show_position: + self._printer.text('Posicion: %s' % str(sale['position'])) + self.print_enter() + + if sale['state'] in ['draft']: + self._printer.text('Cotizacion: ', sale['order']) + else: + self._printer.text('Factura No. ' + sale['number']) + self.print_enter() + self._printer.text(str(sale.get('pos_notes'))) + self.print_enter() + self.print_horinzontal_line() + self.print_enter() + self._printer.cut() + + def print_col(self, x, l): + self._printer.text(x[:l] + (l - len(x)) * ' ') + + def print_footer(self, ): + if self._footer: + self._printer.text(self._footer) + self.print_enter() + self._printer.text('SOFTWARE POS TRYTON - www.presik.com') + self.print_enter() + self._printer.cut() + if self._cashdraw: + self._printer.cashdraw(2) + self.print_enter() + + def print_orders(self, orders, reversion=None, kind='command'): + res = [] + self.order_kind = kind + for order in orders.values(): + try: + if dev_printers.get(order['host']): + host = dev_printers[order['host']] + else: + host = order['host'] + if order['interface'] == 'usb': + self._printer = printer.File(host) + elif order['interface'] == 'network': + self._printer = printer.Network(host) + elif order['interface'] == 'ssh': + self._printer = printer.FileSSH(*host.split('@')) + if self._printer: + self._printer.open() + elif order['interface'] == 'cups': + pass + if not self._printer: + self.logger.info("Warning: Interface not found for printer!") + res.append(None) + continue + + self.logger.info("Info: Printer is OK!") + res.append(self._print_order(order, reversion)) + except: + self.logger.info("Warning: Can not found Printer!") + res.append(None) + return all(res) + + def _print_order(self, order, reversion): + self.print_body_order(order, reversion) + self._printer.cut() + self._row_characters = order['row_characters'] + if order['interface'] in ('network', 'usb', 'ssh'): + self._printer.close() + return True + + def print_body_order(self, order, reversion): + self._printer.set(font=_FONT_B) + self._printer.set(align='center') + self._printer.text('TURNO: %s' % str(order['turn'])) + self.print_enter() + self.print_enter() + kind = 'COMANDA' + if self.order_kind == 'delivery': + kind = 'PEDIDO' + title = '+ + + + + %s + + + + +' % kind + self._printer.text(title) + self.print_enter() + self._printer.set(align='left') + self.print_enter() + date_ = datetime.now().strftime("%Y-%m-%d %H:%M %p") + self._printer.text('FECHA: ' + date_) + self.print_enter() + + if self.order_kind == 'delivery': + self._printer.text('FACTURA: ' + order['number']) + self.print_enter() + delivery_charge = 'Cliente' + if order['delivery_charge'] == 'company': + delivery_charge = 'Empresa' + self._printer.text('CARGO DEL DOMICILIO: ' + delivery_charge) + self.print_enter() + if order.get('payment_term'): + self._printer.text('FORMA DE PAGO: ' + order['payment_term']) + self.print_enter() + + if order.get('sale_number'): + self._printer.text('PEDIDO: %s' % str(order['sale_number'])) + self.print_enter() + + self._printer.text('POSICION: %s' % str(order['position'])) + self.print_enter() + self._printer.text('VENDEDOR: %s' % order['salesman']) + self.print_enter() + self._printer.text('AMBIENTE: %s' % order['shop']) + self.print_enter() + self._printer.text('CLIENTE: %s' % order['party']) + self.print_enter() + + if self.order_kind == 'delivery': + self._printer.text('VALOR: ' + str(order['total_amount'])) + self.print_enter() + if order.get('pos_notes'): + self._printer.text(order['pos_notes']) + self.print_enter() + self._printer.text('CAJA No: %s' % self._sale_device or '') + self.print_enter() + self.print_enter() + self._printer.set(align='center') + self.print_horinzontal_line() + if not reversion and self.order_kind == 'command': + self._printer.text('-------- PREPARAR Y SERVIR --------') + elif not reversion and self.order_kind == 'delivery': + self._printer.text('-------- ENTREGAR --------') + else: + self._printer.text('<<<< R E V E R S I O N >>>>') + self.print_enter() + + if self.order_kind != 'delivery': + self.print_horinzontal_line() + self._printer.set(align='left') + self.print_enter() + self._printer.set(align='left') + + self.print_enter() + self.print_horinzontal_line() + self.print_col('CANT', self.order_col_1) + self.print_col('PRODUCTO', self.order_col_2) + self.print_col(' ' + 'PRECIO', self.order_col_3) + + for line in order['lines']: + qty = str(int(Decimal(line['quantity']))) + self.print_col(qty, self.order_col_1) + self.print_col(line['name'], self.order_col_2) + self.print_col(' ' + str(line['unit_price']), self.order_col_3) + if line['note']: + self.print_enter() + self._printer.text(' ----->> NOTA: ' + line['note']) + self.print_enter() + + self.print_enter() + self.print_horinzontal_double_line() + + self.print_enter() + self._printer.text('NOTA:') + self.print_enter() + if order['comment']: + self._printer.text(str(order['comment'])) + self.print_enter() + self.print_horinzontal_line() + self.print_enter() + self.print_enter() + + +class CupsPrinter(object): + "Cups Printer" + __name__ = 'sale_pos_frontend.cups_printer' + + def __init__(self, _file, row_characters): + self._file = _file + self.align = 'left' + self._row_characters = row_characters + + def text(self, text): + self._text(text) + + def set(self, align='left', font=_FONT_A): + if align: + self.align = align + if font: + self.font = font + + def cut(self): + pass + + def cashdraw(number): + pass + + def _text(self, text): + start_spaces = '' + if self.align == 'center': + start_spaces = int((self._row_characters - len(text)) / 2) * ' ' + elif self.align == 'right': + start_spaces = int(self._row_characters - len(text)) * ' ' + else: + pass + text = start_spaces + text + self._file.write(text) + + +if __name__ == '__main__': + + # Test for Escpos interface printer Linux + + # Network example + device = 'network', '192.168.0.32' + + # Unix-like Usb example + # device = 'usb','/dev/usb/lp1' + + # Windows Usb example for printer nameb SATPOS + # device = 'usb', 'SATPOS' + + # SSH example + # device = 'ssh', 'psk@xxxxx@192.168.0.5@23@/dev/usb/lp1' + + example_dev = { + 'interface': device[0], + 'device': device[1], + } + + ctx_printing = {} + ctx_printing['company'] = 'OSCORP INC' + ctx_printing['sale_device'] = 'CAJA-10' + ctx_printing['shop'] = 'Shop Wall Boulevard' + ctx_printing['street'] = 'Cll 21 # 172-81. Central Park' + ctx_printing['user'] = 'Charles Chapplin' + ctx_printing['city'] = 'Dallas' + ctx_printing['zip'] = '0876' + ctx_printing['phone'] = '591 5513 455' + ctx_printing['id_number'] = '123456789-0' + ctx_printing['tax_regime'] = 'none' + + receipt = Receipt(ctx_printing) + receipt.set_printer(example_dev) + receipt.test_printer() diff --git a/app/share/accept.svg b/app/share/accept.svg new file mode 100644 index 0000000..7edc1f1 --- /dev/null +++ b/app/share/accept.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/share/beer.svg b/app/share/beer.svg new file mode 100644 index 0000000..8679548 --- /dev/null +++ b/app/share/beer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/breakfast.svg b/app/share/breakfast.svg new file mode 100644 index 0000000..613b05e --- /dev/null +++ b/app/share/breakfast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/burger.svg b/app/share/burger.svg new file mode 100644 index 0000000..f406764 --- /dev/null +++ b/app/share/burger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/calendar.svg b/app/share/calendar.svg new file mode 100644 index 0000000..e67e2df --- /dev/null +++ b/app/share/calendar.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/share/cancel.svg b/app/share/cancel.svg new file mode 100644 index 0000000..91ecc55 --- /dev/null +++ b/app/share/cancel.svg @@ -0,0 +1,97 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/cash.svg b/app/share/cash.svg new file mode 100644 index 0000000..6ee4dba --- /dev/null +++ b/app/share/cash.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/share/cheese.svg b/app/share/cheese.svg new file mode 100644 index 0000000..d6cd000 --- /dev/null +++ b/app/share/cheese.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/chicken-leg.svg b/app/share/chicken-leg.svg new file mode 100644 index 0000000..76d4694 --- /dev/null +++ b/app/share/chicken-leg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/chinese-food.svg b/app/share/chinese-food.svg new file mode 100644 index 0000000..b7ec558 --- /dev/null +++ b/app/share/chinese-food.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/coffee.svg b/app/share/coffee.svg new file mode 100644 index 0000000..0ee7ab2 --- /dev/null +++ b/app/share/coffee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/comment.svg b/app/share/comment.svg new file mode 100644 index 0000000..dc0c0a0 --- /dev/null +++ b/app/share/comment.svg @@ -0,0 +1,85 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/croissant.svg b/app/share/croissant.svg new file mode 100644 index 0000000..370900d --- /dev/null +++ b/app/share/croissant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/delete_line.svg b/app/share/delete_line.svg new file mode 100644 index 0000000..1f1869c --- /dev/null +++ b/app/share/delete_line.svg @@ -0,0 +1,89 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/donut.svg b/app/share/donut.svg new file mode 100644 index 0000000..afc6051 --- /dev/null +++ b/app/share/donut.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/draft.svg b/app/share/draft.svg new file mode 100644 index 0000000..77877b3 --- /dev/null +++ b/app/share/draft.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/share/fork.svg b/app/share/fork.svg new file mode 100644 index 0000000..cdfbae0 --- /dev/null +++ b/app/share/fork.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/global_discount.svg b/app/share/global_discount.svg new file mode 100644 index 0000000..6a32645 --- /dev/null +++ b/app/share/global_discount.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/hot-dog.svg b/app/share/hot-dog.svg new file mode 100644 index 0000000..d391e2e --- /dev/null +++ b/app/share/hot-dog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/ice-cream.svg b/app/share/ice-cream.svg new file mode 100644 index 0000000..0466ffb --- /dev/null +++ b/app/share/ice-cream.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/kebab.svg b/app/share/kebab.svg new file mode 100644 index 0000000..dcaf9ed --- /dev/null +++ b/app/share/kebab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/menu.svg b/app/share/menu.svg new file mode 100644 index 0000000..f971e76 --- /dev/null +++ b/app/share/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/menu_section.svg b/app/share/menu_section.svg new file mode 100644 index 0000000..5f675ed --- /dev/null +++ b/app/share/menu_section.svg @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + Menu + + + + + + + + + + + + + + + + + + + + + + + + + + Menu + + diff --git a/app/share/milkshake.svg b/app/share/milkshake.svg new file mode 100644 index 0000000..a967b0c --- /dev/null +++ b/app/share/milkshake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/new_sale.svg b/app/share/new_sale.svg new file mode 100644 index 0000000..043b878 --- /dev/null +++ b/app/share/new_sale.svg @@ -0,0 +1,81 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/party.svg b/app/share/party.svg new file mode 100644 index 0000000..c106eeb --- /dev/null +++ b/app/share/party.svg @@ -0,0 +1,77 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/app/share/payment.svg b/app/share/payment.svg new file mode 100644 index 0000000..9bb0b7e --- /dev/null +++ b/app/share/payment.svg @@ -0,0 +1,91 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/payment_term.svg b/app/share/payment_term.svg new file mode 100644 index 0000000..b763967 --- /dev/null +++ b/app/share/payment_term.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/pizza-slice.svg b/app/share/pizza-slice.svg new file mode 100644 index 0000000..c6ad6db --- /dev/null +++ b/app/share/pizza-slice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/plus.svg b/app/share/plus.svg new file mode 100644 index 0000000..e249e58 --- /dev/null +++ b/app/share/plus.svg @@ -0,0 +1,61 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/app/share/pos-icon.ico b/app/share/pos-icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ab05f7a6c27965e2bc8ce93bb4f4970bfaf6aad1 GIT binary patch literal 31982 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 literal 0 HcmV?d00001 diff --git a/app/share/pos-icon.svg b/app/share/pos-icon.svg new file mode 100644 index 0000000..8a60771 --- /dev/null +++ b/app/share/pos-icon.svg @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/app/share/pos_banner.png b/app/share/pos_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..4ee72f64ff4321312dd99bab947e85dbe9442206 GIT binary patch literal 29661 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 literal 0 HcmV?d00001 diff --git a/app/share/position.svg b/app/share/position.svg new file mode 100644 index 0000000..b73a4ab --- /dev/null +++ b/app/share/position.svg @@ -0,0 +1,81 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/print_order.svg b/app/share/print_order.svg new file mode 100644 index 0000000..4a222de --- /dev/null +++ b/app/share/print_order.svg @@ -0,0 +1,77 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/app/share/print_sale.svg b/app/share/print_sale.svg new file mode 100644 index 0000000..5438f2d --- /dev/null +++ b/app/share/print_sale.svg @@ -0,0 +1,89 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/reservations.svg b/app/share/reservations.svg new file mode 100644 index 0000000..997fcf9 --- /dev/null +++ b/app/share/reservations.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/share/salesman.svg b/app/share/salesman.svg new file mode 100644 index 0000000..30aeaf5 --- /dev/null +++ b/app/share/salesman.svg @@ -0,0 +1,78 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/search_product.svg b/app/share/search_product.svg new file mode 100644 index 0000000..8e750fc --- /dev/null +++ b/app/share/search_product.svg @@ -0,0 +1,70 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/app/share/search_sale.svg b/app/share/search_sale.svg new file mode 100644 index 0000000..383603b --- /dev/null +++ b/app/share/search_sale.svg @@ -0,0 +1,81 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/share/table.svg b/app/share/table.svg new file mode 100644 index 0000000..5354cfe --- /dev/null +++ b/app/share/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/tables.svg b/app/share/tables.svg new file mode 100644 index 0000000..4605144 --- /dev/null +++ b/app/share/tables.svg @@ -0,0 +1,152 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/share/taco.svg b/app/share/taco.svg new file mode 100644 index 0000000..7838eaa --- /dev/null +++ b/app/share/taco.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/tea.svg b/app/share/tea.svg new file mode 100644 index 0000000..5e5e7c6 --- /dev/null +++ b/app/share/tea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/tip.svg b/app/share/tip.svg new file mode 100644 index 0000000..2651fe0 --- /dev/null +++ b/app/share/tip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/waffle.svg b/app/share/waffle.svg new file mode 100644 index 0000000..35953f3 --- /dev/null +++ b/app/share/waffle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/share/waiter.svg b/app/share/waiter.svg new file mode 100644 index 0000000..82235f3 --- /dev/null +++ b/app/share/waiter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/small_screen.css b/app/small_screen.css new file mode 100644 index 0000000..6ed94b1 --- /dev/null +++ b/app/small_screen.css @@ -0,0 +1,164 @@ + +QAbstractButton { + font-family: "DejaVu Sans"; + border-style: groove; + font: 12pt; + color: rgb(102, 102, 102); + background-color: rgb(242, 242, 242); + min-height: 50px; + min-width : 110px; + border-color: rgb(208, 208, 208); + border-width: 0px; +} + +#field_total_amount, #field_amount, #label_input, + #field_default, + #field_small_blue, + #field_small_gray, + #field_invoice { + background-color : white; + border-style : groove; + border-width : 0.5px; + border-color : rgb(208, 208, 208); +} + +#label_default { + font : 14pt; + color : rgb(102, 102, 102); + min-height : 10px; + min-width : 10px; +} + +#field_default { + font: bold 14pt; + min-height : 10px; +} + +#field_small_gray, #label_small_gray { + font : 16px; + color : rgb(54, 54, 54); +} + +#field_medium_gray, #label_medium_gray { + font : 24px; + color : rgb(54, 54, 54); +} + +#field_big_gray, #label_big_gray { + font : 32px; + color : rgb(54, 54, 54); +} + +#field_small_blue, #label_small_blue { + font : 16px; + color : rgb(0, 30, 80); +} + +#field_medium_blue, #label_medium_blue { + font : 24px; + color : rgb(0, 30, 80); +} + +#field_big_blue, #label_big_blue { + font : bold 32px; + color : rgb(0, 30, 80); +} + +#field_small_orange, #label_small_orange { + font : 16px; + color : rgb(235, 160, 15); +} + +#field_medium_orange, #label_medium_orange { + font : 24px; + color : rgb(235, 160, 15); +} + +#field_big_orange, #label_big_orange { + font : bold 32px; + color : rgb(235, 160, 15); +} + +QDialog { + min-height : 300px; + max-height : 400px; + min-width : 500px; +} + +#label_gray { + font: bold 14pt; + min-height : 10px; + min-width : 10px; + color: rgb(102, 102, 102); +} + +#label_blue { + font: bold 14pt; + color: rgb(17, 84, 102); + min-height : 10px; + min-width : 10px; +} + +#label_message { + font : 14pt; + min-height : 45px; + min-width : 10px; +} + +#field_invoice { + font : 15pt; + min-height : 45px; + min-width : 110px; +} + +#label_input { + font : 15pt; + min-height : 45px; + min-width : 200px; +} + +#field_sign { + font: bold 26pt; + min-height : 70px; + max-width: 100px; +} + +#field_amount { + font: 24pt; + min-height : 60px; + max-width: 310px; +} + +#field_total_amount, #label_total_amount { + color : rgb(50, 65, 75); +} + +#table_sale_lines { + color : rgb(70, 70, 70); + font : 11pt; + max-height: 380px; +} + +#label_product, #label_qty, #spin_box_qty, #row_field_price, + #label_price, #row_field_note { + font : 24px; + alignment : center; +} + +QSpinBox { + padding-right: 15px; + border-width: 3; + height: 40px; +} + +QSpinBox::up-button { + subcontrol-position: right; + height: 38px; + width: 38px; +} + +QSpinBox::down-button { + subcontrol-position: left; + height: 38px; + width: 38px; +} diff --git a/app/states.py b/app/states.py new file mode 100644 index 0000000..ea8b57f --- /dev/null +++ b/app/states.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +from re import compile + +# States of mainwindow +STATES = { + 'add': { + 'button': 'button_accept', + 're': compile(r'^(\*[0-9]*|[0-9]+)$|-|/|\*[0-9]*[.]*'), + }, + 'accept': { + 'button': 'button_cash', + 're': compile(r'^[0-9]+$'), + }, + 'cash': { + 'button': None, + 're': compile(r'^[0-9]+(,[0-9]{,2})?$') + }, + 'paid': { + 'button': None, + 're': compile(r'^(\*[0-9]*|[0-9]+)$'), + }, + 'cancel': { + 'button': None, + 're': compile(r'^(\*[0-9]*|[0-9]+)$'), + }, + 'disabled': { + 'button': None, + 're': compile(r'^(\*[0-9]*|[0-9]+)$'), + }, +} + +RE_SIGN = { + 'quantity': compile(r'\d+|\.\d+|\d+\.'), +} diff --git a/app/translations/i18n_es.qm b/app/translations/i18n_es.qm new file mode 100644 index 0000000000000000000000000000000000000000..420adf21b7206fc7102123bd5cfb7c724cc3c87d GIT binary patch literal 13013 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 + + + + ButtonsFunction + + + SEARCH + BUSCAR + + + + CUSTOMER + CLIENTE + + + + CANCEL + CANCELAR + + + + PRINT + IMPRIMIR + + + + SALESMAN + VENDEDOR + + + + GLOBAL DISCOUNT + DESCUENTO GLOBAL + + + + ORDER + ENV. ORDEN + + + + NEW SALE + NUEVA VENTA + + + + PAY MODE + MEDIO DE PAGO + + + + PAY TERM + PLAZO DE PAGO + + + + POSITION + POSICION + + + + NOTE + NOTA + + + + TIP + PROPINA + + + + TABLES + MESAS + + + + RESERVATIONS + RESERVACIONES + + + + S. SALE + B. VENTA + + + + WAITER + MESERO + + + + MainWindow + + + SYSTEM READY... + SISTEMA LISTO... + + + + DO YOU WANT TO EXIT? + DESEA SALIR? + + + + PLEASE CONFIRM YOUR PAYMENT TERM AS CREDIT? + POR FAVOR CONFIRMAR SI SU PLAZO DE PAGO ES CREDITO? + + + + SALE ORDER / INVOICE NUMBER NOT FOUND! + ORDER / FACTURA DE VENTA NO ENCONTRADA! + + + + THIS SALE IS CLOSED, YOU CAN NOT TO MODIFY! + ESTA VENTA ESTA CERRADA, Y USTED NO PUEDE MODIFICARLA! + + + + DISCOUNT VALUE IS NOT VALID! + EL DESCUENTO NO ES VALIDO! + + + + YOU CAN NOT ADD PAYMENTS TO SALE ON DRAFT STATE! + NO PUEDE AGREGAR PAGOS A UNA VENTA EN BORRADOR! + + + + ENTER QUANTITY... + INGRESE LA CANTIDAD... + + + + ENTER DISCOUNT... + INGRESE EL DESCUENTO... + + + + ENTER PAYMENT AMOUNT BY: %s + INGRESE EL VALOR DEL PAGO EN: %s + + + + ENTER NEW PRICE... + INGRESE EL NUEVO PRECIO... + + + + ORDER SUCCESUFULLY SENT. + ORDEN ENVIADA EXITOSAMENTE. + + + + FAILED SEND ORDER! + FALLO EL ENVIO DE LA ORDEN! + + + + MISSING AGENT! + FALTA EL AGENTE! + + + + THERE IS NOT SALESMAN FOR THE SALE! + NO SE DEFINIDO EL VENDEDOR EN LA VENTA! + + + + YOU CAN NOT CONFIRM A SALE WITHOUT PRODUCTS! + NO PUEDE CONFIRMAR UNA VENTA SIN PRODUCTOS! + + + + USER WITHOUT PERMISSION FOR SALE POS! + USUARIO SIN PERMISOS PARA VENTA POS! + + + + THE QUANTITY IS NOT VALID...! + LA CANTIDAD NO ES VALIDAD...! + + + + MISSING THE DEFAULT PARTY ON SHOP CONFIGURATION! + FALTA CONFIGURAR EL TERCERO EN LA TIENDA! + + + + MISSING SET THE JOURNAL ON DEVICE! + FALTA EL ESTADO DE CUENTA PARA LA CAJA! + + + + PRODUCT NOT FOUND! + PRODUCTO NO ENCONTRADO! + + + + DO YOU WANT CREATE NEW SALE? + DESEA CREAR UNA NUEVA VENTA? + + + + ARE YOU WANT TO CANCEL SALE? + DESEA CANCELAR LA VENTA? + + + + AGENT NOT FOUND! + AGENTE NO ENCONTRADO! + + + + COMMISSION NOT VALID! + LA COMISIÓN NO ES VÁLIDA! + + + + CREDIT LIMIT FOR CUSTOMER EXCEED! + EL CLIENTE SUPERA SU CUPO DE CREDITO! + + + + THE CUSTOMER CREDIT CAPACITY IS ABOVE 80% + EL CUPO DE CREDITO DEL CLIENTE ESTA SOBRE EL 80% + + + + YOU CAN NOT FORCE ASSIGN! + NO PUEDE FORZAR UNA ASIGACIÓN! + + + + INVOICE: + FACTURA: + + + + INVOICE + FACTURA + + + + PARTY + CLIENTE + + + + DATE + FECHA + + + + SALESMAN + VENDEDOR + + + + PAYMENT TERM + PLAZO DE PAGO + + + + No ORDER + No PEDIDO + + + + POSITION + POSICION + + + + AGENT + AGENTE + + + + DELIVERY CHARGE + CARGO DOMICILIO + + + + SUBTOTAL + SUBTOTAL + + + + TAXES + IMPUESTOS + + + + DISCOUNT + DESCUENTO + + + + TOTAL + TOTAL + + + + PAID + PAGADO + + + + CHANGE + CAMBIO + + + + SHOP + TIENDA + + + + DEVICE + CAJA + + + + DATABASE + BD + + + + USER + USUARIO + + + + PRINTER + IMPRESORA + + + + ID + ID + + + + NUMBER + NUMERO + + + + TOTAL AMOUNT + VALOR TOTAL + + + + SEARCH SALES... + BUSCAR VENTAS... + + + + CODE + CÓDIGO + + + + STOCK + INVENTARIO + + + + NAME + NOMBRE + + + + DESCRIPTION + DESCRIPCIÓN + + + + BRAND + MARCA + + + + PRICE + PRECIO + + + + LOCATION + LOCACIÓN + + + + IMAGE + IMAGEN + + + + ID NUMBER + NUMERO ID + + + + PHONE + TELÉFONO + + + + PAYMENT MODE: + MEDIO DE PAGO: + + + + SELECT PAYMENT MODE: + SELECCIONE EL MEDIO DE PAGO: + + + + WAREHOUSE + BODEGA + + + + QUANTITY + CANTIDAD + + + + STOCK BY PRODUCT: + INVENTARIO POR PRODUCTO: + + + + Id + Id + + + + Salesman + Vendedor + + + + CHOOSE SALESMAN + ESCOGE EL VENDEDOR + + + + CHOOSE TAX + ESCOJA EL IMPUESTO + + + + SELECT PAYMENT TERM + SELECCIONE EL MODO DE PAGO + + + + INVOICE NUMBER + NUMERO DE FACTURA + + + + TYPE + TIPO + + + + ORDER + PEDIDO + + + + INSERT PASSWORD FOR CANCEL + INGRESE LA CONTRASEÑA PARA CANCELAR + + + + GLOBAL DISCOUNT + DESCUENTO GLOBAL + + + + PASSWORD FORCE ASSIGN + CONTRASEÑA PARA FORZAR ASIGNACIÓN + + + + VOUCHER NUMBER + NÚMERO DE VOUCHER + + + + COMMISSION + COMISIÓN + + + + AMOUNT + VALOR + + + + COMMENTS + COMENTARIOS + + + + QUANTITY: + CANTIDAD: + + + + UNIT PRICE: + PRECIO UNITARIO: + + + + COD + COD + + + + UNIT + UND + + + + QTY + CANT + + + + DISC + DESC + + + + NOTE + NOTA + + + + UNIT PRICE W TAX + PRECIO UNIT CON IMP + + + + STATEMENT JOURNAL + ESTADO DE CUENTA + + + + THE USER HAVE NOT PERMISSIONS FOR ACCESS TO DEVICE! + EL USUARIO NO TIENE PERMISOS PARA ACCEDER A CAJA! + + + + THERE IS NOT A STATEMENT OPEN FOR THIS DEVICE! + NO HAY ESTADO DE CUENTA ABIERTOS POR ESTA CAJA! + + + + YOU HAVE NOT PERMISSIONS FOR DELETE THIS SALE! + NO TIENE PERMISOS PARA BORRAR ESTA VENTA! + + + + YOU HAVE NOT PERMISSIONS FOR CANCEL THIS SALE! + NO TIENE PERMISOS PARA CANCELAR LA VENTA! + + + + THE CUSTOMER HAS NOT CREDIT! + EL CLIENTE NO TIENE CREDITO! + + + + CUSTOMER + CLIENTE + + + + COMPANY + COMPAÑIA + + + + ADDRESS + DIRECCION + + + + SEARCH CUSTOMER + BUSCAR CLIENTE + + + + ASSIGNED TABLE + MESA ASIGNADA + + + + FIRST YOU MUST CREATE/LOAD A SALE! + PRIMERO DEBE AGREGAR/CARGAR UNA VENTA! + + + + FRACTION: + FRACCIÓN: + + + + FRAC + FRAC + + + + DO YOU WANT TO CONFIRM THE SEND ORDER? + DESEAS CONFIRMAR EL ENVIO DE LA ORDEN? + + + + ActionButton + + + &ACCEPT + &ACEPTAR + + + + &CANCEL + &CANCELAR + + + + FrontWindow + + + APPLICATION + APLICACION + + + + HelpDialog + + + Keys Shortcuts... + Atajos de Teclado... + + + + Action + Acción + + + + Shortcut + Atajo + + + + Login + + + HOST + SERVIDOR + + + + DATABASE + BASE DE DATOS + + + + USER + USUARIO + + + + PASSWORD + CONTRASEÑA + + + + C&ANCEL + C&ANCELAR + + + + &CONNECT + &CONECTAR + + + + Error: username or password invalid...! + Error: nombre de usuario o contraseña inválido! + + + + MenuButtons + + + Menu... + Menu... + + + + &ACCEPT + &ACCEPT + + + + &BACK + &BACK + + + + QuickDialog + + + Warning... + Advertencia... + + + + Information... + Información... + + + + Action... + Acción... + + + + Help... + Ayuda... + + + + Error... + Error... + + + + Question... + Pregunta... + + + + Selection... + Selección... + + + + Dialog... + Dialogo... + + + + SQLModel + + + Name + + + + + Salary + + + + + SearchDialog + + + Search Products... + Buscar Productos... + + + + SearchWindow + + + SEARCH... + BUSCAR... + + + + FILTER: + FILTRO: + + + + SelectionWindow + + + SEARCH... + BUSCAR... + + + + &ACCEPT + &ACEPTAR + + + + &RETURN + &VOLVER + + + + main + + + Enter your password: + Ingrese su password: + + + diff --git a/config_pos.ini b/config_pos.ini new file mode 100644 index 0000000..236dd16 --- /dev/null +++ b/config_pos.ini @@ -0,0 +1,59 @@ +[General] +#Valid protocols: xml, json, local +protocol=xml +server=127.0.0.1 +api_url=localhost:5070 +port=8000 +database=DEMO +user=admin + +######################################### +# Printer Interface and Printer name +# For Unix use eg: +# usb,/dev/usb/lp0 +# network,192.168.0.36 +# cups,EPSON-TM-T20 +# +# For Windows use just namep printer eg: +# usb,SATPOS +# +########################################## +printer_sale_name=usb,/dev/usb/lp0 + +# ROW CHARACTERS: 33 / 48 / 28 +# --- EPSON TM-T20 = 33 +# --- TALLY DT-230 = 48 +# --- SAT = 42 +row_characters=48 + +# Define mode to print receipt: automatic or manually +print_receipt=automatic + +# Define electronic scale is used for set quantity +active_weighing=False + +# True / False +print_order=False + +print_auto_order=False + +auto_print_commission=False + +active_timeout=True + +timeout=10000 + +tablet_mode=False + +# Mode of window: fullscreen / maximized +mode_window=maximized + +locale=es_CO.UTF-8 + +language=es_CO + +# Options: retail / restaurant +enviroment=retail + + +profile_name=MY DEMO diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..f0eb28b --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,33 @@ +Tryton POS Client (Using Qt5 and Python3) +========================================= + +# FOR WINDOWS + +Download and install python-3.5.2-amd64 + +Download and install Visual C++ Build Tools 2013 + +Download and install swigwin-3.0.12 + +Add swig.exe to Windows PATH + + +> python -m pip install -U pip + +> pip install -U setuptools + +> pip install -U virtualenv + +> pip install PyQt5 + +> downlod TortoiseHG instalador mercurial + +Copy config_pos.ini on user AppData/Local/tryton + +Modify config_pos.ini + +Put launcher on desktop and set icon change +name "pospro.pyw" + + + diff --git a/doc/translation_guide.rst b/doc/translation_guide.rst new file mode 100644 index 0000000..8ff897f --- /dev/null +++ b/doc/translation_guide.rst @@ -0,0 +1,19 @@ +# Three steps for translating Qt App + +From main directory: + +1. Clean obsolete translations: + + pylupdate5 -noobsolete project.pro + +2. Do translate .ts file, opening linguist tool: + + /usr/lib/qt5/bin/linguist + +3. Release translation + + lrelease app/translations/i18n_es.ts + + + + diff --git a/pospro b/pospro new file mode 100755 index 0000000..e724d7a --- /dev/null +++ b/pospro @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import os +import sys + +from PyQt5.QtWidgets import QApplication +from PyQt5.QtCore import QTranslator +from neox.commons.dblogin import Login +from app import mainwindow + +try: + DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, + '..', '..', '..'))) + if os.path.isdir(DIR): + sys.path.insert(0, os.path.dirname(DIR)) +except NameError: + pass + +locale_app = os.path.join(os.path.abspath( + os.path.dirname(__file__)), 'app', 'translations', 'i18n_es.qm') + + +class Client(object): + + def __init__(self, parent=None): + self.app = QApplication(sys.argv) + self.translator = QTranslator() + self.translator.load(locale_app) + self.app.installTranslator(self.translator) + + def init_login(self): + login = Login(file_config='config_pos.ini') + + while not login.connection: + login.run() + login.exec_() + + return login.connection, login.params + + def main(self, conn, params): + mw = mainwindow.MainWindow(conn, params) + self.app.exec_() + + +client = Client() +conn, params = client.init_login() + +if conn: + client.main(conn, params) +sys.exit() diff --git a/pospro.pyw b/pospro.pyw new file mode 100755 index 0000000..d5856a9 --- /dev/null +++ b/pospro.pyw @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import os +import sys + +from PyQt5.QtWidgets import QApplication, QStyleFactory +from PyQt5.QtCore import QTranslator + +from neox.commons.dblogin import Login +from app import mainwindow + +try: + DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, + '..', '..', '..'))) + if os.path.isdir(DIR): + sys.path.insert(0, os.path.dirname(DIR)) +except NameError: + pass + + +class Client(object): + + def __init__(self, parent=None): + self.app = QApplication(sys.argv) + path_trans = os.path.join(os.path.abspath( + os.path.dirname(__file__)), 'app', 'translations', 'i18n_es.qm') + self.translator = QTranslator() + self.translator.load(path_trans) + # This is to make Qt use locale configuration; i.e. Standard Buttons + # in your system's language. + self.app.installTranslator(self.translator) + self.app.setStyle(QStyleFactory.create('Fusion')) + + def init_login(self): + login = Login(file_config='config_pos.ini') + + while not login.connection: + login.run() + login.exec_() + + return login.connection, login.params + + def main(self, conn, params): + mw = mainwindow.MainWindow(conn, params) + self.app.exec_() + + +client = Client() +conn, params = client.init_login() + +if conn: + client.main(conn, params) +sys.exit() diff --git a/posproc b/posproc new file mode 100644 index 0000000000000000000000000000000000000000..b3166a5b9823d57068ed2361b2de31686c1b0ef9 GIT binary patch literal 640 zcmYLFO>fjN5Pg$mx7*Nk*)2#tAdYCoVQ<_}g#;Y9RQ&)IMMzQDnBtJwD|?VwuKNS{ z5&S!@z4QiSH(MfS=K0O@%(K&*BL6vRKCCc&6UndS_qP%nS%eNK?=hjI$S3F$yx$lo zpiR+cJ22XTG1MNKbhk2WN7zzWlL$C<~|p?IGGJwqp4ZO7O#gKP4z4F4u2+w^Yak=DN(%SlJ)oEo|%MOdj5;)YY=Tws4c%Di=+nn=@4CE61^yP^Rt` z?~CA6z+306i^s%j6yMp6f-~!1NAvM6+Gwsido44=@Rc