2017-10-10 18:49:05 -05:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
from xml.etree import ElementTree as ET
|
|
|
|
from xml.dom.minidom import parseString
|
|
|
|
|
|
|
|
from logbook import Logger
|
|
|
|
|
|
|
|
#~ from settings import DEBUG
|
|
|
|
|
|
|
|
|
|
|
|
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',
|
|
|
|
'schema': 'http://www.sat.gob.mx/nomina http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina11.xsd',
|
|
|
|
},
|
|
|
|
'nomina12': {
|
|
|
|
'version': '1.2',
|
|
|
|
'prefix': 'nomina',
|
|
|
|
'xmlns': 'http://www.sat.gob.mx/nomina12',
|
|
|
|
'schema': 'http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd',
|
|
|
|
},
|
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',
|
|
|
|
},
|
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
|
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-20 00:47:23 -06:00
|
|
|
#~ if 'complementos' in datos:
|
|
|
|
#~ self._complementos(datos['complementos'])
|
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'))
|
|
|
|
|
|
|
|
def add_sello(self, sello):
|
|
|
|
self._cfdi.attrib['Sello'] = sello
|
|
|
|
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):
|
2017-11-19 00:42:16 -06:00
|
|
|
if datos['impuestos']['total_locales_trasladados'] or \
|
|
|
|
datos['impuestos']['total_locales_retenciones']:
|
|
|
|
self._impuestos_locales = True
|
|
|
|
|
2017-11-19 21:59:14 -06:00
|
|
|
if datos['donativo']:
|
|
|
|
self._donativo = True
|
|
|
|
|
2017-11-20 00:47:23 -06:00
|
|
|
if 'ine' in datos['complementos']:
|
|
|
|
self._ine = True
|
|
|
|
|
2017-10-10 18:49:05 -05:00
|
|
|
if 'nomina' in datos:
|
|
|
|
return self._validate_nomina(datos)
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _validate_nomina(self, datos):
|
|
|
|
comprobante = datos['comprobante']
|
|
|
|
|
|
|
|
validators = (
|
|
|
|
('MetodoDePago', 'NA'),
|
|
|
|
('TipoCambio', '1'),
|
|
|
|
('Moneda', 'MXN'),
|
|
|
|
('TipoDeComprobante', 'egreso'),
|
|
|
|
)
|
|
|
|
for f, v in validators:
|
|
|
|
if f in comprobante:
|
|
|
|
if v != comprobante[f]:
|
|
|
|
msg = 'El atributo: {}, debe ser: {}'.format(f, v)
|
|
|
|
self.error = msg
|
|
|
|
return False
|
|
|
|
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']
|
|
|
|
schema_donativo = SAT['ine']['schema']
|
|
|
|
|
2017-11-19 00:42:16 -06:00
|
|
|
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
|
2017-11-20 00:47:23 -06:00
|
|
|
schema_locales + schema_donativo +schema_ine
|
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):
|
|
|
|
conceptos = ET.SubElement(self._cfdi, '{}:Conceptos'.format(self._pre))
|
|
|
|
for row in datos:
|
|
|
|
complemento = {}
|
|
|
|
if 'complemento' in row:
|
|
|
|
complemento = row.pop('complemento')
|
2017-12-06 23:30:40 -06:00
|
|
|
cuenta_predial = row.pop('CuentaPredial', '')
|
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)
|
|
|
|
|
|
|
|
if 'InformacionAduanera' in row:
|
|
|
|
for field in fields:
|
|
|
|
if field in row['InformacionAduanera']:
|
|
|
|
attributes[field] = row['InformacionAduanera'][field]
|
|
|
|
if attributes:
|
|
|
|
node_name = '{}:InformacionAduanera'.format(self._pre)
|
|
|
|
ET.SubElement(concepto, node_name, attributes)
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
if 'autRVOE' in row:
|
|
|
|
fields = (
|
|
|
|
'version',
|
|
|
|
'nombreAlumno',
|
|
|
|
'CURP',
|
|
|
|
'nivelEducativo',
|
|
|
|
'autRVOE',
|
|
|
|
)
|
|
|
|
for field in fields:
|
|
|
|
if field in row['autRVOE']:
|
|
|
|
attributes[field] = row['autRVOE'][field]
|
|
|
|
node_name = '{}:ComplementoConcepto'.format(self._pre)
|
|
|
|
complemento = ET.SubElement(concepto, node_name)
|
|
|
|
ET.SubElement(complemento, 'iedu:instEducativas', attributes)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _impuestos(self, datos):
|
|
|
|
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):
|
|
|
|
sat_nomina = SAT[NOMINA_ACTUAL]
|
|
|
|
pre = sat_nomina['prefix']
|
|
|
|
complemento = ET.SubElement(self._cfdi, '{}:Complemento'.format(self._pre))
|
|
|
|
|
|
|
|
emisor = datos.pop('Emisor', None)
|
|
|
|
receptor = datos.pop('Receptor', None)
|
|
|
|
percepciones = datos.pop('Percepciones', None)
|
|
|
|
deducciones = datos.pop('Deducciones', None)
|
|
|
|
|
|
|
|
attributes = {}
|
|
|
|
attributes['xmlns:{}'.format(pre)] = sat_nomina['xmlns']
|
|
|
|
attributes['xsi:schemaLocation'] = sat_nomina['schema']
|
|
|
|
attributes.update(datos)
|
|
|
|
|
|
|
|
if not 'Version' in attributes:
|
|
|
|
attributes['Version'] = sat_nomina['version']
|
|
|
|
|
|
|
|
nomina = ET.SubElement(complemento, '{}:Nomina'.format(pre), attributes)
|
|
|
|
if emisor:
|
|
|
|
ET.SubElement(nomina, '{}:Emisor'.format(pre), emisor)
|
|
|
|
if receptor:
|
|
|
|
ET.SubElement(nomina, '{}:Receptor'.format(pre), receptor)
|
|
|
|
if percepciones:
|
|
|
|
detalle = percepciones.pop('detalle', None)
|
|
|
|
percepciones = ET.SubElement(nomina, '{}:Percepciones'.format(pre), percepciones)
|
|
|
|
for row in detalle:
|
|
|
|
ET.SubElement(percepciones, '{}:Percepcion'.format(pre), row)
|
|
|
|
if deducciones:
|
|
|
|
detalle = deducciones.pop('detalle', None)
|
|
|
|
deducciones = ET.SubElement(nomina, '{}:Deducciones'.format(pre), deducciones)
|
|
|
|
for row in detalle:
|
|
|
|
ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row)
|
|
|
|
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))
|
|
|
|
|
|
|
|
if 'ine' in datos:
|
|
|
|
atributos = {'Version': SAT['ine']['version']}
|
|
|
|
atributos.update(datos['ine'])
|
|
|
|
ET.SubElement(self._complemento, 'ine:INE', atributos)
|
|
|
|
|
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
|