add RTE to invoice
FossilOrigin-Name: 8d9aa1694a61e0e358e4cc8c5ecb1b3544d18e353535491f5b8a576c5b5fe446
This commit is contained in:
		| @@ -75,7 +75,7 @@ | |||||||
| 				<SimpleValue>06</SimpleValue> | 				<SimpleValue>06</SimpleValue> | ||||||
| 			</Value> | 			</Value> | ||||||
| 			<Value ColumnRef="name"> | 			<Value ColumnRef="name"> | ||||||
| 				<SimpleValue>ReteFuente</SimpleValue> | 				<SimpleValue>ReteRenta</SimpleValue> | ||||||
| 			</Value> | 			</Value> | ||||||
| 		</Row> | 		</Row> | ||||||
| 		<Row> | 		<Row> | ||||||
| @@ -158,6 +158,7 @@ | |||||||
| 				<SimpleValue>Impuesto al Consumo de Datos</SimpleValue> | 				<SimpleValue>Impuesto al Consumo de Datos</SimpleValue> | ||||||
| 			</Value> | 			</Value> | ||||||
| 		</Row> | 		</Row> | ||||||
|  |   | ||||||
| 		<Row> | 		<Row> | ||||||
| 			<Value ColumnRef="code"> | 			<Value ColumnRef="code"> | ||||||
| 				<SimpleValue>ZZ</SimpleValue> | 				<SimpleValue>ZZ</SimpleValue> | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| # This file is part of facho.  The COPYRIGHT file at the top level of | # This file is part of facho.  The COPYRIGHT file at the top level of | ||||||
| # this repository contains the full copyright notices and license terms. | # this repository contains the full copyright notices and license terms. | ||||||
|  |  | ||||||
| import hashlib | import hashlib | ||||||
| from functools import reduce | from functools import reduce | ||||||
| import copy | import copy | ||||||
| @@ -11,7 +10,6 @@ from collections import defaultdict | |||||||
| import decimal | import decimal | ||||||
| from decimal import Decimal | from decimal import Decimal | ||||||
| import typing | import typing | ||||||
|  |  | ||||||
| from ..data.dian import codelist | from ..data.dian import codelist | ||||||
|  |  | ||||||
| DECIMAL_PRECISION = 6 | DECIMAL_PRECISION = 6 | ||||||
| @@ -266,7 +264,6 @@ class TaxScheme: | |||||||
|     code: str |     code: str | ||||||
|     name: str = '' |     name: str = '' | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __post_init__(self): |     def __post_init__(self): | ||||||
|         if self.code not in codelist.TipoImpuesto: |         if self.code not in codelist.TipoImpuesto: | ||||||
|             raise ValueError("code not found") |             raise ValueError("code not found") | ||||||
| @@ -325,7 +322,6 @@ class TaxTotal: | |||||||
|  |  | ||||||
|     def calculate(self, invline): |     def calculate(self, invline): | ||||||
|         self.taxable_amount = invline.total_amount |         self.taxable_amount = invline.total_amount | ||||||
|  |  | ||||||
|         for subtax in self.subtotals: |         for subtax in self.subtotals: | ||||||
|             subtax.calculate(invline) |             subtax.calculate(invline) | ||||||
|             self.tax_amount += subtax.tax_amount |             self.tax_amount += subtax.tax_amount | ||||||
| @@ -338,6 +334,36 @@ class TaxTotalOmit(TaxTotal): | |||||||
|     def calculate(self, invline): |     def calculate(self, invline): | ||||||
|         pass |         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 | @dataclass | ||||||
| class Price: | class Price: | ||||||
|     amount: Amount |     amount: Amount | ||||||
| @@ -474,7 +500,7 @@ class InvoiceLine: | |||||||
|     # la factura y el percent es unico por type_code |     # la factura y el percent es unico por type_code | ||||||
|     # de subtotal |     # de subtotal | ||||||
|     tax: typing.Optional[TaxTotal] |     tax: typing.Optional[TaxTotal] | ||||||
|  |     withholding: typing.Optional[WithholdingTaxTotal] | ||||||
|     allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(default_factory=list) |     allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(default_factory=list) | ||||||
|  |  | ||||||
|     def add_allowance_charge(self, charge): |     def add_allowance_charge(self, charge): | ||||||
| @@ -518,8 +544,17 @@ class InvoiceLine: | |||||||
|     def taxable_amount(self): |     def taxable_amount(self): | ||||||
|         return self.tax.taxable_amount |         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): |     def calculate(self): | ||||||
|         self.tax.calculate(self) |         self.tax.calculate(self) | ||||||
|  |         self.withholding.calculate(self) | ||||||
|  |  | ||||||
|     def __post_init__(self): |     def __post_init__(self): | ||||||
|         if not isinstance(self.quantity, Quantity): |         if not isinstance(self.quantity, Quantity): | ||||||
| @@ -528,6 +563,9 @@ class InvoiceLine: | |||||||
|         if self.tax is None: |         if self.tax is None: | ||||||
|             self.tax = TaxTotalOmit() |             self.tax = TaxTotalOmit() | ||||||
|              |              | ||||||
|  |         if self.withholding is None: | ||||||
|  |             self.withholding = WithholdingTaxTotalOmit() | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| class LegalMonetaryTotal: | class LegalMonetaryTotal: | ||||||
|     line_extension_amount: Amount = Amount(0.0) |     line_extension_amount: Amount = Amount(0.0) | ||||||
|   | |||||||
| @@ -147,7 +147,6 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|         fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', |         fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', | ||||||
|                           invoice.invoice_customer.tax_scheme.code) |                           invoice.invoice_customer.tax_scheme.code) | ||||||
|  |  | ||||||
|  |  | ||||||
|         #DIAN 1.7.-2020: CAJ41 |         #DIAN 1.7.-2020: CAJ41 | ||||||
|         fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', |         fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', | ||||||
|                           invoice.invoice_customer.tax_scheme.name) |                           invoice.invoice_customer.tax_scheme.name) | ||||||
| @@ -421,6 +420,7 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|  |  | ||||||
|     def set_invoice_totals(fexml, invoice): |     def set_invoice_totals(fexml, invoice): | ||||||
|         tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) |         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) |         percent_for = defaultdict(lambda: None) | ||||||
|  |  | ||||||
|         #requeridos para CUFE |         #requeridos para CUFE | ||||||
| @@ -433,6 +433,7 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|         #tax_amount_for['03']['taxable_amount'] += 0.0 |         #tax_amount_for['03']['taxable_amount'] += 0.0 | ||||||
|  |  | ||||||
|         total_tax_amount = Amount(0.0) |         total_tax_amount = Amount(0.0) | ||||||
|  |         total_withholding_amount = Amount(0.0) | ||||||
|  |  | ||||||
|         for invoice_line in invoice.invoice_lines: |         for invoice_line in invoice.invoice_lines: | ||||||
|             for subtotal in invoice_line.tax.subtotals: |             for subtotal in invoice_line.tax.subtotals: | ||||||
| @@ -445,12 +446,22 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|  |  | ||||||
|                 total_tax_amount += subtotal.tax_amount |                 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): |         if total_tax_amount != Amount(0.0): | ||||||
|             fexml.placeholder_for('./cac:TaxTotal') |             fexml.placeholder_for('./cac:TaxTotal') | ||||||
|             fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount', |             fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount', | ||||||
|                     total_tax_amount) |                     total_tax_amount) | ||||||
|          |          | ||||||
|          |  | ||||||
|         for index, item in enumerate(tax_amount_for.items()): |         for index, item in enumerate(tax_amount_for.items()): | ||||||
|             cod_impuesto, amount_of = item |             cod_impuesto, amount_of = item | ||||||
|             next_append = index > 0 |             next_append = index > 0 | ||||||
| @@ -488,6 +499,43 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|             line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', |             line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', | ||||||
|                     'IVA') |                     '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 |     # abstract method | ||||||
|     def tag_document(fexml): |     def tag_document(fexml): | ||||||
|         return 'Invoice' |         return 'Invoice' | ||||||
| @@ -516,6 +564,28 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|                 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:ID', subtotal.scheme.code) | ||||||
|                 line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) |                 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): |     def set_invoice_lines(fexml, invoice): | ||||||
|         next_append = False |         next_append = False | ||||||
|         for index, invoice_line in enumerate(invoice.invoice_lines): |         for index, invoice_line in enumerate(invoice.invoice_lines): | ||||||
| @@ -531,6 +601,9 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|             if not isinstance(invoice_line.tax, TaxTotalOmit): |             if not isinstance(invoice_line.tax, TaxTotalOmit): | ||||||
|                 fexml.set_invoice_line_tax(line, invoice_line) |                 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/cbc:Description', invoice_line.item.description) | ||||||
|  |  | ||||||
|             line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID', |             line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID', | ||||||
|   | |||||||
| @@ -499,6 +499,5 @@ class DIANSupportDocumentXML(fe.FeXML): | |||||||
|         return fexml |         return fexml | ||||||
|  |  | ||||||
|     def customize(fexml, invoice): |     def customize(fexml, 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""" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 sinergia
					sinergia