diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index b1e8d46..87944ea 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -30,6 +30,7 @@ import requests import sqlite3 import socket import subprocess +import sys import tempfile import textwrap import threading @@ -44,7 +45,6 @@ from pathlib import Path from xml.etree import ElementTree as ET from xml.dom.minidom import parseString - try: import uno from com.sun.star.beans import PropertyValue @@ -52,9 +52,20 @@ try: from com.sun.star.view.PaperFormat import LETTER APP_LIBO = True except ImportError: - APP_LIBO = False + path_fun = '/usr/lib/libreoffice/program/fundamentalrc' + path_uno = '/usr/lib/libreoffice/program/' + os.environ['URE_BOOTSTRAP'] = f'vnd.sun.star.pathname:{path_fun}' + os.environ['UNO_PATH'] = path_uno + sys.path.append(path_uno) + try: + import uno + from com.sun.star.beans import PropertyValue + from com.sun.star.awt import Size + from com.sun.star.view.PaperFormat import LETTER + APP_LIBO = True + except ImportError: + APP_LIBO = False -# ~ import pyqrcode from dateutil import parser from lxml import etree @@ -69,7 +80,6 @@ from settings import DEBUG, MV, log, template_lookup, COMPANIES, DB_SAT, \ PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES, DIR_FACTURAS from settings import USAR_TOKEN, API, DECIMALES_TAX -# ~ from .configpac import AUTH from .utils import get_qr @@ -951,6 +961,7 @@ class LIBO(object): return # ~ print(data) + qr = data.pop('qr', False) figuras = data.pop('figuras') mercancias = data.pop('mercancias') detalle = mercancias.pop('detalle') @@ -1019,7 +1030,27 @@ class LIBO(object): cell_3 = self._set_cell(v=unidad, cell=cell_3) cell_4 = self._set_cell(v=cantidad, cell=cell_4) cell_5 = self._set_cell(v=peso, cell=cell_5) + if qr: + self._timbre_carta(qr) + return + def _timbre_carta(self, qr): + pd = self._sheet.getDrawPage() + image = self._template.createInstance('com.sun.star.drawing.GraphicObjectShape') + gp = self._create_instance('com.sun.star.graphic.GraphicProvider') + pd.add(image) + + instance = 'com.sun.star.io.SequenceInputStream' + stream = self._create_instance(instance) + stream.initialize((uno.ByteSequence(qr.getvalue()),)) + properties = self._set_properties({'InputStream': stream}) + image.Graphic = gp.queryGraphic(properties) + + s = Size() + s.Width = 4000 + s.Height = 4000 + image.setSize(s) + image.Anchor = self._set_cell('{cp.qr}') return def _comercio_exterior(self, data): @@ -1690,7 +1721,7 @@ def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'): version = f'{version}_cn_{version_nomina}' if 'carta_porte' in data: - default = 'plantilla_factura_ccp.ods' + default = 'plantilla_ccp.ods' version = '{}_ccp_{}'.format(version, data['carta_porte']['version']) if data.get('pagos', False): @@ -1844,24 +1875,6 @@ def to_letters(value, currency): return NumLet(value, currency).letras -# ~ def get_qr(data, p=True): - # ~ qr = pyqrcode.create(data, mode='binary') - # ~ if p: - # ~ path = get_path_temp('.qr') - # ~ qr.png(path, scale=7) - # ~ return path - - # ~ buffer = io.BytesIO() - # ~ qr.png(buffer, scale=8) - # ~ return base64.b64encode(buffer.getvalue()).decode() - - -# ~ def get_qr2(data, kind='svg'): - # ~ buffer = io.BytesIO() - # ~ segno.make(data).save(buffer, kind=kind, scale=8, border=2) - # ~ return buffer - - def _get_relacionados(doc, version): node = doc.find('{}CfdiRelacionados'.format(PRE[version])) if node is None: @@ -3146,7 +3159,8 @@ def parse_xml2(xml_str): def get_idccp(): - uuid_v4 = uuid.uuid4() - custom_uuid_str = f"CCC{uuid_v4.hex[3:8].upper()}-{uuid_v4.hex[8:12].upper()}-{uuid_v4.hex[12:16].upper()}-{uuid_v4.hex[16:20].upper()}-{uuid_v4.hex[20:32].upper()}" + uuid4 = str(uuid.uuid4()).upper() + custom_uuid_str = f'CCC{uuid4[3:]}' + # ~ custom_uuid_str = f"CCC{uuid_v4.hex[3:8].upper()}-{uuid_v4.hex[8:12].upper()}-{uuid_v4.hex[12:16].upper()}-{uuid_v4.hex[16:20].upper()}-{uuid_v4.hex[20:32].upper()}" return custom_uuid_str diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py index 313a788..5ef7bb1 100644 --- a/source/app/controllers/utils.py +++ b/source/app/controllers/utils.py @@ -261,9 +261,11 @@ class CfdiToDict(object): 'cfdi4.0': 'http://www.sat.gob.mx/cfd/4', } NS = { + 'tfd': 'http://www.sat.gob.mx/TimbreFiscalDigital', 'divisas': 'http://www.sat.gob.mx/divisas', 'leyendasFisc': 'http://www.sat.gob.mx/leyendasFiscales', 'cartaporte20': 'http://www.sat.gob.mx/CartaPorte20', + 'cartaporte30': 'http://www.sat.gob.mx/CartaPorte30', 'nomina12': 'http://www.sat.gob.mx/nomina12', 'cce20': 'http://www.sat.gob.mx/ComercioExterior20', } @@ -397,11 +399,23 @@ class CfdiToDict(object): self.version = self._root.attrib['Version'] ns = f'cfdi{self.version}' self.NS['cfdi'] = self.NS_VERSION[ns] + self._timbre_fiscal() self._informacion_global() self._receptor() self._complementos() return + def _timbre_fiscal(self): + path = '//tfd:TimbreFiscalDigital' + data = self._root.xpath(path, namespaces=self.NS) + if not data: + return + + data = data[0] + attr = CaseInsensitiveDict(data.attrib) + self._fecha_timbrado = attr['FechaTimbrado'] + return + def _informacion_global(self): self._values['informacion_global'] = {} @@ -514,10 +528,75 @@ class CfdiToDict(object): self._values['carta_porte'] = values + self._complemento_carta_porte(complemento) self._complemento_comercio_exterior(complemento) return + def _complemento_carta_porte(self, complemento): + path = '//cartaporte30:CartaPorte' + carta_porte = complemento.xpath(path, namespaces=self.NS) + if carta_porte: + self._get_carta_porte_3(carta_porte) + return + + def _get_carta_porte_3(self, carta_porte): + URL = 'https://verificacfdi.facturaelectronica.sat.gob.mx/verificaccp/default.aspx' + PRE = '//cartaporte30' + values = CaseInsensitiveDict(carta_porte[0].attrib) + idccp = values['idccp'] + for node in carta_porte[0]: + if 'FiguraTransporte' in node.tag: + figuras = CaseInsensitiveDict(node[0].attrib) + figuras['TipoFigura'] = self.tipo_figura[figuras['TipoFigura']] + values['figuras'] = figuras + elif 'Mercancias' in node.tag: + mercancias = CaseInsensitiveDict(node.attrib) + detalle = [CaseInsensitiveDict(n.attrib) + for n in node if 'Mercancia' in n.tag] + values['mercancias'] = { + 'mercancias': mercancias, + 'detalle': detalle, + } + + path = f'{PRE}:Autotransporte' + node_auto = node.xpath(path, namespaces=self.NS)[0] + values_auto = CaseInsensitiveDict(node_auto.attrib) + values['autotransporte'] = values_auto + + path = f'{PRE}:IdentificacionVehicular' + node_tmp = node_auto.xpath(path, namespaces=self.NS)[0] + values_auto = CaseInsensitiveDict(node_tmp.attrib) + values['autotransporte'].update(values_auto) + + path = f'{PRE}:Seguros' + node_tmp = node_auto.xpath(path, namespaces=self.NS)[0] + values_auto = CaseInsensitiveDict(node_tmp.attrib) + values['autotransporte'].update(values_auto) + + path = f'{PRE}:Remolques' + try: + node_tmp = node_auto.xpath(path, namespaces=self.NS)[0][0] + values_auto = CaseInsensitiveDict(node_tmp.attrib) + values['autotransporte'].update(values_auto) + except IndexError: + pass + elif 'Ubicaciones' in node.tag: + ubicaciones = [] + for n in node: + ubicacion = CaseInsensitiveDict(n.attrib) + if ubicacion['TipoUbicacion'] == 'Origen': + fecha_origen = ubicacion['FechaHoraSalidaLlegada'] + ubicacion['domicilio'] = self._set_carta_porte_domicilio( + CaseInsensitiveDict(n[0].attrib)) + ubicaciones.append(ubicacion) + values['FechaOrigen'] = fecha_origen + values['ubicaciones'] = ubicaciones + qr_data = f'{URL}?IdCCP={idccp}&FechaOrig={fecha_origen}&FechaTimb={self._fecha_timbrado}' + values['qr'] = get_qr(qr_data, 'png') + self._values['carta_porte'] = values + return + def _complemento_comercio_exterior(self, complemento): path = '//cce20:ComercioExterior' comercio_exterior = complemento.xpath(path, namespaces=self.NS) diff --git a/source/app/models/main.py b/source/app/models/main.py index 4551753..072ca1a 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -4823,16 +4823,6 @@ class Facturas(BaseModel): obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion) values['tiporelacion'] = str(obj) - # ~ 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): diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 125fb21..67db55a 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -882,68 +882,9 @@ function guardar_y_timbrar(values){ var usar_cartaporte = $$('chk_cfdi_usar_cartaporte').getValue() if(usar_cartaporte){ - //~ var total_distance = 0.00 - //~ var total_weight = 0.00 - //~ var cartaporte = { - //~ TranspInternac: $$('lst_carta_TranspInternac').getValue(), - //~ TotalDistRec: total_distance, - //~ } - //~ var ubicaciones = $$('grid_carta_ubicaciones').data.getRange() - //~ ubicaciones.forEach(function(row, index){ - //~ delete row['id'] - //~ delete row['delete'] - //~ if(row['DistanciaRecorrida']){ - //~ total_distance += parseFloat(row['DistanciaRecorrida']) - //~ } - //~ }) - //~ cartaporte['TotalDistRec'] = total_distance - //~ cartaporte['ubicaciones'] = ubicaciones - - //~ var row = $$('grid_carta_autotransporte').data.getRange()[0] - //~ var autotransporte = { - //~ PermSCT: row['PermSCT'], - //~ NumPermisoSCT: row['NumPermisoSCT'], - //~ identificacion: { - //~ ConfigVehicular: row['ConfigVehicular'], - //~ PlacaVM: row['PlacaVM'], - //~ AnioModeloVM: row['AnioModeloVM'], - //~ }, - //~ seguros: { - //~ AseguraRespCivil: row['AseguraRespCivil'], - //~ PolizaRespCivil: row['PolizaRespCivil'], - //~ }, - //~ remolque: { - //~ SubTipoRem: row['SubTipoRem'], - //~ Placa: row['Placa'], - //~ } - //~ } - - //~ var mercancias = $$('grid_carta_mercancias').data.getRange() - //~ mercancias.forEach(function(row, index){ - //~ delete row['id'] - //~ delete row['delete'] - //~ row['Cantidad'] = String(row['Cantidad']) - //~ row['ValorMercancia'] = String(row['ValorMercancia']) - //~ if(row['PesoEnKg']){ - //~ total_weight += parseFloat(row['PesoEnKg']) - //~ } - //~ }) - //~ var mercancias = { - //~ 'PesoBrutoTotal': 0.00, - //~ 'UnidadPeso': $$('lst_carta_UnidadPeso').getValue(), - //~ 'NumTotalMercancias': String(mercancias.length), - //~ mercancias: mercancias, - //~ autotransporte: autotransporte, - //~ } - //~ cartaporte['mercancias'] = mercancias - - //~ var tiposfigura = $$('grid_carta_tipos_figuras').data.getRange() - //~ tiposfigura.forEach(function(row, index){ - //~ delete row['id'] - //~ }) - //~ cartaporte['tiposfigura'] = tiposfigura var result = _get_values_carta_porte() data['cartaporte'] = result.values + $$('chk_cfdi_usar_cartaporte').setValue(false) } var usar_comercioe = $$('chk_cfdi_usar_comercioe').getValue() diff --git a/source/templates/plantilla_ccp.ods b/source/templates/plantilla_ccp.ods new file mode 100644 index 0000000..2339002 Binary files /dev/null and b/source/templates/plantilla_ccp.ods differ