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]
|
||||
----------------------
|
||||
- Error: Al cancelar nómina
|
||||
|
|
|
@ -13,3 +13,4 @@ pyqrcode
|
|||
pypng
|
||||
reportlab
|
||||
psycopg2-binary
|
||||
cryptography
|
||||
|
|
|
@ -84,6 +84,12 @@ SAT = {
|
|||
'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',
|
||||
},
|
||||
'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._pagos = False
|
||||
self._is_nomina = False
|
||||
self._divisas = ''
|
||||
self.error = ''
|
||||
|
||||
def _now(self):
|
||||
|
@ -152,6 +159,8 @@ class CFDI(object):
|
|||
self._ine = True
|
||||
self._pagos = bool(datos['complementos'].get('pagos', False))
|
||||
|
||||
self._divisas = datos['comprobante'].pop('divisas', '')
|
||||
|
||||
if 'nomina' in datos:
|
||||
self._is_nomina = True
|
||||
return self._validate_nomina(datos)
|
||||
|
@ -190,6 +199,12 @@ class CFDI(object):
|
|||
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'])
|
||||
|
@ -204,7 +219,7 @@ class CFDI(object):
|
|||
|
||||
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
|
||||
schema_locales + schema_donativo + schema_ine + schema_edu + \
|
||||
schema_nomina + schema_pagos
|
||||
schema_divisas + schema_nomina + schema_pagos
|
||||
attributes.update(datos)
|
||||
|
||||
if not 'Version' in attributes:
|
||||
|
@ -426,6 +441,13 @@ class CFDI(object):
|
|||
self._complemento = ET.SubElement(
|
||||
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:
|
||||
atributos = {'Version': SAT['ine']['version']}
|
||||
atributos.update(datos['ine'])
|
||||
|
|
|
@ -47,6 +47,7 @@ class AppLogin(object):
|
|||
session.invalidate()
|
||||
values = req.params
|
||||
values['rfc'] = values['rfc'].upper()
|
||||
values['ip'] = req.remote_addr
|
||||
result, user = self._db.authenticate(values)
|
||||
if result['login']:
|
||||
session.save()
|
||||
|
@ -485,7 +486,14 @@ class AppNomina(object):
|
|||
|
||||
def on_get(self, req, resp):
|
||||
values = req.params
|
||||
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):
|
||||
|
|
|
@ -872,7 +872,7 @@ class LIBO(object):
|
|||
currency = self.CELL_STYLE.get(self._currency, 'peso')
|
||||
return '{}{}'.format(currency, match.groups()[1])
|
||||
|
||||
def _conceptos(self, data):
|
||||
def _conceptos(self, data, pakings):
|
||||
first = True
|
||||
col1 = []
|
||||
col2 = []
|
||||
|
@ -881,8 +881,9 @@ class LIBO(object):
|
|||
col5 = []
|
||||
col6 = []
|
||||
col7 = []
|
||||
col8 = []
|
||||
count = len(data) - 1
|
||||
for concepto in data:
|
||||
for i, concepto in enumerate(data):
|
||||
key = concepto.get('noidentificacion', '')
|
||||
description = concepto['descripcion']
|
||||
unidad = concepto['unidad']
|
||||
|
@ -899,6 +900,8 @@ class LIBO(object):
|
|||
cell_5 = self._set_cell('{valorunitario}', valor_unitario, value=True)
|
||||
cell_6 = self._set_cell('{importe}', importe, 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:
|
||||
row = cell_1.getCellAddress().Row + 1
|
||||
self._sheet.getRows().insertByIndex(row, count)
|
||||
|
@ -912,6 +915,8 @@ class LIBO(object):
|
|||
col5.append((float(valor_unitario),))
|
||||
col6.append((float(importe),))
|
||||
col7.append((float(descuento),))
|
||||
if pakings:
|
||||
col8.append((pakings[i],))
|
||||
self._total_cantidades += float(cantidad)
|
||||
if not count:
|
||||
return
|
||||
|
@ -919,6 +924,9 @@ class LIBO(object):
|
|||
style_5 = self._get_style(cell_5)
|
||||
style_6 = self._get_style(cell_6)
|
||||
style_7 = self._get_style(cell_7)
|
||||
style_8 = ''
|
||||
if pakings:
|
||||
style_8 = self._get_style(cell_8)
|
||||
|
||||
col = cell_1.getCellAddress().Column
|
||||
target1 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
||||
|
@ -933,9 +941,13 @@ class LIBO(object):
|
|||
col = cell_6.getCellAddress().Column
|
||||
target6 = self._sheet.getCellRangeByPosition(col, row+1, col, row+count)
|
||||
target7 = None
|
||||
target8 = None
|
||||
if not cell_7 is None:
|
||||
col = cell_7.getCellAddress().Column
|
||||
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))
|
||||
target2.setDataArray(tuple(col2))
|
||||
|
@ -945,6 +957,8 @@ class LIBO(object):
|
|||
target6.setDataArray(tuple(col6))
|
||||
if not target7 is None:
|
||||
target7.setDataArray(tuple(col7))
|
||||
if not target8 is None:
|
||||
target8.setDataArray(tuple(col8))
|
||||
|
||||
if style_5:
|
||||
cell_5.CellStyle = style_5
|
||||
|
@ -955,6 +969,9 @@ class LIBO(object):
|
|||
if style_7:
|
||||
cell_7.CellStyle = style_7
|
||||
target7.CellStyle = style_7
|
||||
if style_8:
|
||||
cell_8.CellStyle = style_8
|
||||
target8.CellStyle = style_8
|
||||
return
|
||||
|
||||
def _add_totales(self, data):
|
||||
|
@ -1067,6 +1084,12 @@ class LIBO(object):
|
|||
self._set_cell('{ine.%s}' % k, v)
|
||||
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):
|
||||
if not data:
|
||||
return
|
||||
|
@ -1275,10 +1298,12 @@ class LIBO(object):
|
|||
self._currency = data['totales']['moneda']
|
||||
self._pagos = data.pop('pagos', False)
|
||||
|
||||
pakings = data.pop('pakings', [])
|
||||
|
||||
self._comprobante(data['comprobante'])
|
||||
self._emisor(data['emisor'])
|
||||
self._receptor(data['receptor'])
|
||||
self._conceptos(data['conceptos'])
|
||||
self._conceptos(data['conceptos'], pakings)
|
||||
|
||||
if self._pagos:
|
||||
self._cfdipays(data['pays'])
|
||||
|
@ -1291,6 +1316,8 @@ class LIBO(object):
|
|||
self._donataria(data['donataria'])
|
||||
self._ine(data['ine'])
|
||||
|
||||
self._divisas(data.get('divisas', {}))
|
||||
|
||||
self._cancelado(data['cancelada'])
|
||||
self._clean()
|
||||
return
|
||||
|
@ -1298,6 +1325,7 @@ class LIBO(object):
|
|||
def pdf(self, path, data, ods=False):
|
||||
options = {'AsTemplate': True, 'Hidden': True}
|
||||
log.debug('Abrir plantilla...')
|
||||
|
||||
self._template = self._doc_open(path, options)
|
||||
if self._template is None:
|
||||
return b''
|
||||
|
@ -1444,7 +1472,16 @@ class LIBO(object):
|
|||
return {}, msg
|
||||
|
||||
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):
|
||||
rows, msg = self._get_data(doc, 'OtrosPagos')
|
||||
|
@ -1508,7 +1545,7 @@ class LIBO(object):
|
|||
doc.close(True)
|
||||
return {}, msg
|
||||
|
||||
deducciones, msg = self._get_deducciones(doc, len(nomina))
|
||||
deducciones, new_titles, msg = self._get_deducciones(doc, len(nomina))
|
||||
if msg:
|
||||
doc.close(True)
|
||||
return {}, msg
|
||||
|
@ -1534,6 +1571,28 @@ class LIBO(object):
|
|||
return {}, msg
|
||||
|
||||
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['percepciones'] = percepciones
|
||||
data['deducciones'] = deducciones
|
||||
|
@ -1541,6 +1600,7 @@ class LIBO(object):
|
|||
data['separacion'] = separacion
|
||||
data['horas_extras'] = horas_extras
|
||||
data['incapacidades'] = incapacidades
|
||||
data['new_titles'] = new_titles
|
||||
|
||||
return data, ''
|
||||
|
||||
|
@ -1563,11 +1623,13 @@ class LIBO(object):
|
|||
|
||||
def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'):
|
||||
rfc = data['emisor']['rfc']
|
||||
default = 'plantilla_factura.ods'
|
||||
if DEBUG:
|
||||
rfc = emisor_rfc
|
||||
version = data['comprobante']['version']
|
||||
if 'nomina' in data and data['nomina']:
|
||||
version = '{}_{}'.format(data['nomina']['version'], version)
|
||||
default = 'plantilla_nomina.ods'
|
||||
|
||||
pagos = ''
|
||||
if data.get('pagos', False):
|
||||
|
@ -1584,7 +1646,7 @@ def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'):
|
|||
if data['donativo']:
|
||||
donativo = '_donativo'
|
||||
name = '{}_{}{}{}.ods'.format(rfc.lower(), pagos, version, donativo)
|
||||
path = get_template_ods(name)
|
||||
path = get_template_ods(name, default)
|
||||
if path:
|
||||
return app.pdf(path, data, ods)
|
||||
|
||||
|
@ -2105,7 +2167,7 @@ def get_data_from_xml(invoice, values):
|
|||
data['pagos'] = values.get('pagos', False)
|
||||
if data['pagos']:
|
||||
data['pays'] = _cfdipays(doc, data, version)
|
||||
|
||||
data['pakings'] = values.get('pakings', [])
|
||||
return data
|
||||
|
||||
|
||||
|
@ -3764,3 +3826,5 @@ def validate_rfc(value):
|
|||
def parse_xml2(xml_str):
|
||||
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):
|
||||
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'])
|
||||
|
||||
|
||||
|
|
|
@ -384,8 +384,10 @@ class StorageEngine(object):
|
|||
def get_tickets(self, values):
|
||||
return main.Tickets.get_by(values)
|
||||
|
||||
def get_invoices(self, values):
|
||||
return main.Facturas.get_(values)
|
||||
def get_invoices(self, filters):
|
||||
if filters.get('by', ''):
|
||||
return main.Facturas.get_by(filters)
|
||||
return main.Facturas.get_(filters)
|
||||
|
||||
def get_preinvoices(self, 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__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
# ~ v2
|
||||
from controllers import utils
|
||||
|
||||
# ~ v1
|
||||
from controllers import util
|
||||
|
||||
from settings import log, DEBUG, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \
|
||||
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
|
||||
CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \
|
||||
|
@ -286,6 +290,7 @@ def config_timbrar():
|
|||
'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'),
|
||||
'cfdi_ine': Configuracion.get_bool('chk_config_ine'),
|
||||
'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_condicion_pago': Configuracion.get_bool('chk_config_ocultar_condiciones_pago'),
|
||||
'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'
|
||||
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
|
||||
def get_bool(cls, key):
|
||||
data = (Configuracion
|
||||
|
@ -368,11 +384,45 @@ class Configuracion(BaseModel):
|
|||
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||
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):
|
||||
fields = (
|
||||
'chk_config_ine',
|
||||
'chk_config_edu',
|
||||
'chk_config_pagos',
|
||||
'chk_config_divisas',
|
||||
'chk_cfg_pays_data_bank',
|
||||
)
|
||||
data = (Configuracion
|
||||
|
@ -401,6 +451,28 @@ class Configuracion(BaseModel):
|
|||
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||
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
|
||||
def get_(cls, keys):
|
||||
if isinstance(keys, str):
|
||||
|
@ -412,27 +484,18 @@ class Configuracion(BaseModel):
|
|||
return data[0].valor
|
||||
return ''
|
||||
|
||||
options = ('partners', 'complements', 'folios')
|
||||
options = ('partners',
|
||||
'admin_products',
|
||||
'main_products',
|
||||
'complements',
|
||||
'folios',
|
||||
'correo',
|
||||
'admin_config_users',
|
||||
)
|
||||
opt = keys['fields']
|
||||
if opt in options:
|
||||
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':
|
||||
try:
|
||||
emisor = Emisor.select()[0]
|
||||
|
@ -465,10 +528,6 @@ class Configuracion(BaseModel):
|
|||
'chk_config_tax_locales_truncate',
|
||||
'chk_config_decimales_precios',
|
||||
'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_ticket_pdf_show',
|
||||
'chk_ticket_direct_print',
|
||||
|
@ -493,16 +552,7 @@ class Configuracion(BaseModel):
|
|||
values[f] = Configuracion.get_(f)
|
||||
return values
|
||||
|
||||
if keys['fields'] == 'correo':
|
||||
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':
|
||||
if keys['fields'] == 'path_cer':
|
||||
fields = ('path_key', 'path_cer')
|
||||
data = (Configuracion
|
||||
.select()
|
||||
|
@ -3204,6 +3254,8 @@ class Productos(BaseModel):
|
|||
es_activo = BooleanField(default=True)
|
||||
impuestos = ManyToManyField(SATImpuestos, related_name='productos')
|
||||
tags = ManyToManyField(Tags, related_name='productos_tags')
|
||||
cantidad_empaque = DecimalField(default=0.0, max_digits=14, decimal_places=4,
|
||||
auto_round=True)
|
||||
|
||||
class Meta:
|
||||
order_by = ('descripcion',)
|
||||
|
@ -3415,6 +3467,7 @@ class Productos(BaseModel):
|
|||
Productos.inventario,
|
||||
Productos.existencia,
|
||||
Productos.minimo,
|
||||
Productos.cantidad_empaque.alias('cant_by_packing'),
|
||||
)
|
||||
.where(Productos.id==id).dicts()[0]
|
||||
)
|
||||
|
@ -3442,6 +3495,7 @@ class Productos(BaseModel):
|
|||
descripcion = util.spaces(values.pop('descripcion'))
|
||||
fields = util.clean(values)
|
||||
|
||||
fields['cantidad_empaque'] = fields.pop('cant_by_packing', 0.0)
|
||||
fields.pop('precio_con_impuestos', '')
|
||||
fields['es_activo'] = fields.pop('es_activo_producto')
|
||||
fields['descripcion'] = descripcion
|
||||
|
@ -3484,6 +3538,7 @@ class Productos(BaseModel):
|
|||
def actualizar(cls, values, id):
|
||||
values['cuenta_predial'] = values.get('cuenta_predial', '')
|
||||
values['codigo_barras'] = values.get('codigo_barras', '')
|
||||
|
||||
fields, taxes = cls._clean(cls, values)
|
||||
obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
|
||||
with database_proxy.transaction():
|
||||
|
@ -3577,6 +3632,7 @@ class Facturas(BaseModel):
|
|||
estatus = TextField(default='Guardada')
|
||||
estatus_sat = TextField(default='Vigente')
|
||||
regimen_fiscal = TextField(default='')
|
||||
divisas = TextField(default='')
|
||||
notas = TextField(default='')
|
||||
saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
|
@ -3663,6 +3719,65 @@ class Facturas(BaseModel):
|
|||
obj.save()
|
||||
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
|
||||
def filter_years(cls):
|
||||
data = [{'id': -1, 'value': 'Todos'}]
|
||||
|
@ -3695,6 +3810,8 @@ class Facturas(BaseModel):
|
|||
return {'ok': True, 'msg': 'Notas guardadas correctamente'}
|
||||
|
||||
def _get_not_in_xml(self, invoice, emisor):
|
||||
pdf_from = Configuracion.get_('make_pdf_from') or '1'
|
||||
|
||||
values = {}
|
||||
|
||||
values['notas'] = invoice.notas
|
||||
|
@ -3732,6 +3849,16 @@ class Facturas(BaseModel):
|
|||
for k, v in receptor.items():
|
||||
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
|
||||
|
||||
@classmethod
|
||||
|
@ -3748,7 +3875,11 @@ class Facturas(BaseModel):
|
|||
|
||||
pdf_from = Configuracion.get_('make_pdf_from') or '1'
|
||||
values = cls._get_not_in_xml(cls, obj, emisor)
|
||||
|
||||
#Tmp to v2
|
||||
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)
|
||||
|
||||
if sync:
|
||||
|
@ -3980,6 +4111,7 @@ class Facturas(BaseModel):
|
|||
@classmethod
|
||||
def send(cls, id, rfc):
|
||||
values = Configuracion.get_({'fields': 'correo'})
|
||||
contra = Configuracion.get_('correo_contra')
|
||||
in_zip = Configuracion.get_bool('chk_config_send_zip')
|
||||
|
||||
if not values:
|
||||
|
@ -4006,7 +4138,7 @@ class Facturas(BaseModel):
|
|||
'puerto': values['correo_puerto'],
|
||||
'ssl': bool(int(values['correo_ssl'])),
|
||||
'usuario': values['correo_usuario'],
|
||||
'contra': values['correo_contra'],
|
||||
'contra': utils.decrypt(contra, rfc),
|
||||
}
|
||||
options = {
|
||||
'para': obj.cliente.correo_facturas,
|
||||
|
@ -4285,6 +4417,8 @@ class Facturas(BaseModel):
|
|||
tax_locales = Configuracion.get_bool('chk_config_tax_locales')
|
||||
tax_locales_truncate = Configuracion.get_bool('chk_config_tax_locales_truncate')
|
||||
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
|
||||
use_packing = Configuracion.get_bool('chk_use_packing')
|
||||
|
||||
subtotal = 0
|
||||
descuento_cfdi = 0
|
||||
totals_tax = {}
|
||||
|
@ -4312,6 +4446,10 @@ class Facturas(BaseModel):
|
|||
precio_final = valor_unitario - descuento
|
||||
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['valor_unitario'] = valor_unitario
|
||||
product['descuento'] = round(descuento * cantidad, DECIMALES)
|
||||
|
@ -4434,6 +4572,12 @@ class Facturas(BaseModel):
|
|||
ine = values.pop('ine', {})
|
||||
tipo_comprobante = values['tipo_comprobante']
|
||||
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]
|
||||
values['serie'] = cls._get_serie(cls, user, values['serie'])
|
||||
|
@ -4504,6 +4648,9 @@ class Facturas(BaseModel):
|
|||
relacionados = {}
|
||||
donativo = {}
|
||||
complementos = FacturasComplementos.get_(invoice)
|
||||
comprobante['divisas'] = invoice.divisas
|
||||
if invoice.divisas:
|
||||
complementos['divisas'] = True
|
||||
|
||||
if invoice.donativo:
|
||||
donativo['noAutorizacion'] = emisor.autorizacion
|
||||
|
@ -4736,6 +4883,7 @@ class Facturas(BaseModel):
|
|||
'edu': is_edu,
|
||||
'complementos': complementos,
|
||||
}
|
||||
|
||||
return util.make_xml(data, certificado, auth)
|
||||
|
||||
@classmethod
|
||||
|
@ -5616,6 +5764,8 @@ class FacturasDetalle(BaseModel):
|
|||
nivel = TextField(default='')
|
||||
autorizacion = TextField(default='')
|
||||
cuenta_predial = TextField(default='')
|
||||
empaques = DecimalField(default=0.0, max_digits=14, decimal_places=4,
|
||||
auto_round=True)
|
||||
|
||||
class Meta:
|
||||
order_by = ('factura',)
|
||||
|
@ -7493,7 +7643,6 @@ class CfdiNomina(BaseModel):
|
|||
data['fecha_pago'] = util.calc_to_date(row['fecha_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['dias_pagados'] = util.get_days(data['fecha_inicial_pago'], data['fecha_final_pago'])
|
||||
data['dias_pagados'] = days_pay
|
||||
|
||||
return data, ''
|
||||
|
@ -7546,7 +7695,7 @@ class CfdiNomina(BaseModel):
|
|||
|
||||
return data, totals, ''
|
||||
|
||||
def _validate_deducciones(self, headers, row):
|
||||
def _validate_deducciones(self, headers, row, new_titles):
|
||||
total_retenciones = 0.0
|
||||
total_otras_deducciones = 0.0
|
||||
|
||||
|
@ -7572,6 +7721,7 @@ class CfdiNomina(BaseModel):
|
|||
new = {
|
||||
'tipo_deduccion': td,
|
||||
'importe': importe,
|
||||
'concepto': new_titles.get(i, ''),
|
||||
}
|
||||
data.append(new)
|
||||
|
||||
|
@ -7735,6 +7885,7 @@ class CfdiNomina(BaseModel):
|
|||
separacion = data['separacion'][2:]
|
||||
horas_extras = data['horas_extras'][2:]
|
||||
incapacidades = data['incapacidades'][2:]
|
||||
new_titles = data['new_titles']
|
||||
|
||||
for i, row in enumerate(data['nomina']):
|
||||
row['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
|
||||
|
@ -7756,7 +7907,7 @@ class CfdiNomina(BaseModel):
|
|||
continue
|
||||
|
||||
new_deducciones, total_deducciones, msg = \
|
||||
self._validate_deducciones(self, hd, deducciones[i])
|
||||
self._validate_deducciones(self, hd, deducciones[i], new_titles)
|
||||
if msg:
|
||||
util.log_file('nomina', msg)
|
||||
continue
|
||||
|
@ -8190,11 +8341,34 @@ class CfdiNomina(BaseModel):
|
|||
|
||||
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
|
||||
def get_by(cls, values):
|
||||
if not values:
|
||||
return cls._get(cls)
|
||||
|
||||
if values.get('by', ''):
|
||||
return getattr(cls, f"_get_by_{values['by']}")(cls, values)
|
||||
|
||||
if values['opt'] == 'dates':
|
||||
dates = util.loads(values['range'])
|
||||
filters = CfdiNomina.fecha.between(
|
||||
|
@ -8508,6 +8682,50 @@ def _save_log(user, action, table):
|
|||
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):
|
||||
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
|
||||
values = util.get_con(args['rfc'])
|
||||
|
@ -8532,7 +8750,11 @@ def authenticate(args):
|
|||
respuesta['login'] = True
|
||||
respuesta['user'] = str(obj)
|
||||
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
|
||||
|
||||
|
||||
|
@ -8835,10 +9057,34 @@ def _migrate_tables(rfc=''):
|
|||
activa = BooleanField(default=True)
|
||||
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:
|
||||
with database_proxy.atomic() as txn:
|
||||
migrate(*migrations)
|
||||
|
||||
Configuracion.add({'version': VERSION})
|
||||
|
||||
log.info('Tablas migradas correctamente...')
|
||||
_importar_valores('', rfc)
|
||||
|
||||
|
|
|
@ -47,8 +47,8 @@ except ImportError:
|
|||
|
||||
|
||||
DEBUG = DEBUG
|
||||
VERSION = '1.27.1'
|
||||
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
||||
VERSION = '1.28.0'
|
||||
EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
|
||||
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
|
||||
|
||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
|
|
|
@ -47,6 +47,13 @@
|
|||
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;
|
||||
|
|
|
@ -59,11 +59,13 @@ var controllers = {
|
|||
$$('grid_admin_unidades').attachEvent('onItemClick', grid_admin_unidades_click)
|
||||
$$('grid_moneda_found').attachEvent('onValueSuggest', grid_moneda_found_click)
|
||||
$$('cmd_agregar_impuesto').attachEvent('onItemClick', cmd_agregar_impuesto_click)
|
||||
|
||||
//~ Usuarios
|
||||
$$('cmd_usuario_agregar').attachEvent('onItemClick', cmd_usuario_agregar_click)
|
||||
$$('grid_usuarios').attachEvent('onItemClick', grid_usuarios_click)
|
||||
$$('grid_usuarios').attachEvent('onCheck', grid_usuarios_on_check)
|
||||
$$('grid_usuarios').attachEvent('onItemDblClick', grid_usuarios_double_click)
|
||||
$$('chk_users_notify_access').attachEvent('onItemClick', chk_config_item_click)
|
||||
admin_ui_windows.init()
|
||||
|
||||
//~ Opciones
|
||||
|
@ -83,6 +85,13 @@ var controllers = {
|
|||
//~ Partners
|
||||
$$('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_condiciones_pago').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_edu').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_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_ticket_pdf_show').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(){
|
||||
webix.ajax().sync().get('/values/allusuarios', function(text, data){
|
||||
var values = data.json()
|
||||
var rows = data.json()
|
||||
$$('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) {
|
||||
var values = data.json()
|
||||
//~ showvar(values)
|
||||
Object.keys(values).forEach(function(key){
|
||||
$$(key).setValue(values[key])
|
||||
})
|
||||
|
@ -814,6 +832,7 @@ function cmd_probar_correo_click(){
|
|||
|
||||
function save_config_mail(values){
|
||||
|
||||
values['opt'] = 'save_mail'
|
||||
webix.ajax().sync().post('/config', values, {
|
||||
error: function(text, data, xhr) {
|
||||
msg = 'Error al guardar la configuración'
|
||||
|
@ -1277,6 +1296,7 @@ function tab_options_change(nv, ov){
|
|||
var cv = {
|
||||
tab_admin_templates: 'templates',
|
||||
tab_admin_partners: 'partners',
|
||||
tab_admin_products: 'admin_products',
|
||||
tab_admin_complements: 'complements',
|
||||
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('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)
|
||||
|
||||
init_config_invoices()
|
||||
|
@ -217,6 +220,7 @@ function default_config(){
|
|||
$$('grid_details').showColumn('student')
|
||||
}
|
||||
show('fs_students', values.cfdi_edu)
|
||||
show('fs_divisas', values.cfdi_divisas)
|
||||
show('txt_folio_custom', values.cfdi_folio_custom)
|
||||
})
|
||||
}
|
||||
|
@ -638,6 +642,7 @@ function guardar_y_timbrar(values){
|
|||
data['donativo'] = donativo
|
||||
data['notas'] = values.notas
|
||||
data['folio_custom'] = $$('txt_folio_custom').getValue()
|
||||
data['divisas'] = $$('opt_divisas').getValue()
|
||||
|
||||
var usar_ine = $$('chk_cfdi_usar_ine').getValue()
|
||||
if(usar_ine){
|
||||
|
@ -1376,20 +1381,23 @@ function cmd_invoice_cancelar_click(){
|
|||
}
|
||||
|
||||
|
||||
function get_invoices(rango){
|
||||
if(rango == undefined){
|
||||
var fy = $$('filter_year')
|
||||
var fm = $$('filter_month')
|
||||
function get_filters_invoices(){
|
||||
var filters = $$('filter_dates').getValue()
|
||||
filters['year'] = $$('filter_year').getValue()
|
||||
filters['month'] = $$('filter_month').getValue()
|
||||
filters['client'] = $$('grid_invoices').getFilter('cliente').value
|
||||
return filters
|
||||
}
|
||||
|
||||
var y = fy.getValue()
|
||||
var m = fm.getValue()
|
||||
rango = {'year': y, 'month': m}
|
||||
}
|
||||
|
||||
function get_invoices(){
|
||||
var filters = get_filters_invoices()
|
||||
filters['by'] = 'dates'
|
||||
|
||||
var grid = $$('grid_invoices')
|
||||
grid.showProgress({type: 'icon'})
|
||||
|
||||
webix.ajax().get('/invoices', rango, {
|
||||
webix.ajax().get('/invoices', filters, {
|
||||
error: function(text, data, xhr) {
|
||||
msg_error('Error al consultar')
|
||||
},
|
||||
|
@ -1416,7 +1424,7 @@ function filter_month_change(nv, ov){
|
|||
|
||||
function filter_dates_change(range){
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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_timbrar').attachEvent('onItemClick', cmd_nomina_timbrar_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)
|
||||
|
@ -490,4 +491,29 @@ function cancel_nomina(id){
|
|||
|
||||
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');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ var cfg_products = new Object()
|
|||
|
||||
|
||||
function products_default_config(){
|
||||
webix.ajax().get('/config', {'fields': 'productos'}, {
|
||||
webix.ajax().get('/config', {'fields': 'main_products'}, {
|
||||
error: function(text, data, xhr) {
|
||||
msg = 'Error al consultar'
|
||||
msg_error(msg)
|
||||
|
@ -18,6 +18,7 @@ function products_default_config(){
|
|||
if(cfg_products['inventario']){
|
||||
$$('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();
|
||||
|
||||
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)){
|
||||
msg_error('La clave SAT no existe')
|
||||
return
|
||||
|
@ -422,4 +428,4 @@ function up_products_upload_complete(response){
|
|||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -681,18 +681,6 @@ var options_admin_otros = [
|
|||
labelRight: 'Ayuda para generar anticipos'},
|
||||
{}]},
|
||||
{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'},
|
||||
{cols: [{maxWidth: 15},
|
||||
{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 = [
|
||||
{maxHeight: 20},
|
||||
{cols: [{maxWidth: 15},
|
||||
|
@ -753,6 +756,12 @@ var options_admin_complements = [
|
|||
{view: 'text', id: 'txt_config_cfdipay_folio', name: 'txt_config_cfdipay_serie',
|
||||
label: 'Folio', labelWidth: 50, labelAlign: 'right'},
|
||||
{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}},
|
||||
{header: 'Clientes y Proveedores', body: {id: 'tab_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',
|
||||
view: 'scrollview', body: {rows: options_admin_complements}}},
|
||||
{header: 'Otros', body: {id: 'tab_admin_otros', view: 'scrollview',
|
||||
|
@ -1214,6 +1225,10 @@ var usuarios_admin = [
|
|||
{maxHeight: 20},
|
||||
{template: 'Usuarios Registrados', type: 'section'},
|
||||
{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},
|
||||
{view: 'daterangepicker', id: 'filter_dates', label: 'Fechas',
|
||||
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 = {
|
||||
view: 'richselect',
|
||||
id: 'lst_regimen_fiscal',
|
||||
|
@ -593,6 +602,7 @@ var controls_generate = [
|
|||
{view: 'fieldset', label: 'Comprobante', body: body_comprobante},
|
||||
{view: 'fieldset', label: 'Opciones de Pago', body: body_opciones},
|
||||
{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',
|
||||
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_doc = "<a class='link_doc' target='_blank' href='https://doc.empresalibre.net'><b>?</b> </a>";
|
||||
var link_blog = "<a class='link_default' target='_blank' href='https://blog.empresalibre.mx'>Blog</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 = {
|
||||
|
@ -72,8 +73,10 @@ var ui_main = {
|
|||
}
|
||||
},
|
||||
{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,
|
||||
{view: 'button', id: 'cmd_update_timbres', type: 'icon', width: 45,
|
||||
css: 'app_button', icon: 'bell-o', badge: 0},
|
||||
|
|
|
@ -20,6 +20,8 @@ var toolbar_nomina_util = [
|
|||
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'},
|
||||
|
|
|
@ -135,9 +135,13 @@ var controls_generals = [
|
|||
{view: "richselect", id: "unidad", name: "unidad", label: "Unidad",
|
||||
width: 300, labelWidth: 130, labelAlign: "right", required: true,
|
||||
invalidMessage: "La Unidad es requerida", options: []},
|
||||
{view: 'text', id: 'tags_producto', name: 'tags_producto',
|
||||
labelAlign: 'right', label: 'Etiquetas',
|
||||
placeholder: 'Separadas por comas'}
|
||||
{view: 'text', id: 'cant_by_packing', name: 'cant_by_packing',
|
||||
labelAlign: 'right', labelWidth: 150, inputAlign: "right",
|
||||
label: 'Cantidad por empaque:'},
|
||||
{},
|
||||
//~ {view: 'text', id: 'tags_producto', name: 'tags_producto',
|
||||
//~ labelAlign: 'right', label: 'Etiquetas',
|
||||
//~ placeholder: 'Separadas por comas'}
|
||||
]},
|
||||
{cols: [
|
||||
{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="iedu.xslt"/>
|
||||
<xsl:include href="pagos10.xslt"/>
|
||||
<xsl:include href="divisas.xslt"/>
|
||||
|
||||
<!--
|
||||
<xsl:include href="ecc11.xslt"/>
|
||||
<xsl:include href="Divisas.xslt"/>
|
||||
<xsl:include href="pfic.xslt"/>
|
||||
<xsl:include href="TuristaPasajeroExtranjero.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