diff --git a/facho/fe/data/dian/codelist/TipoImpuesto-2.1.gc b/facho/fe/data/dian/codelist/TipoImpuesto-2.1.gc index 90a591a..c70c793 100644 --- a/facho/fe/data/dian/codelist/TipoImpuesto-2.1.gc +++ b/facho/fe/data/dian/codelist/TipoImpuesto-2.1.gc @@ -1,170 +1,171 @@ - - - - - TipoImpuesto - Tipo de Tributos - 1 - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto-2.1 - http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoImpuesto-2.1.gc - - DIAN (Dirección de Impuestos y Aduanas Nacionales) - 195 - - - - - Code - Codigo Comun - - - - Name - Nombre - - - - CodeKey - - - - - - - 01 - - - IVA - - - - - 02 - - - IC - - - - - 03 - - - ICA - - - - - 04 - - - INC - - - - - 05 - - - ReteIVA - - - - - 06 - - - ReteFuente - - - - - 07 - - - ReteICA - - - - - 08 - - - ReteCREE - - - - - 20 - - - FtoHorticultura - - - - - 21 - - - Timbre - - - - - 22 - - - Bolsas - - - - - 23 - - - INCarbono - - - - - 24 - - - INCombustibles - - - - - 25 - - - Sobretasa Combustibles - - - - - 26 - - - Sordicom - - - - - 30 - - - Impuesto al Consumo de Datos - - - - - ZZ - - - Nombre de la figura tributaria - - - + + + + + TipoImpuesto + Tipo de Tributos + 1 + urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto + urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto-2.1 + http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoImpuesto-2.1.gc + + DIAN (Dirección de Impuestos y Aduanas Nacionales) + 195 + + + + + Code + Codigo Comun + + + + Name + Nombre + + + + CodeKey + + + + + + + 01 + + + IVA + + + + + 02 + + + IC + + + + + 03 + + + ICA + + + + + 04 + + + INC + + + + + 05 + + + ReteIVA + + + + + 06 + + + ReteRenta + + + + + 07 + + + ReteICA + + + + + 08 + + + ReteCREE + + + + + 20 + + + FtoHorticultura + + + + + 21 + + + Timbre + + + + + 22 + + + Bolsas + + + + + 23 + + + INCarbono + + + + + 24 + + + INCombustibles + + + + + 25 + + + Sobretasa Combustibles + + + + + 26 + + + Sordicom + + + + + 30 + + + Impuesto al Consumo de Datos + + + + + + ZZ + + + Nombre de la figura tributaria + + + diff --git a/facho/fe/form/__init__.py b/facho/fe/form/__init__.py index a215693..729173a 100644 --- a/facho/fe/form/__init__.py +++ b/facho/fe/form/__init__.py @@ -1,6 +1,5 @@ # This file is part of facho. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. - import hashlib from functools import reduce import copy @@ -11,7 +10,6 @@ from collections import defaultdict import decimal from decimal import Decimal import typing - from ..data.dian import codelist DECIMAL_PRECISION = 6 @@ -266,7 +264,6 @@ class TaxScheme: code: str name: str = '' - def __post_init__(self): if self.code not in codelist.TipoImpuesto: raise ValueError("code not found") @@ -325,7 +322,6 @@ class TaxTotal: def calculate(self, invline): self.taxable_amount = invline.total_amount - for subtax in self.subtotals: subtax.calculate(invline) self.tax_amount += subtax.tax_amount @@ -338,6 +334,36 @@ class TaxTotalOmit(TaxTotal): def calculate(self, invline): pass +@dataclass +class WithholdingTaxSubTotal: + percent: float + scheme: typing.Optional[TaxScheme] = None + tax_amount: Amount = Amount(0.0) + + def calculate(self, invline): + if self.percent is not None: + self.tax_amount = invline.total_amount * Amount(self.percent / 100) + +@dataclass +class WithholdingTaxTotal: + subtotals: list + tax_amount: Amount = Amount(0.0) + taxable_amount: Amount = Amount(0.0) + + def calculate(self, invline): + self.taxable_amount = invline.total_amount + + for subtax in self.subtotals: + subtax.calculate(invline) + self.tax_amount += subtax.tax_amount + +class WithholdingTaxTotalOmit(WithholdingTaxTotal): + def __init__(self): + super().__init__([]) + + def calculate(self, invline): + pass + @dataclass class Price: amount: Amount @@ -474,7 +500,7 @@ class InvoiceLine: # la factura y el percent es unico por type_code # de subtotal tax: typing.Optional[TaxTotal] - + withholding: typing.Optional[WithholdingTaxTotal] allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(default_factory=list) def add_allowance_charge(self, charge): @@ -518,8 +544,17 @@ class InvoiceLine: def taxable_amount(self): return self.tax.taxable_amount + @property + def withholding_amount(self): + return self.withholding.tax_amount + + @property + def withholding_taxable_amount(self): + return self.withholding.taxable_amount + def calculate(self): self.tax.calculate(self) + self.withholding.calculate(self) def __post_init__(self): if not isinstance(self.quantity, Quantity): @@ -527,6 +562,9 @@ class InvoiceLine: if self.tax is None: self.tax = TaxTotalOmit() + + if self.withholding is None: + self.withholding = WithholdingTaxTotalOmit() @dataclass class LegalMonetaryTotal: diff --git a/facho/fe/form_xml/invoice.py b/facho/fe/form_xml/invoice.py index 42b277a..e27e0e5 100644 --- a/facho/fe/form_xml/invoice.py +++ b/facho/fe/form_xml/invoice.py @@ -147,7 +147,6 @@ class DIANInvoiceXML(fe.FeXML): fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', invoice.invoice_customer.tax_scheme.code) - #DIAN 1.7.-2020: CAJ41 fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', invoice.invoice_customer.tax_scheme.name) @@ -421,6 +420,7 @@ class DIANInvoiceXML(fe.FeXML): def set_invoice_totals(fexml, invoice): tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) + withholding_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) percent_for = defaultdict(lambda: None) #requeridos para CUFE @@ -433,23 +433,34 @@ class DIANInvoiceXML(fe.FeXML): #tax_amount_for['03']['taxable_amount'] += 0.0 total_tax_amount = Amount(0.0) + total_withholding_amount = Amount(0.0) for invoice_line in invoice.invoice_lines: for subtotal in invoice_line.tax.subtotals: if subtotal.scheme is not None: tax_amount_for[subtotal.scheme.code]['tax_amount'] += subtotal.tax_amount tax_amount_for[subtotal.scheme.code]['taxable_amount'] += invoice_line.taxable_amount - + # MACHETE ojo InvoiceLine.tax pasar a Invoice percent_for[subtotal.scheme.code] = subtotal.percent total_tax_amount += subtotal.tax_amount + for subtotal_withholding in invoice_line.withholding.subtotals: + if subtotal_withholding.scheme is not None: + withholding_amount_for[subtotal_withholding.scheme.code]['tax_amount'] += subtotal_withholding.tax_amount + withholding_amount_for[subtotal_withholding.scheme.code]['taxable_amount'] += invoice_line.withholding_taxable_amount + + # MACHETE ojo InvoiceLine.tax pasar a Invoice + + percent_for[subtotal_withholding.scheme.code] = subtotal_withholding.percent + + total_withholding_amount += subtotal_withholding.tax_amount + if total_tax_amount != Amount(0.0): fexml.placeholder_for('./cac:TaxTotal') fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount', total_tax_amount) - for index, item in enumerate(tax_amount_for.items()): cod_impuesto, amount_of = item @@ -487,7 +498,44 @@ class DIANInvoiceXML(fe.FeXML): cod_impuesto) line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', 'IVA') - + + for index, item in enumerate(withholding_amount_for.items()): + cod_impuesto, amount_of = item + next_append = index > 0 + + #DIAN 1.7.-2020: FAS01 + line = fexml.fragment('./cac:WithholdingTaxTotal', append=next_append) + #DIAN 1.7.-2020: FAU06 + tax_amount = amount_of['tax_amount'] + fexml.set_element_amount_for(line, + '/cac:WithholdingTaxTotal/cbc:TaxAmount', + tax_amount) + + #DIAN 1.7.-2020: FAS05 + fexml.set_element_amount_for(line, + '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', + amount_of['taxable_amount']) + + #DIAN 1.7.-2020: FAU06 + fexml.set_element_amount_for(line, + '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount', + amount_of['tax_amount']) + + #DIAN 1.7.-2020: FAS07 + if percent_for[cod_impuesto]: + line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:Percent', + percent_for[cod_impuesto]) + + + if percent_for[cod_impuesto]: + line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', + percent_for[cod_impuesto]) + + line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', + cod_impuesto) + line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', + 'ReteRenta') + # abstract method def tag_document(fexml): return 'Invoice' @@ -515,7 +563,29 @@ class DIANInvoiceXML(fe.FeXML): #DIAN 1.7.-2020: FAX15 line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) - + + + def set_invoice_line_withholding(fexml, line, invoice_line): + fexml.set_element_amount_for(line, + './cac:WithholdingTaxTotal/cbc:TaxAmount', + invoice_line.withholding_amount) + #DIAN 1.7.-2020: FAX05 + fexml.set_element_amount_for(line, + './cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', + invoice_line.withholding_taxable_amount) + + for subtotal in invoice_line.withholding.subtotals: + line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP') + + if subtotal.percent is not None: + line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2)) + + if subtotal.scheme is not None: + #DIAN 1.7.-2020: FAX15 + line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) + line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) + + def set_invoice_lines(fexml, invoice): next_append = False for index, invoice_line in enumerate(invoice.invoice_lines): @@ -531,6 +601,9 @@ class DIANInvoiceXML(fe.FeXML): if not isinstance(invoice_line.tax, TaxTotalOmit): fexml.set_invoice_line_tax(line, invoice_line) + if not isinstance(invoice_line.withholding, WithholdingTaxTotalOmit): + fexml.set_invoice_line_withholding(line, invoice_line) + line.set_element('./cac:Item/cbc:Description', invoice_line.item.description) line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID', diff --git a/facho/fe/form_xml/support_document.py b/facho/fe/form_xml/support_document.py index 1b5c086..6bd48e9 100644 --- a/facho/fe/form_xml/support_document.py +++ b/facho/fe/form_xml/support_document.py @@ -499,6 +499,5 @@ class DIANSupportDocumentXML(fe.FeXML): return fexml def customize(fexml, invoice): - """adiciona etiquetas a FEXML y retorna FEXML en caso de fallar validacion retorna None"""