Skip to content

WEB-884 feat: auto-fill address fields from postal code using Zippopotam API#3479

Open
shubhamkumar9199 wants to merge 1 commit intoopenMF:devfrom
shubhamkumar9199:feat/postal-code-auto-fill-address
Open

WEB-884 feat: auto-fill address fields from postal code using Zippopotam API#3479
shubhamkumar9199 wants to merge 1 commit intoopenMF:devfrom
shubhamkumar9199:feat/postal-code-auto-fill-address

Conversation

@shubhamkumar9199
Copy link
Copy Markdown
Contributor

@shubhamkumar9199 shubhamkumar9199 commented Apr 7, 2026

When entering a Postal Code in the client address dialog, the system now automatically queries the https://api.zippopotam.us API and auto-fills the City, State/Province, and Country fields. This reduces manual data entry errors and speeds up address capture for field officers.

Key design :

  • Created a dedicated PostalCodeLookupService in shared/services/ so any address form in the app can reuse it (not just client addresses)
  • Used HttpBackend to bypass Fineract interceptors for external API calls
  • 3-tier matching strategy (exact → starts-with → contains with 4+ char minimum) to prevent false positives like "MA" matching "Zambia"
  • When no country is pre-selected, the service tries common MFI country codes (us, mx, in, ke, ph, br, gb) until one matches
  • Country name aliases handle API vs Fineract naming differences (e.g., "Great Britain" ↔ "United Kingdom")

Note: State/Province and Country auto-fill only works when matching code values are configured in Fineract (Admin → System → Manage Codes under STATE
and COUNTRY). City always fills since it is a free-text field.

WEB-884

Screen.Recording.2026-04-07.183156.mp4

Please make sure these boxes are checked before submitting your pull request - thanks!

  • If you have multiple commits please combine them into one commit by squashing them.

  • Read and understood the contribution guidelines at web-app/.github/CONTRIBUTING.md.

Summary by CodeRabbit

  • New Features
    • Automatic postal-code lookup in address dialogs: entering a postal code now auto-populates city, state/province, and country. Works in both add and edit flows, supports multi-country fallback and fuzzy name matching, and clears auto-filled fields when no match is found.
  • Chores
    • Runtime feature flag added to enable/disable postal-code lookup.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'pre_merge_checks'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Integrates a postal-code lookup feature: adds a PostalCodeLookupService, country-code constants and models, feature flags, and wires postal-code-driven auto-fill into AddressTabComponent to populate city/state/country in the address dialog.

Changes

Cohort / File(s) Summary
Address Tab integration
src/app/clients/clients-view/address-tab/address-tab.component.ts
Adds PostalCodeLookupService usage, opens dialogs then calls setupPostalCodeLookup(...) to subscribe to postalCode valueChanges (debounce, distinct, min length), chooses lookup strategy, converts to ResolvedAddress, applies/clears auto-filled city, stateProvinceId, countryId, tracks auto-filled fields, and unsubscribes on dialog close.
Postal code lookup service
src/app/shared/services/postal-code-lookup.service.ts
New root-provided service using HttpBackend to call Zippopotam.us; exposes enabled, lookup, lookupWithFallback, resolveCountryCode, toResolvedAddress, and findBestMatch with alias expansion and staged matching; maps errors to null.
Country code constants
src/app/shared/constants/country-codes.ts
New mappings: COUNTRY_NAME_TO_ISO_CODE, DEFAULT_LOOKUP_COUNTRY_CODES, and COUNTRY_NAME_ALIASES for normalization, aliasing, and fallback lookup ordering.
Models
src/app/shared/models/postal-code-lookup.model.ts
New interfaces: PostalCodePlace, PostalCodeLookupResponse, and ResolvedAddress to type API responses and normalized results.
Environment/runtime flag
src/assets/env.js, src/environments/environment.ts, src/environments/environment.prod.ts
Adds window.env.enablePostalCodeLookup (default 'false') and environment.enablePostalCodeLookup boolean derived from runtime env for feature toggle.

Sequence Diagram

sequenceDiagram
    participant User
    participant FormDialog as FormDialogComponent
    participant AddressTab as AddressTabComponent
    participant LookupSvc as PostalCodeLookupService
    participant API as Zippopotam.us API

    User->>FormDialog: Open address dialog / enter postal code
    FormDialog->>AddressTab: expose form & postalCode valueChanges via dialogRef.componentInstance.form
    AddressTab->>AddressTab: debounceTime(600ms), distinctUntilChanged, trim, length>=3
    AddressTab->>LookupSvc: lookup(countryCode?, postalCode) OR lookupWithFallback(postalCode)
    LookupSvc->>API: HTTP GET
    API-->>LookupSvc: PostalCodeLookupResponse | error
    LookupSvc->>LookupSvc: resolveCountryCode(...) and toResolvedAddress(...)
    LookupSvc-->>AddressTab: ResolvedAddress | null
    AddressTab->>FormDialog: applyResolvedAddress -> set city/stateProvinceId/countryId, mark dirty, track auto-filled fields
    FormDialog->>User: show auto-populated fields
    AddressTab->>AddressTab: unsubscribe and clear auto-filled tracking on dialog close
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • gkbishnoi07
  • IOhacker
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature: auto-filling address fields from postal code using the Zippopotam API, which is the primary change across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/clients/clients-view/address-tab/address-tab.component.ts`:
- Around line 184-190: The current postal-code lookup subscription in
address-tab.component.ts exits early on a miss and leaves previously auto-filled
fields intact; update the subscribe callback for
postalCodeLookup.toResolvedAddress (and the similar block around the other
lookup at lines ~217-240) so that when response is falsy or
postalCodeLookup.toResolvedAddress(response) returns null you explicitly clear
only the fields that were auto-filled (e.g., city, state, country controls)
instead of returning; add/reuse a helper like clearAutoFilledAddress(form) and
call it on misses, and keep applyResolvedAddress(form, resolved) for successful
resolves.
- Around line 176-181: The postal-code lookup is incorrectly using an
auto-filled countryId (set by applyResolvedAddress) as if the user selected it;
change the logic so you only call postalCodeLookup.lookup(countryCode, ...) when
the country was present initially or explicitly changed by the user. Implement
this by tracking whether the country control was user-modified (e.g., check
control.dirty/wasUserChosen or add a boolean like countryManuallySelected set on
user change and cleared only when user resets), then update the switchMap that
calls getSelectedCountryCode(form) to ignore auto-filled country values unless
that manual flag is true; also apply the same fix to the duplicate lookup logic
around the other switchMap (the code referenced at lines ~233-239) so both paths
use the new manual-selection check instead of blindly using countryId from
applyResolvedAddress.

In `@src/app/shared/services/postal-code-lookup.service.ts`:
- Around line 33-44: The postal lookup currently always calls the third‑party
API via API_BASE in lookup(), which leaks client data; add a deployment opt‑in
toggle (e.g., a boolean config flag like useExternalPostalLookup) and
short‑circuit lookup() when disabled. Modify the PostalCodeLookupService to read
the flag from a configuration provider (environment, ConfigService, or injected
token) at construction, reference API_BASE and lookup() to check the flag first,
and if disabled return of(null) (or an appropriate Observable) instead of
calling externalHttp.get; ensure unit tests and callers handle the null/disabled
case.
- Around line 71-72: resolveCountryCode currently only looks up
COUNTRY_NAME_TO_ISO_CODE, so alias inputs like "United States of America"
(modeled in COUNTRY_NAME_ALIASES) return null; update
resolveCountryCode(countryName: string) to normalize the input (trim +
toLowerCase()), first check COUNTRY_NAME_ALIASES for an alias -> canonical name
mapping, and if found use that canonical name to look up
COUNTRY_NAME_TO_ISO_CODE; otherwise fall back to directly looking up the
normalized name in COUNTRY_NAME_TO_ISO_CODE and return null if still not found.
Ensure you reference resolveCountryCode, COUNTRY_NAME_ALIASES and
COUNTRY_NAME_TO_ISO_CODE in the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ca2d65b1-7303-44e1-bf8b-4dbb2e8357d2

📥 Commits

Reviewing files that changed from the base of the PR and between aa08907 and a2ab8a2.

📒 Files selected for processing (4)
  • src/app/clients/clients-view/address-tab/address-tab.component.ts
  • src/app/shared/constants/country-codes.ts
  • src/app/shared/models/postal-code-lookup.model.ts
  • src/app/shared/services/postal-code-lookup.service.ts

@shubhamkumar9199 shubhamkumar9199 force-pushed the feat/postal-code-auto-fill-address branch from a2ab8a2 to 5a17805 Compare April 7, 2026 14:46
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/app/clients/clients-view/address-tab/address-tab.component.ts (1)

158-159: ⚠️ Potential issue | 🟠 Major

Track auto-filled values, not just field names.

Once a field is added to autoFilledFields, later user edits are still treated as auto-filled. In practice that means a manual country change after a fallback hit is still ignored by isUserSelectedCountry, and a later miss can blank user-entered city/state/country. Storing the last auto-filled value per control is safer than storing only the control name.

Suggested direction
-  private autoFilledFields = new Set<string>();
+  private autoFilledValues = new Map<string, unknown>();
...
-            const isUserSelectedCountry =
-              currentCountryId && (currentCountryId === initialCountryId || !this.autoFilledFields.has('countryId'));
+            const autoFilledCountryId = this.autoFilledValues.get('countryId');
+            const isUserSelectedCountry =
+              currentCountryId &&
+              (currentCountryId === initialCountryId || currentCountryId !== autoFilledCountryId);
...
-    for (const fieldName of this.autoFilledFields) {
+    for (const [fieldName, autoFilledValue] of this.autoFilledValues) {
       const control = form.get(fieldName);
-      if (control) {
+      if (control?.value === autoFilledValue) {
         control.setValue('');
         control.markAsDirty();
       }
     }
-    this.autoFilledFields.clear();
+    this.autoFilledValues.clear();
...
-      this.autoFilledFields.add('city');
+      this.autoFilledValues.set('city', address.city);
...
-        this.autoFilledFields.add('stateProvinceId');
+        this.autoFilledValues.set('stateProvinceId', matched.id);
...
-        this.autoFilledFields.add('countryId');
+        this.autoFilledValues.set('countryId', matched.id);

Also applies to: 176-190, 244-252, 255-281

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/clients/clients-view/address-tab/address-tab.component.ts` around
lines 158 - 159, The Set autoFilledFields currently only records control names
so later user edits are treated as still auto-filled; replace it with a
Map<string,string> (e.g. lastAutoFilledValues) that stores the last auto-filled
value per control and update usages (including where autoFilledFields is mutated
and checks like isUserSelectedCountry) to compare the control's current value to
the stored auto-filled value before deciding it is auto-filled; ensure you
update add/remove logic to set or delete the map entry with the auto-filled
value (not just the key) and use equality checks against
lastAutoFilledValues.get(controlName) wherever the code previously checked
autoFilledFields.has(controlName).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/clients/clients-view/address-tab/address-tab.component.ts`:
- Around line 180-199: The postal code valueChanges filter currently drops
short/empty values so the postalSub subscribe never runs to clear auto-filled
fields; update the pipeline by removing the length check from the filter and
moving it inside the switchMap in postalSub (use postalCodeControl.valueChanges
-> debounceTime/distinctUntilChanged/filter only for non-null then in switchMap
check trimmed length: if length < 3 return of(null) so the downstream subscribe
always fires), keep the existing logic that chooses between
this.postalCodeLookup.lookup(...) and lookupWithFallback(...) when length >= 3,
and ensure the subscribe handler treats a null result as a signal to clear
autoFilledFields (countryId/city/state).

In `@src/assets/env.js`:
- Around line 115-117: The env default enables postal-code lookup by setting
window['env']['enablePostalCodeLookup'] = 'true' which contradicts the comment;
change the default to opt-in by setting window['env']['enablePostalCodeLookup']
= 'false' (matching the surrounding string-based env values) so the external
postal-code API is disabled unless explicitly overridden.

---

Duplicate comments:
In `@src/app/clients/clients-view/address-tab/address-tab.component.ts`:
- Around line 158-159: The Set autoFilledFields currently only records control
names so later user edits are treated as still auto-filled; replace it with a
Map<string,string> (e.g. lastAutoFilledValues) that stores the last auto-filled
value per control and update usages (including where autoFilledFields is mutated
and checks like isUserSelectedCountry) to compare the control's current value to
the stored auto-filled value before deciding it is auto-filled; ensure you
update add/remove logic to set or delete the map entry with the auto-filled
value (not just the key) and use equality checks against
lastAutoFilledValues.get(controlName) wherever the code previously checked
autoFilledFields.has(controlName).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a9d1479f-f714-4152-877f-459c76dc441f

📥 Commits

Reviewing files that changed from the base of the PR and between a2ab8a2 and 5a17805.

📒 Files selected for processing (7)
  • src/app/clients/clients-view/address-tab/address-tab.component.ts
  • src/app/shared/constants/country-codes.ts
  • src/app/shared/models/postal-code-lookup.model.ts
  • src/app/shared/services/postal-code-lookup.service.ts
  • src/assets/env.js
  • src/environments/environment.prod.ts
  • src/environments/environment.ts
✅ Files skipped from review due to trivial changes (2)
  • src/app/shared/models/postal-code-lookup.model.ts
  • src/app/shared/constants/country-codes.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/shared/services/postal-code-lookup.service.ts

@shubhamkumar9199 shubhamkumar9199 force-pushed the feat/postal-code-auto-fill-address branch from 5a17805 to 2f5ffba Compare April 7, 2026 15:10
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/app/clients/clients-view/address-tab/address-tab.component.ts (2)

238-258: Misplaced JSDoc comment.

The JSDoc at lines 238-242 ("Applies the resolved address to the form fields...") describes applyResolvedAddress, but it's positioned before clearAutoFilledFields. The actual applyResolvedAddress method at line 258 has no documentation.

📝 Suggested fix - reorder JSDoc
-  /**
-   * Applies the resolved address to the form fields.
-   * Passes both full name and abbreviation to maximize match chances
-   * against Fineract's configured code values.
-   */
   /**
    * Clears form fields that were previously set by auto-fill,
    * so stale data doesn't persist when a new lookup fails or returns different results.
    */
   private clearAutoFilledFields(form: UntypedFormGroup) {
     // ... existing implementation
   }

+  /**
+   * Applies the resolved address to the form fields.
+   * Passes both full name and abbreviation to maximize match chances
+   * against Fineract's configured code values.
+   */
   private applyResolvedAddress(form: UntypedFormGroup, address: ResolvedAddress) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/clients/clients-view/address-tab/address-tab.component.ts` around
lines 238 - 258, The JSDoc describing "Applies the resolved address to the form
fields..." is placed above clearAutoFilledFields but belongs to
applyResolvedAddress; move that comment block so it directly precedes the
applyResolvedAddress method declaration and remove the redundant empty JSDoc
above clearAutoFilledFields, ensuring the comment references
applyResolvedAddress and stays adjacent to the
applyResolvedAddress(UntypedFormGroup, ResolvedAddress) method.

247-256: Redundant conditional in setValue.

Line 251 has a ternary that evaluates to the same value on both branches:

control.setValue(fieldName === 'city' ? '' : '');

This appears to be a copy-paste remnant. Simplify to:

-        control.setValue(fieldName === 'city' ? '' : '');
+        control.setValue('');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/clients/clients-view/address-tab/address-tab.component.ts` around
lines 247 - 256, In clearAutoFilledFields, the control.setValue call uses a
redundant ternary; replace that expression with a single literal (e.g.,
control.setValue('')) so the loop simply sets each auto-filled control to the
intended empty value, keeps control.markAsDirty(), and then clears
this.autoFilledFields; reference: clearAutoFilledFields, this.autoFilledFields,
control.setValue.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/app/clients/clients-view/address-tab/address-tab.component.ts`:
- Around line 238-258: The JSDoc describing "Applies the resolved address to the
form fields..." is placed above clearAutoFilledFields but belongs to
applyResolvedAddress; move that comment block so it directly precedes the
applyResolvedAddress method declaration and remove the redundant empty JSDoc
above clearAutoFilledFields, ensuring the comment references
applyResolvedAddress and stays adjacent to the
applyResolvedAddress(UntypedFormGroup, ResolvedAddress) method.
- Around line 247-256: In clearAutoFilledFields, the control.setValue call uses
a redundant ternary; replace that expression with a single literal (e.g.,
control.setValue('')) so the loop simply sets each auto-filled control to the
intended empty value, keeps control.markAsDirty(), and then clears
this.autoFilledFields; reference: clearAutoFilledFields, this.autoFilledFields,
control.setValue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b31d2668-8347-4278-b62a-e28fb5e9238e

📥 Commits

Reviewing files that changed from the base of the PR and between 5a17805 and 2f5ffba.

📒 Files selected for processing (7)
  • src/app/clients/clients-view/address-tab/address-tab.component.ts
  • src/app/shared/constants/country-codes.ts
  • src/app/shared/models/postal-code-lookup.model.ts
  • src/app/shared/services/postal-code-lookup.service.ts
  • src/assets/env.js
  • src/environments/environment.prod.ts
  • src/environments/environment.ts
✅ Files skipped from review due to trivial changes (3)
  • src/assets/env.js
  • src/app/shared/constants/country-codes.ts
  • src/app/shared/models/postal-code-lookup.model.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/environments/environment.ts

Copy link
Copy Markdown
Contributor

@IOhacker IOhacker left a comment

Choose a reason for hiding this comment

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

Lgtm

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.

2 participants