Skip to content

Commit 5a7544e

Browse files
authored
Merge pull request #160 from OpenSPP/fix/name-format-on-create-write
fix(spp_registry): format individual name on create and write
2 parents 7639fe8 + 281dd2b commit 5a7544e

2 files changed

Lines changed: 46 additions & 18 deletions

File tree

spp_api_v2/tests/test_search_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ def test_search_by_name(self):
5757
self.assertEqual(total, 2)
5858
self.assertEqual(len(records), 2)
5959
names = [r.name for r in records]
60-
self.assertIn("Alice Johnson", names)
61-
self.assertIn("Alice Brown", names)
60+
self.assertIn("JOHNSON, ALICE", names)
61+
self.assertIn("BROWN, ALICE", names)
6262

6363
def test_parse_identifier_param(self):
6464
"""identifier=system|value creates proper domain"""
@@ -224,7 +224,7 @@ def test_search_combined_params(self):
224224
# Should find Alice Johnson and Alice Brown (both female)
225225
self.assertEqual(total, 2)
226226
for record in records:
227-
self.assertIn("Alice", record.name)
227+
self.assertIn("ALICE", record.name)
228228
self.assertEqual(record.gender_id, self.gender_female)
229229

230230

spp_registry/models/individual.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,28 @@ class SPPIndividual(models.Model):
4747
# Membership fields
4848
individual_membership_ids = fields.One2many("spp.group.membership", "individual", "Membership to Groups")
4949

50+
# Fields that trigger name recomputation
51+
_name_fields = {"family_name", "given_name", "addl_name"}
52+
53+
@api.model
54+
def _format_individual_name(self, family_name, given_name, addl_name):
55+
"""Compute display name from individual name parts.
56+
57+
Format: "FAMILY_NAME, GIVEN_NAME ADDL_NAME" (uppercase).
58+
Used by onchange (UI), create, and write to ensure consistent naming.
59+
"""
60+
name_vals = [
61+
f"{family_name}," if family_name and (given_name or addl_name) else f"{family_name}" if family_name else "",
62+
given_name,
63+
addl_name,
64+
]
65+
return " ".join(filter(None, name_vals)).upper()
66+
5067
@api.onchange("is_group", "family_name", "given_name", "addl_name")
5168
def name_change(self):
52-
vals = {}
5369
if not self.is_group:
54-
name_vals = [
55-
f"{self.family_name},"
56-
if self.family_name and (self.given_name or self.addl_name)
57-
else f"{self.family_name}"
58-
if self.family_name
59-
else "",
60-
self.given_name,
61-
self.addl_name,
62-
]
63-
64-
name = " ".join(filter(None, name_vals))
65-
vals.update({"name": name.upper()})
66-
self.update(vals)
70+
name = self._format_individual_name(self.family_name, self.given_name, self.addl_name)
71+
self.update({"name": name})
6772

6873
@api.depends("birthdate")
6974
def _compute_calc_age(self):
@@ -129,8 +134,23 @@ def _recompute_parent_groups(self, records):
129134
self.env.add_to_compute(field, groups)
130135

131136
def write(self, vals):
132-
"""Override to invalidate group metrics when demographics change."""
137+
"""Override to recompute name and invalidate group metrics."""
138+
# Recompute name if any name field changed (only for non-group individuals)
139+
name_updates = {}
140+
if self._name_fields & vals.keys() and not self.env.context.get("skip_name_format"):
141+
for rec in self.filtered(lambda r: not r.is_group):
142+
name_updates[rec.id] = self._format_individual_name(
143+
vals.get("family_name", rec.family_name),
144+
vals.get("given_name", rec.given_name),
145+
vals.get("addl_name", rec.addl_name),
146+
)
147+
133148
res = super().write(vals)
149+
150+
# Apply computed names after super to avoid interfering with the main write
151+
if name_updates:
152+
for rec_id, name in name_updates.items():
153+
super(SPPIndividual, self.browse(rec_id).with_context(skip_name_format=True)).write({"name": name})
134154
self._recompute_parent_groups(self)
135155

136156
# Fields that affect group-level metrics
@@ -177,6 +197,14 @@ def _invalidate_parent_group_metrics(self, individuals):
177197

178198
@api.model_create_multi
179199
def create(self, vals_list):
200+
# Compute name from parts before create (injects into vals, no extra write)
201+
for vals in vals_list:
202+
if not vals.get("is_group") and self._name_fields & vals.keys():
203+
vals["name"] = self._format_individual_name(
204+
vals.get("family_name", ""),
205+
vals.get("given_name", ""),
206+
vals.get("addl_name", ""),
207+
)
180208
res = super().create(vals_list)
181209
self._recompute_parent_groups(res)
182210
return res

0 commit comments

Comments
 (0)