# 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