diff --git a/spp_cr_type_assign_program/__manifest__.py b/spp_cr_type_assign_program/__manifest__.py index 30bc2493..f81be529 100644 --- a/spp_cr_type_assign_program/__manifest__.py +++ b/spp_cr_type_assign_program/__manifest__.py @@ -1,6 +1,6 @@ { "name": "OpenSPP CR Type - Assign to Program", - "version": "19.0.1.0.0", + "version": "19.0.1.0.1", "sequence": 53, "category": "OpenSPP", "summary": "Change request type for assigning a registrant to a program", diff --git a/spp_cr_type_assign_program/models/change_request.py b/spp_cr_type_assign_program/models/change_request.py index 21d52ddd..780dfc57 100644 --- a/spp_cr_type_assign_program/models/change_request.py +++ b/spp_cr_type_assign_program/models/change_request.py @@ -4,10 +4,11 @@ class SPPChangeRequest(models.Model): """Custom conflict-detection hook for the assign-program CR type. - The base conflict rule scoped to the same registrant flags every in-flight - `assign_program` CR for that registrant. We narrow the match to those - targeting the same `(registrant, program)` pair so two CRs assigning the - same registrant to *different* programs are allowed to proceed. + The rule uses `scope='custom'`, so the base domain does not pre-filter by + registrant. We narrow the candidate set to those targeting the same + `(registrant, program)` pair: two CRs assigning the same registrant to + *different* programs may proceed, and two CRs assigning *different* + registrants to the same program may also proceed. """ _inherit = "spp.change.request" @@ -44,4 +45,6 @@ def _check_custom_conflicts(self, candidates, rule): ) .ids ) - return candidates.filtered(lambda c: c.detail_res_id in matching_detail_ids) + return candidates.filtered( + lambda c: c.detail_res_id in matching_detail_ids and c.registrant_id == self.registrant_id + ) diff --git a/spp_cr_type_assign_program/tests/test_assign_program.py b/spp_cr_type_assign_program/tests/test_assign_program.py index 7baf7f89..06eca757 100644 --- a/spp_cr_type_assign_program/tests/test_assign_program.py +++ b/spp_cr_type_assign_program/tests/test_assign_program.py @@ -253,6 +253,29 @@ def test_f3_two_crs_for_same_registrant_different_programs_allowed(self): self.assertEqual(cr2.conflict_status, "none") self.assertFalse(cr2.conflicting_cr_ids) + def test_f5_two_crs_for_different_registrants_same_program_allowed(self): + # Two different registrants assigning to the *same* program must both + # be able to proceed. The rule's intent is "same (registrant, program) + # pair" — different registrants are not in conflict even when the + # program matches. + other_individual = self.Partner.create( + { + "name": "Other Individual", + "given_name": "Other", + "family_name": "Individual", + "is_registrant": True, + "is_group": False, + } + ) + + _cr1, _d1 = self._make_cr(self.test_individual, self.indiv_program_active) + cr2, _d2 = self._make_cr(other_individual, self.indiv_program_active) + + cr2._run_conflict_checks() + + self.assertEqual(cr2.conflict_status, "none") + self.assertFalse(cr2.conflicting_cr_ids) + def test_a10_apply_translates_unique_violation_to_user_error(self): """Race-path: a concurrent transaction inserts the same (registrant, program) pair between our validate() and create().