Improve computation of sepa on account.payment.order: check IBAN is in SEPA zone

Update move line generation to get transfer account from bank journal
Update payment mode configuration accordingly (3 fields removed)
Several improvements in payment order tree and form view
This commit is contained in:
Alexis de Lattre
2021-06-09 00:39:32 +02:00
parent dbd48acc90
commit cbe1eed5ac
12 changed files with 203 additions and 149 deletions

View File

@@ -58,17 +58,66 @@ class AccountPaymentOrder(models.Model):
"transfer of the SEPA XML file.",
)
@api.model
def _sepa_iban_prefix_list(self):
# List of IBAN prefixes (not country codes !)
# Source: https://www.europeanpaymentscouncil.eu/sites/default/files/kb/file/2020-10/EPC409-09%20EPC%20List%20of%20SEPA%20Scheme%20Countries%20v3.0_1.pdf # noqa: B950
# Some countries use IBAN but are not part of the SEPA zone
# example: Turkey, Madagascar, Tunisia, etc.
return [
"BE",
"BG",
"ES",
"HR",
"CY",
"CZ",
"DK",
"EE",
"FI",
"FR",
"DE",
"GI",
"GR",
"GB",
"HU",
"IS",
"IE",
"IT",
"LV",
"LI",
"LT",
"LU",
"PT",
"MT",
"MC",
"NL",
"NO",
"PL",
"RO",
"SM",
"SK",
"SI",
"SE",
"CH",
"VA",
]
@api.depends(
"company_partner_bank_id.acc_type",
"company_partner_bank_id.sanitized_acc_number",
"payment_line_ids.currency_id",
"payment_line_ids.partner_bank_id.acc_type",
"payment_line_ids.partner_bank_id.sanitized_acc_number",
)
def _compute_sepa(self):
eur = self.env.ref("base.EUR")
sepa_list = self._sepa_iban_prefix_list()
for order in self:
sepa = True
if order.company_partner_bank_id.acc_type != "iban":
sepa = False
if order.company_partner_bank_id and order.company_partner_bank_id.sanitized_acc_number[:2] not in sepa_list:
sepa = False
for pline in order.payment_line_ids:
if pline.currency_id != eur:
sepa = False
@@ -76,6 +125,9 @@ class AccountPaymentOrder(models.Model):
if pline.partner_bank_id.acc_type != "iban":
sepa = False
break
if pline.partner_bank_id and pline.partner_bank_id.sanitized_acc_number[:2] not in sepa_list:
sepa = False
break
sepa = order.compute_sepa_final_hook(sepa)
self.sepa = sepa

View File

@@ -20,4 +20,18 @@
</field>
</field>
</record>
<record id="account_payment_line_tree" model="ir.ui.view">
<field name="name">pain.base.account.payment.tree</field>
<field name="model">account.payment.line</field>
<field
name="inherit_id"
ref="account_payment_order.account_payment_line_tree"
/>
<field name="arch" type="xml">
<field name="communication" position="after">
<field name="priority" optional="hide" />
</field>
</field>
</record>
</odoo>

View File

@@ -21,5 +21,19 @@
/>
</field>
</field>
</record>
</record>
<record id="account_payment_order_tree" model="ir.ui.view">
<field name="name">pain.base.account.payment.order.tree</field>
<field name="model">account.payment.order</field>
<field
name="inherit_id"
ref="account_payment_order.account_payment_order_tree"
/>
<field name="arch" type="xml">
<field name="description" position="after">
<field name="sepa" optional="hide" />
</field>
</field>
</record>
</odoo>

View File

@@ -17,4 +17,15 @@
</field>
</field>
</record>
<record id="bank_payment_line_tree" model="ir.ui.view">
<field name="name">pain.base.bank.payment.line.tree</field>
<field name="model">bank.payment.line</field>
<field name="inherit_id" ref="account_payment_order.bank_payment_line_tree" />
<field name="arch" type="xml">
<field name="communication" position="after">
<field name="priority" optional="hide" />
</field>
</field>
</record>
</odoo>

View File

@@ -68,7 +68,7 @@ class AccountPaymentLine(models.Model):
)
date = fields.Date(string="Payment Date")
communication = fields.Char(
required=True, help="Label of the payment that will be seen by the destinee"
required=False, help="Label of the payment that will be seen by the destinee"
)
communication_type = fields.Selection(
selection=[("normal", "Free")], required=True, default="normal"

View File

@@ -75,27 +75,6 @@ class AccountPaymentMode(models.Model):
generate_move = fields.Boolean(
string="Generate Accounting Entries On File Upload", default=True
)
offsetting_account = fields.Selection(
selection=[
("bank_account", "Bank Account"),
("transfer_account", "Transfer Account"),
],
default="bank_account",
)
transfer_account_id = fields.Many2one(
comodel_name="account.account",
domain=[("reconcile", "=", True)],
help="Pay off lines in 'file uploaded' payment orders with a move on "
"this account. You can only select accounts "
"that are marked for reconciliation",
check_company=True,
)
transfer_journal_id = fields.Many2one(
comodel_name="account.journal",
help="Journal to write payment entries when confirming "
"payment/debit orders of this mode",
check_company=True,
)
move_option = fields.Selection(
selection=[
("date", "One move per payment date"),
@@ -105,47 +84,14 @@ class AccountPaymentMode(models.Model):
)
post_move = fields.Boolean(default=True)
@api.constrains(
"generate_move",
"offsetting_account",
"transfer_account_id",
"transfer_journal_id",
"move_option",
)
@api.constrains("generate_move", "move_option")
def transfer_move_constrains(self):
for mode in self:
if mode.generate_move:
if not mode.offsetting_account:
raise ValidationError(
_(
"On the payment mode '%s', you must select an "
"option for the 'Offsetting Account' parameter"
)
% mode.name
)
elif mode.offsetting_account == "transfer_account":
if not mode.transfer_account_id:
if mode.generate_move and not mode.move_option:
raise ValidationError(
_(
"On the payment mode '%s', you must "
"select a value for the 'Transfer Account'."
)
% mode.name
)
if not mode.transfer_journal_id:
raise ValidationError(
_(
"On the payment mode '%s', you must "
"select a value for the 'Transfer Journal'."
)
% mode.name
)
if not mode.move_option:
raise ValidationError(
_(
"On the payment mode '%s', you must "
"choose an option for the 'Move Option' "
"parameter."
"choose an option for the 'Move Option' parameter."
)
% mode.name
)
@@ -175,16 +121,6 @@ class AccountPaymentMode(models.Model):
def generate_move_change(self):
if self.generate_move:
# default values
self.offsetting_account = "bank_account"
self.move_option = "date"
else:
self.offsetting_account = False
self.transfer_account_id = False
self.transfer_journal_id = False
self.move_option = False
@api.onchange("offsetting_account")
def offsetting_account_change(self):
if self.offsetting_account == "bank_account":
self.transfer_account_id = False
self.transfer_journal_id = False

View File

@@ -118,14 +118,14 @@ class AccountPaymentOrder(models.Model):
payment_line_ids = fields.One2many(
comodel_name="account.payment.line",
inverse_name="order_id",
string="Transaction Lines",
string="Transactions",
readonly=True,
states={"draft": [("readonly", False)]},
)
bank_line_ids = fields.One2many(
comodel_name="bank.payment.line",
inverse_name="order_id",
string="Bank Payment Lines",
string="Bank Transactions",
readonly=True,
help="The bank payment lines are used to generate the payment file. "
"They are automatically created from transaction lines upon "
@@ -138,7 +138,7 @@ class AccountPaymentOrder(models.Model):
compute="_compute_total", store=True, currency_field="company_currency_id"
)
bank_line_count = fields.Integer(
compute="_compute_bank_line_count", string="Number of Bank Lines"
compute="_compute_bank_line_count", string="Number of Bank Transactions"
)
move_ids = fields.One2many(
comodel_name="account.move",
@@ -146,6 +146,9 @@ class AccountPaymentOrder(models.Model):
string="Journal Entries",
readonly=True,
)
move_count = fields.Integer(
compute="_compute_move_count", string="Number of Journal Entries"
)
description = fields.Char()
@api.depends("payment_mode_id")
@@ -210,6 +213,19 @@ class AccountPaymentOrder(models.Model):
for order in self:
order.bank_line_count = len(order.bank_line_ids)
@api.depends("move_ids")
def _compute_move_count(self):
rg_res = self.env["account.move"].read_group(
[("payment_order_id", "in", self.ids)],
["payment_order_id"],
["payment_order_id"],
)
mapped_data = {
x["payment_order_id"][0]: x["payment_order_id_count"] for x in rg_res
}
for order in self:
order.move_count = mapped_data.get(order.id, 0)
@api.model
def create(self, vals):
if vals.get("name", "New") == "New":
@@ -420,27 +436,19 @@ class AccountPaymentOrder(models.Model):
return True
def _prepare_move(self, bank_lines=None):
move_date = False
if self.payment_type == "outbound":
ref = _("Payment order %s") % self.name
else:
ref = _("Debit order %s") % self.name
if bank_lines and len(bank_lines) == 1:
ref += " - " + bank_lines.name
if self.payment_mode_id.offsetting_account == "bank_account":
journal_id = self.journal_id.id
if bank_lines:
move_date = bank_lines[0].date
elif self.payment_mode_id.offsetting_account == "transfer_account":
journal_id = self.payment_mode_id.transfer_journal_id.id
vals = {
"journal_id": journal_id,
"date": bank_lines[0].date,
"journal_id": self.journal_id.id,
"ref": ref,
"payment_order_id": self.id,
"line_ids": [],
}
if move_date:
vals.update({"date": move_date})
total_company_currency = total_payment_currency = 0
for bline in bank_lines:
total_company_currency += bline.amount_company_currency
@@ -458,18 +466,10 @@ class AccountPaymentOrder(models.Model):
):
vals = {}
if self.payment_type == "outbound":
name = _("Payment order %s") % self.name
account_id = self.journal_id.payment_credit_account_id.id
else:
name = _("Debit order %s") % self.name
if self.payment_mode_id.offsetting_account == "bank_account":
vals.update({"date": bank_lines[0].date})
else:
vals.update({"date_maturity": bank_lines[0].date})
account_id = self.journal_id.payment_debit_account_id.id
if self.payment_mode_id.offsetting_account == "bank_account":
account_id = self.journal_id.default_account_id.id
elif self.payment_mode_id.offsetting_account == "transfer_account":
account_id = self.payment_mode_id.transfer_account_id.id
partner_id = False
for index, bank_line in enumerate(bank_lines):
if index == 0:
@@ -480,7 +480,6 @@ class AccountPaymentOrder(models.Model):
break
vals.update(
{
"name": name,
"partner_id": partner_id,
"account_id": account_id,
"credit": (
@@ -586,10 +585,19 @@ class AccountPaymentOrder(models.Model):
def action_move_journal_line(self):
self.ensure_one()
action = self.env.ref("account.action_move_journal_line")
action_dict = action.read()[0]
action_dict["domain"] = [("id", "in", self.move_ids.ids)]
action = self.env.ref("account.action_move_journal_line").sudo().read()[0]
if self.move_count == 1:
action.update(
{
"view_mode": "form,tree,kanban",
"views": False,
"view_id": False,
"res_id": self.move_ids[0].id,
}
)
else:
action["domain"] = [("id", "in", self.move_ids.ids)]
ctx = self.env.context.copy()
ctx.update({"search_default_misc_filter": 0})
action_dict["context"] = ctx
return action_dict
action["context"] = ctx
return action

View File

@@ -42,7 +42,7 @@
<field name="payment_mode_id" position="after">
<field name="payment_order_ok" invisible="1" />
</field>
<field name="ref" position="after">
<field name="payment_reference" position="before">
<field
name="reference_type"
required="1"
@@ -51,7 +51,7 @@
/>
</field>
</field>
</record>
</record>
<record
id="account_invoice_create_account_payment_line_action"
model="ir.actions.act_window"
@@ -62,4 +62,21 @@
<field name="target">new</field>
<field name="binding_model_id" ref="account.model_account_move" />
</record>
<record id="view_invoice_tree" model="ir.ui.view">
<field name="name">account_payment_order.view_invoice_tree</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_invoice_tree" />
<field name="arch" type="xml">
<button name="action_register_payment" position="after">
<button
name="%(account_invoice_create_account_payment_line_action)d"
type="action"
string="Add to Payment/Debit Order"
groups="account_payment_order.group_account_payment"
invisible="context.get('default_move_type') not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')"
/>
</button>
</field>
</record>
</odoo>

View File

@@ -59,12 +59,12 @@
<field name="partner_id" />
<field name="communication" />
<field name="partner_bank_id" />
<field name="move_line_id" invisible="1" />
<field name="ml_maturity_date" />
<field name="move_line_id" optional="hide" />
<field name="ml_maturity_date" optional="show" />
<field name="date" />
<field name="amount_currency" string="Amount" />
<field name="currency_id" />
<field name="name" />
<field name="currency_id" invisible="1" />
<field name="name" optional="show" />
<field
name="amount_company_currency"
sum="Total in Company Currency"

View File

@@ -38,21 +38,6 @@
attrs="{'invisible': [('payment_order_ok', '=', False)]}"
>
<field name="generate_move" />
<field
name="offsetting_account"
widget="radio"
attrs="{'required': [('generate_move', '=', True)], 'invisible': [('generate_move', '=', False)]}"
/>
<field
name="transfer_account_id"
attrs="{'invisible': [('offsetting_account', '!=', 'transfer_account')], 'required': [('offsetting_account', '=', 'transfer_account')]}"
context="{'default_reconcile': True, 'default_company_id': company_id}"
/>
<!-- We can't put a default vue to user_type_id... -->
<field
name="transfer_journal_id"
attrs="{'invisible': [('offsetting_account', '!=', 'transfer_account')], 'required': [('offsetting_account', '=', 'transfer_account')]}"
/>
<field
name="move_option"
attrs="{'invisible': [('generate_move', '=', False)], 'required': [('generate_move', '=', True)]}"

View File

@@ -52,24 +52,26 @@
states="uploaded"
string="Cancel Payments"
/>
<field name="state" widget="statusbar" />
<field
name="state"
widget="statusbar"
statusbar_visible="draft,open,generated,uploaded,done"
/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button
class="oe_stat_button"
name="action_bank_payment_line"
string="Bank Payment Lines"
type="object"
icon="fa-bars"
/>
<button
class="oe_stat_button"
name="action_move_journal_line"
string="Transfer Journal Entries"
type="object"
icon="fa-bars"
>
<field
string="Journal Entries"
name="move_count"
widget="statinfo"
/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only" />
@@ -82,12 +84,10 @@
<field
name="payment_mode_id"
domain="[('payment_order_ok', '=', True), ('payment_type', '=', payment_type)]"
widget="selection"
/>
<field name="allowed_journal_ids" invisible="1" />
<field
name="journal_id"
widget="selection"
domain="[('id', 'in', allowed_journal_ids)]"
/>
<field name="bank_account_link" invisible="1" />
@@ -97,7 +97,10 @@
groups="base.group_multi_company"
/>
<field name="payment_type" invisible="0" />
<field name="bank_line_count" />
<field
name="bank_line_count"
attrs="{'invisible': [('state', 'in', ('draft', 'cancel'))]}"
/>
</group>
<group name="head-right">
<field name="date_prefered" />
@@ -118,6 +121,13 @@
context="{'default_payment_type': payment_type}"
/>
</page>
<page
name="bank-lines"
string="Bank Transactions"
attrs="{'invisible': [('state', 'in', ('draft', 'cancel'))]}"
>
<field name="bank_line_ids" edit="0" create="0" />
</page>
</notebook>
</sheet>
<div class="oe_chatter">
@@ -131,22 +141,29 @@
<field name="name">account.payment.order.tree</field>
<field name="model">account.payment.order</field>
<field name="arch" type="xml">
<tree
string="Payment Orders"
decoration-info="state=='draft'"
decoration-success="state=='generated'"
decoration-danger="state=='open'"
decoration-muted="state=='cancel'"
>
<field name="name" />
<tree string="Payment Orders">
<field name="name" decoration-bf="1" />
<field name="payment_mode_id" />
<field name="journal_id" />
<field name="company_id" groups="base.group_multi_company" />
<field name="date_uploaded" />
<field name="description" />
<field name="description" optional="show" />
<field
name="bank_line_count"
optional="hide"
string="Bank Transactions"
/>
<field name="total_company_currency" sum="Total Company Currency" />
<field name="company_currency_id" invisible="1" />
<field name="state" />
<field
name="state"
decoration-info="state == 'draft'"
decoration-success="state in ('uploaded', 'done')"
decoration-warning="state == 'open'"
decoration-danger="state == 'generated'"
decoration-muted="state == 'cancel'"
widget="badge"
/>
</tree>
</field>
</record>

View File

@@ -49,8 +49,8 @@
<field name="partner_bank_id" />
<field name="date" />
<field name="amount_currency" sum="Total Amount" />
<field name="currency_id" />
<field name="name" />
<field name="currency_id" invisible="1" />
<field name="name" optional="show" />
<field
name="company_id"
groups="base.group_multi_company"