diff --git a/addons/tncsc_deployment/__init__.py b/addons/tncsc_deployment/__init__.py index e69de29..7fb25fd 100644 --- a/addons/tncsc_deployment/__init__.py +++ b/addons/tncsc_deployment/__init__.py @@ -0,0 +1,11 @@ +from . import models + + +def post_init_hook(env): + """Best-effort immediate attempt at the accounting bootstrap. The daily + cron in data/ir_cron.xml (calling the same idempotent method from a + separate transaction) is what guarantees eventual correctness - see + models/res_company.py for why a single call from within this hook's own + transaction isn't reliable on this Odoo build. + """ + env.company._tncsc_setup_accounting() diff --git a/addons/tncsc_deployment/__manifest__.py b/addons/tncsc_deployment/__manifest__.py index 397417b..8c33aa2 100644 --- a/addons/tncsc_deployment/__manifest__.py +++ b/addons/tncsc_deployment/__manifest__.py @@ -34,9 +34,19 @@ that applies to the community_* product modules. 'community_benefits', 'community_interac', 'community_portal', + 'l10n_ca', + ], + 'data': [ + 'security/tncsc_roles.xml', + 'data/res_company_data.xml', + 'data/theme_data.xml', + 'data/membership_data.xml', + 'data/mail_template_overrides.xml', + 'data/ir_cron.xml', + 'views/website_pages.xml', ], - 'data': [], 'demo': [], 'application': False, 'installable': True, + 'post_init_hook': 'post_init_hook', } diff --git a/addons/tncsc_deployment/data/ir_cron.xml b/addons/tncsc_deployment/data/ir_cron.xml new file mode 100644 index 0000000..5c07fbe --- /dev/null +++ b/addons/tncsc_deployment/data/ir_cron.xml @@ -0,0 +1,23 @@ + + + + + + TNCSC: Verify Accounting Setup + + code + model.search([])._tncsc_setup_accounting() + 1 + days + + + + diff --git a/addons/tncsc_deployment/data/mail_template_overrides.xml b/addons/tncsc_deployment/data/mail_template_overrides.xml new file mode 100644 index 0000000..44e0993 --- /dev/null +++ b/addons/tncsc_deployment/data/mail_template_overrides.xml @@ -0,0 +1,59 @@ + + + + + + TNCSC membership renewal due | TNCSC உறுப்பினர் புதுப்பித்தல் தேவை + +
+

English

+

Dear Member,

+

+ Your Tamil Nadu Cultural Society of Canada membership + () + expires on . +

+

Please renew soon to keep your membership active and avoid a lapse in benefits.

+
+

தமிழ்

+

அன்புள்ள உறுப்பினர்,

+

+ கனடா தமிழ்நாடு பண்பாட்டு சங்கத்தில் உங்கள் உறுப்பினர் காலம் + அன்று + காலாவதியாகிறது. +

+

உங்கள் உறுப்பினர் தகுதியைத் தொடர தயவுசெய்து விரைவில் புதுப்பிக்கவும்.

+
+
+
+ + + Your TNCSC membership has expired | TNCSC உறுப்பினர் காலாவதியானது + +
+

English

+

Dear Member,

+

+ Your Tamil Nadu Cultural Society of Canada membership + () + expired on . +

+

A renewal invoice has been prepared for you. Please get in touch to reactivate your membership.

+
+

தமிழ்

+

அன்புள்ள உறுப்பினர்,

+

+ கனடா தமிழ்நாடு பண்பாட்டு சங்கத்தில் உங்கள் உறுப்பினர் தகுதி + அன்று + காலாவதியானது. +

+

உங்களுக்கான புதுப்பித்தல் விலைப்பட்டியல் தயாரிக்கப்பட்டுள்ளது. உறுப்பினர் தகுதியை மீண்டும் + பெற எங்களைத் தொடர்பு கொள்ளவும்.

+
+
+
+
diff --git a/addons/tncsc_deployment/data/membership_data.xml b/addons/tncsc_deployment/data/membership_data.xml new file mode 100644 index 0000000..3661f11 --- /dev/null +++ b/addons/tncsc_deployment/data/membership_data.xml @@ -0,0 +1,59 @@ + + + + + community_membership.org_name + Tamil Nadu Cultural Society of Canada + + + community_membership.member_id_format + TNCSC-{year}-{seq} + + + community_membership.renewal_offsets + 30,14,7 + + + + + Individual + IND + 50.0 + + annual + 10 + + + Family + FAM + 80.0 + + annual + 20 + + + Student + STU + 20.0 + + annual + 30 + + + Senior + SEN + 30.0 + + annual + 40 + + + Life + LIFE + 500.0 + + one_time + 50 + + diff --git a/addons/tncsc_deployment/data/res_company_data.xml b/addons/tncsc_deployment/data/res_company_data.xml new file mode 100644 index 0000000..558adfa --- /dev/null +++ b/addons/tncsc_deployment/data/res_company_data.xml @@ -0,0 +1,21 @@ + + + + True + + + + + Tamil Nadu Cultural Society of Canada + info@tncsc.example.org + https://www.tncsc.example.org + + + diff --git a/addons/tncsc_deployment/data/theme_data.xml b/addons/tncsc_deployment/data/theme_data.xml new file mode 100644 index 0000000..c9ee456 --- /dev/null +++ b/addons/tncsc_deployment/data/theme_data.xml @@ -0,0 +1,26 @@ + + + + + community_theme_base.primary_color + #05091E + + + community_theme_base.secondary_color + #F97316 + + + community_theme_base.accent_color + #0EA5FF + + + community_theme_base.heading_font + Inter, sans-serif + + + community_theme_base.body_font + Inter, sans-serif + + diff --git a/addons/tncsc_deployment/models/__init__.py b/addons/tncsc_deployment/models/__init__.py new file mode 100644 index 0000000..aff44f3 --- /dev/null +++ b/addons/tncsc_deployment/models/__init__.py @@ -0,0 +1 @@ +from . import res_company diff --git a/addons/tncsc_deployment/models/res_company.py b/addons/tncsc_deployment/models/res_company.py new file mode 100644 index 0000000..9601e95 --- /dev/null +++ b/addons/tncsc_deployment/models/res_company.py @@ -0,0 +1,105 @@ +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'] diff --git a/addons/tncsc_deployment/security/tncsc_roles.xml b/addons/tncsc_deployment/security/tncsc_roles.xml new file mode 100644 index 0000000..c8b34de --- /dev/null +++ b/addons/tncsc_deployment/security/tncsc_roles.xml @@ -0,0 +1,69 @@ + + + + + + TNCSC Roles + 5 + + + + TNCSC Roles + + + + + Board Admin + + Full access across membership, school, classifieds, benefits, events, and Interac payments. + + + + + Treasurer + + Confirms Interac e-Transfer payments and manages accounting. + + + + + Events Officer + + Manages events and staffs the check-in desk. + + + + + School Coordinator + + Manages Tamil School terms, classes, students, and enrollment. + + + + + Teacher + + Takes attendance for their own Tamil School classes. + + + + + Classifieds Moderator + + Reviews and publishes classified listings. + + + diff --git a/addons/tncsc_deployment/tests/__init__.py b/addons/tncsc_deployment/tests/__init__.py new file mode 100644 index 0000000..8b559c8 --- /dev/null +++ b/addons/tncsc_deployment/tests/__init__.py @@ -0,0 +1 @@ +from . import test_tncsc_deployment diff --git a/addons/tncsc_deployment/tests/test_tncsc_deployment.py b/addons/tncsc_deployment/tests/test_tncsc_deployment.py new file mode 100644 index 0000000..7ae1894 --- /dev/null +++ b/addons/tncsc_deployment/tests/test_tncsc_deployment.py @@ -0,0 +1,85 @@ +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, + ) diff --git a/addons/tncsc_deployment/views/website_pages.xml b/addons/tncsc_deployment/views/website_pages.xml new file mode 100644 index 0000000..a6e2904 --- /dev/null +++ b/addons/tncsc_deployment/views/website_pages.xml @@ -0,0 +1,160 @@ + + + + + + TNCSC Home + qweb + tncsc_deployment.page_home + + + +
+

Tamil Nadu Cultural Society of Canada

+

Celebrating Tamil language, arts, and culture in Canada.

+

Content to be finalized by TNCSC - this is a placeholder home page + generated by the deployment layer.

+ Register for Tamil School + Browse Classifieds +
+
+
+
+
+ + / + + True + + + + + TNCSC About + qweb + tncsc_deployment.page_about + + + +
+

About Us

+

Placeholder content - to be filled in with TNCSC's real history, mission, + and board information.

+
+
+
+
+
+ + /about + + True + + + + + TNCSC Tamil School + qweb + tncsc_deployment.page_tamil_school + + + +
+

Tamil School

+

Weekend Tamil language classes for children and youth. See current terms, + levels, and class schedules below.

+ Register +
+
+
+
+
+ + /tamil-school + + True + + + + + TNCSC Sponsors + qweb + tncsc_deployment.page_sponsors + + + +
+

Our Sponsors

+

Placeholder content - sponsor logos and tiers to be added.

+ See Member Benefits +
+
+
+
+
+ + /sponsors + + True + + + + + TNCSC Contact + qweb + tncsc_deployment.page_contact + + + +
+

Contact Us

+

Email:

+

Placeholder content - add a real address, phone number, and contact form.

+
+
+
+
+
+ + /contact + + True + + + + + About + /about + + 10 + + + Tamil School + /tamil-school + + 20 + + + Classifieds + /classifieds + + 30 + + + Benefits + /benefits + + 40 + + + Sponsors + /sponsors + + 50 + + + Contact + /contact + + 60 + +
diff --git a/deploy/odoo.conf b/deploy/odoo.conf index 544df98..8043337 100644 --- a/deploy/odoo.conf +++ b/deploy/odoo.conf @@ -1,5 +1,5 @@ [options] -admin_passwd = $pbkdf2-sha512$600000$l5KS8v6/977XOgcAIITwvg$F92iQue/QiuEv1Jgf8xefn.ikVIanCsopqLdhpWbFal1DnM7UqCRtZyPwFr4DsVL5hHEOuFcfkrRH5Wv6sDSuQ +admin_passwd = $pbkdf2-sha512$600000$h9A6h9Cac04pBaDUGoOwFg$FrvE9uDPOH6MSH6kkZvcRdsNDnnghqvWQtnjdSNGo8w0R139ubAGlyf.YVE3Zvd9Z9kTG5Twvg5yIjfgSIAV7w db_host = db db_port = 5432 db_user = odoo