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 now deduplicated before saving to ensure a single entry per charge.
    • Pre-population of charges is more reliable—product templates are used for new loans and existing loan data for modifications, improving edit flows and data consistency.

Copy link

coderabbitai bot commented Sep 30, 2025

Walkthrough

Adds deduplication of loan charges both when initializing the charges step and before submitting edits. Charges are normalized and deduped by id/chargeId. Charges-step initialization now prefers product template but falls back to account template when modifying. Other payload fields and date formatting remain unchanged.

Changes

Cohort / File(s) Summary of Changes
Charges Deduplication in Edit Submission
src/app/loans/edit-loans-account/edit-loans-account.component.ts
Before submitting, builds a map keyed by chargeId (from id or chargeId) to remove duplicate or malformed charge entries; payload charges now uses the deduplicated list with normalized chargeId. Other payload and date processing unchanged.
Charges Step Initialization and Deduplication
src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts
Introduces isModification (based on loanId) to choose initial charges (prefer product template, fall back to loans account template when modifying). Adds private getUniqueCharges helper and updates getter to return deduplicated charges by id/chargeId, preserving last occurrence. Overdue-charges logic retained.

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• Prefer product template when datasource empty\n• If modifying (loanId present), may use account template\n• Overdue-charges branch retained
  C->>C: getUniqueCharges(charges)\n(deduplicate by id/chargeId)
  U->>E: Submit edited loan account
  E->>E: Build uniqueCharges map\n(skip malformed entries, normalize id/chargeId)
  E->>API: POST payload with deduplicated `charges`
  API-->>E: Response
  E-->>U: Confirmation / result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title includes the JIRA key and unclear parentheses, and reads more like a bug description than a concise summary of the implemented change; it does not clearly communicate the actual fix for duplicated charges. Rename the title to a clear, imperative summary of the fix without the issue ID or awkward punctuation, for example: “Prevent duplication of loan account charges on application modification.”
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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

📜 Recent 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 f31b61e.

📒 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/edit-loans-account/edit-loans-account.component.ts
  • src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts

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

Copy link

coderabbitai bot commented Oct 6, 2025

✅ Actions performed

Full review triggered.

@gkbishnoi07
Copy link
Collaborator

@coderabbitai is this correct solution?

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?

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