2025-11-21 15:38:25 +01:00
|
|
|
# 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.i18n import gettext
|
|
|
|
|
from trytond.model import fields
|
|
|
|
|
from trytond.pool import Pool, PoolMeta
|
|
|
|
|
from trytond.pyson import Eval
|
|
|
|
|
|
2026-01-15 22:34:01 +01:00
|
|
|
from .exceptions import InvoicePeppolError, InvoicePeppolRequired
|
2025-11-21 15:38:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Invoice(metaclass=PoolMeta):
|
|
|
|
|
__name__ = 'account.invoice'
|
|
|
|
|
|
|
|
|
|
peppol = fields.One2Many(
|
|
|
|
|
'edocument.peppol', 'invoice', "Peppol", readonly=True,
|
|
|
|
|
states={
|
|
|
|
|
'invisible': ~Eval('peppol', []),
|
|
|
|
|
})
|
2026-03-05 11:48:32 +01:00
|
|
|
send_via_peppol = fields.Function(
|
|
|
|
|
fields.Boolean("Send via Peppol"),
|
|
|
|
|
'get_send_via_peppol')
|
|
|
|
|
|
|
|
|
|
def get_send_via_peppol(self, name):
|
|
|
|
|
if not self.peppol:
|
|
|
|
|
peppol_types = self.party.get_multivalue(
|
|
|
|
|
'peppol_types', company=self.company.id)
|
|
|
|
|
return peppol_types and 'bis-billing-3' in peppol_types
|
|
|
|
|
else:
|
|
|
|
|
return bool(self.peppol)
|
2025-11-21 15:38:25 +01:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def peppol_required(self):
|
|
|
|
|
def is_be_vat(identifier):
|
2026-01-05 11:52:11 +01:00
|
|
|
return (identifier
|
|
|
|
|
and (identifier.type == 'be_vat'
|
|
|
|
|
or (identifier.type == 'eu_vat'
|
2026-01-16 16:56:47 +01:00
|
|
|
and identifier.code_compact.startswith('BE'))))
|
2025-11-21 15:38:25 +01:00
|
|
|
if (self.invoice_date
|
|
|
|
|
and self.invoice_date.year >= 2026
|
|
|
|
|
and is_be_vat(self.tax_identifier)
|
|
|
|
|
and is_be_vat(self.party_tax_identifier)):
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def _post(cls, invoices):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
Peppol = pool.get('edocument.peppol')
|
|
|
|
|
Warning = pool.get('res.user.warning')
|
|
|
|
|
posted_invoices = {
|
|
|
|
|
i for i in invoices if i.state in {'draft', 'validated'}}
|
|
|
|
|
super()._post(invoices)
|
|
|
|
|
peppol = []
|
|
|
|
|
for invoice in posted_invoices:
|
|
|
|
|
if invoice.type != 'out':
|
|
|
|
|
continue
|
2026-03-05 11:48:32 +01:00
|
|
|
if invoice.send_via_peppol:
|
2026-01-15 22:34:01 +01:00
|
|
|
invoice.check_peppol()
|
2025-11-21 15:38:25 +01:00
|
|
|
peppol.append(Peppol(
|
|
|
|
|
direction='out',
|
|
|
|
|
company=invoice.company,
|
|
|
|
|
type='bis-billing-3',
|
|
|
|
|
invoice=invoice))
|
|
|
|
|
elif invoice.peppol_required:
|
|
|
|
|
warning_key = Warning.format(
|
|
|
|
|
'invoice_peppol_required', [
|
|
|
|
|
invoice.party, invoice.company])
|
|
|
|
|
if Warning.check(warning_key):
|
2026-01-07 09:16:02 +01:00
|
|
|
raise InvoicePeppolRequired(warning_key, gettext(
|
2025-11-21 15:38:25 +01:00
|
|
|
'edocument_peppol'
|
|
|
|
|
'.msg_invoice_party_peppol_required',
|
|
|
|
|
party=invoice.party.rec_name,
|
|
|
|
|
invoice=invoice.rec_name))
|
|
|
|
|
if peppol:
|
|
|
|
|
Peppol.save(peppol)
|
|
|
|
|
Peppol.submit(peppol)
|
2026-01-15 22:34:01 +01:00
|
|
|
|
|
|
|
|
def check_peppol(self):
|
|
|
|
|
"Check if the invoice is valid for Peppol"
|
|
|
|
|
for line in self.lines:
|
|
|
|
|
if line.type == 'line' and line.unit_price < 0: # BR-27
|
|
|
|
|
raise InvoicePeppolError(gettext(
|
|
|
|
|
'edocument_peppol.'
|
|
|
|
|
'msg_invoice_line_unit_price_not_negative',
|
|
|
|
|
invoice=self.rec_name,
|
|
|
|
|
line=line.rec_name))
|