diff --git a/facho/fe/data/dian/codelist/TipoImpuesto-2.1.custom.gc b/facho/fe/data/dian/codelist/TipoImpuesto-2.1.custom.gc new file mode 100644 index 0000000..8b5caea --- /dev/null +++ b/facho/fe/data/dian/codelist/TipoImpuesto-2.1.custom.gc @@ -0,0 +1,42 @@ + + + + + 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 + + + + + + + ZY + + + No causa + + + + diff --git a/facho/fe/data/dian/codelist/__init__.py b/facho/fe/data/dian/codelist/__init__.py index 63c874c..a474393 100644 --- a/facho/fe/data/dian/codelist/__init__.py +++ b/facho/fe/data/dian/codelist/__init__.py @@ -86,7 +86,8 @@ TipoResponsabilidad = CodeList(path_for_codelist('TipoResponsabilidad-2.1.gc'), .update(CodeList(path_for_codelist('TipoResponsabilidad-2.1.custom.gc'), 'code', 'name')) TipoAmbiente = CodeList(path_for_codelist('TipoAmbiente-2.1.gc'), 'code', 'name') TipoDocumento = CodeList(path_for_codelist('TipoDocumento-2.1.gc'), 'code', 'name') -TipoImpuesto = CodeList(path_for_codelist('TipoImpuesto-2.1.gc'), 'code', 'name') +TipoImpuesto = CodeList(path_for_codelist('TipoImpuesto-2.1.gc'), 'code', 'name')\ + .update(CodeList(path_for_codelist('TipoImpuesto-2.1.custom.gc'), 'code', 'name')) CodigoPrecioReferencia = CodeList(path_for_codelist('CodigoPrecioReferencia-2.1.gc'), 'code', 'name') MediosPago = CodeList(path_for_codelist('MediosPago-2.1.gc'), 'code', 'name') RegimenFiscal = CodeList(path_for_codelist('RegimenFiscal-2.1.custom.gc'), 'code', 'name') diff --git a/facho/fe/fe.py b/facho/fe/fe.py index 2cc3b47..c07b483 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -135,15 +135,17 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): build_vars['ValorBruto'] = invoice.invoice_legal_monetary_total.line_extension_amount build_vars['ValorTotalPagar'] = invoice.invoice_legal_monetary_total.payable_amount ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0)) - build_vars['CodImpuesto1'] = 1 - build_vars['CodImpuesto2'] = 4 - build_vars['CodImpuesto3'] = 3 + build_vars['CodImpuesto1'] = '01' + build_vars['CodImpuesto2'] = '04' + build_vars['CodImpuesto3'] = '03' 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, form.Amount(0.0)) - ValorImpuestoPara[codigo_impuesto] += subtotal.tax_amount + if subtotal.scheme is not None: + # TODO cual es la naturaleza de tax_scheme_ident? + codigo_impuesto = subtotal.scheme.code + ValorImpuestoPara.setdefault(codigo_impuesto, form.Amount(0.0)) + ValorImpuestoPara[codigo_impuesto] += subtotal.tax_amount + build_vars['ValorImpuestoPara'] = ValorImpuestoPara build_vars['NitOFE'] = invoice.invoice_supplier.ident build_vars['NumAdq'] = invoice.invoice_customer.ident @@ -184,11 +186,11 @@ class DianXMLExtensionCUFE(DianXMLExtensionCUDFE): '%s' % build_vars['FecFac'], '%s' % build_vars['HoraFac'], form.Amount(build_vars['ValorBruto']).format('%.02f'), - '%02d' % CodImpuesto1, + CodImpuesto1, form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).format('%.02f'), - '%02d' % CodImpuesto2, + CodImpuesto2, form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto2, 0.0)).format('%.02f'), - '%02d' % CodImpuesto3, + CodImpuesto3, form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto3, 0.0)).format('%.02f'), form.Amount(build_vars['ValorTotalPagar']).format('%.02f'), '%s' % build_vars['NitOFE'], @@ -221,11 +223,11 @@ class DianXMLExtensionCUDE(DianXMLExtensionCUDFE): '%s' % build_vars['FecFac'], '%s' % build_vars['HoraFac'], form.Amount(build_vars['ValorBruto']).format('%.02f'), - '%02d' % CodImpuesto1, + CodImpuesto1, form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).format('%.02f'), - '%02d' % CodImpuesto2, + CodImpuesto2, form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto2, 0.0)).format('%.02f'), - '%02d' % CodImpuesto3, + CodImpuesto3, form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto3, 0.0)).format('%.02f'), form.Amount(build_vars['ValorTotalPagar']).format('%.02f'), '%s' % build_vars['NitOFE'], diff --git a/facho/fe/form/__init__.py b/facho/fe/form/__init__.py index e2a24ea..93d55e8 100644 --- a/facho/fe/form/__init__.py +++ b/facho/fe/form/__init__.py @@ -272,16 +272,29 @@ class Party: if self.organization_code not in codelist.TipoOrganizacion: raise ValueError("organization_code not found") + +@dataclass +class TaxScheme: + code: str = '01' + + @property + def name(self): + return codelist.TipoImpuesto[self.code]['name'] + + def __post_init__(self): + if self.code not in codelist.TipoImpuesto: + raise ValueError("code [%s] not found" % (self.code)) + + @dataclass class TaxSubTotal: percent: float - tax_scheme_ident: str = '01' - tax_scheme_name: str = 'IVA' - + scheme: typing.Optional[TaxScheme] = None tax_amount: Amount = Amount(0.0) def calculate(self, invline): - self.tax_amount = invline.total_amount * Amount(self.percent / 100) + if self.percent is not None: + self.tax_amount = invline.total_amount * Amount(self.percent / 100) @dataclass @@ -298,6 +311,13 @@ class TaxTotal: self.tax_amount += subtax.tax_amount +class TaxTotalOmit(TaxTotal): + def __init__(self): + super().__init__([]) + + def calculate(self, invline): + pass + @dataclass class Price: amount: Amount @@ -377,7 +397,8 @@ class InvoiceLine: @property def total_tax_inclusive_amount(self): - return self.tax.taxable_amount + self.tax.tax_amount + # FAU06 + return self.total_amount + self.tax.tax_amount @property def total_tax_exclusive_amount(self): diff --git a/facho/fe/form_xml/invoice.py b/facho/fe/form_xml/invoice.py index 335ee4e..d9a8caf 100644 --- a/facho/fe/form_xml/invoice.py +++ b/facho/fe/form_xml/invoice.py @@ -424,8 +424,8 @@ class DIANInvoiceXML(fe.FeXML): percent_for = defaultdict(lambda: None) #requeridos para CUFE - tax_amount_for['01']['tax_amount'] = Amount(0.0) - tax_amount_for['01']['taxable_amount'] = Amount(0.0) + #tax_amount_for['01']['tax_amount'] = Amount(0.0) + #tax_amount_for['01']['taxable_amount'] = Amount(0.0) #DIAN 1.7.-2020: FAS07 => Se debe construir estrategia para su manejo #tax_amount_for['04']['tax_amount'] = 0.0 #tax_amount_for['04']['taxable_amount'] = 0.0 @@ -435,13 +435,15 @@ class DIANInvoiceXML(fe.FeXML): total_tax_amount = Amount(0.0) for invoice_line in invoice.invoice_lines: - for subtotal in invoice_line.tax.subtotals: - tax_amount_for[subtotal.tax_scheme_ident]['tax_amount'] += subtotal.tax_amount - tax_amount_for[subtotal.tax_scheme_ident]['taxable_amount'] += invoice_line.taxable_amount + 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 - # MACHETE ojo InvoiceLine.tax pasar a Invoice - percent_for[subtotal.tax_scheme_ident] = subtotal.percent fexml.placeholder_for('./cac:TaxTotal') fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount', @@ -491,6 +493,27 @@ class DIANInvoiceXML(fe.FeXML): def tag_document_concilied(fexml): return 'Invoiced' + def set_invoice_line_tax(fexml, line, invoice_line): + fexml.set_element_amount_for(line, + './cac:TaxTotal/cbc:TaxAmount', + invoice_line.tax_amount) + + #DIAN 1.7.-2020: FAX05 + fexml.set_element_amount_for(line, + './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', + invoice_line.taxable_amount) + + for subtotal in invoice_line.tax.subtotals: + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP') + + if subtotal.percent is not None: + line.set_element('./cac:TaxTotal/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: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_lines(fexml, invoice): next_append = False for index, invoice_line in enumerate(invoice.invoice_lines): @@ -502,20 +525,10 @@ class DIANInvoiceXML(fe.FeXML): fexml.set_element_amount_for(line, './cbc:LineExtensionAmount', invoice_line.total_amount) - fexml.set_element_amount_for(line, - './cac:TaxTotal/cbc:TaxAmount', - invoice_line.tax_amount) - #DIAN 1.7.-2020: FAX05 - fexml.set_element_amount_for(line, - './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', - invoice_line.taxable_amount) + if not isinstance(invoice_line.tax, TaxTotalOmit): + fexml.set_invoice_line_tax(line, invoice_line) - for subtotal in invoice_line.tax.subtotals: - line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP') - line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', subtotal.percent) - line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.tax_scheme_ident) - line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.tax_scheme_name) line.set_element('./cac:Item/cbc:Description', invoice_line.item.description) line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID',