se extrae generacion de CUFE a DianXMLExtensionCUFE
FossilOrigin-Name: 964ad19875edfc7f6990f795396eeeb4c66809eefe93303b1479d6c3ad2e2484
This commit is contained in:
		| @@ -80,7 +80,8 @@ def generate_invoice(private_key, passphrase, scriptname): | |||||||
|  |  | ||||||
|     invoice = module.invoice() |     invoice = module.invoice() | ||||||
|     invoice.calculate() |     invoice.calculate() | ||||||
|     xml = form.DIANInvoiceXML(invoice) |     params = module.params() | ||||||
|  |     xml = form.DIANInvoiceXML(invoice, **params) | ||||||
|      |      | ||||||
|     extensions = module.extensions(invoice) |     extensions = module.extensions(invoice) | ||||||
|     for extension in extensions: |     for extension in extensions: | ||||||
|   | |||||||
| @@ -2,4 +2,5 @@ from .fe import FeXML | |||||||
| from .fe import NAMESPACES | from .fe import NAMESPACES | ||||||
| from .fe import DianXMLExtensionSigner | from .fe import DianXMLExtensionSigner | ||||||
| from .fe import DianXMLExtensionSoftwareSecurityCode | from .fe import DianXMLExtensionSoftwareSecurityCode | ||||||
|  | from .fe import DianXMLExtensionCUFE | ||||||
| from .fe import DianZIP | from .fe import DianZIP | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import zipfile | |||||||
| import warnings | import warnings | ||||||
| import hashlib | import hashlib | ||||||
| from contextlib import contextmanager | from contextlib import contextmanager | ||||||
|  | from .data import dian | ||||||
|  |  | ||||||
| NAMESPACES = { | NAMESPACES = { | ||||||
|     'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', |     'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', | ||||||
| @@ -39,6 +40,73 @@ class FeXML(FachoXML): | |||||||
|         #self.find_or_create_element(self._cn) |         #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): | class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension): | ||||||
|     # RESOLUCION 0001: pagina 535 |     # RESOLUCION 0001: pagina 535 | ||||||
|   | |||||||
| @@ -92,7 +92,6 @@ class Invoice: | |||||||
|         self.invoice_period_end = None |         self.invoice_period_end = None | ||||||
|         self.invoice_issue = None |         self.invoice_issue = None | ||||||
|         self.invoice_ident = None |         self.invoice_ident = None | ||||||
|         self.invoice_cufe = None |  | ||||||
|         self.invoice_legal_monetary_total = LegalMonetaryTotal(0, 0, 0, 0, 0) |         self.invoice_legal_monetary_total = LegalMonetaryTotal(0, 0, 0, 0, 0) | ||||||
|         self.invoice_customer = None |         self.invoice_customer = None | ||||||
|         self.invoice_supplier = None |         self.invoice_supplier = None | ||||||
| @@ -170,18 +169,11 @@ class DianResolucion0001Validator: | |||||||
|  |  | ||||||
|  |  | ||||||
| class DIANInvoiceXML(fe.FeXML): | class DIANInvoiceXML(fe.FeXML): | ||||||
|     AMBIENTE_PRUEBAS = 'Pruebas' |  | ||||||
|     AMBIENTE_PRODUCCION = 'Producción' |  | ||||||
|  |  | ||||||
|     def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS, clave_tecnica = ''): |     def __init__(self, invoice): | ||||||
|         super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1') |         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) |         self.attach_invoice(invoice) | ||||||
|  |  | ||||||
|     def _tipo_ambiente(self): |  | ||||||
|         return int(dian.TipoAmbiente[self.tipo_ambiente]['code']) |  | ||||||
|  |  | ||||||
|     def attach_invoice(self, invoice): |     def attach_invoice(self, invoice): | ||||||
|         """adiciona etiquetas a FEXML y retorna FEXML |         """adiciona etiquetas a FEXML y retorna FEXML | ||||||
|         en caso de fallar validacion retorna None""" |         en caso de fallar validacion retorna None""" | ||||||
| @@ -189,11 +181,7 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|  |  | ||||||
|         invoice.calculate() |         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: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: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/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')) |         fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:StartDate', invoice.invoice_period_start.strftime('%Y-%m-%d')) | ||||||
| @@ -266,52 +254,3 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|     def issue_date(self, datetime_): |     def issue_date(self, datetime_): | ||||||
|         return datetime_.strftime('%Y-%m-%d') |         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() |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -94,6 +94,9 @@ def test_invoicesimple_build_with_cufe(simple_invoice): | |||||||
|     simple_invoice.validate(invoice_validator) |     simple_invoice.validate(invoice_validator) | ||||||
|     assert invoice_validator.valid() == True |     assert invoice_validator.valid() == True | ||||||
|     xml = form.DIANInvoiceXML(simple_invoice) |     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') |     cufe = xml.get_element_text('/fe:Invoice/cbc:UUID') | ||||||
|     assert cufe != '' |     assert cufe != '' | ||||||
|  |  | ||||||
| @@ -139,6 +142,9 @@ def test_invoice_line_count_numeric(simple_invoice): | |||||||
|      |      | ||||||
| def test_invoice_profileexecutionid(simple_invoice): | def test_invoice_profileexecutionid(simple_invoice): | ||||||
|     xml_invoice = form.DIANInvoiceXML(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) |     id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:ProfileExecutionID', format_=int) | ||||||
|     assert id_ == 2 |     assert id_ == 2 | ||||||
|  |  | ||||||
| @@ -190,9 +196,15 @@ def test_invoice_cufe(simple_invoice_without_lines): | |||||||
|         def issue_date(self, datetime_): |         def issue_date(self, datetime_): | ||||||
|             return '2019-01-16' |             return '2019-01-16' | ||||||
|  |  | ||||||
|     xml_invoice = FakeDIANInvoiceXML(simple_invoice, |     xml_invoice = FakeDIANInvoiceXML(simple_invoice) | ||||||
|                                      tipo_ambiente = form.DIANInvoiceXML.AMBIENTE_PRODUCCION, |                                       | ||||||
|                                      clave_tecnica = '693ff6f2a553c3646a063436fd4dd9ded0311471') |     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') |     cufe = xml_invoice.get_element_text('/fe:Invoice/cbc:UUID') | ||||||
|     # RESOLUCION 004: pagina 689 |     # RESOLUCION 004: pagina 689 | ||||||
|     assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4' |     assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user