Files
einvoice/jr_einvoice/facturx.py
T

111 lines
5.7 KiB
Python

"""UN/CEFACT CII D16B, EN16931: positive, taxable service invoices."""
from pathlib import Path
from decimal import Decimal
import xml.etree.ElementTree as ET
from .calc import compute_totals, line_net
from .models import Invoice
NS = {k: f'urn:un:unece:uncefact:data:standard:{v}:100' for k, v in {
'rsm': 'CrossIndustryInvoice', 'ram': 'ReusableAggregateBusinessInformationEntity',
'udt': 'UnqualifiedDataType'}.items()}
for prefix, uri in NS.items():
ET.register_namespace(prefix, uri)
def node(parent, name, value=None, **attrs):
prefix, local = name.split(':')
el = ET.SubElement(parent, f'{{{NS[prefix]}}}{local}', attrs)
if value is not None:
el.text = format(value, "f") if isinstance(value, Decimal) else str(value)
return el
def ram(parent, name, value=None, **attrs):
return node(parent, 'ram:' + name, value, **attrs)
def date_node(parent, name, value):
node(ram(parent, name), 'udt:DateTimeString', value.strftime('%Y%m%d'), format='102')
def party(parent, name, data):
p = ram(parent, name)
ram(p, 'Name', data.name)
address = ram(p, 'PostalTradeAddress')
for key, value in [('PostcodeCode', data.address.postal_code), ('LineOne', data.address.street),
('CityName', data.address.city), ('CountryID', data.address.country)]:
ram(address, key, value)
if data.email:
ram(ram(p, 'URIUniversalCommunication'), 'URIID', data.email, schemeID='EM')
for value, scheme in [(data.vat_id, 'VA'), (data.tax_number, 'FC')]:
if value:
ram(ram(p, 'SpecifiedTaxRegistration'), 'ID', value, schemeID=scheme)
def generate_facturx_xml(inv: Invoice, out_xml: Path, logger) -> Path:
totals = compute_totals(inv)
root = ET.Element(f'{{{NS["rsm"]}}}CrossIndustryInvoice')
context = node(root, 'rsm:ExchangedDocumentContext')
ram(ram(context, 'GuidelineSpecifiedDocumentContextParameter'), 'ID', 'urn:cen.eu:en16931:2017')
doc = node(root, 'rsm:ExchangedDocument')
ram(doc, 'ID', inv.meta.invoice_number)
ram(doc, 'TypeCode', '380')
date_node(doc, 'IssueDateTime', inv.meta.invoice_date)
if inv.meta.notes:
ram(ram(doc, 'IncludedNote'), 'Content', inv.meta.notes)
transaction = node(root, 'rsm:SupplyChainTradeTransaction')
for index, item in enumerate(inv.items, 1):
line = ram(transaction, 'IncludedSupplyChainTradeLineItem')
ram(ram(line, 'AssociatedDocumentLineDocument'), 'LineID', index)
product = ram(line, 'SpecifiedTradeProduct')
ram(product, 'Name', item.name)
if item.description:
ram(product, 'Description', item.description)
agreement = ram(line, 'SpecifiedLineTradeAgreement')
ram(ram(agreement, 'NetPriceProductTradePrice'), 'ChargeAmount', item.unit_price_net)
ram(ram(line, 'SpecifiedLineTradeDelivery'), 'BilledQuantity', item.quantity, unitCode=item.unit)
settlement = ram(line, 'SpecifiedLineTradeSettlement')
tax = ram(settlement, 'ApplicableTradeTax')
ram(tax, 'TypeCode', 'VAT')
ram(tax, 'CategoryCode', 'S')
ram(tax, 'RateApplicablePercent', item.tax_rate)
ram(ram(settlement, 'SpecifiedTradeSettlementLineMonetarySummation'), 'LineTotalAmount', line_net(item))
agreement = ram(transaction, 'ApplicableHeaderTradeAgreement')
if inv.meta.buyer_reference:
ram(agreement, 'BuyerReference', inv.meta.buyer_reference)
party(agreement, 'SellerTradeParty', inv.seller)
party(agreement, 'BuyerTradeParty', inv.buyer)
if inv.meta.order_reference:
ram(ram(agreement, 'BuyerOrderReferencedDocument'), 'IssuerAssignedID', inv.meta.order_reference)
delivery = ram(transaction, 'ApplicableHeaderTradeDelivery')
date_node(ram(delivery, 'ActualDeliverySupplyChainEvent'), 'OccurrenceDateTime', inv.meta.service_date)
settlement = ram(transaction, 'ApplicableHeaderTradeSettlement')
ram(settlement, 'PaymentReference', inv.payment.reference)
ram(settlement, 'InvoiceCurrencyCode', inv.meta.currency)
if inv.payment.iban:
means = ram(settlement, 'SpecifiedTradeSettlementPaymentMeans')
ram(means, 'TypeCode', '58')
ram(ram(means, 'PayeePartyCreditorFinancialAccount'), 'IBANID', inv.payment.iban.replace(' ', ''))
if inv.payment.bic:
ram(ram(means, 'PayeeSpecifiedCreditorFinancialInstitution'), 'BICID', inv.payment.bic)
for rate, base in totals.basis_by_rate.items():
tax = ram(settlement, 'ApplicableTradeTax')
ram(tax, 'CalculatedAmount', totals.tax_by_rate[rate])
ram(tax, 'TypeCode', 'VAT')
ram(tax, 'BasisAmount', base)
ram(tax, 'CategoryCode', 'S')
ram(tax, 'RateApplicablePercent', rate)
if inv.meta.service_period_start:
period = ram(settlement, 'BillingSpecifiedPeriod')
date_node(period, 'StartDateTime', inv.meta.service_period_start)
date_node(period, 'EndDateTime', inv.meta.service_period_end)
terms = ram(settlement, 'SpecifiedTradePaymentTerms')
date_node(terms, 'DueDateDateTime', inv.payment.due_date)
summary = ram(settlement, 'SpecifiedTradeSettlementHeaderMonetarySummation')
for name, amount in [('LineTotalAmount', totals.net_total), ('ChargeTotalAmount', '0.00'),
('AllowanceTotalAmount', '0.00'), ('TaxBasisTotalAmount', totals.net_total)]:
ram(summary, name, amount)
ram(summary, 'TaxTotalAmount', totals.tax_total, currencyID=inv.meta.currency)
ram(summary, 'GrandTotalAmount', totals.gross_total)
ram(summary, 'DuePayableAmount', totals.gross_total)
ET.indent(root)
out_xml.parent.mkdir(parents=True, exist_ok=True)
ET.ElementTree(root).write(out_xml, encoding='utf-8', xml_declaration=True)
logger.info('Wrote EN16931 XML: %s', out_xml)
return out_xml