149 lines
6.5 KiB
Python
149 lines
6.5 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.exceptions import RedirectWarning, UserError
|
|
|
|
import base64
|
|
from datetime import datetime
|
|
|
|
|
|
class AccountBatchPayment(models.Model):
|
|
_inherit = 'account.batch.payment'
|
|
|
|
iso20022_batch_booking = fields.Boolean(
|
|
string="SCT Batch Booking",
|
|
default=False,
|
|
help="Ask the bank to group the bank transactions corresponding to these payments into a single transaction.",
|
|
)
|
|
|
|
payment_method_is_iso20022 = fields.Boolean(related='payment_method_id.is_iso20022')
|
|
|
|
def _get_methods_generating_files(self):
|
|
rslt = super()._get_methods_generating_files()
|
|
rslt.extend(['sepa_ct', 'iso20022', 'iso20022_se', 'iso20022_ch', 'iso20022_us'])
|
|
return rslt
|
|
|
|
def validate_batch(self):
|
|
self.ensure_one()
|
|
if self.payment_method_id.code == 'iso20022_se' and not self.env.company.iso20022_orgid_id:
|
|
action = self.env.ref('account.action_account_config')
|
|
raise RedirectWarning(
|
|
message=_(
|
|
"The Name Identification and Issuer details are required to proceed. "
|
|
"Please configure them in the settings.",
|
|
),
|
|
action=action.id,
|
|
button_text=_('Go to settings'),
|
|
)
|
|
|
|
if self.payment_method_code and self.payment_method_code == 'sepa_ct':
|
|
if self.journal_id.bank_account_id.acc_type != 'iban':
|
|
raise RedirectWarning(
|
|
_(
|
|
"The bank account %(account)s, of journal '%(journal)s', is not of type IBAN.\nA valid IBAN account is required to use SEPA features.",
|
|
account=self.journal_id.bank_account_id.acc_number,
|
|
journal=self.journal_id.name,
|
|
),
|
|
{
|
|
'view_mode': 'form',
|
|
'res_model': 'account.journal',
|
|
'type': 'ir.actions.act_window',
|
|
'res_id': self.journal_id.id,
|
|
'views': [[False, 'form']],
|
|
},
|
|
_("Configure Journal"),
|
|
)
|
|
if no_iban_payments := self.payment_ids.filtered(lambda x: x.partner_bank_id.acc_type != 'iban'):
|
|
raise RedirectWarning(
|
|
_("The customer bank account set on some payments does not have an IBAN number. This is required for SEPA."),
|
|
no_iban_payments._get_records_action(name=_("Payments without IBAN")),
|
|
_("View Payments"),
|
|
)
|
|
if no_eur_payments := self.payment_ids.filtered(lambda x: x.currency_id.name != 'EUR'):
|
|
raise RedirectWarning(
|
|
_("SEPA only accepts payments in EUR. Some payments are using another currency."),
|
|
no_eur_payments._get_records_action(name=_("Non-EUR Payments")),
|
|
_("View Payments"),
|
|
)
|
|
return super().validate_batch()
|
|
|
|
def check_payments_for_errors(self):
|
|
rslt = super().check_payments_for_errors()
|
|
|
|
if self.payment_method_code not in ['sepa_ct', 'iso20022', 'iso20022_se', 'iso20022_ch', 'iso20022_us']:
|
|
return rslt
|
|
|
|
no_bank_acc_payments = self.env['account.payment']
|
|
too_big_payments = self.env['account.payment']
|
|
|
|
amount_upper_bounds = {
|
|
'sepa_ct': 999999999.99,
|
|
'iso20022_ch': 9999999999.99,
|
|
}
|
|
for payment in self.payment_ids:
|
|
if not payment.partner_bank_id:
|
|
no_bank_acc_payments += payment
|
|
|
|
amount_upper_bound = amount_upper_bounds.get(payment.payment_method_id.code, None)
|
|
if amount_upper_bound and payment.currency_id.compare_amounts(payment.amount, amount_upper_bound) > 0:
|
|
too_big_payments += payment
|
|
|
|
if no_bank_acc_payments:
|
|
rslt.append({'title': _("Some payments have no recipient bank account set."), 'records': no_bank_acc_payments})
|
|
|
|
if too_big_payments:
|
|
rslt.append({
|
|
'title': _("Some payments are above the maximum amount allowed."),
|
|
'records': too_big_payments,
|
|
})
|
|
|
|
return rslt
|
|
|
|
def _get_export_file_name_prefix(self):
|
|
return "SCT-" if self.payment_method_code == 'sepa_ct' else "PAIN-"
|
|
|
|
def _generate_export_file(self):
|
|
if self.payment_method_code in ['sepa_ct', 'iso20022', 'iso20022_se', 'iso20022_ch', 'iso20022_us']:
|
|
payments = self.payment_ids.sorted(key=lambda r: r.id)
|
|
payment_dicts = self._generate_payment_template(payments)
|
|
xml_doc = self.with_context(
|
|
bban=self.payment_method_code == 'iso20022_se' and len({'bban_se', 'plusgiro', 'bankgiro', *self.payment_ids.mapped('partner_bank_id.acc_type')}) == 3
|
|
).journal_id.create_iso20022_credit_transfer(
|
|
payment_dicts,
|
|
self.payment_method_code,
|
|
batch_booking=self.iso20022_batch_booking,
|
|
)
|
|
prefix = self._get_export_file_name_prefix()
|
|
return {
|
|
'file': base64.encodebytes(xml_doc),
|
|
'filename': "%s%s-%s.xml" % (prefix, self.journal_id.code, datetime.now().strftime('%Y%m%d%H%M%S')),
|
|
}
|
|
return super()._generate_export_file()
|
|
|
|
def _get_payment_vals(self, payment):
|
|
return {
|
|
'id': payment.id,
|
|
'name': str(payment.id) + '-' + (payment.memo or self.journal_id.code + '-' + str(fields.Date.today())),
|
|
'payment_date': payment.date,
|
|
'amount': payment.amount,
|
|
'journal_id': self.journal_id.id,
|
|
'currency_id': payment.currency_id.id,
|
|
'payment_type': payment.payment_type,
|
|
'memo': payment.memo,
|
|
'partner_id': payment.partner_id.id,
|
|
'partner_bank_id': payment.partner_bank_id.id,
|
|
'partner_country_code': payment.partner_id.country_id.code,
|
|
'end_to_end_uuid': payment.end_to_end_uuid,
|
|
'iso20022_uetr': payment.iso20022_uetr,
|
|
'iso20022_charge_bearer': payment.iso20022_charge_bearer,
|
|
'iso20022_priority': payment.iso20022_priority,
|
|
}
|
|
|
|
def _generate_payment_template(self, payments):
|
|
payment_dicts = []
|
|
for payment in payments:
|
|
if not payment.partner_bank_id:
|
|
raise UserError(_('A bank account is not defined.'))
|
|
payment_dicts.append(self._get_payment_vals(payment))
|
|
return payment_dicts
|