diff --git a/facho/cli.py b/facho/cli.py index f9c29f5..1a01fb2 100644 --- a/facho/cli.py +++ b/facho/cli.py @@ -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: diff --git a/facho/fe/fe.py b/facho/fe/fe.py index 25a5418..dd7f757 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -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, diff --git a/facho/fe/form.py b/facho/fe/form.py index ecc06c9..2cc2f59 100644 --- a/facho/fe/form.py +++ b/facho/fe/form.py @@ -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 diff --git a/facho/fe/form_xml.py b/facho/fe/form_xml.py new file mode 100644 index 0000000..4ee2ac3 --- /dev/null +++ b/facho/fe/form_xml.py @@ -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 diff --git a/tests/test_amount.py b/tests/test_amount.py new file mode 100644 index 0000000..04bf846 --- /dev/null +++ b/tests/test_amount.py @@ -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) diff --git a/tests/test_fe_form.py b/tests/test_fe_form.py index 60a0cd2..05a8325 100644 --- a/tests/test_fe_form.py +++ b/tests/test_fe_form.py @@ -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, diff --git a/tests/test_form.py b/tests/test_form.py index efa1667..fed69e3 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -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)