From fc80b83e5679556e0680978abee2cd7f4975e6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 21 Oct 2014 19:01:20 +0200 Subject: [PATCH] Add pain.001.003.03 and pain.008.003.02 flavors issue4216 review9761002 --- CHANGELOG | 2 + payment.py | 6 +- template/base.003.xml | 53 +++ template/pain.001.003.03.xml | 76 ++++ template/pain.008.003.02.xml | 90 +++++ tests/pain.001.003.03.xsd | 474 ++++++++++++++++++++++ tests/pain.008.003.02.xsd | 614 +++++++++++++++++++++++++++++ tests/test_account_payment_sepa.py | 17 + 8 files changed, 1331 insertions(+), 1 deletion(-) create mode 100644 template/base.003.xml create mode 100644 template/pain.001.003.03.xml create mode 100644 template/pain.008.003.02.xml create mode 100644 tests/pain.001.003.03.xsd create mode 100644 tests/pain.008.003.02.xsd diff --git a/CHANGELOG b/CHANGELOG index 74bcdab2ce..f0b27a373e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add pain.001.003.03 and pain.008.003.02 flavors + Version 3.4.0 - 2014-10-20 * Bug fixes (see mercurial logs for details) * Add mandate report diff --git a/payment.py b/payment.py index dea5935e56..92c370a18b 100644 --- a/payment.py +++ b/payment.py @@ -2,6 +2,7 @@ #this repository contains the full copyright notices and license terms. import datetime import os +import unicodedata from itertools import groupby from io import BytesIO @@ -43,6 +44,7 @@ class Journal: (None, ''), ('pain.001.001.03', 'pain.001.001.03'), ('pain.001.001.05', 'pain.001.001.05'), + ('pain.001.003.03', 'pain.001.003.03'), ], 'Payable Flavor', states={ 'required': Eval('process_method') == 'sepa', 'invisible': Eval('process_method') != 'sepa', @@ -53,6 +55,7 @@ class Journal: (None, ''), ('pain.008.001.02', 'pain.008.001.02'), ('pain.008.001.04', 'pain.008.001.04'), + ('pain.008.003.02', 'pain.008.003.02'), ], 'Receivable Flavor', states={ 'required': Eval('process_method') == 'sepa', 'invisible': Eval('process_method') != 'sepa', @@ -156,7 +159,8 @@ class Group: if not self.sepa_messages: self.sepa_messages = () message = tmpl.generate(group=self, - datetime=datetime).filter(remove_comment).render() + datetime=datetime, normalize=unicodedata.normalize, + ).filter(remove_comment).render() message = Message(message=message, type='in', state='waiting') self.sepa_messages += (message,) diff --git a/template/base.003.xml b/template/base.003.xml new file mode 100644 index 0000000000..39555e0587 --- /dev/null +++ b/template/base.003.xml @@ -0,0 +1,53 @@ + + + + + + + ${normalize('NFKD', party.name).encode('ascii', 'replace')[:70]} + + + + ${PostalAddress(address)} + + + + + + + ${party.sepa_creditor_identifier_used} + + SEPA + + + + + + + + + + ${normalize('NFKD', address.country.code).encode('ascii', 'replace')} + ${normalize('NFKD', address.street).encode('ascii', 'replace')[:70]} + ${normalize('NFKD', address.streetbis).encode('ascii', 'replace')[:70]} + + + + + ${account_number.compact_iban} + + + ${account_number.account.currency.code} + + + + + + ${bank.bic} + + NOTPROVIDED + + + + diff --git a/template/pain.001.003.03.xml b/template/pain.001.003.03.xml new file mode 100644 index 0000000000..a54207760b --- /dev/null +++ b/template/pain.001.003.03.xml @@ -0,0 +1,76 @@ + + + + + + + + ${group.reference[:35]} + ${datetime.datetime.now().isoformat()} + ${len(group.payments)} + ${sum(p.amount for p in group.payments)} + + ${PartyIdentification(group.sepa_initiating_party, with_address=False)} + + + + ${group.sepa_group_payment_id(key)[:35]} + TRF + ${'true' if group.journal.sepa_batch_booking else 'false'} + ${len(payments)} + ${sum(p.amount for p in payments)} + + + + SEPA + + + + ${key['date'].isoformat()} + + ${PartyIdentification(group.company.party)} + + + ${Account(group.journal.sepa_bank_account_number)} + + + ${FinancialInstitution(group.journal.sepa_bank_account_number.account.bank, only_bic=True)} + + + SLEV + + + ${payment.sepa_instruction_id} + ${payment.sepa_end_to_end_id} + + + + ${payment.amount} + + + + + ${FinancialInstitution(payment.sepa_bank_account_number.account.bank, only_bic=True)} + + + ${PartyIdentification(payment.party)} + + + ${Account(payment.sepa_bank_account_number, with_ccy=False)} + + + + + ${normalize('NFKD', payment.sepa_remittance_information).encode('ascii', 'replace')[:140]} + + + + + diff --git a/template/pain.008.003.02.xml b/template/pain.008.003.02.xml new file mode 100644 index 0000000000..21d70c832d --- /dev/null +++ b/template/pain.008.003.02.xml @@ -0,0 +1,90 @@ + + + + + + + + ${group.reference[:35]} + ${datetime.datetime.now().isoformat()} + ${len(group.payments)} + ${sum(p.amount for p in group.payments)} + + ${PartyIdentification(group.sepa_initiating_party, with_address=False)} + + + + ${group.sepa_group_payment_id(key)[:35]} + DD + ${'true' if group.journal.sepa_batch_booking else 'false'} + ${len(payments)} + ${sum(p.amount for p in payments)} + + + SEPA + + + ${key['scheme']} + + ${key['sequence_type']} + + + ${key['date'].isoformat()} + + ${PartyIdentification(group.company.party, id=False)} + + + ${Account(group.journal.sepa_bank_account_number)} + + + ${FinancialInstitution(group.journal.sepa_bank_account_number.account.bank, only_bic=True)} + + + SLEV + + ${PartyIdentification(group.company.party, with_name=False, with_address=False)} + + + + ${payment.sepa_instruction_id} + ${payment.sepa_end_to_end_id} + + ${payment.amount} + + + + ${mandate.identification} + ${mandate.signature_date.isoformat()} + + + + + + + + + ${FinancialInstitution(payment.sepa_bank_account_number.account.bank, only_bic=True)} + + + ${PartyIdentification(payment.party)} + + + ${Account(payment.sepa_bank_account_number, with_ccy=False)} + + + + + ${normalize('NFKD', payment.sepa_remittance_information).encode('ascii', 'replace')[:140]} + + + + + + diff --git a/tests/pain.001.003.03.xsd b/tests/pain.001.003.03.xsd new file mode 100644 index 0000000000..a90ed99a1e --- /dev/null +++ b/tests/pain.001.003.03.xsd @@ -0,0 +1,474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + If used, it is recommended to be used at ‘Payment Information’ level and not at ‘Credit Transfer Transaction Information’ level. + + + + + + It is recommended that this element be specified at ‘Payment Information’ level. + + + + + This data element may be present either at ‘Payment Information’ or at ‘Credit Transfer Transaction Information’ level. + + + + + + + + + + + + + + + + If a Creditor Reference contains a check digit, the receiving bank is not required to validate this. +If the receiving bank validates the check digit and if this validation fails, the bank may continue its processing and send the transaction to the next party in the chain. +RF Creditor Reference may be used (ISO 11649). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Either ‘BIC or BEI’ or one +occurrence of ‘Other’ is allowed. + + + + + Either ‘Date and Place of Birth’ or one occurrence of ‘Other’ is allowed. + + + + + + + + + + ‘Name’ is limited to 70 characters +in length. + + + + + + + + + + ‘Name’ is limited to 70 characters +in length. + + + + + + + + + + + + + + + + + + Only ‘TRF’ is allowed. + + + + + If present and contains ‘true’, batch booking is requested. If present and contains ‘false’, booking per transaction is requested. If element is not present, pre-agreed customer-to-bank conditions apply. + + + + + + + If used, it is recommended to be used only at ‘Payment Information’ level and not at Credit Transfer Transaction Information’ level. +When Instruction Priority is to be used, ‘Payment Type Information’ must be present at ‘Payment Information’ level. + + + + + + + + + This data element may be present either at ‘Payment Information’ or at ‘Credit Transfer Transaction Information’ level. + + + + + It is recommended that this element be specified at ‘Payment Information’ level. + + + + + + + + + + + + + + + If present, pre-agreed customer-to-bank conditions apply. + + + + + + Depending on the agreement between the Originator and the Originator Bank, ‘Category Purpose’ may be forwarded to the Beneficiary Bank. + + + + + + + + + + Depending on the agreement between the Originator and the Originator Bank, ‘Category Purpose’ may be forwarded to the Beneficiary Bank. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Only codes from the ISO 20022 ExternalPurposeCode list are allowed. + + + + + + + + + + + + + + + + + + + + + + When present, the receiving bank is not obliged to validate the the reference information. + + + + + + + + + + diff --git a/tests/pain.008.003.02.xsd b/tests/pain.008.003.02.xsd new file mode 100644 index 0000000000..99992b5e2b --- /dev/null +++ b/tests/pain.008.003.02.xsd @@ -0,0 +1,614 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mandatory if changes occur in ‘Mandate Identification’, otherwise not to be used. + + + + + Mandatory if changes occur in 'Creditor Scheme Identification', otherwise not to be used. + + + + + To be used only for changes of accounts within the same bank. + + + + + To use 'Identification’ under 'Other' under 'Financial Institution Identifier with code ‘SMNDA’ to indicate same mandate with new Debtor Agent. To be used with the ‘FRST’ indicator in the ‘Sequence Type’. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + If a Creditor Reference contains a check digit, the receiving bank is not required to validate this. +If the receiving bank validates the check digit and if this validation fails, the bank may continue its processing and send the transaction to the next party in the chain. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + It is recommended that all transactions within the same ‘Payment Information’ block have the same ‘Creditor Scheme Identification’. +This data element must be present at either ‘Payment Information’ or ‘Direct Debit +Transaction’ level. + + + + + + + + + + + It is recommended that this element be specified at ‘Payment Information’ level. + + + + + + This data element may be present either at ‘Payment Information’ or at ‘Direct Debit Transaction Information’ level. + + + + + + + + Mandatory if provided by the debtor in the mandate. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mandatory if 'Amendment Indicator' is 'TRUE' +The reason code from the Rulebook is indicated using one of the following message subelements. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Either ‘BIC or BEI’ or one +occurrence of ‘Other’ is allowed. + + + + + Either ‘Date and Place of Birth’ or one occurrence of ‘Other’ is allowed + + + + + + + + + + Private Identification is used to identify either an organisation or a private +person. + + + + + + + + + ‘Name’ is limited to 70 characters in length. + + + + + + + + + + ‘Name’ is limited to 70 characters in length. + + + + + + + + + + + + + + + + If present the new’ Name’ must be specified under ‘Creditor’. ‘Name’ is limited to 70 characters in length. + + + + + + + + + + ‘Name’ is limited to 70 characters in length. + + + + + + + + + + + + + + + + + + If present and contains ‘true’, batch booking is requested. If present and contains ‘false’, booking per transaction is requested. If element is not present, pre-agreed customer-to-bank conditions apply. + + + + + + + + + + + + This data element may be present either at ‘Payment Information’ or at ‘Direct Debit Transaction Information’ level. + + + + + It is recommended that this element be specified at ‘Payment Information’ level. + + + + + It is recommended that all transactions within the same ‘Payment Information’ block have the same ‘Creditor Scheme Identification’. +This data element must be present at either ‘Payment Information’ or ‘Direct Debit +Transaction’ level. + + + + + + + + + + + + + + + + Only ‘B2B’, 'CORE' or 'COR1' is allowed. The mixing of different Local Instrument values is not allowed in the same message. + + + + + If 'Amendment Indicator' is 'true' and 'Original Debtor Agent' is set to 'SMNDA' this message element must indicate 'FRST' + + + + + Depending on the agreement between the Creditor and the Creditor Bank, ‘Category Purpose’ may be forwarded to the Debtor Bank. + + + + + + + + + + + + + + + + + Only one occurrence of ‘Other’ is allowed, and no other sub-elements are allowed. +Identification must be used with an identifier described in General Message Element Specifications, Chapter 1.5.2 of the Implementation Guide. +Scheme Name’ under ‘Other’ must specify ‘SEPA’ under ‘Proprietary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Only codes from the ISO 20022 ExternalPurposeCode list are allowed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + When present, the receiving bank is not obliged to validate the reference information. + + + + + + + + + + + + + + + + + + diff --git a/tests/test_account_payment_sepa.py b/tests/test_account_payment_sepa.py index 9b1658a07d..7a186b4f52 100644 --- a/tests/test_account_payment_sepa.py +++ b/tests/test_account_payment_sepa.py @@ -16,6 +16,7 @@ from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT from trytond.transaction import Transaction from trytond.exceptions import UserError from trytond.modules.account_payment_sepa.payment import CAMT054 +from trytond.pool import Pool class AccountPaymentSepaTestCase(unittest.TestCase): @@ -47,6 +48,9 @@ class AccountPaymentSepaTestCase(unittest.TestCase): test_depends() def setup_environment(self): + pool = Pool() + Address = pool.get('party.address') + company, = self.company.search([ ('rec_name', '=', 'Dunder Mifflin'), ]) @@ -64,6 +68,9 @@ class AccountPaymentSepaTestCase(unittest.TestCase): bank = self.bank(party=bank_party, bic='BICODEBBXXX') bank.save() customer = self.party(name='Customer') + address = Address(street='street', streetbis='street bis', + zip='1234', city='City') + customer.addresses = [address] customer.save() return { 'company': company, @@ -75,12 +82,14 @@ class AccountPaymentSepaTestCase(unittest.TestCase): return self.bank_account.create([{ 'bank': bank, 'owners': [('add', [company.party])], + 'currency': company.currency.id, 'numbers': [('create', [{ 'type': 'iban', 'number': 'ES8200000000000000000000', }])]}, { 'bank': bank, 'owners': [('add', [customer])], + 'currency': company.currency.id, 'numbers': [('create', [{ 'type': 'iban', 'number': 'ES3600000000050000000001', @@ -155,6 +164,10 @@ class AccountPaymentSepaTestCase(unittest.TestCase): 'Test pain001.001.05 xsd validation' self.validate_file('pain.001.001.05', 'payable') + def test_pain001_003_03(self): + 'Test pain001.003.03 xsd validation' + self.validate_file('pain.001.003.03', 'payable') + def test_pain008_001_02(self): 'Test pain008.001.02 xsd validation' self.validate_file('pain.008.001.02', 'receivable') @@ -163,6 +176,10 @@ class AccountPaymentSepaTestCase(unittest.TestCase): 'Test pain008.001.04 xsd validation' self.validate_file('pain.008.001.04', 'receivable') + def test_pain008_003_02(self): + 'Test pain008.003.02 xsd validation' + self.validate_file('pain.008.003.02', 'receivable') + def test_sepa_mandate_sequence(self): 'Test SEPA mandate sequence' Configuration = POOL.get('account.configuration')