Parse Cross Industry Invoice as supplier invoice
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Parse Cross Industry Invoice as supplier invoice
|
||||
|
||||
Version 8.0.0 - 2026-04-20
|
||||
--------------------------
|
||||
|
||||
@@ -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 @@ def remove_comment(stream):
|
||||
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 @@ class Invoice(Model):
|
||||
super().__setup__()
|
||||
cls.__rpc__.update({
|
||||
'render': RPC(instantiate=0),
|
||||
'parse': RPC(readonly=False, result=int),
|
||||
})
|
||||
|
||||
def __init__(self, invoice):
|
||||
@@ -217,3 +238,779 @@ class Invoice(Model):
|
||||
@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
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -45,6 +45,7 @@ include = [
|
||||
'tests/**/*.rst',
|
||||
'tests/**/*.json',
|
||||
'tests/**/*.xsd',
|
||||
'tests/**/*.xml',
|
||||
]
|
||||
exclude = ['doc']
|
||||
|
||||
@@ -52,7 +53,11 @@ exclude = ['doc']
|
||||
"" = "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 @@ readme = 'README.rst'
|
||||
test = ['lxml']
|
||||
|
||||
[tool.hatch.metadata.hooks.tryton.tryton-optional-dependencies]
|
||||
test = []
|
||||
test = ['proteus']
|
||||
|
||||
@@ -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>
|
||||
@@ -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])
|
||||
@@ -110,7 +110,7 @@ def get_invoice():
|
||||
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):
|
||||
|
||||
@@ -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)
|
||||
@@ -1,14 +1,26 @@
|
||||
[tryton]
|
||||
version=8.1.0
|
||||
depends:
|
||||
ir
|
||||
edocument_unece
|
||||
ir
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user