facho: Amount nueva clase para gestion moneda.

facho/fe/form_xml(DIANInvoiceXML): se extrae de form.py
para resolver ciclo en dependencias.
facho/fe/form.py(Amount): clase para gestion de moneda.
test/: se actualizan para hacer uso de Amount.
This commit is contained in:
Jovany Leandro G.C 2020-10-21 21:09:47 -05:00
parent 54bed07e91
commit a892fe664c
7 changed files with 561 additions and 444 deletions

View File

@ -259,6 +259,7 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr
spec.loader.exec_module(module)
import facho.fe.form as form
from facho.fe.form_xml import DIANInvoiceXML
from facho import fe
invoice = module.invoice()
@ -269,7 +270,7 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr
print("ERROR:", error)
if generate:
xml = form.DIANInvoiceXML(invoice)
xml = DIANInvoiceXML(invoice)
extensions = module.extensions(invoice)
for extension in extensions:

View File

@ -12,6 +12,7 @@ import warnings
import hashlib
from contextlib import contextmanager
from .data.dian import codelist
from . import form
SCHEME_AGENCY_ATTRS = {
'schemeAgencyName': 'CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)',
@ -112,17 +113,14 @@ class DianXMLExtensionCUFE(FachoXMLExtension):
ValorBruto = invoice.invoice_legal_monetary_total.line_extension_amount
ValorTotalPagar = invoice.invoice_legal_monetary_total.payable_amount
ValorImpuestoPara = {}
ValorImpuesto1 = 0.0
CodImpuesto1 = 1
ValorImpuesto2 = 0.0
CodImpuesto2 = 4
ValorImpuesto3 = 0.0
CodImpuesto3 = 3
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, 0.0)
ValorImpuestoPara.setdefault(codigo_impuesto, form.Amount(0.0))
ValorImpuestoPara[codigo_impuesto] += subtotal.tax_amount
NitOFE = invoice.invoice_supplier.ident
@ -134,14 +132,14 @@ class DianXMLExtensionCUFE(FachoXMLExtension):
'%s' % NumFac,
'%s' % FecFac,
'%s' % HoraFac,
'%.02f' % ValorBruto,
'%.02f' % round(ValorBruto, 2),
'%02d' % CodImpuesto1,
'%.02f' % ValorImpuestoPara.get(CodImpuesto1, 0.0),
'%.02f' % round(ValorImpuestoPara.get(CodImpuesto1, 0.0), 2),
'%02d' % CodImpuesto2,
'%.02f' % ValorImpuestoPara.get(CodImpuesto2, 0.0),
'%.02f' % round(ValorImpuestoPara.get(CodImpuesto2, 0.0), 2),
'%02d' % CodImpuesto3,
'%.02f' % ValorImpuestoPara.get(CodImpuesto3, 0.0),
'%.02f' % ValorTotalPagar,
'%.02f' % round(ValorImpuestoPara.get(CodImpuesto3, 0.0), 2),
'%.02f' % round(ValorTotalPagar, 2),
'%s' % NitOFE,
'%s' % NumAdq,
'%s' % ClTec,

View File

@ -7,10 +7,89 @@ import copy
from dataclasses import dataclass
from datetime import datetime
from collections import defaultdict
import decimal
from decimal import Decimal
from .data.dian import codelist
from . import fe
DECIMAL_PRECISION = 6
class AmountCurrencyError(TypeError):
pass
@dataclass
class Currency:
code: str
def __eq__(self, other):
return self.code == other.code
def __str__(self):
return self.code
class Collection:
def __init__(self, array):
self.array = array
def filter(self, filterer):
new_array = filter(filterer, self.array)
return self.__class__(new_array)
def map(self, mapper):
new_array = map(mapper, self.array)
return self.__class__(new_array)
def sum(self):
return sum(self.array)
class AmountCollection(Collection):
def sum(self):
total = Amount(0)
for v in self.array:
total += v
return total
class Amount:
def __init__(self, amount: int or float or Amount, currency: Currency = Currency('COP')):
if isinstance(amount, Amount):
self.amount = amount.amount
self.currency = amount.currency
else:
self.amount = Decimal(amount, decimal.Context(prec=DECIMAL_PRECISION, rounding=decimal.ROUND_HALF_DOWN ))
self.currency = currency
def __round__(self, prec):
return round(self.amount, prec)
def __str__(self):
return '%.06f' % self.amount
def __eq__(self, other):
if not self.is_same_currency(other):
raise AmountCurrencyError()
return round(self.amount, DECIMAL_PRECISION) == round(other.amount, DECIMAL_PRECISION)
def __add__(self, other):
if not self.is_same_currency(other):
raise AmountCurrencyError()
return Amount(self.amount + other.amount, self.currency)
def __sub__(self, other):
if not self.is_same_currency(other):
raise AmountCurrencyError()
return Amount(self.amount - other.amount, self.currency)
def __mul__(self, other):
if not self.is_same_currency(other):
raise AmountCurrencyError()
return Amount(self.amount * other.amount, self.currency)
def is_same_currency(self, other):
return self.currency == other.currency
@dataclass
class Item:
@ -97,19 +176,19 @@ class TaxSubTotal:
tax_scheme_ident: str = '01'
tax_scheme_name: str = 'IVA'
tax_amount: float = 0.0
taxable_amount: float = 0.0
tax_amount: Amount = Amount(0.0)
taxable_amount: Amount = Amount(0.0)
def calculate(self, invline):
self.tax_amount = invline.total_amount * (self.percent / 100)
self.tax_amount = invline.total_amount * Amount(self.percent / 100)
self.taxable_amount = invline.total_amount
@dataclass
class TaxTotal:
subtotals: list
tax_amount: float = 0.0
taxable_amount: float = 0.0
tax_amount: Amount = Amount(0.0)
taxable_amount: Amount = Amount(0.0)
def calculate(self, invline):
for subtax in self.subtotals:
@ -120,7 +199,7 @@ class TaxTotal:
@dataclass
class Price:
amount: float
amount: Amount
type_code: str
type: str
@ -140,7 +219,7 @@ class PaymentMean:
@dataclass
class PrePaidPayment:
#DIAN 1.7.-2020: FBD03
paid_amount: float = 0.0
paid_amount: Amount = Amount(0.0)
@dataclass
@ -158,7 +237,7 @@ class InvoiceLine:
@property
def total_amount(self):
return self.quantity * self.price.amount
return Amount(self.quantity) * self.price.amount
@property
def total_tax_inclusive_amount(self):
@ -182,19 +261,19 @@ class InvoiceLine:
@dataclass
class LegalMonetaryTotal:
line_extension_amount: float = 0.0
tax_exclusive_amount: float = 0.0
tax_inclusive_amount: float = 0.0
charge_total_amount: float = 0.0
allowance_total_amount: float = 0.0
payable_amount: float = 0.0
prepaid_amount: float = 0.0
line_extension_amount: Amount = Amount(0.0)
tax_exclusive_amount: Amount = Amount(0.0)
tax_inclusive_amount: Amount = Amount(0.0)
charge_total_amount: Amount = Amount(0.0)
allowance_total_amount: Amount = Amount(0.0)
payable_amount: Amount = Amount(0.0)
prepaid_amount: Amount = Amount(0.0)
@dataclass
class AllowanceCharge:
#DIAN 1.7.-2020: FAQ03
charge_indicator: bool = True
amount: float = 0.0
amount: Amount = Amount(0.0)
def isCharge(self):
return self.charge_indicator == True
@ -217,7 +296,7 @@ class Invoice:
self.invoice_issue = None
self.invoice_ident = None
self.invoice_operation_type = None
self.invoice_legal_monetary_total = LegalMonetaryTotal(0, 0, 0, 0, 0)
self.invoice_legal_monetary_total = LegalMonetaryTotal()
self.invoice_customer = None
self.invoice_supplier = None
self.invoice_payment_mean = None
@ -274,18 +353,21 @@ class Invoice:
self.invoice_legal_monetary_total.tax_inclusive_amount += invline.total_tax_inclusive_amount
#DIAN 1.7.-2020: FAU08
allownaces = filter(lambda charge: charge.isDiscount(), self.invoice_allowance_charge)
amounts_allowance = map(lambda charge: charge.amount, allownaces)
self.invoice_legal_monetary_total.allowance_total_amount = sum(amounts_allowance)
self.invoice_legal_monetary_total.allowance_total_amount = AmountCollection(self.invoice_allowance_charge)\
.filter(lambda charge: charge.isDiscount())\
.map(lambda charge: charge.amount)\
.sum()
#DIAN 1.7.-2020: FAU10
allowance_charges = filter(lambda charge: charge.isCharge(), self.invoice_allowance_charge)
amounts_allowance_charge = map(lambda charge: charge.amount, allowance_charges)
self.invoice_legal_monetary_total.charge_total_amount = sum(amounts_allowance_charge)
self.invoice_legal_monetary_total.charge_total_amount = AmountCollection(self.invoice_allowance_charge)\
.filter(lambda charge: charge.isCharge())\
.map(lambda charge: charge.amount)\
.sum()
#DIAN 1.7.-2020: FAU12
amounts_prepaid = map(lambda paid: paid.paid_amount, self.invoice_prepaid_payment)
self.invoice_legal_monetary_total.prepaid_amount = sum(amounts_prepaid)
self.invoice_legal_monetary_total.prepaid_amount = AmountCollection(self.invoice_prepaid_payment)\
.map(lambda paid: paid.paid_amount)\
.sum()
#DIAN 1.7.-2020: FAU14
self.invoice_legal_monetary_total.payable_amount = \
@ -294,6 +376,7 @@ class Invoice:
+ self.invoice_legal_monetary_total.charge_total_amount \
- self.invoice_legal_monetary_total.prepaid_amount
def calculate(self):
for invline in self.invoice_lines:
invline.calculate()
@ -371,379 +454,3 @@ class DianResolucion0001Validator:
def valid(self):
return not self.errors
class DIANInvoiceXML(fe.FeXML):
"""
DianInvoiceXML mapea objeto form.Invoice a XML segun
lo indicado para la facturacion electronica.
"""
def __init__(self, invoice):
super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1')
self.placeholder_for('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
# ZE02 se requiere existencia para firmar
ublextension = self.fragment('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append=True)
extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent')
self.attach_invoice(invoice)
def set_supplier(fexml, invoice):
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty')
#DIAN 1.7.-2020: FAJ02
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cbc:AdditionalAccountID',
invoice.invoice_supplier.organization_code)
#DIAN 1.7.-2020: FAJ06
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name',
invoice.invoice_supplier.name)
#DIAN 1.7.-2020: FAJ07
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address')
#DIAN 1.7.-2020: FAJ08
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID',
invoice.invoice_supplier.address.city.code)
#DIAN 1.7.-2020: FAJ09
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName',
invoice.invoice_supplier.address.city.name)
#DIAN 1.7.-2020: FAJ11
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name)
#DIAN 1.7.-2020: FAJ12
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code)
#DIAN 1.7.-2020: FAJ14
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street)
#DIAN 1.7.-2020: FAJ16
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code)
supplier_address_id_attrs = {'languageID' : 'es'}
#DIAN 1.7.-2020: FAJ17
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name,
#DIAN 1.7.-2020: FAJ18
**supplier_address_id_attrs)
supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
supplier_company_id_attrs.update({'schemeID': invoice.invoice_supplier.ident.dv,
'schemeName': invoice.invoice_supplier.ident.type_fiscal})
#DIAN 1.7.-2020: FAJ19
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme')
#DIAN 1.7.-2020: FAJ20
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_supplier.legal_name)
#DIAN 1.7.-2020: FAJ21
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_supplier.ident,
#DIAN 1.7.-2020: FAJ22,FAJ23,FAJ24,FAJ25
**supplier_company_id_attrs)
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
#DIAN 1.7.-2020: FAJ26
invoice.invoice_supplier.responsability_code,
#DIAN 1.7.-2020: FAJ27
listName=invoice.invoice_supplier.responsability_regime_code)
#DIAN 1.7.-2020: FAJ28
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress')
#DIAN 1.7.-2020: FAJ29
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID',
invoice.invoice_supplier.address.city.code)
#DIAN 1.7.-2020: FAJ30
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', invoice.invoice_supplier.address.city.name)
#DIAN 1.7.-2020: FAJ31
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name)
#DIAN 1.7.-2020: FAJ32
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code)
#DIAN 1.7.-2020: FAJ33,FAJ34
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street)
#DIAN 1.7.-2020: FAJ35,FAJ36
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code)
supplier_address_id_attrs = {'languageID' : 'es'}
#DIAN 1.7.-2020: FAJ37,FAJ38
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name,
**supplier_address_id_attrs)
#DIAN 1.7.-2020: FAJ39
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
#DIAN 1.7.-2020: FAJ42
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity')
#DIAN 1.7.-2020: FAJ43
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName',
invoice.invoice_supplier.legal_name)
#DIAN 1.7.-2020: FAJ44,FAJ45,FAJ46,FAJ47,FAJ48
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID',
invoice.invoice_supplier.ident,
**supplier_company_id_attrs)
#DIAN 1.7.-2020: FAJ49
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme')
#DIAN 1.7.-2020: FAJ50
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme/cbc:ID',
'SETP')
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:Contact')
#DIAN 1.7.-2020: FAJ71
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc:ElectronicMail',
invoice.invoice_supplier.email)
def set_customer(fexml, invoice):
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty')
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cbc:AdditionalAccountID',
invoice.invoice_customer.organization_code)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID',
invoice.invoice_customer.ident)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name',
invoice.invoice_customer.name)
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation')
customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
#DIAN 1.7.-2020: FAK25
customer_company_id_attrs.update({'schemeID': invoice.invoice_customer.ident.dv,
'schemeName': invoice.invoice_customer.ident.type_fiscal})
#DIAN 1.7.-2020: FAK07
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address')
#DIAN 1.7.-2020: FAK08
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID',
invoice.invoice_customer.address.city.code)
#DIAN 1.7.-2020: FAK09
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', invoice.invoice_customer.address.city.name)
#DIAN 1.7.-2020: FAK11
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity',
invoice.invoice_customer.address.countrysubentity.name)
#DIAN 1.7.-2020: FAK12
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode',
invoice.invoice_customer.address.countrysubentity.code)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address.street)
#DIAN 1.7.-2020: FAK16
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code)
customer_address_id_attrs = {'languageID' : 'es'}
#DIAN 1.7.-2020: FAK17
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name',
invoice.invoice_customer.address.country.name,
#DIAN 1.7.-2020: FAK18
**customer_address_id_attrs)
#DIAN 1.7.-2020: FAK17,FAK19
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme')
#DIAN 1.7.-2020: FAK17,FAK20
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_customer.legal_name)
#DIAN 1.7.-2020: FAK21
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_customer.ident,
**customer_company_id_attrs)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
#DIAN 1.7.-2020: FAK26
invoice.invoice_customer.responsability_code,
#DIAN 1.7.-2020: FAK27
listName=invoice.invoice_customer.responsability_regime_code)
#DIAN 1.7.-2020: FAK28
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress')
#DIAN 1.7.-2020: FAK29
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID',
invoice.invoice_customer.address.city.code)
#DIAN 1.7.-2020: FAK30
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName',
invoice.invoice_customer.address.city.name)
#DIAN 1.7.-2020: FAK31
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity',
invoice.invoice_customer.address.countrysubentity.name)
#DIAN 1.7.-2020: FAK32
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode',
invoice.invoice_customer.address.countrysubentity.code)
#DIAN 1.7.-2020: FAK33
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine')
#DIAN 1.7.-2020: FAK34
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address.street)
#DIAN 1.7.-2020: FAK35
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country')
#DIAN 1.7.-2020: FAK36
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code)
#DIAN 1.7.-2020: FAK37
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', invoice.invoice_customer.address.country.name)
#DIAN 1.7.-2020: FAK38
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code,
**customer_address_id_attrs)
#DIAN 1.7.-2020: FAK39
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
#DIAN 1.7.-2020: FAK40 Machete Construir Validación
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
'ZY')
#DIAN 1.7.-2020: FAK41 Machete Construir Validación
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
'No causa')
#DIAN 1.7.-2020: FAK42
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity')
#DIAN 1.7.-2020: FAK43
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName',
invoice.invoice_customer.legal_name)
#DIAN 1.7.-2020: FAK44,FAK45,FAK46,FAK47,FAK48
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID',
invoice.invoice_customer.ident,
**customer_company_id_attrs)
#DIAN 1.7.-2020: FAK51
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity')
#DIAN 1.7.-2020: FAK55
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail',
invoice.invoice_customer.email)
def set_payment_mean(fexml, invoice):
payment_mean = invoice.invoice_payment_mean
fexml.set_element('/fe:Invoice/cac:PaymentMeans/cbc:ID', payment_mean.id)
fexml.set_element('/fe:Invoice/cac:PaymentMeans/cbc:PaymentMeansCode', payment_mean.code)
fexml.set_element('/fe:Invoice/cac:PaymentMeans/cbc:PaymentDueDate', payment_mean.due_at.strftime('%Y-%m-%d'))
fexml.set_element('/fe:Invoice/cac:PaymentMeans/cbc:PaymentID', payment_mean.payment_id)
def set_legal_monetary(fexml, invoice):
fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:LineExtensionAmount',
#MACHETE redondeo en valor
'%.02f' % (invoice.invoice_legal_monetary_total.line_extension_amount),
currencyID='COP')
fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount',
#MACHETE redondeo en valor
'%.02f' % (invoice.invoice_legal_monetary_total.tax_exclusive_amount),
currencyID='COP')
fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount',
#MACHETE ...
'%.02f' % (invoice.invoice_legal_monetary_total.tax_inclusive_amount),
currencyID='COP')
fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:ChargeTotalAmount',
#MACHETE ...
'%.02f' % (invoice.invoice_legal_monetary_total.charge_total_amount),
currencyID='COP')
fexml.set_element('/fe:Invoice/cac:LegalMonetaryTotal/cbc:PayableAmount',
#MACHETE ...
'%.02f' % (invoice.invoice_legal_monetary_total.payable_amount),
currencyID='COP')
def set_invoice_totals(fexml, invoice):
tax_amount_for = defaultdict(lambda: defaultdict(lambda: 0.0))
percent_for = defaultdict(lambda: None)
#requeridos para CUFE
tax_amount_for['01']['tax_amount'] = 0.0
tax_amount_for['01']['taxable_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
#tax_amount_for['03']['tax_amount'] = 0.0
#tax_amount_for['03']['taxable_amount'] = 0.0
total_tax_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'] += subtotal.taxable_amount
total_tax_amount += subtotal.tax_amount
# MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal.tax_scheme_ident] = subtotal.percent
fexml.placeholder_for('/fe:Invoice/cac:TaxTotal')
fexml.set_element('/fe:Invoice/cac:TaxTotal/cbc:TaxAmount', total_tax_amount,
currencyID='COP')
for index, item in enumerate(tax_amount_for.items()):
cod_impuesto, amount_of = item
next_append = index > 0
#DIAN 1.7.-2020: FAS01
line = fexml.fragment('/fe:Invoice/cac:TaxTotal', append=next_append)
#DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount']
line.set_element('/cac:TaxTotal/cbc:TaxAmount',
# MACHETE
'%.02f' % (tax_amount), currencyID='COP')
#DIAN 1.7.-2020: FAS05
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
# MACHETE
'%.02f' % (amount_of['taxable_amount']), currencyID='COP')
#DIAN 1.7.-2020: FAU06
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
# MACHETE
'%.02f' % (amount_of['tax_amount']), currencyID='COP')
#DIAN 1.7.-2020: FAS07
if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:Percent',
percent_for[cod_impuesto])
if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
percent_for[cod_impuesto])
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
cod_impuesto)
def set_invoice_lines(fexml, invoice):
next_append = False
for index, invoice_line in enumerate(invoice.invoice_lines):
line = fexml.fragment('/fe:Invoice/cac:InvoiceLine', append=next_append)
next_append = True
line.set_element('/cac:InvoiceLine/cbc:ID', index + 1)
line.set_element('/cac:InvoiceLine/cbc:InvoicedQuantity', invoice_line.quantity, unitCode = 'NAR')
line.set_element('/cac:InvoiceLine/cbc:LineExtensionAmount', invoice_line.total_amount, currencyID="COP")
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cbc:TaxAmount', invoice_line.tax_amount, currencyID='COP')
#condition_price = line.fragment('/cac:InvoiceLine/cac:PricingReference/cac:AlternativeConditionPrice')
#condition_price.set_element('/cac:AlternativeConditionPrice/cbc:PriceAmount', invoice_line.price.amount, currencyID='COP')
#condition_price.set_element('/cac:AlternativeConditionPrice/cbc:PriceTypeCode', invoice_line.price.type_code)
#condition_price.set_element('/cac:AlternativeConditionPrice/cbc:PriceType', invoice_line.price.type)
for subtotal in invoice_line.tax.subtotals:
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', subtotal.taxable_amount, currencyID='COP')
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP')
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', subtotal.percent)
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.tax_scheme_ident)
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.tax_scheme_name)
line.set_element('/cac:InvoiceLine/cac:Item/cbc:Description', invoice_line.item.description)
# TODO
line.set_element('/cac:InvoiceLine/cac:Item/cac:StandardItemIdentification/cbc:ID', invoice_line.item.id)
line.set_element('/cac:InvoiceLine/cac:Price/cbc:PriceAmount', invoice_line.price.amount, currencyID="COP")
#DIAN 1.7.-2020: FBB04
line.set_element('/cac:InvoiceLine/cac:Price/cbc:BaseQuantity', invoice_line.price.amount)
def attach_invoice(fexml, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML
en caso de fallar validacion retorna None"""
fexml.placeholder_for('/fe:Invoice/ext:UBLExtensions')
fexml.set_element('/fe:Invoice/cbc:UBLVersionID', 'UBL 2.1')
fexml.set_element('/fe:Invoice/cbc:CustomizationID', invoice.invoice_operation_type)
fexml.placeholder_for('/fe:Invoice/cbc:ProfileID')
fexml.placeholder_for('/fe:Invoice/cbc:ProfileExecutionID')
fexml.set_element('/fe:Invoice/cbc:ID', invoice.invoice_ident)
fexml.placeholder_for('/fe:Invoice/cbc:UUID')
fexml.set_element('/fe:Invoice/cbc:DocumentCurrencyCode', 'COP')
fexml.set_element('/fe:Invoice/cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d'))
#DIAN 1.7.-2020: FAD10
fexml.set_element('/fe:Invoice/cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00'))
fexml.set_element('/fe:Invoice/cbc:InvoiceTypeCode', codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'],
listAgencyID='195',
listAgencyName='No matching global declaration available for the validation root',
listURI='http://www.dian.gov.co')
fexml.set_element('/fe:Invoice/cbc:LineCountNumeric', len(invoice.invoice_lines))
fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:StartDate', invoice.invoice_period_start.strftime('%Y-%m-%d'))
fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:EndDate', invoice.invoice_period_end.strftime('%Y-%m-%d'))
fexml.set_supplier(invoice)
fexml.set_customer(invoice)
fexml.set_legal_monetary(invoice)
fexml.set_invoice_totals(invoice)
fexml.set_invoice_lines(invoice)
fexml.set_payment_mean(invoice)
return fexml

391
facho/fe/form_xml.py Normal file
View File

@ -0,0 +1,391 @@
from . import fe
from .form import *
class DIANInvoiceXML(fe.FeXML):
"""
DianInvoiceXML mapea objeto form.Invoice a XML segun
lo indicado para la facturacion electronica.
"""
def __init__(self, invoice):
super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1')
self.placeholder_for('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
# ZE02 se requiere existencia para firmar
ublextension = self.fragment('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append=True)
extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent')
self.attach_invoice(invoice)
def set_supplier(fexml, invoice):
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty')
#DIAN 1.7.-2020: FAJ02
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cbc:AdditionalAccountID',
invoice.invoice_supplier.organization_code)
#DIAN 1.7.-2020: FAJ06
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name',
invoice.invoice_supplier.name)
#DIAN 1.7.-2020: FAJ07
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address')
#DIAN 1.7.-2020: FAJ08
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID',
invoice.invoice_supplier.address.city.code)
#DIAN 1.7.-2020: FAJ09
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName',
invoice.invoice_supplier.address.city.name)
#DIAN 1.7.-2020: FAJ11
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name)
#DIAN 1.7.-2020: FAJ12
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code)
#DIAN 1.7.-2020: FAJ14
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street)
#DIAN 1.7.-2020: FAJ16
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code)
supplier_address_id_attrs = {'languageID' : 'es'}
#DIAN 1.7.-2020: FAJ17
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name,
#DIAN 1.7.-2020: FAJ18
**supplier_address_id_attrs)
supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
supplier_company_id_attrs.update({'schemeID': invoice.invoice_supplier.ident.dv,
'schemeName': invoice.invoice_supplier.ident.type_fiscal})
#DIAN 1.7.-2020: FAJ19
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme')
#DIAN 1.7.-2020: FAJ20
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_supplier.legal_name)
#DIAN 1.7.-2020: FAJ21
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_supplier.ident,
#DIAN 1.7.-2020: FAJ22,FAJ23,FAJ24,FAJ25
**supplier_company_id_attrs)
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
#DIAN 1.7.-2020: FAJ26
invoice.invoice_supplier.responsability_code,
#DIAN 1.7.-2020: FAJ27
listName=invoice.invoice_supplier.responsability_regime_code)
#DIAN 1.7.-2020: FAJ28
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress')
#DIAN 1.7.-2020: FAJ29
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID',
invoice.invoice_supplier.address.city.code)
#DIAN 1.7.-2020: FAJ30
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', invoice.invoice_supplier.address.city.name)
#DIAN 1.7.-2020: FAJ31
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name)
#DIAN 1.7.-2020: FAJ32
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code)
#DIAN 1.7.-2020: FAJ33,FAJ34
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street)
#DIAN 1.7.-2020: FAJ35,FAJ36
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code)
supplier_address_id_attrs = {'languageID' : 'es'}
#DIAN 1.7.-2020: FAJ37,FAJ38
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name,
**supplier_address_id_attrs)
#DIAN 1.7.-2020: FAJ39
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
#DIAN 1.7.-2020: FAJ42
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity')
#DIAN 1.7.-2020: FAJ43
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName',
invoice.invoice_supplier.legal_name)
#DIAN 1.7.-2020: FAJ44,FAJ45,FAJ46,FAJ47,FAJ48
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID',
invoice.invoice_supplier.ident,
**supplier_company_id_attrs)
#DIAN 1.7.-2020: FAJ49
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme')
#DIAN 1.7.-2020: FAJ50
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme/cbc:ID',
'SETP')
fexml.placeholder_for('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:Contact')
#DIAN 1.7.-2020: FAJ71
fexml.set_element('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc:ElectronicMail',
invoice.invoice_supplier.email)
def set_customer(fexml, invoice):
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty')
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cbc:AdditionalAccountID',
invoice.invoice_customer.organization_code)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID',
invoice.invoice_customer.ident)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name',
invoice.invoice_customer.name)
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation')
customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
#DIAN 1.7.-2020: FAK25
customer_company_id_attrs.update({'schemeID': invoice.invoice_customer.ident.dv,
'schemeName': invoice.invoice_customer.ident.type_fiscal})
#DIAN 1.7.-2020: FAK07
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address')
#DIAN 1.7.-2020: FAK08
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID',
invoice.invoice_customer.address.city.code)
#DIAN 1.7.-2020: FAK09
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', invoice.invoice_customer.address.city.name)
#DIAN 1.7.-2020: FAK11
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity',
invoice.invoice_customer.address.countrysubentity.name)
#DIAN 1.7.-2020: FAK12
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode',
invoice.invoice_customer.address.countrysubentity.code)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address.street)
#DIAN 1.7.-2020: FAK16
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code)
customer_address_id_attrs = {'languageID' : 'es'}
#DIAN 1.7.-2020: FAK17
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name',
invoice.invoice_customer.address.country.name,
#DIAN 1.7.-2020: FAK18
**customer_address_id_attrs)
#DIAN 1.7.-2020: FAK17,FAK19
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme')
#DIAN 1.7.-2020: FAK17,FAK20
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_customer.legal_name)
#DIAN 1.7.-2020: FAK21
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_customer.ident,
**customer_company_id_attrs)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
#DIAN 1.7.-2020: FAK26
invoice.invoice_customer.responsability_code,
#DIAN 1.7.-2020: FAK27
listName=invoice.invoice_customer.responsability_regime_code)
#DIAN 1.7.-2020: FAK28
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress')
#DIAN 1.7.-2020: FAK29
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID',
invoice.invoice_customer.address.city.code)
#DIAN 1.7.-2020: FAK30
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName',
invoice.invoice_customer.address.city.name)
#DIAN 1.7.-2020: FAK31
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity',
invoice.invoice_customer.address.countrysubentity.name)
#DIAN 1.7.-2020: FAK32
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode',
invoice.invoice_customer.address.countrysubentity.code)
#DIAN 1.7.-2020: FAK33
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine')
#DIAN 1.7.-2020: FAK34
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address.street)
#DIAN 1.7.-2020: FAK35
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country')
#DIAN 1.7.-2020: FAK36
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code)
#DIAN 1.7.-2020: FAK37
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', invoice.invoice_customer.address.country.name)
#DIAN 1.7.-2020: FAK38
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code,
**customer_address_id_attrs)
#DIAN 1.7.-2020: FAK39
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
#DIAN 1.7.-2020: FAK40 Machete Construir Validación
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
'ZY')
#DIAN 1.7.-2020: FAK41 Machete Construir Validación
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
'No causa')
#DIAN 1.7.-2020: FAK42
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity')
#DIAN 1.7.-2020: FAK43
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName',
invoice.invoice_customer.legal_name)
#DIAN 1.7.-2020: FAK44,FAK45,FAK46,FAK47,FAK48
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID',
invoice.invoice_customer.ident,
**customer_company_id_attrs)
#DIAN 1.7.-2020: FAK51
fexml.placeholder_for('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity')
#DIAN 1.7.-2020: FAK55
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail',
invoice.invoice_customer.email)
def set_payment_mean(fexml, invoice):
payment_mean = invoice.invoice_payment_mean
fexml.set_element('/fe:Invoice/cac:PaymentMeans/cbc:ID', payment_mean.id)
fexml.set_element('/fe:Invoice/cac:PaymentMeans/cbc:PaymentMeansCode', payment_mean.code)
fexml.set_element('/fe:Invoice/cac:PaymentMeans/cbc:PaymentDueDate', payment_mean.due_at.strftime('%Y-%m-%d'))
fexml.set_element('/fe:Invoice/cac:PaymentMeans/cbc:PaymentID', payment_mean.payment_id)
def set_element_amount_for(fexml, xml, xpath, amount):
if not isinstance(amount, Amount):
raise TypeError("amount not is Amount")
xml.set_element(xpath, amount, currencyID=amount.currency.code)
def set_element_amount(fexml, xpath, amount):
if not isinstance(amount, Amount):
raise TypeError("amount not is Amount")
fexml.set_element(xpath, amount, currencyID=amount.currency.code)
def set_legal_monetary(fexml, invoice):
fexml.set_element_amount('/fe:Invoice/cac:LegalMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount)
fexml.set_element_amount('/fe:Invoice/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount)
fexml.set_element_amount('/fe:Invoice/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount)
fexml.set_element_amount('/fe:Invoice/cac:LegalMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount)
fexml.set_element_amount('/fe:Invoice/cac:LegalMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount)
def set_invoice_totals(fexml, invoice):
tax_amount_for = defaultdict(lambda: defaultdict(lambda: 0.0))
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)
#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
#tax_amount_for['03']['tax_amount'] = 0.0
#tax_amount_for['03']['taxable_amount'] = 0.0
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'] += subtotal.taxable_amount
total_tax_amount += subtotal.tax_amount
# MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal.tax_scheme_ident] = subtotal.percent
fexml.placeholder_for('/fe:Invoice/cac:TaxTotal')
fexml.set_element_amount('/fe:Invoice/cac:TaxTotal/cbc:TaxAmount',
total_tax_amount)
for index, item in enumerate(tax_amount_for.items()):
cod_impuesto, amount_of = item
next_append = index > 0
#DIAN 1.7.-2020: FAS01
line = fexml.fragment('/fe:Invoice/cac:TaxTotal', append=next_append)
#DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount']
fexml.set_element_amount_for(line,
'/cac:TaxTotal/cbc:TaxAmount',
tax_amount)
#DIAN 1.7.-2020: FAS05
fexml.set_element_amount_for(line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
amount_of['taxable_amount'])
#DIAN 1.7.-2020: FAU06
fexml.set_element_amount_for(line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
amount_of['tax_amount'])
#DIAN 1.7.-2020: FAS07
if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:Percent',
percent_for[cod_impuesto])
if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
percent_for[cod_impuesto])
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
cod_impuesto)
def set_invoice_lines(fexml, invoice):
next_append = False
for index, invoice_line in enumerate(invoice.invoice_lines):
line = fexml.fragment('/fe:Invoice/cac:InvoiceLine', append=next_append)
next_append = True
line.set_element('/cac:InvoiceLine/cbc:ID', index + 1)
line.set_element('/cac:InvoiceLine/cbc:InvoicedQuantity', invoice_line.quantity, unitCode = 'NAR')
fexml.set_element_amount_for(line,
'/cac:InvoiceLine/cbc:LineExtensionAmount',
invoice_line.total_amount)
fexml.set_element_amount_for(line,
'/cac:InvoiceLine/cac:TaxTotal/cbc:TaxAmount',
invoice_line.tax_amount)
#condition_price = line.fragment('/cac:InvoiceLine/cac:PricingReference/cac:AlternativeConditionPrice')
#condition_price.set_element('/cac:AlternativeConditionPrice/cbc:PriceAmount', invoice_line.price.amount, currencyID='COP')
#condition_price.set_element('/cac:AlternativeConditionPrice/cbc:PriceTypeCode', invoice_line.price.type_code)
#condition_price.set_element('/cac:AlternativeConditionPrice/cbc:PriceType', invoice_line.price.type)
for subtotal in invoice_line.tax.subtotals:
fexml.set_element_amount_for(line,
'/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
subtotal.taxable_amount)
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP')
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', subtotal.percent)
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.tax_scheme_ident)
line.set_element('/cac:InvoiceLine/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.tax_scheme_name)
line.set_element('/cac:InvoiceLine/cac:Item/cbc:Description', invoice_line.item.description)
# TODO
line.set_element('/cac:InvoiceLine/cac:Item/cac:StandardItemIdentification/cbc:ID', invoice_line.item.id)
line.set_element('/cac:InvoiceLine/cac:Price/cbc:PriceAmount', invoice_line.price.amount, currencyID="COP")
#DIAN 1.7.-2020: FBB04
line.set_element('/cac:InvoiceLine/cac:Price/cbc:BaseQuantity', invoice_line.price.amount)
def attach_invoice(fexml, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML
en caso de fallar validacion retorna None"""
fexml.placeholder_for('/fe:Invoice/ext:UBLExtensions')
fexml.set_element('/fe:Invoice/cbc:UBLVersionID', 'UBL 2.1')
fexml.set_element('/fe:Invoice/cbc:CustomizationID', invoice.invoice_operation_type)
fexml.placeholder_for('/fe:Invoice/cbc:ProfileID')
fexml.placeholder_for('/fe:Invoice/cbc:ProfileExecutionID')
fexml.set_element('/fe:Invoice/cbc:ID', invoice.invoice_ident)
fexml.placeholder_for('/fe:Invoice/cbc:UUID')
fexml.set_element('/fe:Invoice/cbc:DocumentCurrencyCode', 'COP')
fexml.set_element('/fe:Invoice/cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d'))
#DIAN 1.7.-2020: FAD10
fexml.set_element('/fe:Invoice/cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00'))
fexml.set_element('/fe:Invoice/cbc:InvoiceTypeCode', codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'],
listAgencyID='195',
listAgencyName='No matching global declaration available for the validation root',
listURI='http://www.dian.gov.co')
fexml.set_element('/fe:Invoice/cbc:LineCountNumeric', len(invoice.invoice_lines))
fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:StartDate', invoice.invoice_period_start.strftime('%Y-%m-%d'))
fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:EndDate', invoice.invoice_period_end.strftime('%Y-%m-%d'))
fexml.set_supplier(invoice)
fexml.set_customer(invoice)
fexml.set_legal_monetary(invoice)
fexml.set_invoice_totals(invoice)
fexml.set_invoice_lines(invoice)
fexml.set_payment_mean(invoice)
return fexml

18
tests/test_amount.py Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
"""Tests for `facho` package."""
import pytest
import facho.fe.form as form
def test_amount_equals():
price1 = form.Amount(110.0)
price2 = form.Amount(100 + 10.0)
assert price1 == price2
assert price1 == form.Amount(100) + form.Amount(10)
assert price1 == form.Amount(10) * form.Amount(10) + form.Amount(10)

View File

@ -12,7 +12,7 @@ import zipfile
import facho.fe.form as form
from facho import fe
from facho.fe.form_xml import DIANInvoiceXML
@pytest.fixture
def simple_invoice_without_lines():
@ -80,10 +80,10 @@ def simple_invoice():
quantity = 1,
description = 'producto facho',
item = form.StandardItem('test', 9999),
price = form.Price(100.0, '01', ''),
price = form.Price(form.Amount(100.0), '01', ''),
tax = form.TaxTotal(
tax_amount = 0.0,
taxable_amount = 0.0,
tax_amount = form.Amount(0.0),
taxable_amount = form.Amount(0.0),
subtotals = [
form.TaxSubTotal(
percent = 19.0,
@ -99,7 +99,7 @@ def test_invoicesimple_build(simple_invoice):
invoice_validator.validate(simple_invoice)
assert invoice_validator.errors == []
xml = form.DIANInvoiceXML(simple_invoice)
xml = DIANInvoiceXML(simple_invoice)
supplier_name = xml.get_element_text('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name')
assert supplier_name == simple_invoice.invoice_supplier.name
@ -111,7 +111,7 @@ def test_invoicesimple_build(simple_invoice):
def test_invoicesimple_build_with_cufe(simple_invoice):
invoice_validator = form.DianResolucion0001Validator()
assert invoice_validator.validate(simple_invoice) == True
xml = form.DIANInvoiceXML(simple_invoice)
xml = DIANInvoiceXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)
xml.add_extension(cufe_extension)
cufe = xml.get_element_text('/fe:Invoice/cbc:UUID')
@ -121,7 +121,7 @@ def test_invoicesimple_build_with_cufe(simple_invoice):
def test_invoicesimple_xml_signed(monkeypatch, simple_invoice):
invoice_validator = form.DianResolucion0001Validator()
assert invoice_validator.validate(simple_invoice) == True
xml = form.DIANInvoiceXML(simple_invoice)
xml = DIANInvoiceXML(simple_invoice)
signer = fe.DianXMLExtensionSigner('./tests/example.p12')
@ -135,7 +135,7 @@ def test_invoicesimple_xml_signed(monkeypatch, simple_invoice):
assert elem.text is not None
def test_invoicesimple_zip(simple_invoice):
xml_invoice = form.DIANInvoiceXML(simple_invoice)
xml_invoice = DIANInvoiceXML(simple_invoice)
zipdata = io.BytesIO()
with fe.DianZIP(zipdata) as dianzip:
@ -147,24 +147,24 @@ def test_invoicesimple_zip(simple_invoice):
def test_bug_cbcid_empty_on_invoice_line(simple_invoice):
xml_invoice = form.DIANInvoiceXML(simple_invoice)
xml_invoice = DIANInvoiceXML(simple_invoice)
cbc_id = xml_invoice.get_element_text('/fe:Invoice/cac:InvoiceLine[1]/cbc:ID', format_=int)
assert cbc_id == 1
def test_invoice_line_count_numeric(simple_invoice):
xml_invoice = form.DIANInvoiceXML(simple_invoice)
xml_invoice = DIANInvoiceXML(simple_invoice)
count = xml_invoice.get_element_text('/fe:Invoice/cbc:LineCountNumeric', format_=int)
assert count == len(simple_invoice.invoice_lines)
def test_invoice_profileexecutionid(simple_invoice):
xml_invoice = form.DIANInvoiceXML(simple_invoice)
xml_invoice = DIANInvoiceXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)
xml_invoice.add_extension(cufe_extension)
id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:ProfileExecutionID', format_=int)
assert id_ == 2
def test_invoice_invoice_type_code(simple_invoice):
xml_invoice = form.DIANInvoiceXML(simple_invoice)
xml_invoice = DIANInvoiceXML(simple_invoice)
id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:InvoiceTypeCode', format_=int)
assert id_ == 1
@ -178,7 +178,7 @@ def test_invoice_totals(simple_invoice_without_lines):
quantity = 1,
description = 'producto',
item = form.StandardItem('test', 9999),
price = form.Price(1_500_000, '', ''),
price = form.Price(form.Amount(1_500_000), '', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
@ -188,8 +188,8 @@ def test_invoice_totals(simple_invoice_without_lines):
))
simple_invoice.calculate()
assert 1 == len(simple_invoice.invoice_lines)
assert 1_500_000 == simple_invoice.invoice_legal_monetary_total.line_extension_amount
assert 1_785_000 == simple_invoice.invoice_legal_monetary_total.payable_amount
assert form.Amount(1_500_000) == simple_invoice.invoice_legal_monetary_total.line_extension_amount
assert form.Amount(1_785_000) == simple_invoice.invoice_legal_monetary_total.payable_amount
def test_invoice_cufe(simple_invoice_without_lines):
simple_invoice = simple_invoice_without_lines
@ -201,7 +201,7 @@ def test_invoice_cufe(simple_invoice_without_lines):
quantity = 1,
description = 'producto',
item = form.StandardItem('test', 111),
price = form.Price(1_500_000, '', ''),
price = form.Price(form.Amount(1_500_000), '', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
@ -211,7 +211,7 @@ def test_invoice_cufe(simple_invoice_without_lines):
))
simple_invoice.calculate()
xml_invoice = form.DIANInvoiceXML(simple_invoice)
xml_invoice = DIANInvoiceXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(
simple_invoice,

View File

@ -20,7 +20,7 @@ def test_invoice_legalmonetary():
description = 'producto facho',
item = form.StandardItem('test', 9999),
price = form.Price(
amount = 100.0,
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
@ -33,10 +33,10 @@ def test_invoice_legalmonetary():
)
))
inv.calculate()
assert inv.invoice_legal_monetary_total.line_extension_amount == 100.0
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == 100.0
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == 119.0
assert inv.invoice_legal_monetary_total.charge_total_amount == 0.0
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(119.0)
assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(0.0)
def test_FAU10():
@ -46,7 +46,7 @@ def test_FAU10():
description = 'producto facho',
item = form.StandardItem('test', 9999),
price = form.Price(
amount = 100.0,
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
@ -58,13 +58,13 @@ def test_FAU10():
]
)
))
inv.add_allownace_charge(form.AllowanceCharge(amount=19.0))
inv.add_allownace_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
inv.calculate()
assert inv.invoice_legal_monetary_total.line_extension_amount == 100.0
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == 100.0
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == 119.0
assert inv.invoice_legal_monetary_total.charge_total_amount == 19.0
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(119.0)
assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(19.0)
def test_FAU14():
@ -74,7 +74,7 @@ def test_FAU14():
description = 'producto facho',
item = form.StandardItem('test', 9999),
price = form.Price(
amount = 100.0,
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
@ -86,8 +86,10 @@ def test_FAU14():
]
)
))
inv.add_allownace_charge(form.AllowanceCharge(amount=19.0))
inv.add_prepaid_payment(form.PrePaidPayment(paid_amount = 50.0))
inv.add_allownace_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
inv.add_prepaid_payment(form.PrePaidPayment(paid_amount = form.Amount(50.0)))
inv.calculate()
assert inv.invoice_legal_monetary_total.payable_amount == 119.0 + 19.0 - 50.0
wants = form.Amount(119.0 + 19.0 - 50.0)
assert inv.invoice_legal_monetary_total.payable_amount == wants, "got %s want %s" % (inv.invoice_legal_monetary_total.payable_amount, wants)