Add payment reference to statement rule

This commit is contained in:
Cédric Krier
2025-09-30 00:11:08 +02:00
parent 3c8223a153
commit e967e80974
6 changed files with 252 additions and 14 deletions
+15 -1
View File
@@ -167,7 +167,8 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
states={
'invisible': Eval('type', 'out') != 'out',
}),
'get_customer_payment_reference')
'get_customer_payment_reference',
searcher='search_customer_payment_reference')
sequence = fields.Integer("Sequence", readonly=True)
sequence_type_cache = fields.Selection([
(None, ""),
@@ -656,6 +657,19 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
return self.format_customer_payment_reference(
self._customer_payment_reference_type())
@classmethod
def search_customer_payment_reference(cls, name, clause):
_, operator, value = clause[:3]
if operator == '=':
return ['OR', *cls._search_customer_payment_reference(value)]
else:
return []
@classmethod
def _search_customer_payment_reference(cls, value):
if iso11649.is_valid(value):
yield ('number_alnum', '=', value[4:])
def _customer_payment_reference_type(self):
return 'creditor_reference'
+1
View File
@@ -1,3 +1,4 @@
Add payment reference to statement rule
Version 7.6.0 - 2025-04-28
--------------------------
+72 -12
View File
@@ -6,6 +6,7 @@ import re
from decimal import Decimal
from simpleeval import simple_eval
from stdnum import iso11649
from trytond.model import ModelSQL, ModelView, fields, sequence_ordered
from trytond.modules.currency.fields import Monetary
@@ -95,7 +96,8 @@ class StatementRule(sequence_ordered(), ModelSQL, ModelView):
"It may define the groups named:\n"
"'party'\n"
"'bank_account'\n"
"'invoice'")
"'invoice'\n"
"'payment_reference'")
information_rules = fields.One2Many(
'account.statement.rule.information', 'rule', "Information Rules")
@@ -322,7 +324,7 @@ class StatementRuleLine(sequence_ordered(), ModelSQL, ModelView):
currency = origin.statement.journal.currency
amount = currency.round(simple_eval(decistmt(self.amount), **context))
party = self._get_party(origin, keywords)
party = self._get_party(origin, keywords, amount=amount)
related_to = list(
filter(None, self._get_related_to(
origin, keywords, party=party, amount=amount)))
@@ -370,15 +372,21 @@ class StatementRuleLine(sequence_ordered(), ModelSQL, ModelView):
line.related_to = related_to
return line
def _get_party(self, origin, keywords):
def _get_party(self, origin, keywords, amount=0):
pool = Pool()
Party = pool.get('party.party')
Line = pool.get('account.statement.line')
Configuration = pool.get('account.configuration')
try:
AccountNumber = pool.get('bank.account.number')
except KeyError:
AccountNumber = None
configuration = Configuration(1)
customer_payment_reference_number = configuration.get_multivalue(
'customer_payment_reference_number',
company=origin.company.id)
party = self.party
if not party:
if keywords.get('bank_account') and AccountNumber:
@@ -402,15 +410,29 @@ class StatementRuleLine(sequence_ordered(), ModelSQL, ModelView):
if lines:
line, = lines
party = line.party
elif keywords.get('party'):
parties = Party.search(
[('rec_name', 'ilike', keywords['party'])])
elif (keywords.get('party')
or (keywords.get('payment_reference')
and amount > 0
and customer_payment_reference_number == 'party')):
domain = []
if party_rec_name := keywords.get('party'):
domain.append(('rec_name', 'ilike', party_rec_name))
if ((payment_reference := keywords.get('payment_reference'))
and amount > 0
and customer_payment_reference_number == 'party'):
domain.extend(
self._party_payment_reference(payment_reference))
if domain:
domain.insert(0, 'OR')
parties = Party.search(domain)
else:
parties = []
if len(parties) == 1:
party, = parties
else:
elif party_rec_name:
lines = Line.search([
('statement.state', 'in', ['validated', 'posted']),
('origin.keywords.party', '=', keywords['party']),
('origin.keywords.party', '=', party_rec_name),
('party', '!=', None),
],
order=[('date', 'DESC')], limit=1)
@@ -419,8 +441,14 @@ class StatementRuleLine(sequence_ordered(), ModelSQL, ModelView):
party = line.party
return party
@classmethod
def _party_payment_reference(cls, value):
if iso11649.is_valid(value):
yield ('code_alnum', '=', value[4:])
def _get_related_to(self, origin, keywords, party=None, amount=0):
return {self._get_invoice(origin, keywords, party=party)}
return {
self._get_invoice(origin, keywords, party=party, amount=amount)}
def _get_party_from(self, related_to):
pool = Pool()
@@ -434,16 +462,48 @@ class StatementRuleLine(sequence_ordered(), ModelSQL, ModelView):
if isinstance(related_to, Invoice):
return related_to.account
def _get_invoice(self, origin, keywords, party=None):
def _get_invoice(self, origin, keywords, party=None, amount=0):
pool = Pool()
Invoice = pool.get('account.invoice')
if keywords.get('invoice'):
Configuration = pool.get('account.configuration')
configuration = Configuration(1)
customer_payment_reference_number = configuration.get_multivalue(
'customer_payment_reference_number',
company=origin.company.id)
if keywords.get('invoice') or keywords.get('payment_reference'):
kdomain = ['OR']
if invoice_rec_name := keywords.get('invoice'):
kdomain.append(('rec_name', '=', invoice_rec_name))
if payment_reference := keywords.get('payment_reference'):
if (amount > 0
and customer_payment_reference_number == 'invoice'):
kdomain.append(
('customer_payment_reference', '=', payment_reference))
elif amount < 0:
kdomain.append(
('supplier_payment_reference', '=', payment_reference))
domain = [
('rec_name', '=', keywords['invoice']),
kdomain,
('company', '=', origin.company.id),
('currency', '=', origin.currency.id),
('state', '=', 'posted'),
]
if amount > 0:
domain.append(['OR',
[('type', '=', 'out'),
('total_amount', '>=', 0)],
[('type', '=', 'in'),
('total_amount', '<=', 0)],
])
elif amount < 0:
domain.append(['OR',
[('type', '=', 'out'),
('total_amount', '<=', 0)],
[('type', '=', 'in'),
('total_amount', '>=', 0)],
])
if party:
domain.append(['OR',
('party', '=', party.id),
+1 -1
View File
@@ -44,7 +44,7 @@ else:
download_url = 'http://downloads.tryton.org/%s.%s/' % (
major_version, minor_version)
requires = ['simpleeval']
requires = ['simpleeval', '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))
@@ -0,0 +1,93 @@
=========================================================
Account Statement Rule Keyword Payment Reference Scenario
==========================================================
Imports::
>>> import datetime as dt
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import (
... create_chart, create_fiscalyear, get_accounts)
>>> from trytond.modules.account_invoice.tests.tools import (
... set_fiscalyear_invoice_sequences)
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
>>> today = dt.date.today()
Activate modules::
>>> config = activate_modules(
... ['account_statement_rule'],
... create_company, create_chart)
>>> AccountConfiguration = Model.get('account.configuration')
>>> AccountJournal = Model.get('account.journal')
>>> Invoice = Model.get('account.invoice')
>>> Party = Model.get('party.party')
>>> Rule = Model.get('account.statement.rule')
>>> Statement = Model.get('account.statement')
>>> StatementJournal = Model.get('account.statement.journal')
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(
... create_fiscalyear(today=today))
>>> fiscalyear.click('create_period')
Get accounts::
>>> accounts = get_accounts()
>>> account_configuration = AccountConfiguration(1)
>>> account_configuration.customer_payment_reference_number = 'invoice'
>>> account_configuration.save()
Create a party::
>>> customer = Party(name="Customer", code="CUST-001")
>>> customer.save()
Create an invoice::
>>> invoice = Invoice(party=customer)
>>> line = invoice.lines.new()
>>> line.quantity = 1
>>> line.unit_price = Decimal('40.0000')
>>> line.account = accounts['revenue']
>>> invoice.click('post')
>>> invoice.state
'posted'
Create statement rules::
>>> rule = Rule(name="Payment Reference Rule")
>>> rule.description = r"Ref: *(?P<payment_reference>.*)"
>>> line = rule.lines.new()
>>> line.amount = "pending"
>>> rule.save()
Create a statement::
>>> account_journal, = AccountJournal.find([('code', '=', 'STA')], limit=1)
>>> journal_number = StatementJournal(
... name="Number",
... journal=account_journal,
... account=accounts['cash'],
... validation='number_of_lines')
>>> journal_number.save()
>>> statement = Statement(name="Test")
>>> statement.journal = journal_number
>>> statement.number_of_lines = 1
>>> origin = statement.origins.new()
>>> origin.date = today
>>> origin.amount = Decimal('50.00')
>>> origin.description = "Ref: " + invoice.customer_payment_reference
>>> statement.click('apply_rules')
>>> line, = statement.lines
>>> assertEqual(line.related_to, invoice)
>>> assertEqual(line.party, customer)
>>> assertEqual(line.account, invoice.account)
@@ -0,0 +1,70 @@
==================================================================
Account Statement Rule Keyword Payment Reference Customer Scenario
==================================================================
Imports::
>>> import datetime as dt
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
>>> today = dt.date.today()
Activate modules::
>>> config = activate_modules(
... ['account_statement_rule'],
... create_company, create_chart)
>>> AccountConfiguration = Model.get('account.configuration')
>>> AccountJournal = Model.get('account.journal')
>>> Party = Model.get('party.party')
>>> Rule = Model.get('account.statement.rule')
>>> Statement = Model.get('account.statement')
>>> StatementJournal = Model.get('account.statement.journal')
Get accounts::
>>> accounts = get_accounts()
>>> account_configuration = AccountConfiguration(1)
>>> account_configuration.customer_payment_reference_number = 'party'
>>> account_configuration.save()
Create a party::
>>> customer = Party(name="Customer", code="CUST-001")
>>> customer.save()
Create statement rules::
>>> rule = Rule(name="Payment Reference Rule")
>>> rule.description = r"Ref: *(?P<payment_reference>.*)"
>>> line = rule.lines.new()
>>> line.amount = "pending"
>>> rule.save()
Create a statement::
>>> account_journal, = AccountJournal.find([('code', '=', 'STA')], limit=1)
>>> journal_number = StatementJournal(
... name="Number",
... journal=account_journal,
... account=accounts['cash'],
... validation='number_of_lines')
>>> journal_number.save()
>>> statement = Statement(name="Test")
>>> statement.journal = journal_number
>>> statement.number_of_lines = 1
>>> origin = statement.origins.new()
>>> origin.date = today
>>> origin.amount = Decimal('50.00')
>>> origin.description = "Ref: RF92CUST001"
>>> statement.click('apply_rules')
>>> line, = statement.lines
>>> assertEqual(line.party, customer)