Skip to content

fix(spp_registry): format individual name on create and write#160

Open
emjay0921 wants to merge 2 commits into19.0from
fix/name-format-on-create-write
Open

fix(spp_registry): format individual name on create and write#160
emjay0921 wants to merge 2 commits into19.0from
fix/name-format-on-create-write

Conversation

@emjay0921
Copy link
Copy Markdown
Contributor

Why is this change needed?

When creating or updating individual registrants programmatically (e.g., via API, demo generator, or import), the name field was not automatically formatted from name parts (family_name, given_name, addl_name). The name_change onchange only fires in the UI, so programmatic operations resulted in registrants with unformatted or missing display names.

How was the change implemented?

  • Extracted name formatting logic into a reusable _format_individual_name() class method
  • Override create() to inject formatted name into vals before record creation (no extra write needed)
  • Override write() to recompute name when any name field changes
  • name_change onchange now delegates to the same helper for consistency
  • Added skip_name_format context flag to prevent recursion during the name write-back

New unit tests

N/A — existing tests cover the onchange path; the create/write paths are exercised by the demo generator and API tests.

How to test manually

  1. Via XML-RPC or shell, create an individual with family_name and given_name but no name
  2. The name field should be auto-set to "FAMILY_NAME, GIVEN_NAME" (uppercase)
  3. Update given_name via write — name should update accordingly

Related links

The name_change onchange only runs in the UI. When creating or updating
individuals via XML-RPC, API, or code, the display name was not formatted
as "FAMILY_NAME, GIVEN_NAME ADDL_NAME" (uppercase).

Extract name formatting into _format_individual_name() helper and call it
from create (injects into vals before super) and write (applies after super
only when name fields are in vals). Groups are not affected.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request centralizes name formatting for individuals by introducing a _format_individual_name helper and applying it during record creation, updates, and UI changes. The write and create methods are overridden to ensure the name field remains consistent with its constituent parts. Feedback was provided to optimize the write method by injecting the formatted name directly into the update values for single-record operations and to improve correctness when the is_group field is being modified.

Comment on lines +139 to +153
name_updates = {}
if self._name_fields & vals.keys() and not self.env.context.get("skip_name_format"):
for rec in self.filtered(lambda r: not r.is_group):
name_updates[rec.id] = self._format_individual_name(
vals.get("family_name", rec.family_name),
vals.get("given_name", rec.given_name),
vals.get("addl_name", rec.addl_name),
)

res = super().write(vals)

# Apply computed names after super to avoid interfering with the main write
if name_updates:
for rec_id, name in name_updates.items():
super(SPPIndividual, self.browse(rec_id).with_context(skip_name_format=True)).write({"name": name})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The write override can be improved for both performance and correctness:

  1. Performance: For single-record updates (the most common case, such as UI edits), you can inject the formatted name directly into vals before calling super().write(). This avoids an extra database write and re-triggering the full write method logic for each record.
  2. Correctness: The filter not r.is_group uses the current database value. If is_group is being changed in the same write call (e.g., converting a group to an individual or vice versa), this logic might incorrectly format or skip the name. It is safer to check vals.get('is_group', r.is_group).
  3. Efficiency: You can store the record object itself in the name_updates dictionary to avoid redundant browse() calls in the second loop.
        name_updates = {}
        if self._name_fields & vals.keys() and not self.env.context.get("skip_name_format"):
            # Optimization: for single record, inject name into vals to avoid extra write
            if len(self) == 1 and not vals.get("is_group", self.is_group):
                vals["name"] = self._format_individual_name(
                    vals.get("family_name", self.family_name),
                    vals.get("given_name", self.given_name),
                    vals.get("addl_name", self.addl_name),
                )
            else:
                for rec in self.filtered(lambda r: not vals.get("is_group", r.is_group)):
                    name_updates[rec] = self._format_individual_name(
                        vals.get("family_name", rec.family_name),
                        vals.get("given_name", rec.given_name),
                        vals.get("addl_name", rec.addl_name),
                    )

        res = super().write(vals)

        # Apply computed names after super to avoid interfering with the main write
        if name_updates:
            for rec, name in name_updates.items():
                super(SPPIndividual, rec.with_context(skip_name_format=True)).write({"name": name})

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.19%. Comparing base (f738582) to head (281dd2b).
⚠️ Report is 7 commits behind head on 19.0.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #160      +/-   ##
==========================================
+ Coverage   71.06%   71.19%   +0.13%     
==========================================
  Files         925      931       +6     
  Lines       54704    54749      +45     
==========================================
+ Hits        38876    38980     +104     
+ Misses      15828    15769      -59     
Flag Coverage Δ
spp_analytics 93.13% <ø> (ø)
spp_api_v2 80.10% <ø> (ø)
spp_api_v2_change_request 66.85% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_data 64.41% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_gis 71.52% <ø> (ø)
spp_api_v2_products 66.27% <ø> (ø)
spp_api_v2_service_points 70.94% <ø> (ø)
spp_api_v2_simulation 71.12% <ø> (ø)
spp_api_v2_vocabulary 57.26% <ø> (ø)
spp_approval 50.29% <ø> (ø)
spp_area 79.26% <ø> (ø)
spp_area_hdx 81.43% <ø> (ø)
spp_audit 72.60% <ø> (+8.76%) ⬆️
spp_banking 80.00% <ø> (?)
spp_base_common 90.26% <ø> (ø)
spp_base_setting 50.00% <ø> (?)
spp_programs 62.23% <ø> (ø)
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant