[IMP] account_banking_sepa_direct_debit: black, isort

This commit is contained in:
Marçal Isern
2020-02-24 20:35:52 +01:00
committed by Reyes4711
parent c25254a70d
commit d6ee39b32e
28 changed files with 975 additions and 715 deletions

View File

@@ -1,3 +1,2 @@
from . import models
from .post_install import update_bank_journals

View File

@@ -4,30 +4,25 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
'name': 'Account Banking SEPA Direct Debit',
'summary': 'Create SEPA files for Direct Debit',
'version': '12.0.1.2.0',
'license': 'AGPL-3',
'author': "Akretion, "
"Tecnativa, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/bank-payment',
'category': 'Banking addons',
'depends': [
'account_banking_pain_base',
'account_banking_mandate',
"name": "Account Banking SEPA Direct Debit",
"summary": "Create SEPA files for Direct Debit",
"version": "12.0.1.2.0",
"license": "AGPL-3",
"author": "Akretion, " "Tecnativa, " "Odoo Community Association (OCA)",
"website": "https://github.com/OCA/bank-payment",
"category": "Banking addons",
"depends": ["account_banking_pain_base", "account_banking_mandate"],
"data": [
"views/account_banking_mandate_view.xml",
"views/res_config.xml",
"views/account_payment_mode.xml",
"data/mandate_expire_cron.xml",
"data/account_payment_method.xml",
"data/report_paperformat.xml",
"reports/sepa_direct_debit_mandate.xml",
"views/report_sepa_direct_debit_mandate.xml",
],
'data': [
'views/account_banking_mandate_view.xml',
'views/res_config.xml',
'views/account_payment_mode.xml',
'data/mandate_expire_cron.xml',
'data/account_payment_method.xml',
'data/report_paperformat.xml',
'reports/sepa_direct_debit_mandate.xml',
'views/report_sepa_direct_debit_mandate.xml',
],
'demo': ['demo/sepa_direct_debit_demo.xml'],
'post_init_hook': 'update_bank_journals',
'installable': True,
"demo": ["demo/sepa_direct_debit_demo.xml"],
"post_init_hook": "update_bank_journals",
"installable": True,
}

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="sepa_direct_debit" model="account.payment.method">
<field name="name">SEPA Direct Debit for customers</field>
<field name="code">sepa_direct_debit</field>
<field name="payment_type">inbound</field>
<field name="bank_account_required" eval="True"/>
<field name="mandate_required" eval="True"/>
<field name="bank_account_required" eval="True" />
<field name="mandate_required" eval="True" />
<field name="pain_version">pain.008.001.02</field>
</record>
</odoo>

View File

@@ -1,20 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2013-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<record id="sdd_mandate_expire_cron" model="ir.cron">
<field name="name">Set SEPA Direct Debit Mandates to Expired</field>
<field name="active" eval="True"/>
<field name="model_id" ref="model_account_banking_mandate"/>
<field name="active" eval="True" />
<field name="model_id" ref="model_account_banking_mandate" />
<field name="state">code</field>
<field name="code">model._sdd_mandate_set_state_to_expired()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="user_id" ref="base.user_root"/>
<field name="user_id" ref="base.user_root" />
</record>
</odoo>

View File

@@ -922,4 +922,4 @@
<xs:simpleType name="TrueFalseIndicator">
<xs:restriction base="xs:boolean"/>
</xs:simpleType>
</xs:schema>
</xs:schema>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="paperformat_euro_sepa_lowmargin" model="report.paperformat">
<field name="name">European A4 low margin for SEPA</field>
<field name="default" eval="True" />
@@ -16,5 +15,4 @@
<field name="header_spacing">0</field>
<field name="dpi">80</field>
</record>
</odoo>

View File

@@ -1,49 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data noupdate="1">
<record id="payment_mode_inbound_sepa_dd1" model="account.payment.mode">
<field name="name">SEPA Direct Debit of customers</field>
<field name="company_id" ref="base.main_company"/>
<field name="bank_account_link">variable</field>
<field name="payment_method_id" ref="sepa_direct_debit"/>
<field name="default_journal_ids" search="[('type', 'in', ('sale', 'sale_refund'))]"/>
</record>
<record id="base.main_company" model="res.company">
<field name="sepa_creditor_identifier">FR78ZZZ424242</field>
</record>
<!-- Camptocamp -->
<record id="res_partner_12_mandate" model="account.banking.mandate">
<field name="partner_bank_id" ref="account_payment_mode.res_partner_12_iban"/>
<field name="format">sepa</field>
<field name="type">recurrent</field>
<field name="recurrent_sequence_type">first</field>
<field name="signature_date" eval="time.strftime('%Y-01-01')"/>
<field name="state">valid</field>
</record>
<record id="base.res_partner_12" model="res.partner">
<field name="customer_payment_mode_id" ref="payment_mode_inbound_sepa_dd1"/>
</record>
<!-- Agrolait -->
<record id="res_partner_2_mandate" model="account.banking.mandate">
<field name="partner_bank_id" ref="account_payment_mode.res_partner_2_iban"/>
<field name="format">sepa</field>
<field name="type">recurrent</field>
<field name="recurrent_sequence_type">first</field>
<field name="signature_date" eval="time.strftime('%Y-%m-01')" />
<field name="state">valid</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field name="customer_payment_mode_id" ref="payment_mode_inbound_sepa_dd1"/>
</record>
</data>
<data noupdate="1">
<record id="payment_mode_inbound_sepa_dd1" model="account.payment.mode">
<field name="name">SEPA Direct Debit of customers</field>
<field name="company_id" ref="base.main_company" />
<field name="bank_account_link">variable</field>
<field name="payment_method_id" ref="sepa_direct_debit" />
<field
name="default_journal_ids"
search="[('type', 'in', ('sale', 'sale_refund'))]"
/>
</record>
<record id="base.main_company" model="res.company">
<field name="sepa_creditor_identifier">FR78ZZZ424242</field>
</record>
<!-- Camptocamp -->
<record id="res_partner_12_mandate" model="account.banking.mandate">
<field
name="partner_bank_id"
ref="account_payment_mode.res_partner_12_iban"
/>
<field name="format">sepa</field>
<field name="type">recurrent</field>
<field name="recurrent_sequence_type">first</field>
<field name="signature_date" eval="time.strftime('%Y-01-01')" />
<field name="state">valid</field>
</record>
<record id="base.res_partner_12" model="res.partner">
<field
name="customer_payment_mode_id"
ref="payment_mode_inbound_sepa_dd1"
/>
</record>
<!-- Agrolait -->
<record id="res_partner_2_mandate" model="account.banking.mandate">
<field
name="partner_bank_id"
ref="account_payment_mode.res_partner_2_iban"
/>
<field name="format">sepa</field>
<field name="type">recurrent</field>
<field name="recurrent_sequence_type">first</field>
<field name="signature_date" eval="time.strftime('%Y-%m-01')" />
<field name="state">valid</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field
name="customer_payment_mode_id"
ref="payment_mode_inbound_sepa_dd1"
/>
</record>
</data>
</odoo>

View File

@@ -1,4 +1,3 @@
from . import res_company
from . import res_config
from . import account_banking_mandate

View File

@@ -1,10 +1,12 @@
# Copyright 2013-2016 Akretion - Alexis de Lattre
# Copyright 2014 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, exceptions, _
from datetime import datetime
from dateutil.relativedelta import relativedelta
import logging
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import _, api, exceptions, fields, models
NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY = 36
@@ -13,89 +15,97 @@ logger = logging.getLogger(__name__)
class AccountBankingMandate(models.Model):
"""SEPA Direct Debit Mandate"""
_inherit = 'account.banking.mandate'
_rec_name = 'display_name'
format = fields.Selection(
selection_add=[('sepa', 'Sepa Mandate')], default='sepa')
_inherit = "account.banking.mandate"
_rec_name = "display_name"
format = fields.Selection(selection_add=[("sepa", "Sepa Mandate")], default="sepa")
type = fields.Selection(
selection_add=[
('recurrent', 'Recurrent'),
('oneoff', 'One-Off')
])
selection_add=[("recurrent", "Recurrent"), ("oneoff", "One-Off")]
)
recurrent_sequence_type = fields.Selection(
[('first', 'First'),
('recurring', 'Recurring'),
('final', 'Final')],
string='Sequence Type for Next Debit', track_visibility='onchange',
[("first", "First"), ("recurring", "Recurring"), ("final", "Final")],
string="Sequence Type for Next Debit",
track_visibility="onchange",
help="This field is only used for Recurrent mandates, not for "
"One-Off mandates.", default="first")
scheme = fields.Selection([
('CORE', 'Basic (CORE)'),
('B2B', 'Enterprise (B2B)')],
string='Scheme', default="CORE", track_visibility='onchange')
"One-Off mandates.",
default="first",
)
scheme = fields.Selection(
[("CORE", "Basic (CORE)"), ("B2B", "Enterprise (B2B)")],
string="Scheme",
default="CORE",
track_visibility="onchange",
)
unique_mandate_reference = fields.Char(size=35) # cf ISO 20022
display_name = fields.Char(compute='_compute_display_name2', store=True)
display_name = fields.Char(compute="_compute_display_name2", store=True)
@api.multi
@api.constrains('type', 'recurrent_sequence_type')
@api.constrains("type", "recurrent_sequence_type")
def _check_recurring_type(self):
for mandate in self:
if (mandate.type == 'recurrent' and
not mandate.recurrent_sequence_type):
if mandate.type == "recurrent" and not mandate.recurrent_sequence_type:
raise exceptions.Warning(
_("The recurrent mandate '%s' must have a sequence type.")
% mandate.unique_mandate_reference)
% mandate.unique_mandate_reference
)
@api.multi
@api.depends('unique_mandate_reference', 'recurrent_sequence_type')
@api.depends("unique_mandate_reference", "recurrent_sequence_type")
def _compute_display_name2(self):
for mandate in self:
if mandate.format == 'sepa':
mandate.display_name = '%s (%s)' % (
mandate.unique_mandate_reference,
mandate.recurrent_sequence_type)
if mandate.format == "sepa":
mandate.display_name = "{} ({})".format(
mandate.unique_mandate_reference, mandate.recurrent_sequence_type,
)
else:
mandate.display_name = mandate.unique_mandate_reference
@api.multi
@api.onchange('partner_bank_id')
@api.onchange("partner_bank_id")
def mandate_partner_bank_change(self):
for mandate in self:
super(AccountBankingMandate, self).mandate_partner_bank_change()
res = {}
if (
mandate.state == 'valid' and
mandate.partner_bank_id and
mandate.type == 'recurrent' and
mandate.recurrent_sequence_type != 'first'
mandate.state == "valid"
and mandate.partner_bank_id
and mandate.type == "recurrent"
and mandate.recurrent_sequence_type != "first"
):
mandate.recurrent_sequence_type = 'first'
res['warning'] = {
'title': _('Mandate update'),
'message': _("As you changed the bank account attached "
"to this mandate, the 'Sequence Type' has "
"been set back to 'First'."),
mandate.recurrent_sequence_type = "first"
res["warning"] = {
"title": _("Mandate update"),
"message": _(
"As you changed the bank account attached "
"to this mandate, the 'Sequence Type' has "
"been set back to 'First'."
),
}
return res
@api.model
def _sdd_mandate_set_state_to_expired(self):
logger.info('Searching for SDD Mandates that must be set to Expired')
logger.info("Searching for SDD Mandates that must be set to Expired")
expire_limit_date = datetime.today() + relativedelta(
months=-NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY)
expire_limit_date_str = expire_limit_date.strftime('%Y-%m-%d')
months=-NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY
)
expire_limit_date_str = expire_limit_date.strftime("%Y-%m-%d")
expired_mandates = self.search(
['|',
('last_debit_date', '=', False),
('last_debit_date', '<=', expire_limit_date_str),
('state', '=', 'valid'),
('signature_date', '<=', expire_limit_date_str)])
[
"|",
("last_debit_date", "=", False),
("last_debit_date", "<=", expire_limit_date_str),
("state", "=", "valid"),
("signature_date", "<=", expire_limit_date_str),
]
)
if expired_mandates:
expired_mandates.write({'state': 'expired'})
expired_mandates.write({"state": "expired"})
logger.info(
'The following SDD Mandate IDs has been set to expired: %s'
% expired_mandates.ids)
"The following SDD Mandate IDs has been set to expired: %s"
% expired_mandates.ids
)
else:
logger.info('0 SDD Mandates had to be set to Expired')
logger.info("0 SDD Mandates had to be set to Expired")
return True

View File

@@ -1,18 +1,19 @@
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models, _
from odoo import _, api, models
from odoo.exceptions import UserError
class AccountPaymentLine(models.Model):
_inherit = 'account.payment.line'
_inherit = "account.payment.line"
@api.multi
def draft2open_payment_line_check(self):
res = super(AccountPaymentLine, self).draft2open_payment_line_check()
sepa_dd_lines = self.filtered(
lambda l: l.order_id.payment_method_id.code == 'sepa_direct_debit')
lambda l: l.order_id.payment_method_id.code == "sepa_direct_debit"
)
sepa_dd_lines._check_sepa_direct_debit_ready()
return res
@@ -25,24 +26,33 @@ class AccountPaymentLine(models.Model):
"""
for rec in self:
if not rec.mandate_id:
raise UserError(_(
"Missing SEPA Direct Debit mandate on the line with "
"partner {partner_name} (reference {reference}).").format(
partner_name=rec.partner_id.name, reference=rec.name))
if rec.mandate_id.state != 'valid':
raise UserError(_(
"The SEPA Direct Debit mandate with reference "
"{mandate_ref} for partner {partner_name} has "
"expired.").format(
mandate_ref=rec.mandate_id.unique_mandate_reference,
partner_name=rec.partner_id.name))
if rec.mandate_id.type == 'oneoff' and \
rec.mandate_id.last_debit_date:
raise UserError(_(
"The SEPA Direct Debit mandate with reference "
"{mandate_ref} for partner {partner_name} has type set "
"to 'One-Off' but has a last debit date set to "
"{last_debit_date}. Therefore, it cannot be used.").format(
mandate_ref=rec.mandate_id.unique_mandate_reference,
partner_name=rec.partner_id.name,
last_debit_date=rec.mandate_id.last_debit_date))
raise UserError(
_(
"Missing SEPA Direct Debit mandate on the line with "
"partner {partner_name} (reference {reference})."
).format(partner_name=rec.partner_id.name, reference=rec.name)
)
if rec.mandate_id.state != "valid":
raise UserError(
_(
"The SEPA Direct Debit mandate with reference "
"{mandate_ref} for partner {partner_name} has "
"expired."
).format(
mandate_ref=rec.mandate_id.unique_mandate_reference,
partner_name=rec.partner_id.name,
)
)
if rec.mandate_id.type == "oneoff" and rec.mandate_id.last_debit_date:
raise UserError(
_(
"The SEPA Direct Debit mandate with reference "
"{mandate_ref} for partner {partner_name} has type set "
"to 'One-Off' but has a last debit date set to "
"{last_debit_date}. Therefore, it cannot be used."
).format(
mandate_ref=rec.mandate_id.unique_mandate_reference,
partner_name=rec.partner_id.name,
last_debit_date=rec.mandate_id.last_debit_date,
)
)

View File

@@ -1,26 +1,30 @@
# Copyright 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
from odoo import api, fields, models
class AccountPaymentMethod(models.Model):
_inherit = 'account.payment.method'
_inherit = "account.payment.method"
pain_version = fields.Selection(selection_add=[
('pain.008.001.02', 'pain.008.001.02 (recommended for direct debit)'),
('pain.008.001.03', 'pain.008.001.03'),
('pain.008.001.04', 'pain.008.001.04'),
('pain.008.003.02', 'pain.008.003.02 (direct debit in Germany)'),
])
pain_version = fields.Selection(
selection_add=[
("pain.008.001.02", "pain.008.001.02 (recommended for direct debit)"),
("pain.008.001.03", "pain.008.001.03"),
("pain.008.001.04", "pain.008.001.04"),
("pain.008.003.02", "pain.008.003.02 (direct debit in Germany)"),
]
)
@api.multi
def get_xsd_file_path(self):
self.ensure_one()
if self.pain_version in [
'pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04',
'pain.008.003.02']:
path = 'account_banking_sepa_direct_debit/data/%s.xsd'\
% self.pain_version
"pain.008.001.02",
"pain.008.001.03",
"pain.008.001.04",
"pain.008.003.02",
]:
path = "account_banking_sepa_direct_debit/data/%s.xsd" % self.pain_version
return path
return super(AccountPaymentMethod, self).get_xsd_file_path()

View File

@@ -1,32 +1,37 @@
# Copyright 2016 Tecnativa - Antonio Espinosa
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from .common import is_sepa_creditor_identifier_valid
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from .common import is_sepa_creditor_identifier_valid
class AccountPaymentMode(models.Model):
_inherit = 'account.payment.mode'
_inherit = "account.payment.mode"
sepa_creditor_identifier = fields.Char(
string='SEPA Creditor Identifier', size=35,
string="SEPA Creditor Identifier",
size=35,
help="Enter the Creditor Identifier that has been attributed to your "
"company to make SEPA Direct Debits. If not defined, "
"SEPA Creditor Identifier from company will be used.\n"
"This identifier is composed of :\n"
"- your country ISO code (2 letters)\n"
"- a 2-digits checkum\n"
"- a 3-letters business code\n"
"- a country-specific identifier")
"company to make SEPA Direct Debits. If not defined, "
"SEPA Creditor Identifier from company will be used.\n"
"This identifier is composed of :\n"
"- your country ISO code (2 letters)\n"
"- a 2-digits checkum\n"
"- a 3-letters business code\n"
"- a country-specific identifier",
)
@api.multi
@api.constrains('sepa_creditor_identifier')
@api.constrains("sepa_creditor_identifier")
def _check_sepa_creditor_identifier(self):
for payment_mode in self:
if payment_mode.sepa_creditor_identifier:
if not is_sepa_creditor_identifier_valid(
payment_mode.sepa_creditor_identifier):
payment_mode.sepa_creditor_identifier
):
raise ValidationError(
_("The SEPA Creditor Identifier '%s' is invalid.")
% payment_mode.sepa_creditor_identifier)
% payment_mode.sepa_creditor_identifier
)

View File

@@ -2,64 +2,72 @@
# Copyright 2018 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from lxml import etree
from odoo import _, api, exceptions, fields, models
from odoo.exceptions import UserError
from lxml import etree
class AccountPaymentOrder(models.Model):
_inherit = 'account.payment.order'
_inherit = "account.payment.order"
@api.multi
def generate_payment_file(self):
"""Creates the SEPA Direct Debit file. That's the important code !"""
self.ensure_one()
if self.payment_method_id.code != 'sepa_direct_debit':
if self.payment_method_id.code != "sepa_direct_debit":
return super(AccountPaymentOrder, self).generate_payment_file()
pain_flavor = self.payment_method_id.pain_version
# We use pain_flavor.startswith('pain.008.001.xx')
# to support country-specific extensions such as
# pain.008.001.02.ch.01 (cf l10n_ch_sepa)
if pain_flavor.startswith('pain.008.001.02'):
bic_xml_tag = 'BIC'
if pain_flavor.startswith("pain.008.001.02"):
bic_xml_tag = "BIC"
name_maxsize = 70
root_xml_tag = 'CstmrDrctDbtInitn'
elif pain_flavor.startswith('pain.008.003.02'):
bic_xml_tag = 'BIC'
root_xml_tag = "CstmrDrctDbtInitn"
elif pain_flavor.startswith("pain.008.003.02"):
bic_xml_tag = "BIC"
name_maxsize = 70
root_xml_tag = 'CstmrDrctDbtInitn'
elif pain_flavor.startswith('pain.008.001.03'):
bic_xml_tag = 'BICFI'
root_xml_tag = "CstmrDrctDbtInitn"
elif pain_flavor.startswith("pain.008.001.03"):
bic_xml_tag = "BICFI"
name_maxsize = 140
root_xml_tag = 'CstmrDrctDbtInitn'
elif pain_flavor.startswith('pain.008.001.04'):
bic_xml_tag = 'BICFI'
root_xml_tag = "CstmrDrctDbtInitn"
elif pain_flavor.startswith("pain.008.001.04"):
bic_xml_tag = "BICFI"
name_maxsize = 140
root_xml_tag = 'CstmrDrctDbtInitn'
root_xml_tag = "CstmrDrctDbtInitn"
else:
raise UserError(
_("Payment Type Code '%s' is not supported. The only "
"Payment Type Code supported for SEPA Direct Debit are "
"'pain.008.001.02', 'pain.008.001.03' and "
"'pain.008.001.04'.") % pain_flavor)
_(
"Payment Type Code '%s' is not supported. The only "
"Payment Type Code supported for SEPA Direct Debit are "
"'pain.008.001.02', 'pain.008.001.03' and "
"'pain.008.001.04'."
)
% pain_flavor
)
pay_method = self.payment_mode_id.payment_method_id
xsd_file = pay_method.get_xsd_file_path()
gen_args = {
'bic_xml_tag': bic_xml_tag,
'name_maxsize': name_maxsize,
'convert_to_ascii': pay_method.convert_to_ascii,
'payment_method': 'DD',
'file_prefix': 'sdd_',
'pain_flavor': pain_flavor,
'pain_xsd_file': xsd_file,
"bic_xml_tag": bic_xml_tag,
"name_maxsize": name_maxsize,
"convert_to_ascii": pay_method.convert_to_ascii,
"payment_method": "DD",
"file_prefix": "sdd_",
"pain_flavor": pain_flavor,
"pain_xsd_file": xsd_file,
}
nsmap = self.generate_pain_nsmap()
attrib = self.generate_pain_attrib()
xml_root = etree.Element('Document', nsmap=nsmap, attrib=attrib)
xml_root = etree.Element("Document", nsmap=nsmap, attrib=attrib)
pain_root = etree.SubElement(xml_root, root_xml_tag)
# A. Group header
group_header, nb_of_transactions_a, control_sum_a = \
self.generate_group_header_block(pain_root, gen_args)
(
group_header,
nb_of_transactions_a,
control_sum_a,
) = self.generate_group_header_block(pain_root, gen_args)
transactions_count_a = 0
amount_control_sum_a = 0.0
lines_per_group = {}
@@ -70,22 +78,21 @@ class AccountPaymentOrder(models.Model):
priority = line.priority
categ_purpose = line.category_purpose
scheme = line.mandate_id.scheme
if line.mandate_id.type == 'oneoff':
seq_type = 'OOFF'
elif line.mandate_id.type == 'recurrent':
seq_type_map = {
'recurring': 'RCUR',
'first': 'FRST',
'final': 'FNAL',
}
if line.mandate_id.type == "oneoff":
seq_type = "OOFF"
elif line.mandate_id.type == "recurrent":
seq_type_map = {"recurring": "RCUR", "first": "FRST", "final": "FNAL"}
seq_type_label = line.mandate_id.recurrent_sequence_type
assert seq_type_label is not False
seq_type = seq_type_map[seq_type_label]
else:
raise exceptions.UserError(_(
"Invalid mandate type in '%s'. Valid ones are 'Recurrent' "
"or 'One-Off'"
) % line.mandate_id.unique_mandate_reference)
raise exceptions.UserError(
_(
"Invalid mandate type in '%s'. Valid ones are 'Recurrent' "
"or 'One-Off'"
)
% line.mandate_id.unique_mandate_reference
)
# The field line.date is the requested payment date
# taking into account the 'date_preferred' setting
# cf account_banking_payment_export/models/account_payment.py
@@ -96,128 +103,169 @@ class AccountPaymentOrder(models.Model):
else:
lines_per_group[key] = [line]
for (requested_date, priority, categ_purpose, sequence_type, scheme),\
lines in list(lines_per_group.items()):
for (
(requested_date, priority, categ_purpose, sequence_type, scheme),
lines,
) in list(lines_per_group.items()):
requested_date = fields.Date.to_string(requested_date)
# B. Payment info
payment_info, nb_of_transactions_b, control_sum_b = \
self.generate_start_payment_info_block(
pain_root,
"self.name + '-' + "
"sequence_type + '-' + requested_date.replace('-', '') "
"+ '-' + priority + '-' + category_purpose",
priority, scheme, categ_purpose,
sequence_type, requested_date, {
'self': self,
'sequence_type': sequence_type,
'priority': priority,
'category_purpose': categ_purpose or 'NOcateg',
'requested_date': requested_date,
}, gen_args)
(
payment_info,
nb_of_transactions_b,
control_sum_b,
) = self.generate_start_payment_info_block(
pain_root,
"self.name + '-' + "
"sequence_type + '-' + requested_date.replace('-', '') "
"+ '-' + priority + '-' + category_purpose",
priority,
scheme,
categ_purpose,
sequence_type,
requested_date,
{
"self": self,
"sequence_type": sequence_type,
"priority": priority,
"category_purpose": categ_purpose or "NOcateg",
"requested_date": requested_date,
},
gen_args,
)
self.generate_party_block(
payment_info, 'Cdtr', 'B',
self.company_partner_bank_id, gen_args)
charge_bearer = etree.SubElement(payment_info, 'ChrgBr')
payment_info, "Cdtr", "B", self.company_partner_bank_id, gen_args
)
charge_bearer = etree.SubElement(payment_info, "ChrgBr")
if self.sepa:
charge_bearer_text = 'SLEV'
charge_bearer_text = "SLEV"
else:
charge_bearer_text = self.charge_bearer
charge_bearer.text = charge_bearer_text
creditor_scheme_identification = etree.SubElement(
payment_info, 'CdtrSchmeId')
payment_info, "CdtrSchmeId"
)
self.generate_creditor_scheme_identification(
creditor_scheme_identification,
'self.payment_mode_id.sepa_creditor_identifier or '
'self.company_id.sepa_creditor_identifier',
'SEPA Creditor Identifier', {'self': self}, 'SEPA', gen_args)
"self.payment_mode_id.sepa_creditor_identifier or "
"self.company_id.sepa_creditor_identifier",
"SEPA Creditor Identifier",
{"self": self},
"SEPA",
gen_args,
)
transactions_count_b = 0
amount_control_sum_b = 0.0
for line in lines:
transactions_count_b += 1
# C. Direct Debit Transaction Info
dd_transaction_info = etree.SubElement(
payment_info, 'DrctDbtTxInf')
payment_identification = etree.SubElement(
dd_transaction_info, 'PmtId')
dd_transaction_info = etree.SubElement(payment_info, "DrctDbtTxInf")
payment_identification = etree.SubElement(dd_transaction_info, "PmtId")
instruction_identification = etree.SubElement(
payment_identification, 'InstrId')
payment_identification, "InstrId"
)
instruction_identification.text = self._prepare_field(
'Instruction Identification', 'line.name',
{'line': line}, 35, gen_args=gen_args)
"Instruction Identification",
"line.name",
{"line": line},
35,
gen_args=gen_args,
)
end2end_identification = etree.SubElement(
payment_identification, 'EndToEndId')
payment_identification, "EndToEndId"
)
end2end_identification.text = self._prepare_field(
'End to End Identification', 'line.name',
{'line': line}, 35, gen_args=gen_args)
"End to End Identification",
"line.name",
{"line": line},
35,
gen_args=gen_args,
)
currency_name = self._prepare_field(
'Currency Code', 'line.currency_id.name',
{'line': line}, 3, gen_args=gen_args)
"Currency Code",
"line.currency_id.name",
{"line": line},
3,
gen_args=gen_args,
)
instructed_amount = etree.SubElement(
dd_transaction_info, 'InstdAmt', Ccy=currency_name)
instructed_amount.text = '%.2f' % line.amount_currency
dd_transaction_info, "InstdAmt", Ccy=currency_name
)
instructed_amount.text = "%.2f" % line.amount_currency
amount_control_sum_a += line.amount_currency
amount_control_sum_b += line.amount_currency
dd_transaction = etree.SubElement(
dd_transaction_info, 'DrctDbtTx')
mandate_related_info = etree.SubElement(
dd_transaction, 'MndtRltdInf')
dd_transaction = etree.SubElement(dd_transaction_info, "DrctDbtTx")
mandate_related_info = etree.SubElement(dd_transaction, "MndtRltdInf")
mandate_identification = etree.SubElement(
mandate_related_info, 'MndtId')
mandate_related_info, "MndtId"
)
mandate_identification.text = self._prepare_field(
'Unique Mandate Reference',
'line.mandate_id.unique_mandate_reference',
{'line': line}, 35, gen_args=gen_args)
"Unique Mandate Reference",
"line.mandate_id.unique_mandate_reference",
{"line": line},
35,
gen_args=gen_args,
)
mandate_signature_date = etree.SubElement(
mandate_related_info, 'DtOfSgntr')
mandate_related_info, "DtOfSgntr"
)
mandate_signature_date.text = self._prepare_field(
'Mandate Signature Date',
'signature_date',
"Mandate Signature Date",
"signature_date",
{
'line': line,
'signature_date': fields.Date.to_string(
line.mandate_id.signature_date,
"line": line,
"signature_date": fields.Date.to_string(
line.mandate_id.signature_date
),
}, 10, gen_args=gen_args)
if sequence_type == 'FRST' and line.mandate_id.last_debit_date:
},
10,
gen_args=gen_args,
)
if sequence_type == "FRST" and line.mandate_id.last_debit_date:
amendment_indicator = etree.SubElement(
mandate_related_info, 'AmdmntInd')
amendment_indicator.text = 'true'
mandate_related_info, "AmdmntInd"
)
amendment_indicator.text = "true"
amendment_info_details = etree.SubElement(
mandate_related_info, 'AmdmntInfDtls')
mandate_related_info, "AmdmntInfDtls"
)
ori_debtor_account = etree.SubElement(
amendment_info_details, 'OrgnlDbtrAcct')
ori_debtor_account_id = etree.SubElement(
ori_debtor_account, 'Id')
amendment_info_details, "OrgnlDbtrAcct"
)
ori_debtor_account_id = etree.SubElement(ori_debtor_account, "Id")
ori_debtor_agent_other = etree.SubElement(
ori_debtor_account_id, 'Othr')
ori_debtor_account_id, "Othr"
)
ori_debtor_agent_other_id = etree.SubElement(
ori_debtor_agent_other, 'Id')
ori_debtor_agent_other_id.text = 'SMNDA'
ori_debtor_agent_other, "Id"
)
ori_debtor_agent_other_id.text = "SMNDA"
# Until 20/11/2016, SMNDA meant
# "Same Mandate New Debtor Agent"
# After 20/11/2016, SMNDA means
# "Same Mandate New Debtor Account"
self.generate_party_block(
dd_transaction_info, 'Dbtr', 'C',
line.partner_bank_id, gen_args, line)
dd_transaction_info,
"Dbtr",
"C",
line.partner_bank_id,
gen_args,
line,
)
if line.purpose:
purpose = etree.SubElement(
dd_transaction_info, 'Purp')
etree.SubElement(purpose, 'Cd').text = line.purpose
purpose = etree.SubElement(dd_transaction_info, "Purp")
etree.SubElement(purpose, "Cd").text = line.purpose
self.generate_remittance_info_block(
dd_transaction_info, line, gen_args)
self.generate_remittance_info_block(dd_transaction_info, line, gen_args)
nb_of_transactions_b.text = str(transactions_count_b)
control_sum_b.text = '%.2f' % amount_control_sum_b
control_sum_b.text = "%.2f" % amount_control_sum_b
nb_of_transactions_a.text = str(transactions_count_a)
control_sum_a.text = '%.2f' % amount_control_sum_a
control_sum_a.text = "%.2f" % amount_control_sum_a
return self.finalize_sepa_file_creation(
xml_root, gen_args)
return self.finalize_sepa_file_creation(xml_root, gen_args)
@api.multi
def generated2uploaded(self):
@@ -230,7 +278,7 @@ class AccountPaymentOrder(models.Model):
# is generated BEFORE, which will allow the split
# of the account move per sequence_type
res = super(AccountPaymentOrder, self).generated2uploaded()
abmo = self.env['account.banking.mandate']
abmo = self.env["account.banking.mandate"]
for order in self:
to_expire_mandates = abmo.browse([])
first_mandates = abmo.browse([])
@@ -239,25 +287,25 @@ class AccountPaymentOrder(models.Model):
if bline.mandate_id in all_mandates:
continue
all_mandates += bline.mandate_id
if bline.mandate_id.type == 'oneoff':
if bline.mandate_id.type == "oneoff":
to_expire_mandates += bline.mandate_id
elif bline.mandate_id.type == 'recurrent':
elif bline.mandate_id.type == "recurrent":
seq_type = bline.mandate_id.recurrent_sequence_type
if seq_type == 'final':
if seq_type == "final":
to_expire_mandates += bline.mandate_id
elif seq_type == 'first':
elif seq_type == "first":
first_mandates += bline.mandate_id
all_mandates.write(
{'last_debit_date': order.date_generated})
to_expire_mandates.write({'state': 'expired'})
first_mandates.write({
'recurrent_sequence_type': 'recurring',
})
all_mandates.write({"last_debit_date": order.date_generated})
to_expire_mandates.write({"state": "expired"})
first_mandates.write({"recurrent_sequence_type": "recurring"})
for first_mandate in first_mandates:
first_mandate.message_post(body=_(
"Automatically switched from <b>First</b> to "
"<b>Recurring</b> when the debit order "
"<a href=# data-oe-model=account.payment.order "
"data-oe-id=%d>%s</a> has been marked as uploaded.")
% (order.id, order.name))
first_mandate.message_post(
body=_(
"Automatically switched from <b>First</b> to "
"<b>Recurring</b> when the debit order "
"<a href=# data-oe-model=account.payment.order "
"data-oe-id=%d>%s</a> has been marked as uploaded."
)
% (order.id, order.name)
)
return res

View File

@@ -1,11 +1,11 @@
# Copyright 2015-2016 Akretion - Alexis de Lattre
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, api
from odoo import api, models
class BankPaymentLine(models.Model):
_inherit = 'bank.payment.line'
_inherit = "bank.payment.line"
@api.multi
def move_line_offsetting_account_hashcode(self):
@@ -16,7 +16,6 @@ class BankPaymentLine(models.Model):
So we split the transfer move lines by mandate type, so easier
reconciliation of the bank statement.
"""
hashcode = super(BankPaymentLine, self).\
move_line_offsetting_account_hashcode()
hashcode += '-' + str(self.mandate_id.recurrent_sequence_type)
hashcode = super(BankPaymentLine, self).move_line_offsetting_account_hashcode()
hashcode += "-" + str(self.mandate_id.recurrent_sequence_type)
return hashcode

View File

@@ -19,20 +19,17 @@ def is_sepa_creditor_identifier_valid(sepa_creditor_identifier):
try:
sci = str(sepa_creditor_identifier).lower()
except Exception:
logger.warning(
"SEPA Creditor ID should contain only ASCII characters.")
logger.warning("SEPA Creditor ID should contain only ASCII characters.")
return False
if len(sci) < 9:
return False
before_replacement = sci[7:] + sci[0:2] + '00'
logger.debug(
"SEPA ID check before_replacement = %s" % before_replacement)
after_replacement = ''
before_replacement = sci[7:] + sci[0:2] + "00"
logger.debug("SEPA ID check before_replacement = %s" % before_replacement)
after_replacement = ""
for char in before_replacement:
if char.isalpha():
after_replacement += str(ord(char) - 87)
else:
after_replacement += char
logger.debug(
"SEPA ID check after_replacement = %s" % after_replacement)
logger.debug("SEPA ID check after_replacement = %s" % after_replacement)
return int(sci[2:4]) == (98 - (int(after_replacement) % 97))

View File

@@ -3,29 +3,34 @@
# Copyright 2016 Tecnativa - Antonio Espinosa
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from .common import is_sepa_creditor_identifier_valid
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from .common import is_sepa_creditor_identifier_valid
class ResCompany(models.Model):
_inherit = 'res.company'
_inherit = "res.company"
sepa_creditor_identifier = fields.Char(
string='SEPA Creditor Identifier', size=35,
string="SEPA Creditor Identifier",
size=35,
help="Enter the Creditor Identifier that has been attributed to your "
"company to make SEPA Direct Debits. This identifier is composed "
"of :\n- your country ISO code (2 letters)\n- a 2-digits "
"checkum\n- a 3-letters business code\n- a country-specific "
"identifier")
"company to make SEPA Direct Debits. This identifier is composed "
"of :\n- your country ISO code (2 letters)\n- a 2-digits "
"checkum\n- a 3-letters business code\n- a country-specific "
"identifier",
)
@api.multi
@api.constrains('sepa_creditor_identifier')
@api.constrains("sepa_creditor_identifier")
def _check_sepa_creditor_identifier(self):
for company in self:
if company.sepa_creditor_identifier:
if not is_sepa_creditor_identifier_valid(
company.sepa_creditor_identifier):
company.sepa_creditor_identifier
):
raise ValidationError(
_("The SEPA Creditor Identifier '%s' is invalid.")
% company.sepa_creditor_identifier)
% company.sepa_creditor_identifier
)

View File

@@ -1,13 +1,12 @@
# Copyright 2016 Akretion - Alexis de Lattre
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
_inherit = "res.config.settings"
sepa_creditor_identifier = fields.Char(
related='company_id.sepa_creditor_identifier',
readonly=False,
related="company_id.sepa_creditor_identifier", readonly=False
)

View File

@@ -1,18 +1,15 @@
# Copyright 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, SUPERUSER_ID
from odoo import SUPERUSER_ID, api
def update_bank_journals(cr, registry):
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
ajo = env['account.journal']
journals = ajo.search([('type', '=', 'bank')])
sdd = env.ref(
'account_banking_sepa_direct_debit.sepa_direct_debit')
ajo = env["account.journal"]
journals = ajo.search([("type", "=", "bank")])
sdd = env.ref("account_banking_sepa_direct_debit.sepa_direct_debit")
if sdd:
journals.write({
'inbound_payment_method_ids': [(4, sdd.id)],
})
journals.write({"inbound_payment_method_ids": [(4, sdd.id)]})
return

View File

@@ -1,12 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="assets_common" name="account_banking_sepa_direct_debit pdf assets" inherit_id="web.report_assets_common">
<template
id="assets_common"
name="account_banking_sepa_direct_debit pdf assets"
inherit_id="web.report_assets_common"
>
<xpath expr="." position="inside">
<link href="/account_banking_sepa_direct_debit/static/src/css/report.css" rel="stylesheet"/>
<link
href="/account_banking_sepa_direct_debit/static/src/css/report.css"
rel="stylesheet"
/>
</xpath>
</template>
<!-- QWeb Report -->
<report
id="report_sepa_direct_debit_mandate"
@@ -15,11 +20,11 @@
report_type="qweb-pdf"
name="account_banking_sepa_direct_debit.sepa_direct_debit_mandate"
file="account_banking_sepa_direct_debit.sepa_direct_debit_mandate"
/>
/>
<record id="report_sepa_direct_debit_mandate" model="ir.actions.report">
<field name="paperformat_id"
ref="account_banking_sepa_direct_debit.paperformat_euro_sepa_lowmargin"/>
<field
name="paperformat_id"
ref="account_banking_sepa_direct_debit.paperformat_euro_sepa_lowmargin"
/>
</record>
</odoo>

View File

@@ -89,4 +89,4 @@
y="109.17231"
id="tspan2995"
style="font-weight:bold;-inkscape-font-specification:Sans Bold">DEBIT</tspan></text>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -1,27 +1,27 @@
.sepa_direct_debit h4 {
margin-top: 2px;
margin-bottom: 2px;
margin-top: 2px;
margin-bottom: 2px;
}
.sepa_direct_debit .under-line{
border-bottom:1px solid;
.sepa_direct_debit .under-line {
border-bottom: 1px solid;
}
.sepa_direct_debit input{
.sepa_direct_debit input {
vertical-align: baseline;
}
.sepa_direct_debit .card{
border:2px solid;
.sepa_direct_debit .card {
border: 2px solid;
margin-bottom: 4px;
}
.sepa_direct_debit .card-body{
.sepa_direct_debit .card-body {
padding: 2px 14px;
}
.sepa_direct_debit p{
.sepa_direct_debit p {
margin: 0 0 4px;
font-size: 8px;
}
.sepa_direct_debit .native_lang{
.sepa_direct_debit .native_lang {
font-style: italic;
}
.sepa_direct_debit .native_lang_small{
.sepa_direct_debit .native_lang_small {
font-size: 0.8em;
}

View File

@@ -1,3 +1,2 @@
from . import test_sdd
from . import test_mandate

View File

@@ -1,46 +1,48 @@
# Copyright 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
class TestMandate(TransactionCase):
def test_contrains(self):
with self.assertRaises(ValidationError):
self.mandate.recurrent_sequence_type = False
self.mandate.type = 'recurrent'
self.mandate.type = "recurrent"
self.mandate._check_recurring_type()
def test_onchange_bank(self):
self.mandate.write({
'type': 'recurrent', 'recurrent_sequence_type': 'recurring'
})
self.mandate.write(
{"type": "recurrent", "recurrent_sequence_type": "recurring"}
)
self.mandate.validate()
self.mandate.partner_bank_id = self.env.ref(
'account_payment_mode.res_partner_2_iban'
"account_payment_mode.res_partner_2_iban"
)
self.mandate.mandate_partner_bank_change()
self.assertEqual(self.mandate.recurrent_sequence_type, 'first')
self.assertEqual(self.mandate.recurrent_sequence_type, "first")
def test_expire(self):
self.mandate.signature_date = fields.Date.today() + relativedelta(
months=-50)
self.mandate.signature_date = fields.Date.today() + relativedelta(months=-50)
self.mandate.validate()
self.assertEqual(self.mandate.state, 'valid')
self.env['account.banking.mandate']._sdd_mandate_set_state_to_expired()
self.assertEqual(self.mandate.state, 'expired')
self.assertEqual(self.mandate.state, "valid")
self.env["account.banking.mandate"]._sdd_mandate_set_state_to_expired()
self.assertEqual(self.mandate.state, "expired")
def setUp(self):
res = super(TestMandate, self).setUp()
self.partner = self.env.ref('base.res_partner_12')
bank_account = self.env.ref('account_payment_mode.res_partner_12_iban')
self.mandate = self.env['account.banking.mandate'].create({
'partner_bank_id': bank_account.id,
'format': 'sepa',
'type': 'oneoff',
'signature_date': '2015-01-01',
})
self.partner = self.env.ref("base.res_partner_12")
bank_account = self.env.ref("account_payment_mode.res_partner_12_iban")
self.mandate = self.env["account.banking.mandate"].create(
{
"partner_bank_id": bank_account.id,
"format": "sepa",
"type": "oneoff",
"signature_date": "2015-01-01",
}
)
return res

View File

@@ -3,230 +3,253 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import base64
import time
from lxml import etree
from odoo.tests import common
from odoo.tools import float_compare
import time
from lxml import etree
class TestSDD(common.HttpCase):
def setUp(self):
super(TestSDD, self).setUp()
self.company = self.env['res.company']
self.account_model = self.env['account.account']
self.move_model = self.env['account.move']
self.journal_model = self.env['account.journal']
self.payment_order_model = self.env['account.payment.order']
self.payment_line_model = self.env['account.payment.line']
self.mandate_model = self.env['account.banking.mandate']
self.bank_line_model = self.env['bank.payment.line']
self.partner_bank_model = self.env['res.partner.bank']
self.attachment_model = self.env['ir.attachment']
self.invoice_model = self.env['account.invoice']
self.invoice_line_model = self.env['account.invoice.line']
self.partner_agrolait = self.env.ref('base.res_partner_2')
self.partner_c2c = self.env.ref('base.res_partner_12')
self.eur_currency = self.env.ref('base.EUR')
self.main_company = self.env['res.company'].create({
'name': 'Test EUR company',
'currency_id': self.eur_currency.id,
'sepa_creditor_identifier': 'FR78ZZZ424242',
})
self.env.user.write({
'company_ids': [(6, 0, self.main_company.ids)],
'company_id': self.main_company.id,
})
self.company = self.env["res.company"]
self.account_model = self.env["account.account"]
self.move_model = self.env["account.move"]
self.journal_model = self.env["account.journal"]
self.payment_order_model = self.env["account.payment.order"]
self.payment_line_model = self.env["account.payment.line"]
self.mandate_model = self.env["account.banking.mandate"]
self.bank_line_model = self.env["bank.payment.line"]
self.partner_bank_model = self.env["res.partner.bank"]
self.attachment_model = self.env["ir.attachment"]
self.invoice_model = self.env["account.invoice"]
self.invoice_line_model = self.env["account.invoice.line"]
self.partner_agrolait = self.env.ref("base.res_partner_2")
self.partner_c2c = self.env.ref("base.res_partner_12")
self.eur_currency = self.env.ref("base.EUR")
self.main_company = self.env["res.company"].create(
{
"name": "Test EUR company",
"currency_id": self.eur_currency.id,
"sepa_creditor_identifier": "FR78ZZZ424242",
}
)
self.env.user.write(
{
"company_ids": [(6, 0, self.main_company.ids)],
"company_id": self.main_company.id,
}
)
self.partner_agrolait.company_id = self.main_company.id
self.partner_c2c.company_id = self.main_company.id
self.env.ref(
'l10n_generic_coa.configurable_chart_template'
"l10n_generic_coa.configurable_chart_template"
).try_loading_for_current_company()
self.account_revenue = self.account_model.search([
('user_type_id', '=',
self.env.ref(
'account.data_account_type_revenue').id),
('company_id', '=', self.main_company.id),
], limit=1)
self.account_receivable = self.account_model.search([
('user_type_id', '=',
self.env.ref('account.data_account_type_receivable').id),
('company_id', '=', self.main_company.id),
], limit=1)
self.company_bank = self.env.ref(
'account_payment_mode.main_company_iban'
).copy({
'company_id': self.main_company.id,
'partner_id': self.main_company.partner_id.id,
'bank_id': (
self.env.ref('account_payment_mode.bank_la_banque_postale').id
),
})
self.account_revenue = self.account_model.search(
[
(
"user_type_id",
"=",
self.env.ref("account.data_account_type_revenue").id,
),
("company_id", "=", self.main_company.id),
],
limit=1,
)
self.account_receivable = self.account_model.search(
[
(
"user_type_id",
"=",
self.env.ref("account.data_account_type_receivable").id,
),
("company_id", "=", self.main_company.id),
],
limit=1,
)
self.company_bank = self.env.ref("account_payment_mode.main_company_iban").copy(
{
"company_id": self.main_company.id,
"partner_id": self.main_company.partner_id.id,
"bank_id": (
self.env.ref("account_payment_mode.bank_la_banque_postale").id
),
}
)
# create journal
self.bank_journal = self.journal_model.create({
'name': 'Company Bank journal',
'type': 'bank',
'code': 'BNKFC',
'bank_account_id': self.company_bank.id,
'bank_id': self.company_bank.bank_id.id,
})
self.bank_journal = self.journal_model.create(
{
"name": "Company Bank journal",
"type": "bank",
"code": "BNKFC",
"bank_account_id": self.company_bank.id,
"bank_id": self.company_bank.bank_id.id,
}
)
# update payment mode
self.payment_mode = self.env.ref(
'account_banking_sepa_direct_debit.payment_mode_inbound_sepa_dd1'
).copy({
'company_id': self.main_company.id,
})
self.payment_mode.write({
'bank_account_link': 'fixed',
'fixed_journal_id': self.bank_journal.id,
})
"account_banking_sepa_direct_debit.payment_mode_inbound_sepa_dd1"
).copy({"company_id": self.main_company.id})
self.payment_mode.write(
{"bank_account_link": "fixed", "fixed_journal_id": self.bank_journal.id}
)
# Copy partner bank accounts
bank1 = self.env.ref('account_payment_mode.res_partner_12_iban').copy({
'company_id': self.main_company.id,
})
bank1 = self.env.ref("account_payment_mode.res_partner_12_iban").copy(
{"company_id": self.main_company.id}
)
self.mandate12 = self.env.ref(
'account_banking_sepa_direct_debit.res_partner_12_mandate'
).copy({
'partner_bank_id': bank1.id,
'company_id': self.main_company.id,
'state': 'valid',
'unique_mandate_reference': 'BMTEST12',
})
bank2 = self.env.ref('account_payment_mode.res_partner_2_iban').copy({
'company_id': self.main_company.id,
})
"account_banking_sepa_direct_debit.res_partner_12_mandate"
).copy(
{
"partner_bank_id": bank1.id,
"company_id": self.main_company.id,
"state": "valid",
"unique_mandate_reference": "BMTEST12",
}
)
bank2 = self.env.ref("account_payment_mode.res_partner_2_iban").copy(
{"company_id": self.main_company.id}
)
self.mandate2 = self.env.ref(
'account_banking_sepa_direct_debit.res_partner_2_mandate'
).copy({
'partner_bank_id': bank2.id,
'company_id': self.main_company.id,
'state': 'valid',
'unique_mandate_reference': 'BMTEST2',
})
"account_banking_sepa_direct_debit.res_partner_2_mandate"
).copy(
{
"partner_bank_id": bank2.id,
"company_id": self.main_company.id,
"state": "valid",
"unique_mandate_reference": "BMTEST2",
}
)
# Trigger the recompute of account type on res.partner.bank
self.partner_bank_model.search([])._compute_acc_type()
def test_pain_001_02(self):
self.payment_mode.payment_method_id.pain_version = 'pain.008.001.02'
self.payment_mode.payment_method_id.pain_version = "pain.008.001.02"
self.check_sdd()
def test_pain_003_02(self):
self.payment_mode.payment_method_id.pain_version = 'pain.008.003.02'
self.payment_mode.payment_method_id.pain_version = "pain.008.003.02"
self.check_sdd()
def test_pain_001_03(self):
self.payment_mode.payment_method_id.pain_version = 'pain.008.001.03'
self.payment_mode.payment_method_id.pain_version = "pain.008.001.03"
self.check_sdd()
def test_pain_001_04(self):
self.payment_mode.payment_method_id.pain_version = 'pain.008.001.04'
self.payment_mode.payment_method_id.pain_version = "pain.008.001.04"
self.check_sdd()
def check_sdd(self):
self.mandate2.recurrent_sequence_type = 'first'
invoice1 = self.create_invoice(
self.partner_agrolait.id, self.mandate2, 42.0,
)
self.mandate12.type = 'oneoff'
invoice2 = self.create_invoice(
self.partner_c2c.id, self.mandate12, 11.0,
)
self.mandate2.recurrent_sequence_type = "first"
invoice1 = self.create_invoice(self.partner_agrolait.id, self.mandate2, 42.0)
self.mandate12.type = "oneoff"
invoice2 = self.create_invoice(self.partner_c2c.id, self.mandate12, 11.0)
for inv in [invoice1, invoice2]:
action = inv.create_account_payment_line()
self.assertEqual(action['res_model'], 'account.payment.order')
payment_order = self.payment_order_model.browse(action['res_id'])
self.assertEqual(
payment_order.payment_type, 'inbound')
self.assertEqual(
payment_order.payment_mode_id, self.payment_mode)
self.assertEqual(
payment_order.journal_id, self.bank_journal)
self.assertEqual(action["res_model"], "account.payment.order")
payment_order = self.payment_order_model.browse(action["res_id"])
self.assertEqual(payment_order.payment_type, "inbound")
self.assertEqual(payment_order.payment_mode_id, self.payment_mode)
self.assertEqual(payment_order.journal_id, self.bank_journal)
# Check payment line
pay_lines = self.payment_line_model.search([
('partner_id', '=', self.partner_agrolait.id),
('order_id', '=', payment_order.id)])
pay_lines = self.payment_line_model.search(
[
("partner_id", "=", self.partner_agrolait.id),
("order_id", "=", payment_order.id),
]
)
self.assertEqual(len(pay_lines), 1)
agrolait_pay_line1 = pay_lines[0]
accpre = self.env['decimal.precision'].precision_get('Account')
accpre = self.env["decimal.precision"].precision_get("Account")
self.assertEqual(agrolait_pay_line1.currency_id, self.eur_currency)
self.assertEqual(agrolait_pay_line1.mandate_id, invoice1.mandate_id)
self.assertEqual(
agrolait_pay_line1.currency_id, self.eur_currency)
agrolait_pay_line1.partner_bank_id, invoice1.mandate_id.partner_bank_id
)
self.assertEqual(
agrolait_pay_line1.mandate_id, invoice1.mandate_id)
self.assertEqual(
agrolait_pay_line1.partner_bank_id,
invoice1.mandate_id.partner_bank_id)
self.assertEqual(float_compare(
agrolait_pay_line1.amount_currency, 42, precision_digits=accpre),
0)
self.assertEqual(agrolait_pay_line1.communication_type, 'normal')
float_compare(
agrolait_pay_line1.amount_currency, 42, precision_digits=accpre
),
0,
)
self.assertEqual(agrolait_pay_line1.communication_type, "normal")
self.assertEqual(agrolait_pay_line1.communication, invoice1.number)
payment_order.draft2open()
self.assertEqual(payment_order.state, 'open')
self.assertEqual(payment_order.state, "open")
self.assertEqual(payment_order.sepa, True)
# Check bank payment line
bank_lines = self.bank_line_model.search([
('partner_id', '=', self.partner_agrolait.id)])
bank_lines = self.bank_line_model.search(
[("partner_id", "=", self.partner_agrolait.id)]
)
self.assertEqual(len(bank_lines), 1)
agrolait_bank_line = bank_lines[0]
self.assertEqual(agrolait_bank_line.currency_id, self.eur_currency)
self.assertEqual(
agrolait_bank_line.currency_id, self.eur_currency)
self.assertEqual(float_compare(
agrolait_bank_line.amount_currency, 42.0, precision_digits=accpre),
0)
self.assertEqual(agrolait_bank_line.communication_type, 'normal')
float_compare(
agrolait_bank_line.amount_currency, 42.0, precision_digits=accpre
),
0,
)
self.assertEqual(agrolait_bank_line.communication_type, "normal")
self.assertEqual(agrolait_bank_line.communication, invoice1.number)
self.assertEqual(agrolait_bank_line.mandate_id, invoice1.mandate_id)
self.assertEqual(
agrolait_bank_line.communication, invoice1.number)
self.assertEqual(
agrolait_bank_line.mandate_id, invoice1.mandate_id)
self.assertEqual(
agrolait_bank_line.partner_bank_id,
invoice1.mandate_id.partner_bank_id)
agrolait_bank_line.partner_bank_id, invoice1.mandate_id.partner_bank_id
)
action = payment_order.open2generated()
self.assertEqual(payment_order.state, 'generated')
self.assertEqual(action['res_model'], 'ir.attachment')
attachment = self.attachment_model.browse(action['res_id'])
self.assertEqual(attachment.datas_fname[-4:], '.xml')
self.assertEqual(payment_order.state, "generated")
self.assertEqual(action["res_model"], "ir.attachment")
attachment = self.attachment_model.browse(action["res_id"])
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["p"] = xml_root.nsmap[None]
namespaces.pop(None)
pay_method_xpath = xml_root.xpath(
'//p:PmtInf/p:PmtMtd', namespaces=namespaces)
self.assertEqual(pay_method_xpath[0].text, 'DD')
pay_method_xpath = xml_root.xpath("//p:PmtInf/p:PmtMtd", namespaces=namespaces)
self.assertEqual(pay_method_xpath[0].text, "DD")
sepa_xpath = xml_root.xpath(
'//p:PmtInf/p:PmtTpInf/p:SvcLvl/p:Cd', namespaces=namespaces)
self.assertEqual(sepa_xpath[0].text, 'SEPA')
"//p:PmtInf/p:PmtTpInf/p:SvcLvl/p:Cd", namespaces=namespaces
)
self.assertEqual(sepa_xpath[0].text, "SEPA")
debtor_acc_xpath = xml_root.xpath(
'//p:PmtInf/p:CdtrAcct/p:Id/p:IBAN', namespaces=namespaces)
"//p:PmtInf/p:CdtrAcct/p:Id/p:IBAN", namespaces=namespaces
)
self.assertEqual(
debtor_acc_xpath[0].text,
payment_order.company_partner_bank_id.sanitized_acc_number)
payment_order.company_partner_bank_id.sanitized_acc_number,
)
payment_order.generated2uploaded()
self.assertEqual(payment_order.state, 'uploaded')
self.assertEqual(payment_order.state, "uploaded")
for inv in [invoice1, invoice2]:
self.assertEqual(inv.state, 'paid')
self.assertEqual(self.mandate2.recurrent_sequence_type, 'recurring')
self.assertEqual(inv.state, "paid")
self.assertEqual(self.mandate2.recurrent_sequence_type, "recurring")
return
def create_invoice(
self, partner_id, mandate, price_unit, type='out_invoice'):
invoice = self.invoice_model.create({
'partner_id': partner_id,
'reference_type': 'none',
'currency_id': self.env.ref('base.EUR').id,
'name': 'test',
'account_id': self.account_receivable.id,
'type': type,
'date_invoice': time.strftime('%Y-%m-%d'),
'payment_mode_id': self.payment_mode.id,
'mandate_id': mandate.id,
})
self.invoice_line_model.create({
'invoice_id': invoice.id,
'price_unit': price_unit,
'quantity': 1,
'name': 'Great service',
'account_id': self.account_revenue.id,
})
def create_invoice(self, partner_id, mandate, price_unit, type="out_invoice"):
invoice = self.invoice_model.create(
{
"partner_id": partner_id,
"reference_type": "none",
"currency_id": self.env.ref("base.EUR").id,
"name": "test",
"account_id": self.account_receivable.id,
"type": type,
"date_invoice": time.strftime("%Y-%m-%d"),
"payment_mode_id": self.payment_mode.id,
"mandate_id": mandate.id,
}
)
self.invoice_line_model.create(
{
"invoice_id": invoice.id,
"price_unit": price_unit,
"quantity": 1,
"name": "Great service",
"account_id": self.account_revenue.id,
}
)
invoice.action_invoice_open()
return invoice

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2013-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
@@ -7,58 +7,76 @@
Customize all mandate views (and actions) to fit SEPA mandate style
-->
<odoo>
<record id="view_mandate_form" model="ir.ui.view">
<field name="name">sdd.mandate.form</field>
<field name="model">account.banking.mandate</field>
<field name="inherit_id" ref="account_banking_mandate.view_mandate_form"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="scheme" attrs="{'invisible': [('format', '!=', 'sepa')],
'required': [('format', '=', 'sepa')]}"/>
<field name="type" attrs="{'invisible': [('format', '!=', 'sepa')],
'required': [('format', '=', 'sepa')]}"/>
<field name="recurrent_sequence_type"
attrs="{'invisible': ['|', ('type', '=', 'oneoff'), ('format', '!=', 'sepa')],
'required': [('type', '=', 'recurrent')]}"/>
<record id="view_mandate_form" model="ir.ui.view">
<field name="name">sdd.mandate.form</field>
<field name="model">account.banking.mandate</field>
<field name="inherit_id" ref="account_banking_mandate.view_mandate_form" />
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field
name="scheme"
attrs="{'invisible': [('format', '!=', 'sepa')],
'required': [('format', '=', 'sepa')]}"
/>
<field
name="type"
attrs="{'invisible': [('format', '!=', 'sepa')],
'required': [('format', '=', 'sepa')]}"
/>
<field
name="recurrent_sequence_type"
attrs="{'invisible': ['|', ('type', '=', 'oneoff'), ('format', '!=', 'sepa')],
'required': [('type', '=', 'recurrent')]}"
/>
</field>
</field>
</field>
</record>
<record id="view_mandate_tree" model="ir.ui.view">
<field name="name">sdd.mandate.tree</field>
<field name="model">account.banking.mandate</field>
<field name="inherit_id" ref="account_banking_mandate.view_mandate_tree"/>
<field name="arch" type="xml">
<field name="unique_mandate_reference" position="after">
<field name="scheme"/>
<field name="type" string="Type"/>
<field name="recurrent_sequence_type" string="Sequence Type"/>
</record>
<record id="view_mandate_tree" model="ir.ui.view">
<field name="name">sdd.mandate.tree</field>
<field name="model">account.banking.mandate</field>
<field name="inherit_id" ref="account_banking_mandate.view_mandate_tree" />
<field name="arch" type="xml">
<field name="unique_mandate_reference" position="after">
<field name="scheme" />
<field name="type" string="Type" />
<field name="recurrent_sequence_type" string="Sequence Type" />
</field>
</field>
</field>
</record>
<record id="view_mandate_search" model="ir.ui.view">
<field name="name">sdd.mandate.search</field>
<field name="model">account.banking.mandate</field>
<field name="inherit_id" ref="account_banking_mandate.view_mandate_search"/>
<field name="arch" type="xml">
<filter name="expired" position="after">
<filter name="oneoff" string="One-Off" domain="[('type', '=', 'oneoff')]" />
<filter name="recurrent" string="Recurrent" domain="[('type', '=', 'recurrent')]" />
</filter>
<filter name="signature_date_groupby" position="before">
<filter name="type_groupby" string="Type"
context="{'group_by': 'type'}"/>
<filter name="scheme_groupby" string="Scheme"
context="{'group_by': 'scheme'}"/>
<filter name="recurrent_sequence_type_groupby"
string="Sequence Type"
context="{'group_by': 'recurrent_sequence_type'}"/>
</filter>
</field>
</record>
</record>
<record id="view_mandate_search" model="ir.ui.view">
<field name="name">sdd.mandate.search</field>
<field name="model">account.banking.mandate</field>
<field name="inherit_id" ref="account_banking_mandate.view_mandate_search" />
<field name="arch" type="xml">
<filter name="expired" position="after">
<filter
name="oneoff"
string="One-Off"
domain="[('type', '=', 'oneoff')]"
/>
<filter
name="recurrent"
string="Recurrent"
domain="[('type', '=', 'recurrent')]"
/>
</filter>
<filter name="signature_date_groupby" position="before">
<filter
name="type_groupby"
string="Type"
context="{'group_by': 'type'}"
/>
<filter
name="scheme_groupby"
string="Scheme"
context="{'group_by': 'scheme'}"
/>
<filter
name="recurrent_sequence_type_groupby"
string="Sequence Type"
context="{'group_by': 'recurrent_sequence_type'}"
/>
</filter>
</field>
</record>
</odoo>

View File

@@ -1,20 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2015-2017 Tecnativa
Copyright 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -->
<odoo>
<record id="account_payment_mode_form" model="ir.ui.view">
<field name="name">Add SEPA identifiers on payment mode form</field>
<field name="model">account.payment.mode</field>
<field name="inherit_id" ref="account_banking_pain_base.account_payment_mode_form"/>
<field name="arch" type="xml">
<group name="main" position="inside">
<field name="sepa_creditor_identifier" groups="account_banking_pain_base.group_pain_multiple_identifier"/>
</group>
</field>
</record>
<record id="account_payment_mode_form" model="ir.ui.view">
<field name="name">Add SEPA identifiers on payment mode form</field>
<field name="model">account.payment.mode</field>
<field
name="inherit_id"
ref="account_banking_pain_base.account_payment_mode_form"
/>
<field name="arch" type="xml">
<group name="main" position="inside">
<field
name="sepa_creditor_identifier"
groups="account_banking_pain_base.group_pain_multiple_identifier"
/>
</group>
</field>
</record>
</odoo>

View File

@@ -1,23 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="sepa_direct_debit_mandate_document">
<t t-set="doc" t-value="doc.with_context({'lang':doc.partner_id.lang})" />
<t t-set="is_not_english" t-value="doc.partner_id.lang and doc.partner_id.lang[:2] != 'en'" />
<t
t-set="is_not_english"
t-value="doc.partner_id.lang and doc.partner_id.lang[:2] != 'en'"
/>
<div class="page sepa_direct_debit">
<div class="row mt0">
<div class="col-12 text-center">
<img t-if="doc.company_id.logo" t-att-src="image_data_uri(doc.company_id.logo)" style="max-height: 45px;" class="float-left"/>
<img
t-if="doc.company_id.logo"
t-att-src="image_data_uri(doc.company_id.logo)"
style="max-height: 45px;"
class="float-left"
/>
<h4 t-if="doc.scheme != 'B2B'">
<strong>Sepa Direct Debit Mandate</strong>
<t t-if="is_not_english">
<br/><span class="native_lang native_lang_small" t-esc="'Sepa Direct Debit Mandate'"/>
<br />
<span
class="native_lang native_lang_small"
t-esc="'Sepa Direct Debit Mandate'"
/>
</t>
</h4>
<h4 t-if="doc.scheme == 'B2B'">
<strong>Sepa Business-To-Business Direct debit Mandate</strong>
<t t-if="is_not_english">
<br/><span class="native_lang native_lang_small" t-esc="'Sepa Business-To-Business Direct debit Mandate'"/>
<br />
<span
class="native_lang native_lang_small"
t-esc="'Sepa Business-To-Business Direct debit Mandate'"
/>
</t>
</h4>
</div>
@@ -28,71 +43,128 @@
<div class="col-12 text-right" style="font-size:8px;">
To be completed by the creditor
<t t-if="is_not_english">
<span class="native_lang" t-esc="'(To be completed by the creditor)'"/>
<span
class="native_lang"
t-esc="'(To be completed by the creditor)'"
/>
</t>
</div>
<div class="card-body">
<div class="col-12">
<em>Mandate Reference:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Mandate Reference)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Mandate Reference)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.unique_mandate_reference"/></div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.unique_mandate_reference" />
</div>
<div class="col-12">
<em>Identifier:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Identifier)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Identifier)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.company_id.sepa_creditor_identifier"/></div>
<div class="col-10 offset-2 under-line mt4">
<span
t-field="doc.company_id.sepa_creditor_identifier"
/>
</div>
<div class="col-12">
<em>Creditor's Name:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Creditor\'s Name)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Creditor\'s Name)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.company_id.partner_id.name"/></div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.company_id.partner_id.name" />
</div>
<div class="col-12">
<em>Address:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Address)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Address)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.company_id.partner_id.street"/></div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.company_id.partner_id.street" />
</div>
<div class="col-12">
<em>Postal Code - City - Town:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Postal Code - City - Town)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Postal Code - City - Town)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt8">
<span t-field="doc.company_id.partner_id.zip"/> -
<span t-field="doc.company_id.partner_id.city"/> -
<span t-field="doc.company_id.partner_id.state_id"/>
<span t-field="doc.company_id.partner_id.zip" /> -
<span t-field="doc.company_id.partner_id.city" /> -
<span t-field="doc.company_id.partner_id.state_id" />
</div>
<div class="col-12">
<em>Country:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Country)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Country)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.company_id.partner_id.country_id"/></div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.company_id.partner_id.country_id" />
</div>
</div>
</div>
</div>
</div>
<div class="row mt0">
<div class="col-12">
<p>By signing this mandate form, you authorise (A) <strong t-if="doc.scheme == 'B2B'"> <span t-field="doc.company_id.name"/> </strong>
<p>By signing this mandate form, you authorise (A) <strong
t-if="doc.scheme == 'B2B'"
>
<span t-field="doc.company_id.name" />
</strong>
to send instructions to your bank to debit your account and (B) your bank to
debit your account in accordance with the instructions from <strong t-if="doc.scheme == 'B2B'"> <span t-field="doc.company_id.name"/> </strong>.
debit your account in accordance with the instructions from <strong
t-if="doc.scheme == 'B2B'"
>
<span t-field="doc.company_id.name" />
</strong>.
<t t-if="is_not_english">
<br/>
<span class="native_lang" t-esc="'(By signing this mandate form, you authorise (A) '"/>
<t t-if="doc.scheme == 'B2B'"> <span class="native_lang" t-field="doc.company_id.name"/> </t>
<span class="native_lang" t-esc="' to send instructions to your bank to debit your account and (B) your bank to debit your account in accordance with the instructions from '"/>
<t t-if="doc.scheme == 'B2B'"> <span class="native_lang" t-field="doc.company_id.name"/> </t>.)
<br />
<span
class="native_lang"
t-esc="'(By signing this mandate form, you authorise (A) '"
/>
<t t-if="doc.scheme == 'B2B'">
<span
class="native_lang"
t-field="doc.company_id.name"
/>
</t>
<span
class="native_lang"
t-esc="' to send instructions to your bank to debit your account and (B) your bank to debit your account in accordance with the instructions from '"
/>
<t t-if="doc.scheme == 'B2B'">
<span
class="native_lang"
t-field="doc.company_id.name"
/>
</t>.)
</t>
</p>
</div>
@@ -105,7 +177,11 @@
been debited, but you are entitled to request your bank
not to debit your account up until the day on which the payment is due.
<t t-if="is_not_english">
<br/><span class="native_lang" t-esc="'(This mandate is only intended for business-to-business transactions. You are not entitled to a refund from your bank after your account has been debited, but you are entitled to request your bank not to debit your account up until the day on which the payment is due.)'"/>
<br />
<span
class="native_lang"
t-esc="'(This mandate is only intended for business-to-business transactions. You are not entitled to a refund from your bank after your account has been debited, but you are entitled to request your bank not to debit your account up until the day on which the payment is due.)'"
/>
</t>
</p>
<p t-if="doc.scheme != 'B2B'">
@@ -114,7 +190,11 @@
with your bank.
A refund must be claimed within 8 weeks starting from the date on which your account was debited.
<t t-if="is_not_english">
<br/><span class="native_lang" t-esc="'(As part of your rights, you are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited.)'"/>
<br />
<span
class="native_lang"
t-esc="'(As part of your rights, you are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited.)'"
/>
</t>
</p>
</div>
@@ -125,91 +205,143 @@
<div class="col-12 text-right" style="font-size:8px;">
To be completed by the debtor
<t t-if="is_not_english">
<span class="native_lang" t-esc="'(To be completed by the debtor)'"/>
<span
class="native_lang"
t-esc="'(To be completed by the debtor)'"
/>
</t>
</div>
<div class="card-body">
<div class="col-12">
<em>Debtor's Name:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Debtor\'s Name)'"/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.partner_id"/></div>
<div class="col-12">
<em>Address of the Debtor:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Address of the Debtor)'"/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.partner_id.street"/></div>
<div class="col-12">
<em>Postal Code - City - Town:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Postal Code - City - Town)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Debtor\'s Name)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.partner_id.zip"/> -
<span t-field="doc.partner_id.city"/> -
<span t-field="doc.partner_id.state_id"/>
<span t-field="doc.partner_id" />
</div>
<div class="col-12">
<em>Address of the Debtor:</em>
<t t-if="is_not_english">
<span
class="native_lang native_lang_small"
t-esc="'(Address of the Debtor)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.partner_id.street" />
</div>
<div class="col-12">
<em>Postal Code - City - Town:</em>
<t t-if="is_not_english">
<span
class="native_lang native_lang_small"
t-esc="'(Postal Code - City - Town)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.partner_id.zip" /> -
<span t-field="doc.partner_id.city" /> -
<span t-field="doc.partner_id.state_id" />
</div>
<div class="col-12">
<em>Country of the debtor:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Country of the debtor)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Country of the debtor)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.partner_id.country_id"/></div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.partner_id.country_id" />
</div>
<div class="col-12">
<em>Swift BIC (up to 8 or 11 characteres):</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Swift BIC, up to 8 or 11 characteres)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Swift BIC, up to 8 or 11 characteres)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.partner_bank_id.bank_bic"/></div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.partner_bank_id.bank_bic" />
</div>
<div class="col-12">
<em>Account Number - IBAN:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Account Number - IBAN)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Account Number - IBAN)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt4"><span t-field="doc.partner_bank_id.acc_number"/></div>
<div class="col-10 offset-2 under-line mt4">
<span t-field="doc.partner_bank_id.acc_number" />
</div>
<div class="col-12">
<em>Type of payment:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Type of payment)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Type of payment)'"
/>
</t>
</div>
<div class="col-10 offset-2 mt4">
<input type="checkbox" t-att-checked="doc.type=='recurrent' or None">
<input
type="checkbox"
t-att-checked="doc.type=='recurrent' or None"
>
Recurrent
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Recurrent)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Recurrent)'"
/>
</t>
</input>
-
<input type="checkbox" t-att-checked="doc.type=='oneoff' or None">
<input
type="checkbox"
t-att-checked="doc.type=='oneoff' or None"
>
One-Off
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(One-Off)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(One-Off)'"
/>
</t>
</input>
</div>
<div class="col-12">
<em>Date - Location:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Date - Location)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Date - Location)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt16"/>
<div class="col-10 offset-2 under-line mt16" />
<div class="col-12">
<em>Signature of the debtor:</em>
<t t-if="is_not_english">
<span class="native_lang native_lang_small" t-esc="'(Signature of the debtor)'"/>
<span
class="native_lang native_lang_small"
t-esc="'(Signature of the debtor)'"
/>
</t>
</div>
<div class="col-10 offset-2 under-line mt16"/>
<div class="col-10 offset-2 under-line mt16" />
</div>
</div>
</div>
@@ -219,7 +351,11 @@
<p t-if="doc.scheme != 'B2B'">
ALL GAPS ARE MANDATORY. ONCE THIS MANDATE HAS BEEN SIGNED MUST BE SENT TO CREDITOR FOR STORAGE.
<t t-if="is_not_english">
<br/><span class="native_lang" t-esc="'(ALL GAPS ARE MANDATORY. ONCE THIS MANDATE HAS BEEN SIGNED MUST BE SENT TO CREDITOR FOR STORAGE.)'"/>
<br />
<span
class="native_lang"
t-esc="'(ALL GAPS ARE MANDATORY. ONCE THIS MANDATE HAS BEEN SIGNED MUST BE SENT TO CREDITOR FOR STORAGE.)'"
/>
</t>
</p>
<p t-if="doc.scheme == 'B2B'">
@@ -227,22 +363,27 @@
NEVERTHELESS, THE BANK OF DEBTOR REQUIRES DEBTORS AUTHORIZATION BEFORE DEBITING B2B DIRECT DEBITS IN THE ACCOUNT.
THE DEBTOR WILL BE ABLE TO MANAGE THE MENTIONED AUTHORIZATION THROUGH THE MEANS PROVIDED BY HIS BANK.
<t t-if="is_not_english">
<br/><span class="native_lang" t-esc="'(ALL GAPS ARE MANDATORY. ONCE THIS MANDATE HAS BEEN SIGNED MUST BE SENT TO CREDITOR FOR STORAGE. NEVERTHELESS, THE BANK OF DEBTOR REQUIRES DEBTORS AUTHORIZATION BEFORE DEBITING B2B DIRECT DEBITS IN THE ACCOUNT. THE DEBTOR WILL BE ABLE TO MANAGE THE MENTIONED AUTHORIZATION THROUGH THE MEANS PROVIDED BY HIS BANK.)'"/>
<br />
<span
class="native_lang"
t-esc="'(ALL GAPS ARE MANDATORY. ONCE THIS MANDATE HAS BEEN SIGNED MUST BE SENT TO CREDITOR FOR STORAGE. NEVERTHELESS, THE BANK OF DEBTOR REQUIRES DEBTORS AUTHORIZATION BEFORE DEBITING B2B DIRECT DEBITS IN THE ACCOUNT. THE DEBTOR WILL BE ABLE TO MANAGE THE MENTIONED AUTHORIZATION THROUGH THE MEANS PROVIDED BY HIS BANK.)'"
/>
</t>
</p>
</div>
</div>
</div>
</template>
<template id="sepa_direct_debit_mandate">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.basic_layout">
<t t-call="account_banking_sepa_direct_debit.sepa_direct_debit_mandate_document" t-lang="doc.partner_id.lang"/>
<t
t-call="account_banking_sepa_direct_debit.sepa_direct_debit_mandate_document"
t-lang="doc.partner_id.lang"
/>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -1,24 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_account_config_settings" model="ir.ui.view">
<field name="name">sepa_direct_debit.account_config_settings.form
</field>
<field name="model">res.config.settings</field>
<field name="inherit_id"
ref="account_banking_pain_base.view_account_config_settings"/>
<field
name="inherit_id"
ref="account_banking_pain_base.view_account_config_settings"
/>
<field name="arch" type="xml">
<xpath expr="//div[@id='pain']/div/div/div" position="inside">
<div class="row mt16">
<label for="sepa_creditor_identifier"
class="col-md-3 o_light_label"/>
<field name="sepa_creditor_identifier"
placeholder="Write the ICS of your company"/>
<label
for="sepa_creditor_identifier"
class="col-md-3 o_light_label"
/>
<field
name="sepa_creditor_identifier"
placeholder="Write the ICS of your company"
/>
</div>
</xpath>
</field>
</record>
</odoo>