WEB-670: Loan modification saves charge value instead of percent (#3096)

Co-authored-by: Jose Alberto Hernandez <alberto@black-box.local>
This commit is contained in:
Jose Alberto Hernandez
2026-02-06 12:22:58 -06:00
committed by GitHub
co-authored by Jose Alberto Hernandez
parent 0cb4d7b0cc
commit e198f92ba4
13 changed files with 99 additions and 28 deletions
@@ -142,7 +142,7 @@
</table> </table>
</div> </div>
<div class="layout-row responsive-column align-center gap-2px margin-t"> <div class="layout-row responsive-column align-center gap-2px margin-t stepper-buttons">
<button mat-raised-button matStepperPrevious> <button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon> <fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }} {{ 'labels.buttons.Previous' | translate }}
@@ -27,7 +27,6 @@ import {
/** Dialog Components */ /** Dialog Components */
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
// import { LoansAccountAddCollateralDialogComponent } from 'app/loans/custom-dialog/loans-account-add-collateral-dialog/loans-account-add-collateral-dialog.component';
/** Custom Services */ /** Custom Services */
import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base'; import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base';
@@ -35,14 +34,16 @@ import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-
import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base'; import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base';
import { SettingsService } from 'app/settings/settings.service'; import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates'; import { Dates } from 'app/core/utils/dates';
import { ActivatedRoute, RouterLink } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { MatButton, MatIconButton } from '@angular/material/button'; import { MatIconButton } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatDivider } from '@angular/material/divider'; import { MatDivider } from '@angular/material/divider';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper'; import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { DateFormatPipe } from '../../../pipes/date-format.pipe'; import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { FormatNumberPipe } from '../../../pipes/format-number.pipe'; import { FormatNumberPipe } from '../../../pipes/format-number.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { TranslateService } from '@ngx-translate/core';
import { LoanCharge } from 'app/loans/models/loan-charge.model';
/** /**
* Recurring Deposit Account Charges Step * Recurring Deposit Account Charges Step
@@ -77,6 +78,7 @@ export class LoansAccountChargesStepComponent implements OnInit, OnChanges {
private dateUtils = inject(Dates); private dateUtils = inject(Dates);
private route = inject(ActivatedRoute); private route = inject(ActivatedRoute);
private settingsService = inject(SettingsService); private settingsService = inject(SettingsService);
private translateService = inject(TranslateService);
// @Input loansAccountProductTemplate: LoansAccountProductTemplate // @Input loansAccountProductTemplate: LoansAccountProductTemplate
@Input() loansAccountProductTemplate: any; @Input() loansAccountProductTemplate: any;
@@ -148,11 +150,13 @@ export class LoansAccountChargesStepComponent implements OnInit, OnChanges {
ngOnInit() { ngOnInit() {
if (this.loansAccountTemplate && this.loansAccountTemplate.charges) { if (this.loansAccountTemplate && this.loansAccountTemplate.charges) {
this.chargesDataSource = this.chargesDataSource =
this.loansAccountTemplate.charges.map((charge: any) => { this.loansAccountTemplate.charges.map((loanCharge: LoanCharge) => {
const amount = this.isPercentageCharge(loanCharge) ? loanCharge.percentage : loanCharge.amount;
return { return {
...charge, ...loanCharge,
id: charge.id, amount,
chargeId: charge.chargeId id: loanCharge.id,
chargeId: loanCharge.chargeId
}; };
}) || []; }) || [];
} }
@@ -189,11 +193,13 @@ export class LoansAccountChargesStepComponent implements OnInit, OnChanges {
})) || []; })) || [];
} else if (isModification && this.loansAccountTemplate && this.loansAccountTemplate.charges) { } else if (isModification && this.loansAccountTemplate && this.loansAccountTemplate.charges) {
this.chargesDataSource = this.chargesDataSource =
this.loansAccountTemplate.charges.map((charge: any) => { this.loansAccountTemplate.charges.map((loanCharge: LoanCharge) => {
const amount = this.isPercentageCharge(loanCharge) ? loanCharge.percentage : loanCharge.amount;
return { return {
...charge, ...loanCharge,
id: charge.id, amount,
chargeId: charge.chargeId id: loanCharge.id,
chargeId: loanCharge.chargeId
}; };
}) || []; }) || [];
} }
@@ -222,13 +228,16 @@ export class LoansAccountChargesStepComponent implements OnInit, OnChanges {
new InputBase({ new InputBase({
controlName: 'amount', controlName: 'amount',
label: 'Amount', label: 'Amount',
value: charge.amount, value: this.isPercentageCharge(charge) ? charge.amountOrPercentage : charge.amount,
type: 'number', type: 'number',
required: false required: false
}) })
]; ];
const data = { const data = {
title: 'Edit Charge Amount', title:
this.translateService.instant('labels.buttons.Edit') +
' ' +
this.translateService.instant('labels.inputs.Charge Amount'),
layout: { addButtonText: 'Confirm' }, layout: { addButtonText: 'Confirm' },
formfields: formfields formfields: formfields
}; };
@@ -254,11 +263,15 @@ export class LoansAccountChargesStepComponent implements OnInit, OnChanges {
label: 'Date', label: 'Date',
value: charge.dueDate || charge.feeOnMonthDay || '', value: charge.dueDate || charge.feeOnMonthDay || '',
type: 'datetime-local', type: 'datetime-local',
maxDate: this.settingsService.maxFutureDate,
required: false required: false
}) })
]; ];
const data = { const data = {
title: 'Edit Charge Date', title:
this.translateService.instant('labels.buttons.Edit') +
' ' +
this.translateService.instant('labels.inputs.Charge Date'),
layout: { addButtonText: 'Confirm' }, layout: { addButtonText: 'Confirm' },
formfields: formfields formfields: formfields
}; };
@@ -380,4 +393,8 @@ export class LoansAccountChargesStepComponent implements OnInit, OnChanges {
this.selectAllItems = this.selectAllItems =
len === 0 ? false : this.activeClientMembers.filter((item: any) => item.selected).length === len; len === 0 ? false : this.activeClientMembers.filter((item: any) => item.selected).length === len;
} }
private isPercentageCharge(loanCharge: LoanCharge): boolean {
return loanCharge.chargeCalculationType.code.includes('.percent.');
}
} }
@@ -54,7 +54,7 @@
} }
</div> </div>
<div class="layout-row align-center gap-2percent margin-t responsive-column"> <div class="layout-row align-center gap-2percent margin-t responsive-column stepper-buttons">
<button mat-raised-button matStepperPrevious disabled> <button mat-raised-button matStepperPrevious disabled>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon> <fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }} {{ 'labels.buttons.Previous' | translate }}
@@ -138,7 +138,7 @@
</div> </div>
} }
<div class="layout-row align-center gap-2percent margin-t responsive-column"> <div class="layout-row align-center gap-2percent margin-t responsive-column stepper-buttons">
<button mat-raised-button matStepperPrevious disabled> <button mat-raised-button matStepperPrevious disabled>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon> <fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }} {{ 'labels.buttons.Previous' | translate }}
@@ -390,7 +390,7 @@
} }
</div> </div>
<div class="layout-row responsive-column align-center gap-2px margin-t"> <div class="layout-row responsive-column align-center gap-2px margin-t stepper-buttons">
<button mat-raised-button matStepperPrevious> <button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon> <fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }} {{ 'labels.buttons.Previous' | translate }}
@@ -19,7 +19,7 @@
</mifosx-repayment-schedule-tab> </mifosx-repayment-schedule-tab>
</div> </div>
<div class="layout-row responsive-column align-center gap-2px margin-t"> <div class="layout-row responsive-column align-center gap-2px margin-t stepper-buttons">
<button mat-raised-button matStepperPrevious> <button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon> <fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }} {{ 'labels.buttons.Previous' | translate }}
@@ -726,7 +726,7 @@
</table> </table>
</div> </div>
<div class="layout-row align-center gap-2percent margin-t responsive-column"> <div class="layout-row align-center gap-2percent margin-t responsive-column stepper-buttons">
<button mat-raised-button matStepperPrevious> <button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon> <fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }} {{ 'labels.buttons.Previous' | translate }}
@@ -59,7 +59,7 @@
</div> </div>
} }
<div class="layout-row responsive-column align-center gap-2px margin-t"> <div class="layout-row responsive-column align-center gap-2px margin-t stepper-buttons">
<button mat-raised-button matStepperPrevious> <button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon> <fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }} {{ 'labels.buttons.Previous' | translate }}
@@ -56,7 +56,12 @@
<ng-container matColumnDef="calculationtype"> <ng-container matColumnDef="calculationtype">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Calculation Type' | translate }}</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Calculation Type' | translate }}</th>
<td mat-cell *matCellDef="let charge">{{ charge.chargeCalculationType.value }}</td> <td mat-cell *matCellDef="let charge">
{{ charge.chargeCalculationType.value }}
@if (isPercentageCharge(charge)) {
({{ charge.percentage | formatNumber: 2 }}%)
}
</td>
</ng-container> </ng-container>
<ng-container matColumnDef="due"> <ng-container matColumnDef="due">
@@ -8,7 +8,7 @@
/** Angular Imports */ /** Angular Imports */
import { Component, OnInit, ViewChild, inject } from '@angular/core'; import { Component, OnInit, ViewChild, inject } from '@angular/core';
import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { MatSort, MatSortHeader } from '@angular/material/sort'; import { MatSort, MatSortHeader } from '@angular/material/sort';
@@ -47,6 +47,8 @@ import { CurrencyPipe } from '@angular/common';
import { MatTooltip } from '@angular/material/tooltip'; import { MatTooltip } from '@angular/material/tooltip';
import { DateFormatPipe } from '../../../pipes/date-format.pipe'; import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { LoanCharge } from 'app/loans/models/loan-charge.model';
import { FormatNumberPipe } from '@pipes/format-number.pipe';
@Component({ @Component({
selector: 'mifosx-charges-tab', selector: 'mifosx-charges-tab',
@@ -69,7 +71,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatRow, MatRow,
MatPaginator, MatPaginator,
CurrencyPipe, CurrencyPipe,
DateFormatPipe DateFormatPipe,
FormatNumberPipe
] ]
}) })
export class ChargesTabComponent implements OnInit { export class ChargesTabComponent implements OnInit {
@@ -78,14 +81,14 @@ export class ChargesTabComponent implements OnInit {
private dateUtils = inject(Dates); private dateUtils = inject(Dates);
private router = inject(Router); private router = inject(Router);
private translateService = inject(TranslateService); private translateService = inject(TranslateService);
dialog = inject(MatDialog); private dialog = inject(MatDialog);
private settingsService = inject(SettingsService); private settingsService = inject(SettingsService);
private systemService = inject(SystemService); private systemService = inject(SystemService);
/** Loan Details Data */ /** Loan Details Data */
loanDetails: any; loanDetails: any;
/** Charges Data */ /** Charges Data */
chargesData: any; chargesData: LoanCharge[] = [];
/** Status */ /** Status */
status: any; status: any;
/** Columns to be displayed in charges table. */ /** Columns to be displayed in charges table. */
@@ -232,7 +235,7 @@ export class ChargesTabComponent implements OnInit {
new InputBase({ new InputBase({
controlName: 'amount', controlName: 'amount',
label: 'Amount', label: 'Amount',
value: charge.amount || charge.amountOrPercentage, value: this.isPercentageCharge(charge) ? charge.amountOrPercentage : charge.amount,
type: 'number', type: 'number',
required: true required: true
}) })
@@ -295,4 +298,8 @@ export class ChargesTabComponent implements OnInit {
.navigateByUrl(`/clients/${clientId}/loans-accounts`, { skipLocationChange: true }) .navigateByUrl(`/clients/${clientId}/loans-accounts`, { skipLocationChange: true })
.then(() => this.router.navigate([url])); .then(() => this.router.navigate([url]));
} }
isPercentageCharge(loanCharge: LoanCharge): boolean {
return loanCharge.chargeCalculationType.code.includes('.percent.');
}
} }
@@ -32,7 +32,7 @@
matTooltip="{{ 'labels.status.' + loanDetailsData.status.value | translate }}" matTooltip="{{ 'labels.status.' + loanDetailsData.status.value | translate }}"
[ngClass]="iconLoanStatusColor() | statusLookup" [ngClass]="iconLoanStatusColor() | statusLookup"
></i> ></i>
<span class="m-r-5">{{ 'labels.heading.Loan Product' | translate }} :</span> <span class="m-l-5 m-r-5">{{ 'labels.heading.Loan Product' | translate }} :</span>
<span class="m-r-5" <span class="m-r-5"
><mifosx-long-text textValue="{{ loanDetailsData.loanProductName }}"></mifosx-long-text ><mifosx-long-text textValue="{{ loanDetailsData.loanProductName }}"></mifosx-long-text
></span> ></span>
+38
View File
@@ -0,0 +1,38 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Charge, Currency } from 'app/shared/models/general.model';
import { OptionData } from 'app/shared/models/option-data.model';
export interface LoanCharge {
id: number;
chargeId: number;
name: string;
chargeTimeType: Charge;
submittedOnDate: number[];
dueDate: number[];
chargeCalculationType: OptionData;
percentage: number;
amountPercentageAppliedTo: number;
currency: Currency;
amount: number;
amountPaid: number;
amountWaived: number;
amountWrittenOff: number;
amountOutstanding: number;
amountOrPercentage: number;
penalty: boolean;
chargePaymentMode: OptionData;
chargeTime: OptionData;
paid: boolean;
waived: boolean;
chargePayable: boolean;
loanId: number;
externalId: string;
externalLoanId: string;
}
+4
View File
@@ -44,3 +44,7 @@ textarea.noresize {
margin-top: 5px; margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;
} }
.stepper-buttons {
height: 38px;
}