[ADD] account_edi_ubl_bis3,l10n_nl_edi : added bis3 and nlcius formats
closes odoo/odoo#68514 Signed-off-by: Laurent Smet <smetl@users.noreply.github.com>
This commit is contained in:
committed by
Benjamin Frantzen (bfr)
parent
b58810a77b
commit
6db346767e
@@ -420,7 +420,7 @@ class AccountEdiFormat(models.Model):
|
||||
element = xml_element.xpath(xpath, namespaces=namespaces)
|
||||
return element[0].text if element else None
|
||||
|
||||
def _retrieve_partner(self, name=None, phone=None, mail=None, vat=None):
|
||||
def _retrieve_partner(self, name=None, phone=None, mail=None, vat=None, domain=None):
|
||||
'''Search all partners and find one that matches one of the parameters.
|
||||
|
||||
:param name: The name of the partner.
|
||||
@@ -430,14 +430,17 @@ class AccountEdiFormat(models.Model):
|
||||
:returns: A partner or an empty recordset if not found.
|
||||
'''
|
||||
domains = []
|
||||
for value, domain in (
|
||||
for value, dom in (
|
||||
(name, [('name', 'ilike', name)]),
|
||||
(phone, expression.OR([[('phone', '=', phone)], [('mobile', '=', phone)]])),
|
||||
(mail, [('email', '=', mail)]),
|
||||
(vat, [('vat', 'like', vat)]),
|
||||
):
|
||||
if value is not None:
|
||||
domains.append(domain)
|
||||
domains.append(dom)
|
||||
|
||||
if domain:
|
||||
domains.append(domain)
|
||||
|
||||
domain = expression.OR(domains)
|
||||
return self.env['res.partner'].search(domain, limit=1)
|
||||
|
||||
@@ -433,6 +433,7 @@ class AccountMoveLine(models.Model):
|
||||
taxes_added_to_base.add(tax.id)
|
||||
|
||||
vals['tax_amount_currency'] += tax_vals['amount']
|
||||
vals['tax_amount_currency_closing'] += tax_vals['amount'] if tax_rep.use_in_tax_closing else 0
|
||||
for tag_id in tax_rep.tag_ids:
|
||||
vals['tag_ids'].add(tag_id)
|
||||
|
||||
|
||||
@@ -234,6 +234,14 @@ class AccountEdiFormat(models.Model):
|
||||
return super()._is_compatible_with_journal(journal)
|
||||
return journal.type == 'sale'
|
||||
|
||||
def _is_enabled_by_default_on_journal(self, journal):
|
||||
# OVERRIDE
|
||||
# UBL is disabled by default to prevent conflict with other implementations of UBL.
|
||||
self.ensure_one()
|
||||
if self.code != 'ubl_2_1':
|
||||
return super()._is_enabled_by_default_on_journal(journal)
|
||||
return False
|
||||
|
||||
def _post_invoice_edi(self, invoices, test_mode=False):
|
||||
# OVERRIDE
|
||||
self.ensure_one()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Import/Export invoices with UBL (BIS3)',
|
||||
'description': '''
|
||||
Support for Export/Import in UBL format (BIS3).
|
||||
''',
|
||||
'version': '1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'depends': ['account_edi_ubl'],
|
||||
'data': [
|
||||
'data/bis3_templates.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
res.partner
|
||||
-->
|
||||
<template id="export_bis3_invoice_partner">
|
||||
<cac:Party
|
||||
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
||||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||||
<t t-set="partner" t-value="partner_vals['partner']"/>
|
||||
<cbc:EndpointID
|
||||
t-att-schemeID="partner_vals['bis3_endpoint_scheme']"
|
||||
t-esc="partner_vals['bis3_endpoint']"/>
|
||||
<cac:PartyIdentification t-if="partner_vals.get('partner_identification')">
|
||||
<cbc:ID t-esc="partner_vals['partner_identification']"/>
|
||||
</cac:PartyIdentification>
|
||||
<cac:PartyName>
|
||||
<cbc:Name t-esc="partner.name"/>
|
||||
</cac:PartyName>
|
||||
<cac:PostalAddress>
|
||||
<cbc:StreetName
|
||||
t-if="partner.street"
|
||||
t-esc="partner.street"/>
|
||||
<cbc:AdditionalStreetName
|
||||
t-if="partner.street2"
|
||||
t-esc="partner.street2"/>
|
||||
<cbc:CityName
|
||||
t-if="partner.city"
|
||||
t-esc="partner.city"/>
|
||||
<cbc:PostalZone
|
||||
t-if="partner.zip"
|
||||
t-esc="partner.zip"/>
|
||||
<cbc:CountrySubentity
|
||||
t-if="partner.state_id"
|
||||
t-esc="partner.state_id.name"/>
|
||||
<cac:Country
|
||||
t-if="partner.country_id">
|
||||
<cbc:IdentificationCode t-esc="partner.country_id.code"/>
|
||||
</cac:Country>
|
||||
</cac:PostalAddress>
|
||||
<cac:PartyTaxScheme t-if="partner.vat">
|
||||
<cbc:CompanyID t-esc="partner.vat"/>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:PartyTaxScheme>
|
||||
<cac:PartyLegalEntity>
|
||||
<cbc:RegistrationName t-esc="partner.name"/>
|
||||
<cbc:CompanyID
|
||||
t-if="partner_vals.get('legal_entity')"
|
||||
t-att-schemeID="partner_vals['legal_entity_scheme']"
|
||||
t-esc="partner_vals['legal_entity']"/>
|
||||
</cac:PartyLegalEntity>
|
||||
<cac:Contact>
|
||||
<cbc:Name t-esc="partner.name"/>
|
||||
<cbc:Telephone t-if="partner.phone" t-esc="partner.phone"/>
|
||||
<cbc:ElectronicMail t-if="partner.email" t-esc="partner.email"/>
|
||||
</cac:Contact>
|
||||
</cac:Party>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
account.move.line
|
||||
-->
|
||||
<template id="export_bis3_invoice_line">
|
||||
<cac:InvoiceLine
|
||||
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
||||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||||
<t t-set="line" t-value="line_vals['line']"/>
|
||||
<cbc:ID t-esc="line_vals['index']"/>
|
||||
<cbc:Note t-if="line.discount">Discount (<t t-esc='line.discount'/> %)</cbc:Note>
|
||||
<cbc:InvoicedQuantity unitCode="ZZ" t-esc="line.quantity"/>
|
||||
<cbc:LineExtensionAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(line_vals['price_subtotal_with_no_tax_closing'])"/>
|
||||
<cac:Item>
|
||||
<cbc:Description t-esc="line.name.replace('\n', ', ')"/>
|
||||
<cbc:Name t-esc="(line.name or '').replace('\n', ', ')"/>
|
||||
<cac:SellersItemIdentification t-if="line.product_id.default_code">
|
||||
<cbc:ID t-esc="line.product_id.default_code"/>
|
||||
</cac:SellersItemIdentification>
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID t-esc="line_vals['tax_detail_vals']['tax_category']"/>
|
||||
<cbc:Percent t-esc="line_vals['tax_detail_vals']['tax_percent']"/>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(line_vals['price_subtotal_with_no_tax_closing'] / line.quantity)"/>
|
||||
<cbc:BaseQuantity t-esc="line.quantity"/>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
invoice
|
||||
-->
|
||||
<template id="export_bis3_invoice">
|
||||
<Invoice
|
||||
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
||||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||||
<cbc:CustomizationID t-esc="customization_id"/>
|
||||
<cbc:ProfileID t-esc="profile_id"/>
|
||||
<cbc:ID t-esc="record.name"/>
|
||||
<cbc:IssueDate t-esc="record.invoice_date"/>
|
||||
<cbc:DueDate t-esc="record.invoice_date_due"/>
|
||||
<cbc:InvoiceTypeCode t-esc="type_code"/>
|
||||
<cbc:Note t-esc="record.narration"/>
|
||||
<cbc:DocumentCurrencyCode t-esc="record.currency_id.name"/>
|
||||
<cbc:TaxCurrencyCode t-if="record.currency_id != record.company_currency_id"
|
||||
t-esc="record.company_currency_id.name"/>
|
||||
<cbc:BuyerReference t-esc="record.commercial_partner_id.name"/>
|
||||
<cac:OrderReference t-if="record.invoice_origin">
|
||||
<cbc:ID t-esc="record.invoice_origin"/>
|
||||
</cac:OrderReference>
|
||||
<cac:AccountingSupplierParty t-call="account_edi_ubl_bis3.export_bis3_invoice_partner">
|
||||
<t t-set="partner_vals" t-value="supplier_vals"/>
|
||||
</cac:AccountingSupplierParty>
|
||||
<cac:AccountingCustomerParty t-call="account_edi_ubl_bis3.export_bis3_invoice_partner">
|
||||
<t t-set="partner_vals" t-value="customer_vals"/>
|
||||
</cac:AccountingCustomerParty>
|
||||
<cac:PaymentMeans>
|
||||
<cbc:PaymentMeansCode t-esc="payment_means_code"/>
|
||||
<cac:PayeeFinancialAccount t-if="bank_account">
|
||||
<cbc:ID t-esc="bank_account.acc_number"/>
|
||||
</cac:PayeeFinancialAccount>
|
||||
</cac:PaymentMeans>
|
||||
<cac:PaymentTerms t-if="record.invoice_payment_term_id">
|
||||
<cbc:Note t-esc="record.invoice_payment_term_id.name"/>
|
||||
</cac:PaymentTerms>
|
||||
<cac:TaxTotal>
|
||||
<cbc:TaxAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(total_tax_amount_currency)"/>
|
||||
<cac:TaxSubtotal t-foreach="tax_detail_vals_list" t-as="tax_detail_vals">
|
||||
<cbc:TaxableAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(tax_detail_vals['base_currency'])"/>
|
||||
<cbc:TaxAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(tax_detail_vals['amount_currency'])"/>
|
||||
<cac:TaxCategory>
|
||||
<cbc:ID t-esc="tax_detail_vals['tax_category']"/>
|
||||
<cbc:Percent t-esc="tax_detail_vals['tax_percent']"/>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:TaxCategory>
|
||||
</cac:TaxSubtotal>
|
||||
</cac:TaxTotal>
|
||||
<cac:TaxTotal t-if="record.currency_id != record.company_currency_id">
|
||||
<cbc:TaxAmount
|
||||
t-att-currencyID="record.company_currency_id.name"
|
||||
t-esc="format_monetary(total_tax_amount)"/>
|
||||
</cac:TaxTotal>
|
||||
<cac:LegalMonetaryTotal>
|
||||
<cbc:LineExtensionAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(total_untaxed_amount)"/>
|
||||
<cbc:TaxExclusiveAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(total_untaxed_amount)"/>
|
||||
<cbc:TaxInclusiveAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(record.amount_total)"/>
|
||||
<cbc:PrepaidAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(record.amount_total - record.amount_residual)"/>
|
||||
<cbc:PayableAmount
|
||||
t-att-currencyID="record.currency_id.name"
|
||||
t-esc="format_monetary(record.amount_residual)"/>
|
||||
</cac:LegalMonetaryTotal>
|
||||
<t t-foreach="invoice_line_vals_list" t-as="line_vals">
|
||||
<t t-call="account_edi_ubl_bis3.export_bis3_invoice_line"/>
|
||||
</t>
|
||||
</Invoice>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import account_edi_format
|
||||
@@ -0,0 +1,219 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import Form
|
||||
|
||||
|
||||
COUNTRY_EAS = {
|
||||
'HU': 9910,
|
||||
|
||||
'AD': 9922,
|
||||
'AL': 9923,
|
||||
'BA': 9924,
|
||||
'BE': 9925,
|
||||
'BG': 9926,
|
||||
'CH': 9927,
|
||||
'CY': 9928,
|
||||
'CZ': 9929,
|
||||
'DE': 9930,
|
||||
'EE': 9931,
|
||||
'UK': 9932,
|
||||
'GR': 9933,
|
||||
'HR': 9934,
|
||||
'IE': 9935,
|
||||
'LI': 9936,
|
||||
'LT': 9937,
|
||||
'LU': 9938,
|
||||
'LV': 9939,
|
||||
'MC': 9940,
|
||||
'ME': 9941,
|
||||
'MK': 9942,
|
||||
'MT': 9943,
|
||||
'NL': 9944,
|
||||
'PL': 9945,
|
||||
'PT': 9946,
|
||||
'RO': 9947,
|
||||
'RS': 9948,
|
||||
'SI': 9949,
|
||||
'SK': 9950,
|
||||
'SM': 9951,
|
||||
'TR': 9952,
|
||||
'VA': 9953,
|
||||
|
||||
'SE': 9955,
|
||||
|
||||
'FR': 9957
|
||||
}
|
||||
|
||||
|
||||
class AccountEdiFormat(models.Model):
|
||||
''' This edi_format is "abstract" meaning that it provides an additional layer for similar edi_format (formats
|
||||
deriving from EN16931) that share some functionalities but needs to be extended to be used.
|
||||
'''
|
||||
_inherit = 'account.edi.format'
|
||||
|
||||
####################################################
|
||||
# Export
|
||||
####################################################
|
||||
|
||||
def _get_bis3_values(self, invoice):
|
||||
values = super()._get_ubl_values(invoice)
|
||||
values.update({
|
||||
'customization_id': 'urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0',
|
||||
'profile_id': 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
|
||||
})
|
||||
|
||||
all_tax_detail_per_tax = {}
|
||||
for line_vals in values['invoice_line_vals_list']:
|
||||
line_vals['price_subtotal_with_no_tax_closing'] = line_vals['line'].price_subtotal
|
||||
tax_detail_per_tax = {}
|
||||
for tax_detail_vals in line_vals['tax_detail_vals_list']:
|
||||
tax = tax_detail_vals['tax']
|
||||
|
||||
tax_percent = tax.amount
|
||||
tax_category = 'S' if tax_percent else 'Z'
|
||||
key = (tax_category, tax_percent)
|
||||
tax_detail_per_tax.setdefault(key, {
|
||||
'base': tax_detail_vals['tax_base_amount'],
|
||||
'base_currency': tax_detail_vals['tax_base_amount_currency'],
|
||||
'amount': 0.0,
|
||||
'amount_currency': 0.0,
|
||||
'tax_percent': tax_percent,
|
||||
'tax_category': tax_category,
|
||||
})
|
||||
vals = tax_detail_per_tax[key]
|
||||
|
||||
vals['amount'] += tax_detail_vals['tax_amount_closing']
|
||||
vals['amount_currency'] += tax_detail_vals['tax_amount_currency_closing']
|
||||
delta_tax = tax_detail_vals['tax_amount_currency'] - tax_detail_vals['tax_amount_currency_closing']
|
||||
line_vals['price_subtotal_with_no_tax_closing'] += delta_tax
|
||||
|
||||
if len(tax_detail_per_tax) > 1:
|
||||
raise UserError("Multiple vat percentage not supported on the same invoice line")
|
||||
|
||||
line_vals['tax_detail_vals'] = list(tax_detail_per_tax.values())[0]
|
||||
|
||||
for key, tax_vals in tax_detail_per_tax.items():
|
||||
all_tax_detail_per_tax.setdefault(key, {
|
||||
**tax_vals,
|
||||
'base': 0.0,
|
||||
'base_currency': 0.0,
|
||||
'amount': 0.0,
|
||||
'amount_currency': 0.0,
|
||||
})
|
||||
vals = all_tax_detail_per_tax[key]
|
||||
vals['base'] += tax_vals['base']
|
||||
vals['base_currency'] += tax_vals['base_currency']
|
||||
vals['amount'] += tax_vals['amount']
|
||||
vals['amount_currency'] += tax_vals['amount_currency']
|
||||
|
||||
values['tax_detail_vals_list'] = list(all_tax_detail_per_tax.values())
|
||||
values['total_untaxed_amount'] = sum(x['price_subtotal_with_no_tax_closing'] for x in values['invoice_line_vals_list'])
|
||||
values['total_tax_amount'] = sum(x['amount'] for x in values['tax_detail_vals_list'])
|
||||
values['total_tax_amount_currency'] = sum(x['amount_currency'] for x in values['tax_detail_vals_list'])
|
||||
|
||||
for partner_vals in (values['customer_vals'], values['supplier_vals']):
|
||||
partner = partner_vals['partner']
|
||||
if partner.country_id.code in COUNTRY_EAS:
|
||||
partner_vals['bis3_endpoint'] = partner.vat
|
||||
partner_vals['bis3_endpoint_scheme'] = COUNTRY_EAS[partner.country_id.code]
|
||||
|
||||
return values
|
||||
|
||||
####################################################
|
||||
# Import
|
||||
####################################################
|
||||
|
||||
def _bis3_get_extra_partner_domains(self, tree):
|
||||
""" Returns an additional domain to find the partner of the invoice based on specific implementation of BIS3.
|
||||
TO OVERRIDE
|
||||
|
||||
:returns: a list of domains
|
||||
"""
|
||||
return []
|
||||
|
||||
def _decode_bis3(self, tree, invoice):
|
||||
""" Decodes an EN16931 invoice into an invoice.
|
||||
:param tree: the UBL (EN16931) tree to decode.
|
||||
:param invoice: the invoice to update or an empty recordset.
|
||||
:returns: the invoice where the UBL (EN16931) data was imported.
|
||||
"""
|
||||
def _find_value(path, root=tree):
|
||||
element = root.find(path)
|
||||
return element.text if element is not None else None
|
||||
|
||||
element = tree.find('./{*}InvoiceTypeCode')
|
||||
if element is not None:
|
||||
type_code = element.text
|
||||
move_type = 'in_refund' if type_code == '381' else 'in_invoice'
|
||||
else:
|
||||
move_type = 'in_invoice'
|
||||
|
||||
default_journal = invoice.with_context(default_move_type=move_type)._get_default_journal()
|
||||
|
||||
with Form(invoice.with_context(default_move_type=move_type, default_journal_id=default_journal.id)) as invoice_form:
|
||||
# Reference
|
||||
element = tree.find('./{*}ID')
|
||||
if element is not None:
|
||||
invoice_form.ref = element.text
|
||||
|
||||
# Dates
|
||||
element = tree.find('./{*}IssueDate')
|
||||
if element is not None:
|
||||
invoice_form.invoice_date = element.text
|
||||
element = tree.find('./{*}DueDate')
|
||||
if element is not None:
|
||||
invoice_form.invoice_date_due = element.text
|
||||
|
||||
# Currency
|
||||
currency = self._retrieve_currency(_find_value('./{*}DocumentCurrencyCode'))
|
||||
if currency:
|
||||
invoice_form.currency_id = currency
|
||||
|
||||
# Partner
|
||||
specific_domain = self._bis3_get_extra_partner_domains(tree)
|
||||
invoice_form.partner_id = self._retrieve_partner(
|
||||
name=_find_value('./{*}AccountingSupplierParty/{*}Party/*/{*}Name'),
|
||||
phone=_find_value('./{*}AccountingSupplierParty/{*}Party/*/{*}Telephone'),
|
||||
mail=_find_value('./{*}AccountingSupplierParty/{*}Party/*/{*}ElectronicMail'),
|
||||
vat=_find_value('./{*}AccountingSupplierParty/{*}Party/{*}PartyTaxScheme/{*}CompanyID'),
|
||||
domain=specific_domain,
|
||||
)
|
||||
|
||||
# Lines
|
||||
for eline in tree.findall('.//{*}InvoiceLine'):
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
# Product
|
||||
invoice_line_form.product_id = self._retrieve_product(
|
||||
default_code=_find_value('./{*}Item/{*}SellersItemIdentification/{*}ID', eline),
|
||||
name=_find_value('./{*}Item/{*}Name', eline),
|
||||
barcode=_find_value('./{*}Item/{*}StandardItemIdentification/{*}ID[@schemeID=\'0160\']', eline)
|
||||
)
|
||||
|
||||
# Quantity
|
||||
element = eline.find('./{*}InvoicedQuantity')
|
||||
quantity = element is not None and float(element.text) or 1.0
|
||||
invoice_line_form.quantity = quantity
|
||||
|
||||
# Price Unit
|
||||
element = eline.find('./{*}Price/{*}PriceAmount')
|
||||
price_unit = element is not None and float(element.text) or 0.0
|
||||
line_extension_amount = element is not None and float(element.text) or 0.0
|
||||
invoice_line_form.price_unit = price_unit or line_extension_amount / invoice_line_form.quantity or 0.0
|
||||
|
||||
# Name
|
||||
element = eline.find('./{*}Item/{*}Description')
|
||||
invoice_line_form.name = element is not None and element.text or ''
|
||||
|
||||
# Taxes
|
||||
tax_elements = eline.findall('./{*}Item/{*}ClassifiedTaxCategory')
|
||||
invoice_line_form.tax_ids.clear()
|
||||
for tax_element in tax_elements:
|
||||
invoice_line_form.tax_ids.add(self._retrieve_tax(
|
||||
amount=_find_value('./{*}Percent', tax_element),
|
||||
type_tax_use=invoice_form.journal_id.type
|
||||
))
|
||||
|
||||
return invoice_form.save()
|
||||
@@ -8,7 +8,6 @@ class ResPartner(models.Model):
|
||||
"""Inherit res.partner object to add NPWP field and Kode Transaksi"""
|
||||
_inherit = "res.partner"
|
||||
|
||||
country_code = fields.Char(related='country_id.code', string='Country Code')
|
||||
l10n_id_pkp = fields.Boolean(string="ID PKP", compute='_compute_l10n_id_pkp', store=True, readonly=False)
|
||||
l10n_id_nik = fields.Char(string='NIK')
|
||||
l10n_id_tax_address = fields.Char('Tax Address')
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="vat" position="after">
|
||||
<field name="country_code" invisible="1"/>
|
||||
<field name="l10n_id_pkp" attrs="{'invisible': [('country_id', '!=', False), ('country_code', '!=', 'ID')]}"/>
|
||||
</field>
|
||||
<page name="accounting" position="inside">
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
<record id="partner_demo_company_nl" model="res.partner">
|
||||
<field name="name">NL Company</field>
|
||||
<field name="vat">NL43603B11</field>
|
||||
<field name="street"></field>
|
||||
<field name="street">Bloemstraat 42</field>
|
||||
<field name="city">Groningen</field>
|
||||
<field name="country_id" ref="base.nl"/>
|
||||
<field name="state_id" ref="base.state_nl_bq3"/>
|
||||
<field name="zip"></field>
|
||||
<field name="zip">9700</field>
|
||||
<field name="phone">+31 6 12345678</field>
|
||||
<field name="email">info@company.nlexample.com</field>
|
||||
<field name="website">www.nlexample.com</field>
|
||||
<field name="l10n_nl_kvk">12345678</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_company_nl" model="res.company">
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import account_journal
|
||||
from . import account_chart_template
|
||||
from . import res_partner
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# coding: utf-8
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
l10n_nl_kvk = fields.Char(string='KVK-nummer')
|
||||
l10n_nl_oin = fields.Char(string='Organisatie Indentificatie Nummer')
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_partner_form_inherit_l10n_nl" model="ir.ui.view">
|
||||
<field name="name">res.partner.form.inherit.l10n_nl</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='vat']" position="after">
|
||||
<field name="l10n_nl_kvk" attrs="{'invisible': ['|', ('country_code', '!=', 'NL'), ('is_company', '=', False)]}"/>
|
||||
<field name="l10n_nl_oin" attrs="{'invisible': ['|', ('country_code', '!=', 'NL'), ('is_company', '=', False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Netherlands - E-Invoicing (NLCIUS 1.0.3)',
|
||||
'version': '0.1',
|
||||
'category': 'Accounting/Localizations/EDI',
|
||||
'summary': 'E-Invoicing, Universal Business Language (NLCIUS 1.0.3), nlc',
|
||||
'description': """
|
||||
NLCIUS is the Dutch implementation of EN 16931 norm. Both for UBL and UN / CEFACT XML CII.
|
||||
""",
|
||||
'depends': ['l10n_nl', 'account_edi_ubl_bis3'],
|
||||
'data': [
|
||||
'data/account_edi_data.xml',
|
||||
'data/nlcius_template.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="edi_nlcius_1" model="account.edi.format">
|
||||
<field name="name">NLCIUS (Netherlands)</field>
|
||||
<field name="code">nlcius_1</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<template id="export_nlcius_invoice_partner"
|
||||
inherit_id="account_edi_ubl_bis3.export_bis3_invoice_partner"
|
||||
primary="True">
|
||||
<xpath expr="//*[local-name()='CountrySubentity']" position="replace"/>
|
||||
</template>
|
||||
|
||||
<template id="export_nlcius_invoice"
|
||||
inherit_id="account_edi_ubl_bis3.export_bis3_invoice"
|
||||
primary="True">
|
||||
<xpath expr="//*[local-name()='AccountingSupplierParty']" position="replace">
|
||||
<cac:AccountingSupplierParty t-call="l10n_nl_edi.export_nlcius_invoice_partner"
|
||||
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
|
||||
<t t-set="partner_vals" t-value="supplier_vals"/>
|
||||
</cac:AccountingSupplierParty>
|
||||
</xpath>
|
||||
<xpath expr="//*[local-name()='AccountingCustomerParty']" position="replace">
|
||||
<cac:AccountingCustomerParty t-call="l10n_nl_edi.export_nlcius_invoice_partner"
|
||||
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
|
||||
<t t-set="partner_vals" t-value="customer_vals"/>
|
||||
</cac:AccountingCustomerParty>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from . import account_edi_format
|
||||
@@ -0,0 +1,148 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, _
|
||||
|
||||
import base64
|
||||
|
||||
|
||||
class AccountEdiFormat(models.Model):
|
||||
_inherit = 'account.edi.format'
|
||||
|
||||
####################################################
|
||||
# Import
|
||||
####################################################
|
||||
|
||||
def _is_ubl(self, filename, tree):
|
||||
""" OVERRIDE so that the generic ubl parser does not parse BIS3 any longer.
|
||||
"""
|
||||
is_ubl = super()._is_ubl(filename, tree)
|
||||
return is_ubl and not self._is_nlcius(filename, tree)
|
||||
|
||||
def _is_nlcius(self, filename, tree):
|
||||
profile_id = tree.find('./{*}ProfileID')
|
||||
customization_id = tree.find('./{*}CustomizationID')
|
||||
return tree.tag == '{urn:oasis:names:specification:ubl:schema:xsd:Invoice-2}Invoice' and \
|
||||
profile_id is not None and 'peppol' in profile_id.text and \
|
||||
customization_id is not None and 'nlcius' in customization_id.text
|
||||
|
||||
def _bis3_get_extra_partner_domains(self, tree):
|
||||
if self.code == 'nlcius_1':
|
||||
endpoint = tree.find('./{*}AccountingSupplierParty/{*}Party/{*}EndpointID')
|
||||
if endpoint is not None:
|
||||
scheme = endpoint.attrib['schemeID']
|
||||
if scheme == '0106' and endpoint.text:
|
||||
return [('l10n_nl_kvk', '=', endpoint.text)]
|
||||
elif scheme == '0190' and endpoint.text:
|
||||
return [('l10n_nl_oin', '=', endpoint.text)]
|
||||
return []
|
||||
|
||||
####################################################
|
||||
# Export
|
||||
####################################################
|
||||
|
||||
def _get_nlcius_values(self, invoice):
|
||||
values = super()._get_bis3_values(invoice)
|
||||
values.update({
|
||||
'customization_id': 'urn:cen.eu:en16931:2017#compliant#urn:fdc:nen.nl:nlcius:v1.0',
|
||||
'payment_means_code': 30,
|
||||
})
|
||||
|
||||
for partner_vals in (values['customer_vals'], values['supplier_vals']):
|
||||
partner = partner_vals['partner']
|
||||
endpoint = partner.l10n_nl_oin or partner.l10n_nl_kvk
|
||||
if partner.country_code == 'NL' and endpoint:
|
||||
scheme = '0190' if partner.l10n_nl_oin else '0106'
|
||||
partner_vals.update({
|
||||
'bis3_endpoint': endpoint,
|
||||
'bis3_endpoint_scheme': scheme,
|
||||
'legal_entity': endpoint,
|
||||
'legal_entity_scheme': scheme,
|
||||
'partner_identification': endpoint,
|
||||
})
|
||||
|
||||
return values
|
||||
|
||||
def _export_nlcius(self, invoice):
|
||||
self.ensure_one()
|
||||
# Create file content.
|
||||
xml_content = b"<?xml version='1.0' encoding='UTF-8'?>"
|
||||
xml_content += self.env.ref('l10n_nl_edi.export_nlcius_invoice')._render(self._get_nlcius_values(invoice))
|
||||
vat = invoice.company_id.partner_id.commercial_partner_id.vat
|
||||
xml_name = 'nlcius-%s%s%s.xml' % (vat or '', '-' if vat else '', invoice.name.replace('/', '_'))
|
||||
return self.env['ir.attachment'].create({
|
||||
'name': xml_name,
|
||||
'datas': base64.encodebytes(xml_content),
|
||||
'res_model': 'account.move',
|
||||
'res_id': invoice.id,
|
||||
'mimetype': 'application/xml'
|
||||
})
|
||||
|
||||
####################################################
|
||||
# Account.edi.format override
|
||||
####################################################
|
||||
|
||||
def _check_move_configuration(self, invoice):
|
||||
res = super()._check_move_configuration(invoice)
|
||||
if self.code != 'nlcius_1':
|
||||
return res
|
||||
|
||||
errors = []
|
||||
|
||||
supplier = invoice.company_id.partner_id.commercial_partner_id
|
||||
if not supplier.street or not supplier.zip or not supplier.city:
|
||||
errors.append(_("The supplier's address must include street, zip and city (%s).", supplier.display_name))
|
||||
if supplier.country_code == 'NL' and not supplier.l10n_nl_kvk and not supplier.l10n_nl_oin:
|
||||
errors.append(_("The supplier %s must have a KvK-nummer or OIN.", supplier.display_name))
|
||||
if not supplier.vat:
|
||||
errors.append(_("Please define a VAT number for '%s'.", supplier.display_name))
|
||||
|
||||
customer = invoice.commercial_partner_id
|
||||
if customer.country_code == 'NL' and (not customer.street or not customer.zip or not customer.city):
|
||||
errors.append(_("Customer's address must include street, zip and city (%s).", customer.display_name))
|
||||
if customer.country_code == 'NL' and not customer.l10n_nl_kvk and not customer.l10n_nl_oin:
|
||||
errors.append(_("The customer %s must have a KvK-nummer or OIN.", customer.display_name))
|
||||
|
||||
if not invoice.partner_bank_id:
|
||||
errors.append(_("The supplier %s must have a bank account.", supplier.display_name))
|
||||
|
||||
if invoice.invoice_line_ids.filtered(lambda l: not (l.product_id.name or l.name)):
|
||||
errors.append(_('Each invoice line must have a product or a label.'))
|
||||
|
||||
if invoice.invoice_line_ids.tax_ids.invoice_repartition_line_ids.filtered(lambda r: r.use_in_tax_closing) and \
|
||||
not supplier.vat:
|
||||
errors.append(_("When vat is present, the supplier must have a vat number."))
|
||||
|
||||
return errors
|
||||
|
||||
def _is_compatible_with_journal(self, journal):
|
||||
self.ensure_one()
|
||||
if self.code != 'nlcius_1':
|
||||
return super()._is_compatible_with_journal(journal)
|
||||
return journal.type == 'sale' and journal.country_code == 'NL'
|
||||
|
||||
def _post_invoice_edi(self, invoices, test_mode=False):
|
||||
self.ensure_one()
|
||||
if self.code != 'nlcius_1':
|
||||
return super()._post_invoice_edi(invoices, test_mode=test_mode)
|
||||
|
||||
invoice = invoices # no batch ensure that there is only one invoice
|
||||
attachment = self._export_nlcius(invoice)
|
||||
return {invoice: {'attachment': attachment}}
|
||||
|
||||
def _is_embedding_to_invoice_pdf_needed(self):
|
||||
self.ensure_one()
|
||||
if self.code != 'nlcius_1':
|
||||
return super()._is_embedding_to_invoice_pdf_needed()
|
||||
return False
|
||||
|
||||
def _create_invoice_from_xml_tree(self, filename, tree):
|
||||
self.ensure_one()
|
||||
if self.code == 'nlcius_1' and self._is_nlcius(filename, tree):
|
||||
return self._decode_bis3(tree, self.env['account.move'])
|
||||
return super()._create_invoice_from_xml_tree(filename, tree)
|
||||
|
||||
def _update_invoice_from_xml_tree(self, filename, tree, invoice):
|
||||
self.ensure_one()
|
||||
if self.code == 'nlcius_1' and self._is_nlcius(filename, tree):
|
||||
return self._decode_bis3(tree, invoice)
|
||||
return super()._update_invoice_from_xml_tree(filename, tree, invoice)
|
||||
@@ -0,0 +1,138 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<Invoice xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||||
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:nen.nl:nlcius:v1.0</cbc:CustomizationID>
|
||||
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
|
||||
<cbc:ID>INV/2020/11/0001</cbc:ID>
|
||||
<cbc:IssueDate>2020-11-02</cbc:IssueDate>
|
||||
<cbc:DueDate>2020-11-02</cbc:DueDate>
|
||||
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
||||
|
||||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||||
|
||||
<cbc:BuyerReference>Azure Interior</cbc:BuyerReference>
|
||||
|
||||
<cac:AccountingSupplierParty>
|
||||
<cac:Party>
|
||||
<cbc:EndpointID schemeID="0106">77777677</cbc:EndpointID>
|
||||
<cac:PartyIdentification>
|
||||
<cbc:ID>77777677</cbc:ID>
|
||||
</cac:PartyIdentification>
|
||||
<cac:PostalAddress>
|
||||
<cbc:StreetName>De Silva St 4557</cbc:StreetName>
|
||||
|
||||
<cbc:CityName>Fremont</cbc:CityName>
|
||||
<cbc:PostalZone>94538</cbc:PostalZone>
|
||||
<cac:Country>
|
||||
<cbc:IdentificationCode>NL</cbc:IdentificationCode>
|
||||
</cac:Country>
|
||||
</cac:PostalAddress>
|
||||
<cac:PartyTaxScheme>
|
||||
<cbc:CompanyID>NL000021504B09</cbc:CompanyID>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:PartyTaxScheme>
|
||||
<cac:PartyLegalEntity>
|
||||
<cbc:RegistrationName>partner_a</cbc:RegistrationName>
|
||||
<cbc:CompanyID schemeID="0106">77777677</cbc:CompanyID>
|
||||
</cac:PartyLegalEntity>
|
||||
</cac:Party>
|
||||
</cac:AccountingSupplierParty>
|
||||
<cac:AccountingCustomerParty>
|
||||
<cac:Party>
|
||||
<cbc:EndpointID schemeID="0106">12345678</cbc:EndpointID>
|
||||
<cac:PartyIdentification>
|
||||
<cbc:ID>12345678</cbc:ID>
|
||||
</cac:PartyIdentification>
|
||||
<cac:PartyName>
|
||||
<cbc:Name>NL Company</cbc:Name>
|
||||
</cac:PartyName>
|
||||
<cac:PostalAddress>
|
||||
<cbc:StreetName>Bloemstraat 42</cbc:StreetName>
|
||||
|
||||
<cbc:CityName>Groningen</cbc:CityName>
|
||||
<cbc:PostalZone>6700</cbc:PostalZone>
|
||||
<cac:Country>
|
||||
<cbc:IdentificationCode>NL</cbc:IdentificationCode>
|
||||
</cac:Country>
|
||||
</cac:PostalAddress>
|
||||
<cac:PartyTaxScheme>
|
||||
<cbc:CompanyID>NL000043603B11</cbc:CompanyID>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:PartyTaxScheme>
|
||||
<cac:PartyLegalEntity>
|
||||
<cbc:RegistrationName>NL Company</cbc:RegistrationName>
|
||||
<cbc:CompanyID schemeID="0106">12345678</cbc:CompanyID>
|
||||
</cac:PartyLegalEntity>
|
||||
<cac:Contact>
|
||||
<cbc:Name>NL Company</cbc:Name>
|
||||
<cbc:Telephone>+31 6 12345678</cbc:Telephone>
|
||||
<cbc:ElectronicMail>info@company.nlexample.com</cbc:ElectronicMail>
|
||||
</cac:Contact>
|
||||
</cac:Party>
|
||||
</cac:AccountingCustomerParty>
|
||||
<cac:PaymentMeans>
|
||||
<cbc:PaymentMeansCode>30</cbc:PaymentMeansCode>
|
||||
<cac:PayeeFinancialAccount>
|
||||
<cbc:ID>BE68 5390 0754 7034</cbc:ID>
|
||||
</cac:PayeeFinancialAccount>
|
||||
</cac:PaymentMeans>
|
||||
|
||||
<cac:TaxTotal>
|
||||
<cbc:TaxAmount currencyID="EUR">67.20</cbc:TaxAmount>
|
||||
|
||||
<cac:TaxSubtotal>
|
||||
<cbc:TaxableAmount currencyID="EUR">320.00</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount currencyID="EUR">67.20</cbc:TaxAmount>
|
||||
<cac:TaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>21.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:TaxCategory>
|
||||
</cac:TaxSubtotal>
|
||||
|
||||
</cac:TaxTotal>
|
||||
|
||||
<cac:LegalMonetaryTotal>
|
||||
<cbc:LineExtensionAmount currencyID="EUR">320.00</cbc:LineExtensionAmount>
|
||||
<cbc:TaxExclusiveAmount currencyID="EUR">320.00</cbc:TaxExclusiveAmount>
|
||||
<cbc:TaxInclusiveAmount currencyID="EUR">387.20</cbc:TaxInclusiveAmount>
|
||||
<cbc:PrepaidAmount currencyID="EUR">0.00</cbc:PrepaidAmount>
|
||||
<cbc:PayableAmount currencyID="EUR">387.20</cbc:PayableAmount>
|
||||
</cac:LegalMonetaryTotal>
|
||||
|
||||
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>1</cbc:ID>
|
||||
|
||||
|
||||
<cbc:InvoicedQuantity unitCode="ZZ">1.0</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount currencyID="EUR">320.00</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description>[E-COM07] Large Cabinet</cbc:Description>
|
||||
<cbc:Name>Large Cabinet</cbc:Name>
|
||||
<cac:SellersItemIdentification>
|
||||
<cbc:ID>E-COM07</cbc:ID>
|
||||
</cac:SellersItemIdentification>
|
||||
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>21.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
|
||||
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount currencyID="EUR">320.00</cbc:PriceAmount>
|
||||
<cbc:BaseQuantity>1.0</cbc:BaseQuantity>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
|
||||
</Invoice>
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_nlcius
|
||||
@@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.addons.account_edi.tests.common import AccountEdiTestCommon
|
||||
from freezegun import freeze_time
|
||||
|
||||
|
||||
class TestUBL(AccountEdiTestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref='l10n_nl.l10nnl_chart_template', edi_format_ref='l10n_nl_edi.edi_nlcius_1'):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
|
||||
|
||||
cls.company_data['company'].partner_id.write({
|
||||
'street': 'Archefstraat 42',
|
||||
'zip': '1000',
|
||||
'city': 'Amsterdam',
|
||||
'country_id': cls.env.ref('base.nl').id,
|
||||
'l10n_nl_kvk': '82777822',
|
||||
'vat': 'NL000099998B57',
|
||||
})
|
||||
|
||||
cls.partner_a.write({
|
||||
'l10n_nl_kvk': '77777677',
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
'vat': 'BE0477472701',
|
||||
})
|
||||
|
||||
bank_account = cls.env['res.partner.bank'].create({
|
||||
'acc_number': 'BE93999574162167',
|
||||
'partner_id': cls.partner_a.id,
|
||||
})
|
||||
|
||||
cls.tax_sale_b.write({
|
||||
'amount': 15
|
||||
})
|
||||
|
||||
cls.invoice = cls.env['account.move'].create({
|
||||
'partner_id': cls.partner_a.id,
|
||||
'move_type': 'out_invoice',
|
||||
'partner_bank_id': bank_account.id,
|
||||
'invoice_date_due': '2020-12-16',
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'product_id': cls.product_a.id,
|
||||
'quantity': 150,
|
||||
'price_unit': 250,
|
||||
'discount': 10,
|
||||
'tax_ids': [(6, 0, cls.tax_sale_a.ids)],
|
||||
}),
|
||||
(0, 0, {
|
||||
'product_id': cls.product_b.id,
|
||||
'quantity': 12,
|
||||
'price_unit': 100,
|
||||
'tax_ids': [(6, 0, cls.tax_sale_b.ids)],
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
cls.expected_invoice_values = '''
|
||||
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||||
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:nen.nl:nlcius:v1.0</cbc:CustomizationID>
|
||||
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
|
||||
<cbc:ID>INV/2020/12/0001</cbc:ID>
|
||||
<cbc:IssueDate>2020-12-16</cbc:IssueDate>
|
||||
<cbc:DueDate>2020-12-16</cbc:DueDate>
|
||||
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
||||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||||
<cbc:BuyerReference>partner_a</cbc:BuyerReference>
|
||||
<cac:AccountingSupplierParty>
|
||||
<cac:Party>
|
||||
<cbc:EndpointID schemeID="0106">82777822</cbc:EndpointID>
|
||||
<cac:PartyIdentification>
|
||||
<cbc:ID>82777822</cbc:ID>
|
||||
</cac:PartyIdentification>
|
||||
<cac:PartyName>
|
||||
<cbc:Name>company_1_data</cbc:Name>
|
||||
</cac:PartyName>
|
||||
<cac:PostalAddress>
|
||||
<cbc:StreetName>Archefstraat 42</cbc:StreetName>
|
||||
<cbc:CityName>Amsterdam</cbc:CityName>
|
||||
<cbc:PostalZone>1000</cbc:PostalZone>
|
||||
<cac:Country>
|
||||
<cbc:IdentificationCode>NL</cbc:IdentificationCode>
|
||||
</cac:Country>
|
||||
</cac:PostalAddress>
|
||||
<cac:PartyTaxScheme>
|
||||
<cbc:CompanyID>NL000099998B57</cbc:CompanyID>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:PartyTaxScheme>
|
||||
<cac:PartyLegalEntity>
|
||||
<cbc:RegistrationName>company_1_data</cbc:RegistrationName>
|
||||
<cbc:CompanyID schemeID="0106">82777822</cbc:CompanyID>
|
||||
</cac:PartyLegalEntity>
|
||||
<cac:Contact>
|
||||
<cbc:Name>company_1_data</cbc:Name>
|
||||
</cac:Contact>
|
||||
</cac:Party>
|
||||
</cac:AccountingSupplierParty>
|
||||
<cac:AccountingCustomerParty>
|
||||
<cac:Party>
|
||||
<cbc:EndpointID schemeID="9925">BE0477472701</cbc:EndpointID>
|
||||
<cac:PartyName>
|
||||
<cbc:Name>partner_a</cbc:Name>
|
||||
</cac:PartyName>
|
||||
<cac:PostalAddress>
|
||||
<cac:Country>
|
||||
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
|
||||
</cac:Country>
|
||||
</cac:PostalAddress>
|
||||
<cac:PartyTaxScheme>
|
||||
<cbc:CompanyID>BE0477472701</cbc:CompanyID>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:PartyTaxScheme>
|
||||
<cac:PartyLegalEntity>
|
||||
<cbc:RegistrationName>partner_a</cbc:RegistrationName>
|
||||
</cac:PartyLegalEntity>
|
||||
<cac:Contact>
|
||||
<cbc:Name>partner_a</cbc:Name>
|
||||
</cac:Contact>
|
||||
</cac:Party>
|
||||
</cac:AccountingCustomerParty>
|
||||
<cac:PaymentMeans>
|
||||
<cbc:PaymentMeansCode>30</cbc:PaymentMeansCode>
|
||||
<cac:PayeeFinancialAccount>
|
||||
<cbc:ID>BE93 9995 7416 2167</cbc:ID>
|
||||
</cac:PayeeFinancialAccount>
|
||||
</cac:PaymentMeans>
|
||||
<cac:TaxTotal>
|
||||
<cbc:TaxAmount currencyID="EUR">7267.50</cbc:TaxAmount>
|
||||
<cac:TaxSubtotal>
|
||||
<cbc:TaxableAmount currencyID="EUR">33750.00</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount currencyID="EUR">7087.50</cbc:TaxAmount>
|
||||
<cac:TaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>21.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:TaxCategory>
|
||||
</cac:TaxSubtotal>
|
||||
<cac:TaxSubtotal>
|
||||
<cbc:TaxableAmount currencyID="EUR">1200.00</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount currencyID="EUR">180.00</cbc:TaxAmount>
|
||||
<cac:TaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>15.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:TaxCategory>
|
||||
</cac:TaxSubtotal>
|
||||
</cac:TaxTotal>
|
||||
<cac:LegalMonetaryTotal>
|
||||
<cbc:LineExtensionAmount currencyID="EUR">34950.00</cbc:LineExtensionAmount>
|
||||
<cbc:TaxExclusiveAmount currencyID="EUR">34950.00</cbc:TaxExclusiveAmount>
|
||||
<cbc:TaxInclusiveAmount currencyID="EUR">42217.50</cbc:TaxInclusiveAmount>
|
||||
<cbc:PrepaidAmount currencyID="EUR">0.00</cbc:PrepaidAmount>
|
||||
<cbc:PayableAmount currencyID="EUR">42217.50</cbc:PayableAmount>
|
||||
</cac:LegalMonetaryTotal>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>1</cbc:ID>
|
||||
<cbc:Note>Discount (10.0 %)</cbc:Note>
|
||||
<cbc:InvoicedQuantity unitCode="ZZ">150.0</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount currencyID="EUR">33750.00</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description>product_a</cbc:Description>
|
||||
<cbc:Name>product_a</cbc:Name>
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>21.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount currencyID="EUR">225.00</cbc:PriceAmount>
|
||||
<cbc:BaseQuantity>150.0</cbc:BaseQuantity>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>2</cbc:ID>
|
||||
<cbc:InvoicedQuantity unitCode="ZZ">12.0</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount currencyID="EUR">1200.00</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description>product_b</cbc:Description>
|
||||
<cbc:Name>product_b</cbc:Name>
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>15.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
||||
<cbc:BaseQuantity>12.0</cbc:BaseQuantity>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
</Invoice>
|
||||
'''
|
||||
|
||||
def test_nlcius_import(self):
|
||||
invoice = self.env['account.move'].with_context(default_move_type='in_invoice').create({})
|
||||
invoice_count = len(self.env['account.move'].search([]))
|
||||
self.update_invoice_from_file('l10n_nl_edi', 'test_xml_file', 'nlcius_test.xml', invoice)
|
||||
self.assertEqual(len(self.env['account.move'].search([])), invoice_count)
|
||||
self.assertEqual(invoice.amount_total, 387.2)
|
||||
self.assertEqual(invoice.amount_tax, 67.2)
|
||||
self.assertEqual(invoice.partner_id, self.partner_a)
|
||||
|
||||
@freeze_time('2020-12-16')
|
||||
def test_nlcius_export(self):
|
||||
self.assert_generated_file_equal(self.invoice, self.expected_invoice_values)
|
||||
@@ -198,6 +198,7 @@ class Partner(models.Model):
|
||||
city = fields.Char()
|
||||
state_id = fields.Many2one("res.country.state", string='State', ondelete='restrict', domain="[('country_id', '=?', country_id)]")
|
||||
country_id = fields.Many2one('res.country', string='Country', ondelete='restrict')
|
||||
country_code = fields.Char(related='country_id.code', string="Country Code")
|
||||
partner_latitude = fields.Float(string='Geo Latitude', digits=(10, 7))
|
||||
partner_longitude = fields.Float(string='Geo Longitude', digits=(10, 7))
|
||||
email = fields.Char()
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
<field name="is_company" invisible="1"/>
|
||||
<field name="commercial_partner_id" invisible="1"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="country_code" invisible="1"/>
|
||||
<field name="company_type" widget="radio" options="{'horizontal': true}"/>
|
||||
<h1>
|
||||
<field class="o_text_overflow" name="name" default_focus="1" placeholder="e.g. Lumber Inc" attrs="{'required' : [('type', '=', 'contact')], 'invisible': [('is_company','=', False)]}"/>
|
||||
|
||||
Reference in New Issue
Block a user