[MIG] account_banking_sepa_credit_transfer

This commit is contained in:
etobella
2017-10-17 12:26:34 +02:00
committed by Enric Tobella
parent 1cffc59d6d
commit fa12e54896
7 changed files with 100 additions and 77 deletions

View File

@@ -11,10 +11,10 @@ SEPA PAIN (PAyment INitiation) is the new european standard for
Customer-to-Bank payment instructions. This module implements SEPA Credit
Transfer (SCT), more specifically PAIN versions 001.001.02, 001.001.03,
001.001.04 and 001.001.05. It is part of the ISO 20022 standard, available on
http://www.iso20022.org.
https://www.iso20022.org.
The Implementation Guidelines for SEPA Credit Transfer published by the
European Payments Council (http://http://www.europeanpaymentscouncil.eu) use
European Payments Council (https://www.europeanpaymentscouncil.eu) use
PAIN version 001.001.03, so it's probably the version of PAIN that you should
try first.
@@ -43,13 +43,13 @@ Configuration
Usage
=====
In the menu *Accounting > Payments > Payment Order*, create a new
In the menu *Invoicing/Accounting > Payments > Payment Order*, create a new
payment order and select the Payment Mode dedicated to SEPA Credit
Transfer that you created during the configuration step.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/173/10.0
:target: https://runbot.odoo-community.org/runbot/173/11.0
Known issues / Roadmap
======================
@@ -84,12 +84,12 @@ Contributors
Maintainer
----------
.. image:: http://odoo-community.org/logo.png
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://odoo-community.org
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.
To contribute to this module, please visit https://odoo-community.org.

View File

@@ -2,12 +2,12 @@
# © 2010-2016 Akretion (www.akretion.com)
# © 2014 Tecnativa - Pedro M. Baeza
# © 2016 Tecnativa - Antonio Espinosa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
'name': 'Account Banking SEPA Credit Transfer',
'summary': 'Create SEPA XML files for Credit Transfers',
'version': '10.0.1.0.0',
'version': '11.0.1.0.0',
'license': 'AGPL-3',
'author': "Akretion, "
"Tecnativa, "

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# © 2017 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# © 2010-2016 Akretion (www.akretion.com)
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, api, _
from odoo.exceptions import UserError
@@ -19,11 +19,12 @@ class AccountPaymentOrder(models.Model):
return super(AccountPaymentOrder, self).generate_payment_file()
pain_flavor = self.payment_method_id.pain_version
if not pain_flavor:
pain_flavor = 'pain.001.001.03'
# We use pain_flavor.startswith('pain.001.001.xx')
# to support country-specific extensions such as
# pain.001.001.03.ch.02 (cf l10n_ch_sepa)
if not pain_flavor:
raise UserError(
_("PAIN version '%s' is not supported.") % pain_flavor)
if pain_flavor.startswith('pain.001.001.02'):
bic_xml_tag = 'BIC'
name_maxsize = 70
@@ -94,7 +95,7 @@ class AccountPaymentOrder(models.Model):
else:
lines_per_group[key] = [line]
for (requested_date, priority, local_instrument, categ_purpose),\
lines in lines_per_group.items():
lines in list(lines_per_group.items()):
# B. Payment info
payment_info, nb_of_transactions_b, control_sum_b = \
self.generate_start_payment_info_block(
@@ -155,12 +156,12 @@ class AccountPaymentOrder(models.Model):
self.generate_remittance_info_block(
credit_transfer_transaction_info, line, gen_args)
if not pain_flavor.startswith('pain.001.001.02'):
nb_of_transactions_b.text = unicode(transactions_count_b)
nb_of_transactions_b.text = str(transactions_count_b)
control_sum_b.text = '%.2f' % amount_control_sum_b
if not pain_flavor.startswith('pain.001.001.02'):
nb_of_transactions_a.text = unicode(transactions_count_a)
nb_of_transactions_a.text = str(transactions_count_a)
control_sum_a.text = '%.2f' % amount_control_sum_a
else:
nb_of_transactions_a.text = unicode(transactions_count_a)
nb_of_transactions_a.text = str(transactions_count_a)
control_sum_a.text = '%.2f' % amount_control_sum_a
return self.finalize_sepa_file_creation(xml_root, gen_args)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, SUPERUSER_ID

View File

@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.addons.account.tests.account_test_classes\
import AccountingTestCase
import base64
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.exceptions import UserError
from odoo.tools import float_compare
import time
from lxml import etree
@@ -60,7 +61,28 @@ class TestSCT(AccountingTestCase):
for bank_acc in self.partner_bank_model.search([]):
bank_acc.acc_number = bank_acc.acc_number
def test_eur_currency_sct(self):
def test_no_pain(self):
self.payment_mode.payment_method_id.pain_version = False
with self.assertRaises(UserError):
self.check_eur_currency_sct()
def test_pain_001_03(self):
self.payment_mode.payment_method_id.pain_version = 'pain.001.001.03'
self.check_eur_currency_sct()
def test_pain_001_04(self):
self.payment_mode.payment_method_id.pain_version = 'pain.001.001.04'
self.check_eur_currency_sct()
def test_pain_001_05(self):
self.payment_mode.payment_method_id.pain_version = 'pain.001.001.05'
self.check_eur_currency_sct()
def test_pain_003_03(self):
self.payment_mode.payment_method_id.pain_version = 'pain.001.003.03'
self.check_eur_currency_sct()
def check_eur_currency_sct(self):
invoice1 = self.create_invoice(
self.partner_agrolait.id,
'account_payment_mode.res_partner_2_iban', self.eur_currency.id,
@@ -83,70 +105,70 @@ class TestSCT(AccountingTestCase):
41.0, 'I1643')
for inv in [invoice1, invoice2, invoice3, invoice4, invoice5]:
action = inv.create_account_payment_line()
self.assertEquals(action['res_model'], 'account.payment.order')
self.assertEqual(action['res_model'], 'account.payment.order')
self.payment_order = self.payment_order_model.browse(action['res_id'])
self.assertEquals(
self.assertEqual(
self.payment_order.payment_type, 'outbound')
self.assertEquals(
self.assertEqual(
self.payment_order.payment_mode_id, self.payment_mode)
self.assertEquals(
self.assertEqual(
self.payment_order.journal_id, self.bank_journal)
pay_lines = self.payment_line_model.search([
('partner_id', '=', self.partner_agrolait.id),
('order_id', '=', self.payment_order.id)])
self.assertEquals(len(pay_lines), 3)
self.assertEqual(len(pay_lines), 3)
agrolait_pay_line1 = pay_lines[0]
accpre = self.env['decimal.precision'].precision_get('Account')
self.assertEquals(agrolait_pay_line1.currency_id, self.eur_currency)
self.assertEquals(
self.assertEqual(agrolait_pay_line1.currency_id, self.eur_currency)
self.assertEqual(
agrolait_pay_line1.partner_bank_id, invoice1.partner_bank_id)
self.assertEquals(float_compare(
self.assertEqual(float_compare(
agrolait_pay_line1.amount_currency, 42, precision_digits=accpre),
0)
self.assertEquals(agrolait_pay_line1.communication_type, 'normal')
self.assertEquals(agrolait_pay_line1.communication, 'F1341')
self.assertEqual(agrolait_pay_line1.communication_type, 'normal')
self.assertEqual(agrolait_pay_line1.communication, 'F1341')
self.payment_order.draft2open()
self.assertEquals(self.payment_order.state, 'open')
self.assertEquals(self.payment_order.sepa, True)
self.assertEqual(self.payment_order.state, 'open')
self.assertEqual(self.payment_order.sepa, True)
bank_lines = self.bank_line_model.search([
('partner_id', '=', self.partner_agrolait.id)])
self.assertEquals(len(bank_lines), 1)
self.assertEqual(len(bank_lines), 1)
agrolait_bank_line = bank_lines[0]
self.assertEquals(agrolait_bank_line.currency_id, self.eur_currency)
self.assertEquals(float_compare(
self.assertEqual(agrolait_bank_line.currency_id, self.eur_currency)
self.assertEqual(float_compare(
agrolait_bank_line.amount_currency, 49.0, precision_digits=accpre),
0)
self.assertEquals(agrolait_bank_line.communication_type, 'normal')
self.assertEquals(
self.assertEqual(agrolait_bank_line.communication_type, 'normal')
self.assertEqual(
agrolait_bank_line.communication, 'F1341-F1342-A1301')
self.assertEquals(
self.assertEqual(
agrolait_bank_line.partner_bank_id, invoice1.partner_bank_id)
action = self.payment_order.open2generated()
self.assertEquals(self.payment_order.state, 'generated')
self.assertEquals(action['res_model'], 'ir.attachment')
self.assertEqual(self.payment_order.state, 'generated')
self.assertEqual(action['res_model'], 'ir.attachment')
attachment = self.attachment_model.browse(action['res_id'])
self.assertEquals(attachment.datas_fname[-4:], '.xml')
xml_file = attachment.datas.decode('base64')
self.assertEqual(attachment.datas_fname[-4:], '.xml')
xml_file = base64.b64decode(attachment.datas)
xml_root = etree.fromstring(xml_file)
namespaces = xml_root.nsmap
namespaces['p'] = xml_root.nsmap[None]
namespaces.pop(None)
pay_method_xpath = xml_root.xpath(
'//p:PmtInf/p:PmtMtd', namespaces=namespaces)
self.assertEquals(pay_method_xpath[0].text, 'TRF')
self.assertEqual(pay_method_xpath[0].text, 'TRF')
sepa_xpath = xml_root.xpath(
'//p:PmtInf/p:PmtTpInf/p:SvcLvl/p:Cd', namespaces=namespaces)
self.assertEquals(sepa_xpath[0].text, 'SEPA')
self.assertEqual(sepa_xpath[0].text, 'SEPA')
debtor_acc_xpath = xml_root.xpath(
'//p:PmtInf/p:DbtrAcct/p:Id/p:IBAN', namespaces=namespaces)
self.assertEquals(
self.assertEqual(
debtor_acc_xpath[0].text,
self.payment_order.company_partner_bank_id.sanitized_acc_number)
self.payment_order.generated2uploaded()
self.assertEquals(self.payment_order.state, 'uploaded')
self.assertEqual(self.payment_order.state, 'uploaded')
for inv in [invoice1, invoice2, invoice3, invoice4, invoice5]:
self.assertEquals(inv.state, 'paid')
self.assertEqual(inv.state, 'paid')
return
def test_usd_currency_sct(self):
@@ -160,70 +182,70 @@ class TestSCT(AccountingTestCase):
1012.0, 'Inv9033')
for inv in [invoice1, invoice2]:
action = inv.create_account_payment_line()
self.assertEquals(action['res_model'], 'account.payment.order')
self.assertEqual(action['res_model'], 'account.payment.order')
self.payment_order = self.payment_order_model.browse(action['res_id'])
self.assertEquals(
self.assertEqual(
self.payment_order.payment_type, 'outbound')
self.assertEquals(
self.assertEqual(
self.payment_order.payment_mode_id, self.payment_mode)
self.assertEquals(
self.assertEqual(
self.payment_order.journal_id, self.bank_journal)
pay_lines = self.payment_line_model.search([
('partner_id', '=', self.partner_asus.id),
('order_id', '=', self.payment_order.id)])
self.assertEquals(len(pay_lines), 2)
self.assertEqual(len(pay_lines), 2)
asus_pay_line1 = pay_lines[0]
accpre = self.env['decimal.precision'].precision_get('Account')
self.assertEquals(asus_pay_line1.currency_id, self.usd_currency)
self.assertEquals(
self.assertEqual(asus_pay_line1.currency_id, self.usd_currency)
self.assertEqual(
asus_pay_line1.partner_bank_id, invoice1.partner_bank_id)
self.assertEquals(float_compare(
self.assertEqual(float_compare(
asus_pay_line1.amount_currency, 2042, precision_digits=accpre),
0)
self.assertEquals(asus_pay_line1.communication_type, 'normal')
self.assertEquals(asus_pay_line1.communication, 'Inv9032')
self.assertEqual(asus_pay_line1.communication_type, 'normal')
self.assertEqual(asus_pay_line1.communication, 'Inv9032')
self.payment_order.draft2open()
self.assertEquals(self.payment_order.state, 'open')
self.assertEquals(self.payment_order.sepa, False)
self.assertEqual(self.payment_order.state, 'open')
self.assertEqual(self.payment_order.sepa, False)
bank_lines = self.bank_line_model.search([
('partner_id', '=', self.partner_asus.id)])
self.assertEquals(len(bank_lines), 1)
self.assertEqual(len(bank_lines), 1)
asus_bank_line = bank_lines[0]
self.assertEquals(asus_bank_line.currency_id, self.usd_currency)
self.assertEquals(float_compare(
self.assertEqual(asus_bank_line.currency_id, self.usd_currency)
self.assertEqual(float_compare(
asus_bank_line.amount_currency, 3054.0, precision_digits=accpre),
0)
self.assertEquals(asus_bank_line.communication_type, 'normal')
self.assertEquals(
self.assertEqual(asus_bank_line.communication_type, 'normal')
self.assertEqual(
asus_bank_line.communication, 'Inv9032-Inv9033')
self.assertEquals(
self.assertEqual(
asus_bank_line.partner_bank_id, invoice1.partner_bank_id)
action = self.payment_order.open2generated()
self.assertEquals(self.payment_order.state, 'generated')
self.assertEquals(action['res_model'], 'ir.attachment')
self.assertEqual(self.payment_order.state, 'generated')
self.assertEqual(action['res_model'], 'ir.attachment')
attachment = self.attachment_model.browse(action['res_id'])
self.assertEquals(attachment.datas_fname[-4:], '.xml')
xml_file = attachment.datas.decode('base64')
self.assertEqual(attachment.datas_fname[-4:], '.xml')
xml_file = base64.b64decode(attachment.datas)
xml_root = etree.fromstring(xml_file)
namespaces = xml_root.nsmap
namespaces['p'] = xml_root.nsmap[None]
namespaces.pop(None)
pay_method_xpath = xml_root.xpath(
'//p:PmtInf/p:PmtMtd', namespaces=namespaces)
self.assertEquals(pay_method_xpath[0].text, 'TRF')
self.assertEqual(pay_method_xpath[0].text, 'TRF')
sepa_xpath = xml_root.xpath(
'//p:PmtInf/p:PmtTpInf/p:SvcLvl/p:Cd', namespaces=namespaces)
self.assertEquals(len(sepa_xpath), 0)
self.assertEqual(len(sepa_xpath), 0)
debtor_acc_xpath = xml_root.xpath(
'//p:PmtInf/p:DbtrAcct/p:Id/p:IBAN', namespaces=namespaces)
self.assertEquals(
self.assertEqual(
debtor_acc_xpath[0].text,
self.payment_order.company_partner_bank_id.sanitized_acc_number)
self.payment_order.generated2uploaded()
self.assertEquals(self.payment_order.state, 'uploaded')
self.assertEqual(self.payment_order.state, 'uploaded')
for inv in [invoice1, invoice2]:
self.assertEquals(inv.state, 'paid')
self.assertEqual(inv.state, 'paid')
return
def create_invoice(