From 52632babb2816b0e1b100e10534012fa01518084 Mon Sep 17 00:00:00 2001 From: "bit4bit@riseup.net" Date: Thu, 28 May 2020 00:49:14 +0000 Subject: [PATCH] se extrae generacion de CUFE a DianXMLExtensionCUFE FossilOrigin-Name: 964ad19875edfc7f6990f795396eeeb4c66809eefe93303b1479d6c3ad2e2484 --- facho/cli.py | 5 +-- facho/facho.py | 2 +- facho/fe/__init__.py | 1 + facho/fe/fe.py | 68 +++++++++++++++++++++++++++++++++++++++++ facho/fe/form.py | 71 +++---------------------------------------- tests/test_fe_form.py | 18 +++++++++-- 6 files changed, 93 insertions(+), 72 deletions(-) diff --git a/facho/cli.py b/facho/cli.py index feaeda6..ef5e476 100644 --- a/facho/cli.py +++ b/facho/cli.py @@ -80,12 +80,13 @@ def generate_invoice(private_key, passphrase, scriptname): invoice = module.invoice() invoice.calculate() - xml = form.DIANInvoiceXML(invoice) + params = module.params() + xml = form.DIANInvoiceXML(invoice, **params) extensions = module.extensions(invoice) for extension in extensions: xml.add_extension(extension) - + if private_key: signer = fe.DianXMLExtensionSigner(private_key, passphrase=passphrase) xml.add_extension(signer) diff --git a/facho/facho.py b/facho/facho.py index da2aa65..123972c 100644 --- a/facho/facho.py +++ b/facho/facho.py @@ -136,7 +136,7 @@ class FachoXML: elem = self.set_element('/'+ root_tag + xpath, elements) else: for new_element in elements: - elem = self.find_or_create_element('/'+ root_tag + xpath) + elem = self.find_or_create_element('/' + root_tag + xpath) self.builder.append(elem, new_element) def fragment(self, xpath, append=False): diff --git a/facho/fe/__init__.py b/facho/fe/__init__.py index 2b35d35..3b4484b 100644 --- a/facho/fe/__init__.py +++ b/facho/fe/__init__.py @@ -2,4 +2,5 @@ from .fe import FeXML from .fe import NAMESPACES from .fe import DianXMLExtensionSigner from .fe import DianXMLExtensionSoftwareSecurityCode +from .fe import DianXMLExtensionCUFE from .fe import DianZIP diff --git a/facho/fe/fe.py b/facho/fe/fe.py index 787c33a..aaf6b74 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -10,6 +10,7 @@ import zipfile import warnings import hashlib from contextlib import contextmanager +from .data import dian NAMESPACES = { 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', @@ -39,7 +40,74 @@ class FeXML(FachoXML): #self.find_or_create_element(self._cn) +class DianXMLExtensionCUFE(FachoXMLExtension): + AMBIENTE_PRUEBAS = 'Pruebas' + AMBIENTE_PRODUCCION = 'Producción' + + def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS, clave_tecnica = ''): + self.tipo_ambiente = tipo_ambiente + self.clave_tecnica = clave_tecnica + self.invoice = invoice + def _tipo_ambiente(self): + return int(dian.TipoAmbiente[self.tipo_ambiente]['code']) + + def build(self, fachoxml): + cufe = self._generate_cufe(self.invoice, fachoxml) + fachoxml.set_element('/fe:Invoice/cbc:UUID[schemaName="CUFE-SHA384"]', cufe) + fachoxml.set_element('/fe:Invoice/cbc:ProfileExecutionID', self._tipo_ambiente()) + return '', [] + + def _generate_cufe(self, invoice, fachoxml): + NumFac = invoice.invoice_ident + FecFac = fachoxml.issue_date(invoice.invoice_issue) + HoraFac = fachoxml.issue_time(invoice.invoice_issue) + ValorBruto = invoice.invoice_legal_monetary_total.line_extension_amount + ValorTotalPagar = invoice.invoice_legal_monetary_total.payable_amount + ValorImpuestoPara = {} + ValorImpuesto1 = 0.0 + CodImpuesto1 = 1 + ValorImpuesto2 = 0.0 + CodImpuesto2 = 4 + ValorImpuesto3 = 0.0 + CodImpuesto3 = 3 + for invoice_line in invoice.invoice_lines: + for subtotal in invoice_line.tax.subtotals: + # TODO cual es la naturaleza de tax_scheme_ident? + codigo_impuesto = int(subtotal.tax_scheme_ident) + ValorImpuestoPara.setdefault(codigo_impuesto, 0.0) + ValorImpuestoPara[codigo_impuesto] += subtotal.tax_amount + + NitOFE = invoice.invoice_supplier.ident + NumAdq = invoice.invoice_customer.ident + TipoAmb = self._tipo_ambiente() + ClTec = str(self.clave_tecnica) + + formatVars = [ + '%s' % NumFac, + '%s' % FecFac, + '%s' % HoraFac, + '%.02f' % ValorBruto, + '%02d' % CodImpuesto1, + '%.02f' % ValorImpuestoPara.get(CodImpuesto1, 0.0), + '%02d' % CodImpuesto2, + '%.02f' % ValorImpuestoPara.get(CodImpuesto2, 0.0), + '%02d' % CodImpuesto3, + '%.02f' % ValorImpuestoPara.get(CodImpuesto3, 0.0), + '%.02f' % ValorTotalPagar, + '%s' % NitOFE, + '%s' % NumAdq, + '%s' % ClTec, + '%d' % TipoAmb, + ] + cufe = "".join(formatVars) + + # crear hash... + h = hashlib.sha384() + h.update(cufe.encode('utf-8')) + return h.hexdigest() + + class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension): # RESOLUCION 0001: pagina 535 diff --git a/facho/fe/form.py b/facho/fe/form.py index c15260b..464dced 100644 --- a/facho/fe/form.py +++ b/facho/fe/form.py @@ -92,7 +92,6 @@ class Invoice: self.invoice_period_end = None self.invoice_issue = None self.invoice_ident = None - self.invoice_cufe = None self.invoice_legal_monetary_total = LegalMonetaryTotal(0, 0, 0, 0, 0) self.invoice_customer = None self.invoice_supplier = None @@ -168,19 +167,12 @@ class DianResolucion0001Validator: def valid(self): return not self.errors - -class DIANInvoiceXML(fe.FeXML): - AMBIENTE_PRUEBAS = 'Pruebas' - AMBIENTE_PRODUCCION = 'Producción' - - def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS, clave_tecnica = ''): - super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1') - self.tipo_ambiente = tipo_ambiente - self.clave_tecnica = clave_tecnica - self.attach_invoice(invoice) - def _tipo_ambiente(self): - return int(dian.TipoAmbiente[self.tipo_ambiente]['code']) +class DIANInvoiceXML(fe.FeXML): + + def __init__(self, invoice): + super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1') + self.attach_invoice(invoice) def attach_invoice(self, invoice): """adiciona etiquetas a FEXML y retorna FEXML @@ -189,11 +181,7 @@ class DIANInvoiceXML(fe.FeXML): invoice.calculate() - cufe = self._generate_cufe(invoice) - - fexml.set_element('/fe:Invoice/cbc:ProfileExecutionID', self._tipo_ambiente()) fexml.set_element('/fe:Invoice/cbc:ID', invoice.invoice_ident) - fexml.set_element('/fe:Invoice/cbc:UUID[schemaName="CUFE-SHA384"]', cufe) fexml.set_element('/fe:Invoice/cbc:IssueDate', self.issue_date(invoice.invoice_issue)) fexml.set_element('/fe:Invoice/cbc:IssueTime', self.issue_time(invoice.invoice_issue)) fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:StartDate', invoice.invoice_period_start.strftime('%Y-%m-%d')) @@ -265,53 +253,4 @@ class DIANInvoiceXML(fe.FeXML): return datetime_.strftime('%H:%M:%S%z') def issue_date(self, datetime_): return datetime_.strftime('%Y-%m-%d') - - def _generate_cufe(self, invoice): - NumFac = invoice.invoice_ident - FecFac = self.issue_date(invoice.invoice_issue) - HoraFac = self.issue_time(invoice.invoice_issue) - ValorBruto = invoice.invoice_legal_monetary_total.line_extension_amount - ValorTotalPagar = invoice.invoice_legal_monetary_total.payable_amount - ValorImpuestoPara = {} - ValorImpuesto1 = 0.0 - CodImpuesto1 = 1 - ValorImpuesto2 = 0.0 - CodImpuesto2 = 4 - ValorImpuesto3 = 0.0 - CodImpuesto3 = 3 - for invoice_line in invoice.invoice_lines: - for subtotal in invoice_line.tax.subtotals: - # TODO cual es la naturaleza de tax_scheme_ident? - codigo_impuesto = int(subtotal.tax_scheme_ident) - ValorImpuestoPara.setdefault(codigo_impuesto, 0.0) - ValorImpuestoPara[codigo_impuesto] += subtotal.tax_amount - - NitOFE = invoice.invoice_supplier.ident - NumAdq = invoice.invoice_customer.ident - TipoAmb = self._tipo_ambiente() - ClTec = str(self.clave_tecnica) - - formatVars = [ - '%s' % NumFac, - '%s' % FecFac, - '%s' % HoraFac, - '%.02f' % ValorBruto, - '%02d' % CodImpuesto1, - '%.02f' % ValorImpuestoPara.get(CodImpuesto1, 0.0), - '%02d' % CodImpuesto2, - '%.02f' % ValorImpuestoPara.get(CodImpuesto2, 0.0), - '%02d' % CodImpuesto3, - '%.02f' % ValorImpuestoPara.get(CodImpuesto3, 0.0), - '%.02f' % ValorTotalPagar, - '%s' % NitOFE, - '%s' % NumAdq, - '%s' % ClTec, - '%d' % TipoAmb, - ] - cufe = "".join(formatVars) - - # crear hash... - h = hashlib.sha384() - h.update(cufe.encode('utf-8')) - return h.hexdigest() diff --git a/tests/test_fe_form.py b/tests/test_fe_form.py index b35194a..b3684d2 100644 --- a/tests/test_fe_form.py +++ b/tests/test_fe_form.py @@ -94,6 +94,9 @@ def test_invoicesimple_build_with_cufe(simple_invoice): simple_invoice.validate(invoice_validator) assert invoice_validator.valid() == True xml = form.DIANInvoiceXML(simple_invoice) + cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice) + xml.add_extension(cufe_extension) + xml.attach_extensions() cufe = xml.get_element_text('/fe:Invoice/cbc:UUID') assert cufe != '' @@ -139,6 +142,9 @@ def test_invoice_line_count_numeric(simple_invoice): def test_invoice_profileexecutionid(simple_invoice): xml_invoice = form.DIANInvoiceXML(simple_invoice) + cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice) + xml_invoice.add_extension(cufe_extension) + xml_invoice.attach_extensions() id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:ProfileExecutionID', format_=int) assert id_ == 2 @@ -190,9 +196,15 @@ def test_invoice_cufe(simple_invoice_without_lines): def issue_date(self, datetime_): return '2019-01-16' - xml_invoice = FakeDIANInvoiceXML(simple_invoice, - tipo_ambiente = form.DIANInvoiceXML.AMBIENTE_PRODUCCION, - clave_tecnica = '693ff6f2a553c3646a063436fd4dd9ded0311471') + xml_invoice = FakeDIANInvoiceXML(simple_invoice) + + cufe_extension = fe.DianXMLExtensionCUFE( + simple_invoice, + tipo_ambiente = fe.DianXMLExtensionCUFE.AMBIENTE_PRODUCCION, + clave_tecnica = '693ff6f2a553c3646a063436fd4dd9ded0311471' + ) + xml_invoice.add_extension(cufe_extension) + xml_invoice.attach_extensions() cufe = xml_invoice.get_element_text('/fe:Invoice/cbc:UUID') # RESOLUCION 004: pagina 689 assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4'