diff --git a/src/app/loans/loans-account-stepper/loans-account-schedule-step/loans-account-schedule-step.component.ts b/src/app/loans/loans-account-stepper/loans-account-schedule-step/loans-account-schedule-step.component.ts index 865dadf91..b40d3a838 100644 --- a/src/app/loans/loans-account-stepper/loans-account-schedule-step/loans-account-schedule-step.component.ts +++ b/src/app/loans/loans-account-stepper/loans-account-schedule-step/loans-account-schedule-step.component.ts @@ -1,6 +1,7 @@ import { Component, Input } from '@angular/core'; import { ActivatedRoute, RouterLink } from '@angular/router'; import { LoansService } from 'app/loans/loans.service'; +import { RepaymentSchedule } from 'app/loans/models/loan-account.model'; import { SettingsService } from 'app/settings/settings.service'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { RepaymentScheduleTabComponent } from '../../loans-view/repayment-schedule-tab/repayment-schedule-tab.component'; @@ -23,15 +24,15 @@ export class LoansAccountScheduleStepComponent { /** Currency Code */ @Input() currencyCode: string; /** Loans Account Template */ - @Input() loansAccountTemplate: any; + @Input() loansAccountTemplate: Record; /** Loans Account Product Template */ - @Input() loansAccountProductTemplate: any; + @Input() loansAccountProductTemplate: { calendarOptions?: unknown }; /** Loans Account Data */ - @Input() loansAccount: any; + @Input() loansAccount: Record; - repaymentScheduleDetails: any = { periods: [] }; + repaymentScheduleDetails: RepaymentSchedule | null = null; - loanId: any = null; + loanId: string | null = null; constructor( private loansService: LoansService, @@ -42,7 +43,7 @@ export class LoansAccountScheduleStepComponent { } showRepaymentInfo(): void { - this.repaymentScheduleDetails = { periods: [] }; + this.repaymentScheduleDetails = null; const locale = this.settingsService.language.code; const dateFormat = this.settingsService.dateFormat; const payload = this.loansService.buildLoanRequestPayload( @@ -55,7 +56,7 @@ export class LoansAccountScheduleStepComponent { delete payload['enableInstallmentLevelDelinquency']; delete payload['externalId']; - this.loansService.calculateLoanSchedule(payload).subscribe((response: any) => { + this.loansService.calculateLoanSchedule(payload).subscribe((response: RepaymentSchedule) => { this.repaymentScheduleDetails = response; }); } diff --git a/src/app/loans/loans-view/loan-account-actions/edit-repayment-schedule/edit-repayment-schedule.component.ts b/src/app/loans/loans-view/loan-account-actions/edit-repayment-schedule/edit-repayment-schedule.component.ts index 1e74b1ee8..4b224f163 100644 --- a/src/app/loans/loans-view/loan-account-actions/edit-repayment-schedule/edit-repayment-schedule.component.ts +++ b/src/app/loans/loans-view/loan-account-actions/edit-repayment-schedule/edit-repayment-schedule.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { Dates } from 'app/core/utils/dates'; import { LoansService } from 'app/loans/loans.service'; +import { EditableRepaymentSchedule, EditablePeriod, ScheduleChangeRecord } from 'app/loans/models/loan-account.model'; import { SettingsService } from 'app/settings/settings.service'; import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component'; import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; @@ -30,11 +31,9 @@ export class EditRepaymentScheduleComponent implements OnInit { /** Indicates If the Schedule has been validated */ wasValidated = false; /** Stores the Repayment Schedule data */ - repaymentScheduleDetails: { - periods: { period?: number; dueDate: string; totalDueForPeriod?: number; changed?: boolean }[]; - } | null = null; + repaymentScheduleDetails: EditableRepaymentSchedule | null = null; /** Stores the Installments changed */ - repaymentScheduleChanges: any = {}; + repaymentScheduleChanges: Record = {}; /** * @param {LoansService} systemService Loan Service. @@ -62,14 +61,23 @@ export class EditRepaymentScheduleComponent implements OnInit { } getRepaymentSchedule(): void { - this.loansService.getLoanAccountResource(this.loanId, 'repaymentSchedule').subscribe((response: any) => { - this.repaymentScheduleDetails = response.repaymentSchedule; + this.loansService.getLoanAccountResource(this.loanId, 'repaymentSchedule').subscribe({ + next: (response: { repaymentSchedule: EditableRepaymentSchedule }) => { + this.repaymentScheduleDetails = response.repaymentSchedule; + }, + error: (err) => { + console.error('Failed to load repayment schedule:', err); + } }); } applyPattern(): void { - const periods: any = []; - this.repaymentScheduleDetails['periods'].forEach((period: any) => { + if (!this.repaymentScheduleDetails) { + return; + } + + const periods: Array<{ idx: number; dueDate: string }> = []; + this.repaymentScheduleDetails.periods.forEach((period: EditablePeriod) => { if (period.period) { periods.push({ idx: period.period, @@ -106,27 +114,29 @@ export class EditRepaymentScheduleComponent implements OnInit { formfields: formfields }; const addDialogRef = this.dialog.open(FormDialogComponent, { data }); - addDialogRef.afterClosed().subscribe((response: any) => { - if (response.data) { - const fromPeriod = response.data.value.fromPeriod; - const toPeriod = response.data.value.toPeriod; - const amount = response.data.value.amount; - const periodsVariation: any = []; - this.repaymentScheduleDetails['periods'].forEach((period: any) => { - const dueDate = this.dateUtils.formatDate(period.dueDate, this.settingsService.dateFormat); - if (period.period && fromPeriod <= period.period && toPeriod >= period.period) { - if (period.totalDueForPeriod !== amount) { - period.totalDueForPeriod = amount; - this.repaymentScheduleChanges[dueDate] = { dueDate: dueDate, installmentAmount: amount }; - this.wasChanged = true; - period['changed'] = true; + addDialogRef + .afterClosed() + .subscribe((response: { data?: { value?: { fromPeriod: number; toPeriod: number; amount: number } } }) => { + if (response.data?.value && this.repaymentScheduleDetails) { + const fromPeriod = response.data.value.fromPeriod; + const toPeriod = response.data.value.toPeriod; + const amount = response.data.value.amount; + const periodsVariation: EditablePeriod[] = []; + this.repaymentScheduleDetails.periods.forEach((period: EditablePeriod) => { + const dueDate = this.dateUtils.formatDate(period.dueDate, this.settingsService.dateFormat); + if (period.period && fromPeriod <= period.period && toPeriod >= period.period) { + if (period.totalDueForPeriod !== amount) { + period.totalDueForPeriod = amount; + this.repaymentScheduleChanges[dueDate] = { dueDate: dueDate, installmentAmount: amount }; + this.wasChanged = true; + period.changed = true; + } } - } - periodsVariation.push(period); - }); - this.repaymentScheduleDetails['periods'] = periodsVariation; - } - }); + periodsVariation.push(period); + }); + this.repaymentScheduleDetails.periods = periodsVariation; + } + }); } reset(): void { @@ -138,42 +148,63 @@ export class EditRepaymentScheduleComponent implements OnInit { ) } }); - recoverScheduleDialogRef.afterClosed().subscribe((responseConfirmation: any) => { + recoverScheduleDialogRef.afterClosed().subscribe((responseConfirmation: { confirm?: boolean }) => { if (responseConfirmation.confirm) { - this.loansService - .applyCommandLoanScheduleVariations(this.loanId, 'deleteVariations', {}) - .subscribe((response: any) => { + this.loansService.applyCommandLoanScheduleVariations(this.loanId, 'deleteVariations', {}).subscribe({ + next: () => { this.getRepaymentSchedule(); this.wasChanged = false; this.wasValidated = false; - }); + }, + error: (err) => { + console.error('Failed to delete schedule variations:', err); + } + }); } }); } validate(): void { + if (!this.repaymentScheduleDetails) { + return; + } + this.loansService .applyCommandLoanScheduleVariations(this.loanId, 'calculateLoanSchedule', this.getPayload()) - .subscribe((response: any) => { - this.repaymentScheduleDetails['periods'] = []; - response['periods'].forEach((period: any) => { - period['changed'] = true; - this.repaymentScheduleDetails['periods'].push(period); - this.wasValidated = true; - }); + .subscribe({ + next: (response: EditableRepaymentSchedule) => { + if (this.repaymentScheduleDetails) { + this.repaymentScheduleDetails.periods = []; + response.periods.forEach((period: EditablePeriod) => { + period.changed = true; + this.repaymentScheduleDetails!.periods.push(period); + this.wasValidated = true; + }); + } + }, + error: (err) => { + console.error('Failed to calculate loan schedule:', err); + } }); } submit(): void { - this.loansService - .applyCommandLoanScheduleVariations(this.loanId, 'addVariations', this.getPayload()) - .subscribe((response: any) => { + this.loansService.applyCommandLoanScheduleVariations(this.loanId, 'addVariations', this.getPayload()).subscribe({ + next: () => { this.router.navigate(['../../repayment-schedule'], { relativeTo: this.route }); - }); + }, + error: (err) => { + console.error('Failed to add schedule variations:', err); + } + }); } - private getPayload(): any { - const modifiedinstallments: any = []; + private getPayload(): { + exceptions: { modifiedinstallments: ScheduleChangeRecord[] }; + dateFormat: string; + locale: string; + } { + const modifiedinstallments: ScheduleChangeRecord[] = []; Object.keys(this.repaymentScheduleChanges).forEach((key: string) => { modifiedinstallments.push(this.repaymentScheduleChanges[key]); }); diff --git a/src/app/loans/loans-view/loan-account-actions/loan-reaging/loan-reaging.component.html b/src/app/loans/loans-view/loan-account-actions/loan-reaging/loan-reaging.component.html index bc73abb75..5a6b4209f 100644 --- a/src/app/loans/loans-view/loan-account-actions/loan-reaging/loan-reaging.component.html +++ b/src/app/loans/loans-view/loan-account-actions/loan-reaging/loan-reaging.component.html @@ -84,6 +84,16 @@ > {{ 'labels.buttons.Submit' | translate }} + diff --git a/src/app/loans/loans-view/loan-account-actions/loan-reaging/loan-reaging.component.ts b/src/app/loans/loans-view/loan-account-actions/loan-reaging/loan-reaging.component.ts index ed05825b5..24400c706 100644 --- a/src/app/loans/loans-view/loan-account-actions/loan-reaging/loan-reaging.component.ts +++ b/src/app/loans/loans-view/loan-account-actions/loan-reaging/loan-reaging.component.ts @@ -1,11 +1,14 @@ import { Component, Input, OnInit } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; +import { MatDialog } from '@angular/material/dialog'; import { Dates } from 'app/core/utils/dates'; import { LoansService } from 'app/loans/loans.service'; +import { RepaymentSchedule } from 'app/loans/models/loan-account.model'; import { SettingsService } from 'app/settings/settings.service'; import { OptionData } from 'app/shared/models/option-data.model'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { ReAgePreviewDialogComponent } from './re-age-preview-dialog/re-age-preview-dialog.component'; @Component({ selector: 'mifosx-loan-reaging', @@ -31,13 +34,16 @@ export class LoanReagingComponent implements OnInit { /** Maximum Date allowed. */ maxDate = new Date(); + currencyCode: string = ''; + constructor( private formBuilder: UntypedFormBuilder, private route: ActivatedRoute, private router: Router, private settingsService: SettingsService, private loanService: LoansService, - private dateUtils: Dates + private dateUtils: Dates, + private dialog: MatDialog ) { this.loanId = this.route.snapshot.params['loanId']; } @@ -49,6 +55,11 @@ export class LoanReagingComponent implements OnInit { this.reAgeInterestHandlingOptions = this.dataObject.reAgeInterestHandlingOptions; this.periodFrequencyOptions = this.dataObject.periodFrequencyOptions; + const loanDetailsData = this.route.parent?.parent?.snapshot.data['loanDetailsData']; + if (loanDetailsData?.currency?.code) { + this.currencyCode = loanDetailsData.currency.code; + } + this.createReagingLoanForm(); } @@ -79,21 +90,67 @@ export class LoanReagingComponent implements OnInit { }); } - submit(): void { - const reagingLoanFormData = this.reagingLoanForm.value; + private prepareReagingData() { + const reagingLoanFormData = { ...this.reagingLoanForm.value }; const locale = this.settingsService.language.code; const dateFormat = this.settingsService.dateFormat; const startDate: Date = this.reagingLoanForm.value.startDate; if (reagingLoanFormData.startDate instanceof Date) { reagingLoanFormData.startDate = this.dateUtils.formatDate(startDate, dateFormat); } - const data = { + if (reagingLoanFormData.reAgeInterestHandling && typeof reagingLoanFormData.reAgeInterestHandling === 'object') { + reagingLoanFormData.reAgeInterestHandling = reagingLoanFormData.reAgeInterestHandling.id; + } + return { ...reagingLoanFormData, dateFormat, locale }; - this.loanService.submitLoanActionButton(this.loanId, data, 'reAge').subscribe((response: any) => { - this.router.navigate(['../../transactions'], { relativeTo: this.route }); + } + + preview(): void { + if (this.reagingLoanForm.invalid) { + return; + } + const data = this.prepareReagingData(); + + this.loanService.getReAgePreview(this.loanId, data).subscribe({ + next: (response: RepaymentSchedule) => { + const currencyCode = response.currency?.code || this.currencyCode; + + if (!currencyCode) { + console.error('Currency code is not available in API response or loan details'); + return; + } + + this.dialog.open(ReAgePreviewDialogComponent, { + data: { + repaymentSchedule: response, + currencyCode: currencyCode + }, + width: '95%', + maxWidth: '1400px', + height: '90vh' + }); + }, + error: (error) => { + console.error('Error loading re-age preview:', error); + } + }); + } + + submit(): void { + if (this.reagingLoanForm.invalid) { + return; + } + const data = this.prepareReagingData(); + this.loanService.submitLoanActionButton(this.loanId, data, 'reAge').subscribe({ + next: (response: any) => { + this.router.navigate(['../../transactions'], { relativeTo: this.route }); + }, + error: (error) => { + console.error('Error submitting re-age:', error); + } }); } } diff --git a/src/app/loans/loans-view/loan-account-actions/loan-reaging/re-age-preview-dialog/re-age-preview-dialog.component.html b/src/app/loans/loans-view/loan-account-actions/loan-reaging/re-age-preview-dialog/re-age-preview-dialog.component.html new file mode 100644 index 000000000..e33efb236 --- /dev/null +++ b/src/app/loans/loans-view/loan-account-actions/loan-reaging/re-age-preview-dialog/re-age-preview-dialog.component.html @@ -0,0 +1,16 @@ +

{{ 'labels.heading.Repayment Schedule Preview' | translate }}

+ + + + + + + + + diff --git a/src/app/loans/loans-view/loan-account-actions/loan-reaging/re-age-preview-dialog/re-age-preview-dialog.component.scss b/src/app/loans/loans-view/loan-account-actions/loan-reaging/re-age-preview-dialog/re-age-preview-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/loans/loans-view/loan-account-actions/loan-reaging/re-age-preview-dialog/re-age-preview-dialog.component.ts b/src/app/loans/loans-view/loan-account-actions/loan-reaging/re-age-preview-dialog/re-age-preview-dialog.component.ts new file mode 100644 index 000000000..aa5a63de3 --- /dev/null +++ b/src/app/loans/loans-view/loan-account-actions/loan-reaging/re-age-preview-dialog/re-age-preview-dialog.component.ts @@ -0,0 +1,47 @@ +import { Component, Inject } from '@angular/core'; +import { + MAT_DIALOG_DATA, + MatDialogRef, + MatDialogTitle, + MatDialogContent, + MatDialogActions +} from '@angular/material/dialog'; +import { MatButton } from '@angular/material/button'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { RepaymentSchedule } from 'app/loans/models/loan-account.model'; +import { RepaymentScheduleTabComponent } from '../../../repayment-schedule-tab/repayment-schedule-tab.component'; + +export interface ReAgePreviewDialogData { + repaymentSchedule: RepaymentSchedule; + currencyCode: string; +} + +@Component({ + selector: 'mifosx-re-age-preview-dialog', + templateUrl: './re-age-preview-dialog.component.html', + styleUrls: ['./re-age-preview-dialog.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDialogTitle, + MatDialogContent, + MatDialogActions, + MatButton, + RepaymentScheduleTabComponent + ] +}) +export class ReAgePreviewDialogComponent { + repaymentSchedule: RepaymentSchedule; + currencyCode: string; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ReAgePreviewDialogData + ) { + this.repaymentSchedule = data.repaymentSchedule; + this.currencyCode = data.currencyCode; + } + + close(): void { + this.dialogRef.close(); + } +} diff --git a/src/app/loans/loans-view/repayment-schedule-tab/repayment-schedule-tab.component.html b/src/app/loans/loans-view/repayment-schedule-tab/repayment-schedule-tab.component.html index 4da711a54..7aa2a3d19 100644 --- a/src/app/loans/loans-view/repayment-schedule-tab/repayment-schedule-tab.component.html +++ b/src/app/loans/loans-view/repayment-schedule-tab/repayment-schedule-tab.component.html @@ -5,7 +5,7 @@ - +
@@ -63,7 +63,7 @@ @@ -77,7 +77,7 @@ @@ -89,7 +89,7 @@ @@ -103,7 +103,9 @@ @@ -115,7 +117,7 @@ @@ -126,7 +128,7 @@ {{ item.totalPaidForPeriod | formatNumber }} @@ -138,7 +140,7 @@ {{ item.totalPaidInAdvanceForPeriod | formatNumber }} @@ -148,7 +150,7 @@ {{ item.totalPaidLateForPeriod | formatNumber }} @@ -161,7 +163,7 @@ {{ item.totalWaivedForPeriod | formatNumber }} @@ -180,7 +182,7 @@ @@ -218,8 +220,8 @@
# {{ item.period }} - {{ repaymentScheduleDetails.totalPrincipalExpected | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} - {{ repaymentScheduleDetails.totalInterestCharged | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ repaymentScheduleDetails?.totalInterestCharged | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} - {{ repaymentScheduleDetails.totalFeeChargesCharged | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ repaymentScheduleDetails?.totalFeeChargesCharged | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} - {{ repaymentScheduleDetails.totalPenaltyChargesCharged | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ + repaymentScheduleDetails?.totalPenaltyChargesCharged | currency: currencyCode : 'symbol-narrow' : '1.2-2' + }} - {{ repaymentScheduleDetails.totalRepaymentExpected | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ repaymentScheduleDetails?.totalRepaymentExpected | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} - {{ repaymentScheduleDetails.totalRepayment | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ repaymentScheduleDetails?.totalRepayment | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} - {{ repaymentScheduleDetails.totalPaidInAdvance | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ repaymentScheduleDetails?.totalPaidInAdvance | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} - {{ repaymentScheduleDetails.totalPaidLate | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ repaymentScheduleDetails?.totalPaidLate | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} - {{ repaymentScheduleDetails.totalWaived | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ repaymentScheduleDetails?.totalWaived | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} {{ item.totalOutstandingForPeriod | formatNumber }} - {{ repaymentScheduleDetails.totalOutstanding | currency: currencyCode : 'symbol-narrow' : '1.2-2' }} + {{ repaymentScheduleDetails?.totalOutstanding | currency: currencyCode : 'symbol-narrow' : '1.2-2' }}
@@ -285,7 +287,7 @@
{{ 'labels.inputs.Actions' | translate }} - +