se adiciona xml para linea de AllowanceCharge
FossilOrigin-Name: 20a354ef9c6c5aa3fb0436716b43d68cac7bfa1b56be550abc18af79444f1662
This commit is contained in:
		| @@ -4,6 +4,7 @@ | |||||||
| import hashlib | import hashlib | ||||||
| from functools import reduce | from functools import reduce | ||||||
| import copy | import copy | ||||||
|  | import dataclasses | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from datetime import datetime, date | from datetime import datetime, date | ||||||
| from collections import defaultdict | from collections import defaultdict | ||||||
| @@ -413,6 +414,12 @@ class AllowanceCharge: | |||||||
|     amount: Amount = Amount(0.0) |     amount: Amount = Amount(0.0) | ||||||
|     reason: AllowanceChargeReason = None |     reason: AllowanceChargeReason = None | ||||||
|  |  | ||||||
|  |     #Valor Base para calcular el descuento o el cargo | ||||||
|  |     base_amount: typing.Optional[Amount] = Amount(0.0) | ||||||
|  |      | ||||||
|  |     # Porcentaje: Porcentaje que aplicar. | ||||||
|  |     multiplier_factor_numeric: Amount = Amount(1.0) | ||||||
|  |      | ||||||
|     def isCharge(self): |     def isCharge(self): | ||||||
|         return self.charge_indicator == True |         return self.charge_indicator == True | ||||||
|  |  | ||||||
| @@ -428,6 +435,9 @@ class AllowanceCharge: | |||||||
|     def hasReason(self): |     def hasReason(self): | ||||||
|         return self.reason is not None |         return self.reason is not None | ||||||
|  |  | ||||||
|  |     def set_base_amount(self, amount): | ||||||
|  |         self.base_amount = amount | ||||||
|  |  | ||||||
| class AllowanceChargeAsDiscount(AllowanceCharge): | class AllowanceChargeAsDiscount(AllowanceCharge): | ||||||
|     def __init__(self, amount: Amount = Amount(0.0)): |     def __init__(self, amount: Amount = Amount(0.0)): | ||||||
|         self.charge_indicator = False |         self.charge_indicator = False | ||||||
| @@ -446,16 +456,31 @@ class InvoiceLine: | |||||||
|     # de subtotal |     # de subtotal | ||||||
|     tax: typing.Optional[TaxTotal] |     tax: typing.Optional[TaxTotal] | ||||||
|  |  | ||||||
|     allowance_charge = [] |     allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(default_factory=list) | ||||||
|  |  | ||||||
|     def add_allowance_charge(charge): |     def add_allowance_charge(self, charge): | ||||||
|         if not isinstance(charge, AllowanceCharge): |         if not isinstance(charge, AllowanceCharge): | ||||||
|             raise TypeError('charge invalid type expected AllowanceCharge') |             raise TypeError('charge invalid type expected AllowanceCharge') | ||||||
|  |         charge.set_base_amount(self.total_amount_without_charge) | ||||||
|         self.allowance_charge.add(charge) |         self.allowance_charge.add(charge) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def total_amount_without_charge(self): | ||||||
|  |         return (self.quantity * self.price.amount) | ||||||
|  |      | ||||||
|     @property |     @property | ||||||
|     def total_amount(self): |     def total_amount(self): | ||||||
|         return self.quantity * self.price.amount |         charge = AmountCollection(self.allowance_charge)\ | ||||||
|  |             .filter(lambda charge: charge.isCharge())\ | ||||||
|  |             .map(lambda charge: charge.amount)\ | ||||||
|  |             .sum() | ||||||
|  |  | ||||||
|  |         discount = AmountCollection(self.allowance_charge)\ | ||||||
|  |             .filter(lambda charge: charge.isDiscount())\ | ||||||
|  |             .map(lambda charge: charge.amount)\ | ||||||
|  |             .sum() | ||||||
|  |  | ||||||
|  |         return self.total_amount_without_charge + charge - discount | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def total_tax_inclusive_amount(self): |     def total_tax_inclusive_amount(self): | ||||||
| @@ -649,11 +674,22 @@ class Invoice: | |||||||
|         #DIAN 1.7.-2020: FAU14 |         #DIAN 1.7.-2020: FAU14 | ||||||
|         self.invoice_legal_monetary_total.calculate() |         self.invoice_legal_monetary_total.calculate() | ||||||
|  |  | ||||||
|  |     def _refresh_charges_base_amount(self): | ||||||
|  |         if self.invoice_allowance_charge: | ||||||
|  |             for invline in self.invoice_lines: | ||||||
|  |                 if invline.allowance_charge: | ||||||
|  |                     # TODO actualmente solo uno de los cargos es permitido | ||||||
|  |                     raise ValueError('allowance charge in invoice exclude invoice line') | ||||||
|  |              | ||||||
|  |         # cargos a nivel de factura | ||||||
|  |         for charge in self.invoice_allowance_charge: | ||||||
|  |             charge.set_base_amount(self.invoice_legal_monetary_total.line_extension_amount) | ||||||
|  |          | ||||||
|     def calculate(self): |     def calculate(self): | ||||||
|         for invline in self.invoice_lines: |         for invline in self.invoice_lines: | ||||||
|             invline.calculate() |             invline.calculate() | ||||||
|         self._calculate_legal_monetary_total() |         self._calculate_legal_monetary_total() | ||||||
|  |         self._refresh_charges_base_amount() | ||||||
|  |  | ||||||
| class NationalSalesInvoice(Invoice): | class NationalSalesInvoice(Invoice): | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|   | |||||||
| @@ -543,13 +543,17 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|                              invoice_line.price.quantity, |                              invoice_line.price.quantity, | ||||||
|                              unitCode=invoice_line.quantity.code) |                              unitCode=invoice_line.quantity.code) | ||||||
|  |  | ||||||
|  |             for idx, charge in enumerate(invoice_line.allowance_charge): | ||||||
|  |                 next_append_charge = idx > 0 | ||||||
|  |                 fexml.append_allowance_charge(line, index + 1, charge, append=next_append_charge) | ||||||
|  |                  | ||||||
|     def set_allowance_charge(fexml, invoice): |     def set_allowance_charge(fexml, invoice): | ||||||
|         for idx, charge in enumerate(invoice.invoice_allowance_charge): |         for idx, charge in enumerate(invoice.invoice_allowance_charge): | ||||||
|             next_append = idx > 0 |             next_append = idx > 0 | ||||||
|             fexml.append_allowance_charge(fexml, idx + 1, charge, append=next_append) |             fexml.append_allowance_charge(fexml, idx + 1, charge, append=next_append) | ||||||
|  |  | ||||||
|     def append_allowance_charge(fexml, parent, idx, charge, append=False): |     def append_allowance_charge(fexml, parent, idx, charge, append=False): | ||||||
|             line = fexml.fragment('./cac:AllowanceCharge', append=append) |             line = parent.fragment('./cac:AllowanceCharge', append=append) | ||||||
|             #DIAN 1.7.-2020: FAQ02 |             #DIAN 1.7.-2020: FAQ02 | ||||||
|             line.set_element('./cbc:ID', idx) |             line.set_element('./cbc:ID', idx) | ||||||
|             #DIAN 1.7.-2020: FAQ03 |             #DIAN 1.7.-2020: FAQ03 | ||||||
| @@ -557,8 +561,9 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|             if charge.reason: |             if charge.reason: | ||||||
|                 line.set_element('./cbc:AllowanceChargeReasonCode', charge.reason.code) |                 line.set_element('./cbc:AllowanceChargeReasonCode', charge.reason.code) | ||||||
|                 line.set_element('./cbc:allowanceChargeReason', charge.reason.reason) |                 line.set_element('./cbc:allowanceChargeReason', charge.reason.reason) | ||||||
|  |             line.set_element('./cbc:MultiplierFactorNumeric', str(round(charge.multiplier_factor_numeric, 2))) | ||||||
|             fexml.set_element_amount_for(line, './cbc:Amount', charge.amount) |             fexml.set_element_amount_for(line, './cbc:Amount', charge.amount) | ||||||
|  |             fexml.set_element_amount_for(line, './cbc:BaseAmount', charge.base_amount) | ||||||
|              |              | ||||||
|     def attach_invoice(fexml, invoice): |     def attach_invoice(fexml, invoice): | ||||||
|         """adiciona etiquetas a FEXML y retorna FEXML |         """adiciona etiquetas a FEXML y retorna FEXML | ||||||
|   | |||||||
| @@ -40,3 +40,6 @@ def test_amount_truncate(): | |||||||
|     assert form.Amount(10084.03).truncate_as_string(2) == '10084.03' |     assert form.Amount(10084.03).truncate_as_string(2) == '10084.03' | ||||||
|     assert form.Amount(10000.02245).truncate_as_string(2) == '10000.02' |     assert form.Amount(10000.02245).truncate_as_string(2) == '10000.02' | ||||||
|     assert form.Amount(10000.02357).truncate_as_string(2) == '10000.02' |     assert form.Amount(10000.02357).truncate_as_string(2) == '10000.02' | ||||||
|  |  | ||||||
|  | def test_amount_format(): | ||||||
|  |     assert str(round(form.Amount(1.1569),2)) == '1.16' | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
|  | import copy | ||||||
|  |  | ||||||
| from facho.fe import form | from facho.fe import form | ||||||
| from facho.fe import form_xml | from facho.fe import form_xml | ||||||
| @@ -32,8 +33,8 @@ def test_import_DIANCreditNoteXML(): | |||||||
|     except AttributeError: |     except AttributeError: | ||||||
|         pytest.fail("unexpected not found") |         pytest.fail("unexpected not found") | ||||||
|  |  | ||||||
| def test_FAU10(simple_invoice_without_lines): | def test_allowance_charge_in_invoice(simple_invoice_without_lines): | ||||||
|     inv = simple_invoice_without_lines |     inv = copy.copy(simple_invoice_without_lines) | ||||||
|     inv.add_invoice_line(form.InvoiceLine( |     inv.add_invoice_line(form.InvoiceLine( | ||||||
|         quantity = form.Quantity(1, '94'), |         quantity = form.Quantity(1, '94'), | ||||||
|         description = 'producto facho', |         description = 'producto facho', | ||||||
| @@ -52,8 +53,44 @@ def test_FAU10(simple_invoice_without_lines): | |||||||
|         ) |         ) | ||||||
|     )) |     )) | ||||||
|     inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0))) |     inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0))) | ||||||
|  |     inv.calculate() | ||||||
|      |      | ||||||
|     xml = form_xml.DIANInvoiceXML(inv) |     xml = form_xml.DIANInvoiceXML(inv) | ||||||
|     assert xml.get_element_text('./cac:AllowanceCharge/cbc:ID') == '1' |     assert xml.get_element_text('./cac:AllowanceCharge/cbc:ID') == '1' | ||||||
|     assert xml.get_element_text('./cac:AllowanceCharge/cbc:ChargeIndicator') == 'true' |     assert xml.get_element_text('./cac:AllowanceCharge/cbc:ChargeIndicator') == 'true' | ||||||
|     assert xml.get_element_text('./cac:AllowanceCharge/cbc:Amount') == '19.0' |     assert xml.get_element_text('./cac:AllowanceCharge/cbc:Amount') == '19.0' | ||||||
|  |     assert xml.get_element_text('./cac:AllowanceCharge/cbc:BaseAmount') == '100.0' | ||||||
|  |  | ||||||
|  | def test_allowance_charge_in_invoice_line(simple_invoice_without_lines): | ||||||
|  |     inv = copy.copy(simple_invoice_without_lines) | ||||||
|  |     inv.add_invoice_line(form.InvoiceLine( | ||||||
|  |         quantity = form.Quantity(1, '94'), | ||||||
|  |         description = 'producto facho', | ||||||
|  |         item = form.StandardItem(9999), | ||||||
|  |         price = form.Price( | ||||||
|  |             amount = form.Amount(100.0), | ||||||
|  |             type_code = '01', | ||||||
|  |             type = 'x' | ||||||
|  |         ), | ||||||
|  |         tax = form.TaxTotal( | ||||||
|  |             subtotals = [ | ||||||
|  |                 form.TaxSubTotal( | ||||||
|  |                     percent = 19.0, | ||||||
|  |                 ) | ||||||
|  |             ] | ||||||
|  |         ), | ||||||
|  |         allowance_charge = [ | ||||||
|  |             form.AllowanceChargeAsDiscount(amount=form.Amount(10.0)) | ||||||
|  |         ] | ||||||
|  |     )) | ||||||
|  |     inv.calculate() | ||||||
|  |  | ||||||
|  |     # se aplico descuento | ||||||
|  |     assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(90.0) | ||||||
|  |      | ||||||
|  |     xml = form_xml.DIANInvoiceXML(inv) | ||||||
|  |  | ||||||
|  |     with pytest.raises(AttributeError): | ||||||
|  |         assert xml.get_element_text('/fe:Invoice/cac:AllowanceCharge/cbc:ID') == '1' | ||||||
|  |     xml.get_element_text('/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:ID') == '1' | ||||||
|  |     xml.get_element_text('/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:BaseAmount') == '100.0' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user