Data-only deployment layer seeding TNCSC's branding (navy/orange/electric-
blue from the plan), 5 membership tiers in CAD (Individual $50, Family $80,
Student $20, Senior $30, Life $500 - the plan's own example figures;
update via Settings once TNCSC confirms real pricing), the
'TNCSC-{year}-{seq}' member-ID format, a Canadian (Ontario/HST) chart of
accounts via l10n_ca plus non-profit-specific accounts (Membership Dues/
Event/Sponsorship/School Fees/Donations Revenue, Deferred Event Revenue
liability, a Stripe clearing account) and a Donations journal, bilingual
EN/Tamil overrides of two membership email templates (Tamil text is a
best-effort draft only, explicitly flagged as needing native-speaker
review before go-live), five placeholder website pages (Home/About/Tamil
School/Sponsors/Contact), and TNCSC-named role groups (Board Admin,
Treasurer, Events Officer, School Coordinator, Teacher, Classifieds
Moderator) that imply the existing generic product-layer groups rather
than defining new permission logic.
Chasing the chart-of-accounts setup down to a genuinely working state
took real digging: setting company.country_id on a brand-new company
auto-schedules this Odoo build's own chart-template installer via a
precommit hook (res.company.install_l10n_modules), which races an
explicit try_loading('ca_2023', ...) call made in the same install
transaction and silently replaces its result afterwards (reverting
currency to USD, chart_template to 'generic_coa', and deleting the custom
accounts) - confirmed via raw SQL checks that the correct state exists
right up until the post_init_hook transaction commits, and is gone by the
time the install process exits. Since the precommit auto-trigger only
ever fires once per company (guarded by chart_template being unset), a
second call from a separate transaction is immune to the race. The fix:
the actual setup logic lives in an idempotent res.company._tncsc_setup_
accounting() method (models/res_company.py - a narrow, documented
exception to "no models" in the deployment layer, since cramming this
into a sandboxed ir.cron code string wasn't practical), called as a
best-effort from post_init_hook and guaranteed by a daily safety-net cron
que runs in its own transaction. Also hit two smaller, separate bugs on
the way: ir.cron code execution forbids direct attribute assignment
(STORE_ATTR) in its sandbox, and Html config_parameter fields must not be
wrapped in CDATA in data XML.
Verified end-to-end on a genuinely fresh database (not the long-lived dev
DB, which already has posted entries and correctly refuses a currency
change): installed tncsc_deployment alone, pulling in all 8 product
modules plus l10n_ca as dependencies, confirmed the post-install state via
raw SQL, manually triggered the safety-net cron and confirmed it reached
the fully-correct state (CAD, ca_2023, 355 accounts including all 7
custom ones, the Donations journal, tier products linked to the dues
account), confirmed the cron is idempotent on a second run, and ran the
full test suite (9/9 passing) with the same deterministic setup called
from the test transaction directly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
86 lines
4.2 KiB
Python
86 lines
4.2 KiB
Python
from odoo.tests.common import TransactionCase, tagged
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestTncscDeployment(TransactionCase):
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
# Deterministic setup for testing: this Odoo build's own chart-
|
|
# template auto-install can race with post_init_hook's call to this
|
|
# same method within the install transaction (see
|
|
# models/res_company.py) - calling it again here, synchronously,
|
|
# from the test's own transaction avoids the test suite being
|
|
# sensitive to that race. The method is idempotent either way.
|
|
self.env.company._tncsc_setup_accounting()
|
|
|
|
def test_branding_config_seeded(self):
|
|
get_param = self.env['ir.config_parameter'].sudo().get_param
|
|
self.assertEqual(get_param('community_theme_base.primary_color'), '#05091E')
|
|
self.assertEqual(get_param('community_theme_base.secondary_color'), '#F97316')
|
|
self.assertEqual(get_param('community_theme_base.accent_color'), '#0EA5FF')
|
|
|
|
def test_membership_settings_seeded(self):
|
|
get_param = self.env['ir.config_parameter'].sudo().get_param
|
|
self.assertEqual(get_param('community_membership.org_name'), 'Tamil Nadu Cultural Society of Canada')
|
|
self.assertEqual(get_param('community_membership.member_id_format'), 'TNCSC-{year}-{seq}')
|
|
|
|
def test_five_membership_tiers_seeded(self):
|
|
tiers = self.env['community.membership.tier'].search([])
|
|
self.assertEqual(len(tiers), 5)
|
|
by_code = {tier.code: tier for tier in tiers}
|
|
self.assertEqual(by_code['IND'].price, 50.0)
|
|
self.assertEqual(by_code['FAM'].price, 80.0)
|
|
self.assertEqual(by_code['STU'].price, 20.0)
|
|
self.assertEqual(by_code['SEN'].price, 30.0)
|
|
self.assertEqual(by_code['LIFE'].price, 500.0)
|
|
for tier in tiers:
|
|
self.assertEqual(tier.currency_id.name, 'CAD')
|
|
self.assertTrue(tier.product_id, "Every seeded tier should still auto-create its product")
|
|
|
|
def test_chart_of_accounts_loaded(self):
|
|
company = self.env.company
|
|
self.assertEqual(company.currency_id.name, 'CAD')
|
|
self.assertEqual(company.country_id.code, 'CA')
|
|
# The Canadian template seeds thousands of accounts; a small sanity count is enough.
|
|
account_count = self.env['account.account'].search_count([('company_ids', 'in', company.id)])
|
|
self.assertGreater(account_count, 100)
|
|
|
|
def test_custom_nonprofit_accounts_created(self):
|
|
Account = self.env['account.account']
|
|
names = {'Membership Dues Revenue', 'Event Revenue', 'Sponsorship Revenue',
|
|
'School Fees Revenue', 'Donations Revenue', 'Deferred Event Revenue',
|
|
'Stripe Clearing Account'}
|
|
for name in names:
|
|
account = Account.search([('name', '=', name)], limit=1)
|
|
self.assertTrue(account, f"Expected custom account '{name}' to exist")
|
|
|
|
def test_donations_journal_created(self):
|
|
journal = self.env['account.journal'].search([('code', '=', 'DON')], limit=1)
|
|
self.assertTrue(journal)
|
|
self.assertEqual(journal.type, 'sale')
|
|
|
|
def test_membership_tier_products_use_dues_account(self):
|
|
dues_account = self.env['account.account'].search([('name', '=', 'Membership Dues Revenue')], limit=1)
|
|
tiers = self.env['community.membership.tier'].search([])
|
|
for tier in tiers:
|
|
self.assertEqual(tier.product_id.property_account_income_id, dues_account)
|
|
|
|
def test_website_pages_published(self):
|
|
for url in ('/', '/about', '/tamil-school', '/sponsors', '/contact'):
|
|
page = self.env['website.page'].search([('url', '=', url)], limit=1)
|
|
self.assertTrue(page, f"Expected a website.page for {url}")
|
|
self.assertTrue(page.is_published)
|
|
|
|
def test_tncsc_role_groups_exist_and_imply_product_groups(self):
|
|
board_admin = self.env.ref('tncsc_deployment.group_tncsc_board_admin')
|
|
self.assertIn(
|
|
self.env.ref('community_membership.group_membership_manager'),
|
|
board_admin.implied_ids,
|
|
)
|
|
treasurer = self.env.ref('tncsc_deployment.group_tncsc_treasurer')
|
|
self.assertIn(
|
|
self.env.ref('community_interac.group_interac_verifier'),
|
|
treasurer.implied_ids,
|
|
)
|