se adiciona xml para linea de AllowanceCharge
FossilOrigin-Name: 20a354ef9c6c5aa3fb0436716b43d68cac7bfa1b56be550abc18af79444f1662
This commit is contained in:
parent
1143b26988
commit
38f4c5ae45
@ -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'
|
||||||
|
Loading…
Reference in New Issue
Block a user