Add initiator id on journal

Each country/bank has its own requirement about the ID to use on the initiator
party so we add an option on the journal to define which identifier to use.

issue8063
review284021004
This commit is contained in:
Cédric Krier committed 2019-10-08 18:24:45 +02:00
1 parent 977ebe483f
commit 3a1bdb65bf
18 files changed
+182 -52

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Add initiator identifiers on journal
* Allow to store SEPA message content on the file system
Version 5.2.0 - 2019-05-06
+13 -13
View File
@@ -1,23 +1,23 @@
# 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.pool import Pool
from .payment import *
from .party import *
from .account import *
from . import payment
from . import party
from . import account
def register():
Pool.register(
Journal,
Group,
Mandate,
Payment,
Message,
Party,
PartyIdentifier,
Configuration,
ConfigurationSepaMandateSequence,
payment.Journal,
payment.Group,
payment.Mandate,
payment.Payment,
payment.Message,
party.Party,
party.PartyIdentifier,
account.Configuration,
account.ConfigurationSepaMandateSequence,
module='account_payment_sepa', type_='model')
Pool.register(
MandateReport,
payment.MandateReport,
module='account_payment_sepa', type_='report')
+6
View File
@@ -14,9 +14,15 @@ The Journal has some fields when the process method is SEPA:
- Payable Flavor:
- pain.001.001.03
- pain.001.001.05
- pain.001.003.05
- Receivable Flavor:
- pain.008.001.02
- pain.008.001.04
- pain.008.003.02
- Payable/Receivable Initiator Identifier:
- SEPA Creditor Identifier
- Belgian Enterprise Number
- Spanish VAT Number
- Batch Booking.
- Charge Bearer:
- Debtor
+7
View File
@@ -0,0 +1,7 @@
# 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.exceptions import UserError
class PartyIdentificationdError(UserError):
pass
+3
View File
@@ -18,5 +18,8 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.message" id="msg_party_invalid_sepa">
<field name="text">The SEPA creditor identifier "%(code)s" for party "%(party)s" is not valid.</field>
</record>
<record model="ir.message" id="msg_party_no_id">
<field name="text">The party "%(party)s" is missing a "%(type)s" identifier.</field>
</record>
</data>
</tryton>
+38 -2
View File
@@ -10,10 +10,10 @@ from trytond.i18n import gettext
from trytond.pool import PoolMeta, Pool
from trytond.model import fields
from trytond.transaction import Transaction
from trytond.pyson import Eval
from trytond.modules.party.exceptions import InvalidIdentifierCode
__all__ = ['Party', 'PartyIdentifier']
from .exceptions import PartyIdentificationdError
class Party(metaclass=PoolMeta):
@@ -51,10 +51,31 @@ class Party(metaclass=PoolMeta):
if identifier.type == 'sepa':
return identifier.code
def get_sepa_identifier(self, name):
pool = Pool()
Identifier = pool.get('party.identifier')
for identifier in self.identifiers:
if identifier.type == name:
return identifier.sepa_identifier
else:
selection = Identifier.fields_get(['type'])['type']['selection']
type = dict(selection).get(name, name)
raise PartyIdentificationdError(
gettext('account_payment_sepa.msg_party_no_id',
party=self.rec_name,
type=type))
class PartyIdentifier(metaclass=PoolMeta):
__name__ = 'party.identifier'
sepa_es_suffix = fields.Char(
"SEPA Suffix", size=3,
states={
'invisible': Eval('type') != 'es_nif',
},
depends=['type'])
@classmethod
def __setup__(cls):
super(PartyIdentifier, cls).__setup__()
@@ -84,3 +105,18 @@ class PartyIdentifier(metaclass=PoolMeta):
except stdnum.exceptions.ValidationError:
pass
return code
@property
def sepa_identifier(self):
identifier = {
'Type': 'OrgId',
'Id': self.code,
}
if self.type == 'sepa':
identifier['Type'] = 'PrvtId'
identifier['SchmeNm'] = {'Prtry': 'SEPA'}
elif self.type == 'be_vat':
identifier['Issr'] = 'KBO-BCE'
elif self.type == 'es_nif':
identifier['Id'] += self.sepa_es_suffix or '000'
return identifier
+6
View File
@@ -3,6 +3,12 @@
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="party_identifier_view_form">
<field name="model">party.identifier</field>
<field name="inherit" ref="party.identifier_form"/>
<field name="name">party_identifier_form</field>
</record>
<record model="ir.action.act_window" id="act_mandate_form2">
<field name="name">Mandates</field>
<field name="res_model">account.payment.sepa.mandate</field>
+21 -3
View File
@@ -27,9 +27,6 @@ from trytond.modules.company import CompanyReport
from .sepa_handler import CAMT054
__all__ = ['Journal', 'Group', 'Payment', 'Mandate', 'Message',
'MandateReport']
# XXX fix: https://genshi.edgewall.org/ticket/582
from genshi.template.astutil import ASTCodeGenerator, ASTTransformer
if not hasattr(ASTCodeGenerator, 'visit_NameConstant'):
@@ -55,6 +52,13 @@ else:
file_id = None
store_prefix = None
INITIATOR_IDS = [
(None, ''),
('sepa', "SEPA Creditor Identifier"),
('be_vat', "Belgian Enterprise Number"),
('es_nif', "Spanish VAT Number"),
]
class Journal(metaclass=PoolMeta):
__name__ = 'account.payment.journal'
@@ -92,6 +96,20 @@ class Journal(metaclass=PoolMeta):
},
translate=False,
depends=['process_method'])
sepa_payable_initiator_id = fields.Selection(
INITIATOR_IDS, "SEPA Payable Initiator Identifier",
states={
'invisible': Eval('process_method') != 'sepa',
},
depends=['process_method'],
help="The identifier used for the initiating party.")
sepa_receivable_initiator_id = fields.Selection(
INITIATOR_IDS, "SEPA Receivable Initiator Identifier",
states={
'invisible': Eval('process_method') != 'sepa',
},
depends=['process_method'],
help="The identifier used for the initiating party.")
sepa_batch_booking = fields.Boolean('Batch Booking', states={
'invisible': Eval('process_method') != 'sepa',
},
+31 -11
View File
@@ -3,7 +3,7 @@
this repository contains the full copyright notices and license terms. -->
<py:strip xmlns:py="http://genshi.edgewall.org/">
<!-- only ASCII -->
<py:def function="PartyIdentification(party, id=True, with_name=True, with_address=True)">
<py:def function="PartyIdentification(party, id=None, with_name=True, with_address=True)">
<!-- EPC limits to 70 instead of 140 -->
<Nm py:if="with_name">${normalize('NFKD', party.name).encode('ascii', 'replace')[:70]}</Nm>
<py:if test="with_address">
@@ -13,16 +13,36 @@ this repository contains the full copyright notices and license terms. -->
</PstlAdr>
</py:with>
</py:if>
<Id py:if="id and party.sepa_creditor_identifier_used">
<PrvtId>
<Othr>
<Id>${party.sepa_creditor_identifier_used}</Id>
<SchmeNm>
<Prtry>SEPA</Prtry>
</SchmeNm>
<!-- Issr -->
</Othr>
</PrvtId>
<Id py:if="id">
<py:with vars="identifier = party.get_sepa_identifier(id)">
<OrgId py:if="identifier['Type'] == 'OrgId'">
<BICOrBEI py:if="identifier.get('BICOrBEI')">${identifier['BICOrBEI']}</BICOrBEI>
<Othr py:if="identifier.get('Id')">
<Id>${identifier['Id']}</Id>
<SchmeNm py:if="identifier.get('SchmeNm')">
<Cd py:if="identifier['SchmeNm'].get('Cd')">${identifier['SchmeNm']['Cd']}</Cd>
<Prtry py:if="identifier['SchmeNm'].get('Prtry')">${identifier['SchmeNm']['Cd']}</Prtry>
</SchmeNm>
<Issr py:if="identifier.get('Issr')">${identifier['Issr']}</Issr>
</Othr>
</OrgId>
<PrvtId py:if="identifier['Type'] == 'PrvtId'">
<DtAndPlcOfBirth py:if="identifier.get('DtAndPlcOfBirth')">
<BirthDt>${identifier['DtAndPlcOfBirth']['BirthDt'].isoformat()}</BirthDt>
<PrvcOfBirth py:if="identifier['DtAndPlcOfBirth'].get('PrvcOfBirth')">${identifier['DtAndPlcOfBirth']['PrvcOfBirth']}</PrvcOfBirth>
<CityOfBirth>${identifier['DtAndPlcOfBirth']['CityOfBirth']}</CityOfBirth>
<CtryOfBirth>${identifier['DtAndPlcOfBirth']['CtryOfBirth']}</CtryOfBirth>
</DtAndPlcOfBirth>
<Othr py:if="identifier.get('Id')">
<Id>${identifier['Id']}</Id>
<SchmeNm py:if="identifier.get('SchmeNm')">
<Cd py:if="identifier['SchmeNm'].get('Cd')">${identifier['SchmeNm']['Cd']}</Cd>
<Prtry py:if="identifier['SchmeNm'].get('Prtry')">${identifier['SchmeNm']['Prtry']}</Prtry>
</SchmeNm>
<Issr py:if="identifier.get('Issr')">${identifier['Issr']}</Issr>
</Othr>
</PrvtId>
</py:with>
</Id>
<!-- CtryOfRes -->
<!-- CtctDtls -->
+31 -11
View File
@@ -2,7 +2,7 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<py:strip xmlns:py="http://genshi.edgewall.org/">
<py:def function="PartyIdentification(party, id=True)">
<py:def function="PartyIdentification(party, id=None)">
<!-- EPC limits to 70 instead of 140 -->
<Nm>${party.name[:70]}</Nm>
<py:with vars="address = party.address_get()">
@@ -10,16 +10,36 @@ this repository contains the full copyright notices and license terms. -->
${PostalAddress(address)}
</PstlAdr>
</py:with>
<Id py:if="id and party.sepa_creditor_identifier_used">
<PrvtId>
<Othr>
<Id>${party.sepa_creditor_identifier_used}</Id>
<SchmeNm>
<Prtry>SEPA</Prtry>
</SchmeNm>
<!-- Issr -->
</Othr>
</PrvtId>
<Id py:if="id">
<py:with vars="identifier = party.get_sepa_identifier(id)">
<OrgId py:if="identifier['Type'] == 'OrgId'">
<BICOrBEI py:if="identifier.get('BICOrBEI')">${identifier['BICOrBEI']}</BICOrBEI>
<Othr py:if="identifier.get('Id')">
<Id>${identifier['Id']}</Id>
<SchmeNm py:if="identifier.get('SchmeNm')">
<Cd py:if="identifier['SchmeNm'].get('Cd')">${identifier['SchmeNm']['Cd']}</Cd>
<Prtry py:if="identifier['SchmeNm'].get('Prtry')">${identifier['SchmeNm']['Cd']}</Prtry>
</SchmeNm>
<Issr py:if="identifier.get('Issr')">${identifier['Issr']}</Issr>
</Othr>
</OrgId>
<PrvtId py:if="identifier['Type'] == 'PrvtId'">
<DtAndPlcOfBirth py:if="identifier.get('DtAndPlcOfBirth')">
<BirthDt>${identifier['DtAndPlcOfBirth']['BirthDt'].isoformat()}</BirthDt>
<PrvcOfBirth py:if="identifier['DtAndPlcOfBirth'].get('PrvcOfBirth')">${identifier['DtAndPlcOfBirth']['PrvcOfBirth']}</PrvcOfBirth>
<CityOfBirth>${identifier['DtAndPlcOfBirth']['CityOfBirth']}</CityOfBirth>
<CtryOfBirth>${identifier['DtAndPlcOfBirth']['CtryOfBirth']}</CtryOfBirth>
</DtAndPlcOfBirth>
<Othr py:if="identifier.get('Id')">
<Id>${identifier['Id']}</Id>
<SchmeNm py:if="identifier.get('SchmeNm')">
<Cd py:if="identifier['SchmeNm'].get('Cd')">${identifier['SchmeNm']['Cd']}</Cd>
<Prtry py:if="identifier['SchmeNm'].get('Prtry')">${identifier['SchmeNm']['Prtry']}</Prtry>
</SchmeNm>
<Issr py:if="identifier.get('Issr')">${identifier['Issr']}</Issr>
</Othr>
</PrvtId>
</py:with>
</Id>
<!-- CtryOfRes -->
<!-- CtctDtls -->
+1 -1
View File
@@ -36,7 +36,7 @@ this repository contains the full copyright notices and license terms. -->
<!-- PmtTpInf -->
<!-- ReqdColltnDt -->
<InitgPty>
${PartyIdentification(group.sepa_initiating_party)}
${PartyIdentification(group.sepa_initiating_party, id=group.journal.sepa_payable_initiator_id)}
</InitgPty>
<!-- FwdgAgt -->
</GrpHdr>
+1 -1
View File
@@ -17,7 +17,7 @@ this repository contains the full copyright notices and license terms. -->
<!-- PmtTpInf -->
<!-- ReqdColltnDt -->
<InitgPty>
${PartyIdentification(group.sepa_initiating_party)}
${PartyIdentification(group.sepa_initiating_party, id=group.journal.sepa_payable_initiator_id)}
</InitgPty>
<!-- FwdgAgt -->
</GrpHdr>
+1 -1
View File
@@ -17,7 +17,7 @@ this repository contains the full copyright notices and license terms. -->
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
<CtrlSum>${sum(p.amount for p in group.payments)}</CtrlSum>
<InitgPty>
${PartyIdentification(group.sepa_initiating_party, with_address=False)}
${PartyIdentification(group.sepa_initiating_party, id=group.journal.sepa_payable_initiator_id, with_address=False)}
</InitgPty>
</GrpHdr>
<PmtInf py:for="key, payments in group.sepa_payments">
+3 -3
View File
@@ -36,7 +36,7 @@ this repository contains the full copyright notices and license terms. -->
<!-- PmtTpInf -->
<!-- ReqdColltnDt -->
<InitgPty>
${PartyIdentification(group.sepa_initiating_party)}
${PartyIdentification(group.sepa_initiating_party, id=group.journal.sepa_receivable_initiator_id)}
</InitgPty>
<!-- FwdgAgt -->
</GrpHdr>
@@ -61,7 +61,7 @@ this repository contains the full copyright notices and license terms. -->
</PmtTpInf>
<ReqdColltnDt>${key['date'].isoformat()}</ReqdColltnDt>
<Cdtr>
${PartyIdentification(group.company.party, id=False)}
${PartyIdentification(group.company.party)}
</Cdtr>
<CdtrAcct>
${Account(group.journal.sepa_bank_account_number)}
@@ -75,7 +75,7 @@ this repository contains the full copyright notices and license terms. -->
<!-- ChrgsAcct -->
<!-- ChrgsAcctAgt -->
<CdtrSchmeId>
${PartyIdentification(group.company.party)}
${PartyIdentification(group.company.party, id='sepa')}
</CdtrSchmeId>
<DrctDbtTxInf py:for="payment in payments">
<PmtId>
+3 -3
View File
@@ -17,7 +17,7 @@ this repository contains the full copyright notices and license terms. -->
<!-- PmtTpInf -->
<!-- ReqdColltnDt -->
<InitgPty>
${PartyIdentification(group.sepa_initiating_party)}
${PartyIdentification(group.sepa_initiating_party, id=group.journal.sepa_receivable_initiator_id)}
</InitgPty>
<!-- FwdgAgt -->
</GrpHdr>
@@ -42,7 +42,7 @@ this repository contains the full copyright notices and license terms. -->
</PmtTpInf>
<ReqdColltnDt>${key['date'].isoformat()}</ReqdColltnDt>
<Cdtr>
${PartyIdentification(group.company.party, id=False)}
${PartyIdentification(group.company.party)}
</Cdtr>
<CdtrAcct>
${Account(group.journal.sepa_bank_account_number)}
@@ -56,7 +56,7 @@ this repository contains the full copyright notices and license terms. -->
<!-- ChrgsAcct -->
<!-- ChrgsAcctAgt -->
<CdtrSchmeId>
${PartyIdentification(group.company.party)}
${PartyIdentification(group.company.party, id='sepa')}
</CdtrSchmeId>
<DrctDbtTxInf py:for="payment in payments">
<PmtId>
+3 -3
View File
@@ -17,7 +17,7 @@ this repository contains the full copyright notices and license terms. -->
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
<CtrlSum>${sum(p.amount for p in group.payments)}</CtrlSum>
<InitgPty>
${PartyIdentification(group.sepa_initiating_party, with_address=False)}
${PartyIdentification(group.sepa_initiating_party, id=group.journal.sepa_receivable_initiator_id, with_address=False)}
</InitgPty>
</GrpHdr>
<PmtInf py:for="key, payments in group.sepa_payments">
@@ -38,7 +38,7 @@ this repository contains the full copyright notices and license terms. -->
</PmtTpInf>
<ReqdColltnDt>${key['date'].isoformat()}</ReqdColltnDt>
<Cdtr>
${PartyIdentification(group.company.party, id=False)}
${PartyIdentification(group.company.party)}
</Cdtr>
<CdtrAcct>
${Account(group.journal.sepa_bank_account_number)}
@@ -49,7 +49,7 @@ this repository contains the full copyright notices and license terms. -->
<!-- UltmtCdtr -->
<ChrgBr>SLEV</ChrgBr>
<CdtrSchmeId>
${PartyIdentification(group.company.party, with_name=False, with_address=False)}
${PartyIdentification(group.company.party, id='sepa', with_name=False, with_address=False)}
</CdtrSchmeId>
<DrctDbtTxInf py:for="payment in payments">
<PmtId>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='code']" position="after">
<label name="sepa_es_suffix"/>
<field name="sepa_es_suffix"/>
</xpath>
</data>
+4
View File
@@ -15,5 +15,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="sepa_payable_flavor"/>
<label name="sepa_receivable_flavor"/>
<field name="sepa_receivable_flavor"/>
<label name="sepa_payable_initiator_id"/>
<field name="sepa_payable_initiator_id"/>
<label name="sepa_receivable_initiator_id"/>
<field name="sepa_receivable_initiator_id"/>
</xpath>
</data>