diff --git a/source/helper/util.py b/source/helper/util.py index d3a3798..6f9a0d7 100644 --- a/source/helper/util.py +++ b/source/helper/util.py @@ -10,6 +10,9 @@ from settings import DEBUG, log, PATH_XSLT, DELETE_FILES, PAC_AUTH from helper.comercio import PACComercioDigital as PAC +from . import util2 + + def _call(args): return subprocess.check_output(args, shell=True).decode() @@ -80,6 +83,7 @@ class DictToCfdi(): self._cfdi = None self._root = None self._attr_complementos = {} + self._node_addenda = None self._make_cfdi() @property @@ -95,6 +99,7 @@ class DictToCfdi(): self._conceptos() self._impuestos() self._complementos() + self._addenda() xml = ET.tostring(self._root, pretty_print=True, xml_declaration=True, encoding='utf-8') @@ -233,6 +238,74 @@ class DictToCfdi(): ET.SubElement(node_leyendas, node_name, leyenda) return + def _addenda(self): + type_addenda = self._data['addenda'].get('type', '') + data = self._data['addenda'].get('boveda', False) + self._boveda(data) + if type_addenda: + data = self._data['addenda'].get('cliente', False) + partes = self._data['addenda']['partes'] + getattr(self, f'_addenda_{type_addenda}')(data, partes) + return + + def _boveda(self, data): + if not data: + return + + XMLNS = 'http://kontender.mx/namespace/boveda' + NSMAP = { + 'bovadd': 'http://kontender.mx/namespace/boveda', + 'kon': 'http://kontender.mx/namespace', + } + + node_name = f'{{{self._XMLNS}}}Addenda' + self._node_addenda = ET.SubElement(self._root, node_name) + + schema = 'http://kontender.mx/namespace/boveda http://kontender.mx/namespace/boveda/BOVEDAFISCAL.xsd http://kontender.mx/namespace http://kontender.mx/namespace/AddendaK.xsd' + attr_qname = ET.QName( + 'http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation') + schema = {attr_qname: schema} + + node_name = f'{{{XMLNS}}}BOVEDAFISCAL' + node = ET.SubElement(self._node_addenda, node_name, schema, nsmap=NSMAP) + + for k, v in data.items(): + node_name = f'{{{XMLNS}}}{k}' + n = ET.SubElement(node, node_name) + n.text = v + + return + + def _addenda_02(self, data, partes): + if not data: + return + + XMLNS = 'http://www.sas-automative/en/locations/local-offices-and-plants/mexico/plant-puebla.html' + NSMAP = {'PMT': XMLNS} + version = data.pop('version') + attr = {'version': version} + node_name = f'{{{XMLNS}}}Factura' + node = ET.SubElement(self._node_addenda, node_name, **attr, nsmap=NSMAP) + + for key, attr in data.items(): + node_name = f'{{{XMLNS}}}{key}' + ET.SubElement(node, node_name, **attr) + + if not partes: + return + + node_name = f'{{{XMLNS}}}Partes' + node = ET.SubElement(node, node_name) + + for parte in partes: + referencias = parte.pop('referencias') + node_name = f'{{{XMLNS}}}Parte' + sub_node = ET.SubElement(node, node_name, **parte) + node_name = f'{{{XMLNS}}}Referencias' + ET.SubElement(sub_node, node_name, **referencias) + + return + class DataToDict(): TRASLADO = 'T' @@ -245,11 +318,16 @@ class DataToDict(): '05': '_conceptos', '06': '_impuestos', '10': '_leyendas', + '50': '_boveda', + '51': '_addenda', + '52': '_addenda_partes', } def __init__ (self, data): self._data = data - self._cfdi = {'conceptos': [], 'complementos': {}} + self._cfdi = {'conceptos': [], 'complementos': {}, 'addenda': {}} + self._type_header = '' + self._partes = [] self._process_data() @property @@ -268,6 +346,8 @@ class DataToDict(): continue if hasattr(self, header): getattr(self, header)(parts[2:]) + + self._cfdi['addenda']['partes'] = self._partes return def _comprobante(self, data): @@ -434,6 +514,68 @@ class DataToDict(): self._cfdi['complementos']['leyendas'] = leyendas return + def _boveda(self, data): + type_addenda = data[0] + fields = ( + 'Razon_Social_destino', + 'Calle_Destino', + 'Colonia_Destino', + 'Ciudad_Destino', + 'Estado_Destino', + 'Pais_Destino', + 'CP_Destino_consigan', + 'RFC_Destino_consigna', + 'Telefono_Receptor', + 'Peso_Bruto', + 'Peso_Neto', + 'Incoterm', + 'leyenda_pie', + 'R.vto', + 'TIPO_CAMBIO_FACTURA', + 'R.cte', + 'RI_Solicitante', + 'R.fefa', + 'Razon_Social_facturado', + 'Calle_facturado', + 'Colonia_facturado', + 'RFC_destino', + 'Telefono_facturado', + 'NUMCTAPAGO', + ) + boveda = {} + for i, f in enumerate(fields): + boveda[f] = data[i+1] + + self._cfdi['addenda']['type'] = type_addenda + self._cfdi['addenda']['boveda'] = boveda + + return + + def _addenda(self, header): + self._type_header = header[1] + if self._type_header == '4': + data = {'version': header[3]} + data['Moneda'] = {'tipoMoneda': header[7]} + data['Proveedor'] = {'codigo': header[10], 'nombre': header[11]} + data['Referencias'] = {'referenciaProveedor': header[16]} + + self._cfdi['addenda']['cliente'] = data + return + + def _addenda_partes(self, parte): + if self._type_header == '4': + attr = {'posicion': parte[0], + 'numeroMaterial': parte[1], + 'descripcionMaterial': parte[2], + 'referencias': { + 'ordenCompra': parte[7], + 'numeroPKN': parte[8], + 'numeroASN': parte[9], + } + } + self._partes.append(attr) + return + def stamp_cfdi(cfdi, cert): xslt = open(PATH_XSLT, 'rb') @@ -486,6 +628,8 @@ def make_cfdi(source, target, dir_cert, nombre): cert = Cert(dir_cert, nombre) paths = _get_files(source, 'txt') for path in paths: + # ~ _version33(path, target) + # ~ continue data = _read_file(path) data = DataToDict(data).cfdi cfdi = DictToCfdi(data).cfdi @@ -515,3 +659,16 @@ def stamp_pac(source, target): f.write(result['xml']) log.info(f'\tTimbrada: {new_path}') return + + +# ~ To delete + +def _version33(path, target): + from .cfdi_xml import CFDI + + data = util2.load_data(path) + cfdi = CFDI() + xml = cfdi.get_xml(data) + _save_file(path, target, xml) + + return