Support Belgian structured communication on invoices, payments and statement rules
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Support structured communication on invoices, payments and statement rules
|
||||
|
||||
Version 7.6.0 - 2025-04-28
|
||||
--------------------------
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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:]}'
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user