diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 90c6d8aef3..606f5d6974 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,7 @@ /** Angular Imports */ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; +import { I18nService } from './core/i18n/i18n.service'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HttpBackend, @@ -130,7 +131,9 @@ export function HttpLoaderFactory(http: HttpClient) { provide: HTTP_INTERCEPTORS, useClass: !environment.OIDC.oidcServerEnabled ? TokenInterceptor : ZitadelTokenInterceptor, multi: true - } + }, + I18nService + ] }) export class AppModule {} diff --git a/src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts b/src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts index c1d04dcfb7..3af7a00547 100644 --- a/src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts +++ b/src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts @@ -177,37 +177,42 @@ export class CreateGlimAccountComponent { }; } - setData(client: any, totalLoan: number): any { + setData(applicationId: number, client: any, totalLoan: number, isFirst: boolean, isLast: boolean): any { const locale = this.settingsService.language.code; const dateFormat = this.settingsService.dateFormat; // const monthDayFormat = 'dd MMMM'; - const data = { + const data: any = { ...this.loansAccount, charges: this.loansAccount.charges.map((charge: any) => ({ chargeId: charge.id, - amount: charge.amount + amount: charge.amount, + currency: charge.currency })), clientId: client.id, totalLoan: totalLoan, loanType: 'glim', + applicationId: applicationId, amortizationType: 1, - isParentAccount: true, principal: client.principal, syncDisbursementWithMeeting: false, expectedDisbursementDate: this.dateUtils.formatDate(this.loansAccount.expectedDisbursementDate, dateFormat), submittedOnDate: this.dateUtils.formatDate(this.loansAccount.submittedOnDate, dateFormat), - dateFormat, - // monthDayFormat, - locale + dateFormat: dateFormat, + locale: locale, + groupId: this.loansAccountTemplate.group.id }; - data.groupId = this.loansAccountTemplate.group.id; - + if (isFirst) { + data.isParentAccount = true; + } + if (isLast) { + data.lastApplication = true; + } delete data.principalAmount; - // TODO: 2025-03-17: Apparently (?!) unsupported for GLIM delete data.allowPartialPeriodInterestCalculation; delete data.multiDisburseLoan; delete data.isFloatingInterestRate; - + delete data.moratoriumPrincipal; + delete data.moratoriumInterest; return JSON.stringify(data); } @@ -216,12 +221,17 @@ export class CreateGlimAccountComponent { const requestData = []; const memberSelected = this.selectedMembers?.selectedMembers ?? []; const totalLoan = this.totalLoanAmount(); + const applicationId = Math.floor(1000000000 + Math.random() * 9000000000); + for (let index = 0; index < memberSelected.length; index++) { + const isFirst = index === 0; + const isLast = index === memberSelected.length - 1; requestData.push({ requestId: index.toString(), + reference: index === 0 ? null : (index - 1).toString(), method: 'POST', relativeUrl: 'loans', - body: this.setData(memberSelected[index], totalLoan) + body: this.setData(applicationId, memberSelected[index], totalLoan, isFirst, isLast) }); } return requestData; @@ -236,57 +246,10 @@ export class CreateGlimAccountComponent { return total; } - /** - * Creates a new GLIM account. - */ - submit() { - this.selectedMembers = this.loansActiveClientMembers?.selectedClientMembers; - const memberSelected = this.loansActiveClientMembers?.selectedClientMembers?.selectedMembers ?? []; - if (!memberSelected.length) return; - const gsimMemberIds = new Set(this.dataSource.map((m: any) => Number(m.id))); - for (const member of memberSelected) { - const memberId = Number(member.id); - // Validate savings account ownership - const ownerId = Number(member.linkAccountOwnerId); - if (member.linkAccountId && member.linkAccountOwnerId && ownerId !== memberId) { - this.i18nService.translate('errors.linkedSavingsAccountOwnership').subscribe((msg: string) => { - this.notify({ defaultUserMessage: msg, errors: [] }, { memberId }); - }); - return; - } - // Validate GSIM membership - if (!gsimMemberIds.has(memberId)) { - this.i18nService.translate('errors.clientNotInGSIM', { id: memberId }).subscribe((msg: string) => { - this.notify({ defaultUserMessage: msg, errors: [] }, { memberId }); - }); - return; - } - } - - // Use date format from settingsService for interestChargedFromDate - const data = this.buildRequestData(); - this.loansService.createGlimAccount(data).subscribe((response: any) => { - const body = JSON.parse(response[0].body); - if (body.glimId) { - this.router.navigate( - [ - '../', - body.glimId - ], - { relativeTo: this.route } - ); - } else { - this.notify(body, { batchSize: data.length }); - } - }); - } - - notify(body: any, context?: { [k: string]: unknown }) { - const parts: string[] = [String(body?.defaultUserMessage ?? '')]; - if (Array.isArray(body?.errors)) { - for (const e of body.errors) parts.push(String(e?.developerMessage ?? '')); - } - if (context) parts.push(`Context: ${JSON.stringify(context)}`); - console.error(parts.join(' ').trim()); + notify(body: any, data: any) { + let message = body.defaultUserMessage + ' '; + body.errors?.forEach((error: any) => (message += error.developerMessage + ' ')); + message += 'Data: ' + JSON.stringify(data); + console.error(message); } } diff --git a/src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.html b/src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.html index bec25484fa..bd323915a2 100644 --- a/src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.html +++ b/src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.html @@ -24,7 +24,7 @@ {{ 'labels.inputs.name' | translate }} - {{ charge.name + ', ' + charge.currency.displaySymbol }} + {{ charge.name }}, {{ charge.currency.displaySymbol }} @@ -39,7 +39,13 @@ {{ 'labels.inputs.Amount' | translate }} {{ charge.amount }} - @@ -84,6 +90,8 @@ charge.chargeTimeType.value === 'Specified due date' " (click)="editChargeDate(charge)" + type="button" + aria-label="Edit charge date" > @@ -93,7 +101,7 @@ {{ 'labels.inputs.Actions' | translate }} - @@ -110,7 +118,9 @@

{{ 'labels.heading.Overdue Charges' | translate }} {{ 'labels.inputs.name' | translate }} - {{ charge.name }},{{ charge.currency.displaySymbol }} + + {{ charge.name }}, {{ charge.currency.displaySymbol }} + @@ -120,7 +130,18 @@

{{ 'labels.heading.Overdue Charges' | translate }} {{ 'labels.inputs.Amount' | translate }} - {{ charge.amount | formatNumber }} + + {{ charge.amount | formatNumber }} + + @@ -134,15 +155,15 @@

{{ 'labels.heading.Overdue Charges' | translate }}
- - -
diff --git a/src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts b/src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts index 1dc89530ea..f216e7610c 100644 --- a/src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts +++ b/src/app/loans/loans-account-stepper/loans-account-charges-step/loans-account-charges-step.component.ts @@ -326,6 +326,33 @@ export class LoansAccountChargesStepComponent implements OnInit, OnChanges { }); } + editOverdueChargeAmount(charge: { amount: number; [key: string]: any }) { + const formfields: FormfieldBase[] = [ + new InputBase({ + controlName: 'amount', + label: 'Amount', + value: charge.amount, + type: 'number', + required: false + }) + + ]; + const data = { + title: 'Edit Overdue Charge Amount', + layout: { addButtonText: 'Confirm' }, + formfields: formfields + }; + const editNoteDialogRef = this.dialog.open(FormDialogComponent, { data }); + editNoteDialogRef.afterClosed().subscribe((response?: { data?: { value: { amount: number } } }) => { + if (response?.data) { + const newCharge = { ...charge, amount: response.data.value.amount }; + this.overDueChargesDataSource.splice(this.overDueChargesDataSource.indexOf(charge), 1, newCharge); + this.overDueChargesDataSource = this.overDueChargesDataSource.concat([]); + this.pristine = false; + } + }); + } + get isValid() { return true; // !this.activeClientMembers || diff --git a/src/app/loans/loans-account-stepper/loans-account-preview-step/loans-account-preview-step.component.html b/src/app/loans/loans-account-stepper/loans-account-preview-step/loans-account-preview-step.component.html index cbe1a36f47..2ecaafc1fd 100644 --- a/src/app/loans/loans-account-stepper/loans-account-preview-step/loans-account-preview-step.component.html +++ b/src/app/loans/loans-account-stepper/loans-account-preview-step/loans-account-preview-step.component.html @@ -262,14 +262,14 @@

{{ 'labels.heading.Charges' | translate }}

{{ 'labels.inputs.name' | translate }} - {{ charge.name + ', ' + charge.currency.displaySymbol }} + {{ charge.name }}, {{ charge.currency.displaySymbol }} {{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType.value }} + {{ charge.chargeCalculationType?.value || '' }} @@ -283,7 +283,7 @@

{{ 'labels.heading.Charges' | translate }}

{{ 'labels.inputs.Collected On' | translate }} - {{ charge.chargeTimeType.value }} + {{ charge.chargeTimeType?.value || '' }} @@ -291,20 +291,22 @@

{{ 'labels.heading.Charges' | translate }}

{{ 'labels.inputs.Date' | translate }} {{ (charge.dueDate | dateFormat) || 'Unassigned' }} - + {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }} @@ -329,7 +331,9 @@

{{ 'labels.heading.Overdue Charges' | translate }} {{ 'labels.inputs.name' | translate }} - {{ charge.name }},{{ charge.currency.displaySymbol }} + + {{ charge.name }},{{ charge.currency.displaySymbol }} + diff --git a/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.html b/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.html index 82b3c03e95..45dc3fb9cc 100644 --- a/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.html +++ b/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.html @@ -1,4 +1,28 @@
+
+ + {{ 'labels.inputs.Moratorium on Principal (months)' | translate }} + + + + {{ 'labels.inputs.Moratorium on Interest (months)' | translate }} + + +
{{ 'labels.inputs.Loan Term' | translate }} - + {{ 'labels.inputs.Loan Term' | translate }} {{ 'labels.commons.is' | translate }} {{ 'labels.commons.required' | translate }} @@ -39,7 +63,7 @@

{{ 'labels.inputs.Fixed Length' | translate }} - + {{ loansAccountTermsForm.value.loanTermFrequencyType | find: termFrequencyTypeData : 'id' : 'value' @@ -54,6 +78,7 @@

{{ 'labels.inputs.Repayments' | translate }}

matInput formControlName="numberOfRepayments" matTooltip="{{ 'tooltips.Enter the total count of repayments' | translate }}" + aria-label="Number of repayments" /> {{ 'labels.inputs.Number of repayments' | translate }} {{ 'labels.commons.is' | translate }} @@ -63,7 +88,7 @@

{{ 'labels.inputs.Repayments' | translate }}

{{ 'labels.inputs.Installment Amount' | translate }} - + @@ -75,6 +100,7 @@

{{ 'labels.inputs.Repayments' | translate }}

matTooltip="{{ 'tooltips.May be entered to override' | translate }}" [matDatepicker]="repaymentsPicker" formControlName="repaymentsStartingFromDate" + aria-label="First repayment on" /> @@ -89,6 +115,7 @@

{{ 'labels.inputs.Repayments' | translate }}

[matDatepicker]="interestPicker" matTooltip="{{ 'tooltips.May be entered to override the date' | translate }}" formControlName="interestChargedFromDate" + aria-label="Interest charged from" /> @@ -110,6 +137,7 @@

required formControlName="repaymentEvery" matTooltip="{{ 'tooltips.Fields are input to calculating the repayment schedule' | translate }}" + aria-label="Repaid every" /> {{ 'labels.inputs.Repaid every' | translate }} {{ 'labels.commons.is' | translate }} @@ -165,7 +193,7 @@

{{ 'labels.inputs.Nominal interest rate' | translate {{ 'labels.inputs.Nominal interest rate' | translate }} % - + @@ -212,7 +240,12 @@

{{ 'labels.inputs.Nominal interest rate' | translate {{ 'labels.inputs.Principal Percentage Per Installment' | translate }} - + {{ 'labels.heading.Interest Calculations' | translate type="number" formControlName="inArrearsTolerance" matTooltip="{{ 'tooltips.With Arrears tolerance' | translate }}" + aria-label="Arrears tolerance" /> @@ -338,6 +372,7 @@

{{ 'labels.heading.Interest Calculations' | translate matInput formControlName="graceOnInterestCharged" matTooltip="{{ 'tooltips.If the Interest Free Period' | translate }}" + aria-label="Interest free period" /> @@ -348,17 +383,17 @@

{{ 'labels.inputs.Grace on principal payment' | translate }} - + {{ 'labels.inputs.Grace on interest payment' | translate }} - + {{ 'labels.inputs.On arrears ageing' | translate }} - +
@@ -490,7 +525,13 @@

{{ 'labels.heading.Loan Tranche Details' | translate
{{ 'labels.inputs.Maximum allowed outstanding balance' | translate }} - + {{ 'labels.inputs.Maximum allowed outstanding balance' | translate }} {{ 'labels.commons.is' | translate }} {{ 'labels.commons.required' | translate }} @@ -501,6 +542,7 @@

{{ 'labels.heading.Loan Tranche Details' | translate type="button" mat-icon-button color="primary" + aria-label="Add Disbursement Data Entry" required (click)="addDisbursementDataEntry(disbursementData)" [disabled]="isMultiDisbursedCompleted" @@ -537,6 +579,7 @@

type="button" mat-icon-button color="warn" + aria-label="Remove Disbursement Data Entry" (click)="removeDisbursementDataEntry(rowIndex)" matTooltip="{{ 'tooltips.Delete' | translate }}" matTooltipPosition="left" @@ -614,7 +657,7 @@

{{ 'labels.heading.Collaterals Data' | translate }}
-
@@ -644,7 +687,13 @@

{{ 'labels.heading.Collaterals Data' | translate }} {{ 'labels.inputs.Actions' | translate }} - @@ -656,15 +705,15 @@

{{ 'labels.heading.Collaterals Data' | translate }}
- - -
diff --git a/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts b/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts index fd294e31ad..cbe8d2686a 100644 --- a/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts +++ b/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts @@ -219,7 +219,10 @@ export class LoansAccountTermsStepComponent implements OnInit, OnChanges { multiDisburseLoan: this.loansAccountTermsData.multiDisburseLoan, interestRateFrequencyType: this.loansAccountTermsData.interestRateFrequencyType.id, balloonRepaymentAmount: this.loansAccountTermsData.balloonRepaymentAmount, - interestRecognitionOnDisbursementDate: this.loansAccountTermsData.interestRecognitionOnDisbursementDate || false + interestRecognitionOnDisbursementDate: + this.loansAccountTermsData.interestRecognitionOnDisbursementDate || false, + moratoriumPrincipal: this.loansAccountTermsData.moratoriumPrincipal ?? 0, + moratoriumInterest: this.loansAccountTermsData.moratoriumInterest ?? 0 }); this.setAdvancedPaymentStrategyControls(); @@ -345,7 +348,10 @@ export class LoansAccountTermsStepComponent implements OnInit, OnChanges { multiDisburseLoan: this.loansAccountTermsData.multiDisburseLoan, interestRateFrequencyType: this.loansAccountTermsData.interestRateFrequencyType.id, balloonRepaymentAmount: this.loansAccountTermsData.balloonRepaymentAmount, - interestRecognitionOnDisbursementDate: this.loansAccountTermsData.interestRecognitionOnDisbursementDate || false + interestRecognitionOnDisbursementDate: + this.loansAccountTermsData.interestRecognitionOnDisbursementDate || false, + moratoriumPrincipal: this.loansAccountTermsData.moratoriumPrincipal ?? 0, + moratoriumInterest: this.loansAccountTermsData.moratoriumInterest ?? 0 }); } this.createloansAccountTermsForm(); @@ -358,12 +364,12 @@ export class LoansAccountTermsStepComponent implements OnInit, OnChanges { this.loansAccountTermsForm.removeControl('maxOutstandingLoanBalance'); this.loansAccountTermsForm.addControl( 'maxOutstandingLoanBalance', - new UntypedFormControl(this.loansAccountTermsData.maxOutstandingLoanBalance, Validators.required) + new UntypedFormControl(this.loansAccountTermsData?.maxOutstandingLoanBalance ?? 0, Validators.required) ); } else { this.loansAccountTermsForm.addControl( 'maxOutstandingLoanBalance', - new UntypedFormControl(this.loansAccountTermsData.maxOutstandingLoanBalance) + new UntypedFormControl(this.loansAccountTermsData?.maxOutstandingLoanBalance ?? 0) ); } } @@ -517,7 +523,9 @@ export class LoansAccountTermsStepComponent implements OnInit, OnChanges { multiDisburseLoan: [false], interestRateFrequencyType: [''], balloonRepaymentAmount: [''], - interestRecognitionOnDisbursementDate: [false] + interestRecognitionOnDisbursementDate: [false], + moratoriumPrincipal: [0], + moratoriumInterest: [0] }); } diff --git a/src/app/login/login-form/login-form.component.ts b/src/app/login/login-form/login-form.component.ts index dcf93cae15..445179c0fd 100644 --- a/src/app/login/login-form/login-form.component.ts +++ b/src/app/login/login-form/login-form.component.ts @@ -10,7 +10,7 @@ import { AuthenticationService } from '../../core/authentication/authentication. import { MatFormField, MatPrefix, MatLabel, MatError, MatSuffix } from '@angular/material/form-field'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatIconButton, MatButton } from '@angular/material/button'; -import { MatCheckbox } from '@angular/material/checkbox'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatProgressBar } from '@angular/material/progress-bar'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @@ -31,6 +31,7 @@ import { environment } from '../../../environments/environment'; MatPrefix, FaIconComponent, MatIconButton, + MatCheckboxModule, MatProgressBar, MatProgressSpinner ]