chore: import enterprise addons 19.0 bulk snapshot

This commit is contained in:
1b-agent committed 2026-09-16 10:13:08 +00:00
commit ca4af030d3
39951 files changed
+15570005

No files matched your search

@@ -0,0 +1,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_batch_payment
from . import account_journal
from . import account_payment
from . import account_payment_method
from . import l10n_ca_cpa005_transaction_code
from . import res_company
from . import res_partner_bank
@@ -0,0 +1,192 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo import fields, models, _
from odoo.exceptions import ValidationError, RedirectWarning
from odoo.tools import remove_accents
class AccountBatchPayment(models.Model):
_inherit = "account.batch.payment"
l10n_ca_cpa005_file_creation_number = fields.Char(
string="File Creation Number used in Canadian EFT",
copy=False,
help="Leave blank to auto-populate with the next number in the sequence. The FCN is a 4-digit sequence from 0001 "
"to 9999. This will be used by your bank to identify the file. You can set a value here to override the FCN "
"sequence just for this payment.",
)
def _l10n_ca_cpa005_pre_validate(self):
journal = self.journal_id
journal_errors = []
payments_without_tx_code = self.env["account.payment"]
if not journal.company_id.l10n_ca_cpa005_short_name:
raise RedirectWarning(
_("Please set a Canadian EFT short company name on the %s company.", journal.company_id.display_name),
journal.company_id._get_records_action(),
_("Go to company"),
)
if not journal.l10n_ca_cpa005_originator_id:
journal_errors.append(_("- Please set an originator ID on the %(journal)s journal."))
if not journal.l10n_ca_cpa005_destination_data_center:
journal_errors.append(_("- Please set a destination data center on the %(journal)s journal."))
if not journal.bank_account_id:
journal_errors.append(_("- Please set an account number on the %(journal)s journal."))
elif not journal.bank_account_id.l10n_ca_financial_institution_number:
raise RedirectWarning(
_(
"Please set a Financial Institution ID Number on the %(account)s bank account of %(partner)s.",
account=journal.bank_account_id.display_name,
partner=journal.bank_account_id.partner_id.display_name,
),
journal.bank_account_id._get_records_action(),
_("Go to bank account"),
)
if journal_errors:
raise RedirectWarning(
_(
"Please fix the following issue(s) on the %(journal)s journal:\n%(errors)s",
journal=journal.display_name,
errors="\n".join(journal_errors) % {"journal": journal.display_name},
),
journal._get_records_action(),
_("Go to journal"),
)
for payment in self.payment_ids:
if not payment.l10n_ca_cpa005_transaction_code_id:
payments_without_tx_code |= payment
if payments_without_tx_code:
raise RedirectWarning(
_(
"Please set an EFT/CPA transaction code on the following payment(s):\n%s",
"\n".join(f"- {payment.display_name}" for payment in payments_without_tx_code),
),
payments_without_tx_code._get_records_action(name=_("Payments missing EFT/CPA transaction codes")),
_("Go to payments"),
)
def _l10n_ca_cpa005_get_currency(self):
batch_currency = self.payment_ids.mapped("currency_id")
if len(batch_currency) != 1:
raise ValidationError(
_(
"A Canadian EFT file can not contain multiple currencies (%s).",
", ".join(batch_currency.mapped("display_name")),
)
)
if batch_currency not in self.env.ref("base.CAD") | self.env.ref("base.USD"):
raise ValidationError(
_(
"A Canadian EFT file can not contain %s. It can contain either exclusively payments "
"in Canadian dollars or exclusively payments in United States dollars.",
batch_currency.display_name,
)
)
return batch_currency
def _l10n_ca_cpa005_get_total_cents(self, payments):
return sum(round(payment.amount * 100) for payment in payments)
def _l10n_ca_cpa005_generate_header(self, currency, file_creation_nr):
return remove_accents(
"A" # 01 1 1 "A" Logical Record Type ID
"000000001" # 02 2-10 9 "000000001" Logical Record Count
f"{self.journal_id.l10n_ca_cpa005_originator_id:10.10}" # 03 11-20 10 Alphanumeric Originator's ID
f"{file_creation_nr}" # 04 21-24 4 Numeric File Creation No.
f"0{self.date.strftime('%y%j')}" # 05 25-30 6 Numeric Creation Date
f"{self.journal_id.l10n_ca_cpa005_destination_data_center:0>5.5}" # 06 31-35 5 Numeric Destination Data Centre
f"{' ':20}" # 07 36-55 20 Alphanumeric Reserved Customer-Direct Clearer Communication area
f"{currency.name:3.3}" # 08 56-58 3 Alphanumeric Currency Code Identifier
f"{' ':1406}" # 09 59-1464 1406 Alphanumeric Filler
)
def _l10n_ca_cpa005_outgoing_payment(self, file_creation_nr, payment, logical_record_count):
journal = self.journal_id
return remove_accents(
"C" # 01 1 1 "C" Logical Record Type ID
f"{logical_record_count:09d}" # 02 2-10 9 Numeric Logical Record Count
f"{journal.l10n_ca_cpa005_originator_id:10.10}{file_creation_nr}" # 03 11-24 14 Alphanumeric Origination Control Data (originator ID + Numeric File Creation No.)
f"{payment.l10n_ca_cpa005_transaction_code_id.code:3.3}" # 04 25-27 3 Numeric Transaction Type
f"{self._l10n_ca_cpa005_get_total_cents(payment):010d}" # 05 28-37 10 Numeric Amount
f"0{payment.date.strftime('%y%j')}" # 06 38-43 6 Numeric Date Funds to be Available
f"{payment.partner_bank_id.l10n_ca_financial_institution_number:0>9}" # 07 44-52 9 Numeric Institutional Identification No.
f"{payment.partner_bank_id.acc_number:12.12}" # 08 53-64 12 Alphanumeric Payee Account No.
f"{payment.id:022d}" # 09 65-86 22 Numeric Item Trace No.
f"{0:03d}" # 10 87-89 3 Numeric Stored Transaction Type (RBC says to zero-fill)
f"{journal.company_id.l10n_ca_cpa005_short_name:15.15}" # 11 90-104 15 Alphanumeric Originator's Short Name
f"{payment.partner_id.name:30.30}" # 12 105-134 30 Alphanumeric Payee Name
f"{journal.company_id.name:30.30}" # 13 135-164 30 Alphanumeric Originator's Long Name
f"{journal.l10n_ca_cpa005_originator_id:10.10}" # 14 165-174 10 Alphanumeric Originating Direct Clearer's User's ID
f"{(payment.payment_reference or ''):19.19}" # 15 175-193 19 Alphanumeric Originator's Cross Reference No.
f"{journal.bank_account_id.l10n_ca_financial_institution_number:0>9}" # 16 194-202 9 Numeric Institutional ID Number for Returns (RBC says to zero-fill)
f"{journal.bank_account_id.acc_number:12.12}" # 17 203-214 12 Alphanumeric Account No. for Returns (RBC says to zero-fill)
f"{' ':15}" # 18 215-229 15 Alphanumeric Originator's Sundry Information (optional)
f"{' ':22}" # 19 230-251 22 Alphanumeric Filler
f"{' ':2}" # 20 252-253 2 Alphanumeric Originator-Direct Clearer Settlement code (RBC says to zero-fill)
f"{0:011d}" # 21 254-264 11 Numeric Invalid Data Element I.D.
f"{' ':1200}" # padding for segments 2-6, in practice only one payment is provided per record
)
def _l10n_ca_cpa005_generate_footer(self, file_creation_nr, logical_record_count):
journal = self.journal_id
payments = self.payment_ids
return remove_accents(
"Z" # 01 1 1 "Z" Logical Record Type ID
f"{logical_record_count:09d}" # 02 2-10 9 Numeric Logical Record Count
f"{journal.l10n_ca_cpa005_originator_id:10.10}{file_creation_nr}" # 03 11-24 14 Alphanumeric Origination Control Data (originator ID + Numeric File Creation No.)
f"{0:014d}" # 04 25-38 14 Numeric Total Value of Debit Transactions "D" and "J"
f"{0:08d}" # 05 39-46 8 Numeric Total Number of Debit Transactions "D" and "J"
f"{self._l10n_ca_cpa005_get_total_cents(payments):014d}" # 06 47-60 14 Numeric Total Value of Credit Transactions "C" and "I"
f"{len(payments):08d}" # 07 61-68 8 Numeric Total Number of Credit Transactions "C" and "I"
f"{0:014d}" # 08 69-82 14 Numeric Total Value of Error Corrections "E"
f"{0:08d}" # 09 83-90 8 Numeric Total Number of Error Corrections "E"
f"{0:014d}" # 10 91-104 14 Numeric Total Value of Error Corrections "F"
f"{0:08d}" # 11 105-112 8 Numeric Total Number of Error Corrections "F"
f"{' ':1352}" # 12 113-1464 1352 Alphanumeric Filler
)
def _generate_cpa005_file(self):
records = []
self._l10n_ca_cpa005_pre_validate()
currency = self._l10n_ca_cpa005_get_currency()
file_creation_nr = self.l10n_ca_cpa005_file_creation_number
if not file_creation_nr:
file_creation_nr = self.journal_id._l10n_ca_cpa005_next_file_creation_nr()
self.l10n_ca_cpa005_file_creation_number = file_creation_nr
records.append(self._l10n_ca_cpa005_generate_header(currency, file_creation_nr))
for payment in self.payment_ids.sorted():
records.append(self._l10n_ca_cpa005_outgoing_payment(file_creation_nr, payment, len(records) + 1))
records.append(self._l10n_ca_cpa005_generate_footer(file_creation_nr, len(records) + 1))
return "\r\n".join(records)
def _get_methods_generating_files(self):
res = super()._get_methods_generating_files()
res.append("cpa005")
return res
def _generate_export_file(self):
if self.payment_method_code == "cpa005":
data = self._generate_cpa005_file()
date = fields.Datetime.today().strftime("%Y-%m-%d") # CA date format
return {
"file": base64.encodebytes(data.encode()),
"filename": "CPA005-%s-%s.txt" % (self.journal_id.code, date),
}
else:
return super()._generate_export_file()
@@ -0,0 +1,70 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api
class AccountJournal(models.Model):
_inherit = "account.journal"
l10n_ca_cpa005_destination_data_center = fields.Char(
"Destination Data Center",
size=5,
copy=False,
help="5-digit ID provided by your bank. It is needed for generating a Canadian EFT file (CPA 005).",
)
l10n_ca_cpa005_originator_id = fields.Char(
"Originator ID",
size=10,
copy=False,
help="10-digit code supplied by your bank. It is needed for generating a Canadian EFT file (CPA 005).",
)
l10n_ca_cpa005_fcn_sequence_id = fields.Many2one(
"ir.sequence",
"File Creation Number Sequence",
copy=False,
help="This is used when generating the File Creation Number for Canadian EFT files. The referenced sequence can "
"be customized to suit the banks requirements.",
)
l10n_ca_cpa005_fcn_number_next = fields.Integer(
"Next File Creation Number (FCN)",
compute="_compute_l10n_ca_cpa005_fcn_number_next",
inverse="_set_l10n_ca_cpa005_fcn_next_number",
help="Next File Creation Number (FCN) that will be used when validating a Canadian EFT batch payment.The FCN is "
"a 4-digit sequence from 0001 to 9999. If you need to adjust any details of the FCN go to Sequences in the Technical "
"menu to adjust them.",
)
@api.depends("l10n_ca_cpa005_fcn_sequence_id.number_next_actual")
def _compute_l10n_ca_cpa005_fcn_number_next(self):
for journal in self:
journal._l10n_ca_cpa005_create_sequence_if_needed()
journal.l10n_ca_cpa005_fcn_number_next = self._l10n_ca_cpa005_modulo_file_creation_number(
journal.l10n_ca_cpa005_fcn_sequence_id.number_next_actual
)
def _set_l10n_ca_cpa005_fcn_next_number(self):
for journal in self:
journal._l10n_ca_cpa005_create_sequence_if_needed()
journal.l10n_ca_cpa005_fcn_sequence_id.sudo().number_next_actual = journal.l10n_ca_cpa005_fcn_number_next
def _l10n_ca_cpa005_modulo_file_creation_number(self, number):
"""Modulo number to be in 1 <= number <= 9999. 0 is skipped because 0000 is not a valid FCN."""
return (number - 1) % 9999 + 1
def _l10n_ca_cpa005_create_sequence_if_needed(self):
if not self.l10n_ca_cpa005_fcn_sequence_id and "cpa005" in self.outbound_payment_method_line_ids.mapped("code"):
vals = {
"name": f"Canadian EFT File Creation Number (FCN) for journal {self.name}",
"company_id": self.company_id.id,
}
self.l10n_ca_cpa005_fcn_sequence_id = self.env["ir.sequence"].sudo().create(vals)
def _default_outbound_payment_methods(self):
res = super()._default_outbound_payment_methods()
if self._is_payment_method_available("cpa005"):
res |= self.env.ref("l10n_ca_payment_cpa005.account_payment_method_cpa005")
return res
def _l10n_ca_cpa005_next_file_creation_nr(self):
self._l10n_ca_cpa005_create_sequence_if_needed()
next_fcn = int(self.l10n_ca_cpa005_fcn_sequence_id.next_by_id())
return f"{self._l10n_ca_cpa005_modulo_file_creation_number(next_fcn):04}"
@@ -0,0 +1,21 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, fields
class AccountPayment(models.Model):
_inherit = "account.payment"
l10n_ca_cpa005_transaction_code_id = fields.Many2one(
"l10n_ca_cpa005.transaction.code",
string="EFT/CPA transaction code",
help="Select the option that better represents the type/purpose of the payment. Every payment initiated using the "
"Canadian EFT service must include a valid CPA code.",
)
@api.model
def _get_method_codes_using_bank_account(self):
return super()._get_method_codes_using_bank_account() + ["cpa005"]
@api.model
def _get_method_codes_needing_bank_account(self):
return super()._get_method_codes_needing_bank_account() + ["cpa005"]
@@ -0,0 +1,17 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class AccountPaymentMethod(models.Model):
_inherit = "account.payment.method"
@api.model
def _get_payment_method_information(self):
res = super()._get_payment_method_information()
res["cpa005"] = {
"mode": "multi",
"type": ("bank",),
"currency_ids": (self.env.ref("base.CAD") | self.env.ref("base.USD")).ids,
"country_id": self.env.ref("base.ca").id,
}
return res
@@ -0,0 +1,17 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api
class L10n_Ca_Cpa005TransactionCode(models.Model):
_name = 'l10n_ca_cpa005.transaction.code'
_description = "Canadian EFT transaction codes as defined in Payments Canada Standard 007"
_rec_names_search = ["name", "code"]
name = fields.Char("Name", required=True)
active = fields.Boolean("Active", default=True)
code = fields.Char("Code", required=True)
@api.depends("name", "code")
def _compute_display_name(self):
for tx_code in self:
tx_code.display_name = f"[{tx_code.code}] {tx_code.name}"
@@ -0,0 +1,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields
class ResCompany(models.Model):
_inherit = "res.company"
l10n_ca_cpa005_short_name = fields.Char(
"Short Name used in Canadian EFT",
size=15,
help="15 character field used to represent a short version of the Originator's name in Canadian EFT files. It will "
"typically be used for bank statements. Most banks require this value to be all uppercase.",
)
@@ -0,0 +1,30 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class ResPartnerBank(models.Model):
_inherit = "res.partner.bank"
l10n_ca_financial_institution_number = fields.Char(
"Financial Institution ID Number",
size=9,
help="9-digit number that identifies the financial institution where the vendor holds an account. It typically "
"starts with a 0, followed by a 3-digit institution number or ID, and a 5-digit branch routing number. This is a "
"mandatory field for Canadian EFT file generation.",
)
@api.constrains("l10n_ca_financial_institution_number")
def _check_l10n_ca_cpa005_financial_institution_number(self):
for bank in self:
financial_institution_number = bank.l10n_ca_financial_institution_number
if financial_institution_number and (
len(financial_institution_number) != 9 or not financial_institution_number.isdigit()
):
raise ValidationError(
_(
'The Financial Institution ID Number of the "%s" bank account must be a 9 digit number. The '
"format is a 0, followed by a 3-digit institution number, and a 5-digit branch routing number.",
bank.display_name,
)
)