se adiciona xml para linea de AllowanceCharge

FossilOrigin-Name: 20a354ef9c6c5aa3fb0436716b43d68cac7bfa1b56be550abc18af79444f1662
This commit is contained in:
bit4bit 2020-12-02 20:30:28 +00:00
parent 1143b26988
commit 38f4c5ae45
4 changed files with 89 additions and 8 deletions

View File

@ -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):

View File

@ -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

View File

@ -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'

View File

@ -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'