from odoo import models CUSTOM_ACCOUNT_CODE_START = 800001 REVENUE_ACCOUNT_SPECS = [ ('Membership Dues Revenue', 'income'), ('Event Revenue', 'income'), ('Sponsorship Revenue', 'income'), ('School Fees Revenue', 'income'), ('Donations Revenue', 'income_other'), ] class ResCompany(models.Model): """Deliberate, narrow exception to "no models" in the deployment layer: this is bootstrap-only glue (idempotent, TNCSC-specific, never called by any product module), not a reusable feature. It exists because this Odoo build's own chart-template auto-install (triggered the first time a fresh company's country_id is set, before this module's data even loads) races with an explicit try_loading() call made in the same install transaction, and can silently replace it afterwards. A second call from a separate transaction (the daily cron in data/ir_cron.xml) is immune to that race, since the auto-trigger only fires once, for a company that has never had a chart_template set at all. """ _inherit = 'res.company' def _tncsc_setup_accounting(self): for company in self: company._tncsc_fix_chart_template() company._tncsc_create_custom_accounts() def _tncsc_fix_chart_template(self): self.ensure_one() env = self.env cad = env.ref('base.CAD') if self.currency_id == cad and self.chart_template == 'ca_2023': return self.with_context(chart_template_load=True).write({ 'currency_id': cad.id, 'country_id': env.ref('base.ca').id, 'state_id': env.ref('base.state_ca_on').id, }) env['account.chart.template'].try_loading('ca_2023', self, install_demo=False) if self.currency_id != cad: self.currency_id = cad def _tncsc_create_custom_accounts(self): self.ensure_one() env = self.env Account = env['account.account'] if Account.search_count([('company_ids', 'in', self.id), ('name', '=', 'Membership Dues Revenue')]): return def _next_free_code(start): code = start while Account.search_count([('company_ids', 'in', self.id), ('code', '=', str(code))]): code += 1 return str(code) accounts = {} next_code = CUSTOM_ACCOUNT_CODE_START for name, account_type in REVENUE_ACCOUNT_SPECS: code = _next_free_code(next_code) next_code = int(code) + 1 accounts[name] = Account.create({ 'name': name, 'code': code, 'account_type': account_type, 'company_ids': [(6, 0, [self.id])], }) deferred_code = _next_free_code(next_code) next_code = int(deferred_code) + 1 Account.create({ 'name': 'Deferred Event Revenue', 'code': deferred_code, 'account_type': 'liability_current', 'company_ids': [(6, 0, [self.id])], }) stripe_code = _next_free_code(next_code) Account.create({ 'name': 'Stripe Clearing Account', 'code': stripe_code, 'account_type': 'asset_current', 'company_ids': [(6, 0, [self.id])], }) if not env['account.journal'].search_count([('code', '=', 'DON'), ('company_id', '=', self.id)]): env['account.journal'].create({ 'name': 'Donations', 'code': 'DON', 'type': 'sale', 'company_id': self.id, 'default_account_id': accounts['Donations Revenue'].id, }) tiers = env['community.membership.tier'].search([]) for tier in tiers: if tier.product_id: tier.product_id.property_account_income_id = accounts['Membership Dues Revenue']