Skip to content

WEB-642: Add Loan Originators management#3095

Merged
IOhacker merged 1 commit intoopenMF:devfrom
alberto-art3ch:WEB-642/add-loan-originators-management
Feb 6, 2026
Merged

WEB-642: Add Loan Originators management#3095
IOhacker merged 1 commit intoopenMF:devfrom
alberto-art3ch:WEB-642/add-loan-originators-management

Conversation

@alberto-art3ch
Copy link
Collaborator

@alberto-art3ch alberto-art3ch commented Feb 5, 2026

Description

Add Loan Originators management to Create / Modify / Delete originator details

WEB-642

Screenshots, if any

Screenshot 2026-02-06 at 10 01 50 a m
Screen.Recording.2026-02-05.at.5.45.25.p.m.mov

Checklist

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

    • Manage loan originators (list, create, view, edit, delete) and menu entry in Organization settings.
    • Attach/detach loan originators to loans with a new loan action and UI workflow.
  • Improvements

    • Originator columns show descriptive type/channel names instead of numeric IDs.
    • Removed Transaction Amount from loan approval form.
    • Added unlink icon for detach action.
  • Localization

    • Added translations for loan originator features and status labels in multiple languages.

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 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

Introduces loan originator management: new organization CRUD UI, resolvers, and routes; adds attach/detach originator UI on loan accounts, LoansService endpoints, model changes to use CodeValue for originator/channel types, translations, and an icon registration.

Changes

Cohort / File(s) Summary
Organization - Loan Originators UI & Resolvers
src/app/organization/loan-originators/loan-originators.component.*, src/app/organization/loan-originators/create-loan-originator/*, src/app/organization/loan-originators/edit-loan-originator/*, src/app/organization/loan-originators/view-loan-originator/*, src/app/organization/loan-originators/*.resolver.ts
Adds standalone components for list/create/edit/view, template resolvers, table UI, forms with validation, delete flow and template data resolvers.
Organization Service API
src/app/organization/organization.service.ts
Adds CRUD endpoints and template endpoint for loan originators: getLoanOriginators, getLoanOriginatorsTemplate, getLoanOriginator, createLoanOriginator, updateLoanOriginator, deleteLoanOriginator.
Loan Account — Attach/Detach Originator
src/app/loans/loans-view/loan-account-actions/attach-originator/*, src/app/loans/loans-view/loan-account-actions/loan-account-actions.component.*, src/app/loans/common-resolvers/loan-action-button.resolver.ts, src/app/loans/loans-view/loan-accounts-button-config.ts
Adds AttachOriginatorComponent (template, styles, logic), integrates "Attach Loan Originator" action into actions map and button configuration, and extends loan-action-button resolver to return originators via OrganizationService.
Loans Service
src/app/loans/loans.service.ts
Adds attachLoanOriginator and dettachLoanOriginator methods invoking POST/DELETE endpoints for loan originator association.
Loan Originators Tab (Loan details)
src/app/loans/loans-view/loan-originators-tab/*
Updates columns to use nested CodeValue fields (originatorType/channelType), adds actions column with detach confirmation dialog, dettachLoanOriginator method and navigation refresh.
Data Model
src/app/loans/models/loan-account.model.ts
Replaces numeric originatorTypeId/channelTypeId with originatorType: CodeValue and channelType: CodeValue; adds CodeValue import.
Routing & Navigation
src/app/organization/organization-routing.module.ts, src/app/organization/organization.component.html, src/app/organization/organization.component.ts
Registers manage-loan-originators routes and resolvers, adds menu entry guarded by permission, and adjusts arrowBooleans length.
Icons & Styling
src/app/shared/icons.module.ts, src/app/organization/loan-originators/**/*.scss, src/app/loans/loans-view/loan-account-actions/attach-originator/*.scss
Registers faUnlink icon and adds SCSS files (container width, table/utility styles).
Translations
src/assets/translations/*.json
Adds translation keys for Manage Loan Originators, Loan Originator, Attach Loan Originator, descriptive note and status labels across multiple locales.
Small UI change
src/app/loans/loans-view/loan-account-actions/approve-loan/approve-loan.component.html
Removes the Transaction Amount input field (approvedLoanAmount) from the approve loan form.

Sequence Diagram(s)

sequenceDiagram
  participant User as User (Browser)
  participant Router as Router/Resolver
  participant OrgSvc as OrganizationService
  participant UI as AttachOriginatorComponent
  participant LoansSvc as LoansService
  participant API as Backend API

  User->>Router: Navigate to Attach Originator view
  Router->>OrgSvc: resolve() -> getLoanOriginators()/getLoanOriginatorsTemplate()
  OrgSvc-->>Router: originators list
  Router-->>UI: route data (loanOriginators)
  User->>UI: select originator & submit
  UI->>LoansSvc: attachLoanOriginator(loanId, originatorId)
  LoansSvc->>API: POST /loans/{loanId}/originators/{originatorId}
  API-->>LoansSvc: 200 OK
  LoansSvc-->>UI: success
  UI->>Router: navigate to ../../general
  Router-->>User: updated loan view
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • 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 PR title clearly and concisely summarizes the main change: adding loan originators management functionality. It directly aligns with the changeset, which includes new components, services, and UI for creating, viewing, editing, deleting, and attaching loan originators.
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 docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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: 6

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts (1)

8-114: ⚠️ Potential issue | 🟡 Minor

Guard route param access and clean up route subscriptions.

Line 76 directly accesses this.route.parent.parent.snapshot.paramMap without optional chaining (line 77 inconsistently uses it), which can throw if the hierarchy changes. Both subscriptions in the constructor (lines 78–83) lack takeUntilDestroyed, violating clean observable patterns. Additionally, loanId and clientId are used in dettachLoanOriginator() (line 100) and reload() (line 113) without null guards, and nested properties are accessed without null coalescing.

Suggested fixes
-import { Component, inject } from '@angular/core';
+import { Component, DestroyRef, inject } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@
 export class LoanOriginatorsTabComponent {
   private route = inject(ActivatedRoute);
   private router = inject(Router);
   private loansService = inject(LoansService);
   private translateService = inject(TranslateService);
   private dialog = inject(MatDialog);
+  private destroyRef = inject(DestroyRef);
@@
   constructor() {
-    this.clientId = this.route.parent.parent.snapshot.paramMap.get('clientId');
-    this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
-    this.route.parent.parent.data.subscribe((data: { loanDetailsData: any }) => {
-      this.loanStatus = data.loanDetailsData.status;
-    });
-    this.route.parent.data.subscribe((data: { loanOriginatorsData: any }) => {
-      this.loanOriginatorsData = data.loanOriginatorsData.originators;
-    });
+    const parent = this.route.parent?.parent;
+    this.clientId = parent?.snapshot.paramMap.get('clientId');
+    this.loanId = parent?.snapshot.paramMap.get('loanId');
+
+    parent?.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanDetailsData: any }) => {
+      this.loanStatus = data.loanDetailsData?.status ?? null;
+    });
+    this.route.parent?.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanOriginatorsData: any }) => {
+      this.loanOriginatorsData = data.loanOriginatorsData?.originators ?? [];
+    });
   }
@@
   dettachLoanOriginator(loanOriginator: LoanOriginator): void {
+    if (!this.loanId) {
+      return;
+    }
     const dettachCodeDialogRef = this.dialog.open(ConfirmationDialogComponent, {
@@
   private reload() {
+    if (!this.clientId) {
+      return;
+    }
     const url: string = this.router.url;
     this.router
       .navigateByUrl(`/clients/${this.clientId}/loans-accounts`, { skipLocationChange: true })
       .then(() => this.router.navigate([url]));
   }
🤖 Fix all issues with AI agents
In
`@src/app/loans/loans-view/loan-account-actions/attach-originator/attach-originator.component.ts`:
- Around line 75-79: The submit method uses loanId without validating it and
does not use takeUntilDestroyed for subscription cleanup or check form validity;
update submit() in attach-originator.component.ts to first ensure
attachLoanOriginatorForm.valid and that this.loanId is non-null (return early
and surface an error if not), then call
this.loanService.attachLoanOriginator(this.loanId,
approveLoanFormData.originatorId).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(...)
to navigate on success and handle errors in the error handler (e.g., log or set
form error); import takeUntilDestroyed from `@angular/core/rxjs-interop` and
inject DestroyRef as destroyRef to enable this pattern.

In
`@src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.html`:
- Around line 105-112: The Submit button in edit-loan-originator.component.html
uses the wrong permission token ('CREATE_LOAN_ORIGINATOR'); update the
permission check on the <button> that uses *mifosxHasPermission to use the
edit/update permission 'UPDATE_LOAN_ORIGINATOR' instead, keeping the rest of the
template (mat-raised-button, color="primary",
[disabled]="!loanOriginatorForm.valid") unchanged so the permission directive
grants access only for update operations.

In
`@src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.ts`:
- Around line 71-83: In createLoanOriginatorForm replace the externalId initial
value to use the existing record (loanOriginatorsData.externalId) instead of
loanOriginatorsTemplateData.externalId so the edit form is prefilled with the
originator's value; also tighten the name Validators.pattern to only allow
letters at start (e.g. use /^[A-Za-z].*/ instead of '(^[A-z]).*') so non-letter
ASCII chars are excluded—update the loanOriginatorForm definition where
externalId and name validators are set and keep Validators.required intact.
- Around line 103-111: The edit form currently deletes externalId before sending
updates in submit() while the form marks externalId as required and editable;
fix by choosing one of two consistent approaches: (A) If externalId should not
be changed on edit, remove the required validator and make the FormControl
readOnly/disabled in the loanOriginatorForm setup (the FormGroup where
externalId is declared) and keep the delete statement so the payload omits it;
or (B) If externalId is editable on update, remove the delete
loanOriginatorFormData.externalId line in submit() so
organizationService.updateLoanOriginator(this.loanOriginatorsData.id, data)
includes externalId; update any template bindings accordingly (e.g., disabling
the input when choosing option A). Ensure the chosen change is applied to the
loanOriginatorForm initialization and submit() logic for consistency.

In `@src/app/organization/organization.component.html`:
- Around line 326-349: The arrowBooleans array is initialized too short (only
indices 0–17) but the template references arrowBooleans[18]; update the
initialization of the arrowBooleans variable in organization.component.ts (where
it’s currently set as new Array(18).fill(false)) to allocate 19 entries (new
Array(19).fill(false)) so index 18 is valid, then run a quick build/test to
ensure no other out-of-bounds indices exist.

In `@src/app/organization/organization.service.ts`:
- Around line 852-875: The three methods use inconsistent types for
originatorId: getLoanOriginator(originatorId: string) but
updateLoanOriginator(originatorId: number) and
deleteLoanOriginator(originatorId: number); change updateLoanOriginator and
deleteLoanOriginator signatures to accept originatorId: string (matching
getLoanOriginator) so route params and template literals are typed correctly,
and update any callers to pass strings (or coerce to string before calling) to
avoid type mismatches.
🟡 Minor comments (15)
src/assets/translations/de-DE.json-2683-2683 (1)

2683-2683: ⚠️ Potential issue | 🟡 Minor

Use consistent terminology for “Loan Originator”.

Line 2683 uses “Darlehensgeber” (lender) while Line 1981 uses “Kreditvermittler”. Consider aligning to avoid UX confusion.

💡 Suggested fix
-      "Attach Loan Originator": "Darlehensgeber hinzufügen",
+      "Attach Loan Originator": "Kreditvermittler hinzufügen",
src/assets/translations/fr-FR.json-1115-1115 (1)

1115-1115: ⚠️ Potential issue | 🟡 Minor

Unify French terminology for “Loan Originator”.

The new strings mix “courtiers en prêts”, “Agent de prêt”, and “initiateur de crédit”. This is inconsistent and risks confusion with “Loan Officer”. Please standardize to a single term across menu/labels/text.

💡 Suggested alignment (example)
-      "Loan Originator": "Agent de prêt",
+      "Loan Originator": "Courtier en prêts",
@@
-      "Attach Loan Originator": "Attacher l'initiateur de crédit",
+      "Attach Loan Originator": "Attacher le courtier en prêts",

Also applies to: 1981-1981, 2681-2681, 3188-3188

src/assets/translations/ne-NE.json-1979-1979 (1)

1979-1979: ⚠️ Potential issue | 🟡 Minor

Inconsistent terminology for "Loan Originator" translation.

This line translates "Loan Originator" as "ऋण प्रवर्तक" (prevertak), but all other related translations use "ऋण सुरुवातकर्ता" (suruwatkarta):

  • Line 1091: "Loan Originators" → "ऋण सुरुवातकर्ताहरू"
  • Line 1113: "Manage Loan Originators" → "ऋण सुरुवातकर्ताहरू व्यवस्थापन गर्नुहोस्"
  • Line 2679: "Attach Loan Originator" → "ऋण सुरुवातकर्ता संलग्न गर्नुहोस्"

For consistency across the UI, consider using "ऋण सुरुवातकर्ता" here as well.

🌐 Proposed fix for terminology consistency
-      "Loan Originator": "ऋण प्रवर्तक",
+      "Loan Originator": "ऋण सुरुवातकर्ता",
src/assets/translations/es-MX.json-1981-1981 (1)

1981-1981: ⚠️ Potential issue | 🟡 Minor

Prefer singular “crédito” for the singular label.

“Loan Originator” is singular, but the translation uses plural “Créditos”. Consider using singular and sentence case for natural Spanish.

Suggested update
-      "Loan Originator": "Originador de Créditos",
+      "Loan Originator": "Originador de crédito",
...
-      "Attach Loan Originator": "Adjuntar Originador de Créditos",
+      "Attach Loan Originator": "Adjuntar originador de crédito",

Also applies to: 2685-2685

src/assets/translations/es-MX.json-1114-1114 (1)

1114-1114: ⚠️ Potential issue | 🟡 Minor

Unify “préstamos” vs “créditos” for consistency.

These two strings use “préstamos” while most es-MX UI strings use “Crédito”. Consider aligning the terminology.

Suggested update
-      "Manage Loan Originators": "Administrar originadores de préstamos",
+      "Manage Loan Originators": "Administrar originadores de créditos",
...
-      "Loan Originators are associated with loan accounts": "Los originadores de préstamos están asociados con cuentas de préstamos",
+      "Loan Originators are associated with loan accounts": "Los originadores de créditos están asociados con cuentas de crédito",

Also applies to: 3193-3193

src/assets/translations/es-CL.json-1115-1115 (1)

1115-1115: ⚠️ Potential issue | 🟡 Minor

Unify “préstamos” vs “créditos” terminology for Loan Originator strings.

Right now the feature mixes “originadores de préstamos” with “Originador de Créditos,” which reads inconsistent in the same UI flow. Please pick one term and apply it across these keys (and ideally align with the existing “Loan Originators” label).

💡 Suggested alignment (toward “préstamos”)
-      "Loan Originator": "Originador de Créditos",
+      "Loan Originator": "Originador de préstamos",
@@
-      "Attach Loan Originator": "Adjuntar Originador de Créditos",
+      "Attach Loan Originator": "Adjuntar originador de préstamos",

Also applies to: 1981-1981, 2682-2682, 3190-3190

src/app/loans/loans.service.ts-661-669 (1)

661-669: ⚠️ Potential issue | 🟡 Minor

Remove the empty body argument from .delete() to match codebase patterns, and clarify intent for .post().

The second parameter to HttpClient.delete() is an options object, not a body. Passing an empty {} is ineffective. However, instead of using { body: {} }, align with the established codebase pattern: remove the second argument entirely (all other .delete() calls in the service have no options argument).

Additionally, attachLoanOriginator passing an empty body to .post() is inconsistent with all other POST calls in loans.service.ts, which pass actual data. Clarify whether these endpoints truly require no payload or if data is missing.

Suggested changes
  attachLoanOriginator(loanId: string, originatorId: string): Observable<any> {
-   const emptyBody = {};
-   return this.http.post(`/loans/${loanId}/originators/${originatorId}`, emptyBody);
+   return this.http.post(`/loans/${loanId}/originators/${originatorId}`, {});
  }

  dettachLoanOriginator(loanId: string, originatorId: string): Observable<any> {
-   const emptyBody = {};
-   return this.http.delete(`/loans/${loanId}/originators/${originatorId}`, emptyBody);
+   return this.http.delete(`/loans/${loanId}/originators/${originatorId}`);
  }
src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.html-37-48 (1)

37-48: ⚠️ Potential issue | 🟡 Minor

Guard nested CodeValue access against nulls.
If originatorType/channelType is missing, the template will throw. Consider safe navigation with a fallback.

Suggested fix
-        {{ item.originatorType.name }}
+        {{ item.originatorType?.name || '-' }}
...
-        {{ item.channelType.name }}
+        {{ item.channelType?.name || '-' }}
src/app/loans/loans-view/loan-account-actions/attach-originator/attach-originator.component.ts-49-57 (1)

49-57: ⚠️ Potential issue | 🟡 Minor

Guard against undefined data and remove debug logging.
dataObject.forEach will throw if the input is missing; also remove the console.log.

Suggested fix
-    console.log(this.dataObject);
-    this.loanOriginators = [];
-    this.dataObject.forEach((loanOriginator: LoanOriginator) => {
-      if (loanOriginator.status === 'ACTIVE') {
-        this.loanOriginators.push(loanOriginator);
-      }
-    });
+    const source = Array.isArray(this.dataObject) ? this.dataObject : [];
+    this.loanOriginators = source.filter((loanOriginator) => loanOriginator.status === 'ACTIVE');
src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.html-49-65 (1)

49-65: ⚠️ Potential issue | 🟡 Minor

Align tooltip/title text for the detach action.
The title says “Dettach” while the tooltip says “Delete.” Use one consistent label (and spelling) to avoid UX confusion.

Suggested fix
-            title="{{ 'tooltips.Dettach' | translate }}"
             mat-icon-button
-            matTooltip="{{ 'tooltips.Delete' | translate }}"
+            matTooltip="{{ 'tooltips.Dettach' | translate }}"
             *mifosxHasPermission="'DETTACH_LOAN_ORIGINATOR'"
src/app/organization/loan-originators/loan-originators.component.html-87-87 (1)

87-87: ⚠️ Potential issue | 🟡 Minor

Stop event propagation on delete button click.

The delete button is inside a clickable row with [routerLink]. Clicking delete will also trigger the row navigation. Add $event.stopPropagation() to prevent this.

🔧 Suggested fix
-            (click)="deleteLoanOriginator(loanOriginator)"
+            (click)="deleteLoanOriginator(loanOriginator); $event.stopPropagation()"
src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.ts-100-108 (1)

100-108: ⚠️ Potential issue | 🟡 Minor

Add error handling for the create operation.

The submit() method calls createLoanOriginator without handling potential errors. Failed API calls will silently fail without user feedback.

🔧 Suggested fix
   submit() {
     const loanOriginatorFormData = this.loanOriginatorForm.value;
     const data = {
       ...loanOriginatorFormData
     };
-    this.organizationService.createLoanOriginator(data).subscribe((response: any) => {
-      this.router.navigate(['../'], { relativeTo: this.route });
-    });
+    this.organizationService.createLoanOriginator(data).subscribe({
+      next: () => {
+        this.router.navigate(['../'], { relativeTo: this.route });
+      },
+      error: (error) => {
+        // Handle error - show notification to user
+        console.error('Failed to create loan originator:', error);
+      }
+    });
   }
src/app/organization/loan-originators/loan-originators.component.ts-134-140 (1)

134-140: ⚠️ Potential issue | 🟡 Minor

Add null check for dialog response and error handling for delete operation.

If the dialog is dismissed (e.g., clicking outside), response may be undefined, causing response.delete to throw. Also, the delete operation lacks error handling.

🔧 Suggested fix
   deleteCodeDialogRef.afterClosed().subscribe((response: any) => {
-      if (response.delete) {
+      if (response?.delete) {
         this.organizationService.deleteLoanOriginator(loanOriginator.id).subscribe(() => {
           this.router.navigate(['/organization/manage-loan-originators']);
+        }, (error) => {
+          // Error handling - could show a snackbar/toast notification
+          console.error('Failed to delete loan originator:', error);
         });
       }
     });
src/app/organization/loan-originators/loan-originators.component.html-67-75 (1)

67-75: ⚠️ Potential issue | 🟡 Minor

Add safe navigation for nested property access.

Accessing loanOriginator.originatorType.name and loanOriginator.channelType.name will throw if these nested objects are null/undefined. Use optional chaining for defensive access.

🛡️ Suggested fix
       <ng-container matColumnDef="originatorType">
         <th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Originator Type' | translate }}</th>
-        <td mat-cell *matCellDef="let loanOriginator">{{ loanOriginator.originatorType.name }}</td>
+        <td mat-cell *matCellDef="let loanOriginator">{{ loanOriginator.originatorType?.name }}</td>
       </ng-container>

       <ng-container matColumnDef="channelType">
         <th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Channel Type' | translate }}</th>
-        <td mat-cell *matCellDef="let loanOriginator">{{ loanOriginator.channelType.name }}</td>
+        <td mat-cell *matCellDef="let loanOriginator">{{ loanOriginator.channelType?.name }}</td>
       </ng-container>
src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.ts-74-80 (1)

74-80: ⚠️ Potential issue | 🟡 Minor

Name pattern excludes international characters.

The pattern (^[A-z]).* only allows names starting with ASCII letters A-Z or a-z. This excludes valid names with accented characters (e.g., "Ángel", "Müller", "Øystein"). Consider using a more inclusive Unicode pattern.

🔧 Suggested fix
       name: [
         '',
         [
           Validators.required,
-          Validators.pattern('(^[A-z]).*')
+          Validators.pattern('^[\\p{L}].*', 'u')
         ]
       ],

Note: Angular's pattern validator may need adjustment for Unicode support. Alternatively, use ^[a-zA-ZÀ-ÿ].* for common Latin characters or create a custom validator.

🧹 Nitpick comments (14)
src/app/loans/loans-view/loan-account-actions/attach-originator/attach-originator.component.html (1)

15-21: Use a stable track expression for the originator loop.

Tracking by object identity can cause unnecessary DOM churn if the array is rehydrated; track by id instead.

♻️ Suggested change
-            `@for` (loanOriginator of loanOriginators; track loanOriginator) {
+            `@for` (loanOriginator of loanOriginators; track loanOriginator.id) {

As per coding guidelines, for Angular code: verify component separation, trackBy on *ngFor, strict type safety, and clean observable patterns.

src/app/organization/loan-originators/view-loan-originator/view-loan-originator.component.ts (1)

28-41: Prefer a typed observable + async pipe to keep strict typing and avoid manual subscriptions.
This eliminates the uninitialized field and aligns with cleaner observable patterns.

♻️ Suggested refactor
-import { Component, inject } from '@angular/core';
+import { Component, inject } from '@angular/core';
+import { map } from 'rxjs';
 ...
 export class ViewLoanOriginatorComponent {
   private route = inject(ActivatedRoute);
-
-  /** Employee data. */
-  loanOriginatorData: LoanOriginator;
-
-  /**
-   * Retrieves the Loan Originator data from `resolve`.
-   * `@param` {ActivatedRoute} route Activated Route.
-   */
-  constructor() {
-    this.route.data.subscribe((data: { loanOriginatorData: LoanOriginator }) => {
-      this.loanOriginatorData = data.loanOriginatorData;
-    });
-  }
+  /** Loan originator data. */
+  readonly loanOriginatorData$ = this.route.data.pipe(
+    map((data: { loanOriginatorData: LoanOriginator }) => data.loanOriginatorData)
+  );
 }

As per coding guidelines, verify strict type safety and clean observable patterns.

src/app/organization/loan-originators/view-loan-originator/view-loan-originator.component.html (1)

16-59: If you adopt the async refactor, wrap the view in an @if guard to avoid undefined dereferences.

✅ Template adjustment example
-<div class="container">
+@if (loanOriginatorData$ | async; as loanOriginatorData) {
+<div class="container">
   <mat-card>
     <mat-card-content>
       ...
     </mat-card-content>
   </mat-card>
 </div>
+}

As per coding guidelines, ensure clean observable patterns and strict type safety in templates.

src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.html (1)

65-89: Track option rows by stable ids to avoid unnecessary re-renders.

♻️ Suggested tweak
-@for (originatorType of originatorTypeOptions; track originatorType) {
+@for (originatorType of originatorTypeOptions; track originatorType.id) {
   <mat-option [value]="originatorType.id">
     {{ originatorType.name }}
   </mat-option>
 }
 ...
-@for (channelTypeI of channelTypeOptions; track channelTypeI) {
+@for (channelTypeI of channelTypeOptions; track channelTypeI.id) {
   <mat-option [value]="channelTypeI.id">
     {{ channelTypeI.name }}
   </mat-option>
 }

As per coding guidelines, ensure trackBy usage on iteration blocks.

src/app/organization/loan-originators/loan-originators-template.resolver.ts (1)

25-31: Create a LoanOriginatorsTemplate interface and use it instead of Observable<any>.

No template model currently exists in the codebase. The template data is accessed in consuming components (edit-loan-originator, create-loan-originator) with known properties like statusOptions, originatorTypeOptions, channelTypeOptions, and externalId. Define a concrete interface for this shape and update the resolver and OrganizationService.getLoanOriginatorsTemplate() to return Observable<LoanOriginatorsTemplate> to enforce strict type safety as required by the coding guidelines for Angular code.

src/app/organization/loan-originators/loan-originators.resolver.ts (1)

30-37: Consider adding stricter type safety for the resolver return type.

The resolver returns Observable<any>, which loses type information. Consider using a union type or generic to preserve type safety.

♻️ Suggested type improvement
-  resolve(route: ActivatedRouteSnapshot): Observable<any> {
+  resolve(route: ActivatedRouteSnapshot): Observable<LoanOriginator | LoanOriginator[]> {
     const originatorId = route.paramMap.get('id');
     if (originatorId) {
       return this.organizationService.getLoanOriginator(originatorId);
     } else {
       return this.organizationService.getLoanOriginators();
     }
   }

This requires importing LoanOriginator from app/loans/models/loan-account.model.

src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.html (2)

68-72: Use track originatorType.id instead of tracking by object reference.

Tracking by object reference can cause unnecessary re-renders when the array is replaced with new objects that have the same data. Track by the unique id property instead.

♻️ Suggested fix
-            `@for` (originatorType of originatorTypeOptions; track originatorType) {
+            `@for` (originatorType of originatorTypeOptions; track originatorType.id) {

As per coding guidelines: "For Angular code: verify component separation, trackBy on *ngFor, strict type safety, and clean observable patterns."


85-89: Use track channelTypeI.id instead of tracking by object reference.

Same issue as originatorType - track by the unique id property for optimal change detection.

♻️ Suggested fix
-            `@for` (channelTypeI of channelTypeOptions; track channelTypeI) {
+            `@for` (channelType of channelTypeOptions; track channelType.id) {
               <mat-option [value]="channelTypeI.id">
                 {{ channelTypeI.name }}
               </mat-option>

Also consider renaming channelTypeI to channelType for consistency with the other loops.

As per coding guidelines: "For Angular code: verify component separation, trackBy on *ngFor, strict type safety, and clean observable patterns."

src/app/organization/loan-originators/loan-originators.component.html (1)

22-23: Add type assertion or null check for event target.

$event.target could be null, and accessing .value without type assertion may cause issues. Consider using a template reference variable or type assertion.

♻️ Suggested fix using template reference
-      <input matInput (keyup)="applyFilter($event.target.value)" />
+      <input matInput `#filterInput` (keyup)="applyFilter(filterInput.value)" />
src/app/organization/loan-originators/loan-originators.component.ts (2)

93-96: Remove unused ViewChild references if not needed.

tableLoanOriginators and templateTableLoanOriginators ViewChild references appear to be unused in the component. Consider removing them to reduce code clutter.


66-126: Consider adding a trackBy function for table performance.

The mat-table iterates over dataSource but no trackBy is configured. For better change detection performance, especially with larger datasets, add a trackBy function.

♻️ Suggested implementation

Add this method to the component:

trackByOriginatorId(index: number, loanOriginator: LoanOriginator): number {
  return loanOriginator.id;
}

Then in the template, use [trackBy]="trackByOriginatorId" on the mat-table (note: mat-table doesn't directly support trackBy, but the underlying MatTableDataSource handles this internally - consider this optional).

As per coding guidelines: "For Angular code: verify component separation, trackBy on *ngFor, strict type safety, and clean observable patterns."

src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.ts (1)

44-47: Remove unused ViewChild references.

createLoanOriginatorFormRef and templateCreateLoanOriginatorForm appear to be unused. Consider removing them to keep the component clean.

src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.ts (2)

11-44: Prefer typed reactive forms and a typed template-data shape.

Untyped forms plus any undermine strict type safety, which the Angular guideline calls out. A small typed form/interface will make this component safer and self-documenting.

♻️ Example typed-form refactor
-import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
+import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';

-  loanOriginatorForm: UntypedFormGroup;
-  loanOriginatorsTemplateData: any;
+  loanOriginatorForm: FormGroup<{
+    externalId: FormControl<string>;
+    name: FormControl<string>;
+    status: FormControl<string>;
+    originatorTypeId: FormControl<number>;
+    channelTypeId: FormControl<number>;
+  }>;
+  loanOriginatorsTemplateData: {
+    externalId?: string;
+    statusOptions: string[];
+    originatorTypeOptions: CodeValue[];
+    channelTypeOptions: CodeValue[];
+  };

51-66: Move the route-data subscription to ngOnInit and bind it to the destroy lifecycle.

This keeps constructor side‑effect‑free and aligns with clean observable patterns.

♻️ Example lifecycle-safe subscription
-import { Component, OnInit, TemplateRef, ElementRef, ViewChild, inject } from '@angular/core';
+import { Component, OnInit, TemplateRef, ElementRef, ViewChild, inject, DestroyRef } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

   private route = inject(ActivatedRoute);
   private router = inject(Router);
+  private destroyRef = inject(DestroyRef);

-  constructor() {
-    this.route.data.subscribe((data: { loanOriginatorData: LoanOriginator; loanOriginatorsTemplateData: any }) => {
-      this.loanOriginatorsData = data.loanOriginatorData;
-      this.loanOriginatorsTemplateData = data.loanOriginatorsTemplateData;
-      this.statusOptions = data.loanOriginatorsTemplateData.statusOptions;
-      this.originatorTypeOptions = data.loanOriginatorsTemplateData.originatorTypeOptions;
-      this.channelTypeOptions = data.loanOriginatorsTemplateData.channelTypeOptions;
-    });
-  }
+  constructor() {}
 
   ngOnInit() {
+    this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanOriginatorData: LoanOriginator; loanOriginatorsTemplateData: any }) => {
+      this.loanOriginatorsData = data.loanOriginatorData;
+      this.loanOriginatorsTemplateData = data.loanOriginatorsTemplateData;
+      this.statusOptions = data.loanOriginatorsTemplateData.statusOptions;
+      this.originatorTypeOptions = data.loanOriginatorsTemplateData.originatorTypeOptions;
+      this.channelTypeOptions = data.loanOriginatorsTemplateData.channelTypeOptions;
+    });
     this.createLoanOriginatorForm();
   }

Comment on lines +75 to +79
submit() {
const approveLoanFormData = this.attachLoanOriginatorForm.value;
this.loanService.attachLoanOriginator(this.loanId, approveLoanFormData.originatorId).subscribe((response: any) => {
this.router.navigate(['../../general'], { relativeTo: this.route });
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/app/loans/loans-view/loan-account-actions/attach-originator/attach-originator.component.ts | head -100

Repository: openMF/web-app

Length of output: 2998


🏁 Script executed:

rg -n "takeUntilDestroyed|take\(1\)|first\(" src/app -g '*.ts' | head -30

Repository: openMF/web-app

Length of output: 4364


Use takeUntilDestroyed for cleanup and validate loanId before submission.

The loanId is typed as string | null but used without validation. Additionally, add form validity check and error handling. Per codebase convention, use takeUntilDestroyed(this.destroyRef) instead of take(1) for proper lifecycle management.

Suggested fix
  submit() {
+   if (!this.loanId || this.attachLoanOriginatorForm.invalid) {
+     return;
+   }
    const approveLoanFormData = this.attachLoanOriginatorForm.value;
    this.loanService
      .attachLoanOriginator(this.loanId, approveLoanFormData.originatorId)
-     .subscribe((response: any) => {
+     .pipe(takeUntilDestroyed(this.destroyRef))
+     .subscribe({
+       next: () => {
          this.router.navigate(['../../general'], { relativeTo: this.route });
-     });
+       },
+       error: () => {
+         // TODO: surface error to user
+       }
+     });
  }

Import takeUntilDestroyed from @angular/core/rxjs-interop and inject DestroyRef.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
submit() {
const approveLoanFormData = this.attachLoanOriginatorForm.value;
this.loanService.attachLoanOriginator(this.loanId, approveLoanFormData.originatorId).subscribe((response: any) => {
this.router.navigate(['../../general'], { relativeTo: this.route });
});
submit() {
if (!this.loanId || this.attachLoanOriginatorForm.invalid) {
return;
}
const approveLoanFormData = this.attachLoanOriginatorForm.value;
this.loanService
.attachLoanOriginator(this.loanId, approveLoanFormData.originatorId)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => {
this.router.navigate(['../../general'], { relativeTo: this.route });
},
error: () => {
// TODO: surface error to user
}
});
}
🤖 Prompt for AI Agents
In
`@src/app/loans/loans-view/loan-account-actions/attach-originator/attach-originator.component.ts`
around lines 75 - 79, The submit method uses loanId without validating it and
does not use takeUntilDestroyed for subscription cleanup or check form validity;
update submit() in attach-originator.component.ts to first ensure
attachLoanOriginatorForm.valid and that this.loanId is non-null (return early
and surface an error if not), then call
this.loanService.attachLoanOriginator(this.loanId,
approveLoanFormData.originatorId).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(...)
to navigate on success and handle errors in the error handler (e.g., log or set
form error); import takeUntilDestroyed from `@angular/core/rxjs-interop` and
inject DestroyRef as destroyRef to enable this pattern.

Comment on lines 71 to 85
createLoanOriginatorForm() {
this.loanOriginatorForm = this.formBuilder.group({
externalId: [
this.loanOriginatorsTemplateData.externalId,
Validators.required
],
name: [
this.loanOriginatorsData.name,
[
Validators.required,
Validators.pattern('(^[A-z]).*')
]
],
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prefill externalId from the originator record (and tighten the name regex).

In an edit flow, Line 74 should use the existing record value; using template data can blank or reset the field. Also, [A-z] includes non-letters (e.g., [ and \).

🐛 Suggested fix
       externalId: [
-        this.loanOriginatorsTemplateData.externalId,
+        this.loanOriginatorsData.externalId,
         Validators.required
       ],
       name: [
         this.loanOriginatorsData.name,
         [
           Validators.required,
-          Validators.pattern('(^[A-z]).*')
+          Validators.pattern('^[A-Za-z].*')
         ]
       ],
🤖 Prompt for AI Agents
In
`@src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.ts`
around lines 71 - 83, In createLoanOriginatorForm replace the externalId initial
value to use the existing record (loanOriginatorsData.externalId) instead of
loanOriginatorsTemplateData.externalId so the edit form is prefilled with the
originator's value; also tighten the name Validators.pattern to only allow
letters at start (e.g. use /^[A-Za-z].*/ instead of '(^[A-z]).*') so non-letter
ASCII chars are excluded—update the loanOriginatorForm definition where
externalId and name validators are set and keep Validators.required intact.

Comment on lines +326 to +349
<div #manageLoanOriginators>
<mat-list-item *mifosxHasPermission="'READ_LOAN_ORIGINATOR'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['manage-loan-originators']">
<mat-icon matListIcon>
<fa-icon icon="edit" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Manage Loan Originators' | translate }}
@if (arrowBooleans[18]) {
<p matLine [routerLink]="['manage-loan-originators']" class="menu-explanation">
{{ 'labels.text.Loan Originators are associated with loan accounts' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[18]) {
<fa-icon (click)="arrowBooleansToggle(18)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[18]) {
<fa-icon (click)="arrowBooleansToggle(18)" icon="arrow-up" size="md"></fa-icon>
}
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the component TypeScript file and search for arrowBooleans
fd -a "organization.component.ts" | head -5

Repository: openMF/web-app

Length of output: 123


🏁 Script executed:

#!/bin/bash
# Search for arrowBooleans initialization and usage patterns
rg -n "arrowBooleans" --type ts -C 5 src/app/organization/

Repository: openMF/web-app

Length of output: 3589


🏁 Script executed:

#!/bin/bash
# Also check for any array initialization that might set the size
rg -n "new Array|arrowBooleans\s*=" --type ts src/app/organization/

Repository: openMF/web-app

Length of output: 393


Expand arrowBooleans array to support index 18.

The array at line 70 of organization.component.ts is initialized with only 18 elements (new Array(18).fill(false)), which creates indices 0–17. However, the new menu item uses arrowBooleans[18] at lines 341 and 344, which is out of bounds. Change the initialization to new Array(19).fill(false) to support index 18.

🤖 Prompt for AI Agents
In `@src/app/organization/organization.component.html` around lines 326 - 349, The
arrowBooleans array is initialized too short (only indices 0–17) but the
template references arrowBooleans[18]; update the initialization of the
arrowBooleans variable in organization.component.ts (where it’s currently set as
new Array(18).fill(false)) to allocate 19 entries (new Array(19).fill(false)) so
index 18 is valid, then run a quick build/test to ensure no other out-of-bounds
indices exist.

Comment on lines +852 to +875
getLoanOriginator(originatorId: string): Observable<any> {
return this.http.get(`/loan-originators/${originatorId}`);
}

/**
* @returns {Observable<any>}
*/
createLoanOriginator(payload: any): Observable<any> {
return this.http.post(`/loan-originators`, payload);
}

/**
* @returns {Observable<any>}
*/
updateLoanOriginator(originatorId: number, payload: any): Observable<any> {
return this.http.put(`/loan-originators/${originatorId}`, payload);
}

/**
* @returns {Observable<any>}
*/
deleteLoanOriginator(originatorId: number): Observable<any> {
return this.http.delete(`/loan-originators/${originatorId}`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inconsistent parameter types for originatorId across methods.

getLoanOriginator accepts string, while updateLoanOriginator and deleteLoanOriginator accept number. Route parameters are strings, so calling update/delete with a route param will cause type mismatch. Standardize to string or use template literal coercion consistently.

🔧 Suggested fix - standardize to string
   /**
    * `@returns` {Observable<any>}
    */
-  updateLoanOriginator(originatorId: number, payload: any): Observable<any> {
+  updateLoanOriginator(originatorId: string | number, payload: any): Observable<any> {
     return this.http.put(`/loan-originators/${originatorId}`, payload);
   }

   /**
    * `@returns` {Observable<any>}
    */
-  deleteLoanOriginator(originatorId: number): Observable<any> {
+  deleteLoanOriginator(originatorId: string | number): Observable<any> {
     return this.http.delete(`/loan-originators/${originatorId}`);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getLoanOriginator(originatorId: string): Observable<any> {
return this.http.get(`/loan-originators/${originatorId}`);
}
/**
* @returns {Observable<any>}
*/
createLoanOriginator(payload: any): Observable<any> {
return this.http.post(`/loan-originators`, payload);
}
/**
* @returns {Observable<any>}
*/
updateLoanOriginator(originatorId: number, payload: any): Observable<any> {
return this.http.put(`/loan-originators/${originatorId}`, payload);
}
/**
* @returns {Observable<any>}
*/
deleteLoanOriginator(originatorId: number): Observable<any> {
return this.http.delete(`/loan-originators/${originatorId}`);
}
getLoanOriginator(originatorId: string): Observable<any> {
return this.http.get(`/loan-originators/${originatorId}`);
}
/**
* `@returns` {Observable<any>}
*/
createLoanOriginator(payload: any): Observable<any> {
return this.http.post(`/loan-originators`, payload);
}
/**
* `@returns` {Observable<any>}
*/
updateLoanOriginator(originatorId: string | number, payload: any): Observable<any> {
return this.http.put(`/loan-originators/${originatorId}`, payload);
}
/**
* `@returns` {Observable<any>}
*/
deleteLoanOriginator(originatorId: string | number): Observable<any> {
return this.http.delete(`/loan-originators/${originatorId}`);
}
🤖 Prompt for AI Agents
In `@src/app/organization/organization.service.ts` around lines 852 - 875, The
three methods use inconsistent types for originatorId:
getLoanOriginator(originatorId: string) but updateLoanOriginator(originatorId:
number) and deleteLoanOriginator(originatorId: number); change
updateLoanOriginator and deleteLoanOriginator signatures to accept originatorId:
string (matching getLoanOriginator) so route params and template literals are
typed correctly, and update any callers to pass strings (or coerce to string
before calling) to avoid type mismatches.

@alberto-art3ch alberto-art3ch force-pushed the WEB-642/add-loan-originators-management branch from 163f75b to 1b9eb51 Compare February 5, 2026 22:44
Copy link

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/loans/models/loan-account.model.ts (1)

176-183: ⚠️ Potential issue | 🔴 Critical

Critical: Form components not updated to match interface changes.

The LoanOriginator interface correctly updated originatorTypeId and channelTypeId from primitive numbers to CodeValue objects. However, the form components in edit-loan-originator and create-loan-originator still use the old field names (originatorTypeId, channelTypeId) in their form groups. While the edit component correctly extracts .id from the new CodeValue objects, the form submission will fail because the field names don't match the updated interface structure. Both form components must be updated to use the new field names or properly map the form data during submission.

🤖 Fix all issues with AI agents
In
`@src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts`:
- Around line 98-104: The afterClosed subscription on dettachCodeDialogRef can
receive undefined when the dialog is dismissed, so update the handler in
loan-originators-tab.component (the dettachCodeDialogRef.afterClosed().subscribe
callback) to first check that response is truthy (e.g., if (response &&
response.confirm)) before accessing response.confirm, and only call
this.loansService.dettachLoanOriginator(this.loanId,
String(loanOriginator.id)).subscribe(...) and this.reload() when that check
passes; keep the existing dettachLoanOriginator and reload calls intact and
scoped inside the guarded branch.

In `@src/app/organization/loan-originators/loan-originators.component.ts`:
- Around line 134-140: The dialog close handler may receive undefined if the
user dismisses the dialog, so update the deleteCodeDialogRef.afterClosed()
subscription to guard against a null/undefined response before accessing
response.delete; specifically, in the callback that currently calls
this.organizationService.deleteLoanOriginator(loanOriginator.id) and
this.router.navigate(...), check that response exists (e.g., response &&
response.delete or response?.delete) and only invoke
deleteLoanOriginator(loanOriginator.id) when that condition is true to avoid a
runtime TypeError.

In `@src/assets/translations/es-MX.json`:
- Line 1114: Two translation entries use "préstamos" instead of the locale's
consistent "Crédito": update the value for the key "Manage Loan Originators"
(currently "Administrar originadores de préstamos") and the other affected key
at the same locale (line referenced 3193) to replace "préstamos" with "Crédito"
(e.g., "Administrar originadores de Crédito") so the terminology matches
existing strings like "Cuentas de Crédito".

In `@src/assets/translations/fr-FR.json`:
- Line 1115: Replace mixed French terms for "Loan Originator" with a single
consistent term across the translations: use "Initiateur de prêt" (plural
"Initiateurs de prêt") and update the value for the key "Manage Loan
Originators" to "Gérer les initiateurs de prêt", then find and replace all other
occurrences (the other related strings mentioned) so every related key/value
uses "Initiateur(s) de prêt" consistently instead of "courtiers en prêts",
"Agent de prêt", or "initiateur de crédit".

In `@src/assets/translations/pt-PT.json`:
- Line 1980: The translation value for the "Loan Originator" key uses sentence
case ("Originador de empréstimo") but should match title case like the action
label; update the value for the "Loan Originator" key to "Originador de
Empréstimo" (and scan nearby related keys to ensure consistent title‑case
styling across similar labels such as other role/action keys).
- Line 1114: The translation for "Manage Loan Originators" uses "Originadores de
Empréstimos" while the existing key "Loan Originators" (key: "Loan Originators")
uses "Agentes de Crédito"; pick one term and make them consistent by updating
the "Manage Loan Originators" value (and any other related keys/descriptive
sentences referencing loan originators) to use the chosen term (e.g., change
"Manage Loan Originators" translation to "Gerir Agentes de Crédito" if you
standardize on "Agentes de Crédito"), ensuring pluralization and verb form match
other entries.
🧹 Nitpick comments (8)
src/app/organization/loan-originators/view-loan-originator/view-loan-originator.component.html (1)

48-58: Add null-safe navigation for nested properties.

Accessing originatorType.name and channelType.name without optional chaining will throw a runtime error if these objects are null or undefined. Use Angular's safe navigation operator for defensive coding.

♻️ Proposed fix
        <div class="flex-50">
-          {{ loanOriginatorData.originatorType.name }}
+          {{ loanOriginatorData.originatorType?.name }}
        </div>

        <div class="flex-50 mat-body-strong">
          {{ 'labels.inputs.Channel Type' | translate }}
        </div>

        <div class="flex-50">
-          {{ loanOriginatorData.channelType.name }}
+          {{ loanOriginatorData.channelType?.name }}
        </div>

As per coding guidelines, Angular code should maintain strict type safety.

src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts (3)

86-86: Typo: "dettach" should be "detach".

The method name dettachLoanOriginator contains a spelling error. This also appears to propagate to the service method name on line 100.

✏️ Proposed fix
-  dettachLoanOriginator(loanOriginator: LoanOriginator): void {
-    const dettachCodeDialogRef = this.dialog.open(ConfirmationDialogComponent, {
+  detachLoanOriginator(loanOriginator: LoanOriginator): void {
+    const detachCodeDialogRef = this.dialog.open(ConfirmationDialogComponent, {

Also update line 98:

-    dettachCodeDialogRef.afterClosed().subscribe((response: any) => {
+    detachCodeDialogRef.afterClosed().subscribe((response: any) => {

78-83: Subscriptions in constructor are not cleaned up.

The subscriptions to route.parent.parent.data and route.parent.data are not unsubscribed when the component is destroyed. While route data observables often complete when navigation occurs, explicitly managing these subscriptions or using takeUntilDestroyed() ensures proper cleanup.

♻️ Proposed fix using takeUntilDestroyed
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

 export class LoanOriginatorsTabComponent {
+  private destroyRef = inject(DestroyRef);
   // ...

   constructor() {
     this.clientId = this.route.parent.parent.snapshot.paramMap.get('clientId');
     this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
-    this.route.parent.parent.data.subscribe((data: { loanDetailsData: any }) => {
+    this.route.parent.parent.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanDetailsData: any }) => {
       this.loanStatus = data.loanDetailsData.status;
     });
-    this.route.parent.data.subscribe((data: { loanOriginatorsData: any }) => {
+    this.route.parent.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanOriginatorsData: any }) => {
       this.loanOriginatorsData = data.loanOriginatorsData.originators;
     });
   }

76-77: Inconsistent null-safety on parent route access.

Line 76 accesses this.route.parent.parent.snapshot without optional chaining, while line 77 uses this.route.parent?.parent?.snapshot. This inconsistency could lead to runtime errors if the route hierarchy is different than expected.

♻️ Proposed fix for consistency
-    this.clientId = this.route.parent.parent.snapshot.paramMap.get('clientId');
-    this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
+    this.clientId = this.route.parent?.parent?.snapshot.paramMap.get('clientId') ?? null;
+    this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId') ?? null;
src/app/organization/loan-originators/loan-originators.component.ts (2)

86-86: Use strict typing for MatTableDataSource.

Replace any with LoanOriginator to maintain type safety consistent with the loanOriginatorsData property.

♻️ Suggested fix
   /** Data source for Loan Originators table. */
-  dataSource: MatTableDataSource<any>;
+  dataSource: MatTableDataSource<LoanOriginator>;

As per coding guidelines: "For Angular code: verify ... strict type safety."


98-102: Unsubscribe from route data observable to avoid memory leaks.

The subscription in the constructor should be cleaned up when the component is destroyed. Consider using takeUntilDestroyed() or storing the subscription and unsubscribing in ngOnDestroy.

♻️ Suggested fix using `takeUntilDestroyed()`
+import { Component, OnInit, TemplateRef, ElementRef, ViewChild, inject, DestroyRef } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
...

 export class LoanOriginatorsComponent implements OnInit {
   private route = inject(ActivatedRoute);
   private router = inject(Router);
+  private destroyRef = inject(DestroyRef);
   ...

   constructor() {
-    this.route.data.subscribe((data: { loanOriginatorsData: LoanOriginator[] }) => {
+    this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanOriginatorsData: LoanOriginator[] }) => {
       this.loanOriginatorsData = data.loanOriginatorsData;
     });
   }

As per coding guidelines: "For Angular code: verify ... clean observable patterns."

src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.ts (2)

41-41: Add proper typing for loanOriginatorsTemplateData.

Using any reduces type safety. Consider defining an interface for the template data structure.

♻️ Suggested improvement
+interface LoanOriginatorTemplateData {
+  externalId?: string;
+  statusOptions: string[];
+  originatorTypeOptions: CodeValue[];
+  channelTypeOptions: CodeValue[];
+}
+
 export class EditLoanOriginatorComponent implements OnInit {
   ...
-  loanOriginatorsTemplateData: any;
+  loanOriginatorsTemplateData: LoanOriginatorTemplateData;

As per coding guidelines: "For Angular code: verify ... strict type safety."


51-59: Unsubscribe from route data observable to avoid memory leaks.

Same pattern as the list component—consider using takeUntilDestroyed() for clean observable management.

♻️ Suggested fix
+import { Component, OnInit, TemplateRef, ElementRef, ViewChild, inject, DestroyRef } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
...

 export class EditLoanOriginatorComponent implements OnInit {
   private formBuilder = inject(UntypedFormBuilder);
   private organizationService = inject(OrganizationService);
   private route = inject(ActivatedRoute);
   private router = inject(Router);
+  private destroyRef = inject(DestroyRef);
   ...

   constructor() {
-    this.route.data.subscribe((data: { loanOriginatorData: LoanOriginator; loanOriginatorsTemplateData: any }) => {
+    this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanOriginatorData: LoanOriginator; loanOriginatorsTemplateData: any }) => {
       this.loanOriginatorsData = data.loanOriginatorData;
       ...
     });
   }

As per coding guidelines: "For Angular code: verify ... clean observable patterns."

Comment on lines +98 to +104
dettachCodeDialogRef.afterClosed().subscribe((response: any) => {
if (response.confirm) {
this.loansService.dettachLoanOriginator(this.loanId, String(loanOriginator.id)).subscribe((response) => {
this.reload();
});
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsafe access to response.confirm when dialog is dismissed.

If the user dismisses the dialog by clicking outside or pressing Escape, response may be undefined, causing a runtime error when accessing response.confirm.

🐛 Proposed fix
     dettachCodeDialogRef.afterClosed().subscribe((response: any) => {
-      if (response.confirm) {
+      if (response?.confirm) {
         this.loansService.dettachLoanOriginator(this.loanId, String(loanOriginator.id)).subscribe((response) => {
           this.reload();
         });
       }
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dettachCodeDialogRef.afterClosed().subscribe((response: any) => {
if (response.confirm) {
this.loansService.dettachLoanOriginator(this.loanId, String(loanOriginator.id)).subscribe((response) => {
this.reload();
});
}
});
dettachCodeDialogRef.afterClosed().subscribe((response: any) => {
if (response?.confirm) {
this.loansService.dettachLoanOriginator(this.loanId, String(loanOriginator.id)).subscribe((response) => {
this.reload();
});
}
});
🤖 Prompt for AI Agents
In
`@src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts`
around lines 98 - 104, The afterClosed subscription on dettachCodeDialogRef can
receive undefined when the dialog is dismissed, so update the handler in
loan-originators-tab.component (the dettachCodeDialogRef.afterClosed().subscribe
callback) to first check that response is truthy (e.g., if (response &&
response.confirm)) before accessing response.confirm, and only call
this.loansService.dettachLoanOriginator(this.loanId,
String(loanOriginator.id)).subscribe(...) and this.reload() when that check
passes; keep the existing dettachLoanOriginator and reload calls intact and
scoped inside the guarded branch.

Comment on lines +134 to +142
deleteCodeDialogRef.afterClosed().subscribe((response: any) => {
if (response.delete) {
this.organizationService.deleteLoanOriginator(loanOriginator.id).subscribe(() => {
this.router.navigate(['/organization/manage-loan-originators']);
});
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add null check for dialog response to prevent runtime error.

If the user dismisses the dialog by clicking outside or pressing Escape, response may be undefined, causing response.delete to throw a TypeError.

🐛 Suggested fix
     deleteCodeDialogRef.afterClosed().subscribe((response: any) => {
-      if (response.delete) {
+      if (response?.delete) {
         this.organizationService.deleteLoanOriginator(loanOriginator.id).subscribe(() => {
           this.router.navigate(['/organization/manage-loan-originators']);
         });
       }
     });
🤖 Prompt for AI Agents
In `@src/app/organization/loan-originators/loan-originators.component.ts` around
lines 134 - 140, The dialog close handler may receive undefined if the user
dismisses the dialog, so update the deleteCodeDialogRef.afterClosed()
subscription to guard against a null/undefined response before accessing
response.delete; specifically, in the callback that currently calls
this.organizationService.deleteLoanOriginator(loanOriginator.id) and
this.router.navigate(...), check that response exists (e.g., response &&
response.delete or response?.delete) and only invoke
deleteLoanOriginator(loanOriginator.id) when that condition is true to avoid a
runtime TypeError.

"Manage Holidays": "Administrar festivos",
"Manage Hooks": "Administrar ganchos",
"Manage Jobs": "Administrar procesos",
"Manage Loan Originators": "Administrar originadores de préstamos",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Align “Loan” terminology with existing “Crédito” usage.

Line 1114 and Line 3193 use “préstamos”, while the rest of this locale consistently uses “Crédito” (e.g., “Cuentas de Crédito”). Please standardize to avoid mixed terminology.

Suggested diff
-      "Manage Loan Originators": "Administrar originadores de préstamos",
+      "Manage Loan Originators": "Administrar originadores de créditos",
...
-      "Loan Originators are associated with loan accounts": "Los originadores de préstamos están asociados con cuentas de préstamos",
+      "Loan Originators are associated with loan accounts": "Los originadores de créditos están asociados con cuentas de crédito",

Also applies to: 3193-3193

🤖 Prompt for AI Agents
In `@src/assets/translations/es-MX.json` at line 1114, Two translation entries use
"préstamos" instead of the locale's consistent "Crédito": update the value for
the key "Manage Loan Originators" (currently "Administrar originadores de
préstamos") and the other affected key at the same locale (line referenced 3193)
to replace "préstamos" with "Crédito" (e.g., "Administrar originadores de
Crédito") so the terminology matches existing strings like "Cuentas de Crédito".

"Manage Holidays": "Gérer les jours fériés",
"Manage Hooks": "Gérer les hooks",
"Manage Jobs": "Gérer les travaux",
"Manage Loan Originators": "Gérer les courtiers en prêts",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Standardize the French term for “Loan Originator” to avoid user confusion.

These entries mix “courtiers en prêts”, “Agent de prêt”, and “initiateur de crédit”, which is inconsistent and overlaps with “Loan Officer.” Please choose one distinct term and use it consistently across all related strings.

✍️ Example consistency update (pick the final term you prefer)
-      "Manage Loan Originators": "Gérer les courtiers en prêts",
+      "Manage Loan Originators": "Gérer les originateurs de prêts",

-      "Loan Originator": "Agent de prêt",
+      "Loan Originator": "Originateur de prêt",

-      "Attach Loan Originator": "Attacher l'initiateur de crédit",
+      "Attach Loan Originator": "Attacher l'originateur de prêt",

-      "Loan Originators are associated with loan accounts": "Les courtiers en prêts sont associés aux comptes de prêt.",
+      "Loan Originators are associated with loan accounts": "Les originateurs de prêts sont associés aux comptes de prêt."

Also applies to: 1981-1981, 2681-2681, 3188-3188

🤖 Prompt for AI Agents
In `@src/assets/translations/fr-FR.json` at line 1115, Replace mixed French terms
for "Loan Originator" with a single consistent term across the translations: use
"Initiateur de prêt" (plural "Initiateurs de prêt") and update the value for the
key "Manage Loan Originators" to "Gérer les initiateurs de prêt", then find and
replace all other occurrences (the other related strings mentioned) so every
related key/value uses "Initiateur(s) de prêt" consistently instead of
"courtiers en prêts", "Agent de prêt", or "initiateur de crédit".

"Manage Holidays": "Gerenciar feriados",
"Manage Hooks": "Gerenciar ganchos",
"Manage Jobs": "Gerenciar trabalhos",
"Manage Loan Originators": "Gerenciar Originadores de Empréstimos",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Standardize terminology for “Loan Originator(s)”.

This new label uses “Originadores de Empréstimos”, but the existing key “Loan Originators” (Line 1092) is translated as “Agentes de Crédito”. Please choose one term and align all related keys (including this new one and the descriptive sentence) to avoid inconsistent UI wording.

🤖 Prompt for AI Agents
In `@src/assets/translations/pt-PT.json` at line 1114, The translation for "Manage
Loan Originators" uses "Originadores de Empréstimos" while the existing key
"Loan Originators" (key: "Loan Originators") uses "Agentes de Crédito"; pick one
term and make them consistent by updating the "Manage Loan Originators" value
(and any other related keys/descriptive sentences referencing loan originators)
to use the chosen term (e.g., change "Manage Loan Originators" translation to
"Gerir Agentes de Crédito" if you standardize on "Agentes de Crédito"), ensuring
pluralization and verb form match other entries.

"Loan Documents": "Documentos de empréstimo",
"Loan Id": "ID do empréstimo",
"Loan Officer": "Oficial de Empréstimo",
"Loan Originator": "Originador de empréstimo",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Match title‑case styling for the label.

Here “empréstimo” is lower‑case, while the action label uses title case (“Originador de Empréstimo”). Consider aligning for UI consistency.

✅ Suggested adjustment
-      "Loan Originator": "Originador de empréstimo",
+      "Loan Originator": "Originador de Empréstimo",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Loan Originator": "Originador de empréstimo",
"Loan Originator": "Originador de Empréstimo",
🤖 Prompt for AI Agents
In `@src/assets/translations/pt-PT.json` at line 1980, The translation value for
the "Loan Originator" key uses sentence case ("Originador de empréstimo") but
should match title case like the action label; update the value for the "Loan
Originator" key to "Originador de Empréstimo" (and scan nearby related keys to
ensure consistent title‑case styling across similar labels such as other
role/action keys).

@alberto-art3ch alberto-art3ch force-pushed the WEB-642/add-loan-originators-management branch from 1b9eb51 to 5dcbdf5 Compare February 6, 2026 15:18
Copy link
Collaborator

@adamsaghy adamsaghy left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link

@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: 16

🤖 Fix all issues with AI agents
In
`@src/app/loans/loans-view/loan-account-actions/attach-originator/attach-originator.component.ts`:
- Line 51: Remove the debug console.log statement that prints this.dataObject in
attach-originator.component.ts; locate the console.log(this.dataObject) call
(inside the AttachOriginatorComponent or its initialization method) and delete
it, or replace it with a proper structured logger call if runtime logging is
required (e.g., use the application's Logger service and ensure sensitive data
in dataObject is not logged).

In
`@src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.html`:
- Around line 52-60: The button shown when loanStatus.pendingApproval has
inconsistent tooltip text—title uses "Dettach" and matTooltip uses "Delete"—so
remove one tooltip attribute and standardize the text: fix the spelling to
"Detach" and use the same translate key for the remaining tooltip (e.g., replace
title and matTooltip with a single matTooltip="{{ 'tooltips.Detach' | translate
}}" on the button that also keeps the
*mifosxHasPermission="'DETTACH_LOAN_ORIGINATOR'" attribute). Ensure only one
tooltip attribute remains and that it references the consistent translate key.

In
`@src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts`:
- Line 86: The method name dettachLoanOriginator is misspelled and the inline
comment contains "fot" — rename dettachLoanOriginator to detachLoanOriginator in
the component and update all callers/usages (including
LoansService.dettachLoanOriginator if it exists) to the new name to keep
consistency, and fix the comment text "fot" to "for"; search for
"dettachLoanOriginator" across the codebase and refactor both definition and
references to avoid breakage.
- Around line 110-115: The reload() method can navigate to
`/clients/null/loans-accounts` when this.clientId is null; add a guard in the
reload() function to check that this.clientId is non-null/undefined (and
non-empty if desired) before calling
router.navigateByUrl(`/clients/${this.clientId}/loans-accounts`, ...); if
clientId is missing, either skip the navigation or use a safe fallback route
(e.g., the parent clients list), and ensure subsequent router.navigate([url]) is
only invoked when the first navigation succeeded.
- Around line 75-83: The constructor uses this.route.parent.parent without
null-safety causing potential crashes; update it to consistently guard
route.parent and route.parent.parent (e.g., read const grandParent =
this.route.parent?.parent and const parent = this.route.parent) and then use
grandParent?.snapshot?.paramMap.get('clientId') and
grandParent?.snapshot?.paramMap.get('loanId') or bail out if grandParent is
undefined; likewise subscribe only when parent or grandParent exists (use
parent?.data.subscribe(...) and grandParent?.data.subscribe(...) or an if-check)
so that clientId, loanId, loanStatus and loanOriginatorsData assignments and
subscriptions (refer to constructor, loanStatus, loanOriginatorsData, clientId,
loanId) are guarded against null parents.

In `@src/app/loans/loans.service.ts`:
- Around line 661-669: The DELETE call in dettachLoanOriginator currently passes
an unnecessary empty options/body object; remove the emptyBody constant and call
this.http.delete(`/loans/${loanId}/originators/${originatorId}`) with no second
argument so it matches other methods like deleteGuarantor and deleteLoanNote in
the service (update the dettachLoanOriginator function accordingly).

In
`@src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.ts`:
- Around line 74-79: The Validators.pattern on the form control "name" is using
the incorrect regex '(^[A-z]).*' which matches non-letter ASCII characters;
update the pattern used in the Validators.pattern call in
create-loan-originator.component.ts (the "name" control definition) to use
'^[A-Za-z].*' (or equivalent) so it only allows names that start with an ASCII
letter; keep the existing Validators.required and overall validator array
intact.
- Line 10: Remove the unused ViewChild property named
templateCreateLoanOriginatorForm from the component: delete the declaration
decorated with `@ViewChild`('templateCreateLoanOriginatorForm') and its associated
property (templateCreateLoanOriginatorForm: TemplateRef<any> | undefined) so the
component no longer contains an unused member; keep the other ViewChild,
ElementRef and TemplateRef usages/imports intact since they are referenced
elsewhere.

In
`@src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.ts`:
- Around line 20-21: The file-level JSDoc comment is a leftover saying "Create
Loan Originator component" but this file implements the Edit component; update
the top comment to correctly describe the component (e.g., "Edit Loan Originator
component" or remove the incorrect doc comment) so it matches the
EditLoanOriginatorComponent (or the exported class/component name) in
edit-loan-originator.component.ts.

In
`@src/app/organization/loan-originators/view-loan-originator/view-loan-originator.component.ts`:
- Around line 19-27: The `@Component` decorator for ViewLoanOriginatorComponent
declares an imports array (FaIconComponent and STANDALONE_SHARED_IMPORTS) which
only works for standalone components; add standalone: true to the component
metadata so Angular treats ViewLoanOriginatorComponent as standalone and the
imports are applied correctly (update the `@Component` config for the class
declared in view-loan-originator.component.ts).
- Around line 28-41: The component currently subscribes to this.route.data in
the constructor without teardown and declares loanOriginatorData uninitialized;
change loanOriginatorData's type to LoanOriginator | null and initialize it to
null, then pipe the route.data subscription through takeUntilDestroyed() (e.g.,
this.route.data.pipe(takeUntilDestroyed()).subscribe(...)) so the subscription
is automatically cleaned up; update the constructor subscription code using
takeUntilDestroyed() and adjust any template usage to handle the nullable
loanOriginatorData.

In `@src/assets/translations/cs-CS.json`:
- Line 2682: The Czech translations use both "půjčka"/"půjček" and
"úvěr"/"úvěrů" for "Loan" which is inconsistent; pick one term (recommend
"úvěr") and update the translation values for the affected keys such as "Attach
Loan Originator" (currently "Připojit poskytovatele půjčky"), the occurrences
translated as "Poskytovatel úvěrů" and "Poskytovatelé půjček" so they all use
the same form (e.g., "Připojit poskytovatele úvěru", "Poskytovatel úvěrů",
"Poskytovatelé úvěrů"), then run a quick grep across the translations file to
ensure no other "půjčka"/"půjček" remain for loan-originator related keys.

In `@src/assets/translations/ko-KO.json`:
- Line 1982: The translation for the key "Loan Originator" currently duplicates
"Loan Officer" ("대출 담당자"); update the "Loan Originator" value to a distinct
Korean term (e.g., "대출 개시자" or "대출 발행기관") to reflect that originators are
organizational entities, and apply the same change for every occurrence of the
"Loan Originator" key in this file (also present near the other occurrences
noted: the entries at/from lines referenced as 1115, 2682, and 3189) so all
instances consistently use the new distinct translation while leaving "Loan
Officer" ("대출 담당자") unchanged.

In `@src/assets/translations/lv-LV.json`:
- Around line 3773-3775: Update the Latvian translation value for the "INACTIVE"
key from "NEAKTIVS" to "NEAKTĪVS" so it matches the diacritic used in "AKTĪVS";
locate the "INACTIVE" JSON entry and replace the string, ensuring the file
remains UTF-8 encoded to preserve the Ī character.

In `@src/assets/translations/ne-NE.json`:
- Line 1979: Replace the inconsistent Nepali translation for the "Loan
Originator" key in the translations file: change the value "ऋण प्रवर्तक" to
match the other originator-related keys by using "ऋण सुरुवातकर्ता" so the UI
uses the same Nepali term for "Originator" across all keys (i.e., update the
translation value for the "Loan Originator" string to "ऋण सुरुवातकर्ता").
- Around line 3770-3773: Remove the unused tooltip translation keys "ACTIVE",
"PENDING", and "INACTIVE" from the tooltips section in the ne-NE JSON (the
entries with keys ACTIVE, PENDING, INACTIVE); delete those three key/value
lines, then run a quick repo-wide search for usages of tooltips.ACTIVE,
tooltips.PENDING, and tooltips.INACTIVE to confirm they aren't referenced and,
if you have an i18n extraction/lint step, run it to ensure no missing-key errors
are introduced.
🧹 Nitpick comments (7)
src/assets/translations/cs-CS.json (1)

3772-3775: Status enum translations placed in the wrong section.

ACTIVE, PENDING, and INACTIVE are status labels, not tooltip explanations. The tooltips section contains descriptive/explanatory text (e.g., "A borrower loan counter (cycle) ..."). These status values would be better placed in labels.catalogs (which already contains similar enum-like values) or labels.status for consistency with the file's organizational convention.

Also note that "Create Interest Refund" on Line 3772 appears unrelated to the loan originator feature—verify this was intentionally included in this PR.

src/app/organization/organization.service.ts (1)

838-874: Type Loan Originator endpoints with domain models.

Returning Observable<any> (and accepting any payloads) weakens compile-time checks for new APIs. Consider using the LoanOriginator model (and DTOs/Partial types) for stricter typing. As per coding guidelines, verify component separation, trackBy on *ngFor, strict type safety, and clean observable patterns.

♻️ Suggested refactor
+import { LoanOriginator } from 'app/loans/models/loan-account.model';
...
-  getLoanOriginators(): Observable<any> {
+  getLoanOriginators(): Observable<LoanOriginator[]> {
     return this.http.get('/loan-originators');
   }
...
-  getLoanOriginator(originatorId: string): Observable<any> {
+  getLoanOriginator(originatorId: string): Observable<LoanOriginator> {
     return this.http.get(`/loan-originators/${originatorId}`);
   }
...
-  createLoanOriginator(payload: any): Observable<any> {
+  createLoanOriginator(payload: Partial<LoanOriginator>): Observable<LoanOriginator> {
     return this.http.post(`/loan-originators`, payload);
   }
...
-  updateLoanOriginator(originatorId: number, payload: any): Observable<any> {
+  updateLoanOriginator(originatorId: number, payload: Partial<LoanOriginator>): Observable<LoanOriginator> {
     return this.http.put(`/loan-originators/${originatorId}`, payload);
   }
...
-  deleteLoanOriginator(originatorId: number): Observable<any> {
+  deleteLoanOriginator(originatorId: number): Observable<void> {
     return this.http.delete(`/loan-originators/${originatorId}`);
   }
src/app/organization/loan-originators/loan-originators.component.ts (1)

87-128: Strongly type the table data source.

Use MatTableDataSource<LoanOriginator> to keep filtering/sorting and column bindings type-safe. As per coding guidelines, verify component separation, trackBy on *ngFor, strict type safety, and clean observable patterns.

♻️ Suggested refactor
-  dataSource: MatTableDataSource<any>;
+  dataSource: MatTableDataSource<LoanOriginator>;
...
-    this.dataSource = new MatTableDataSource(this.loanOriginatorsData);
+    this.dataSource = new MatTableDataSource<LoanOriginator>(this.loanOriginatorsData);
src/app/organization/loan-originators/loan-originators.resolver.ts (1)

22-36: Tighten resolver typing (avoid Observable<any>).

Consider implementing Resolve<LoanOriginator | LoanOriginator[]> and returning a typed observable to align with strict typing.

🔧 Example adjustment
-import { Injectable, inject } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
+import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
+import { LoanOriginator } from 'app/loans/models/loan-account.model';
 ...
-import { ActivatedRouteSnapshot } from '@angular/router';
 ...
-export class LoanOriginatorsResolver {
+export class LoanOriginatorsResolver implements Resolve<LoanOriginator | LoanOriginator[]> {
   private organizationService = inject(OrganizationService);
 ...
-  resolve(route: ActivatedRouteSnapshot): Observable<any> {
+  resolve(route: ActivatedRouteSnapshot): Observable<LoanOriginator | LoanOriginator[]> {
     const originatorId = route.paramMap.get('id');
     if (originatorId) {
       return this.organizationService.getLoanOriginator(originatorId);
     } else {
       return this.organizationService.getLoanOriginators();
     }
   }
 }
As per coding guidelines, "For Angular code: verify component separation, trackBy on *ngFor, strict type safety, and clean observable patterns."
src/app/loans/loans-view/loan-account-actions/attach-originator/attach-originator.component.ts (1)

49-57: Guard against dataObject being null/undefined and prefer Array.filter.

If the parent component doesn't pass dataObject or the resolver data is missing, this.dataObject.forEach(...) will throw a runtime error. Additionally, .filter() is more idiomatic than manual push.

♻️ Proposed fix
   ngOnInit() {
     this.setAttachLoanOriginatorForm();
-    console.log(this.dataObject);
-    this.loanOriginators = [];
-    this.dataObject.forEach((loanOriginator: LoanOriginator) => {
-      if (loanOriginator.status === 'ACTIVE') {
-        this.loanOriginators.push(loanOriginator);
-      }
-    });
+    this.loanOriginators = (this.dataObject ?? []).filter(
+      (lo: LoanOriginator) => lo.status === 'ACTIVE'
+    );
   }
src/app/organization/loan-originators/create-loan-originator/create-loan-originator.component.ts (1)

98-106: No error handling on createLoanOriginator subscription.

If the API call fails, the user receives no feedback. Consider adding an error handler or using a global error interceptor pattern.

src/app/organization/loan-originators/edit-loan-originator/edit-loan-originator.component.ts (1)

103-106: The delete loanOriginatorFormData.externalId is a no-op — disabled controls are excluded from FormGroup.value.

Since externalId is disabled (Line 76), this.loanOriginatorForm.value already omits it. The delete statement is redundant. Not harmful, but can be cleaned up for clarity.


ngOnInit() {
this.setAttachLoanOriginatorForm();
console.log(this.dataObject);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove console.log debug statement.

This debug log should not ship to production.

🧹 Proposed fix
   ngOnInit() {
     this.setAttachLoanOriginatorForm();
-    console.log(this.dataObject);
     this.loanOriginators = [];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(this.dataObject);
ngOnInit() {
this.setAttachLoanOriginatorForm();
this.loanOriginators = [];
🤖 Prompt for AI Agents
In
`@src/app/loans/loans-view/loan-account-actions/attach-originator/attach-originator.component.ts`
at line 51, Remove the debug console.log statement that prints this.dataObject
in attach-originator.component.ts; locate the console.log(this.dataObject) call
(inside the AttachOriginatorComponent or its initialization method) and delete
it, or replace it with a proper structured logger call if runtime logging is
required (e.g., use the application's Logger service and ensure sensitive data
in dataObject is not logged).

Comment on lines +52 to +60
@if (loanStatus.pendingApproval) {
<button
type="button"
color="warn"
title="{{ 'tooltips.Dettach' | translate }}"
mat-icon-button
matTooltip="{{ 'tooltips.Delete' | translate }}"
*mifosxHasPermission="'DETTACH_LOAN_ORIGINATOR'"
matTooltipPosition="above"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a single tooltip message for the detach action.

title shows “Dettach” while matTooltip shows “Delete,” which is confusing for a detach action. Keep one, consistent tooltip string.

💡 Suggested fix
-            title="{{ 'tooltips.Dettach' | translate }}"
-            matTooltip="{{ 'tooltips.Delete' | translate }}"
+            matTooltip="{{ 'tooltips.Dettach' | translate }}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@if (loanStatus.pendingApproval) {
<button
type="button"
color="warn"
title="{{ 'tooltips.Dettach' | translate }}"
mat-icon-button
matTooltip="{{ 'tooltips.Delete' | translate }}"
*mifosxHasPermission="'DETTACH_LOAN_ORIGINATOR'"
matTooltipPosition="above"
`@if` (loanStatus.pendingApproval) {
<button
type="button"
color="warn"
mat-icon-button
matTooltip="{{ 'tooltips.Dettach' | translate }}"
*mifosxHasPermission="'DETTACH_LOAN_ORIGINATOR'"
matTooltipPosition="above"
🤖 Prompt for AI Agents
In
`@src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.html`
around lines 52 - 60, The button shown when loanStatus.pendingApproval has
inconsistent tooltip text—title uses "Dettach" and matTooltip uses "Delete"—so
remove one tooltip attribute and standardize the text: fix the spelling to
"Detach" and use the same translate key for the remaining tooltip (e.g., replace
title and matTooltip with a single matTooltip="{{ 'tooltips.Detach' | translate
}}" on the button that also keeps the
*mifosxHasPermission="'DETTACH_LOAN_ORIGINATOR'" attribute). Ensure only one
tooltip attribute remains and that it references the consistent translate key.

Comment on lines 75 to 83
constructor() {
const loanIdParam = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
this.loanId = loanIdParam ? Number(loanIdParam) : null;
this.clientId = this.route.parent.parent.snapshot.paramMap.get('clientId');
this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
this.route.parent.parent.data.subscribe((data: { loanDetailsData: any }) => {
this.loanStatus = data.loanDetailsData.status;
});
this.route.parent.data.subscribe((data: { loanOriginatorsData: any }) => {
this.loanOriginatorsData = data.loanOriginatorsData.originators;
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inconsistent null safety on route.parent access.

Line 76 accesses this.route.parent.parent.snapshot without optional chaining, while Line 77 uses this.route.parent?.parent?.snapshot. Line 78 also lacks the guard. If parent is ever null, Line 76 and 78 will throw.

🐛 Proposed fix
-    this.clientId = this.route.parent.parent.snapshot.paramMap.get('clientId');
-    this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
-    this.route.parent.parent.data.subscribe((data: { loanDetailsData: any }) => {
+    this.clientId = this.route.parent?.parent?.snapshot.paramMap.get('clientId') ?? null;
+    this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId') ?? null;
+    this.route.parent?.parent?.data.subscribe((data: { loanDetailsData: any }) => {
       this.loanStatus = data.loanDetailsData.status;
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constructor() {
const loanIdParam = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
this.loanId = loanIdParam ? Number(loanIdParam) : null;
this.clientId = this.route.parent.parent.snapshot.paramMap.get('clientId');
this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
this.route.parent.parent.data.subscribe((data: { loanDetailsData: any }) => {
this.loanStatus = data.loanDetailsData.status;
});
this.route.parent.data.subscribe((data: { loanOriginatorsData: any }) => {
this.loanOriginatorsData = data.loanOriginatorsData.originators;
});
constructor() {
this.clientId = this.route.parent?.parent?.snapshot.paramMap.get('clientId') ?? null;
this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId') ?? null;
this.route.parent?.parent?.data.subscribe((data: { loanDetailsData: any }) => {
this.loanStatus = data.loanDetailsData.status;
});
this.route.parent.data.subscribe((data: { loanOriginatorsData: any }) => {
this.loanOriginatorsData = data.loanOriginatorsData.originators;
});
🤖 Prompt for AI Agents
In
`@src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts`
around lines 75 - 83, The constructor uses this.route.parent.parent without
null-safety causing potential crashes; update it to consistently guard
route.parent and route.parent.parent (e.g., read const grandParent =
this.route.parent?.parent and const parent = this.route.parent) and then use
grandParent?.snapshot?.paramMap.get('clientId') and
grandParent?.snapshot?.paramMap.get('loanId') or bail out if grandParent is
undefined; likewise subscribe only when parent or grandParent exists (use
parent?.data.subscribe(...) and grandParent?.data.subscribe(...) or an if-check)
so that clientId, loanId, loanStatus and loanOriginatorsData assignments and
subscriptions (refer to constructor, loanStatus, loanOriginatorsData, clientId,
loanId) are guarded against null parents.

});
}

dettachLoanOriginator(loanOriginator: LoanOriginator): void {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typos: dettachdetach, fotfor.

"dettach" is misspelled in the method name (Line 86) and the comment on Line 108 has "fot" instead of "for". The method name typo may need to stay consistent with the LoansService.dettachLoanOriginator method name — if so, consider fixing the service method name too.

#!/bin/bash
# Check if the typo "dettach" is in the service definition as well
rg -n "dettach" --type=ts -C2

Also applies to: 108-108

🤖 Prompt for AI Agents
In
`@src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts`
at line 86, The method name dettachLoanOriginator is misspelled and the inline
comment contains "fot" — rename dettachLoanOriginator to detachLoanOriginator in
the component and update all callers/usages (including
LoansService.dettachLoanOriginator if it exists) to the new name to keep
consistency, and fix the comment text "fot" to "for"; search for
"dettachLoanOriginator" across the codebase and refactor both definition and
references to avoid breakage.

Comment on lines +110 to +115
private reload() {
const url: string = this.router.url;
this.router
.navigateByUrl(`/clients/${this.clientId}/loans-accounts`, { skipLocationChange: true })
.then(() => this.router.navigate([url]));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

reload() will produce a broken URL if clientId is null.

If clientId was not resolved from the route, the navigation will go to /clients/null/loans-accounts. Add a guard.

🛡️ Proposed fix
   private reload() {
+    if (!this.clientId) {
+      return;
+    }
     const url: string = this.router.url;
     this.router
       .navigateByUrl(`/clients/${this.clientId}/loans-accounts`, { skipLocationChange: true })
       .then(() => this.router.navigate([url]));
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private reload() {
const url: string = this.router.url;
this.router
.navigateByUrl(`/clients/${this.clientId}/loans-accounts`, { skipLocationChange: true })
.then(() => this.router.navigate([url]));
}
private reload() {
if (!this.clientId) {
return;
}
const url: string = this.router.url;
this.router
.navigateByUrl(`/clients/${this.clientId}/loans-accounts`, { skipLocationChange: true })
.then(() => this.router.navigate([url]));
}
🤖 Prompt for AI Agents
In
`@src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts`
around lines 110 - 115, The reload() method can navigate to
`/clients/null/loans-accounts` when this.clientId is null; add a guard in the
reload() function to check that this.clientId is non-null/undefined (and
non-empty if desired) before calling
router.navigateByUrl(`/clients/${this.clientId}/loans-accounts`, ...); if
clientId is missing, either skip the navigation or use a safe fallback route
(e.g., the parent clients list), and ensure subsequent router.navigate([url]) is
only invoked when the first navigation succeeded.

"Application Menu": "Nabídka aplikací",
"Approve": "Schvalovat",
"Assign Loan Officer": "Přiřadit úvěrového úředníka",
"Attach Loan Originator": "Připojit poskytovatele půjčky",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent Czech terminology for "Loan" across the new translations.

Lines 1114 and 1980 translate "Loan" as "úvěr" (e.g., "Poskytovatel úvěrů"), while Line 2682 uses "půjčky" ("Připojit poskytovatele půjčky") and Line 3190 uses "půjček" ("Poskytovatelé půjček"). Consider unifying on a single term—either "úvěr" or "půjčka"—for all loan originator translations to avoid confusing Czech-speaking users.

Also applies to: 3190-3190

🤖 Prompt for AI Agents
In `@src/assets/translations/cs-CS.json` at line 2682, The Czech translations use
both "půjčka"/"půjček" and "úvěr"/"úvěrů" for "Loan" which is inconsistent; pick
one term (recommend "úvěr") and update the translation values for the affected
keys such as "Attach Loan Originator" (currently "Připojit poskytovatele
půjčky"), the occurrences translated as "Poskytovatel úvěrů" and "Poskytovatelé
půjček" so they all use the same form (e.g., "Připojit poskytovatele úvěru",
"Poskytovatel úvěrů", "Poskytovatelé úvěrů"), then run a quick grep across the
translations file to ensure no other "půjčka"/"půjček" remain for
loan-originator related keys.

"Loan Documents": "대출 서류",
"Loan Id": "대출 ID",
"Loan Officer": "대출 담당자",
"Loan Originator": "대출 담당자",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

"Loan Originator" and "Loan Officer" share the same Korean translation "대출 담당자".

On line 1981, "Loan Officer" is already translated as "대출 담당자". Using the identical translation for "Loan Originator" will confuse Korean-speaking users, especially since the PR screenshot shows originators are organizations (e.g., "Langosh Group") with types like AFFILIATE/PLATFORM—fundamentally different from a loan officer (a person).

Consider a distinct term such as "대출 개시자" (loan initiator/originator) or "대출 발행기관" (loan-issuing entity) to differentiate the two concepts. This same change should propagate to lines 1115, 2682, and 3189.

🤖 Prompt for AI Agents
In `@src/assets/translations/ko-KO.json` at line 1982, The translation for the key
"Loan Originator" currently duplicates "Loan Officer" ("대출 담당자"); update the
"Loan Originator" value to a distinct Korean term (e.g., "대출 개시자" or "대출 발행기관")
to reflect that originators are organizational entities, and apply the same
change for every occurrence of the "Loan Originator" key in this file (also
present near the other occurrences noted: the entries at/from lines referenced
as 1115, 2682, and 3189) so all instances consistently use the new distinct
translation while leaving "Loan Officer" ("대출 담당자") unchanged.

Comment on lines +3773 to +3775
"ACTIVE": "AKTĪVS",
"PENDING": "NEAPSTRĀDĀTS",
"INACTIVE": "NEAKTIVS"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix diacritic in Latvian translation for INACTIVE.

“NEAKTIVS” is missing the long ī, while “AKTĪVS” uses it. Align for consistency.

✏️ Suggested fix
-    "INACTIVE": "NEAKTIVS"
+    "INACTIVE": "NEAKTĪVS"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"ACTIVE": "AKTĪVS",
"PENDING": "NEAPSTRĀDĀTS",
"INACTIVE": "NEAKTIVS"
"ACTIVE": "AKTĪVS",
"PENDING": "NEAPSTRĀDĀTS",
"INACTIVE": "NEAKTĪVS"
🤖 Prompt for AI Agents
In `@src/assets/translations/lv-LV.json` around lines 3773 - 3775, Update the
Latvian translation value for the "INACTIVE" key from "NEAKTIVS" to "NEAKTĪVS"
so it matches the diacritic used in "AKTĪVS"; locate the "INACTIVE" JSON entry
and replace the string, ensuring the file remains UTF-8 encoded to preserve the
Ī character.

"Loan Documents": "ऋण कागजातहरू",
"Loan Id": "ऋण आईडी",
"Loan Officer": "ऋण अधिकारी",
"Loan Originator": "ऋण प्रवर्तक",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent Nepali translation for "Originator".

Line 1979 translates "Loan Originator" as "ऋण प्रवर्तक", while all other originator-related keys (Lines 1091, 1113, 2679, 3187) consistently use "सुरुवातकर्ता". This inconsistency will surface in the UI — a user could see two different Nepali words for the same concept on different screens.

Proposed fix: align with the other keys
-      "Loan Originator": "ऋण प्रवर्तक",
+      "Loan Originator": "ऋण सुरुवातकर्ता",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Loan Originator": "ऋण प्रवर्तक",
"Loan Originator": "ऋण सुरुवातकर्ता",
🤖 Prompt for AI Agents
In `@src/assets/translations/ne-NE.json` at line 1979, Replace the inconsistent
Nepali translation for the "Loan Originator" key in the translations file:
change the value "ऋण प्रवर्तक" to match the other originator-related keys by
using "ऋण सुरुवातकर्ता" so the UI uses the same Nepali term for "Originator"
across all keys (i.e., update the translation value for the "Loan Originator"
string to "ऋण सुरुवातकर्ता").

Comment on lines +3770 to +3773
"Create Interest Refund": "ब्याज फिर्ता सिर्जना गर्नुहोस्",
"ACTIVE": "सक्रिय",
"PENDING": "पेन्डिङ",
"INACTIVE": "निष्क्रिय"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for references to tooltips.ACTIVE, tooltips.PENDING, tooltips.INACTIVE in component code

echo "=== Searching for tooltips.ACTIVE references ==="
rg "tooltips\s*\.\s*ACTIVE|'tooltips\.ACTIVE'|\"tooltips\.ACTIVE\"" --type=ts --type=tsx --type=js --type=jsx -C2 2>/dev/null | head -50

echo ""
echo "=== Searching for tooltips.PENDING references ==="
rg "tooltips\s*\.\s*PENDING|'tooltips\.PENDING'|\"tooltips\.PENDING\"" --type=ts --type=tsx --type=js --type=jsx -C2 2>/dev/null | head -50

echo ""
echo "=== Searching for tooltips.INACTIVE references ==="
rg "tooltips\s*\.\s*INACTIVE|'tooltips\.INACTIVE'|\"tooltips\.INACTIVE\"" --type=ts --type=tsx --type=js --type=jsx -C2 2>/dev/null | head -50

echo ""
echo "=== Broader search: translate() with ACTIVE/PENDING/INACTIVE in tooltips context ==="
rg "translate.*tooltips" --type=ts --type=tsx --type=js --type=jsx -C1 2>/dev/null | grep -E "(ACTIVE|PENDING|INACTIVE)" | head -20

Repository: openMF/web-app

Length of output: 295


🏁 Script executed:

#!/bin/bash
# Check if these keys are used anywhere at all
echo "=== Searching for any translate() calls with 'ACTIVE' ==="
rg "translate.*ACTIVE" --type=ts --type=tsx --type=js --type=jsx -C1 2>/dev/null | head -30

echo ""
echo "=== Searching for any translate() calls with 'PENDING' ==="
rg "translate.*PENDING" --type=ts --type=tsx --type=js --type=jsx -C1 2>/dev/null | head -30

echo ""
echo "=== Examine the JSON structure around lines 3770-3773 ==="
sed -n '3750,3780p' src/assets/translations/ne-NE.json | cat -n

echo ""
echo "=== Check if these keys exist in the default English translation ==="
rg -n "ACTIVE|PENDING|INACTIVE" src/assets/translations/en.json | head -20

Repository: openMF/web-app

Length of output: 2579


🏁 Script executed:

#!/bin/bash
# Find the English translation file
echo "=== List translation files ==="
ls -la src/assets/translations/ | head -20

echo ""
echo "=== Search for ACTIVE/PENDING/INACTIVE in all translation files ==="
rg -l "ACTIVE|PENDING|INACTIVE" src/assets/translations/ | head -10

echo ""
echo "=== Check if these keys are in the English translation and where ==="
for file in src/assets/translations/en*.json; do
  if [ -f "$file" ]; then
    echo "=== File: $(basename $file) ==="
    rg -n "\"(ACTIVE|PENDING|INACTIVE)\"" "$file" -A 0 -B 2 | head -30
  fi
done

Repository: openMF/web-app

Length of output: 1844


🏁 Script executed:

#!/bin/bash
# More thorough search for any usage of these keys

echo "=== Search for 'ACTIVE' in component code (any context) ==="
rg -n "ACTIVE" --type=ts --type=tsx --type=js --type=jsx src/ -i 2>/dev/null | grep -v "node_modules" | head -20

echo ""
echo "=== Check what section these keys should logically belong to ==="
echo "Looking for other status definitions in en-US.json:"
rg -n "labels\.status|labels\.catalogs" src/assets/translations/en-US.json | head -10

echo ""
echo "=== Get context around the tooltips section in en-US.json ==="
grep -n "\"tooltips\"" src/assets/translations/en-US.json | head -3

Repository: openMF/web-app

Length of output: 319


Remove unused status constants "ACTIVE" / "PENDING" / "INACTIVE" from tooltips section.

These translation keys (lines 3770–3773) are not referenced anywhere in the codebase—no component uses tooltips.ACTIVE, tooltips.PENDING, or tooltips.INACTIVE. The same keys exist identically in en-US.json and across multiple language files, suggesting they were added but never integrated. If they are not actively used, they should be removed to keep the translation file clean and maintainable.

🤖 Prompt for AI Agents
In `@src/assets/translations/ne-NE.json` around lines 3770 - 3773, Remove the
unused tooltip translation keys "ACTIVE", "PENDING", and "INACTIVE" from the
tooltips section in the ne-NE JSON (the entries with keys ACTIVE, PENDING,
INACTIVE); delete those three key/value lines, then run a quick repo-wide search
for usages of tooltips.ACTIVE, tooltips.PENDING, and tooltips.INACTIVE to
confirm they aren't referenced and, if you have an i18n extraction/lint step,
run it to ensure no missing-key errors are introduced.

@IOhacker IOhacker merged commit 0bc1926 into openMF:dev Feb 6, 2026
5 checks passed
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.

3 participants