Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

128 changed files with 49568 additions and 62763 deletions

22
.gitignore vendored
View File

@ -2,37 +2,23 @@
__pycache__/
*.py[cod]
*$py.class
Pipfile*
# Django stuff:
*.log
local_settings.py
conf.py
main.ini
source/fixtures
source/media
# Sphinx documentation
docs/
*.ods
*.xlsx
bk/
source/docs/
site/
vedev/
# Virtualenv
.env/
virtual/
env
docs/build
cache/
credenciales.conf
*.sqlite
*.sql
*.service
*.orig
rfc.db
Dockerfile
chuletas/
configpac.py

View File

@ -1,840 +1,3 @@
v 2.3.3 [27-Jun-2024]
---------------------
- Fix: Retención de RESICOS para Personas Morales.
v 2.3.2 [10-Abr-2024]
---------------------
- Fix: En las mercancías en la Carta Porte al generar el PDF.
v 2.3.1 [02-Abr-2024]
---------------------
- Fix: En la cantidad de la mercancia en la Carta Porte.
v 2.3.0 [01-Abr-2024]
---------------------
- Mejora: Soporte para complemento Carta Porte 3.0
- **IMPORTANTE**: Aunque no lo uses, esto afecta al JS de facturación, por
lo que tienes que forzar el refresco (CTRL+F5) si tienes algún problema.
v 2.2.0 [24-Ene-2024]
---------------------
- Mejora: Soporte para complemento Comercio Exterior 2.0
- **IMPORTANTE**: Aunque no lo uses, esto afecta al JS de facturación, por
lo que tienes que forzar el refresco (CTRL+F5) si tienes algún problema.
v 2.1.0 [26-Dic-2023]
---------------------
- Mejora: Se agrega filtro por día en facturas.
v 2.0.9 [20-Dic-2023]
---------------------
- Fix: Issue 98 y 107
v 2.0.8 [30-Oct-2023]
---------------------
- Fix: Permitir generar CFDI de egreso para facturas globales sin datos globales.
- Fix: Permitir cambiar la zona horaria para cuando se usa en servidor.
Es necesario hacer una migración:
```
cd /opt/empresa-libre
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 2.0.7 [06-Ju1-2023]
---------------------
- Fix: En tasa de retención de un Resico a una Persona Moral.
v 2.0.6 [14-Jun-2023]
---------------------
- Fix: Al generar complementos de pago con facturas con retención de impuestos.
v 2.0.5 [05-Jun-2023]
---------------------
- Fix: Al generar complementos de pago con facturas con varios impuestos.
- Fix: ticket #103, hay que usar {receptor.correo_facturas} y {receptor.telefonos}
v 2.0.2 [01-Abr-2023]
---------------------
- Fix: Obtener la clave del sat al facturar por lote.
v 2.0.1 [29-Mar-2023]
---------------------
- Fix ticket #97
Es necesario hacer una migración:
```
cd /opt/empresa-libre
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 2.0.0 [08-Ene-2023]
----------------------
- Liberamos para todos la versión CFDI 4.0
- **IMPORTANTE** NO intentes timbrar si **antes** no has validado en nuestro demo que puedes timbrar tus CFDIs habituales.
* IMPORTANTE:
Es necesario hacer una migración:
```
cd /opt/empresa-libre
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
Y reiniciar todo. IMPORTANTE: dependiendo desde que versión actualices, tal vez debas de hacer algún prodecimiento extra. Siempre revisa este historial.
v 2.0.0 [31-Mar-2022]
----------------------
- Primera versión de timbrado con CFDI4
- **IMPORTANTE** NO intentes timbrar si **antes** no has validado en nuestro demo que puedes timbrar tus CFDIs habituales.
v 1.47.0 [28-Mar-2022]
----------------------
- Mejora: Soporte basico para complemento Comercio Exterior.
v 1.46.5 [10-Mar-2022]
----------------------
- Error: Al timbrar nómina con Comercio Digital
v 1.46.4 [18-Feb-2022]
----------------------
- Error: Issue #54
v 1.46.3 [15-Feb-2022]
----------------------
- Error: Issue #53
v 1.46.2 [31-Ene-2022]
----------------------
- Error: Al generar Carta Porte sin remolque.
- Error: Al cancelar con Finkok.
v 1.46.1 [29-Ene-2022]
----------------------
- Error: Issue #49
- Mejora: Agregar validación para distancia en origen de Carta Porte.
v 1.46.0 [27-Ene-2022]
----------------------
- Mejora: Issue #45
- Mejora: Agregar tipos de persimos SCT para Carta Porte
- Mejora: Buscar estado y municipio por CP en Carta Porte
v 1.45.4 [25-Ene-2022]
----------------------
- Error: Al timbrar carta porte.
- Error: Al cancelar con Comercio Digital.
- Error: Issue #42
- Mejora: Issue #44
* IMPORTANTE: Es necesario subir de nuevo tus certificados de sello, **solo** si timbras con Comercio Digital.
v 1.45.3 [23-Ene-2022]
----------------------
- Error: El enviar por correo CFDI de pago. Ticket #40
v 1.45.2 [21-Ene-2022]
----------------------
- Error: Al cancelar un CFDI
v 1.45.1 [20-Ene-2022]
----------------------
- Error: Al enviar correos con la nueva configuración
* IMPORTANTE: Revisa tu configuración de correo para verificar que todo funcione.
v 1.45.0 [20-Ene-2022]
----------------------
- Importar Carta Porte desde archivo JSON
v 1.44.2 [19-Ene-2022]
----------------------
- Agregar opción STARTTLS que requieren algunos servidores de correo
* IMPORTANTE: Revisa tu configuración de correo para verificar si tienes que usar esta opción.
v 1.44.1 [19-Ene-2022]
----------------------
- Correciones en generación de Carta Porte v2.0
- Plantilla para representación impresa de Carta Porte v2.0
v 1.44.0 [10-Ene-2022]
----------------------
- Soporte para Carta Porte v2 con CFDI 3.3
- Soporte para el nuevo esquema de cancelación del SAT
* IMPORTANTE:
Es necesario hacer una migración:
```
cd /opt/empresa-libre
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 1.43.0 [12-Dic-2021]
----------------------
- Soporte para entradas de almacen.
- Soporte para multi almacen.
- Soporte para regenerar un ticket.
- Soporte para movimientos entre almacenes
- Consulta en SAT para estatus de nómina
* IMPORTANTE:
Instalar nuevo requerimiento:
```
pip install segno
```
Es necesario hacer una migración:
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 1.42.2 [14-Sep-2021]
----------------------
- Los productos pueden no llevar ningún impuesto.
v 1.42.1 [01-Jun-2021]
----------------------
- Error - Ticket #5
v 1.42.0 [24-May-2021]
----------------------
- Opción para que solo un admin pueda cancelar.
v 1.41.4 [13-Abr-2021]
----------------------
- Error - Ticket #4
v 1.41.3 [12-Feb-2021]
----------------------
- Error - Ticket #3
v 1.41.2 [12-Feb-2021]
----------------------
- Error - Ticket #2
v 1.41.1 [11-Feb-2021]
----------------------
- Error - Ticket #1
v 1.41.0 [10-Feb-2021]
----------------------
- Importar clientes desde archivo CSV
v 1.40.1 [09-Feb-2021]
----------------------
- Fix #422
v 1.40.0 [05-ene-2021]
----------------------
- Error: Al parsear XML en Python 3.9+
- Mejora: Agregar versión de Empresa Libre a plantilla.
- Mejora: Sellado en memoria
- Mejora: Se agrega un segundo PAC y se refactoriza el timbrado.
* **IMPORTANTE**
Es necesario seguir una serie de pasos **obligatorios** para migrar a esta
versión, **no continues hasta seguir paso a paso** estas instrucciones.
**Antes** de comenzar ten a la mano tus certificados de sello para timbrar, es
necesario subirlos de nuevo. **NO actualices si no tienes tus certificados**
con su respectiva contraseña, te quedarás sin poder timbrar.
Estas instrucciones solamente aplican para la maquina virtual con Ubuntu Server,
la ultima y única versión sobre la que se dará soporte.
1. Entra a la parte administrativa y toma de tus credenciales de timbrado en el
menú "Emisor" ficha "Otros Datos", usuario y token de timbrado.
1. Agregar nuevos requerimientos `sudo apt install pkgconf libxml2-dev libxmlsec1-dev libxmlsec1-openssl`
1. Agregar nuevo requerimiento `pip install --user xmlsec`
1. Entrar a la carpeta del sistema: `/opt/empresa-libre`
1. Actualizar `git pull origin master`
1. Entrar a `source/app/controllers/pacs/comerciodigital` y copiar `conf.py.example` a `conf.py`
1. Entrar a `source/app/controllers/pacs/finkok` y copiar `conf.py.example` a `conf.py`
1. Agregar la variable `TOKEN = ''` al archivo `source/app/conf.py` mira el archivo de ejemplo en el mismo directorio.
1. Reiniciar el servicio: `sudo systemctl restart empresalibre`
1. Si no ves los cambios descritos a continuación, fuerza el refresco de tu navegador, generalmente con **CTRL+F5**
1. Sube de nuevo tus certificados en el menú "Emisor" ficha "Certificado".
1. Ve al menú "Opciones", ficha "Otros".
1. Selecciona tu PAC, si tu usuario es un correo electrónico, invariablemente
debes seleccionar Finkok. Si tienes duda ponte en contacto con nosotros.
1. Establece las credenciales del punto 1.
1. Guarda los datos.
v 1.39.1 [17-sep-2020]
----------------------
- Error: Esquema para complemento IEDU
v 1.39.0 [25-ago-2020]
----------------------
- Mejora: Consulta de las facturas de pago en el SAT
- Mejora: Mostrar totales en nómina
- Mejora: Mostrar totales por cantidad al facturar
- Mejora: Validar líneas con importe cero antes de facturar
- Error: Validaciones de namespace en CFDI de nómina por parte del PAC
v 1.38.1 [30-mar-2020]
----------------------
- Error: En nómina al timbrar asimilados
v 1.38.0 [08-mar-2020]
----------------------
- Mejora: Factura global por ticket o nota
- Error: Al generar algunos PDFs
v 1.37.0 [02-mar-2020]
----------------------
- Mejora: Soporte para complemento Leyendas Fiscales
* IMPORTANTE:
Es necesario hacer una migración, y agregar los campos necesarios para mostrar
las leyendas, mira la carpeta pública con la plantilla de ejemplo.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 1.36.0 [25-feb-2020]
----------------------
- Mejora: Permitir ver a usuarios todos los documentos
v 1.35.0 [24-feb-2020]
----------------------
- Mejora: Filtrado de documentos por sucursal
v 1.34.1 [09-feb-2020]
----------------------
- Error: Al timbrar nómina de asimilados
v 1.34.0 [29-ene-2020]
----------------------
- Error: Al timbrar nómina
v 1.33.2 [27-ene-2020]
----------------------
- Se actualizan métodos de pago en catálogos del SAT
* IMPORTANTE:
Es necesario actualizar los catálogos del SAT
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -us
```
v 1.33.0 [22-ene-2020]
----------------------
- Mejora: Cambio del mensaje para cuando se intenta dar de alta un cliente ya existente.
- Mejora: Solo los admins pueden ver la nómina.
- Se agrega un segundo PAC
- Se actualizan los catálogos del SAT
* IMPORTANTE:
Es necesario actualizar los catálogos del SAT
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -us
```
v 1.32.0 [05-ene-2020]
----------------------
- Mejora: Recuperar facturas no aceptadas para cancelación por el receptor
v 1.31.2 [28-oct-2019]
----------------------
- Error: Al generar PDF con tags en las series
v 1.31.1 [28-ago-2019]
----------------------
- Error: Al agregar nuevo impuesto #369
v 1.31.0 [23-abr-2019]
----------------------
- Error: Validar cantidad o valor unitario cero en tickets
- Mejora: Envío de nómina por correo al empleado
v 1.30.0 [24-mar-2019]
----------------------
- Mejora: Se actualizan los catálogos de Nómina
* IMPORTANTE:
Es necesario realizar una migración, después de actualizar.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
v 1.29.0 [08-mar-2019]
----------------------
- Error: Al mostrar detalle en facturas importadas #343
- Mejora: Editar la descripción de un movimiento bancario.
v 1.28.2 [04-mar-2019]
----------------------
- Error: Al enviar facturas de pago
v 1.28.1 [04-mar-2019]
----------------------
- Error: Al buscar facturas por fechas
v 1.28.0 [17-feb-2019]
----------------------
- Mejora: Manejo de empaques para mensajeria
- Mejora: Usar concepto personalizado en deducciones de nómina 004 Otros
- Mejora: Búsqueda en notas
- Mejora: Soporte para el complemento de Divisas
- Mejora: Descarga de nómina en lote
* IMPORTANTE:
Es necesario realizar una migración, después de actualizar.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
Es necesario agregar un nuevo requerimiento.
```
sudo pip install cryptography
```
**IMPORTANTE** Si envias tus facturas por correo directamente, es necesario
volver a capturar la contraseña de tu servidor de correo y guardar los datos
nuevamente.
v 1.27.1 [23-ene-2019]
----------------------
- Error: Al cancelar nómina
v 1.27.0 [17-ene-2019]
----------------------
- Mejora: Permitir capturar folio manualmente
v 1.26.1 [16-ene-2019]
----------------------
- Error: Guardar logos de emisor
v 1.26.0 [15-ene-2019]
----------------------
- Mejora: Generar PDF desde plantilla JSON
v 1.25.0 [07-nov-2018]
----------------------
- Fix: Al importar días de pago en nómina
- Fix: Al importar descripción en factura en lote
v 1.24.0 [06-nov-2018]
----------------------
- Fix: Consulta estatus SAT
v 1.23.0 [30-oct-2018]
----------------------
- Mejora: Permitir importar CFDI 3.2
v 1.22.0 [25-oct-2018]
----------------------
- Mejora: Permitir cambiar descripción al facturar en lote
v 1.21.2 [23-oct-2018]
----------------------
- Error unicode
v 1.21.1 [14-oct-2018]
----------------------
- Error #307
v 1.21.0 [12-oct-2018]
----------------------
- Error #287
- Mejora: Complemento de pago con datos de cuentas
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar.
v 1.20.0 [08-oct-2018]
----------------------
- Error #295
- Mejora: Cuentas de banco para clientes
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar.
v 1.19.1 [03-oct-2018]
----------------------
- Error #291
- Error al generar PDF de factura de pago con relacionados sin serie
- Error al relacionar facturas versión 3.2
v 1.19.0 [28-sep-2018]
----------------------
- Mejora #280
- Mejora #288
- Error #290
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
v 1.18.0 [27-sep-2018]
----------------------
- Fix #282 Factura de pago en otras monedas
v 1.17.0 [25-sep-2018]
----------------------
- Fix - Al generar factura de pago con documentos relacionados en otras monedas
- Fix - Al generar factura de pago sin serie en documentos relacionados
- Fix #278
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
v 1.16.1 [18-sep-2018]
----------------------
- Error https://gitlab.com/mauriciobaeza/empresa-libre/issues/268
- IMPORTANTE: Actualizar si usas cuatro decimales en impuestos
v 1.16.0 [16-sep-2018]
----------------------
- Se puede editar el saldo de un cliente
- Se muestra la cantidad de facturas de pago en los movimientos
v 1.15.0 [12-sep-2018]
----------------------
- Se pueden mover las facturas con doble clic en los movimientos de banco.
- Fix - Al sumar las facturas en los depósitos
- Fix - Al importar los pedimentos en facturas por lotes
- Fix - Al guardar los datos del emisor
v 1.14.0 [10-sep-2018]
----------------------
- Personalizar plantilla para factura de pago
- Fix - Mostrar serie y folio capturado para factura de pago
- Fix - Agregar nueva cuenta de banco
v 1.13.0 [10-sep-2018]
----------------------
- Cancelar factura de pago
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
v 1.12.0 [31-ago-2018]
----------------------
- Soporte para facturas (complemento) de pago.
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
v 1.11.1 [21-ago-2018]
----------------------
- Fix - Quitar columna en tabla facturaspagos.
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -m
```
v 1.11.0 [25-jul-2018]
----------------------
- Se cambia la forma de consultar los folios restantes. Es indispensable actualizar a esta versión para ver tus timbres restantes.
v 1.10.0 [10-jul-2018]
----------------------
- Ahora se pueden manejar precios con cuatro decimales.
v 1.9.3 [08-jul-2018]
---------------------
- Fix: Al refacturar conceptos con descuento
v 1.9.2 [05-jul-2018]
---------------------
- Fix: Al generar el reporte de facturas en PDF
v 1.9.1 [25-jun-2018]
---------------------
- Fix: Al mostrar el título de la aplicación
- Se agrega el registro de acción al borrar una factura
v 1.9.0 [18-jun-2018]
---------------------
- Se agrega la vista del detalle de facturas
- Fix: Al timbrar nómina
v 1.8.1 [14-jun-2018]
---------------------
- Fix: Se agrega una barra de desplazamiento al buscar productos o clientes
- Se cambia el servidor de consulta de timbres
v 1.8.0 [03-jun-2018]
---------------------
- Se permiten 4 decimales en Tipo de cambio
- Se agrega el campo {total_cantidades} al generar el PDF
- Se agrega opción para generar respaldos de la BD en MV
- Fix: Al generar con complemento EDU
v 1.7.0 [23-may-2018]
---------------------
- Se agrega soporte para truncar impuestos locales, para las estulticias de los "ingenieros" de las dependencias de gobierno
v 1.6.1 [09-abr-2018]
---------------------
- Fix: Nómina con separación
v 1.6.0 [18-feb-2018]
---------------------
- Facturacion a extranjeros
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -m
```
v 1.5.0 [30-ene-2018]
---------------------
- Timbrado de Nómina
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -m
```
v 1.4.0 [01-ene-2018]
---------------------
- Impresión de tickets
v 1.3.0 [27-Dic-2017]
---------------------
- Punto de venta
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -m
```
v 1.2.0 [18-Dic-2017]
---------------------
* IMPORTANTE: Es necesario actualizar la base de datos, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bd
```
v 0.1.0 [26-Oct-2017]
---------------------
- Generar y timbrar con CFDI 3.3

View File

@ -13,3 +13,9 @@
* Propon nuevas funcionalidades
* Difunde Empresa Libre
### Prioridades
1. Darle continuidad a la facturación de los clientes
2. Cubrir todas las funcionalidades que cubre ahora Factura Libre
3. Agregar nuevas funcionalidades

View File

@ -8,53 +8,21 @@
Este proyecto está en continuo desarrollo, contratar un esquema de soporte,
nos ayuda a continuar su desarrollo. Ponte en contacto con nosotros para
contratar: administracion ARROBA empresalibre.net
contratar.
#### Ahora también puede aportar con criptomonedas:
G1: `A5DdXxCKPw3QKWVdDVs7CzkNugNUW1sHu5zDJFWxCU2h`
BCH: `qztd3l00xle5tffdqvh2snvadkuau2ml0uqm4n875d`
## Requerimientos:
### Requerimientos:
* Servidor web, recomendado Nginx
* uwsgi
* python 3.8
* xsltproc
* openssl
* xmlsec
* Ubuntu 20.04
```
apt install pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl xsltproc
```
* python3
Debería de funcionar con cualquier combinación servidor-wsgi que soporte
aplicaciones Python.
El sistema tiene soporte solo para PostgreSQL, debes de instalar el servidor de
la base de datos y su driver respectivo.
El sistema tiene soporte para tres bases de datos: SQLite, MySQL y PostgreSQL
(recomendado), debes de instalar el servidor de la base de datos y sus drivers
respectivos, excepto SQLite que es nativo en Python.
## Configuración para desarrollo local
* Crea un entorno virtual con python 3.8 y actívalo. Por ejemplo con virtualenv:
virtualenv .venv
source .venv/bin/activate
* Instala las dependencias
pip install -r requirements.txt
* Copia y ajusta algunos archivos necesarios.
- `source/app/conf.py`
- `source/app/controllers/pacs/comerciodigital/conf.py.example`
- `source/app/controllers/pacs/finkok/conf.py.example`
* Finalmente ejecutamos la aplicación. Para esto vamos a necesitar un servidor
wsgi como uwsgi:
pip install uwsgi
cd source/app
uwsgi main_debug.ini
* Ahora puedes ver la aplicación en http://localhost:8000

View File

@ -1,3 +0,0 @@
[ ] Permitir más de un remolque en la Carta Porte
[ ] Campos de captura de Comercio Exterior
[ ] Representación impresa de Comercio Exterior

View File

@ -1 +0,0 @@
2.3.3

View File

@ -1,17 +1,14 @@
falcon==1.4.1
falcon
falcon-multipart
Beaker
Mako
peewee==2.10.2
peewee
Click
logbook
bcrypt
python-dateutil
zeep
chardet
pyqrcode
pypng
reportlab
psycopg2-binary
cryptography==3.4.8
xmlsec
segno
# python-escpos

View File

@ -1,21 +1,10 @@
#!/usr/bin/env python
from peewee import SqliteDatabase
DEBUG = False
MV = False
DEBUG = True
ID_SUPPORT = ''
# ~ Es la contraseña predeterminada de los usuarios admin y superadmin al crear
# ~ una nueva base de datos, personaliza por la que quieras.
DEFAULT_PASSWORD = 'salgueiro3.3'
TITLE_APP = 'Empresa Libre'
#~ Establece una ruta accesible para el servidor web
LOG_PATH = '/var/log/empresalibre/empresa-libre.log'
# ~ Establece un token personalizado para encriptar las claves
# ~ from secrets import token_hex
# ~ token_hex(32)
# ~ IMPORTANTE: Cada vez que cambies este valor, debes de subir de nuevo
# ~ tus certificados
TOKEN = ''
DATABASE = None
if DEBUG:
DATABASE = SqliteDatabase('empresalibre.sqlite')

View File

@ -1,47 +1,25 @@
#!/usr/bin/env python3
# ~ Empresa Libre
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.net)
# ~
# ~ 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 <http://www.gnu.org/licenses/>.
#!/usr/bin/env python
import datetime
from xml.etree import ElementTree as ET
from xml.dom.minidom import parseString
from dateutil import parser
from logbook import Logger
#~ from settings import DEBUG
log = Logger('XML')
CFDI_ACTUAL = 'cfdi40'
CFDI_ACTUAL = 'cfdi33'
NOMINA_ACTUAL = 'nomina12'
PUBLIC = 'PUBLICO EN GENERAL'
CFDI_EGRESO = 'E'
DEFAULT = {
'exportacion': '01',
}
SAT = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'cfdi40': {
'version': '4.0',
'cfdi32': {
'version': '3.2',
'prefix': 'cfdi',
'xmlns': 'http://www.sat.gob.mx/cfd/4',
'schema': 'http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd',
'xmlns': 'http://www.sat.gob.mx/cfd/3',
'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd',
},
'cfdi33': {
'version': '3.3',
@ -49,79 +27,18 @@ SAT = {
'xmlns': 'http://www.sat.gob.mx/cfd/3',
'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd',
},
'cfdi32': {
'version': '3.2',
'prefix': 'cfdi',
'xmlns': 'http://www.sat.gob.mx/cfd/3',
'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd',
},
'nomina11': {
'version': '1.1',
'prefix': 'nomina',
'xmlns': 'http://www.sat.gob.mx/nomina',
'schema': ' http://www.sat.gob.mx/nomina http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina11.xsd',
'schema': 'http://www.sat.gob.mx/nomina http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina11.xsd',
},
'nomina': {
'nomina12': {
'version': '1.2',
'prefix': 'nomina12',
'prefix': 'nomina',
'xmlns': 'http://www.sat.gob.mx/nomina12',
'schema': ' http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd',
'schema': 'http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd',
},
'locales': {
'version': '1.0',
'prefix': 'implocal',
'xmlns': 'http://www.sat.gob.mx/implocal',
'schema': ' http://www.sat.gob.mx/implocal http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd',
},
'donativo': {
'version': '1.1',
'prefix': 'donat',
'xmlns': 'http://www.sat.gob.mx/donat',
'schema': ' http://www.sat.gob.mx/donat http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd',
'leyenda': 'Este comprobante ampara un donativo, el cual será destinado por la donataria a los fines propios de su objeto social. En el caso de que los bienes donados hayan sido deducidos previamente para los efectos del impuesto sobre la renta, este donativo no es deducible. La reproducción no autorizada de este comprobante constituye un delito en los términos de las disposiciones fiscales.',
},
'ine': {
'version': '1.1',
'prefix': 'ine',
'xmlns': 'http://www.sat.gob.mx/ine',
'schema': ' http://www.sat.gob.mx/ine http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd',
},
'edu': {
'version': '1.0',
'prefix': 'iedu',
'xmlns': 'http://www.sat.gob.mx/iedu',
'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd',
},
'pagos': {
'version': '2.0',
'prefix': 'pago20',
'xmlns': 'http://www.sat.gob.mx/Pagos20',
'schema': ' http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd',
},
'divisas': {
'version': '1.0',
'prefix': 'divisas',
'xmlns': 'http://www.sat.gob.mx/divisas',
'schema': ' http://www.sat.gob.mx/divisas http://www.sat.gob.mx/sitio_internet/cfd/divisas/divisas.xsd',
},
'leyendas': {
'version': '1.0',
'prefix': 'leyendasFisc',
'xmlns': 'http://www.sat.gob.mx/leyendasFiscales',
'schema': ' http://www.sat.gob.mx/leyendasFiscales http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd',
},
'cartaporte': {
'version': '3.0',
'prefix': 'cartaporte30',
'xmlns': 'http://www.sat.gob.mx/CartaPorte30',
'schema': ' http://www.sat.gob.mx/CartaPorte30 http://www.sat.gob.mx/sitio_internet/cfd/CartaPorte/CartaPorte30.xsd',
},
'comercioe': {
'version': '2.0',
'prefix': 'cce20',
'xmlns': 'http://www.sat.gob.mx/ComercioExterior20',
'schema': ' http://www.sat.gob.mx/ComercioExterior20 http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior20/ComercioExterior20.xsd',
}
}
@ -132,19 +49,6 @@ class CFDI(object):
self._xsi = SAT['xsi']
self._pre = self._sat_cfdi['prefix']
self._cfdi = None
self._complemento = None
self._impuestos_locales = False
self._donativo = False
self._ine = False
self._edu = False
self._pagos = False
self._is_nomina = False
self._leyendas = False
self._carta_porte = False
self._comercio_exterior = False
self._divisas = ''
self._tipo_de_comprobante = ''
self._exportacion = DEFAULT['exportacion']
self.error = ''
def _now(self):
@ -155,27 +59,18 @@ class CFDI(object):
return ''
self._comprobante(datos['comprobante'])
if 'global' in datos:
self._informacion_global(datos['global'])
self._relacionados(datos['relacionados'])
self._emisor(datos['emisor'])
self._receptor(datos['receptor'])
self._conceptos(datos['conceptos'])
self._impuestos(datos['impuestos'])
self._locales(datos['impuestos'])
self._donatarias(datos['donativo'])
self._complementos(datos['complementos'])
if 'nomina' in datos:
self._nomina(datos['nomina'])
if 'complementos' in datos:
self._complementos(datos['complementos'])
return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
xml = self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
return xml
def add_sello(self, sello, cert_txt):
def add_sello(self, sello):
self._cfdi.attrib['Sello'] = sello
self._cfdi.attrib['Certificado'] = cert_txt
return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
def _to_pretty_xml(self, source):
@ -184,169 +79,65 @@ class CFDI(object):
return xml
def _validate(self, datos):
if datos['impuestos']:
if datos['impuestos']['total_locales_trasladados'] or \
datos['impuestos']['total_locales_retenciones']:
self._impuestos_locales = True
if datos['donativo']:
self._donativo = True
self._edu = datos.get('edu', False)
if datos['complementos']:
if 'ine' in datos['complementos']:
self._ine = True
self._pagos = bool(datos['complementos'].get('pagos', False))
self._leyendas = bool(datos['complementos'].get('leyendas', False))
self._carta_porte = bool(datos['complementos'].get('cartaporte', False))
self._comercio_exterior = bool(datos['complementos'].get('comercioe', False))
if self._comercio_exterior:
self._exportacion = datos['complementos']['comercioe'].pop('Exportacion')
self._divisas = datos['comprobante'].pop('divisas', '')
if 'nomina' in datos:
self._is_nomina = True
return self._validate_nomina(datos)
return True
def _validate_nomina(self, datos):
comprobante = datos['comprobante']
validators = (
('MetodoDePago', 'NA'),
('TipoCambio', '1'),
('Moneda', 'MXN'),
('TipoDeComprobante', 'egreso'),
)
for f, v in validators:
if f in comprobante:
if v != comprobante[f]:
msg = 'El atributo: {}, debe ser: {}'.format(f, v)
self.error = msg
return False
return True
def _comprobante(self, datos):
attributes = {}
attributes['xmlns:{}'.format(self._pre)] = self._sat_cfdi['xmlns']
attributes['xmlns:xsi'] = self._xsi
schema_locales = ''
if self._impuestos_locales:
name = 'xmlns:{}'.format(SAT['locales']['prefix'])
attributes[name] = SAT['locales']['xmlns']
schema_locales = SAT['locales']['schema']
schema_donativo = ''
if self._donativo:
name = 'xmlns:{}'.format(SAT['donativo']['prefix'])
attributes[name] = SAT['donativo']['xmlns']
schema_donativo = SAT['donativo']['schema']
schema_ine = ''
if self._ine:
name = 'xmlns:{}'.format(SAT['ine']['prefix'])
attributes[name] = SAT['ine']['xmlns']
schema_ine = SAT['ine']['schema']
schema_edu = ''
if self._edu:
name = 'xmlns:{}'.format(SAT['edu']['prefix'])
attributes[name] = SAT['edu']['xmlns']
schema_edu = SAT['edu']['schema']
schema_divisas = ''
if self._divisas:
name = 'xmlns:{}'.format(SAT['divisas']['prefix'])
attributes[name] = SAT['divisas']['xmlns']
schema_divisas = SAT['divisas']['schema']
schema_nomina = ''
if self._is_nomina:
name = 'xmlns:{}'.format(SAT['nomina']['prefix'])
attributes[name] = SAT['nomina']['xmlns']
schema_nomina = SAT['nomina']['schema']
schema_pagos = ''
if self._pagos:
name = 'xmlns:{}'.format(SAT['pagos']['prefix'])
attributes[name] = SAT['pagos']['xmlns']
schema_pagos = SAT['pagos']['schema']
schema_leyendas = ''
if self._leyendas:
name = 'xmlns:{}'.format(SAT['leyendas']['prefix'])
attributes[name] = SAT['leyendas']['xmlns']
schema_leyendas = SAT['leyendas']['schema']
schema_carta_porte = ''
if self._carta_porte:
name = 'xmlns:{}'.format(SAT['cartaporte']['prefix'])
attributes[name] = SAT['cartaporte']['xmlns']
schema_carta_porte = SAT['cartaporte']['schema']
schema_comercioe = ''
if self._comercio_exterior:
name = 'xmlns:{}'.format(SAT['comercioe']['prefix'])
attributes[name] = SAT['comercioe']['xmlns']
schema_carta_porte = SAT['comercioe']['schema']
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
schema_locales + schema_donativo + schema_ine + schema_edu + \
schema_divisas + schema_nomina + schema_pagos + schema_leyendas + \
schema_carta_porte + schema_comercioe
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema']
attributes.update(datos)
#~ if DEBUG:
#~ attributes['Fecha'] = self._now()
#~ attributes['NoCertificado'] = CERT_NUM
if not 'Version' in attributes:
attributes['Version'] = self._sat_cfdi['version']
if not 'Fecha' in attributes:
attributes['Fecha'] = self._now()
# ~ cfdi4
if not 'Exportacion' in attributes:
attributes['Exportacion'] = self._exportacion
self._tipo_de_comprobante = attributes['TipoDeComprobante']
self._cfdi = ET.Element('{}:Comprobante'.format(self._pre), attributes)
return
def _informacion_global(self, datos):
if not datos:
return
node_name = '{}:InformacionGlobal'.format(self._pre)
node = ET.SubElement(self._cfdi, node_name, datos)
return
def _relacionados(self, datos):
if not datos or not datos['tipo'] or not datos['cfdis']:
return
node_name = '{}:CfdiRelacionados'.format(self._pre)
value = {'TipoRelacion': datos['tipo']}
node = ET.SubElement(self._cfdi, node_name, value)
for uuid in datos['cfdis']:
node_name = '{}:CfdiRelacionado'.format(self._pre)
value = {'UUID': uuid}
ET.SubElement(node, node_name, value)
return
def _emisor(self, datos):
#~ if DEBUG:
#~ datos['Rfc'] = RFC_TEST
node_name = '{}:Emisor'.format(self._pre)
emisor = ET.SubElement(self._cfdi, node_name, datos)
return
def _receptor(self, datos):
receptor_name = datos['Nombre'].upper()
if receptor_name == PUBLIC and self._tipo_de_comprobante == CFDI_EGRESO:
receptor_name = datos['Nombre']
datos['Nombre'] = receptor_name
node_name = '{}:Receptor'.format(self._pre)
emisor = ET.SubElement(self._cfdi, node_name, datos)
return
def _conceptos(self, datos):
from xml.sax.saxutils import escape, unescape
conceptos = ET.SubElement(self._cfdi, '{}:Conceptos'.format(self._pre))
# ~ for row in reversed(datos):
for row in datos:
complemento = {}
if 'complemento' in row:
complemento = row.pop('complemento')
cuenta_predial = row.pop('CuentaPredial', '')
pedimento = row.pop('Pedimento', '')
student = row.pop('student', '')
taxes = {}
if 'impuestos' in row:
@ -370,26 +161,36 @@ class CFDI(object):
ET.SubElement(
retenciones, '{}:Retencion'.format(self._pre), retencion)
if pedimento:
attributes = {'NumeroPedimento': pedimento}
node_name = '{}:InformacionAduanera'.format(self._pre)
ET.SubElement(concepto, node_name, attributes)
if 'InformacionAduanera' in row:
for field in fields:
if field in row['InformacionAduanera']:
attributes[field] = row['InformacionAduanera'][field]
if attributes:
node_name = '{}:InformacionAduanera'.format(self._pre)
ET.SubElement(concepto, node_name, attributes)
if cuenta_predial:
attributes = {'Numero': cuenta_predial}
if 'CuentaPredial' in row:
attributes = {'numero': row['CuentaPredial']}
node_name = '{}:CuentaPredial'.format(self._pre)
ET.SubElement(concepto, node_name, attributes)
if student:
if 'autRVOE' in row:
fields = (
'version',
'nombreAlumno',
'CURP',
'nivelEducativo',
'autRVOE',
)
for field in fields:
if field in row['autRVOE']:
attributes[field] = row['autRVOE'][field]
node_name = '{}:ComplementoConcepto'.format(self._pre)
complemento = ET.SubElement(concepto, node_name)
ET.SubElement(complemento, 'iedu:instEducativas', student)
ET.SubElement(complemento, 'iedu:instEducativas', attributes)
return
def _impuestos(self, datos):
if self._is_nomina or not datos:
return
if not datos:
node_name = '{}:Impuestos'.format(self._pre)
ET.SubElement(self._cfdi, node_name)
@ -400,10 +201,6 @@ class CFDI(object):
for field in fields:
if field in datos:
attributes[field] = datos[field]
if not attributes:
return
node_name = '{}:Impuestos'.format(self._pre)
impuestos = ET.SubElement(self._cfdi, node_name, attributes)
@ -419,276 +216,93 @@ class CFDI(object):
return
def _nomina(self, datos):
pre = SAT['nomina']['prefix']
sat_nomina = SAT[NOMINA_ACTUAL]
pre = sat_nomina['prefix']
complemento = ET.SubElement(self._cfdi, '{}:Complemento'.format(self._pre))
if self._complemento is None:
self._complemento = ET.SubElement(
self._cfdi, '{}:Complemento'.format(self._pre))
emisor = datos.pop('emisor', None)
receptor = datos.pop('receptor', None)
percepciones = datos.pop('percepciones', None)
deducciones = datos.pop('deducciones', None)
otros_pagos = datos.pop('otros_pagos', ())
incapacidades = datos.pop('incapacidades', ())
nomina = ET.SubElement(
self._complemento, '{}:Nomina'.format(pre), datos['nomina'])
if emisor:
ET.SubElement(nomina, '{}:Emisor'.format(pre), emisor)
if receptor:
node = ET.SubElement(nomina, '{}:Receptor'.format(pre), receptor)
if percepciones:
details = percepciones.pop('details', None)
hours_extra = percepciones.pop('hours_extra', None)
separacion = percepciones.pop('separacion', None)
if details:
node = ET.SubElement(nomina, '{}:Percepciones'.format(pre), percepciones)
for row in details:
nodep = ET.SubElement(node, '{}:Percepcion'.format(pre), row)
if row['TipoPercepcion'] == '019' and hours_extra:
for he in hours_extra:
ET.SubElement(nodep, '{}:HorasExtra'.format(pre), he)
hours_extra = None
if separacion:
ET.SubElement(node, '{}:SeparacionIndemnizacion'.format(pre), separacion)
if deducciones:
details = deducciones.pop('details', None)
if details:
deducciones = ET.SubElement(nomina, '{}:Deducciones'.format(pre), deducciones)
for row in details:
ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row)
if otros_pagos:
node = ET.SubElement(nomina, '{}:OtrosPagos'.format(pre))
for row in otros_pagos:
subsidio = row.pop('subsidio', None)
subnode = ET.SubElement(node, '{}:OtroPago'.format(pre), row)
if subsidio:
ET.SubElement(subnode, '{}:SubsidioAlEmpleo'.format(pre), subsidio)
if incapacidades:
node = ET.SubElement(nomina, '{}:Incapacidades'.format(pre))
for row in incapacidades:
ET.SubElement(node, '{}:Incapacidad'.format(pre), row)
return
def _locales(self, datos):
if not self._impuestos_locales:
return
if self._complemento is None:
self._complemento = ET.SubElement(
self._cfdi, '{}:Complemento'.format(self._pre))
emisor = datos.pop('Emisor', None)
receptor = datos.pop('Receptor', None)
percepciones = datos.pop('Percepciones', None)
deducciones = datos.pop('Deducciones', None)
attributes = {}
attributes['version'] = SAT['locales']['version']
if not datos['total_locales_trasladados']:
datos['total_locales_trasladados'] = '0.00'
attributes['TotaldeTraslados'] = datos['total_locales_trasladados']
if not datos['total_locales_retenciones']:
datos['total_locales_retenciones'] = '0.00'
attributes['TotaldeRetenciones'] = datos['total_locales_retenciones']
node = ET.SubElement(
self._complemento, 'implocal:ImpuestosLocales', attributes)
for retencion in datos['locales_retenciones']:
ET.SubElement(node, 'implocal:RetencionesLocales', retencion)
for traslado in datos['locales_trasladados']:
ET.SubElement(node, 'implocal:TrasladosLocales', traslado)
return
def _donatarias(self, datos):
if not datos:
return
if self._complemento is None:
self._complemento = ET.SubElement(
self._cfdi, '{}:Complemento'.format(self._pre))
attributes = {}
attributes['version'] = SAT['donativo']['version']
attributes['leyenda'] = SAT['donativo']['leyenda']
attributes['xmlns:{}'.format(pre)] = sat_nomina['xmlns']
attributes['xsi:schemaLocation'] = sat_nomina['schema']
attributes.update(datos)
node = ET.SubElement(self._complemento, 'donat:Donatarias', attributes)
return
def _complemento_comercio_exterior(self, datos):
prefix = SAT['comercioe']['prefix']
emisor = datos.pop('emisor')
propietarios = datos.pop('propietarios', {})
receptor = datos.pop('receptor')
destinatario = datos.pop('destinatario', {})
mercancias = datos.pop('mercancias')
attr = {'Version': SAT['comercioe']['version']}
attr.update(datos)
ce = ET.SubElement(
self._complemento, f'{prefix}:ComercioExterior', attr)
attributes = {}
if 'Curp' in emisor:
attributes = {'Curp': emisor.pop('Curp')}
node = ET.SubElement(ce, '{}:Emisor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), emisor)
attributes = {}
if 'NumRegIdTrib' in receptor:
attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
node = ET.SubElement(ce, '{}:Receptor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), receptor)
node = ET.SubElement(ce, '{}:Mercancias'.format(prefix))
fields = ('Marca', 'Modelo', 'SubModelo', 'NumeroSerie')
for row in mercancias:
detalle = {}
for f in fields:
if f in row and row[f]:
detalle[f] = row[f]
row.pop(f)
concepto = ET.SubElement(node, '{}:Mercancia'.format(prefix), row)
if detalle:
ET.SubElement(
concepto, '{}:DescripcionesEspecificas'.format(prefix), detalle)
if not 'Version' in attributes:
attributes['Version'] = sat_nomina['version']
nomina = ET.SubElement(complemento, '{}:Nomina'.format(pre), attributes)
if emisor:
ET.SubElement(nomina, '{}:Emisor'.format(pre), emisor)
if receptor:
ET.SubElement(nomina, '{}:Receptor'.format(pre), receptor)
if percepciones:
detalle = percepciones.pop('detalle', None)
percepciones = ET.SubElement(nomina, '{}:Percepciones'.format(pre), percepciones)
for row in detalle:
ET.SubElement(percepciones, '{}:Percepcion'.format(pre), row)
if deducciones:
detalle = deducciones.pop('detalle', None)
deducciones = ET.SubElement(nomina, '{}:Deducciones'.format(pre), deducciones)
for row in detalle:
ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row)
return
def _complementos(self, datos):
if not datos:
return
complemento = ET.SubElement(self._cfdi, '{}:Complemento'.format(self._pre))
if 'ce' in datos:
pre = 'cce11'
datos = datos.pop('ce')
emisor = datos.pop('emisor')
propietario = datos.pop('propietario')
receptor = datos.pop('receptor')
destinatario = datos.pop('destinatario')
conceptos = datos.pop('conceptos')
if self._complemento is None:
self._complemento = ET.SubElement(
self._cfdi, '{}:Complemento'.format(self._pre))
attributes = {}
attributes['xmlns:{}'.format(pre)] = \
'http://www.sat.gob.mx/ComercioExterior11'
attributes['xsi:schemaLocation'] = \
'http://www.sat.gob.mx/ComercioExterior11 ' \
'http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior11/ComercioExterior11.xsd'
attributes.update(datos)
ce = ET.SubElement(
complemento, '{}:ComercioExterior'.format(pre), attributes)
if self._carta_porte:
datos = datos['cartaporte']
ubicaciones = datos.pop('ubicaciones')
mercancias = datos.pop('mercancias', ())
tiposfigura = datos.pop('tiposfigura', ())
attributes = {}
if 'Curp' in emisor:
attributes = {'Curp': emisor.pop('Curp')}
node = ET.SubElement(ce, '{}:Emisor'.format(pre), attributes)
ET.SubElement(node, '{}:Domicilio'.format(pre), emisor)
autotransporte = datos.pop('autotransporte', {})
identificacion = autotransporte.pop('identificacion')
seguros = autotransporte.pop('seguros')
remolques = autotransporte.pop('remolques')
if propietario:
ET.SubElement(ce, '{}:Propietario'.format(pre), propietario)
atributos = {'Version': SAT['cartaporte']['version']}
atributos.update(datos)
attributes = {}
if 'NumRegIdTrib' in receptor:
attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
node = ET.SubElement(ce, '{}:Receptor'.format(pre), attributes)
ET.SubElement(node, '{}:Domicilio'.format(pre), receptor)
prefix = SAT['cartaporte']['prefix']
node_carta = ET.SubElement(self._complemento, f'{prefix}:CartaPorte', atributos)
node = ET.SubElement(node_carta, f'{prefix}:Ubicaciones')
for ubicacion in ubicaciones:
domicilio = ubicacion.pop('domicilio', {})
dt = parser.parse(ubicacion['FechaHoraSalidaLlegada'])
ubicacion['FechaHoraSalidaLlegada'] = dt.isoformat()[:19]
sub_node = ET.SubElement(node, f'{prefix}:Ubicacion', ubicacion)
if domicilio:
ET.SubElement(sub_node, f'{prefix}:Domicilio', domicilio)
attr = mercancias
mercancias = attr.pop('mercancias')
node = ET.SubElement(node_carta, f'{prefix}:Mercancias', attr)
for mercancia in mercancias:
ET.SubElement(node, f'{prefix}:Mercancia', mercancia)
sub_node = ET.SubElement(node, f'{prefix}:Autotransporte', autotransporte)
ET.SubElement(sub_node, f'{prefix}:IdentificacionVehicular', identificacion)
ET.SubElement(sub_node, f'{prefix}:Seguros', seguros)
if remolques:
node_remolques = ET.SubElement(sub_node, f'{prefix}:Remolques')
for remolque in remolques:
ET.SubElement(node_remolques, f'{prefix}:Remolque', remolque)
if tiposfigura:
sub_node = ET.SubElement(node_carta, f'{prefix}:FiguraTransporte')
for figura in tiposfigura:
ET.SubElement(sub_node, f'{prefix}:TiposFigura', figura)
if self._divisas:
atributos = {
'version': SAT['divisas']['version'],
'tipoOperacion': self._divisas,
}
ET.SubElement(self._complemento, 'divisas:Divisas', atributos)
if 'ine' in datos:
atributos = {'Version': SAT['ine']['version']}
ine_key_entidad = datos['ine'].pop('ClaveEntidad', '')
ine_ambito = datos['ine'].pop('Ambito', '')
if ine_key_entidad:
ine_id_conta = datos['ine'].pop('IdContabilidad', '')
atributos.update(datos['ine'])
node_ine = ET.SubElement(self._complemento, 'ine:INE', atributos)
if ine_key_entidad:
attr = {'ClaveEntidad': ine_key_entidad}
if ine_ambito:
attr['Ambito'] = ine_ambito
node_entidad = ET.SubElement(node_ine, 'ine:Entidad', attr)
attr = {'IdContabilidad': ine_id_conta}
ET.SubElement(node_entidad, 'ine:Contabilidad', attr)
if 'pagos' in datos:
datos = datos.pop('pagos')
totales = datos.pop('totales')
relacionados = datos.pop('relacionados')
taxes_pay = datos.pop('taxes_pay')
pre = SAT['pagos']['prefix']
attributes = {'Version': SAT['pagos']['version']}
pagos = ET.SubElement(
self._complemento, '{}:Pagos'.format(pre), attributes)
ET.SubElement(pagos, '{}:Totales'.format(pre), totales)
node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), datos)
for row in relacionados:
taxes = row.pop('taxes')
node = ET.SubElement(node_pago, f'{pre}:DoctoRelacionado', row)
nodex_tax = None
if taxes['traslados'] or taxes['retenciones']:
node_tax = ET.SubElement(node, f'{pre}:ImpuestosDR')
if taxes['retenciones']:
node = ET.SubElement(node_tax, f'{pre}:RetencionesDR')
for tax in taxes['retenciones']:
ET.SubElement(node, f'{pre}:RetencionDR', tax)
if taxes['traslados']:
node = ET.SubElement(node_tax, f'{pre}:TrasladosDR')
for tax in taxes['traslados']:
ET.SubElement(node, f'{pre}:TrasladoDR', tax)
if taxes_pay['traslados'] or taxes_pay['retenciones']:
node_tax = ET.SubElement(node_pago, f'{pre}:ImpuestosP')
if taxes_pay['retenciones']:
node = ET.SubElement(node_tax, f'{pre}:RetencionesP')
for key, importe in taxes_pay['retenciones'].items():
attr = {'ImpuestoP': key, 'ImporteP': importe}
ET.SubElement(node, f'{pre}:RetencionP', attr)
if taxes_pay['traslados']:
node = ET.SubElement(node_tax, f'{pre}:TrasladosP')
for key, tax in taxes_pay['traslados'].items():
ET.SubElement(node, f'{pre}:TrasladoP', tax)
if 'leyendas' in datos:
pre = SAT['leyendas']['prefix']
attributes = {'version': SAT['leyendas']['version']}
node_leyend = ET.SubElement(
self._complemento, '{}:LeyendasFiscales'.format(pre), attributes)
for leyend in datos['leyendas']:
ET.SubElement(node_leyend, '{}:Leyenda'.format(pre), leyend)
if self._comercio_exterior:
datos = datos.pop('comercioe')
self._complemento_comercio_exterior(datos)
attributes = {}
if 'NumRegIdTrib' in destinatario:
attributes = {'NumRegIdTrib': destinatario.pop('NumRegIdTrib')}
if 'Nombre' in destinatario:
attributes.update({'Nombre': destinatario.pop('Nombre')})
node = ET.SubElement(ce, '{}:Destinatario'.format(pre), attributes)
ET.SubElement(node, '{}:Domicilio'.format(pre), destinatario)
node = ET.SubElement(ce, '{}:Mercancias'.format(pre))
fields = ('Marca', 'Modelo', 'SubModelo', 'NumeroSerie')
for row in conceptos:
detalle = {}
for f in fields:
if f in row:
detalle[f] = row.pop(f)
concepto = ET.SubElement(node, '{}:Mercancia'.format(pre), row)
if detalle:
ET.SubElement(
concepto, '{}:DescripcionesEspecificas'.format(pre), detalle)
return

View File

@ -1,14 +1,10 @@
#!/usr/bin/env python3
import io
import os
#~ import falcon
import re
import smtplib
import ssl
import collections
from xml.sax.saxutils import escape
from collections import OrderedDict
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
@ -16,12 +12,6 @@ from email.mime.text import MIMEText
from email import encoders
from email.utils import formatdate
import requests
try:
from escpos import printer
except ImportError:
printer = None
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
@ -32,9 +22,6 @@ from reportlab.platypus import Paragraph, Table, TableStyle, Spacer
from reportlab.pdfgen import canvas
CANCEL = False
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
class CaseInsensitiveDict(collections.MutableMapping):
"""A case-insensitive ``dict``-like object.
@ -114,20 +101,12 @@ class NumLet(object):
def letras(self):
return self._letras.upper()
def _letters(self, numero, currency='MXN'):
# ~ print (currency)
monedas = {
'MXN': 'peso',
'USD': 'dólar',
'EUR': 'euro',
}
moneda = monedas.get(currency, currency)
tf = {
'MXN': 'm.n.',
}
#~ def _letters(self, numero, moneda='peso', texto_inicial='-(',
#~ texto_final='/100 m.n.)-', fraccion_letras=False, fraccion=''):
def _letters(self, numero, moneda='peso'):
texto_inicial = '-('
texto_final = '/100 {})-'.format(tf.get(currency, currency))
texto_final = '/100 m.n.)-'
fraccion_letras = False
fraccion = ''
@ -269,18 +248,10 @@ class SendMail(object):
def _login(self):
try:
if self._config['ssl'] and self._config['starttls']:
self._server = smtplib.SMTP(
self._config['servidor'],
self._config['puerto'], timeout=10)
self._server.ehlo()
self._server.starttls()
self._server.ehlo()
elif self._config['ssl']:
if self._config['ssl']:
self._server = smtplib.SMTP_SSL(
self._config['servidor'],
self._config['puerto'], timeout=10)
self._server.ehlo()
else:
self._server = smtplib.SMTP(
self._config['servidor'],
@ -291,7 +262,6 @@ class SendMail(object):
if '535' in str(e):
self._error = 'Nombre de usuario o contraseña inválidos'
return False
# ~ print (e)
if '534' in str(e) and 'gmail' in self._config['servidor']:
self._error = 'Necesitas activar el acceso a otras ' \
'aplicaciones en tu cuenta de GMail'
@ -312,15 +282,10 @@ class SendMail(object):
message['CC'] = options['copia']
message['Subject'] = options['asunto']
message['Date'] = formatdate(localtime=True)
if options['confirmar']:
message['Disposition-Notification-To'] = message['From']
message.attach(MIMEText(options['mensaje'], 'html'))
for f in options['files']:
part = MIMEBase('application', 'octet-stream')
if isinstance(f[0], str):
part.set_payload(f[0].encode('utf-8'))
else:
part.set_payload(f[0])
part.set_payload(f[0])
encoders.encode_base64(part)
part.add_header(
'Content-Disposition',
@ -344,7 +309,6 @@ class SendMail(object):
class NumberedCanvas(canvas.Canvas):
X = 20.59 * cm
XC = 21.6 * cm / 2 + 1.5 * cm
Y = 1.5 * cm
def __init__(self, *args, **kwargs):
@ -362,33 +326,19 @@ class NumberedCanvas(canvas.Canvas):
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number(page_count)
self.draw_cancel()
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
return
def draw_page_number(self, page_count):
self.setFont('Helvetica', 8)
# ~ self.setFillColor(colors.darkred)
text = f'Página {self._pageNumber} de {page_count}'
self.setFillColor(colors.darkred)
text = 'Página {} de {}'.format(self._pageNumber, page_count)
self.drawRightString(self.X, self.Y, text)
text = 'Este documento es una representación impresa de un CFDI'
self.drawCentredString(self.XC, self.Y, text)
text = 'Factura elaborada con software libre'
text = 'Factura elaborada con software libre: www.empresalibre.net'
self.drawString(1.5 * cm, 1.5 * cm, text)
return
def draw_cancel(self):
global CANCEL
if CANCEL:
self.rotate(45)
self.setFont('Helvetica', 100)
self.setFillColor(colors.red)
text = 'Cancelada'
self.drawCentredString(20 * cm, 3 * cm, text)
return
class TemplateInvoice(BaseDocTemplate):
@ -437,9 +387,6 @@ class TemplateInvoice(BaseDocTemplate):
def _emisor(self, styles, data):
logo_path = data.pop('logo', '')
logo_style = styles.pop('logo', {})
logo_path2 = data.pop('logo2', '')
logo_style2 = styles.pop('logo2', {})
sucursales = styles.pop('sucursales', {})
for k, v in styles.items():
self._set_text(styles[k], data.get(k, ''))
@ -450,28 +397,6 @@ class TemplateInvoice(BaseDocTemplate):
for k in keys:
rect[k] = rect[k] * cm
self.canv.drawImage(logo_path, **rect)
if logo_path2 and logo_style2:
rect = logo_style2['rectangulo']
keys = ('x', 'y', 'width', 'height')
for k in keys:
rect[k] = rect[k] * cm
self.canv.drawImage(logo_path2, **rect)
if sucursales:
for k, sucursal in sucursales.items():
values = sucursal.pop('textos')
x = sucursal['rectangulo']['x']
y = sucursal['rectangulo']['y']
w = sucursal['rectangulo']['width']
h = sucursal['rectangulo']['height']
for v in values:
self._set_text(sucursal, v)
y -= h
sucursal['rectangulo']['x'] = x
sucursal['rectangulo']['y'] = y
sucursal['rectangulo']['width'] = w
sucursal['rectangulo']['height'] = h
return
def _receptor(self, styles, data):
@ -493,7 +418,6 @@ class TemplateInvoice(BaseDocTemplate):
def _comprobante1(self, styles, data):
title = styles.pop('titulo', {})
self.canv.setTitle(f"Factura {data.get('seriefolio', '')}")
for k, v in styles.items():
self._set_text(styles[k], data.get(k, ''))
@ -525,12 +449,6 @@ class TemplateInvoice(BaseDocTemplate):
fields = ('valorunitario', 'importe')
if field in fields:
return self._currency(value)
if field == 'descripcion':
html = '<font color="black" size=7>{}</font>'
style_bt = getSampleStyleSheet()['BodyText']
return Paragraph(html.format(value), style_bt)
return value
def _conceptos(self, conceptos):
@ -566,8 +484,8 @@ class TemplateInvoice(BaseDocTemplate):
('GRID', (0, 0), (-1, -1), 0.05 * cm, colors.white),
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
('FONTSIZE', (0, 0), (-1, -1), 8),
('BACKGROUND', (1, 0), (-1, -1), colors.lightgrey),
('TEXTCOLOR', (1, 0), (-1, -1), colors.black),
('BACKGROUND', (1, 0), (-1, -1), colors.linen),
('TEXTCOLOR', (1, 0), (-1, -1), colors.darkred),
('FACE', (1, 0), (-1, -1), 'Helvetica-Bold'),
]
table = Table(rows, colWidths=widths, spaceBefore=0.25*cm)
@ -575,7 +493,7 @@ class TemplateInvoice(BaseDocTemplate):
return table
def _comprobante2(self, styles, data):
leyendas = styles.pop('leyendas', {})
leyenda = styles.pop('leyenda', {})
ls = []
for k, v in styles.items():
@ -583,27 +501,17 @@ class TemplateInvoice(BaseDocTemplate):
if 'spaceBefore' in v['estilo']:
v['estilo']['spaceBefore'] = v['estilo']['spaceBefore'] * cm
ps = ParagraphStyle(**v['estilo'])
p = Paragraph(escape(data[k]), ps)
ls.append(p)
elif k=='formametodopago':
ps = ParagraphStyle(**v['estilo'])
v = f"{data['formadepago']} - {data['metododepago']}"
p = Paragraph(v, ps)
ls.append(p)
elif k=='monedatipocambio':
ps = ParagraphStyle(**v['estilo'])
v = f"{data['moneda']} - {data['tipocambio']}"
p = Paragraph(v, ps)
p = Paragraph(data[k], ps)
ls.append(p)
cbb = Image(data['cbb'])
cbb = Image(data['path_cbb'])
cbb.drawHeight = 4 * cm
cbb.drawWidth = 4 * cm
style_bt = getSampleStyleSheet()['BodyText']
style_bt.leading = 8
html_t = '<b><font size=6>{}</font></b>'
html = '<font color="black" size=5>{}</font>'
html = '<font color="darkred" size=5>{}</font>'
msg = 'Cadena original del complemento de certificación digital del SAT'
rows = [
(cbb, Paragraph(html_t.format('Sello Digital del CFDI'), style_bt)),
@ -619,13 +527,13 @@ class TemplateInvoice(BaseDocTemplate):
('FONTSIZE', (0, 0), (-1, -1), 6),
('SPAN', (0, 0), (0, -1)),
('FACE', (1, 0), (1, 0), 'Helvetica-Bold'),
('BACKGROUND', (1, 1), (1, 1), colors.lightgrey),
('BACKGROUND', (1, 1), (1, 1), colors.linen),
('TEXTCOLOR', (1, 1), (1, 1), colors.darkred),
('FACE', (1, 2), (1, 2), 'Helvetica-Bold'),
('BACKGROUND', (1, 3), (1, 3), colors.lightgrey),
('BACKGROUND', (1, 3), (1, 3), colors.linen),
('TEXTCOLOR', (1, 3), (1, 3), colors.darkred),
('FACE', (1, 4), (1, 4), 'Helvetica-Bold'),
('BACKGROUND', (1, 5), (1, 5), colors.lightgrey),
('BACKGROUND', (1, 5), (1, 5), colors.linen),
('TEXTCOLOR', (1, 5), (1, 5), colors.darkred),
('ALIGN', (0, 0), (0, 0), 'CENTER'),
('VALIGN', (0, 0), (0, 0), 'MIDDLE'),
@ -634,14 +542,14 @@ class TemplateInvoice(BaseDocTemplate):
table.setStyle(TableStyle(table_styles))
ls.append(table)
if leyendas:
if 'spaceBefore' in leyendas['estilo']:
leyendas['estilo']['spaceBefore'] = \
leyendas['estilo']['spaceBefore'] * cm
for t in leyendas['textos']:
ps = ParagraphStyle(**leyendas['estilo'])
p = Paragraph(t, ps)
ls.append(p)
if leyenda:
if 'spaceBefore' in leyenda['estilo']:
leyenda['estilo']['spaceBefore'] = \
leyenda['estilo']['spaceBefore'] * cm
msg = 'Este documento es una representación impresa de un CFDI'
ps = ParagraphStyle(**leyenda['estilo'])
p = Paragraph(msg, ps)
ls.append(p)
return ls
@ -657,10 +565,8 @@ class TemplateInvoice(BaseDocTemplate):
return self._data
@data.setter
def data(self, values):
global CANCEL
# ~ print (values)
#~ print (values)
self._data = values
CANCEL = self._data['cancelada']
rows = self._conceptos(self._data['conceptos'])
widths = [2 * cm, 9 * cm, 1.5 * cm, 2 * cm, 2 * cm, 3 * cm]
@ -670,13 +576,13 @@ class TemplateInvoice(BaseDocTemplate):
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FACE', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('BACKGROUND', (0, 0), (-1, 0), colors.darkred),
('FONTSIZE', (0, 1), (-1, -1), 7),
('VALIGN', (0, 1), (-1, -1), 'TOP'),
('ALIGN', (0, 1), (0, -1), 'CENTER'),
('ALIGN', (2, 1), (2, -1), 'CENTER'),
('ALIGN', (3, 1), (5, -1), 'RIGHT'),
('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.grey),
('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.darkred),
('LINEBEFORE', (0, 1), (-1, -1), 0.05 * cm, colors.white),
]
table_conceptos = Table(rows, colWidths=widths, repeatRows=1)
@ -686,7 +592,7 @@ class TemplateInvoice(BaseDocTemplate):
comprobante = self._comprobante2(
self._custom_styles['comprobante'], self.data['comprobante'])
self._rows = [Spacer(0, 5.7 * cm), table_conceptos, totales] + comprobante
self._rows = [Spacer(0, 6*cm), table_conceptos, totales] + comprobante
def render(self):
frame = Frame(self.leftMargin, self.bottomMargin,
@ -818,303 +724,3 @@ class ReportTemplate(BaseDocTemplate):
return
class SeaFileAPI(object):
FILE_DOES_NOT_EXIST = 441
def __init__(self, url, username, password):
self._url = url
self._headers = self._get_auth(username, password)
@property
def is_connect(self):
return bool(self._headers)
def _open(self, path):
return open(path, 'rb')
def _get_auth(self, username, password):
url = self._url + 'auth-token/'
data = {
'username': username,
'password': password,
}
resp = requests.post(url, data=data)
if resp.status_code != requests.codes.ok:
msg = 'Token Error: {}'.format(resp.status_code)
print (msg)
return {}
headers = {'Authorization': 'Token {}'.format(resp.json()['token'])}
return headers
def _get_upload_link(self, repo_id):
if not self._headers:
return ''
url = '{}repos/{}/upload-link/'.format(self._url, repo_id)
resp = requests.get(url, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Error: {}'.format(resp.status_code)
print (msg)
return ''
return resp.json()
def _get_update_link(self, repo_id):
if not self._headers:
return ''
url = '{}repos/{}/update-link/'.format(self._url, repo_id)
data = {'p': '/'}
resp = requests.get(url, data=data, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Error: {}'.format(resp.status_code)
print (msg)
return ''
return resp.json()
def _decrypt(self, repo_id, password):
if not self._headers:
return False
url = '{}repos/{}/'.format(self._url, repo_id)
data = {'password': password}
resp = requests.post(url, data=data, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Error: {}'.format(resp.status_code)
print (msg)
return False
return True
def upload_file(self, path_file, repo_id, relative_path, password=''):
if not self._headers:
return False
if password:
if not self._decrypt(repo_id, password):
return False
upload_link = self._get_upload_link(repo_id)
if isinstance(path_file, tuple):
obj_file = path_file[0]
filename = path_file[1]
else:
obj_file = self._open(path_file)
_, filename = self._info_path(path_file)
data = {
# ~ 'filename': filename,
'parent_dir': relative_path,
'relative_path': '',
}
files = {'file': (filename, obj_file)}
resp = requests.post(
upload_link, data=data, files=files, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Upload Code: {}\n{}'.format(resp.status_code, resp.text)
print (msg)
return False
return True
def _info_path(self, path):
path, filename = os.path.split(path)
return path, filename
def list_directory(self, repo_id):
if not self._headers:
return False
url = '{}repos/{}/dir/'.format(self._url, repo_id)
params = {'p': '/', 't': 'd', 'recursive': '1'}
try:
resp = requests.get(url, params=params, headers=self._headers)
return resp.json()
except Exception as e:
return False
def create_dir(self, repo_id, name):
url = '{}repos/{}/dir/'.format(self._url, repo_id)
data = {'operation': 'mkdir'}
params = {'p': name}
resp = requests.post(url, data=data, headers=self._headers, params=params)
if resp.status_code != requests.codes.created:
msg = 'Create Dir Error: {}'.format(resp.status_code)
print (msg)
return False
return True
def _validate_directory(self, repo_id, target_file):
if target_file == '/':
return True
names = target_file.split('/')[:-1]
directories = self.list_directory(repo_id)
if isinstance(directories, bool):
return False
exists = False
parent_dir = '/'
name_dir = ''
for name in names:
name_dir += '/' + name
for directory in directories:
if name == directory['name'] and parent_dir == directory['parent_dir']:
exists = True
break
if exists:
exists = False
else:
self.create_dir(repo_id, name_dir)
if parent_dir == '/':
parent_dir += name
else:
parent_dir += '/' + name
return True
def update_file(self, path_file, repo_id, target_file, password, create=True):
if not self._headers:
return False
if not self._validate_directory(repo_id, target_file):
return False
if password:
if not self._decrypt(repo_id, password):
return False
update_link = self._get_update_link(repo_id)
if isinstance(path_file, tuple):
obj_file = path_file[0]
filename = path_file[1]
else:
obj_file = self._open(path_file)
_, filename = self._info_path(path_file)
files = {
'file': (filename, obj_file),
'filename': (None, filename),
'target_file': (None, '{}{}'.format(target_file, filename))
}
resp = requests.post(update_link, files=files, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Update Code: {}\n{}'.format(resp.status_code, resp.text)
print (msg)
if resp.status_code == self.FILE_DOES_NOT_EXIST and create:
relative_path = '/'
if target_file != '/':
relative_path += target_file
return self.upload_file(path_file, repo_id, relative_path, password)
else:
return False
return True
def ping(self):
url = self._url + 'ping/'
resp = requests.get(url)
return resp.json()
def auth_ping(self):
url = self._url + 'auth/ping/'
resp = requests.get(url, headers=self._headers)
return resp.json()
class PrintTicket(object):
LINE = '------------------------------------------------\n'
TITLES = 'CANT. U ARTICULO P.U. TOTAL\n'
LEYENDA = 'GRACIAS POR SU COMPRA\n\nGuarde este ticket para cualquier ' \
'aclaración.\nComprobante simplificado de operación con\npúblico en ' \
'general de acuerdo al Art. 37\nFracc II inc. v del Reglamento del\n' \
'Código Fiscal de la Federación.\n\n'
def __init__(self, info):
self.p = self._init_printer(info)
def _init_printer(self, info):
try:
if info['ip']:
p = printer.Network(info['ip'])
else:
p = printer.Usb(*info['usb'])
p.codepage = 'cp850'
return p
except Exception as e:
print (e)
return
def _set(self, *data):
self.p.set(*data)
return
def _t(self, text):
self.p.text(text)
return
def _l(self):
self._t(self.LINE)
return
def printer(self, data):
if self.p is None:
return False
self._emisor(data['emisor'])
self._receptor(data['receptor'])
self._ticket(data['ticket'])
self._products(data['products'])
self._footer(data['ticket'])
self.p.cut()
return True
def _emisor(self, data):
self._set('center', 'B', 'B', 2, 2)
self._t(data['name'])
self._set('center', 'B', 'B', 2)
self._t(data['rfc'])
self._set('center', 'A')
self._t(data['regimen'])
self._t(data['address'])
return
def _receptor(self, data):
self._set('center', 'B', 'B', 2)
self._t(data['name'])
return
def _ticket(self, data):
self._set('left', 'B', 'B', 2)
self._t(data['title'])
self._set('left', 'A')
self._t(data['date'])
return
def _products(self, data):
self._l()
self._t(self.TITLES)
self._l()
for p in data:
self._t(p)
self._l()
self._t('Total artículos: {}\n'.format(len(data)))
self._l()
return
def _footer(self, data):
self._set('right', 'B', 'B', 2)
self._t(data['total'])
self._set('center', 'A')
self._t(data['letters'])
self._t(self.LEYENDA)
self._set('center', 'A', 'B')
self._t('empresalibre.net')
return

View File

@ -2,35 +2,6 @@
import falcon
from middleware import get_template
from urllib.parse import unquote
class AppEmpresas(object):
template = 'empresas.html'
def __init__(self, db):
self._db = db
@falcon.after(get_template)
def on_get(self, req, resp):
values = req.params
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
opt = values.pop('opt', '1')
if opt == '1':
req.context['result'] = self._db.empresa_agregar(values)
elif opt == '2':
req.context['result'] = self._db.respaldar_dbs()
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.empresa_borrar(values):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppLogin(object):
@ -45,14 +16,11 @@ class AppLogin(object):
def on_post(self, req, resp):
session = req.env['beaker.session']
session.invalidate()
values = req.params
values['rfc'] = values['rfc'].upper()
values['ip'] = req.remote_addr
result, user = self._db.authenticate(values)
result = self._db.authenticate(values)
if result['login']:
session.save()
session['userobj'] = user
session['user'] = result['user']
session['rfc'] = values['rfc']
req.context['result'] = result
@ -94,101 +62,24 @@ class AppMain(object):
class AppValues(object):
TABLES = ('allusuarios', 'usuario', 'usuarioupdate', 'editusuario',
'addusuario')
def __init__(self, db):
self._db = db
def _valid_user(self, table, user):
if table in self.TABLES:
if user.es_admin or user.es_superusuario:
return True
else:
return False
return True
def on_get(self, req, resp, table):
values = req.params
session = req.env['beaker.session']
if table == 'product':
original = values['name']
try:
values['name'] = unquote(req.query_string.split('=')[1])
except Exception as e:
values['name'] = original
if req.path in ('/values/titlelogin', '/values/empresas'):
req.context['result'] = self._db.get_values(table, values, session)
resp.status = falcon.HTTP_200
return
if not 'userobj' in session and req.path != '/values/empresas':
session.invalidate()
raise falcon.HTTPTemporaryRedirect('/')
if table == 'admin':
req.context['result'] = session['userobj'].es_superusuario \
or session['userobj'].es_admin
else:
if not self._valid_user(table, session['userobj']):
resp.status = falcon.HTTP_403
return
req.context['result'] = self._db.get_values(table, values, session)
req.context['result'] = self._db.get_values(table, values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp, table):
values = req.params
session = req.env['beaker.session']
if table == 'usuario' and (session['userobj'].id == int(values['id'])):
resp.status = falcon.HTTP_204
return
if not self._valid_user(table, session['userobj']):
resp.status = falcon.HTTP_403
return
if self._db.delete(table, values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
def on_post(self, req, resp, table):
file_object = req.get_param('upload')
if file_object is None:
session = req.env['beaker.session']
values = req.params
if not self._valid_user(table, session['userobj']):
resp.status = falcon.HTTP_403
return
if table == 'correo':
req.context['result'] = self._db.validate_email(values)
elif table == 'sendmail':
req.context['result'] = self._db.send_email(values, session)
elif table == 'enviarprefac':
req.context['result'] = self._db.enviar_prefac(values)
elif table == 'addmoneda':
req.context['result'] = self._db.add_moneda(values)
elif table == 'addunidad':
req.context['result'] = self._db.add_unidad(values)
elif table == 'addimpuesto':
req.context['result'] = self._db.add_impuesto(values)
elif table == 'addusuario':
req.context['result'] = self._db.add_usuario(values)
elif table == 'editusuario':
req.context['result'] = self._db.edit_usuario(values)
elif table == 'bdfl':
req.context['result'] = self._db.importar_bdfl()
elif table == 'invoicenotes':
req.context['result'] = self._db.save_invoice_notes(values)
elif table == 'nivedu':
req.context['result'] = self._db.add_nivel_educativo(values)
else:
req.context['result'] = self._db.validate_cert(values, session)
else:
@ -196,22 +87,6 @@ class AppValues(object):
resp.status = falcon.HTTP_200
class AppFiles(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp, table):
values = req.params
resp.status = falcon.HTTP_200
def on_post(self, req, resp, table):
session = req.env['beaker.session']
file_object = req.get_param('upload')
req.context['result'] = self._db.upload_file(session, table, file_object)
resp.status = falcon.HTTP_200
class AppConfig(object):
def __init__(self, db):
@ -227,12 +102,6 @@ class AppConfig(object):
req.context['result'] = self._db.add_config(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.delete('config', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppPartners(object):
@ -258,29 +127,6 @@ class AppPartners(object):
resp.status = falcon.HTTP_204
class AppStudents(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_students(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.students(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.delete('students', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppProducts(object):
def __init__(self, db):
@ -288,17 +134,12 @@ class AppProducts(object):
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
if 'opt' in values:
req.context['result'] = self._db.products_get(values, user)
else:
req.context['result'] = self._db.get_products(values)
req.context['result'] = self._db.get_products(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.products(values)
req.context['result'] = self._db.product(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
@ -316,72 +157,22 @@ class AppInvoices(object):
def on_get(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.get_invoices(values, session['userobj'])
req.context['result'] = self._db.get_invoices(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.invoice(values, session['userobj'])
resp.status = falcon.HTTP_200
def on_put(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.invoice_put(values, session['userobj'])
req.context['result'] = self._db.invoice(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
session = req.env['beaker.session']
if self._db.delete('invoice', values['id'], session['userobj']):
if self._db.delete('invoice', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppPreInvoices(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_preinvoices(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.preinvoice(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.delete('preinvoice', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppTickets(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.get_tickets(values, session['userobj'])
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.tickets(values, session['userobj'])
resp.status = falcon.HTTP_200
class AppEmisor(object):
def __init__(self, db):
@ -406,55 +197,6 @@ class AppEmisor(object):
resp.status = falcon.HTTP_204
class AppCuentasBanco(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.get_cuentasbanco(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.cuentasbanco(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.delete('cuentasbanco', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppMovimientosBanco(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.get_movimientosbanco(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
# ~ req.context['result'] = self._db.add_movbanco(values)
req.context['result'] = self._db.bankmovement(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.delete('movbanco', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppFolios(object):
def __init__(self, db):
@ -478,350 +220,17 @@ class AppFolios(object):
resp.status = falcon.HTTP_204
class AppEmployees(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_employees(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.employees(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.delete('employee', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppNomina(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
if 'opt' in values:
req.context['result'] = self._db.nomina_get(values, user)
else:
by = values.get('by', '')
req.context['result'] = self._db.get_nomina(values)
if 'download' in by:
name = req.context['result']['name']
req.context['blob'] = req.context['result']['data']
resp.content_type = 'application/octet-stream'
resp.append_header(
'Content-Disposition', f'attachment; filename={name}')
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.nomina(values, session['userobj'])
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.delete('nomina', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppDocumentos(object):
def __init__(self, db):
self._db = db
#~ self._not_json = True
def on_get(self, req, resp, type_doc, id_doc):
# ~ print('TD', type_doc)
session = req.env['beaker.session']
req.context['result'], file_name, content_type = \
self._db.get_doc(type_doc, id_doc, session['rfc'])
if not type_doc in ('pdf', 'pre', 'tpdf', 'pdfpago', 'html', 'nompdf'):
resp.append_header('Content-Disposition',
'attachment; filename={}'.format(file_name))
if type_doc in ('pdf', 'nompdf', 'pdfpago'):
resp.append_header('Content-Disposition',
'inline; filename={}'.format(file_name))
resp.append_header('Content-Disposition',
'attachment; filename={}'.format(file_name))
resp.content_type = content_type
resp.status = falcon.HTTP_200
# ~ Revisado
class AppInvoicePay(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_invoicepay(values)
resp.status = falcon.HTTP_200
class AppCfdiPay(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_cfdipay(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.cfdipay(values)
resp.status = falcon.HTTP_200
class AppSATBancos(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_sat_bancos(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.sat_bancos(values)
resp.status = falcon.HTTP_200
class AppSATFormaPago(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_sat_forma_pago(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.sat_forma_pago(values)
resp.status = falcon.HTTP_200
class AppSATLeyendaFiscales(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.sat_leyendas_fiscales_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.sat_leyendas_fiscales_post(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.sat_leyendas_fiscales_delete(values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppSociosCuentasBanco(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_partners_accounts_bank(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.partners_accounts_bank(values)
resp.status = falcon.HTTP_200
class AppCert(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.cert_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.cert_post(values)
resp.status = falcon.HTTP_200
class AppSucursales(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.sucursales_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.sucursales_post(values)
resp.status = falcon.HTTP_200
class AppPartnerProducts(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.partner_products_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.partner_products_post(values)
resp.status = falcon.HTTP_200
class AppInventoryEntries(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.inventory_entries_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.inventory_entries_post(values, user)
resp.status = falcon.HTTP_200
class AppWareHouse(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.warehouse_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.warehouse_post(values, user)
resp.status = falcon.HTTP_200
class AppWareHouseProduct(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.warehouseproduct_get(values, user)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.warehouseproduct_post(values, user)
resp.status = falcon.HTTP_200
class AppTicketsDetails(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.ticketsdetails_get(values, user)
resp.status = falcon.HTTP_200
class AppUsers(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.users_get(values, user)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.users_post(values, user)
resp.status = falcon.HTTP_200
class AppSATUnidadesPeso(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.sat_unidades_peso_get(values, user)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.sat_unidades_peso_post(values, user)
resp.status = falcon.HTTP_200
class AppSATRegimenes(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.sat_regimenes_get(values, user)
resp.status = falcon.HTTP_200
class AppSociosRegimenes(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.socios_regimenes_get(values, user)
resp.status = falcon.HTTP_200

View File

@ -0,0 +1,622 @@
#!/usr/bin/env python
#~ import re
#~ from xml.etree import ElementTree as ET
#~ from requests import Request, Session, exceptions
import datetime
import hashlib
import os
import requests
import time
from lxml import etree
from xml.dom.minidom import parseString
from xml.sax.saxutils import escape, unescape
from uuid import UUID
from logbook import Logger
from zeep import Client
from zeep.plugins import HistoryPlugin
from zeep.cache import SqliteCache
from zeep.transports import Transport
from zeep.exceptions import Fault, TransportError
if __name__ == '__main__':
from configpac import DEBUG, TIMEOUT, AUTH, URL
else:
from .configpac import DEBUG, TIMEOUT, AUTH, URL
log = Logger('PAC')
#~ node = client.create_message(client.service, SERVICE, **args)
#~ print(etree.tostring(node, pretty_print=True).decode())
class Ecodex(object):
def __init__(self):
self.codes = URL['codes']
self.error = ''
self.message = ''
self._transport = Transport(cache=SqliteCache(), timeout=TIMEOUT)
self._plugins = None
self._history = None
if DEBUG:
self._history = HistoryPlugin()
self._plugins = [self._history]
def _get_token(self, rfc):
client = Client(URL['seguridad'],
transport=self._transport, plugins=self._plugins)
try:
result = client.service.ObtenerToken(rfc, self._get_epoch())
except Fault as e:
self.error = str(e)
log.error(self.error)
return ''
s = '{}|{}'.format(AUTH['ID'], result.Token)
return hashlib.sha1(s.encode()).hexdigest()
def _get_token_rest(self, rfc):
data = {
'rfc': rfc,
'grant_type': 'authorization_token',
}
headers = {'Content-type': 'application/x-www-form-urlencoded'}
result = requests.post(URL['token'], data=data, headers=headers)
data = result.json()
s = '{}|{}'.format(AUTH['ID'], data['service_token'])
return hashlib.sha1(s.encode()).hexdigest(), data['access_token']
def _validate_xml(self, xml):
NS_CFDI = {'cfdi': 'http://www.sat.gob.mx/cfd/3'}
if os.path.isfile(xml):
tree = etree.parse(xml).getroot()
else:
tree = etree.fromstring(xml.encode())
fecha = tree.get('Fecha')
rfc = tree.xpath('string(//cfdi:Emisor/@Rfc)', namespaces=NS_CFDI)
data = {
'ComprobanteXML': etree.tostring(tree).decode(),
'RFC': rfc,
'Token': self._get_token(rfc),
'TransaccionID': self._get_epoch(fecha),
}
return data
def _get_by_hash(self, sh, rfc):
token, access_token = self._get_token_rest(rfc)
url = URL['hash'].format(sh)
headers = {
'Authorization': 'Bearer {}'.format(access_token),
'X-Auth-Token': token,
}
result = requests.get(url, headers=headers)
if result.status_code == 200:
print (result.json())
return
def timbra_xml(self, xml):
data = self._validate_xml(xml)
client = Client(URL['timbra'],
transport=self._transport, plugins=self._plugins)
try:
result = client.service.TimbraXML(**data)
except Fault as e:
error = str(e)
if self.codes['HASH'] in error:
sh = error.split(' ')[3]
return self._get_by_hash(sh[:40], data['RFC'])
self.error = error
return ''
tree = parseString(result.ComprobanteXML.DatosXML)
xml = tree.toprettyxml(encoding='utf-8').decode('utf-8')
return xml
def _get_epoch(self, date=None):
if isinstance(date, str):
f = '%Y-%m-%dT%H:%M:%S'
e = int(time.mktime(time.strptime(date, f)))
else:
date = datetime.datetime.now()
e = int(time.mktime(date.timetuple()))
return e
def estatus_cuenta(self, rfc):
#~ Codigos:
#~ 100 = Cuenta encontrada
#~ 101 = RFC no dado de alta en el sistema ECODEX
token = self._get_token(rfc)
if not token:
return {}
data = {
'RFC': rfc,
'Token': token,
'TransaccionID': self._get_epoch()
}
client = Client(URL['clients'],
transport=self._transport, plugins=self._plugins)
try:
result = client.service.EstatusCuenta(**data)
except Fault as e:
log.error(str(e))
return
#~ print (result)
return result.Estatus
class Finkok(object):
def __init__(self, auth={}):
self.codes = URL['codes']
self.error = ''
self.message = ''
self._transport = Transport(cache=SqliteCache(), timeout=TIMEOUT)
self._plugins = None
self._history = None
self.uuid = ''
self.fecha = None
if DEBUG:
self._history = HistoryPlugin()
self._plugins = [self._history]
self._auth = AUTH
else:
self._auth = auth
def _debug(self):
if not DEBUG:
return
print('SEND', self._history.last_sent)
print('RESULT', self._history.last_received)
return
def _check_result(self, method, result):
#~ print ('CODE', result.CodEstatus)
#~ print ('INCIDENCIAS', result.Incidencias)
self.message = ''
MSG = {
'OK': 'Comprobante timbrado satisfactoriamente',
'307': 'Comprobante timbrado previamente',
}
status = result.CodEstatus
if status is None and result.Incidencias:
for i in result.Incidencias['Incidencia']:
self.error += 'Error: {}\n{}'.format(
i['CodigoError'], i['MensajeIncidencia'])
return ''
if method == 'timbra' and status in (MSG['OK'], MSG['307']):
#~ print ('UUID', result.UUID)
#~ print ('FECHA', result.Fecha)
if status == MSG['307']:
self.message = MSG['307']
tree = parseString(result.xml)
response = tree.toprettyxml(encoding='utf-8').decode('utf-8')
self.uuid = result.UUID
self.fecha = result.Fecha
return response
def _load_file(self, path):
try:
with open(path, 'rb') as f:
data = f.read()
except Exception as e:
self.error = str(e)
return
return data
def _validate_xml(self, file_xml):
if os.path.isfile(file_xml):
try:
with open(file_xml, 'rb') as f:
xml = f.read()
except Exception as e:
self.error = str(e)
return False, ''
else:
xml = file_xml.encode('utf-8')
return True, xml
def _validate_uuid(self, uuid):
try:
UUID(uuid)
return True
except ValueError:
self.error = 'UUID no válido: {}'.format(uuid)
return False
def timbra_xml(self, file_xml):
self.error = ''
if not DEBUG and not self._auth:
self.error = 'Sin datos para timbrar'
return
method = 'timbra'
ok, xml = self._validate_xml(file_xml)
if not ok:
return ''
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'xml': xml,
}
if URL['quick_stamp']:
try:
result = client.service.quick_stamp(**args)
except Fault as e:
self.error = str(e)
return
else:
try:
result = client.service.stamp(**args)
except Fault as e:
self.error = str(e)
return
return self._check_result(method, result)
def _get_xml(self, uuid):
if not self._validate_uuid(uuid):
return ''
method = 'util'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'uuid': uuid,
'taxpayer_id': self.rfc,
'invoice_type': 'I',
}
try:
result = client.service.get_xml(**args)
except Fault as e:
self.error = str(e)
return ''
except TransportError as e:
self.error = str(e)
return ''
if result.error:
self.error = result.error
return ''
tree = parseString(result.xml)
xml = tree.toprettyxml(encoding='utf-8').decode('utf-8')
return xml
def recupera_xml(self, file_xml='', uuid=''):
self.error = ''
if uuid:
return self._get_xml(uuid)
method = 'timbra'
ok, xml = self._validate_xml(file_xml)
if not ok:
return ''
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
try:
result = client.service.stamped(
xml, self._auth['user'], self._auth['pass'])
except Fault as e:
self.error = str(e)
return ''
return self._check_result(method, result)
def estatus_xml(self, uuid):
method = 'timbra'
if not self._validate_uuid(uuid):
return ''
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
try:
result = client.service.query_pending(
self._auth['USER'], self._auth['PASS'], uuid)
#~ print (result.date)
#~ tree = parseString(unescape(result.xml))
#~ response = tree.toprettyxml(encoding='utf-8').decode('utf-8')
return result.status
except Fault as e:
self.error = str(e)
return ''
def cancel_xml(self, rfc, uuids, path_cer, path_key):
for u in uuids:
if not self._validate_uuid(u):
return ''
cer = self._load_file(path_cer)
key = self._load_file(path_key)
method = 'cancel'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
uuid_type = client.get_type('ns1:UUIDS')
sa = client.get_type('ns0:stringArray')
args = {
'UUIDS': uuid_type(uuids=sa(string=uuids)),
'username': self._auth['USER'],
'password': self._auth['PASS'],
'taxpayer_id': rfc,
'cer': cer,
'key': key,
'store_pending': True,
}
try:
result = client.service.cancel(**args)
except Fault as e:
self.error = str(e)
return ''
if result.CodEstatus and self.codes['205'] in result.CodEstatus:
self.error = result.CodEstatus
return ''
return result
def cancel_signature(self, file_xml):
method = 'cancel'
if os.path.isfile(file_xml):
root = etree.parse(file_xml).getroot()
else:
root = etree.fromstring(file_xml)
xml = etree.tostring(root)
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'xml': xml,
'store_pending': True,
}
result = client.service.cancel_signature(**args)
return result
def get_acuse(self, rfc, uuids, type_acuse='C'):
for u in uuids:
if not self._validate_uuid(u):
return ''
method = 'cancel'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'taxpayer_id': rfc,
'uuid': '',
'type': type_acuse,
}
try:
result = []
for u in uuids:
args['uuid'] = u
r = client.service.get_receipt(**args)
result.append(r)
except Fault as e:
self.error = str(e)
return ''
return result
def estatus_cancel(self, uuids):
for u in uuids:
if not self._validate_uuid(u):
return ''
method = 'cancel'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'uuid': '',
}
try:
result = []
for u in uuids:
args['uuid'] = u
r = client.service.query_pending_cancellation(**args)
result.append(r)
except Fault as e:
self.error = str(e)
return ''
return result
def add_token(self, rfc, email):
"""
Se requiere cuenta de reseller para usar este método
"""
method = 'util'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': AUTH['USER'],
'password': AUTH['PASS'],
'name': rfc,
'token_username': email,
'taxpayer_id': rfc,
'status': True,
}
result = client.service.add_token(**args)
return result
def get_date(self):
method = 'util'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
try:
result = client.service.datetime(AUTH['USER'], AUTH['PASS'])
except Fault as e:
self.error = str(e)
return ''
if result.error:
self.error = result.error
return
return result.datetime
def add_client(self, rfc, type_user=False):
"""
Se requiere cuenta de reseller para usar este método
type_user: False == 'P' == Prepago or True == 'O' == On demand
"""
tu = {False: 'P', True: 'O'}
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': AUTH['USER'],
'reseller_password': AUTH['PASS'],
'taxpayer_id': rfc,
'type_user': tu[type_user],
'added': datetime.datetime.now().isoformat()[:19],
}
try:
result = client.service.add(**args)
except Fault as e:
self.error = str(e)
return ''
return result
def edit_client(self, rfc, status=True):
"""
Se requiere cuenta de reseller para usar este método
status = 'A' or 'S'
"""
sv = {False: 'S', True: 'A'}
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': AUTH['USER'],
'reseller_password': AUTH['PASS'],
'taxpayer_id': rfc,
'status': sv[status],
}
try:
result = client.service.edit(**args)
except Fault as e:
self.error = str(e)
return ''
return result
def get_client(self, rfc):
"""
Se requiere cuenta de reseller para usar este método
"""
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': AUTH['USER'],
'reseller_password': AUTH['PASS'],
'taxpayer_id': rfc,
}
try:
result = client.service.get(**args)
except Fault as e:
self.error = str(e)
return ''
except TransportError as e:
self.error = str(e)
return ''
return result
def assign_client(self, rfc, credit):
"""
Se requiere cuenta de reseller para usar este método
"""
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': AUTH['USER'],
'password': AUTH['PASS'],
'taxpayer_id': rfc,
'credit': credit,
}
try:
result = client.service.assign(**args)
except Fault as e:
self.error = str(e)
return ''
return result
def _get_data_sat(path):
BF = 'string(//*[local-name()="{}"]/@{})'
NS_CFDI = {'cfdi': 'http://www.sat.gob.mx/cfd/3'}
try:
if os.path.isfile(path):
tree = etree.parse(path).getroot()
else:
tree = etree.fromstring(path)
data = {}
emisor = escape(
tree.xpath('string(//cfdi:Emisor/@rfc)', namespaces=NS_CFDI) or
tree.xpath('string(//cfdi:Emisor/@Rfc)', namespaces=NS_CFDI)
)
receptor = escape(
tree.xpath('string(//cfdi:Receptor/@rfc)', namespaces=NS_CFDI) or
tree.xpath('string(//cfdi:Receptor/@Rfc)', namespaces=NS_CFDI)
)
data['total'] = tree.get('total') or tree.get('Total')
data['emisor'] = emisor
data['receptor'] = receptor
data['uuid'] = tree.xpath(BF.format('TimbreFiscalDigital', 'UUID'))
except Exception as e:
print (e)
return {}
return '?re={emisor}&rr={receptor}&tt={total}&id={uuid}'.format(**data)
def get_status_sat(xml):
data = _get_data_sat(xml)
if not data:
return
URL = 'https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc?wsdl'
client = Client(URL, transport=Transport(cache=SqliteCache()))
try:
result = client.service.Consulta(expresionImpresa=data)
except Exception as e:
return 'Error: {}'.format(str(e))
return result.Estado
def main():
return
if __name__ == '__main__':
main()

View File

@ -1,4 +0,0 @@
#!/usr/bin/env python
from .comerciodigital import PACComercioDigital
from .finkok import PACFinkok

View File

@ -1,350 +0,0 @@
#!/usr/bin/env python3
import argparse
import base64
import datetime
import getpass
import hashlib
import subprocess
from pathlib import Path
import lxml.etree as ET
import xmlsec
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.x509.oid import ExtensionOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from conf import TOKEN
class SATCertificate(object):
def __init__(self, cer=b'', key=b'', password=''):
self._error = ''
self._init_values()
self._get_data_cer(cer)
self._get_data_key(key, password)
# ~ if not password:
# ~ self._test()
# ~ def _test(self):
# ~ key = self._get_key('')
# ~ self._p = ''
# ~ self._key_der = key.private_bytes(
# ~ encoding=serialization.Encoding.DER,
# ~ format=serialization.PrivateFormat.PKCS8,
# ~ encryption_algorithm=serialization.BestAvailableEncryption(self._p.encode())
# ~ )
# ~ return
def _init_values(self):
self._rfc = ''
self._serial_number = ''
self._serial_number2 = ''
self._subject = ''
self._issuer = ''
self._not_before = None
self._not_after = None
self._is_fiel = False
self._are_couple = False
self._is_valid_time = False
self._key = b''
self._cer = b''
self._cer_pem = ''
self._cer_txt = ''
self._key_enc = b''
self._key_der = b''
self._p12 = b''
self._cer_modulus = 0
self._key_modulus = 0
return
def __str__(self):
msg = '\tRFC: {}\n'.format(self.rfc)
msg += '\tNo de Serie: {}\n'.format(self.serial_number)
msg += '\tVálido desde: {}\n'.format(self.not_before)
msg += '\tVálido hasta: {}\n'.format(self.not_after)
msg += '\tEs vigente: {}\n'.format(self.is_valid_time)
msg += '\tSon pareja: {}\n'.format(self.are_couple)
msg += '\tEs FIEL: {}\n'.format(self.is_fiel)
return msg
def __bool__(self):
return self.is_valid
def _get_hash(self):
digest = hashes.Hash(hashes.SHA512(), default_backend())
digest.update(self._rfc.encode())
digest.update(self._serial_number.encode())
digest.update(TOKEN.encode())
return digest.finalize()
def _get_data_cer(self, cer):
self._cer = cer
obj = x509.load_der_x509_certificate(cer, default_backend())
self._issuer = obj.issuer.rfc4514_string()
self._subject = obj.subject.rfc4514_string()
self._rfc = obj.subject.get_attributes_for_oid(
NameOID.X500_UNIQUE_IDENTIFIER)[0].value.split(' ')[0]
self._serial_number2 = '{0:x}'.format(obj.serial_number)
self._serial_number = self._serial_number2[1::2]
self._not_before = obj.not_valid_before
self._not_after = obj.not_valid_after
now = datetime.datetime.utcnow()
self._is_valid_time = (now > self.not_before) and (now < self.not_after)
if not self._is_valid_time:
msg = 'El certificado no es vigente'
self._error = msg
self._is_fiel = obj.extensions.get_extension_for_oid(
ExtensionOID.KEY_USAGE).value.key_agreement
self._cer_pem = obj.public_bytes(serialization.Encoding.PEM).decode()
self._cer_txt = ''.join(self._cer_pem.split('\n')[1:-2])
self._cer_modulus = obj.public_key().public_numbers().n
return
def _get_data_key(self, key, password):
self._key = key
self._keyp = password
self._key_enc = key
if not key or not password:
return
try:
obj = serialization.load_der_private_key(
key, password.encode(), default_backend())
except ValueError:
msg = 'La contraseña es incorrecta'
self._error = msg
return
p = self._get_hash()
self._key_enc = obj.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(p)
)
self._key_modulus = obj.public_key().public_numbers().n
self._are_couple = self._cer_modulus == self._key_modulus
if not self._are_couple:
msg = 'El CER y el KEY no son pareja'
self._error = msg
return
def _get_key(self, password):
if not password:
password = self._get_hash()
private_key = serialization.load_pem_private_key(
self._key_enc, password=password, backend=default_backend())
return private_key
def _get_key_pem(self):
obj = self._get_key('')
key_pem = obj.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
return key_pem
# Not work
def _get_p12(self):
obj = serialization.pkcs12.serialize_key_and_certificates('test',
self.key_pem, self.cer_pem, None,
encryption_algorithm=serialization.NoEncryption()
)
return obj
def sign(self, data, password=''):
private_key = self._get_key(password)
firma = private_key.sign(data, padding.PKCS1v15(), hashes.SHA256())
return base64.b64encode(firma).decode()
def sign_xml(self, tree):
node = xmlsec.tree.find_node(tree, xmlsec.constants.NodeSignature)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self.key_pem, xmlsec.constants.KeyDataFormatPem)
ctx.key = key
ctx.sign(node)
node = xmlsec.tree.find_node(tree, 'X509Certificate')
node.text = self.cer_txt
node = xmlsec.tree.find_node(tree, 'X509IssuerName')
node.text = self.issuer
node = xmlsec.tree.find_node(tree, 'X509SerialNumber')
node.text = self.serial_number
node = xmlsec.tree.find_node(tree, 'SignatureValue')
node.text = node.text.replace('\n', '')
node = xmlsec.tree.find_node(tree, 'Modulus')
node.text = node.text.replace('\n', '')
# ~ xml_signed = ET.tostring(tree,
# ~ xml_declaration=True, encoding='UTF-8').decode()
xml_signed = ET.tostring(tree, encoding='UTF-8').decode().replace('\n', '')
return xml_signed
@property
def rfc(self):
return self._rfc
@property
def serial_number(self):
return self._serial_number
@property
def serial_number2(self):
return self._serial_number2
@property
def issuer(self):
return self._issuer
@property
def subject(self):
return self._subject
@property
def not_before(self):
return self._not_before
@property
def not_after(self):
return self._not_after
@property
def is_fiel(self):
return self._is_fiel
@property
def are_couple(self):
return self._are_couple
@property
def is_valid(self):
return not bool(self.error)
@property
def is_valid_time(self):
return self._is_valid_time
@property
def cer(self):
return self._cer
@property
def cer_pem(self):
return self._cer_pem.encode()
@property
def cer_txt(self):
return self._cer_txt
@property
def key_pem(self):
return self._get_key_pem()
@property
def key_enc(self):
return self._key_enc
@property
def p12(self):
return self._get_p12()
@property
def error(self):
return self._error
@classmethod
def save_cert(cls, obj):
import hashlib
cer = x509.load_pem_x509_certificate(obj.cer_pem.encode(), default_backend())
cer_der = cer.public_bytes(serialization.Encoding.DER)
token = hashlib.md5(obj.rfc.encode()).hexdigest()
path = Path(f'/tmp/{obj.rfc}.key')
path.write_text(obj.key_enc)
args = f'openssl rsa -inform PEM -outform PEM -in "{str(path)}" -passin pass:{token}'
pem = subprocess.check_output(args, shell=True).decode()
key = serialization.load_pem_private_key(
pem.encode(), password=None, backend=default_backend())
digest = hashes.Hash(hashes.SHA512(), default_backend())
digest.update(obj.rfc.encode())
digest.update(obj.serie.encode())
digest.update(TOKEN.encode())
p = digest.finalize()
key_enc = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(p)
)
cert = SATCertificate(cer_der, key_enc)
obj.key_enc = cert.key_enc
obj.cer = cert.cer
obj.serie = cert.serial_number
obj.desde = cert.not_before
obj.hasta = cert.not_after
obj.save()
return
def main(args):
# ~ contra = getpass.getpass('Introduce la contraseña del archivo KEY: ')
contra = '12345678a'
if not contra.strip():
msg = 'La contraseña es requerida'
print(msg)
return
path_cer = Path(args.cer)
path_key = Path(args.key)
if not path_cer.is_file():
msg = 'El archivo CER es necesario'
print(msg)
return
if not path_key.is_file():
msg = 'El archivo KEY es necesario'
print(msg)
return
cer = path_cer.read_bytes()
key = path_key.read_bytes()
cert = SATCertificate(cer, key, contra)
if cert.error:
print(cert.error)
else:
print(cert)
return
def _process_command_line_arguments():
parser = argparse.ArgumentParser(description='CFDI Certificados')
help = 'Archivo CER'
parser.add_argument('-c', '--cer', help=help, default='')
help = 'Archivo KEY'
parser.add_argument('-k', '--key', help=help, default='')
args = parser.parse_args()
return args
if __name__ == '__main__':
args = _process_command_line_arguments()
main(args)

View File

@ -1,3 +0,0 @@
#!/usr/bin/env python3
from .comercio import PACComercioDigital

View File

@ -1,431 +0,0 @@
#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ 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 <http://www.gnu.org/licenses/>.
import base64
import logging
import lxml.etree as ET
import requests
from requests.exceptions import ConnectionError
from .conf import DEBUG
# ~ , AUTH
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
log = logging.getLogger(__name__)
logging.getLogger('requests').setLevel(logging.ERROR)
TIMEOUT = 10
NAMESPACES = {
'3.3': 'http://www.sat.gob.mx/cfd/3',
'4.0': 'http://www.sat.gob.mx/cfd/4',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
def pretty_print_POST(req):
"""
At this point it is completely built and ready
to be fired; it is "prepared".
However pay attention at the formatting used in
this function because it is programmed to be pretty
printed and may differ from the actual request.
"""
print('{}\n{}\r\n{}\r\n\r\n{}'.format(
'-----------START-----------',
req.method + ' ' + req.url,
'\r\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
req.body,
))
class PACComercioDigital(object):
ws = 'https://{}.comercio-digital.mx/{}'
api = 'https://app2.comercio-digital.mx/{}'
URL = {
'timbra': ws.format('ws', 'timbre4/timbrarV5'),
'cancel': ws.format('cancela', 'cancela4/cancelarUuid'),
'cancelxml': ws.format('cancela', 'cancela4/cancelarXml'),
'status': ws.format('cancela', 'arws/consultaEstatus'),
'client': api.format('x3/altaEmpresa'),
'saldo': api.format('x3/saldo'),
'timbres': api.format('x3/altaTimbres'),
}
CODES = {
'000': '000 Exitoso',
'004': '004 RFC {} ya esta dado de alta con Estatus=A',
'704': '704 Usuario Invalido',
'702': '702 Error rfc/empresa invalido',
}
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/4',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
if DEBUG:
ws = 'https://pruebas.comercio-digital.mx/{}'
ws6 = 'https://pruebas6.comercio-digital.mx/arws/{}'
URL = {
'timbra': ws.format('timbre4/timbrarV5'),
'cancel': ws.format('cancela4/cancelarUuid'),
'cancelxml': ws.format('cancela4/cancelarXml'),
'status': ws6.format('consultaEstatus'),
'client': api.format('x3/altaEmpresa'),
'saldo': api.format('x3/saldo'),
'timbres': api.format('x3/altaTimbres'),
}
def __init__(self):
self.error = ''
def _error(self, msg):
self.error = str(msg)
log.error(msg)
return
def _post(self, url, data, headers={}):
result = None
headers['host'] = url.split('/')[2]
headers['Content-type'] = 'text/plain'
headers['Connection'] = 'Keep-Alive'
headers['Expect'] = '100-continue'
if DEBUG:
req = requests.Request('POST', url, headers=headers, data=data)
prepared = req.prepare()
pretty_print_POST(prepared)
try:
result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT)
except ConnectionError as e:
self._error(e)
return result
def _validate_cfdi(self, xml):
"""
Comercio Digital solo soporta la declaración con doble comilla
"""
# ~ tree = ET.fromstring(xml.encode())
# ~ xml = ET.tostring(tree,
# ~ pretty_print=True, doctype='<?xml version="1.0" encoding="utf-8"?>')
return xml.encode('utf-8')
def stamp(self, cfdi, auth):
# ~ if DEBUG or not auth:
# ~ auth = AUTH
url = self.URL['timbra']
headers = {
'usrws': auth['user'],
'pwdws': auth['pass'],
'tipo': 'XML',
}
cfdi = self._validate_cfdi(cfdi)
result = self._post(url, cfdi, headers)
if result is None:
return ''
if result.status_code != 200:
return ''
if 'errmsg' in result.headers:
self._error(result.headers['errmsg'])
return ''
xml = result.content
tree = ET.fromstring(xml)
cfdi_uuid = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
namespaces=self.NS_CFDI)
date_stamped = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@FechaTimbrado)',
namespaces=self.NS_CFDI)
data = {
'xml': xml.decode(),
'uuid': cfdi_uuid,
'date': date_stamped,
}
return data
def _get_data_cancel(self, cfdi, info, auth):
info['tipo'] = 'cfdi'
info['key'] = base64.b64encode(info['key_enc']).decode()
info['cer'] = base64.b64encode(info['cer_ori']).decode()
tree = ET.fromstring(cfdi.encode())
version = tree.attrib['Version']
namespaces = {
'cfdi': NAMESPACES[version],
'tdf': NAMESPACES['tdf'],
}
tipo = tree.xpath(
'string(//cfdi:Comprobante/@TipoDeComprobante)',
namespaces=namespaces)
total = tree.xpath(
'string(//cfdi:Comprobante/@Total)',
namespaces=namespaces)
rfc_emisor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
namespaces=namespaces)
rfc_receptor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
namespaces=namespaces)
uid = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
namespaces=namespaces)
data = (
f"USER={auth['user']}",
f"PWDW={auth['pass']}",
f"RFCE={rfc_emisor}",
f"UUID={uid}",
f"PWDK={info['pass']}",
f"KEYF={info['key']}",
f"CERT={info['cer']}",
f"TIPO1={info['tipo']}",
f"ACUS=SI",
f"RFCR={rfc_receptor}",
f"TIPOC={tipo}",
f"TOTAL={total}",
f"UUIDREL={info['args']['uuid']}",
f"MOTIVO={info['args']['reason']}",
)
return '\n'.join(data)
def cancel(self, cfdi, info, auth):
# ~ if DEBUG or not auth:
# ~ auth = AUTH
url = self.URL['cancel']
data = self._get_data_cancel(cfdi, info, auth)
result = self._post(url, data)
if result is None:
return ''
if result.status_code != 200:
return ''
if result.headers['codigo'] != '000':
self._error(result.headers['errmsg'])
return ''
tree = ET.fromstring(result.text)
date_cancel = tree.xpath('string(//Acuse/@Fecha)')[:19]
data = {
'acuse': result.text,
'date': date_cancel,
}
return data
def _get_headers_cancel_xml(self, cfdi, info, auth):
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
tree = ET.fromstring(cfdi.encode())
tipocfdi = tree.xpath(
'string(//cfdi:Comprobante/@TipoDeComprobante)',
namespaces=NS_CFDI)
total = tree.xpath(
'string(//cfdi:Comprobante/@Total)',
namespaces=NS_CFDI)
rfc_receptor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
namespaces=NS_CFDI)
headers = {
'usrws': auth['user'],
'pwdws': auth['pass'],
'rfcr': rfc_receptor,
'total': total,
'tipocfdi': tipocfdi,
}
headers.update(info)
return headers
def cancel_xml(self, xml, auth, cfdi='', info={'tipo': 'cfdi'}):
# ~ if DEBUG or not auth:
# ~ auth = AUTH
url = self.URL['cancelxml']
headers = self._get_headers_cancel_xml(cfdi, info, auth)
result = self._post(url, xml, headers)
if result is None:
return ''
if result.status_code != 200:
return ''
if result.headers['codigo'] != '000':
self._error(result.headers['errmsg'])
return ''
tree = ET.fromstring(result.text)
date_cancel = tree.xpath('string(//Acuse/@Fecha)')[:19]
data = {
'acuse': result.text,
'date': date_cancel,
}
return data
def status(self, data, auth):
# ~ if not auth:
# ~ auth = AUTH
url = self.URL['status']
data = (
f"USER={auth['user']}",
f"PWDW={auth['pass']}",
f"RFCR={data['rfc_receptor']}",
f"RFCE={data['rfc_emisor']}",
f"TOTAL={data['total']}",
f"UUID={data['uuid']}",
)
data = '\n'.join(data)
result = self._post(url, data)
if result is None:
return ''
if result.status_code != 200:
self._error(result.status_code)
return self.error
return result.text
def _get_data_client(self, auth, values):
data = [f"usr_ws={auth['user']}", f"pwd_ws={auth['pass']}"]
fields = (
'rfc_contribuyente',
'nombre_contribuyente',
'calle',
'noExterior',
'noInterior',
'colonia',
'localidad',
'municipio',
'estado',
'pais',
'cp',
'contacto',
'telefono',
'email',
'rep_nom',
'rep_rfc',
'email_fact',
'pwd_asignado',
)
data += [f"{k}={values[k]}" for k in fields]
return '\n'.join(data)
def client_add(self, data, auth):
# ~ auth = AUTH
url = self.URL['client']
data = self._get_data_client(auth, data)
result = self._post(url, data)
if result is None:
return False
if result.status_code != 200:
self._error(f'Code: {result.status_code}')
return False
if result.text != self.CODES['000']:
self._error(result.text)
return False
return True
def client_balance(self, data, rfc=''):
url = self.URL['saldo']
host = url.split('/')[2]
headers = {
'Content-type': 'text/plain',
'Host': host,
'Connection' : 'Keep-Alive',
}
data = {'usr': data['user'], 'pwd': data['pass']}
try:
result = requests.get(url, params=data, headers=headers, timeout=TIMEOUT)
except ConnectionError as e:
self._error(e)
return ''
if result.status_code != 200:
return ''
if result.text == self.CODES['704']:
self._error(result.text)
return ''
if result.text == self.CODES['702']:
self._error(result.text)
return ''
return result.text
def client_add_timbres(self, data, auth):
# ~ if not auth:
# ~ auth = AUTH
url = self.URL['timbres']
data = '\n'.join((
f"usr_ws={auth['user']}",
f"pwd_ws={auth['pass']}",
f"rfc_recibir={data['rfc']}",
f"num_timbres={data['timbres']}"
))
result = self._post(url, data)
if result is None:
return False
if result.status_code != 200:
self._error(f'Code: {result.status_code}')
return False
if result.text != self.CODES['000']:
self._error(result.text)
return False
return True

View File

@ -1,38 +0,0 @@
#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ 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 <http://www.gnu.org/licenses/>.
# ~ Siempre consulta la documentación de PAC
# ~ AUTH = Las credenciales de timbrado proporcionadas por el PAC
# ~ NO cambies las credenciales de prueba
DEBUG = False
AUTH = {
'user': '',
'pass': '',
}
if DEBUG:
AUTH = {
'user': 'AAA010101AAA',
'pass': 'PWD',
}

View File

@ -1,3 +0,0 @@
#!/usr/bin/env python3
from .finkok import PACFinkok

View File

@ -1,46 +0,0 @@
#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ 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 <http://www.gnu.org/licenses/>.
# ~ Siempre consulta la documentación de PAC
# ~ AUTH = Las credenciales de timbrado proporcionadas por el PAC
# ~ NO cambies las credenciales de prueba
DEBUG = False
AUTH = {
'user': '',
'pass': '',
'RESELLER': {
'user': '',
'pass': ''
}
}
if DEBUG:
AUTH = {
'user': 'pruebas-finkok@correolibre.net',
'pass': '5c9a88da105bff9a8c430cb713f6d35269f51674bdc5963c1501b7316366',
'RESELLER': {
'user': '',
'pass': ''
}
}

View File

@ -1,580 +0,0 @@
#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ 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 <http://www.gnu.org/licenses/>.
# ~ import base64
import datetime
import logging
import os
import re
from io import BytesIO
from xml.sax.saxutils import unescape
import lxml.etree as ET
from zeep import Client
from zeep.plugins import Plugin
from zeep.cache import SqliteCache
from zeep.transports import Transport
from zeep.exceptions import Fault, TransportError
from requests.exceptions import ConnectionError
from .conf import DEBUG, AUTH
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
log = logging.getLogger(__name__)
logging.getLogger('requests').setLevel(logging.ERROR)
logging.getLogger('zeep').setLevel(logging.ERROR)
TIMEOUT = 10
DEBUG_SOAP = False
class DebugPlugin(Plugin):
def _to_string(self, envelope, name):
if DEBUG_SOAP:
data = ET.tostring(envelope, pretty_print=True, encoding='utf-8').decode()
path = f'/tmp/soap_{name}.xml'
with open(path, 'w') as f:
f.write(data)
# ~ print(data)
return
def egress(self, envelope, http_headers, operation, binding_options):
self._to_string(envelope, 'request')
return envelope, http_headers
def ingress(self, envelope, http_headers, operation):
self._to_string(envelope, 'response')
return envelope, http_headers
class PACFinkok(object):
WS = 'https://facturacion.finkok.com/servicios/soap/{}.wsdl'
NS_TYPE = 'ns1'
if DEBUG:
WS = 'https://demo-facturacion.finkok.com/servicios/soap/{}.wsdl'
NS_TYPE = 'ns0'
URL = {
'quick_stamp': False,
'timbra': WS.format('stamp'),
'cancel': WS.format('cancel'),
'client': WS.format('registration'),
'util': WS.format('utilities'),
}
CODE = {
'200': 'Comprobante timbrado satisfactoriamente',
'205': 'No Encontrado',
'307': 'Comprobante timbrado previamente',
'702': 'No se encontro el RFC del emisor',
'IP': 'Invalid Passphrase',
'IPMSG': 'Frase de paso inválida',
'NE': 'No Encontrado',
}
def __init__(self):
self._error = ''
self._transport = Transport(cache=SqliteCache(), timeout=TIMEOUT)
self._plugins = [DebugPlugin()]
@property
def error(self):
return self._error
def _validate_result(self, result):
if hasattr(result, 'Incidencias') and not result.Incidencias is None:
fault = result.Incidencias.Incidencia[0]
cod_error = fault.CodigoError.encode('utf-8').decode()
msg_error = fault.MensajeIncidencia.encode('utf-8').decode()
error = 'Error: {}\n{}'.format(cod_error, msg_error)
if cod_error == '307':
return result
self._error = self.CODE.get(cod_error, error)
return {}
if hasattr(result, 'CodEstatus'):
ce = result.CodEstatus
if ce is None:
return result
if ce == self.CODE['IP']:
self._error = self.CODE['IPMSG']
return {}
if self.CODE['NE'] in ce:
self._error = 'UUID ' + self.CODE['NE']
return {}
if ce == 'UUID Not Found':
self._error = 'UUID ' + self.CODE['NE']
return {}
if self.CODE['200'] != ce:
self._error = ce
return {}
return result
return result
def _get_result(self, client, method, args):
self._error = ''
try:
result = getattr(client.service, method)(**args)
except Fault as e:
self._error = str(e)
return {}
except TransportError as e:
if '413' in str(e):
self._error = '413<BR><BR><b>Documento muy grande para timbrar</b>'
else:
self._error = str(e)
return {}
except ConnectionError as e:
msg = '502 - Error de conexión'
self._error = msg
return {}
return self._validate_result(result)
def _to_string(self, data):
root = ET.parse(BytesIO(data.encode('utf-8'))).getroot()
xml = ET.tostring(root,
pretty_print=True, xml_declaration=True, encoding='utf-8')
return xml.decode('utf-8')
def stamp(self, cfdi, auth={}):
if DEBUG or not auth:
auth = AUTH
method = 'timbra'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
args = {
'username': auth['user'],
'password': auth['pass'],
'xml': cfdi.encode('utf-8'),
}
result = self._get_result(client, 'stamp', args)
if self.error:
log.error(self.error)
return ''
data = {
'xml': self._to_string(result.xml),
'uuid': result.UUID,
'date': result.Fecha,
}
return data
def _get_data_cancel(self, cfdi):
VERSIONS = {
'3.3': 'http://www.sat.gob.mx/cfd/3',
'4.0': 'http://www.sat.gob.mx/cfd/4',
}
NS_CFDI = {
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
tree = ET.fromstring(cfdi.encode())
version = tree.attrib['Version']
NS_CFDI['cfdi'] = VERSIONS[version]
rfc_emisor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
namespaces=NS_CFDI)
cfdi_uuid = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
namespaces=NS_CFDI)
return rfc_emisor, cfdi_uuid
def cancel(self, cfdi, info, auth={}):
if DEBUG or not auth:
auth = AUTH
rfc_emisor, cfdi_uuid = self._get_data_cancel(cfdi)
method = 'cancel'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
uuid_type = client.get_type(f'{self.NS_TYPE}:UUIDS')
ns1_uuid = client.get_type(f'{self.NS_TYPE}:UUID')
# ~ sa = client.get_type('ns0:stringArray')
data_uuid = {
'UUID': cfdi_uuid,
'FolioSustitucion': info['args']['uuid'],
'Motivo': info['args']['reason'],
}
# ~ 'UUIDS': uuid_type(uuids=sa(string=cfdi_uuid)),
args = {
'UUIDS': uuid_type(ns1_uuid(**data_uuid)),
'username': auth['user'],
'password': auth['pass'],
'taxpayer_id': rfc_emisor,
'cer': info['cer'],
'key': info['key'],
'store_pending': False,
}
result = self._get_result(client, 'cancel', args)
if self.error:
log.error(self.error)
return ''
folio = result['Folios']['Folio'][0]
status = folio['EstatusUUID']
if status != '201':
log.debug(f'Cancel status: {status} - {cfdi_uuid}')
data = {
'acuse': result['Acuse'],
'date': result['Fecha'],
}
return data
def cancel_xml(self, xml, auth={}, cfdi=''):
if DEBUG or not auth:
auth = AUTH
method = 'cancel'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
client.set_ns_prefix('can', 'http://facturacion.finkok.com/cancel')
args = {
'xml': xml.encode(),
'username': auth['user'],
'password': auth['pass'],
'store_pending': False,
}
result = self._get_result(client, 'cancel_signature', args)
if self.error:
log.error(self.error)
return ''
folio = result['Folios']['Folio'][0]
status = folio['EstatusUUID']
if status == '708':
self._error = 'Error 708 del SAT, intenta más tarde.'
log.error(self.error)
return ''
if status != '201':
log.debug(f'Cancel status: {status} -')
data = {
'acuse': result['Acuse'],
'date': result['Fecha'],
}
return data
def client_add(self, rfc, type_user=False):
"""Agrega un nuevo cliente para timbrado.
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del nuevo cliente
Kwargs:
type_user (bool):
False == 'P' == Prepago
True == 'O' == On demand
Returns:
True or False
origin PAC
'message':
'Account Created successfully'
'Account Already exists'
'success': True or False
"""
auth = AUTH['RESELLER']
tu = {True: 'O', False: 'P'}
method = 'client'
client = Client(
self.URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['user'],
'reseller_password': auth['pass'],
'taxpayer_id': rfc,
'type_user': tu[type_user],
'added': datetime.datetime.now().isoformat()[:19],
}
result = self._get_result(client, 'add', args)
if self.error:
return False
if not result.success:
self.error = result.message
return False
# ~ PAC success debería ser False
msg = 'Account Already exists'
if result.message == msg:
self.error = msg
return True
return result.success
def client_get_token(self, rfc, email):
"""Genera un nuevo token al cliente para timbrado.
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del cliente, ya debe existir
email (str): El correo del cliente, funciona como USER al timbrar
Returns:
token (str): Es la contraseña para timbrar
origin PAC
dict
'username': 'username',
'status': True or False
'name': 'name',
'success': True or False
'token': 'Token de timbrado',
'message': None
"""
auth = AUTH['RESELLER']
method = 'util'
client = Client(
self.URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': auth['user'],
'password': auth['pass'],
'name': rfc,
'token_username': email,
'taxpayer_id': rfc,
'status': True,
}
result = self._get_result(client, 'add_token', args)
if self.error:
log.error(self.error)
return ''
if not result.success:
self.error = result.message
log.error(self.error)
return ''
return result.token
def client_add_timbres(self, rfc, credit):
"""Agregar credito a un emisor
Se requiere cuenta de reseller
Args:
rfc (str): El RFC del emisor, debe existir
credit (int): Cantidad de folios a agregar
Returns:
dict
'success': True or False,
'credit': nuevo credito despues de agregar or None
'message':
'Success, added {credit} of credit to {RFC}.'
'RFC no encontrado'
"""
auth = AUTH['RESELLER']
method = 'client'
client = Client(
self.URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': auth['user'],
'password': auth['pass'],
'taxpayer_id': rfc,
'credit': credit,
}
result = self._get_result(client, 'assign', args)
if self.error:
log.error(error)
return ''
if not result.success:
self.error = result.message
return 0
return result.credit
def client_balance(self, auth={}, rfc=''):
"""Regresa los timbres restantes del cliente
Se pueden usar las credenciales de relleser o las credenciales del emisor
Args:
auth (dict): Credenciales del emisor
rfc (str): El RFC del emisor
Returns:
int Cantidad de timbres restantes
"""
if not auth:
auth = AUTH['RESELLER']
method = 'client'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['user'],
'reseller_password': auth['pass'],
'taxpayer_id': rfc,
}
result = self._get_result(client, 'get', args)
if self.error:
log.error(self.error)
return ''
success = bool(result.users)
if not success:
self._error = result.message or 'RFC no existe'
log.error(self.error)
return 0
return result.users.ResellerUser[0].credit
def client_set_status(self, rfc, status):
"""Edita el estatus (Activo o Suspendido) de un cliente
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del cliente
Kwargs:
status (bool):
True == 'A' == Activo
False == 'S' == Suspendido
Returns:
dict
'message':
'Account Created successfully'
'Account Already exists'
'success': True or False
"""
auth = AUTH['RESELLER']
ts = {True: 'A', False: 'S'}
method = 'client'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['user'],
'reseller_password': auth['pass'],
'taxpayer_id': rfc,
'status': ts[status],
}
result = self._get_result(client, 'edit', args)
if self.error:
return False
if not result.success:
self.error = result.message
return False
return True
def client_switch(self, rfc, type_user):
"""Edita el tipo de timbrado (OnDemand o Prepago) de un cliente
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del cliente
Kwargs:
status (bool):
True == 'O' == OnDemand
False == 'P' == Prepago
Returns:
dict
'message':
'Account Created successfully'
'Account Already exists'
'success': True or False
"""
auth = AUTH['RESELLER']
tu = {True: 'O', False: 'P'}
method = 'client'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
args = {
'username': auth['user'],
'password': auth['pass'],
'taxpayer_id': rfc,
'type_user': tu[type_user],
}
result = self._get_result(client, 'switch', args)
if self.error:
return False
if not result.success:
self.error = result.message
return False
return True
def client_report_folios(self, rfc, date_from, date_to, invoice_type='I'):
"""Obtiene un reporte del total de facturas timbradas
"""
auth = AUTH['RESELLER']
args = {
'username': auth['user'],
'password': auth['pass'],
'taxpayer_id': rfc,
'date_from': date_from,
'date_to': date_to,
'invoice_type': invoice_type,
}
method = 'util'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
result = self._get_result(client, 'report_total', args)
if result.result is None:
# ~ PAC - Debería regresar RFC inexistente o sin registros
self.error = 'RFC no existe o no tiene registros'
return 0
total = result.result.ReportTotal[0].total
return total

View File

@ -1,87 +0,0 @@
#!/usr/bin/env python3
from decimal import Decimal, getcontext
# ~ getcontext().prec = 6
import lxml.etree as ET
from requests.structures import CaseInsensitiveDict as CIDict
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tfd': 'http://www.sat.gob.mx/TimbreFiscalDigital',
'nomina12': 'http://www.sat.gob.mx/nomina12',
}
PRE = '/cfdi:Comprobante'
class CfdiRead(object):
def __init__(self, source):
self._source = source
self._data = {}
self._error = ''
self._rfc_emisor = ''
self._rfc_receptor = ''
self._parse()
@property
def source(self):
return self._source
@property
def data(self):
return self._data
@property
def rfc_emisor(self):
return self._rfc_emisor
@property
def rfc_receptor(self):
return self._rfc_receptor
@property
def error(self):
return self._error
def _parse(self):
self._tree = ET.fromstring(self.source)
self._data['cfdi'] = dict(self._tree.attrib)
node_name = f'{PRE}/cfdi:Emisor'
self._data['emisor'] = self._get_attr(node_name)
self._rfc_emisor = self._data['emisor']['Rfc']
node_name = f'{PRE}/cfdi:Receptor'
self._data['receptor'] = self._get_attr(node_name)
self._rfc_receptor = self._data['receptor']['Rfc']
node_name = f'{PRE}/cfdi:Complemento/tfd:TimbreFiscalDigital'
self._data['timbre'] = self._get_attr(node_name)
self._parse_details()
return
def _get_attr(self, node_name):
node = self._tree.xpath(node_name, namespaces=NS_CFDI)[0]
attr = dict(node.attrib)
return attr
def _parse_details(self):
node_name = f'{PRE}/cfdi:Conceptos/cfdi:Concepto'
details = self._tree.xpath(node_name, namespaces=NS_CFDI)
rows = []
for detail in details:
row = dict(detail.attrib)
for k, v in row.items():
if k in ('Cantidad', 'ValorUnitario', 'Descuento', 'Importe'):
row[k] = Decimal(v)
# ~ row['taxes'] = self._get_taxes(detail)
rows.append(row)
self._data['conceptos'] = rows
return
class CfdiWrite(object):
pass

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
server {
listen 80;
server_name cfdi.empresalibre.net;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3033;
access_log /var/log/nginx/empresa-libre.access.log;
error_log /var/log/nginx/empresa-libre.error.log;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location /static {
alias /opt/empresa-libre/static;
}
if ($http_user_agent ~* (python|curl) ) {
return 404;
}
# Necesario para Let's Encrypt
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /opt/www;
}
}

View File

@ -1,9 +0,0 @@
[Unit]
Description=uWSGI instance to serve Empresa Libre
[Service]
ExecStart=/usr/bin/uwsgi /opt/empresa-libre/app/main.ini
KillSignal=SIGQUIT
[Install]
WantedBy=multi-user.target

13
source/app/main.ini Normal file
View File

@ -0,0 +1,13 @@
[uwsgi]
socket = 127.0.0.1:3033
uid = nginx
gid = nginx
chdir = /srv/app/empresa-libre/app
wsgi-file = main.py
callable = app
master = true
processes = 4
threads = 4
thunder-lock = true
#~ stats = 127.0.0.1:9191
logger = file:/srv/log/empresalibre-uwsgi.log

View File

@ -1,19 +0,0 @@
[uwsgi]
#~ Debe de corresponder con tu configuracion proxi en tu servidor web
socket = 127.0.0.1:3033
uid = nginx
gid = nginx
#~ Establece una ruta accesible para el servidor web
chdir = /srv/app/empresa-libre/app
wsgi-file = main.py
callable = app
master = true
#~ Puedes configurar de acuerdo a los recursos de tu servidor
processes = 4
threads = 4
thunder-lock = true
#~ stats = 127.0.0.1:9191
#~ Establece una ruta accesible para el servidor web
logger = file:/srv/log/empresalibre-uwsgi.log
log-maxsize = 1000000
http-timeout = 180

View File

@ -8,29 +8,16 @@ from middleware import (
AuthMiddleware,
JSONTranslator,
ConnectionMiddleware,
static,
handle_404
)
from models.db import StorageEngine
from controllers.main import (AppEmpresas,
from controllers.main import (
AppLogin, AppLogout, AppAdmin, AppEmisor, AppConfig,
AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios,
AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco,
AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina,
AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco,
AppSATFormaPago, AppSATLeyendaFiscales, AppCert, AppSucursales,
AppPartnerProducts,
AppInventoryEntries,
AppTicketsDetails,
AppUsers,
AppWareHouse,
AppWareHouseProduct,
AppSATUnidadesPeso,
AppSATRegimenes,
AppSociosRegimenes,
AppDocumentos
)
from settings import DEBUG, MV, NO_HTTPS, PATH_SESSIONS
from settings import DEBUG
db = StorageEngine()
@ -44,7 +31,6 @@ api = falcon.API(middleware=[
api.req_options.auto_parse_form_urlencoded = True
api.add_sink(handle_404, '')
api.add_route('/empresas', AppEmpresas(db))
api.add_route('/', AppLogin(db))
api.add_route('/logout', AppLogout(db))
api.add_route('/admin', AppAdmin(db))
@ -52,49 +38,23 @@ api.add_route('/emisor', AppEmisor(db))
api.add_route('/folios', AppFolios(db))
api.add_route('/main', AppMain(db))
api.add_route('/values/{table}', AppValues(db))
api.add_route('/files/{table}', AppFiles(db))
api.add_route('/config', AppConfig(db))
api.add_route('/doc/{type_doc}/{id_doc}', AppDocumentos(db))
api.add_route('/partners', AppPartners(db))
api.add_route('/products', AppProducts(db))
api.add_route('/invoices', AppInvoices(db))
api.add_route('/preinvoices', AppPreInvoices(db))
api.add_route('/tickets', AppTickets(db))
api.add_route('/cuentasbanco', AppCuentasBanco(db))
api.add_route('/movbanco', AppMovimientosBanco(db))
api.add_route('/students', AppStudents(db))
api.add_route('/employees', AppEmployees(db))
api.add_route('/nomina', AppNomina(db))
api.add_route('/invoicepay', AppInvoicePay(db))
api.add_route('/cfdipay', AppCfdiPay(db))
api.add_route('/satbancos', AppSATBancos(db))
api.add_route('/satformapago', AppSATFormaPago(db))
api.add_route('/socioscb', AppSociosCuentasBanco(db))
api.add_route('/leyendasfiscales', AppSATLeyendaFiscales(db))
api.add_route('/cert', AppCert(db))
api.add_route('/sucursales', AppSucursales(db))
api.add_route('/partnerproducts', AppPartnerProducts(db))
api.add_route('/inventoryentries', AppInventoryEntries(db))
api.add_route('/warehouse', AppWareHouse(db))
api.add_route('/warehouseproduct', AppWareHouseProduct(db))
api.add_route('/ticketsdetails', AppTicketsDetails(db))
api.add_route('/users', AppUsers(db))
api.add_route('/satunidadespeso', AppSATUnidadesPeso(db))
api.add_route('/satregimenes', AppSATRegimenes(db))
api.add_route('/sociosregimenes', AppSociosRegimenes(db))
if DEBUG:
api.add_sink(static, '/static')
session_options = {
'session.type': 'file',
'session.cookie_expires': True,
'session.httponly': True,
'session.secure': True,
'session.data_dir': PATH_SESSIONS['data'],
'session.lock_dir': PATH_SESSIONS['lock'],
'session.data_dir': '/tmp/cache/data',
'session.lock_dir': '/tmp/cache/lock',
}
# ~ Si no usas (NO deberías) certificados en tu servidor, ponla siempre en False
if DEBUG or MV or NO_HTTPS:
session_options['session.secure'] = False
app = SessionMiddleware(api, session_options)

View File

@ -1,11 +1,9 @@
[uwsgi]
http = 127.0.0.1:8000
#~ http = 37.228.132.181:9000
wsgi-file = main.py
callable = app
master = true
processes = 4
threads = 4
py-autoreload = 1
thunder-lock = true
static-map = /static=../static
http-timeout = 300

View File

@ -1,13 +0,0 @@
[uwsgi]
http = :8000
uid = empresa
gid = empresa
chdir = /home/empresa/.opt/empresa-libre/source/app
wsgi-file = main.py
callable = app
master = true
processes = 4
threads = 4
thunder-lock = true
static-map = /static=../static
logger = file:../../../empresa-libre-uwsgi.log

View File

@ -3,7 +3,7 @@
import falcon
from controllers import util
from models import main
from settings import MV
from settings import PATH_STATIC
def handle_404(req, resp):
@ -20,44 +20,22 @@ def get_template(req, resp, resource):
resp.body = util.get_template(resource.template, data)
# ~ def static(req, res):
# ~ path = PATH_STATIC + req.path
# ~ if util.is_file(path):
# ~ res.content_type = util.get_mimetype(path)
# ~ res.stream, res.stream_len = util.get_stream(path)
# ~ res.status = falcon.HTTP_200
# ~ else:
# ~ res.status = falcon.HTTP_404
def static(req, res):
path = PATH_STATIC + req.path
if util.is_file(path):
res.content_type = util.get_mimetype(path)
res.stream, res.stream_len = util.get_stream(path)
res.status = falcon.HTTP_200
else:
res.status = falcon.HTTP_404
class AuthMiddleware(object):
def process_response(self, req, resp, resource):
pass
def process_resource(self, req, resp, resource, params):
session = req.env['beaker.session']
user = session.get('userobj', None)
id_session = req.cookies.get('beaker.session.id', '')
if req.path == '/values/titlelogin':
pass
elif req.path == '/empresas' or req.path == '/values/empresas':
if MV:
pass
else:
raise falcon.HTTPTemporaryRedirect('/')
elif id_session and req.path == '/admin':
if user is None:
raise falcon.HTTPTemporaryRedirect('/')
elif not user.es_admin and not user.es_superusuario:
raise falcon.HTTPTemporaryRedirect('/main')
elif not id_session and req.path != '/':
if not id_session and req.path != '/':
raise falcon.HTTPTemporaryRedirect('/')
elif id_session and user is None:
session.delete()
if req.path == '/main':
raise falcon.HTTPTemporaryRedirect('/')
class JSONTranslator(object):
@ -68,15 +46,9 @@ class JSONTranslator(object):
def process_response(self, req, resp, resource):
if 'result' not in req.context:
return
if '/doc/' in req.path:
resp.body = req.context['result']
return
if 'blob' in req.context:
resp.body = req.context['blob']
return
resp.body = util.dumps(req.context['result'])
@ -89,10 +61,7 @@ class ConnectionMiddleware(object):
rfc = session.get('rfc', '')
if id_session and rfc:
opt = util.get_con(rfc)
if opt:
main.conectar(opt)
else:
raise falcon.HTTPTemporaryRedirect('/')
main.conectar(opt)
def process_response(self, req, resp, resource):
main.desconectar()

View File

@ -1,21 +1,5 @@
#!/usr/bin/env python
# ~ Empresa Libre
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.net)
# ~
# ~ 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 <http://www.gnu.org/licenses/>.
from . import main
@ -27,89 +11,17 @@ class StorageEngine(object):
def authenticate(self, args):
return main.authenticate(args)
def get_employees(self, values):
return main.Empleados.get_by(values)
def get_nomina(self, values):
return main.CfdiNomina.get_by(values)
def empresa_agregar(self, values):
# ~ return main.empresa_agregar(values['alta_rfc'], False)
return main._new_client(values['alta_rfc'], False)
def empresa_borrar(self, values):
# ~ return main.empresa_borrar(values['rfc'])
return main._delete_client(values['rfc'], False, False)
def respaldar_dbs(self):
return main.respaldar_dbs()
def get_values(self, table, values=None, session=None):
if table in ('allusuarios', 'usuarioupdate', 'main'):
return getattr(self, '_get_{}'.format(table))(values, session)
def get_values(self, table, values=None):
return getattr(self, '_get_{}'.format(table))(values)
def _get_timbres(self, values):
return main.Emisor.get_timbres()
def _get_schoolgroups(self, values):
return main.Grupos.get_by(values)
def _get_nivedusat(self, values):
return main.SATNivelesEducativos.get_by()
def _get_niveduall(self, values):
return main.NivelesEducativos.get_all()
def _get_titlelogin(self, values):
return main.get_title_app(2)
def _get_canopenpre(self, values):
return main.PreFacturasDetalle.can_open(values['id'])
def _get_importinvoice(self, values):
return main.import_invoice()
def _get_importceods(self, values):
return main.import_ceods()
def _get_main(self, values, session):
return main.config_main(session['userobj'])
def _get_configtimbrar(self, values):
return main.config_timbrar()
def _get_invoicenotes(self, values):
return main.Facturas.get_notes(values['id'])
def save_invoice_notes(self, values):
return main.Facturas.save_notes(values)
def _get_configticket(self, values):
return main.config_ticket()
def _get_saldocuenta(self, values):
return main.CuentasBanco.get_saldo(values['id'])
def _get_validartimbrar(self, values):
return main.validar_timbrar()
def _get_preproductos(self, values):
return main.PreFacturasDetalle.facturar(values['id'])
def upload_file(self, session, table, file_obj):
if not 'rfc' in session:
return {'status': 'error'}
return main.upload_file(session['rfc'], table, file_obj)
def get_config(self, values):
return main.Configuracion.get_(values)
def add_config(self, values):
return main.Configuracion.add(values)
def add_cert(self, file_obj):
return main.Certificado.add(file_obj)
def add_cert(self, file_object):
return main.Certificado.add(file_object)
def validate_cert(self, values, session):
return main.Certificado.validate(values, session)
@ -120,26 +32,6 @@ class StorageEngine(object):
def send_email(self, values, session):
return main.Facturas.send(values['id'], session['rfc'])
def enviar_prefac(self, values):
return main.PreFacturas.enviar(values['id'])
def _get_verifysat(self, values):
return main.Facturas.get_verify_sat(values['id'])
def _get_filteryears(self, values):
years1 = main.Facturas.filter_years()
years2 = main.PreFacturas.filter_years()
return [years1, years2]
def _get_filteryearsticket(self, values):
return main.Tickets.filter_years()
def _get_filteryearsnomina(self, values):
return main.CfdiNomina.filter_years()
def _get_cuentayears(self, values):
return main.CuentasBanco.get_years()
def _get_cert(self, values):
return main.Certificado.get_data()
@ -149,12 +41,6 @@ class StorageEngine(object):
def _get_formapago(self, values):
return main.SATFormaPago.get_activos(values)
def _get_tiporelacion(self, values):
return main.SATTipoRelacion.get_activos(values)
def _get_condicionespago(self, values):
return main.CondicionesPago.get_()
def _get_categorias(self, values):
return main.Categorias.get_all()
@ -164,246 +50,70 @@ class StorageEngine(object):
def _get_unidades(self, values):
return main.SATUnidades.get_activos()
def _get_unitbykey(self, values):
return main.SATUnidades.get_activos_by_key()
def add_moneda(self, values):
return main.SATMonedas.add(values)
def add_unidad(self, values):
return main.SATUnidades.add(values)
def add_impuesto(self, values):
return main.SATImpuestos.add(values)
def add_usuario(self, values):
return main.Usuarios.add(values)
def edit_usuario(self, values):
return main.Usuarios.edit(values)
def _get_taxes(self, values):
return main.SATImpuestos.get_activos()
def _get_alltaxes(self, values):
return main.SATImpuestos.get_()
def _get_allcurrencies(self, values):
return main.SATMonedas.get_()
def _get_allbancos(self, values):
return main.SATBancos.get_()
def _get_allunidades(self, values):
return main.SATUnidades.get_()
def _get_allformasdepago(self, values):
return main.SATFormaPago.get_()
def _get_allusoscfdi(self, values):
return main.SATUsoCfdi.get_all()
def _get_allregimenes(self, values):
filters = {'opt': 'all'}
return main.SATRegimenes.get_data(filters, None)
def _get_allusuarios(self, values, session):
return main.Usuarios.get_(session['userobj'])
def _get_usuarioupdate(self, values, session):
return main.Usuarios.actualizar(values, session['userobj'])
def _get_taxupdate(self, values):
return main.SATImpuestos.actualizar(values)
def _get_currencyupdate(self, values):
return main.SATMonedas.actualizar(values)
def _get_bancoupdate(self, values):
return main.SATBancos.actualizar(values)
def _get_emisorbancoupdate(self, values):
return main.CuentasBanco.activate(values)
def _get_unidadupdate(self, values):
return main.SATUnidades.actualizar(values)
def _get_formasdepagoupdate(self, values):
return main.SATFormaPago.actualizar(values)
def _get_usocfdiupdate(self, values):
return main.SATUsoCfdi.actualizar(values)
def _get_regimenesupdate(self, values):
return main.SATRegimenes.actualizar(values)
def _get_emisorcuentasbanco(self, values):
return main.CuentasBanco.emisor()
def _get_satkey(self, values):
return main.get_sat_key(values['key'])
def _get_satmonedas(self, values):
return main.get_sat_monedas(values['key'])
def _get_satunidades(self, values):
return main.get_sat_unidades(values['key'])
def _get_satunidadespeso(self, values):
return main.get_sat_unidadespeso(values['key'])
def _get_satproductos(self, values):
return main.get_sat_productos(values['key'])
def _get_series(self, values):
return main.Folios.get_all()
def _get_monedas(self, values):
return main.SATMonedas.get_activos()
def _get_monedasid(self, values):
return main.SATMonedas.get_activos_by_id()
def _get_bancosid(self, values):
return main.SATBancos.get_activos_by_id()
def _get_regimenes(self, values):
return main.Emisor.get_regimenes()
def _get_usocfdi(self, values):
return main.SATUsoCfdi.get_activos()
return main.SATUsoCfdi.get_activos(values)
def _get_ebancomov(self, values):
return main.MovimientosBanco.con(values['id'])
def delete(self, table, id, user=None):
def delete(self, table, id):
if table == 'partner':
return main.Socios.remove(id)
if table == 'product':
return main.Productos.remove(id)
if table == 'invoice':
return main.Facturas.remove(id, user)
return main.Facturas.remove(id)
if table == 'folios':
return main.Folios.remove(id)
if table == 'preinvoice':
return main.PreFacturas.remove(id)
if table == 'satimpuesto':
return main.SATImpuestos.remove(id)
if table == 'satunit':
return main.SATUnidades.remove(id)
if table == 'cuentasbanco':
return main.CuentasBanco.remove(id)
if table == 'movbanco':
return main.MovimientosBanco.remove(id)
if table == 'usuario':
return main.Usuarios.remove(id)
if table == 'config':
return main.Configuracion.remove(id)
if table == 'nivedu':
return main.NivelesEducativos.remove(id)
if table == 'students':
return main.Alumnos.remove(id)
if table == 'employee':
return main.Empleados.remove(id)
if table == 'nomina':
return main.CfdiNomina.remove(id)
return False
def _get_client(self, values):
return main.Socios.get_by_client(values)
def _get_student(self, values):
return main.Alumnos.get_by_name(values)
def _get_product(self, values):
return main.Productos.get_by(values)
def _get_productokey(self, values):
return main.Productos.get_by_key(values)
def get_partners(self, values):
return main.Socios.get_(values)
def partner(self, values):
if 'opt' in values:
return main.Socios.opt(values)
id = int(values.pop('id', '0'))
if id:
return main.Socios.actualizar(values, id)
return main.Socios.add(values)
def get_products(self, values):
return main.Productos.get_(values)
def products(self, values):
def product(self, values):
id = int(values.pop('id', '0'))
if id:
return main.Productos.actualizar(values, id)
opt = values.get('opt', '')
if opt:
return main.Productos.opt(values)
return main.Productos.add(values)
def invoice(self, values, user):
if 'opt' in values:
return main.Facturas.opt(values, user)
def invoice(self, values):
id = int(values.pop('id', '0'))
if id:
return main.Facturas.actualizar(values, id)
return main.Facturas.add(values)
return main.Facturas.add(values, user)
def get_invoices(self, values):
return main.Facturas.get_(values)
def invoice_put(self, values, user):
return main.Facturas.put(values, user)
def preinvoice(self, values):
id = int(values.pop('id', '0'))
#~ if id:
#~ return main.PreFacturas.actualizar(values, id)
return main.PreFacturas.add(values)
def get_students(self, values):
return main.Alumnos.get_by(values)
def students(self, values):
opt = values.pop('opt')
if opt == 'add':
return main.Alumnos.add(values['values'])
if opt == 'edit':
return main.Alumnos.actualizar(values['values'])
def tickets(self, values, user):
opt = values.pop('opt')
if opt == 'add':
return main.Tickets.add(values, user)
if opt == 'cancel':
return main.Tickets.cancel(values, user)
if opt == 'invoice':
return main.Tickets.invoice(values, user)
if opt == 'print':
return main.Tickets.printer(values)
def get_tickets(self, values, user):
return main.Tickets.get_by(values, user)
def get_invoices(self, filters, user):
if filters.get('by', ''):
return main.Facturas.get_by(filters, user)
return main.Facturas.get_(filters)
def get_preinvoices(self, values):
return main.PreFacturas.get_(values)
# ~ def _get_timbrar(self, values):
# ~ return main.Facturas.timbrar(values)
def _get_anticipoegreso(self, values):
return main.Facturas.anticipo_egreso(int(values['id']))
def _get_timbrar(self, values):
return main.Facturas.timbrar(int(values['id']))
def get_emisor(self, rfc):
return main.Emisor.get_(rfc)
@ -411,138 +121,21 @@ class StorageEngine(object):
def emisor(self, values):
return main.Emisor.add(values)
def cuentasbanco(self, values):
return main.CuentasBanco.add(values)
# ~ def add_movbanco(self, values):
# ~ return main.MovimientosBanco.add(values)
def get_cuentasbanco(self, values):
return main.CuentasBanco.get_(values)
def get_folios(self):
return main.Folios.get_()
def add_folios(self, values):
return main.Folios.add(values)
def add_nivel_educativo(self, values):
return main.NivelesEducativos.add(values)
def get_doc(self, type_doc, id, rfc):
return main.get_doc(type_doc, id, rfc)
def get_movimientosbanco(self, values):
return main.MovimientosBanco.get_(values)
def importar_bdfl(self):
return main.importar_bdfl()
# ~ Revisado
def get_invoicepay(self, values):
return main.FacturasPagos.get_values(values)
def get_cfdipay(self, values):
return main.CfdiPagos.get_values(values)
def get_sat_bancos(self, values):
return main.SATBancos.get_values(values)
def get_sat_forma_pago(self, values):
return main.SATFormaPago.get_values(values)
def get_partners_accounts_bank(self, values):
return main.SociosCuentasBanco.get_values(values)
def cfdipay(self, values):
return main.CfdiPagos.post(values)
def bankmovement(self, values):
return main.MovimientosBanco.post(values)
def sat_bancos(self, values):
return main.SATBancos.post(values)
def sat_forma_pago(self, values):
return main.SATFormaPago.post(values)
def partners_accounts_bank(self, values):
return main.SociosCuentasBanco.post(values)
def nomina(self, values, user):
return main.CfdiNomina.post(values, user)
def sat_leyendas_fiscales_get(self, values):
return main.SATLeyendasFiscales.get_values(values)
def sat_leyendas_fiscales_post(self, values):
return main.SATLeyendasFiscales.post(values)
def sat_leyendas_fiscales_delete(self, values):
return main.SATLeyendasFiscales.remove(values)
# ~ v2
def cert_get(self, filters):
return main.Certificado.get_data(filters)
def cert_post(self, filters):
return main.Certificado.post(filters)
def sucursales_get(self, filters):
return main.Sucursales.get_data(filters)
def sucursales_post(self, filters):
return main.Sucursales.post(filters)
def partner_products_get(self, filters):
return main.PartnerProducts.get_data(filters)
def inventory_entries_get(self, filters):
return main.InventoryEntries.get_data(filters)
def inventory_entries_post(self, filters, user):
return main.InventoryEntries.post(filters, user)
def warehouse_get(self, filters):
return main.Almacenes.get_data(filters)
def warehouse_post(self, values, user):
return main.Almacenes.post(values, user)
def warehouseproduct_get(self, filters, user):
return main.WareHouseProduct.get_data(filters, user)
def warehouseproduct_post(self, filters, user):
return main.WareHouseProduct.post(filters, user)
def users_get(self, filters, user):
return main.Usuarios.get_data(filters, user)
def users_post(self, args, user):
return main.Usuarios.post(args, user)
def ticketsdetails_get(self, filters, user):
return main.TicketsDetalle.get_data(filters, user)
def products_get(self, filters, user):
return main.Productos.get_data(filters, user)
def nomina_get(self, filters, user):
return main.CfdiNomina.get_data(filters, user)
def sat_unidades_peso_get(self, filters, user):
return main.SATUnidadesPeso.get_data(filters, user)
def sat_unidades_peso_post(self, args, user):
return main.SATUnidadesPeso.post(args, user)
def sat_regimenes_get(self, filters, user):
return main.SATRegimenes.get_data(filters, user)
def socios_regimenes_get(self, filters, user):
return main.SociosRegimenes.get_data(filters, user)
# Companies only in MV
def _get_empresas(self, values):
return main.companies_get()
if type_doc == 'xml':
data, file_name = main.Facturas.get_xml(id)
content_type = 'application/xml'
if type_doc == 'pdf':
data, file_name = main.Facturas.get_pdf(id, rfc)
content_type = 'application/pdf'
if type_doc == 'zip':
data, file_name = main.Facturas.get_zip(id, rfc)
content_type = 'application/octet-stream'
return data, file_name, content_type

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,4 @@
#!/usr/bin/env python3
# ~ Empresa Libre
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (publico@cuates.net)
# ~
# ~ 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 <http://www.gnu.org/licenses/>.
#!/usr/bin/env python
import logbook
import os
@ -23,40 +7,15 @@ from mako.lookup import TemplateLookup
from logbook import Logger, StreamHandler, RotatingFileHandler
logbook.set_datetime_format('local')
from conf import DEBUG, MV, LOG_PATH
try:
from conf import DEFAULT_PASSWORD
except ImportError:
DEFAULT_PASSWORD = 'salgueiro4.0'
TITLE_APP = 'Empresa Libre'
try:
from conf import NO_HTTPS
except ImportError:
NO_HTTPS = True
from conf import DEBUG
DEBUG = DEBUG
VERSION = '2.3.3'
VERSION = '0.1.0'
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
path_static = os.path.abspath(os.path.join(BASE_DIR, '..', 'static'))
path_docs = os.path.abspath(os.path.join(BASE_DIR, '..', 'docs'))
path_css = os.path.join(path_static, 'css')
path_img = os.path.join(path_static, 'img')
path_user_template = os.path.join(path_docs, 'templates')
path_user_logos = os.path.join(path_docs, 'logos')
# ~ PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..'))
PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..'))
PATH_TEMPLATES = os.path.abspath(os.path.join(BASE_DIR, '..', 'templates'))
PATH_MEDIA = os.path.abspath(os.path.join(BASE_DIR, '..', 'docs'))
@ -64,26 +23,17 @@ PATH_CP = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'cp.db'))
COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
DB_SAT = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'sat.db'))
PATH_SESSIONS = {
'data': os.path.abspath(os.path.join(BASE_DIR, '..', 'cache', 'data')),
'lock': os.path.abspath(os.path.join(BASE_DIR, '..', 'cache', 'lock')),
}
IV = 'valores_iniciales.json'
INIT_VALUES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', IV))
# ~ CT = 'cancel_template.xml'
# ~ TEMPLATE_CANCEL = os.path.abspath(os.path.join(PATH_TEMPLATES, CT))
PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt'))
PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin'))
PATH_TEMPLATES_USER = os.path.abspath(os.path.join(
BASE_DIR, '..', 'docs', 'templates'))
directories=[PATH_TEMPLATES, PATH_TEMPLATES_USER]
template_lookup = TemplateLookup(directories=directories,
template_lookup = TemplateLookup(directories=[PATH_TEMPLATES],
input_encoding='utf-8',
output_encoding='utf-8')
LOG_PATH = 'empresalibre.log'
LOG_NAME = 'API'
LOG_LEVEL = 'INFO'
@ -100,6 +50,7 @@ if DEBUG:
level=LOG_LEVEL,
format_string=format_string).push_application()
else:
LOG_PATH = '/srv/log/empresalibre.log'
RotatingFileHandler(
LOG_PATH,
backup_count=10,
@ -118,202 +69,22 @@ log = Logger(LOG_NAME)
PATH_XSLTPROC = 'xsltproc'
PATH_OPENSSL = 'openssl'
PATH_XMLSEC = 'xmlsec1'
if 'win' in sys.platform:
PATH_XSLTPROC = os.path.join(PATH_BIN, 'xsltproc.exe')
PATH_OPENSSL = os.path.join(PATH_BIN, 'openssl.exe')
PATH_XMLSEC = os.path.join(PATH_BIN, 'xmlsec.exe')
PRE_DEFAULT = {
'CFDI': {'VERSION': '4.0', 'PRE': '{http://www.sat.gob.mx/cfd/4}'},
'NOMINA': {'VERSION': '1.2', 'PRE': '{http://www.sat.gob.mx/nomina12}'},
'PAGOS': {'VERSION': '2.0', 'PRE': '{http://www.sat.gob.mx/Pagos20}'},
'TIBRE': {'VERSION': '1.1', 'PRE': '{http://www.sat.gob.mx/TimbreFiscalDigital}'},
}
pre2 ='{http://www.sat.gob.mx/cfd/2}'
pre3 ='{http://www.sat.gob.mx/cfd/3}'
PRE_HISTORY = {
'CFDI': {'2.0': pre2, '2.2': pre2,
'3.0': pre3, '3.2': pre3, '3.3': pre3},
'NOMINA': {'1.1': '{http://www.sat.gob.mx/nomina}'},
'PAGOS': {'1.0': '{http://www.sat.gob.mx/Pagos}'},
}
PRE = {
'2.0': '{http://www.sat.gob.mx/cfd/2}',
'2.2': '{http://www.sat.gob.mx/cfd/2}',
'3.0': '{http://www.sat.gob.mx/cfd/3}',
'3.2': '{http://www.sat.gob.mx/cfd/3}',
'3.3': '{http://www.sat.gob.mx/cfd/3}',
'4.0': '{http://www.sat.gob.mx/cfd/4}',
'TIMBRE': '{http://www.sat.gob.mx/TimbreFiscalDigital}',
'DONATARIA': '{http://www.sat.gob.mx/donat}',
'INE': '{http://www.sat.gob.mx/ine}',
'LOCALES': '{http://www.sat.gob.mx/implocal}',
'NOMINA': {
'1.1': '{http://www.sat.gob.mx/nomina}',
'1.2': '{http://www.sat.gob.mx/nomina12}',
},
'PAGOS': {
'1.0': '{http://www.sat.gob.mx/Pagos}',
}
}
}
# To delete
# ~ CURRENT_CFDI = '4.0'
# ~ CURRENT_CFDI_NOMINA = '1.2'
DECIMALES = 2
DECIMALES_TAX = 4
DECIMALES_PRECIOS = 4
IMPUESTOS = {
'ISR': '001',
'IVA': '002',
'IEPS': '003',
'EXENTO': '000',
'ISH': '000',
'INSPECCION DE OBRA': '000',
'ICIC': '000',
'CEDULAR': '000',
'CMIC': '000',
'SUPERVISION': '000',
}
DEFAULT_SAT_PRODUCTO = '01010101'
DEFAULT_SERIE_TICKET = 'T'
DEFAULT_CFDIPAY = {
'SERIE': 'FP',
'TYPE': 'P',
'WAYPAY': 'PPD',
'CURRENCY': 'XXX',
'TC': '1',
'USED': 'CP01',
'KEYSAT': '84111506',
'UNITKEY': 'ACT',
'DESCRIPTION': 'Pago',
'TYPE_RELATION': '04',
}
DIR_FACTURAS = 'facturas'
USAR_TOKEN = False
CANCEL_SIGNATURE = False
PUBLIC = 'Público en general'
DEFAULT_SAT_NOMINA = {
'SERIE': 'N',
'FORMA_PAGO': '99',
'USO_CFDI': 'CN01',
'CLAVE': '84111505',
'UNIDAD': 'ACT',
'DESCRIPCION': 'Pago de nómina',
}
API = 'https://api.empresalibre.net{}'
CURRENCY_MN = 'MXN'
# ~ v2
CANCEL_VERSION = ('3.3', '4.0')
CFDI_VERSIONS = CANCEL_VERSION
IS_MV = MV
DB_COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
path_bk = os.path.join(path_docs, 'tmp')
path_local = 'facturas'
path_sat = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'valores_iniciales.json'))
EXT = {
'CSS': 'css',
'HTML': 'html',
'ODS': 'ods',
'PNG': 'png',
'JSON': 'json',
}
MXN = 'MXN'
CARTA_PORTE = {
'MONEDA': 'XXX',
}
PATHS = {
'STATIC': path_static,
'CSS': path_css,
'IMG': path_img,
'DOCS': path_docs,
'USER': path_user_template,
'LOGOS': path_user_logos,
'BK': path_bk,
'LOCAL': path_local,
'SAT': path_sat,
'xslt': PATH_XSLT,
}
VALUES_PDF = {
'CANCEL': {True: 'inline', False: 'none'},
'TYPE': {'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado'},
'TAX': {'001': 'ISR', '002': 'IVA', '003': 'IEPS'},
'METHOD': {
'PUE': 'Pago en una sola exhibición',
'PPD': 'Pago en parcialidades o diferido',
},
}
RESICO = '626'
RFCS = {
'PUBLIC': 'XAXX010101000',
'FOREIGN': 'XEXX010101000',
'CVD110412TF6': 'finkok',
'SCD110105654': 'comercio',
'AAA010101AAA': 'comercio',
'SPR190613I52': 'comercio',
}
URL = {
'SEAFILE': 'https://seafile.cuates.net',
}
DEFAULT_GLOBAL = {
'cantidad': 1.00,
'unidad': 'ACT',
'descripcion': 'Venta',
'clave_sat': '01010101',
}
# ~ TEMPLATE_CANCEL = """<CancelaCFD xmlns="http://cancelacfd.sat.gob.mx">
# ~ <Cancelacion xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RfcEmisor="{rfc}" Fecha="{fecha}">
# ~ <Folios>
# ~ <Folio UUID="{uuid}" Motivo="{motivo}"{folio}/>
# ~ </Folios>
# ~ <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
# ~ <SignedInfo>
# ~ <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
# ~ <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
# ~ <Reference URI="">
# ~ <Transforms>
# ~ <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
# ~ </Transforms>
# ~ <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
# ~ <DigestValue/>
# ~ </Reference>
# ~ </SignedInfo>
# ~ <SignatureValue/>
# ~ <KeyInfo>
# ~ <X509Data>
# ~ <X509IssuerSerial>
# ~ <X509IssuerName/>
# ~ <X509SerialNumber/>
# ~ </X509IssuerSerial>
# ~ <X509Certificate/>
# ~ </X509Data>
# ~ <KeyValue>
# ~ <RSAKeyValue>
# ~ <Modulus/>
# ~ <Exponent/>
# ~ </RSAKeyValue>
# ~ </KeyValue>
# ~ </KeyInfo>
# ~ </Signature>
# ~ </Cancelacion>
# ~ </CancelaCFD>"""
TEMPLATE_CANCEL = """<CancelaCFD xmlns="http://cancelacfd.sat.gob.mx"><Cancelacion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Fecha="{fecha}" RfcEmisor="{rfc}" xmlns="http://cancelacfd.sat.gob.mx"><Folios><Folio UUID="{uuid}" Motivo="{motivo}"{folio}/></Folios><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue/></Reference></SignedInfo><SignatureValue/><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName/><X509SerialNumber/></X509IssuerSerial><X509Certificate/></X509Data><KeyValue><RSAKeyValue><Modulus/><Exponent/></RSAKeyValue></KeyValue></KeyInfo></Signature></Cancelacion></CancelaCFD>"""
CURRENT_CFDI = '3.3'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,473 +0,0 @@
id key name
1 Tu Contenedor externo
2 X1A Tambor de acero
3 X1B Tambor de aluminio
4 X1D Tambor contrachapado
5 X1F Contenedor flexible
6 X1G Tambor de fibra
7 X1w Tambor de madera
8 X2C Barril de madera
9 X3A Bidón de acero
10 X3H Bidón de plástico
11 X43 Bolsa de gran tamaño
12 X44 Bolsa de plástico
13 X4A Caja de acero
14 X4B Caja de aluminio
15 X4C Caja de madera natural
16 X4D Caja de contrachapado
17 X4F Caja de madera reconstituida
18 X4G Caja de cartón
19 X4H Caja de plástico
20 X5H Bolsa de plástico tejido
21 X5L Bolsa textil
22 X5M Bolsa de papel
23 X6H Recipiente de plástico, Contenedor compuesto.
24 X6P Recipiente de vidrio, Contenedor compuesto.
25 X7A Estuche para carro
26 X7B Estuche de madera
27 X8A Pallet de madera
28 X8B Cajón de madera
29 X8C Madera flejada
30 XAA Contenedor intermedio para gráneles de plástico rígido
31 XAB Contenedor de fibra
32 XAC Contenedor de papel
33 XAD Contenedor de madera
34 XAE Aerosol
35 XAF Pallet modular con collares, 80cms * 60cms
36 XAG Pallet o empaquetado
37 XAH Pallet, 100cms X 110cms
38 XAI Contenedor tipo concha
39 XAJ Cono
40 XAL Esfera
41 XAM Ampolleta no protegida
42 XAP Ampolleta protegida
43 XAT Atomizador
44 XAV Cápsula
45 XB4 Cinturón
46 XBA Barril
47 XBB Bobina
48 XBC Cajón para botellas / Estante para botellas
49 XBD Tablero
50 XBE Flejado
51 XBF Globo no protegido
52 XBG Bolso
53 XBH Manojo
54 XBI Compartimiento
55 XBJ Cubeta
56 XBK Cesta
57 XBL Paca comprimida
58 XBM Cuenco
59 XBN Paca no comprimida
60 XBO Botella no-protegida y cilíndrica
61 XBP Globo protegido
62 XBQ Botella cilíndrica protegida
63 XBR Barra
64 XBS Botella, no-protegida en forma de bulbo
65 XBT Rollo de tela
66 XBU Butt
67 XBV Botella de bulbo protegido
68 XBW Caja para líquidos
69 XBX Caja
70 XBY Tablero, con fleje/ agrupados/ armados
71 XBZ Barras, con fleje/ agrupados/ armados
72 XCA Lata rectangular
73 XCB Cajón para cerveza
74 XCC Mantequera
75 XCD Lata con mango y boquilla
76 XCE Cesto tejido
77 XCF Cofre
78 XCG Contenedor tipo Jaula
79 XCH Cajonera
80 XCI Frasco
81 XCJ Ataúd
82 XCK Barrica
83 XCL Espiral
84 XCM Paquete de tarjetas
85 XCN Contenedor, no especificado como equipo de transporte
86 XCO Garrafón no protegido
87 XCP Garrafón protegido
88 XCQ Cartucho
89 XCR Cajón
90 XCS Estuche
91 XCT Cartón
92 XCU Vaso
93 XCV Cubierta
94 XCW Jaula estilo rodillo
95 XCX Lata cilíndrica
96 XCY Cilindro
97 XCZ Lona
98 XDA Cajón multicapa de plástico
99 XDB Cajón de varias capas de madera
100 XDC Cajón multicapa de cartón
101 XDG Jaula, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP))
102 XDH Caja, Según la clasificación de la compañía (CHEP), Eurobox
103 XDI Tambor de hierro
104 XDJ damajuana o garrafa, no protegido
105 XDK Cajón a granel, cartón
106 XDL Cajas de plástico
107 XDM Cajones a granel de madera
108 XDN Dispensador
109 XDP damajuana o garrafa, protegido
110 XDR Tambor
111 XDS Bandeja de una capa sin cubierta y de plástico
112 XDT Bandeja de una capa sin cubierta y de madera
113 XDU Bandeja de una capa sin cubierta y poliestireno
114 XDV Bandeja de una capa sin cubierta y de cartón
115 XDW Bandeja de dos capas sin tapa y con bandeja de plástico
116 XDX Bandeja de dos capas sin cubierta y de madera
117 XDY Bandeja de dos capas sin cubierta y de cartón
118 XEC Bolsa de plástico
119 XED Estuche, con pallet de base
120 XEE Estuche, con pallet base de madera
121 XEF Estuche, con pallet base de cartón
122 XEG Estuche, con pallet base de plástico
123 XEH Estuche, con pallet base de metal
124 XEI Estuche isotérmico
125 XEN Sobre
126 XFB Bolsa flexible
127 XFC Cajón para fruta
128 XFD Cajón enmarcado
129 XFE Tanque flexible
130 XFI Firkin
131 XFL Matraz
132 XFO Cajón para zapatos
133 XFP Caja auxiliar para película fotográfica
134 XFR Marco
135 XFT Contenedor para alimentos
136 XFW Carro de cama plana
137 XFX Bolsa flexible tipo contenedor
138 XGB Botella para gas
139 XGI Viga
140 XGL Contenedor tipo galón
141 XGR Recipiente de vidrio
142 XGU Bandeja contenedor para apilar horizontalmente objetos planos
143 XGY Costal de Yute
144 XGZ Vigas con correas o agrupadas
145 XHA Cesta con mango y de plástico
146 XHB Cesta con mango y de madera
147 XHC Cesta con asa y de cartón
148 XHG Hogshead
149 XHN Gancho
150 XHR Cesto
151 XIA Paquete con pantalla y de madera
152 XIB Paquete con pantalla y de cartón
153 XIC Paquete con pantalla y de plástico
154 XID Paquete con pantalla y de metal
155 XIE Paquete de mostrador.
156 XIF Envase para alimentos
157 XIG Paquete envuelto en papel
158 XIH Tambor de plástico
159 XIK Paquete de cartón con los agujeros para botellas
160 XIL Bandeja rígida con tapa y apilable (CEN TS 14482: 2002)
161 XIN Lingote
162 XIZ Lingotes con correas/ agrupados
163 XJB Bolsa jumbo
164 XJC Bidón rectangular
165 XJG Jarra
166 XJR Tarro
167 XJT Bolsa de yute
168 XJY Bidón, cilíndrico
169 XKG Barrilete
170 XKI Kit (Conjunto de piezas)
171 XLE Valijas
172 XLG Bitácora
173 XLT Lote
174 XLU Caja de arrastre
175 XLV Contenedor pequeño
176 XLZ Registros con fleje/ agrupados/ armados
177 XMA Cajón metálico
178 XMB Múltiplo de bolsas
179 XMC Cajón para leche
180 XME Contenedor de metal
181 XMR Recipiente de metal
182 XMS Saco milti-pared
183 XMT Tapete
184 XMW Contenedor envuelto en plástico
185 XMX Caja pequeña de cerillos
186 XNA No disponible
187 XNE Sin empaque o no empaquetado
188 XNF Sin empaque o no empaquetado, unidad simple
189 XNG Sin empaque o no empaquetado, unidades múltiples
190 XNS Caja nido
191 XNT Red
192 XNU Red de plástico con tubo
193 XNV Red textil con tubo
194 XOA Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 40 cm x 60 cm
195 XOB Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 80 cm x 120 cm
196 XOC Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 100 cm x 120 cm
197 XOD Pallet, AS 4068-1993
198 XOE Pallet, ISO T11
199 XOF Plataforma, peso o dimensión no especificada
200 XOK Bloque
201 XOT Octabin
202 XP2 Charola
203 XPA Cajetilla
204 XPB Pallet, Caja combinada y abierta con caja y pallet.
205 XPC Paquete postal
206 XPD Pallet modular con collares (80cms * 100cms)
207 XPE Pallet modular con collares (80cms * 120cms)
208 XPF Corral
209 XPG Placa
210 XPH Cantaro
211 XPI Pleca
212 XPJ Canastilla
213 XPK Paquete
214 XPL Balde
215 XPN Tablón
216 XPO Bolsa pequeña
217 XPR Contenedor de plástico
218 XPT Maceta
219 XPU Cacerola
220 XPV Tubos, con fleje/ agrupados/ armados
221 XPX Pallet
222 XPY Placas con fleje/ agrupados/ armados
223 XPZ Tablones con fleje/ agrupados/ armados
224 XQA Tambor de acero con cabeza no desmontable
225 XQB Tambor de acero con cabeza extraíble
226 XQC Tambor de aluminio con cabeza no extraíble
227 XQD Tambor de aluminio con cabeza extraíble
228 XQF Tambor, plástico con cabeza no desmontable
229 XQG Tambor, plástico, cabeza extraíble
230 XQH Barril de madera con tapón
231 XQJ Barril de madera con cabeza desprendible
232 XQK Bidón de acero con cabeza no desmontable
233 XQL Bidón de acero con cabeza desmontable
234 XQM Bidón de plástico con cabeza no desmontable
235 XQN Bidón de plástico con cabeza extraíble
236 XQP Caja de madera natural estándar
237 XQQ Caja de madera natural con muros a prueba de filtraciones
238 XQR Caja de plástico expandida
239 XQS Caja de plástico sólida
240 XRD Rod
241 XRG Anillo
242 XRJ Estante, Perchero para ropa
243 XRK Estante
244 XRL Carrete
245 XRO Rollo
246 XRT Red Roja
247 XRZ Varillas con fleje/ agrupados/ armados
248 XSA Saco
249 XSB Losa
250 XSC Cajón poco profundo
251 XSD Huso
252 XSE Baúl
253 XSH Bolsa pequeña hermética
254 XSI Patín
255 XSK Carcasa esqueleto
256 XSL Hoja de deslizamiento
257 XSM Hoja de metal
258 XSO Carrete pequeño
259 XSP Hoja de empaque de plástico
260 XSS Cajón de acero
261 XSU Maleta
262 XSV Sobre de acero
263 XSW Envoltorio
264 XSY Manga
265 XSZ Hojas con fleje/ agrupados/ armados
266 XT1 Tableta
267 XTB Tina
268 XTC Caja para té
269 XTD Tubo plegable
270 XTG Contenedor de tanque genérico
271 XTI Tierce
272 XTK Tanque rectangular
273 XTL Tina con tapa
274 XTN Hojalata
275 XTO Tonel
276 XTR Maletero
277 XTS Estructura
278 XTT Bolsa de mano
279 XTU Tubo
280 XTV Tubo con boquilla
281 XTW Pallet tricapa
282 XTY Tanque cilíndrico
283 XTZ Tubos con fleje/ agrupados/ armados
284 XUC Sin empaque
285 XUN Unidad
286 XVA Tanque
287 XVG Tanque de gas (a 1,031 mbar y 15° C)
288 XVI Frasco pequeño
289 XVK Paquete transportable
290 XVL Contenedor para líquidos a granel
291 XVN Vehículo
292 XVO "Contenedor para sólido de partículas grandes a granel (""nódulos"")"
293 XVP Envasado al vacío
294 XVQ Tanque para Gas licuado (a temperatura / presión anormal)
295 XVR Contenedor para sólidos de partículas granulares a granel (Granos)
296 XVS Contenedor de chatarra a granel
297 XVY "Contenedor para sólido de partículas finas a granel (""polvos"")"
298 XWA Contenedor de granel intermedio
299 XWB Botella de mimbre
300 XWC Contenedor intermedio para gráneles y de acero
301 XWD Contenedor intermedio para gráneles y de aluminio
302 XWF Contenedor intermedio para gráneles y de metal
303 XWG Contenedor intermedio para gráneles y de acero presurizado menor a 10 kpa
304 XWH Contenedor intermedio para gráneles y de aluminio, presurizado menor a 10 kpa
305 XWJ Contenedor intermedio para gráneles y de metal con una presión de 10 kpa
306 XWK Contenedor intermedio para gráneles y de acero para líquido
307 XWL Contenedor intermedio para gráneles y de aluminio para líquido
308 XWM Contenedor intermedio para gráneles y de metal para líquido
309 XWN Contenedor intermedio para gráneles con tejido plástico sin capa con revestimiento
310 XWP Contenedor intermedio para gráneles con tejido plástico y recubierto
311 XWQ Contenedor intermedio para gráneles con tejido plástico con revestimiento
312 XWR Contenedor intermedio para gráneles con tejido plástico, revestido y con forro
313 XWS Contenedor intermedio para gráneles con película de plástico
314 XWT Contenedor intermedio para gráneles textil sin capa / forro
315 XWU Contenedor intermedio para gráneles de madera natural con forro interior
316 XWV Contenedor intermedio para gráneles textil recubierto
317 XWW Contenedor intermedio para gráneles textil con revestimiento
318 XWX Contenedor intermedio para gráneles textil recubierto y con forro
319 XWY Contenedor intermedio para gráneles contrachapado con revestimiento interior
320 XWZ Contenedor intermedio para gráneles de madera reconstituida con revestimiento interior
321 XXA Bolsa de tejido plástico, sin abrigo interior ni forro
322 XXB Bolsa de tejido plástico a prueba de filtraciones
323 XXC Bolsa de tejido plástico resistente al agua
324 XXD Bolsa con película de plástico
325 XXF Bolsa textil sin capa ni forro interior
326 XXG Bolsa textil a prueba de filtraciones
327 XXH Bolsa textil resistente al agua
328 XXJ Bolsa de papel multi-pared
329 XXK Bolsa de papel multi-pared, resistente al agua
330 XYA Empaque compuesto, recipiente de plástico en tambor de acero
331 XYB Empaque compuesto, recipiente de plástico en cajas de acero
332 XYC Empaque compuesto, recipiente de plástico en tambor de aluminio
333 XYD Empaque compuesto, recipiente de plástico en cajón de aluminio
334 XYF Empaque compuesto, recipiente de plástico en caja de madera
335 XYG Empaque compuesto, recipiente de plástico en tambor de madera contrachapada
336 XYH Empaque compuesto, recipiente de plástico en caja de madera contrachapada
337 XYJ Empaque compuesto, recipiente de plástico en tambor de fibra
338 XYK Empaque compuesto, recipiente de plástico en caja de cartón
339 XYL Empaque compuesto, recipiente de plástico en el tambor de plástico
340 XYM Empaque compuesto, recipiente de plástico en caja de plástico sólido
341 XYN Empaque compuesto, receptáculo de vidrio en tambor de acero
342 XYP Empaque compuesto, receptáculo de vidrio en caja de cajas de acero
343 XYQ Empaque compuesto, recipiente de vidrio en tambor de aluminio
344 XYR Empaque compuesto, receptáculo de vidrio en caja de aluminio
345 XYS Empaque compuesto, recipiente de vidrio en caja de madera
346 XYT Empaque compuesto, recipiente de vidrio en tambor de madera contrachapada
347 Xyv Empaque compuesto, recipiente de vidrio en el cesto de mimbre
348 XYW Empaque compuesto, recipiente de vidrio en tambor de fibra
349 XYX Empaque compuesto, recipiente de vidrio en caja de cartón
350 XYY Empaque compuesto, recipiente de vidrio en paquete de plástico expandible
351 XYZ Empaque compuesto, recipiente de vidrio en paquete de plástico sólido
352 XZA Contenedor de granel intermedio, papel, multi-pared
353 XZB Bolsa grande
354 XZC Contenedor intermedio para gráneles de papel, multi-pared y resistente al agua
355 XZD Contenedor intermedio para gráneles de plástico rígido, con equipo estructural para sólidos
356 XZF Contenedor intermedio para gráneles de plástico rígido, autoportante para sólidos
357 XZG Contenedor intermedio para gráneles de plástico rígido, con equipo estructural, presurizado
358 XZH Contenedor intermedio para gráneles de plástico rígido, autoportante y presurizado
359 XZJ Contenedor intermedio para gráneles de plástico rígido, con equipo estructural para líquidos
360 XZK Contenedor intermedio para gráneles de plástico rígido, autoportante, líquidos
361 XZL Contenedor intermedio para gráneles, compuesto y de plástico rígido, sólidos
362 XZM Contenedor intermedio para gráneles, compuesto y de plástico flexible, sólidos
363 XZN Contenedor intermedio para gráneles, compuesto y de plástico rígido, presurizado
364 XZP Contenedor intermedio para gráneles, compuesto y de plástico flexible, presurizado
365 XZQ Contenedor intermedio para gráneles, compuesto y de plástico rígido, líquidos
366 XZR Contenedor intermedio para gráneles, compuesto y de plástico flexible para líquidos
367 XZS Contenedor intermedio para gráneles, compuesto
368 XZT Contenedor intermedio para gráneles con tablero de fibras
369 XZU Contenedor intermedio para gráneles flexible
370 XZV Contenedor intermedio para gráneles de metal, distinto del acero
371 XZW Contenedor intermedio para gráneles, de madera natural
372 XZX Contenedor intermedio para gráneles, de contrachapado
373 XZY Contenedor intermedio para gráneles, de madera reconstituida
374 KGM Kilogramo
375 MC Microgramo
376 DJ Decagramo
377 DG Decigramo
378 GRM Gramo
379 CGM Centigramo
380 TNE Tonelada (tonelada métrica)
381 DTN Decitonelada métrica
382 MGM Miligramo
383 HGM Hectogramo
384 KTN Kilotonelada Métrica
385 2U Megagramo
386 LBR Libra
387 GRN Grano
388 ONZ Onza (avoirdupois)
389 CWI Hundredweight
390 CWA Hundred pound
391 LTN Tonelada (UK) o tonelada larga (estados unidos)
392 STI Estone (UK)
393 STN Tonelada (estados unidos) o tonelada corta (UK y estados unidos)
394 APZ Onza troy u onza farmacéutica
395 F13 Slug
396 K64 Libra (avoirdupois) por grado fahrenheit
397 L69 Tonelada por kelvin
398 L87 Tonelada corta por grado fahrenheit
399 M85 Tonelada, ensayo
400 M86 Libra Alemana
401 J33 Microgramo por kilogramo
402 L32 Nanogramo por kilogramo
403 NA Miligramo por kilogramo
404 M29 Kilogramo por kilogramo
405 M91 Libra por libra
406 Q29 Microgramo por hectogramo
407 MTQ Metro cúbico
408 MAL Megalitro
409 LTR Litro
410 MMQ Milímetro cúbico
411 CMQ Centímetro cúbico
412 DMQ Decímetro cúbico
413 MLT Mililitro
414 HLT Hectolitro
415 CLT Centilitro
416 DMA Decámetro cúbico
417 H19 Hectómetro cúbico
418 H20 Kilómetro cúbico
419 M71 Metro cúbico por pascal (joules)
420 DLT Decilitro
421 4G Microlitro
422 K6 Kilolitro
423 A44 Decalitro
424 G94 Centímetro cúbico por bar
425 G95 Litro por bar
426 G96 Metro cúbico por bar
427 G97 Mililitro por bar
428 5I Pies cúbicos estándar
429 INQ Pulgada cúbica
430 FTQ Pie cúbico
431 YDQ Yarda cúbica
432 GLI Galón (UK)
433 GLL Galón (EUA)
434 PT Pinta (US)
435 PTI Pint (uk)
436 QTI Cuarto (UK)
437 PTL Pinta líquida (estados unidos)
438 QTL Cuarto de líquido (estadis unidos)
439 PTD Pinta seca (estados unidos)
440 OZI Onza líquida (UK)
441 QT Cuarto (EUA)
442 J57 Barril (uk petróleo)
443 K21 Pie cúbico por grado fahrenheit
444 K23 Pie cúbico por psi (libra por pulgada cuadrada)
445 L43 Peck (UK)
446 L61 Pinta (US seco)
447 L62 Cuarto de galón (seco de los EUA)
448 L84 Tonelada (flota UK)
449 L86 Tonelada (flota estados unidos)
450 M11 Yarda cúbica por grado fahrenheit
451 M14 Yarda cúbica por psi (libra por pulgada cuadrada)
452 OZA Onza líquida (estados unidos)
453 BUI Bushel (UK)
454 BUA Bushel (EUA)
455 BLL Barril (EUA)
456 BLD Barril seco (EUA)
457 GLD Galón seco (EUA)
458 QTD Cuarto seco (estados unidos)
459 G26 Estere
460 G21 Taza (unidad de volumen)
461 G24 Cucharada (estados unidos)
462 G25 Cucharilla (estados unidos)
463 G23 Peck
464 M67 Acre-pie
465 M68 Cordón
466 M69 Milla cúbica (reino unido)
467 M70 Unidad tradicional de capacidad de carga
468 Q32 Femtolitro
469 Q33 Picolitro
470 Q34 Nanolitro
471 NM3 Metro cúbico normalizado
472 SM3 Metro cúbico estándar
1 id key name
2 1 Tu Contenedor externo
3 2 X1A Tambor de acero
4 3 X1B Tambor de aluminio
5 4 X1D Tambor contrachapado
6 5 X1F Contenedor flexible
7 6 X1G Tambor de fibra
8 7 X1w Tambor de madera
9 8 X2C Barril de madera
10 9 X3A Bidón de acero
11 10 X3H Bidón de plástico
12 11 X43 Bolsa de gran tamaño
13 12 X44 Bolsa de plástico
14 13 X4A Caja de acero
15 14 X4B Caja de aluminio
16 15 X4C Caja de madera natural
17 16 X4D Caja de contrachapado
18 17 X4F Caja de madera reconstituida
19 18 X4G Caja de cartón
20 19 X4H Caja de plástico
21 20 X5H Bolsa de plástico tejido
22 21 X5L Bolsa textil
23 22 X5M Bolsa de papel
24 23 X6H Recipiente de plástico, Contenedor compuesto.
25 24 X6P Recipiente de vidrio, Contenedor compuesto.
26 25 X7A Estuche para carro
27 26 X7B Estuche de madera
28 27 X8A Pallet de madera
29 28 X8B Cajón de madera
30 29 X8C Madera flejada
31 30 XAA Contenedor intermedio para gráneles de plástico rígido
32 31 XAB Contenedor de fibra
33 32 XAC Contenedor de papel
34 33 XAD Contenedor de madera
35 34 XAE Aerosol
36 35 XAF Pallet modular con collares, 80cms * 60cms
37 36 XAG Pallet o empaquetado
38 37 XAH Pallet, 100cms X 110cms
39 38 XAI Contenedor tipo concha
40 39 XAJ Cono
41 40 XAL Esfera
42 41 XAM Ampolleta no protegida
43 42 XAP Ampolleta protegida
44 43 XAT Atomizador
45 44 XAV Cápsula
46 45 XB4 Cinturón
47 46 XBA Barril
48 47 XBB Bobina
49 48 XBC Cajón para botellas / Estante para botellas
50 49 XBD Tablero
51 50 XBE Flejado
52 51 XBF Globo no protegido
53 52 XBG Bolso
54 53 XBH Manojo
55 54 XBI Compartimiento
56 55 XBJ Cubeta
57 56 XBK Cesta
58 57 XBL Paca comprimida
59 58 XBM Cuenco
60 59 XBN Paca no comprimida
61 60 XBO Botella no-protegida y cilíndrica
62 61 XBP Globo protegido
63 62 XBQ Botella cilíndrica protegida
64 63 XBR Barra
65 64 XBS Botella, no-protegida en forma de bulbo
66 65 XBT Rollo de tela
67 66 XBU Butt
68 67 XBV Botella de bulbo protegido
69 68 XBW Caja para líquidos
70 69 XBX Caja
71 70 XBY Tablero, con fleje/ agrupados/ armados
72 71 XBZ Barras, con fleje/ agrupados/ armados
73 72 XCA Lata rectangular
74 73 XCB Cajón para cerveza
75 74 XCC Mantequera
76 75 XCD Lata con mango y boquilla
77 76 XCE Cesto tejido
78 77 XCF Cofre
79 78 XCG Contenedor tipo Jaula
80 79 XCH Cajonera
81 80 XCI Frasco
82 81 XCJ Ataúd
83 82 XCK Barrica
84 83 XCL Espiral
85 84 XCM Paquete de tarjetas
86 85 XCN Contenedor, no especificado como equipo de transporte
87 86 XCO Garrafón no protegido
88 87 XCP Garrafón protegido
89 88 XCQ Cartucho
90 89 XCR Cajón
91 90 XCS Estuche
92 91 XCT Cartón
93 92 XCU Vaso
94 93 XCV Cubierta
95 94 XCW Jaula estilo rodillo
96 95 XCX Lata cilíndrica
97 96 XCY Cilindro
98 97 XCZ Lona
99 98 XDA Cajón multicapa de plástico
100 99 XDB Cajón de varias capas de madera
101 100 XDC Cajón multicapa de cartón
102 101 XDG Jaula, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP))
103 102 XDH Caja, Según la clasificación de la compañía (CHEP), Eurobox
104 103 XDI Tambor de hierro
105 104 XDJ damajuana o garrafa, no protegido
106 105 XDK Cajón a granel, cartón
107 106 XDL Cajas de plástico
108 107 XDM Cajones a granel de madera
109 108 XDN Dispensador
110 109 XDP damajuana o garrafa, protegido
111 110 XDR Tambor
112 111 XDS Bandeja de una capa sin cubierta y de plástico
113 112 XDT Bandeja de una capa sin cubierta y de madera
114 113 XDU Bandeja de una capa sin cubierta y poliestireno
115 114 XDV Bandeja de una capa sin cubierta y de cartón
116 115 XDW Bandeja de dos capas sin tapa y con bandeja de plástico
117 116 XDX Bandeja de dos capas sin cubierta y de madera
118 117 XDY Bandeja de dos capas sin cubierta y de cartón
119 118 XEC Bolsa de plástico
120 119 XED Estuche, con pallet de base
121 120 XEE Estuche, con pallet base de madera
122 121 XEF Estuche, con pallet base de cartón
123 122 XEG Estuche, con pallet base de plástico
124 123 XEH Estuche, con pallet base de metal
125 124 XEI Estuche isotérmico
126 125 XEN Sobre
127 126 XFB Bolsa flexible
128 127 XFC Cajón para fruta
129 128 XFD Cajón enmarcado
130 129 XFE Tanque flexible
131 130 XFI Firkin
132 131 XFL Matraz
133 132 XFO Cajón para zapatos
134 133 XFP Caja auxiliar para película fotográfica
135 134 XFR Marco
136 135 XFT Contenedor para alimentos
137 136 XFW Carro de cama plana
138 137 XFX Bolsa flexible tipo contenedor
139 138 XGB Botella para gas
140 139 XGI Viga
141 140 XGL Contenedor tipo galón
142 141 XGR Recipiente de vidrio
143 142 XGU Bandeja contenedor para apilar horizontalmente objetos planos
144 143 XGY Costal de Yute
145 144 XGZ Vigas con correas o agrupadas
146 145 XHA Cesta con mango y de plástico
147 146 XHB Cesta con mango y de madera
148 147 XHC Cesta con asa y de cartón
149 148 XHG Hogshead
150 149 XHN Gancho
151 150 XHR Cesto
152 151 XIA Paquete con pantalla y de madera
153 152 XIB Paquete con pantalla y de cartón
154 153 XIC Paquete con pantalla y de plástico
155 154 XID Paquete con pantalla y de metal
156 155 XIE Paquete de mostrador.
157 156 XIF Envase para alimentos
158 157 XIG Paquete envuelto en papel
159 158 XIH Tambor de plástico
160 159 XIK Paquete de cartón con los agujeros para botellas
161 160 XIL Bandeja rígida con tapa y apilable (CEN TS 14482: 2002)
162 161 XIN Lingote
163 162 XIZ Lingotes con correas/ agrupados
164 163 XJB Bolsa jumbo
165 164 XJC Bidón rectangular
166 165 XJG Jarra
167 166 XJR Tarro
168 167 XJT Bolsa de yute
169 168 XJY Bidón, cilíndrico
170 169 XKG Barrilete
171 170 XKI Kit (Conjunto de piezas)
172 171 XLE Valijas
173 172 XLG Bitácora
174 173 XLT Lote
175 174 XLU Caja de arrastre
176 175 XLV Contenedor pequeño
177 176 XLZ Registros con fleje/ agrupados/ armados
178 177 XMA Cajón metálico
179 178 XMB Múltiplo de bolsas
180 179 XMC Cajón para leche
181 180 XME Contenedor de metal
182 181 XMR Recipiente de metal
183 182 XMS Saco milti-pared
184 183 XMT Tapete
185 184 XMW Contenedor envuelto en plástico
186 185 XMX Caja pequeña de cerillos
187 186 XNA No disponible
188 187 XNE Sin empaque o no empaquetado
189 188 XNF Sin empaque o no empaquetado, unidad simple
190 189 XNG Sin empaque o no empaquetado, unidades múltiples
191 190 XNS Caja nido
192 191 XNT Red
193 192 XNU Red de plástico con tubo
194 193 XNV Red textil con tubo
195 194 XOA Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 40 cm x 60 cm
196 195 XOB Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 80 cm x 120 cm
197 196 XOC Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 100 cm x 120 cm
198 197 XOD Pallet, AS 4068-1993
199 198 XOE Pallet, ISO T11
200 199 XOF Plataforma, peso o dimensión no especificada
201 200 XOK Bloque
202 201 XOT Octabin
203 202 XP2 Charola
204 203 XPA Cajetilla
205 204 XPB Pallet, Caja combinada y abierta con caja y pallet.
206 205 XPC Paquete postal
207 206 XPD Pallet modular con collares (80cms * 100cms)
208 207 XPE Pallet modular con collares (80cms * 120cms)
209 208 XPF Corral
210 209 XPG Placa
211 210 XPH Cantaro
212 211 XPI Pleca
213 212 XPJ Canastilla
214 213 XPK Paquete
215 214 XPL Balde
216 215 XPN Tablón
217 216 XPO Bolsa pequeña
218 217 XPR Contenedor de plástico
219 218 XPT Maceta
220 219 XPU Cacerola
221 220 XPV Tubos, con fleje/ agrupados/ armados
222 221 XPX Pallet
223 222 XPY Placas con fleje/ agrupados/ armados
224 223 XPZ Tablones con fleje/ agrupados/ armados
225 224 XQA Tambor de acero con cabeza no desmontable
226 225 XQB Tambor de acero con cabeza extraíble
227 226 XQC Tambor de aluminio con cabeza no extraíble
228 227 XQD Tambor de aluminio con cabeza extraíble
229 228 XQF Tambor, plástico con cabeza no desmontable
230 229 XQG Tambor, plástico, cabeza extraíble
231 230 XQH Barril de madera con tapón
232 231 XQJ Barril de madera con cabeza desprendible
233 232 XQK Bidón de acero con cabeza no desmontable
234 233 XQL Bidón de acero con cabeza desmontable
235 234 XQM Bidón de plástico con cabeza no desmontable
236 235 XQN Bidón de plástico con cabeza extraíble
237 236 XQP Caja de madera natural estándar
238 237 XQQ Caja de madera natural con muros a prueba de filtraciones
239 238 XQR Caja de plástico expandida
240 239 XQS Caja de plástico sólida
241 240 XRD Rod
242 241 XRG Anillo
243 242 XRJ Estante, Perchero para ropa
244 243 XRK Estante
245 244 XRL Carrete
246 245 XRO Rollo
247 246 XRT Red Roja
248 247 XRZ Varillas con fleje/ agrupados/ armados
249 248 XSA Saco
250 249 XSB Losa
251 250 XSC Cajón poco profundo
252 251 XSD Huso
253 252 XSE Baúl
254 253 XSH Bolsa pequeña hermética
255 254 XSI Patín
256 255 XSK Carcasa esqueleto
257 256 XSL Hoja de deslizamiento
258 257 XSM Hoja de metal
259 258 XSO Carrete pequeño
260 259 XSP Hoja de empaque de plástico
261 260 XSS Cajón de acero
262 261 XSU Maleta
263 262 XSV Sobre de acero
264 263 XSW Envoltorio
265 264 XSY Manga
266 265 XSZ Hojas con fleje/ agrupados/ armados
267 266 XT1 Tableta
268 267 XTB Tina
269 268 XTC Caja para té
270 269 XTD Tubo plegable
271 270 XTG Contenedor de tanque genérico
272 271 XTI Tierce
273 272 XTK Tanque rectangular
274 273 XTL Tina con tapa
275 274 XTN Hojalata
276 275 XTO Tonel
277 276 XTR Maletero
278 277 XTS Estructura
279 278 XTT Bolsa de mano
280 279 XTU Tubo
281 280 XTV Tubo con boquilla
282 281 XTW Pallet tricapa
283 282 XTY Tanque cilíndrico
284 283 XTZ Tubos con fleje/ agrupados/ armados
285 284 XUC Sin empaque
286 285 XUN Unidad
287 286 XVA Tanque
288 287 XVG Tanque de gas (a 1,031 mbar y 15° C)
289 288 XVI Frasco pequeño
290 289 XVK Paquete transportable
291 290 XVL Contenedor para líquidos a granel
292 291 XVN Vehículo
293 292 XVO Contenedor para sólido de partículas grandes a granel ("nódulos")
294 293 XVP Envasado al vacío
295 294 XVQ Tanque para Gas licuado (a temperatura / presión anormal)
296 295 XVR Contenedor para sólidos de partículas granulares a granel (Granos)
297 296 XVS Contenedor de chatarra a granel
298 297 XVY Contenedor para sólido de partículas finas a granel ("polvos")
299 298 XWA Contenedor de granel intermedio
300 299 XWB Botella de mimbre
301 300 XWC Contenedor intermedio para gráneles y de acero
302 301 XWD Contenedor intermedio para gráneles y de aluminio
303 302 XWF Contenedor intermedio para gráneles y de metal
304 303 XWG Contenedor intermedio para gráneles y de acero presurizado menor a 10 kpa
305 304 XWH Contenedor intermedio para gráneles y de aluminio, presurizado menor a 10 kpa
306 305 XWJ Contenedor intermedio para gráneles y de metal con una presión de 10 kpa
307 306 XWK Contenedor intermedio para gráneles y de acero para líquido
308 307 XWL Contenedor intermedio para gráneles y de aluminio para líquido
309 308 XWM Contenedor intermedio para gráneles y de metal para líquido
310 309 XWN Contenedor intermedio para gráneles con tejido plástico sin capa con revestimiento
311 310 XWP Contenedor intermedio para gráneles con tejido plástico y recubierto
312 311 XWQ Contenedor intermedio para gráneles con tejido plástico con revestimiento
313 312 XWR Contenedor intermedio para gráneles con tejido plástico, revestido y con forro
314 313 XWS Contenedor intermedio para gráneles con película de plástico
315 314 XWT Contenedor intermedio para gráneles textil sin capa / forro
316 315 XWU Contenedor intermedio para gráneles de madera natural con forro interior
317 316 XWV Contenedor intermedio para gráneles textil recubierto
318 317 XWW Contenedor intermedio para gráneles textil con revestimiento
319 318 XWX Contenedor intermedio para gráneles textil recubierto y con forro
320 319 XWY Contenedor intermedio para gráneles contrachapado con revestimiento interior
321 320 XWZ Contenedor intermedio para gráneles de madera reconstituida con revestimiento interior
322 321 XXA Bolsa de tejido plástico, sin abrigo interior ni forro
323 322 XXB Bolsa de tejido plástico a prueba de filtraciones
324 323 XXC Bolsa de tejido plástico resistente al agua
325 324 XXD Bolsa con película de plástico
326 325 XXF Bolsa textil sin capa ni forro interior
327 326 XXG Bolsa textil a prueba de filtraciones
328 327 XXH Bolsa textil resistente al agua
329 328 XXJ Bolsa de papel multi-pared
330 329 XXK Bolsa de papel multi-pared, resistente al agua
331 330 XYA Empaque compuesto, recipiente de plástico en tambor de acero
332 331 XYB Empaque compuesto, recipiente de plástico en cajas de acero
333 332 XYC Empaque compuesto, recipiente de plástico en tambor de aluminio
334 333 XYD Empaque compuesto, recipiente de plástico en cajón de aluminio
335 334 XYF Empaque compuesto, recipiente de plástico en caja de madera
336 335 XYG Empaque compuesto, recipiente de plástico en tambor de madera contrachapada
337 336 XYH Empaque compuesto, recipiente de plástico en caja de madera contrachapada
338 337 XYJ Empaque compuesto, recipiente de plástico en tambor de fibra
339 338 XYK Empaque compuesto, recipiente de plástico en caja de cartón
340 339 XYL Empaque compuesto, recipiente de plástico en el tambor de plástico
341 340 XYM Empaque compuesto, recipiente de plástico en caja de plástico sólido
342 341 XYN Empaque compuesto, receptáculo de vidrio en tambor de acero
343 342 XYP Empaque compuesto, receptáculo de vidrio en caja de cajas de acero
344 343 XYQ Empaque compuesto, recipiente de vidrio en tambor de aluminio
345 344 XYR Empaque compuesto, receptáculo de vidrio en caja de aluminio
346 345 XYS Empaque compuesto, recipiente de vidrio en caja de madera
347 346 XYT Empaque compuesto, recipiente de vidrio en tambor de madera contrachapada
348 347 Xyv Empaque compuesto, recipiente de vidrio en el cesto de mimbre
349 348 XYW Empaque compuesto, recipiente de vidrio en tambor de fibra
350 349 XYX Empaque compuesto, recipiente de vidrio en caja de cartón
351 350 XYY Empaque compuesto, recipiente de vidrio en paquete de plástico expandible
352 351 XYZ Empaque compuesto, recipiente de vidrio en paquete de plástico sólido
353 352 XZA Contenedor de granel intermedio, papel, multi-pared
354 353 XZB Bolsa grande
355 354 XZC Contenedor intermedio para gráneles de papel, multi-pared y resistente al agua
356 355 XZD Contenedor intermedio para gráneles de plástico rígido, con equipo estructural para sólidos
357 356 XZF Contenedor intermedio para gráneles de plástico rígido, autoportante para sólidos
358 357 XZG Contenedor intermedio para gráneles de plástico rígido, con equipo estructural, presurizado
359 358 XZH Contenedor intermedio para gráneles de plástico rígido, autoportante y presurizado
360 359 XZJ Contenedor intermedio para gráneles de plástico rígido, con equipo estructural para líquidos
361 360 XZK Contenedor intermedio para gráneles de plástico rígido, autoportante, líquidos
362 361 XZL Contenedor intermedio para gráneles, compuesto y de plástico rígido, sólidos
363 362 XZM Contenedor intermedio para gráneles, compuesto y de plástico flexible, sólidos
364 363 XZN Contenedor intermedio para gráneles, compuesto y de plástico rígido, presurizado
365 364 XZP Contenedor intermedio para gráneles, compuesto y de plástico flexible, presurizado
366 365 XZQ Contenedor intermedio para gráneles, compuesto y de plástico rígido, líquidos
367 366 XZR Contenedor intermedio para gráneles, compuesto y de plástico flexible para líquidos
368 367 XZS Contenedor intermedio para gráneles, compuesto
369 368 XZT Contenedor intermedio para gráneles con tablero de fibras
370 369 XZU Contenedor intermedio para gráneles flexible
371 370 XZV Contenedor intermedio para gráneles de metal, distinto del acero
372 371 XZW Contenedor intermedio para gráneles, de madera natural
373 372 XZX Contenedor intermedio para gráneles, de contrachapado
374 373 XZY Contenedor intermedio para gráneles, de madera reconstituida
375 374 KGM Kilogramo
376 375 MC Microgramo
377 376 DJ Decagramo
378 377 DG Decigramo
379 378 GRM Gramo
380 379 CGM Centigramo
381 380 TNE Tonelada (tonelada métrica)
382 381 DTN Decitonelada métrica
383 382 MGM Miligramo
384 383 HGM Hectogramo
385 384 KTN Kilotonelada Métrica
386 385 2U Megagramo
387 386 LBR Libra
388 387 GRN Grano
389 388 ONZ Onza (avoirdupois)
390 389 CWI Hundredweight
391 390 CWA Hundred pound
392 391 LTN Tonelada (UK) o tonelada larga (estados unidos)
393 392 STI Estone (UK)
394 393 STN Tonelada (estados unidos) o tonelada corta (UK y estados unidos)
395 394 APZ Onza troy u onza farmacéutica
396 395 F13 Slug
397 396 K64 Libra (avoirdupois) por grado fahrenheit
398 397 L69 Tonelada por kelvin
399 398 L87 Tonelada corta por grado fahrenheit
400 399 M85 Tonelada, ensayo
401 400 M86 Libra Alemana
402 401 J33 Microgramo por kilogramo
403 402 L32 Nanogramo por kilogramo
404 403 NA Miligramo por kilogramo
405 404 M29 Kilogramo por kilogramo
406 405 M91 Libra por libra
407 406 Q29 Microgramo por hectogramo
408 407 MTQ Metro cúbico
409 408 MAL Megalitro
410 409 LTR Litro
411 410 MMQ Milímetro cúbico
412 411 CMQ Centímetro cúbico
413 412 DMQ Decímetro cúbico
414 413 MLT Mililitro
415 414 HLT Hectolitro
416 415 CLT Centilitro
417 416 DMA Decámetro cúbico
418 417 H19 Hectómetro cúbico
419 418 H20 Kilómetro cúbico
420 419 M71 Metro cúbico por pascal (joules)
421 420 DLT Decilitro
422 421 4G Microlitro
423 422 K6 Kilolitro
424 423 A44 Decalitro
425 424 G94 Centímetro cúbico por bar
426 425 G95 Litro por bar
427 426 G96 Metro cúbico por bar
428 427 G97 Mililitro por bar
429 428 5I Pies cúbicos estándar
430 429 INQ Pulgada cúbica
431 430 FTQ Pie cúbico
432 431 YDQ Yarda cúbica
433 432 GLI Galón (UK)
434 433 GLL Galón (EUA)
435 434 PT Pinta (US)
436 435 PTI Pint (uk)
437 436 QTI Cuarto (UK)
438 437 PTL Pinta líquida (estados unidos)
439 438 QTL Cuarto de líquido (estadis unidos)
440 439 PTD Pinta seca (estados unidos)
441 440 OZI Onza líquida (UK)
442 441 QT Cuarto (EUA)
443 442 J57 Barril (uk petróleo)
444 443 K21 Pie cúbico por grado fahrenheit
445 444 K23 Pie cúbico por psi (libra por pulgada cuadrada)
446 445 L43 Peck (UK)
447 446 L61 Pinta (US seco)
448 447 L62 Cuarto de galón (seco de los EUA)
449 448 L84 Tonelada (flota UK)
450 449 L86 Tonelada (flota estados unidos)
451 450 M11 Yarda cúbica por grado fahrenheit
452 451 M14 Yarda cúbica por psi (libra por pulgada cuadrada)
453 452 OZA Onza líquida (estados unidos)
454 453 BUI Bushel (UK)
455 454 BUA Bushel (EUA)
456 455 BLL Barril (EUA)
457 456 BLD Barril seco (EUA)
458 457 GLD Galón seco (EUA)
459 458 QTD Cuarto seco (estados unidos)
460 459 G26 Estere
461 460 G21 Taza (unidad de volumen)
462 461 G24 Cucharada (estados unidos)
463 462 G25 Cucharilla (estados unidos)
464 463 G23 Peck
465 464 M67 Acre-pie
466 465 M68 Cordón
467 466 M69 Milla cúbica (reino unido)
468 467 M70 Unidad tradicional de capacidad de carga
469 468 Q32 Femtolitro
470 469 Q33 Picolitro
471 470 Q34 Nanolitro
472 471 NM3 Metro cúbico normalizado
473 472 SM3 Metro cúbico estándar

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/*
@license
webix UI v.5.4.3
webix UI v.5.0.0
This software is covered by Webix Commercial License.
Usage without proper license is prohibited.
(c) XB Software Ltd.
@ -9,12 +9,12 @@ Usage without proper license is prohibited.
.webix_view.webix_form{background-color:#fff}
body{-webkit-tap-highlight-color:transparent!important}
.webix_abslayout{position:relative}
.webix_abslayout>.webix_view{position:absolute!important}
.webix_abslayout>.webix_view{position:absolute}
.webix_layout_space,.webix_layout_wide{background-color:#ebebeb}
.webix_layout_accordion,.webix_layout_clean,.webix_layout_line,.webix_multiview{background:0 0}
.webix_overlay{width:100%;height:100%;position:absolute;z-index:10;text-align:center;padding-top:10px}
.webix_view>.webix_disabled{width:100%;height:100%;position:absolute;top:0;left:0;background-color:#ddd;opacity:.4;filter:alpha(opacity=40);z-index:1}
.webix_disabled_view{overflow:hidden!important;position:relative}
.webix_disabled_view{overflow:hidden!important}
body.webix_full_screen{margin:0;padding:0;overflow:hidden}
.webix_noselect,.webix_noselect div{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}
.webix_selectable{-moz-user-select:text;-webkit-user-select:text;user-select:text}
@ -25,38 +25,38 @@ body.webix_full_screen{margin:0;padding:0;overflow:hidden}
.webix_section>.webix_template{padding:0 8px;background-color:#fff;display:inline}
.webix_layout_space>.webix_section>.webix_template,.webix_layout_wide>.webix_section>.webix_template{padding:0 8px;background-color:#ebebeb}
.webix_layout_clean>.webix_section>.webix_template,.webix_layout_line>.webix_section>.webix_template{padding:0 8px;background-color:#fff}
.webix_header>div{padding-left:10px;font-family:Helvetica,Verdana;color:#1e2022;font-size:13px;font-weight:700;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x;line-height:32px;line-height:34px;padding-top:0;padding-bottom:0}
.webix_header>div{padding-left:10px;font-family:Helvetica,Verdana;font-size:13px;font-weight:700;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);line-height:32px;line-height:34px;padding-top:0;padding-bottom:0}
.webix_header>div .webix_el_label,.webix_header>div .webix_inp_label{color:#1e2022}
.webix_spacer{background:0 0}
.webix_view_animate{position:relative;width:100%;height:100%}
x\:ui{display:none}
.webix_view.webix_flexlayout{display:flex!important;flex-wrap:wrap;flex-direction:row;overflow:hidden;box-sizing:border-box}
.webix_flexlayout{display:flex!important;flex-wrap:wrap;flex-direction:row;overflow:hidden;box-sizing:border-box}
.webix_flexlayout>div{display:block!important}
.webix_view_align{background:0 0}
.webix_view a.webix_list_item,.webix_view div,.webix_view span{outline:0}
.webix_view a.webix_list_item:focus,.webix_view div:focus,.webix_view span:focus,.webix_view.webix_window:focus{outline-color:#727981;outline-style:dotted;outline-width:1px}
.webix_view a.webix_list_item:focus,.webix_view div:focus,.webix_view span:focus,.webix_view.webix_window:focus{outline:#727981 dotted 1px}
.webix_view div.webix_inp_static:focus,button,input,select,textarea{outline:0}
.webix_resize_area{position:absolute;top:0;left:0;width:100%;height:100%}
.webix_resize_handle_x .webix_handle_content,.webix_resize_handle_y .webix_handle_content{width:100%;height:100%;background:rgba(100,100,100,.1);border:1px dotted #9fa5aa}
.webix_resize_handle_x{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:ew-resize;width:1px;height:100%;position:absolute;z-index:2;top:0}
.webix_resize_handle_x{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:e-resize;width:1px;height:100%;position:absolute;z-index:2;top:0}
.webix_resize_handle_x .webix_handle_content{border-width:0 1px}
.webix_resize_origin_x{cursor:ew-resize;width:3px;height:100%;position:absolute;z-index:2;top:0}
.webix_resize_handle_y{cursor:ns-resize;height:1px;width:100%;position:absolute;z-index:2;left:0}
.webix_resize_origin_x{cursor:e-resize;width:3px;height:100%;position:absolute;z-index:2;top:0}
.webix_resize_handle_y{cursor:n-resize;height:1px;width:100%;position:absolute;z-index:2;left:0}
.webix_resize_handle_y .webix_handle_content{border-width:1px 0}
.webix_resize_origin_y{cursor:ns-resize;height:3px;width:100%;position:absolute;z-index:2;left:0}
.webix_resize_origin_y{cursor:n-resize;height:3px;width:100%;position:absolute;z-index:2;left:0}
.webix_resizer.webix_disabled_view{cursor:default}
.webix_resizer_vy{cursor:ns-resize;margin-top:0!important;border:0!important;padding:0!important;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAADAQMAAABCowZuAAAABlBMVEUAAACkvtSA7tmIAAAAAXRSTlMAQObYZgAAAA9JREFUCB1jXMUABoxAGgAJaAFXPIkJqAAAAABJRU5ErkJggg==) no-repeat center center}
.webix_resizer_vx{cursor:ew-resize;margin-left:0!important;border:0!important;padding:0!important;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAXAQMAAAD0oWdxAAAABlBMVEUAAACkvtSA7tmIAAAAAXRSTlMAQObYZgAAAA5JREFUeF5jWMDAQDwGAKyuB4FpGQdzAAAAAElFTkSuQmCC) no-repeat center center}
.webix_resizer_y{cursor:ns-resize;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAADAQMAAABCowZuAAAABlBMVEUAAACkvtSA7tmIAAAAAXRSTlMAQObYZgAAAA9JREFUCB1jXMUABoxAGgAJaAFXPIkJqAAAAABJRU5ErkJggg==) no-repeat center center;background-color:#fff}
.webix_resizer_x{cursor:ew-resize;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAXAQMAAAD0oWdxAAAABlBMVEUAAACkvtSA7tmIAAAAAXRSTlMAQObYZgAAAA5JREFUeF5jWMDAQDwGAKyuB4FpGQdzAAAAAElFTkSuQmCC) no-repeat center center;background-color:#fff}
.webix_resizer_vy{cursor:n-resize;margin-top:0!important;border:0!important;padding:0!important;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAADAQMAAABCowZuAAAABlBMVEUAAACkvtSA7tmIAAAAAXRSTlMAQObYZgAAAA9JREFUCB1jXMUABoxAGgAJaAFXPIkJqAAAAABJRU5ErkJggg==) center center no-repeat}
.webix_resizer_vx{cursor:e-resize;margin-left:0!important;border:0!important;padding:0!important;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAXAQMAAAD0oWdxAAAABlBMVEUAAACkvtSA7tmIAAAAAXRSTlMAQObYZgAAAA5JREFUeF5jWMDAQDwGAKyuB4FpGQdzAAAAAElFTkSuQmCC) center center no-repeat}
.webix_resizer_y{cursor:n-resize;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAADAQMAAABCowZuAAAABlBMVEUAAACkvtSA7tmIAAAAAXRSTlMAQObYZgAAAA9JREFUCB1jXMUABoxAGgAJaAFXPIkJqAAAAABJRU5ErkJggg==) center center no-repeat #fff}
.webix_resizer_x{cursor:e-resize;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAXAQMAAAD0oWdxAAAABlBMVEUAAACkvtSA7tmIAAAAAXRSTlMAQObYZgAAAA5JREFUeF5jWMDAQDwGAKyuB4FpGQdzAAAAAElFTkSuQmCC) center center no-repeat #fff}
.webix_accordionitem_body{position:relative}
.webix_accordionitem_header{border-bottom:1px solid #a4bed4;position:relative;z-index:2;font-family:Helvetica,Verdana;color:#1e2022;font-size:13px;font-weight:700;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x;line-height:32px;cursor:pointer;overflow:hidden}
.webix_accordionitem_header{border-bottom:1px solid #a4bed4;position:relative;z-index:2;font-family:Helvetica,Verdana;font-size:13px;font-weight:700;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);line-height:32px;cursor:pointer;overflow:hidden}
.webix_accordionitem_header .webix_el_label,.webix_accordionitem_header .webix_inp_label{color:#1e2022}
.webix_ie.horizontal>.webix_accordionitem_header.collapsed{-ms-writing-mode:tb-lr}
.webix_ie.horizontal>.webix_accordionitem_header.collapsed .webix_accordionitem_label{padding-left:0;padding-top:10px}
.webix_ie.horizontal>.webix_accordionitem_header.collapsed .webix_accordionitem_button{margin:0}
.webix_accordionitem_label{height:100%;padding-left:10px}
.webix_accordionitem_button{float:right;position:relative;height:11px;width:11px;margin:14px 14px}
.webix_accordionitem_button{float:right;position:relative;height:11px;width:11px;margin:14px}
.webix_carousel{position:relative}
.webix_nav_panel{bottom:15px;height:auto;line-height:8px;overflow:hidden;position:absolute;text-align:center}
.webix_nav_item{display:inline-block;height:8px;padding:6px 5px 6px 4px;width:8px;cursor:pointer;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}
@ -89,10 +89,10 @@ x\:ui{display:none}
.webix_dark .webix_nav_button_side.webix_nav_button_next .webix_nav_button_inner{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAiCAYAAACwaJKDAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gcdCSE1IU5u4gAAAW1JREFUSMet1rtOwzAUgOG/5jbBwsQD8BAwIAa2TggegjmRgELbuFeo1GRlQEJMMDN264CUiYdgg0dolzIkFUEk9nHSbLGtT76cc+xaMA53gUegDnwCgfa9Vyp8CngCToFNYB940WF0WRWt57SPqsAK+C7oG+kwuiqLdg3992Xgtelk8jGN4w3gqGDMyTSOZ8eHB+8uM0X7XhMYGMbd6TC6dkIzcH8VsMr+aN9rCeCGEyqEhzZY5TWmcM8C3zihKdy2wIMiWJmWURZWtk1P4a4FvnVCUziwwP0srKQBLYSbALXFYuGU1zqMNBAYhrRUiSI0s/RvKcdZNoChcQt8Tz5TKSg/fQdQFqdJcJvAXha0Z1QCDixgW577drCbBxZXKRkYyOtpkm6lwf+VPwH7VcC/d5Qd7EjA39s0KQQ2UIvvfbZ3zoEHU2Rp3+u4vlDGqwSX6N4qwSX6ltMelAUB1oELYA6cAV/poTxXeZ/+AOFqn1e/Gh7yAAAAAElFTkSuQmCC)}
.webix_list_item{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2px 10px;line-height:23px;cursor:pointer;border-bottom:1px solid #ebebeb;text-align:left}
.webix_list_item.webix_invalid{background-color:#fee}
.webix_list_item.webix_selected{background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_list_item.webix_selected{color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background:-webkit-linear-gradient(#ffebc2,#ffdb8f);background:-moz-linear-gradient(#ffebc2,#ffdb8f);background:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_list .webix_list_item.webix_selected{border-bottom:1px solid #ffd47b;box-shadow:0 1px #ffe3a9 inset;padding:2px 10px}
.webix_group{position:relative}
.webix_group_back,.webix_group_back.webix_selected{position:relative;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x}
.webix_group_back,.webix_group_back.webix_selected{position:relative;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%)}
.webix_arrow_icon{position:absolute;top:50%;bottom:50%;margin-top:-12px;right:15px;width:9px;height:22px;line-height:22px;color:#1e2022;font-size:18px;font-family:FontAwesome}
.webix_arrow_icon:before{content:'\f105'}
.webix_group_back{padding-left:29px}
@ -103,19 +103,18 @@ x\:ui{display:none}
.webix_unit_header{padding:0 10px;line-height:19px;text-align:left;background-color:#d6e8ff}
.webix_list-x .webix_list_item{display:inline-block;vertical-align:top;border-right:1px solid #ebebeb;border-bottom:none}
.webix_list-x .webix_list_item.webix_selected{border-right:1px solid #ffd47b;border-left:1px solid #ffd47b;padding-left:9px}
.webix_list-x .webix_scroll_cont{display:inline-block;overflow:visible}
.webix_list-x .webix_scroll_cont{display:inline}
.webix_view.webix_list-x{white-space:nowrap}
.webix_list_item.webix_disabled{color:#8b949e}
.webix_list_item.webix_disabled .webix_submenu_icon{visibility:hidden}
.webix_view.webix_pager{padding:2px 0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;background-color:transparent}
.webix_pager_item,.webix_pager_item_selected{padding:0 6px;text-align:center;height:100%;width:auto;min-width:32px;border:1px solid #a4bed4;margin:0 2px;cursor:pointer;background-color:#fff;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;font-family:Helvetica,Verdana;font-size:13px}
.webix_pager_item,.webix_pager_item_selected{padding:0 6px;text-align:center;height:100%;width:32px;border:1px solid #a4bed4;margin:0 2px;cursor:pointer;background-color:#fff;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;font-family:Helvetica,Verdana;font-size:13px}
.webix_pager_item_selected{cursor:default;background-color:#ffdb8f;border:1px solid #ffd47b}
.webix_animation .webix_ss_header{position:relative;z-index:10}
.webix_animation .webix_ss_footer,.webix_animation .webix_vscroll_x{position:absolute;bottom:0;z-index:10}
.webix_allow_selection{-moz-user-select:text;-webkit-user-select:text;user-select:text}
.webix_dataview_item{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2px 10px;line-height:23px;cursor:pointer;border-bottom:1px solid #ebebeb;text-align:left;border-right:1px solid #ebebeb}
.webix_dataview_item.webix_selected{background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f);border-bottom:1px solid #ffd47b;border-right:1px solid #ffd47b}
.webix_dataview_item.webix_invalid{background-color:#fee}
.webix_dataview_item.webix_selected{color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background:-webkit-linear-gradient(#ffebc2,#ffdb8f);background:-moz-linear-gradient(#ffebc2,#ffdb8f);background:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background:-o-linear-gradient(top,#ffebc2,#ffdb8f);border-bottom:1px solid #ffd47b;border-right:1px solid #ffd47b}
.webix_chart{position:relative;font-family:Helvetica,Verdana;font-size:13px;color:#1e2022;overflow:hidden;z-index:1}
.webix_chart canvas{position:absolute}
.webix_canvas_text{position:absolute;text-align:center;overflow:hidden;white-space:nowrap;font-size:12px;z-index:100}
@ -156,11 +155,11 @@ x\:ui{display:none}
.webix_dt_editor select{padding-right:0;padding-left:5px}
.webix_clipbuffer{width:2000px;height:1px;padding:0;margin:0;border:0;left:0;top:0;white-space:pre-wrap;position:fixed;filter:alpha(opacity=0);opacity:0;pointer-events:none;cursor:default}
.webix_message_area{position:fixed;right:5px;width:250px;z-index:1000}
.webix_info{min-width:120px;font-family:Tahoma,Arial;z-index:10000;overflow:hidden;margin:5px;margin-bottom:10px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}
.webix_info{min-width:120px;font-family:Tahoma,Arial;z-index:10000;overflow:hidden;margin:5px 5px 10px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}
.webix_no_transition{-webkit-transition:none;-moz-transition:none;transition:none}
.webix_info.hidden{height:0;min-height:0;padding-top:0;padding-bottom:0;border-width:0;margin-top:0;margin-bottom:0;overflow:hidden}
.webix_modal_box{overflow:hidden;display:inline-block;min-width:250px;width:250px;text-align:center;position:fixed;background-color:#fff;box-shadow:0 3px 6px #c8c8c8;font-family:Helvetica,Verdana;z-index:20000;border-radius:0;outline:0}
.webix_popup_title{border-top-left-radius:-1px;border-top-right-radius:-1px;border-width:0;background-image:-webkit-linear-gradient(top,#707070 1%,#3d3d3d 70%,#4c4c4c 97%,#393939 97%);background-image:-moz-linear-gradient(top,#707070 1%,#3d3d3d 70%,#4c4c4c 97%,#393939 97%)}
.webix_popup_title{border-top-left-radius:-1px;border-top-right-radius:-1px;border-width:0}
.webix_button,.webix_info,.webix_popup_button{cursor:pointer}
.webix_popup_text{overflow:hidden}
.webix_popup_controls{border-radius:6px;padding:5px 10px 10px}
@ -172,12 +171,12 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_alert_error .webix_popup_title,.webix_confirm_error .webix_popup_title{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAsCAIAAAArRUU2AAAATklEQVR4nIWLuw2AMBBDjVuQiBT2oWbRDATrnB0KQOJoqPzRe3BrHI6dcBASYREKovtK6/6DsDOX+stN+3H1YX9ciRgnYq5EWYhS2dftBIuLT4JyIrPCAAAAAElFTkSuQmCC)}
.webix_alert_error,.webix_confirm_error{border:1px solid red}
.webix_button,.webix_popup_button{border:1px solid #a4bed4}
.webix_button input,.webix_popup_button div{font-size:13px;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background-image:-webkit-linear-gradient(#fff,#e6e6e6);background-image:-moz-linear-gradient(#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);height:28px;line-height:28px}
.webix_popup_button.confirm div{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x}
.webix_button input,.webix_popup_button div{font-size:13px;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background:-webkit-linear-gradient(#fff,#e6e6e6);background:-moz-linear-gradient(#fff,#e6e6e6);background:-ms-linear-gradient(top,#fff,#e6e6e6);background:-o-linear-gradient(top,#fff,#e6e6e6);height:28px;line-height:28px}
.webix_popup_button.confirm div{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%)}
.webix_popup_title{color:#fff;height:40px;line-height:40px}
.webix_popup_text{margin:0 0 5px 0;padding-top:25px;font-size:13px;color:#1e2022;min-height:60px}
.webix_popup_text{margin:0 0 5px;padding-top:25px;font-size:13px;color:#1e2022;min-height:60px}
.webix_error,.webix_info{font-size:13px;color:#333;box-shadow:0 0 10px #888;padding:0;background-color:#FFF;border-radius:3px;border:1px solid #fff}
.webix_info div{padding:5px 10px 5px 10px;background-color:#fff;border-radius:3px;border:1px solid #B8B8B8}
.webix_info div{padding:5px 10px;background-color:#fff;border-radius:3px;border:1px solid #B8B8B8}
.webix_error{background-color:#d81b1b;border:1px solid #ff3c3c;box-shadow:0 0 10px #000}
.webix_error div{background-color:#d81b1b;border:1px solid #940000;color:#FFF}
.webix_drag_zone{position:absolute;box-shadow:1px 1px 5px #a4bed4;background-color:#fff;font-family:Helvetica,Verdana;font-size:13px;color:#1e2022;pointer-events:none}
@ -202,13 +201,13 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_block_selection,.webix_cell,.webix_hcell,.webix_ss_footer td,.webix_ss_header td,.webix_table_cell,.webix_view.webix_table_cell{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_block_selection{position:absolute}
.webix_ss_sort_asc,.webix_ss_sort_desc{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}
.webix_hcell.webix_ss_filter{padding:2px 5px 2px 5px}
.webix_hcell.webix_ss_filter{padding:2px 5px}
.webix_ss_filter input,.webix_ss_filter select{width:100%;font-size:inherit;color:#1e2022!important;height:30px;margin-top:-2px;padding:0 3px;vertical-align:top;border:1px solid #a4bed4;line-height:30px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;font-family:Helvetica,Verdana}
.webix_richfilter{margin-top:1px;line-height:30px}
.webix_ss_filter select{padding:3px}
.webix_dd_drag_column,.webix_ss_header,.webix_ss_header TD,.webix_ss_vscroll_header{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x}
.webix_dd_drag_column,.webix_ss_header,.webix_ss_header TD,.webix_ss_vscroll_header{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%)}
.webix_ss_footer TD,.webix_ss_vscroll_footer{background:#fafafa}
.webix_hcell{padding:0 10px 0 10px;text-align:left}
.webix_hcell{padding:0 10px;text-align:left}
.webix_ss_header td{border-right:1px solid #a4bed4;border-bottom:1px solid #a4bed4}
.webix_ss_footer td{border-right:1px solid #ebebeb;border-top:1px solid #ebebeb}
.webix_size_row td{border:none}
@ -223,32 +222,31 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_block_selection{background:rgba(0,0,0,.1);border:2px solid #ffd47b}
.webix_dd_drag{white-space:nowrap;overflow:hidden;border-left:1px solid #ebebeb;height:34px}
.webix_dd_drag>div{display:inline-block;vertical-align:top;background:#fff;border-top:1px solid #ebebeb;border-right:1px solid #ebebeb;border-bottom:1px solid #ebebeb;padding:5px 10px;height:34px;line-height:26px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_dd_drag_column{padding:5px 10px 5px 10px}
.webix_dd_drag_column{padding:5px 10px}
.webix_column>.webix_drag_over.webix_cell{background-color:#e6e6e6;color:#1e2022}
.webix_ss_header table,.webix_ss_header td{padding:0;margin:0}
.webix_cell.webix_invalid,.webix_column>.webix_cell.webix_invalid:nth-child(even){background-color:#fee}
.webix_invalid_cell{position:relative}
.webix_invalid_cell:after{content:"";position:absolute;top:0;right:0;width:0;height:1px;border-top:8px solid #f44;border-left:8px solid transparent}
.webix_column>div.webix_cell_select,.webix_column>div.webix_column_select,.webix_column>div.webix_row_select{background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_column>div.webix_cell_select,.webix_column>div.webix_column_select,.webix_column>div.webix_row_select{color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background:-webkit-linear-gradient(#ffebc2,#ffdb8f);background:-moz-linear-gradient(#ffebc2,#ffdb8f);background:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_column>div.webix_cell_select{border-bottom:1px solid #ffd47b;box-shadow:0 1px #ffe3a9 inset;border-right:1px solid #ffd47b;border-left:1px solid #ffd47b;padding-top:0;padding-left:9px}
.webix_ss_right .webix_column.webix_first>div.webix_cell_select{padding-left:10px}
.webix_column>div.webix_invalid.webix_row_select:nth-child(even),.webix_column>div.webix_row_select{border-bottom:1px solid #ffd47b;box-shadow:0 1px #ffe3a9 inset;padding-top:0}
.webix_column>div.webix_column_select{border-right:1px solid #ffd47b;border-left:1px solid #ffd47b;padding-left:9px}
.webix_column>.webix_cell.webix_invalid.webix_cell_select:nth-child(even){background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_cell.webix_dtable_colrow,.webix_cell.webix_dtable_subrow,.webix_dtable_subview{position:absolute;left:0;top:0;background-color:#fff;width:100%;padding-right:28px;padding-left:10px;padding-top:1px;border-bottom:1px solid #ebebeb}
.webix_cell.webix_dtable_colrow.webix_selected{background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_column>.webix_cell.webix_invalid.webix_cell_select:nth-child(even){color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background:-webkit-linear-gradient(#ffebc2,#ffdb8f);background:-moz-linear-gradient(#ffebc2,#ffdb8f);background:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_cell.webix_dtable_colrow,.webix_cell.webix_dtable_subrow,.webix_cell.webix_dtable_subview{position:absolute;left:0;top:0;background-color:#fff;width:100%;padding-right:28px;padding-left:10px;padding-top:1px;border-bottom:1px solid #ebebeb}
.webix_dtable_subrow{border-top:1px solid #ebebeb;text-align:right}
.webix_dtable_subview{background:#ebebeb;border-top:1px solid #ebebeb;padding:0;padding-left:10px;box-sizing:border-box}
.webix_dtable_subview{background:#ebebeb;border-top:1px solid #ebebeb;padding:0 0 0 10px;box-sizing:border-box}
.number .webix_cell{text-align:right}
.webix_rotate{transform:rotate(-90deg);-webkit-transform:rotate(-90deg);line-height:normal}
.webix_measure_rotate{line-height:normal;white-space:normal;padding:10px}
.webix_span_layer,.webix_span_layer_top{background:0 0;position:absolute;left:0;top:0;height:0;width:0;overflow:visible}
.webix_span_layer_top{z-index:1}
.webix_span_layer_top{z-index:1;overflow:hidden}
.webix_ss_right .webix_span_layer,.webix_ss_right .webix_span_layer_top{border-left:1px solid #a4bed4}
.webix_dtable_span{position:absolute;background:#fff;text-align:left}
.webix_dtable_span.webix_selected{background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_dtable_span.webix_selected{color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background:-webkit-linear-gradient(#ffebc2,#ffdb8f);background:-moz-linear-gradient(#ffebc2,#ffdb8f);background:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_hcell span.webix_input_icon{background-color:#a4bed4}
.webix_area_selection_layer{position:absolute;z-index:2}
.webix_area_selection_layer{position:absolute;z-index:1}
.webix_area_selection{position:absolute;background:#1e2022}
.webix_area_selection_bottom,.webix_area_selection_top{height:2px}
.webix_area_selection_left,.webix_area_selection_right{width:2px}
@ -258,7 +256,7 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_win_body{overflow:hidden}
.webix_win_body,.webix_win_body>.webix_view,.webix_win_body>.webix_view .webix_cal_month,.webix_win_content,.webix_window{border-radius:0}
.webix_win_head{border-bottom:1px solid silver}
.webix_win_head>.webix_view>.webix_template{font-family:Helvetica,Verdana;color:#1e2022;font-size:13px;font-weight:700;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x;padding:0;text-align:center;line-height:34px}
.webix_win_head>.webix_view>.webix_template{font-family:Helvetica,Verdana;font-size:13px;font-weight:700;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);padding:0;text-align:center;line-height:34px}
.webix_win_head>.webix_view>.webix_template .webix_el_label,.webix_win_head>.webix_view>.webix_template .webix_inp_label{color:#1e2022}
.webix_win_content{height:auto;width:auto;overflow:hidden}
.webix_modal{width:100%;height:100%;position:fixed;top:0;left:0;background-color:#000;filter:alpha(opacity=20);opacity:.2}
@ -270,19 +268,18 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_point_bottom{background-position:0 0}
.webix_point_right{background-position:0 -22px}
.webix_point_left{background-position:0 -9px}
.webix_resize_handle{position:absolute;z-index:2;width:15px;height:15px;bottom:0;line-height:15px;right:-1px;text-align:center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAABlBMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAABZJREFUeAFjIAUwUshlpJDLSIhLGAAACQ4AFk79JaMAAAAASUVORK5CYII=);cursor:nw-resize}
.webix_resize_handle{position:absolute;width:15px;height:15px;bottom:0;line-height:15px;right:-1px;text-align:center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAABlBMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAABZJREFUeAFjIAUwUshlpJDLSIhLGAAACQ4AFk79JaMAAAAASUVORK5CYII=);cursor:nw-resize}
.webix_resize_frame{position:absolute;filter:alpha(opacity=10);opacity:.1;background:#d6e8ff;display:block;border:1px solid #70afff;box-shadow:1px 3px 6px #000}
.webix_resize_cursor{cursor:nw-resize}
.webix_tree .webix_scroll_cont>.webix_tree_leaves{padding:9px 10px 9px 9px}
.webix_tree_item{clear:both;height:22px;line-height:22px;white-space:nowrap}
.webix_tree_item.webix_selected{background:0 0}
.webix_tree_item.webix_selected span{padding:4px 10px 4px 4px;margin-left:-4px;background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_tree_item.webix_invalid{background-color:#fee}
.webix_tree_item.webix_selected span{padding:4px 10px 4px 4px;margin-left:-4px;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background:-webkit-linear-gradient(#ffebc2,#ffdb8f);background:-moz-linear-gradient(#ffebc2,#ffdb8f);background:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_tree_checkbox{margin:1px 0 0;padding:0;float:left;vertical-align:middle;height:100%;width:13px}
.webix_indeterminate{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gcCCSwfRyGesAAAAAxpVFh0Q29tbWVudAAAAAAAvK6ymQAAAExJREFUKM9jLCgo+M9AAmBhYGBgaGtrY+Tk5MSrsLCwkIGBgeE/EwOJgGQNLMgcJSUlBgYGBnQ/Md67d498G+jsB6hbGQfWSYykJg0AUMsPVoNrFX0AAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:left center;height:100%;width:13px;margin-top:0!important}
.webix_measure_size input,.webix_measure_size select,.webix_measure_size textarea{width:5px}
.webix_measure_size div{margin-left:-5px;float:none}
.webix_measure_size a,.webix_measure_size div{display:inline-block!important;position:static!important}
.webix_measure_size a,.webix_measure_size div{display:inline-block!important}
.webix_tree_close,.webix_tree_file,.webix_tree_folder,.webix_tree_folder_open,.webix_tree_none,.webix_tree_open{float:left;width:20px;height:100%;cursor:pointer;margin:0;background-repeat:no-repeat}
.webix_tree_close,.webix_tree_none,.webix_tree_open{background-position:center center}
.webix_cell .webix_tree_close,.webix_cell .webix_tree_none,.webix_cell .webix_tree_open{background-color:transparent}
@ -294,7 +291,7 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_tree_file{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQVAPv7+2RkZPf39/r6+vj4+Pn5+fT09Pb29vX19fHx8fDw8Onp6fLy8u7u7uzs7PPz8+vr6+rq6uXl5ejo6O/v7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABUALAAAAAASABIAAAV4YCWOZGkGaKqaVSAMQCwLQnAOj6ErzUTXpQDgQDgcGJTFDzgSEp4HRCKytDUBMKzAoIAsJACrSCjTGhINR5hELg8ECEZifS3HCgQEgj62xwZPAnwtbgMDBQU0g21Yh4iIi3aGkzBihH52lgEEmDIEli0qoigspSYhADs=)}
.webix_tree_folder{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAPvjpP/24fPHVvnUdvzotdSiKv/23vvrw/LCS/C+RfnelvPMaf/88/nbi/rsyerYq/TLYv7y1v7wzeCsLO+8P86dJfvv0MuaIsydKPPOc+SqGKGEPe/AS+zVneWwLf///yH5BAEAAB8ALAAAAAASABIAAAWZ4CdeZGleYvpdA+O+zNUBqHoZOB4EceHUqVtuF+n9VCtdQCI5BC6FA3BkCEQkBALAACVMV9eDVhGJQGnIixigaAwiG6jie2G7BxDLpVJozG13EAICPxUeGRleQQMLgggJUhiHC3pBggKPFJETGZRAF5gJCRSaFxgTC55BCByioz+nEKojGgmtHBwPJAUCELo2J8FfSMTFxsUhADs=)}
.webix_tree_folder_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAPXMWPK9N/fipOzLbPrVaP/89frqwv3tuvvprfLES+7PdP7xyP7z0vPAQfvtyMuaIv/32/735OSqFv3de+7ReP7lmf7gh/nv0v/67unFXfb29vK/PenFSaGEPfLnyv///yH5BAEAAB8ALAAAAAASABIAAAWS4Cc+ZGk+YvqZRVtgGiag6sNBThlFWnTQqQcE4sqRFj/VashsYR6HZI0JWSAjDwQCOGIgERULg/EQzJRQsGVCYHTK51qYTQDkFPj4iA4AJHIUgQoGQA8EdX4NhHgKAxeFfQkJGwGLeI6Fkg2UlQ8Dn5hBCZuchKChIxKkGw05Ga8DHlwkqg2tJ1xBtDlKvTUkviEAOw==)}
.webix_tree_img{background:0 0;float:left;width:18px;height:22px;background-position:0 0}
.webix_tree_img{background:0 0;float:left;width:18px;height:22px}
.webix_tree_plus2{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAFSWrmi52FCPplORqVmet8HBwfn//02KoE+NpEyIn2a21E2Jn2u/3mzB4Vqguk6LomGtyWGuy27E5VqivG/H51OUrE+OpVyhu1eXr2CqxV6lwFaas06LoXHJ6lKTqgAAACH5BAEAAB8ALAAAAAASABIAAAVZ4CeOYkGeaJmuKpuaLgrHX7IcD2IJrs0hAk8FAGAlDoiBYUNwTIyPAcagyUAirAXCwOUGAoVwmHSwVAiGSIDRYB0EgIthLaG44RN1g9JxwR0QdDSDhIWGKyEAOw==);background-repeat:no-repeat}
.webix_tree_plus3{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAFSWrmi52FCPplORqVmet8HBwfn//02KoE+NpEyIn2a21E2Jn2u/3mzB4Vqguk6LomGtyWGuy27E5VqivG/H51OUrE+OpVyhu1eXr2CqxV6lwFaas06LoXHJ6lKTqgAAACH5BAEAAB8ALAAAAAASABIAAAVb4CeOYkGeaJmuKpuaLgrHX7IcD2IJrs0hAk8FAGAlDoiBYUNwTIyPAcagyUAirAXCwOUGAoVwmHSwVAiGSIDRYB0EgIthLaG44RN1g9JxwR0QdDQtg4SFM4csIQA7)}
.webix_tree_plus4{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAFSWrmi52FCPplORqVmet/n//8HBwU2KoE+NpEyIn2a21E2Jn2u/3mzB4VqgumGtyWGuy27E5VqivE6Lom/H51OUrE+OpVyhu1eXr2CqxV6lwFaas06LoVKTqnHJ6gAAACH5BAEAAB8ALAAAAAASABIAAAVY4CeOZGmeaKqubEsmyzEhlqDCHCJ0FQCkiQNiUNgQHBLgZIApaDIPSGqBKFitgYBhuyUdLBVCARJgNFIHAeBSKEcoaLWE3KB4VGrHw+0yuEZ+fx+Bf4QmIQA7)}
@ -311,9 +308,9 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_cal_next_button,.webix_cal_prev_button{cursor:pointer;position:absolute;top:17px;width:30px;height:28px;background-position:11.5px 4px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}
.webix_cal_prev_button{left:17px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAALCAYAAACzkJeoAAAASUlEQVR42p2QMQoAQQgD79EZ0Mqn5xCu2cPdYgUbJ0rMMxXgLeiuKq8gsJAz8weQ0QiwJEfECnr4wdlEnwJ68+yyhReC+c85oRecj0Um+pmo9wAAAABJRU5ErkJggg==);background-repeat:no-repeat}
.webix_cal_next_button{right:17px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAALCAYAAACzkJeoAAAARElEQVR42qXPsQoAIAhF0T76PdDJT7cUmtQhcmk4N8QVQ9JXN2bmgWOgqp+BiCRG2AYAMoi3/LxYdyagAnhA6G93TrABZaJFJjrFY8IAAAAASUVORK5CYII=);background-repeat:no-repeat}
.webix_view>.webix_cal_header{margin:0 17px 10px 17px;height:auto}
.webix_view>.webix_cal_header{margin:0 17px 10px;height:auto}
.webix_view>.webix_cal_header div{float:left;text-align:center;height:19px;font-size:11px;color:#1e2022;border-bottom:#cecece 1px solid;margin-bottom:7px}
.webix_cal_body{margin:0 17px 0 17px}
.webix_cal_body{margin:0 17px}
.webix_cal_body .webix_cal_row{clear:both}
.webix_cal_body .webix_cal_row>div{float:left;text-align:center;height:100%}
.webix_cal_body .webix_cal_select,.webix_cal_body .webix_cal_select.webix_cal_today,.webix_cal_body .webix_selected{color:#1e2022;background:#ffdb8f}
@ -328,30 +325,30 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_cal_block{float:left;text-align:center;cursor:pointer;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}
.webix_selected{background:#ffdb8f;color:#1e2022}
.webix_cal_footer{text-align:center;font-size:12px;color:#444;margin:4px 17px;padding-top:2px;cursor:pointer}
.webix_cal_icons{float:right;padding:4px 4px}
.webix_cal_icons{float:right;padding:4px}
.webix_cal_time{padding:2px 4px}
.webix_cal_time_icons{float:left}
.webix_cal_icons .webix_cal_icon{text-align:center;padding:2px 4px}
.webix_cal_icon:hover,.webix_cal_month_name:hover,.webix_cal_time:hover{text-decoration:underline;color:#d6e8ff}
.webix_cal_month_name.webix_readonly:hover{text-decoration:none;color:#1e2022;cursor:default}
.webix_time_header{padding:17px 17px 1px 17px}
.webix_time_header{padding:17px 17px 1px}
.webix_time_header .webix_cal_hours,.webix_time_header .webix_cal_minutes{display:inline-block;font-size:14px;text-align:center;height:26px;line-height:22px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border-bottom:1px solid #a4bed4}
.webix_time_header .webix_cal_minutes{padding-left:1px;border-left:none}
.webix_time_header .webix_cal_hours{border-right:none!important}
.webix_cal_body .webix_hours{float:left;border-right:1px solid #cecece;padding-right:1px}
.webix_cal_body .webix_minutes{float:left;padding-left:1px}
.webix_cal_block_empty{float:left;text-align:center}
.webix_time_footer{text-align:center;font-size:12px;padding:7px 17px 0 17px;cursor:pointer}
.webix_cal_done{margin-top:3px;padding:2px 7px;font-size:12px;border-radius:6px;border:1px solid #a4bed4;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x;float:right}
.webix_time_footer{text-align:center;font-size:12px;padding:7px 17px 0;cursor:pointer}
.webix_cal_done{margin-top:3px;padding:2px 7px;font-size:12px;border-radius:6px;border:1px solid #a4bed4;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);float:right}
.webix_cal_blurred_hours{background-color:#f7f7f7}
.webix_property_line{clear:both;width:100%;min-height:24px;height:auto}
.webix_property_line:nth-child(odd){background-color:#f7f7f7}
.webix_property_label,.webix_property_value{height:24px;line-height:24px;padding-left:10px;float:left;border-bottom:1px solid #a4bed4;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden}
.webix_property_label{border-right:1px solid #a4bed4}
.webix_property_label_line{padding-left:10px;font-family:Helvetica,Verdana;color:#1e2022;font-size:13px;font-weight:700;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x;line-height:32px;line-height:23px;border-bottom:1px solid #ebebeb}
.webix_property_label_line{padding-left:10px;font-family:Helvetica,Verdana;font-size:13px;font-weight:700;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);line-height:32px;line-height:23px;border-bottom:1px solid #ebebeb}
.webix_property_label_line .webix_el_label,.webix_property_label_line .webix_inp_label{color:#1e2022}
.webix_property_col_val{width:100%;height:100%;-moz-user-select:text;-webkit-user-select:text;user-select:text}
.webix_property_col_ind{float:right;border:1px solid #a4bed4;border-radius:3px;width:16px;cursor:pointer;height:16px;margin:4px 10px 3px 0}
.webix_property_col_ind{float:right;border:1px solid #a4bed4;border-radius:2px;width:30px;cursor:pointer;height:13px;margin:3px 10px 3px 0}
.webix_property_value{text-overflow:ellipsis;white-space:nowrap}
.webix_property_check{margin-top:5px}
.webix_tooltip{display:none;position:absolute;z-index:10000;background-color:#fff;padding:5px 10px;border:1px solid #a4bed4;font-family:Helvetica,Verdana;font-size:13px;color:#1e2022;border-radius:0;box-shadow:1px 1px 0 0 #CCC}
@ -380,16 +377,16 @@ div.webix_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=
.webix_el_button button,.webix_el_button input,.webix_el_toggle button,.webix_el_toggle input,.webixbutton{border-radius:6px;border:1px solid #a4bed4;height:100%;width:100%;cursor:pointer;font-size:13px;font-family:Helvetica,Verdana;-webkit-appearance:none}
.webix_el_button .webix_img_btn,.webix_el_button .webix_img_btn .webix_img_btn_text,.webix_el_button .webix_img_btn_top,.webix_el_button .webix_img_btn_top .webix_img_btn_text,.webix_el_toggle .webix_img_btn,.webix_el_toggle .webix_img_btn .webix_img_btn_text,.webix_el_toggle .webix_img_btn_top,.webix_el_toggle .webix_img_btn_top .webix_img_btn_text{color:#1e2022}
.webix_el_button .webix_img_btn,.webix_el_button .webix_img_btn_top,.webix_el_toggle .webix_img_btn,.webix_el_toggle .webix_img_btn_top{border:0}
.webixbutton,.webixtype_base,.webixtype_next,.webixtype_prev{color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background-image:-webkit-linear-gradient(#fff,#e6e6e6);background-image:-moz-linear-gradient(#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);font-size:13px;font-family:Helvetica,Verdana;padding:0;border:0}
.webixtype_form{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x}
.webixtype_form:active{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);background:#d6e8ff;background-repeat:no-repeat}
.webix_el_button .webixtype_danger{background:#c10;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e7877e),color-stop(100%,#c10));background-image:-webkit-linear-gradient(#e7877e,#c10);background-image:-moz-linear-gradient(#e7877e,#c10);background-image:-ms-linear-gradient(top,#e7877e,#c10);background-image:-o-linear-gradient(top,#e7877e,#c10);border:none!important;color:#fff}
.webixbutton,.webixtype_base,.webixtype_next,.webixtype_prev{color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background:-webkit-linear-gradient(#fff,#e6e6e6);background:-moz-linear-gradient(#fff,#e6e6e6);background:-ms-linear-gradient(top,#fff,#e6e6e6);background:-o-linear-gradient(top,#fff,#e6e6e6);font-size:13px;font-family:Helvetica,Verdana;padding:0;border:0}
.webixtype_form{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%)}
.webixtype_form:active{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);background:no-repeat #d6e8ff}
.webix_el_button .webixtype_danger{background:#c10;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e7877e),color-stop(100%,#c10));background:-webkit-linear-gradient(#e7877e,#c10);background:-moz-linear-gradient(#e7877e,#c10);background:-ms-linear-gradient(top,#e7877e,#c10);background:-o-linear-gradient(top,#e7877e,#c10);border:none!important;color:#fff}
.webix_el_button .webix_el_box,.webix_el_toggle .webix_el_box{position:relative}
.webix_el_button .webix_el_arrow,.webix_el_toggle .webix_el_arrow{position:absolute}
.webixtype_next,.webixtype_prev{position:absolute;z-index:1;top:0}
input.webixtype_prev{border-left-width:0;border-radius:0;-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:6px}
input.webixtype_next{border-right-width:0;border-radius:0;-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:6px;border-top-right-radius:0;border-bottom-right-radius:0}
.webix_el_arrow{width:18px;height:18px;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background-image:-webkit-linear-gradient(#fff,#e6e6e6);background-image:-moz-linear-gradient(#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);border:1px solid #a4bed4}
.webix_el_button .webix_el_arrow,.webix_el_toggle .webix_el_arrow{position:absolute;z-index:1}
.webixtype_next,.webixtype_prev{position:absolute;z-index:2;top:0}
input.webixtype_prev{border-left-width:0;border-radius:0 6px 6px 0;-webkit-border-radius:0 6px 6px 0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}
input.webixtype_next{border-right-width:0;border-radius:6px 0 0 6px;-webkit-border-radius:6px 0 0 6px;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}
.webix_el_arrow{width:18px;height:18px;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background:-webkit-linear-gradient(#fff,#e6e6e6);background:-moz-linear-gradient(#fff,#e6e6e6);background:-ms-linear-gradient(top,#fff,#e6e6e6);background:-o-linear-gradient(top,#fff,#e6e6e6);border:1px solid #a4bed4}
.webixtype_prev_arrow{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg)}
.webixtype_next_arrow{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg)}
.webix_img_btn_abs_top .webix_icon,.webix_img_btn_top .webix_icon{font-size:22px;position:absolute;top:8px;left:50%}
@ -408,7 +405,7 @@ input.webixtype_next{border-right-width:0;border-radius:0;-webkit-border-top-lef
.webix_img_btn_top{background-position:center top;vertical-align:top}
.webix_img_btn_abs .webix_img_btn_text{bottom:8px}
.webix_pressed .webix_img_btn,.webix_pressed .webix_img_btn_top{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);background-color:rgba(0,0,0,.05)}
.webix_control .webix_disabled_box .webix_el_arrow,.webix_control .webix_disabled_box .webix_img_btn,.webix_control .webix_disabled_box .webixtype_base,.webix_control .webix_disabled_box .webixtype_danger,.webix_control .webix_disabled_box .webixtype_form,.webix_control .webix_disabled_box .webixtype_next,.webix_control .webix_disabled_box .webixtype_prev{color:#aaa!important;background:#e9e9e9;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fbfbfb),color-stop(100%,#e9e9e9));background-image:-webkit-linear-gradient(#fbfbfb,#e9e9e9);background-image:-moz-linear-gradient(#fbfbfb,#e9e9e9);background-image:-ms-linear-gradient(top,#fbfbfb,#e9e9e9);background-image:-o-linear-gradient(top,#fbfbfb,#e9e9e9);border-color:#d5d5d5!important}
.webix_control .webix_disabled_box .webix_el_arrow,.webix_control .webix_disabled_box .webix_img_btn,.webix_control .webix_disabled_box .webixtype_base,.webix_control .webix_disabled_box .webixtype_danger,.webix_control .webix_disabled_box .webixtype_form,.webix_control .webix_disabled_box .webixtype_next,.webix_control .webix_disabled_box .webixtype_prev{color:#aaa!important;background:#e9e9e9;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fbfbfb),color-stop(100%,#e9e9e9));background:-webkit-linear-gradient(#fbfbfb,#e9e9e9);background:-moz-linear-gradient(#fbfbfb,#e9e9e9);background:-ms-linear-gradient(top,#fbfbfb,#e9e9e9);background:-o-linear-gradient(top,#fbfbfb,#e9e9e9);border-color:#d5d5d5!important}
.webix_control .webix_disabled_box .webix_img_btn_text{color:#aaa}
.webix_control .webix_disabled_box .webix_image{opacity:.4}
.webix_disabled_view.webix_control .webix_icon,.webix_disabled_view.webix_control .webix_icon_btn,.webix_layout_toolbar.webix_toolbar .webix_disabled_view.webix_control .webix_disabled_box .webix_img_btn_text,.webix_layout_toolbar.webix_toolbar .webix_disabled_view.webix_control .webix_icon,.webix_layout_toolbar.webix_toolbar .webix_disabled_view.webix_control .webix_icon_btn{color:#aaa}
@ -418,31 +415,31 @@ input.webixtype_next{border-right-width:0;border-radius:0;-webkit-border-top-lef
.webix_richtext_container .webix_template{padding:10px;overflow-x:auto;border:solid #ccc;border-width:0 1px 1px;border-radius:0 0 6px 6px}
.webix_control{background:0 0}
.webix_control button,.webix_control input,.webix_control textarea,.webix_el_label,.webix_inp_bottom_label,.webix_inp_label,.webix_inp_top_label{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;font-family:Helvetica,Verdana}
.webix_el_box{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2px 2px;height:100%;width:100%;font-size:13px;overflow:hidden}
.webix_el_box{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2px;height:100%;width:100%;font-size:13px;overflow:hidden}
.webix_el_colorpicker input,.webix_el_combo input,.webix_el_datepicker input,.webix_el_search input,.webix_el_text input{height:100%;padding:0 10px;border:1px solid #a4bed4;color:#1e2022;font-size:13px;font-family:Helvetica,Verdana;-webkit-appearance:none;border-radius:0}
.webix_inp_label,.webix_inp_top_label,.webix_label_right{color:#1e2022;text-transform:none;font-size:13px}
.webix_inp_label{float:left;padding-top:1px;padding-left:2px;padding-right:7.5px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}
.webix_inp_top_label{float:none;padding:2px 2px;padding-bottom:0}
.webix_inp_top_label{float:none;padding:2px 2px 0}
.webix_inp_bottom_label{font-size:11px;color:#2a2d30}
.webix_label_right{padding-left:7.5px}
.webix_el_checkbox .webix_label_right{display:inline-block;padding-top:2px}
.webix_el_label{color:#1e2022;line-height:30px;padding:2px 2px}
.webix_el_label{color:#1e2022;line-height:30px;padding:2px}
.webix_toolbar .webix_el_label{padding-left:10px;text-transform:none}
.webix_el_select select{height:100%;font-size:13px;font-family:Helvetica,Verdana;border:1px solid #a4bed4}
body:not(:-moz-handler-blocked) .webix_el_select select{padding:.2em}
.webix_all_segments,.webix_el_group{height:100%;white-space:nowrap}
.webix_inp_counter_next,.webix_inp_counter_prev,.webix_inp_counter_value{border:1px solid #a4bed4;cursor:pointer;-webkit-appearance:none;width:20px;height:100%;vertical-align:top}
.webix_inp_counter_value{font-size:13px;width:50px;color:#1e2022;text-align:center;border-left-width:0;border-right-width:0;-webkit-appearance:none;border-radius:0}
.webix_inp_counter_prev{padding-left:6px;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background-image:-webkit-linear-gradient(#fff,#e6e6e6);background-image:-moz-linear-gradient(#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:6px;border-top-right-radius:0;border-bottom-right-radius:0}
.webix_inp_counter_next{padding-left:6px;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background-image:-webkit-linear-gradient(#fff,#e6e6e6);background-image:-moz-linear-gradient(#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:6px}
.webix_el_textarea textarea{border:1px solid #a4bed4;height:100%;margin:0;padding:5px;color:#1e2022;resize:none;font-size:13px;font-family:Helvetica,Verdana;padding-left:10px;padding-right:10px}
.webix_inp_counter_prev{padding-left:6px;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background:-webkit-linear-gradient(#fff,#e6e6e6);background:-moz-linear-gradient(#fff,#e6e6e6);background:-ms-linear-gradient(top,#fff,#e6e6e6);background:-o-linear-gradient(top,#fff,#e6e6e6);-webkit-border-radius:6px 0 0 6px;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-radius:6px 0 0 6px}
.webix_inp_counter_next{padding-left:6px;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background:-webkit-linear-gradient(#fff,#e6e6e6);background:-moz-linear-gradient(#fff,#e6e6e6);background:-ms-linear-gradient(top,#fff,#e6e6e6);background:-o-linear-gradient(top,#fff,#e6e6e6);-webkit-border-radius:0 6px 6px 0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;border-radius:0 6px 6px 0}
.webix_el_textarea textarea{border:1px solid #a4bed4;height:100%;margin:0;padding:5px 10px;color:#1e2022;resize:none;font-size:13px;font-family:Helvetica,Verdana}
.webix_el_textarea .webix_inp_label{padding-top:8.5px}
.webix_segment_0,.webix_segment_1{border:1px solid #a4bed4;border-right-width:0;height:100%}
.webix_segment_0{-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:6px;border-top-right-radius:0;border-bottom-right-radius:0}
.webix_segment_N{border:1px solid #a4bed4;height:100%;-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:6px}
.webix_segment_N:first-child{-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;border-top-left-radius:6px;border-bottom-left-radius:6px;border-top-right-radius:6px;border-bottom-right-radius:6px}
.webix_segment_0,.webix_segment_1,.webix_segment_N{-webkit-appearance:none;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background-image:-webkit-linear-gradient(#fff,#e6e6e6);background-image:-moz-linear-gradient(#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);font-size:13px;font-family:Helvetica,Verdana;cursor:pointer;padding:0}
.webix_segment_0.webix_selected,.webix_segment_1.webix_selected,.webix_segment_N.webix_selected{background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_segment_0{-webkit-border-radius:6px 0 0 6px;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-radius:6px 0 0 6px}
.webix_segment_N{border:1px solid #a4bed4;height:100%;-webkit-border-radius:0 6px 6px 0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;border-radius:0 6px 6px 0}
.webix_segment_N:first-child{-webkit-border-radius:6px;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;border-radius:6px}
.webix_segment_0,.webix_segment_1,.webix_segment_N{-webkit-appearance:none;color:#1e2022;background:#e6e6e6;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e6e6e6));background:-webkit-linear-gradient(#fff,#e6e6e6);background:-moz-linear-gradient(#fff,#e6e6e6);background:-ms-linear-gradient(top,#fff,#e6e6e6);background:-o-linear-gradient(top,#fff,#e6e6e6);font-size:13px;font-family:Helvetica,Verdana;cursor:pointer;padding:0}
.webix_segment_0.webix_selected,.webix_segment_1.webix_selected,.webix_segment_N.webix_selected{color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background:-webkit-linear-gradient(#ffebc2,#ffdb8f);background:-moz-linear-gradient(#ffebc2,#ffdb8f);background:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_inp_static{border:1px solid #a4bed4;background:#fff;overflow:hidden;padding:0 10px;height:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;float:left}
.webix_inp_static .webix_placeholder{color:#666d73}
.webix_el_colorpicker,.webix_el_combo,.webix_el_datepicker,.webix_el_richselect,.webix_el_search{font-size:13px}
@ -468,15 +465,15 @@ body:not(:-moz-handler-blocked) .webix_el_select select{padding:.2em}
.webix_disabled_view.webix_control .webix_image{-webkit-filter:grayscale(100%);filter:grayscale(100%)}
.webix_view.webix_control .webix_disabled_box .webix_input_icon,.webix_view.webix_control .webix_disabled_box button,.webix_view.webix_control .webix_disabled_box label{color:#aaa}
.webix_view.webix_control .webix_disabled_box .webix_inp_static,.webix_view.webix_control .webix_disabled_box input,.webix_view.webix_control .webix_disabled_box select,.webix_view.webix_control .webix_disabled_box textarea{color:#aaa;background:#eee}
.webix_view.webix_control .webix_disabled_box .webix_inp_counter_next,.webix_view.webix_control .webix_disabled_box .webix_inp_counter_prev,.webix_view.webix_control .webix_disabled_box .webix_segment_0,.webix_view.webix_control .webix_disabled_box .webix_segment_1,.webix_view.webix_control .webix_disabled_box .webix_segment_N{color:#aaa!important;background:#e9e9e9;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fbfbfb),color-stop(100%,#e9e9e9));background-image:-webkit-linear-gradient(#fbfbfb,#e9e9e9);background-image:-moz-linear-gradient(#fbfbfb,#e9e9e9);background-image:-ms-linear-gradient(top,#fbfbfb,#e9e9e9);background-image:-o-linear-gradient(top,#fbfbfb,#e9e9e9);border-color:#d5d5d5!important}
.webix_view.webix_control .webix_disabled_box .webix_inp_counter_next,.webix_view.webix_control .webix_disabled_box .webix_inp_counter_prev,.webix_view.webix_control .webix_disabled_box .webix_segment_0,.webix_view.webix_control .webix_disabled_box .webix_segment_1,.webix_view.webix_control .webix_disabled_box .webix_segment_N{color:#aaa!important;background:#e9e9e9;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fbfbfb),color-stop(100%,#e9e9e9));background:-webkit-linear-gradient(#fbfbfb,#e9e9e9);background:-moz-linear-gradient(#fbfbfb,#e9e9e9);background:-ms-linear-gradient(top,#fbfbfb,#e9e9e9);background:-o-linear-gradient(top,#fbfbfb,#e9e9e9);border-color:#d5d5d5!important}
.webix_disabled_top_label{color:#aaa}
label.webix_required:after,legend.webix_required:after{padding-left:4px;content:"*";color:red}
label.webix_required:after{padding-left:4px;content:"*";color:red}
.webix_multicombo .webix_inp_static{padding:0 3px}
.webix_el_text .webix_multicombo_input{background-color:transparent;border:0;float:left;vertical-align:middle;padding:0;color:#1e2022;font-size:13px;font-family:Helvetica,Verdana;margin-left:7px}
.webix_el_text .webix_multicombo_input:focus{border:0;box-shadow:none}
.webix_multicombo_listbox{width:auto;line-height:1;margin:0;padding:0;list-style:none}
.webix_multicombo_tag,.webix_multicombo_value{padding:0 5px 0 7px;background-color:#f2f2f2;display:inline-block;margin:3px;float:left;border-radius:6px}
.webix_multicombo_tag{min-width:15px;text-align:center;padding:0 5px 0 5px}
.webix_multicombo_tag{min-width:15px;text-align:center;padding:0 5px}
.webix_multicombo_delete{margin-left:10px;padding:0 5px;color:#000;cursor:pointer}
.webix_multilist .webix_selected{background:#f2f2f2;color:#1e2022}
.webix_checksuggest_select_all{border:none;border-bottom:1px solid #ebebeb;border-color:#ebebeb;margin-bottom:1px}
@ -488,13 +485,13 @@ label.webix_required:after,legend.webix_required:after{padding-left:4px;content:
.webix_tab_filler{display:table-cell;background:0 0;font-size:1px;border-bottom:1px solid #a4bed4;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_tab_filler:first-child{border-right:1px solid #a4bed4}
.webix_item_tab:first-child{border-left:1px solid #a4bed4}
.webix_item_tab{display:table-cell;text-align:center;vertical-align:middle;overflow:hidden;cursor:pointer;border:1px solid #a4bed4;border-left-width:0;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x;-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_item_tab{display:table-cell;text-align:center;vertical-align:middle;overflow:hidden;cursor:pointer;border:1px solid #a4bed4;border-left-width:0;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);-webkit-border-radius:0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-radius:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webixtype_bottom .webix_item_tab{border-radius:0}
.webix_item_tab.webix_selected{border-bottom-width:0;background:#fff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e5f1ff),color-stop(100%,#fff));background-image:-webkit-linear-gradient(#e5f1ff,#fff);background-image:-moz-linear-gradient(#e5f1ff,#fff);background-image:-ms-linear-gradient(top,#e5f1ff,#fff);background-image:-o-linear-gradient(top,#e5f1ff,#fff)}
.webixtype_bottom .webix_item_tab.webix_selected{border-bottom-width:1px;background-size:100% 100%;background-position:0 0;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background-image:-webkit-linear-gradient(#ffebc2,#ffdb8f);background-image:-moz-linear-gradient(#ffebc2,#ffdb8f);background-image:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background-image:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_item_tab.webix_selected{border-bottom-width:0;background:#fff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e5f1ff),color-stop(100%,#fff));background:-webkit-linear-gradient(#e5f1ff,#fff);background:-moz-linear-gradient(#e5f1ff,#fff);background:-ms-linear-gradient(top,#e5f1ff,#fff);background:-o-linear-gradient(top,#e5f1ff,#fff)}
.webixtype_bottom .webix_item_tab.webix_selected{border-bottom-width:1px;color:#1e2022;background:#ffdb8f;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ffebc2),color-stop(100%,#ffdb8f));background:-webkit-linear-gradient(#ffebc2,#ffdb8f);background:-moz-linear-gradient(#ffebc2,#ffdb8f);background:-ms-linear-gradient(top,#ffebc2,#ffdb8f);background:-o-linear-gradient(top,#ffebc2,#ffdb8f)}
.webix_before_all_tabs{width:100%}
.webix_after_all_tabs{width:100%;border-left:1px solid #a4bed4;border-right:1px solid #a4bed4;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_view.webix_control .webix_disabled_box .webix_item_tab{color:#aaa!important;background:#e9e9e9;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fbfbfb),color-stop(100%,#e9e9e9));background-image:-webkit-linear-gradient(#fbfbfb,#e9e9e9);background-image:-moz-linear-gradient(#fbfbfb,#e9e9e9);background-image:-ms-linear-gradient(top,#fbfbfb,#e9e9e9);background-image:-o-linear-gradient(top,#fbfbfb,#e9e9e9);border-color:#d5d5d5!important}
.webix_view.webix_control .webix_disabled_box .webix_item_tab{color:#aaa!important;background:#e9e9e9;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fbfbfb),color-stop(100%,#e9e9e9));background:-webkit-linear-gradient(#fbfbfb,#e9e9e9);background:-moz-linear-gradient(#fbfbfb,#e9e9e9);background:-ms-linear-gradient(top,#fbfbfb,#e9e9e9);background:-o-linear-gradient(top,#fbfbfb,#e9e9e9);border-color:#d5d5d5!important}
.webix_el_tabbar .webixtype_icon .webix_img_btn{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0 auto;text-align:center}
.webix_el_tabbar .webixtype_icon .webix_img_btn .webix_icon_btn{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0 3px}
.webix_el_tabbar .webixtype_icon .webix_item_tab,.webix_el_tabbar .webixtype_iconTop .webix_item_tab,.webix_el_tabbar .webixtype_image .webix_item_tab{padding:2px 0;border-radius:0}
@ -508,17 +505,17 @@ label.webix_required:after,legend.webix_required:after{padding-left:4px;content:
.webix_menu .webix_list_item .webix_submenu_icon,.webix_menu-x .webix_list_item .webix_submenu_icon{float:right;text-align:right;position:relative;margin-left:4px}
.webix_menu-x .webix_list_item:first-child{margin-left:0}
.webix_menu-x .webix_submenu_icon{top:13px}
.webix_menu-x{background-color:#fff;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x}
.webix_menu-x{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%)}
.webix_menu-x .webix_list_item{display:inline-block;line-height:30px;-moz-background-size:1px 100%,100% 100%,1px 100%;background-size:1px 100%,100% 100%,1px 100%;background-color:transparent;margin:1px 0;background-position:0 0,1px 0,100% 0;background-repeat:no-repeat;background-image:-webkit-gradient(linear,left top,left bottom,#f0f6ff,#fff,#f0f6ff),-webkit-gradient(linear,left top,left bottom,transparent,transparent),-webkit-gradient(linear,left top,left bottom,#f0f6ff,#a4bed4,#f0f6ff);background-image:-webkit-linear-gradient(#f0f6ff,#fff,#f0f6ff),-webkit-linear-gradient(transparent,transparent),-webkit-linear-gradient(#f0f6ff,#a4bed4,#f0f6ff);background-image:-moz-linear-gradient(#f0f6ff,#fff,#f0f6ff),-moz-linear-gradient(transparent,transparent),-moz-linear-gradient(#f0f6ff,#a4bed4,#f0f6ff);background-image:-ms-linear-gradient(top,#f0f6ff,#fff,#f0f6ff),-ms-linear-gradient(transparent,transparent),-ms-linear-gradient(top,#f0f6ff,#a4bed4,#f0f6ff);background-image:-o-linear-gradient(top,#f0f6ff,#fff,#f0f6ff),-o-linear-gradient(transparent,transparent),-o-linear-gradient(top,#f0f6ff,#a4bed4,#f0f6ff)}
.webix_menu-x .webix_list_item:last-child{background-size:1px 100%,100% 100%,1px 100%;background-repeat:no-repeat;background-image:-webkit-gradient(linear,left top,left bottom,#f0f6ff,#fff,#f0f6ff),-webkit-gradient(linear,left top,left bottom,transparent,transparent),-webkit-gradient(linear,left top,left bottom,transparent,transparent);background-image:-webkit-linear-gradient(#f0f6ff,#fff,#f0f6ff),-webkit-linear-gradient(transparent,transparent),-webkit-linear-gradient(transparent,transparent);background-image:-moz-linear-gradient(#f0f6ff,#fff,#f0f6ff),-moz-linear-gradient(transparent,transparent),-moz-linear-gradient(transparent,transparent);background-image:-ms-linear-gradient(top,#f0f6ff,#fff,#f0f6ff),-ms-linear-gradient(transparent,transparent),-ms-linear-gradient(transparent,transparent);background-image:-o-linear-gradient(top,#f0f6ff,#fff,#f0f6ff),-o-linear-gradient(transparent,transparent),-o-linear-gradient(transparent,transparent);border-right:0}
.webix_menu{background:#fff;border:1px solid #a4bed4;border-radius:0}
.webix_menu .webix_list_item{display:block}
.webix_menu .webix_context_separator{height:3px;padding:1px 3px 0 3px}
.webix_menu .webix_context_separator{height:3px;padding:1px 3px 0}
.webix_menu .webix_context_separator .sep_line{height:1px;border-top:1px solid #a4bed4}
.webix_menu .webix_submenu_icon{top:11px}
.webix_menu .webix_context_spacer{height:28px}
.webix_menu_link{color:inherit;text-decoration:none;display:block;height:100%}
.webix_layout_toolbar,.webix_toolbar.webix_layout_subbar{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x}
.webix_layout_toolbar,.webix_toolbar.webix_layout_subbar{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%)}
.webix_toolbar .webix_layout_clean,.webix_toolbar .webix_layout_line,.webix_toolbar .webix_layout_toolbar{background:0 0}
.webix_el_button .webix_hidden_upload,.webix_hidden_upload{height:1px;width:1px;border:0!important;font-size:1px;position:absolute;top:0;left:0;z-index:-1}
.webix_upload_client,.webix_upload_error,.webix_upload_server{width:60px;text-align:center;float:right}
@ -533,42 +530,29 @@ label.webix_required:after,legend.webix_required:after{padding-left:4px;content:
.webix_fieldset fieldset{border:1px solid #81a5c4;margin:0;padding:5px 8px}
.webix_fieldset legend{color:#1e2022;font-size:12px}
.webix_forminput fieldset{border:none;margin:0;padding:0}
.webix_forminput legend{color:#1e2022;line-height:30px;padding:2px 5px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;font-family:Helvetica,Verdana;float:left;padding-right:7.5px}
.webix_slider_box{position:relative;padding:0 10px;height:100%;padding-top:13px;float:left;border-radius:5px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin-top:-11px}
.webix_slider_box .webix_slider_left{height:10px;float:left;width:100px;border:1px solid #b5cadc;-webkit-border-top-left-radius:5px;-webkit-border-bottom-left-radius:5px;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:5px;-moz-border-radius-bottomleft:5px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-top-left-radius:5px;border-bottom-left-radius:5px;border-top-right-radius:0;border-bottom-right-radius:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_slider_box .webix_slider_right{height:10px;float:left;width:100px;background:#fff;-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:5px;-webkit-border-bottom-right-radius:5px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:5px;-moz-border-radius-bottomright:5px;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:5px;border-bottom-right-radius:5px;border:1px solid #b5cadc;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_forminput legend{color:#1e2022;line-height:30px;padding:2px 7.5px 2px 2px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;font-family:Helvetica,Verdana;float:left}
.webix_slider_box{position:relative;padding:13px 10px 0;height:100%;float:left;border-radius:5px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin-top:-11px}
.webix_slider_box .webix_slider_left{height:10px;float:left;width:100px;border:1px solid #b5cadc;-webkit-border-radius:5px 0 0 5px;-moz-border-radius-topleft:5px;-moz-border-radius-bottomleft:5px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-radius:5px 0 0 5px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_slider_box .webix_slider_right{height:10px;float:left;width:100px;background:#fff;-webkit-border-radius:0 5px 5px 0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:5px;-moz-border-radius-bottomright:5px;border-radius:0 5px 5px 0;border:1px solid #b5cadc;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.webix_slider_box .webix_slider_handle{z-index:1;position:absolute;width:14px;height:14px;border-radius:8px}
.webix_slider_box .webix_slider_left{background:#f5f9ff}
.webix_slider_box .webix_slider_handle{border:1px solid #a4bed4;-moz-box-shadow:inset 0 1px 1px 1px #fff;-webkit-box-shadow:inset 0 1px 1px 1px #fff;box-shadow:inset 0 1px 1px #fff;background:#fafcff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#d6e8ff),color-stop(100%,#fafcff));background-image:-webkit-linear-gradient(#d6e8ff,#fafcff);background-image:-moz-linear-gradient(#d6e8ff,#fafcff);background-image:-ms-linear-gradient(top,#d6e8ff,#fafcff);background-image:-o-linear-gradient(top,#d6e8ff,#fafcff);top:10px}
.webix_slider_box .webix_slider_handle{border:1px solid #a4bed4;-moz-box-shadow:inset 0 1px 1px 1px #fff;-webkit-box-shadow:inset 0 1px 1px 1px #fff;box-shadow:inset 0 1px 1px #fff;background:#fafcff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#d6e8ff),color-stop(100%,#fafcff));background:-webkit-linear-gradient(#d6e8ff,#fafcff);background:-moz-linear-gradient(#d6e8ff,#fafcff);background:-ms-linear-gradient(top,#d6e8ff,#fafcff);background:-o-linear-gradient(top,#d6e8ff,#fafcff);top:10px}
.webix_rangeslider .webix_slider_box .webix_slider_left{position:absolute}
.webix_rangeslider .webix_slider_box .webix_slider_active{z-index:1}
.webix_rangeslider .webix_slider_box .webix_slider_right{border-radius:5px}
.webix_rangeslider .webix_slider_title_box{position:relative;float:left;width:1px}
.webix_rangeslider .webix_slider_title.webix_slider_move{position:absolute}
.webix_rangeslider.webix_slider_vertical .webix_slider_box .webix_slider_right{-webkit-border-top-left-radius:5px;-webkit-border-bottom-left-radius:5px;-webkit-border-top-right-radius:5px;-webkit-border-bottom-right-radius:5px;-moz-border-radius-topleft:5px;-moz-border-radius-bottomleft:5px;-moz-border-radius-topright:5px;-moz-border-radius-bottomright:5px;border-top-left-radius:5px;border-bottom-left-radius:5px;border-top-right-radius:5px;border-bottom-right-radius:5px}
.webix_rangeslider.webix_slider_vertical .webix_slider_title.webix_slider_move{display:block;position:relative}
.webix_slider_alt .webix_slider_box .webix_slider_left,.webix_slider_alt .webix_slider_box .webix_slider_right{border:1px solid #e1e1e1}
.webix_slider_alt .webix_slider_box .webix_slider_handle{width:11px;margin:0 2px;height:14px;border-radius:2px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAICAYAAAAx8TU7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OTJCMjdFNENBRTg4MTFFMjk2NjJGMTJENjkyNDA2NTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OTJCMjdFNERBRTg4MTFFMjk2NjJGMTJENjkyNDA2NTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5MkIyN0U0QUFFODgxMUUyOTY2MkYxMkQ2OTI0MDY1OSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5MkIyN0U0QkFFODgxMUUyOTY2MkYxMkQ2OTI0MDY1OSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Powz/icAAAAaSURBVHjaYmBgYDBjgAA4zcSABQysIECAAQBn+ACx2PqtbAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:3px center}
.webix_slider_alt .webix_slider_box .webix_slider_left{background:#f0f6ff}
.webix_slider_alt .webix_slider_box .webix_slider_right{background:#eee}
.webix_slider_alt .webix_slider_box .webix_slider_handle{background-color:#f5f9ff;top:10px}
.webix_slider_vertical .webix_slider_box .webix_slider_left,.webix_slider_vertical .webix_slider_box .webix_slider_right{width:10px;float:none}
.webix_slider_vertical .webix_slider_box .webix_slider_left{-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:5px;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:5px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:5px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:5px;border-top-left-radius:0;border-bottom-left-radius:5px;border-top-right-radius:0;border-bottom-right-radius:5px}
.webix_slider_vertical .webix_slider_box .webix_slider_right{-webkit-border-top-left-radius:5px;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:5px;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:5px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:5px;-moz-border-radius-bottomright:0;border-top-left-radius:5px;border-bottom-left-radius:0;border-top-right-radius:5px;border-bottom-right-radius:0}
.webix_slider_vertical .webix_slider_box .webix_slider_left{-webkit-border-radius:0 0 5px 5px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:5px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:5px;border-radius:0 0 5px 5px}
.webix_slider_vertical .webix_slider_box .webix_slider_right{-webkit-border-radius:5px 5px 0 0;-moz-border-radius-topleft:5px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:5px;-moz-border-radius-bottomright:0;border-radius:5px 5px 0 0}
.webix_slider_vertical .webix_slider_box .webix_slider_handle{left:7px}
.webix_slider_vertical .webix_slider_title{padding-top:1px;text-align:left}
.webix_slider_vertical.webix_slider_alt .webix_slider_handle{height:11px;width:14px;left:5px;background-position:4px center}
.webix_slider_title{text-align:center}
.webix_slider_title.webix_slider_move{position:relative;text-align:left;display:inline-block;width:auto;white-space:nowrap}
.webix_el_box .webix_switch_box{margin-top:4px}
.webix_switch_toggle{position:absolute;visibility:hidden}
.webix_switch_box{display:block;position:relative;cursor:pointer;height:24px;width:60px;background-color:#f1f1f1;border:1px solid #a4bed4;border-radius:60px;box-sizing:border-box;transition:background-color .4s ease;color:#666;text-align:center;float:left}
.webix_el_switch .webix_label_right{display:inline-block;padding-top:3px;margin-top:4px}
.webix_switch_handle{height:22px;width:22px;border-radius:100%;box-shadow:0 1px 5px rgba(0,0,0,.3);border:1px solid #eee;position:absolute;top:0;background-color:#fff;transition:left .3s ease;cursor:pointer;box-sizing:border-box}
.webix_switch_box:active .webix_switch_handle,.webix_switch_handle:focus{box-shadow:0 0 2px 2px rgba(0,0,0,.2)}
.webix_switch_text{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;padding:1px 0 0 18px;display:inline-block;box-sizing:border-box;width:100%}
.webix_switch_on .webix_switch_text{padding:1px 18px 0 0}
.webix_switch_box.webix_switch_on{color:#fff}
.webix_slider_title.webix_slider_move{position:relative;text-align:left;display:inline-block;width:auto}
.webix_progress_bottom,.webix_progress_top{width:100%;position:absolute;top:0;left:0;height:6px;overflow:hidden;z-index:20}
.webix_progress_bottom{bottom:0;top:auto;height:17px}
.webix_progress_bottom .webix_progress_state,.webix_progress_top .webix_progress_state{width:0;height:6px;background:#ffdb8f;-moz-transition:width 3s;transition:width 3s}
@ -578,7 +562,7 @@ label.webix_required:after,legend.webix_required:after{padding-left:4px;content:
.webix_gage{height:100%;width:100%;transform:rotate(180deg)}
.webix_gage_label{font-size:.9em;text-align:center;margin-top:10px}
.webix_gage_placeholder{margin-top:-20px}
.webix_gage_info{text-align:center;font-size:.7em;position:relative;margin:0 auto 10px auto}
.webix_gage_info{text-align:center;font-size:.7em;position:relative;margin:0 auto 10px}
.webix_gage_min_range{left:-74px}
.webix_gage_max_range{right:-74px}
.webix_gage_max_range,.webix_gage_min_range{display:inline-block;margin-top:20px;position:absolute;text-align:center;width:150px}
@ -587,190 +571,22 @@ label.webix_required:after,legend.webix_required:after{padding-left:4px;content:
.webix_gage_gradient_point_animated{transition:transform 1.3s linear}
.webix_gage_animated{stroke:#0f0;animation:gage_dash 1.3s linear forwards;transition:stroke 1.3s linear,stroke-dasharray 1.3s linear}
.webix_gage_animated_first_load{stroke:#0f0;animation:gage_dash 1.3s linear forwards;transition:stroke 1.3s linear}
@keyframes gage_dash{to{stroke-dashoffset:0}
}
@keyframes gage_dash{to{stroke-dashoffset:0}}
.webix_bullet_name{width:100}
.webix_bullet_header{font-weight:700;font-size:15px}
.webix_bullet_subheader{font-size:15px}
.webix_bullet_scale{font-size:12px;font-weight:700}
.webix_organogram canvas{position:absolute}
.webix_organogram_item{position:absolute;z-index:1;text-align:center;border-radius:4px;background-color:#e3f2fd;border:1px solid #bbdefb;padding:7px 7px 10px 7px;box-sizing:border-box}
.webix_organogram_item{position:absolute;z-index:1;text-align:center;border-radius:4px;background-color:#e3f2fd;border:1px solid #bbdefb;padding:7px 7px 10px;box-sizing:border-box}
.webix_organogram_item.webix_selected{background-color:#ffdb8f;border-color:#ffdb8f}
.webix_organogram_list{border-radius:4px;position:absolute;z-index:1;height:auto;border:1px solid #ddd;box-sizing:border-box}
.webix_organogram_list .webix_organogram_list_item{border:none;text-align:left;position:static;padding:5px;border-radius:0;box-sizing:border-box}
.webix_icon,.webix_icon_btn,.webix_input_icon{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0,0)}
.webix_icon,.webix_input_icon{font-size:17px;display:inline-block;width:20px}
.webix_input_icon{float:right;font-size:19px;color:#888}
.webix_icon_button{background-color:transparent;border:none;padding:0;cursor:pointer;position:relative}
.webix_icon_button .webix_icon{font-size:20px;height:23px;width:21px;display:inline-block;text-align:center;color:#606060}
.webix_icon_btn{font-size:17px;display:inline-block;text-align:center;width:24px;color:#606060}
.webix_el_iconBbutton .webix_icon_btn{text-align:center;width:100%;padding:5px 0}
.webix_el_icon{cursor:pointer}
.webix_badge{background-color:#ff8839;color:#fff;border-radius:50%;font-size:12px;height:22px;min-width:22px;box-sizing:border-box;padding:0 4px;text-align:center;line-height:21px;position:absolute;right:0;top:0}
.webix_list_item .webix_badge{position:static;float:right;margin:4px -5px 0 10px}
.webixtype_base .webix_badge{top:50%;margin-top:-11px;margin-right:8px}
.webix_treemap{background-color:#f5f5f5}
.webix_treemap .webix_scroll_cont{position:relative;height:100%;width:100%}
.webix_treemap_item{position:absolute;color:#444;overflow:hidden;text-align:left;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding:3px 5px;border-width:0 1px 1px 0;border-style:solid;border-color:rgba(0,0,0,.2);background:0 0;cursor:pointer}
.webix_treemap_level_top{z-index:1;border-color:rgba(0,0,0,.4)}
.webix_treemap_item_bottom{border-bottom-width:0}
.webix_treemap_item_right{border-right-width:0}
.webix_treemap_item:hover{box-shadow:inset 0 0 5px #666}
.webix_treemap_item.webix_selected{border-color:#ffdb8f;box-shadow:inset 0 0 1px 2px #ffdb8f}
.webix_treemap_header{width:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 10px;border-bottom:1px solid #a4bed4}
.webix_treemap_header_item{cursor:pointer}
.webix_treemap_header_item:last-child{cursor:default}
.webix_treemap_reset{float:right;width:25px;text-align:center}
.webix_treemap_reset:before{content:"\f00d";color:#1e2022;font-family:FontAwesome;font-size:16px;display:block;cursor:pointer}
.webix_treemap_path_icon{width:20px;text-align:center}
.webix_barcode{position:relative}
.webix_barcode .webix_canvas_text{font-size:13px;padding:0 2px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.portlet_drag{position:absolute;z-index:1;top:5px;right:5px;width:18px;opacity:.5;cursor:pointer}
.portlet_in_drag{opacity:.4}
.portlet_marker,.portlet_markerbottom,.portlet_markerleft,.portlet_markerright,.portlet_markertop{opacity:.5;background:#9169BE;position:absolute;z-index:2;transition:top .5s,left .5s,width .5s,height .5s;top:0;left:0;width:100%;height:100%}
.portlet_markertop{height:50%}
.portlet_markerbottom{height:50%;top:50%}
.portlet_markerleft{width:50%}
.portlet_markerright{width:50%;left:50%}
.panel_icon{position:absolute;z-index:100;top:5px;right:5px;line-height:normal;font-size:13px;width:18px;opacity:.5;cursor:pointer}
.panel_target{position:absolute;background:rgba(120,120,120,.84);box-shadow:0 0 10px 5px #666;z-index:3}
.webix_popup.webix_sidemenu.webix_sidemenu_left{border-width:0 1px 0 0;box-shadow:2px 0 2px rgba(0,0,0,.05)}
.webix_sidemenu_left.webix_animate{transition:left .6s}
.webix_popup.webix_sidemenu.webix_sidemenu_right{left:auto;border-width:0 0 0 1px;box-shadow:-2px 0 2px rgba(0,0,0,.05)}
.webix_sidemenu_right.webix_animate{transition:right .6s}
.webix_popup.webix_sidemenu.webix_sidemenu_top{border-width:0 0 1px 0;box-shadow:0 2px 2px rgba(0,0,0,.05)}
.webix_sidemenu_top .webix_win_body{position:relative}
.webix_sidemenu_top .webix_win_body>.webix_view{position:absolute;top:auto;left:0;bottom:0}
.webix_sidemenu_top.webix_animate,.webix_sidemenu_top.webix_animate .webix_win_body{transition:height .6s}
.webix_popup.webix_sidemenu.webix_sidemenu_bottom{border-width:1px 0 0 0;top:auto;box-shadow:0 -2px 2px rgba(0,0,0,.05)}
.webix_sidemenu_bottom.webix_animate,.webix_sidemenu_bottom.webix_animate .webix_win_body{transition:height .6s}
.webix_sidebar{background:#ECEFF1}
.webix_sidebar .webix_tree_item{color:#454545;height:35px;line-height:35px}
.webix_sidebar .webix_scroll_cont>.webix_tree_leaves{padding:0}
.webix_sidebar .webix_tree_leaves .webix_tree_leaves{margin-left:0}
.webix_sidebar_expanded .webix_tree_item:hover,.webix_sidebar_selected{background-color:rgba(0,0,0,.02)}
.webix_sidebar .webix_tree_item.webix_selected,.webix_sidebar .webix_tree_item.webix_selected span{background-color:#27ae60;padding-right:0}
.webix_sidebar .webix_tree_branch_1 .webix_tree_item{padding-left:40px}
.webix_sidebar .webix_tree_branch_1>.webix_tree_item{height:40px;line-height:40px;padding-left:0}
.webix_sidebar .webix_tree_branch_1{border-bottom:1px solid #e5e5e5}
.webix_sidebar .webix_tree_item span,.webix_sidebar .webix_tree_item.webix_selected span{margin:0;padding:0}
.webix_sidebar_icon{width:40px;text-align:center}
.webix_sidebar_dir_icon{float:right;line-height:inherit}
.webix_sidebar_popup{border:none!important;box-shadow:2px 3px 3px #ddd}
.webix_sidebar_popup,.webix_sidebar_popup .webix_list_item{border-radius:0}
.webix_sidebar_popup_right{box-shadow:-1px 3px 3px #ddd}
.webix_sidebar_popup_list.webix_sidebar_popup_left .webix_icon{float:right;line-height:inherit;width:7px}
.webix_sidebar_popup_title{background:#ECEFF1}
.webix_sidebar_popup_title.webix_selected{border-left-color:#27ae60;background:#27ae60}
.webix_sidebar_popup_title .webix_template{line-height:40px;padding:0 10px;border:1px solid #E5E5E5;border-left:none}
.webix_sidebar_selected.webix_sidebar_popup_title .webix_template{background:rgba(0,0,0,.03);border-left:none}
.webix_sidebar_popup_list .webix_list_item{border-left:1px solid #E5E5E5;border-right:1px solid #E5E5E5}
.webix_sidebar_popup_list .webix_list_item:first-child{border-top:1px solid #E5E5E5}
.webix_sidebar_popup_list .webix_list_item:hover{background:#f6f9fb}
.webix_sidebar_popup_list .webix_list_item.webix_selected:hover{background:#27ae60}
.webix_menu .webix_list_item.webix_sidebar_selected{background:rgba(0,0,0,.02)}
.webix_menu .webix_list_item.webix_sidebar_selected:hover{background:rgba(0,0,0,.02)!important}
.webix_view.webix_pdf{background-color:#404040;overflow:auto;-webkit-overflow-scrolling:touch}
.webix_pdf .canvas_wrapper{margin:0 auto;box-shadow:5px 5px 15px #1c1c1c;width:100%;height:100%}
.webix_toolbar.pdf_bar{background-color:#474747}
.webix_toolbar.pdf_bar .webix_icon_btn{font-size:15px}
.webix_toolbar.pdf_bar .webix_img_btn:focus,.webix_toolbar.pdf_bar .webix_img_btn:hover{background-color:#404040}
.webix_toolbar.pdf_bar .webix_template{background-color:#3498db;color:#fff;line-height:2em}
.webix_toolbar.pdf_bar .webix_el_box input,.webix_toolbar.pdf_bar .webix_inp_static{background-color:#5c5c5c;color:#fff;border-color:#404040}
.webix_toolbar.pdf_bar .webix_el_box input:focus,.webix_toolbar.pdf_bar .webix_inp_static:focus{border-color:#333}
.webix_toolbar.pdf_bar .webix_el_box .webix_input_icon{color:#fff}
.webix_view.webix_popup.pdf_opt_list{box-shadow:5px 5px 15px #1c1c1c;border:none}
.pdf_opt_list .webix_list{background-color:#404040;color:#fff}
.pdf_opt_list .webix_list .webix_list_item{border-color:#474747;line-height:1.5em}
.pdf_opt_list .webix_list .webix_list_item.webix_selected,.pdf_opt_list .webix_list .webix_list_item:hover{color:#404040;background-color:#fff}
.webix_dbllist .webix_list{background:#ededed}
.webix_dbllist .webix_list_item{background:#fff;margin:3px 4px 0 4px;box-sizing:border-box}
.webix_dbllist .webix_list_item.webix_selected{color:#FFF;background:#27ae60}
.webix_dbllist button{width:45%;height:30px;margin-top:5px;background:#fff;border-radius:6px;border:1px solid #a4bed4;cursor:pointer;font-size:13px;font-family:Helvetica,Verdana}
.webix_dbllist button:active{box-shadow:inset 1px 1px 0 silver}
.webix_dbllist button .webix_icon{color:#888;font-size:26px}
.webix_dbllist button:first-child{margin-top:32px;margin-right:5%}
.webix_dbllist button:last-child{margin-left:5%}
.webix_dbllist .bottom_label{font-size:10px;text-transform:uppercase;background:#ededed;padding-left:13px}
.webix_invalid .webix_list{background:#f8e2e2}
.webix_toolbar .webix_el_label,.webix_toolbar .webix_inp_label{color:#1e2022}
.webix_ss_sort_asc{position:absolute;float:right;right:3px;top:8px;width:7px;height:13px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)}
.webix_ss_sort_desc{position:absolute;right:3px;top:8px;width:7px;height:13px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)}
.webix_group_back .webix_arrow_icon{color:#1e2022}
.webix_menu-x .webix_list_item:active,.webix_menu-x .webix_list_item:focus,.webix_menu-x .webix_list_item:hover{background-color:rgba(255,255,255,.4)!important}
.webix_menu .webix_list_item:active,.webix_menu .webix_list_item:hover{background-color:rgba(0,0,0,.05)!important}
.webix_list_item .webix_submenu_icon{width:0;height:0;border-width:4px;border-style:solid}
.webix_menu-x .webix_list_item .webix_submenu_icon{border-color:#375975 transparent transparent transparent}
.webix_menu .webix_list_item .webix_submenu_icon{border-color:transparent transparent transparent #375975}
.webix_popup_title{color:#1e2022!important;background:#e3e3e3!important;font-size:15px!important;font-weight:700;border-bottom:1px solid #dedede!important;text-shadow:none!important}
.webix_modal_box{background:#ebebeb!important;border:1px solid #a4bed4!important}
.confirmButton{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x}
.confirmButtonActive{background:#fff}
.webix_item_tab{font-size:13px}
.buttonStyle{font-size:13px;font-family:Helvetica,Verdana}
.activeShadow{box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}
.webix_img_btn_abs:active+input,.webix_inp_counter_next:active,.webix_inp_counter_prev:active,.webixtype_base:active{background:#f5f5f5;box-shadow:inset 0 3px 5px rgba(0,0,0,.125);background-repeat:none}
.webix_icon_button:active,.webix_img_btn:active,.webix_img_btn_top:active{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);background-color:rgba(0,0,0,.05)}
.webix_pressed .webix_img_btn_abs+input,.webix_pressed .webixtype_base{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);background:#f5f5f5!important}
.webix_slider_vertical .webix_slider_box{margin-top:-8px}
.webix_rangeslider .webix_slider_title_box{height:17px}
.webix_switch_box.webix_switch_on{background-color:#a4bed4}
.webix_switch_text{line-height:21px}
.webix_accordionitem_label .webix_icon{font-size:19px;width:25px}
.webix_accordionitem.vertical>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAHCAYAAADebrddAAAARUlEQVR42o2PAQYAMAwD9+iEFvTpnZYZU7EQRa4kaxKAXD9y9yTZluCBALRJpATNrIG6rAdjjmBVeCuB6EyAYkNEiDE33yMJRSaycR8/AAAAAElFTkSuQmCC);background-repeat:no-repeat}
.webix_accordionitem.vertical.collapsed>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAHCAYAAADebrddAAAASElEQVR42n2PMQoAMAgD++gEdPLpFkshS9oDEcxlcA1V1euNcpI9k5m2MPfrqABCBS8KBptAR8QJZks0kGjgjMQn/oc/AKy4Acp9RSZl+HEYAAAAAElFTkSuQmCC);background-repeat:no-repeat}
.webix_accordionitem.horizontal>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAALCAYAAACzkJeoAAAASUlEQVR42p2QMQoAQQgD79EZ0Mqn5xCu2cPdYgUbJ0rMMxXgLeiuKq8gsJAz8weQ0QiwJEfECnr4wdlEnwJ68+yyhReC+c85oRecj0Um+pmo9wAAAABJRU5ErkJggg==);background-repeat:no-repeat}
.webix_accordionitem.horizontal:last-child>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAALCAYAAACzkJeoAAAARElEQVR42qXPsQoAIAhF0T76PdDJT7cUmtQhcmk4N8QVQ9JXN2bmgWOgqp+BiCRG2AYAMoi3/LxYdyagAnhA6G93TrABZaJFJjrFY8IAAAAASUVORK5CYII=);background-repeat:no-repeat}
.webix_accordionitem.horizontal.collapsed:last-child>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAHCAYAAADebrddAAAASElEQVR42n2PMQoAMAgD++gEdPLpFkshS9oDEcxlcA1V1euNcpI9k5m2MPfrqABCBS8KBptAR8QJZks0kGjgjMQn/oc/AKy4Acp9RSZl+HEYAAAAAElFTkSuQmCC);background-repeat:no-repeat}
.webix_accordionitem.horizontal.collapsed>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAHCAYAAADebrddAAAARUlEQVR42o2PAQYAMAwD9+iEFvTpnZYZU7EQRa4kaxKAXD9y9yTZluCBALRJpATNrIG6rAdjjmBVeCuB6EyAYkNEiDE33yMJRSaycR8/AAAAAElFTkSuQmCC);background-repeat:no-repeat}
.webix_progress_bottom,.webix_progress_top{background:#f7f7f7;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f2f2f2),color-stop(50%,#f7f7f7),color-stop(100%,#f2f2f2));background-image:-webkit-linear-gradient(top,#f2f2f2 0,#f7f7f7 50%,#f2f2f2 100%);background-image:-moz-linear-gradient(top,#f2f2f2 0,#f7f7f7 60%,#f2f2f2 100%);background-image:-ms-linear-gradient(top,#f2f2f2 0,#f7f7f7 60%,#f2f2f2 100%);background-image:-o-linear-gradient(top,#f2f2f2 0,#f7f7f7 60%,#f2f2f2 100%);background-position:0 1px;background-repeat:repeat-x}
.webix_daterange .webix_range_timepicker .webix_cal_time{padding:5px 15px}
.webix_daterange .webix_range_footer .webix_template{padding:10px 0}
.webix_cal_range{background-color:#fff4dc}
.webix_item_tab .webix_icon{width:30px;font-size:19px}
.webix_tab_more_icon{border-bottom:1px solid #a4bed4}
.webix_skin_mark{height:110px}
.webix_sparklines{padding:1px 0;box-sizing:border-box}
.webix_sparklines svg{box-sizing:border-box}
.webix_sparklines_line{stroke:#3498db;stroke-width:1;fill:transparent;stroke-linecap:round}
.webix_sparklines_item{fill:#3498db;stroke-width:1}
.webix_sparklines_bar{fill:#6dbcf0}
.webix_sparklines_bar_negative{fill:#d86c79}
.webix_sparklines_area{fill:#e5eef4}
.webix_sparklines_origin{stroke:#888;stroke-width:1}
.webix_sparklines_event_area{fill:transparent}
.webix_sparklines_area_chart .webix_sparklines_event_area:hover,.webix_sparklines_line_chart .webix_sparklines_event_area:hover,.webix_sparklines_splinearea_chart .webix_sparklines_event_area:hover{fill:rgba(220,220,220,.4);stroke:rgba(255,255,255,.6)}
.webix_sparklines_bar_chart .webix_sparklines_event_area:hover,.webix_sparklines_pie_chart .webix_sparklines_event_area:hover{fill:rgba(255,255,255,.3)}
.webix_ui_print{display:none}
body.webix_print{margin-top:0}
@media print{body.webix_print{overflow:visible!important;background-color:none!important}
body.webix_print>*{display:none}
body.webix_print *{visibility:hidden}
.webix_ui_print{display:block!important;margin:0!important;visibility:visible!important}
.webix_ui_print *{visibility:visible!important}
.webix_print_noscroll{height:auto!important;width:auto!important}
.webix_print_noscroll,.webix_print_noscroll .webix_scroll_cont{overflow:visible!important}
.webix_print_pagebreak{page-break-after:always}
.webix_print_footer,.webix_print_header{display:block!important;margin:20px 0;text-align:center;height:auto!important;visibility:visible!important}
.webix_print_footer *,.webix_print_header *{visibility:visible!important}
.webix_table_print{display:table;visibility:visible!important;table-layout:fixed;width:100%;border:1px solid #ebebeb;margin-bottom:20px;position:initial}
.webix_table_print td{overflow:hidden}
.webix_table_print tr{page-break-inside:avoid}
.webix_table_print .webix_cell{display:table-cell!important;border-right:1px solid #ebebeb;border-bottom:1px solid #ebebeb;padding:0 10px;white-space:nowrap}
.webix_table_print .webix_header_cell{display:table-cell;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:#e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff));background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-position:0 1px;background-repeat:repeat-x;border-right:1px solid #a4bed4;border-bottom:1px solid #a4bed4}
.webix_table_print .webix_footer_cell{display:table-cell;background:#fafafa;border-right:1px solid #ebebeb;border-top:1px solid #ebebeb;border-bottom:1px solid #ebebeb}
.webix_table_print .webix_cell.webix_dtable_span{position:initial!important;white-space:normal}
.webix_table_print .webix_rotate{transform-origin:center 40%!important;-webkit-transform-origin:center 40%!important}
.webix_table_print.borderless,.webix_table_print.borderless .webix_cell{border:none}
.webix_table_print.webix_view.webix_list-x{white-space:normal}
.webix_table_print.webix_view.webix_list-x .webix_list_item{display:table-cell}
}
/*!
* Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
@font-face{font-family:FontAwesome;src:url(../fonts/fontawesome-webfont.eot?v=4.7.0);src:url(../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0) format('embedded-opentype'),url(../fonts/fontawesome-webfont.woff2?v=4.7.0) format('woff2'),url(../fonts/fontawesome-webfont.woff?v=4.7.0) format('woff'),url(../fonts/fontawesome-webfont.ttf?v=4.7.0) format('truetype'),url(../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular) format('svg');font-weight:400;font-style:normal}
.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0,0)}
.fa,.webix_icon,.webix_icon_btn,.webix_input_icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0,0)}
.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}
.fa-2x{font-size:2em}
.fa-3x{font-size:3em}
@ -781,7 +597,7 @@ body.webix_print *{visibility:hidden}
.fa-ul>li{position:relative}
.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}
.fa-li.fa-lg{left:-1.85714286em}
.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}
.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}
.fa-pull-left{float:left}
.fa-pull-right{float:right}
.fa.fa-pull-left{margin-right:.3em}
@ -793,11 +609,9 @@ body.webix_print *{visibility:hidden}
.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}
.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}
@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}
100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}
}
100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}
@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}
100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}
}
100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}
.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}
.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}
.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}
@ -1486,6 +1300,140 @@ body.webix_print *{visibility:hidden}
.fa-meetup:before{content:"\f2e0"}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}
.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
.webix_icon,.webix_input_icon{font-size:17px;display:inline-block;width:20px}
.webix_input_icon{float:right;font-size:19px;color:#888}
.webix_icon_button{background-color:transparent;border:none;padding:0;cursor:pointer;position:relative}
.webix_icon_button .webix_icon{font-size:20px;height:23px;width:21px;display:inline-block;text-align:center;color:#606060}
.webix_icon_btn{font-size:17px;display:inline-block;text-align:center;width:24px;color:#606060}
.webix_el_iconBbutton .webix_icon_btn{text-align:center;width:100%;padding:5px 0}
.webix_el_icon{cursor:pointer}
.webix_badge{background-color:#ff8839;color:#fff;border-radius:50%;font-size:12px;height:22px;min-width:22px;box-sizing:border-box;padding:0 4px;text-align:center;line-height:21px;position:absolute;right:0;top:0}
.webix_list_item .webix_badge{position:static;float:right;margin:4px -5px 0 10px}
.webixtype_base .webix_badge{top:50%;margin-top:-11px;margin-right:8px}
.webix_treemap{background-color:#f5f5f5}
.webix_treemap .webix_scroll_cont{position:relative;height:100%;width:100%}
.webix_treemap_item{position:absolute;color:#444;overflow:hidden;text-align:left;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding:3px 5px;border-width:0 1px 1px 0;border-style:solid;border-color:rgba(0,0,0,.2);background:0 0}
.webix_treemap_level_top{z-index:1;border-color:rgba(0,0,0,.4)}
.webix_treemap_item_bottom{border-bottom-width:0}
.webix_treemap_item_right{border-right-width:0}
.webix_treemap_item:hover{box-shadow:inset 0 0 5px #666}
.webix_treemap_item.webix_selected{border-color:#ffdb8f;box-shadow:inset 0 0 1px 2px #ffdb8f}
.webix_treemap_header{width:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 10px;border-bottom:1px solid #a4bed4}
.webix_treemap_header_item{cursor:pointer}
.webix_treemap_header_item:last-child{cursor:default}
.webix_treemap_reset{float:right;width:25px;text-align:center}
.webix_treemap_reset:before{content:"\f00d";color:#1e2022;font-family:FontAwesome;font-size:16px;display:block;cursor:pointer}
.webix_treemap_path_icon{width:20px;text-align:center}
.webix_barcode{position:relative}
.webix_barcode .webix_canvas_text{font-size:13px;padding:0 2px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
.portlet_drag{position:absolute;z-index:1;top:5px;right:5px;width:18px;opacity:.5;cursor:pointer}
.portlet_in_drag{opacity:.4}
.portlet_marker,.portlet_markerbottom,.portlet_markerleft,.portlet_markerright,.portlet_markertop{opacity:.5;background:#9169BE;position:absolute;transition:top .5s,left .5s,width .5s,height .5s;top:0;left:0;width:100%;height:100%}
.portlet_markertop{height:50%}
.portlet_markerbottom{height:50%;top:50%}
.portlet_markerleft{width:50%}
.portlet_markerright{width:50%;left:50%}
.webix_popup.webix_sidemenu.webix_sidemenu_left{border-width:0 1px 0 0;box-shadow:2px 0 2px rgba(0,0,0,.05)}
.webix_sidemenu_left.webix_animate{transition:left .6s}
.webix_popup.webix_sidemenu.webix_sidemenu_right{left:auto;border-width:0 0 0 1px;box-shadow:-2px 0 2px rgba(0,0,0,.05)}
.webix_sidemenu_right.webix_animate{transition:right .6s}
.webix_popup.webix_sidemenu.webix_sidemenu_top{border-width:0 0 1px;box-shadow:0 2px 2px rgba(0,0,0,.05)}
.webix_sidemenu_top .webix_win_body{position:relative}
.webix_sidemenu_top .webix_win_body>.webix_view{position:absolute;top:auto;left:0;bottom:0}
.webix_sidemenu_top.webix_animate,.webix_sidemenu_top.webix_animate .webix_win_body{transition:height .6s}
.webix_popup.webix_sidemenu.webix_sidemenu_bottom{border-width:1px 0 0;top:auto;box-shadow:0 -2px 2px rgba(0,0,0,.05)}
.webix_sidemenu_bottom.webix_animate,.webix_sidemenu_bottom.webix_animate .webix_win_body{transition:height .6s}
.webix_view.webix_pdf{background-color:#404040;overflow:auto;-webkit-overflow-scrolling:touch}
.webix_pdf .canvas_wrapper{margin:0 auto;box-shadow:5px 5px 15px #1c1c1c;width:100%;height:100%}
.webix_toolbar.pdf_bar{background-color:#474747}
.webix_toolbar.pdf_bar .webix_icon_btn{font-size:15px}
.webix_toolbar.pdf_bar .webix_img_btn:focus,.webix_toolbar.pdf_bar .webix_img_btn:hover{background-color:#404040}
.webix_toolbar.pdf_bar .webix_template{background-color:#3498db;color:#fff;line-height:2em}
.webix_toolbar.pdf_bar .webix_el_box input,.webix_toolbar.pdf_bar .webix_inp_static{background-color:#5c5c5c;color:#fff;border-color:#404040}
.webix_toolbar.pdf_bar .webix_el_box input:focus,.webix_toolbar.pdf_bar .webix_inp_static:focus{border-color:#333}
.webix_toolbar.pdf_bar .webix_el_box .webix_input_icon{color:#fff}
.webix_view.webix_popup.pdf_opt_list{box-shadow:5px 5px 15px #1c1c1c;border:none}
.pdf_opt_list .webix_list{background-color:#404040;color:#fff}
.pdf_opt_list .webix_list .webix_list_item{border-color:#474747;line-height:1.5em}
.pdf_opt_list .webix_list .webix_list_item.webix_selected,.pdf_opt_list .webix_list .webix_list_item:hover{color:#404040;background-color:#fff}
.webix_dbllist .webix_list{background:#ededed}
.webix_dbllist .webix_list_item{background:#fff;margin:3px 4px 0;box-sizing:border-box}
.webix_dbllist .webix_list_item.webix_selected{color:#FFF;background:#27ae60}
.webix_dbllist button{width:45%;height:30px;margin-top:5px;background:#fff;border-radius:6px;border:1px solid #a4bed4;cursor:pointer;font-size:13px;font-family:Helvetica,Verdana}
.webix_dbllist button:active{box-shadow:inset 1px 1px 0 silver}
.webix_dbllist button .webix_icon{color:#888;font-size:26px}
.webix_dbllist button:first-child{margin-top:32px;margin-right:5%}
.webix_dbllist button:last-child{margin-left:5%}
.webix_dbllist .bottom_label{font-size:10px;text-transform:uppercase;background:#ededed;padding-left:13px}
.webix_invalid .webix_list{background:#f8e2e2}
.webix_toolbar .webix_el_label,.webix_toolbar .webix_inp_label{color:#1e2022}
.webix_ss_sort_asc{position:absolute;float:right;right:3px;top:8px;width:7px;height:13px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)}
.webix_ss_sort_desc{position:absolute;right:3px;top:8px;width:7px;height:13px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)}
.webix_group_back .webix_arrow_icon{color:#1e2022}
.webix_menu-x .webix_list_item:active,.webix_menu-x .webix_list_item:focus,.webix_menu-x .webix_list_item:hover{background-color:rgba(255,255,255,.4)!important}
.webix_menu .webix_list_item:active,.webix_menu .webix_list_item:hover{background-color:rgba(0,0,0,.05)!important}
.webix_list_item .webix_submenu_icon{width:0;height:0;border-width:4px;border-style:solid}
.webix_menu-x .webix_list_item .webix_submenu_icon{border-color:#375975 transparent transparent}
.webix_menu .webix_list_item .webix_submenu_icon{border-color:transparent transparent transparent #375975}
.webix_popup_title{color:#1e2022!important;background:#e3e3e3!important;font-size:15px!important;font-weight:700;border-bottom:1px solid #dedede!important;text-shadow:none!important}
.webix_modal_box{background:#ebebeb!important;border:1px solid #a4bed4!important}
.confirmButton{color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%)}
.confirmButtonActive{background:#fff}
.webix_item_tab{font-size:13px}
.buttonStyle{font-size:13px;font-family:Helvetica,Verdana}
.activeShadow{box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}
.webix_img_btn_abs:active+input,.webix_inp_counter_next:active,.webix_inp_counter_prev:active,.webixtype_base:active{background:none #f5f5f5;box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}
.webix_icon_button:active,.webix_img_btn:active,.webix_img_btn_top:active{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);background-color:rgba(0,0,0,.05)}
.webix_pressed .webix_img_btn_abs+input,.webix_pressed .webixtype_base{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);background:#f5f5f5!important}
.webix_slider_vertical .webix_slider_box{margin-top:-8px}
.webix_accordionitem_label .webix_icon{font-size:19px;width:25px}
.webix_accordionitem.vertical>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAHCAYAAADebrddAAAARUlEQVR42o2PAQYAMAwD9+iEFvTpnZYZU7EQRa4kaxKAXD9y9yTZluCBALRJpATNrIG6rAdjjmBVeCuB6EyAYkNEiDE33yMJRSaycR8/AAAAAElFTkSuQmCC);background-repeat:no-repeat}
.webix_accordionitem.vertical.collapsed>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAHCAYAAADebrddAAAASElEQVR42n2PMQoAMAgD++gEdPLpFkshS9oDEcxlcA1V1euNcpI9k5m2MPfrqABCBS8KBptAR8QJZks0kGjgjMQn/oc/AKy4Acp9RSZl+HEYAAAAAElFTkSuQmCC);background-repeat:no-repeat}
.webix_accordionitem.horizontal>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAALCAYAAACzkJeoAAAASUlEQVR42p2QMQoAQQgD79EZ0Mqn5xCu2cPdYgUbJ0rMMxXgLeiuKq8gsJAz8weQ0QiwJEfECnr4wdlEnwJ68+yyhReC+c85oRecj0Um+pmo9wAAAABJRU5ErkJggg==);background-repeat:no-repeat}
.webix_accordionitem.horizontal:last-child>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAALCAYAAACzkJeoAAAARElEQVR42qXPsQoAIAhF0T76PdDJT7cUmtQhcmk4N8QVQ9JXN2bmgWOgqp+BiCRG2AYAMoi3/LxYdyagAnhA6G93TrABZaJFJjrFY8IAAAAASUVORK5CYII=);background-repeat:no-repeat}
.webix_accordionitem.horizontal.collapsed:last-child>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAHCAYAAADebrddAAAASElEQVR42n2PMQoAMAgD++gEdPLpFkshS9oDEcxlcA1V1euNcpI9k5m2MPfrqABCBS8KBptAR8QJZks0kGjgjMQn/oc/AKy4Acp9RSZl+HEYAAAAAElFTkSuQmCC);background-repeat:no-repeat}
.webix_accordionitem.horizontal.collapsed>.webix_accordionitem_header .webix_accordionitem_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAHCAYAAADebrddAAAARUlEQVR42o2PAQYAMAwD9+iEFvTpnZYZU7EQRa4kaxKAXD9y9yTZluCBALRJpATNrIG6rAdjjmBVeCuB6EyAYkNEiDE33yMJRSaycR8/AAAAAElFTkSuQmCC);background-repeat:no-repeat}
.webix_progress_bottom,.webix_progress_top{background:0 1px repeat-x #f7f7f7;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f2f2f2),color-stop(50%,#f7f7f7),color-stop(100%,#f2f2f2)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#f2f2f2 0,#f7f7f7 50%,#f2f2f2 100%);background-image:-moz-linear-gradient(top,#f2f2f2 0,#f7f7f7 60%,#f2f2f2 100%);background-image:-ms-linear-gradient(top,#f2f2f2 0,#f7f7f7 60%,#f2f2f2 100%);background-image:-o-linear-gradient(top,#f2f2f2 0,#f7f7f7 60%,#f2f2f2 100%)}
.webix_daterange .webix_range_timepicker .webix_cal_time{padding:5px 15px}
.webix_daterange .webix_range_footer .webix_template{padding:10px 0}
.webix_cal_range{background-color:#fff4dc}
.webix_item_tab .webix_icon{width:30px;font-size:19px}
.webix_tab_more_icon{border-bottom:1px solid #a4bed4}
.webix_skin_mark{height:110px}
.webix_sparklines{padding:1px 0;box-sizing:border-box}
.webix_sparklines svg{box-sizing:border-box}
.webix_sparklines_line{stroke:#3498db;stroke-width:1;fill:transparent;stroke-linecap:round}
.webix_sparklines_item{fill:#3498db;stroke-width:1}
.webix_sparklines_bar{fill:#6dbcf0}
.webix_sparklines_bar_negative{fill:#d86c79}
.webix_sparklines_area{fill:#e5eef4}
.webix_sparklines_origin{stroke:#888;stroke-width:1}
.webix_sparklines_event_area{fill:transparent}
.webix_sparklines_area_chart .webix_sparklines_event_area:hover,.webix_sparklines_line_chart .webix_sparklines_event_area:hover,.webix_sparklines_splinearea_chart .webix_sparklines_event_area:hover{fill:rgba(220,220,220,.4);stroke:rgba(255,255,255,.6)}
.webix_sparklines_bar_chart .webix_sparklines_event_area:hover,.webix_sparklines_pie_chart .webix_sparklines_event_area:hover{fill:rgba(255,255,255,.3)}
.webix_ui_print{display:none}
body.webix_print{margin-top:0}
@media print{body.webix_print{overflow:visible!important;background-color:none!important}
body.webix_print>*{display:none}
body.webix_print *{visibility:hidden}
.webix_ui_print{display:block!important;margin:0!important;visibility:visible!important}
.webix_ui_print *{visibility:visible!important}
.webix_print_noscroll{height:auto!important;width:auto!important}
.webix_print_noscroll,.webix_print_noscroll .webix_scroll_cont{overflow:visible!important}
.webix_print_pagebreak{page-break-after:always}
.webix_print_footer,.webix_print_header{display:block!important;margin:20px 0;text-align:center;height:auto!important;visibility:visible!important}
.webix_print_footer *,.webix_print_header *{visibility:visible!important}
.webix_table_print{display:table;visibility:visible!important;table-layout:fixed;width:100%;border:1px solid #ebebeb;margin-bottom:20px;position:initial}
.webix_table_print td{overflow:hidden}
.webix_table_print tr{page-break-inside:avoid}
.webix_table_print .webix_cell{display:table-cell!important;border-right:1px solid #ebebeb;border-bottom:1px solid #ebebeb;padding:0 10px;white-space:nowrap}
.webix_table_print .webix_header_cell{display:table-cell;color:#1e2022!important;box-shadow:0 1px 1px #fff inset;background:0 1px repeat-x #e5f1ff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#eaf3ff),color-stop(50%,#e5f1ff),color-stop(100%,#d6e8ff)) 0 1px repeat-x;background-image:-webkit-linear-gradient(top,#eaf3ff 0,#e5f1ff 50%,#d6e8ff 100%);background-image:-moz-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-ms-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);background-image:-o-linear-gradient(top,#eaf3ff 0,#e5f1ff 60%,#d6e8ff 100%);border-right:1px solid #a4bed4;border-bottom:1px solid #a4bed4}
.webix_table_print .webix_footer_cell{display:table-cell;background:#fafafa;border-right:1px solid #ebebeb;border-top:1px solid #ebebeb;border-bottom:1px solid #ebebeb}
.webix_table_print .webix_cell.webix_dtable_span{position:initial!important;white-space:normal}
.webix_table_print .webix_rotate{transform-origin:center 40%!important;-webkit-transform-origin:center 40%!important}
.webix_table_print.borderless,.webix_table_print.borderless .webix_cell{border:none}
.webix_table_print.webix_view.webix_list-x{white-space:normal}
.webix_table_print.webix_view.webix_list-x .webix_list_item{display:table-cell}}
.webix_strong{font-weight:700;white-space:nowrap}
.webix_light{color:#666d73}
.webix_debug div{border:1px solid orange;background-color:#FF9;color:#000}

View File

@ -24,43 +24,12 @@
font-size: 125%;
color: DarkRed;
}
.right_footer2 {
text-align: right;
font-weight: bold;
font-size: 150%;
color: DarkRed;
}
.right_footer3 {
text-align: right;
font-weight: bold;
font-size: 120%;
color: DarkBlue;
}
.footer3 {
font-weight: bold;
font-size: 120%;
color: DarkBlue;
}
.lbl_partner {
font-weight: bold;
font-size: 125%;
}
.link_default {
font-weight: bold;
color: #610B0B;
text-decoration: none;
}
.link_default:hover {text-decoration:underline;}
.link_forum {
font-weight: bold;
color: #610B0B;
text-decoration: none;
}
.link_forum:hover {text-decoration:underline;}
.delete {
text-align: center;
font-weight: bold;
@ -68,10 +37,6 @@
color: red;
}
.cancel {
color: red;
}
.cmd_close_partner div button {
background-color: red !important;
@ -91,28 +56,6 @@
.webix_success div {
background-color: #00a65a !important;
font-weight: bold;
font-size: 1.5vw;
font-size: 1vw;
color: white;
}
.webix_error div {
background-color: #DF0101 !important;
font-weight: bold;
font-size: 1.5vw;
color: white;
}
.table_sg .webix_hcell{
background: #A64C4C;
color: white;
font-weight: bold;
}
.table_sg .webix_column{
font-style: italic;
background: #FFF8DC;
}
.table_sg .webix_column > div{
border-color: #ddd;
}

View File

@ -1,519 +0,0 @@
@page{
size: Letter;
margin: 0.5cm;
background-color: white !important;
}
@media print {
thead {display: table-header-group;}
body {
background-color: #fff;
}
}
.emisor-cintilla{
background-color: #975759;
color: #fff;
}
.fiscales-emisor{
color: #7d1916;
}
.fiscales-emisor .telefono, .fiscales-emisor .correo, .fiscales-emisor .web{
color: #333;
}
header .titulo-vertical{
background-color: #dbc5c6;
color: #000;
}
.receptor .nombre{
color: #000;
}
.receptor .rfc{
color: #000;
}
.receptor .direccion{
color: #000;
}
.receptor .estado{
color: #000;
}
.cfdi .folio span{
color: #ed483d;
}
.cfdi .tipo span{
color: #ed483d;
}
.cfdi .folio-fiscal span{
color: #ed483d;
}
.conceptos th{
background-color: #975759;
}
.conceptos td.clave, .conceptos td.unidad, .conceptos td.valor-unitario{
background-color: #dbc5c6;
}
.conceptos td.descripcion, .conceptos td.cantidad, .conceptos td.importe{
background-color: #f0e7e7;
}
table.subtotal th{
background-color: #dbc5c6;
}
table.subtotal td{
background-color: #f0e7e7;
}
.sello .cadenas-sello .cadena div {
background-color: #dbc5c6;
color: #7d1916;
}
.rfc-pac {
background-color: #dbc5c6;
color: #7d1916;
}
.cancelada{
color: #ed3833;
}
@font-face {
font-family: 'Avenir Next';
src: url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot');
src: url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot?#iefix') format('embedded-opentype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff2') format('woff2'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff') format('woff'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.ttf') format('truetype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.svg#AvenirNextLTPro-Regular') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Avenir Next';
src: url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot');
src: url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot?#iefix') format('embedded-opentype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff2') format('woff2'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff') format('woff'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.ttf') format('truetype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.svg#AvenirNextLTPro-Bold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Avenir Next';
src: url('/static/fonts/avenir_next/AvenirNextLTPro-It.eot');
src: url('/static/fonts/avenir_next/AvenirNextLTPro-It.eot?#iefix') format('embedded-opentype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-It.woff2') format('woff2'),
url('/static/fonts/avenir_next/AvenirNextLTPro-It.woff') format('woff'),
url('/static/fonts/avenir_next/AvenirNextLTPro-It.ttf') format('truetype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-It.svg#AvenirNextLTPro-It') format('svg');
font-weight: normal;
font-style: italic;
}
body {
/*
font-family: 'Avenir Next';
*/
}
#plantilla {
border: 1px solid #fff;
border-color: rgb(204,204,204);
background: #fff;
display: block;
margin: 0 auto;
width: 19.5cm;
height: 25.9cm;
position: relative;
}
.cancelada{
-ms-transform: rotate(320deg);
-webkit-transform: rotate(320deg);
transform: rotate(320deg);
color: #ed3833;
font-size: 100px;
font-weight: bold;
left: 18%;
position: absolute;
text-align: center;
top: 30%;
z-index: 1000;
}
.cancelada div{
font-size: 20px;
}
.emisor-cintilla{
background-color: #975759;
color: #fff;
font-size: 7pt;
padding: 5px;
text-align: center;
}
.datos-emisor .logo{
margin-left: 20px;
max-width: 180px;
}
.datos-emisor .fiscales-emisor{
float: right;
}
.fiscales-emisor{
color: #7d1916;
font-weight: bold;
margin-top: 20px;
text-align: right;
}
.fiscales-emisor .nombre{
font-size: 18px;
padding-right: 30px;
}
.fiscales-emisor .rfc{
font-size: 16px;
padding-right: 30px;
}
.fiscales-emisor .regimen{
font-size: 12px;
padding-right: 30px;
}
.fiscales-emisor .telefono, .fiscales-emisor .correo, .fiscales-emisor .web{
color: #333;
font-size: 14px;
line-height: 15px;
padding-right: 10px;
}
.fiscales-emisor img{
margin-left: 10px;
width: 10px;
}
.clear{
clear: both;
}
header .titulo-vertical{
background-color: #dbc5c6;
-ms-transform: rotate(270deg);
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
font-size: 15px;
font-weight: bold;
left: -31px;
line-height: 35px;
position: absolute;
text-align: center;
top: 35px;
width: 105px;
}
header .receptor{
box-sizing: border-box;
float: left;
font-size: 10px;
height: 106px;
margin-top: 10px;
position: relative;
padding-bottom: 5px;
padding-left: 45px;
padding-top: 5px;
width: 60%;
}
.receptor .nombre{
color: #000;
font-size: 17px;
font-weight: bold;
}
.receptor .rfc{
color: #000;
font-size: 15px;
font-weight: bold;
}
.receptor .direccion,
.receptor .estado{
color: #000;
font-size: 10px;
}
.receptor img{
width: 10px;
}
.receptor .correo,
.receptor .telefono{
float: right;
width: 40%;
}
.receptor .uso-cfdi{
color: #000;
font-size: 12px;
font-weight: bold;
float: left;
width: 60%;
}
header .cfdi{
box-sizing: border-box;
float: right;
font-size: 10px;
height: 100px;
margin-top: 10px;
position: relative;
padding-bottom: 5px;
padding-left: 45px;
padding-top: 5px;
width: 35%;
}
.cfdi .folio, .cfdi .tipo{
color: #000;
float: left;
font-size: 11px;
font-weight: bold;
line-height: 15px;
width: 50%;
}
.cfdi .folio span, .cfdi .tipo span{
color: #ed483d;
}
.cfdi .folio span{
font-size: 10px;
}
.cfdi .tipo span{
font-size: 10px;
}
.cfdi .folio-fiscal{
color: #000;
font-size: 9px;
font-weight: bold;
line-height: 15px;
text-align: center;
}
.cfdi .folio-fiscal span{
color: #ed483d;
}
.cfdi .fecha-emision, .cfdi .fecha-certificacion, .cfdi .lugar-expedicion{
color: #333;
font-size: 10px;
font-weight: bold;
line-height: 15px;
}
.conceptos{
margin-bottom: 10px;
margin-top: 20px;
width: 100%;
text-align: center;
}
.conceptos th{
background-color: #975759;
color: #fff;
font-size: 11px;
line-height: 15px;
}
.conceptos .clave{
width: 10%;
}
.conceptos .descripcion{
width: 45%;
}
.conceptos .descripcion div{
text-align: justify;
}
.conceptos .unidad{
width: 8%;
}
.conceptos .cantidad{
width: 9%;
}
.conceptos .valor-unitario{
width: 13%;
}
.conceptos .importe{
width: 15%;
}
.conceptos td{
background-color: #975759;
color: #000;
font-size: 11px;
line-height: 15px;
}
.conceptos td.clave, .conceptos td.unidad, .conceptos td.valor-unitario{
background-color: #dbc5c6;
}
.conceptos td.descripcion, .conceptos td.cantidad, .conceptos td.importe{
background-color: #f0e7e7;
}
.conceptos td.valor-unitario, .conceptos td.importe{
text-align: right;
}
.total-letras{
float: left;
font-size: 12px;
line-height: 15px;
margin: 5px 0;
width: 63%;
}
table.subtotal{
float: right;
font-size: 12px;
line-height: 15px;
text-align: right;
width: 37%;
font-weight: bold;
}
table.subtotal th{
background-color: #f0e7e7;
width: 60%;
padding: 3px 3px 3px 3px;
}
table.subtotal td{
background-color: #dbc5c6;
width: 40%;
padding: 3px 3px 3px 3px;
}
.condiciones-pago{
font-size: 11px;
line-height: 14px;
}
.notas{
float: left;
font-size: 11px;
margin: 20px 0;
width: 70%;
}
.formapago-metodopago, .moneda-tipocambio, .tiporelacion, .relacionados{
float: left;
font-size: 11px;
line-height: 12px;
width: 70%;
}
.factura-info{
float: left;
font-size: 11px;
line-height: 13px;
width: 70%;
}
.tipocomite, .tipoproceso, .idcontabilidad{
float: right;
font-size: 10px;
line-height: 12px;
width: 20%;
}
.sello{
margin: 10px 0;
}
.sello .cbb{
border: 1px solid #000;
height: auto;
margin-top: 10px;
margin-right: 1px;
width: 18%;
}
.sello .cadenas-sello{
color: #000;
float: right;
font-size: 8px;
font-weight: bold;
line-height: 15px;
width: 79%;
margin-left: 5px;
margin-right: 5px;
word-break: break-all;
}
.sello .cadenas-sello .cadena{
margin: 5px 0;
}
.sello .cadenas-sello .cadena div{
font-weight: normal;
background-color: #dbc5c6;
color: #7d1916;
}
.cadena-original{
color: #000;
float: right;
font-size: 8px;
font-weight: bold;
line-height: 15px;
width: 99%;
margin: 5px;
word-break: break-all;
}
.cadena-original div{
font-weight: normal;
background-color: #dbc5c6;
color: #7d1916;
}
.rfc-pac{
background-color: #dbc5c6;
color: #7d1916;
font-size: 10px;
line-height: 15px;
text-align: center;
margin: 5px;
}

View File

@ -0,0 +1,95 @@
/* SideBar*/
.webix_sidebar{
background: #ECEFF1;
}
.webix_sidebar .webix_tree_item {
color: #454545;
height: 35px;
line-height: 35px;
}
.webix_sidebar .webix_scroll_cont > .webix_tree_leaves {
padding: 0;
}
.webix_sidebar .webix_tree_leaves .webix_tree_leaves {
margin-left: 0px;
}
.webix_sidebar_selected,
.webix_sidebar_expanded .webix_tree_item:hover{
background-color: rgba(0,0,0,0.02);
}
.webix_sidebar .webix_tree_item.webix_selected,
.webix_sidebar .webix_tree_item.webix_selected span{
background-color: #27ae60;
padding-right:0;
}
.webix_sidebar .webix_tree_branch_1 .webix_tree_item{
padding-left:40px;
}
.webix_sidebar .webix_tree_branch_1>.webix_tree_item{
height: 40px;
line-height: 40px;
padding-left:0;
}
.webix_sidebar .webix_tree_branch_1{
border-bottom:1px solid #e5e5e5;
}
.webix_sidebar .webix_tree_item.webix_selected span,
.webix_sidebar .webix_tree_item span{
margin:0;
padding:0px;
}
.webix_sidebar_icon{
width: 40px;
text-align: center;
}
.webix_sidebar_dir_icon{
float: right;
line-height: inherit;
}
/*SubMenu (Popup) */
.webix_sidebar_popup{
border:none !important;
box-shadow: 2px 3px 3px #ddd;
}
.webix_sidebar_popup, .webix_sidebar_popup .webix_list_item{
border-radius:0;
}
.webix_sidebar_popup_right{
box-shadow: -1px 3px 3px #ddd;
}
/*SubMenu: title*/
.webix_sidebar_popup_title{
background: #ECEFF1;
}
.webix_sidebar_popup_title.webix_selected{
border-left-color: #27ae60;
background: #27ae60;
}
.webix_sidebar_popup_title .webix_template{
line-height: 40px;
padding: 0 10px;
border: 1px solid #E5E5E5;
border-left:none;
}
.webix_sidebar_selected.webix_sidebar_popup_title .webix_template{
background: rgba(0,0,0,0.03);
border-left: none;
}
.webix_sidebar_popup_list .webix_list_item{
border-left: 1px solid #E5E5E5;
border-right: 1px solid #E5E5E5;
}
/*SubMenu: list*/
.webix_sidebar_popup_list .webix_list_item:first-child{
border-top: 1px solid #E5E5E5;
}
.webix_sidebar_popup_list .webix_list_item:hover{
background: #f6f9fb;
}
.webix_sidebar_popup_list .webix_list_item.webix_selected:hover{
background: #27ae60;
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 188 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 169 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,57 @@
var gi = null
function configuracion_inicial(){
webix.ajax().get('/values/admin', function(text, data){
var values = data.json()
show('cmd_ir_al_admin', values)
})
webix.ajax().sync().get('/values/main', function(text, data){
var values = data.json()
$$('lbl_title_main').setValue(values.empresa)
var pos = 4
if(values.escuela){
var node = {
id: 'app_school',
icon: 'graduation-cap',
value: 'Escuela'}
$$('main_sidebar').add(node, pos)
pos += 1
}
if(values.nomina){
var node = {
id: 'app_nomina',
icon: 'users',
value: 'Nómina'}
$$('main_sidebar').add(node, pos)
pos += 1
}
if(values.punto_de_venta){
var node = {
id: 'app_tickets',
icon: 'money',
value: 'Punto de venta'}
$$('main_sidebar').add(node, pos)
}
$$('cmd_update_timbres').define('badge', values.timbres)
$$('cmd_update_timbres').refresh()
add_config({'key': 'decimales_precios', 'value': values.decimales_precios})
add_config({'key': 'used_cfdi_pays', 'value': values.pagos})
add_config({'key': 'multi_currency', 'value': values.multi_currency})
add_config({'key': 'pays_data_bank', 'value': values.pays_data_bank})
add_config({'key': 'show_filter_by_day', 'value': values.show_filter_by_day})
})
get_way_payment()
}
function cmd_ir_al_admin_click(){
window.location = '/admin'
}
var controllers = {
init: function(){
//~ Main
$$('menu_user').attachEvent('onMenuItemClick', menu_user_click);
configuracion_inicial()
var tb_invoice = $$('tv_invoice').getTabbar()
tb_invoice.attachEvent('onChange', tb_invoice_change)
$$('prefilter_year').attachEvent('onChange', prefilter_year_change)
$$('prefilter_month').attachEvent('onChange', prefilter_month_change)
$$('cmd_delete_preinvoice').attachEvent('onItemClick', cmd_delete_preinvoice_click)
$$('cmd_facturar_preinvoice').attachEvent('onItemClick', cmd_facturar_preinvoice_click)
$$('cmd_update_timbres').attachEvent('onItemClick', cmd_update_timbres_click)
$$('grid_preinvoices').attachEvent('onItemClick', grid_preinvoices_click)
partners_controllers.init()
products_controllers.init()
bancos_controllers.init()
invoices_controllers.init()
controllers_school.init()
nomina_controllers.init()
tickets_controllers.init()
//~ Partner
$$('cmd_new_partner').attachEvent('onItemClick', cmd_new_partner_click);
$$('cmd_new_contact').attachEvent('onItemClick', cmd_new_contact_click);
$$('cmd_edit_partner').attachEvent('onItemClick', cmd_edit_partner_click);
$$('cmd_delete_partner').attachEvent('onItemClick', cmd_delete_partner_click);
$$('cmd_save_partner').attachEvent('onItemClick', cmd_save_partner_click);
$$('cmd_cancel_partner').attachEvent('onItemClick', cmd_cancel_partner_click);
$$('cmd_cancel_contact').attachEvent('onItemClick', cmd_cancel_contact_click);
$$('codigo_postal').attachEvent('onKeyPress', postal_code_key_press);
$$('codigo_postal').attachEvent('onTimedKeyPress', postal_code_key_up);
$$('colonia').attachEvent('onFocus', colonia_on_focus)
$$("tipo_persona").attachEvent( "onChange", opt_tipo_change)
$$("es_cliente").attachEvent( "onChange", is_client_change)
$$("es_proveedor").attachEvent( "onChange", is_supplier_change)
$$("rfc").attachEvent( "onBlur", rfc_lost_focus)
$$('multi').attachEvent('onViewChange', multi_change)
//~ Products
$$("cmd_new_product").attachEvent("onItemClick", cmd_new_product_click)
$$("cmd_edit_product").attachEvent("onItemClick", cmd_edit_product_click)
$$("cmd_delete_product").attachEvent("onItemClick", cmd_delete_product_click)
$$("cmd_save_product").attachEvent("onItemClick", cmd_save_product_click)
$$("cmd_cancel_product").attachEvent("onItemClick", cmd_cancel_product_click)
$$("chk_automatica").attachEvent("onChange", chk_automatica_change)
$$("valor_unitario").attachEvent("onChange", valor_unitario_change)
//~ Invoices
$$('cmd_new_invoice').attachEvent("onItemClick", cmd_new_invoice_click)
$$('cmd_refacturar').attachEvent("onItemClick", cmd_refacturar_click)
$$('cmd_delete_invoice').attachEvent("onItemClick", cmd_delete_invoice_click)
$$('cmd_timbrar').attachEvent('onItemClick', cmd_timbrar_click)
$$('cmd_close_invoice').attachEvent('onItemClick', cmd_close_invoice_click)
$$('search_client_id').attachEvent('onKeyPress', search_client_id_key_press)
$$('grid_clients_found').attachEvent('onValueSuggest', grid_clients_found_click)
$$('search_product_id').attachEvent('onKeyPress', search_product_id_key_press)
$$('grid_products_found').attachEvent('onValueSuggest', grid_products_found_click)
$$('grid_details').attachEvent('onItemClick', grid_details_click)
$$('grid_details').attachEvent('onHeaderClick', grid_details_header_click)
$$('grid_details').attachEvent('onBeforeEditStart', grid_details_before_edit_start)
$$('grid_details').attachEvent('onBeforeEditStop', grid_details_before_edit_stop)
$$('cmd_invoice_timbrar').attachEvent('onItemClick', cmd_invoice_timbrar_click)
$$('cmd_invoice_cancelar').attachEvent('onItemClick', cmd_invoice_cancelar_click)
$$('grid_invoices').attachEvent('onItemClick', grid_invoices_click)
}
}
function get_uso_cfdi_to_table(){
webix.ajax().sync().get('/values/usocfdi', function(text, data){
function get_uso_cfdi_to_table(args){
webix.ajax().sync().get('/values/usocfdi', args, function(text, data){
var values = data.json()
table_usocfdi.clear()
table_usocfdi.insert(values)
@ -90,18 +60,52 @@ function get_uso_cfdi_to_table(){
function get_partners(){
webix.ajax().get('/partners', {}, {
webix.ajax().get("/partners", {}, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
webix.message({ type:"error", text: "Error al consultar"});
},
success: function(text, data, xhr) {
var values = data.json();
$$('grid_partners').clearAll();
if (values.data){
$$('grid_partners').parse(values.data, 'json');
$$("grid_partners").clearAll();
if (values.ok){
$$("grid_partners").parse(values.rows, 'json');
};
}
})
});
}
function get_products(){
var grid = $$('grid_products')
webix.ajax().get('/products', {}, {
error: function(text, data, xhr) {
webix.message({type: 'error', text: 'Error al consultar'})
},
success: function(text, data, xhr) {
var values = data.json();
grid.clearAll();
if (values.ok){
grid.parse(values.rows, 'json');
};
}
});
}
function get_invoices(){
var grid = $$('grid_invoices')
webix.ajax().get('/invoices', {}, {
error: function(text, data, xhr) {
webix.message({type: 'error', text: 'Error al consultar'})
},
success: function(text, data, xhr) {
var values = data.json();
grid.clearAll();
if (values.ok){
grid.parse(values.rows, 'json');
};
}
});
}
@ -113,41 +117,8 @@ function menu_user_click(id, e, node){
}
function current_dates(){
var fy = $$('filter_year')
var fm = $$('filter_month')
var fd = $$('filter_day')
var pfy = $$('prefilter_year')
var pfm = $$('prefilter_month')
var d = new Date()
fy.blockEvent()
fm.blockEvent()
fd.blockEvent()
pfy.blockEvent()
pfm.blockEvent()
fm.setValue(d.getMonth() + 1)
fd.setValue(d.getDate())
pfm.setValue(d.getMonth() + 1)
webix.ajax().sync().get('/values/filteryears', function(text, data){
var values = data.json()
fy.getList().parse(values[0])
pfy.getList().parse(values[1])
fy.setValue(d.getFullYear())
pfy.setValue(d.getFullYear())
})
fy.unblockEvent()
fm.unblockEvent()
fd.unblockEvent()
pfy.unblockEvent()
pfm.unblockEvent()
}
function multi_change(prevID, nextID){
//~ webix.message(nextID)
if(nextID == 'app_partners'){
active = $$('multi_partners').getActiveId()
if(active == 'partners_home'){
@ -164,44 +135,10 @@ function multi_change(prevID, nextID){
return
}
if(nextID == 'app_bancos'){
active = $$('multi_bancos').getActiveId()
if(active == 'banco_home'){
get_cuentas_banco()
}
return
}
if(nextID == 'app_school'){
active = $$('multi_school').getActiveId()
if(active == 'school_home'){
init_config_school()
}
return
}
if(nextID == 'app_nomina'){
active = $$('multi_nomina').getActiveId()
if(active == 'nomina_home'){
default_config_nomina()
}
return
}
if(nextID == 'app_tickets'){
active = $$('multi_tickets').getActiveId()
if(active == 'tickets_home'){
configuracion_inicial_ticket()
}
return
}
if(nextID == 'app_invoices'){
active = $$('multi_invoices').getActiveId()
if(active == 'invoices_home'){
current_dates()
get_invoices()
validar_timbrar()
}
gi = $$('grid_invoices')
return
@ -219,12 +156,3 @@ function get_taxes(){
$$("grid_product_taxes").parse(values, 'json')
})
}
function cmd_update_timbres_click(){
webix.ajax().get('/values/timbres', function(text, data){
var value = data.json()
$$('cmd_update_timbres').define('badge', value)
$$('cmd_update_timbres').refresh()
})
}

View File

@ -1,627 +0,0 @@
var query = []
var cfg_nomina = new Object()
var nomina_controllers = {
init: function(){
$$('cmd_nomina_import').attachEvent('onItemClick', cmd_nomina_import_click)
$$('cmd_empleados').attachEvent('onItemClick', cmd_empleados_click)
$$('cmd_close_empleados').attachEvent('onItemClick', cmd_close_empleados_click)
$$('cmd_delete_empleado').attachEvent('onItemClick', cmd_delete_empleado_click)
$$('cmd_import_empleados').attachEvent('onItemClick', cmd_import_empleados_click)
$$('cmd_nomina_without_stamp').attachEvent('onItemClick', cmd_nomina_without_stamp_click)
$$('cmd_nomina_delete').attachEvent('onItemClick', cmd_nomina_delete_click)
$$('cmd_nomina_timbrar').attachEvent('onItemClick', cmd_nomina_timbrar_click)
$$('cmd_nomina_sat').attachEvent('onItemClick', cmd_nomina_sat_click)
$$('cmd_nomina_log').attachEvent('onItemClick', cmd_nomina_log_click)
$$('cmd_nomina_download').attachEvent('onItemClick', cmd_nomina_download_click)
$$('cmd_nomina_cancel').attachEvent('onItemClick', cmd_nomina_cancel_click)
$$('grid_nomina').attachEvent('onItemClick', grid_nomina_click)
$$('filter_year_nomina').attachEvent('onChange', filter_year_nomina_change)
$$('filter_month_nomina').attachEvent('onChange', filter_month_nomina_change)
$$('filter_dates_nomina').attachEvent('onChange', filter_dates_nomina_change)
$$('grid_nomina').attachEvent('onSelectChange', grid_nomina_on_select_change)
webix.extend($$('grid_nomina'), webix.ProgressBar)
}
}
function default_config_nomina(){
current_dates_nomina()
get_nomina()
}
function current_dates_nomina(){
var fy = $$('filter_year_nomina')
var fm = $$('filter_month_nomina')
var d = new Date()
fy.blockEvent()
fm.blockEvent()
fm.setValue(d.getMonth() + 1)
webix.ajax().sync().get('/values/filteryearsnomina', function(text, data){
var values = data.json()
fy.getList().parse(values)
fy.setValue(d.getFullYear())
})
fy.unblockEvent()
fm.unblockEvent()
}
function get_nomina(filters){
var grid = $$('grid_nomina')
grid.showProgress({type: 'icon'})
webix.ajax().get('/nomina', filters, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if (values.ok){
grid.clearAll();
grid.parse(values.rows, 'json');
}else{
msg_error(values.msg)
}
}
})
}
function cmd_nomina_import_click(){
win_import_nomina.init()
$$('win_import_nomina').show()
}
function cmd_import_template_nomina_click(){
var form = $$('form_upload_nomina')
var values = form.getValues()
if(!$$('lst_upload_nomina').count()){
$$('win_import_nomina').close()
return
}
if($$('lst_upload_nomina').count() > 1){
msg = 'Selecciona solo un archivo'
msg_error(msg)
return
}
var template = $$('up_nomina').files.getItem($$('up_nomina').files.getFirstId())
if(template.type.toLowerCase() != 'ods'){
msg = 'Archivo inválido.\n\nSe requiere un archivo ODS'
msg_error(msg)
return
}
msg = '¿Estás seguro de importar este archivo?'
webix.confirm({
title: 'Importar Nómina',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
$$('up_nomina').send()
}
}
})
}
function up_nomina_upload_complete(response){
if(response.status != 'server'){
msg = 'Ocurrio un error al subir el archivo'
msg_error(msg)
return
}
msg = 'Archivo subido correctamente.\n\nComenzando importación.'
msg_ok(msg)
$$('win_import_nomina').close()
webix.ajax().get('/nomina', {opt: 'import'}, {
error: function(text, data, xhr) {
msg = 'Error al importar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if (values.ok){
msg_ok(values.msg)
get_nomina()
}else{
msg_error(values.msg)
}
}
})
}
function get_employees(){
webix.ajax().get('/employees', {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if (values.ok){
$$('grid_employees').clearAll();
$$('grid_employees').parse(values.rows, 'json');
}else{
msg_error(values.msg)
}
}
})
}
function cmd_empleados_click(){
get_employees()
$$('multi_nomina').setValue('nomina_empleados')
}
function cmd_close_empleados_click(){
$$('multi_nomina').setValue('nomina_home')
}
function cmd_import_empleados_click(){
win_import_employees.init()
$$('win_import_employees').show()
}
function cmd_import_employees_click(){
var form = $$('form_upload_employees')
var values = form.getValues()
if(!$$('lst_upload_employees').count()){
$$('win_import_employees').close()
return
}
if($$('lst_upload_employees').count() > 1){
msg = 'Selecciona solo un archivo'
msg_error(msg)
return
}
var template = $$('up_employees').files.getItem($$('up_employees').files.getFirstId())
if(template.type.toLowerCase() != 'ods'){
msg = 'Archivo inválido.\n\nSe requiere un archivo ODS'
msg_error(msg)
return
}
msg = '¿Estás seguro de importar este archivo?'
webix.confirm({
title: 'Importar Empleados',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
$$('up_employees').send()
}
}
})
}
function up_employees_upload_complete(response){
if(response.status != 'server'){
msg = 'Ocurrio un error al subir el archivo'
msg_error(msg)
return
}
msg = 'Archivo subido correctamente.\n\nComenzando importación.'
msg_ok(msg)
$$('win_import_employees').close()
webix.ajax().get('/employees', {opt: 'import'}, {
error: function(text, data, xhr) {
msg = 'Error al importar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if (values.ok){
msg_ok(values.msg)
get_employees()
}else{
msg_error(values.msg)
}
}
})
}
function delete_empleado(id){
webix.ajax().del('/employees', {id: id}, function(text, xml, xhr){
var msg = 'Empleado eliminado correctamente'
if(xhr.status == 200){
$$('grid_employees').remove(id);
msg_ok(msg)
}else{
msg = 'El Empleado tiene recibos timbrados'
msg_error(msg)
}
})
}
function cmd_delete_empleado_click(){
var row = $$('grid_employees').getSelectedItem()
if (row == undefined){
msg = 'Selecciona un Empleado'
msg_error(msg)
return
}
msg = '¿Estás seguro de eliminar al Empleado?<BR><BR>'
msg += row['nombre_completo'] + ' (' + row['rfc'] + ')'
msg += '<BR><BR>ESTA ACCIÓN NO SE PUEDE DESHACER<BR><BR>'
webix.confirm({
title: 'Eliminar Empleado',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if (result){
delete_empleado(row['id'])
}
}
})
}
function cmd_nomina_without_stamp_click(){
get_nomina()
}
function cmd_nomina_delete_click(){
var rows = $$('grid_nomina').getSelectedItem()
if (rows == undefined){
msg = 'Selecciona al menos un registro'
msg_error(msg)
return
}
var ids = []
if(Array.isArray(rows)){
for(var i in rows){
ids.push(rows[i].id)
}
}else{
ids.push(rows.id)
}
msg = '¿Estás seguro de eliminar los recibos seleccionado?<BR><BR>'
msg += 'ESTA ACCIÓN NO SE PUEDE DESHACER<BR><BR>'
msg += 'Solo se eliminan recibos no timbrados'
webix.confirm({
title: 'Eliminar Nomina',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if (result){
delete_nomina(ids)
}
}
})
}
function delete_nomina(ids){
webix.ajax().del('/nomina', {id: ids}, function(text, xml, xhr){
var msg = 'Registros eliminados correctamente'
if (xhr.status == 200){
get_nomina()
msg_ok(msg)
} else {
msg = 'No se pudo eliminar.'
msg_error(msg)
}
})
}
function cmd_nomina_timbrar_click(){
get_nomina()
msg = 'Se enviarán a timbrar todos los recibos sin timbrar<BR><BR>'
msg += '¿Estás seguro de continuar?<BR><BR>'
webix.confirm({
title: 'Enviar a timbrar',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if (result){
timbrar_nomina()
}
}
})
}
function timbrar_nomina(){
webix.ajax().get('/nomina', {opt: 'stamp'}, {
error: function(text, data, xhr) {
msg = 'Error al timbrar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if(values.ok){
cmd_update_timbres_click()
get_nomina()
msg_ok(values.msg_ok)
}
if(values.error){
webix.alert({
title: 'Error al Timbrar',
text: values.msg_error,
type: 'alert-error'
})
}
}
})
}
function send_mail(row){
if(!row.uuid){
msg_error('La nómina no esta timbrada')
return
}
var data = {'opt': 'send_mail', 'id': row.id}
msg = '¿Estás seguro de enviar por correo este Recibo de Nómina?'
webix.confirm({
title: 'Enviar Recibo de Nómina',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
webix.ajax().post('/nomina', data, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
msg_ok(values.msg)
}else{
msg_error(values.msg)
}
}
})
}
}
})
}
function grid_nomina_click(id, e, node){
var row = this.getItem(id)
if(id.column == 'xml'){
location = '/doc/nomxml/' + row.id
}else if(id.column == 'pdf'){
window.open('/doc/nompdf/' + row.id, '_blank')
}else if(id.column == 'email'){
send_mail(row)
}
}
function filter_year_nomina_change(nv, ov){
var fm = $$('filter_month_nomina')
filters = {'opt': 'yearmonth', 'year': nv, 'month': fm.getValue()}
get_nomina(filters)
}
function filter_month_nomina_change(nv, ov){
var fy = $$('filter_year_nomina')
filters = {'opt': 'yearmonth', 'year': fy.getValue(), 'month': nv}
get_nomina(filters)
}
function filter_dates_nomina_change(range){
if(range.start != null && range.end != null){
filters = {'opt': 'dates', 'range': range}
get_nomina(filters)
}
}
function cmd_nomina_cancel_click(){
var row = $$('grid_nomina').getSelectedItem()
if(row == undefined){
msg = 'Selecciona un registro'
msg_error(msg)
return
}
if(Array.isArray(row)){
msg = 'Selecciona solo un registro'
msg_error(msg)
return
}
if(row['estatus'] != 'Timbrado'){
msg = 'Solo se pueden cancelar recibos timbrados'
msg_error(msg)
return
}
win_invoice_cancel_nomina.init()
$$('win_invoice_cancel_nomina').show()
}
function cmd_win_cancel_nomina_close_click(){
$$('win_invoice_cancel_nomina').close()
}
function cmd_invoice_cancel_nomina_click(){
var reason = $$('lst_reasons_cancel').getValue()
var uuid = $$('txt_cancel_uuid').getValue()
if(!reason){
msg = 'Selecciona un motivo para esta cancelación'
msg_error(msg)
return
}
if(reason=='01' & !uuid){
msg = 'Debes de capturar el UUID que reemplaza a este CFDI'
msg_error(msg)
return
}
$$('win_invoice_cancel_nomina').close()
send_cancel_nomina(reason, uuid)
}
function send_cancel_nomina(reason, uuid){
var grid = $$('grid_nomina')
var row = grid.getSelectedItem()
var data = new Object()
data['opt'] = 'cancel'
data['id'] = row.id
data['args'] = {reason: reason, uuid: uuid}
webix.ajax().sync().post('nomina', data, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
grid.updateItem(row.id, values.row)
msg_ok(values.msg)
}else{
msg_error(values.msg)
}
}
})
}
function cmd_nomina_log_click(){
location = '/doc/nomlog/0'
}
function cmd_nomina_download_click(){
var grid = $$('grid_nomina')
if(!grid.count()){
msg = 'Sin documentos a descargar'
msg_error(msg)
return
}
var ids = []
grid.eachRow(function(row){
var r = grid.getItem(row)
ids.push(r.id)
})
var filters = {'by': 'download', 'ids': ids}
webix.ajax().response('blob').get('/nomina', filters, function(text, data){
webix.html.download(data, 'nomina.zip');
});
}
function grid_nomina_on_select_change(){
var g = $$('grid_nomina')
var rows = g.getSelectedItem(true)
var total = 0
for (i = 0; i < rows.length; i++) {
if(typeof(rows[i].total) == 'string'){
total += rows[i].total.to_float()
}else{
total += rows[i].total
}
}
g.getColumnConfig('empleado').footer[0].text = webix.i18n.priceFormat(total)
g.refreshColumns()
}
function cmd_nomina_sat_click(){
var g = $$('grid_nomina')
if(g.count() == 0){
return
}
var row = g.getSelectedItem()
if (row == undefined){
msg_error('Selecciona un recibo de nómina')
return
}
if (row instanceof Array){
msg_error('Selecciona solo un recibo de nómina')
return
}
if(!row.uuid){
msg_error('La factura no esta timbrada, solo es posible consultar \
el estatus en el SAT de facturas timbradas')
return
}
var options = {opt: 'status_sat', id: row.id}
webix.ajax().get('/nomina', options, function(text, data){
var value = data.json()
if(value == 'Vigente'){
msg_ok(value)
}else if(value == 'uncancel'){
ask_invoice_uncancel(row.id)
}else{
msg_error(value)
}
})
}

View File

@ -1,101 +1,16 @@
//~ Empresa Libre
//~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.net)
//~
//~ 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 <http://www.gnu.org/licenses/>.
var cfg_partners = new Object()
var partners_controllers = {
init: function(){
$$('cmd_new_partner').attachEvent('onItemClick', cmd_new_partner_click);
//~ $$('cmd_new_contact').attachEvent('onItemClick', cmd_new_contact_click);
$$('cmd_edit_partner').attachEvent('onItemClick', cmd_edit_partner_click);
$$('cmd_delete_partner').attachEvent('onItemClick', cmd_delete_partner_click);
$$('cmd_save_partner').attachEvent('onItemClick', cmd_save_partner_click);
$$('cmd_cancel_partner').attachEvent('onItemClick', cmd_cancel_partner_click);
//~ $$('cmd_cancel_contact').attachEvent('onItemClick', cmd_cancel_contact_click);
//~ $$('cmd_partner_zero').attachEvent('onItemClick', cmd_partner_zero_click);
$$('codigo_postal').attachEvent('onKeyPress', postal_code_key_press);
$$('codigo_postal').attachEvent('onTimedKeyPress', postal_code_key_up);
$$('colonia').attachEvent('onFocus', colonia_on_focus)
$$("tipo_persona").attachEvent( "onChange", opt_tipo_change)
$$("es_cliente").attachEvent( "onChange", is_client_change)
$$("es_proveedor").attachEvent( "onChange", is_supplier_change)
$$("rfc").attachEvent( "onBlur", rfc_lost_focus)
$$('multi').attachEvent('onViewChange', multi_change)
$$('grid_partners').attachEvent('onItemDblClick', grid_partners_double_click)
//~ $$('grid_partners').attachEvent('onSelectChange', grid_partners_on_select_change)
$$('partner_balance').attachEvent('onChange', partner_balance_on_change)
$$('cmd_partner_add_account_bank').attachEvent('onItemClick', cmd_partner_add_account_bank_click)
$$('grid_partner_account_bank').attachEvent('onItemClick', grid_partner_account_bank_click)
default_config_partners()
}
}
function default_config_partners(){
webix.ajax().get('/config', {'fields': 'partners'}, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
cfg_partners = values
//~ show('cmd_partner_zero', cfg_partners['chk_config_change_balance_partner'])
}
})
}
function get_condicion_pago(){
webix.ajax().get('/values/condicionespago', {
error: function(text, data, xhr) {
},
success: function(text, data, xhr) {
var values = data.json();
$$('condicion_pago').define('suggest', values)
$$('condicion_pago').refresh()
}
})
}
function cmd_new_partner_click(id, e, node){
$$('form_partner').clearValidation()
$$('form_partner_account_bank').clearValidation()
$$('form_partner').setValues({
id: 0, pais: 'México', tipo_persona: 1, es_activo: true,
partner_balance: 0.00})
id: 0, pais: 'México', tipo_persona: 1, es_activo: true})
$$('forma_pago').getList().load('/values/formapago')
get_condicion_pago()
$$('grid_partners').clearSelection()
$$('multi_partners').setValue('partners_new')
$$('tab_partner').setValue('Datos Fiscales')
get_uso_cfdi_to_table()
get_uso_cfdi_to_table({})
query = table_usocfdi.chain().find({fisica: true}).data()
$$('lst_uso_cfdi_socio').getList().parse(query)
$$('partner_balance').define('readonly', !cfg_partners['chk_config_change_balance_partner'])
get_partner_banks()
get_partner_accounts_bank(0)
get_sat_regimenes()
}
@ -105,39 +20,30 @@ function cmd_new_contact_click(id, e, node){
}
function cmd_edit_partner_click(){
function cmd_edit_partner_click(id, e, node){
var msg = ''
var row = $$('grid_partners').getSelectedItem()
$$('form_partner_account_bank').clearValidation()
if (row == undefined){
msg = 'Selecciona un Socio de Negocio'
msg_error(msg)
webix.message({type:'error', text: msg})
return
}
get_condicion_pago()
webix.ajax().get("/partners", {id: row['id']}, {
error: function(text, data, xhr) {
msg_error()
webix.message({type:"error", text: "Error al consultar"})
},
success: function(text, data, xhr){
var values = data.json()
$$('form_partner').clearValidation()
$$('form_partner').setValues(values)
$$('forma_pago').getList().load('/values/formapago')
$$('partner_balance').define('readonly', !cfg_partners['chk_config_change_balance_partner'])
get_uso_cfdi_to_table()
get_uso_cfdi_to_table({})
if(values.tipo_persona == 1){
query = table_usocfdi.chain().find({fisica: true}).data()
get_sat_regimenes()
}else if(values.tipo_persona == 2){
query = table_usocfdi.chain().find({moral: true}).data()
get_sat_regimenes(true)
}else{
query = [{id: 'P01', value: 'Por definir'}]
}
@ -148,17 +54,12 @@ function cmd_edit_partner_click(){
if(values.es_proveedor){
$$('cuenta_proveedor').enable()
}
get_partner_accounts_bank(row['id'])
pause(250)
$$('lst_receptor_regimenes_fiscales').select(values.regimenes)
}
})
$$('multi_partners').setValue('partners_new')
$$('tab_partner').setValue('Datos Fiscales')
get_partner_banks()
}
};
function cmd_delete_partner_click(id, e, node){
@ -167,7 +68,7 @@ function cmd_delete_partner_click(id, e, node){
if (row == undefined){
msg = 'Selecciona un Cliente o Proveedor'
msg_error(msg)
webix.message({type:'error', text: msg})
return
}
@ -196,10 +97,10 @@ function delete_partner(id){
var msg = 'Socio eliminado correctamente'
if (xhr.status == 200){
$$('grid_partners').remove(id);
msg_ok(msg)
webix.message({type: 'success', text: msg})
} else {
msg = 'No se pudo eliminar. Asegurate de que no tenga documentos relacionados'
msg_error(msg)
webix.message({type: 'error', text: msg})
}
})
}
@ -210,27 +111,18 @@ function cmd_save_partner_click(id, e, node){
var form = this.getFormView();
if (!form.validate()) {
msg_error(msg)
webix.message({type: 'error', text: msg})
return
}
var values = form.getValues();
if(!values.rfc){
msg = 'Captura el RFC'
if(values.codigo_postal && values.codigo_postal.length != 5){
msg = 'Longitud inválida del C.P.'
msg_error(msg)
$$('tab_partner').setValue('Datos Fiscales')
return
}
if(values.tipo_persona != 4){
if(values.codigo_postal && values.codigo_postal.length != 5){
msg = 'Longitud inválida del C.P.'
msg_error(msg)
return
}
}
if (!values.es_cliente && !values.es_proveedor){
msg = 'Selecciona si es cliente, proveedor o ambos'
msg_error(msg)
@ -238,37 +130,17 @@ function cmd_save_partner_click(id, e, node){
return
}
if(values.tipo_persona == 4){
if(values.pais && values.pais.length != 3){
msg = 'Longitud de país inválida'
msg_error(msg)
return
}
}
var ids_regimenes = $$('lst_receptor_regimenes_fiscales').getSelectedId()
if(values.tipo_persona < 3){
if(!ids_regimenes){
msg = 'Selecciona al menos un Regimen Fiscal'
msg_error(msg)
return
}
}
values['accounts'] = $$('grid_partner_account_bank').data.getRange()
values['regimenes'] = ids_regimenes
webix.ajax().post('/partners', values, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico';
msg_error(msg)
webix.message({type:'error', text:msg});
},
success:function(text, data, XmlHttpRequest){
var values = data.json();
if (values.ok) {
update_grid_partner(values)
} else {
msg_error(values.msg)
webix.message({type:'error', text:values.msg});
}
}
})
@ -286,7 +158,7 @@ function update_grid_partner(values){
$$("grid_partners").updateItem(values.row['id'], values.row)
}
$$('multi_partners').setValue('partners_home')
msg_ok(msg)
webix.message({type:'success', text: msg})
}
@ -306,13 +178,13 @@ function postal_code_key_up(){
if( value.length == 5 ){
webix.ajax().get('/values/cp', {cp: value}, {
error: function(text, data, xhr) {
msg_error('Error al consultar el C.P.')
webix.message({type:'error', text:'Error al consultar el C.P.'})
},
success: function(text, data, xhr) {
var values = data.json();
if (values.estado == undefined){
msg = 'No se encontró el C.P., asegurate de que sea correcto'
msg_error(msg)
webix.message({type:'error', text:msg})
} else {
$$('form_partner').setValues({
estado: values.estado,
@ -357,31 +229,20 @@ function opt_tipo_change(new_value, old_value){
$$("nombre").define("value", "")
$$("pais").define("readonly", true)
$$("pais").define("value", PAIS)
$$('id_fiscal').define('value', '')
show('id_fiscal', new_value == 4)
$$('lst_receptor_regimenes_fiscales').clearAll()
var regimen_616 = {id: 11, value: '[616] Sin obligaciones fiscales'}
if (new_value == 1 || new_value == 2){
$$("rfc").define("value", "")
$$("rfc").define("readonly", false)
moral = false
if(new_value == 2){
moral = true
}
get_sat_regimenes(moral)
$$("rfc").define("value", "");
$$("rfc").define("readonly", false);
} else if (new_value == 3) {
$$("rfc").define("value", RFC_PUBLICO)
$$("nombre").define("value", PUBLICO)
$$("rfc").define("readonly", true)
$$('lst_receptor_regimenes_fiscales').parse(regimen_616)
$$("rfc").define("value", RFC_PUBLICO);
$$("nombre").define("value", PUBLICO);
$$("rfc").define("readonly", true);
} else if (new_value == 4) {
$$("rfc").define("value", RFC_EXTRANJERO)
$$("rfc").define("readonly", true)
$$("pais").define("readonly", false)
$$("pais").define("value", "")
$$('lst_receptor_regimenes_fiscales').parse(regimen_616)
$$("rfc").define("value", RFC_EXTRANJERO);
$$("rfc").define("readonly", true);
$$("pais").define("readonly", false);
$$("pais").define("value", "");
}
$$("nombre").refresh();
@ -399,12 +260,10 @@ function opt_tipo_change(new_value, old_value){
}else if (new_value == 2){
query = table_usocfdi.chain().find({moral: true}).data()
}else{
query = [{id: 'S01', value: '[S01] Sin efectos fiscales. '}]
query = [{id: 'P01', value: 'Por definir'}]
}
$$('lst_uso_cfdi_socio').getList().parse(query)
$$('lst_uso_cfdi_socio').refresh()
}
@ -421,9 +280,9 @@ function is_client_change(new_value, old_value){
function is_supplier_change(new_value, old_value){
var value = Boolean(new_value)
if (value){
$$("cuenta_proveedor").enable()
$$("cuenta_proveedor").enable();
} else {
$$("cuenta_proveedor").disable()
$$("cuenta_proveedor").disable();
}
}
@ -465,204 +324,6 @@ function rfc_lost_focus(prev_view){
function multi_partners_change(prevID, nextID){
//~ webix.message(prevID)
//~ webix.message(nextID)
webix.message(prevID)
webix.message(nextID)
}
function grid_partners_double_click(id, e, node){
//~ if(id.column!='saldo_cliente'){
cmd_edit_partner_click()
//~ }
}
function partner_balance_on_change(new_value, old_value){
if(!isFinite(new_value)){
this.config.value = old_value
this.refresh()
}
}
function cmd_partner_add_account_bank_click(){
var form = $$('form_partner_account_bank')
if (!form.validate()){
msg = 'Valores inválidos'
msg_error(msg)
return
}
var values = form.getValues()
var id_partner = $$('form_partner').getValues().id
var account = {
id_partner: id_partner,
delete: '-',
banco: $$('lst_partner_bank').getText(),
cuenta: values.partner_account.trim(),
clabe: values.partner_clabe.trim(),
}
if(!account.cuenta){
msg = 'La cuenta es requerida'
msg_error(msg)
return
}
if(!account.cuenta.is_number()){
msg = 'Solo digitos en la cuenta'
msg_error(msg)
return
}
if(account.cuenta.length < 10){
msg = 'Longitud incorrecta de la cuenta'
msg_error(msg)
return
}
if(!account.clabe){
msg = 'La CLABE es requerida'
msg_error(msg)
return
}
if(account.clabe.length != 18){
msg = 'La CLABE debe ser de 18 digitos'
msg_error(msg)
return
}
if(!account.clabe.is_number()){
msg = 'Solo digitos en la CLABE'
msg_error(msg)
return
}
var grid = $$('grid_partner_account_bank')
if(id_partner){
partner_new_account_bank(account, grid)
}else{
grid.add(account)
}
form.setValues({})
}
function get_partner_banks(){
webix.ajax().get('/satbancos', {opt: 'active'}, function(text, data){
var values = data.json()
$$('lst_partner_bank').getList().parse(values)
})
}
function partner_new_account_bank(account, grid){
webix.ajax().post('/socioscb', {opt: 'new', values: account}, {
error: function(text, data, xhr) {
msg = 'Error al guardar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
if(values.ok){
account['id'] = values.id
grid.add(account)
msg_ok(values.msg)
}else{
msg_error(values.msg)
}
}
})
}
function get_partner_accounts_bank(id_partner){
var grid = $$('grid_partner_account_bank')
grid.clearAll()
if(id_partner){
var data = {opt: 'by_partner', id_partner: id_partner}
webix.ajax().get('/socioscb', data, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
grid.parse(values)
}
})
}
}
function grid_partner_account_bank_click(id, e, node){
if(id.column != 'delete'){
return
}
var id_partner = $$('form_partner').getValues().id
var grid = $$('grid_partner_account_bank')
var msg = '¿Estás seguro de eliminar la cuenta de banco seleccionada?<BR><BR>'
msg += 'ESTA ACCION NO SE PUEDE DESHACER'
webix.confirm({
title: 'Eliminar cuenta de banco',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if (result){
if(id_partner){
partner_delete_account_bank(id.row)
}else{
grid.remove(id.row)
}
}
}
})
}
function partner_delete_account_bank(row){
var grid = $$('grid_partner_account_bank')
webix.ajax().post('/socioscb', {opt: 'delete', values: {id: row}}, {
error: function(text, data, xhr) {
msg = 'Error al eliminar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
if(values.ok){
grid.remove(row)
msg_ok(values.msg)
}else{
msg_error(values.msg)
}
}
})
}
function get_sat_regimenes(morales=false){
var data = {opt: 'actives', morales: morales}
webix.ajax().sync().get('/satregimenes', data, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
$$('lst_receptor_regimenes_fiscales').clearAll()
$$('lst_receptor_regimenes_fiscales').parse(values)
}
})
}

View File

@ -1,141 +1,33 @@
var cfg_products = new Object()
var gis_admin = false
function products_default_config(){
webix.ajax().get('/config', {'fields': 'main_products'}, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
cfg_products['inventario'] = values.chk_llevar_inventario
cfg_products['predial'] = values.chk_config_cuenta_predial
cfg_products['codigo_barras'] = values.chk_config_codigo_barras
cfg_products['con_impuestos'] = values.chk_config_precio_con_impuestos
cfg_products['default_unit'] = values.default_unidad
cfg_products['default_tax'] = values.default_tax
if(cfg_products['inventario']){
$$('grid_products').showColumn('existencia')
}
//~ show('cant_by_packing', values.chk_use_packing)
show('cmd_show_exists', values.chk_multi_stock)
}
})
webix.ajax().get('/users', {'opt': 'is_admin'}, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
gis_admin = values.is_admin
if(values.is_admin){
$$('cmd_add_inventory').show()
//~ $$('cmd_products_add').show()
}
}
})
}
var products_controllers = {
init: function(){
$$('cmd_new_product').attachEvent('onItemClick', cmd_new_product_click)
$$("cmd_edit_product").attachEvent("onItemClick", cmd_edit_product_click)
$$("cmd_delete_product").attachEvent("onItemClick", cmd_delete_product_click)
$$("cmd_save_product").attachEvent("onItemClick", cmd_save_product_click)
$$("cmd_cancel_product").attachEvent("onItemClick", cmd_cancel_product_click)
$$("cmd_import_products").attachEvent("onItemClick", cmd_import_products_click)
$$("cmd_add_inventory").attachEvent("onItemClick", cmd_add_inventory_click)
$$("cmd_products_add").attachEvent("onItemClick", cmd_products_add_click)
$$('cmd_add_products_from_xml').attachEvent('onItemClick', cmd_add_products_from_xml_click)
$$('cmd_show_exists').attachEvent('onItemClick', cmd_show_exists_click)
$$('cmd_save_products_add').attachEvent('onItemClick', cmd_save_products_add_click)
$$('cmd_close_products_add').attachEvent('onItemClick', cmd_close_products_add_click)
$$("chk_automatica").attachEvent("onChange", chk_automatica_change)
$$("valor_unitario").attachEvent("onChange", valor_unitario_change)
$$('precio_con_impuestos').attachEvent('onChange', precio_con_impuestos_change)
$$('precio_con_impuestos').attachEvent('onTimedKeyPress', precio_con_impuestos_key_up);
$$("chk_inventario").attachEvent("onChange", chk_inventario_change)
$$('grid_products').attachEvent('onItemDblClick', cmd_edit_product_click)
products_default_config()
}
}
function configurar_producto(){
show('cuenta_predial', cfg_products['predial'])
show('codigo_barras', cfg_products['codigo_barras'])
show('precio_con_impuestos', cfg_products['con_impuestos'])
show('chk_inventario', cfg_products['inventario'])
show('txt_existencia', cfg_products['inventario'])
show('txt_minimo', cfg_products['inventario'])
}
function get_categorias(){
webix.ajax().sync().get('/values/categorias', function(text, data){
var values = data.json()
$$('categoria').getList().parse(values, 'plainjs')
})
}
function get_products(){
var grid = $$('grid_products')
webix.ajax().get('/products', {}, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr) {
var values = data.json();
grid.clearAll();
if (values.ok){
grid.parse(values.rows, 'json');
grid.refresh()
};
}
});
}
function cmd_new_product_click(id, e, node){
get_taxes()
$$('unidad').getList().load('/values/unidades')
configurar_producto()
$$('form_product').setValues({
id: 0, es_activo_producto: true})
id: 0, es_activo_producto: true})
add_config({'key': 'id_product', 'value': ''})
get_new_key()
get_categorias()
$$('unidad').setValue(cfg_products['default_unit'])
$$('grid_product_taxes').select(cfg_products['default_tax'])
get_taxes()
$$('grid_products').clearSelection()
$$('categoria').getList().load('/values/categorias')
$$('unidad').getList().load('/values/unidades')
$$("multi_products").setValue("product_new")
}
function cmd_edit_product_click(){
get_taxes()
$$('unidad').getList().load('/values/unidades')
configurar_producto()
function cmd_edit_product_click(id, e, node){
var grid = $$('grid_products')
var row = grid.getSelectedItem()
if(row == undefined){
msg_error('Selecciona un Producto')
webix.message({type: 'error', text: 'Selecciona un Producto'})
return
}
get_taxes()
$$('categoria').getList().load('/values/categorias')
$$('unidad').getList().load('/values/unidades')
webix.ajax().get('/products', {id: row['id']}, {
webix.ajax().get('/products', {id:row['id']}, {
error: function(text, data, xhr) {
msg_error(text)
webix.message({type: 'error', text: 'Error al consultar'})
},
success: function(text, data, xhr){
var values = data.json()
@ -156,10 +48,10 @@ function delete_product(id){
var msg = 'Producto eliminado correctamente'
if(xhr.status == 200){
$$('grid_products').remove(id)
msg_ok(msg)
webix.message({type:'success', text:msg})
}else{
msg = 'No se pudo eliminar'
msg_error(msg)
webix.message({type:'error', text:msg})
}
})
}
@ -168,7 +60,7 @@ function delete_product(id){
function cmd_delete_product_click(id, e, node){
var row = $$('grid_products').getSelectedItem()
if (row == undefined){
msg_error('Selecciona un Producto')
webix.message({type:'error', text: 'Selecciona un Producto'})
return
}
@ -216,9 +108,8 @@ function update_grid_products(values){
msg = 'Producto actualizado correctamente'
$$("grid_products").updateItem(values.row['id'], values.row)
}
$$('grid_products').refresh()
$$('multi_products').setValue('products_home')
msg_ok(msg)
webix.message({type: 'success', text: msg})
}
@ -227,48 +118,35 @@ function cmd_save_product_click(id, e, node){
var form = this.getFormView()
if(!form.validate()){
msg_error('Valores inválidos')
webix.message({type: 'error', text: 'Valores inválidos'})
return
}
var rows = $$('grid_product_taxes').getSelectedId(true, true)
if (rows.length == 0){
webix.message({type: 'error', text: 'Selecciona un impuesto'})
return
}
var values = form.getValues();
if(!validate_sat_key_product(values.clave_sat, false)){
msg_error('La clave SAT no existe')
return
}
if(values.descripcion.length > 1000){
msg_error('Descripción con ' + values.descripcion.length + 'caracteres, captura solo 1000 caracteres')
return
}
if(values['objeto_impuesto']=='01' && rows.length > 0){
msg = 'Si Objeto de Impuestos = 01, no debes seleccionar ningún impuesto'
msg_error(msg)
return
}
if(values['objeto_impuesto']=='02' && rows.length == 0){
msg = 'Si Objeto de Impuestos = 02, debes de seleccionar al menos un impuesto'
msg_error(msg)
if (!validate_sat_key_product(values.clave_sat, false)){
webix.message({ type:'error', text:'La clave SAT no existe' })
return
}
values['taxes'] = JSON.stringify(rows)
webix.ajax().sync().post('products', values, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
webix.message({type: 'error', text: msg})
},
success:function(text, data, XmlHttpRequest){
var values = data.json();
if (values.ok) {
update_grid_products(values)
}else{
msg_error(values.msg)
webix.message({type:'error', text:values.msg})
}
}
})
@ -276,7 +154,9 @@ function cmd_save_product_click(id, e, node){
function cmd_cancel_product_click(id, e, node){
$$("multi_products").setValue("products_home")
};
@ -301,24 +181,10 @@ function chk_automatica_change(new_value, old_value){
}
function chk_inventario_change(new_value, old_value){
var value = Boolean(new_value)
if(value){
$$('txt_existencia').enable()
$$('txt_minimo').enable()
}else{
$$('txt_existencia').disable()
$$('txt_minimo').disable()
$$('txt_existencia').setValue(0)
$$('txt_minimo').setValue(0)
}
}
function get_new_key(){
webix.ajax().get('/values/newkey', {
error: function(text, data, xhr) {
msg_error(text)
webix.message({type:'error', text: text})
},
success: function(text, data, xhr) {
var values = data.json();
@ -335,657 +201,3 @@ function valor_unitario_change(new_value, old_value){
this.refresh()
}
}
function precio_con_impuestos_change(new_value, old_value){
if(!isFinite(new_value)){
this.config.value = old_value
this.refresh()
}
}
function calcular_sin_impuestos(value, taxes){
var vu = $$('valor_unitario')
var precio = value
taxes.forEach(function(tax){
var tasa = 1.00 + tax.tasa.to_float()
if(tax.tipo == 'T' && tax.name == 'IVA'){
precio = (value / tasa).round(DECIMALES)
}
})
vu.setValue(precio)
}
function precio_con_impuestos_key_up(){
var value = this.getValue()
if(!value){
return
}
var taxes = $$('grid_product_taxes').getSelectedItem(true)
if (taxes.length == 0){
msg = 'Selecciona al menos un impuesto'
msg_error(msg)
return
}
if(!isFinite(value)){
msg = 'Captura un valor válido'
msg_error(msg)
return
}
calcular_sin_impuestos(parseFloat(value), taxes)
}
function cmd_import_products_click(){
win_import_products.init()
$$('win_import_products').show()
}
function cmd_upload_products_click(){
var form = $$('form_upload_products')
var values = form.getValues()
if(!$$('lst_upload_products').count()){
$$('win_import_products').close()
return
}
if($$('lst_upload_products').count() > 1){
msg = 'Selecciona solo un archivo'
msg_error(msg)
return
}
var template = $$('up_products').files.getItem($$('up_products').files.getFirstId())
if(template.type.toLowerCase() != 'ods'){
msg = 'Archivo inválido.\n\nSe requiere un archivo ODS'
msg_error(msg)
return
}
msg = '¿Estás seguro de importar este archivo?'
webix.confirm({
title: 'Importar Productos',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
$$('up_products').send()
}
}
})
}
function up_products_upload_complete(response){
if(response.status != 'server'){
msg = 'Ocurrio un error al subir el archivo'
msg_error(msg)
return
}
msg = 'Archivo subido correctamente.\n\nComenzando importación.'
msg_ok(msg)
$$('win_import_products').close()
webix.ajax().post('/products', {opt: 'import'}, {
error: function(text, data, xhr) {
msg = 'Error al importar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if (values.ok){
get_products()
webix.alert({
title: 'Importación terminada',
text: values.msg,
})
}else{
msg_error(values.msg)
}
}
})
}
//~ Add inventory
function cmd_add_inventory_click(id, e, node){
var row = $$('grid_products').getSelectedItem()
if (row == undefined){
msg_error('Selecciona un Producto')
return
}
win_add_inventory.init()
$$('txt_add_id').setValue(row.id)
$$('txt_add_key').setValue(row.clave)
$$('txt_add_unit').setValue(row.unidad)
$$('txt_add_description').setValue(row.descripcion)
$$('lst_warehouses').getList().load('/warehouse?opt=for_select')
$$('win_add_inventory').show()
}
//~ Show details inventory
function cmd_show_exists_click(id, e, node){
var row = $$('grid_products').getSelectedItem()
if (row == undefined){
msg_error('Selecciona un Producto')
return
}
win_show_exists.init()
$$('txt_id_product').setValue(row.id)
$$('grid_warehouse_exists').load('warehouseproduct?opt=by_product&id=' + row.id)
if(gis_admin){
$$('lst_warehouse_target').getList().load('/warehouse?opt=for_select')
}else{
$$('lbl_title_move').hide()
$$('txt_cant_to_move').hide()
$$('lst_warehouse_target').hide()
$$('cmd_warehouse_move').hide()
}
$$('win_show_exists').show()
}
function cmd_warehouse_move_click(id, e, node){
var id_product = $$('txt_id_product').getValue()
var row = $$('grid_warehouse_exists').getSelectedItem()
if (row == undefined){
msg_error('Selecciona un Almacen origen')
return
}
var warehouse_source = row.id
var cant_to_move = $$('txt_cant_to_move').getValue()
var warehouse_target = $$('lst_warehouse_target').getValue()
if(!cant_to_move){
msg_error('La cantidad no puede ser cero')
return
}
if(cant_to_move > row.exists){
msg_error('La cantidad a mover no puede ser mayor a la existencia')
return
}
if (warehouse_target == ''){
msg_error('Selecciona un Almacen destino')
return
}
if (warehouse_source == warehouse_target){
msg_error('Los almacenes origen y destino deben ser diferentes')
return
}
var values = {
id_product: id_product,
cant: cant_to_move,
source: warehouse_source,
target: warehouse_target
}
msg = '¿Estás seguro de hacer este movimiento?'
webix.confirm({
title: 'Movimiento de Almacen',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
_warehouse_movement(values)
}
}
})
}
function _warehouse_movement(args){
var values = {
opt: 'warehouse_movement',
values: args,
}
webix.ajax().sync().post('inventoryentries', values, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
var values = data.json();
if (values.ok) {
$$('txt_cant_to_move').setValue(0)
$$('lst_warehouse_target').setValue('')
$$('grid_warehouse_exists').load('warehouseproduct?opt=by_product&id=' + args.id_product)
$$('grid_warehouse_exists').clearSelection()
msg_ok('Movimiento realizado correctamente')
}else{
msg_error(values.msg)
}
}
})
}
function cmd_win_show_exists_close_click(id, e, node){
$$('win_show_exists').close()
}
//~ Add products
function cmd_products_add_click(id, e, node){
$$("multi_products").setValue("product_add")
cfg_products['partner_id'] = 0
cfg_products['partner_rfc'] = ''
}
//~ Close add products
function cmd_close_products_add_click(id, e, node){
var grid = $$('grid_partner_products')
$$('multi_invoices').setValue('products_home')
$$('lbl_partner').setValue('')
grid.clearAll()
}
function _add_entries_inventory(data){
var grid = $$('grid_partner_products')
var values = {
opt: 'create',
values: data,
}
webix.ajax().sync().post('inventoryentries', values, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
var values = data.json();
if (values.ok) {
get_products()
$$('multi_invoices').setValue('products_home')
$$('lbl_partner').setValue('')
grid.clearAll()
}else{
msg_error(values.msg)
}
}
})
}
//~ Save add products
function cmd_save_products_add_click(id, e, node){
var grid = $$('grid_partner_products')
var count = 0
var products = []
var validate_import = false
var validate_cant = false
grid.eachRow(function(row){
var r = grid.getItem(row)
if(r.select){
var p = {}
count += 1
p.id_product = r.id_product
p.key = r.key
p.key_sat = r.key_sat1
p.description = r.description1
p.unit = r.unit
p.unit_value = r.unit_value1
p.cant = r.cant1
products.push(p)
if(p.unit_value < parseFloat(r.unit_value)){
validate_import = true
}
if(p.cant > r.cant){
validate_cant = true
}
}
})
if(!count){
msg = 'Selecciona al menos un registro'
msg_error(msg)
return
}
if(validate_import){
msg = 'El Valor Unitario no puede ser menor al Valor Unitario del proveedor'
msg_error(msg)
return
}
if(validate_cant){
msg = 'La Cantidad no puede ser mayor a la Cantidad del proveedor'
msg_error(msg)
return
}
var data = {
partner: cfg_products['partner'],
products: products,
}
msg = '¿Estás seguro de ingresar estos productos? <br/><br/>\
Esta acción no se puede deshacer.'
webix.confirm({
title: 'Agregar entrada',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
_add_entries_inventory(data)
}
}
})
}
//~ Import from xml
function cmd_add_products_from_xml_click(){
win_add_products_from_xml.init()
$$('win_add_products_from_xml').show()
}
//~ Upload XML
function cmd_upload_products_from_xml_click(){
var form = $$('form_upload_products_from_xml')
var values = form.getValues()
var list = $$('lst_up_products_from_xml')
var upload = $$('up_products_from_xml')
if(!list.count()){
$$('win_add_products_from_xml').close()
return
}
if(list.count() > 1){
msg = 'Selecciona solo un archivo'
msg_error(msg)
return
}
var template = upload.files.getItem(upload.files.getFirstId())
if(template.type.toLowerCase() != 'xml'){
msg = 'Archivo inválido.\n\nSe requiere un archivo XML'
msg_error(msg)
return
}
msg = '¿Estás seguro de importar este archivo? <br/><br/>\
Si hay datos previos seran reemplazados.'
webix.confirm({
title: 'Importar Productos',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
upload.send()
}
}
})
}
function up_products_from_xml_upload_complete(response){
if(response.status != 'server'){
msg = 'Ocurrio un error al subir el archivo'
msg_error(msg)
return
}
$$('win_add_products_from_xml').close()
if(response.error){
msg_error(response.error)
return
}
var grid = $$('grid_partner_products')
var data = response.data
cfg_products['partner'] = data.emisor
//~ cfg_products['xml'] = data.xml
var html = '<span class="webix_icon fa-user"></span><span class="lbl_partner">'
html += data.emisor.nombre + ' (' + data.emisor.rfc + ')</span>'
$$('lbl_partner').setValue(html)
grid.clearAll()
grid.parse(data.conceptos, 'json')
grid.refresh()
}
function get_partner_product(grid, row){
grid.refresh(row.id)
var partner_id = cfg_products['partner'].id
var filters = {
opt: 'product',
partner: cfg_products['partner'],
product_key: row.key,
}
if(!partner_id){
msg = 'El Proveedor no esta dado de alta'
msg_ok(msg)
return
}
webix.ajax().get('/partnerproducts', filters, {
error: function(text, data, xhr) {
msg_error('Ocurrio un error, consulta a soporte técnico.')
},
success: function(text, data, xhr){
var values = data.json()
if(values.ok){
row['id_product'] = values.row.id_product
row['key_sat1'] = values.row.key_sat1
row['description1'] = values.row.description1
row['unit_value1'] = values.row.unit_value1
grid.refresh(row.id)
}else{
msg_error(values.msg)
}
}
})
}
function grid_partner_products_select(row_id, state){
var grid = $$('grid_partner_products')
var row = grid.getItem(row_id)
if(state){
row['key_sat1'] = row.key_sat
row['description1'] = row.description
row['cant1'] = row.cant
row['unit_value1'] = 0.0
get_partner_product(grid, row)
}else{
row['key_sat1'] = ''
row['description1'] = ''
row['cant1'] = ''
grid.refresh(row_id)
}
}
function cmd_add_inventory_cancel_click(id, e, node){
$$('win_add_inventory').close()
}
function cmd_add_inventory_save_click(id, e, node){
var product_id = $$('txt_add_id').getValue()
//~ var product_key = $$('txt_add_key').getValue()
var new_cant = $$('txt_new_cant').getValue()
var warehouse = $$('lst_warehouses').getValue()
if(new_cant<=0) {
msg = 'La cantidad no puede ser cero'
msg_error(msg)
return
}
if($$('lst_warehouses').isVisible()){
if(!warehouse){
msg = 'Selecciona un almacen'
msg_error(msg)
return
}
}
msg = '¿Estas seguro de guardar esta entrada? <br/><br/>\
Esta acción no se puede deshacer'
webix.confirm({
title: 'Agregar entrada',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
add_product_stock(product_id, new_cant, warehouse)
}
}
})
}
function add_product_stock(product_id, new_cant, warehouse){
var data = {
product_id: product_id,
cant: new_cant,
warehouse: warehouse,
}
_add_entries_inventory_manual(product_id, data)
$$('win_add_inventory').close()
}
function _add_entries_inventory_manual(row_id, data){
var grid = $$('grid_products')
var values = {
opt: 'create_manual',
values: data,
}
webix.ajax().sync().post('inventoryentries', values, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
var values = data.json();
if (values.ok) {
grid.updateItem(row_id, values.row)
grid.refresh()
}else{
msg_error(values.msg)
}
}
})
}
function cmd_adjust_stock_click(id, e, node){
var id_product = $$('txt_id_product').getValue()
var row = $$('grid_warehouse_exists').getSelectedItem()
if (row == undefined){
msg_error('Selecciona un Almacen origen')
return
}
var warehouse_source = row.id
var cant_to_adjust = $$('txt_cant_to_adjust').getValue()
if(cant_to_adjust == 0){
msg_error('La cantidad a ajustar no puede ser cero')
return
}
if(cant_to_adjust > row.exists){
msg_error('La cantidad a ajustar no puede ser mayor a la existencia')
return
}
var values = {
id_product: id_product,
cant: cant_to_adjust,
storage: warehouse_source,
}
msg = '¿Estás seguro de hacer este ajuste?'
webix.confirm({
title: 'Ajuste de Almacen',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
_adjust_stock(values)
}
}
})
}
function _adjust_stock(args){
var grid = $$('grid_products')
var row = grid.getSelectedItem()
var values = {
opt: 'adjust_stock',
values: args,
}
webix.ajax().sync().post('warehouseproduct', values, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
var values = data.json();
if (values.ok) {
$$('txt_cant_to_adjust').setValue(0)
$$('grid_warehouse_exists').load('warehouseproduct?opt=by_product&id=' + args.id_product)
$$('grid_warehouse_exists').clearSelection()
grid.updateItem(row['id'], values.row)
grid.refresh()
msg_ok('Ajuste realizado correctamente')
}else{
msg_error(values.msg)
}
}
})
}

View File

@ -1,155 +0,0 @@
var controllers_school = {
init: function(){
$$('cmd_new_student').attachEvent('onItemClick', cmd_new_student_click)
$$('cmd_edit_student').attachEvent('onItemClick', cmd_edit_student_click)
$$('cmd_delete_student').attachEvent('onItemClick', cmd_delete_student_click)
$$('cmd_save_student').attachEvent('onItemClick', cmd_save_student_click)
$$('cmd_cancel_student').attachEvent('onItemClick', cmd_cancel_student_click)
}
}
function get_school_groups(){
webix.ajax().get('/values/schoolgroups', {
error: function(text, data, xhr) {
},
success: function(text, data, xhr) {
var values = data.json();
$$('student_grupo').define('suggest', values)
$$('student_grupo').refresh()
}
})
}
function init_config_school(){
get_students()
get_school_groups()
}
function cmd_new_student_click(){
$$('form_student').setValues({})
$$('grid_students').clearSelection()
$$('multi_school').setValue('new_student')
}
function cmd_edit_student_click(){
var row = $$('grid_students').getSelectedItem()
if (row == undefined){
msg = 'Selecciona un Alumno'
msg_error(msg)
return
}
webix.ajax().get('/students', {id: row['id']}, {
error: function(text, data, xhr) {
msg_error()
},
success: function(text, data, xhr){
var values = data.json()
$$('form_student').setValues(values)
}
})
$$('multi_school').setValue('new_student')
}
function delete_student(id){
webix.ajax().del('/students', {id: id}, function(text, xml, xhr){
msg = 'Alumno eliminado correctamente'
if (xhr.status == 200){
$$('grid_students').remove(id);
msg_ok(msg)
} else {
msg = 'No se pudo eliminar.'
msg_error(msg)
}
})
}
function cmd_delete_student_click(){
var row = $$('grid_students').getSelectedItem()
if (row == undefined){
msg = 'Selecciona un Alumno'
msg_error(msg)
return
}
msg = '¿Estás seguro de eliminar al Alumno?<BR><BR>'
msg += row['nombre'] + ' ' + row['paterno'] + ' (' + row['rfc'] + ')'
msg += '<BR><BR>ESTA ACCIÓN NO SE PUEDE DESHACER<BR><BR>'
webix.confirm({
title:'Eliminar Alumno',
ok:'Si',
cancel:'No',
type:'confirm-error',
text:msg,
callback:function(result){
if (result){
delete_student(row['id'])
}
}
})
}
function cmd_cancel_student_click(){
$$('multi_school').setValue('school_home')
}
function cmd_save_student_click(){
msg = ''
var form = this.getFormView();
if (!form.validate()) {
msg_error(msg)
return
}
var values = form.getValues();
opt = 'add'
if(values.id){
opt = 'edit'
}
webix.ajax().post('/students', {opt: opt, values: values}, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico';
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
var values = data.json();
if (values.ok) {
form.setValues({})
$$('multi_school').setValue('school_home')
get_students()
} else {
msg_error(values.msg)
}
}
})
}
function get_students(){
webix.ajax().get('/students', {}, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr) {
var values = data.json()
$$('grid_students').clearAll()
$$('grid_students').parse(values)
}
})
}

View File

@ -1,903 +0,0 @@
var query = []
var msg = ''
var cfg_ticket = new Object()
var last_forma_pago = ''
var tickets_controllers = {
init: function(){
$$('cmd_nuevo_ticket').attachEvent('onItemClick', cmd_nuevo_ticket_click)
$$('cmd_ticket_from_ticket').attachEvent('onItemClick', cmd_ticket_from_ticket_click)
$$('cmd_ticket_to_invoice').attachEvent('onItemClick', cmd_ticket_to_invoice_click)
$$('cmd_ticket_report_pdf').attachEvent('onItemClick', cmd_ticket_report_pdf_click)
$$('cmd_ticket_report_xls').attachEvent('onItemClick', cmd_ticket_report_xls_click)
$$('cmd_generar_ticket').attachEvent('onItemClick', cmd_generar_ticket_click)
$$('cmd_cerrar_ticket').attachEvent('onItemClick', cmd_cerrar_ticket_click)
$$('cmd_new_invoice_from_ticket').attachEvent('onItemClick', cmd_new_invoice_from_ticket_click)
$$('cmd_close_ticket_invoice').attachEvent('onItemClick', cmd_cerrar_ticket_click)
$$('cmd_cancelar_ticket').attachEvent('onItemClick', cmd_cancelar_ticket_click)
$$('cmd_move_tickets_right').attachEvent('onItemClick', cmd_move_tickets_right_click)
$$('cmd_move_tickets_left').attachEvent('onItemClick', cmd_move_tickets_left_click)
$$('cmd_ticket_notes').attachEvent('onItemClick', cmd_ticket_notes_click)
$$('tsearch_product_key').attachEvent('onKeyPress', tsearch_product_key_press)
$$('grid_tickets').attachEvent('onItemClick', grid_tickets_click)
$$('grid_tdetails').attachEvent('onItemClick', grid_ticket_details_click)
$$('grid_tdetails').attachEvent('onBeforeEditStop', grid_tickets_details_before_edit_stop)
$$('gt_productos_found').attachEvent('onValueSuggest', gt_productos_found_click)
$$('cmd_ticket_filter_today').attachEvent('onItemClick', cmd_ticket_filter_today_click)
$$('filter_year_ticket').attachEvent('onChange', filter_year_ticket_change)
$$('filter_month_ticket').attachEvent('onChange', filter_month_ticket_change)
$$('filter_dates_ticket').attachEvent('onChange', filter_dates_ticket_change)
$$('chk_is_invoice_day').attachEvent('onChange', chk_is_invoice_day_change)
$$('grid_tickets_active').attachEvent('onItemDblClick', grid_tickets_active_double_click)
$$('grid_tickets_invoice').attachEvent('onItemDblClick', grid_tickets_invoice_double_click)
$$('tsearch_client_key').attachEvent('onKeyPress', tsearch_client_key_press)
$$('grid_ticket_clients_found').attachEvent('onValueSuggest', grid_ticket_clients_found_click)
$$('grid_tdetails').attachEvent('onAfterRender', grid_tdetails_render)
webix.extend($$('grid_tickets'), webix.ProgressBar)
webix.extend($$('grid_tickets_active'), webix.ProgressBar)
}
}
function current_dates_tickets(){
var fy = $$('filter_year_ticket')
var fm = $$('filter_month_ticket')
var d = new Date()
fy.blockEvent()
fm.blockEvent()
fm.setValue(d.getMonth() + 1)
webix.ajax().sync().get('/values/filteryearsticket', function(text, data){
var values = data.json()
fy.getList().parse(values)
fy.setValue(d.getFullYear())
})
fy.unblockEvent()
fm.unblockEvent()
}
function get_tickets(filters){
if(filters == undefined){
filters = {'opt': 'today'}
}
var grid = $$('grid_tickets')
grid.showProgress({type: 'icon'})
webix.ajax().get('/tickets', filters, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr) {
var values = data.json();
grid.clearAll();
if (values.ok){
grid.parse(values.rows, 'json')
}
}
})
}
function cmd_ticket_filter_today_click(){
get_tickets()
}
function filter_year_ticket_change(nv, ov){
var fm = $$('filter_month_ticket')
filters = {'opt': 'yearmonth', 'year': nv, 'month': fm.getValue()}
get_tickets(filters)
}
function filter_month_ticket_change(nv, ov){
var fy = $$('filter_year_ticket')
filters = {'opt': 'yearmonth', 'year': fy.getValue(), 'month': nv}
get_tickets(filters)
}
function filter_dates_ticket_change(range){
if(range.start != null && range.end != null){
filters = {'opt': 'dates', 'range': range}
get_tickets(filters)
}
}
function configuracion_inicial_ticket(){
current_dates_tickets()
get_tickets()
webix.ajax().sync().get('/values/configticket', function(text, data){
var values = data.json()
//~ showvar(values)
cfg_ticket['open_pdf'] = values.open_pdf
cfg_ticket['direct_print'] = values.direct_print
cfg_ticket['edit_cant'] = values.edit_cant
cfg_ticket['total_up'] = values.total_up
})
}
function get_active_tickets(grid){
filters = {'opt': 'active'}
grid.showProgress({type: 'icon'})
webix.ajax().get('/tickets', filters, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr) {
var values = data.json();
grid.clearAll();
if (values.ok){
grid.parse(values.rows, 'json')
}
}
})
}
function configuracion_inicial_ticket_to_invoice(){
var grid = $$('grid_tickets_active')
var gridt = $$('grid_tickets_invoice')
var form = $$('form_ticket_invoice')
var chk = $$('chk_is_invoice_day')
chk.setValue(false)
get_active_tickets(grid)
form.setValues({id_partner: 0, lbl_tclient: 'Ninguno'})
gridt.attachEvent('onAfterAdd', function(id, index){
gridt.adjustColumn('index')
gridt.adjustColumn('folio', 'all')
gridt.adjustColumn('fecha', 'all')
});
gridt.clearAll()
}
function configuracion_inicial_nuevo_ticket(){
var grid = $$('grid_tdetails')
webix.ajax().sync().get('/values/taxes', function(text, data){
var values = data.json()
table_taxes.clear()
table_taxes.insert(values)
})
get_forma_pago('lst_ticket_forma_pago')
grid.clearAll()
table_pt.clear()
table_totals.clear()
show('grid_ticket_total_up', cfg_ticket['total_up'])
$$('form_new_ticket').setValues({notas: '', forma_pago: last_forma_pago})
}
function cmd_nuevo_ticket_click(){
configuracion_inicial_nuevo_ticket()
$$('multi_tickets').setValue('tickets_new')
}
function cmd_ticket_from_ticket_click(){
var grid = $$('grid_tickets')
if(grid.count() == 0){
return
}
var row = grid.getSelectedItem()
if (row == undefined){
msg_error('Selecciona un ticket')
return
}
configuracion_inicial_nuevo_ticket()
$$('multi_tickets').setValue('tickets_new')
$$('grid_tdetails').load('ticketsdetails?opt=by_ticket_id&id=' + row.id)
}
function cmd_ticket_to_invoice_click(){
configuracion_inicial_ticket_to_invoice()
$$('multi_tickets').setValue('tickets_invoice')
}
function validar_ticket(){
var grid = $$('grid_tdetails')
if(!grid.count()){
webix.UIManager.setFocus('tsearch_product_key')
msg = 'Agrega al menos un producto'
msg_error(msg)
return false
}
var values = $$('form_new_ticket').getValues()
if(!values.forma_pago){
msg = 'La forma de pago es requerida'
msg_error(msg)
return false
}
return true
}
function get_ticket_pdf(id){
window.open('/doc/tpdf/' + id, '_blank')
}
function guardar_ticket(values){
var gd = $$('grid_tdetails')
var grid = $$('grid_tickets')
var rows = gd.data.getRange()
for (i = 0; i < rows.length; i++) {
delete rows[i]['id']
delete rows[i]['delete']
delete rows[i]['clave']
delete rows[i]['clave_sat']
delete rows[i]['unidad']
delete rows[i]['importe']
rows[i]['valor_unitario'] = parseFloat(rows[i]['valor_unitario'])
rows[i]['descuento'] = parseFloat(rows[i]['descuento'])
}
last_forma_pago = values.forma_pago
var data = new Object()
data['opt'] = 'add'
data['productos'] = rows
data['forma_pago'] = values.forma_pago
data['notas'] = values.notas
webix.ajax().sync().post('tickets', data, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
msg_ok('Ticket generado correctamente')
$$('form_new_ticket').setValues({})
gd.clearAll()
grid.add(values.row)
$$('multi_tickets').setValue('tickets_home')
if(cfg_ticket.open_pdf){
get_ticket_pdf(values.row.id)
}
if(cfg_ticket.direct_print){
print_ticket(values.row.id)
}
}else{
msg_error(values.msg)
}
}
})
}
function cmd_generar_ticket_click(){
var form = $$('form_new_ticket')
if(!form.validate()) {
msg_error('Valores inválidos')
return
}
var values = form.getValues()
if(!validar_ticket()){
return
}
msg = '¿Todos los datos son correctos?<BR><BR>'
msg += '¿Estás seguro de generar este Ticket?'
webix.confirm({
title: 'Generar Ticket',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
guardar_ticket(values)
}
}
})
}
function cmd_cerrar_ticket_click(){
$$('multi_tickets').setValue('tickets_home')
}
function calcular_precio_con_impuestos(precio, taxes){
var precio_final = precio
for(var tax of taxes){
impuesto = table_taxes.findOne({'id': tax.tax})
if(impuesto.tipo == 'E'){
continue
}
var base = precio
if(impuesto.tipo == 'R'){
base = (precio * -1).round(DECIMALES)
}
precio_final += (impuesto.tasa * base).round(DECIMALES)
}
return precio_final
}
function edit_cant(id){
if(!cfg_ticket['edit_cant']){
return
}
$$('grid_tdetails').edit({row: id, column: 'cantidad'})
}
function agregar_producto(values){
var taxes = values.taxes
var producto = values.row
var form = $$('form_new_ticket')
var grid = $$('grid_tdetails')
var row = grid.getItem(producto.id)
var precio_final = 0.0
if(row == undefined){
producto['cantidad'] = 1
producto['valor_unitario'] = calcular_precio_con_impuestos(
parseFloat(producto['valor_unitario']), taxes)
producto['importe'] = producto['valor_unitario']
//~ var id = grid.add(producto, 0)
//~ edit_cant(id)
}else{
producto['cantidad'] = parseFloat(row.cantidad) + 1
producto['descuento'] = parseFloat(row.descuento)
producto['valor_unitario'] = parseFloat(row.valor_unitario)
precio_final = producto['valor_unitario'] - producto['descuento']
producto['importe'] = (precio_final * producto['cantidad']).round(DECIMALES)
//~ grid.updateItem(row.id, producto)
}
form.setValues({tsearch_product_key: '', tsearch_product_name: ''}, true)
//~ Validate stock
if(producto.inventario){
if(producto.cantidad > producto.existencia){
msg_error('No hay suficiente existencia de este producto')
return
}
}
if(row == undefined){
var id = grid.add(producto, 0)
edit_cant(id)
}else{
grid.updateItem(row.id, producto)
}
}
function buscar_producto_key(key){
var filters = {
opt: 'by_key',
key: key,
}
webix.ajax().get('/products', filters, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr){
var values = data.json()
if (values.ok){
agregar_producto(values)
} else {
msg = 'No se encontró la clave<BR><BR>' + key
msg_error(msg)
}
}
})
//~ webix.ajax().get('/values/productokey', {'key': key}, {
//~ error: function(text, data, xhr) {
//~ msg_error('Error al consultar')
//~ },
//~ success: function(text, data, xhr){
//~ var values = data.json()
//~ if (values.ok){
//~ agregar_producto(values)
//~ } else {
//~ msg = 'No se encontró la clave<BR><BR>' + key
//~ msg_error(msg)
//~ }
//~ }
//~ })
}
function tsearch_product_key_press(code, e){
var value = this.getValue()
if(code == 13 && value.trim().length > 0){
buscar_producto_key(value.trim())
}
}
function grid_ticket_details_click(id, e, node){
if(id.column != 'delete'){
return
}
var grid = $$('grid_tdetails')
grid.remove(id.row)
}
function grid_tickets_details_before_edit_stop(state, editor){
var grid = $$('grid_tdetails')
pause(500)
var row = grid.getItem(editor.row)
if(editor.column == 'cantidad'){
var cantidad = parseFloat(state.value)
if(isNaN(cantidad)){
msg = 'La cantidad debe ser un número'
msg_error(msg)
grid.blockEvent()
state.value = state.old
grid.editCancel()
grid.unblockEvent()
return true
}else if(cantidad <= 0){
msg = 'La cantidad no puede cero o menor'
msg_error(msg)
grid.blockEvent()
state.value = state.old
grid.editCancel()
grid.unblockEvent()
return true
}
//~ Validate stock
if(row['inventario']){
if(cantidad > row['existencia']){
msg = 'No hay suficiente existencia de este producto'
msg_error(msg)
grid.blockEvent()
state.value = state.old
grid.editCancel()
grid.unblockEvent()
return true
}
}
var valor_unitario = parseFloat(row['valor_unitario'])
var descuento = parseFloat(row['descuento'])
}
if(editor.column == 'valor_unitario'){
var valor_unitario = parseFloat(state.value)
if(isNaN(valor_unitario)){
msg = 'El valor unitario debe ser un número'
msg_error(msg)
grid.blockEvent()
state.value = state.old
grid.editCancel()
grid.unblockEvent()
return true
}else if(valor_unitario <= 0){
msg = 'El valor unitario no puede cero o menor'
msg_error(msg)
grid.blockEvent()
state.value = state.old
grid.editCancel()
grid.unblockEvent()
return true
}
var cantidad = parseFloat(row['cantidad'])
var descuento = parseFloat(row['descuento'])
}
if(editor.column == 'descuento'){
var descuento = parseFloat(state.value)
if(isNaN(descuento)){
msg = 'El descuento debe ser un número'
msg_error(msg)
grid.blockEvent()
state.value = state.old
grid.editCancel()
grid.unblockEvent()
return true
}
var cantidad = parseFloat(row['cantidad'])
var valor_unitario = parseFloat(row['valor_unitario'])
}
var precio_final = valor_unitario - descuento
row['importe'] = (cantidad * precio_final).round(DECIMALES)
grid.refresh()
if(cfg_ticket['edit_cant']){
focus('tsearch_product_name')
}
}
function gt_productos_found_click(obj){
buscar_producto_key(obj.clave)
}
function cancel_ticket(id){
var grid = $$('grid_tickets')
var data = new Object()
data['opt'] = 'cancel'
data['id'] = id
webix.ajax().sync().post('tickets', data, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
grid.updateItem(id, values.row)
msg_ok(values.msg)
}else{
msg_error(values.msg)
}
}
})
}
function cmd_cancelar_ticket_click(){
var grid = $$('grid_tickets')
if(grid.count() == 0){
return
}
var row = grid.getSelectedItem()
if (row == undefined){
msg_error('Selecciona un ticket')
return
}
if(row['estatus']=='Cancelado'){
msg_error('El ticket ya esta cancelado')
return
}
if(row['estatus']=='Facturado'){
msg_error('El ticket esta facturado')
return
}
msg = '¿Estás seguro de cancelar el siguiente Ticket?<BR><BR>'
msg += 'Folio: ' + row['folio']
msg += '<BR><BR>ESTA ACCIÓN NO SE PUEDE DESHACER'
webix.confirm({
title:'Cancelar Ticket',
ok:'Si',
cancel:'No',
type:'confirm-error',
text:msg,
callback:function(result){
if (result){
cancel_ticket(row['id'])
}
}
})
}
function chk_is_invoice_day_change(new_value, old_value){
var value = Boolean(new_value)
show('fs_ticket_search_client', !value)
enable('lst_global_periodicidad_2', value)
enable('lst_global_months_2', value)
var current_date = new Date()
var current_month = (current_date.getMonth() + 1).toString().padStart(2, '0')
$$('lst_global_months_2').setValue(current_month)
}
function send_timbrar_invoice(id){
webix.ajax().post('invoices', {opt: 'timbrar', id: id, update: false}, function(text, data){
var values = data.json()
if(values.ok){
msg_ok(values.msg)
}else{
webix.alert({
title: 'Error al Timbrar',
text: values.msg,
type: 'alert-error'
})
}
})
}
function save_ticket_to_invoice(data){
webix.ajax().sync().post('tickets', data, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
msg_ok(values.msg)
send_timbrar_invoice(values.id)
$$('chk_is_invoice_day').setValue(false)
$$('multi_tickets').setValue('tickets_home')
}else{
msg_error(values.msg)
}
}
})
}
function cmd_new_invoice_from_ticket_click(){
var form = this.getFormView();
var chk = $$('chk_is_invoice_day')
var grid = $$('grid_tickets_invoice')
var values = form.getValues()
var tickets = []
if(!chk.getValue()){
if(values.id_partner == 0){
webix.UIManager.setFocus('tsearch_client_name')
msg = 'Selecciona un cliente'
msg_error(msg)
return false
}
}
if(!grid.count()){
msg = 'Agrega al menos un ticket a facturar'
msg_error(msg)
return false
}
grid.eachRow(function(row){
tickets.push(row)
})
var data = new Object()
data['opt'] = 'invoice'
data['client'] = values.id_partner
data['tickets'] = tickets
data['is_invoice_day'] = chk.getValue()
var periodicidad = ''
if(data['is_invoice_day']){
periodicidad = $$('lst_global_periodicidad_2').getValue() + '|'
periodicidad += $$('lst_global_months_2').getValue() + '|'
periodicidad += new Date().getFullYear()
}
data['periodicidad'] = periodicidad
msg = 'Todos los datos son correctos.<BR><BR>'
if(data['is_invoice_day']){
msg += 'Es Factura Global.<BR><BR>'
}
msg += '¿Estás seguro de generar esta factura?'
webix.confirm({
title: 'Generar Factura',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
save_ticket_to_invoice(data)
}
}
})
}
function grid_tickets_active_double_click(id, e, node){
this.move(id.row, -1, $$('grid_tickets_invoice'))
}
function grid_tickets_invoice_double_click(id, e, node){
this.move(id.row, -1, $$('grid_tickets_active'))
}
function cmd_move_tickets_right_click(){
var source = $$('grid_tickets_active')
var target = $$('grid_tickets_invoice')
_move_tickets(source, target)
}
function cmd_move_tickets_left_click(){
var target = $$('grid_tickets_active')
var source = $$('grid_tickets_invoice')
_move_tickets(source, target)
}
function _move_tickets(source, target){
var rows = source.getSelectedItem()
if(rows == undefined){
source.eachRow(
function(row){
this.copy(row, -1, target)
}
)
source.clearAll()
}
}
function ticket_set_client(row){
var form = $$('form_ticket_invoice')
var html = '<span class="webix_icon fa-user"></span><span class="lbl_partner">'
form.setValues({
id_partner: row.id,
tsearch_client_key: '',
tsearch_client_name: ''}, true)
html += row.nombre + ' (' + row.rfc + ')</span>'
$$('lbl_tclient').setValue(html)
}
function ticket_search_client_by_id(id){
webix.ajax().get('/values/client', {'id': id}, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr){
var values = data.json()
if (values.ok){
ticket_set_client(values.row)
}else{
msg = 'No se encontró un cliente con la clave: ' + id
msg_error(msg)
}
}
})
}
function tsearch_client_key_press(code, e){
var value = this.getValue()
if(code == 13 && value.length > 0){
var id = parseInt(value, 10)
if (isNaN(id)){
msg_error('Captura una clave válida')
}else{
ticket_search_client_by_id(id)
}
}
}
function grid_ticket_clients_found_click(obj){
ticket_set_client(obj)
}
function cmd_ticket_notes_click(){
win_ticket_notes.init()
var values = $$('form_new_ticket').getValues()
$$('ticket_notes').setValue(values.notas)
$$('win_ticket_notes').show()
to_end('ticket_notes')
}
function cmd_ticket_save_note_click(){
var value = $$('ticket_notes').getValue()
$$('form_new_ticket').setValues({notas: value}, true)
$$('win_ticket_notes').close()
}
function print_ticket(id){
var data = new Object()
data['opt'] = 'print'
data['id'] = id
webix.ajax().post('tickets', data, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
msg_ok(values.msg)
}else{
msg_error(values.msg)
}
}
})
}
function grid_tickets_click(id, e, node){
if(id.column == 'pdf'){
//~ window.open('/doc/tpdf/' + id, '_blank')
get_ticket_pdf(id.row)
return
}
if(id.column == 'print'){
print_ticket(id.row)
return
}
}
function cmd_ticket_report_pdf_click(){
webix.toPDF($$('grid_tickets'), {
ignore: {'pdf': true, 'print': true},
filename: 'Reporte_Tickets',
width: 612,
height: 792,
columns:{
index: true,
serie: {width: 50},
folio: {width: 50},
fecha: {width: 125},
estatus: true,
total: {css: 'right'},
}
})
}
function cmd_ticket_report_xls_click(){
webix.toExcel($$('grid_tickets'), {
filename: 'Reporte_Tickets',
name: 'Tickets',
ignore: {'pdf': true, 'print': true},
rawValues: true,
})
}
function grid_tdetails_render(data){
var total = 0.0
this.eachRow(function(id){
var item = this.getItem(id)
total += item.importe
})
var id = $$('grid_ticket_total_up').getFirstId()
$$('grid_ticket_total_up').updateItem(id, {total: total})
}

View File

@ -1,30 +1,8 @@
//~ Empresa Libre
//~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.net)
//~
//~ 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 <http://www.gnu.org/licenses/>.
var PUBLICO = "PUBLICO EN GENERAL";
var PUBLICO = "Público en general";
var RFC_PUBLICO = "XAXX010101000";
var RFC_EXTRANJERO = "XEXX010101000";
var PAIS = "México";
var DECIMALES = 2;
//~ var DECIMALES_PU = 4;
var DECIMALES_TAX = 4;
var CLAVE_ANTICIPOS = '84111506';
var CURRENCY_MN = 'MXN';
var KEY_SAT_01 = '01010101';
var db = new loki('data.db');
@ -34,149 +12,20 @@ var table_pt = db.addCollection('productstaxes')
var table_totals = db.addCollection('totals', {unique: ['tax']})
var table_series = db.addCollection('series')
var table_usocfdi = db.addCollection('usocfdi')
var table_relaciones = db.addCollection('relaciones')
var table_waypayment = db.addCollection('waypayment')
var msg = ''
var months = [
{id: -1, value: 'Todos'},
{id: 1, value: 'Enero'},
{id: 2, value: 'Febrero'},
{id: 3, value: 'Marzo'},
{id: 4, value: 'Abril'},
{id: 5, value: 'Mayo'},
{id: 6, value: 'Junio'},
{id: 7, value: 'Julio'},
{id: 8, value: 'Agosto'},
{id: 9, value: 'Septiembre'},
{id: 10, value: 'Octubre'},
{id: 11, value: 'Noviembre'},
{id: 12, value: 'Diciembre'},
]
var days = [
{id: -1, value: '00'},
{id: 1, value: '01'},
{id: 2, value: '02'},
{id: 3, value: '03'},
{id: 4, value: '04'},
{id: 5, value: '05'},
{id: 6, value: '06'},
{id: 7, value: '07'},
{id: 8, value: '08'},
{id: 9, value: '09'},
{id: 10, value: '10'},
{id: 11, value: '11'},
{id: 12, value: '12'},
{id: 13, value: '13'},
{id: 14, value: '14'},
{id: 15, value: '15'},
{id: 16, value: '16'},
{id: 17, value: '17'},
{id: 18, value: '18'},
{id: 19, value: '19'},
{id: 20, value: '20'},
{id: 21, value: '21'},
{id: 22, value: '22'},
{id: 23, value: '23'},
{id: 24, value: '24'},
{id: 25, value: '25'},
{id: 26, value: '26'},
{id: 27, value: '27'},
{id: 28, value: '28'},
{id: 29, value: '29'},
{id: 30, value: '30'},
{id: 31, value: '31'},
]
function get_icon(tipo){
icons = {
xml: 'fa-file-code-o',
pdf: 'fa-file-pdf-o',
html: 'fa-html5',
zip: 'fa-file-zip-o',
email: 'fa-envelope-o',
print: 'fa-print',
table: 'fa-table',
}
return "<span class='webix_icon " + icons[tipo] + "'></span>"
}
function focus(name){
webix.UIManager.setFocus(name)
}
function select_all(name){
focus(name)
$$(name).getInputNode().select()
}
function to_end(name){
focus(name)
var txt = $$(name)
var pos = txt.getValue().length
var height = txt.getInputNode().scrollHeight
webix.html.setSelectionRange(txt.getInputNode(), pos)
txt.getInputNode().scrollTop = height
}
function showvar(values){
function show(values){
webix.message(JSON.stringify(values, null, 2))
}
function showtype(values){
webix.message(typeof(values))
}
function show(nombre, value){
if(value == '0'){
value = false
}
if(value){
$$(nombre).show()
}else{
$$(nombre).hide()
}
}
function show_column(table, column){
$$(table).showColumn(column)
}
function enable(nombre, value){
if(value == '0'){
value = false
}
if(value){
$$(nombre).enable()
}else{
$$(nombre).disable()
}
}
function msg_error(msg){
if(!msg){
msg = 'Error al consultar'
}
webix.message({type: 'error', text: msg})
}
function msg_ok(msg){
webix.message({type: 'success', text: msg})
function msg_sucess(msg){
webix.message({type: 'sucess', text: msg})
}
@ -185,78 +34,12 @@ Number.prototype.round = function(decimals){
}
String.prototype.is_number = function(){
return /^\d+$/.test(this)
}
String.prototype.to_float = function(){
return get_float(this)
}
String.prototype.to_float4 = function(){
return get_float4(this)
}
function get_float(value){
var f = parseFloat(value.replace('$', '').replace(/,/g, '').trim()).round(DECIMALES)
if(!f){
f = 0.00
}
return f
}
function get_float4(value){
var f = parseFloat(value.replace('$', '').replace(/,/g, '').trim()).round(DECIMALES_TAX)
if(!f){
f = 0.00
}
return f
}
var format_decimal_2 = webix.Number.numToStr({
groupSize: 3,
decimalSize: 2,
groupDelimiter: ",",
decimalDelimiter: "."
})
var format_decimal_4 = webix.Number.numToStr({
groupSize: 3,
decimalSize: 4,
groupDelimiter: ",",
decimalDelimiter: "."
})
function format_currency(value){
var fv = ''
if(get_config('decimales_precios') == 4){
fv = '$ ' + format_decimal_4(value)
}else{
fv = '$ ' + format_decimal_2(value)
}
return fv
}
function format_currency2(value){
return '$ ' + format_decimal_2(value)
}
function format_currency4(value){
return '$ ' + format_decimal_4(value)
}
webix.protoUI({
$cssName: "text",
name: "currency",
$init:function(){
this.attachEvent("onItemClick", function(){
this.$setValue(this.config.raw, true)
this.getInputNode().select()
})
this.attachEvent("onBlur", function(){
this.$setValue(this.config.value)
@ -268,138 +51,24 @@ webix.protoUI({
$setValue:function(value, raw){
this.config.raw = value
if(!raw){
value = format_currency(value)
value = webix.i18n.priceFormat(value)
}
this.getInputNode().value = value
}
}, webix.ui.text)
webix.protoUI({
$cssName: "text",
name: "currency4",
$init:function(){
this.attachEvent("onItemClick", function(){
this.$setValue(this.config.raw, true)
this.getInputNode().select()
})
this.attachEvent("onBlur", function(){
this.$setValue(this.config.value)
})
},
$render:function(){
this.$setValue(this.config.value)
},
$setValue:function(value, raw){
this.config.raw = value
if(!raw){
value = format_currency4(value)
}
this.getInputNode().value = value
webix.ui.datafilter.rowCount = webix.extend({
refresh:function(master, node, value){
node.firstChild.innerHTML = master.count();
}
}, webix.ui.text)
webix.protoUI({
$cssName: "text",
name: "currency2",
$init:function(){
this.attachEvent("onItemClick", function(){
this.$setValue(this.config.raw, true)
this.getInputNode().select()
})
this.attachEvent("onBlur", function(){
this.$setValue(this.config.value)
})
},
$render:function(){
this.$setValue(this.config.value)
},
$setValue:function(value, raw){
this.config.raw = value
if(!raw){
value = format_currency2(value)
}
this.getInputNode().value = value
}
}, webix.ui.text)
webix.ui.datafilter.countRows = webix.extend({
refresh:function(master, node, value){
node.firstChild.innerHTML = master.count();
}
}, webix.ui.datafilter.summColumn);
webix.ui.datafilter.summActive = webix.extend({
refresh:function(master, node, value){
node.firstChild.innerHTML = this.summGenerate(master);
},
summGenerate:function(master){
var sum = 0
master.eachRow(function(id){
var row = master.getItem(id)
if(row.estatus == 'Generado'){
var importe = row.total
if(typeof importe === 'string'){
importe = row.total.to_float()
}
sum += importe
}
})
return webix.i18n.priceFormat(sum)
}
}, webix.ui.datafilter.summColumn);
webix.ui.datafilter.summTimbrada = webix.extend({
refresh:function(master, node, value){
node.firstChild.innerHTML = this.summGenerate(master);
},
summGenerate:function(master){
var sum = 0
master.eachRow(function(id){
var row = master.getItem(id)
if(row.estatus == 'Timbrada'){
var importe = row.total_mn
if(typeof importe === 'string'){
importe = row.total_mn.to_float()
}
sum += importe
}
})
return webix.i18n.priceFormat(sum)
}
}, webix.ui.datafilter.summColumn);
webix.ui.datafilter.summTimbradaN = webix.extend({
refresh:function(master, node, value){
node.firstChild.innerHTML = this.summGenerate(master);
},
summGenerate:function(master){
var sum = 0
master.eachRow(function(id){
var row = master.getItem(id)
if(row.estatus == 'Timbrado'){
var importe = row.total
if(typeof importe === 'string'){
importe = row.total.to_float()
}
sum += importe
}
})
return webix.i18n.priceFormat(sum)
}
}, webix.ui.datafilter.summColumn);
}, webix.ui.datafilter.summColumn)
function validate_rfc(value){
rfc = value.trim().toUpperCase();
if ( rfc == ""){
msg_error('El RFC no puede estar vacío')
webix.message({ type:"error", text:"El RFC no puede estar vacío" });
return false
}
@ -411,93 +80,41 @@ function validate_rfc(value){
start = 2
}
if (rfc.length != length){
msg_error('Longitud incorrecta del RFC')
webix.message({ type:"error", text:"Longitud incorrecta del RFC" });
return false
}
if (tipo_persona < 3 && (rfc == RFC_PUBLICO || rfc == RFC_EXTRANJERO)){
msg_error('RFC incorrecto')
webix.message({ type:"error", text:"RFC incorrecto" });
return false
}
var part = rfc.slice(0, start);
var re = new RegExp('[a-z&Ñ]{' + start + '}', 'i');
if (!part.match(re)){
msg_error('El RFC tiene caractéres inválidos al inicio')
webix.message({ type:"error", text: "El RFC tiene caractéres inválidos al inicio" });
return false
}
part = rfc.slice(-3);
re = new RegExp('[a-z0-9]{3}', 'i');
if (!part.match(re)){
msg_error('El RFC tiene caractéres inválidos al final')
webix.message({ type:"error", text: "El RFC tiene caractéres inválidos al final" });
return false
}
part = rfc.slice(-9, -3);
re = new RegExp('[0-9]{6}', 'i');
if (!part.match(re)){
msg_error('Fecha inválida')
webix.message({ type:"error", text: "Fecha inválida" });
return false
}
var month = parseInt(part.slice(-4, -2))
if (month == 0 || month > 12 ){
msg_error('Fecha inválida')
webix.message({ type:"error", text: "Fecha inválida" });
return false
}
var day = parseInt(part.slice(-2))
if (day == 0 || day > 31 ){
msg_error('Fecha inválida')
return false
}
return true
}
function validar_rfc(value){
rfc = value.trim().toUpperCase();
if (rfc == ""){
msg_error('El RFC no puede estar vacío')
return false
}
if (rfc.length < 12 || rfc.length > 13){
msg_error('Longitud incorrecta del RFC')
return false
}
var length = rfc.length
var start = 4
if(length==12){
start = 2
}
var part = rfc.slice(0, start);
var re = new RegExp('[a-z&Ñ]{' + start + '}', 'i');
if (!part.match(re)){
msg_error('El RFC tiene caractéres inválidos al inicio')
return false
}
part = rfc.slice(-3);
re = new RegExp('[a-z0-9]{3}', 'i');
if (!part.match(re)){
msg_error('El RFC tiene caractéres inválidos al final')
return false
}
part = rfc.slice(-9, -3);
re = new RegExp('[0-9]{6}', 'i');
if (!part.match(re)){
msg_error('Fecha inválida')
return false
}
var month = parseInt(part.slice(-4, -2))
if (month == 0 || month > 12 ){
msg_error('Fecha inválida')
return false
}
var day = parseInt(part.slice(-2))
if (day == 0 || day > 31 ){
msg_error('Fecha inválida')
webix.message({ type:"error", text: "Fecha inválida" });
return false
}
@ -530,167 +147,3 @@ function get_config(value){
return key.value
}
}
webix.DataDriver.plainjs = webix.extend({
arr2hash:function(data){
var hash = {};
for (var i=0; i<data.length; i++){
var pid = data[i].parent_id;
if (!hash[pid]) hash[pid]=[];
hash[pid].push(data[i]);
}
return hash;
},
hash2tree:function(hash, level){
var top = hash[level];
for (var i=0; i<top.length; i++){
var branch = top[i].id;
if (hash[branch])
top[i].data = this.hash2tree(hash, branch);
}
return top;
},
getRecords:function(data, id){
var hash = this.arr2hash(data);
return this.hash2tree(hash, 0);
}
}, webix.DataDriver.json)
function get_forma_pago(control){
webix.ajax().get('/values/formapago', {key: true}, function(text, data){
var values = data.json()
$$(control).getList().parse(values)
})
}
function get_way_payment(){
webix.ajax().get('/satformapago', {opt: 'active_by_id'}, function(text, data){
var values = data.json()
table_waypayment.clear()
table_waypayment.insert(values)
})
}
function set_way_payment(control, filter99=false, current_way_payment=''){
if(filter99){
var values = table_waypayment.chain().find({'value': { '$ne' : 'Por definir' }}).data()
}else{
var values = table_waypayment.chain().data()
}
$$(control).getList().parse(values)
if(current_way_payment){
$$(control).setValue(current_way_payment)
}
}
function validate_regexp(value, pattern){
re = new RegExp(pattern, 'i');
if(value.match(re)){
return true
}else{
return false
}
}
function validate_pedimento(value){
var pattern = '[0-9]{2} [0-9]{2} [0-9]{4} [0-9]{7}'
return validate_regexp(value, pattern)
}
function validate_curp(value){
var pattern = '[A-Z][A,E,I,O,U,X][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][M,H][A-Z]{2}[B,C,D,F,G,H,J,K,L,M,N,Ñ,P,Q,R,S,T,V,W,X,Y,Z]{3}[0-9,A-Z][0-9]'
return validate_regexp(value, pattern)
}
//config may as well include only text, color and date hash
webix.editors.$popup = {
text:{
view: 'popup', width:500, height:200,
body: {view: 'textarea'}
},
};
function pause(milliseconds) {
var dt = new Date();
while ((new Date()) - dt <= milliseconds) { /* Do nothing */ }
}
//~ Revisado
function lst_clear(lst){
lst.setValue('')
lst.define('options', [])
lst.refresh()
}
function lst_parse(lst, values){
lst.getList().parse(values)
}
function lst_parse2(lst_name, values){
obj = $$(lst_name)
obj.getList().parse(values)
}
function set_value(control, value){
obj = $$(control)
obj.blockEvent()
obj.setValue(value)
obj.unblockEvent()
}
function grid_parse(grid_name, values){
obj = $$(grid_name)
obj.clearAll()
obj.parse(values)
}
function activate_tab(parent, name){
$$(parent).getTabbar().setValue(name)
}
var opt_global_periodicidad = [
{id: '01', value: '[01] Diario'},
{id: '02', value: '[02] Semanal'},
{id: '03', value: '[03] Quincenal'},
{id: '04', value: '[04] Mensual'},
{id: '05', value: '[05] Bimestral'},
]
var opt_global_months = [
{id: '01', value: '[01] Enero'},
{id: '02', value: '[02] Febrero'},
{id: '03', value: '[03] Marzo'},
{id: '04', value: '[04] Abril'},
{id: '05', value: '[05] Mayo'},
{id: '06', value: '[06] Junio'},
{id: '07', value: '[07] Julio'},
{id: '08', value: '[08] Agosto'},
{id: '09', value: '[09] Septiembre'},
{id: '10', value: '[10] Octubre'},
{id: '11', value: '[11] Noviembre'},
{id: '12', value: '[12] Diciembre'},
{id: '13', value: '[13] Enero-Febrero'},
{id: '14', value: '[14] Marzo-Abril'},
{id: '15', value: '[15] Mayo-Junio'},
{id: '16', value: '[16] Julio-Agosto'},
{id: '17', value: '[17] Septiembre-Octubre'},
{id: '18', value: '[18] Noviembre-Diciembre'},
]

View File

@ -29,6 +29,10 @@ webix.i18n.locales["es-ES"] = {
today: "Hoy"
},
controls:{
select:"Seleccione",
invalidMessage: "Valor de entrada no válido"
},
dataExport:{
page:"Página",
of:"de"
@ -77,12 +81,7 @@ webix.i18n.locales["es-ES"] = {
italic: "Itálico"
},
combo:{
select:"Seleccionar",
selectAll:"Seleccionar todo",
unselectAll:"Deselecciona todo"
},
message:{
ok:"OK",
cancel:"Cancelar"
}
};

File diff suppressed because one or more lines are too long

275
source/static/js/sidebar.js Normal file
View File

@ -0,0 +1,275 @@
webix.protoUI({
name: "sidebar",
defaults:{
titleHeight: 40,
type: "sideBar",
activeTitle: true,
select: true,
scroll: false,
collapsed: false,
collapsedWidth: 41,
position: "left",
width: 250,
mouseEventDelay: 10
},
$init: function(config){
this.$ready.push(this._initSidebar);
this.$ready.push(this._initContextMenu);
},
on_context:{},
on_mouse_move:{},
_initSidebar: function(){
this._fullWidth = this.config.width;
this.attachEvent("onBeforeOpen", function(id){
if(!this.config.multipleOpen)
this.closeAll();
return !this.config.collapsed;
});
this.attachEvent("onItemClick", function(id, ev, node){
if(this.getPopup() && !this.getPopup().config.hidden)
ev.showpopup = true;
if(webix.env.touch)
this._showPopup(id, node);
});
this.attachEvent("onBeforeSelect", function(id){
if(!this.getItem(id).$count){
var selected = this.getSelectedId();
if(selected && id!= selected){
var parentId = this.getParentId(selected);
this.removeCss(parentId, "webix_sidebar_selected");
}
return true;
}
return false;
});
this.attachEvent("onAfterSelect", function(id){
var parentId = this.getParentId(id);
this.addCss(parentId, "webix_sidebar_selected");
var title = this.getPopupTitle();
title.callEvent("onMasterSelect",[id]);
});
this.attachEvent("onMouseMove", function(id, ev, node){
this._showPopup(id, node);
});
if(this.config.collapsed)
this.collapse();
},
_showPopup: function(id, node){
if(this.config.collapsed){
var popup = this.getPopup();
if(popup){
var title = this.getPopupTitle();
if(title){
this._updateTitle(id);
}
var list = this.getPopupList();
if(list){
this._updateList(id);
}
var x = (this.config.position == "left"?this.config.collapsedWidth:-popup.config.width);
popup.show(node, {x: x , y:-1});
}
}
},
_updateTitle: function(id){
var title = this.getPopupTitle();
title.masterId = id;
title.parse(this.getItem(id));
var selectedId = this.getSelectedId();
if(selectedId && this.getParentId(selectedId) == id){
webix.html.addCss(title.$view, "webix_sidebar_selected", true);
}
else{
webix.html.removeCss(title.$view, "webix_sidebar_selected");
}
if(selectedId == id){
webix.html.addCss(title.$view, "webix_selected", true);
}
else{
webix.html.removeCss(title.$view, "webix_selected");
}
},
_updateList: function(id){
var list = this.getPopupList();
list.masterId = id;
var selectedId = this.getSelectedId();
var data = [].concat(webix.copy(this.data.getBranch(id)));
list.unselect();
if(data.length){
list.show();
list.data.importData(data);
if(list.exists(selectedId))
list.select(selectedId);
}
else
list.hide();
},
_initContextMenu: function(){
var config = this.config,
popup;
if(config.popup){
popup = webix.$$(config.popup);
}
if(!popup){
var dirClassName = (config.position=="left"?"webix_sidebar_popup_left":"webix_sidebar_popup_right");
var popupConfig = {
view:"popup",
css: "webix_sidebar_popup "+dirClassName,
autofit: false,
width: this._fullWidth - this.config.collapsedWidth,
borderless: true,
padding:0,
body:{
rows:[
{
view: "template", borderless: true, css: "webix_sidebar_popup_title",
template: "#value#", height: this.config.titleHeight+2,
on:{
onMasterSelect: function(id){
var master = this.getTopParentView().master;
if( master && master.getParentId(id) == this.masterId){
webix.html.addCss(this.$view, "webix_sidebar_selected", true);
}
if(master.config.collapsed && master.getItem(id).$level ==1){
webix.html.addCss(this.$view, "webix_selected", true);
}
}
},
onClick:{
webix_template: function(){
var id = this.masterId;
var master = this.getTopParentView().master;
if(!master.getItem(id).$count)
master.select(id);
}
}
},
{ view: "list", select: true, borderless: true, css: "webix_sidebar_popup_list", autoheight: true,
on:{
onAfterSelect: function(id){
this.getTopParentView().master.select(id);
}
}
}
]
}
};
webix.extend(popupConfig, config.popup||{}, true);
popup = webix.ui(popupConfig);
popup.master = this;
}
popup.attachEvent("onBeforeShow",function(){
return config.collapsed;
});
var master = this;
var h = webix.event(document.body,"mousemove", function(e){
var trg = e.target || e.srcElement;
if(!popup.config.hidden && !popup.$view.contains(trg) && !master.$view.firstChild.contains(trg)){
popup.hide();
}
});
this.attachEvent("onDestruct", function(){
if(webix.removeEvent)
webix.removeEvent(h);
if(popup)
popup.destructor();
});
config.popupId = popup.config.id;
},
getPopup: function(){
return webix.$$(this.config.popupId);
},
getPopupTitle: function(){
var popup = this.getPopup();
return popup.getBody().getChildViews()[0];
},
getPopupList: function(){
var popup = this.getPopup();
return popup.getBody().getChildViews()[1];
},
position_setter:function(value){
var newPos = value;
var oldPos = value=="left"?"right":"left";
webix.html.removeCss(this.$view, "webix_sidebar_"+oldPos);
webix.html.addCss(this.$view, "webix_sidebar_"+newPos, true);
var popup = this.getPopup();
if(popup){
var popupEl = popup.$view;
webix.html.removeCss(popupEl, "webix_sidebar_popup_"+oldPos);
webix.html.addCss(popupEl, "webix_sidebar_popup_"+newPos, true);
}
return value;
},
collapse: function(){
this.define("collapsed", true);
},
expand: function(){
this.define("collapsed", false);
},
toggle: function(){
var collapsed = !this.config.collapsed;
this.define("collapsed", collapsed);
},
collapsed_setter: function(value){
var width;
if(!value){
width = this._fullWidth;
}
else{
width = this.config.collapsedWidth;
this.closeAll();
}
if(!value){
this.type.collapsed = false;
webix.html.addCss(this.$view, "webix_sidebar_expanded", true);
}
else{
this.type.collapsed = true;
webix.html.removeCss(this.$view, "webix_sidebar_expanded");
}
this.define("width",width);
this.resize();
return value;
}
}, webix.ui.tree);
webix.type(webix.ui.tree, {
name:"sideBar",
height: "auto",
css: "webix_sidebar",
template: function(obj, common){
if(common.collapsed)
return common.icon(obj, common);
return common.arrow(obj, common)+common.icon(obj, common) +"<span>"+obj.value+"</span>";
},
arrow: function(obj, common){
var html = "";
var open = "";
for (var i=1; i<=obj.$level; i++){
if (i==obj.$level && obj.$count){
var className = "webix_sidebar_dir_icon webix_icon fa-angle-"+(obj.open?"down":"left");
html+="<span class='"+className+"'></span>";
}
}
return html;
},
icon:function(obj, common){
if(obj.icon)
return "<span class='webix_icon webix_sidebar_icon fa-"+obj.icon+"'></span>";
return "";
}
});

File diff suppressed because it is too large Load Diff

View File

@ -1,688 +0,0 @@
//~ Empresa Libre
//~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.net)
//~
//~ 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 <http://www.gnu.org/licenses/>.
var toolbar_banco = [
{view: 'richselect', id: 'lst_cuentas_banco', label: 'Cuenta',
labelWidth: 100, options: []},
{view: 'text', id: 'txt_cuenta_moneda', label: 'Moneda', readonly: true},
{view: 'currency2', id: 'txt_cuenta_saldo', label: 'Saldo', readonly: true,
inputAlign: 'right', value: 0}
]
var toolbar_filtro_cuenta = [
{view: 'richselect', id: 'filter_cuenta_year', label: 'Año',
labelAlign: 'right', labelWidth: 50, width: 150, options: []},
{view: 'richselect', id: 'filter_cuenta_month', label: 'Mes',
labelAlign: 'right', labelWidth: 50, width: 200, options: months},
{view: 'daterangepicker', id: 'filter_cuenta_dates', label: 'Fechas',
labelAlign: 'right', width: 300},
{},
]
var toolbar_movimientos_banco = [
{view: 'button', id: 'cmd_agregar_retiro', label: 'Retiro',
type: 'iconButton', autowidth: true, icon: 'minus'},
{view: 'button', id: 'cmd_agregar_deposito', label: 'Depósito',
type: 'iconButton', autowidth: true, icon: 'plus'},
{},
{view: 'button', id: 'cmd_complemento_pago', label: 'Generar Factura de Pago',
type: 'iconButton', autowidth: true, icon: 'file-code-o'},
{view: 'button', id: 'cmd_show_invoice_pay', label: 'Ver Facturas de Pago',
type: 'iconButton', autowidth: true, icon: 'table'},
{},
{view: 'button', id: 'cmd_cancelar_movimiento', label: 'Cancelar',
type: 'iconButton', autowidth: true, icon: 'ban'},
]
var grid_cuentabanco_cols = [
{id: 'id', header:'ID', hidden: true},
{id: 'fecha', header: 'Fecha', width: 150},
{id: 'numero_operacion', header: 'Referencia'},
{id: 'way_payment', header: 'Forma de Pago', hidden: true},
{id: 'descripcion', header: ['Descripción', {content: 'textFilter'}],
fillspace:true},
{id: 'retiro', header: ['Retiro', {content: 'numberFilter'}],
width: 125, format: webix.i18n.priceFormat, css: 'right'},
{id: 'deposito', header: ['Depósito', {content: 'numberFilter'}],
width: 125, format: webix.i18n.priceFormat, css: 'right'},
{id: 'saldo', header: ['Saldo'],
width: 125, format: webix.i18n.priceFormat, css: 'right'},
{id: 'invoice', header: ['FP'], width: 40, css: 'center', hidden: true},
]
var grid_cuentabanco = {
view: 'datatable',
id: 'grid_cuentabanco',
select: 'row',
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_cuentabanco_cols,
}
var grid_cfdi_por_pagar_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right'},
{id: 'id', header: 'ID', hidden: true},
{id: 'serie', header: 'Serie', adjust: 'data'},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'data', css: 'right'},
{id: 'uuid', header: 'UUID', width: 250, hidden: true},
{id: 'fecha', header: 'Fecha y Hora', width: 150, sort: 'date'},
{id: 'tipo_comprobante', header: 'Tipo', adjust: 'data'},
{id: 'estatus', header: 'Estatus', adjust: 'header'},
{id: 'cliente', header: ['Razón Social', {content: 'selectFilter'}],
fillspace:true, sort: 'string'},
{id: 'total', header: ['Total'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right', hidden: true},
{id: 'currency', header: ['Moneda', {content: 'selectFilter'}],
adjust: 'data', sort: 'string', hidden: true},
{id: 'total_mn', header: ['Total M.N'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right'},
{id: 'saldo', header: ['Saldo'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right'},
]
var grid_cfdi_este_deposito_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right'},
{id: 'id', header: 'ID', hidden: true},
{id: 'serie', header: 'Serie', adjust: 'data'},
{id: 'folio', header: 'Folio', adjust: 'data', css: 'right'},
{id: 'uuid', header: 'UUID', width: 250, hidden: true},
{id: 'fecha', header: 'Fecha y Hora', width: 150, sort: 'date'},
{id: 'tipo_comprobante', header: 'Tipo', adjust: 'data'},
{id: 'estatus', header: 'Estatus', adjust: 'header'},
{id: 'cliente', header: ['Razón Social'], fillspace: true},
{id: 'total', header: ['Total'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right', hidden: true},
{id: 'currency', header: ['Moneda', {content: 'selectFilter'}],
adjust: 'data', sort: 'string', hidden: true},
{id: 'total_mn', header: ['Total M.N'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right'},
{id: 'saldo', header: ['Saldo'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right'},
{id: 'this_pay', header: ['Este pago'], width: 125, sort: 'int', hidden: true,
format: webix.i18n.priceFormat, css: 'right', editor: 'text'},
{id: 'importe', header: ['Este pago'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right', editor: 'text'},
{id: 'type_change', header: ['T.C.'], width: 75, hidden: true,
format: format_currency4, css: 'right'},
]
var grid_cfdi_pay_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right',
footer: {content: 'countRows', colspan: 3, css: 'right'}},
{id: "id", header:"ID", hidden:true},
{id: 'serie', header: ["Serie"], adjust: "data", sort: 'string'},
{id: 'folio', header: ['Folio'], adjust: 'data',
sort: 'int', css: 'right', footer: {text: 'Facturas', colspan: 3}},
{id: "uuid", header: ["UUID"], adjust: "data",
sort:"string", hidden:true},
{id: "fecha", header: ["Fecha y Hora"],
adjust: "data", sort: "string"},
{id: "tipo_comprobante", header: ["Tipo"],
adjust: 'header', sort: 'string'},
{id: "estatus", header: ["Estatus"],
adjust: "data", sort:"string"},
{id: 'cliente', header: ['Razón Social'], fillspace: true},
{id: 'xml', header: 'XML', adjust: 'data', template: get_icon('xml')},
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
{id: 'email', header: '@', adjust: 'data', template: get_icon('email')}
]
var grid_pay_related_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right'},
{id: 'id', header: 'ID', hidden: true},
{id: 'serie', header: 'Serie', adjust: 'data'},
{id: 'folio', header: 'Folio', adjust: 'data', css: 'right'},
{id: 'uuid', header: 'UUID', width: 250, hidden: true},
{id: 'fecha', header: 'Fecha y Hora', width: 150, sort: 'date'},
{id: 'tipo_comprobante', header: 'Tipo', adjust: 'data'},
{id: 'estatus', header: 'Estatus', adjust: 'header'},
{id: 'cliente', header: ['Razón Social'], fillspace: true},
{id: 'total', header: ['Total'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right'},
{id: 'saldo', header: ['Saldo'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right', css: 'right',
footer: 'Importe Depósito'},
{id: 'importe', header: ['Este pago'], width: 125, sort: 'int',
format: webix.i18n.priceFormat, css: 'right',
footer: {content: 'summColumn', css: 'right'}},
]
var grid_cfdi_por_pagar = {
view: 'datatable',
id: 'grid_cfdi_por_pagar',
select: 'row',
multiselect: true,
autoConfig: false,
adjust: true,
height: 250,
resizeColumn: true,
headermenu: true,
drag: true,
columns: grid_cfdi_por_pagar_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var grid_cfdi_este_deposito = {
view: 'datatable',
id: 'grid_cfdi_este_deposito',
select: 'row',
multiselect: true,
autoConfig: false,
adjust: true,
height: 200,
resizeColumn: true,
headermenu: true,
drag: true,
editable: true,
columns: grid_cfdi_este_deposito_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var grid_cfdi_pay = {
view: 'datatable',
id: 'grid_cfdi_pay',
select: 'row',
autoConfig: false,
adjust: true,
autoheight: true,
resizeColumn: true,
headermenu: true,
columns: grid_cfdi_pay_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var grid_pay_related = {
view: 'datatable',
id: 'grid_pay_related',
select: 'row',
autoConfig: false,
adjust: true,
autoheight: true,
resizeColumn: true,
headermenu: true,
footer: true,
columns: grid_pay_related_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var grid_bank_invoice_pay_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right',
footer: {content: 'countRows', colspan: 3, css: 'right'}},
{id: "id", header:"ID", hidden:true},
{id: 'serie', header: ["Serie"], adjust: "data", sort: 'string',
template: '{common.subrow()} #serie#'},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header',
sort: 'int', css: 'right', footer: {text: 'Facturas', colspan: 3}},
{id: "uuid", header: ["UUID", {content: "textFilter"}], adjust: "data",
sort:"string", hidden:true},
{id: "fecha", header: ["Fecha y Hora"], width: 135,
sort: "string"},
{id: "tipo_comprobante", header: ["Tipo"], adjust: 'data',
sort: 'string'},
{id: "estatus", header: ["Estatus", {content: "selectFilter"}],
adjust: "header", sort:"string"},
{id: 'movimiento', header: ['Movimiento', {content: "selectFilter"}],
adjust: 'data', sort: 'string'},
{id: 'total', header: ['Total', {content: 'numberFilter'}],
width: 125, sort: 'int', format: webix.i18n.priceFormat, css: 'right',
hidden: true},
{id: 'currency', header: ['Moneda', {content: 'selectFilter'}],
adjust: 'data', sort: 'string', hidden: true},
{id: 'total_mn', header: ['Total M.N.', {content: 'numberFilter'}],
width: 150, sort: 'int', format: webix.i18n.priceFormat, css: 'right',
footer: {content: 'summTimbrada', css: 'right'}},
{id: 'cliente', header: ['Razón Social', {content: 'selectFilter'}],
fillspace: true, sort: 'string'},
{id: 'xml', header: 'XML', adjust: 'data', template: get_icon('xml')},
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
//~ {id: 'ods', header: 'ODS', adjust: 'data', template: get_icon('table')},
//~ {id: 'zip', header: 'ZIP', adjust: 'data', template: get_icon('zip')},
{id: 'email', header: '@', adjust: 'data', template: get_icon('email')}
]
var grid_bank_invoice_pay = {
view: 'datatable',
id: 'grid_bank_invoice_pay',
select: 'row',
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_bank_invoice_pay_cols,
scheme:{
$change:function(item){
if (item.estatus == 'Cancelada'){
item.$css = 'cancel'
}
}
},
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var toolbar_banco_retiro = [
{view: 'label', label: 'Agregar retiro de banco', id: 'title_bank_retiro'},
{view: 'button', id: 'cmd_guardar_retiro', label: 'Guardar Retiro',
type: 'iconButton', autowidth: true, icon: 'minus'},
{view: 'icon', click: '$$("multi_bancos").setValue("banco_home")',
icon: 'times-circle'}
]
var toolbar_banco_deposito = [
{view: 'label', label: 'Agregar depósito de banco', id: 'title_bank_deposit'},
{view: 'button', id: 'cmd_guardar_deposito', label: 'Guardar Depósito',
type: 'iconButton', autowidth: true, icon: 'plus'},
{view: 'icon', click: '$$("multi_bancos").setValue("banco_home")',
icon: 'times-circle'}
]
var toolbar_bank_pay = [
{view: 'label', label: 'Factura de pago'},
{},
{view: 'button', id: 'cmd_pay_stamp', label: 'Timbrar',
type: 'iconButton', autowidth: true, icon: 'ticket'},
{view: 'button', id: 'cmd_pay_cancel', label: 'Cancelar',
type: 'iconButton', autowidth: true, icon: 'minus'},
{view: 'button', id: 'cmd_pay_delete', label: 'Eliminar',
type: 'iconButton', autowidth: true, icon: 'ban'},
{view: 'checkbox', id: 'chk_pay_close_when_stamp',
label: 'Cerrar al timbrar', tooltip: 'Cerrar al timbrar'},
{},
{view: 'icon', click: '$$("multi_bancos").setValue("banco_home")',
icon: 'times-circle'}
]
var toolbar_bank_invoice_pay = [
{view: 'label', label: 'Administración de Facturas de Pago'},
{},
{view: 'icon', click: '$$("multi_bancos").setValue("banco_home")',
icon: 'times-circle'}
]
var toolbar_bank_invoice_pay_filter = [
{view: 'richselect', id: 'filter_invoice_pay_year', label: 'Año',
labelAlign: 'right', labelWidth: 50, width: 150, options: []},
{view: 'richselect', id: 'filter_invoice_pay_month', label: 'Mes',
labelAlign: 'right', labelWidth: 50, width: 175, options: months},
{view: 'daterangepicker', id: 'filter_invoice_pay_dates', label: 'Fechas',
labelAlign: 'right', width: 275},
{view: 'button', id: 'cmd_invoice_pay_sat', label: 'SAT',
type: 'iconButton', autowidth: true, icon: 'check-circle'},
{view: 'button', id: 'cmd_invoice_pay_cancel', label: 'Cancelar',
type: 'iconButton', autowidth: true, icon: 'ban'},
{},
]
var controls_banco_retiro = [
{view: 'toolbar', elements: toolbar_banco_retiro},
{cols: [
{view: 'datepicker', id: 'date_retiro', name: 'retiro_fecha',
label: 'Fecha', format: '%d-%M-%Y', labelAlign: 'right',
required: true, invalidMessage: 'Selecciona una fecha',
labelWidth: 125},
{view: 'search', id: 'time_retiro', name: 'retiro_hora',
label: 'Hora', icon: 'clock-o', labelAlign: 'right',
pattern:{mask: '##:##:##', allow:/[0-9]/g}, required: true,
invalidMessage: 'Captura una hora'},
{view: 'text', id: 'retiro_referencia', name: 'retiro_referencia',
label: 'Referencia', labelAlign: 'right'},
]},
{cols: [
{view: 'richselect', id: 'lst_retiro_forma_pago',
name: 'retiro_forma_pago', label: 'Forma de Pago', required: true,
options: [], labelWidth: 125, labelAlign: 'right'},
{view: 'currency2', type: 'text', id: 'txt_retiro_importe',
name: 'retiro_importe', label: 'Importe', labelAlign: 'right',
required: true, invalidMessage: 'Captura un valor númerico',
inputAlign: 'right', value: ''}
]},
{cols: [
{view: 'text', id: 'retiro_descripcion', name: 'retiro_descripcion',
label: 'Descripción', labelAlign: 'right', required: true,
labelWidth: 125},
]},
]
var controls_banco_deposito = [
{view: 'toolbar', elements: toolbar_banco_deposito},
{cols: [
{view: 'datepicker', id: 'date_deposito', name: 'deposito_fecha',
label: 'Fecha', format: '%d-%M-%Y', labelAlign: 'right',
required: true, invalidMessage: 'Selecciona una fecha',
labelWidth: 125},
{view: 'search', id: 'time_deposito', name: 'deposito_hora',
label: 'Hora', icon: 'clock-o', labelAlign: 'right',
pattern:{mask: '##:##:##', allow:/[0-9]/g}, required: true,
invalidMessage: 'Captura una hora'},
{view: 'text', id: 'deposito_referencia', name: 'deposito_referencia',
label: 'Referencia', labelAlign: 'right'},
]},
{cols: [
{view: 'richselect', id: 'lst_deposito_forma_pago',
name: 'deposito_forma_pago', label: 'Forma de Pago', required: true,
options: [], labelWidth: 125, labelAlign: 'right'},
{view: 'currency2', type: 'text', id: 'txt_deposito_importe',
name: 'deposito_importe', label: 'Importe', labelAlign: 'right',
required: true, invalidMessage: 'Captura un valor númerico',
inputAlign: 'right', value: ''},
{view: 'currency4', type: 'text', id: 'deposit_type_change',
name: 'deposit_type_change', label: 'T.C.', labelAlign: 'right',
required: false, invalidMessage: 'Captura un valor númerico',
inputAlign: 'right', value: '1.0000', width: 175, hidden: true}
]},
{cols: [
{view: 'textarea', id: 'deposito_descripcion', label: 'Descripción',
name: 'deposito_descripcion', labelAlign: 'right', required: true,
labelWidth: 125, height: 70},
]},
{cols: [
{view: 'label', label: '<b>Facturas por pagar: </b>'},
{view: 'richselect', id: 'lst_partner_account_bank', hidden: true,
name: 'partner_account_bank', label: 'Cuenta Origen', required: false,
options: [], labelWidth: 125, labelAlign: 'right'},
{},
{view: 'button', id: 'cmd_invoice_payed', label: 'Solo marcar pagada',
type: 'iconButton', autowidth: true, icon: 'check-circle',
tooltip: 'No afecta a saldos'},
]},
grid_cfdi_por_pagar,
{view: 'label', label: '<b>Facturas a pagar en este depósito: </b>'},
grid_cfdi_este_deposito,
]
var controls_bank_pay = [
{view: 'toolbar', elements: toolbar_bank_pay},
{view: 'label', label: '<b>Este depósito: </b>'},
{cols: [
{view: 'datepicker', id: 'pay_date', name: 'pay_date',
label: 'Fecha', format: '%d-%M-%Y', labelAlign: 'right',
required: true, invalidMessage: 'Selecciona una fecha',
labelWidth: 125, readonly: true},
{view: 'search', id: 'pay_time', name: 'pay_time', label: 'Hora',
icon: 'clock-o', labelAlign: 'right', required: true,
readonly: true,
invalidMessage: 'Captura una hora'},
{view: 'text', id: 'pay_reference', name: 'pay_reference',
label: 'Referencia', labelAlign: 'right', readonly: true},
]},
{cols: [
{view: 'richselect', id: 'pay_way_payment', readonly: true,
name: 'pay_way_payment', label: 'Forma de Pago', required: true,
options: [], labelWidth: 125, labelAlign: 'right'},
{view: 'currency', type: 'text', id: 'pay_import', name: 'pay_import',
label: 'Importe', labelAlign: 'right', required: true, readonly: true,
invalidMessage: 'Captura un valor númerico', inputAlign: 'right'}
]},
{cols: [
{view: 'textarea', id: 'pay_description', label: 'Descripción',
name: 'pay_description', labelAlign: 'right', required: true,
labelWidth: 125, height: 70, readonly: true},
]},
{view: 'label', label: '<b>Facturas de pago de este depósito: </b>'},
grid_cfdi_pay,
{view: 'label', label: '<b>Facturas relacionadas en este pago: </b>'},
grid_pay_related
]
var controls_bank_invoice_pay = [
{view: 'toolbar', elements: toolbar_bank_invoice_pay},
{view: 'toolbar', elements: toolbar_bank_invoice_pay_filter},
grid_bank_invoice_pay
]
var form_banco_retiro = {
type: 'space',
responsive: true,
cols: [{
view: 'form',
id: 'form_banco_retiro',
complexData: true,
scroll: true,
elements: controls_banco_retiro,
}],
}
var form_banco_deposito = {
type: 'space',
responsive: true,
cols: [{
view: 'form',
id: 'form_banco_deposito',
complexData: true,
scroll: true,
elements: controls_banco_deposito,
}],
}
var form_bank_pay = {
type: 'space',
responsive: true,
cols: [{
view: 'form',
id: 'form_bank_pay',
complexData: true,
scroll: true,
elements: controls_bank_pay,
}],
}
var form_bank_invoice_pay = {
type: 'space',
responsive: true,
cols: [{
view: 'form',
id: 'form_bank_invoice_pay',
complexData: true,
scroll: true,
elements: controls_bank_invoice_pay,
}],
}
var multi_bancos = {
id: 'multi_bancos',
animate: true,
cells:[
{id: 'banco_home', rows:[
{view: 'toolbar', elements: toolbar_banco},
{view: 'toolbar', elements: toolbar_filtro_cuenta},
{view: 'toolbar', elements: toolbar_movimientos_banco},
grid_cuentabanco,
]},
{id: 'banco_retiro', rows: [form_banco_retiro]},
{id: 'banco_deposito', rows: [form_banco_deposito]},
{id: 'bank_pay', rows: [form_bank_pay]},
{id: 'bank_invoice_pay', rows: [form_bank_invoice_pay]}
],
}
var title_partners = 'Administración de Bancos'
var app_bancos = {
id: 'app_bancos',
rows:[
{view: 'template', id: 'th_bancos', type: 'header',
template: title_partners},
multi_bancos
]
}
var win_body_mov_description = {rows: [
{minHeight: 5, maxHeight: 5},
{view: 'textarea', id: 'mov_description', name: 'mov_description',
height: 300},
{minHeight: 5, maxHeight: 5},
{cols: [{},
{view: 'button', id: 'cmd_save_mov_description', autowidth: true,
label: 'Guardar', type: 'iconButton', icon: 'save', hotkey: 'Ctrl+enter'},
{maxWidth: 50},
{view: 'button', id: 'cmd_close_mov_description', autowidth: true,
label: 'Cerrar', type: 'iconButton', icon: 'times-circle', hotkey: 'esc'},
{}]},
{minHeight: 5, maxHeight: 5},
],}
var win_mov_description = {
init: function(){
webix.ui({
view: 'window',
id: 'win_mov_description',
height: 400,
width: 600,
modal: true,
position: 'center',
head: 'Descripción del movimiento',
body: win_body_mov_description,
})
$$('cmd_save_mov_description').attachEvent('onItemClick', cmd_save_mov_description_click)
$$('cmd_close_mov_description').attachEvent('onItemClick', cmd_close_mov_description_click)
}
}
var opt_reasons_cancel_pay = [
{id: '', value: ''},
{id: '01', value: '[01] Comprobante emitido con errores con relación'},
{id: '02', value: '[02] Comprobante emitido con errores sin relación'},
{id: '03', value: '[03] No se llevó acabo la operación'},
{id: '04', value: '[04] Operación nominativa relacionada en una factura global'},
]
var body_invoice_cancel_pay = {rows: [{minHeight: 15},
{view: 'richselect', id: 'lst_reasons_cancel', labelPosition: 'top', label: 'Razón de cancelación', options: opt_reasons_cancel_pay, value: '', width: 400},
{view: 'text', id: 'txt_cancel_uuid', labelPosition: 'top', label: 'UUID que sustituye', width: 400},
{view: 'label', label: 'Esta acción no se puede deshacer', autowidth: true, align: 'center'},
{view: 'label', label: '¿Estás seguro de continuar?', autowidth: true, align: 'center'},
{cols: [{},
{view: 'button', id: 'cmd_invoice_cancel_pay', width: 100, label: 'Cancelar', type: 'danger', icon: 'ban'},
{maxWidth: 25},
{view: 'button', id: 'cmd_win_cancel_pay_close', width: 100, label: 'Cerrar'},
{}
]},
{minHeight: 20},
]}
var win_invoice_cancel_pay = {
init: function(){
webix.ui({
view: 'window',
id: 'win_invoice_cancel_pay',
modal: true,
width: 400,
position: 'center',
head: 'Cancelar CFDI',
body: body_invoice_cancel_pay,
})
$$('cmd_invoice_cancel_pay').attachEvent('onItemClick', cmd_invoice_cancel_pay_click)
$$('cmd_win_cancel_pay_close').attachEvent('onItemClick', cmd_win_cancel_pay_close_click)
}
}
var body_invoice_cancel_pay2 = {rows: [{minHeight: 15},
{view: 'richselect', id: 'lst_reasons_cancel2', labelPosition: 'top', label: 'Razón de cancelación', options: opt_reasons_cancel_pay, value: '', width: 400},
{view: 'text', id: 'txt_cancel_uuid2', labelPosition: 'top', label: 'UUID que sustituye', width: 400},
{view: 'label', label: 'Esta acción no se puede deshacer', autowidth: true, align: 'center'},
{view: 'label', label: '¿Estás seguro de continuar?', autowidth: true, align: 'center'},
{cols: [{},
{view: 'button', id: 'cmd_invoice_cancel_pay2', width: 100, label: 'Cancelar', type: 'danger', icon: 'ban'},
{maxWidth: 25},
{view: 'button', id: 'cmd_win_cancel_pay_close2', width: 100, label: 'Cerrar'},
{}
]},
{minHeight: 20},
]}
var win_invoice_cancel_pay2 = {
init: function(){
webix.ui({
view: 'window',
id: 'win_invoice_cancel_pay2',
modal: true,
width: 400,
position: 'center',
head: 'Cancelar CFDI de Pago',
body: body_invoice_cancel_pay2,
})
$$('cmd_invoice_cancel_pay2').attachEvent('onItemClick', cmd_invoice_cancel_pay2_click)
$$('cmd_win_cancel_pay_close2').attachEvent('onItemClick', cmd_win_cancel_pay_close2_click)
}
}

View File

@ -1,69 +0,0 @@
var msg_rfc = 'El RFC es requerido'
var form_controls_empresa = [
{view: 'text', label: 'RFC', id: 'txt_alta_rfc', name: 'alta_rfc',
labelPosition: 'top', required: true, invalidMessage: msg_rfc},
{margin: 10, cols:[{}, {view: 'button', value: 'Agregar RFC',
click: 'validate_nuevo_rfc', hotkey: 'enter'}, {}]}
]
var msg_header = '<font color="#610B0B">Bienvenido a Empresa Libre</font>'
var header = [
{view: 'label', label: '<b><font color="#610B0B">Alta de Emisor</font></b>'},
{},
{view: 'button', type: 'icon', width: 40, css: 'app_button',
icon: 'home', click: 'window.location = "/"'},
]
var footer = [
{},
{view: 'button', value: 'Respaldar BD', click: 'cmd_respaldar_bd'},
{},
]
var grid_empresas_cols = [
{id: 'delete', header: '', width: 30, css: 'delete'},
{id: 'rfc', header: 'RFC Emisor', fillspace: true,
footer: {content: 'countRows', css: 'right'}},
]
var grid_empresas = {
view: 'datatable',
id: 'grid_empresas',
select: 'row',
url: '/values/empresas',
adjust: true,
autoheight: true,
headermenu: true,
footer: true,
columns: grid_empresas_cols,
}
var ui_empresas = {
rows: [
{maxHeight: 50},
{view: 'template', template: msg_header, maxHeight: 50, css: 'login_header'},
{maxHeight: 50},
{cols: [{}, {type: 'space', padding: 5,
rows: [
{view: 'toolbar', elements: header},
{
container: 'form_empresas',
view: 'form',
id: 'form_empresas',
width: 400,
elements: form_controls_empresa,
rules:{
alta_rfc:function(value){ return value.trim() != '';},
}
},
grid_empresas,
{view: 'toolbar', elements: footer},
]}, {}, ]
},
]
}

File diff suppressed because it is too large Load Diff

View File

@ -16,16 +16,16 @@ var form_controls = [
]
var msg_header = 'Bienvenido a Empresa Libre'
var ui_login = {
rows: [
{maxHeight: 50},
{view: 'template', id: 'title_login', template: '', maxHeight: 50,
css: 'login_header'},
{view: 'template', template: msg_header, maxHeight: 50, css: 'login_header'},
{maxHeight: 50},
{cols: [{}, {type: 'space', padding: 5,
rows: [
{view: 'template', type: 'header',
template: '<font color="#610B0B">Acceso al sistema</font>'},
{view: 'template', template: 'Acceso al sistema', type: 'header'},
{
container: 'form_login',
view: 'form',

View File

@ -1,17 +1,14 @@
var menu_data = [
{id: 'app_home', icon: 'dashboard', value: 'Inicio'},
{id: 'app_partners', icon: 'address-book-o', value: 'Clientes y Proveedores'},
{id: 'app_partners', icon: 'users', value: 'Clientes y Proveedores'},
{id: 'app_products', icon: 'server', value: 'Productos y Servicios'},
{id: 'app_bancos', icon: 'university', value: 'Bancos'},
{id: 'app_invoices', icon: 'file-code-o', value: 'Facturas'},
]
{id: 'app_invoices', icon: 'cart-plus', value: 'Facturas'},
];
var sidebar = {
view: 'sidebar',
id: 'main_sidebar',
data: menu_data,
ready: function(){
this.select('app_home');
@ -21,8 +18,8 @@ var sidebar = {
onAfterSelect: function(id){
$$('multi').setValue(id)
}
},
}
}
};
var multi_main = {
@ -36,13 +33,9 @@ var multi_main = {
},
app_partners,
app_products,
app_bancos,
app_school,
app_nomina,
app_tickets,
app_invoices,
],
}
]
};
var menu_user = {
@ -56,33 +49,20 @@ var menu_user = {
type: {
subsign: true,
},
}
var link_blog = "<a class='link_default' target='_blank' href='https://empresalibre.net/blog'>Blog</a>";
var link_forum = "<a class='link_default' target='_blank' href='https://git.cuates.net/elmau/empresa-libre/issues'>Foro</a>";
var link_doc = "<a class='link_default' target='_blank' href='https://empresalibre.net/docs'>Doc</a>";
};
var ui_main = {
rows: [
{view: 'toolbar', padding: 3, elements: [
{view: 'button', type: 'icon', icon: 'bars',
width: 37, align: 'left', css: 'app_button', click: function(){
$$('main_sidebar').toggle()
$$('$sidebar1').toggle()
}
},
{view: 'label', id: 'lbl_title_main', label: '<b>Empresa Libre</b>'},
{view: 'label', label: 'Empresa Libre'},
{},
{view: 'label', id: 'lbl_blog', label: link_blog, align: 'right', width: 30},
{view: 'label', id: 'lbl_forum', label: link_forum, align: 'right', width: 30},
{view: 'label', id: 'lbl_doc', label: link_doc, align: 'right', width: 25},
menu_user,
{view: 'button', id: 'cmd_update_timbres', type: 'icon', width: 45,
css: 'app_button', icon: 'bell-o', badge: 0},
{view: 'button', type: 'icon', width: 30, css: 'app_button',
icon: 'cogs', id: 'cmd_ir_al_admin', hidden: true,
click: 'cmd_ir_al_admin_click'}
{view: 'button', type: 'icon', width: 45, css: 'app_button', icon: 'bell-o', badge: 1}
]
},
{

View File

@ -1,288 +0,0 @@
var toolbar_nomina = [
{view: 'button', id: 'cmd_empleados', label: 'Empleados', type: 'iconButton',
autowidth: true, icon: 'users'},
{},
{view: 'button', id: 'cmd_nomina_report', label: 'Reporte', type: 'iconButton',
autowidth: true, icon: 'table'},
{},
{view: 'button', id: 'cmd_nomina_delete', label: 'Eliminar',
type: 'iconButton', autowidth: true, icon: 'minus'},
]
var toolbar_nomina_util = [
{view: 'button', id: 'cmd_nomina_import', label: 'Importar',
type: 'iconButton', autowidth: true, icon: 'upload'},
{view: 'button', id: 'cmd_nomina_timbrar', label: 'Timbrar',
type: 'iconButton', autowidth: true, icon: 'ticket'},
{view: 'button', id: 'cmd_nomina_sat', label: 'SAT',
type: 'iconButton', autowidth: true, icon: 'check-circle'},
{view: 'button', id: 'cmd_nomina_log', label: 'Log',
type: 'iconButton', autowidth: true, icon: 'download'},
{view: 'button', id: 'cmd_nomina_download', label: 'Descargar',
type: 'iconButton', autowidth: true, icon: 'download'},
{},
{view: 'button', id: 'cmd_nomina_cancel', label: 'Cancelar',
type: 'iconButton', autowidth: true, icon: 'ban'},
]
var toolbar_nomina_filter = [
{view: 'richselect', id: 'filter_year_nomina', label: 'Año',
labelAlign: 'right', labelWidth: 50, width: 150, options: []},
{view: 'richselect', id: 'filter_month_nomina', label: 'Mes',
labelAlign: 'right', labelWidth: 50, width: 200, options: months},
{view: 'daterangepicker', id: 'filter_dates_nomina', label: 'Fechas',
labelAlign: 'right', width: 300},
{},
{view: 'button', id: 'cmd_nomina_without_stamp', label: 'Sin Timbrar',
type: 'iconButton', autowidth: true, icon: 'filter'},
]
var grid_cols_nomina = [
{id: 'index', header: '#', adjust: 'data', css: 'right',
footer: {content: 'countRows', colspan: 3, css: 'right'}},
{id: "id", header:"ID", hidden:true},
{id: "serie", header: ["Serie"], adjust: "header"},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header',
sort: 'int', css: 'right', footer: {text: 'Recibos', colspan: 3}},
{id: "uuid", header: ["UUID", {content: "textFilter"}], adjust: "data",
sort:"string", hidden:true},
{id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string"},
{id: "estatus", header: ["Estatus", {content: "selectFilter"}],
adjust: "data", sort:"string"},
{id: 'fecha_pago', header: ['Fecha de Pago', {content: 'selectFilter'}],
adjust: 'data', sort: 'string'},
{id: 'total', header: ['Total', {content: 'numberFilter'}], width: 150,
sort: 'int', format: webix.i18n.priceFormat, css: 'right',
footer: {content: 'summTimbradaN', css: 'right'}},
{id: "empleado", header: ["Empleado", {content: "selectFilter"}],
fillspace:true, sort:"string", footer: {text: '$ 0.00'}},
{id: 'xml', header: 'XML', adjust: 'data', template: get_icon('xml')},
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
{id: 'email', header: '@', adjust: 'data', template: get_icon('email')}
]
var grid_nomina = {
view: 'datatable',
id: 'grid_nomina',
select: 'row',
multiselect: true,
scrollY: true,
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_cols_nomina,
scheme:{
$change:function(item){
if (item.estatus == 'Cancelado'){
item.$css = 'cancel'
}
}
},
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var rows_nomina_home = [
{view: 'toolbar', elements: toolbar_nomina},
{view: 'toolbar', elements: toolbar_nomina_util},
{view: 'toolbar', elements: toolbar_nomina_filter},
grid_nomina,
]
var toolbar_nomina_empleados = [
{view: 'button', id: 'cmd_new_empleado', label: 'Nuevo', type: 'iconButton',
autowidth: true, icon: 'user-plus'},
{view: 'button', id: 'cmd_edit_empleado', label: 'Editar', type: 'iconButton',
autowidth: true, icon: 'user'},
{view: 'button', id: 'cmd_delete_empleado', label: 'Eliminar', type: 'iconButton',
autowidth: true, icon: 'user-times'},
{},
{view: 'button', id: 'cmd_import_empleados', label: 'Importar',
type: 'iconButton', autowidth: true, icon: 'upload'},
{},
{view: 'button', id: 'cmd_close_empleados', label: 'Cerrar', type: 'iconButton',
autowidth: true, icon: 'times-circle-o'},
]
var grid_cols_empleados = [
{id: 'index', header: '#', adjust: 'data', css: 'right',
footer: {content: 'countRows', colspan: 3, css: 'right'}},
{id: "id", header:"ID", hidden:true},
{id: "num_empleado", header: ["No Empleado"], adjust: "data"},
{id: "rfc", header: ["RFC", {content: 'textFilter'}], adjust: "data", sort: "string"},
{id: "curp", header: ["CURP"], adjust: "data", hidden:true, sort: "string"},
{id: "nombre_completo", header: ["Empleado", {content: 'textFilter'}],
adjust: "data", fillspace: true, sort: "string"},
{id: 'imss', header: ['IMSS'], adjust: 'data'},
{id: 'salario_base', header: ['Salario Base'], adjust: 'header',
format: webix.i18n.priceFormat, css: 'right'},
{id: 'salario_diario', header: ['Salario Diario'], adjust: 'header',
format: webix.i18n.priceFormat, css: 'right'},
{id: "fecha_ingreso", header: ["Fecha de Ingreso"], adjust: "header",
sort: "string"},
]
var grid_empleados = {
view: 'datatable',
id: 'grid_employees',
select: 'row',
scrollY: true,
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_cols_empleados,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var rows_nomina_empleados = [
{view: 'toolbar', elements: toolbar_nomina_empleados},
grid_empleados,
]
var multi_nomina = {
id: 'multi_nomina',
view: 'multiview',
animate: true,
cells:[
{id: 'nomina_home', rows: rows_nomina_home},
{id: 'nomina_empleados', rows: rows_nomina_empleados},
],
}
var app_nomina = {
id: 'app_nomina',
rows:[
{view: 'template', type: 'header', template: 'Timbrado de Nómina'},
multi_nomina
],
}
var body_import_employees = {rows: [
{view: 'form', id: 'form_upload_employees', rows: [
{cols: [{},
{view: 'uploader', id: 'up_employees', autosend: false,
link: 'lst_upload_employees', value: 'Seleccionar Plantilla',
upload: '/files/employees'}, {}]},
{cols: [
{view: 'list', id: 'lst_upload_employees', name: 'lst_employees',
type: 'uploader', autoheight: true, borderless: true}]},
{cols: [{}, {view: 'button', id: 'cmd_import_employees',
label: 'Importar Empleados'}, {}]},
]},
],}
var win_import_employees = {
init: function(){
webix.ui({
view: 'window',
id: 'win_import_employees',
width: 400,
modal: true,
position: 'center',
head: 'Importar Empleados',
body: body_import_employees,
})
$$('cmd_import_employees').attachEvent('onItemClick', cmd_import_employees_click)
$$('up_employees').attachEvent('onUploadComplete', up_employees_upload_complete)
}
}
var body_import_nomina = {rows: [
{view: 'form', id: 'form_upload_nomina', rows: [
{cols: [{},
{view: 'uploader', id: 'up_nomina', autosend: false,
link: 'lst_upload_nomina', value: 'Seleccionar Plantilla',
upload: '/files/nomina'}, {}]},
{cols: [
{view: 'list', id: 'lst_upload_nomina', name: 'lst_nomina',
type: 'uploader', autoheight: true, borderless: true}]},
{cols: [{}, {view: 'button', id: 'cmd_import_template_nomina',
label: 'Importar Nómina'}, {}]},
]},
],}
var win_import_nomina = {
init: function(){
webix.ui({
view: 'window',
id: 'win_import_nomina',
width: 400,
modal: true,
position: 'center',
head: 'Importar Nómina',
body: body_import_nomina,
})
$$('cmd_import_template_nomina').attachEvent('onItemClick', cmd_import_template_nomina_click)
$$('up_nomina').attachEvent('onUploadComplete', up_nomina_upload_complete)
}
}
var opt_reasons_cancel_nomina = [
{id: '', value: ''},
{id: '01', value: '[01] Comprobante emitido con errores con relación'},
{id: '02', value: '[02] Comprobante emitido con errores sin relación'},
{id: '03', value: '[03] No se llevó acabo la operación'},
{id: '04', value: '[04] Operación nominativa relacionada en una factura global'},
]
var body_invoice_cancel_nomina = {rows: [{minHeight: 15},
{view: 'richselect', id: 'lst_reasons_cancel', labelPosition: 'top', label: 'Razón de cancelación', options: opt_reasons_cancel_pay, value: '', width: 400},
{view: 'text', id: 'txt_cancel_uuid', labelPosition: 'top', label: 'UUID que sustituye', width: 400},
{view: 'label', label: 'Esta acción no se puede deshacer', autowidth: true, align: 'center'},
{view: 'label', label: '¿Estás segura de continuar?', autowidth: true, align: 'center'},
{cols: [{},
{view: 'button', id: 'cmd_invoice_cancel_nomina', width: 100, label: 'Cancelar', type: 'danger', icon: 'ban'},
{maxWidth: 25},
{view: 'button', id: 'cmd_win_cancel_nomina_close', width: 100, label: 'Cerrar'},
{}
]},
{minHeight: 20},
]}
var win_invoice_cancel_nomina = {
init: function(){
webix.ui({
view: 'window',
id: 'win_invoice_cancel_nomina',
modal: true,
width: 400,
position: 'center',
head: 'Cancelar CFDI',
body: body_invoice_cancel_nomina,
})
$$('cmd_invoice_cancel_nomina').attachEvent('onItemClick', cmd_invoice_cancel_nomina_click)
$$('cmd_win_cancel_nomina_close').attachEvent('onItemClick', cmd_win_cancel_nomina_close_click)
}
}

View File

@ -1,18 +1,3 @@
//~ Empresa Libre
//~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.net)
//~
//~ 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 <http://www.gnu.org/licenses/>.
var toolbar_partners = [
@ -22,22 +7,17 @@ var toolbar_partners = [
autowidth: true, icon: 'user'},
{view: 'button', id: 'cmd_delete_partner', label: 'Eliminar', type: 'iconButton',
autowidth: true, icon: 'user-times'},
{},
//~ {view: 'button', id: 'cmd_partner_zero', label: 'Saldo 0', type: 'iconButton',
//~ autowidth: true, icon: 'power-off', disabled: true},
]
var grid_partners_cols = [
{id: 'index', header:'#', css: 'right',
footer: {content: 'countRows', colspan: 2, css: 'right'}},
{id: 'id', header: 'Clave', sort: 'int', css: 'right'},
{id: 'rfc', header: ['RFC', {content: 'textFilter'}], adjust: 'data',
{id: 'index', header:'#', adjust:'data', css: 'right',
footer: {content: 'rowCount', colspan: 2, css: 'right'}},
{id: 'id', header: 'Clave', adjust:'data', sort: 'int', css: 'right'},
{id: 'rfc', header: ['RFC', {content: 'textFilter'}], adjust:'data',
sort: 'string', footer: {text: 'Clientes y Proveedores', colspan: 2}},
{id: 'nombre', header: ['Razón Social', {content: 'textFilter'}],
fillspace:true, sort: 'string'},
{id: 'saldo_cliente', header: ['Saldo Cliente', {content: 'numberFilter'}],
width: 150, sort: 'int', format: webix.i18n.priceFormat, css: 'right'},
]
@ -50,15 +30,6 @@ var grid_partners = {
resizeColumn: true,
headermenu: true,
columns: grid_partners_cols,
pager: 'pager_clientes',
//~ datafetch: 100,
//~ loadahead: 100,
//~ url: '/partners',
ready:function(){
this.adjustColumn('index');
this.adjustColumn('id');
this.adjustColumn('rfc');
},
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
@ -84,10 +55,7 @@ var controls_fiscales = [
invalidMessage: 'El Tipo de Persona es requerido'},
{template: 'Dirección Fiscal', type: 'section'},
{cols: [{view: 'text', id: 'rfc', name: 'rfc', label: 'RFC: ', width: 300,
required: true, invalidMessage: 'RFC inválido', attributes: {maxlength: 13}},
{view: 'text', id: 'id_fiscal', name: 'id_fiscal', label: 'ID Fiscal: ',
width: 400, required: false, attributes: {maxlength: 40}, hidden: true},
{}]},
required: true, invalidMessage: 'RFC inválido', attributes: {maxlength: 13}},{}]},
{view: 'text', id: 'nombre', name: 'nombre', label: 'Razón Social: ', required: true,
invalidMessage: 'La Razón Social es requerida'},
{view: 'text', id: 'calle', name: 'calle', label: 'Calle: '},
@ -96,19 +64,19 @@ var controls_fiscales = [
{cols: [{view: 'text', id: 'no_interior', name: 'no_interior', width: 300,
label: 'No Interior: '},{}]},
{cols: [{view: 'search', id: 'codigo_postal', name: 'codigo_postal',
width: 300, label: 'C.P.: ', attributes: {maxlength: 5}, required: true},{}]},
width: 300, label: 'C.P.: ', attributes: {maxlength: 5}},{}]},
{view: 'text', id: 'colonia', name: 'colonia', label: 'Colonia: '},
{view: 'text', id: 'municipio', name: 'municipio', label: 'Municipio: '},
{view: 'text', id: 'estado', name: 'estado', label: 'Estado: '},
{view: 'text', id: 'pais', name: 'pais', label: 'País: ', value: 'México',
readonly: true, placeholder: 'Usa solo tres letras de acuerdo al catalogo del SAT'},
{view: 'text', id: 'pais', name: 'pais', label: 'País: ',
value: 'México', readonly: true},
{template: 'Condiciones Comerciales', type: 'section'},
{cols: [
{view: 'richselect', id: 'forma_pago', name: 'forma_pago',
label: 'Forma de Pago: ', required: true, options: [],
invalidMessage: 'La Forma de pago es requerida'},
{view: 'text', id: 'condicion_pago', name: 'condicion_pago',
label: 'Condiciones de Pago: ', suggest: []},
label: 'Condiciones de Pago: '},
]},
{cols: [
{view: 'counter', id: 'dias_pago', name: 'dias_pago',
@ -122,41 +90,25 @@ var controls_fiscales = [
{view: 'richselect', id: 'lst_uso_cfdi_socio', name: 'uso_cfdi_socio',
label: 'Uso del CFDI', options: []},
{},
]},
{template: 'Regimenes Fiscales', type: 'section'},
{cols: [
{view: 'list', id: 'lst_receptor_regimenes_fiscales', data: [],
select: 'multiselect', width: 600, height: 125, required: true},
{},
]}
]
var controls_others = [
{view: 'checkbox', id: 'es_activo', name: 'es_activo', label: 'Activo: ',
value: true, bottomLabel: '&nbsp;&nbsp;Se recomienda solo desactivar y no eliminar'},
value: true, bottomLabel: '&nbsp;&nbsp;&nbsp;Se recomienda solo desactivar y no eliminar'},
{view: 'text', id: 'commercial_name', name: 'nombre_comercial',
label: 'Nombre Comercial: '},
{view: 'text', id: 'telefonos', name: 'telefonos', label: 'Teléfonos: '},
{view: 'text', id: 'web', name: 'web', label: 'Página Web: '},
{cols: [
{view: 'text', id: 'correo_facturas', name: 'correo_facturas',
label: 'Correos para Facturas: ', tooltip: 'Separados por comas',
bottomLabel: 'Uno o más correos electrónicos separados por comas'},
{view: 'text', id: 'partner_email_fp', name: 'partner_email_fp',
label: 'Para Facturas de pago: ', tooltip: 'Separados por comas',
bottomLabel: 'Uno o más correos electrónicos separados por comas'},
]},
{cols: [
{view: 'checkbox', id: 'es_cliente', name: 'es_cliente',
label: 'Es Cliente: ', value: true, width: 180},
{view: 'text', id: 'cuenta_cliente', name: 'cuenta_cliente',
label: 'Cuenta Cliente: ', disabled: true},
{view: "currency", type: "text", id: 'partner_balance', width: 300,
name: 'partner_balance', label: 'Saldo', labelWidth: 100,
labelAlign: "right", required: true, inputAlign: "right",
invalidMessage: "Captura un valor númerico", readonly: true},
]
label: 'Cuenta Cliente: ', disabled: true}, {}]
},
{cols: [
{view: 'checkbox', id: 'es_proveedor', name: 'es_proveedor',
@ -165,56 +117,12 @@ var controls_others = [
label: 'Cuenta Proveedor: ', disabled: true}, {}]
},
{view: 'checkbox', name: 'es_ong', label: 'Es ONG: ', value: false},
{view: 'text', name: 'tags', label: 'Etiquetas', disabled: true,
{view: 'text', name: 'tags', label: 'Etiquetas',
tooltip: 'Utiles para filtrados rápidos. Separa por comas.'},
{view: 'textarea' , height: 200, name: 'notas', label: 'Notas'},
]
var grid_partner_account_bank_cols = [
{id: 'id', header: 'ID', hidden: true},
{id: 'delete', header: '', width: 30, css: 'delete'},
{id: 'banco', header: 'Banco', fillspace: 1},
{id: 'cuenta', header: 'Cuenta', fillspace: 1},
{id: 'clabe', header: 'CLABE', fillspace: 1},
//~ {id: 'moneda', header: 'Moneda', fillspace: 1},
]
var grid_partner_account_bank = {
view: 'datatable',
id: 'grid_partner_account_bank',
select: 'row',
adjust: true,
autoheight: true,
columns: grid_partner_account_bank_cols,
}
var controls_partner_bank = [
{template: 'Agregar cuenta de banco', type: 'section'},
{view: 'form', id: 'form_partner_account_bank', rows: [
{cols: [
{view: 'richselect', id: 'lst_partner_bank', name: 'partner_bank',
label: 'Banco: ', required: true, options: []},
{view: 'text', id: 'partner_account', name: 'partner_account',
label: 'Cuenta: ', required: true},
{view: 'text', id: 'partner_clabe', name: 'partner_clabe',
label: 'CLABE: ', required: true},
]},
{minHeight: 10},
{cols: [{},
{view: 'button', id: 'cmd_partner_add_account_bank', maxWidth: 200,
label: 'Agregar cuenta'},
{}]},
],
},
{minHeight: 20, maxHeight: 20},
{template: 'Cuentas de banco existentes', type: 'section'},
grid_partner_account_bank,
{minHeight: 50},
]
var toolbar_contacts = [
{view: 'button', id: 'cmd_new_contact', label: 'Nuevo', type: 'iconButton',
@ -224,7 +132,7 @@ var toolbar_contacts = [
var grid_contacts_cols = [
{id: 'index', header: '#', adjust:'data', css:'right',
footer: {content: 'countRows'}},
footer: {content: 'rowCount'}},
{id: 'id', header: '', hidden: true},
{id: 'title', header: 'Título', adjust:'data', sort: 'string',
footer: 'Contactos'},
@ -352,12 +260,11 @@ var controls_partner = [
{
view: 'tabview',
id: 'tab_partner',
tabbar: {ptions: ['Datos Fiscales', 'Otros Datos', 'Cuentas de Banco']},
animate: true,
tabbar: {options: ['Datos Fiscales', 'Otros Datos', 'Contactos']}, animate: true,
cells: [
{id: 'Datos Fiscales', rows: controls_fiscales},
{id: 'Otros Datos', rows: controls_others},
{id: 'Cuentas de Banco', rows: controls_partner_bank}
{id: 'Contactos', rows: [multi_contacts]},
]
},
{rows: [
@ -377,12 +284,12 @@ var form_partner = {
view: 'form',
id: 'form_partner',
complexData: true,
scroll: true,
elements: controls_partner,
elementsConfig: {
labelWidth: 150,
labelAlign: 'right'
},
autoheight: true,
rules: {
nombre: function(value){ return value.trim() != '';},
rfc: validate_rfc,
@ -391,15 +298,6 @@ var form_partner = {
}
var pager_clientes = {
view: "pager",
id: "pager_clientes",
template: "{common.prev()} {common.pages()} {common.next()}",
size: 100,
group: 10,
}
var multi_partners = {
id: 'multi_partners',
animate: true,
@ -407,9 +305,8 @@ var multi_partners = {
{id: 'partners_home', rows:[
{view: 'toolbar', elements: toolbar_partners},
grid_partners,
pager_clientes,
]},
{id: 'partners_new', rows:[form_partner]}
{id: 'partners_new', rows:[form_partner, {}]}
]
}

View File

@ -7,60 +7,38 @@ var toolbar_products = [
autowidth: true, icon: "pencil"},
{view: "button", id: "cmd_delete_product", label: "Eliminar",
type: "iconButton", autowidth: true, icon: "minus"},
{},
{view: 'button', id: 'cmd_import_products', label: 'Importar',
type: 'iconButton', autowidth: true, icon: 'upload'},
{view: "button", id: "cmd_add_inventory", label: "Altas", hidden: true,
type: "iconButton", autowidth: true, icon: "plus"},
{view: "button", id: "cmd_products_add", label: "Altas CFDI", hidden: true,
type: "iconButton", autowidth: true, icon: "plus"},
{view: 'button', id: 'cmd_show_exists', label: 'Existencias', hidden: true,
type: 'iconButton', autowidth: true, icon: 'table'},
]
var grid_products_cols = [
{ id: "id", header: "ID", width: 75, hidden: true},
{ id: 'es_activo', header: 'Activo', hidden: true},
{ id: "clave_sat", header: ["Clave SAT"], width: 100,},
{ id: "id", header: "ID", width: 75},
{ id: "clave", header: ["Clave", {content: "textFilter"}], width: 100,
sort: 'string', footer: {content: 'countRows', css: 'right'}},
sort:"string" },
{ id: "descripcion", header: ["Descripción", {content: "textFilter"}],
fillspace:true, sort: 'string', footer: 'Productos y Servicios'},
fillspace:true, sort:"string" },
{ id: "unidad", header: ["Unidad", {content: "selectFilter"}], width: 150,
sort:"string" },
{ id: "valor_unitario", header: ["Precio", {content: "numberFilter"}],
width: 150, sort: 'int', format: format_currency, css: "right" },
{ id: 'existencia', header: ['Existencia', {content: 'numberFilter'}],
width: 100, sort: 'int', format: webix.i18n.numberFormat,
hidden: true, css: 'right' },
{ id: "valor_unitario", header: ["Precio", {content: "numberFilter"}], width: 150,
sort:"int", format: webix.i18n.priceFormat, css: "right" },
]
var grid_products = {
view: 'datatable',
id: 'grid_products',
select: 'row',
view: "datatable",
id: "grid_products",
select: "row",
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_products_cols,
scheme:{
$change:function(item){
if(!item.es_activo){
item.$css = 'cancel'
}
}
},
}
var suggest_categories = {
view: 'datasuggest',
type: 'tree',
view: "datasuggest",
type: "tree",
width: 400,
body: {data: []},
body: { data: [] },
}
@ -85,55 +63,19 @@ var grid_product_taxes = {
}
var suggest_sat_producto = {
view: 'gridsuggest',
id: 'grid_producto_found',
name: 'grid_producto_found',
body: {
autoConfig: false,
scroll: true,
autoheight: false,
header: true,
yCount: 10,
columns: [
{id: 'id', hidden: true},
{id: 'key', adjust: 'data', header: 'Clave SAT'},
{id: 'name', header: 'Producto', width: 750},
],
dataFeed:function(text){
if (text.length > 3){
this.load('/values/satproductos?key=' + text)
}else{
this.hide()
}
}
},
}
var opt_tax_object = [
{id: '01', value: '[01] No objeto de impuesto.'},
{id: '02', value: '[02] Sí objeto de impuesto.'},
{id: '03', value: '[03] Sí objeto del impuesto y no obligado al desglose.'},
{id: '04', value: '[04] Sí objeto del impuesto y no causa impuesto.'},
{id: '05', value: '[05] Sí objeto del impuesto, IVA crédito PODEBI.'},
]
var controls_generals = [
{view: 'checkbox', id: 'es_activo_producto', name: 'es_activo_producto',
label: 'Activo: ', value: true,
bottomLabel: 'Se recomienda solo desactivar y no eliminar'},
{cols: [
{view: 'combo', id: 'categoria', name: 'categoria', label: 'Categoría',
labelPosition: 'top', suggest: suggest_categories},
labelPosition: 'top', options: suggest_categories},
{view: 'text', id: 'clave', name: 'clave', label: 'Clave',
labelPosition: 'top', readonly: true, required: true},
{view: 'checkbox', id: 'chk_automatica', label: 'Automática',
labelPosition: 'top', value: true, maxWidth: 80},
{view: 'search', id: 'clave_sat', name: 'clave_sat', label: 'Clave SAT',
labelPosition: 'top', required: true, suggest: suggest_sat_producto,
placeholder: 'Al menos 4 caracteres...'},
labelPosition: 'top', required: true, placeholder: 'Buscar clave...'},
]},
{cols: [
{view: 'text', id: 'codigo_barras', name: 'codigo_barras',
@ -143,37 +85,28 @@ var controls_generals = [
{id: 'txt_col1'}]},
{view: "textarea", id: "descripcion", name: "descripcion", height: 200,
label: "Descripción", required: true, labelPosition: "top",
invalidMessage: "La Descripción es requerida",
placeholder: 'El SAT solo permite 1000 caracteres en este campo'},
invalidMessage: "La Descripción es requerida" },
{minHeight: 5},
{cols: [
{view: "richselect", id: "unidad", name: "unidad", label: "Unidad",
width: 300, labelWidth: 130, labelAlign: "right", required: true,
invalidMessage: "La Unidad es requerida", options: []},
{view: 'richselect', id: 'objeto_impuesto', name: 'objeto_impuesto', label: 'Objeto de Impuestos',
width: 500, labelWidth: 150, labelAlign: "right", required: true,
invalidMessage: 'Este campo es requerido', options: opt_tax_object},
{},
{view: 'text', id: 'tags_producto', name: 'tags_producto',
labelAlign: 'right', label: 'Etiquetas',
placeholder: 'Separadas por comas'}
]},
{cols: [{view: "currency", type: "text", id: "valor_unitario",
name: "valor_unitario", label: "Valor Unitario", width: 300,
labelWidth: 130, labelAlign: "right", required: true,
invalidMessage: "Captura un valor númerico", inputAlign: "right" },{}]},
{cols: [
{view: "currency", type: "text", id: "valor_unitario",
name: "valor_unitario", label: "Valor Unitario", width: 300,
labelWidth: 130, labelAlign: "right", required: true,
invalidMessage: "Captura un valor númerico", inputAlign: "right"},
{view: 'currency', type: 'text', id: 'precio_con_impuestos',
name: 'precio_con_impuestos', label: 'Con Impuestos', width: 300,
labelWidth: 115, labelAlign: 'right', required: false,
invalidMessage: 'Captura un valor númerico', inputAlign: 'right'},
{},]},
{cols: [
{view: 'checkbox', id: 'chk_inventario', name: 'inventario', hidden: true,
{view: 'checkbox', id: 'inventario', name: 'inventario', hidden: true,
label: 'Inventario', labelAlign: 'right', labelWidth: 130},
{view: 'counter', id: 'txt_existencia', name: 'existencia',
hidden: true, label: 'Existencia', step: 5, value: 0, min: 0,
disabled: true, readonly: true},
{view: 'counter', id: 'txt_minimo', name: 'minimo', hidden: true,
{view: 'counter', id: 'existencia', name: 'existencia', hidden: true,
label: 'Existencia', step: 5, value: 0, min: 0, disabled: true},
{view: 'counter', id: 'minimo', name: 'minimo', hidden: true,
label: 'Mínimo', step: 5, value: 0, min: 0, disabled: true},
{},]},
{id: 'txt_col2'}]},
{cols:[{view:'label', label:'Impuestos', width: 300, align:'center'}, {}]},
{cols:[grid_product_taxes, {}]}
]
@ -188,8 +121,8 @@ var controls_products = [
],
},
{rows: [
{template: "", type: "section"},
{margin: 10, cols: [{},
{ template:"", type: "section" },
{ margin: 10, cols: [{},
{view: "button", id: "cmd_save_product", label: "Guardar" , type: "form", autowidth: true, align:"center"},
{view: "button", id: "cmd_cancel_product", label: "Cancelar" , type: "danger", autowidth: true, align:"center"},
{}]
@ -203,7 +136,7 @@ var form_product = {
cols: [{
view: "form",
id: "form_product",
scroll: true,
//~ width: 600,
complexData: true,
elements: controls_products,
rules: {
@ -214,129 +147,6 @@ var form_product = {
}
var toolbar_products_add = {view: 'toolbar', elements: [{},
{view: 'button', id: 'cmd_add_products_from_xml', label: 'Desde XML',
type: 'iconButton', autowidth: true, icon: 'upload'},
{}]}
var rows_pro_add_partner = [
{view: 'fieldset', label: 'Buscar Proveedor', body: {rows: [
{cols: [
{view: 'search', id: 'search_partner_id', label: 'por Clave',
labelPosition: 'top', maxWidth: 200, placeholder: 'Captura la clave'},
{view: 'search', id: 'search_partner_name', label: 'por Nombre o RFC',
labelPosition: 'top', placeholder: 'Captura al menos tres letras'},
]},
{cols: [
{view: 'label', id: 'lbl_partner_title', label: 'Seleccionado: ', autowidth: true},
{view: 'label', id: 'lbl_partner', label: 'Ninguno'},
]}
]}},
]
var grid_partner_products_cols = [
{id: 'select', header: '', template:'{common.checkbox()}', width:35},
{id: 'key', header: {text: 'Clave', css: 'center'}, width: 100,
adjust: 'data'},
{id: 'key_sat', header:{text: 'Clave SAT', css: 'center'}, width: 100,
adjust: 'data'},
{id: 'description', header:{text: 'Descripción', css: 'center'},
fillspace: true},
{id: "pedimento", header: 'Pedimento', editor: 'text', hidden: true},
{id: 'unit', header:{text: 'Unidad', css: 'center'}, width: 100,
adjust: 'data'},
{id: 'unit_value', header:{text: 'Valor Unitario', css: 'center'},
width: 100, format: format_currency, css: 'right'},
{id: 'cant', header: {text: 'Cantidad', css: 'center'}, width: 50,
format: webix.i18n.numberFormat, css: 'right'},
{id: 'separate', header: '', width: 25},
{id: 'id_product', header: '', hidden: true},
{id: 'key1', header:{text: 'Clave', css: 'center'}, width: 100,
adjust: true, editor: 'text', hidden: true},
{id: 'key_sat1', header:{text: 'Clave SAT', css: 'center'}, width: 100,
adjust: true, editor: 'text'},
{id: 'description1', header:{text: 'Descripción', css: 'center'},
fillspace: true, editor: 'popup'},
{id: 'unit_value1', header:{text: 'Valor Unitario', css: 'center'},
width: 100, format: format_currency, css: 'right', editor: 'text'},
{id: 'cant1', header: {text: 'Cantidad', css: 'center'}, width: 50,
format: webix.i18n.numberFormat, css: 'right', editor: 'text'},
]
var grid_partner_products = {
view: 'datatable',
id: 'grid_partner_products',
select: 'row',
adjust: true,
autoheight: true,
editable: true,
columns: grid_partner_products_cols,
data: [],
fixedRowHeight: false,
on:{
onCheck:function(rowId, colId, state){
grid_partner_products_select(rowId, state);
}
}
}
var controls_products_add = [
{minHeight: 10, maxHeight: 10},
toolbar_products_add,
{minHeight: 10, maxHeight: 10},
{cols: [
{rows: rows_pro_add_partner},
{maxWidth: 10},
{},
]},
{view: 'label', label: 'Detalle', height: 30, align: 'left'},
{cols: [
grid_partner_products,
//~ grid_products_add,
]},
{rows: [
{template:"", type: "section" },
{margin: 10, cols: [{},
{view: 'button', id: 'cmd_save_products_add', label: 'Guardar',
autowidth: true, align: 'center'},
{},
{view: 'button', id: 'cmd_close_products_add', label: 'Cancelar',
type: 'danger', autowidth: true, align: 'center'}
]
},
]}
]
var controls_form_products_add = [
{
view: 'tabview',
id: 'tv_invoice',
animate: true,
cells: [
{id: 'Altas a inventario', rows: controls_products_add},
]
},
]
var form_products_add = {
type: 'space',
responsive: true,
cols: [{
view: 'form',
id: 'form_products_add',
complexData: true,
scroll: true,
elements: controls_form_products_add,
}]
}
var multi_products = {
id: "multi_products",
animate: true,
@ -345,197 +155,17 @@ var multi_products = {
{view:"toolbar", elements: toolbar_products},
grid_products,
]},
{id: "product_new", rows:[form_product]},
{id: "product_add", rows:[form_products_add]}
{id: "product_new", rows:[form_product, {}]}
],
}
var app_products = {
id: 'app_products',
id: "app_products",
rows:[
{view: 'template', id: 'th_products', type: 'header',
template: 'Administración de Productos y Servicios'},
{view: "template", id: "th_products", type: "header", template:"Administración de Productos" },
multi_products
],
}
var body_import_products = {rows: [
{view: 'form', id: 'form_upload_products', rows: [
{cols: [{},
{view: 'uploader', id: 'up_products', autosend: false,
link: 'lst_upload_products', value: 'Seleccionar Archivo',
upload: '/files/products'}, {}]},
{cols: [
{view: 'list', id: 'lst_upload_products', name: 'lst_upload_products',
type: 'uploader', autoheight: true, borderless: true}]},
{cols: [{}, {view: 'button', id: 'cmd_upload_products',
label: 'Importar Productos'}, {}]},
]},
]}
var win_import_products = {
init: function(){
webix.ui({
view: 'window',
id: 'win_import_products',
width: 400,
modal: true,
position: 'center',
head: 'Importar Productos',
body: body_import_products,
})
$$('cmd_upload_products').attachEvent('onItemClick', cmd_upload_products_click)
$$('up_products').attachEvent('onUploadComplete', up_products_upload_complete)
}
}
var body_add_products_from_xml = {rows: [
{view: 'form', id: 'form_upload_products_from_xml', rows: [
{cols: [{},
{view: 'uploader', id: 'up_products_from_xml', autosend: false,
link: 'lst_up_products_from_xml', value: 'Seleccionar Archivo',
upload: '/files/productsadd'}, {}]},
{cols: [
{view: 'list', id: 'lst_up_products_from_xml', type: 'uploader',
autoheight: true, borderless: true}]},
{cols: [{}, {view: 'button', id: 'cmd_upload_products_from_xml',
label: 'Cargar Productos'}, {}]},
]},
]}
var win_add_products_from_xml = {
init: function(){
webix.ui({
view: 'window',
id: 'win_add_products_from_xml',
width: 400,
modal: true,
position: 'center',
head: 'Agregar Productos desde XML',
body: body_add_products_from_xml,
})
$$('cmd_upload_products_from_xml').attachEvent('onItemClick', cmd_upload_products_from_xml_click)
$$('up_products_from_xml').attachEvent('onUploadComplete', up_products_from_xml_upload_complete)
}
}
var body_add_inventory = {rows: [{minHeight: 10},
{view: 'text', id: 'txt_add_id', readonly: true, hidden: true},
{cols: [
{view: 'text', id: 'txt_add_key', label: 'Clave',
labelPosition: 'top', readonly: true},
{view: 'text', id: 'txt_add_unit', label: 'Unidad',
labelPosition: 'top', readonly: true},
]},
{view: 'textarea', id: 'txt_add_description', height: 100,
label: 'Descripción', readonly: true, labelPosition: 'top'},
{minHeight: 10},
{cols: [
{view: 'counter', id: 'txt_new_cant', label: 'Cantidad a agregar',
labelWidth: 'auto', step: 1, value: 0, min: 0.01},
{view: 'richselect', id: 'lst_warehouses', label: 'Almacen: ',
labelPosition: 'left', required: false, options: [], hidden: false},
]},
{minHeight: 20},
{cols: [{},
{view: 'button', id: 'cmd_add_inventory_save', label: 'Guardar'}, {},
{view: 'button', id: 'cmd_add_inventory_cancel', label: 'Cancelar', type: 'danger'}, {}
]},
{minHeight: 20},
]}
var win_add_inventory = {
init: function(){
webix.ui({
view: 'window',
id: 'win_add_inventory',
width: 600,
modal: true,
position: 'center',
head: 'Agregar entrada manualmente',
body: body_add_inventory,
})
$$('cmd_add_inventory_save').attachEvent('onItemClick', cmd_add_inventory_save_click)
$$('cmd_add_inventory_cancel').attachEvent('onItemClick', cmd_add_inventory_cancel_click)
}
}
var grid_warehouse_exists_cols = [
{ id: 'id', header: 'ID', width: 75, hidden: true},
{ id: 'warehouse', header: ['Almacen'], fillspace:true,
footer: {text: 'Total', css: 'right'}},
{ id: 'exists', header: ['Existencia'], width: 100, sort: 'int',
format: webix.i18n.numberFormat, css: 'right',
footer: {content: 'summColumn', css: 'right'} },
]
var grid_warehouse_exists = {
view: 'datatable',
id: 'grid_warehouse_exists',
adjust: true,
autoheight: true,
select: 'row',
footer: true,
columns: grid_warehouse_exists_cols,
}
var body_win_show_exists = {rows: [{maxHeight: 10, minHeight: 10},
{minWidth: 500},
{cols: [
{maxWidth: 10},
{view: 'text', id: 'txt_id_product', readonly: true, hidden: true},
grid_warehouse_exists,
{maxWidth: 10}
]},
{maxHeight: 10},
{cols: [{maxWidth: 10},
{view: 'label', id: 'lbl_title_move', label: 'Primero, selecciona el almacen origen en la tabla superior.'},
]},
{cols: [{maxWidth: 10},
{view: 'counter', id: 'txt_cant_to_move', label: 'Cantidad a mover:',
labelPosition: 'top', step: 1, value: 0, min: 0.01},
{view: 'richselect', id: 'lst_warehouse_target', label: 'Almacen destino: ',
labelPosition: 'top', required: false, options: []}, {maxWidth: 10},
{view: 'button', id: 'cmd_warehouse_move', label: 'Mover', maxWidth: 100},
{maxWidth: 10}]},
{minHeight: 25, maxHeight: 25},
{template: 'Ajuste de almacen', type: 'section'},
{cols: [{maxWidth: 10},
{view: 'counter', id: 'txt_cant_to_adjust', label: 'Cantidad a ajustar:',
labelPosition: 'top', step: 1, value: 0, min: -1000000},
{view: 'button', id: 'cmd_adjust_stock', label: 'Ajustar', maxWidth: 100},
{maxWidth: 10}]},
{maxHeight: 20, minHeight: 20},
{cols: [{},
{view: 'button', id: 'cmd_win_show_exists_close', label: 'Cerrar', type: 'danger'},
{}]},
{maxHeight: 20, minHeight: 20},
]}
var win_show_exists = {
init: function(){
webix.ui({
view: 'window',
id: 'win_show_exists',
width: 500,
modal: true,
position: 'center',
head: 'Existencia por Almacen',
body: body_win_show_exists,
})
$$('cmd_win_show_exists_close').attachEvent('onItemClick', cmd_win_show_exists_close_click)
$$('cmd_warehouse_move').attachEvent('onItemClick', cmd_warehouse_move_click)
$$('cmd_adjust_stock').attachEvent('onItemClick', cmd_adjust_stock_click)
}
}

View File

@ -1,146 +0,0 @@
var toolbar_students = [
{view: 'button', id: 'cmd_new_student', label: 'Nuevo', type: 'iconButton',
autowidth: true, icon: 'user-plus'},
{view: 'button', id: 'cmd_edit_student', label: 'Editar', type: 'iconButton',
autowidth: true, icon: 'user'},
{view: 'button', id: 'cmd_delete_student', label: 'Eliminar', type: 'iconButton',
autowidth: true, icon: 'user-times'},
]
var grid_cols_students = [
{id: 'index', header:'#', css: 'right',
footer: {content: 'countRows', colspan: 2, css: 'right'}},
{id: 'id', header: 'Clave', sort: 'int', css: 'right'},
{id: 'nombre', header: ['Nombre', {content: 'textFilter'}],
sort: 'string'},
{id: 'paterno', header: ['A. Paterno', {content: 'textFilter'}],
sort: 'string'},
{id: 'materno', header: ['A. Materno', {content: 'textFilter'}],
sort: 'string'},
{id: 'rfc', header: ['RFC', {content: 'textFilter'}], adjust: 'data',
sort: 'string'},
{id: 'curp', header: ['CURP'], sort: 'string', adjust: 'data'},
]
var grid_students = {
view: 'datatable',
id: 'grid_students',
select: 'row',
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_cols_students,
ready:function(){
this.adjustColumn('index');
this.adjustColumn('id');
this.adjustColumn('nombre');
this.adjustColumn('rfc');
this.adjustColumn('curp');
},
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i+1;
})
}
},
}
var rows_school_home = [
{view: 'toolbar', elements: toolbar_students},
grid_students,
]
var student_controls_generales = [
{view: 'text', id: 'student_name', name: 'nombre', label: 'Nombre: ',
required: true, invalidMessage: 'El nombre es requerido'},
{view: 'text', id: 'student_paterno', name: 'paterno', label: 'Apellido Paterno: ',
required: true, invalidMessage: 'El apellido paterno es requerido'},
{view: 'text', id: 'student_materno', name: 'materno',
label: 'Apellido Materno: '},
{cols: [
{view: 'text', id: 'student_rfc', name: 'rfc', label: 'RFC: ',
invalidMessage: 'RFC inválido', adjust: 'data',
attributes: {maxlength: 13}},
{view: 'text', id: 'student_curp', name: 'curp', label: 'CURP: ',
required: true, invalidMessage: 'CURP inválido', adjust: 'data',
attributes: {maxlength: 20}},
{}]},
{cols: [
{view: 'richselect', id: 'student_grupo', name: 'grupo',
label: 'Nivel Educativo: ', required: true, options: [],
invalidMessage: 'El Nivel Educativo es requerido'},
{},
]},
]
var form_controls_student = [
{
view: 'tabview',
id: 'tab_student',
tabbar: {options: ['Datos Generales']}, animate: true,
cells: [
{id: 'Datos Generales', rows: student_controls_generales},
]
},
{rows: [
{ template:"", type: "section" },
{ margin: 10, cols: [{},
{view: "button", id: "cmd_save_student", label: "Guardar" ,
type: "form", autowidth: true, align: "center"},
{view: "button", id: "cmd_cancel_student", label: "Cancelar" ,
type: "danger", autowidth: true, align: "center"},
{}]
},
]}
]
var form_student = {
type: 'space',
cols: [{
view: 'form',
id: 'form_student',
complexData: true,
scroll: true,
elements: form_controls_student,
elementsConfig: {
labelWidth: 150,
labelAlign: 'right'
},
rules: {
nombre: function(value){ return value.trim() != '';},
curp: validate_curp,
}
}]
}
var multi_school = {
id: 'multi_school',
view: 'multiview',
animate: true,
cells:[
{id: 'school_home', rows: rows_school_home},
{id: 'school_groups', rows: []},
{id: 'new_student', rows: [form_student]},
],
}
var app_school = {
id: 'app_school',
rows:[
{view: 'template', id: 'th_school', type: 'header',
template: 'Administración de Escuela'},
multi_school
],
}

View File

@ -1,492 +0,0 @@
var toolbar_tickets = [
{view: 'button', id: 'cmd_nuevo_ticket', label: 'Nuevo', type: 'iconButton',
autowidth: true, icon: 'plus'},
{view: 'button', id: 'cmd_ticket_from_ticket', label: 'Regenerar',
type: 'iconButton', autowidth: true, icon: 'pencil'},
{view: 'button', id: 'cmd_ticket_to_invoice', label: 'Facturar',
type: 'iconButton', autowidth: true, icon: 'file-code-o'},
{},
{view: 'button', id: 'cmd_ticket_report_pdf', label: 'Reporte',
type: 'iconButton', autowidth: true, icon: 'file-pdf-o'},
{view: 'button', id: 'cmd_ticket_report_xls', label: 'Reporte',
type: 'iconButton', autowidth: true, icon: 'table'},
{},
{view: 'button', id: 'cmd_cancelar_ticket', label: 'Cancelar',
type: 'iconButton', autowidth: true, icon: 'ban'},
]
var toolbar_tickets_filter = [
{view: 'button', id: 'cmd_ticket_filter_today', label: 'Hoy', type: 'iconButton',
autowidth: true, icon: 'filter'},
{view: 'richselect', id: 'filter_year_ticket', label: 'Año',
labelAlign: 'right', labelWidth: 50, width: 150, options: []},
{view: 'richselect', id: 'filter_month_ticket', label: 'Mes',
labelAlign: 'right', labelWidth: 50, width: 200, options: months},
{view: 'daterangepicker', id: 'filter_dates_ticket', label: 'Fechas',
labelAlign: 'right', width: 300},
]
var grid_tickets_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right',
footer: {content: 'countRows', colspan: 3, css: 'right'}},
{id: "id", header:"ID", hidden:true},
{id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "header",
sort: "string"},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header',
sort: 'int', css: 'right', footer: {text: 'Tickets', colspan: 3}},
{id: "fecha", header: ["Fecha y Hora"],
adjust: "data", sort: "date"},
{id: "estatus", header: ["Estatus", {content: "selectFilter"}],
adjust: "data", sort:"string"},
{id: 'total', header: ['Total', {content: 'numberFilter'}], width: 150,
sort: 'int', format: webix.i18n.priceFormat, css: 'right',
footer: {content: 'summActive', css: 'right'}},
{id: "cliente", header: ["Razón Social", {content: "selectFilter"}],
fillspace:true, sort:"string", hidden: true},
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
{id: 'print', header: '', adjust: 'data', template: get_icon('print')},
]
var grid_tickets = {
view: 'datatable',
id: 'grid_tickets',
select: 'row',
scrollY: true,
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_tickets_cols,
scheme:{
$change:function(item){
if (item.estatus == 'Cancelado'){
item.$css = 'cancel'
}
}
},
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var rows_tickets_home = [
{view: 'toolbar', elements: toolbar_tickets},
{view: 'toolbar', elements: toolbar_tickets_filter},
grid_tickets,
]
var ticket_suggest_products = {
view: 'gridsuggest',
id: 'gt_productos_found',
name: 'gt_productos_found',
body: {
autoConfig: false,
header: true,
columns: [
{id: 'id', hidden: true},
{id: 'clave', header: 'Clave', adjust: 'data'},
{id: 'descripcion', header: 'Descripción', width: 500},
{id: 'unidad', header: 'Unidad', adjust: 'data'},
{id: 'valor_unitario', header: 'Valor Unitario', adjust: 'data',
format: webix.i18n.priceFormat}
],
dataFeed:function(text){
if (text.length > 2){
this.load('/values/product?name=' + text)
}else{
this.hide()
}
}
},
}
var tbody_buscar_producto = {rows: [
{cols: [
{view: 'search', id: 'tsearch_product_key', name: 'tsearch_product_key',
label: 'por Clave', labelPosition:'top', maxWidth: 250,
placeholder: 'Presiona ENTER para buscar'},
{view: 'search', id: 'tsearch_product_name', name: 'tsearch_product_name',
label: 'por Descripción', labelPosition:'top',
suggest: ticket_suggest_products, placeholder: 'Captura al menos tres letras'},
]},
]}
var grid_tdetails_cols = [
{id: "id_product", header:"ID", hidden: true},
{id: 'delete', header: '', width: 30, css: 'delete'},
{id: "clave", header:{text: 'Clave', css: 'center'}, width: 100,
footer: {text: 'Artículos', css: 'right_footer3'}},
{id: "clave_sat", hidden: true},
{id: "descripcion", header:{text: 'Descripción', css: 'center'},
fillspace: true, footer: {content: 'countRows', css: 'footer3'}},
{id: "unidad", header:{text: 'Unidad', css: 'center'}, width: 100,
editor: 'select', options: 'values/unidades'},
{id: 'cantidad', header: {text: 'Cantidad', css: 'center'}, width: 100,
format: webix.i18n.numberFormat, css: 'right', editor: 'text',
footer: {content: 'summColumn', css: 'right_footer3'}},
{id: "valor_unitario", header:{text: 'Valor Unitario', css: 'center'},
width: 100, format: webix.i18n.priceFormat, css: 'right', editor: 'text',
footer: {text: 'Total ', css: 'right_footer2'}},
{id: 'descuento', header: {text: 'Descuento', css: 'center'}, hidden: true,
width: 80, format: webix.i18n.priceFormat, css: 'right', editor: 'text'},
{id: 'precio_final', hidden: true, header: 'precio_final', width: 80,
format: webix.i18n.priceFormat, css: 'right'},
{id: "importe", header:{text: 'Importe', css: 'center'}, width: 150,
format: webix.i18n.priceFormat, css: 'right',
footer: {content: 'summColumn', css: 'right_footer2'}},
{id: "inventario", hidden: true},
{id: "existencia", hidden: true},
]
var grid_tdetails = {
view: 'datatable',
id: 'grid_tdetails',
select: 'row',
adjust: true,
autoheight: true,
editable: true,
footer: true,
columns: grid_tdetails_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.delete = '-'
})
}
},
data: [],
}
var body_ticket_informacion = {rows: [
{view: 'richselect', id: 'lst_ticket_forma_pago', name: 'forma_pago',
label: 'Forma de Pago', labelPosition: 'top', required: true,
options: []},
],}
var toolbar_new_ticket = {
view: 'toolbar', elements: [
{view: 'button', id: 'cmd_generar_ticket', label: 'Generar',
icon: 'ticket', type: 'iconButton', autowidth: true,
align: 'center', hotkey: 'Ctrl+g'}, {},
{view: 'button', id: 'cmd_ticket_notes', label: 'Notas',
autowidth: true, type: 'iconButton', icon: 'commenting-o'}, {},
{view: 'button', id: 'cmd_cerrar_ticket', label: 'Cerrar',
type: 'danger', autowidth: true, align: 'center'}
]}
var grid_ticket_total_up = {
view: 'datatable',
id: 'grid_ticket_total_up',
adjust: true,
autoheight: true,
width: 250,
header: false,
footer: true,
hidden: true,
rowHeight: 0,
columns: [
{id: 'id', hidden: true},
{id: 'title', width: 100,
footer: {text: 'Total ', css: 'right_footer2'}},
{id: 'total', fillspace: true, format: webix.i18n.priceFormat,
footer: {content: 'summColumn', css: 'right_footer2'}},
],
data: [{id:0, title: '', total: 0.00}]
}
var cells_new_ticket = [
{minHeight: 10, maxHeight: 10},
toolbar_new_ticket,
{minHeight: 10, maxHeight: 10},
{cols: [{rows: [
{view: 'fieldset', label: 'Buscar Producto', body: tbody_buscar_producto},
]},
{maxWidth: 10},
{maxWidth: 300, rows: [
{view: 'fieldset', label: 'Información', body: body_ticket_informacion},
]},
]},
{cols: [
{view: 'label', label: 'Detalle', height: 30, align: 'left'},
{},
grid_ticket_total_up,
]},
grid_tdetails,
]
var toolbar_ticket_invoice = {view: 'toolbar', elements: [{},
{view: 'checkbox', id: 'chk_is_invoice_day', labelWidth: 0, width: 150,
labelRight: 'Es Factura Global'},
{view: 'richselect', id: 'lst_global_periodicidad_2', labelWidth: 90, width: 225,
label: 'Periodicidad:', options: opt_global_periodicidad, value: '01', disabled: true},
{view: 'richselect', id: 'lst_global_months_2', labelWidth: 50, width: 250,
label: 'Mes:', options: opt_global_months, value: '01', disabled: true},
{},
{view: 'button', id: 'cmd_close_ticket_invoice', label: 'Cerrar',
type: 'danger', autowidth: true, align: 'center'}
]}
var ticket_suggest_partners = {
view: 'gridsuggest',
id: 'grid_ticket_clients_found',
name: 'grid_ticket_clients_found',
body: {
autoConfig: false,
header: false,
columns: [
{id: 'id', hidden: true},
{id: 'nombre', adjust: 'data'},
{id: 'rfc', adjust: 'data'},
{id: 'forma_pago', hidden: true},
{id: 'uso_cfdi', hidden: true},
],
dataFeed:function(text){
if (text.length > 2){
this.load('/values/client?name=' + text)
}else{
this.hide()
}
}
}
}
var ticket_search_client = {cols: [{rows: [
{view: 'fieldset', id: 'fs_ticket_search_client', label: 'Buscar Cliente', body: {rows: [
{cols: [
{view: 'search', id: 'tsearch_client_key', name: 'tsearch_client_key',
label: 'por Clave', labelPosition: 'top', maxWidth: 250,
placeholder:'Presiona ENTER para buscar'},
{view: 'search', id: 'tsearch_client_name',
name: 'tsearch_client_name', label: 'por Nombre o RFC',
labelPosition: 'top', suggest: ticket_suggest_partners,
placeholder: 'Captura al menos tres letras'},
]},
{cols: [
{view: 'label', id: 'lbl_tclient_title', autowidth:true,
name: "lbl_tclient_title", label: 'Seleccionado: ' },
{view: 'label', id: 'lbl_tclient', name: 'lbl_tclient',
label: 'Ninguno'},
]}
]}},
]},]}
var grid_tickets_active_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right'},
{id: "id", header:"ID", hidden:true},
{id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data",
sort: "string"},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header',
sort: 'int', css: 'right', footer: {content: 'countRows', css: 'right'}},
{id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string",
footer: 'Tickets'},
{id: 'total', header: 'Total', width: 150,sort: 'int', css: 'right',
format: webix.i18n.priceFormat, footer: {content: 'summColumn',
css: 'right'}},
]
var grid_tickets_active = {
view: 'datatable',
id: 'grid_tickets_active',
select: 'row',
multiselect: true,
adjust: true,
footer: true,
drag: true,
resizeColumn: true,
headermenu: true,
columns: grid_tickets_active_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var grid_tickets_invoice_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right'},
{id: "id", header:"ID", hidden:true},
{id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data",
sort: "string"},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header',
sort: 'int', css: 'right', footer: {content: 'countRows', css: 'right'}},
{id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string",
footer: 'Tickets'},
{id: 'total', header: 'Total', width: 150,sort: 'int', css: 'right',
format: webix.i18n.priceFormat, footer: {content: 'summColumn',
css: 'right'}},
]
var grid_tickets_invoice = {
view: 'datatable',
id: 'grid_tickets_invoice',
select: 'row',
multiselect: true,
adjust: true,
footer: true,
drag: true,
resizeColumn: true,
headermenu: true,
columns: grid_tickets_invoice_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i + 1
})
}
},
}
var controls_ticket_to_invoice = [
{minHeight: 10, maxHeight: 10},
toolbar_ticket_invoice,
{minHeight: 10, maxHeight: 10},
ticket_search_client,
{minHeight: 5, maxHeight: 5},
{cols:[
{rows: [{view: 'label', label: 'Tickets sin facturar', height: 30,
align: 'left'},
grid_tickets_active]},
{rows:[{},
{view: 'button', id: 'cmd_move_tickets_right', label: '->', autowidth: true},
{view: 'button', id: 'cmd_move_tickets_left', label: '<-', autowidth: true},
{}]},
{rows: [{view: 'label', label: 'Tickets a facturar', height: 30,
align: 'left'},
grid_tickets_invoice]},
]},
{minHeight: 20, maxHeight: 20},
{margin: 20, cols: [{},
{view: 'button', id: 'cmd_new_invoice_from_ticket', label: 'Facturar',
icon: 'ticket', type: 'iconButton', autowidth: true, align: 'center'},
{}]
},
]
var controls_new_ticket = [
{
view: 'tabview',
id: 'tv_new_ticket',
animate: true,
cells: [
{id: 'Generar', rows: cells_new_ticket},
]
},
]
var controls_ticket_invoice = [
{
view: 'tabview',
id: 'tv_ticket_invoice',
animate: true,
cells: [
{id: 'Facturar Tickets', rows: controls_ticket_to_invoice},
]
},
]
var form_new_ticket = {
type: 'space',
responsive: true,
cols: [{
view: 'form',
id: 'form_new_ticket',
complexData: true,
scroll: true,
elements: controls_new_ticket,
}],
}
var form_ticket_invoice = {
type: 'space',
responsive: true,
cols: [{
view: 'form',
id: 'form_ticket_invoice',
complexData: true,
scroll: true,
elements: controls_ticket_invoice,
}],
}
var multi_tickets = {
id: 'multi_tickets',
view: 'multiview',
animate: true,
cells:[
{id: 'tickets_home', rows: rows_tickets_home},
{id: 'tickets_new', rows:[form_new_ticket]},
{id: 'tickets_invoice', rows:[form_ticket_invoice]}
],
}
var app_tickets = {
id: 'app_tickets',
rows:[
{view: 'template', id: 'th_ticckets', type: 'header',
template: 'Punto de venta - Tickets'},
multi_tickets
],
}
var body_ticket_notes = {rows: [
{minHeight: 5, maxHeight: 5},
{view: 'textarea', id: 'ticket_notes', name: 'ticket_notes', height: 300,
placeholder: 'Captura las notas'},
{minHeight: 5, maxHeight: 5},
{cols: [{}, {view: 'button', id: 'cmd_ticket_save_note', autowidth: true,
label: 'Guardar y Cerrar', type: 'iconButton', hotkey: 'Ctrl+enter'},
{}]},
{minHeight: 5, maxHeight: 5},
]}
var win_ticket_notes = {
init: function(){
webix.ui({
view: 'window',
id: 'win_ticket_notes',
height: 400,
width: 500,
modal: true,
position: 'center',
head: 'Notas',
body: body_ticket_notes,
})
$$('cmd_ticket_save_note').attachEvent('onItemClick', cmd_ticket_save_note_click)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,14 @@
<%inherit file="base.html"/>
<%block name="media">
<link rel="stylesheet" href="/static/css/sidebar_air.css" type="text/css">
<link rel="stylesheet" href="/static/css/app.css" type="text/css">
<script src="/static/js/sidebar.js" type="text/javascript" ></script>
<script src="/static/js/controller/util.js" type="text/javascript" ></script>
<script src="/static/js/ui/admin.js" type="text/javascript" ></script>
<script src="/static/js/controller/admin.js" type="text/javascript" ></script>
</%block>
<%block name="content">

View File

@ -6,13 +6,10 @@
<meta charset="utf-8" />
<link rel="shortcut icon" href="/static/img/favicon.png">
<link rel="stylesheet" href="/static/css/air.css" type="text/css">
<link rel="stylesheet" href="/static/css/sidebar_air.css" type="text/css">
<link rel="stylesheet" href="/static/css/app.css" type="text/css">
<script src="/static/js/webix.js" type="text/javascript" ></script>
<script src="/static/js/webix_debug.js" type="text/javascript" ></script>
<script src="/static/js/es-MX.js" type="text/javascript" ></script>
<script src="/static/js/lokijs.min.js" type="text/javascript" ></script>
<script src="/static/js/controller/util.js" type="text/javascript" ></script>
<%block name="media"/>
</head>
@ -20,7 +17,7 @@
<body>
<script type="text/javascript" charset="utf-8">
webix.debug = false;
webix.debug = true;
webix.i18n.setLocale("es-MX");
</script>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Cancelacion RfcEmisor="{rfc}" Fecha="{fecha}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://cancelacfd.sat.gob.mx">
<Folios>
<UUID>{uuid}</UUID>
</Folios>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue />
</Reference>
</SignedInfo>
<SignatureValue />
<KeyInfo>
<X509Data>
<X509SubjectName />
<X509IssuerSerial />
<X509Certificate />
</X509Data>
<KeyValue />
</KeyInfo>
</Signature>
</Cancelacion>

View File

@ -1,142 +0,0 @@
<%inherit file="base.html"/>
<%block name="media">
<script src="/static/js/controller/util.js" type="text/javascript" ></script>
<script src="/static/js/ui/empresas.js" type="text/javascript" ></script>
</%block>
<%block name="content">
<div id="form_empresas"></div>
<script type="text/javascript" charset="utf-8">
function agregar_empresa(values){
webix.ajax().post("/empresas", values, function(text, data, xhr) {
var values = data.json();
if (values.ok) {
$$('form_empresas').clear()
$$('grid_empresas').add(values.row)
$$('grid_empresas').refresh()
msg_ok(values.msg)
} else {
msg_error(values.msg)
}
})
}
function validate_nuevo_rfc(){
var msg = ''
var form = this.getFormView();
if (!form.validate()) {
msg = 'Valores inválidos'
msg_error(msg)
return
}
var values = form.getValues()
if(!validar_rfc(values['alta_rfc'])){
return
}
values['opt'] = 1
msg = '¿Estás seguro de agregar este nuevo emisor?'
webix.confirm({
title: 'Agregar Emisor',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
msg = 'Agregando empresa, espera la confirmación...'
msg_ok(msg)
agregar_empresa(values)
}
}
})
}
function borrar_empresa(id){
var row = $$('grid_empresas').getItem(id)
webix.ajax().del('/empresas', {rfc: row.rfc}, function(text, xml, xhr){
msg = 'Emisor eliminado correctamente'
if(xhr.status == 200){
$$('grid_empresas').remove(id)
msg_ok(msg)
}else{
msg = 'No se pudo eliminar'
msg_error(msg)
}
})
}
function grid_empresas_click(id, e, node){
if(id.column != 'delete'){
return
}
msg = '¿Estás seguro de borrar el RFC seleccionado?<BR><BR>ESTA ACCIÓN NO SE PUEDE DESHACER'
webix.confirm({
title: 'Borrar Emisor',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
borrar_empresa(id.row)
}
}
})
}
function respaldar_bd(){
webix.ajax().post("/empresas", {'opt': 2}, function(text, data, xhr) {
var values = data.json();
if (values.ok) {
msg_ok(values.msg)
} else {
msg_error(values.msg)
}
})
}
function cmd_respaldar_bd(){
msg = '¿Estás seguro de respaldar las Bases de Datos?'
webix.confirm({
title: 'Respaldar BD',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
msg = 'Respaldando Bases de datos...'
msg_ok(msg)
respaldar_bd()
}
}
})
}
webix.ready(function(){
webix.ui(ui_empresas)
$$('grid_empresas').attachEvent('onItemClick', grid_empresas_click)
})
</script>
</%block>

View File

@ -36,11 +36,6 @@ function validate_login(){
webix.ready(function(){
webix.ui(ui_login);
webix.ajax().get("/values/titlelogin", function(text, data, xhr){
var value = data.json();
$$("title_login").setHTML(value);
})
$$("txt_rfc").focus();
});
</script>

View File

@ -1,21 +1,17 @@
<%inherit file="base.html"/>
<%block name="media">
<link rel="stylesheet" href="/static/css/sidebar_air.css" type="text/css">
<script src="/static/js/sidebar.js" type="text/javascript" ></script>
<script src="/static/js/controller/util.js" type="text/javascript" ></script>
<script src="/static/js/ui/partners.js" type="text/javascript" ></script>
<script src="/static/js/ui/products.js" type="text/javascript" ></script>
<script src="/static/js/ui/bancos.js" type="text/javascript" ></script>
<script src="/static/js/ui/school.js" type="text/javascript" ></script>
<script src="/static/js/ui/nomina.js" type="text/javascript" ></script>
<script src="/static/js/ui/tickets.js" type="text/javascript" ></script>
<script src="/static/js/ui/invoices.js" type="text/javascript" ></script>
<script src="/static/js/ui/main.js" type="text/javascript" ></script>
<script src="/static/js/controller/partners.js" type="text/javascript" ></script>
<script src="/static/js/controller/products.js" type="text/javascript" ></script>
<script src="/static/js/controller/bancos.js" type="text/javascript" ></script>
<script src="/static/js/controller/school.js" type="text/javascript" ></script>
<script src="/static/js/controller/nomina.js" type="text/javascript" ></script>
<script src="/static/js/controller/tickets.js" type="text/javascript" ></script>
<script src="/static/js/controller/invoices.js" type="text/javascript" ></script>
<script src="/static/js/controller/main.js" type="text/javascript" ></script>

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