diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 09a73871d..7cff67151 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,6 +9,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, @@ -139,7 +140,8 @@ 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 a2ae6ef27..b3be87d18 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 @@ -185,58 +185,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) => { - const chargeId = charge.chargeId ?? charge.id; - if (chargeId == null) { - return null; - } - const mappedCharge: any = { - chargeId, - amount: charge.amount - }; - if (charge.id && charge.id !== chargeId) { - mappedCharge.id = charge.id; - } - if (charge.dueDate) { - mappedCharge.dueDate = this.dateUtils.formatDate(charge.dueDate, dateFormat); - } - if (charge.feeInterval !== undefined) { - mappedCharge.feeInterval = charge.feeInterval; - } - if (charge.feeOnMonthDay !== undefined) { - mappedCharge.feeOnMonthDay = charge.feeOnMonthDay; - } - return mappedCharge; - }) - .filter(Boolean), + charges: this.loansAccount.charges.map((charge: any) => ({ + chargeId: charge.id, + 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); } @@ -245,12 +229,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; @@ -265,57 +254,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 375f96cf8..b7d6bc180 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 @@ -34,7 +34,7 @@ {{ 'labels.inputs.name' | translate }} - {{ charge.name + ', ' + (charge.currency?.displaySymbol || '') }} + {{ charge.name }}, {{ charge.currency.displaySymbol }} @@ -49,7 +49,13 @@ {{ 'labels.inputs.Amount' | translate }} {{ charge.amount }} - @@ -102,7 +108,7 @@ {{ 'labels.inputs.Actions' | translate }} - @@ -119,7 +125,9 @@ - + @@ -129,7 +137,18 @@ - + @@ -143,17 +162,15 @@
- - - @if (loanId) { - - } +
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 11d5a5e0c..b3c93bd28 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 @@ -331,6 +331,32 @@ 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 66ade0198..55bc789b9 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 @@ -344,10 +344,10 @@ } @if ( !( - charge.chargeTimeType.value === 'Monthly Fee' || - charge.chargeTimeType.value === 'Annual Fee' || - charge.chargeTimeType.value === 'Specified due date' || - charge.chargeTimeType.value === 'Weekly Fee' + charge.chargeTimeType?.value === 'Monthly Fee' || + charge.chargeTimeType?.value === 'Annual Fee' || + charge.chargeTimeType?.value === 'Specified due date' || + charge.chargeTimeType?.value === 'Weekly Fee' ) ) { 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 b022eeb7f..a488b9a2d 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 @@ -73,6 +73,7 @@ matInput formControlName="numberOfRepayments" matTooltip="{{ 'tooltips.Enter the total count of repayments' | translate }}" + aria-label="Number of repayments" /> @if (loansAccountTermsForm.controls.numberOfRepayments.hasError('required')) { @@ -98,6 +99,7 @@ matTooltip="{{ 'tooltips.May be entered to override' | translate }}" [matDatepicker]="repaymentsPicker" formControlName="repaymentsStartingFromDate" + aria-label="First repayment on" /> @@ -112,6 +114,7 @@ [matDatepicker]="interestPicker" matTooltip="{{ 'tooltips.May be entered to override the date' | translate }}" formControlName="interestChargedFromDate" + aria-label="Interest charged from" /> @@ -133,6 +136,7 @@ required formControlName="repaymentEvery" matTooltip="{{ 'tooltips.Fields are input to calculating the repayment schedule' | translate }}" + aria-label="Repaid every" /> @if (loansAccountTermsForm.controls.repaymentEvery.hasError('required')) { @@ -198,7 +202,7 @@ @if (!loansAccountTermsData?.isLoanProductLinkedToFloatingRate) { {{ 'labels.inputs.Nominal interest rate' | translate }} % - + {{ 'labels.inputs.Frequency' | translate }} @@ -375,6 +379,7 @@ type="number" formControlName="inArrearsTolerance" matTooltip="{{ 'tooltips.With Arrears tolerance' | translate }}" + aria-label="Arrears tolerance" /> @@ -384,6 +389,7 @@ matInput formControlName="graceOnInterestCharged" matTooltip="{{ 'tooltips.If the Interest Free Period' | translate }}" + aria-label="Interest free period" /> @@ -394,17 +400,17 @@ {{ 'labels.inputs.Grace on principal payment' | translate }} - + {{ 'labels.inputs.Grace on interest payment' | translate }} - + {{ 'labels.inputs.On arrears ageing' | translate }} - + @if (isDelinquencyEnabled()) { @@ -575,6 +581,7 @@ type="button" mat-icon-button color="primary" + aria-label="Add Disbursement Data Entry" required (click)="addDisbursementDataEntry(disbursementData)" [disabled]="isMultiDisbursedCompleted" @@ -610,6 +617,7 @@ type="button" mat-icon-button color="warn" + aria-label="Remove Disbursement Data Entry" (click)="removeDisbursementDataEntry(rowIndex)" matTooltip="{{ 'tooltips.Delete' | translate }}" matTooltipPosition="left" @@ -685,7 +693,7 @@
-
@@ -715,7 +723,13 @@
@@ -727,11 +741,11 @@
- - diff --git a/src/app/login/login-form/login-form.component.ts b/src/app/login/login-form/login-form.component.ts index 40d3b617d..3fd034c4e 100644 --- a/src/app/login/login-form/login-form.component.ts +++ b/src/app/login/login-form/login-form.component.ts @@ -16,8 +16,9 @@ import { finalize } from 'rxjs/operators'; /** Custom Services */ import { AuthenticationService } from '../../core/authentication/authentication.service'; import { MatPrefix } from '@angular/material/form-field'; -import { M3IconComponent } from '../../shared/m3-ui/m3-icon/m3-icon.component'; -import { M3ButtonComponent } from '../../shared/m3-ui/m3-button/m3-button.component'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { MatIconButton, MatButton } from '@angular/material/button'; +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'; @@ -34,8 +35,9 @@ import { environment } from '../../../environments/environment'; imports: [ ...STANDALONE_SHARED_IMPORTS, MatPrefix, - M3IconComponent, - M3ButtonComponent, + FaIconComponent, + MatIconButton, + MatCheckboxModule, MatProgressBar, MatProgressSpinner ]
{{ 'labels.inputs.name' | translate }}{{ charge.name + ', ' + (charge.currency?.displaySymbol || '') }} + {{ charge.name }}, {{ charge.currency.displaySymbol }} + {{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }} + {{ charge.amount | formatNumber }} + + {{ 'labels.inputs.Actions' | translate }} -