2018-08-24 23:04:10 -05:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
# ~ Empresa Libre
|
|
|
|
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.net)
|
|
|
|
# ~
|
|
|
|
# ~ This program is free software: you can redistribute it and/or modify
|
|
|
|
# ~ it under the terms of the GNU General Public License as published by
|
|
|
|
# ~ the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# ~ (at your option) any later version.
|
|
|
|
# ~
|
|
|
|
# ~ This program is distributed in the hope that it will be useful,
|
|
|
|
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# ~ GNU General Public License for more details.
|
|
|
|
# ~
|
|
|
|
# ~ You should have received a copy of the GNU General Public License
|
|
|
|
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2017-10-10 18:49:05 -05:00
|
|
|
|
|
|
|
import datetime
|
|
|
|
from xml.etree import ElementTree as ET
|
|
|
|
from xml.dom.minidom import parseString
|
|
|
|
|
|
|
|
from logbook import Logger
|
|
|
|
|
|
|
|
|
|
|
|
log = Logger('XML')
|
|
|
|
CFDI_ACTUAL = 'cfdi33'
|
|
|
|
NOMINA_ACTUAL = 'nomina12'
|
|
|
|
|
|
|
|
SAT = {
|
|
|
|
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
|
|
'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',
|
|
|
|
},
|
|
|
|
'cfdi33': {
|
|
|
|
'version': '3.3',
|
|
|
|
'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/cfdv33.xsd',
|
|
|
|
},
|
|
|
|
'nomina11': {
|
|
|
|
'version': '1.1',
|
|
|
|
'prefix': 'nomina',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/nomina',
|
2020-08-25 14:15:07 -05:00
|
|
|
'schema': ' http://www.sat.gob.mx/nomina http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina11.xsd',
|
2017-10-10 18:49:05 -05:00
|
|
|
},
|
2018-01-28 21:35:10 -06:00
|
|
|
'nomina': {
|
2017-10-10 18:49:05 -05:00
|
|
|
'version': '1.2',
|
2018-01-28 21:35:10 -06:00
|
|
|
'prefix': 'nomina12',
|
2017-10-10 18:49:05 -05:00
|
|
|
'xmlns': 'http://www.sat.gob.mx/nomina12',
|
2020-08-25 14:15:07 -05:00
|
|
|
'schema': ' http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd',
|
2017-10-10 18:49:05 -05:00
|
|
|
},
|
2017-11-19 00:42:16 -06:00
|
|
|
'locales': {
|
|
|
|
'version': '1.0',
|
|
|
|
'prefix': 'implocal',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/implocal',
|
|
|
|
'schema': ' http://www.sat.gob.mx/implocal http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd',
|
|
|
|
},
|
2017-11-19 21:59:14 -06:00
|
|
|
'donativo': {
|
|
|
|
'version': '1.1',
|
|
|
|
'prefix': 'donat',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/donat',
|
|
|
|
'schema': ' http://www.sat.gob.mx/donat http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd',
|
|
|
|
'leyenda': 'Este comprobante ampara un donativo, el cual serĆ” destinado por la donataria a los fines propios de su objeto social. En el caso de que los bienes donados hayan sido deducidos previamente para los efectos del impuesto sobre la renta, este donativo no es deducible. La reproducciĆ³n no autorizada de este comprobante constituye un delito en los tĆ©rminos de las disposiciones fiscales.',
|
|
|
|
},
|
2017-11-20 00:47:23 -06:00
|
|
|
'ine': {
|
|
|
|
'version': '1.1',
|
|
|
|
'prefix': 'ine',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/ine',
|
|
|
|
'schema': ' http://www.sat.gob.mx/ine http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd',
|
|
|
|
},
|
2018-01-29 13:21:06 -06:00
|
|
|
'edu': {
|
|
|
|
'version': '1.0',
|
|
|
|
'prefix': 'iedu',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/iedu',
|
2020-09-17 13:46:23 -05:00
|
|
|
'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd',
|
2018-01-29 13:21:06 -06:00
|
|
|
},
|
2018-08-24 23:04:10 -05:00
|
|
|
'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',
|
|
|
|
},
|
2019-02-14 22:50:17 -06:00
|
|
|
'divisas': {
|
|
|
|
'version': '1.0',
|
|
|
|
'prefix': 'divisas',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/divisas',
|
|
|
|
'schema': ' http://www.sat.gob.mx/divisas http://www.sat.gob.mx/sitio_internet/cfd/divisas/divisas.xsd',
|
|
|
|
},
|
2020-03-01 23:18:26 -06:00
|
|
|
'leyendas': {
|
|
|
|
'version': '1.0',
|
|
|
|
'prefix': 'leyendasFisc',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/leyendasFiscales',
|
|
|
|
'schema': ' http://www.sat.gob.mx/leyendasFiscales http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd',
|
|
|
|
},
|
2021-12-21 23:01:44 -06:00
|
|
|
'cartaporte': {
|
|
|
|
'version': '2.0',
|
|
|
|
'prefix': 'cartaporte20',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/CartaPorte20',
|
|
|
|
'schema': ' http://www.sat.gob.mx/CartaPorte20 http://www.sat.gob.mx/sitio_internet/cfd/CartaPorte/CartaPorte20.xsd',
|
|
|
|
}
|
2017-10-10 18:49:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class CFDI(object):
|
|
|
|
|
|
|
|
def __init__(self, version=CFDI_ACTUAL):
|
|
|
|
self._sat_cfdi = SAT[version]
|
|
|
|
self._xsi = SAT['xsi']
|
|
|
|
self._pre = self._sat_cfdi['prefix']
|
|
|
|
self._cfdi = None
|
2017-11-19 00:42:16 -06:00
|
|
|
self._complemento = None
|
|
|
|
self._impuestos_locales = False
|
2017-11-19 21:59:14 -06:00
|
|
|
self._donativo = False
|
2017-11-20 00:47:23 -06:00
|
|
|
self._ine = False
|
2018-01-29 13:21:06 -06:00
|
|
|
self._edu = False
|
2018-08-28 01:02:35 -05:00
|
|
|
self._pagos = False
|
2018-01-28 21:35:10 -06:00
|
|
|
self._is_nomina = False
|
2020-03-01 23:18:26 -06:00
|
|
|
self._leyendas = False
|
2019-02-14 22:50:17 -06:00
|
|
|
self._divisas = ''
|
2017-10-10 18:49:05 -05:00
|
|
|
self.error = ''
|
|
|
|
|
|
|
|
def _now(self):
|
|
|
|
return datetime.datetime.now().isoformat()[:19]
|
|
|
|
|
|
|
|
def get_xml(self, datos):
|
|
|
|
if not self._validate(datos):
|
|
|
|
return ''
|
|
|
|
|
|
|
|
self._comprobante(datos['comprobante'])
|
2017-11-08 23:47:15 -06:00
|
|
|
self._relacionados(datos['relacionados'])
|
2017-10-10 18:49:05 -05:00
|
|
|
self._emisor(datos['emisor'])
|
|
|
|
self._receptor(datos['receptor'])
|
|
|
|
self._conceptos(datos['conceptos'])
|
|
|
|
self._impuestos(datos['impuestos'])
|
2017-11-19 21:59:14 -06:00
|
|
|
self._locales(datos['impuestos'])
|
|
|
|
self._donatarias(datos['donativo'])
|
2017-11-20 00:47:23 -06:00
|
|
|
self._complementos(datos['complementos'])
|
2017-11-19 21:59:14 -06:00
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
if 'nomina' in datos:
|
|
|
|
self._nomina(datos['nomina'])
|
2017-11-19 21:59:14 -06:00
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
|
|
|
|
|
2020-12-31 12:01:41 -06:00
|
|
|
def add_sello(self, sello, cert_txt):
|
2017-10-10 18:49:05 -05:00
|
|
|
self._cfdi.attrib['Sello'] = sello
|
2020-12-31 12:01:41 -06:00
|
|
|
self._cfdi.attrib['Certificado'] = cert_txt
|
2017-10-10 18:49:05 -05:00
|
|
|
return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
|
|
|
|
|
|
|
|
def _to_pretty_xml(self, source):
|
|
|
|
tree = parseString(source)
|
|
|
|
xml = tree.toprettyxml(encoding='utf-8').decode('utf-8')
|
|
|
|
return xml
|
|
|
|
|
|
|
|
def _validate(self, datos):
|
2018-01-28 21:35:10 -06:00
|
|
|
if datos['impuestos']:
|
|
|
|
if datos['impuestos']['total_locales_trasladados'] or \
|
|
|
|
datos['impuestos']['total_locales_retenciones']:
|
|
|
|
self._impuestos_locales = True
|
2017-11-19 00:42:16 -06:00
|
|
|
|
2017-11-19 21:59:14 -06:00
|
|
|
if datos['donativo']:
|
|
|
|
self._donativo = True
|
|
|
|
|
2018-06-18 14:10:52 -05:00
|
|
|
self._edu = datos.get('edu', False)
|
2018-06-03 00:00:04 -05:00
|
|
|
|
2018-01-28 21:35:10 -06:00
|
|
|
if datos['complementos']:
|
|
|
|
if 'ine' in datos['complementos']:
|
|
|
|
self._ine = True
|
2018-08-30 00:13:12 -05:00
|
|
|
self._pagos = bool(datos['complementos'].get('pagos', False))
|
2020-03-01 23:18:26 -06:00
|
|
|
self._leyendas = bool(datos['complementos'].get('leyendas', False))
|
2018-01-29 13:21:06 -06:00
|
|
|
|
2019-02-14 22:50:17 -06:00
|
|
|
self._divisas = datos['comprobante'].pop('divisas', '')
|
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
if 'nomina' in datos:
|
2018-01-28 21:35:10 -06:00
|
|
|
self._is_nomina = True
|
2017-10-10 18:49:05 -05:00
|
|
|
return self._validate_nomina(datos)
|
2018-01-28 21:35:10 -06:00
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
return True
|
|
|
|
|
|
|
|
def _validate_nomina(self, datos):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _comprobante(self, datos):
|
|
|
|
attributes = {}
|
|
|
|
attributes['xmlns:{}'.format(self._pre)] = self._sat_cfdi['xmlns']
|
|
|
|
attributes['xmlns:xsi'] = self._xsi
|
2017-11-19 00:42:16 -06:00
|
|
|
|
2017-11-19 21:59:14 -06:00
|
|
|
schema_locales = ''
|
2017-11-19 00:42:16 -06:00
|
|
|
if self._impuestos_locales:
|
|
|
|
name = 'xmlns:{}'.format(SAT['locales']['prefix'])
|
|
|
|
attributes[name] = SAT['locales']['xmlns']
|
2017-11-19 21:59:14 -06:00
|
|
|
schema_locales = SAT['locales']['schema']
|
|
|
|
|
|
|
|
schema_donativo = ''
|
|
|
|
if self._donativo:
|
|
|
|
name = 'xmlns:{}'.format(SAT['donativo']['prefix'])
|
|
|
|
attributes[name] = SAT['donativo']['xmlns']
|
|
|
|
schema_donativo = SAT['donativo']['schema']
|
2017-11-19 00:42:16 -06:00
|
|
|
|
2017-11-20 00:47:23 -06:00
|
|
|
schema_ine = ''
|
|
|
|
if self._ine:
|
|
|
|
name = 'xmlns:{}'.format(SAT['ine']['prefix'])
|
|
|
|
attributes[name] = SAT['ine']['xmlns']
|
2018-01-29 13:21:06 -06:00
|
|
|
schema_ine = SAT['ine']['schema']
|
|
|
|
|
|
|
|
schema_edu = ''
|
|
|
|
if self._edu:
|
|
|
|
name = 'xmlns:{}'.format(SAT['edu']['prefix'])
|
|
|
|
attributes[name] = SAT['edu']['xmlns']
|
2018-01-29 23:51:08 -06:00
|
|
|
schema_edu = SAT['edu']['schema']
|
2018-01-29 22:43:43 -06:00
|
|
|
|
2019-02-14 22:50:17 -06:00
|
|
|
schema_divisas = ''
|
|
|
|
if self._divisas:
|
|
|
|
name = 'xmlns:{}'.format(SAT['divisas']['prefix'])
|
|
|
|
attributes[name] = SAT['divisas']['xmlns']
|
|
|
|
schema_divisas = SAT['divisas']['schema']
|
|
|
|
|
2018-01-28 21:35:10 -06:00
|
|
|
schema_nomina = ''
|
2018-02-07 12:54:19 -06:00
|
|
|
if self._is_nomina:
|
2018-01-28 21:35:10 -06:00
|
|
|
name = 'xmlns:{}'.format(SAT['nomina']['prefix'])
|
|
|
|
attributes[name] = SAT['nomina']['xmlns']
|
|
|
|
schema_nomina = SAT['nomina']['schema']
|
2017-11-20 00:47:23 -06:00
|
|
|
|
2018-08-30 00:13:12 -05:00
|
|
|
schema_pagos = ''
|
|
|
|
if self._pagos:
|
|
|
|
name = 'xmlns:{}'.format(SAT['pagos']['prefix'])
|
|
|
|
attributes[name] = SAT['pagos']['xmlns']
|
|
|
|
schema_pagos = SAT['pagos']['schema']
|
|
|
|
|
2020-03-01 23:18:26 -06:00
|
|
|
schema_leyendas = ''
|
|
|
|
if self._leyendas:
|
|
|
|
name = 'xmlns:{}'.format(SAT['leyendas']['prefix'])
|
|
|
|
attributes[name] = SAT['leyendas']['xmlns']
|
|
|
|
schema_leyendas = SAT['leyendas']['schema']
|
|
|
|
|
2017-11-19 00:42:16 -06:00
|
|
|
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
|
2018-01-29 23:51:08 -06:00
|
|
|
schema_locales + schema_donativo + schema_ine + schema_edu + \
|
2020-03-01 23:18:26 -06:00
|
|
|
schema_divisas + schema_nomina + schema_pagos + schema_leyendas
|
2017-10-10 18:49:05 -05:00
|
|
|
attributes.update(datos)
|
|
|
|
|
|
|
|
if not 'Version' in attributes:
|
|
|
|
attributes['Version'] = self._sat_cfdi['version']
|
|
|
|
if not 'Fecha' in attributes:
|
|
|
|
attributes['Fecha'] = self._now()
|
|
|
|
|
|
|
|
self._cfdi = ET.Element('{}:Comprobante'.format(self._pre), attributes)
|
|
|
|
return
|
|
|
|
|
2017-11-08 23:47:15 -06:00
|
|
|
def _relacionados(self, datos):
|
2017-11-11 14:27:36 -06:00
|
|
|
if not datos or not datos['tipo'] or not datos['cfdis']:
|
2017-11-08 23:47:15 -06:00
|
|
|
return
|
|
|
|
|
|
|
|
node_name = '{}:CfdiRelacionados'.format(self._pre)
|
|
|
|
value = {'TipoRelacion': datos['tipo']}
|
|
|
|
node = ET.SubElement(self._cfdi, node_name, value)
|
|
|
|
for uuid in datos['cfdis']:
|
|
|
|
node_name = '{}:CfdiRelacionado'.format(self._pre)
|
|
|
|
value = {'UUID': uuid}
|
|
|
|
ET.SubElement(node, node_name, value)
|
|
|
|
return
|
2017-10-10 18:49:05 -05:00
|
|
|
|
2017-11-08 23:47:15 -06:00
|
|
|
def _emisor(self, datos):
|
2017-10-10 18:49:05 -05:00
|
|
|
node_name = '{}:Emisor'.format(self._pre)
|
|
|
|
emisor = ET.SubElement(self._cfdi, node_name, datos)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _receptor(self, datos):
|
|
|
|
node_name = '{}:Receptor'.format(self._pre)
|
|
|
|
emisor = ET.SubElement(self._cfdi, node_name, datos)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _conceptos(self, datos):
|
2018-01-13 18:39:22 -06:00
|
|
|
from xml.sax.saxutils import escape, unescape
|
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
conceptos = ET.SubElement(self._cfdi, '{}:Conceptos'.format(self._pre))
|
2021-04-13 22:29:04 -05:00
|
|
|
# ~ for row in reversed(datos):
|
|
|
|
for row in datos:
|
2017-10-10 18:49:05 -05:00
|
|
|
complemento = {}
|
|
|
|
if 'complemento' in row:
|
|
|
|
complemento = row.pop('complemento')
|
2017-12-06 23:30:40 -06:00
|
|
|
cuenta_predial = row.pop('CuentaPredial', '')
|
2018-01-08 09:40:35 -06:00
|
|
|
pedimento = row.pop('Pedimento', '')
|
2018-01-29 13:21:06 -06:00
|
|
|
student = row.pop('student', '')
|
2017-10-10 18:49:05 -05:00
|
|
|
|
|
|
|
taxes = {}
|
|
|
|
if 'impuestos' in row:
|
|
|
|
taxes = row.pop('impuestos')
|
|
|
|
node_name = '{}:Concepto'.format(self._pre)
|
|
|
|
concepto = ET.SubElement(conceptos, node_name, row)
|
|
|
|
|
|
|
|
if taxes:
|
|
|
|
node_name = '{}:Impuestos'.format(self._pre)
|
|
|
|
impuestos = ET.SubElement(concepto, node_name)
|
|
|
|
if 'traslados' in taxes and taxes['traslados']:
|
|
|
|
node_name = '{}:Traslados'.format(self._pre)
|
|
|
|
traslados = ET.SubElement(impuestos, node_name)
|
|
|
|
for traslado in taxes['traslados']:
|
|
|
|
ET.SubElement(
|
|
|
|
traslados, '{}:Traslado'.format(self._pre), traslado)
|
|
|
|
if 'retenciones' in taxes and taxes['retenciones']:
|
|
|
|
node_name = '{}:Retenciones'.format(self._pre)
|
|
|
|
retenciones = ET.SubElement(impuestos, node_name)
|
|
|
|
for retencion in taxes['retenciones']:
|
|
|
|
ET.SubElement(
|
|
|
|
retenciones, '{}:Retencion'.format(self._pre), retencion)
|
|
|
|
|
2018-01-08 09:40:35 -06:00
|
|
|
if pedimento:
|
|
|
|
attributes = {'NumeroPedimento': pedimento}
|
|
|
|
node_name = '{}:InformacionAduanera'.format(self._pre)
|
|
|
|
ET.SubElement(concepto, node_name, attributes)
|
2017-10-10 18:49:05 -05:00
|
|
|
|
2017-12-06 23:30:40 -06:00
|
|
|
if cuenta_predial:
|
|
|
|
attributes = {'Numero': cuenta_predial}
|
2017-10-10 18:49:05 -05:00
|
|
|
node_name = '{}:CuentaPredial'.format(self._pre)
|
|
|
|
ET.SubElement(concepto, node_name, attributes)
|
|
|
|
|
2018-01-29 13:21:06 -06:00
|
|
|
if student:
|
2017-10-10 18:49:05 -05:00
|
|
|
node_name = '{}:ComplementoConcepto'.format(self._pre)
|
|
|
|
complemento = ET.SubElement(concepto, node_name)
|
2018-01-29 13:21:06 -06:00
|
|
|
ET.SubElement(complemento, 'iedu:instEducativas', student)
|
2017-10-10 18:49:05 -05:00
|
|
|
return
|
|
|
|
|
|
|
|
def _impuestos(self, datos):
|
2018-02-16 13:52:30 -06:00
|
|
|
if self._is_nomina or not datos:
|
2018-01-28 21:35:10 -06:00
|
|
|
return
|
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
if not datos:
|
|
|
|
node_name = '{}:Impuestos'.format(self._pre)
|
|
|
|
ET.SubElement(self._cfdi, node_name)
|
|
|
|
return
|
|
|
|
|
|
|
|
attributes = {}
|
|
|
|
fields = ('TotalImpuestosTrasladados', 'TotalImpuestosRetenidos')
|
|
|
|
for field in fields:
|
|
|
|
if field in datos:
|
|
|
|
attributes[field] = datos[field]
|
|
|
|
node_name = '{}:Impuestos'.format(self._pre)
|
|
|
|
impuestos = ET.SubElement(self._cfdi, node_name, attributes)
|
|
|
|
|
|
|
|
if 'retenciones' in datos and datos['retenciones']:
|
|
|
|
retenciones = ET.SubElement(impuestos, '{}:Retenciones'.format(self._pre))
|
|
|
|
for row in datos['retenciones']:
|
|
|
|
ET.SubElement(retenciones, '{}:Retencion'.format(self._pre), row)
|
|
|
|
|
|
|
|
if 'traslados' in datos and datos['traslados']:
|
|
|
|
traslados = ET.SubElement(impuestos, '{}:Traslados'.format(self._pre))
|
|
|
|
for row in datos['traslados']:
|
|
|
|
ET.SubElement(traslados, '{}:Traslado'.format(self._pre), row)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _nomina(self, datos):
|
2018-01-28 21:35:10 -06:00
|
|
|
pre = SAT['nomina']['prefix']
|
2017-10-10 18:49:05 -05:00
|
|
|
|
2018-01-28 21:35:10 -06:00
|
|
|
if self._complemento is None:
|
|
|
|
self._complemento = ET.SubElement(
|
|
|
|
self._cfdi, '{}:Complemento'.format(self._pre))
|
2017-10-10 18:49:05 -05:00
|
|
|
|
2018-01-28 21:35:10 -06:00
|
|
|
emisor = datos.pop('emisor', None)
|
|
|
|
receptor = datos.pop('receptor', None)
|
|
|
|
percepciones = datos.pop('percepciones', None)
|
|
|
|
deducciones = datos.pop('deducciones', None)
|
|
|
|
otros_pagos = datos.pop('otros_pagos', ())
|
|
|
|
incapacidades = datos.pop('incapacidades', ())
|
2017-10-10 18:49:05 -05:00
|
|
|
|
2018-01-28 21:35:10 -06:00
|
|
|
nomina = ET.SubElement(
|
|
|
|
self._complemento, '{}:Nomina'.format(pre), datos['nomina'])
|
2017-10-10 18:49:05 -05:00
|
|
|
|
|
|
|
if emisor:
|
|
|
|
ET.SubElement(nomina, '{}:Emisor'.format(pre), emisor)
|
2018-01-28 21:35:10 -06:00
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
if receptor:
|
2018-01-28 21:35:10 -06:00
|
|
|
node = ET.SubElement(nomina, '{}:Receptor'.format(pre), receptor)
|
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
if percepciones:
|
2018-01-28 21:35:10 -06:00
|
|
|
details = percepciones.pop('details', None)
|
|
|
|
hours_extra = percepciones.pop('hours_extra', None)
|
|
|
|
separacion = percepciones.pop('separacion', None)
|
|
|
|
if details:
|
|
|
|
node = ET.SubElement(nomina, '{}:Percepciones'.format(pre), percepciones)
|
|
|
|
for row in details:
|
|
|
|
nodep = ET.SubElement(node, '{}:Percepcion'.format(pre), row)
|
|
|
|
if row['TipoPercepcion'] == '019' and hours_extra:
|
|
|
|
for he in hours_extra:
|
|
|
|
ET.SubElement(nodep, '{}:HorasExtra'.format(pre), he)
|
|
|
|
hours_extra = None
|
|
|
|
if separacion:
|
|
|
|
ET.SubElement(node, '{}:SeparacionIndemnizacion'.format(pre), separacion)
|
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
if deducciones:
|
2018-01-28 21:35:10 -06:00
|
|
|
details = deducciones.pop('details', None)
|
|
|
|
if details:
|
|
|
|
deducciones = ET.SubElement(nomina, '{}:Deducciones'.format(pre), deducciones)
|
|
|
|
for row in details:
|
|
|
|
ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row)
|
|
|
|
|
|
|
|
if otros_pagos:
|
|
|
|
node = ET.SubElement(nomina, '{}:OtrosPagos'.format(pre))
|
|
|
|
for row in otros_pagos:
|
|
|
|
subsidio = row.pop('subsidio', None)
|
|
|
|
subnode = ET.SubElement(node, '{}:OtroPago'.format(pre), row)
|
|
|
|
if subsidio:
|
|
|
|
ET.SubElement(subnode, '{}:SubsidioAlEmpleo'.format(pre), subsidio)
|
|
|
|
|
|
|
|
if incapacidades:
|
|
|
|
node = ET.SubElement(nomina, '{}:Incapacidades'.format(pre))
|
|
|
|
for row in incapacidades:
|
|
|
|
ET.SubElement(node, '{}:Incapacidad'.format(pre), row)
|
2017-10-10 18:49:05 -05:00
|
|
|
return
|
|
|
|
|
2017-11-19 00:42:16 -06:00
|
|
|
def _locales(self, datos):
|
|
|
|
if not self._impuestos_locales:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self._complemento is None:
|
|
|
|
self._complemento = ET.SubElement(
|
|
|
|
self._cfdi, '{}:Complemento'.format(self._pre))
|
|
|
|
|
|
|
|
attributes = {}
|
|
|
|
attributes['version'] = SAT['locales']['version']
|
2017-12-04 22:48:18 -06:00
|
|
|
if not datos['total_locales_trasladados']:
|
|
|
|
datos['total_locales_trasladados'] = '0.00'
|
2017-11-19 00:42:16 -06:00
|
|
|
attributes['TotaldeTraslados'] = datos['total_locales_trasladados']
|
2017-12-04 22:48:18 -06:00
|
|
|
if not datos['total_locales_retenciones']:
|
|
|
|
datos['total_locales_retenciones'] = '0.00'
|
2017-11-19 00:42:16 -06:00
|
|
|
attributes['TotaldeRetenciones'] = datos['total_locales_retenciones']
|
|
|
|
|
|
|
|
node = ET.SubElement(
|
|
|
|
self._complemento, 'implocal:ImpuestosLocales', attributes)
|
|
|
|
|
|
|
|
for retencion in datos['locales_retenciones']:
|
|
|
|
ET.SubElement(node, 'implocal:RetencionesLocales', retencion)
|
|
|
|
for traslado in datos['locales_trasladados']:
|
|
|
|
ET.SubElement(node, 'implocal:TrasladosLocales', traslado)
|
|
|
|
return
|
|
|
|
|
2017-11-19 21:59:14 -06:00
|
|
|
def _donatarias(self, datos):
|
|
|
|
if not datos:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self._complemento is None:
|
|
|
|
self._complemento = ET.SubElement(
|
|
|
|
self._cfdi, '{}:Complemento'.format(self._pre))
|
|
|
|
|
|
|
|
attributes = {}
|
|
|
|
attributes['version'] = SAT['donativo']['version']
|
|
|
|
attributes['leyenda'] = SAT['donativo']['leyenda']
|
|
|
|
attributes.update(datos)
|
|
|
|
|
|
|
|
node = ET.SubElement(self._complemento, 'donat:Donatarias', attributes)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
def _complementos(self, datos):
|
2017-11-20 00:47:23 -06:00
|
|
|
if not datos:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self._complemento is None:
|
|
|
|
self._complemento = ET.SubElement(
|
|
|
|
self._cfdi, '{}:Complemento'.format(self._pre))
|
|
|
|
|
2019-02-14 22:50:17 -06:00
|
|
|
if self._divisas:
|
|
|
|
atributos = {
|
|
|
|
'version': SAT['divisas']['version'],
|
|
|
|
'tipoOperacion': self._divisas,
|
|
|
|
}
|
|
|
|
ET.SubElement(self._complemento, 'divisas:Divisas', atributos)
|
|
|
|
|
2017-11-20 00:47:23 -06:00
|
|
|
if 'ine' in datos:
|
|
|
|
atributos = {'Version': SAT['ine']['version']}
|
2021-05-31 12:59:45 -05:00
|
|
|
ine_key_entidad = datos['ine'].pop('ClaveEntidad', '')
|
|
|
|
ine_ambito = datos['ine'].pop('Ambito', '')
|
|
|
|
if ine_key_entidad:
|
|
|
|
ine_id_conta = datos['ine'].pop('IdContabilidad', '')
|
2017-11-20 00:47:23 -06:00
|
|
|
atributos.update(datos['ine'])
|
2021-05-31 12:59:45 -05:00
|
|
|
node_ine = ET.SubElement(self._complemento, 'ine:INE', atributos)
|
|
|
|
if ine_key_entidad:
|
|
|
|
attr = {'ClaveEntidad': ine_key_entidad, 'Ambito': ine_ambito}
|
|
|
|
node_entidad = ET.SubElement(node_ine, 'ine:Entidad', attr)
|
|
|
|
attr = {'IdContabilidad': ine_id_conta}
|
|
|
|
ET.SubElement(node_entidad, 'ine:Contabilidad', attr)
|
2017-11-20 00:47:23 -06:00
|
|
|
|
2018-08-24 23:04:10 -05:00
|
|
|
if 'pagos' in datos:
|
|
|
|
datos = datos.pop('pagos')
|
|
|
|
relacionados = datos.pop('relacionados')
|
2018-08-30 00:13:12 -05:00
|
|
|
pre = SAT['pagos']['prefix']
|
|
|
|
attributes = {'Version': SAT['pagos']['version']}
|
2018-08-24 23:04:10 -05:00
|
|
|
pagos = ET.SubElement(
|
2018-08-30 00:13:12 -05:00
|
|
|
self._complemento, '{}:Pagos'.format(pre), attributes)
|
2018-08-28 01:02:35 -05:00
|
|
|
node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), datos)
|
2018-08-24 23:04:10 -05:00
|
|
|
for row in relacionados:
|
|
|
|
ET.SubElement(node_pago, '{}:DoctoRelacionado'.format(pre), row)
|
|
|
|
|
2020-03-01 23:18:26 -06:00
|
|
|
if 'leyendas' in datos:
|
|
|
|
pre = SAT['leyendas']['prefix']
|
|
|
|
attributes = {'version': SAT['leyendas']['version']}
|
|
|
|
node_leyend = ET.SubElement(
|
|
|
|
self._complemento, '{}:LeyendasFiscales'.format(pre), attributes)
|
|
|
|
for leyend in datos['leyendas']:
|
|
|
|
ET.SubElement(node_leyend, '{}:Leyenda'.format(pre), leyend)
|
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
if 'ce' in datos:
|
|
|
|
pre = 'cce11'
|
|
|
|
datos = datos.pop('ce')
|
|
|
|
emisor = datos.pop('emisor')
|
|
|
|
propietario = datos.pop('propietario')
|
|
|
|
receptor = datos.pop('receptor')
|
|
|
|
destinatario = datos.pop('destinatario')
|
|
|
|
conceptos = datos.pop('conceptos')
|
|
|
|
|
|
|
|
attributes = {}
|
|
|
|
attributes['xmlns:{}'.format(pre)] = \
|
|
|
|
'http://www.sat.gob.mx/ComercioExterior11'
|
|
|
|
attributes['xsi:schemaLocation'] = \
|
|
|
|
'http://www.sat.gob.mx/ComercioExterior11 ' \
|
|
|
|
'http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior11/ComercioExterior11.xsd'
|
|
|
|
attributes.update(datos)
|
|
|
|
ce = ET.SubElement(
|
|
|
|
complemento, '{}:ComercioExterior'.format(pre), attributes)
|
|
|
|
|
|
|
|
attributes = {}
|
|
|
|
if 'Curp' in emisor:
|
|
|
|
attributes = {'Curp': emisor.pop('Curp')}
|
|
|
|
node = ET.SubElement(ce, '{}:Emisor'.format(pre), attributes)
|
|
|
|
ET.SubElement(node, '{}:Domicilio'.format(pre), emisor)
|
|
|
|
|
|
|
|
if propietario:
|
|
|
|
ET.SubElement(ce, '{}:Propietario'.format(pre), propietario)
|
|
|
|
|
|
|
|
attributes = {}
|
|
|
|
if 'NumRegIdTrib' in receptor:
|
|
|
|
attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
|
|
|
|
node = ET.SubElement(ce, '{}:Receptor'.format(pre), attributes)
|
|
|
|
ET.SubElement(node, '{}:Domicilio'.format(pre), receptor)
|
|
|
|
|
|
|
|
attributes = {}
|
|
|
|
if 'NumRegIdTrib' in destinatario:
|
|
|
|
attributes = {'NumRegIdTrib': destinatario.pop('NumRegIdTrib')}
|
|
|
|
if 'Nombre' in destinatario:
|
|
|
|
attributes.update({'Nombre': destinatario.pop('Nombre')})
|
|
|
|
node = ET.SubElement(ce, '{}:Destinatario'.format(pre), attributes)
|
|
|
|
ET.SubElement(node, '{}:Domicilio'.format(pre), destinatario)
|
|
|
|
|
|
|
|
node = ET.SubElement(ce, '{}:Mercancias'.format(pre))
|
|
|
|
fields = ('Marca', 'Modelo', 'SubModelo', 'NumeroSerie')
|
|
|
|
for row in conceptos:
|
|
|
|
detalle = {}
|
|
|
|
for f in fields:
|
|
|
|
if f in row:
|
|
|
|
detalle[f] = row.pop(f)
|
|
|
|
concepto = ET.SubElement(node, '{}:Mercancia'.format(pre), row)
|
|
|
|
if detalle:
|
|
|
|
ET.SubElement(
|
|
|
|
concepto, '{}:DescripcionesEspecificas'.format(pre), detalle)
|
|
|
|
return
|