Skip to content

Commit 67cb7a5

Browse files
committed
feat(spp_farmer_registry): filters + form banners for incomplete groups (#939)
Surface group registrants that aren't usable yet because nobody is linked to them, or because no member is flagged as the head of household. Adds two stored computed fields on res.partner — `member_count` and `has_head_member` — so the search filters and form-top banners can reason about non-ended memberships without N+1 reads. Then: - Two new search filters on the registry groups search view: "No Members" (member_count = 0) and "No Head Member" (has_head_member is False). Filters work on any Groups menu now that the module is installed. - Two centered form banners injected at the top of the registrant form, scoped to is_group: a yellow warning when the group has no members at all, and a red danger banner when the group has members but none flagged as head. Mutually exclusive — they never both show. - Two optional columns on the groups list view (# Members, Has Head Member), both hidden by default but toggleable from the column-config menu.
1 parent 98a45a9 commit 67cb7a5

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

spp_farmer_registry/models/farm.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,27 @@ class Farm(models.Model):
148148
help="Sum of all livestock activity quantities",
149149
)
150150

151+
# ═══════════════════════════════════════════════════════════════════════
152+
# MEMBERSHIP COMPLETENESS (#939)
153+
# ═══════════════════════════════════════════════════════════════════════
154+
# Farm-registry users need to surface farms that aren't usable yet because
155+
# nobody is associated with them, or because no member is flagged as the
156+
# head of household. Stored so the search filters and list-row decoration
157+
# can use them without triggering N+1 reads.
158+
159+
member_count = fields.Integer(
160+
string="# Members",
161+
compute="_compute_member_membership_state",
162+
store=True,
163+
help="Number of non-ended members linked to this farm/group.",
164+
)
165+
has_head_member = fields.Boolean(
166+
string="Has Head Member",
167+
compute="_compute_member_membership_state",
168+
store=True,
169+
help="True when at least one non-ended member carries the 'head' membership type.",
170+
)
171+
151172
# ═══════════════════════════════════════════════════════════════════════
152173
# COMPUTED METHODS
153174
# ═══════════════════════════════════════════════════════════════════════
@@ -196,6 +217,21 @@ def _compute_total_livestock_heads(self):
196217
for record in self:
197218
record.total_livestock_heads = sum(act.quantity or 0 for act in record.farm_livestock_act_ids)
198219

220+
@api.depends(
221+
"group_membership_ids",
222+
"group_membership_ids.is_ended",
223+
"group_membership_ids.membership_type_ids",
224+
)
225+
def _compute_member_membership_state(self):
226+
"""Compute member_count and has_head_member from non-ended memberships."""
227+
head_code = self.env["spp.vocabulary.code"].get_code("urn:openspp:vocab:group-membership-type", "head")
228+
for record in self:
229+
active_members = record.group_membership_ids.filtered(lambda m: not m.is_ended)
230+
record.member_count = len(active_members)
231+
record.has_head_member = bool(
232+
head_code and active_members.filtered(lambda m: head_code in m.membership_type_ids)
233+
)
234+
199235
# ═══════════════════════════════════════════════════════════════════════
200236
# HELPER METHODS
201237
# ═══════════════════════════════════════════════════════════════════════

spp_farmer_registry/views/farm_views.xml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,81 @@
215215
</field>
216216
</record>
217217

218+
<!-- ═══════════════════════════════════════════════════════════════════════
219+
Farm membership-completeness UX (#939)
220+
═══════════════════════════════════════════════════════════════════════ -->
221+
222+
<!-- Search filters: surface farms with no members or no head member -->
223+
<record id="view_registry_groups_filter_farm" model="ir.ui.view">
224+
<field name="name">res.partner.search.farm.member_completeness</field>
225+
<field name="model">res.partner</field>
226+
<field name="inherit_id" ref="spp_registry.view_registry_groups_filter" />
227+
<field name="priority">50</field>
228+
<field name="arch" type="xml">
229+
<xpath expr="//filter[@name='has_phone']" position="after">
230+
<separator />
231+
<filter
232+
string="No Members"
233+
name="no_members"
234+
domain="[('member_count', '=', 0)]"
235+
/>
236+
<filter
237+
string="No Head Member"
238+
name="no_head_member"
239+
domain="[('has_head_member', '=', False)]"
240+
/>
241+
</xpath>
242+
</field>
243+
</record>
244+
245+
<!-- List view: warning decoration + optional column for member count.
246+
The decoration scopes to farm records (those with farm_type_id set)
247+
so it doesn't fire on plain group registrants. -->
248+
<record id="view_groups_list_tree_farm" model="ir.ui.view">
249+
<field name="name">res.partner.list.farm.member_completeness</field>
250+
<field name="model">res.partner</field>
251+
<field name="inherit_id" ref="spp_registry.view_groups_list_tree" />
252+
<field name="priority">50</field>
253+
<field name="arch" type="xml">
254+
<xpath expr="//list" position="inside">
255+
<field name="member_count" optional="hide" />
256+
<field name="has_head_member" optional="hide" widget="boolean" />
257+
</xpath>
258+
</field>
259+
</record>
260+
261+
<!-- Form-top banners: warn when a group has no members, or has members
262+
but no designated head. The two banners are mutually exclusive:
263+
no-members fires only when member_count == 0; no-head fires only
264+
when there are members but none flagged as head. Scoped to
265+
is_group so neither appears on individuals. -->
266+
<record id="view_individuals_form_farm_no_members" model="ir.ui.view">
267+
<field name="name">res.partner.form.farm.member_completeness_banners</field>
268+
<field name="model">res.partner</field>
269+
<field name="inherit_id" ref="spp_registry.view_individuals_form" />
270+
<field name="priority">50</field>
271+
<field name="arch" type="xml">
272+
<xpath expr="//div[@name='button_box']" position="before">
273+
<div
274+
class="alert alert-warning text-center"
275+
role="alert"
276+
invisible="not is_group or member_count != 0"
277+
>
278+
<i class="fa fa-exclamation-triangle me-2" title="Warning" />
279+
No members linked to this group yet.
280+
</div>
281+
<div
282+
class="alert alert-danger text-center"
283+
role="alert"
284+
invisible="not is_group or member_count == 0 or has_head_member"
285+
>
286+
<i class="fa fa-exclamation-triangle me-2" title="Warning" />
287+
No head member designated.
288+
</div>
289+
</xpath>
290+
</field>
291+
</record>
292+
218293
<!-- Add view activity actions to res.partner -->
219294
<record id="res_partner_view_crop_activities" model="ir.actions.server">
220295
<field name="name">View Crop Activities</field>

0 commit comments

Comments
 (0)