empresa-libre/source/app/controllers/cfdi_xml.py

644 lines
26 KiB
Python
Raw Normal View History

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
2022-01-03 19:14:20 -06:00
from dateutil import parser
2017-10-10 18:49:05 -05:00
from logbook import Logger
log = Logger('XML')
2022-03-10 20:29:50 -06:00
CFDI_ACTUAL = 'cfdi40'
2017-10-10 18:49:05 -05:00
NOMINA_ACTUAL = 'nomina12'
2022-03-10 20:29:50 -06:00
DEFAULT = {
'exportacion': '01',
}
2017-10-10 18:49:05 -05:00
SAT = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
2022-03-10 20:29:50 -06:00
'cfdi40': {
'version': '4.0',
2017-10-10 18:49:05 -05:00
'prefix': 'cfdi',
2022-03-10 20:29:50 -06:00
'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',
2017-10-10 18:49:05 -05:00
},
'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',
},
2022-03-10 20:29:50 -06:00
'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',
},
2017-10-10 18:49:05 -05:00
'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',
},
'comercioe': {
'version': '1.1',
'prefix': 'cce11',
'xmlns': 'http://www.sat.gob.mx/ComercioExterior11',
'schema': ' http://www.sat.gob.mx/ComercioExterior11 http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior11/ComercioExterior11.xsd',
2021-12-21 23:01:44 -06:00
}
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
2022-01-03 19:14:20 -06:00
self._carta_porte = False
self._comercio_exterior = 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))
2022-01-03 19:14:20 -06:00
self._carta_porte = bool(datos['complementos'].get('cartaporte', False))
self._comercio_exterior = bool(datos['complementos'].get('comercioe', 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 = ''
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']
2022-01-03 19:14:20 -06:00
schema_carta_porte = ''
if self._carta_porte:
name = 'xmlns:{}'.format(SAT['cartaporte']['prefix'])
attributes[name] = SAT['cartaporte']['xmlns']
schema_carta_porte = SAT['cartaporte']['schema']
schema_comercioe = ''
if self._comercio_exterior:
name = 'xmlns:{}'.format(SAT['comercioe']['prefix'])
attributes[name] = SAT['comercioe']['xmlns']
schema_carta_porte = SAT['comercioe']['schema']
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 + \
2022-01-03 19:14:20 -06:00
schema_divisas + schema_nomina + schema_pagos + schema_leyendas + \
schema_carta_porte + schema_comercioe
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()
2022-03-10 20:29:50 -06:00
# ~ cfdi4
if not 'Exportacion' in attributes:
attributes['Exportacion'] = DEFAULT['exportacion']
2017-10-10 18:49:05 -05:00
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')
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
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))
2022-01-03 19:14:20 -06:00
if self._carta_porte:
datos = datos['cartaporte']
ubicaciones = datos.pop('ubicaciones')
mercancias = datos.pop('mercancias', ())
tiposfigura = datos.pop('tiposfigura', ())
atributos = {'Version': SAT['cartaporte']['version']}
atributos.update(datos)
prefix = SAT['cartaporte']['prefix']
node_carta = ET.SubElement(self._complemento, f'{prefix}:CartaPorte', atributos)
node = ET.SubElement(node_carta, f'{prefix}:Ubicaciones')
for ubicacion in ubicaciones:
domicilio = ubicacion.pop('domicilio', {})
2022-01-03 19:14:20 -06:00
dt = parser.parse(ubicacion['FechaHoraSalidaLlegada'])
ubicacion['FechaHoraSalidaLlegada'] = dt.isoformat()[:19]
sub_node = ET.SubElement(node, f'{prefix}:Ubicacion', ubicacion)
if domicilio:
ET.SubElement(sub_node, f'{prefix}:Domicilio', domicilio)
2022-01-03 19:14:20 -06:00
attr = mercancias
mercancias = attr.pop('mercancias')
autotransporte = attr.pop('autotransporte')
identificacion = autotransporte.pop('identificacion')
seguros = autotransporte.pop('seguros')
2022-01-17 21:49:35 -06:00
remolque = autotransporte.pop('remolque')
2022-01-03 19:14:20 -06:00
node = ET.SubElement(node_carta, f'{prefix}:Mercancias', attr)
for mercancia in mercancias:
ET.SubElement(node, f'{prefix}:Mercancia', mercancia)
sub_node = ET.SubElement(node, f'{prefix}:Autotransporte', autotransporte)
ET.SubElement(sub_node, f'{prefix}:IdentificacionVehicular', identificacion)
ET.SubElement(sub_node, f'{prefix}:Seguros', seguros)
if 'SubTipoRem' in remolque and 'Placa' in remolque \
and remolque['SubTipoRem'] and remolque['Placa']:
2022-01-17 21:49:35 -06:00
tmp = ET.SubElement(sub_node, f'{prefix}:Remolques')
ET.SubElement(tmp, f'{prefix}:Remolque', remolque)
if tiposfigura:
sub_node = ET.SubElement(node_carta, f'{prefix}:FiguraTransporte')
for figura in tiposfigura:
ET.SubElement(sub_node, f'{prefix}:TiposFigura', figura)
2022-01-03 19:14:20 -06:00
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)
if self._comercio_exterior:
prefix = SAT['comercioe']['prefix']
datos = datos.pop('comercioe')
2017-10-10 18:49:05 -05:00
emisor = datos.pop('emisor')
# ~ propietario = datos.pop('propietario')
2017-10-10 18:49:05 -05:00
receptor = datos.pop('receptor')
destinatario = datos.pop('destinatario')
conceptos = datos.pop('mercancias')
2017-10-10 18:49:05 -05:00
# ~ 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'
attr = {'Version': SAT['comercioe']['version']}
attr.update(datos)
2017-10-10 18:49:05 -05:00
ce = ET.SubElement(
self._complemento, f'{prefix}:ComercioExterior', attr)
2017-10-10 18:49:05 -05:00
attributes = {}
if 'Curp' in emisor:
attributes = {'Curp': emisor.pop('Curp')}
node = ET.SubElement(ce, '{}:Emisor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), emisor)
2017-10-10 18:49:05 -05:00
# ~ if propietario:
# ~ ET.SubElement(ce, '{}:Propietario'.format(prefix), propietario)
2017-10-10 18:49:05 -05:00
attributes = {}
if 'NumRegIdTrib' in receptor:
attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
node = ET.SubElement(ce, '{}:Receptor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), receptor)
2017-10-10 18:49:05 -05:00
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(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), destinatario)
2017-10-10 18:49:05 -05:00
node = ET.SubElement(ce, '{}:Mercancias'.format(prefix))
2017-10-10 18:49:05 -05:00
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(prefix), row)
2017-10-10 18:49:05 -05:00
if detalle:
ET.SubElement(
concepto, '{}:DescripcionesEspecificas'.format(prefix), detalle)
2017-10-10 18:49:05 -05:00
return