mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
[MIG] account_banking_sepa_credit_transfer
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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, "
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user