forked from elmau/empresa-libre
Merge branch 'develop'
Soporte para complemento de divisas
This commit is contained in:
commit
79cea5b092
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -1,3 +1,35 @@
|
||||||
|
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, despues 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]
|
v 1.27.1 [23-ene-2019]
|
||||||
----------------------
|
----------------------
|
||||||
- Error: Al cancelar nómina
|
- Error: Al cancelar nómina
|
||||||
|
|
|
@ -13,3 +13,4 @@ pyqrcode
|
||||||
pypng
|
pypng
|
||||||
reportlab
|
reportlab
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
|
cryptography
|
||||||
|
|
|
@ -84,6 +84,12 @@ SAT = {
|
||||||
'xmlns': 'http://www.sat.gob.mx/Pagos',
|
'xmlns': 'http://www.sat.gob.mx/Pagos',
|
||||||
'schema': ' http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd',
|
'schema': ' http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.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',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,6 +107,7 @@ class CFDI(object):
|
||||||
self._edu = False
|
self._edu = False
|
||||||
self._pagos = False
|
self._pagos = False
|
||||||
self._is_nomina = False
|
self._is_nomina = False
|
||||||
|
self._divisas = ''
|
||||||
self.error = ''
|
self.error = ''
|
||||||
|
|
||||||
def _now(self):
|
def _now(self):
|
||||||
|
@ -152,6 +159,8 @@ class CFDI(object):
|
||||||
self._ine = True
|
self._ine = True
|
||||||
self._pagos = bool(datos['complementos'].get('pagos', False))
|
self._pagos = bool(datos['complementos'].get('pagos', False))
|
||||||
|
|
||||||
|
self._divisas = datos['comprobante'].pop('divisas', '')
|
||||||
|
|
||||||
if 'nomina' in datos:
|
if 'nomina' in datos:
|
||||||
self._is_nomina = True
|
self._is_nomina = True
|
||||||
return self._validate_nomina(datos)
|
return self._validate_nomina(datos)
|
||||||
|
@ -190,6 +199,12 @@ class CFDI(object):
|
||||||
attributes[name] = SAT['edu']['xmlns']
|
attributes[name] = SAT['edu']['xmlns']
|
||||||
schema_edu = SAT['edu']['schema']
|
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 = ''
|
schema_nomina = ''
|
||||||
if self._is_nomina:
|
if self._is_nomina:
|
||||||
name = 'xmlns:{}'.format(SAT['nomina']['prefix'])
|
name = 'xmlns:{}'.format(SAT['nomina']['prefix'])
|
||||||
|
@ -204,7 +219,7 @@ class CFDI(object):
|
||||||
|
|
||||||
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
|
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
|
||||||
schema_locales + schema_donativo + schema_ine + schema_edu + \
|
schema_locales + schema_donativo + schema_ine + schema_edu + \
|
||||||
schema_nomina + schema_pagos
|
schema_divisas + schema_nomina + schema_pagos
|
||||||
attributes.update(datos)
|
attributes.update(datos)
|
||||||
|
|
||||||
if not 'Version' in attributes:
|
if not 'Version' in attributes:
|
||||||
|
@ -426,6 +441,13 @@ class CFDI(object):
|
||||||
self._complemento = ET.SubElement(
|
self._complemento = ET.SubElement(
|
||||||
self._cfdi, '{}:Complemento'.format(self._pre))
|
self._cfdi, '{}:Complemento'.format(self._pre))
|
||||||
|
|
||||||
|
if self._divisas:
|
||||||
|
atributos = {
|
||||||
|
'version': SAT['divisas']['version'],
|
||||||
|
'tipoOperacion': self._divisas,
|
||||||
|
}
|
||||||
|
ET.SubElement(self._complemento, 'divisas:Divisas', atributos)
|
||||||
|
|
||||||
if 'ine' in datos:
|
if 'ine' in datos:
|
||||||
atributos = {'Version': SAT['ine']['version']}
|
atributos = {'Version': SAT['ine']['version']}
|
||||||
atributos.update(datos['ine'])
|
atributos.update(datos['ine'])
|
||||||
|
|
|
@ -47,6 +47,7 @@ class AppLogin(object):
|
||||||
session.invalidate()
|
session.invalidate()
|
||||||
values = req.params
|
values = req.params
|
||||||
values['rfc'] = values['rfc'].upper()
|
values['rfc'] = values['rfc'].upper()
|
||||||
|
values['ip'] = req.remote_addr
|
||||||
result, user = self._db.authenticate(values)
|
result, user = self._db.authenticate(values)
|
||||||
if result['login']:
|
if result['login']:
|
||||||
session.save()
|
session.save()
|
||||||
|
@ -485,7 +486,14 @@ class AppNomina(object):
|
||||||
|
|
||||||
def on_get(self, req, resp):
|
def on_get(self, req, resp):
|
||||||
values = req.params
|
values = req.params
|
||||||
|
by = values.get('by', '')
|
||||||
req.context['result'] = self._db.get_nomina(values)
|
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
|
resp.status = falcon.HTTP_200
|
||||||
|
|
||||||
def on_post(self, req, resp):
|
def on_post(self, req, resp):
|
||||||
|
|
|
@ -872,7 +872,7 @@ class LIBO(object):
|
||||||
currency = self.CELL_STYLE.get(self._currency, 'peso')
|
currency = self.CELL_STYLE.get(self._currency, 'peso')
|
||||||
return '{}{}'.format(currency, match.groups()[1])
|
return '{}{}'.format(currency, match.groups()[1])
|
||||||
|
|
||||||
def _conceptos(self, data):
|
def _conceptos(self, data, pakings):
|
||||||
first = True
|
first = True
|
||||||
col1 = []
|
col1 = []
|
||||||
col2 = []
|
col2 = []
|
||||||
|
@ -881,8 +881,9 @@ class LIBO(object):
|
||||||
col5 = []
|
col5 = []
|
||||||
col6 = []
|
col6 = []
|
||||||
col7 = []
|
col7 = []
|
||||||
|
col8 = []
|
||||||
count = len(data) - 1
|
count = len(data) - 1
|
||||||
for concepto in data:
|
for i, concepto in enumerate(data):
|
||||||
key = concepto.get('noidentificacion', '')
|
key = concepto.get('noidentificacion', '')
|
||||||
description = concepto['descripcion']
|
description = concepto['descripcion']
|
||||||
unidad = concepto['unidad']
|
unidad = concepto['unidad']
|
||||||
|
@ -899,6 +900,8 @@ class LIBO(object):
|
||||||
cell_5 = self._set_cell('{valorunitario}', valor_unitario, value=True)
|
cell_5 = self._set_cell('{valorunitario}', valor_unitario, value=True)
|
||||||
cell_6 = self._set_cell('{importe}', importe, value=True)
|
cell_6 = self._set_cell('{importe}', importe, value=True)
|
||||||
cell_7 = self._set_cell('{descuento}', descuento, value=True)
|
cell_7 = self._set_cell('{descuento}', descuento, value=True)
|
||||||
|
if pakings:
|
||||||
|
cell_8 = self._set_cell('{empaque}', pakings[i], value=True)
|
||||||
if len(data) > 1:
|
if len(data) > 1:
|
||||||
row = cell_1.getCellAddress().Row + 1
|
row = cell_1.getCellAddress().Row + 1
|
||||||
self._sheet.getRows().insertByIndex(row, count)
|
self._sheet.getRows().insertByIndex(row, count)
|
||||||
|
@ -912,6 +915,8 @@ class LIBO(object):
|
||||||
col5.append((float(valor_unitario),))
|
col5.append((float(valor_unitario),))
|
||||||
col6.append((float(importe),))
|
col6.append((float(importe),))
|
||||||
col7.append((float(descuento),))
|
col7.append((float(descuento),))
|
||||||
|
if pakings:
|
||||||
|
col8.append((pakings[i],))
|
||||||
self._total_cantidades += float(cantidad)
|
self._total_cantidades += float(cantidad)
|
||||||
if not count:
|
if not count:
|
||||||
return
|
return
|
||||||
|
@ -919,6 +924,9 @@ class LIBO(object):
|
||||||
style_5 = self._get_style(cell_5)
|
style_5 = self._get_style(cell_5)
|
||||||
style_6 = self._get_style(cell_6)
|
style_6 = self._get_style(cell_6)
|
||||||
style_7 = self._get_style(cell_7)
|
style_7 = self._get_style(cell_7)
|
||||||
|
style_8 = ''
|
||||||
|
if pakings:
|
||||||
|
style_8 = self._get_style(cell_8)
|
||||||
|
|
||||||
col = cell_1.getCellAddress().Column
|
col = cell_1.getCellAddress().Column
|
||||||
target1 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
target1 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
||||||
|
@ -933,9 +941,13 @@ class LIBO(object):
|
||||||
col = cell_6.getCellAddress().Column
|
col = cell_6.getCellAddress().Column
|
||||||
target6 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
target6 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
||||||
target7 = None
|
target7 = None
|
||||||
|
target8 = None
|
||||||
if not cell_7 is None:
|
if not cell_7 is None:
|
||||||
col = cell_7.getCellAddress().Column
|
col = cell_7.getCellAddress().Column
|
||||||
target7 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
target7 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
||||||
|
if pakings:
|
||||||
|
col = cell_8.getCellAddress().Column
|
||||||
|
target8 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
||||||
|
|
||||||
target1.setFormulaArray(tuple(col1))
|
target1.setFormulaArray(tuple(col1))
|
||||||
target2.setDataArray(tuple(col2))
|
target2.setDataArray(tuple(col2))
|
||||||
|
@ -945,6 +957,8 @@ class LIBO(object):
|
||||||
target6.setDataArray(tuple(col6))
|
target6.setDataArray(tuple(col6))
|
||||||
if not target7 is None:
|
if not target7 is None:
|
||||||
target7.setDataArray(tuple(col7))
|
target7.setDataArray(tuple(col7))
|
||||||
|
if not target8 is None:
|
||||||
|
target8.setDataArray(tuple(col8))
|
||||||
|
|
||||||
if style_5:
|
if style_5:
|
||||||
cell_5.CellStyle = style_5
|
cell_5.CellStyle = style_5
|
||||||
|
@ -955,6 +969,9 @@ class LIBO(object):
|
||||||
if style_7:
|
if style_7:
|
||||||
cell_7.CellStyle = style_7
|
cell_7.CellStyle = style_7
|
||||||
target7.CellStyle = style_7
|
target7.CellStyle = style_7
|
||||||
|
if style_8:
|
||||||
|
cell_8.CellStyle = style_8
|
||||||
|
target8.CellStyle = style_8
|
||||||
return
|
return
|
||||||
|
|
||||||
def _add_totales(self, data):
|
def _add_totales(self, data):
|
||||||
|
@ -1067,6 +1084,12 @@ class LIBO(object):
|
||||||
self._set_cell('{ine.%s}' % k, v)
|
self._set_cell('{ine.%s}' % k, v)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _divisas(self, data):
|
||||||
|
if data:
|
||||||
|
for k, v in data.items():
|
||||||
|
self._set_cell(f'{{divisas.{k}}}', v)
|
||||||
|
return
|
||||||
|
|
||||||
def _nomina(self, data):
|
def _nomina(self, data):
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
@ -1275,10 +1298,12 @@ class LIBO(object):
|
||||||
self._currency = data['totales']['moneda']
|
self._currency = data['totales']['moneda']
|
||||||
self._pagos = data.pop('pagos', False)
|
self._pagos = data.pop('pagos', False)
|
||||||
|
|
||||||
|
pakings = data.pop('pakings', [])
|
||||||
|
|
||||||
self._comprobante(data['comprobante'])
|
self._comprobante(data['comprobante'])
|
||||||
self._emisor(data['emisor'])
|
self._emisor(data['emisor'])
|
||||||
self._receptor(data['receptor'])
|
self._receptor(data['receptor'])
|
||||||
self._conceptos(data['conceptos'])
|
self._conceptos(data['conceptos'], pakings)
|
||||||
|
|
||||||
if self._pagos:
|
if self._pagos:
|
||||||
self._cfdipays(data['pays'])
|
self._cfdipays(data['pays'])
|
||||||
|
@ -1291,6 +1316,8 @@ class LIBO(object):
|
||||||
self._donataria(data['donataria'])
|
self._donataria(data['donataria'])
|
||||||
self._ine(data['ine'])
|
self._ine(data['ine'])
|
||||||
|
|
||||||
|
self._divisas(data.get('divisas', {}))
|
||||||
|
|
||||||
self._cancelado(data['cancelada'])
|
self._cancelado(data['cancelada'])
|
||||||
self._clean()
|
self._clean()
|
||||||
return
|
return
|
||||||
|
@ -1298,6 +1325,7 @@ class LIBO(object):
|
||||||
def pdf(self, path, data, ods=False):
|
def pdf(self, path, data, ods=False):
|
||||||
options = {'AsTemplate': True, 'Hidden': True}
|
options = {'AsTemplate': True, 'Hidden': True}
|
||||||
log.debug('Abrir plantilla...')
|
log.debug('Abrir plantilla...')
|
||||||
|
|
||||||
self._template = self._doc_open(path, options)
|
self._template = self._doc_open(path, options)
|
||||||
if self._template is None:
|
if self._template is None:
|
||||||
return b''
|
return b''
|
||||||
|
@ -1444,7 +1472,16 @@ class LIBO(object):
|
||||||
return {}, msg
|
return {}, msg
|
||||||
|
|
||||||
data = tuple([r[2:] for r in rows[:count+2]])
|
data = tuple([r[2:] for r in rows[:count+2]])
|
||||||
return data, ''
|
|
||||||
|
sheet = doc.Sheets['Deducciones']
|
||||||
|
notes = sheet.getAnnotations()
|
||||||
|
new_titles = {}
|
||||||
|
for n in notes:
|
||||||
|
col = n.getPosition().Column - 2
|
||||||
|
if data[0][col] == '004':
|
||||||
|
new_titles[col] = n.getString()
|
||||||
|
|
||||||
|
return data, new_titles, ''
|
||||||
|
|
||||||
def _get_otros_pagos(self, doc, count):
|
def _get_otros_pagos(self, doc, count):
|
||||||
rows, msg = self._get_data(doc, 'OtrosPagos')
|
rows, msg = self._get_data(doc, 'OtrosPagos')
|
||||||
|
@ -1508,7 +1545,7 @@ class LIBO(object):
|
||||||
doc.close(True)
|
doc.close(True)
|
||||||
return {}, msg
|
return {}, msg
|
||||||
|
|
||||||
deducciones, msg = self._get_deducciones(doc, len(nomina))
|
deducciones, new_titles, msg = self._get_deducciones(doc, len(nomina))
|
||||||
if msg:
|
if msg:
|
||||||
doc.close(True)
|
doc.close(True)
|
||||||
return {}, msg
|
return {}, msg
|
||||||
|
@ -1534,6 +1571,28 @@ class LIBO(object):
|
||||||
return {}, msg
|
return {}, msg
|
||||||
|
|
||||||
doc.close(True)
|
doc.close(True)
|
||||||
|
|
||||||
|
rows = len(nomina) + 2
|
||||||
|
|
||||||
|
if rows != len(percepciones):
|
||||||
|
msg = 'Cantidad de filas incorrecta en: Percepciones'
|
||||||
|
return {}, msg
|
||||||
|
if rows != len(deducciones):
|
||||||
|
msg = 'Cantidad de filas incorrecta en: Deducciones'
|
||||||
|
return {}, msg
|
||||||
|
if rows != len(otros_pagos):
|
||||||
|
msg = 'Cantidad de filas incorrecta en: Otros Pagos'
|
||||||
|
return {}, msg
|
||||||
|
if rows != len(separacion):
|
||||||
|
msg = 'Cantidad de filas incorrecta en: Separación'
|
||||||
|
return {}, msg
|
||||||
|
if rows != len(horas_extras):
|
||||||
|
msg = 'Cantidad de filas incorrecta en: Horas Extras'
|
||||||
|
return {}, msg
|
||||||
|
if rows != len(incapacidades):
|
||||||
|
msg = 'Cantidad de filas incorrecta en: Incapacidades'
|
||||||
|
return {}, msg
|
||||||
|
|
||||||
data['nomina'] = nomina
|
data['nomina'] = nomina
|
||||||
data['percepciones'] = percepciones
|
data['percepciones'] = percepciones
|
||||||
data['deducciones'] = deducciones
|
data['deducciones'] = deducciones
|
||||||
|
@ -1541,6 +1600,7 @@ class LIBO(object):
|
||||||
data['separacion'] = separacion
|
data['separacion'] = separacion
|
||||||
data['horas_extras'] = horas_extras
|
data['horas_extras'] = horas_extras
|
||||||
data['incapacidades'] = incapacidades
|
data['incapacidades'] = incapacidades
|
||||||
|
data['new_titles'] = new_titles
|
||||||
|
|
||||||
return data, ''
|
return data, ''
|
||||||
|
|
||||||
|
@ -1563,11 +1623,13 @@ class LIBO(object):
|
||||||
|
|
||||||
def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'):
|
def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'):
|
||||||
rfc = data['emisor']['rfc']
|
rfc = data['emisor']['rfc']
|
||||||
|
default = 'plantilla_factura.ods'
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
rfc = emisor_rfc
|
rfc = emisor_rfc
|
||||||
version = data['comprobante']['version']
|
version = data['comprobante']['version']
|
||||||
if 'nomina' in data and data['nomina']:
|
if 'nomina' in data and data['nomina']:
|
||||||
version = '{}_{}'.format(data['nomina']['version'], version)
|
version = '{}_{}'.format(data['nomina']['version'], version)
|
||||||
|
default = 'plantilla_nomina.ods'
|
||||||
|
|
||||||
pagos = ''
|
pagos = ''
|
||||||
if data.get('pagos', False):
|
if data.get('pagos', False):
|
||||||
|
@ -1584,7 +1646,7 @@ def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'):
|
||||||
if data['donativo']:
|
if data['donativo']:
|
||||||
donativo = '_donativo'
|
donativo = '_donativo'
|
||||||
name = '{}_{}{}{}.ods'.format(rfc.lower(), pagos, version, donativo)
|
name = '{}_{}{}{}.ods'.format(rfc.lower(), pagos, version, donativo)
|
||||||
path = get_template_ods(name)
|
path = get_template_ods(name, default)
|
||||||
if path:
|
if path:
|
||||||
return app.pdf(path, data, ods)
|
return app.pdf(path, data, ods)
|
||||||
|
|
||||||
|
@ -2105,7 +2167,7 @@ def get_data_from_xml(invoice, values):
|
||||||
data['pagos'] = values.get('pagos', False)
|
data['pagos'] = values.get('pagos', False)
|
||||||
if data['pagos']:
|
if data['pagos']:
|
||||||
data['pays'] = _cfdipays(doc, data, version)
|
data['pays'] = _cfdipays(doc, data, version)
|
||||||
|
data['pakings'] = values.get('pakings', [])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@ -3764,3 +3826,5 @@ def validate_rfc(value):
|
||||||
def parse_xml2(xml_str):
|
def parse_xml2(xml_str):
|
||||||
return etree.fromstring(xml_str.encode('utf-8'))
|
return etree.fromstring(xml_str.encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# ~ Empresa Libre
|
||||||
|
# ~ Copyright (C) 2016-2019 Mauricio Baeza Servin (public@elmau.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 collections
|
||||||
|
import datetime
|
||||||
|
import math
|
||||||
|
import smtplib
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.base import MIMEBase
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email import encoders
|
||||||
|
from email.utils import formatdate
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import lxml.etree as ET
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from dateutil import parser
|
||||||
|
|
||||||
|
|
||||||
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
|
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
|
||||||
|
class CaseInsensitiveDict(collections.MutableMapping):
|
||||||
|
"""
|
||||||
|
A case-insensitive ``dict``-like object.
|
||||||
|
Implements all methods and operations of
|
||||||
|
``collections.MutableMapping`` as well as dict's ``copy``. Also
|
||||||
|
provides ``lower_items``.
|
||||||
|
All keys are expected to be strings. The structure remembers the
|
||||||
|
case of the last key to be set, and ``iter(instance)``,
|
||||||
|
``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
|
||||||
|
will contain case-sensitive keys. However, querying and contains
|
||||||
|
testing is case insensitive:
|
||||||
|
cid = CaseInsensitiveDict()
|
||||||
|
cid['Accept'] = 'application/json'
|
||||||
|
cid['aCCEPT'] == 'application/json' # True
|
||||||
|
list(cid) == ['Accept'] # True
|
||||||
|
For example, ``headers['content-encoding']`` will return the
|
||||||
|
value of a ``'Content-Encoding'`` response header, regardless
|
||||||
|
of how the header name was originally stored.
|
||||||
|
If the constructor, ``.update``, or equality comparison
|
||||||
|
operations are given keys that have equal ``.lower()``s, the
|
||||||
|
behavior is undefined.
|
||||||
|
"""
|
||||||
|
def __init__(self, data=None, **kwargs):
|
||||||
|
self._store = dict()
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
self.update(data, **kwargs)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# Use the lowercased key for lookups, but store the actual
|
||||||
|
# key alongside the value.
|
||||||
|
self._store[key.lower()] = (key, value)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._store[key.lower()][1]
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self._store[key.lower()]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return (casedkey for casedkey, mappedvalue in self._store.values())
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._store)
|
||||||
|
|
||||||
|
def lower_items(self):
|
||||||
|
"""Like iteritems(), but with all lowercase keys."""
|
||||||
|
return (
|
||||||
|
(lowerkey, keyval[1])
|
||||||
|
for (lowerkey, keyval)
|
||||||
|
in self._store.items()
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, collections.Mapping):
|
||||||
|
other = CaseInsensitiveDict(other)
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
# Compare insensitively
|
||||||
|
return dict(self.lower_items()) == dict(other.lower_items())
|
||||||
|
|
||||||
|
# Copy is required
|
||||||
|
def copy(self):
|
||||||
|
return CaseInsensitiveDict(self._store.values())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
|
||||||
|
|
||||||
|
|
||||||
|
class SendMail(object):
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self._config = config
|
||||||
|
self._server = None
|
||||||
|
self._error = ''
|
||||||
|
self._is_connect = self._login()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_connect(self):
|
||||||
|
return self._is_connect
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error(self):
|
||||||
|
return self._error
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
hosts = ('gmail' in self._config['server'] or
|
||||||
|
'outlook' in self._config['server'])
|
||||||
|
try:
|
||||||
|
if self._config['ssl'] and hosts:
|
||||||
|
self._server = smtplib.SMTP(
|
||||||
|
self._config['server'],
|
||||||
|
self._config['port'], timeout=TIMEOUT)
|
||||||
|
self._server.ehlo()
|
||||||
|
self._server.starttls()
|
||||||
|
self._server.ehlo()
|
||||||
|
elif self._config['ssl']:
|
||||||
|
self._server = smtplib.SMTP_SSL(
|
||||||
|
self._config['server'],
|
||||||
|
self._config['port'], timeout=TIMEOUT)
|
||||||
|
self._server.ehlo()
|
||||||
|
else:
|
||||||
|
self._server = smtplib.SMTP(
|
||||||
|
self._config['server'],
|
||||||
|
self._config['port'], timeout=TIMEOUT)
|
||||||
|
self._server.login(self._config['user'], self._config['pass'])
|
||||||
|
return True
|
||||||
|
except smtplib.SMTPAuthenticationError as e:
|
||||||
|
if '535' in str(e):
|
||||||
|
self._error = 'Nombre de usuario o contraseña inválidos'
|
||||||
|
return False
|
||||||
|
if '534' in str(e) and 'gmail' in self._config['server']:
|
||||||
|
self._error = 'Necesitas activar el acceso a otras ' \
|
||||||
|
'aplicaciones en tu cuenta de GMail'
|
||||||
|
return False
|
||||||
|
except smtplib.SMTPException as e:
|
||||||
|
self._error = str(e)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self._error = str(e)
|
||||||
|
return False
|
||||||
|
return
|
||||||
|
|
||||||
|
def send(self, options):
|
||||||
|
try:
|
||||||
|
message = MIMEMultipart()
|
||||||
|
message['From'] = self._config['user']
|
||||||
|
message['To'] = options['to']
|
||||||
|
message['CC'] = options.get('copy', '')
|
||||||
|
message['Subject'] = options['subject']
|
||||||
|
message['Date'] = formatdate(localtime=True)
|
||||||
|
if options.get('confirm', False):
|
||||||
|
message['Disposition-Notification-To'] = message['From']
|
||||||
|
message.attach(MIMEText(options['message'], 'html'))
|
||||||
|
for f in options.get('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])
|
||||||
|
encoders.encode_base64(part)
|
||||||
|
part.add_header(
|
||||||
|
'Content-Disposition',
|
||||||
|
"attachment; filename={}".format(f[1]))
|
||||||
|
message.attach(part)
|
||||||
|
|
||||||
|
receivers = options['to'].split(',') + message['CC'].split(',')
|
||||||
|
self._server.sendmail(
|
||||||
|
self._config['user'], receivers, message.as_string())
|
||||||
|
return ''
|
||||||
|
except Exception as e:
|
||||||
|
return str(e)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
self._server.quit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class CfdiToDict(object):
|
||||||
|
NS = {
|
||||||
|
'cfdi': 'http://www.sat.gob.mx/cfd/3',
|
||||||
|
'divisas': 'http://www.sat.gob.mx/divisas',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, xml):
|
||||||
|
self._values = {}
|
||||||
|
self._root = ET.parse(BytesIO(xml.encode())).getroot()
|
||||||
|
self._get_values()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def values(self):
|
||||||
|
return self._values
|
||||||
|
|
||||||
|
def _get_values(self):
|
||||||
|
self._complementos()
|
||||||
|
return
|
||||||
|
|
||||||
|
def _complementos(self):
|
||||||
|
complemento = self._root.xpath('//cfdi:Complemento', namespaces=self.NS)[0]
|
||||||
|
|
||||||
|
divisas = complemento.xpath('//divisas:Divisas', namespaces=self.NS)
|
||||||
|
if divisas:
|
||||||
|
d = CaseInsensitiveDict(divisas[0].attrib)
|
||||||
|
d.pop('version', '')
|
||||||
|
self._values.update({'divisas': d})
|
||||||
|
return
|
||||||
|
|
||||||
|
def send_mail(data):
|
||||||
|
msg = ''
|
||||||
|
ok = True
|
||||||
|
server = SendMail(data['server'])
|
||||||
|
if server.is_connect:
|
||||||
|
msg = server.send(data['mail'])
|
||||||
|
else:
|
||||||
|
msg = server.error
|
||||||
|
ok = False
|
||||||
|
server.close()
|
||||||
|
|
||||||
|
return {'ok': ok, 'msg': msg}
|
||||||
|
|
||||||
|
|
||||||
|
def round_up(value):
|
||||||
|
return int(math.ceil(value))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_key(password):
|
||||||
|
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
||||||
|
digest.update(password.encode())
|
||||||
|
key = base64.urlsafe_b64encode(digest.finalize())
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt(data, password):
|
||||||
|
f = Fernet(_get_key(password))
|
||||||
|
return f.encrypt(data.encode()).decode()
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt(data, password):
|
||||||
|
f = Fernet(_get_key(password))
|
||||||
|
return f.decrypt(data.encode()).decode()
|
||||||
|
|
||||||
|
|
||||||
|
def to_bool(value):
|
||||||
|
return bool(int(value))
|
||||||
|
|
||||||
|
|
||||||
|
def get_url(url):
|
||||||
|
r = requests.get(url).text
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def parse_date(value, next_day=False):
|
||||||
|
d = parser.parse(value)
|
||||||
|
if next_day:
|
||||||
|
return d + datetime.timedelta(days=1)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def to_zip(files):
|
||||||
|
zip_buffer = BytesIO()
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file:
|
||||||
|
for file_name, data in files.items():
|
||||||
|
zip_file.writestr(file_name, data)
|
||||||
|
|
||||||
|
return zip_buffer.getvalue()
|
||||||
|
|
|
@ -65,9 +65,15 @@ class JSONTranslator(object):
|
||||||
def process_response(self, req, resp, resource):
|
def process_response(self, req, resp, resource):
|
||||||
if 'result' not in req.context:
|
if 'result' not in req.context:
|
||||||
return
|
return
|
||||||
|
|
||||||
if '/doc/' in req.path:
|
if '/doc/' in req.path:
|
||||||
resp.body = req.context['result']
|
resp.body = req.context['result']
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if 'blob' in req.context:
|
||||||
|
resp.body = req.context['blob']
|
||||||
|
return
|
||||||
|
|
||||||
resp.body = util.dumps(req.context['result'])
|
resp.body = util.dumps(req.context['result'])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -384,8 +384,10 @@ class StorageEngine(object):
|
||||||
def get_tickets(self, values):
|
def get_tickets(self, values):
|
||||||
return main.Tickets.get_by(values)
|
return main.Tickets.get_by(values)
|
||||||
|
|
||||||
def get_invoices(self, values):
|
def get_invoices(self, filters):
|
||||||
return main.Facturas.get_(values)
|
if filters.get('by', ''):
|
||||||
|
return main.Facturas.get_by(filters)
|
||||||
|
return main.Facturas.get_(filters)
|
||||||
|
|
||||||
def get_preinvoices(self, values):
|
def get_preinvoices(self, values):
|
||||||
return main.PreFacturas.get_(values)
|
return main.PreFacturas.get_(values)
|
||||||
|
|
|
@ -29,8 +29,12 @@ if __name__ == '__main__':
|
||||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
sys.path.insert(0, parent_dir)
|
sys.path.insert(0, parent_dir)
|
||||||
|
|
||||||
|
# ~ v2
|
||||||
|
from controllers import utils
|
||||||
|
|
||||||
|
# ~ v1
|
||||||
from controllers import util
|
from controllers import util
|
||||||
|
|
||||||
from settings import log, DEBUG, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \
|
from settings import log, DEBUG, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \
|
||||||
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
|
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
|
||||||
CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \
|
CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \
|
||||||
|
@ -286,6 +290,7 @@ def config_timbrar():
|
||||||
'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'),
|
'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'),
|
||||||
'cfdi_ine': Configuracion.get_bool('chk_config_ine'),
|
'cfdi_ine': Configuracion.get_bool('chk_config_ine'),
|
||||||
'cfdi_edu': Configuracion.get_bool('chk_config_edu'),
|
'cfdi_edu': Configuracion.get_bool('chk_config_edu'),
|
||||||
|
'cfdi_divisas': Configuracion.get_bool('chk_config_divisas'),
|
||||||
'cfdi_metodo_pago': Configuracion.get_bool('chk_config_ocultar_metodo_pago'),
|
'cfdi_metodo_pago': Configuracion.get_bool('chk_config_ocultar_metodo_pago'),
|
||||||
'cfdi_condicion_pago': Configuracion.get_bool('chk_config_ocultar_condiciones_pago'),
|
'cfdi_condicion_pago': Configuracion.get_bool('chk_config_ocultar_condiciones_pago'),
|
||||||
'cfdi_open_pdf': Configuracion.get_bool('chk_config_open_pdf'),
|
'cfdi_open_pdf': Configuracion.get_bool('chk_config_open_pdf'),
|
||||||
|
@ -347,6 +352,17 @@ class Configuracion(BaseModel):
|
||||||
msg = 'No se pudo guardar la configuración'
|
msg = 'No se pudo guardar la configuración'
|
||||||
return {'ok': result, 'msg': msg}
|
return {'ok': result, 'msg': msg}
|
||||||
|
|
||||||
|
def _save_mail(self, values):
|
||||||
|
rfc = Emisor.select()[0].rfc
|
||||||
|
values['correo_contra'] = utils.encrypt(values['correo_contra'], rfc)
|
||||||
|
|
||||||
|
for k, v in values.items():
|
||||||
|
obj, created = Configuracion.get_or_create(clave=k)
|
||||||
|
obj.valor = v
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
return {'ok': True}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_bool(cls, key):
|
def get_bool(cls, key):
|
||||||
data = (Configuracion
|
data = (Configuracion
|
||||||
|
@ -368,11 +384,45 @@ class Configuracion(BaseModel):
|
||||||
values = {r.clave: util.get_bool(r.valor) for r in data}
|
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def _get_admin_products(self):
|
||||||
|
fields = (
|
||||||
|
'chk_config_cuenta_predial',
|
||||||
|
'chk_config_codigo_barras',
|
||||||
|
'chk_config_precio_con_impuestos',
|
||||||
|
'chk_llevar_inventario',
|
||||||
|
'chk_use_packing',
|
||||||
|
)
|
||||||
|
data = (Configuracion
|
||||||
|
.select()
|
||||||
|
.where(Configuracion.clave.in_(fields))
|
||||||
|
)
|
||||||
|
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _get_main_products(self):
|
||||||
|
fields = (
|
||||||
|
'chk_config_cuenta_predial',
|
||||||
|
'chk_config_codigo_barras',
|
||||||
|
'chk_config_precio_con_impuestos',
|
||||||
|
'chk_llevar_inventario',
|
||||||
|
'chk_use_packing',
|
||||||
|
)
|
||||||
|
data = (Configuracion
|
||||||
|
.select()
|
||||||
|
.where(Configuracion.clave.in_(fields))
|
||||||
|
)
|
||||||
|
values = {r.clave: r.valor for r in data}
|
||||||
|
values['default_tax'] = SATImpuestos.select()[0].id
|
||||||
|
values['default_unidad'] = SATUnidades.get_default()
|
||||||
|
return values
|
||||||
|
|
||||||
def _get_complements(self):
|
def _get_complements(self):
|
||||||
fields = (
|
fields = (
|
||||||
'chk_config_ine',
|
'chk_config_ine',
|
||||||
'chk_config_edu',
|
'chk_config_edu',
|
||||||
'chk_config_pagos',
|
'chk_config_pagos',
|
||||||
|
'chk_config_divisas',
|
||||||
'chk_cfg_pays_data_bank',
|
'chk_cfg_pays_data_bank',
|
||||||
)
|
)
|
||||||
data = (Configuracion
|
data = (Configuracion
|
||||||
|
@ -401,6 +451,28 @@ class Configuracion(BaseModel):
|
||||||
values = {r.clave: util.get_bool(r.valor) for r in data}
|
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
def _get_correo(self):
|
||||||
|
fields = ('correo_servidor', 'correo_puerto', 'correo_ssl',
|
||||||
|
'correo_usuario', 'correo_copia', 'correo_asunto',
|
||||||
|
'correo_mensaje', 'correo_directo', 'correo_confirmacion')
|
||||||
|
data = (Configuracion
|
||||||
|
.select()
|
||||||
|
.where(Configuracion.clave.in_(fields))
|
||||||
|
)
|
||||||
|
values = {r.clave: r.valor for r in data}
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _get_admin_config_users(self):
|
||||||
|
fields = (
|
||||||
|
'chk_users_notify_access',
|
||||||
|
)
|
||||||
|
data = (Configuracion
|
||||||
|
.select()
|
||||||
|
.where(Configuracion.clave.in_(fields))
|
||||||
|
)
|
||||||
|
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||||
|
return values
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_(cls, keys):
|
def get_(cls, keys):
|
||||||
if isinstance(keys, str):
|
if isinstance(keys, str):
|
||||||
|
@ -412,27 +484,18 @@ class Configuracion(BaseModel):
|
||||||
return data[0].valor
|
return data[0].valor
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
options = ('partners', 'complements', 'folios')
|
options = ('partners',
|
||||||
|
'admin_products',
|
||||||
|
'main_products',
|
||||||
|
'complements',
|
||||||
|
'folios',
|
||||||
|
'correo',
|
||||||
|
'admin_config_users',
|
||||||
|
)
|
||||||
opt = keys['fields']
|
opt = keys['fields']
|
||||||
if opt in options:
|
if opt in options:
|
||||||
return getattr(cls, '_get_{}'.format(opt))(cls)
|
return getattr(cls, '_get_{}'.format(opt))(cls)
|
||||||
|
|
||||||
if keys['fields'] == 'productos':
|
|
||||||
fields = (
|
|
||||||
'chk_config_cuenta_predial',
|
|
||||||
'chk_config_codigo_barras',
|
|
||||||
'chk_config_precio_con_impuestos',
|
|
||||||
'chk_llevar_inventario',
|
|
||||||
)
|
|
||||||
data = (Configuracion
|
|
||||||
.select()
|
|
||||||
.where(Configuracion.clave.in_(fields))
|
|
||||||
)
|
|
||||||
values = {r.clave: r.valor for r in data}
|
|
||||||
values['default_tax'] = SATImpuestos.select()[0].id
|
|
||||||
values['default_unidad'] = SATUnidades.get_default()
|
|
||||||
return values
|
|
||||||
|
|
||||||
if keys['fields'] == 'configtemplates':
|
if keys['fields'] == 'configtemplates':
|
||||||
try:
|
try:
|
||||||
emisor = Emisor.select()[0]
|
emisor = Emisor.select()[0]
|
||||||
|
@ -465,10 +528,6 @@ class Configuracion(BaseModel):
|
||||||
'chk_config_tax_locales_truncate',
|
'chk_config_tax_locales_truncate',
|
||||||
'chk_config_decimales_precios',
|
'chk_config_decimales_precios',
|
||||||
'chk_config_anticipo',
|
'chk_config_anticipo',
|
||||||
'chk_config_cuenta_predial',
|
|
||||||
'chk_config_codigo_barras',
|
|
||||||
'chk_config_precio_con_impuestos',
|
|
||||||
'chk_llevar_inventario',
|
|
||||||
'chk_usar_punto_de_venta',
|
'chk_usar_punto_de_venta',
|
||||||
'chk_ticket_pdf_show',
|
'chk_ticket_pdf_show',
|
||||||
'chk_ticket_direct_print',
|
'chk_ticket_direct_print',
|
||||||
|
@ -493,16 +552,7 @@ class Configuracion(BaseModel):
|
||||||
values[f] = Configuracion.get_(f)
|
values[f] = Configuracion.get_(f)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
if keys['fields'] == 'correo':
|
if keys['fields'] == 'path_cer':
|
||||||
fields = ('correo_servidor', 'correo_puerto', 'correo_ssl',
|
|
||||||
'correo_usuario', 'correo_contra', 'correo_copia',
|
|
||||||
'correo_asunto', 'correo_mensaje', 'correo_directo',
|
|
||||||
'correo_confirmacion')
|
|
||||||
data = (Configuracion
|
|
||||||
.select()
|
|
||||||
.where(Configuracion.clave.in_(fields))
|
|
||||||
)
|
|
||||||
elif keys['fields'] == 'path_cer':
|
|
||||||
fields = ('path_key', 'path_cer')
|
fields = ('path_key', 'path_cer')
|
||||||
data = (Configuracion
|
data = (Configuracion
|
||||||
.select()
|
.select()
|
||||||
|
@ -3204,6 +3254,8 @@ class Productos(BaseModel):
|
||||||
es_activo = BooleanField(default=True)
|
es_activo = BooleanField(default=True)
|
||||||
impuestos = ManyToManyField(SATImpuestos, related_name='productos')
|
impuestos = ManyToManyField(SATImpuestos, related_name='productos')
|
||||||
tags = ManyToManyField(Tags, related_name='productos_tags')
|
tags = ManyToManyField(Tags, related_name='productos_tags')
|
||||||
|
cantidad_empaque = DecimalField(default=0.0, max_digits=14, decimal_places=4,
|
||||||
|
auto_round=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
order_by = ('descripcion',)
|
order_by = ('descripcion',)
|
||||||
|
@ -3415,6 +3467,7 @@ class Productos(BaseModel):
|
||||||
Productos.inventario,
|
Productos.inventario,
|
||||||
Productos.existencia,
|
Productos.existencia,
|
||||||
Productos.minimo,
|
Productos.minimo,
|
||||||
|
Productos.cantidad_empaque.alias('cant_by_packing'),
|
||||||
)
|
)
|
||||||
.where(Productos.id==id).dicts()[0]
|
.where(Productos.id==id).dicts()[0]
|
||||||
)
|
)
|
||||||
|
@ -3442,6 +3495,7 @@ class Productos(BaseModel):
|
||||||
descripcion = util.spaces(values.pop('descripcion'))
|
descripcion = util.spaces(values.pop('descripcion'))
|
||||||
fields = util.clean(values)
|
fields = util.clean(values)
|
||||||
|
|
||||||
|
fields['cantidad_empaque'] = fields.pop('cant_by_packing', 0.0)
|
||||||
fields.pop('precio_con_impuestos', '')
|
fields.pop('precio_con_impuestos', '')
|
||||||
fields['es_activo'] = fields.pop('es_activo_producto')
|
fields['es_activo'] = fields.pop('es_activo_producto')
|
||||||
fields['descripcion'] = descripcion
|
fields['descripcion'] = descripcion
|
||||||
|
@ -3484,6 +3538,7 @@ class Productos(BaseModel):
|
||||||
def actualizar(cls, values, id):
|
def actualizar(cls, values, id):
|
||||||
values['cuenta_predial'] = values.get('cuenta_predial', '')
|
values['cuenta_predial'] = values.get('cuenta_predial', '')
|
||||||
values['codigo_barras'] = values.get('codigo_barras', '')
|
values['codigo_barras'] = values.get('codigo_barras', '')
|
||||||
|
|
||||||
fields, taxes = cls._clean(cls, values)
|
fields, taxes = cls._clean(cls, values)
|
||||||
obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
|
obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
|
||||||
with database_proxy.transaction():
|
with database_proxy.transaction():
|
||||||
|
@ -3577,6 +3632,7 @@ class Facturas(BaseModel):
|
||||||
estatus = TextField(default='Guardada')
|
estatus = TextField(default='Guardada')
|
||||||
estatus_sat = TextField(default='Vigente')
|
estatus_sat = TextField(default='Vigente')
|
||||||
regimen_fiscal = TextField(default='')
|
regimen_fiscal = TextField(default='')
|
||||||
|
divisas = TextField(default='')
|
||||||
notas = TextField(default='')
|
notas = TextField(default='')
|
||||||
saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||||
auto_round=True)
|
auto_round=True)
|
||||||
|
@ -3663,6 +3719,65 @@ class Facturas(BaseModel):
|
||||||
obj.save()
|
obj.save()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def _get_filters(self, values):
|
||||||
|
if 'start' in values:
|
||||||
|
filters = Facturas.fecha.between(
|
||||||
|
utils.parse_date(values['start']),
|
||||||
|
utils.parse_date(values['end'], True)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if values['year'] == '-1':
|
||||||
|
fy = (Facturas.fecha.year > 0)
|
||||||
|
else:
|
||||||
|
fy = (Facturas.fecha.year == int(values['year']))
|
||||||
|
if values['month'] == '-1':
|
||||||
|
fm = (Facturas.fecha.month > 0)
|
||||||
|
else:
|
||||||
|
fm = (Facturas.fecha.month == int(values['month']))
|
||||||
|
filters = (fy & fm)
|
||||||
|
|
||||||
|
if 'client' in values:
|
||||||
|
filters &= (Socios.nombre==values['client'])
|
||||||
|
|
||||||
|
return filters
|
||||||
|
|
||||||
|
def _get_invoices(self, filters):
|
||||||
|
rows = tuple(Facturas.select(
|
||||||
|
Facturas.id,
|
||||||
|
Facturas.serie,
|
||||||
|
Facturas.folio,
|
||||||
|
Facturas.uuid,
|
||||||
|
Facturas.fecha,
|
||||||
|
Facturas.tipo_comprobante,
|
||||||
|
Facturas.estatus,
|
||||||
|
case(Facturas.pagada, (
|
||||||
|
(True, 'Si'),
|
||||||
|
(False, 'No'),
|
||||||
|
)).alias('paid'),
|
||||||
|
Facturas.total,
|
||||||
|
Facturas.moneda.alias('currency'),
|
||||||
|
Facturas.total_mn,
|
||||||
|
Socios.nombre.alias('cliente'))
|
||||||
|
.where(filters)
|
||||||
|
.join(Socios)
|
||||||
|
.switch(Facturas).dicts()
|
||||||
|
)
|
||||||
|
return {'ok': True, 'rows': rows}
|
||||||
|
|
||||||
|
def _get_by_dates(self, filters):
|
||||||
|
filters = self._get_filters(self, filters)
|
||||||
|
return self._get_invoices(self, filters)
|
||||||
|
|
||||||
|
def _get_by_notes(self, filters):
|
||||||
|
notes = filters['notes']
|
||||||
|
filters = self._get_filters(self, filters)
|
||||||
|
filters &= (Facturas.notas.contains(notes))
|
||||||
|
return self._get_invoices(self, filters)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by(cls, filters):
|
||||||
|
return getattr(cls, f"_get_by_{filters['by']}")(cls, filters)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter_years(cls):
|
def filter_years(cls):
|
||||||
data = [{'id': -1, 'value': 'Todos'}]
|
data = [{'id': -1, 'value': 'Todos'}]
|
||||||
|
@ -3695,6 +3810,8 @@ class Facturas(BaseModel):
|
||||||
return {'ok': True, 'msg': 'Notas guardadas correctamente'}
|
return {'ok': True, 'msg': 'Notas guardadas correctamente'}
|
||||||
|
|
||||||
def _get_not_in_xml(self, invoice, emisor):
|
def _get_not_in_xml(self, invoice, emisor):
|
||||||
|
pdf_from = Configuracion.get_('make_pdf_from') or '1'
|
||||||
|
|
||||||
values = {}
|
values = {}
|
||||||
|
|
||||||
values['notas'] = invoice.notas
|
values['notas'] = invoice.notas
|
||||||
|
@ -3732,6 +3849,16 @@ class Facturas(BaseModel):
|
||||||
for k, v in receptor.items():
|
for k, v in receptor.items():
|
||||||
values['receptor'][k] = v
|
values['receptor'][k] = v
|
||||||
|
|
||||||
|
use_packing = Configuracion.get_bool('chk_use_packing')
|
||||||
|
if use_packing:
|
||||||
|
w = FacturasDetalle.factura == invoice
|
||||||
|
q = (FacturasDetalle
|
||||||
|
.select(FacturasDetalle.empaques)
|
||||||
|
.where(w)
|
||||||
|
.order_by(FacturasDetalle.id.asc())
|
||||||
|
.tuples())
|
||||||
|
values['pakings'] = [str(int(r[0])) for r in q]
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -3748,7 +3875,11 @@ class Facturas(BaseModel):
|
||||||
|
|
||||||
pdf_from = Configuracion.get_('make_pdf_from') or '1'
|
pdf_from = Configuracion.get_('make_pdf_from') or '1'
|
||||||
values = cls._get_not_in_xml(cls, obj, emisor)
|
values = cls._get_not_in_xml(cls, obj, emisor)
|
||||||
|
|
||||||
|
#Tmp to v2
|
||||||
data = util.get_data_from_xml(obj, values)
|
data = util.get_data_from_xml(obj, values)
|
||||||
|
data.update(utils.CfdiToDict(obj.xml).values)
|
||||||
|
|
||||||
doc = util.to_pdf(data, emisor.rfc, pdf_from=pdf_from)
|
doc = util.to_pdf(data, emisor.rfc, pdf_from=pdf_from)
|
||||||
|
|
||||||
if sync:
|
if sync:
|
||||||
|
@ -3980,6 +4111,7 @@ class Facturas(BaseModel):
|
||||||
@classmethod
|
@classmethod
|
||||||
def send(cls, id, rfc):
|
def send(cls, id, rfc):
|
||||||
values = Configuracion.get_({'fields': 'correo'})
|
values = Configuracion.get_({'fields': 'correo'})
|
||||||
|
contra = Configuracion.get_('correo_contra')
|
||||||
in_zip = Configuracion.get_bool('chk_config_send_zip')
|
in_zip = Configuracion.get_bool('chk_config_send_zip')
|
||||||
|
|
||||||
if not values:
|
if not values:
|
||||||
|
@ -4006,7 +4138,7 @@ class Facturas(BaseModel):
|
||||||
'puerto': values['correo_puerto'],
|
'puerto': values['correo_puerto'],
|
||||||
'ssl': bool(int(values['correo_ssl'])),
|
'ssl': bool(int(values['correo_ssl'])),
|
||||||
'usuario': values['correo_usuario'],
|
'usuario': values['correo_usuario'],
|
||||||
'contra': values['correo_contra'],
|
'contra': utils.decrypt(contra, rfc),
|
||||||
}
|
}
|
||||||
options = {
|
options = {
|
||||||
'para': obj.cliente.correo_facturas,
|
'para': obj.cliente.correo_facturas,
|
||||||
|
@ -4285,6 +4417,8 @@ class Facturas(BaseModel):
|
||||||
tax_locales = Configuracion.get_bool('chk_config_tax_locales')
|
tax_locales = Configuracion.get_bool('chk_config_tax_locales')
|
||||||
tax_locales_truncate = Configuracion.get_bool('chk_config_tax_locales_truncate')
|
tax_locales_truncate = Configuracion.get_bool('chk_config_tax_locales_truncate')
|
||||||
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
|
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
|
||||||
|
use_packing = Configuracion.get_bool('chk_use_packing')
|
||||||
|
|
||||||
subtotal = 0
|
subtotal = 0
|
||||||
descuento_cfdi = 0
|
descuento_cfdi = 0
|
||||||
totals_tax = {}
|
totals_tax = {}
|
||||||
|
@ -4312,6 +4446,10 @@ class Facturas(BaseModel):
|
||||||
precio_final = valor_unitario - descuento
|
precio_final = valor_unitario - descuento
|
||||||
importe = round(cantidad * precio_final, DECIMALES)
|
importe = round(cantidad * precio_final, DECIMALES)
|
||||||
|
|
||||||
|
if use_packing and p.cantidad_empaque:
|
||||||
|
product['empaques'] = utils.round_up(
|
||||||
|
cantidad / float(p.cantidad_empaque))
|
||||||
|
|
||||||
product['cantidad'] = cantidad
|
product['cantidad'] = cantidad
|
||||||
product['valor_unitario'] = valor_unitario
|
product['valor_unitario'] = valor_unitario
|
||||||
product['descuento'] = round(descuento * cantidad, DECIMALES)
|
product['descuento'] = round(descuento * cantidad, DECIMALES)
|
||||||
|
@ -4434,6 +4572,12 @@ class Facturas(BaseModel):
|
||||||
ine = values.pop('ine', {})
|
ine = values.pop('ine', {})
|
||||||
tipo_comprobante = values['tipo_comprobante']
|
tipo_comprobante = values['tipo_comprobante']
|
||||||
folio_custom = values.pop('folio_custom', '')
|
folio_custom = values.pop('folio_custom', '')
|
||||||
|
divisas = values.pop('divisas', '')
|
||||||
|
if Configuracion.get_bool('chk_config_divisas'):
|
||||||
|
divisas = divisas.lower()
|
||||||
|
if divisas == 'ninguna':
|
||||||
|
divisas = ''
|
||||||
|
values['divisas'] = divisas
|
||||||
|
|
||||||
emisor = Emisor.select()[0]
|
emisor = Emisor.select()[0]
|
||||||
values['serie'] = cls._get_serie(cls, user, values['serie'])
|
values['serie'] = cls._get_serie(cls, user, values['serie'])
|
||||||
|
@ -4504,6 +4648,9 @@ class Facturas(BaseModel):
|
||||||
relacionados = {}
|
relacionados = {}
|
||||||
donativo = {}
|
donativo = {}
|
||||||
complementos = FacturasComplementos.get_(invoice)
|
complementos = FacturasComplementos.get_(invoice)
|
||||||
|
comprobante['divisas'] = invoice.divisas
|
||||||
|
if invoice.divisas:
|
||||||
|
complementos['divisas'] = True
|
||||||
|
|
||||||
if invoice.donativo:
|
if invoice.donativo:
|
||||||
donativo['noAutorizacion'] = emisor.autorizacion
|
donativo['noAutorizacion'] = emisor.autorizacion
|
||||||
|
@ -4736,6 +4883,7 @@ class Facturas(BaseModel):
|
||||||
'edu': is_edu,
|
'edu': is_edu,
|
||||||
'complementos': complementos,
|
'complementos': complementos,
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.make_xml(data, certificado, auth)
|
return util.make_xml(data, certificado, auth)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -5616,6 +5764,8 @@ class FacturasDetalle(BaseModel):
|
||||||
nivel = TextField(default='')
|
nivel = TextField(default='')
|
||||||
autorizacion = TextField(default='')
|
autorizacion = TextField(default='')
|
||||||
cuenta_predial = TextField(default='')
|
cuenta_predial = TextField(default='')
|
||||||
|
empaques = DecimalField(default=0.0, max_digits=14, decimal_places=4,
|
||||||
|
auto_round=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
order_by = ('factura',)
|
order_by = ('factura',)
|
||||||
|
@ -7493,7 +7643,6 @@ class CfdiNomina(BaseModel):
|
||||||
data['fecha_pago'] = util.calc_to_date(row['fecha_pago'])
|
data['fecha_pago'] = util.calc_to_date(row['fecha_pago'])
|
||||||
data['fecha_inicial_pago'] = util.calc_to_date(row['fecha_inicial_pago'])
|
data['fecha_inicial_pago'] = util.calc_to_date(row['fecha_inicial_pago'])
|
||||||
data['fecha_final_pago'] = util.calc_to_date(row['fecha_final_pago'])
|
data['fecha_final_pago'] = util.calc_to_date(row['fecha_final_pago'])
|
||||||
# ~ data['dias_pagados'] = util.get_days(data['fecha_inicial_pago'], data['fecha_final_pago'])
|
|
||||||
data['dias_pagados'] = days_pay
|
data['dias_pagados'] = days_pay
|
||||||
|
|
||||||
return data, ''
|
return data, ''
|
||||||
|
@ -7546,7 +7695,7 @@ class CfdiNomina(BaseModel):
|
||||||
|
|
||||||
return data, totals, ''
|
return data, totals, ''
|
||||||
|
|
||||||
def _validate_deducciones(self, headers, row):
|
def _validate_deducciones(self, headers, row, new_titles):
|
||||||
total_retenciones = 0.0
|
total_retenciones = 0.0
|
||||||
total_otras_deducciones = 0.0
|
total_otras_deducciones = 0.0
|
||||||
|
|
||||||
|
@ -7572,6 +7721,7 @@ class CfdiNomina(BaseModel):
|
||||||
new = {
|
new = {
|
||||||
'tipo_deduccion': td,
|
'tipo_deduccion': td,
|
||||||
'importe': importe,
|
'importe': importe,
|
||||||
|
'concepto': new_titles.get(i, ''),
|
||||||
}
|
}
|
||||||
data.append(new)
|
data.append(new)
|
||||||
|
|
||||||
|
@ -7735,6 +7885,7 @@ class CfdiNomina(BaseModel):
|
||||||
separacion = data['separacion'][2:]
|
separacion = data['separacion'][2:]
|
||||||
horas_extras = data['horas_extras'][2:]
|
horas_extras = data['horas_extras'][2:]
|
||||||
incapacidades = data['incapacidades'][2:]
|
incapacidades = data['incapacidades'][2:]
|
||||||
|
new_titles = data['new_titles']
|
||||||
|
|
||||||
for i, row in enumerate(data['nomina']):
|
for i, row in enumerate(data['nomina']):
|
||||||
row['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
|
row['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
|
||||||
|
@ -7756,7 +7907,7 @@ class CfdiNomina(BaseModel):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
new_deducciones, total_deducciones, msg = \
|
new_deducciones, total_deducciones, msg = \
|
||||||
self._validate_deducciones(self, hd, deducciones[i])
|
self._validate_deducciones(self, hd, deducciones[i], new_titles)
|
||||||
if msg:
|
if msg:
|
||||||
util.log_file('nomina', msg)
|
util.log_file('nomina', msg)
|
||||||
continue
|
continue
|
||||||
|
@ -8190,11 +8341,34 @@ class CfdiNomina(BaseModel):
|
||||||
|
|
||||||
return {'ok': ok, 'msg_ok': msg, 'error': error, 'msg_error': msg_error}
|
return {'ok': ok, 'msg_ok': msg, 'error': error, 'msg_error': msg_error}
|
||||||
|
|
||||||
|
def _get_by_download(self, filters):
|
||||||
|
emisor = Emisor.select()[0]
|
||||||
|
ids = util.loads(filters['ids'])
|
||||||
|
w = CfdiNomina.id.in_(ids)
|
||||||
|
rows = CfdiNomina.select().where(w)
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
for row in rows:
|
||||||
|
name = '{}{}_{}'.format(row.serie, row.folio, row.empleado.rfc)
|
||||||
|
files[f'{name}.xml'] = row.xml
|
||||||
|
|
||||||
|
values = self._get_not_in_xml(self, row, emisor)
|
||||||
|
data = util.get_data_from_xml(row, values)
|
||||||
|
doc = util.to_pdf(data, emisor.rfc)
|
||||||
|
files[f'{name}.pdf'] = doc
|
||||||
|
|
||||||
|
fz = utils.to_zip(files)
|
||||||
|
|
||||||
|
return {'data': fz, 'name': name + 'zip'}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by(cls, values):
|
def get_by(cls, values):
|
||||||
if not values:
|
if not values:
|
||||||
return cls._get(cls)
|
return cls._get(cls)
|
||||||
|
|
||||||
|
if values.get('by', ''):
|
||||||
|
return getattr(cls, f"_get_by_{values['by']}")(cls, values)
|
||||||
|
|
||||||
if values['opt'] == 'dates':
|
if values['opt'] == 'dates':
|
||||||
dates = util.loads(values['range'])
|
dates = util.loads(values['range'])
|
||||||
filters = CfdiNomina.fecha.between(
|
filters = CfdiNomina.fecha.between(
|
||||||
|
@ -8508,6 +8682,50 @@ def _save_log(user, action, table):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@util.run_in_thread
|
||||||
|
def _send_notify_access(args):
|
||||||
|
admins = (Usuarios
|
||||||
|
.select(Usuarios.correo)
|
||||||
|
.where(Usuarios.es_admin==True)
|
||||||
|
.scalar(as_tuple=True))
|
||||||
|
|
||||||
|
if not admins:
|
||||||
|
return
|
||||||
|
|
||||||
|
config = Configuracion.get_({'fields': 'correo'})
|
||||||
|
contra = Configuracion.get_('correo_contra')
|
||||||
|
if not config:
|
||||||
|
return
|
||||||
|
|
||||||
|
user = args['usuario']
|
||||||
|
rfc = args['rfc']
|
||||||
|
ip = args['ip']
|
||||||
|
|
||||||
|
url = f"http://ip-api.com/line/{ip}?fields=city"
|
||||||
|
city = utils.get_url(url)
|
||||||
|
message = f"Desde la IP: {ip} en: {city}"
|
||||||
|
|
||||||
|
server = {
|
||||||
|
'server': config['correo_servidor'],
|
||||||
|
'port': config['correo_puerto'],
|
||||||
|
'ssl': utils.to_bool(config['correo_ssl']),
|
||||||
|
'user': config['correo_usuario'],
|
||||||
|
'pass': utils.decrypt(contra, rfc),
|
||||||
|
}
|
||||||
|
mail = {
|
||||||
|
'to': ','.join(admins),
|
||||||
|
'subject': f"Usuario {user} identificado",
|
||||||
|
'message': message,
|
||||||
|
}
|
||||||
|
data= {
|
||||||
|
'server': server,
|
||||||
|
'mail': mail,
|
||||||
|
}
|
||||||
|
result = utils.send_mail(data)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def authenticate(args):
|
def authenticate(args):
|
||||||
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
|
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
|
||||||
values = util.get_con(args['rfc'])
|
values = util.get_con(args['rfc'])
|
||||||
|
@ -8532,7 +8750,11 @@ def authenticate(args):
|
||||||
respuesta['login'] = True
|
respuesta['login'] = True
|
||||||
respuesta['user'] = str(obj)
|
respuesta['user'] = str(obj)
|
||||||
respuesta['super'] = obj.es_superusuario
|
respuesta['super'] = obj.es_superusuario
|
||||||
#~ respuesta['admin'] = obj.es_superusuario or obj.es_admin
|
|
||||||
|
notify_access = Configuracion.get_bool('chk_users_notify_access')
|
||||||
|
if notify_access:
|
||||||
|
_send_notify_access(args)
|
||||||
|
|
||||||
return respuesta, obj
|
return respuesta, obj
|
||||||
|
|
||||||
|
|
||||||
|
@ -8835,10 +9057,34 @@ def _migrate_tables(rfc=''):
|
||||||
activa = BooleanField(default=True)
|
activa = BooleanField(default=True)
|
||||||
migrations.append(migrator.add_column(table, 'activa', activa))
|
migrations.append(migrator.add_column(table, 'activa', activa))
|
||||||
|
|
||||||
|
table = 'productos'
|
||||||
|
columns = [c.name for c in database_proxy.get_columns(table)]
|
||||||
|
if not 'cantidad_empaque' in columns:
|
||||||
|
cantidad_empaque = DecimalField(default=0.0, max_digits=14,
|
||||||
|
decimal_places=4, auto_round=True)
|
||||||
|
migrations.append(migrator.add_column(
|
||||||
|
table, 'cantidad_empaque', cantidad_empaque))
|
||||||
|
|
||||||
|
table = 'facturasdetalle'
|
||||||
|
columns = [c.name for c in database_proxy.get_columns(table)]
|
||||||
|
if not 'empaques' in columns:
|
||||||
|
empaques = DecimalField(default=0.0, max_digits=14,
|
||||||
|
decimal_places=4, auto_round=True)
|
||||||
|
migrations.append(migrator.add_column(
|
||||||
|
table, 'empaques', empaques))
|
||||||
|
|
||||||
|
table = 'facturas'
|
||||||
|
columns = [c.name for c in database_proxy.get_columns(table)]
|
||||||
|
if not 'divisas' in columns:
|
||||||
|
divisas = TextField(default='')
|
||||||
|
migrations.append(migrator.add_column(table, 'divisas', divisas))
|
||||||
|
|
||||||
if migrations:
|
if migrations:
|
||||||
with database_proxy.atomic() as txn:
|
with database_proxy.atomic() as txn:
|
||||||
migrate(*migrations)
|
migrate(*migrations)
|
||||||
|
|
||||||
|
Configuracion.add({'version': VERSION})
|
||||||
|
|
||||||
log.info('Tablas migradas correctamente...')
|
log.info('Tablas migradas correctamente...')
|
||||||
_importar_valores('', rfc)
|
_importar_valores('', rfc)
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,8 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
DEBUG = DEBUG
|
DEBUG = DEBUG
|
||||||
VERSION = '1.27.1'
|
VERSION = '1.28.0'
|
||||||
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
|
||||||
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
|
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
|
||||||
|
|
||||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
|
@ -47,6 +47,13 @@
|
||||||
font-size: 125%;
|
font-size: 125%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link_default {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #610B0B;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.link_default:hover {text-decoration:underline;}
|
||||||
|
|
||||||
.link_forum {
|
.link_forum {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #610B0B;
|
color: #610B0B;
|
||||||
|
|
|
@ -59,11 +59,13 @@ var controllers = {
|
||||||
$$('grid_admin_unidades').attachEvent('onItemClick', grid_admin_unidades_click)
|
$$('grid_admin_unidades').attachEvent('onItemClick', grid_admin_unidades_click)
|
||||||
$$('grid_moneda_found').attachEvent('onValueSuggest', grid_moneda_found_click)
|
$$('grid_moneda_found').attachEvent('onValueSuggest', grid_moneda_found_click)
|
||||||
$$('cmd_agregar_impuesto').attachEvent('onItemClick', cmd_agregar_impuesto_click)
|
$$('cmd_agregar_impuesto').attachEvent('onItemClick', cmd_agregar_impuesto_click)
|
||||||
|
|
||||||
//~ Usuarios
|
//~ Usuarios
|
||||||
$$('cmd_usuario_agregar').attachEvent('onItemClick', cmd_usuario_agregar_click)
|
$$('cmd_usuario_agregar').attachEvent('onItemClick', cmd_usuario_agregar_click)
|
||||||
$$('grid_usuarios').attachEvent('onItemClick', grid_usuarios_click)
|
$$('grid_usuarios').attachEvent('onItemClick', grid_usuarios_click)
|
||||||
$$('grid_usuarios').attachEvent('onCheck', grid_usuarios_on_check)
|
$$('grid_usuarios').attachEvent('onCheck', grid_usuarios_on_check)
|
||||||
$$('grid_usuarios').attachEvent('onItemDblClick', grid_usuarios_double_click)
|
$$('grid_usuarios').attachEvent('onItemDblClick', grid_usuarios_double_click)
|
||||||
|
$$('chk_users_notify_access').attachEvent('onItemClick', chk_config_item_click)
|
||||||
admin_ui_windows.init()
|
admin_ui_windows.init()
|
||||||
|
|
||||||
//~ Opciones
|
//~ Opciones
|
||||||
|
@ -83,6 +85,13 @@ var controllers = {
|
||||||
//~ Partners
|
//~ Partners
|
||||||
$$('chk_config_change_balance_partner').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_config_change_balance_partner').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
|
||||||
|
//~ Products
|
||||||
|
$$('chk_config_cuenta_predial').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
$$('chk_config_codigo_barras').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
$$('chk_config_precio_con_impuestos').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
$$('chk_llevar_inventario').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
$$('chk_use_packing').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
|
||||||
$$('chk_config_ocultar_metodo_pago').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_config_ocultar_metodo_pago').attachEvent('onItemClick', chk_config_item_click)
|
||||||
$$('chk_config_ocultar_condiciones_pago').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_config_ocultar_condiciones_pago').attachEvent('onItemClick', chk_config_item_click)
|
||||||
$$('chk_config_send_zip').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_config_send_zip').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
@ -98,11 +107,8 @@ var controllers = {
|
||||||
$$('chk_config_ine').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_config_ine').attachEvent('onItemClick', chk_config_item_click)
|
||||||
$$('chk_config_edu').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_config_edu').attachEvent('onItemClick', chk_config_item_click)
|
||||||
$$('chk_config_pagos').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_config_pagos').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
$$('chk_config_divisas').attachEvent('onItemClick', chk_config_item_click)
|
||||||
$$('chk_cfg_pays_data_bank').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_cfg_pays_data_bank').attachEvent('onItemClick', chk_config_item_click)
|
||||||
$$('chk_config_cuenta_predial').attachEvent('onItemClick', chk_config_item_click)
|
|
||||||
$$('chk_config_codigo_barras').attachEvent('onItemClick', chk_config_item_click)
|
|
||||||
$$('chk_config_precio_con_impuestos').attachEvent('onItemClick', chk_config_item_click)
|
|
||||||
$$('chk_llevar_inventario').attachEvent('onItemClick', chk_config_item_click)
|
|
||||||
$$('chk_usar_punto_de_venta').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_usar_punto_de_venta').attachEvent('onItemClick', chk_config_item_click)
|
||||||
$$('chk_ticket_pdf_show').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_ticket_pdf_show').attachEvent('onItemClick', chk_config_item_click)
|
||||||
$$('chk_ticket_direct_print').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_ticket_direct_print').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
@ -405,9 +411,22 @@ function get_admin_usos_cfdi(){
|
||||||
|
|
||||||
function get_admin_usuarios(){
|
function get_admin_usuarios(){
|
||||||
webix.ajax().sync().get('/values/allusuarios', function(text, data){
|
webix.ajax().sync().get('/values/allusuarios', function(text, data){
|
||||||
var values = data.json()
|
var rows = data.json()
|
||||||
$$('grid_usuarios').clearAll()
|
$$('grid_usuarios').clearAll()
|
||||||
$$('grid_usuarios').parse(values, 'json')
|
$$('grid_usuarios').parse(rows)
|
||||||
|
})
|
||||||
|
|
||||||
|
webix.ajax().get('/config', {'fields': 'admin_config_users'}, {
|
||||||
|
error: function(text, data, xhr) {
|
||||||
|
msg = 'Error al consultar'
|
||||||
|
msg_error(msg)
|
||||||
|
},
|
||||||
|
success: function(text, data, xhr) {
|
||||||
|
var values = data.json()
|
||||||
|
Object.keys(values).forEach(function(key){
|
||||||
|
$$(key).setValue(values[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,7 +463,6 @@ function get_config_values(opt){
|
||||||
},
|
},
|
||||||
success: function(text, data, xhr) {
|
success: function(text, data, xhr) {
|
||||||
var values = data.json()
|
var values = data.json()
|
||||||
//~ showvar(values)
|
|
||||||
Object.keys(values).forEach(function(key){
|
Object.keys(values).forEach(function(key){
|
||||||
$$(key).setValue(values[key])
|
$$(key).setValue(values[key])
|
||||||
})
|
})
|
||||||
|
@ -814,6 +832,7 @@ function cmd_probar_correo_click(){
|
||||||
|
|
||||||
function save_config_mail(values){
|
function save_config_mail(values){
|
||||||
|
|
||||||
|
values['opt'] = 'save_mail'
|
||||||
webix.ajax().sync().post('/config', values, {
|
webix.ajax().sync().post('/config', values, {
|
||||||
error: function(text, data, xhr) {
|
error: function(text, data, xhr) {
|
||||||
msg = 'Error al guardar la configuración'
|
msg = 'Error al guardar la configuración'
|
||||||
|
@ -1277,6 +1296,7 @@ function tab_options_change(nv, ov){
|
||||||
var cv = {
|
var cv = {
|
||||||
tab_admin_templates: 'templates',
|
tab_admin_templates: 'templates',
|
||||||
tab_admin_partners: 'partners',
|
tab_admin_partners: 'partners',
|
||||||
|
tab_admin_products: 'admin_products',
|
||||||
tab_admin_complements: 'complements',
|
tab_admin_complements: 'complements',
|
||||||
tab_admin_otros: 'configotros',
|
tab_admin_otros: 'configotros',
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,9 @@ var invoices_controllers = {
|
||||||
$$('txt_folio_custom').attachEvent('onKeyPress', txt_folio_custom_key_press);
|
$$('txt_folio_custom').attachEvent('onKeyPress', txt_folio_custom_key_press);
|
||||||
$$('txt_folio_custom').attachEvent('onBlur', txt_folio_custom_lost_focus);
|
$$('txt_folio_custom').attachEvent('onBlur', txt_folio_custom_lost_focus);
|
||||||
|
|
||||||
|
$$('search_by').attachEvent('onKeyPress', search_by_key_press)
|
||||||
|
$$('search_by').attachEvent('onItemClick', search_by_click)
|
||||||
|
|
||||||
webix.extend($$('grid_invoices'), webix.ProgressBar)
|
webix.extend($$('grid_invoices'), webix.ProgressBar)
|
||||||
|
|
||||||
init_config_invoices()
|
init_config_invoices()
|
||||||
|
@ -217,6 +220,7 @@ function default_config(){
|
||||||
$$('grid_details').showColumn('student')
|
$$('grid_details').showColumn('student')
|
||||||
}
|
}
|
||||||
show('fs_students', values.cfdi_edu)
|
show('fs_students', values.cfdi_edu)
|
||||||
|
show('fs_divisas', values.cfdi_divisas)
|
||||||
show('txt_folio_custom', values.cfdi_folio_custom)
|
show('txt_folio_custom', values.cfdi_folio_custom)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -638,6 +642,7 @@ function guardar_y_timbrar(values){
|
||||||
data['donativo'] = donativo
|
data['donativo'] = donativo
|
||||||
data['notas'] = values.notas
|
data['notas'] = values.notas
|
||||||
data['folio_custom'] = $$('txt_folio_custom').getValue()
|
data['folio_custom'] = $$('txt_folio_custom').getValue()
|
||||||
|
data['divisas'] = $$('opt_divisas').getValue()
|
||||||
|
|
||||||
var usar_ine = $$('chk_cfdi_usar_ine').getValue()
|
var usar_ine = $$('chk_cfdi_usar_ine').getValue()
|
||||||
if(usar_ine){
|
if(usar_ine){
|
||||||
|
@ -1376,20 +1381,23 @@ function cmd_invoice_cancelar_click(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function get_invoices(rango){
|
function get_filters_invoices(){
|
||||||
if(rango == undefined){
|
var filters = $$('filter_dates').getValue()
|
||||||
var fy = $$('filter_year')
|
filters['year'] = $$('filter_year').getValue()
|
||||||
var fm = $$('filter_month')
|
filters['month'] = $$('filter_month').getValue()
|
||||||
|
filters['client'] = $$('grid_invoices').getFilter('cliente').value
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
var y = fy.getValue()
|
|
||||||
var m = fm.getValue()
|
function get_invoices(){
|
||||||
rango = {'year': y, 'month': m}
|
var filters = get_filters_invoices()
|
||||||
}
|
filters['by'] = 'dates'
|
||||||
|
|
||||||
var grid = $$('grid_invoices')
|
var grid = $$('grid_invoices')
|
||||||
grid.showProgress({type: 'icon'})
|
grid.showProgress({type: 'icon'})
|
||||||
|
|
||||||
webix.ajax().get('/invoices', rango, {
|
webix.ajax().get('/invoices', filters, {
|
||||||
error: function(text, data, xhr) {
|
error: function(text, data, xhr) {
|
||||||
msg_error('Error al consultar')
|
msg_error('Error al consultar')
|
||||||
},
|
},
|
||||||
|
@ -1416,7 +1424,7 @@ function filter_month_change(nv, ov){
|
||||||
|
|
||||||
function filter_dates_change(range){
|
function filter_dates_change(range){
|
||||||
if(range.start != null && range.end != null){
|
if(range.start != null && range.end != null){
|
||||||
get_invoices(range)
|
get_invoices()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2265,3 +2273,44 @@ function txt_folio_custom_lost_focus(prev){
|
||||||
validate_folio_exists(prev.getValue())
|
validate_folio_exists(prev.getValue())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function search_by(value){
|
||||||
|
var filters = get_filters_invoices()
|
||||||
|
filters['by'] = 'notes'
|
||||||
|
filters['notes'] = value
|
||||||
|
|
||||||
|
|
||||||
|
var grid = $$('grid_invoices')
|
||||||
|
grid.showProgress({type: 'icon'})
|
||||||
|
|
||||||
|
webix.ajax().get('/invoices', 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 search_by_key_press(code, e){
|
||||||
|
var value = this.getValue().trim()
|
||||||
|
if(code == 13 && value.length > 3){
|
||||||
|
search_by(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function search_by_click(){
|
||||||
|
var value = this.getValue().trim()
|
||||||
|
if(value.length > 3){
|
||||||
|
search_by(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ var nomina_controllers = {
|
||||||
$$('cmd_nomina_delete').attachEvent('onItemClick', cmd_nomina_delete_click)
|
$$('cmd_nomina_delete').attachEvent('onItemClick', cmd_nomina_delete_click)
|
||||||
$$('cmd_nomina_timbrar').attachEvent('onItemClick', cmd_nomina_timbrar_click)
|
$$('cmd_nomina_timbrar').attachEvent('onItemClick', cmd_nomina_timbrar_click)
|
||||||
$$('cmd_nomina_log').attachEvent('onItemClick', cmd_nomina_log_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)
|
$$('cmd_nomina_cancel').attachEvent('onItemClick', cmd_nomina_cancel_click)
|
||||||
$$('grid_nomina').attachEvent('onItemClick', grid_nomina_click)
|
$$('grid_nomina').attachEvent('onItemClick', grid_nomina_click)
|
||||||
$$('filter_year_nomina').attachEvent('onChange', filter_year_nomina_change)
|
$$('filter_year_nomina').attachEvent('onChange', filter_year_nomina_change)
|
||||||
|
@ -491,3 +492,28 @@ function cancel_nomina(id){
|
||||||
function cmd_nomina_log_click(){
|
function cmd_nomina_log_click(){
|
||||||
location = '/doc/nomlog/0'
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ var cfg_products = new Object()
|
||||||
|
|
||||||
|
|
||||||
function products_default_config(){
|
function products_default_config(){
|
||||||
webix.ajax().get('/config', {'fields': 'productos'}, {
|
webix.ajax().get('/config', {'fields': 'main_products'}, {
|
||||||
error: function(text, data, xhr) {
|
error: function(text, data, xhr) {
|
||||||
msg = 'Error al consultar'
|
msg = 'Error al consultar'
|
||||||
msg_error(msg)
|
msg_error(msg)
|
||||||
|
@ -18,6 +18,7 @@ function products_default_config(){
|
||||||
if(cfg_products['inventario']){
|
if(cfg_products['inventario']){
|
||||||
$$('grid_products').showColumn('existencia')
|
$$('grid_products').showColumn('existencia')
|
||||||
}
|
}
|
||||||
|
show('cant_by_packing', values.chk_use_packing)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -212,6 +213,11 @@ function cmd_save_product_click(id, e, node){
|
||||||
|
|
||||||
var values = form.getValues();
|
var values = form.getValues();
|
||||||
|
|
||||||
|
if(!isFinite(values.cant_by_packing)){
|
||||||
|
msg_error('La cantidad por empaque debe ser un número')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if(!validate_sat_key_product(values.clave_sat, false)){
|
if(!validate_sat_key_product(values.clave_sat, false)){
|
||||||
msg_error('La clave SAT no existe')
|
msg_error('La clave SAT no existe')
|
||||||
return
|
return
|
||||||
|
|
|
@ -681,18 +681,6 @@ var options_admin_otros = [
|
||||||
labelRight: 'Ayuda para generar anticipos'},
|
labelRight: 'Ayuda para generar anticipos'},
|
||||||
{}]},
|
{}]},
|
||||||
{maxHeight: 20},
|
{maxHeight: 20},
|
||||||
{template: 'Productos y Servicios', type: 'section'},
|
|
||||||
{cols: [{maxWidth: 15},
|
|
||||||
{view: 'checkbox', id: 'chk_config_cuenta_predial', labelWidth: 0,
|
|
||||||
labelRight: 'Mostrar cuenta predial'},
|
|
||||||
{view: 'checkbox', id: 'chk_config_codigo_barras', labelWidth: 0,
|
|
||||||
labelRight: 'Mostrar código de barras'},
|
|
||||||
{view: 'checkbox', id: 'chk_config_precio_con_impuestos', labelWidth: 0,
|
|
||||||
labelRight: 'Mostrar precio con impuestos'},
|
|
||||||
{view: 'checkbox', id: 'chk_llevar_inventario', labelWidth: 0,
|
|
||||||
labelRight: 'Mostrar inventario'},
|
|
||||||
]},
|
|
||||||
{maxHeight: 20},
|
|
||||||
{template: 'Punto de venta', type: 'section'},
|
{template: 'Punto de venta', type: 'section'},
|
||||||
{cols: [{maxWidth: 15},
|
{cols: [{maxWidth: 15},
|
||||||
{view: 'checkbox', id: 'chk_usar_punto_de_venta', labelWidth: 0,
|
{view: 'checkbox', id: 'chk_usar_punto_de_venta', labelWidth: 0,
|
||||||
|
@ -733,6 +721,21 @@ var options_admin_partners = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
var options_admin_products = [
|
||||||
|
{maxHeight: 20},
|
||||||
|
{cols: [{view: 'checkbox', id: 'chk_config_cuenta_predial', labelWidth: 15,
|
||||||
|
labelRight: 'Mostrar cuenta predial'}]},
|
||||||
|
{cols: [{view: 'checkbox', id: 'chk_config_codigo_barras', labelWidth: 15,
|
||||||
|
labelRight: 'Mostrar código de barras'}]},
|
||||||
|
{cols: [{view: 'checkbox', id: 'chk_config_precio_con_impuestos', labelWidth: 15,
|
||||||
|
labelRight: 'Mostrar precio con impuestos'}]},
|
||||||
|
{cols: [{view: 'checkbox', id: 'chk_llevar_inventario', labelWidth: 15,
|
||||||
|
labelRight: 'Mostrar inventario'}]},
|
||||||
|
{cols: [{view: 'checkbox', id: 'chk_use_packing', labelWidth: 15,
|
||||||
|
labelRight: 'Usar empaques'}]},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
var options_admin_complements = [
|
var options_admin_complements = [
|
||||||
{maxHeight: 20},
|
{maxHeight: 20},
|
||||||
{cols: [{maxWidth: 15},
|
{cols: [{maxWidth: 15},
|
||||||
|
@ -753,6 +756,12 @@ var options_admin_complements = [
|
||||||
{view: 'text', id: 'txt_config_cfdipay_folio', name: 'txt_config_cfdipay_serie',
|
{view: 'text', id: 'txt_config_cfdipay_folio', name: 'txt_config_cfdipay_serie',
|
||||||
label: 'Folio', labelWidth: 50, labelAlign: 'right'},
|
label: 'Folio', labelWidth: 50, labelAlign: 'right'},
|
||||||
{maxWidth: 15}]},
|
{maxWidth: 15}]},
|
||||||
|
{maxHeight: 20},
|
||||||
|
{template: 'Complemento de Divisas', type: 'section'},
|
||||||
|
{cols: [{maxWidth: 15},
|
||||||
|
{view: 'checkbox', id: 'chk_config_divisas', labelWidth: 0,
|
||||||
|
labelRight: 'Usar complemento de divisas'},
|
||||||
|
{maxWidth: 15}]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -765,6 +774,8 @@ var tab_options = {
|
||||||
rows: options_templates}},
|
rows: options_templates}},
|
||||||
{header: 'Clientes y Proveedores', body: {id: 'tab_admin_partners',
|
{header: 'Clientes y Proveedores', body: {id: 'tab_admin_partners',
|
||||||
view: 'scrollview', body: {rows: options_admin_partners}}},
|
view: 'scrollview', body: {rows: options_admin_partners}}},
|
||||||
|
{header: 'Productos y Servicios', body: {id: 'tab_admin_products',
|
||||||
|
view: 'scrollview', body: {rows: options_admin_products}}},
|
||||||
{header: 'Complementos', body: {id: 'tab_admin_complements',
|
{header: 'Complementos', body: {id: 'tab_admin_complements',
|
||||||
view: 'scrollview', body: {rows: options_admin_complements}}},
|
view: 'scrollview', body: {rows: options_admin_complements}}},
|
||||||
{header: 'Otros', body: {id: 'tab_admin_otros', view: 'scrollview',
|
{header: 'Otros', body: {id: 'tab_admin_otros', view: 'scrollview',
|
||||||
|
@ -1214,6 +1225,10 @@ var usuarios_admin = [
|
||||||
{maxHeight: 20},
|
{maxHeight: 20},
|
||||||
{template: 'Usuarios Registrados', type: 'section'},
|
{template: 'Usuarios Registrados', type: 'section'},
|
||||||
{cols: [{maxWidth: 10}, grid_usuarios, {maxWidth: 10}]},
|
{cols: [{maxWidth: 10}, grid_usuarios, {maxWidth: 10}]},
|
||||||
|
{maxHeight: 20},
|
||||||
|
{template: 'Opciones', type: 'section'},
|
||||||
|
{cols: [{view: 'checkbox', id: 'chk_users_notify_access', labelWidth: 15,
|
||||||
|
labelRight: 'Notificar accesos al sistema (solo a administradores)'}]},
|
||||||
{},
|
{},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -234,6 +234,9 @@ var toolbar_invoices_filter = [
|
||||||
labelWidth: 50, width: 200, options: months},
|
labelWidth: 50, width: 200, options: months},
|
||||||
{view: 'daterangepicker', id: 'filter_dates', label: 'Fechas',
|
{view: 'daterangepicker', id: 'filter_dates', label: 'Fechas',
|
||||||
labelAlign: 'right', width: 300},
|
labelAlign: 'right', width: 300},
|
||||||
|
{},
|
||||||
|
{view: 'search', id: 'search_by', name: 'search_by', width: 200,
|
||||||
|
placeholder: 'Captura al menos cuatro letras'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -535,6 +538,12 @@ var body_moneda = {cols: [
|
||||||
]}
|
]}
|
||||||
|
|
||||||
|
|
||||||
|
var body_divisas = {cols: [
|
||||||
|
{view: 'radio', id: 'opt_divisas', name: 'opt_divisas',
|
||||||
|
options: ['Ninguna', 'Compra', 'Venta']},
|
||||||
|
]}
|
||||||
|
|
||||||
|
|
||||||
var body_regimen_fiscal = {
|
var body_regimen_fiscal = {
|
||||||
view: 'richselect',
|
view: 'richselect',
|
||||||
id: 'lst_regimen_fiscal',
|
id: 'lst_regimen_fiscal',
|
||||||
|
@ -593,6 +602,7 @@ var controls_generate = [
|
||||||
{view: 'fieldset', label: 'Comprobante', body: body_comprobante},
|
{view: 'fieldset', label: 'Comprobante', body: body_comprobante},
|
||||||
{view: 'fieldset', label: 'Opciones de Pago', body: body_opciones},
|
{view: 'fieldset', label: 'Opciones de Pago', body: body_opciones},
|
||||||
{view: 'fieldset', id: 'fs_moneda', label: 'Moneda', body: body_moneda},
|
{view: 'fieldset', id: 'fs_moneda', label: 'Moneda', body: body_moneda},
|
||||||
|
{view: 'fieldset', id: 'fs_divisas', label: 'Divisas - Tipo de Operación', body: body_divisas},
|
||||||
{view: 'fieldset', id: 'fs_regimen_fiscal', label: 'Regimen Fiscal',
|
{view: 'fieldset', id: 'fs_regimen_fiscal', label: 'Regimen Fiscal',
|
||||||
body: body_regimen_fiscal},
|
body: body_regimen_fiscal},
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -59,8 +59,9 @@ var menu_user = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var link_forum = "<a class='link_forum' target='_blank' href='https://gitlab.com/mauriciobaeza/empresa-libre/issues'>Foro de Soporte</a>";
|
var link_blog = "<a class='link_default' target='_blank' href='https://blog.empresalibre.mx'>Blog</a>";
|
||||||
var link_doc = "<a class='link_doc' target='_blank' href='https://doc.empresalibre.net'><b>?</b> </a>";
|
var link_forum = "<a class='link_default' target='_blank' href='https://gitlab.com/mauriciobaeza/empresa-libre/issues'>Foro</a>";
|
||||||
|
var link_doc = "<a class='link_default' target='_blank' href='https://doc.empresalibre.mx'>Doc</a>";
|
||||||
|
|
||||||
|
|
||||||
var ui_main = {
|
var ui_main = {
|
||||||
|
@ -72,8 +73,10 @@ var ui_main = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{view: 'label', id: 'lbl_title_main', label: '<b>Empresa Libre</b>'},
|
{view: 'label', id: 'lbl_title_main', label: '<b>Empresa Libre</b>'},
|
||||||
{view: 'label', id: 'lbl_forum', label: link_forum, align: 'right'},
|
{},
|
||||||
{view: 'label', id: 'lbl_doc', label: link_doc, align: 'left', width: 25},
|
{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,
|
menu_user,
|
||||||
{view: 'button', id: 'cmd_update_timbres', type: 'icon', width: 45,
|
{view: 'button', id: 'cmd_update_timbres', type: 'icon', width: 45,
|
||||||
css: 'app_button', icon: 'bell-o', badge: 0},
|
css: 'app_button', icon: 'bell-o', badge: 0},
|
||||||
|
|
|
@ -20,6 +20,8 @@ var toolbar_nomina_util = [
|
||||||
type: 'iconButton', autowidth: true, icon: 'check-circle'},
|
type: 'iconButton', autowidth: true, icon: 'check-circle'},
|
||||||
{view: 'button', id: 'cmd_nomina_log', label: 'Log',
|
{view: 'button', id: 'cmd_nomina_log', label: 'Log',
|
||||||
type: 'iconButton', autowidth: true, icon: 'download'},
|
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',
|
{view: 'button', id: 'cmd_nomina_cancel', label: 'Cancelar',
|
||||||
type: 'iconButton', autowidth: true, icon: 'ban'},
|
type: 'iconButton', autowidth: true, icon: 'ban'},
|
||||||
|
|
|
@ -135,9 +135,13 @@ var controls_generals = [
|
||||||
{view: "richselect", id: "unidad", name: "unidad", label: "Unidad",
|
{view: "richselect", id: "unidad", name: "unidad", label: "Unidad",
|
||||||
width: 300, labelWidth: 130, labelAlign: "right", required: true,
|
width: 300, labelWidth: 130, labelAlign: "right", required: true,
|
||||||
invalidMessage: "La Unidad es requerida", options: []},
|
invalidMessage: "La Unidad es requerida", options: []},
|
||||||
{view: 'text', id: 'tags_producto', name: 'tags_producto',
|
{view: 'text', id: 'cant_by_packing', name: 'cant_by_packing',
|
||||||
labelAlign: 'right', label: 'Etiquetas',
|
labelAlign: 'right', labelWidth: 150, inputAlign: "right",
|
||||||
placeholder: 'Separadas por comas'}
|
label: 'Cantidad por empaque:'},
|
||||||
|
{},
|
||||||
|
//~ {view: 'text', id: 'tags_producto', name: 'tags_producto',
|
||||||
|
//~ labelAlign: 'right', label: 'Etiquetas',
|
||||||
|
//~ placeholder: 'Separadas por comas'}
|
||||||
]},
|
]},
|
||||||
{cols: [
|
{cols: [
|
||||||
{view: "currency", type: "text", id: "valor_unitario",
|
{view: "currency", type: "text", id: "valor_unitario",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -13,9 +13,10 @@
|
||||||
<xsl:include href="ine11.xslt"/>
|
<xsl:include href="ine11.xslt"/>
|
||||||
<xsl:include href="iedu.xslt"/>
|
<xsl:include href="iedu.xslt"/>
|
||||||
<xsl:include href="pagos10.xslt"/>
|
<xsl:include href="pagos10.xslt"/>
|
||||||
|
<xsl:include href="divisas.xslt"/>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<xsl:include href="ecc11.xslt"/>
|
<xsl:include href="ecc11.xslt"/>
|
||||||
<xsl:include href="Divisas.xslt"/>
|
|
||||||
<xsl:include href="pfic.xslt"/>
|
<xsl:include href="pfic.xslt"/>
|
||||||
<xsl:include href="TuristaPasajeroExtranjero.xslt"/>
|
<xsl:include href="TuristaPasajeroExtranjero.xslt"/>
|
||||||
<xsl:include href="cfdiregistrofiscal.xslt"/>
|
<xsl:include href="cfdiregistrofiscal.xslt"/>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:divisas="http://www.sat.gob.mx/divisas">
|
||||||
|
<!-- Manejador de nodos tipo divisas:Divisas -->
|
||||||
|
<xsl:template match="divisas:Divisas">
|
||||||
|
<!-- Iniciamos el tratamiento de los atributos de divisas:Divisas -->
|
||||||
|
<xsl:call-template name="Requerido">
|
||||||
|
<xsl:with-param name="valor" select="./@version"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
<xsl:call-template name="Requerido">
|
||||||
|
<xsl:with-param name="valor" select="./@tipoOperacion"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
Loading…
Reference in New Issue