verificacion de cufe segun resolucion
FossilOrigin-Name: c7b12b74accfb0a8e3abea0b74d2734f4911cfc7594b5294d748d81f9c43fb03
This commit is contained in:
		| @@ -54,6 +54,7 @@ class TaxTotal: | |||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| class InvoiceLine: | class InvoiceLine: | ||||||
|  |     # RESOLUCION 0004: pagina 155 | ||||||
|     quantity: int |     quantity: int | ||||||
|     description: str |     description: str | ||||||
|     item_ident: int |     item_ident: int | ||||||
| @@ -66,11 +67,11 @@ class InvoiceLine: | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def total_tax_inclusive_amount(self): |     def total_tax_inclusive_amount(self): | ||||||
|         return self.tax.taxable_amount |         return self.tax.taxable_amount + self.tax.tax_amount | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def total_tax_exclusive_amount(self): |     def total_tax_exclusive_amount(self): | ||||||
|         return self.tax.tax_amount |         return self.tax.taxable_amount | ||||||
|  |  | ||||||
|     def calculate(self): |     def calculate(self): | ||||||
|         self.tax.calculate(self) |         self.tax.calculate(self) | ||||||
| @@ -125,17 +126,18 @@ class Invoice: | |||||||
|     def _calculate_legal_monetary_total(self): |     def _calculate_legal_monetary_total(self): | ||||||
|         for invline in self.invoice_lines: |         for invline in self.invoice_lines: | ||||||
|             self.invoice_legal_monetary_total.line_extension_amount += invline.total_amount |             self.invoice_legal_monetary_total.line_extension_amount += invline.total_amount | ||||||
|             self.invoice_legal_monetary_total.tax_exclusive_amount += invline.total_amount |             self.invoice_legal_monetary_total.tax_exclusive_amount += invline.total_tax_exclusive_amount | ||||||
|  |             self.invoice_legal_monetary_total.tax_inclusive_amount += invline.total_tax_inclusive_amount | ||||||
|             self.invoice_legal_monetary_total.charge_total_amount += invline.total_amount |             self.invoice_legal_monetary_total.charge_total_amount += invline.total_amount | ||||||
|  |         #self.invoice_legal_monetary_total.payable_amount = self.invoice_legal_monetary_total.tax_exclusive_amount \ | ||||||
|         self.invoice_legal_monetary_total.payable_amount = self.invoice_legal_monetary_total.tax_exclusive_amount \ |         #    + self.invoice_legal_monetary_total.line_extension_amount \ | ||||||
|             + self.invoice_legal_monetary_total.line_extension_amount \ |         #    + self.invoice_legal_monetary_total.tax_inclusive_amount | ||||||
|             + self.invoice_legal_monetary_total.tax_inclusive_amount |         self.invoice_legal_monetary_total.payable_amount = self.invoice_legal_monetary_total.tax_inclusive_amount | ||||||
|          |          | ||||||
|     def calculate(self): |     def calculate(self): | ||||||
|         self._calculate_legal_monetary_total() |  | ||||||
|         for invline in self.invoice_lines: |         for invline in self.invoice_lines: | ||||||
|             invline.calculate() |             invline.calculate() | ||||||
|  |         self._calculate_legal_monetary_total() | ||||||
|  |  | ||||||
|  |  | ||||||
| class DianResolucion0001Validator: | class DianResolucion0001Validator: | ||||||
| @@ -168,24 +170,32 @@ class DianResolucion0001Validator: | |||||||
|  |  | ||||||
|      |      | ||||||
| class DIANInvoiceXML(fe.FeXML): | class DIANInvoiceXML(fe.FeXML): | ||||||
|  |     AMBIENTE_PRUEBAS = 'Pruebas' | ||||||
|  |     AMBIENTE_PRODUCCION = 'Producción' | ||||||
|      |      | ||||||
|     def __init__(self, invoice, TipoAmbiente = 'Pruebas'): |     def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS, clave_tecnica = ''): | ||||||
|         super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1') |         super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1') | ||||||
|         self.attach_invoice(invoice, TipoAmbiente) |         self.tipo_ambiente = tipo_ambiente | ||||||
|  |         self.clave_tecnica = clave_tecnica | ||||||
|  |         self.attach_invoice(invoice) | ||||||
|  |  | ||||||
|     def attach_invoice(self, invoice, TipoAmbiente): |     def _tipo_ambiente(self): | ||||||
|  |         return int(dian.TipoAmbiente[self.tipo_ambiente]['code']) | ||||||
|  |  | ||||||
|  |     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""" | ||||||
|         fexml = self |         fexml = self | ||||||
|  |  | ||||||
|         invoice.calculate() |         invoice.calculate() | ||||||
|  |  | ||||||
|         cufe = self._generate_cufe(invoice, TipoAmbiente) |         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:UUID[schemaName="CUFE-SHA384"]', cufe) | ||||||
|         fexml.set_element('/fe:Invoice/cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) |         fexml.set_element('/fe:Invoice/cbc:IssueDate', self.issue_date(invoice.invoice_issue)) | ||||||
|         fexml.set_element('/fe:Invoice/cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S')) |         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')) | ||||||
|         fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:EndDate', invoice.invoice_period_end.strftime('%Y-%m-%d')) |         fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:EndDate', invoice.invoice_period_end.strftime('%Y-%m-%d')) | ||||||
|  |  | ||||||
| @@ -193,6 +203,8 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|  |  | ||||||
|         fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/cac:PartyIdentification/cbc:ID', |         fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/cac:PartyIdentification/cbc:ID', | ||||||
|                           invoice.invoice_supplier.ident) |                           invoice.invoice_supplier.ident) | ||||||
|  |         fexml.set_element('/Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', | ||||||
|  |                           invoice.invoice_supplier.ident) | ||||||
|         fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyTaxScheme/cbc:TaxLevelCode', |         fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyTaxScheme/cbc:TaxLevelCode', | ||||||
|                           invoice.invoice_supplier.responsability_code) |                           invoice.invoice_supplier.responsability_code) | ||||||
|         fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/cbc:AdditionalAccountID', |         fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/cbc:AdditionalAccountID', | ||||||
| @@ -211,6 +223,8 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|                           invoice.invoice_customer.organization_code) |                           invoice.invoice_customer.organization_code) | ||||||
|         fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/cac:PartyName/cbc:Name', |         fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/cac:PartyName/cbc:Name', | ||||||
|                           invoice.invoice_customer.name) |                           invoice.invoice_customer.name) | ||||||
|  |         fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', | ||||||
|  |                           invoice.invoice_customer.ident) | ||||||
|         fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationName', |         fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationName', | ||||||
|                           invoice.invoice_customer.legal_name) |                           invoice.invoice_customer.legal_name) | ||||||
|         fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PhysicalLocation/fe:Address/cac:AddressLine/cbc:Line', |         fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PhysicalLocation/fe:Address/cac:AddressLine/cbc:Line', | ||||||
| @@ -232,6 +246,7 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|                           invoice.invoice_legal_monetary_total.payable_amount, |                           invoice.invoice_legal_monetary_total.payable_amount, | ||||||
|                           currencyID='COP') |                           currencyID='COP') | ||||||
|  |  | ||||||
|  |         fexml.set_element('/fe:Invoice/cbc:LineCountNumeric', len(invoice.invoice_lines)) | ||||||
|         next_append = False |         next_append = False | ||||||
|         for index, invoice_line in enumerate(invoice.invoice_lines): |         for index, invoice_line in enumerate(invoice.invoice_lines): | ||||||
|             line = fexml.fragment('/fe:Invoice/fe:InvoiceLine', append=next_append) |             line = fexml.fragment('/fe:Invoice/fe:InvoiceLine', append=next_append) | ||||||
| @@ -243,13 +258,18 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|             line.set_element('/fe:InvoiceLine/fe:Price/cbc:PriceAmount', invoice_line.price_amount, currencyID="COP")  |             line.set_element('/fe:InvoiceLine/fe:Price/cbc:PriceAmount', invoice_line.price_amount, currencyID="COP")  | ||||||
|             line.set_element('/fe:InvoiceLine/fe:Item/cbc:Description', invoice_line.description) |             line.set_element('/fe:InvoiceLine/fe:Item/cbc:Description', invoice_line.description) | ||||||
|  |  | ||||||
|  |  | ||||||
|         return fexml |         return fexml | ||||||
|  |  | ||||||
|  |     def issue_time(self, datetime_): | ||||||
|  |         return datetime_.strftime('%H:%M:%S%z') | ||||||
|  |     def issue_date(self, datetime_): | ||||||
|  |         return datetime_.strftime('%Y-%m-%d') | ||||||
|      |      | ||||||
|     def _generate_cufe(self, invoice, TipoAmbiente = 'Pruebas'): |     def _generate_cufe(self, invoice): | ||||||
|         NumFac = invoice.invoice_ident |         NumFac = invoice.invoice_ident | ||||||
|         FecFac = invoice.invoice_issue.strftime('%Y-%m-%d') |         FecFac = self.issue_date(invoice.invoice_issue) | ||||||
|         HoraFac = invoice.invoice_issue.strftime('%H:%H:%S') |         HoraFac = self.issue_time(invoice.invoice_issue) | ||||||
|         ValorBruto = invoice.invoice_legal_monetary_total.line_extension_amount |         ValorBruto = invoice.invoice_legal_monetary_total.line_extension_amount | ||||||
|         ValorTotalPagar = invoice.invoice_legal_monetary_total.payable_amount |         ValorTotalPagar = invoice.invoice_legal_monetary_total.payable_amount | ||||||
|         ValorImpuestoPara = {} |         ValorImpuestoPara = {} | ||||||
| @@ -268,25 +288,27 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|  |  | ||||||
|         NitOFE = invoice.invoice_supplier.ident |         NitOFE = invoice.invoice_supplier.ident | ||||||
|         NumAdq = invoice.invoice_customer.ident |         NumAdq = invoice.invoice_customer.ident | ||||||
|         TipoAmb = int(dian.TipoAmbiente[TipoAmbiente]['code']) |         TipoAmb = self._tipo_ambiente() | ||||||
|  |         ClTec = str(self.clave_tecnica) | ||||||
|          |          | ||||||
|         formatVars = { |         formatVars = [ | ||||||
|             '%s': NumFac, |             '%s' % NumFac, | ||||||
|             '%s': FecFac, |             '%s' % FecFac, | ||||||
|             '%.02f': HoraFac, |             '%s' % HoraFac, | ||||||
|             '%.02f': ValorBruto, |             '%.02f' % ValorBruto, | ||||||
|             '%.02f': ValorTotalPagar, |             '%02d' % CodImpuesto1, | ||||||
|             '%.02f': ValorImpuestoPara.get(CodImpuesto1, 0.0), |             '%.02f' % ValorImpuestoPara.get(CodImpuesto1, 0.0), | ||||||
|             '%02d': CodImpuesto1, |             '%02d' % CodImpuesto2, | ||||||
|             '%.02f': ValorImpuestoPara.get(CodImpuesto2, 0.0), |             '%.02f' % ValorImpuestoPara.get(CodImpuesto2, 0.0), | ||||||
|             '%02d': CodImpuesto2, |             '%02d' % CodImpuesto3, | ||||||
|             '%.02f': ValorImpuestoPara.get(CodImpuesto3, 0.0), |             '%.02f' % ValorImpuestoPara.get(CodImpuesto3, 0.0), | ||||||
|             '%02d': CodImpuesto3, |             '%.02f' % ValorTotalPagar, | ||||||
|             '%s': NitOFE, |             '%s' % NitOFE, | ||||||
|             '%s': NumAdq, |             '%s' % NumAdq, | ||||||
|             '%d': TipoAmb, |             '%s' % ClTec, | ||||||
|         } |             '%d' % TipoAmb, | ||||||
|         cufe = "".join(formatVars.keys()) % tuple(formatVars.values()) |         ] | ||||||
|  |         cufe = "".join(formatVars) | ||||||
|  |  | ||||||
|         # crear hash... |         # crear hash... | ||||||
|         h = hashlib.sha384() |         h = hashlib.sha384() | ||||||
|   | |||||||
| @@ -14,6 +14,25 @@ import facho.fe.form as form | |||||||
| from facho import fe | from facho import fe | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def simple_invoice_without_lines(): | ||||||
|  |     inv = form.Invoice() | ||||||
|  |     inv.set_period(datetime.now(), datetime.now()) | ||||||
|  |     inv.set_issue(datetime.now()) | ||||||
|  |     inv.set_ident('ABC123') | ||||||
|  |     inv.set_supplier(form.Party( | ||||||
|  |         name = 'facho-supplier', | ||||||
|  |         ident = 123, | ||||||
|  |         responsability_code = 'No aplica', | ||||||
|  |         organization_code = 'Persona Natural' | ||||||
|  |     )) | ||||||
|  |     inv.set_customer(form.Party( | ||||||
|  |         name = 'facho-customer', | ||||||
|  |         ident = 321, | ||||||
|  |         responsability_code = 'No aplica', | ||||||
|  |         organization_code = 'Persona Natural' | ||||||
|  |     )) | ||||||
|  |     return inv | ||||||
|  |  | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def simple_invoice(): | def simple_invoice(): | ||||||
| @@ -110,7 +129,70 @@ def test_invoicesimple_zip(simple_invoice): | |||||||
|  |  | ||||||
| def test_bug_cbcid_empty_on_invoice_line(simple_invoice): | def test_bug_cbcid_empty_on_invoice_line(simple_invoice): | ||||||
|     xml_invoice = form.DIANInvoiceXML(simple_invoice) |     xml_invoice = form.DIANInvoiceXML(simple_invoice) | ||||||
|     print(str(xml_invoice)) |  | ||||||
|  |  | ||||||
|     cbc_id = xml_invoice.get_element_text('/fe:Invoice/fe:InvoiceLine[1]/cbc:ID', format_=int) |     cbc_id = xml_invoice.get_element_text('/fe:Invoice/fe:InvoiceLine[1]/cbc:ID', format_=int) | ||||||
|     assert cbc_id == 1 |     assert cbc_id == 1 | ||||||
|  |  | ||||||
|  | def test_invoice_line_count_numeric(simple_invoice): | ||||||
|  |     xml_invoice = form.DIANInvoiceXML(simple_invoice) | ||||||
|  |     count = xml_invoice.get_element_text('/fe:Invoice/cbc:LineCountNumeric', format_=int) | ||||||
|  |     assert count == len(simple_invoice.invoice_lines) | ||||||
|  |      | ||||||
|  | def test_invoice_profileexecutionid(simple_invoice): | ||||||
|  |     xml_invoice = form.DIANInvoiceXML(simple_invoice) | ||||||
|  |     id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:ProfileExecutionID', format_=int) | ||||||
|  |     assert id_ == 2 | ||||||
|  |  | ||||||
|  | def test_invoice_totals(simple_invoice_without_lines): | ||||||
|  |     simple_invoice = simple_invoice_without_lines | ||||||
|  |     simple_invoice.invoice_ident = '323200000129' | ||||||
|  |     simple_invoice.invoice_issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z') | ||||||
|  |     simple_invoice.invoice_supplier.ident = '700085371' | ||||||
|  |     simple_invoice.invoice_customer.ident = '800199436' | ||||||
|  |     simple_invoice.add_invoice_line(form.InvoiceLine( | ||||||
|  |         quantity = 1, | ||||||
|  |         description = 'producto', | ||||||
|  |         item_ident = 9999, | ||||||
|  |         price_amount = 1_500_000, | ||||||
|  |         tax = form.TaxTotal( | ||||||
|  |             subtotals = [ | ||||||
|  |                 form.TaxSubTotal( | ||||||
|  |                     tax_scheme_ident = '01', | ||||||
|  |                     percent = 19.0 | ||||||
|  |                 )]) | ||||||
|  |     )) | ||||||
|  |     simple_invoice.calculate() | ||||||
|  |     assert 1 == len(simple_invoice.invoice_lines) | ||||||
|  |     assert 1_500_000 == simple_invoice.invoice_legal_monetary_total.line_extension_amount | ||||||
|  |     assert 1_785_000 == simple_invoice.invoice_legal_monetary_total.payable_amount | ||||||
|  |  | ||||||
|  | def test_invoice_cufe(simple_invoice_without_lines): | ||||||
|  |     simple_invoice = simple_invoice_without_lines | ||||||
|  |     simple_invoice.invoice_ident = '323200000129' | ||||||
|  |     simple_invoice.invoice_issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z') | ||||||
|  |     simple_invoice.invoice_supplier.ident = '700085371' | ||||||
|  |     simple_invoice.invoice_customer.ident = '800199436' | ||||||
|  |     simple_invoice.add_invoice_line(form.InvoiceLine( | ||||||
|  |         quantity = 1, | ||||||
|  |         description = 'producto', | ||||||
|  |         item_ident = 9999, | ||||||
|  |         price_amount = 1_500_000, | ||||||
|  |         tax = form.TaxTotal( | ||||||
|  |             subtotals = [ | ||||||
|  |                 form.TaxSubTotal( | ||||||
|  |                     tax_scheme_ident = '01', | ||||||
|  |                     percent = 19.0 | ||||||
|  |                 )]) | ||||||
|  |     )) | ||||||
|  |              | ||||||
|  |     class FakeDIANInvoiceXML(form.DIANInvoiceXML): | ||||||
|  |         def issue_time(self, datetime_): | ||||||
|  |             return '10:53:10-05:00' | ||||||
|  |         def issue_date(self, datetime_): | ||||||
|  |             return '2019-01-16' | ||||||
|  |  | ||||||
|  |     xml_invoice = FakeDIANInvoiceXML(simple_invoice, | ||||||
|  |                                      tipo_ambiente = form.DIANInvoiceXML.AMBIENTE_PRODUCCION, | ||||||
|  |                                      clave_tecnica = '693ff6f2a553c3646a063436fd4dd9ded0311471') | ||||||
|  |     cufe = xml_invoice.get_element_text('/fe:Invoice/cbc:UUID') | ||||||
|  |  | ||||||
|  |     assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user