diff --git a/.gitignore b/.gitignore
index f5d45f4..1280f9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ vedev/
# Virtualenv
.env/
virtual/
+env
docs/build
cache/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07bb63d..caebc4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+v 2.0.0 [31-Mar-2022]
+----------------------
+ - Primera versión de timbrado con CFDI4
+ - **IMPORTANTE** NO intentes timbrar si **antes** no has validado en nuestro demo que puedes timbrar tus CFDIs habituales.
+
+
v 1.47.0 [28-Mar-2022]
----------------------
- Mejora: Soporte basico para complemento Comercio Exterior.
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..b66a3c0
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,2 @@
+[ ] Permitir más de un remolque en la Carta Porte
+[ ] Representación impresa de Comercio Exterior
diff --git a/VERSION b/VERSION
index 21998d3..227cea2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.47.0
+2.0.0
diff --git a/docs/delete tables.md b/docs/delete tables.md
new file mode 100644
index 0000000..422755c
--- /dev/null
+++ b/docs/delete tables.md
@@ -0,0 +1,18 @@
+prefacturasdetalle;
+prefacturasimpuestos;
+prefacturas;
+
+ticketsimpuestos;
+ticketsdetalle;
+tickets;
+
+facturasrelacionadas;
+facturaspersonalizados;
+facturaspagos;
+facturasimpuestos;
+facturasdetalle;
+facturascomplementos;
+facturas;
+
+cfdipagos;
+movimientosbanco;
diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py
index 5e17926..f02e587 100644
--- a/source/app/controllers/cfdi_xml.py
+++ b/source/app/controllers/cfdi_xml.py
@@ -25,16 +25,21 @@ from logbook import Logger
log = Logger('XML')
-CFDI_ACTUAL = 'cfdi33'
+CFDI_ACTUAL = 'cfdi40'
NOMINA_ACTUAL = 'nomina12'
+DEFAULT = {
+ 'exportacion': '01',
+}
+
+
SAT = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
- 'cfdi32': {
- 'version': '3.2',
+ 'cfdi40': {
+ 'version': '4.0',
'prefix': 'cfdi',
- 'xmlns': 'http://www.sat.gob.mx/cfd/3',
- 'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd',
+ 'xmlns': 'http://www.sat.gob.mx/cfd/4',
+ 'schema': 'http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd',
},
'cfdi33': {
'version': '3.3',
@@ -42,6 +47,12 @@ SAT = {
'xmlns': 'http://www.sat.gob.mx/cfd/3',
'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd',
},
+ 'cfdi32': {
+ 'version': '3.2',
+ 'prefix': 'cfdi',
+ 'xmlns': 'http://www.sat.gob.mx/cfd/3',
+ 'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd',
+ },
'nomina11': {
'version': '1.1',
'prefix': 'nomina',
@@ -80,10 +91,10 @@ SAT = {
'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd',
},
'pagos': {
- 'version': '1.0',
- 'prefix': 'pago10',
- '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',
+ 'version': '2.0',
+ 'prefix': 'pago20',
+ 'xmlns': 'http://www.sat.gob.mx/Pagos20',
+ 'schema': ' http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd',
},
'divisas': {
'version': '1.0',
@@ -140,6 +151,7 @@ class CFDI(object):
return ''
self._comprobante(datos['comprobante'])
+ self._informacion_global(datos['global'])
self._relacionados(datos['relacionados'])
self._emisor(datos['emisor'])
self._receptor(datos['receptor'])
@@ -270,9 +282,21 @@ class CFDI(object):
if not 'Fecha' in attributes:
attributes['Fecha'] = self._now()
+ # ~ cfdi4
+ if not 'Exportacion' in attributes:
+ attributes['Exportacion'] = DEFAULT['exportacion']
+
self._cfdi = ET.Element('{}:Comprobante'.format(self._pre), attributes)
return
+ def _informacion_global(self, datos):
+ if not datos:
+ return
+
+ node_name = '{}:InformacionGlobal'.format(self._pre)
+ node = ET.SubElement(self._cfdi, node_name, datos)
+ return
+
def _relacionados(self, datos):
if not datos or not datos['tipo'] or not datos['cfdis']:
return
@@ -292,6 +316,7 @@ class CFDI(object):
return
def _receptor(self, datos):
+ datos['Nombre'] = datos['Nombre'].upper()
node_name = '{}:Receptor'.format(self._pre)
emisor = ET.SubElement(self._cfdi, node_name, datos)
return
@@ -553,14 +578,41 @@ class CFDI(object):
if 'pagos' in datos:
datos = datos.pop('pagos')
+ totales = datos.pop('totales')
relacionados = datos.pop('relacionados')
+ taxes_pay = datos.pop('taxes_pay')
pre = SAT['pagos']['prefix']
+
attributes = {'Version': SAT['pagos']['version']}
pagos = ET.SubElement(
self._complemento, '{}:Pagos'.format(pre), attributes)
+
+ ET.SubElement(pagos, '{}:Totales'.format(pre), totales)
+
node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), datos)
for row in relacionados:
- ET.SubElement(node_pago, '{}:DoctoRelacionado'.format(pre), row)
+ taxes = row.pop('taxes')
+ node = ET.SubElement(node_pago, f'{pre}:DoctoRelacionado', row)
+ node_tax = ET.SubElement(node, f'{pre}:ImpuestosDR')
+ if taxes['retenciones']:
+ node = ET.SubElement(node_tax, f'{pre}:RetencionesDR')
+ for tax in taxes['retenciones']:
+ ET.SubElement(node, f'{pre}:RetencionDR', tax)
+ if taxes['traslados']:
+ node = ET.SubElement(node_tax, f'{pre}:TrasladosDR')
+ for tax in taxes['traslados']:
+ ET.SubElement(node, f'{pre}:TrasladoDR', tax)
+
+ node_tax = ET.SubElement(node_pago, f'{pre}:ImpuestosP')
+ if taxes_pay['retenciones']:
+ node = ET.SubElement(node_tax, f'{pre}:RetencionsP')
+ for key, importe in taxes_pay['retenciones'].items():
+ attr = {'ImpuestoP': key, 'ImporteP': importe}
+ ET.SubElement(node, f'{pre}:RetencionP', attr)
+ if taxes_pay['traslados']:
+ node = ET.SubElement(node_tax, f'{pre}:TrasladosP')
+ for key, tax in taxes_pay['traslados'].items():
+ ET.SubElement(node, f'{pre}:TrasladoP', tax)
if 'leyendas' in datos:
pre = SAT['leyendas']['prefix']
diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py
index 1b333ed..1981b6e 100644
--- a/source/app/controllers/main.py
+++ b/source/app/controllers/main.py
@@ -797,3 +797,27 @@ class AppSATUnidadesPeso(object):
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.sat_unidades_peso_post(values, user)
resp.status = falcon.HTTP_200
+
+
+class AppSATRegimenes(object):
+
+ def __init__(self, db):
+ self._db = db
+
+ def on_get(self, req, resp):
+ values = req.params
+ user = req.env['beaker.session']['userobj']
+ req.context['result'] = self._db.sat_regimenes_get(values, user)
+ resp.status = falcon.HTTP_200
+
+
+class AppSociosRegimenes(object):
+
+ def __init__(self, db):
+ self._db = db
+
+ def on_get(self, req, resp):
+ values = req.params
+ user = req.env['beaker.session']['userobj']
+ req.context['result'] = self._db.socios_regimenes_get(values, user)
+ resp.status = falcon.HTTP_200
diff --git a/source/app/controllers/pacs/comerciodigital/comercio.py b/source/app/controllers/pacs/comerciodigital/comercio.py
index a06c5a7..4e08469 100644
--- a/source/app/controllers/pacs/comerciodigital/comercio.py
+++ b/source/app/controllers/pacs/comerciodigital/comercio.py
@@ -41,6 +41,12 @@ logging.getLogger('requests').setLevel(logging.ERROR)
TIMEOUT = 10
+NAMESPACES = {
+ '3.3': 'http://www.sat.gob.mx/cfd/3',
+ '4.0': 'http://www.sat.gob.mx/cfd/4',
+ 'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
+}
+
def pretty_print_POST(req):
"""
@@ -63,7 +69,7 @@ class PACComercioDigital(object):
ws = 'https://{}.comercio-digital.mx/{}'
api = 'https://app2.comercio-digital.mx/{}'
URL = {
- 'timbra': ws.format('ws', 'timbre/timbrarV5.aspx'),
+ 'timbra': ws.format('ws', 'timbre4/timbrarV5'),
'cancel': ws.format('cancela', 'cancela4/cancelarUuid'),
'cancelxml': ws.format('cancela', 'cancela4/cancelarXml'),
'status': ws.format('cancela', 'arws/consultaEstatus'),
@@ -78,7 +84,7 @@ class PACComercioDigital(object):
'702': '702 Error rfc/empresa invalido',
}
NS_CFDI = {
- 'cfdi': 'http://www.sat.gob.mx/cfd/3',
+ 'cfdi': 'http://www.sat.gob.mx/cfd/4',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
@@ -86,7 +92,7 @@ class PACComercioDigital(object):
ws = 'https://pruebas.comercio-digital.mx/{}'
ws6 = 'https://pruebas6.comercio-digital.mx/arws/{}'
URL = {
- 'timbra': ws.format('timbre/timbrarV5.aspx'),
+ 'timbra': ws.format('timbre4/timbrarV5'),
'cancel': ws.format('cancela4/cancelarUuid'),
'cancelxml': ws.format('cancela4/cancelarXml'),
'status': ws6.format('consultaEstatus'),
@@ -175,26 +181,29 @@ class PACComercioDigital(object):
info['key'] = base64.b64encode(info['key_enc']).decode()
info['cer'] = base64.b64encode(info['cer_ori']).decode()
- NS_CFDI = {
- 'cfdi': 'http://www.sat.gob.mx/cfd/3',
- 'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
- }
tree = ET.fromstring(cfdi.encode())
+ version = tree.attrib['Version']
+
+ namespaces = {
+ 'cfdi': NAMESPACES[version],
+ 'tdf': NAMESPACES['tdf'],
+ }
+
tipo = tree.xpath(
'string(//cfdi:Comprobante/@TipoDeComprobante)',
- namespaces=NS_CFDI)
+ namespaces=namespaces)
total = tree.xpath(
'string(//cfdi:Comprobante/@Total)',
- namespaces=NS_CFDI)
+ namespaces=namespaces)
rfc_emisor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
- namespaces=NS_CFDI)
+ namespaces=namespaces)
rfc_receptor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
- namespaces=NS_CFDI)
+ namespaces=namespaces)
uid = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
- namespaces=NS_CFDI)
+ namespaces=namespaces)
data = (
f"USER={auth['user']}",
f"PWDW={auth['pass']}",
diff --git a/source/app/controllers/pacs/finkok/finkok.py b/source/app/controllers/pacs/finkok/finkok.py
index 60e2f8a..9ef693d 100644
--- a/source/app/controllers/pacs/finkok/finkok.py
+++ b/source/app/controllers/pacs/finkok/finkok.py
@@ -74,7 +74,7 @@ class PACFinkok(object):
WS = 'https://facturacion.finkok.com/servicios/soap/{}.wsdl'
NS_TYPE = 'ns1'
if DEBUG:
- WS = 'http://demo-facturacion.finkok.com/servicios/soap/{}.wsdl'
+ WS = 'https://demo-facturacion.finkok.com/servicios/soap/{}.wsdl'
NS_TYPE = 'ns0'
URL = {
'quick_stamp': False,
diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py
index 548bbf1..407158d 100644
--- a/source/app/controllers/util.py
+++ b/source/app/controllers/util.py
@@ -78,9 +78,11 @@ import segno
from .pacs.cfdi_cert import SATCertificate
from settings import (
+ CFDI_VERSIONS,
EXT,
MXN,
PATHS,
+ PRE_DEFAULT,
)
@@ -636,6 +638,12 @@ class LIBO(object):
self._set_cell('{cfdi.%s}' % k, v)
return
+ def _informacion_global(self, data):
+ for k, v in data.items():
+ print(k, v)
+ self._set_cell('{cfdi.%s}' % k, v)
+ return
+
def _emisor(self, data):
for k, v in data.items():
self._set_cell('{emisor.%s}' % k, v)
@@ -873,8 +881,6 @@ class LIBO(object):
image = self._template.createInstance('com.sun.star.drawing.GraphicObjectShape')
gp = self._create_instance('com.sun.star.graphic.GraphicProvider')
pd.add(image)
- # ~ image.GraphicURL = data['path_cbb']
- # ~ properties = self._set_properties({'URL': self._path_url(data['path_cbb'])})
instance = 'com.sun.star.io.SequenceInputStream'
stream = self._create_instance(instance)
@@ -883,11 +889,11 @@ class LIBO(object):
image.Graphic = gp.queryGraphic(properties)
s = Size()
- s.Width = 4150
- s.Height = 4500
+ s.Width = 4000
+ s.Height = 4000
image.setSize(s)
image.Anchor = self._set_cell('{timbre.cbb}')
- # ~ _kill(data['path_cbb'])
+
return
def _donataria(self, data):
@@ -1119,6 +1125,8 @@ class LIBO(object):
return
def _cfdipays(self, data):
+ version = data['Version']
+
related = data.pop('related', [])
for k, v in data.items():
if k.lower() in ('monto',):
@@ -1221,6 +1229,7 @@ class LIBO(object):
pakings = data.pop('pakings', [])
self._comprobante(data['comprobante'])
+ self._informacion_global(data['informacion_global'])
self._emisor(data['emisor'])
self._receptor(data['receptor'])
self._conceptos(data['conceptos'], pakings)
@@ -1358,6 +1367,7 @@ class LIBO(object):
'codigo_postal',
'notas',
'correo',
+ 'regimen_fiscal',
)
rows = tuple([dict(zip(fields, r)) for r in data[1:]])
msg = 'Empleados importados correctamente'
@@ -1553,36 +1563,40 @@ 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']
+ default = f'plantilla_factura_{version}.ods'
if 'nomina' in data and data['nomina']:
- default = 'plantilla_nomina.ods'
- version = '{}_{}'.format(data['nomina']['version'], version)
+ version_nomina = data['nomina']['version']
+ default = f'plantilla_nomina_{version}_{version_nomina}.ods'
+ version = f'{version}_cn_{version_nomina}'
if 'carta_porte' in data:
- default = 'plantilla_factura_cp.ods'
- version = '{}_cp_{}'.format(version, data['carta_porte']['version'])
+ default = 'plantilla_factura_ccp.ods'
+ version = '{}_ccp_{}'.format(version, data['carta_porte']['version'])
- pagos = ''
if data.get('pagos', False):
- version = '1.0'
- pagos = 'pagos_'
+ version_pagos = data['pays']['version']
+ default = f'plantilla_pagos_{version}_{version_pagos}.ods'
+ version = f'{version}_cp_{version_pagos}'
if pdf_from == '2':
return to_pdf_from_json(rfc, version, data)
+ donativo = ''
+ if data['donativo']:
+ donativo = '_donativo'
+
+ template_name = f'{rfc.lower()}_{version}.ods'
+ # ~ print('T', template_name, default)
+
if APP_LIBO:
app = LIBO()
if app.is_running:
- donativo = ''
- if data['donativo']:
- donativo = '_donativo'
- name = '{}_{}{}{}.ods'.format(rfc.lower(), pagos, version, donativo)
- path = get_template_ods(name, default)
+ path = get_template_ods(template_name, default)
if path:
return app.pdf(path, data, ods)
@@ -1662,10 +1676,11 @@ def html_to_pdf(data):
def import_employees(rfc):
+ msg = 'No se pudo cargar el archivo'
name = '{}_employees.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'tmp', name)
if not is_file(path):
- return ()
+ return (), msg
msg = 'LibreOffice no se pudo iniciar'
if APP_LIBO:
@@ -1754,7 +1769,7 @@ def _comprobante(doc, options):
data['tiporelacion'] = options.get('tiporelacion', '')
return data
- if data['version'] == '3.3':
+ if data['version'] in CFDI_VERSIONS:
tipos = {
'I': 'ingreso',
'E': 'egreso',
@@ -1839,7 +1854,7 @@ def _receptor(doc, version, values):
return data
data['usocfdi'] = values['usocfdi']
- data.update(values['receptor'])
+ # ~ data.update(values['receptor'])
return data
@@ -1857,7 +1872,7 @@ def _conceptos(doc, version, options):
data.append(values)
continue
- if version == '3.3':
+ if version in CFDI_VERSIONS:
if 'noidentificacion' in values:
values['noidentificacion'] = '{}\n(SAT {})'.format(
values['noidentificacion'], values['ClaveProdServ'])
@@ -1921,7 +1936,7 @@ def _totales(doc, cfdi, version):
# ~ for n in node.getchildren():
for n in list(node):
tmp = CaseInsensitiveDict(n.attrib.copy())
- if version == '3.3':
+ if version in CFDI_VERSIONS:
tasa = round(float(tmp['tasaocuota']), DECIMALES)
title = 'Traslado {} {}'.format(tn.get(tmp['impuesto']), tasa)
else:
@@ -1933,7 +1948,7 @@ def _totales(doc, cfdi, version):
# ~ for n in node.getchildren():
for n in list(node):
tmp = CaseInsensitiveDict(n.attrib.copy())
- if version == '3.3':
+ if version in CFDI_VERSIONS:
title = 'Retención {} {}'.format(
tn.get(tmp['impuesto']), '')
else:
@@ -1965,7 +1980,7 @@ def _totales(doc, cfdi, version):
def _timbre(doc, version, values, pdf_from='1'):
CADENA = '||{version}|{UUID}|{FechaTimbrado}|{selloCFD}|{noCertificadoSAT}||'
- if version == '3.3':
+ if version in CFDI_VERSIONS:
CADENA = '||{Version}|{UUID}|{FechaTimbrado}|{SelloCFD}|{NoCertificadoSAT}||'
node = doc.find('{}Complemento/{}TimbreFiscalDigital'.format(
PRE[version], PRE['TIMBRE']))
@@ -2095,19 +2110,53 @@ def _nomina(doc, data, values, version_cfdi):
return info
+def _get_info_pays_2(node):
+ pre_pays = PRE_DEFAULT['PAGOS']['PRE']
+ data = CaseInsensitiveDict(node.attrib.copy())
+
+ path = f"{pre_pays}Totales"
+ totales = node.find(path)
+ data.update(CaseInsensitiveDict(totales.attrib.copy()))
+
+ path = f"{pre_pays}Pago"
+ node_pay = node.find(path)
+ data.update(CaseInsensitiveDict(node_pay.attrib.copy()))
+
+ related = []
+ for n in node_pay:
+ attr = CaseInsensitiveDict(n.attrib.copy())
+ if attr:
+ attr['metododepagodr'] = ''
+ related.append(attr)
+
+ data['related'] = related
+
+ return data
+
+
def _cfdipays(doc, data, version):
- node = doc.find('{}Complemento/{}Pagos'.format(PRE[version], PRE['pagos']))
+ #todo: Obtener versión de complemento
+ if version == '4.0':
+ pre_pays = PRE_DEFAULT['PAGOS']['PRE']
+ path = f"{PRE[version]}Complemento/{pre_pays}Pagos"
+ node = doc.find(path)
+ else:
+ node = doc.find('{}Complemento/{}Pagos'.format(PRE[version], PRE['pagos']))
+
if node is None:
return {}
- info = CaseInsensitiveDict(node.attrib.copy())
- related = []
- for n1 in node:
- info.update(CaseInsensitiveDict(n1.attrib.copy()))
- for n2 in n1:
- related.append(CaseInsensitiveDict(n2.attrib.copy()))
+ if version == '4.0':
+ info = _get_info_pays_2(node)
+ else:
+ info = CaseInsensitiveDict(node.attrib.copy())
+ related = []
+ for n1 in node:
+ info.update(CaseInsensitiveDict(n1.attrib.copy()))
+ for n2 in n1:
+ related.append(CaseInsensitiveDict(n2.attrib.copy()))
- info['related'] = related
+ info['related'] = related
data['comprobante']['totalenletras'] = to_letters(
float(info['monto']), info['monedap'])
@@ -2120,6 +2169,7 @@ def get_data_from_xml(invoice, values, pdf_from='1'):
data = {'cancelada': invoice.cancelada, 'donativo': False}
if hasattr(invoice, 'donativo'):
data['donativo'] = invoice.donativo
+
doc = parse_xml(invoice.xml)
data['comprobante'] = _comprobante(doc, values)
version = data['comprobante']['version']
@@ -2226,11 +2276,33 @@ class UpFile(object):
return
+def save_template(rfc, opt, file_obj):
+ result = {'status': 'error', 'ok': False}
+
+ name_template = f'{rfc}{opt}'
+ path_template = _join(PATH_MEDIA, 'templates', name_template)
+
+ if save_file(path_template, file_obj.file.read()):
+ result = {'status': 'server', 'name': file_obj.filename, 'ok': True}
+
+ return result
+
+
def upload_file(rfc, opt, file_obj):
rfc = rfc.lower()
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
+ versions = ('_3.2.ods',
+ '_3.3.ods', '_3.3_cn_1.2.ods', '_3.3_ccp_2.0.ods',
+ '_4.0.ods',
+ '_4.0_cn_1.2.ods',
+ '_4.0_cp_2.0.ods',
+ '_4.0_ccp_2.0.ods',
+ '_4.0_cd_1.1.ods')
+ if opt in versions:
+ return save_template(rfc, opt, file_obj)
+
EXTENSIONS = {
'txt_plantilla_factura_32': EXT['ODS'],
'txt_plantilla_factura_33': EXT['ODS'],
diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py
index 6df126f..cbcb16e 100644
--- a/source/app/controllers/utils.py
+++ b/source/app/controllers/utils.py
@@ -55,7 +55,7 @@ from dateutil import parser
from .cfdi_xml import CFDI
-from settings import DEBUG, DB_COMPANIES, PATHS, TEMPLATE_CANCEL, RFCS
+from settings import DEBUG, DB_COMPANIES, PATHS, TEMPLATE_CANCEL, RFCS, PRE
from .pacs.cfdi_cert import SATCertificate
from .pacs import PACComercioDigital
@@ -88,6 +88,7 @@ PACS = {
'finkok': PACFinkok,
'comercio': PACComercioDigital,
}
+
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
@@ -255,11 +256,15 @@ class SendMail(object):
class CfdiToDict(object):
+ NS_VERSION = {
+ 'cfdi3.3': 'http://www.sat.gob.mx/cfd/3',
+ 'cfdi4.0': 'http://www.sat.gob.mx/cfd/4',
+ }
NS = {
- 'cfdi': 'http://www.sat.gob.mx/cfd/3',
'divisas': 'http://www.sat.gob.mx/divisas',
'leyendasFisc': 'http://www.sat.gob.mx/leyendasFiscales',
'cartaporte20': 'http://www.sat.gob.mx/CartaPorte20',
+ 'nomina12': 'http://www.sat.gob.mx/nomina12',
}
tipo_figura = {
'01': '[01] Operador',
@@ -267,6 +272,53 @@ class CfdiToDict(object):
'03': '[03] Arrendador',
'04': '[04] Notificado',
}
+ REGIMEN_FISCAL = {
+ '601': '[601] General de Ley Personas Morales',
+ '603': '[603] Personas Morales con Fines no Lucrativos',
+ '605': '[605] Sueldos y Salarios e Ingresos Asimilados a Salarios',
+ '606': '[606] Arrendamiento',
+ '607': '[607] Régimen de Enajenación o Adquisición de Bienes',
+ '608': '[608] Demás ingresos',
+ '610': '[610] Residentes en el Extranjero sin Establecimiento Permanente en México',
+ '611': '[611] Ingresos por Dividendos (socios y accionistas)',
+ '612': '[612] Personas Físicas con Actividades Empresariales y Profesionales',
+ '614': '[614] Ingresos por intereses',
+ '615': '[615] Régimen de los ingresos por obtención de premios',
+ '616': '[616] Sin obligaciones fiscales',
+ '620': '[620] Sociedades Cooperativas de Producción que optan por diferir sus ingresos',
+ '621': '[621] Incorporación Fiscal',
+ '622': '[622] Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras',
+ '623': '[623] Opcional para Grupos de Sociedades',
+ '624': '[624] Coordinados',
+ '625': '[625] Régimen de las Actividades Empresariales con ingresos a través de Plataformas Tecnológicas',
+ '626': '[626] Régimen Simplificado de Confianza',
+ }
+ USO_CFDI = {
+ 'G01': '[G01] Adquisición de mercancías.',
+ 'G02': '[G02] Devoluciones, descuentos o bonificaciones.',
+ 'G03': '[G03] Gastos en general.',
+ 'I01': '[I01] Construcciones.',
+ 'I02': '[I02] Mobiliario y equipo de oficina por inversiones.',
+ 'I03': '[I03] Equipo de transporte.',
+ 'I04': '[I04] Equipo de computo y accesorios.',
+ 'I05': '[I05] Dados, troqueles, moldes, matrices y herramental.',
+ 'I06': '[I06] Comunicaciones telefónicas.',
+ 'I07': '[I07] Comunicaciones satelitales.',
+ 'I08': '[I08] Otra maquinaria y equipo.',
+ 'D01': '[D01] Honorarios médicos, dentales y gastos hospitalarios.',
+ 'D02': '[D02] Gastos médicos por incapacidad o discapacidad.',
+ 'D03': '[D03] Gastos funerales.',
+ 'D04': '[D04] Donativos.',
+ 'D05': '[D05] Intereses reales efectivamente pagados por créditos hipotecarios (casa habitación).',
+ 'D06': '[D06] Aportaciones voluntarias al SAR.',
+ 'D07': '[D07] Primas por seguros de gastos médicos.',
+ 'D08': '[D08] Gastos de transportación escolar obligatoria.',
+ 'D09': '[D09] Depósitos en cuentas para el ahorro, primas que tengan como base planes de pensiones.',
+ 'D10': '[D10] Pagos por servicios educativos (colegiaturas).',
+ 'S01': '[S01] Sin efectos fiscales.',
+ 'CP01': '[CP01] Pagos',
+ 'CN01': '[CN01] Nómina',
+ }
PAISES = {
'MEX': 'México',
}
@@ -305,8 +357,30 @@ class CfdiToDict(object):
'YUC': 'Yucatán',
'ZAC': 'Zacatecas',
}
+ PERIODICIDAD = {
+ '01': '[01] Diario',
+ '02': '[02] Semanal',
+ '03': '[03] Quincenal',
+ '04': '[04] Mensual',
+ '05': '[05] Bimestral',
+ }
+ MESES = {
+ '01': '[01] Enero',
+ '02': '[02] Febrero',
+ '03': '[03] Marzo',
+ '04': '[04] Abril',
+ '05': '[05] Mayo',
+ '06': '[06] Junio',
+ '07': '[07] Julio',
+ '08': '[08] Agosto',
+ '09': '[09] Septiembre',
+ '10': '[10] Octubre',
+ '11': '[11] Noviembre',
+ '12': '[12] Diciembre',
+ }
def __init__(self, xml):
+ self.version = ''
self._values = {
'leyendas': (),
}
@@ -318,9 +392,42 @@ class CfdiToDict(object):
return self._values
def _get_values(self):
+ self.version = self._root.attrib['Version']
+ ns = f'cfdi{self.version}'
+ self.NS['cfdi'] = self.NS_VERSION[ns]
+ self._informacion_global()
+ self._receptor()
self._complementos()
return
+ def _informacion_global(self):
+ self._values['informacion_global'] = {}
+
+ path = '//cfdi:InformacionGlobal'
+ data = self._root.xpath(path, namespaces=self.NS)
+ if not data:
+ return
+
+ data = data[0]
+ attr = CaseInsensitiveDict(data.attrib)
+
+ value = f"Periodicidad Factura Global: {self.PERIODICIDAD[attr['Periodicidad']]} "
+ value += f"del mes {self.MESES[attr['Meses']]} "
+ value += f"del año {attr['Año']}"
+ self._values['informacion_global'] = {'informacion_global': value}
+ return
+
+ def _receptor(self):
+ path = '//cfdi:Receptor'
+ receptor = self._root.xpath(path, namespaces=self.NS)[0]
+ attr = CaseInsensitiveDict(receptor.attrib)
+ attr['usocfdi'] = self.USO_CFDI[attr['UsoCFDI']]
+ if self.version == '4.0':
+ attr['domiciliofiscal'] = attr['DomicilioFiscalReceptor']
+ attr['regimenfiscal'] = self.REGIMEN_FISCAL[attr['RegimenFiscalReceptor']]
+ self._values['receptor'] = attr
+ return
+
def _set_carta_porte_domicilio(self, data):
municipio = data['Municipio']
estado = self.ESTADOS[data['Estado']]
@@ -332,6 +439,14 @@ class CfdiToDict(object):
path = '//cfdi:Complemento'
complemento = self._root.xpath(path, namespaces=self.NS)[0]
+ path = '//nomina12:Nomina'
+ nomina = complemento.xpath(path, namespaces=self.NS)
+ if nomina:
+ for node in nomina[0]:
+ if 'Receptor' in node.tag:
+ attr = CaseInsensitiveDict(node.attrib)
+ self._values['receptor'].update(attr)
+
path = '//divisas:Divisas'
divisas = complemento.xpath(path, namespaces=self.NS)
if divisas:
@@ -725,6 +840,9 @@ def xml_cancel(xml, auth, cert, name):
def get_client_balance(auth, rfc=''):
+ if not auth:
+ return 'p/c'
+
pac = PACS[auth['pac']]()
balance = pac.client_balance(auth, rfc)
if pac.error:
@@ -760,8 +878,10 @@ def make_xml(data, certificado):
def get_pac_by_rfc(cfdi):
tree = ET.fromstring(cfdi.encode())
+ version = tree.attrib['Version']
+ namespaces = {'cfdi': PRE[version][1:-1], 'tdf': PRE['TIMBRE'][1:-1]}
path = 'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@RfcProvCertif)'
- rfc_pac = tree.xpath(path, namespaces=NS_CFDI)
+ rfc_pac = tree.xpath(path, namespaces=namespaces)
return RFCS[rfc_pac]
@@ -827,17 +947,20 @@ def cancel_xml_sign(invoice, args, auth, certificado):
def _get_data_sat(xml):
BF = 'string(//*[local-name()="{}"]/@{})'
- NS_CFDI = {'cfdi': 'http://www.sat.gob.mx/cfd/3'}
+ # ~ NS_CFDI = {'cfdi': 'http://www.sat.gob.mx/cfd/3'}
try:
tree = ET.fromstring(xml.encode())
+ version = tree.attrib['Version']
+ namespaces = {'cfdi': PRE[version][1:-1]}
+
emisor = escape(
- tree.xpath('string(//cfdi:Emisor/@rfc)', namespaces=NS_CFDI) or
- tree.xpath('string(//cfdi:Emisor/@Rfc)', namespaces=NS_CFDI)
+ tree.xpath('string(//cfdi:Emisor/@rfc)', namespaces=namespaces) or
+ tree.xpath('string(//cfdi:Emisor/@Rfc)', namespaces=namespaces)
)
receptor = escape(
- tree.xpath('string(//cfdi:Receptor/@rfc)', namespaces=NS_CFDI) or
- tree.xpath('string(//cfdi:Receptor/@Rfc)', namespaces=NS_CFDI)
+ tree.xpath('string(//cfdi:Receptor/@rfc)', namespaces=namespaces) or
+ tree.xpath('string(//cfdi:Receptor/@Rfc)', namespaces=namespaces)
)
total = tree.get('total') or tree.get('Total')
uuid = tree.xpath(BF.format('TimbreFiscalDigital', 'UUID'))
diff --git a/source/app/main.py b/source/app/main.py
index 6346cd4..05fcb02 100644
--- a/source/app/main.py
+++ b/source/app/main.py
@@ -25,6 +25,8 @@ from controllers.main import (AppEmpresas,
AppWareHouse,
AppWareHouseProduct,
AppSATUnidadesPeso,
+ AppSATRegimenes,
+ AppSociosRegimenes,
)
@@ -78,6 +80,8 @@ api.add_route('/warehouseproduct', AppWareHouseProduct(db))
api.add_route('/ticketsdetails', AppTicketsDetails(db))
api.add_route('/users', AppUsers(db))
api.add_route('/satunidadespeso', AppSATUnidadesPeso(db))
+api.add_route('/satregimenes', AppSATRegimenes(db))
+api.add_route('/sociosregimenes', AppSociosRegimenes(db))
session_options = {
diff --git a/source/app/models/db.py b/source/app/models/db.py
index 3d55374..e410cd2 100644
--- a/source/app/models/db.py
+++ b/source/app/models/db.py
@@ -526,6 +526,12 @@ class StorageEngine(object):
def sat_unidades_peso_post(self, args, user):
return main.SATUnidadesPeso.post(args, user)
+ def sat_regimenes_get(self, filters, user):
+ return main.SATRegimenes.get_data(filters, user)
+
+ def socios_regimenes_get(self, filters, user):
+ return main.SociosRegimenes.get_data(filters, user)
+
# Companies only in MV
def _get_empresas(self, values):
return main.companies_get()
diff --git a/source/app/models/main.py b/source/app/models/main.py
index 10ea921..85a73d7 100644
--- a/source/app/models/main.py
+++ b/source/app/models/main.py
@@ -32,9 +32,9 @@ if __name__ == '__main__':
from controllers import util
-from settings import log, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI, \
+from settings import log, COMPANIES, VERSION, PATH_CP, PRE, \
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
- CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \
+ CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, \
DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \
DEFAULT_CFDIPAY, CURRENCY_MN
@@ -50,6 +50,7 @@ from settings import (
IS_MV,
MXN,
PATHS,
+ PRE_DEFAULT,
URL,
VALUES_PDF,
VERSION as VERSION_EMPRESA_LIBRE,
@@ -466,7 +467,7 @@ class Configuracion(BaseModel):
'chk_config_codigo_barras',
'chk_config_precio_con_impuestos',
'chk_llevar_inventario',
- 'chk_use_packing',
+ # ~ 'chk_use_packing',
'chk_multi_stock',
)
data = (Configuracion
@@ -692,8 +693,6 @@ class Configuracion(BaseModel):
)
elif keys['fields'] == 'templates':
fields = (
- 'txt_plantilla_factura_32',
- 'txt_plantilla_factura_33',
'txt_plantilla_factura_html',
'txt_plantilla_factura_css',
'txt_plantilla_factura_json',
@@ -939,6 +938,26 @@ class SATRegimenes(BaseModel):
)
return tuple(rows)
+ @classmethod
+ def _get_actives(cls, filters, user):
+ where = ((SATRegimenes.activo==True) & (SATRegimenes.fisica==True))
+ if (filters['morales']=='true'):
+ where = ((SATRegimenes.activo==True) & (SATRegimenes.moral==True))
+
+ rows = (SATRegimenes
+ .select(
+ SATRegimenes.id,
+ SATRegimenes.name.alias('value'))
+ .where(where)
+ .dicts()
+ )
+ return tuple(rows)
+
+ @classmethod
+ def get_data(cls, filters, user):
+ opt = filters['opt']
+ return getattr(cls, f'_get_{opt}')(filters, user)
+
class Emisor(BaseModel):
rfc = TextField(unique=True)
@@ -1027,8 +1046,6 @@ class Emisor(BaseModel):
'ong_autorizacion': obj.autorizacion,
'ong_fecha': obj.fecha_autorizacion,
'ong_fecha_dof': obj.fecha_dof,
- # ~ 'correo_timbrado': obj.correo_timbrado,
- # ~ 'token_timbrado': obj.token_timbrado,
'token_soporte': obj.token_soporte,
'emisor_registro_patronal': obj.registro_patronal,
'regimenes': [row.id for row in obj.regimenes]
@@ -1745,7 +1762,7 @@ class SATImpuestos(BaseModel):
tipo = 'R'
row = {
- 'key': IMPUESTOS.get(values['impuesto']),
+ 'key': IMPUESTOS.get(values['impuesto'], '000'),
'name': values['impuesto'],
'tipo': tipo,
'tasa': abs(tasa),
@@ -2744,6 +2761,7 @@ class Socios(BaseModel):
uso_cfdi = ForeignKeyField(SATUsoCfdi, null=True)
tags = ManyToManyField(Tags, related_name='socios_tags')
plantilla = TextField(default='')
+ regimen_fiscal = TextField(default='')
def __str__(self):
t = '{} ({})'
@@ -2787,6 +2805,13 @@ class Socios(BaseModel):
if fields['pais'] != 'México':
fields['pais'] = fields['pais'].upper()
+ if 'regimenes' in fields:
+ fields['regimenes'] = utils.loads(fields['regimenes'])
+ if isinstance(fields['regimenes'], list):
+ fields['regimenes'] = tuple(map(int, fields['regimenes']))
+ else:
+ fields['regimenes'] = (fields['regimenes'],)
+
return fields
@classmethod
@@ -2802,18 +2827,9 @@ class Socios(BaseModel):
str(CondicionesPago.get(id=row['condicion_pago']))
row['partner_balance'] = row.pop('saldo_cliente')
row['partner_email_fp'] = row.pop('correo_facturasp')
+ row['regimenes'] = SociosRegimenes.get_by_socio(row['id'])
return row
- #~ return {'data': data['rows'][:100], 'pos':0, 'total_count': 1300}
- #~ start = 0
- #~ count = 0
- #~ end = 100
- #~ if values:
- #~ {'start': '100', 'count': '100', 'continue': 'true'}
- #~ start = int(values['start'])
- #~ cont = int(values['count'])
- #~ end = start + count
-
total = Socios.select().count()
rows = (Socios
@@ -2829,19 +2845,23 @@ class Socios(BaseModel):
@classmethod
def get_by_client(cls, values):
id = int(values.get('id', 0))
+
if id:
row = (Socios
.select(
Socios.id, Socios.nombre, Socios.rfc,
SATFormaPago.key.alias('forma_pago'),
- SATUsoCfdi.key.alias('uso_cfdi'))
+ SATUsoCfdi.key.alias('uso_cfdi'),
+ Socios.codigo_postal)
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
.where((Socios.id==id) & (Socios.es_cliente==True))
.dicts()
)
if len(row):
- return {'ok': True, 'row': row[0]}
+ client = row[0]
+ client['regimenes'] = SociosRegimenes.get_by_key(client['id'])
+ return {'ok': True, 'row': client}
return {'ok': False}
name = values.get('name', '')
@@ -2849,7 +2869,8 @@ class Socios(BaseModel):
rows = (Socios
.select(Socios.id, Socios.nombre, Socios.rfc,
SATFormaPago.key.alias('forma_pago'),
- SATUsoCfdi.key.alias('uso_cfdi'))
+ SATUsoCfdi.key.alias('uso_cfdi'),
+ Socios.codigo_postal)
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
.where((Socios.es_cliente==True & Socios.es_activo==True) &
@@ -2863,6 +2884,7 @@ class Socios(BaseModel):
def add(cls, values):
accounts = util.loads(values.pop('accounts', '[]'))
fields = cls._clean(cls, values)
+ regimenes = fields.pop('regimenes', ())
w = ((Socios.rfc==fields['rfc']) & (Socios.slug==fields['slug']))
if Socios.select().where(w).exists():
@@ -2892,6 +2914,16 @@ class Socios(BaseModel):
except IntegrityError:
pass
+ for regimen in regimenes:
+ try:
+ fields = {
+ 'socio': obj,
+ 'regimen': regimen,
+ }
+ SociosRegimenes.create(**fields)
+ except IntegrityError:
+ pass
+
row = {
'id': obj.id,
'rfc': obj.rfc,
@@ -2905,6 +2937,8 @@ class Socios(BaseModel):
def actualizar(cls, values, id):
fields = cls._clean(cls, values)
fields.pop('accounts', '')
+ regimenes = fields.pop('regimenes', ())
+
try:
q = Socios.update(**fields).where(Socios.id==id)
q.execute()
@@ -2913,6 +2947,19 @@ class Socios(BaseModel):
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
return data
+ obj = Socios.get(Socios.id==id)
+ q = SociosRegimenes.delete().where(SociosRegimenes.socio==id)
+ q.execute()
+ for regimen in regimenes:
+ try:
+ fields = {
+ 'socio': obj,
+ 'regimen': regimen,
+ }
+ SociosRegimenes.create(**fields)
+ except IntegrityError:
+ pass
+
obj = Socios.get(Socios.id==id)
row = {
'id': id,
@@ -2934,6 +2981,8 @@ class Socios(BaseModel):
q = SociosCuentasBanco.delete().where(SociosCuentasBanco.socio==id)
q.execute()
+ q = SociosRegimenes.delete().where(SociosRegimenes.socio==id)
+ q.execute()
q = Socios.delete().where(Socios.id==id)
return bool(q.execute())
@@ -3040,6 +3089,52 @@ class SociosCuentasBanco(BaseModel):
return account.socio == invoice.cliente
+class SociosRegimenes(BaseModel):
+ socio = ForeignKeyField(Socios)
+ regimen = ForeignKeyField(SATRegimenes)
+
+ class Meta:
+ indexes = (
+ (('socio', 'regimen'), True),
+ )
+
+ @classmethod
+ def get_by_key(cls, socio, user=None):
+ fields = (SATRegimenes.key.alias('id'), SATRegimenes.name.alias('value'))
+ where = (SociosRegimenes.socio == socio)
+ regimenes = (SociosRegimenes
+ .select(*fields)
+ .where(where)
+ .join(SATRegimenes).switch(SociosRegimenes)
+ .dicts()
+ )
+ return tuple(regimenes)
+
+ @classmethod
+ def get_by_socio(cls, socio, user=None):
+ fields = (SATRegimenes.id,)
+ where = (SociosRegimenes.socio == socio)
+ regimenes = (SociosRegimenes
+ .select(*fields)
+ .where(where)
+ .join(SATRegimenes).switch(SociosRegimenes)
+ .tuples()
+ )
+ regimenes = [r[0] for r in regimenes]
+ return regimenes
+
+ @classmethod
+ def _get_by_id(cls, filters, user):
+ id = int(filters['id'])
+ return cls.get_by_key(id)
+
+ @classmethod
+ def get_data(cls, filters, user):
+ # ~ print('FILERS', filters)
+ opt = filters['opt']
+ return getattr(cls, f'_get_{opt}')(filters, user)
+
+
class Contactos(BaseModel):
socio = ForeignKeyField(Socios)
titulo = ForeignKeyField(TipoTitulo)
@@ -3615,7 +3710,6 @@ class Sucursales(BaseModel):
def _create(cls, args):
try:
values = utils.loads(args)
- print(values)
Sucursales.create(**values)
result = {'ok': True}
except Exception as e:
@@ -3806,6 +3900,7 @@ class Productos(BaseModel):
cantidad_empaque = DecimalField(default=0.0, max_digits=14, decimal_places=4,
auto_round=True)
is_discontinued = BooleanField(default=False)
+ objeto_impuesto = TextField(default='02')
class Meta:
order_by = ('descripcion',)
@@ -3829,6 +3924,7 @@ class Productos(BaseModel):
Productos.descuento,
Productos.inventario,
Productos.existencia,
+ Productos.objeto_impuesto,
)
where = (
(Productos.es_activo==True) &
@@ -3908,7 +4004,8 @@ class Productos(BaseModel):
Productos.valor_unitario,
Productos.descuento,
Productos.inventario,
- Productos.existencia)
+ Productos.existencia,
+ Productos.objeto_impuesto)
.join(SATUnidades).switch(Productos)
.where((Productos.es_activo==True) &
((Productos.clave==clave) | (Productos.codigo_barras==clave)))
@@ -4081,7 +4178,8 @@ class Productos(BaseModel):
Productos.inventario,
Productos.existencia,
Productos.minimo,
- Productos.cantidad_empaque.alias('cant_by_packing'),
+ Productos.objeto_impuesto,
+ # ~ Productos.cantidad_empaque.alias('cant_by_packing'),
)
.where(Productos.id==id).dicts()[0]
)
@@ -4344,7 +4442,7 @@ class RangosPrecios(BaseModel):
class Facturas(BaseModel):
cliente = ForeignKeyField(Socios)
- version = TextField(default=CURRENT_CFDI)
+ version = TextField(default=PRE_DEFAULT['CFDI']['VERSION'])
serie = TextField(default='')
folio = BigIntegerField(default=0)
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
@@ -4389,6 +4487,9 @@ class Facturas(BaseModel):
egreso_anticipo = BooleanField(default=False)
tipo_relacion = TextField(default='')
error = TextField(default='')
+ exportacion = TextField(default='01')
+ receptor_regimen = TextField(default='')
+ periodicidad = TextField(default='')
class Meta:
order_by = ('fecha',)
@@ -4620,23 +4721,33 @@ class Facturas(BaseModel):
obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion)
values['tiporelacion'] = str(obj)
- receptor = Socios.select().where(Socios.id==invoice.cliente.id).dicts()[0]
- values['receptor'] = {}
- 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]
+ # ~ 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
+ def _get_not_in_xml2(self, invoice, data):
+ fields = (
+ Socios.calle,
+ Socios.no_exterior,
+ Socios.no_interior,
+ Socios.colonia,
+ Socios.municipio,
+ Socios.estado,
+ Socios.pais,
+ )
+ where = (Socios.id==invoice.cliente.id)
+ partner = Socios.select(*fields).where(where).dicts()[0]
+ data['receptor'].update(partner)
+ return
+
@classmethod
def get_pdf(cls, id, rfc, sync=True):
try:
@@ -4654,7 +4765,9 @@ class Facturas(BaseModel):
#Tmp to v2
data = util.get_data_from_xml(obj, values, pdf_from)
+
data.update(utils.CfdiToDict(obj.xml).values)
+ cls._get_not_in_xml2(cls, obj, data)
doc = util.to_pdf(data, emisor.rfc, pdf_from=pdf_from)
@@ -5021,7 +5134,9 @@ class Facturas(BaseModel):
'id': obj.cliente.id,
'nombre': obj.cliente.nombre,
'rfc': obj.cliente.rfc,
+ 'codigo_postal': obj.cliente.codigo_postal,
'notas': obj.notas,
+ 'regimenes': SociosRegimenes.get_by_key(obj.cliente.id)
}
invoice = {
'tipo_comprobante': obj.tipo_comprobante,
@@ -5197,12 +5312,11 @@ class Facturas(BaseModel):
return result
-
def _calculate_totals(self, invoice, products, tipo_comprobante, user):
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')
+ # ~ use_packing = Configuracion.get_bool('chk_use_packing')
subtotal = 0
descuento_cfdi = 0
@@ -5237,9 +5351,9 @@ 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))
+ # ~ 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
@@ -5519,7 +5633,14 @@ class Facturas(BaseModel):
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
decimales_precios = Configuracion.get_bool('chk_config_decimales_precios')
invoice_by_ticket = Configuracion.get_bool('chk_config_invoice_by_ticket')
- is_global = (invoice.cliente.rfc == RFCS['PUBLIC']) and invoice_by_ticket
+ is_global = bool(invoice.periodicidad)
+
+ data_global = {}
+ if is_global:
+ now = utils.now()
+ data_global['Periodicidad'] = invoice.periodicidad
+ data_global['Meses'] = now.strftime('%m')
+ data_global['Año'] = now.strftime('%Y')
frm_vu = FORMAT
if decimales_precios:
@@ -5586,11 +5707,15 @@ class Facturas(BaseModel):
'Nombre': emisor.nombre,
'RegimenFiscal': invoice.regimen_fiscal,
}
+
receptor = {
'Rfc': invoice.cliente.rfc,
'Nombre': invoice.cliente.nombre,
'UsoCFDI': invoice.uso_cfdi,
+ 'DomicilioFiscalReceptor': invoice.cliente.codigo_postal,
+ 'RegimenFiscalReceptor': invoice.receptor_regimen
}
+
if invoice.cliente.tipo_persona == 4:
if invoice.cliente.pais:
receptor['ResidenciaFiscal'] = invoice.cliente.pais
@@ -5612,11 +5737,13 @@ class Facturas(BaseModel):
'NoIdentificacion': key,
'Cantidad': FORMAT.format(row.cantidad),
'ClaveUnidad': row.unidad,
- 'Unidad': SATUnidades.get(SATUnidades.key==row.unidad).name[:20],
'Descripcion': row.descripcion,
'ValorUnitario': frm_vu.format(row.valor_unitario),
'Importe': FORMAT.format(row.importe),
}
+ if not is_global:
+ concepto['Unidad'] = SATUnidades.get(SATUnidades.key==row.unidad).name[:20]
+
if row.descuento:
concepto['Descuento'] = FORMAT.format(row.descuento)
@@ -5701,6 +5828,16 @@ class Facturas(BaseModel):
taxes['retenciones'] = retenciones
concepto['impuestos'] = taxes
+
+ # cfdi4
+ if not is_global:
+ concepto['ObjetoImp'] = row.producto.objeto_impuesto
+ else:
+ if taxes:
+ concepto['ObjetoImp'] = '02'
+ else:
+ concepto['ObjetoImp'] = '01'
+
conceptos.append(concepto)
impuestos = {}
@@ -5749,11 +5886,14 @@ class Facturas(BaseModel):
if tax_decimals:
xml_importe = FORMAT_TAX.format(tax.importe)
+ xml_tax_base = FORMAT_TAX.format(tax.base)
else:
xml_importe = FORMAT.format(tax.importe)
+ xml_tax_base = FORMAT.format(tax.base)
if tax.impuesto.tipo == 'T':
traslado = {
+ "Base": xml_tax_base,
"Impuesto": tax.impuesto.key,
"TipoFactor": tipo_factor,
"TasaOCuota": str(tax.impuesto.tasa),
@@ -5793,6 +5933,7 @@ class Facturas(BaseModel):
'donativo': donativo,
'edu': is_edu,
'complementos': complementos,
+ 'global': data_global,
}
return utils.make_xml(data, certificado)
@@ -6434,7 +6575,7 @@ class PreFacturas(BaseModel):
}
data['comprobante'] = obj
- data['comprobante']['version'] = CURRENT_CFDI
+ data['comprobante']['version'] = PRE_DEFAULT['CFDI']['VERSION']
data['comprobante']['folio'] = str(data['comprobante']['folio'])
data['comprobante']['seriefolio'] = '{}-{}'.format(
data['comprobante']['serie'], data['comprobante']['folio'])
@@ -6893,6 +7034,8 @@ class PreFacturasDetalle(BaseModel):
'rfc': q.cliente.rfc,
'forma_pago': q.forma_pago,
'uso_cfdi': q.uso_cfdi,
+ 'codigo_postal': q.cliente.codigo_postal,
+ 'regimenes': SociosRegimenes.get_by_key(q.cliente.id),
'notas': q.notas,
}
@@ -7140,6 +7283,7 @@ class CfdiPagos(BaseModel):
error = TextField(default='')
cancelada = BooleanField(default=False)
fecha_cancelacion = DateTimeField(null=True)
+ receptor_regimen = TextField(default='')
class Meta:
order_by = ('movimiento',)
@@ -7263,6 +7407,7 @@ class CfdiPagos(BaseModel):
partner = related[0].factura.cliente
partner_name = related[0].factura.cliente.nombre
+ receptor_regimen = related[0].factura.receptor_regimen
emisor = Emisor.select()[0]
# ~ regimen_fiscal = related[0].factura.regimen_fiscal
@@ -7302,6 +7447,7 @@ class CfdiPagos(BaseModel):
fields['folio'] = self._get_folio(self, serie)
fields['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
fields['regimen_fiscal'] = regimen_fiscal
+ fields['receptor_regimen'] = receptor_regimen
with database_proxy.atomic() as txn:
obj = CfdiPagos.create(**fields)
@@ -7319,7 +7465,92 @@ class CfdiPagos(BaseModel):
data = {'ok': True, 'row': row, 'new': True}
return data
+ def _get_taxes_by_pay(self, pay, taxes_pay):
+ invoice = Facturas.get(Facturas.uuid==pay['IdDocumento'])
+ impuestos = {}
+ traslados = []
+ retenciones = []
+
+ where = (FacturasImpuestos.factura==invoice)
+ taxes = FacturasImpuestos.select().where(where)
+
+ tax_proporcion = pay['ImpPagado'] / invoice.total
+ # ~ print('Total', invoice.total)
+ # ~ print('Pagado', pay['ImpPagado'])
+ # ~ print('proporcion', tax_proporcion)
+
+ for tax in taxes:
+ if tax.impuesto.key == '000':
+ # ~ tasa = str(round(tax.impuesto.tasa * 100, 2))
+ # ~ simporte = FORMAT.format(tax.importe)
+ # ~ if tax.impuesto.tipo == 'T':
+ # ~ traslado = {
+ # ~ 'ImpLocTrasladado': tax.impuesto.name,
+ # ~ 'TasadeTraslado': tasa,
+ # ~ 'Importe': simporte,
+ # ~ }
+ # ~ locales_trasladados.append(traslado)
+ # ~ total_locales_trasladados += tax.importe
+ # ~ else:
+ # ~ retencion = {
+ # ~ 'ImpLocRetenido': tax.impuesto.name,
+ # ~ 'TasadeRetencion': tasa,
+ # ~ 'Importe': simporte,
+ # ~ }
+ # ~ locales_retenciones.append(retencion)
+ # ~ total_locales_retenciones += tax.importe
+ continue
+
+ tipo_factor = 'Tasa'
+ if tax.impuesto.factor != 'T':
+ tipo_factor = 'Cuota'
+
+ import_dr = round(tax.importe * tax_proporcion, 2)
+ # ~ xml_importe = FORMAT.format(tax.importe)
+ xml_importe = FORMAT.format(import_dr)
+ base_dr = round(tax.base * tax_proporcion, 2)
+ # ~ xml_tax_base = FORMAT.format(tax.base)
+ xml_tax_base = FORMAT.format(base_dr)
+
+ values = {
+ "BaseDR": xml_tax_base,
+ "ImpuestoDR": tax.impuesto.key,
+ "TipoFactorDR": tipo_factor,
+ "TasaOCuotaDR": str(tax.impuesto.tasa),
+ "ImporteDR": xml_importe,
+ }
+ tax_key = tax.impuesto.key
+ if tax.impuesto.tipo == 'T':
+ traslados.append(values)
+ if tax_key in taxes_pay['traslados']:
+ # ~ taxes_pay['traslados'][tax_key]['ImporteP'] += tax.importe
+ taxes_pay['traslados'][tax_key]['ImporteP'] += import_dr
+ else:
+ values = {
+ # ~ "BaseP": tax.base,
+ "BaseP": base_dr,
+ "ImpuestoP": tax.impuesto.key,
+ "TipoFactorP": tipo_factor,
+ "TasaOCuotaP": str(tax.impuesto.tasa),
+ # ~ "ImporteP": tax.importe,
+ "ImporteP": import_dr,
+ }
+ taxes_pay['traslados'][tax_key] = values
+ else:
+ retenciones.append(values)
+ if tax_key in taxes_pay['retenciones']:
+ taxes_pay['retenciones'][tax_key] += tax.importe
+ else:
+ taxes_pay['retenciones'][tax_key] = tax.importe
+
+ impuestos['traslados'] = traslados
+ impuestos['retenciones'] = retenciones
+
+ return impuestos
+
def _get_related_xml(self, id_mov, currency):
+ TAX_IVA_16 = '002|0.160000'
+
filters = (FacturasPagos.movimiento==id_mov)
related = tuple(FacturasPagos.select(
Facturas.uuid.alias('IdDocumento'),
@@ -7336,13 +7567,21 @@ class CfdiPagos(BaseModel):
.where(filters)
.dicts())
+ taxes_pay = {'retenciones': {}, 'traslados': {}, 'totales': {}}
+
for r in related:
+ r['taxes'] = self._get_taxes_by_pay(self, r, taxes_pay)
# ~ print('\n\nMONEDA', currency, r['MonedaDR'])
r['IdDocumento'] = str(r['IdDocumento'])
r['Folio'] = str(r['Folio'])
r['NumParcialidad'] = str(r['NumParcialidad'])
r['TipoCambioDR'] = FORMAT6.format(r['TipoCambioDR'])
- r['MetodoDePagoDR'] = DEFAULT_CFDIPAY['WAYPAY']
+ # ~ r['MetodoDePagoDR'] = DEFAULT_CFDIPAY['WAYPAY']
+
+ # REVISAR
+ r['EquivalenciaDR'] = '1'
+ r['ObjetoImpDR'] = '02'
+
r['ImpSaldoAnt'] = FORMAT.format(r['ImpSaldoAnt'])
r['ImpPagado'] = FORMAT.format(r['ImpPagado'])
if round(r['ImpSaldoInsoluto'], 2) == 0.0:
@@ -7354,7 +7593,28 @@ class CfdiPagos(BaseModel):
if not r['Serie']:
del r['Serie']
- return related
+ total_tax_iva_16_base = 0
+ total_tax_iva_16_importe = 0
+
+ for key, importe in taxes_pay['retenciones'].items():
+ taxes_pay['retenciones'][key] = FORMAT.format(importe)
+ for k, tax in taxes_pay['traslados'].items():
+ tax_type = taxes_pay['traslados'][k]['ImpuestoP']
+ tax_tasa = taxes_pay['traslados'][k]['TasaOCuotaP']
+ tax_base = taxes_pay['traslados'][k]['BaseP']
+ importe = taxes_pay['traslados'][k]['ImporteP']
+ if f'{tax_type}|{tax_tasa}' == TAX_IVA_16:
+ total_tax_iva_16_base += tax_base
+ total_tax_iva_16_importe += importe
+ taxes_pay['traslados'][k]['BaseP'] = FORMAT.format(tax_base)
+ taxes_pay['traslados'][k]['ImporteP'] = FORMAT.format(importe)
+
+ taxes_pay['totales'] = {
+ 'TotalTrasladosBaseIVA16': FORMAT.format(total_tax_iva_16_base),
+ 'TotalTrasladosImpuestoIVA16': FORMAT.format(total_tax_iva_16_importe),
+ }
+
+ return related, taxes_pay
def _generate_xml(self, invoice):
emisor = Emisor.select()[0]
@@ -7367,9 +7627,9 @@ class CfdiPagos(BaseModel):
cfdi['Folio'] = str(invoice.folio)
cfdi['Fecha'] = invoice.fecha.isoformat()[:19]
cfdi['NoCertificado'] = certificado.serie
- # ~ cfdi['Certificado'] = cert.cer_txt
cfdi['SubTotal'] = '0'
cfdi['Moneda'] = DEFAULT_CFDIPAY['CURRENCY']
+ # ~ cfdi['TipoCambio'] = DEFAULT_CFDIPAY['TC']
cfdi['Total'] = '0'
cfdi['TipoDeComprobante'] = invoice.tipo_comprobante
cfdi['LugarExpedicion'] = invoice.lugar_expedicion
@@ -7390,6 +7650,8 @@ class CfdiPagos(BaseModel):
'Rfc': invoice.socio.rfc,
'Nombre': invoice.socio.nombre,
'UsoCFDI': DEFAULT_CFDIPAY['USED'],
+ 'DomicilioFiscalReceptor': invoice.socio.codigo_postal,
+ 'RegimenFiscalReceptor': invoice.receptor_regimen
}
if invoice.socio.tipo_persona == 4:
if invoice.socio.pais:
@@ -7404,19 +7666,23 @@ class CfdiPagos(BaseModel):
'Descripcion': DEFAULT_CFDIPAY['DESCRIPTION'],
'ValorUnitario': '0',
'Importe': '0',
+ 'ObjetoImp': '01',
},)
impuestos = {}
mov = invoice.movimiento
currency = mov.moneda
- related_docs = self._get_related_xml(self, invoice.movimiento, currency)
+ related_docs, taxes_pay = self._get_related_xml(self, invoice.movimiento, currency)
+ totales = taxes_pay.pop('totales')
pagos = {
'FechaPago': mov.fecha.isoformat()[:19],
'FormaDePagoP': mov.forma_pago.key,
'MonedaP': currency,
+ 'TipoCambioP': '1',
'Monto': FORMAT.format(mov.deposito),
'relacionados': related_docs,
+ 'taxes_pay': taxes_pay,
}
if mov.numero_operacion:
pagos['NumOperacion'] = mov.numero_operacion
@@ -7431,10 +7697,12 @@ class CfdiPagos(BaseModel):
pagos['RfcEmisorCtaBen'] = mov.cuenta.banco.rfc
pagos['CtaBeneficiario'] = mov.cuenta.cuenta
-
if currency != CURRENCY_MN:
pagos['TipoCambioP'] = FORMAT_TAX.format(mov.tipo_cambio)
+ totales['MontoTotalPagos'] = pagos['Monto']
+ pagos['totales'] = totales
+
complementos = {'pagos': pagos}
data = {
'comprobante': cfdi,
@@ -7446,6 +7714,7 @@ class CfdiPagos(BaseModel):
'donativo': {},
'edu': False,
'complementos': complementos,
+ 'global': {},
}
return utils.make_xml(data, certificado)
@@ -7565,6 +7834,8 @@ class CfdiPagos(BaseModel):
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
values = cls._get_not_in_xml(cls, obj, emisor)
data = util.get_data_from_xml(obj, values)
+ data['informacion_global'] = {}
+
obj = SATFormaPago.get(SATFormaPago.key==data['pays']['FormaDePagoP'])
data['pays']['formadepago'] = '{} ({})'.format(obj.name, obj.key)
doc = util.to_pdf(data, emisor.rfc)
@@ -8167,6 +8438,7 @@ class Tickets(BaseModel):
@classmethod
def invoice(cls, values, user):
is_invoice_day = util.get_bool(values['is_invoice_day'])
+ periodicidad = values['periodicidad']
id_client = int(values['client'])
tickets = util.loads(values['tickets'])
invoice_by_ticket = Configuracion.get_bool('chk_config_invoice_by_ticket')
@@ -8183,12 +8455,21 @@ class Tickets(BaseModel):
return data
else:
client = Socios.get(Socios.id==id_client)
+ periodicidad = ''
if client.forma_pago is None:
msg = 'La Forma de Pago del cliente, no esta asignada'
data = {'ok': False, 'msg': msg}
return data
+ try:
+ receptor_regimen = SociosRegimenes.get_by_key(client)[0]['id']
+ except Exception as e:
+ log.error(e)
+ msg = 'Error al obtener el Regimen Fiscal del receptor'
+ data = {'ok': False, 'msg': msg}
+ return data
+
payment_type = cls._get_payment_type(cls, tickets)
emisor = Emisor.select()[0]
@@ -8196,15 +8477,13 @@ class Tickets(BaseModel):
data['cliente'] = client
data['serie'] = cls._get_serie(cls, user, True)
data['folio'] = cls._get_folio_invoice(cls, data['serie'])
- # ~ data['forma_pago'] = client.forma_pago.key
data['forma_pago'] = payment_type
data['tipo_cambio'] = 1.00
data['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
- if client.uso_cfdi is None:
- data['uso_cfdi'] = 'P01'
- else:
- data['uso_cfdi'] = client.uso_cfdi.key
+ data['uso_cfdi'] = client.uso_cfdi.key
data['regimen_fiscal'] = emisor.regimenes[0].key
+ data['receptor_regimen'] = receptor_regimen
+ data['periodicidad'] = periodicidad
with database_proxy.atomic() as txn:
obj = Facturas.create(**data)
@@ -8669,6 +8948,7 @@ class Empleados(BaseModel):
codigo_postal = TextField(default='')
notas = TextField(default='')
correo = TextField(default='')
+ regimen_fiscal = TextField(default='')
class Meta:
order_by = ('nombre_completo',)
@@ -8720,10 +9000,10 @@ class Empleados(BaseModel):
obj = Empleados.create(**data)
en += 1
- msg = 'Empleados encontrados: {}
'.format(len(rows))
- msg += 'Empleados nuevos: {}
'.format(en)
- msg += 'Empleados actualizados: {}
'.format(ea)
- msg += 'Empleados no importados: {}'.format(len(rows) - en - ea)
+ msg = 'Empleados:
Encontrados: {}
'.format(len(rows))
+ msg += 'Nuevos: {}
'.format(en)
+ msg += 'Actualizados: {}
'.format(ea)
+ msg += 'No importados: {}'.format(len(rows) - en - ea)
return {'ok': True, 'msg': msg}
def _get(self):
@@ -8755,13 +9035,13 @@ class Empleados(BaseModel):
try:
q = Empleados.delete().where(Empleados.id==id)
return bool(q.execute())
- except IntegrityError:
+ except Exception as e:
return False
class CfdiNomina(BaseModel):
empleado = ForeignKeyField(Empleados)
- version = TextField(default=CURRENT_CFDI)
+ version = TextField(default=PRE_DEFAULT['CFDI']['VERSION'])
serie = TextField(default='N')
folio = IntegerField(default=0)
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
@@ -8802,7 +9082,7 @@ class CfdiNomina(BaseModel):
acuse = TextField(default='')
tipo_relacion = TextField(default='')
error = TextField(default='')
- version_nomina = TextField(default=CURRENT_CFDI_NOMINA)
+ version_nomina = TextField(default=PRE_DEFAULT['NOMINA']['VERSION'])
registro_patronal = TextField(default='')
rfc_patron_origen = TextField(default='')
tipo_nomina = ForeignKeyField(SATTipoNomina)
@@ -9441,7 +9721,7 @@ class CfdiNomina(BaseModel):
comprobante['Serie'] = cfdi.serie
comprobante['Folio'] = str(cfdi.folio)
comprobante['Fecha'] = cfdi.fecha.isoformat()[:19]
- comprobante['FormaPago'] = cfdi.forma_pago
+ # ~ comprobante['FormaPago'] = cfdi.forma_pago
comprobante['NoCertificado'] = certificado.serie
comprobante['Certificado'] = certificado.cer_txt
comprobante['SubTotal'] = FORMAT.format(cfdi.subtotal)
@@ -9468,6 +9748,8 @@ class CfdiNomina(BaseModel):
receptor = {
'Rfc': cfdi.empleado.rfc,
'Nombre': cfdi.empleado.nombre_completo,
+ 'DomicilioFiscalReceptor': cfdi.empleado.codigo_postal,
+ 'RegimenFiscalReceptor': cfdi.empleado.regimen_fiscal,
'UsoCFDI': cfdi.uso_cfdi,
}
@@ -9481,6 +9763,7 @@ class CfdiNomina(BaseModel):
'Descripcion': row.descripcion,
'ValorUnitario': FORMAT.format(row.valor_unitario),
'Importe': FORMAT.format(row.importe),
+ 'ObjetoImp': '01',
}
if row.descuento:
concepto['Descuento'] = FORMAT.format(row.descuento)
@@ -9841,7 +10124,11 @@ class CfdiNomina(BaseModel):
return b'', name
values = cls._get_not_in_xml(cls, obj, emisor)
+
data = util.get_data_from_xml(obj, values)
+
+ data.update(utils.CfdiToDict(obj.xml).values)
+
doc = util.to_pdf(data, emisor.rfc)
# ~ if sync:
@@ -10405,10 +10692,10 @@ def authenticate(args):
def get_cp(cp):
con = sqlite3.connect(PATH_CP)
cursor = con.cursor()
- sql = """
- SELECT colonia, municipio, estado, municipios.id_municipio
- FROM colonias, municipios, estados
- WHERE colonias.id_municipio=municipios.id
+ sql = """SELECT estado, municipio, clave, colonia, key_estado
+ FROM codigos, colonias, municipios, estados
+ WHERE codigos.id_colonia=colonias.id
+ AND codigos.id_municipio=municipios.id
AND municipios.id_estado=estados.id
AND cp=?
ORDER BY colonia"""
@@ -10420,15 +10707,18 @@ def get_cp(cp):
data = {}
if rows:
data = {
- 'estado': rows[0][2],
+ 'estado': rows[0][0],
'municipio': rows[0][1],
- 'key_municipio': str(rows[0][3]).zfill(3),
+ 'key_municipio': rows[0][2],
+ 'key_estado': rows[0][4],
}
- print(data)
if len(rows) == 1:
- data['colonia'] = rows[0][0]
+ data['colonia'] = rows[0][3]
else:
- data['colonia'] = [r[0] for r in rows]
+ data['colonia'] = [r[3] for r in rows]
+
+ print('CP', cp, data)
+
return data
@@ -10546,6 +10836,7 @@ def _crear_tablas(rfc):
PartnerInvoices,
WareHouseProduct,
SATUnidadesPeso,
+ SociosRegimenes,
]
log.info('Creando tablas...')
database_proxy.create_tables(tablas, True)
@@ -10604,6 +10895,7 @@ def _migrate_tables(rfc=''):
PartnerInvoices,
WareHouseProduct,
SATUnidadesPeso,
+ SociosRegimenes,
]
log.info('Creando tablas nuevas...')
database_proxy.create_tables(tablas, True)
@@ -10647,6 +10939,10 @@ def _migrate_tables(rfc=''):
correo_facturasp = TextField(default='')
migrations.append(
migrator.add_column('socios', 'correo_facturasp', correo_facturasp))
+ if not 'regimen_fiscal' in columns:
+ regimen_fiscal = TextField(default='')
+ migrations.append(
+ migrator.add_column('socios', 'regimen_fiscal', regimen_fiscal))
columns = [c.name for c in database_proxy.get_columns('folios')]
if not 'plantilla' in columns:
@@ -10696,6 +10992,9 @@ def _migrate_tables(rfc=''):
migrations.append(migrator.add_column('cfdipagos', 'socio_id', socio))
migrations.append(migrator.drop_column('cfdipagos', 'cancelado'))
migrations.append(migrator.add_column('cfdipagos', 'cancelada', cancelada))
+ if not 'receptor_regimen' in columns:
+ receptor_regimen = TextField(default='')
+ migrations.append(migrator.add_column('cfdipagos', 'receptor_regimen', receptor_regimen))
if not 'fecha_cancelacion' in columns:
fecha_cancelacion = DateTimeField(null=True)
@@ -10729,6 +11028,10 @@ def _migrate_tables(rfc=''):
is_discontinued = BooleanField(default=False)
migrations.append(migrator.add_column(
table, 'is_discontinued', is_discontinued))
+ if not 'objeto_impuesto' in columns:
+ objeto_impuesto = TextField(default='02')
+ migrations.append(migrator.add_column(table, 'objeto_impuesto', objeto_impuesto))
+
if 'almacen_id' in columns:
migrations.append(migrator.drop_column(table, 'almacen_id'))
@@ -10745,6 +11048,15 @@ def _migrate_tables(rfc=''):
if not 'divisas' in columns:
divisas = TextField(default='')
migrations.append(migrator.add_column(table, 'divisas', divisas))
+ if not 'exportacion' in columns:
+ new_field = TextField(default='01')
+ migrations.append(migrator.add_column(table, 'exportacion', new_field))
+ if not 'receptor_regimen' in columns:
+ receptor_regimen = TextField(default='')
+ migrations.append(migrator.add_column(table, 'receptor_regimen', receptor_regimen))
+ if not 'periodicidad' in columns:
+ periodicidad = TextField(default='')
+ migrations.append(migrator.add_column(table, 'periodicidad', periodicidad))
table = 'almacenes'
columns = [c.name for c in database_proxy.get_columns(table)]
@@ -10772,6 +11084,13 @@ def _migrate_tables(rfc=''):
warehouse = ForeignKeyField(Almacenes, null=True, to_field=Almacenes.id)
migrations.append(migrator.add_column(table, field, warehouse))
+ table = 'empleados'
+ field = 'regimen_fiscal'
+ columns = [c.name for c in database_proxy.get_columns(table)]
+ if not field in columns:
+ regimen_fiscal = TextField(default='')
+ migrations.append(migrator.add_column(table, field, regimen_fiscal))
+
if migrations:
with database_proxy.atomic() as txn:
migrate(*migrations)
@@ -10779,7 +11098,24 @@ def _migrate_tables(rfc=''):
Configuracion.add({'version': VERSION})
log.info('Tablas migradas correctamente...')
- _importar_valores('', rfc)
+ # ~ _importar_valores('', rfc)
+
+ log.info('Actualizando valores...')
+ try:
+ q = SATRegimenes.update(**{'activo': True}).where(SATRegimenes.key=='616')
+ q.execute()
+ except Exception as e:
+ log.error(e)
+ else:
+ log.info('Valores actualizados...')
+
+ try:
+ q = SATEstados.update(**{'key': 'CMX'}).where(SATEstados.key=='DIF')
+ q.execute()
+ except Exception as e:
+ log.error(e)
+ else:
+ log.info('Valores actualizados...')
return
diff --git a/source/app/settings.py b/source/app/settings.py
index 00624bc..986f01f 100644
--- a/source/app/settings.py
+++ b/source/app/settings.py
@@ -42,7 +42,8 @@ except ImportError:
DEBUG = DEBUG
-VERSION = '1.47.0'
+VERSION = '2.0.0'
+
EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
@@ -127,25 +128,45 @@ if 'win' in sys.platform:
PATH_XMLSEC = os.path.join(PATH_BIN, 'xmlsec.exe')
+PRE_DEFAULT = {
+ 'CFDI': {'VERSION': '4.0', 'PRE': '{http://www.sat.gob.mx/cfd/4}'},
+ 'NOMINA': {'VERSION': '1.2', 'PRE': '{http://www.sat.gob.mx/nomina12}'},
+ 'PAGOS': {'VERSION': '2.0', 'PRE': '{http://www.sat.gob.mx/Pagos20}'},
+ 'TIBRE': {'VERSION': '1.1', 'PRE': '{http://www.sat.gob.mx/TimbreFiscalDigital}'},
+}
+
+pre2 ='{http://www.sat.gob.mx/cfd/2}'
+pre3 ='{http://www.sat.gob.mx/cfd/3}'
+PRE_HISTORY = {
+ 'CFDI': {'2.0': pre2, '2.2': pre2,
+ '3.0': pre3, '3.2': pre3, '3.3': pre3},
+ 'NOMINA': {'1.1': '{http://www.sat.gob.mx/nomina}'},
+ 'PAGOS': {'1.0': '{http://www.sat.gob.mx/Pagos}'},
+}
+
PRE = {
'2.0': '{http://www.sat.gob.mx/cfd/2}',
'2.2': '{http://www.sat.gob.mx/cfd/2}',
'3.0': '{http://www.sat.gob.mx/cfd/3}',
'3.2': '{http://www.sat.gob.mx/cfd/3}',
'3.3': '{http://www.sat.gob.mx/cfd/3}',
+ '4.0': '{http://www.sat.gob.mx/cfd/4}',
'TIMBRE': '{http://www.sat.gob.mx/TimbreFiscalDigital}',
'DONATARIA': '{http://www.sat.gob.mx/donat}',
'INE': '{http://www.sat.gob.mx/ine}',
'LOCALES': '{http://www.sat.gob.mx/implocal}',
'NOMINA': {
'1.1': '{http://www.sat.gob.mx/nomina}',
- '1.2': '{http://www.sat.gob.mx/nomina12}',
},
- 'pagos': '{http://www.sat.gob.mx/Pagos}',
+ 'PAGOS': {
+ '1.0': '{http://www.sat.gob.mx/Pagos}',
+ }
}
-CURRENT_CFDI = '3.3'
-CURRENT_CFDI_NOMINA = '1.2'
+# To delete
+# ~ CURRENT_CFDI = '4.0'
+# ~ CURRENT_CFDI_NOMINA = '1.2'
+
DECIMALES = 2
DECIMALES_TAX = 4
DECIMALES_PRECIOS = 4
@@ -168,7 +189,8 @@ DEFAULT_CFDIPAY = {
'TYPE': 'P',
'WAYPAY': 'PPD',
'CURRENCY': 'XXX',
- 'USED': 'P01',
+ 'TC': '1',
+ 'USED': 'CP01',
'KEYSAT': '84111506',
'UNITKEY': 'ACT',
'DESCRIPTION': 'Pago',
@@ -181,7 +203,7 @@ PUBLIC = 'Público en general'
DEFAULT_SAT_NOMINA = {
'SERIE': 'N',
'FORMA_PAGO': '99',
- 'USO_CFDI': 'P01',
+ 'USO_CFDI': 'CN01',
'CLAVE': '84111505',
'UNIDAD': 'ACT',
'DESCRIPCION': 'Pago de nómina',
@@ -193,6 +215,7 @@ CURRENCY_MN = 'MXN'
# ~ v2
CANCEL_VERSION = ('3.3', '4.0')
+CFDI_VERSIONS = CANCEL_VERSION
IS_MV = MV
DB_COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
diff --git a/source/db/cp.db b/source/db/cp.db
index 2f632fa..924d225 100644
Binary files a/source/db/cp.db and b/source/db/cp.db differ
diff --git a/source/db/valores_iniciales.json b/source/db/valores_iniciales.json
index 7af3182..9ca6de0 100644
--- a/source/db/valores_iniciales.json
+++ b/source/db/valores_iniciales.json
@@ -104,7 +104,7 @@
{"key": "611", "name": "Ingresos por Dividendos (socios y accionistas)", "fisica": true, "activo": false},
{"key": "612", "name": "Personas Físicas con Actividades Empresariales y Profesionales", "fisica": true, "activo": true, "default": true},
{"key": "614", "name": "Ingresos por intereses", "fisica": true, "activo": true},
- {"key": "616", "name": "Sin obligaciones fiscales", "fisica": true, "activo": false},
+ {"key": "616", "name": "Sin obligaciones fiscales", "fisica": true, "activo": true},
{"key": "620", "name": "Sociedades Cooperativas de Producción que optan por diferir sus ingresos", "moral": true, "activo": false},
{"key": "621", "name": "Incorporación Fiscal", "fisica": true, "activo": true},
{"key": "622", "name": "Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras", "fisica": true, "moral": true, "activo": false},
@@ -682,7 +682,10 @@
{"key": "D08", "name": "Gastos de transportación escolar obligatoria.", "activo": false},
{"key": "D09", "name": "Depósitos en cuentas para el ahorro, primas que tengan como base planes de pensiones.", "activo": false},
{"key": "D10", "name": "Pagos por servicios educativos (colegiaturas)", "activo": true},
- {"key": "P01", "name": "Por definir", "moral": true, "activo": true}
+ {"key": "P01", "name": "Por definir", "moral": true, "activo": true},
+ {"key": "S01", "name": "Sin efectos fiscales.", "moral": true, "activo": true},
+ {"key": "CP01", "name": "Pagos", "moral": true, "activo": true},
+ {"key": "CN01", "name": "Nómina", "moral": true, "activo": true}
]
},
{
@@ -728,7 +731,7 @@
"pais": "MEX"
},
{
- "key": "DIF",
+ "key": "CMX",
"name": "Ciudad de M\u00e9xico",
"pais": "MEX"
},
diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js
index 0016d98..38db792 100644
--- a/source/static/js/controller/admin.js
+++ b/source/static/js/controller/admin.js
@@ -76,15 +76,13 @@ var controllers = {
//~ Opciones
tb_options = $$('tab_options').getTabbar()
tb_options.attachEvent('onChange', tab_options_change)
- $$('txt_plantilla_factura_32').attachEvent('onItemClick', txt_plantilla_factura_32_click)
- $$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click)
$$('txt_plantilla_factura_html').attachEvent('onItemClick', txt_plantilla_factura_html_click)
$$('txt_plantilla_factura_json').attachEvent('onItemClick', txt_plantilla_factura_json_click)
$$('txt_plantilla_factura_css').attachEvent('onItemClick', txt_plantilla_factura_css_click)
$$('txt_plantilla_ticket').attachEvent('onItemClick', txt_plantilla_ticket_click)
- $$('txt_plantilla_donataria').attachEvent('onItemClick', txt_plantilla_donataria_click)
- $$('txt_plantilla_nomina1233').attachEvent('onItemClick', txt_plantilla_nomina1233_click)
- $$('txt_plantilla_pagos10').attachEvent('onItemClick', txt_plantilla_pagos10_click)
+ //~ $$('txt_plantilla_donataria').attachEvent('onItemClick', txt_plantilla_donataria_click)
+ //~ $$('txt_plantilla_nomina1233').attachEvent('onItemClick', txt_plantilla_nomina1233_click)
+ //~ $$('txt_plantilla_pagos10').attachEvent('onItemClick', txt_plantilla_pagos10_click)
$$('make_pdf_from').attachEvent('onChange', opt_make_pdf_from_on_change)
$$('cmd_template_upload').attachEvent('onItemClick', cmd_template_upload_click)
@@ -503,7 +501,9 @@ function set_config_templates(){
success: function(text, data, xhr) {
var values = data.json()
Object.keys(values).forEach(function(key){
- show(key, values[key])
+ if(key!='txt_plantilla_donataria'){
+ show(key, values[key])
+ }
})
}
})
@@ -1181,42 +1181,42 @@ function txt_plantilla_factura_json_click(e){
}
-function txt_plantilla_donataria_click(e){
+//~ function txt_plantilla_donataria_click(e){
- var body_elements = [
- {cols: [{width: 100}, {view: 'uploader', id: 'up_template', autosend: true, link: 'lst_files',
- value: 'Seleccionar archivo', upload: '/files/txt_plantilla_donataria',
- width: 200}, {width: 100}]},
- {view: 'list', id: 'lst_files', type: 'uploader', autoheight:true,
- borderless: true},
- {},
- {cols: [{}, {view: 'button', label: 'Cerrar', autowidth: true,
- click:("$$('win_template').close();")}, {}]}
- ]
+ //~ var body_elements = [
+ //~ {cols: [{width: 100}, {view: 'uploader', id: 'up_template', autosend: true, link: 'lst_files',
+ //~ value: 'Seleccionar archivo', upload: '/files/txt_plantilla_donataria',
+ //~ width: 200}, {width: 100}]},
+ //~ {view: 'list', id: 'lst_files', type: 'uploader', autoheight:true,
+ //~ borderless: true},
+ //~ {},
+ //~ {cols: [{}, {view: 'button', label: 'Cerrar', autowidth: true,
+ //~ click:("$$('win_template').close();")}, {}]}
+ //~ ]
- var w = webix.ui({
- view: 'window',
- id: 'win_template',
- modal: true,
- position: 'center',
- head: 'Subir Plantilla Donataria',
- body: {
- view: 'form',
- elements: body_elements,
- }
- })
+ //~ var w = webix.ui({
+ //~ view: 'window',
+ //~ id: 'win_template',
+ //~ modal: true,
+ //~ position: 'center',
+ //~ head: 'Subir Plantilla Donataria',
+ //~ body: {
+ //~ view: 'form',
+ //~ elements: body_elements,
+ //~ }
+ //~ })
- w.show()
+ //~ w.show()
- $$('up_template').attachEvent('onUploadComplete', function(response){
- if(response.ok){
- $$('txt_plantilla_donataria').setValue(response.name)
- msg_ok('Plantilla cargada correctamente')
- }else{
- msg_error(response.name)
- }
- })
-}
+ //~ $$('up_template').attachEvent('onUploadComplete', function(response){
+ //~ if(response.ok){
+ //~ $$('txt_plantilla_donataria').setValue(response.name)
+ //~ msg_ok('Plantilla cargada correctamente')
+ //~ }else{
+ //~ msg_error(response.name)
+ //~ }
+ //~ })
+//~ }
function txt_plantilla_nomina1233_click(e){
@@ -1257,42 +1257,42 @@ function txt_plantilla_nomina1233_click(e){
}
-function txt_plantilla_pagos10_click(e){
+//~ function txt_plantilla_pagos10_click(e){
- var body_elements = [
- {cols: [{width: 100}, {view: 'uploader', id: 'up_template',
- autosend: true, link: 'lst_files', value: 'Seleccionar archivo',
- upload: '/files/txt_plantilla_pagos10', width: 200}, {width: 100}]},
- {view: 'list', id: 'lst_files', type: 'uploader', autoheight:true,
- borderless: true},
- {},
- {cols: [{}, {view: 'button', label: 'Cerrar', autowidth: true,
- click:("$$('win_template').close();")}, {}]}
- ]
+ //~ var body_elements = [
+ //~ {cols: [{width: 100}, {view: 'uploader', id: 'up_template',
+ //~ autosend: true, link: 'lst_files', value: 'Seleccionar archivo',
+ //~ upload: '/files/txt_plantilla_pagos10', width: 200}, {width: 100}]},
+ //~ {view: 'list', id: 'lst_files', type: 'uploader', autoheight:true,
+ //~ borderless: true},
+ //~ {},
+ //~ {cols: [{}, {view: 'button', label: 'Cerrar', autowidth: true,
+ //~ click:("$$('win_template').close();")}, {}]}
+ //~ ]
- var w = webix.ui({
- view: 'window',
- id: 'win_template',
- modal: true,
- position: 'center',
- head: 'Subir Plantilla Factura de Pago',
- body: {
- view: 'form',
- elements: body_elements,
- }
- })
+ //~ var w = webix.ui({
+ //~ view: 'window',
+ //~ id: 'win_template',
+ //~ modal: true,
+ //~ position: 'center',
+ //~ head: 'Subir Plantilla Factura de Pago',
+ //~ body: {
+ //~ view: 'form',
+ //~ elements: body_elements,
+ //~ }
+ //~ })
- w.show()
+ //~ w.show()
- $$('up_template').attachEvent('onUploadComplete', function(response){
- if(response.ok){
- $$('txt_plantilla_pagos10').setValue(response.name)
- msg_ok('Plantilla cargada correctamente')
- }else{
- msg_error(response.name)
- }
- })
-}
+ //~ $$('up_template').attachEvent('onUploadComplete', function(response){
+ //~ if(response.ok){
+ //~ $$('txt_plantilla_pagos10').setValue(response.name)
+ //~ msg_ok('Plantilla cargada correctamente')
+ //~ }else{
+ //~ msg_error(response.name)
+ //~ }
+ //~ })
+//~ }
function tab_options_change(nv, ov){
diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js
index b4bbe33..ae95103 100644
--- a/source/static/js/controller/bancos.js
+++ b/source/static/js/controller/bancos.js
@@ -993,6 +993,8 @@ function send_stamp_cfdi_pay(id_mov){
var g = $$('grid_cfdi_pay')
var data = {'opt': 'stamp', 'id_mov': id_mov}
+ var close = $$('chk_pay_close_when_stamp').getValue()
+
//~ ToDo Actualizar cantidad de facturas de pago en el movimiento
webix.ajax().sync().post('cfdipay', data, {
@@ -1010,6 +1012,9 @@ function send_stamp_cfdi_pay(id_mov){
}
}
})
+
+ $$('multi_bancos').setValue('banco_home')
+
}
function save_cfdi_pay(form){
diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js
index e060d42..24eab91 100644
--- a/source/static/js/controller/invoices.js
+++ b/source/static/js/controller/invoices.js
@@ -291,6 +291,11 @@ function cmd_new_invoice_click(){
grid_totals.add({id: 1, concepto: 'SubTotal', importe: 0})
$$('cmd_cfdi_relacionados').disable()
$$('multi_invoices').setValue('invoices_new')
+
+ var lst = $$('lst_invoice_client_regimen')
+ lst.setValue('')
+ lst.getList().clearAll()
+
form.focus('search_client_name')
}
@@ -363,19 +368,25 @@ function validate_invoice(values){
msg_error(msg)
return false
}
-
}
var tipo_comprobante = $$('lst_tipo_comprobante').getValue()
if(tipo_comprobante != 'T'){
if(values.id_partner == 0){
- webix.UIManager.setFocus('search_client_name')
+ focus('search_client_name')
msg = 'Selecciona un cliente'
msg_error(msg)
return false
}
}
+ var regimen_fiscal = $$('lst_invoice_client_regimen').getValue()
+ if(!regimen_fiscal){
+ msg = 'El Regimen Fiscal del Cliente es obligatorio.'
+ msg_error(msg)
+ return false
+ }
+
if(!grid.count()){
webix.UIManager.setFocus('search_product_id')
msg = 'Agrega al menos un producto o servicio'
@@ -685,6 +696,7 @@ function guardar_y_timbrar(values){
data['metodo_pago'] = $$('lst_metodo_pago').getValue()
data['uso_cfdi'] = $$('lst_uso_cfdi').getValue()
data['regimen_fiscal'] = $$('lst_regimen_fiscal').getValue()
+ data['receptor_regimen'] = $$('lst_invoice_client_regimen').getValue()
data['relacionados'] = ids
data['tipo_relacion'] = tipo_relacion
data['anticipo'] = anticipo
@@ -904,6 +916,12 @@ function search_client_by_id(id){
function set_client(row){
+ if(!row.codigo_postal){
+ msg = 'El cliente no tiene capturado su Código Postal, es obligatorio.'
+ msg_error(msg)
+ return
+ }
+
var form = $$('form_invoice')
var html = ''
form.setValues({
@@ -913,6 +931,20 @@ function set_client(row){
html += row.nombre + ' (' + row.rfc + ')'
$$('lbl_client').setValue(html)
$$('cmd_cfdi_relacionados').enable()
+
+ var lst = $$('lst_invoice_client_regimen')
+ lst.getList().clearAll()
+ if(row.regimenes==undefined){
+ var options = {opt: 'by_id', id: row.id}
+ webix.ajax().sync().get('/sociosregimenes', options, function(text, data){
+ var values = data.json()
+ lst.getList().parse(values)
+ })
+ }else{
+ lst.getList().parse(row.regimenes)
+ }
+ lst.setValue(lst.getPopup().getList().getFirstId())
+
form.focus('search_product_id')
}
@@ -1204,11 +1236,9 @@ function grid_carta_ubicaciones_before_edit_stop(state, editor){
msg = 'No se encontró el C.P., asegurate de que sea correcto'
msg_error(msg)
} else {
- row['Estado'] = opt_carta_estados.find(x => x.value === values.estado).id
+ //~ row['Estado'] = opt_carta_estados.find(x => x.value === values.estado).id
+ row['Estado'] = values.key_estado
row['Municipio'] = values.key_municipio
- //~ $$('colonia').define('suggest', [])
- //~ $$('colonia').define('suggest', values.colonia)
- //~ $$('colonia').refresh()
g.refresh()
msg_ok('Municipio:\n' + values.municipio)
}
diff --git a/source/static/js/controller/nomina.js b/source/static/js/controller/nomina.js
index 9a7292c..fee1238 100644
--- a/source/static/js/controller/nomina.js
+++ b/source/static/js/controller/nomina.js
@@ -255,10 +255,10 @@ function up_employees_upload_complete(response){
function delete_empleado(id){
webix.ajax().del('/employees', {id: id}, function(text, xml, xhr){
var msg = 'Empleado eliminado correctamente'
- if (xhr.status == 200){
+ if(xhr.status == 200){
$$('grid_employees').remove(id);
msg_ok(msg)
- } else {
+ }else{
msg = 'El Empleado tiene recibos timbrados'
msg_error(msg)
}
diff --git a/source/static/js/controller/partners.js b/source/static/js/controller/partners.js
index e6dd2ab..1f2b544 100644
--- a/source/static/js/controller/partners.js
+++ b/source/static/js/controller/partners.js
@@ -95,6 +95,7 @@ function cmd_new_partner_click(id, e, node){
$$('partner_balance').define('readonly', !cfg_partners['chk_config_change_balance_partner'])
get_partner_banks()
get_partner_accounts_bank(0)
+ get_sat_regimenes()
}
@@ -123,6 +124,7 @@ function cmd_edit_partner_click(){
},
success: function(text, data, xhr){
var values = data.json()
+
$$('form_partner').clearValidation()
$$('form_partner').setValues(values)
$$('forma_pago').getList().load('/values/formapago')
@@ -132,8 +134,10 @@ function cmd_edit_partner_click(){
if(values.tipo_persona == 1){
query = table_usocfdi.chain().find({fisica: true}).data()
+ get_sat_regimenes()
}else if(values.tipo_persona == 2){
query = table_usocfdi.chain().find({moral: true}).data()
+ get_sat_regimenes(true)
}else{
query = [{id: 'P01', value: 'Por definir'}]
}
@@ -145,12 +149,15 @@ function cmd_edit_partner_click(){
$$('cuenta_proveedor').enable()
}
get_partner_accounts_bank(row['id'])
+ pause(250)
+ $$('lst_receptor_regimenes_fiscales').select(values.regimenes)
}
})
$$('multi_partners').setValue('partners_new')
$$('tab_partner').setValue('Datos Fiscales')
get_partner_banks()
+
}
@@ -239,7 +246,17 @@ function cmd_save_partner_click(id, e, node){
}
}
+ var ids_regimenes = $$('lst_receptor_regimenes_fiscales').getSelectedId()
+ if(values.tipo_persona < 3){
+ if(!ids_regimenes){
+ msg = 'Selecciona al menos un Regimen Fiscal'
+ msg_error(msg)
+ return
+ }
+ }
+
values['accounts'] = $$('grid_partner_account_bank').data.getRange()
+ values['regimenes'] = ids_regimenes
webix.ajax().post('/partners', values, {
error:function(text, data, XmlHttpRequest){
@@ -343,18 +360,28 @@ function opt_tipo_change(new_value, old_value){
$$('id_fiscal').define('value', '')
show('id_fiscal', new_value == 4)
+ $$('lst_receptor_regimenes_fiscales').clearAll()
+ var regimen_616 = {id: 11, value: 'Sin obligaciones fiscales'}
+
if (new_value == 1 || new_value == 2){
$$("rfc").define("value", "")
$$("rfc").define("readonly", false)
+ moral = false
+ if(new_value == 2){
+ moral = true
+ }
+ get_sat_regimenes(moral)
} else if (new_value == 3) {
$$("rfc").define("value", RFC_PUBLICO)
$$("nombre").define("value", PUBLICO)
$$("rfc").define("readonly", true)
+ $$('lst_receptor_regimenes_fiscales').parse(regimen_616)
} else if (new_value == 4) {
$$("rfc").define("value", RFC_EXTRANJERO)
$$("rfc").define("readonly", true)
$$("pais").define("readonly", false)
$$("pais").define("value", "")
+ $$('lst_receptor_regimenes_fiscales').parse(regimen_616)
}
$$("nombre").refresh();
@@ -372,10 +399,12 @@ function opt_tipo_change(new_value, old_value){
}else if (new_value == 2){
query = table_usocfdi.chain().find({moral: true}).data()
}else{
- query = [{id: 'P01', value: 'Por definir'}]
+ query = [{id: 'S01', value: 'Sin efectos fiscales. '}]
}
$$('lst_uso_cfdi_socio').getList().parse(query)
$$('lst_uso_cfdi_socio').refresh()
+
+
}
@@ -619,3 +648,21 @@ function partner_delete_account_bank(row){
}
})
}
+
+
+function get_sat_regimenes(morales=false){
+ var data = {opt: 'actives', morales: morales}
+ webix.ajax().sync().get('/satregimenes', data, {
+ error: function(text, data, xhr) {
+ msg = 'Error al consultar'
+ msg_error(msg)
+ },
+ success: function(text, data, xhr) {
+ var values = data.json()
+ $$('lst_receptor_regimenes_fiscales').clearAll()
+ $$('lst_receptor_regimenes_fiscales').parse(values)
+ }
+ })
+
+}
+
diff --git a/source/static/js/controller/products.js b/source/static/js/controller/products.js
index c7b31c5..39e8307 100644
--- a/source/static/js/controller/products.js
+++ b/source/static/js/controller/products.js
@@ -19,7 +19,7 @@ function products_default_config(){
if(cfg_products['inventario']){
$$('grid_products').showColumn('existencia')
}
- show('cant_by_packing', values.chk_use_packing)
+ //~ show('cant_by_packing', values.chk_use_packing)
show('cmd_show_exists', values.chk_multi_stock)
}
})
@@ -123,6 +123,7 @@ function cmd_edit_product_click(){
get_taxes()
$$('unidad').getList().load('/values/unidades')
configurar_producto()
+
var grid = $$('grid_products')
var row = grid.getSelectedItem()
if(row == undefined){
@@ -131,9 +132,10 @@ function cmd_edit_product_click(){
}
$$('categoria').getList().load('/values/categorias')
- webix.ajax().get('/products', {id:row['id']}, {
+
+ webix.ajax().get('/products', {id: row['id']}, {
error: function(text, data, xhr) {
- msg_error()
+ msg_error(text)
},
success: function(text, data, xhr){
var values = data.json()
@@ -237,10 +239,10 @@ 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(!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')
diff --git a/source/static/js/controller/tickets.js b/source/static/js/controller/tickets.js
index bb0b4ca..31f937d 100644
--- a/source/static/js/controller/tickets.js
+++ b/source/static/js/controller/tickets.js
@@ -611,12 +611,13 @@ function cmd_cancelar_ticket_click(){
function chk_is_invoice_day_change(new_value, old_value){
var value = Boolean(new_value)
+
show('fs_ticket_search_client', !value)
+ enable('lst_periodicidad', value)
}
function send_timbrar_invoice(id){
- //~ webix.ajax().get('/values/timbrar', {id: id, update: false}, function(text, data){
webix.ajax().post('invoices', {opt: 'timbrar', id: id, update: false}, function(text, data){
var values = data.json()
if(values.ok){
@@ -683,6 +684,7 @@ function cmd_new_invoice_from_ticket_click(){
data['client'] = values.id_partner
data['tickets'] = tickets
data['is_invoice_day'] = chk.getValue()
+ data['periodicidad'] = $$('lst_periodicidad').getValue()
data['opt'] = 'invoice'
msg = 'Todos los datos son correctos.
¿Estás seguro de generar esta factura?'
diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js
index e9f1e35..acd55a4 100644
--- a/source/static/js/ui/admin.js
+++ b/source/static/js/ui/admin.js
@@ -596,8 +596,18 @@ var type_make_pdf = [
]
+//~ Templates
var opt_templates_cfdi = [
- {id: '_3.3_cp_2.0.ods', value: 'CFDI v3.3 - Carta Porte 2.0'},
+ {id: '_4.0.ods', value: 'CFDI v4.0'},
+ {id: '_4.0_cn_1.2.ods', value: 'CFDI v4.0 - Nómina v1.2'},
+ {id: '_4.0_cp_2.0.ods', value: 'CFDI v4.0 - Pagos v2.0'},
+ {id: '_4.0_ccp_2.0.ods', value: 'CFDI v4.0 - Carta Porte v2.0'},
+ {id: '_4.0_cd_1.1.ods', value: 'CFDI v4.0 - Donativos v1.1'},
+ {id: '_3.3.ods', value: 'CFDI v3.3'},
+ {id: '_3.3_cn_1.2.ods', value: 'CFDI v3.3 - Nómina v1.2'},
+ {id: '_3.3_ccp_2.0.ods', value: 'CFDI v3.3 - Carta Porte v2.0'},
+ {id: '_3.3_cp_1.0.ods', value: 'CFDI v3.3 - Pagos v1.0'},
+ {id: '_3.2.ods', value: 'CFDI v3.2'},
]
@@ -610,14 +620,6 @@ var options_templates = [
{},
{maxWidth: 20} ]},
{maxHeight: 50},
- {cols: [{maxWidth: 20},
- {view: 'search', id: 'txt_plantilla_factura_32', name: 'plantilla_factura_32',
- label: 'Plantilla Factura v3.2 (ODS): ', labelPosition: 'top',
- icon: 'file'}, {maxWidth: 25},
- {view: 'search', id: 'txt_plantilla_factura_33', labelPosition: 'top',
- label: 'Plantilla Factura v3.3 (ODS): ', icon: 'file'},
- {maxWidth: 20} ]},
- {maxHeight: 20},
{cols: [{maxWidth: 20},
{view: 'search', id: 'txt_plantilla_factura_html', name: 'plantilla_factura_html',
label: 'Plantilla Factura v3.3 (HTML): ', labelPosition: 'top',
@@ -632,25 +634,19 @@ var options_templates = [
label: 'Plantilla Factura v3.3 (JSON): ', labelPosition: 'top',
icon: 'file'}, {maxWidth: 25},
{}, {maxWidth: 20} ]},
-
- {maxHeight: 20},
- {cols: [{maxWidth: 20},
- {view: 'search', id: 'txt_plantilla_nomina1233', name: 'plantilla_nomina1233',
- label: 'Plantilla Nomina v1.2 - Cfdi 3.3 (ODS): ', labelPosition: 'top',
- icon: 'file'}, {maxWidth: 40}, {}]},
- {maxHeight: 20},
- {cols: [{maxWidth: 20},
- {view: 'search', id: 'txt_plantilla_pagos10', name: 'plantilla_pagos10',
- label: 'Plantilla Factura de Pagos v1.0 - Cfdi 3.3 (ODS): ',
- labelPosition: 'top', icon: 'file'}, {maxWidth: 40}, {}]},
{maxHeight: 20},
+ //~ {cols: [{maxWidth: 20},
+ //~ {view: 'search', id: 'txt_plantilla_pagos10', name: 'plantilla_pagos10',
+ //~ label: 'Plantilla Factura de Pagos v1.0 - Cfdi 3.3 (ODS): ',
+ //~ labelPosition: 'top', icon: 'file'}, {maxWidth: 40}, {}]},
+ //~ {maxHeight: 20},
{cols: [{maxWidth: 20},
{view: 'search', id: 'txt_plantilla_ticket', name: 'plantilla_ticket',
label: 'Plantilla para Tickets (ODS): ', labelPosition: 'top',
icon: 'file'},
- {view: 'search', id: 'txt_plantilla_donataria', name: 'plantilla_donataria',
- label: 'Plantilla Donataria (solo ONGs): ', labelPosition: 'top',
- icon: 'file'},
+ //~ {view: 'search', id: 'txt_plantilla_donataria', name: 'plantilla_donataria',
+ //~ label: 'Plantilla Donataria (solo ONGs): ', labelPosition: 'top',
+ //~ icon: 'file'},
{}]},
{maxHeight: 20},
{cols: [{maxWidth: 20},
@@ -832,7 +828,7 @@ var options_admin_products = [
var options_admin_complements = [
- {maxHeight: 20},
+ {maxHeight: 10},
{template: 'Complemento de Nómina', type: 'section'},
{cols: [{maxWidth: 15},
{view: 'checkbox', id: 'chk_usar_nomina', labelWidth: 0,
@@ -842,7 +838,7 @@ var options_admin_complements = [
{view: 'text', id: 'txt_config_nomina_folio', name: 'config_nomina_folio',
label: 'Folio', labelWidth: 50, labelAlign: 'right'},
{maxWidth: 15}]},
- {maxHeight: 20},
+ {maxHeight: 10},
{template: 'Complemento de Pagos', type: 'section'},
{cols: [{maxWidth: 15},
{view: 'checkbox', id: 'chk_config_pagos', labelWidth: 0,
@@ -854,25 +850,25 @@ 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},
+ {maxHeight: 10},
{template: 'Complemento de Divisas', type: 'section'},
{cols: [{maxWidth: 15},
{view: 'checkbox', id: 'chk_config_divisas', labelWidth: 0,
labelRight: 'Usar complemento de divisas'},
{maxWidth: 15}]},
- {maxHeight: 20},
+ {maxHeight: 10},
{template: 'Complemento INE', type: 'section'},
{cols: [{maxWidth: 15},
{view: 'checkbox', id: 'chk_config_ine', labelWidth: 0,
labelRight: 'Usar el complemento INE'},
{maxWidth: 15}]},
- {maxHeight: 20},
+ {maxHeight: 10},
{template: 'Complemento para escuelas EDU', type: 'section'},
{cols: [{maxWidth: 15},
{view: 'checkbox', id: 'chk_config_edu', labelWidth: 0,
labelRight: 'Usar el complemento EDU'},
{maxWidth: 15}]},
- {maxHeight: 20},
+ {maxHeight: 10},
{template: 'Complemento Leyendas Fiscales', type: 'section'},
{cols: [{maxWidth: 15},
{view: 'checkbox', id: 'chk_config_leyendas_fiscales', labelWidth: 0,
@@ -881,7 +877,7 @@ var options_admin_complements = [
type: 'form', align: 'center', autowidth: true, disabled: true},
{}, {maxWidth: 15}
]},
- {maxHeight: 20},
+ {maxHeight: 10},
{template: 'Complemento para Carta Porte', type: 'section'},
{cols: [{maxWidth: 15},
{view: 'checkbox', id: 'chk_config_carta_porte', labelWidth: 0,
@@ -1174,6 +1170,7 @@ var admin_taxes = [
'CEDULAR',
'CMIC',
'SUPERVISION',
+ 'MANO DE OBRA',
]
var admin_sat_impuestos = {cols: [{maxWidth: 15},
diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js
index 459d9a8..4b2fa03 100644
--- a/source/static/js/ui/bancos.js
+++ b/source/static/js/ui/bancos.js
@@ -347,6 +347,8 @@ var toolbar_bank_pay = [
type: 'iconButton', autowidth: true, icon: 'minus'},
{view: 'button', id: 'cmd_pay_delete', label: 'Eliminar',
type: 'iconButton', autowidth: true, icon: 'ban'},
+ {view: 'checkbox', id: 'chk_pay_close_when_stamp',
+ label: 'Cerrar al timbrar', tooltip: 'Cerrar al timbrar'},
{},
{view: 'icon', click: '$$("multi_bancos").setValue("banco_home")',
icon: 'times-circle'}
diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js
index 4e488f2..5f99dec 100644
--- a/source/static/js/ui/invoices.js
+++ b/source/static/js/ui/invoices.js
@@ -418,6 +418,7 @@ var suggest_partners = {
{id: 'rfc', adjust: 'data'},
{id: 'forma_pago', hidden: true},
{id: 'uso_cfdi', hidden: true},
+ {id: 'codigo_postal', hidden: true},
],
dataFeed:function(text){
if (text.length > 2){
@@ -596,6 +597,10 @@ var controls_generate = [
autowidth:true},
{view: 'label', id: 'lbl_client', name: 'lbl_client',
label: 'Ninguno'},
+ ]},
+ {cols: [{
+ view: 'richselect', id: 'lst_invoice_client_regimen',
+ label: 'Regimen Fiscal: ', labelWidth: 150, options: []}
]}
]}},
{view: 'fieldset', label: 'Buscar Producto', body: {rows: [
@@ -1388,7 +1393,6 @@ var win_carta_import_json = {
head: 'Importar Carta Porte JSON',
body: body_upload_carta_json,
})
- //~ $$('cmd_upload_invoice').attachEvent('onItemClick', cmd_upload_invoice_click)
$$('up_invoice_json').attachEvent('onAfterFileAdd', up_invoice_json_on_after_file_add)
}
}
diff --git a/source/static/js/ui/partners.js b/source/static/js/ui/partners.js
index d7aeab2..7a125c0 100644
--- a/source/static/js/ui/partners.js
+++ b/source/static/js/ui/partners.js
@@ -96,7 +96,7 @@ var controls_fiscales = [
{cols: [{view: 'text', id: 'no_interior', name: 'no_interior', width: 300,
label: 'No Interior: '},{}]},
{cols: [{view: 'search', id: 'codigo_postal', name: 'codigo_postal',
- width: 300, label: 'C.P.: ', attributes: {maxlength: 5}},{}]},
+ width: 300, label: 'C.P.: ', attributes: {maxlength: 5}, required: true},{}]},
{view: 'text', id: 'colonia', name: 'colonia', label: 'Colonia: '},
{view: 'text', id: 'municipio', name: 'municipio', label: 'Municipio: '},
{view: 'text', id: 'estado', name: 'estado', label: 'Estado: '},
@@ -122,6 +122,12 @@ var controls_fiscales = [
{view: 'richselect', id: 'lst_uso_cfdi_socio', name: 'uso_cfdi_socio',
label: 'Uso del CFDI', options: []},
{},
+ ]},
+ {template: 'Regimenes Fiscales', type: 'section'},
+ {cols: [
+ {view: 'list', id: 'lst_receptor_regimenes_fiscales', data: [],
+ select: 'multiselect', width: 600, height: 125, required: true},
+ {},
]}
]
@@ -159,7 +165,7 @@ var controls_others = [
label: 'Cuenta Proveedor: ', disabled: true}, {}]
},
{view: 'checkbox', name: 'es_ong', label: 'Es ONG: ', value: false},
- {view: 'text', name: 'tags', label: 'Etiquetas',
+ {view: 'text', name: 'tags', label: 'Etiquetas', disabled: true,
tooltip: 'Utiles para filtrados rápidos. Separa por comas.'},
{view: 'textarea' , height: 200, name: 'notas', label: 'Notas'},
]
diff --git a/source/static/js/ui/products.js b/source/static/js/ui/products.js
index eeb3472..df4ab56 100644
--- a/source/static/js/ui/products.js
+++ b/source/static/js/ui/products.js
@@ -111,6 +111,14 @@ var suggest_sat_producto = {
}
+var opt_tax_object = [
+ {id: '01', value: '[01] No objeto de impuesto.'},
+ {id: '02', value: '[02] Sí objeto de impuesto.'},
+ {id: '03', value: '[03] Sí objeto del impuesto y no obligado al desglose.'},
+ {id: '04', value: '[04] Sí objeto del impuesto y no causa impuesto.'},
+]
+
+
var controls_generals = [
{view: 'checkbox', id: 'es_activo_producto', name: 'es_activo_producto',
label: 'Activo: ', value: true,
@@ -141,10 +149,14 @@ 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: 'cant_by_packing', name: 'cant_by_packing',
- labelAlign: 'right', labelWidth: 150, inputAlign: "right",
- label: 'Cantidad por empaque:'},
+ {view: 'richselect', id: 'objeto_impuesto', name: 'objeto_impuesto', label: 'Objeto de Impuestos',
+ width: 500, labelWidth: 150, labelAlign: "right", required: true,
+ invalidMessage: 'Este campo es requerido', options: opt_tax_object},
{},
+ //~ {view: 'text', id: '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'}
diff --git a/source/static/js/ui/tickets.js b/source/static/js/ui/tickets.js
index e5407e6..bdd148e 100644
--- a/source/static/js/ui/tickets.js
+++ b/source/static/js/ui/tickets.js
@@ -233,9 +233,21 @@ var cells_new_ticket = [
]
+var opt_periodicidad = [
+ {id: '01', value: '[01] Diario'},
+ {id: '02', value: '[02] Semanal'},
+ {id: '03', value: '[03] Quincenal'},
+ {id: '04', value: '[04] Mensual'},
+ //~ {id: '05', value: '[05] Bimestral'},
+]
+
+
var toolbar_ticket_invoice = {view: 'toolbar', elements: [{},
{view: 'checkbox', id: 'chk_is_invoice_day', labelWidth: 0, width: 150,
- labelRight: 'Es factura del día'}, {},
+ labelRight: 'Es factura del día'},
+ {view: 'richselect', id: 'lst_periodicidad', labelWidth: 90, width: 250,
+ label: 'Periodicidad:', options: opt_periodicidad, value: '01', disabled: true},
+ {},
{view: 'button', id: 'cmd_close_ticket_invoice', label: 'Cerrar',
type: 'danger', autowidth: true, align: 'center'}
]}
diff --git a/source/templates/plantilla_factura.ods b/source/templates/plantilla_factura_3.3.ods
similarity index 100%
rename from source/templates/plantilla_factura.ods
rename to source/templates/plantilla_factura_3.3.ods
diff --git a/source/templates/plantilla_factura_4.0.ods b/source/templates/plantilla_factura_4.0.ods
new file mode 100644
index 0000000..982474a
Binary files /dev/null and b/source/templates/plantilla_factura_4.0.ods differ
diff --git a/source/templates/plantilla_factura_cp.ods b/source/templates/plantilla_factura_ccp.ods
similarity index 100%
rename from source/templates/plantilla_factura_cp.ods
rename to source/templates/plantilla_factura_ccp.ods
diff --git a/source/templates/plantilla_nomina.ods b/source/templates/plantilla_nomina_3.3_1.2.ods
similarity index 100%
rename from source/templates/plantilla_nomina.ods
rename to source/templates/plantilla_nomina_3.3_1.2.ods
diff --git a/source/templates/plantilla_nomina_4.0_1.2.ods b/source/templates/plantilla_nomina_4.0_1.2.ods
new file mode 100644
index 0000000..7d8c93a
Binary files /dev/null and b/source/templates/plantilla_nomina_4.0_1.2.ods differ
diff --git a/source/templates/plantilla_pagos.ods b/source/templates/plantilla_pagos_3.3_1.0.ods
similarity index 100%
rename from source/templates/plantilla_pagos.ods
rename to source/templates/plantilla_pagos_3.3_1.0.ods
diff --git a/source/templates/plantilla_pagos_4.0_2.0.ods b/source/templates/plantilla_pagos_4.0_2.0.ods
new file mode 100644
index 0000000..07b2528
Binary files /dev/null and b/source/templates/plantilla_pagos_4.0_2.0.ods differ
diff --git a/source/xslt/cadena.xslt b/source/xslt/cadena.xslt
index f77402c..e238e33 100644
--- a/source/xslt/cadena.xslt
+++ b/source/xslt/cadena.xslt
@@ -1,347 +1,401 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |||
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |||
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/xslt/cadena3.3.xslt b/source/xslt/cadena3.3.xslt
new file mode 100644
index 0000000..f77402c
--- /dev/null
+++ b/source/xslt/cadena3.3.xslt
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |||
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/xslt/pagos20.xslt b/source/xslt/pagos20.xslt
new file mode 100644
index 0000000..1e6cf98
--- /dev/null
+++ b/source/xslt/pagos20.xslt
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/source/xslt/utilerias.xslt b/source/xslt/utilerias.xslt
index d5dd14e..4ae4bf4 100644
--- a/source/xslt/utilerias.xslt
+++ b/source/xslt/utilerias.xslt
@@ -1,22 +1,22 @@
-
+
-
-
- |
-
-
-
+
+
+ |
+
+
+
-
-
-
- |
-
-
-
-
-
-
-
+
+
+
+ |
+
+
+
+
+
+
+
diff --git a/source/xslt/utilerias1.1.xslt b/source/xslt/utilerias1.1.xslt
new file mode 100644
index 0000000..d5dd14e
--- /dev/null
+++ b/source/xslt/utilerias1.1.xslt
@@ -0,0 +1,22 @@
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+