Support Belgian structured communication on invoices, payments and statement rules

This commit is contained in:
Cédric Krier
2025-09-30 00:41:08 +02:00
parent e967e80974
commit 3a4a23c3df
6 changed files with 153 additions and 1 deletions
+1
View File
@@ -1,3 +1,4 @@
* Support structured communication on invoices, payments and statement rules
Version 7.6.0 - 2025-04-28
--------------------------
+77
View File
@@ -1,7 +1,13 @@
# 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 stdnum.exceptions
from sql.aggregate import Min, Sum
try:
from stdnum.be import ogm_vcs
except ImportError:
from . import ogm_vcs
from trytond.model import ModelSQL, ModelView, fields
from trytond.modules.account.exceptions import FiscalYearNotFoundError
from trytond.modules.currency.fields import Monetary
@@ -138,3 +144,74 @@ class BEVATCustomerContext(ModelView):
return None
return fiscalyear.id
return context['fiscalyear']
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
@classmethod
def __setup__(cls):
super().__setup__()
cls.supplier_payment_reference_type.selection.append(
('ogm_vcs', "Belgian structured communication"))
@classmethod
def _format_supplier_payment_reference_ogm_vcs(cls, reference):
try:
return ogm_vcs.format(reference)
except stdnum.exceptions.ValidationError:
return reference
@classmethod
def _search_customer_payment_reference(cls, value):
yield from super()._search_customer_payment_reference(value)
if ogm_vcs.is_valid(value):
yield ('number_digit', '=', int(ogm_vcs.compact(value)[:-2]))
def _customer_payment_reference_type(self):
type = super()._customer_payment_reference_type()
if (self.invoice_address.country
and self.invoice_address.country.code == 'BE'):
type = 'ogm_vcs'
return type
def _format_customer_payment_reference_ogm_vcs(self, source):
number = self._customer_payment_reference_number_digit(source)
if number:
number = number % 10**10
check_digit = ogm_vcs.calc_check_digit(str(number))
reference = f'{self.number_digit:0>10}{check_digit}'
return f'+++{ogm_vcs.format(reference)}+++'
def _check_supplier_payment_reference_ogm_vcs(self):
return ogm_vcs.is_valid(self.supplier_payment_reference)
class Payment(metaclass=PoolMeta):
__name__ = 'account.payment'
@classmethod
def __setup__(cls):
super().__setup__()
cls.reference_type.selection.append(
('ogm_vcs', "Belgian structured communication"))
@classmethod
def _format_reference_ogm_vcs(cls, reference):
try:
return ogm_vcs.format(reference)
except stdnum.exceptions.ValidationError:
return reference
def _check_reference_ogm_vcs(self):
return ogm_vcs.is_valid(self.reference)
class StatementRuleLine(metaclass=PoolMeta):
__name__ = 'account.statement.rule.line'
@classmethod
def _party_payment_reference(cls, value):
yield from super()._party_payment_reference(value)
if ogm_vcs.is_valid(value):
yield ('code_digit', '=', int(ogm_vcs.compact(value)[:-2]))
+49
View File
@@ -0,0 +1,49 @@
# 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 stdnum.exceptions import (
InvalidChecksum, InvalidFormat, InvalidLength, ValidationError)
from stdnum.util import clean, isdigits
def compact(number: str) -> str:
"""Convert the number to the minimal representation. This strips the number
of any invalid separators and removes surrounding whitespace."""
return clean(number, ' +/').strip()
def checksum(number: str) -> int:
"""Calculate the checksum."""
return (int(number) % 97) or 97
def calc_check_digit(number: str) -> str:
"""Calculate the check digit that should be added."""
return '%02d' % checksum(number)
def validate(number: str) -> str:
"""Check if the number is a valid OGM-VCS."""
number = compact(number)
if not isdigits(number) or int(number) <= 0:
raise InvalidFormat()
if len(number) != 12:
raise InvalidLength()
if checksum(number[:-2]) != int(number[-2:]):
raise InvalidChecksum()
return number
def is_valid(number: str) -> bool:
"""Check if the number is a valid VAT number."""
try:
return bool(validate(number))
except ValidationError:
return False
def format(number: str) -> str:
"""Format the number provided for output."""
number = compact(number)
number = number.rjust(12, '0')
return f'{number[:3]}/{number[3:7]}/{number[7:]}'
+10 -1
View File
@@ -44,12 +44,18 @@ else:
download_url = 'http://downloads.tryton.org/%s.%s/' % (
major_version, minor_version)
requires = []
requires = ['python-stdnum']
for dep in info.get('depends', []):
if not re.match(r'(ir|res)(\W|$)', dep):
requires.append(get_require_version('trytond_%s' % dep))
requires.append(get_require_version('trytond'))
tests_require = [
get_require_version('trytond_account_invoice'),
get_require_version('trytond_account_payment'),
get_require_version('trytond_account_statement_rule'),
]
setup(name=name,
version=version,
description='Tryton module with Belgian chart of accounts',
@@ -99,6 +105,9 @@ setup(name=name,
license='GPL-3',
python_requires='>=3.9',
install_requires=requires,
extras_require={
'test': tests_require,
},
zip_safe=False,
entry_points="""
[trytond.modules]
+1
View File
@@ -9,6 +9,7 @@ from trytond.tests.test_tryton import ModuleTestCase, with_transaction
class AccountBETestCase(ModuleTestCase):
'Test Account BE module'
module = 'account_be'
extras = ['account_invoice', 'account_payment', 'account_statement_rule']
language = 'fr'
@with_transaction()
+15
View File
@@ -7,6 +7,9 @@ depends:
extras_depend:
account_asset
account_deposit
account_invoice
account_payment
account_statement_rule
sale_advance_payment
xml:
account.xml
@@ -21,3 +24,15 @@ model:
account.BEVATCustomerContext
wizard:
account.CreateChart
[register account_invoice]
model:
account.Invoice
[register account_payment]
model:
account.Payment
[register account_statement_rule]
model:
account.StatementRuleLine