diff --git a/spp_programs/models/entitlement_base_model.py b/spp_programs/models/entitlement_base_model.py index 53353a86..a0d7bfe9 100644 --- a/spp_programs/models/entitlement_base_model.py +++ b/spp_programs/models/entitlement_base_model.py @@ -17,6 +17,23 @@ class SPPEntitlement(models.Model): _order = "partner_id asc,id desc" _check_company_auto = True + def init(self): + super().init() + self.env.cr.execute( + """ + CREATE INDEX IF NOT EXISTS + spp_entitlement_cycle_id_partner_id_idx + ON spp_entitlement (cycle_id, partner_id) + """ + ) + self.env.cr.execute( + """ + CREATE INDEX IF NOT EXISTS + spp_entitlement_cycle_id_state_idx + ON spp_entitlement (cycle_id, state) + """ + ) + @api.model def _generate_code(self): return str(uuid4())[4:-8][3:] diff --git a/spp_programs/models/program_membership.py b/spp_programs/models/program_membership.py index ee4132ae..d82e0536 100644 --- a/spp_programs/models/program_membership.py +++ b/spp_programs/models/program_membership.py @@ -19,6 +19,16 @@ class SPPProgramMembership(models.Model): _inherits = {"res.partner": "partner_id"} _order = "id desc" + def init(self): + super().init() + self.env.cr.execute( + """ + CREATE INDEX IF NOT EXISTS + spp_program_membership_program_id_state_idx + ON spp_program_membership (program_id, state) + """ + ) + partner_id = fields.Many2one( "res.partner", "Registrant", diff --git a/spp_programs/tests/__init__.py b/spp_programs/tests/__init__.py index c56ebc92..54ab2709 100644 --- a/spp_programs/tests/__init__.py +++ b/spp_programs/tests/__init__.py @@ -15,3 +15,4 @@ from . import test_eligibility_cel_integration from . import test_compliance_cel from . import test_create_program_wizard_cel +from . import test_composite_indexes diff --git a/spp_programs/tests/test_composite_indexes.py b/spp_programs/tests/test_composite_indexes.py new file mode 100644 index 00000000..98ca3b38 --- /dev/null +++ b/spp_programs/tests/test_composite_indexes.py @@ -0,0 +1,64 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +from odoo.tests import TransactionCase + + +class TestCompositeIndexes(TransactionCase): + """Test that composite indexes exist for frequent query patterns.""" + + def test_entitlement_cycle_partner_index_exists(self): + """Composite index on spp_entitlement(cycle_id, partner_id) must exist. + + The prepare_entitlements duplicate check searches entitlements by + (cycle_id, partner_id). Without this index, each batch does a + sequential scan. + """ + self.env.cr.execute( + """ + SELECT 1 FROM pg_indexes + WHERE tablename = 'spp_entitlement' + AND indexdef LIKE '%%cycle_id%%' + AND indexdef LIKE '%%partner_id%%' + """ + ) + self.assertTrue( + self.env.cr.fetchone(), + "Composite index on (cycle_id, partner_id) must exist on spp_entitlement", + ) + + def test_entitlement_cycle_state_index_exists(self): + """Composite index on spp_entitlement(cycle_id, state) must exist. + + Cycle computed fields (total_amount, show_approve_button, + entitlements_count) filter entitlements by cycle_id and state. + """ + self.env.cr.execute( + """ + SELECT 1 FROM pg_indexes + WHERE tablename = 'spp_entitlement' + AND indexdef LIKE '%%cycle_id%%' + AND indexdef LIKE '%%state%%' + """ + ) + self.assertTrue( + self.env.cr.fetchone(), + "Composite index on (cycle_id, state) must exist on spp_entitlement", + ) + + def test_program_membership_program_state_index_exists(self): + """Composite index on spp_program_membership(program_id, state) must exist. + + get_beneficiaries() and count_beneficiaries() filter by + (program_id, state) on every async batch dispatch. + """ + self.env.cr.execute( + """ + SELECT 1 FROM pg_indexes + WHERE tablename = 'spp_program_membership' + AND indexdef LIKE '%%program_id%%' + AND indexdef LIKE '%%state%%' + """ + ) + self.assertTrue( + self.env.cr.fetchone(), + "Composite index on (program_id, state) must exist on spp_program_membership", + )