Skip to content

Conversation

@JaySoni1
Copy link
Contributor

@JaySoni1 JaySoni1 commented Sep 30, 2025

Changes Made :-

-Fixed issue where charges were doubling when editing a loan application.
-Added code to prevent duplicate charges by checking for existing ones.
-Updated how charges are handled during loan modifications to maintain correct values.

WEB-117

Summary by CodeRabbit

  • Bug Fixes

    • Prevents duplicate charges from being added or submitted on loan accounts, reducing double-charging and related errors.
  • Improvements

    • Charges are deduplicated before saving so only a single entry per charge is stored.
    • Charge entries now preserve alternate identifiers and key fields when present.
    • Charge lists pre-populate more reliably from templates or existing loan data, improving edit flows and consistency.

@coderabbitai
Copy link

coderabbitai bot commented Sep 30, 2025

Walkthrough

Deduplicates loan charges during charges-step initialization and before submitting edits. Charges are normalized and deduped by chargeId (preserving id when different); modification mode prefers template fallback. Date/other payload fields and public APIs remain unchanged. (≤50 words)

Changes

Cohort / File(s) Summary of Changes
Edit submission deduplication
src/app/loans/edit-loans-account/edit-loans-account.component.ts
Builds a uniqueCharges map keyed by chargeId (skips null/undefined), replaces payload charges with deduplicated values, normalizes objects to include chargeId, amount, dueDate (formatted), and includes id when it differs from chargeId. Other payload/date logic unchanged.
Charges step init & runtime dedupe
src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts
Populates chargesDataSource preserving id and chargeId; in modification mode falls back to account template charges when present. addCharge ensures a chargeId is set. Adds private getUniqueCharges helper and updates loansAccountCharges getter to return a deduplicated, normalized list (keeps last occurrence per chargeId).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant C as LoansAccountChargesStepComponent
  participant E as EditLoansAccountComponent
  participant API as Backend

  U->>C: Open or edit loan account
  Note over C: Initialize charges\n• Use product template, or\n• if modifying and template present, use account template
  C->>C: getUniqueCharges(charges)\n(deduplicate by chargeId)
  U->>E: Submit edited loan account
  E->>E: Build uniqueCharges map\n(skip null/undefined chargeId, normalize entries)
  E->>API: POST payload with deduplicated `charges`
  API-->>E: Response
  E-->>U: Confirmation / result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • steinwinde
  • gkbishnoi07

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "WEB-117 Loan Account Charge(Amount% charges) Doubling on Application Modification" is clearly related to the main changes in the changeset. According to the PR objectives, the issue being fixed is that charges were doubling when editing a loan application, and the solution adds deduplication logic to prevent this. The title accurately identifies the problem (charges doubling), the component affected (loan account charges), and the context (during application modification). The implementation across both modified files adds deduplication mechanisms that directly address this stated issue. While the notation "(Amount% charges)" could be clearer, the title is sufficiently specific and descriptive to convey the primary change to a developer reviewing the commit history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ 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: 2

🧹 Nitpick comments (5)
src/app/loans/edit-loans-account/edit-loans-account.component.ts (2)

141-145: Include feeOnMonthDay when present to avoid losing Annual Fee dates

The step component sets feeOnMonthDay for 'Annual Fee'. The payload currently only sends dueDate, which may drop that info.

-      charges: Array.from(uniqueCharges.values()).map((charge: any) => ({
-        chargeId: charge.id || charge.chargeId,
-        amount: charge.amount,
-        dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat)
-      })),
+      charges: Array.from(uniqueCharges.values()).map((charge: any) => {
+        const payload: any = {
+          chargeId: charge.id ?? charge.chargeId,
+          amount: charge.amount
+        };
+        if (charge.dueDate) {
+          payload.dueDate = this.dateUtils.formatDate(charge.dueDate, dateFormat);
+        }
+        if (charge.feeOnMonthDay) {
+          payload.feeOnMonthDay = this.dateUtils.formatDate(charge.feeOnMonthDay, dateFormat);
+        }
+        if (charge.feeInterval != null) {
+          payload.feeInterval = charge.feeInterval;
+        }
+        return payload;
+      }),

Please confirm Fineract expects feeOnMonthDay/feeInterval for your charge types in PUT /v1/loans. If not, we can pare this back. Based on learnings.


132-137: Avoid double-dedup: rely on a single, shared helper to keep behavior consistent

We dedup in the step getter and again here. Prefer one source of truth (e.g., a shared util), and keep a server-side guard (filter invalids) here.

I can extract a shared function (e.g., loans-charge-utils.ts) and update both call sites. Want me to draft it?

Also applies to: 141-145

src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts (3)

165-176: Prefer existing loan charges on modification; current order may transiently show product charges

OnChanges runs before OnInit; with empty chargesDataSource, the first branch loads product charges even in edit mode, then OnInit overwrites with existing charges. This causes redundant work and potential UI flicker.

-      const isModification = this.loanId != null;
-      if (
-        this.loansAccountProductTemplate.charges &&
-        this.loansAccountProductTemplate.charges.length > 0 &&
-        this.chargesDataSource.length === 0
-      ) {
-        this.chargesDataSource =
-          this.loansAccountProductTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || [];
-      } else if (isModification && this.loansAccountTemplate && this.loansAccountTemplate.charges) {
-        this.chargesDataSource =
-          this.loansAccountTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || [];
-      }
+      const isModification = this.loanId != null;
+      if (isModification && this.loansAccountTemplate?.charges?.length) {
+        this.chargesDataSource =
+          this.loansAccountTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || [];
+      } else if (this.chargesDataSource.length === 0 && this.loansAccountProductTemplate?.charges?.length) {
+        this.chargesDataSource =
+          this.loansAccountProductTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || [];
+      }

Follow-up: consider moving init from ngOnInit to ngOnChanges and deleting the ngOnInit block that sets chargesDataSource to avoid duplication. Based on coding guidelines.


320-323: Confirm business rule: should duplicate chargeId entries ever be allowed?

If some charge types legitimately appear multiple times (e.g., specified due dates), hard dedup by id will drop them. If duplicates are only the bug scenario (WEB-117), proceed; otherwise, key by a composite (id + dueDate) when the chargeTimeType allows multiples.

I can implement conditional dedup (by id, or by id+date when charge.allowMultiple) if you confirm the rule.


141-146: *Add a trackBy function to the charges table’s ngFor
Define and reference a trackBy in loans-account-charges-step.component.html (e.g. <tr *ngFor="let charge of chargesDataSource; trackBy: trackByChargeId">) and add to the component:

trackByChargeId(_: number, charge: any) { 
  return charge.id; 
}

This ensures row identity is preserved after array updates, avoiding unnecessary DOM churn.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f763365 and 097e45d.

📒 Files selected for processing (2)
  • src/app/loans/edit-loans-account/edit-loans-account.component.ts (1 hunks)
  • src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/app/**

⚙️ CodeRabbit configuration file

src/app/**: For Angular code: verify component separation, trackBy on *ngFor,
strict type safety, and clean observable patterns.

Files:

  • src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts
  • src/app/loans/edit-loans-account/edit-loans-account.component.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Lint, Build and Deploy

@JaySoni1 JaySoni1 force-pushed the WEB-117-loan-account-charge-amount-charges-doubling-on-application-modification branch from 097e45d to f31b61e Compare September 30, 2025 07:22
@JaySoni1
Copy link
Contributor Author

@steinwinde Please Review .

Copy link
Collaborator

@alberto-art3ch alberto-art3ch left a comment

Choose a reason for hiding this comment

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

I think it is not a right solution

const loanType = 'individual';
const uniqueCharges = new Map<number | string, any>();
(this.loansAccount.charges ?? []).forEach((charge: any) => {
const chargeId = charge.id ?? charge.chargeId;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why we are doing this? I mean the Charge has an attribute for the Id and that's all

@JaySoni1
Copy link
Contributor Author

JaySoni1 commented Oct 1, 2025

@alberto-art3ch Initially, I had fixed the charge doubling issue by using a Map to track unique charges based on both id and chargeId fields. After that coderabbit has review the code and it suggested to add proper typing and null checking to handle edege cases .

@JaySoni1
Copy link
Contributor Author

JaySoni1 commented Oct 4, 2025

@IOhacker , @steinwinde , @alberto-art3ch Please Review .

@gkbishnoi07
Copy link
Collaborator

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

✅ Actions performed

Full review triggered.

@gkbishnoi07
Copy link
Collaborator

@coderabbitai is this correct solution?

@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the full content of the modified files to analyze the solution
echo "=== edit-loans-account.component.ts ==="
cat src/app/loans/edit-loans-account/edit-loans-account.component.ts

echo -e "\n\n=== loans-account-charges-step.component.ts ==="
cat src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts

Length of output: 19970


@gkbishnoi07, the solution partially addresses the issue but has some concerns:

Issues with Current Implementation

  1. Inconsistent deduplication keys:

    • edit-loans-account.component.ts uses: charge.id ?? charge.chargeId
    • loans-account-charges-step.component.ts uses: charge.chargeId ?? charge.id

    This inconsistency could cause problems if both properties exist with different values.

  2. Root cause not addressed: The solution deduplicates charges before submission, but doesn't prevent them from appearing twice in the UI. Looking at the ngOnChanges logic:

    if (this.loansAccountProductTemplate.charges && 
        this.loansAccountProductTemplate.charges.length > 0 && 
        this.chargesDataSource.length === 0) {
      this.chargesDataSource = this.loansAccountProductTemplate.charges...
    } else if (isModification && this.loansAccountTemplate && this.loansAccountTemplate.charges) {
      this.chargesDataSource = this.loansAccountTemplate.charges...
    }

    The real question is: Why are charges being duplicated in the first place? Is it because:

    • Both templates contain the same charges?
    • Charges are being added multiple times during component lifecycle?
    • The API returns duplicates?
  3. Last-occurrence semantics: When duplicates exist, the Map keeps the last occurrence. If duplicate charges have different amounts or dates, this could lead to data loss.

Recommendations

  1. Standardize the key: Use the same order everywhere:

    const chargeId = charge.chargeId ?? charge.id;
  2. Investigate the root cause: Add logging to understand where duplicates originate:

    console.log('Product template charges:', this.loansAccountProductTemplate?.charges);
    console.log('Loan template charges:', this.loansAccountTemplate?.charges);
  3. Consider deduplicating earlier: Apply deduplication when populating chargesDataSource in ngOnChanges, not just at submission time.

  4. Add unit tests: Verify the deduplication logic handles edge cases (null ids, different amounts, etc.).

The solution will prevent duplicate charges from being submitted, but understanding and fixing the root cause would be more robust. Would you like me to help investigate where the duplication originates?

Copy link
Collaborator

@alberto-art3ch alberto-art3ch left a comment

Choose a reason for hiding this comment

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

Please review my comments

@JaySoni1 JaySoni1 force-pushed the WEB-117-loan-account-charge-amount-charges-doubling-on-application-modification branch from f31b61e to 1430416 Compare October 21, 2025 23:30
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: 0

♻️ Duplicate comments (1)
src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts (1)

358-370: Deduplication key should fallback to id to match addCharge logic.

Line 362 uses only charge.chargeId without fallback. This will skip charges that have id but no chargeId, causing inconsistency with addCharge (line 201) which uses id as fallback.

Apply this diff to align with addCharge and handle both fields:

   private getUniqueCharges<T extends { id?: number | string; chargeId?: number | string }>(charges: T[]): T[] {
     const uniqueChargesMap = new Map<number | string, T>();
 
     for (const charge of charges ?? []) {
-      const chargeId = charge.chargeId;
+      const chargeId = charge.chargeId ?? charge.id;
       if (chargeId == null) {
         continue;
       }
       uniqueChargesMap.set(chargeId, charge);
     }
 
     return Array.from(uniqueChargesMap.values());
   }

This ensures charges with only id (or only chargeId) are not silently dropped during deduplication.

🧹 Nitpick comments (3)
src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts (3)

143-149: Redundant property assignments after spread.

The spread operator ...charge already includes id and chargeId, so explicitly setting them again has no effect unless the values are being transformed.

Apply this diff to simplify:

-    this.chargesDataSource =
-      this.loansAccountTemplate.charges.map((charge: any) => {
-        return {
-          ...charge,
-          id: charge.id,
-          chargeId: charge.chargeId
-        };
-      }) || [];
+    this.chargesDataSource = this.loansAccountTemplate.charges || [];

Or, if the intent is normalization (ensuring both fields exist), make it explicit:

     this.chargesDataSource =
       this.loansAccountTemplate.charges.map((charge: any) => {
         return {
           ...charge,
-          id: charge.id,
-          chargeId: charge.chargeId
+          chargeId: charge.chargeId ?? charge.id,
+          id: charge.id ?? charge.chargeId
         };
       }) || [];

199-203: Use nullish coalescing for safer fallback.

Line 201 uses || which treats falsy values (0, empty string) as missing. Use ?? to only fallback when null/undefined:

     const newCharge = {
       ...charge.value,
-      chargeId: charge.value.id || charge.value.chargeId
+      chargeId: charge.value.chargeId ?? charge.value.id
     };

Note: This makes chargeId the primary key (falling back to id), which aligns with getUniqueCharges at line 362 that keys by chargeId.


67-82: Consider defining a Charge interface for type safety.

The component uses any throughout for charge objects. Defining a typed interface would improve type safety and developer experience.

As per coding guidelines.

Example:

interface LoanCharge {
  id?: number;
  chargeId?: number;
  name?: string;
  amount?: number;
  dueDate?: string;
  feeInterval?: number;
  feeOnMonthDay?: string;
  chargeCalculationType?: any;
  chargeTimeType?: { value: string };
  // ... other properties
}

Then update:

-  chargesDataSource: {}[] = [];
+  chargesDataSource: LoanCharge[] = [];
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f31b61e and 1430416.

📒 Files selected for processing (2)
  • src/app/loans/edit-loans-account/edit-loans-account.component.ts (1 hunks)
  • src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/loans/edit-loans-account/edit-loans-account.component.ts
🧰 Additional context used
📓 Path-based instructions (1)
src/app/**

⚙️ CodeRabbit configuration file

src/app/**: For Angular code: verify component separation, trackBy on *ngFor,
strict type safety, and clean observable patterns.

Files:

  • src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts
🔇 Additional comments (1)
src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts (1)

171-191: Verify ngOnChanges doesn't introduce duplicates across lifecycle.

The conditional logic populates chargesDataSource from different sources depending on modification mode and whether the array is empty. Since ngOnChanges can fire multiple times, ensure that:

  1. The chargesDataSource.length === 0 check reliably prevents re-population
  2. Both template sources don't contain overlapping charges
  3. The modification branch (lines 182-191) doesn't duplicate charges already populated in the first branch

Additionally, lines 184-190 have the same redundancy as noted in ngOnInit:

-        this.chargesDataSource =
-          this.loansAccountTemplate.charges.map((charge: any) => {
-            return {
-              ...charge,
-              id: charge.id,
-              chargeId: charge.chargeId
-            };
-          }) || [];
+        this.chargesDataSource = this.loansAccountTemplate.charges || [];

Or normalize if both fields might not exist:

         this.chargesDataSource =
           this.loansAccountTemplate.charges.map((charge: any) => {
             return {
               ...charge,
-              id: charge.id,
-              chargeId: charge.chargeId
+              chargeId: charge.chargeId ?? charge.id,
+              id: charge.id ?? charge.chargeId
             };
           }) || [];

@JaySoni1
Copy link
Contributor Author

@alberto-art3ch I have made changes and updated the PR please review .

Copy link
Collaborator

@alberto-art3ch alberto-art3ch left a comment

Choose a reason for hiding this comment

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

LGTM

@alberto-art3ch alberto-art3ch merged commit bddeb56 into openMF:dev Oct 21, 2025
3 checks passed
@JaySoni1
Copy link
Contributor Author

@alberto-art3ch Thank you for the review .

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