details:   https://code.tryton.org/tryton/commit/56a9566c4b88
branch:    default
user:      Cédric Krier <[email protected]>
date:      Mon Aug 31 12:26:39 2026 +0200
description:
        Parse Cross Industry Invoice as supplier invoice
diffstat:

 modules/edocument_uncefact/CHANGELOG                            |    1 +
 modules/edocument_uncefact/edocument.py                         |  799 
+++++++++-
 modules/edocument_uncefact/exceptions.py                        |    8 +
 modules/edocument_uncefact/message.xml                          |   29 +
 modules/edocument_uncefact/pyproject.toml                       |    9 +-
 modules/edocument_uncefact/tests/CII_example1.xml               |  639 +++++++
 modules/edocument_uncefact/tests/scenario_cii_invoice_parse.rst |   84 +
 modules/edocument_uncefact/tests/test_module.py                 |    2 +-
 modules/edocument_uncefact/tests/test_scenario.py               |    8 +
 modules/edocument_uncefact/tryton.cfg                           |   14 +-
 10 files changed, 1588 insertions(+), 5 deletions(-)

diffs (1709 lines):

diff -r 507dd9ca1648 -r 56a9566c4b88 modules/edocument_uncefact/CHANGELOG
--- a/modules/edocument_uncefact/CHANGELOG      Thu Jul 30 09:35:03 2026 +0200
+++ b/modules/edocument_uncefact/CHANGELOG      Mon Aug 31 12:26:39 2026 +0200
@@ -1,3 +1,4 @@
+* Parse Cross Industry Invoice as supplier invoice
 
 Version 8.0.0 - 2026-04-20
 --------------------------
diff -r 507dd9ca1648 -r 56a9566c4b88 modules/edocument_uncefact/edocument.py
--- a/modules/edocument_uncefact/edocument.py   Thu Jul 30 09:35:03 2026 +0200
+++ b/modules/edocument_uncefact/edocument.py   Mon Aug 31 12:26:39 2026 +0200
@@ -1,20 +1,32 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
+
+import bisect
+import datetime as dt
 import functools
 import os
 from decimal import Decimal
+from io import BytesIO
+from itertools import chain
+from operator import itemgetter
 
 import genshi
 import genshi.template
 # XXX fix: https://genshi.edgewall.org/ticket/582
 from genshi.template.astutil import ASTCodeGenerator, ASTTransformer
+from lxml import etree
+from stdnum import bic, iban
 
+from trytond.i18n import gettext, ngettext
 from trytond.model import Model
-from trytond.pool import Pool
+from trytond.modules.product import round_price
+from trytond.pool import Pool, PoolMeta
 from trytond.rpc import RPC
 from trytond.tools import cached_property, slugify
 from trytond.transaction import Transaction
 
+from .exceptions import InvoiceError
+
 if not hasattr(ASTCodeGenerator, 'visit_NameConstant'):
     def visit_NameConstant(self, node):
         if node.value is None:
@@ -51,6 +63,14 @@
         yield kind, data, pos
 
 
+DATETIME_FORMATS = {
+    '102': '%Y%m%d',
+    '203': '%Y%m%d%H%M',
+    '204': '%Y%m%d%H%M%S',
+    '610': '%Y%m',
+    }
+
+
 class Invoice(Model):
     __name__ = 'edocument.uncefact.invoice'
     __slots__ = ('invoice',)
@@ -60,6 +80,7 @@
         super().__setup__()
         cls.__rpc__.update({
                 'render': RPC(instantiate=0),
+                'parse': RPC(readonly=False, result=int),
                 })
 
     def __init__(self, invoice):
@@ -217,3 +238,779 @@
     @classmethod
     def party_legal_ids(cls, party, address):
         return []
+
+    @classmethod
+    def parse(cls, document):
+        pool = Pool()
+        Attachment = pool.get('ir.attachment')
+
+        tree = etree.parse(BytesIO(document))
+        root = tree.getroot()
+        namespace = root.nsmap.get(root.prefix)
+        invoice, attachments = cls.parser(namespace)(root)
+        invoice.save()
+        invoice.update_taxes()
+        attachments = list(attachments)
+        for attachment in attachments:
+            attachment.resource = invoice
+        Attachment.save(attachments)
+        return invoice
+
+    @classmethod
+    def parser(cls, namespace):
+        return {
+            'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100': (
+                cls._parse_cii),
+            }.get(namespace)
+
+    @classmethod
+    def _parse_cii(cls, root):
+        pool = Pool()
+        Invoice = pool.get('account.invoice')
+        Currency = pool.get('currency.currency')
+
+        exchanged_document = root.find('./{*}ExchangedDocument')
+        settlement = root.find(
+            './{*}SupplyChainTradeTransaction/'
+            '{*}ApplicableHeaderTradeSettlement')
+        agreement = root.find(
+            './{*}SupplyChainTradeTransaction/'
+            '{*}ApplicableHeaderTradeAgreement')
+        transaction = root.find('./{*}SupplyChainTradeTransaction')
+
+        type_code = exchanged_document.findtext('./{*}TypeCode')
+        if type_code and type_code not in {'380', '381'}:
+            raise InvoiceError(gettext(
+                    'edocument_uncefact.msg_invoice_type_code_unsupported',
+                    type_code=type_code))
+        type_sign = -1 if type_code == '381' else 1
+
+        invoice = Invoice(type='in')
+
+        if (buyer_trade_party := agreement.find('./{*}BuyerTradeParty')
+                ) is not None:
+            invoice.company = cls._parse_cii_company(buyer_trade_party)
+        else:
+            invoice.company = Invoice.default_company()
+        if not invoice.company:
+            raise InvoiceError(gettext(
+                    'edocument_uncefact.msg_company_not_found',
+                    company=etree.tostring(
+                        buyer_trade_party, pretty_print=True).decode()
+                    if buyer_trade_party else ''))
+
+        invoice.reference = exchanged_document.findtext('./{*}ID')
+        for invoice_date in chain(
+                settlement.iterfind('./{*}InvoiceDateTime/{*}DateTimeString'),
+                exchanged_document.iterfind(
+                    './{*}IssueDateTime/{*}DateTimeString'),
+                ):
+            invoice.invoice_date = dt.datetime.strptime(
+                invoice_date.text,
+                DATETIME_FORMATS.get(invoice_date.get('format') or '102')
+                ).date()
+        invoice.party = cls._parse_cii_party(
+            agreement.find('./{*}SellerTradeParty'), create=True).id
+        payees = [invoice.party]
+        invoice.set_journal()
+        invoice.on_change_party()
+        invoice.invoice_address = cls._parse_cii_address(
+            agreement.find('./{*}SellerTradeParty/{*}PostalTradeAddress'),
+            party=invoice.party)
+        invoice.party_tax_identifier = cls._parse_cii_tax_identifier(
+            agreement.findall(
+                './{*}SellerTradeParty/{*}SpecifiedTaxRegistration'),
+            party=invoice.party, create=True)
+
+        if (payee_trade_party := settlement.find('./{*}PayeeTradeParty')
+                ) is not None:
+            party = cls._parse_cii_party(payee_trade_party, create=True)
+            invoice.alternative_payees = [party]
+            payees.append(party)
+        if (currency_code := settlement.findtext('./{*}InvoiceCurrencyCode')
+                ) is not None:
+            try:
+                invoice.currency, = Currency.search([
+                        ('code', '=', currency_code),
+                        ], limit=1)
+            except ValueError:
+                raise InvoiceError(gettext(
+                        'edocument_uncefact.msg_currency_not_found',
+                        code=currency_code))
+
+        invoice.supplier_payment_reference = settlement.findtext(
+            './{*}SpecifiedTradeSettlementPaymentMeans/{*}ID')
+        invoice.payment_means = cls._parse_cii_payment_means(
+            settlement.findall('./{*}SpecifiedTradeSettlementPaymentMeans'),
+            payees=payees)
+        invoice.payment_term_date = cls._parse_cii_payment_term_date(
+            settlement.findall('./{*}SpecifiedTradePaymentTerms'))
+        lines = [
+            cls._parse_cii_line(
+                line,
+                sign=type_sign,
+                company=invoice.company,
+                currency=invoice.currency,
+                supplier=invoice.party)
+            for line in transaction.iterfind(
+                            './{*}IncludedSupplyChainTradeLineItem')]
+        invoice.lines = lines
+        invoice.taxes = [
+            cls._parse_cii_tax(tax, type_sign, company=invoice.company)
+            for tax in settlement.iterfind('./{*}ApplicableTradeTax')]
+
+        if (hasattr(Invoice, 'cash_rounding')
+                and (settlement.find(
+                        './{*}SpecifiedTradeSettlementHeaderMonetarySummation'
+                        '/{*}RoundingAmount') is not None)):
+            invoice.cash_rounding = True
+
+        if tax_basis_total_amount := settlement.findtext(
+                './{*}SpecifiedTradeSettlementHeaderMonetarySummation'
+                '/{*}TaxBasisTotalAmount'):
+            untaxed_amount = Decimal(tax_basis_total_amount)
+            # TODO: Allowance
+            invoice.source_untaxed_amount = untaxed_amount * type_sign
+
+        if tax_total_amount := settlement.findtext(
+                './{*}SpecifiedTradeSettlementHeaderMonetarySummation'
+                '/{*}TaxTotalAmount'):
+            invoice.source_tax_amount = Decimal(tax_total_amount) * type_sign
+        if grand_total_amount := settlement.findtext(
+                './{*}SpecifiedTradeSettlementHeaderMonetarySummation'
+                '/{*}GrandTotalAmount'):
+            invoice.source_total_amount = (
+                Decimal(grand_total_amount) * type_sign)
+
+        return invoice, cls._parse_cii_attachments(root)
+
+    @classmethod
+    def _parse_cii_line(
+            cls, invoice_item, sign, company, currency, supplier=None):
+        pool = Pool()
+        Line = pool.get('account.invoice.line')
+        UoM = pool.get('product.uom')
+        Tax = pool.get('account.tax')
+        AccountConfiguration = pool.get('account.configuration')
+
+        account_configuration = AccountConfiguration(1)
+
+        line = Line(
+            type='line', company=company, currency=currency, invoice_type='in')
+        if (billed_quantity := invoice_item.find(
+                    './{*}SpecifiedLineTradeDelivery/{*}BilledQuantity')
+                ) is not None:
+            line.quantity = float(billed_quantity.text)
+            digits = (
+                -Decimal(billed_quantity.text)
+                .normalize().as_tuple().exponent)
+            if (unit_code := billed_quantity.get('unitCode')) not in {
+                    None, 'ZZ', 'XZZ'}:
+                try:
+                    line.unit, = UoM.search([
+                            ('unece_code', '=', unit_code),
+                            ('digits', '>=', digits),
+                            ],
+                        order=[('digits', 'ASC')],
+                        limit=1)
+                except ValueError:
+                    raise InvoiceError(ngettext(
+                            'edocument_uncefact.msg_unit_not_found',
+                            digits,
+                            code=unit_code,
+                            digits=digits))
+            else:
+                line.unit = None
+        else:
+            line.quantity = 1
+            line.unit = None
+            digits = 0
+
+        line.product = cls._parse_cii_product(
+            invoice_item.find('./{*}SpecifiedTradeProduct'), supplier=supplier)
+        if line.product:
+            line.on_change_product()
+            if line.unit and line.unit.digits < digits:
+                # Search the unit with enough digits
+                try:
+                    line.unit, = UoM.search([
+                            ('category', '=', line.unit.category),
+                            ('digits', '>=', digits),
+                            ['OR',
+                                ('factor', '=', line.unit.factor),
+                                ('rate', '=', line.unit.rate),
+                                ],
+                            ],
+                        order=[('digits', 'ASC')],
+                        limit=1)
+                except ValueError:
+                    pass
+        line.description = "\n".join(e.text for e in chain(
+                invoice_item.iterfind('./{*}SpecifiedTradeProduct/{*}Name'),
+                invoice_item.iterfind(
+                    './{*}SpecifiedTradeProduct/{*}TradeName'),
+                invoice_item.iterfind(
+                    './{*}SpecifiedTradeProduct/{*}Description'),
+                invoice_item.iterfind(
+                    './{*}SpecifiedTradeProduct/{*}EndItemName'),
+                invoice_item.iterfind(
+                    './{*}SpecifiedTradeProduct/{*}UseDescription'),
+                invoice_item.iterfind(
+                    './{*}SpecifiedTradeProduct/{*}BrandName'),
+                invoice_item.iterfind(
+                    './{*}SpecifiedTradeProduct/{*}SubBrandName'),
+                invoice_item.iterfind(
+                    './{*}SpecifiedTradeProduct/{*}Designation'),
+                ) if e is not None and e.text)
+
+        if not line.product:
+            if line.description:
+                similar_domain = [
+                    ('description', 'ilike', line.description),
+                    ('invoice.company', '=', company),
+                    ('invoice.type', '=', 'in'),
+                    ('invoice.state', 'in',
+                        ['validated', 'posted', 'paid']),
+                    ]
+                if line.unit:
+                    similar_domain.append(
+                        ('unit.category', '=', line.unit.category))
+                similar_lines = Line.search(
+                    similar_domain,
+                    order=[('invoice.invoice_date', 'DESC')],
+                    limit=1)
+            else:
+                similar_lines = []
+            if similar_lines:
+                similar_line, = similar_lines
+                line.account = similar_line.account
+                line.product = similar_line.product
+                if not line.unit:
+                    line.unit = similar_line.unit
+            else:
+                line.account = account_configuration.get_multivalue(
+                    'default_category_account_expense',
+                    company=company.id)
+
+        line_trade_agreement = invoice_item.find(
+            './{*}SpecifiedLineTradeAgreement')
+        for reference in chain(
+                line_trade_agreement.iterfind(
+                    './{*}SellerOrderReferencedDocument'),
+                line_trade_agreement.iterfind(
+                    './{*}BuyerOrderReferencedDocument'),
+                line_trade_agreement.iterfind(
+                    './{*}QuotationReferencedDocument'),
+                line_trade_agreement.iterfind(
+                    './{*}ContractReferencedDocument'),
+                line_trade_agreement.iterfind(
+                    './{*}DemandForecastReferencedDocument'),
+                line_trade_agreement.iterfind(
+                    './{*}PromotionalDealReferencedDocument'),
+                line_trade_agreement.iterfind(
+                    './{*}AdditionalReferencedDocument'),
+                ):
+            line.origin = cls._parse_cii_line_reference(
+                reference, line, company, supplier=supplier)
+            if line.origin:
+                break
+
+        unit_price = Decimal(line_trade_agreement.findtext(
+                './{*}NetPriceProductTradePrice/{*}ChargeAmount'))
+        if (basis_quantity := line_trade_agreement.find(
+                './{*}NetPriceProductTradePrice/{*}BasisQuantity')
+                ) is not None:
+            unit_price /= Decimal(basis_quantity.text)
+        line.unit_price = round_price(abs(unit_price))
+
+        line_trade_settlement = invoice_item.find(
+            './{*}SpecifiedLineTradeSettlement')
+
+        total_amount = Decimal(line_trade_settlement.findtext(
+                './{*}SpecifiedTradeSettlementLineMonetarySummation'
+                '/{*}LineTotalAmount'))
+        line.quantity = (
+            -abs(line.quantity) if total_amount.is_signed()
+            else abs(line.quantity)) * sign
+
+        taxes = []
+        for trade_tax in line_trade_settlement.iterfind(
+                './{*}ApplicableTradeTax'):
+            domain = cls._parse_cii_tax_domain(trade_tax)
+            domain.extend([
+                    ['OR',
+                        ('group', '=', None),
+                        ('group.kind', 'in', ['purchase', 'both']),
+                        ],
+                    ('company', '=', company.id),
+                    ])
+            try:
+                tax, = Tax.search(domain, limit=1)
+            except ValueError:
+                raise InvoiceError(gettext(
+                        'edocument_uncefact.msg_tax_not_found',
+                        tax_category=etree.tostring(
+                            trade_tax, pretty_print=True).decode()))
+            taxes.append(tax)
+        line.taxes = taxes
+        return line
+
+    @classmethod
+    def _parse_cii_party(cls, trade_party, create=False):
+        pool = Pool()
+        Party = pool.get('party.party')
+
+        for identifier in chain(
+                trade_party.iterfind('./{*}ID'),
+                trade_party.iterfind('./{*}GlobalID'),
+                trade_party.iterfind('./{*}SpecifiedLegalOrganization/{*}ID'),
+                trade_party.iterfind('./{*}SpecifiedTaxRegistration/{*}ID'),
+                ):
+            if identifier.text:
+                domain = [
+                    ('code_compact', '=', identifier.text),
+                    ]
+                if schemeId := identifier.get('schemeID'):
+                    if schemeId == 'VA':
+                        domain.append(('type', '=', 'eu_vat'))
+                parties = Party.search([
+                        ('identifiers', 'where', domain),
+                        ])
+                if len(parties) == 1:
+                    party, = parties
+                    return party
+        if create:
+            party = cls._create_cii_party(trade_party)
+            party.save()
+        return party
+
+    @classmethod
+    def _create_cii_party(cls, trade_party):
+        pool = Pool()
+        Party = pool.get('party.party')
+        party = Party()
+        party.name = (
+            trade_party.findtext('./{*}SpecifiedLegalOrganization/{*}Name')
+            or trade_party.findtext(
+                './{*}SpecifiedLegalOrganization/{*}TradingBusinessName')
+            or trade_party.findtext('./{*}Name'))
+        identifiers = []
+        identifiers_done = set()
+        for identifier in chain(
+                trade_party.iterfind('./{*}ID'),
+                trade_party.iterfind('./{*}GlobalID'),
+                trade_party.iterfind('./{*}SpecifiedLegalOrganization/{*}ID'),
+                trade_party.iterfind('./{*}SpecifiedTaxRegistration/{*}ID'),
+                ):
+            if identifier.text:
+                identifier_key = (identifier.text, identifier.get('schemeID'))
+                if identifier_key not in identifiers_done:
+                    identifiers.append(cls._create_cii_party_identifier(
+                            identifier))
+                identifiers_done.add(identifier_key)
+        party.identifiers = identifiers
+        if (address := trade_party.find('./{*}PostalTradeAddress')
+                ) is not None:
+            party.addresses = [cls._create_cii_address(address)]
+        return party
+
+    @classmethod
+    def _create_cii_party_identifier(cls, identifier):
+        pool = Pool()
+        Identifier = pool.get('party.identifier')
+        type = None
+        if schemeID := identifier.get('schemeID'):
+            if schemeID == 'VA':
+                type = 'eu_vat'
+        return Identifier(type=type, code=identifier.text)
+
+    @classmethod
+    def _parse_cii_address(cls, address_el, party):
+        pool = Pool()
+        Address = pool.get('party.address')
+
+        address = cls._create_cii_address(address_el)
+
+        domain = [('party', '=', party)]
+        for fname in Address._fields:
+            if value := getattr(address, fname, None):
+                domain.append((fname, '=', value))
+        try:
+            address, = Address.search(domain, limit=1)
+        except ValueError:
+            address.party = party
+            address.save()
+        return address
+
+    @classmethod
+    def _create_cii_address(cls, address_el):
+        pool = Pool()
+        Address = pool.get('party.address')
+        Country = pool.get('country.country')
+        Subdivision = pool.get('country.subdivision')
+
+        address = Address()
+
+        if address_el is None:
+            return address
+
+        address.postal_code = address_el.findtext('./{*}PostcodeCode')
+        address.post_box = address_el.findtext('./{*}PostOfficeBox')
+        address.building_name = address_el.findtext('./{*}BuildingName')
+        address.street_name = address_el.findtext('./{*}StreetName')
+        address.city = address_el.findtext('./{*}CityName')
+        if country_id := address_el.findtext('./{*}CountryID'):
+            try:
+                country, = Country.search([
+                        ('code', '=', country_id),
+                        ], limit=1)
+            except ValueError:
+                pass
+            else:
+                address.country = country
+                if subdivision_id := address_el.findtext(
+                        './{*}CountrySubDivisionID'):
+                    try:
+                        subdivision, = Subdivision.search([
+                                ('code', '=', subdivision_id),
+                                ('country', '=', country),
+                                ('type', 'in', country.subdivision_types),
+                                ], limit=1)
+                    except ValueError:
+                        pass
+                    else:
+                        address.subdivision = subdivision
+        address.building_number = address_el.findtext('./{*}BuildingNumber')
+
+        address.street_unstructured = '\n'.join(filter(None, (
+                    address_el.findtext('./{*}LineOne'),
+                    address_el.findtext('./{*}LineTwo'),
+                    address_el.findtext('./{*}LineThree'),
+                    address_el.findtext('./{*}LineFour'),
+                    address_el.findtext('./{*}LineFive'),
+                    )))
+        return address
+
+    @classmethod
+    def _parse_cii_tax_identifier(cls, tax_registrations, party, create=False):
+        pool = Pool()
+        Identifier = pool.get('party.identifier')
+
+        tax_identifier_types = party.tax_identifier_types()
+
+        for tax_registration in tax_registrations:
+            id_ = tax_registration.find('./{*}ID')
+            if id_ is not None:
+                scheme_id = id_.get('schemeID')
+                value = id_.text
+
+                for identifier in party.identifiers:
+                    if (identifier.type in tax_identifier_types
+                            and identifier.code_compact == value):
+                        return identifier
+                else:
+                    if create and scheme_id == 'VA':
+                        identifier = Identifier(
+                            party=party,
+                            type='eu_vat',
+                            code=value)
+                        identifier.save()
+                        return identifier
+
+    @classmethod
+    def _parse_cii_company(cls, trade_party):
+        pool = Pool()
+        Company = pool.get('company.company')
+
+        for identifier in chain(
+                trade_party.iterfind('./{*}ID'),
+                trade_party.iterfind('./{*}GlobalID'),
+                trade_party.iterfind('./{*}SpecifiedLegalOrganization/{*}ID'),
+                trade_party.iterfind('./{*}SpecifiedTaxRegistration/{*}ID'),
+                ):
+            if identifier.text:
+                domain = [
+                    ('code_compact', '=', identifier.text),
+                    ]
+                if schemeId := identifier.get('schemeID'):
+                    if schemeId == 'VA':
+                        domain.append(('type', '=', 'eu_vat'))
+                companies = Company.search([
+                        ('party.identifiers', 'where', domain),
+                        ])
+                if len(companies) == 1:
+                    company, = companies
+                    return company
+
+        for name in chain(
+                trade_party.iterfind(
+                    './{*}SpecifiedLegalOrganization/{*}Name'),
+                trade_party.iterfind(
+                    './{*}SpecifiedLegalOrganization/{*}TradingBusinessName'),
+                trade_party.iterfind('./{*}Name'),
+                ):
+            if name is None:
+                continue
+            companies = Company.search([
+                    ('party.name', '=', name.text),
+                    ])
+            if len(companies) == 1:
+                company, = companies
+                return company
+
+    @classmethod
+    def _parse_cii_payment_means(cls, payment_means, payees):
+        pool = Pool()
+        PaymentMean = pool.get('account.invoice.payment.mean')
+        means = []
+        for payment_mean in payment_means:
+            if instrument := cls._parse_cii_payment_mean(payment_mean, payees):
+                means.append(PaymentMean(instrument=instrument))
+        return means
+
+    @classmethod
+    def _parse_cii_payment_mean(cls, payment_mean, payees):
+        pass
+
+    @classmethod
+    def _parse_cii_payment_term_date(cls, payment_terms):
+        dates = []
+        for payment_term in payment_terms:
+            if (date := payment_term.find(
+                    './{*}DueDateDateTime/DateTimeString') is not None):
+                dates.append(dt.datetime.strptime(
+                        date.text,
+                        DATETIME_FORMATS.get(date.get('format') or '102')
+                        ).date())
+        return min(dates, default=None)
+
+    @classmethod
+    def _parse_cii_product(cls, product_el, supplier=None):
+        pool = Pool()
+        Product = pool.get('product.product')
+
+        for identifier in chain(
+                product_el.iterfind('./{*}ID'),
+                product_el.iterfind('./{*}GlobalID'),
+                product_el.iterfind('./{*}ManufacturerAssignedID'),
+                ):
+            if identifier.text:
+                domain = [
+                    ('code', '=', identifier.text),
+                    ]
+                # TOOD: schemeID
+                try:
+                    product, = Product.search([
+                            ('identifiers', 'where', domain),
+                            ], limit=1)
+                except ValueError:
+                    pass
+                else:
+                    return product
+        if code := product_el.findtext('./{*}BuyerAssignedID'):
+            try:
+                product, = Product.search([
+                        ('code', '=', code),
+                        ], limit=1)
+            except ValueError:
+                pass
+            else:
+                return product
+
+    @classmethod
+    def _parse_cii_line_reference(
+            cls, reference, line, company, supplier=None):
+        pass
+
+    @classmethod
+    def _parse_cii_tax_domain(cls, tax):
+        pool = Pool()
+        Tax = pool.get('account.tax')
+        domain = [
+            ('parent', '=', None),
+            ]
+        if (unece_category_code := tax.findtext('./{*}CategoryCode')
+                ) is not None:
+            domain.append(('unece_category_code', '=', unece_category_code))
+        if (unece_code := tax.findtext('./{*}TypeCode')) is not None:
+            domain.append(('unece_code', '=', unece_code))
+        if percent := tax.findtext('./{*}RateApplicablePercent'):
+            domain.append(('type', '=', 'percentage'))
+            domain.append(('rate', '=', Decimal(percent) / 100))
+        if (hasattr(Tax, 'vatex_code')
+                and (tax_exemption_reason_code := tax.findtext(
+                        './{*}ExemptionReasonCode'))):
+            domain.append(['OR',
+                    ('vatex_code', '=', tax_exemption_reason_code),
+                    ('vatex_code', '=', None),
+                    ])
+        return domain
+
+    @classmethod
+    def _parse_cii_tax(cls, tax, sign, company):
+        pool = Pool()
+        Tax = pool.get('account.tax')
+        InvoiceTax = pool.get('account.invoice.tax')
+
+        invoice_tax = InvoiceTax(manual=False)
+        domain = cls._parse_cii_tax_domain(tax)
+        domain.extend([
+                ['OR',
+                    ('group', '=', None),
+                    ('group.kind', 'in', ['purchase', 'both']),
+                    ],
+                ('company', '=', company.id),
+                ])
+        try:
+            invoice_tax.tax, = Tax.search(domain, limit=1)
+        except ValueError:
+            raise InvoiceError(gettext(
+                    'edocument_uncefact.msg_tax_not_found',
+                    tax_category=etree.tostring(
+                        tax, pretty_print=True).decode()))
+
+        invoice_tax.amount = sign * Decimal(
+            tax.findtext('./{*}CalculatedAmount'))
+        if (basis_amount := tax.findtext('./{*}BasisAmount')) is not None:
+            invoice_tax.base = Decimal(basis_amount)
+        else:
+            # Use tax amount to define the sign of unknown base
+            invoice_tax.base = invoice_tax.amount
+
+        invoice_tax.on_change_tax()
+
+        return invoice_tax
+
+    @classmethod
+    def _parse_cii_attachments(cls, root):
+        yield from ()
+
+
+class Invoice_Bank(metaclass=PoolMeta):
+    __name__ = 'edocument.uncefact.invoice'
+
+    @classmethod
+    def _parse_cii_payment_mean(cls, payment_mean, payees):
+        pool = Pool()
+        Account = pool.get('bank.account')
+        instrument = super()._parse_cii_payment_mean(payment_mean, payees)
+        if (financial_account := payment_mean.find(
+                    './{*}PayeePartyCreditorFinancialAccount')) is not None:
+            if iban := financial_account.findtext('./{*}IBANID'):
+                try:
+                    account, = Account.search([
+                            ('numbers', 'where', ['OR',
+                                    ('number', '=', iban),
+                                    ('number_compact', '=', iban),
+                                    ]),
+                            ('owners.id', 'in', payees),
+                            ], limit=1)
+                except ValueError:
+                    party = payees[-1]
+                    if party.id in Transaction().create_records['party.party']:
+                        account = cls._create_cii_bank_account(
+                            financial_account, party)
+                        account.save()
+                    else:
+                        raise InvoiceError(ngettext(
+                                'edocument_uncefact.'
+                                'msg_bank_account_not_found',
+                                len(payees),
+                                parties=', '.join(
+                                    [p.rec_name for p in payees]),
+                                account=etree.tostring(
+                                    financial_account,
+                                    pretty_print=True).decode()))
+                instrument = account
+        return instrument
+
+    @classmethod
+    def _create_cii_bank_account(cls, financial_account, party):
+        pool = Pool()
+        Bank = pool.get('bank')
+        Account = pool.get('bank.account')
+        Number = pool.get('bank.account.number')
+        account = Account(owners=[party])
+        if identifier := financial_account.findtext('./{*}IBANID'):
+            number = Number(number=identifier)
+            if iban.is_valid(identifier):
+                number.type = 'iban'
+            else:
+                number.type = 'other'
+            account.numbers = [number]
+        if identifier := financial_account.findtext(
+                './{*}PayeeSpecifiedCreditorFinancialInstitution/{*}BICID'):
+            if bic.is_valid(identifier):
+                account.bank = Bank.from_bic(identifier)
+        return account
+
+
+class Invoice_Purchase(metaclass=PoolMeta):
+    __name__ = 'edocument.uncefact.invoice'
+
+    @classmethod
+    def _parse_cii_product(cls, product_el, supplier=None):
+        pool = Pool()
+        Product = pool.get('product.product')
+
+        product = super()._parse_cii_product(product_el, supplier=supplier)
+
+        if (not product
+                and supplier
+                and (code := product_el.findtext('./{*}SellerAssignedID'))):
+            try:
+                product, = Product.search([
+                        ('product_suppliers', 'where', [
+                                ('party', '=', supplier),
+                                ('code', '=', code),
+                                ]),
+                        ], limit=1)
+            except ValueError:
+                pass
+        return product
+
+    @classmethod
+    def _parse_cii_line_reference(
+            cls, reference, line, company, supplier=None):
+        pool = Pool()
+        PurchaseLine = pool.get('purchase.line')
+        UoM = pool.get('product.uom')
+
+        origin = super()._parse_cii_line_reference(
+            reference, line, company, supplier=supplier)
+        if origin:
+            return origin
+        if not line or not line.product or not line.unit:
+            return
+
+        if reference.tag not in {
+                'SellerOrderReferencedDocument',
+                'BuyerOrderReferencedDocument',
+                'QuotationReferencedDocument'}:
+            return
+
+        if numbers := list(filter(None, [
+                        reference.findtext('./{*}IssuerAssignedID'),
+                        reference.findtext('./{*}URIID'),
+                        reference.findtext('./{*}GlobalID'),
+                        reference.findtext('./{*}RevisionID'),
+                        ])):
+            purchase_lines = PurchaseLine.search([
+                    ('purchase.company', '=', company),
+                    ('purchase.rec_name', 'in', numbers),
+                    ('type', '=', 'line'),
+                    ('product', '=', line.product),
+                    ])
+            if purchase_lines:
+                quantities = []
+                for purchase_line in purchase_lines:
+                    quantity = UoM.compute_qty(
+                        purchase_line.unit, purchase_line.quantity, line.unit)
+                    quantities.append((quantity, purchase_line))
+                key = itemgetter(0)
+                quantities.sort(key=key)
+                index = bisect.bisect_left(quantities, line.quantity, key=key)
+                if index >= len(quantities):
+                    index = -1
+                origin = quantities[index][1]
+        return origin
diff -r 507dd9ca1648 -r 56a9566c4b88 modules/edocument_uncefact/exceptions.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/edocument_uncefact/exceptions.py  Mon Aug 31 12:26:39 2026 +0200
@@ -0,0 +1,8 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+from trytond.exceptions import UserError
+
+
+class InvoiceError(UserError):
+    pass
diff -r 507dd9ca1648 -r 56a9566c4b88 modules/edocument_uncefact/message.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/edocument_uncefact/message.xml    Mon Aug 31 12:26:39 2026 +0200
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tryton>
+    <data grouped="1">
+        <record model="ir.message" id="msg_invoice_type_code_unsupported">
+            <field name="text">Unsupported invoice type code 
"%(type_code)s".</field>
+        </record>
+        <record model="ir.message" id="msg_company_not_found">
+            <field name="text">Could not find the company for:
+%(company)s</field>
+        </record>
+        <record model="ir.message" id="msg_currency_not_found">
+            <field name="text">Could not find a currency with code 
"%(code)s".</field>
+        </record>
+        <record model="ir.message" id="msg_unit_not_found">
+            <field name="text">Could not find a unit with UNECE code 
"%(code)s" and at least %(digits)s digit.</field>
+            <field name="text_plural">Could not find a unit with UNECE code 
"%(code)s" and at least %(digits)s digits.</field>
+        </record>
+        <record model="ir.message" id="msg_tax_not_found">
+            <field name="text">Could not find tax for:
+%(tax_category)s</field>
+        </record>
+        <record model="ir.message" id="msg_bank_account_not_found">
+            <field name="text">Could not find bank account "%(account)s" for 
party "%(parties)s".</field>
+            <field name="text_plural">Could not find bank account 
"%(account)s" for any of the parties "%(parties)s".</field>
+        </record>
+    </data>
+</tryton>
diff -r 507dd9ca1648 -r 56a9566c4b88 modules/edocument_uncefact/pyproject.toml
--- a/modules/edocument_uncefact/pyproject.toml Thu Jul 30 09:35:03 2026 +0200
+++ b/modules/edocument_uncefact/pyproject.toml Mon Aug 31 12:26:39 2026 +0200
@@ -45,6 +45,7 @@
     'tests/**/*.rst',
     'tests/**/*.json',
     'tests/**/*.xsd',
+    'tests/**/*.xml',
     ]
 exclude = ['doc']
 
@@ -52,7 +53,11 @@
 "" = "trytond/modules/edocument_uncefact"
 
 [tool.hatch.metadata.hooks.tryton]
-dependencies = ['Genshi']
+dependencies = [
+    'Genshi',
+    'lxml',
+    'python-stdnum',
+    ]
 copyright = 'COPYRIGHT'
 readme = 'README.rst'
 
@@ -60,4 +65,4 @@
 test = ['lxml']
 
 [tool.hatch.metadata.hooks.tryton.tryton-optional-dependencies]
-test = []
+test = ['proteus']
diff -r 507dd9ca1648 -r 56a9566c4b88 
modules/edocument_uncefact/tests/CII_example1.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/edocument_uncefact/tests/CII_example1.xml Mon Aug 31 12:26:39 
2026 +0200
@@ -0,0 +1,639 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+    Licensed under European Union Public Licence (EUPL) version 1.2.
+
+-->
+<!-- XML instance generated by Andreas Pelekies -->
+<!-- Example 1: Invoice with multiple line items for EN16931 -->
+<!-- Timestamp: 2017-08-24 00:00:00 +0200 -->
+<rsm:CrossIndustryInvoice xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+ 
xsi:schemaLocation="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100
 
../schema/D16B%20SCRDM%20(Subset)/uncoupled%20clm/CII/uncefact/data/standard/CrossIndustryInvoice_100pD16B.xsd"
+ xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
+ xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
+ xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
+ 
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100">
+    <rsm:ExchangedDocumentContext>
+        <ram:GuidelineSpecifiedDocumentContextParameter>
+            <ram:ID>urn:cen.eu:en16931:2017</ram:ID>
+        </ram:GuidelineSpecifiedDocumentContextParameter>
+    </rsm:ExchangedDocumentContext>
+    <rsm:ExchangedDocument>
+        <ram:ID>12115118</ram:ID>
+        <ram:TypeCode>380</ram:TypeCode>
+        <ram:IssueDateTime>
+            <udt:DateTimeString format="102">20150109</udt:DateTimeString>
+        </ram:IssueDateTime>
+        <ram:IncludedNote>
+            <ram:Content>Alle leveringen zijn franco. Alle prijzen zijn incl. 
BTW. Betalingstermijn: 14 dagen netto. Prijswijzigingen voorbehouden. Op al 
onze aanbiedingen, leveringen en overeenkomsten zijn van toepassing in de 
algemene verkoop en leveringsvoorwaarden. Gedeponeerd bij de K.v.K. te 
Amsterdam 25-04-'85.</ram:Content>
+            <ram:SubjectCode>AAR</ram:SubjectCode>            
+        </ram:IncludedNote>
+    </rsm:ExchangedDocument>
+    <rsm:SupplyChainTradeTransaction>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>1</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>166022</ram:SellerAssignedID>
+                <ram:Name>PATAT FRITES 10MM 10KG</ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>9.95</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">2</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>19.9</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>2</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>661813</ram:SellerAssignedID>
+                <ram:Name>KAAS 50PL. JONG BEL. 1KG</ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>9.85</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>9.85</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>3</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>438146</ram:SellerAssignedID>
+                <ram:Name>POT KETCHUP 3 LT</ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>8.29</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>8.29</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>4</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>438103</ram:SellerAssignedID>
+                <ram:Name>FRITESSAUS 3 LRR</ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>7.23</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">2</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>14.46</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>5</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>666955</ram:SellerAssignedID>
+                <ram:Name>KOFFIE BLIK 3,5KG SNELF </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>35</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>35</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>6</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>664871</ram:SellerAssignedID>
+                <ram:Name>KOFFIE 3.5 KG BLIK STAND </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>35</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>35</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>7</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>350257</ram:SellerAssignedID>
+                <ram:Name>SUIKERKLONT</ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>10.65</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>10.65</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>8</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>350258</ram:SellerAssignedID>
+                <ram:Name>1 KG UL BLOKJES </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>1.55</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>1.55</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>9</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>999998</ram:SellerAssignedID>
+                <ram:Name>BLOCKNOTE A5 </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>4.79</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">3</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>14.37</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>10</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>740810</ram:SellerAssignedID>
+                <ram:Name>CHIPS NAT KLEIN ZAKJES</ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>8.29</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>8.29</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>11</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>740829</ram:SellerAssignedID>
+                <ram:Name>CHIPS PAP KLEINE ZAKJES</ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>8.29</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">2</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>16.58</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>12</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>740828</ram:SellerAssignedID>
+                <ram:Name>TR KL PAKJES APPELSAP </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>9.95</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>9.95</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>13</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>740827</ram:SellerAssignedID>
+                <ram:Name>PK CHOCOLADEMELK </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>1.65</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">2</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>3.3</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>14</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>999996</ram:SellerAssignedID>
+                <ram:Name>KRAT BIER </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>10.8</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>21</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>10.8</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>15</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>999995</ram:SellerAssignedID>
+                <ram:Name>STATIEGELD </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>3.9</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>3.9</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>16</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>102172</ram:SellerAssignedID>
+                <ram:Name>BLEEK 3 X 750 ML </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>3.8</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">2</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>21</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>7.6</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>17</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>999994</ram:SellerAssignedID>
+                <ram:Name>WC PAPIER </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>4.67</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">2</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>21</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>9.34</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>18</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>999993</ram:SellerAssignedID>
+                <ram:Name>BALPENNEN 50 ST BLAUW </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>18.63</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">1</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>21</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>18.63</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>19</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>999992</ram:SellerAssignedID>
+                <ram:Name>EM FRITUURVET </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>17.02</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">6</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>102.12</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:IncludedSupplyChainTradeLineItem>
+            <ram:AssociatedDocumentLineDocument>
+                <ram:LineID>20</ram:LineID>
+            </ram:AssociatedDocumentLineDocument>
+            <ram:SpecifiedTradeProduct>
+                <ram:SellerAssignedID>175137</ram:SellerAssignedID>
+                <ram:Name>FRITUUR VET 10 KG RETOUR </ram:Name>
+            </ram:SpecifiedTradeProduct>
+            <ram:SpecifiedLineTradeAgreement>
+                <ram:NetPriceProductTradePrice>
+                    <ram:ChargeAmount>18.33</ram:ChargeAmount>
+                </ram:NetPriceProductTradePrice>
+            </ram:SpecifiedLineTradeAgreement>
+            <ram:SpecifiedLineTradeDelivery>
+                <ram:BilledQuantity unitCode="H87">6</ram:BilledQuantity>
+            </ram:SpecifiedLineTradeDelivery>
+            <ram:SpecifiedLineTradeSettlement>
+                <ram:ApplicableTradeTax>
+                    <ram:TypeCode>VAT</ram:TypeCode>
+                    <ram:CategoryCode>S</ram:CategoryCode>
+                    <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+                </ram:ApplicableTradeTax>
+                <ram:SpecifiedTradeSettlementLineMonetarySummation>
+                    <ram:LineTotalAmount>-109.98</ram:LineTotalAmount>
+                </ram:SpecifiedTradeSettlementLineMonetarySummation>
+            </ram:SpecifiedLineTradeSettlement>
+        </ram:IncludedSupplyChainTradeLineItem>
+        <ram:ApplicableHeaderTradeAgreement>
+            <ram:SellerTradeParty>
+                <ram:Name>De Koksmaat</ram:Name>
+                <ram:SpecifiedLegalOrganization>
+                    <ram:ID>57151520</ram:ID>
+                </ram:SpecifiedLegalOrganization>
+                <ram:PostalTradeAddress>
+                    <ram:PostcodeCode>1950 AB</ram:PostcodeCode>
+                    <ram:LineOne>Postbus 7l</ram:LineOne>
+                    <ram:CityName>Velsen-Noord</ram:CityName>
+                    <ram:CountryID>NL</ram:CountryID>
+                </ram:PostalTradeAddress>
+                <ram:SpecifiedTaxRegistration>
+                    <ram:ID schemeID="VA">NL8200.98.395.B.01</ram:ID>
+                </ram:SpecifiedTaxRegistration>
+            </ram:SellerTradeParty>
+            <ram:BuyerTradeParty>
+                <ram:ID>10202</ram:ID>
+                <ram:Name>ODIN 59</ram:Name>
+                <ram:DefinedTradeContact>
+                    <ram:PersonName>Dhr. J BLOKKER</ram:PersonName>
+                </ram:DefinedTradeContact>
+                <ram:PostalTradeAddress>
+                    <ram:PostcodeCode>1960 AJ</ram:PostcodeCode>
+                    <ram:LineOne>POSTBUS 367</ram:LineOne>
+                    <ram:CityName>HEEMSKERK</ram:CityName>
+                    <ram:CountryID>NL</ram:CountryID>
+                </ram:PostalTradeAddress>
+            </ram:BuyerTradeParty>
+        </ram:ApplicableHeaderTradeAgreement>
+        <ram:ApplicableHeaderTradeDelivery/>
+        <ram:ApplicableHeaderTradeSettlement>
+            <ram:PaymentReference>Deb. 10202 / Fact. 
12115118</ram:PaymentReference>
+            <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
+            <ram:SpecifiedTradeSettlementPaymentMeans>
+                <ram:TypeCode>30</ram:TypeCode>
+                <ram:PayeePartyCreditorFinancialAccount>
+                    <ram:IBANID>NL57 RABO 0107307510</ram:IBANID>
+                </ram:PayeePartyCreditorFinancialAccount>
+            </ram:SpecifiedTradeSettlementPaymentMeans>
+            <ram:ApplicableTradeTax>
+                <ram:CalculatedAmount>10.99</ram:CalculatedAmount>
+                <ram:TypeCode>VAT</ram:TypeCode>
+                <ram:BasisAmount>183.23</ram:BasisAmount>
+                <ram:CategoryCode>S</ram:CategoryCode>
+                <ram:RateApplicablePercent>6</ram:RateApplicablePercent>
+            </ram:ApplicableTradeTax>
+            <ram:ApplicableTradeTax>
+                <ram:CalculatedAmount>9.74</ram:CalculatedAmount>
+                <ram:TypeCode>VAT</ram:TypeCode>
+                <ram:BasisAmount>46.37</ram:BasisAmount>
+                <ram:CategoryCode>S</ram:CategoryCode>
+                <ram:RateApplicablePercent>21</ram:RateApplicablePercent>
+            </ram:ApplicableTradeTax>
+            <ram:SpecifiedTradePaymentTerms>
+                <ram:DueDateDateTime><udt:DateTimeString 
format="102">20150109</udt:DateTimeString></ram:DueDateDateTime>
+            </ram:SpecifiedTradePaymentTerms>
+            <ram:SpecifiedTradeSettlementHeaderMonetarySummation>
+                <ram:LineTotalAmount>229.6</ram:LineTotalAmount>
+                <ram:TaxBasisTotalAmount>229.6</ram:TaxBasisTotalAmount>
+                <ram:TaxTotalAmount currencyID="EUR">20.73</ram:TaxTotalAmount>
+                <ram:GrandTotalAmount>250.33</ram:GrandTotalAmount>
+                <ram:DuePayableAmount>250.33</ram:DuePayableAmount>
+            </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
+        </ram:ApplicableHeaderTradeSettlement>
+    </rsm:SupplyChainTradeTransaction>
+</rsm:CrossIndustryInvoice>
diff -r 507dd9ca1648 -r 56a9566c4b88 
modules/edocument_uncefact/tests/scenario_cii_invoice_parse.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/edocument_uncefact/tests/scenario_cii_invoice_parse.rst   Mon Aug 
31 12:26:39 2026 +0200
@@ -0,0 +1,84 @@
+=================
+CII Invoice Parse
+=================
+
+Imports::
+
+    >>> import datetime as dt
+    >>> from decimal import Decimal
+
+    >>> from proteus import Model
+    >>> from trytond.modules.account.tests.tools import create_chart, 
create_tax
+    >>> from trytond.modules.company.tests.tools import create_company, 
get_company
+    >>> from trytond.modules.currency.tests.tools import get_currency
+    >>> from trytond.tests.tools import activate_modules, assertEqual
+    >>> from trytond.tools import file_open
+
+Activate modules::
+
+    >>> modules = ['edocument_uncefact', 'account_invoice', 'bank', 'purchase']
+    >>> config = activate_modules(modules, create_company, create_chart)
+
+    >>> EInvoice = Model.get('edocument.uncefact.invoice')
+    >>> Invoice = Model.get('account.invoice')
+    >>> UoM = Model.get('product.uom')
+
+Setup company::
+
+    >>> company = get_company()
+    >>> company.party.name = 'ODIN 59'
+    >>> company.party.save()
+
+Create currency::
+
+    >>> eur = get_currency('EUR')
+    >>> eur.save()
+
+Create tax::
+
+    >>> tax21 = create_tax(Decimal('.21'))
+    >>> tax21.unece_category_code = 'S'
+    >>> tax21.unece_code = 'VAT'
+    >>> tax21.save()
+
+    >>> tax6 = create_tax(Decimal('.06'))
+    >>> tax6.unece_category_code = 'S'
+    >>> tax6.unece_code = 'VAT'
+    >>> tax6.save()
+
+Create unit::
+
+    >>> uom, = UoM.find([('name', '=', "Unit")])
+    >>> _ = uom.duplicate({'name': "piece", 'unece_code': 'H87'})
+
+Parse the CII::
+
+    >>> with file_open(
+    ...         'edocument_uncefact/tests/CII_example1.xml',
+    ...         mode='rb') as fp:
+    ...     invoice_id = EInvoice.parse(fp.read(), {**config.context, 
'company': None})
+
+    >>> invoice = Invoice(invoice_id)
+    >>> invoice.reference
+    '12115118'
+    >>> assertEqual(invoice.invoice_date, dt.date(2015, 1, 9))
+    >>> invoice.party.name
+    'De Koksmaat'
+    >>> invoice.invoice_address.rec_name
+    'De Koksmaat, Postbus 7l, 1950 AB, Velsen-Noord'
+    >>> assertEqual(invoice.company, company)
+    >>> invoice.total_amount
+    Decimal('250.33')
+    >>> invoice.tax_amount
+    Decimal('20.73')
+    >>> assertEqual(invoice.source_untaxed_amount, Decimal('229.60'))
+    >>> assertEqual(invoice.source_tax_amount, Decimal('20.73'))
+    >>> assertEqual(invoice.source_total_amount, Decimal('250.33'))
+    >>> len(invoice.lines)
+    20
+
+    >>> payment_mean, = invoice.payment_means
+    >>> account_number, = payment_mean.instrument.numbers
+    >>> account_number.number
+    'NL57 RABO 0107 3075 10'
+    >>> assertEqual(payment_mean.instrument.owners, [invoice.party])
diff -r 507dd9ca1648 -r 56a9566c4b88 
modules/edocument_uncefact/tests/test_module.py
--- a/modules/edocument_uncefact/tests/test_module.py   Thu Jul 30 09:35:03 
2026 +0200
+++ b/modules/edocument_uncefact/tests/test_module.py   Mon Aug 31 12:26:39 
2026 +0200
@@ -110,7 +110,7 @@
 class EDocumentUNCEFACTTestCase(ModuleTestCase):
     'Test EDocument UN/CEFACT module'
     module = 'edocument_uncefact'
-    extras = ['account_invoice']
+    extras = ['account_invoice', 'bank', 'purchase']
 
     @with_transaction()
     def test_16B_CII_CrossIndustryInvoice(self):
diff -r 507dd9ca1648 -r 56a9566c4b88 
modules/edocument_uncefact/tests/test_scenario.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/edocument_uncefact/tests/test_scenario.py Mon Aug 31 12:26:39 
2026 +0200
@@ -0,0 +1,8 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+from trytond.tests.test_tryton import load_doc_tests
+
+
+def load_tests(*args, **kwargs):
+    return load_doc_tests(__name__, __file__, *args, **kwargs)
diff -r 507dd9ca1648 -r 56a9566c4b88 modules/edocument_uncefact/tryton.cfg
--- a/modules/edocument_uncefact/tryton.cfg     Thu Jul 30 09:35:03 2026 +0200
+++ b/modules/edocument_uncefact/tryton.cfg     Mon Aug 31 12:26:39 2026 +0200
@@ -1,14 +1,26 @@
 [tryton]
 version=8.1.0
 depends:
+    edocument_unece
     ir
-    edocument_unece
+    party
 extras_depend:
     account_invoice
+    bank
+    purchase
 xml:
     account.xml
+    message.xml
 
 [register account_invoice]
 model:
     edocument.Invoice
     account.InvoiceEdocumentStart
+
+[register account_invoice bank]
+model:
+    edocument.Invoice_Bank
+
+[register account_invoice purchase]
+model:
+    edocument.Invoice_Purchase

Reply via email to