Skip to content

Commit d0d65b7

Browse files
committed
fix(change_request): hide Head role on Add Member when the group already has one (#871)
A group can have at most one Head of Household, so the Head role is now removed from the Role options when the target group already has an active head. Implemented as a computed allowed_role_ids driving the Role field domain (UI restriction only; head-of-household configurability stays with #1006).
1 parent e7a8d29 commit d0d65b7

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

spp_change_request_v2/details/add_member.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ class SPPCRDetailAddMember(models.Model):
135135
string="Has Membership Roles",
136136
help="True when the urn:openspp:vocab:group-membership-type vocabulary has any active code.",
137137
)
138+
allowed_role_ids = fields.Many2many(
139+
"spp.vocabulary.code",
140+
string="Allowed Roles",
141+
compute="_compute_allowed_role_ids",
142+
help="Roles selectable for the new member; the Head role is excluded when "
143+
"the group already has a Head of Household (OP#871).",
144+
)
138145

139146
# ──────────────────────────────────────────────────────────────────────
140147
# Read-only context: existing members of the group (for the spec's
@@ -202,3 +209,28 @@ def _compute_existing_memberships(self):
202209
rec.existing_membership_ids = Membership.search([("group", "=", group.id), ("status", "=", "active")])
203210
else:
204211
rec.existing_membership_ids = Membership.browse([])
212+
213+
@api.depends("change_request_id", "change_request_id.registrant_id")
214+
def _compute_allowed_role_ids(self):
215+
"""Restrict the Role selection: a group can have only one Head, so the
216+
Head role is removed from the options when the target group already has
217+
an active Head of Household (OP#871)."""
218+
Code = self.env["spp.vocabulary.code"]
219+
Membership = self.env["spp.group.membership"]
220+
all_roles = Code.search([("vocabulary_id.namespace_uri", "=", "urn:openspp:vocab:group-membership-type")])
221+
for rec in self:
222+
roles = all_roles
223+
group = rec.change_request_id.registrant_id
224+
if group and group.is_group:
225+
group_has_head = bool(
226+
Membership.search_count(
227+
[
228+
("group", "=", group.id),
229+
("status", "=", "active"),
230+
("membership_type_ids.code", "=", "head"),
231+
]
232+
)
233+
)
234+
if group_has_head:
235+
roles = all_roles.filtered(lambda r: r.code != "head")
236+
rec.allowed_role_ids = roles

spp_change_request_v2/tests/test_add_member_strategy.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,35 @@ def test_preview_shows_fields_and_tables(self):
263263
self.assertIn("Occupation", html)
264264
self.assertNotIn("phone_count", html)
265265

266+
def test_allowed_roles_excludes_head_when_group_has_head(self):
267+
"""OP#871: the Head role is removed from the Role options when the group
268+
already has an active Head of Household; otherwise all roles are allowed."""
269+
# Group without a head -> Head is selectable.
270+
no_head_codes = set(
271+
self._make_cr(given_name="A", family_name="B").get_detail().allowed_role_ids.mapped("code")
272+
)
273+
self.assertIn("head", no_head_codes)
274+
275+
# Group with an active head -> Head removed; every other role preserved.
276+
head_indiv = self.partner_model.create({"name": "Head Person", "is_registrant": True, "is_group": False})
277+
group_with_head = self.partner_model.create(
278+
{"name": "Group With Head", "is_registrant": True, "is_group": True}
279+
)
280+
self.membership_model.create(
281+
{
282+
"group": group_with_head.id,
283+
"individual": head_indiv.id,
284+
"membership_type_ids": [Command.link(self.head_kind.id)],
285+
}
286+
)
287+
with_head_codes = set(
288+
self._make_cr(registrant=group_with_head, given_name="C", family_name="D")
289+
.get_detail()
290+
.allowed_role_ids.mapped("code")
291+
)
292+
self.assertNotIn("head", with_head_codes)
293+
self.assertEqual(with_head_codes, no_head_codes - {"head"})
294+
266295
def test_add_member_writes_single_address(self):
267296
"""The single Address field maps to the registry's res.partner.address
268297
on apply, matching how the registry stores it (OP#871 QA round 1)."""

spp_change_request_v2/views/detail_add_member_views.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<field name="is_cr_manager" invisible="1" />
1313
<field name="type_requires_head" invisible="1" />
1414
<field name="roles_available" invisible="1" />
15+
<field name="allowed_role_ids" invisible="1" />
1516
<button
1617
name="action_next_documents"
1718
string="Next: Upload Documents"
@@ -160,6 +161,7 @@
160161
<field
161162
name="membership_type_id"
162163
options="{'no_create': True, 'no_open': True}"
164+
domain="[('id', 'in', allowed_role_ids)]"
163165
required="type_requires_head"
164166
/>
165167
</group>

0 commit comments

Comments
 (0)