diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 4a682a6db1f..87ac3b1e02d 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -321,6 +321,7 @@ class AccountMove(models.Model):
"otherwise a Partner bank account number.",
check_company=True,
tracking=True,
+ ondelete='restrict',
)
fiscal_position_id = fields.Many2one(
'account.fiscal.position',
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 6183503fa37..42c8cec74bb 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -30,7 +30,9 @@ class AccountPayment(models.Model):
readonly=False, store=True, tracking=True,
compute='_compute_partner_bank_id',
domain="[('id', 'in', available_partner_bank_ids)]",
- check_company=True)
+ check_company=True,
+ ondelete='restrict',
+ )
is_internal_transfer = fields.Boolean(string="Internal Transfer",
readonly=False, store=True,
tracking=True,
@@ -528,6 +530,7 @@ class AccountPayment(models.Model):
for pay in self:
if pay.state in ('draft', 'posted') \
and pay.partner_bank_id \
+ and pay.partner_bank_id.allow_out_payment \
and pay.payment_method_line_id.code == 'manual' \
and pay.payment_type == 'outbound' \
and pay.currency_id:
@@ -921,6 +924,11 @@ class AccountPayment(models.Model):
def action_post(self):
''' draft -> posted '''
+ # Do not allow to post if the account is required but not trusted
+ for payment in self:
+ if payment.require_partner_bank_account and not payment.partner_bank_id.allow_out_payment:
+ raise UserError(_('To record payments with %s, the recipient bank account must be manually validated. You should go on the partner bank account in order to validate it.', self.payment_method_line_id.name))
+
self.move_id._post(soft=False)
self.filtered(
diff --git a/addons/account/models/res_partner_bank.py b/addons/account/models/res_partner_bank.py
index 09b0683bc7d..701bc45d455 100644
--- a/addons/account/models/res_partner_bank.py
+++ b/addons/account/models/res_partner_bank.py
@@ -16,6 +16,21 @@ class ResPartnerBank(models.Model):
journal_id = fields.One2many(
'account.journal', 'bank_account_id', domain=[('type', '=', 'bank')], string='Account Journal', readonly=True,
help="The accounting journal corresponding to this bank account.")
+ has_iban_warning = fields.Boolean(
+ compute='_compute_display_account_warning',
+ help='Technical field used to display a warning if the IBAN country is different than the holder country.',
+ store=True,
+ )
+ partner_country_name = fields.Char(related='partner_id.country_id.name')
+ has_money_transfer_warning = fields.Boolean(
+ compute='_compute_display_account_warning',
+ help='Technical field used to display a warning if the account is a transfer service account.',
+ store=True,
+ )
+ money_transfer_service = fields.Char(compute='_compute_money_transfer_service_name')
+ partner_supplier_rank = fields.Integer(related='partner_id.supplier_rank')
+ partner_customer_rank = fields.Integer(related='partner_id.customer_rank')
+ related_moves = fields.One2many('account.move', inverse_name='partner_bank_id')
# Add tracking to the base fields
bank_id = fields.Many2one(tracking=True)
@@ -23,7 +38,13 @@ class ResPartnerBank(models.Model):
acc_number = fields.Char(tracking=True)
acc_holder_name = fields.Char(tracking=True)
partner_id = fields.Many2one(tracking=True)
- allow_out_payment = fields.Boolean(tracking=True)
+ user_has_group_validate_bank_account = fields.Boolean(compute='_compute_user_has_group_validate_bank_account')
+ allow_out_payment = fields.Boolean(
+ tracking=True,
+ help='Sending fake invoices with a fraudulent account number is a common phishing practice. '
+ 'To protect yourself, always verify new bank account numbers, preferably by calling the vendor, as phishing '
+ 'usually happens when their emails are compromised. Once verified, you can activate the ability to send money.'
+ )
currency_id = fields.Many2one(tracking=True)
@api.constrains('journal_id')
@@ -32,6 +53,49 @@ class ResPartnerBank(models.Model):
if len(bank.journal_id) > 1:
raise ValidationError(_('A bank account can belong to only one journal.'))
+ @api.constrains('allow_out_payment')
+ def _check_allow_out_payment(self):
+ """ Block enabling the setting, but it can be set to false without the group. (For example, at creation) """
+ for bank in self:
+ if bank.allow_out_payment:
+ if not self.user_has_groups('account.group_validate_bank_account'):
+ raise ValidationError(_('You do not have the right to trust or un-trust a bank account.'))
+
+ @api.depends('partner_id.country_id', 'sanitized_acc_number', 'allow_out_payment', 'acc_type')
+ def _compute_display_account_warning(self):
+ for bank in self:
+ if bank.allow_out_payment or not bank.sanitized_acc_number or bank.acc_type != 'iban':
+ bank.has_iban_warning = False
+ bank.has_money_transfer_warning = False
+ continue
+ bank_country = bank.sanitized_acc_number[:2]
+ bank.has_iban_warning = bank.partner_id.country_id and bank_country != bank.partner_id.country_id.code
+
+ bank_institution_code = bank.sanitized_acc_number[4:7]
+ bank.has_money_transfer_warning = bank_institution_code in bank._get_money_transfer_services()
+
+ @api.depends('sanitized_acc_number', 'allow_out_payment')
+ def _compute_money_transfer_service_name(self):
+ for bank in self:
+ if bank.sanitized_acc_number:
+ bank_institution_code = bank.sanitized_acc_number[4:7]
+ bank.money_transfer_service = bank._get_money_transfer_services().get(bank_institution_code, False)
+ else:
+ bank.money_transfer_service = False
+
+ def _get_money_transfer_services(self):
+ return {
+ '967': 'Wise',
+ '977': 'Paynovate',
+ '974': 'PPS EU SA',
+ }
+
+ @api.depends('acc_number')
+ def _compute_user_has_group_validate_bank_account(self):
+ user_has_group_validate_bank_account = self.user_has_groups('account.group_validate_bank_account')
+ for bank in self:
+ bank.user_has_group_validate_bank_account = user_has_group_validate_bank_account
+
def _build_qr_code_vals(self, amount, free_communication, structured_communication, currency, debtor_partner, qr_method=None, silent_errors=True):
""" Returns the QR-code vals needed to generate the QR-code report link to pay this account with the given parameters,
or None if no QR-code could be generated.
@@ -215,3 +279,16 @@ class ResPartnerBank(models.Model):
msg = _("Bank Account %s with number %s deleted", account._get_html_link(title=f"#{account.id}"), account.acc_number)
account.partner_id._message_log(body=msg)
return super().unlink()
+
+ def name_get(self):
+ res = super().name_get()
+ if self.env.context.get('display_account_trust'):
+ res = []
+ for acc in self:
+ trusted_label = _('trusted') if acc.allow_out_payment else _('untrusted')
+ if acc.bank_id:
+ name = '{} - {} ({})'.format(acc.acc_number, acc.bank_id.name, trusted_label)
+ else:
+ name = '{} ({})'.format(acc.acc_number, trusted_label)
+ res.append((acc.id, name))
+ return res
diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml
index 0227f9ac26a..035b468a19d 100644
--- a/addons/account/security/account_security.xml
+++ b/addons/account/security/account_security.xml
@@ -82,6 +82,12 @@
Create a credit note
@@ -1513,7 +1513,7 @@
Create a vendor bill
@@ -1565,7 +1565,7 @@
Register a new purchase receipt
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index db8d26da13e..096c6ef8694 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -282,14 +282,14 @@
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
+ Create a new bank account
+
+ Check why.
+
Early Payment Discount applied.