WEB-642: Add Loan Originators management (#3095)
Co-authored-by: Jose Alberto Hernandez <alberto@black-box.local>
This commit is contained in:
co-authored by
Jose Alberto Hernandez
parent
bd2408fcc4
commit
0bc1926199
@@ -15,6 +15,7 @@ import { Observable } from 'rxjs';
|
||||
|
||||
/** Custom Services */
|
||||
import { LoansService } from '../loans.service';
|
||||
import { OrganizationService } from 'app/organization/organization.service';
|
||||
|
||||
/**
|
||||
* Loans notes data resolver.
|
||||
@@ -22,6 +23,7 @@ import { LoansService } from '../loans.service';
|
||||
@Injectable()
|
||||
export class LoanActionButtonResolver {
|
||||
private loansService = inject(LoansService);
|
||||
private organizationService = inject(OrganizationService);
|
||||
|
||||
/**
|
||||
* Returns the Loans Notes Data.
|
||||
@@ -88,6 +90,8 @@ export class LoanActionButtonResolver {
|
||||
return this.loansService.getLoanActionTemplate(loanId, 'reAge');
|
||||
} else if (loanActionButton === 'Re-Amortize') {
|
||||
return this.loansService.getLoanActionTemplate(loanId, 'reAmortization');
|
||||
} else if (loanActionButton === 'Attach Loan Originator') {
|
||||
return this.organizationService.getLoanOriginators();
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
-11
@@ -56,17 +56,6 @@
|
||||
<input matInput [value]="loanData.availableDisbursementAmountWithOverApplied | formatNumber" readonly />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Transaction Amount' | translate }}</mat-label>
|
||||
<input matInput type="number" required formControlName="approvedLoanAmount" />
|
||||
@if (approveLoanForm.controls.approvedLoanAmount.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Transaction Amount' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="note" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<div class="container mat-elevation-z8">
|
||||
<mat-card>
|
||||
<form [formGroup]="attachLoanOriginatorForm" (ngSubmit)="submit()">
|
||||
<mat-card-content>
|
||||
<div class="layout-column">
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Loan Originator' | translate }}</mat-label>
|
||||
<mat-select formControlName="originatorId">
|
||||
@for (loanOriginator of loanOriginators; track loanOriginator) {
|
||||
<mat-option [value]="loanOriginator.id">
|
||||
{{ loanOriginator.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||
<button type="button" mat-raised-button [routerLink]="['../../general']">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
[disabled]="!attachLoanOriginatorForm.valid"
|
||||
*mifosxHasPermission="'ATTACH_LOAN_ORIGINATOR'"
|
||||
>
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card-content>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/** Angular Imports. */
|
||||
import { Component, Input, OnInit, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
/** Custom Services. */
|
||||
import { LoansService } from 'app/loans/loans.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { LoanOriginator } from 'app/loans/models/loan-account.model';
|
||||
|
||||
/**
|
||||
* Attach Loan Originator component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-attach-originator',
|
||||
templateUrl: './attach-originator.component.html',
|
||||
styleUrl: './attach-originator.component.scss',
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS
|
||||
]
|
||||
})
|
||||
export class AttachOriginatorComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private route = inject(ActivatedRoute);
|
||||
private loanService = inject(LoansService);
|
||||
private router = inject(Router);
|
||||
|
||||
/** Attach Loan Originator Loan form. */
|
||||
attachLoanOriginatorForm: UntypedFormGroup;
|
||||
/** Loan data. */
|
||||
loanOriginators: LoanOriginator[] = [];
|
||||
@Input() dataObject: any;
|
||||
|
||||
/** Loan Id */
|
||||
loanId: string | null = null;
|
||||
|
||||
constructor() {
|
||||
this.loanId = this.route.snapshot.params['loanId'];
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setAttachLoanOriginatorForm();
|
||||
console.log(this.dataObject);
|
||||
this.loanOriginators = [];
|
||||
this.dataObject.forEach((loanOriginator: LoanOriginator) => {
|
||||
if (loanOriginator.status === 'ACTIVE') {
|
||||
this.loanOriginators.push(loanOriginator);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Attach Loan Originator form.
|
||||
*/
|
||||
setAttachLoanOriginatorForm() {
|
||||
this.attachLoanOriginatorForm = this.formBuilder.group({
|
||||
originatorId: [
|
||||
'',
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits Approve form.
|
||||
*/
|
||||
submit() {
|
||||
const approveLoanFormData = this.attachLoanOriginatorForm.value;
|
||||
this.loanService.attachLoanOriginator(this.loanId, approveLoanFormData.originatorId).subscribe((response: any) => {
|
||||
this.router.navigate(['../../general'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -114,3 +114,6 @@
|
||||
@if (actions['Undo Write-off']) {
|
||||
<mifosx-undo-write-off [dataObject]="actionButtonData"></mifosx-undo-write-off>
|
||||
}
|
||||
@if (actions['Attach Loan Originator']) {
|
||||
<mifosx-attach-originator [dataObject]="actionButtonData"></mifosx-attach-originator>
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import { LoanReamortizeComponent } from './loan-reamortize/loan-reamortize.compo
|
||||
import { AddInterestPauseComponent } from './add-interest-pause/add-interest-pause.component';
|
||||
import { UndoWriteOffComponent } from './undo-write-off/undo-write-off.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { AttachOriginatorComponent } from './attach-originator/attach-originator.component';
|
||||
|
||||
/**
|
||||
* Loan Account Actions component.
|
||||
@@ -79,7 +80,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
LoanReagingComponent,
|
||||
LoanReamortizeComponent,
|
||||
AddInterestPauseComponent,
|
||||
UndoWriteOffComponent
|
||||
UndoWriteOffComponent,
|
||||
AttachOriginatorComponent
|
||||
]
|
||||
})
|
||||
export class LoanAccountActionsComponent {
|
||||
@@ -130,6 +132,7 @@ export class LoanAccountActionsComponent {
|
||||
'Contract Termination': boolean;
|
||||
'Buy Down Fee': boolean;
|
||||
'Undo Write-off': boolean;
|
||||
'Attach Loan Originator': boolean;
|
||||
} = {
|
||||
Close: false,
|
||||
'Undo Approval': false,
|
||||
@@ -169,7 +172,8 @@ export class LoanAccountActionsComponent {
|
||||
'Capitalized Income': false,
|
||||
'Contract Termination': false,
|
||||
'Buy Down Fee': false,
|
||||
'Undo Write-off': false
|
||||
'Undo Write-off': false,
|
||||
'Attach Loan Originator': false
|
||||
};
|
||||
|
||||
actionButtonData: any;
|
||||
|
||||
@@ -90,6 +90,11 @@ export class LoansAccountButtonConfiguration {
|
||||
name: 'Reject',
|
||||
icon: 'times',
|
||||
taskPermissionName: 'REJECT_LOAN'
|
||||
},
|
||||
{
|
||||
name: 'Attach Loan Originator',
|
||||
icon: 'edit',
|
||||
taskPermissionName: 'ATTACH_LOAN_ORIGINATOR'
|
||||
}
|
||||
];
|
||||
break;
|
||||
|
||||
@@ -34,16 +34,35 @@
|
||||
{{ item.status }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="originatorTypeId">
|
||||
<ng-container matColumnDef="originatorType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Originator Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
{{ item.originatorTypeId }}
|
||||
{{ item.originatorType.name }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="channelTypeId">
|
||||
<ng-container matColumnDef="channelType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Channel Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
{{ item.channelTypeId }}
|
||||
{{ item.channelType.name }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let loanOriginator">
|
||||
@if (loanStatus.pendingApproval) {
|
||||
<button
|
||||
type="button"
|
||||
color="warn"
|
||||
title="{{ 'tooltips.Dettach' | translate }}"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'tooltips.Delete' | translate }}"
|
||||
*mifosxHasPermission="'DETTACH_LOAN_ORIGINATOR'"
|
||||
matTooltipPosition="above"
|
||||
(click)="dettachLoanOriginator(loanOriginator)"
|
||||
>
|
||||
<fa-icon icon="trash" size="lg"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="loanoriginatorsColumns"></tr>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
MatCell,
|
||||
MatCellDef,
|
||||
@@ -18,8 +20,13 @@ import {
|
||||
MatRowDef,
|
||||
MatTable
|
||||
} from '@angular/material/table';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { LoansService } from 'app/loans/loans.service';
|
||||
import { LoanOriginator } from 'app/loans/models/loan-account.model';
|
||||
import { LoanStatus } from 'app/loans/models/loan-status.model';
|
||||
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
@@ -37,29 +44,73 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
MatRow,
|
||||
MatIconButton,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class LoanOriginatorsTabComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private loansService = inject(LoansService);
|
||||
private translateService = inject(TranslateService);
|
||||
private dialog = inject(MatDialog);
|
||||
|
||||
loanOriginatorsData: LoanOriginator[] = [];
|
||||
loanId: number | null = null;
|
||||
loanId: string | null = null;
|
||||
clientId: string | null = null;
|
||||
|
||||
loanStatus: LoanStatus | null = null;
|
||||
|
||||
loanoriginatorsColumns: string[] = [
|
||||
'id',
|
||||
'externalId',
|
||||
'name',
|
||||
'status',
|
||||
'originatorTypeId',
|
||||
'channelTypeId'
|
||||
'originatorType',
|
||||
'channelType',
|
||||
'actions'
|
||||
];
|
||||
|
||||
constructor() {
|
||||
const loanIdParam = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
|
||||
this.loanId = loanIdParam ? Number(loanIdParam) : null;
|
||||
this.clientId = this.route.parent.parent.snapshot.paramMap.get('clientId');
|
||||
this.loanId = this.route.parent?.parent?.snapshot.paramMap.get('loanId');
|
||||
this.route.parent.parent.data.subscribe((data: { loanDetailsData: any }) => {
|
||||
this.loanStatus = data.loanDetailsData.status;
|
||||
});
|
||||
this.route.parent.data.subscribe((data: { loanOriginatorsData: any }) => {
|
||||
this.loanOriginatorsData = data.loanOriginatorsData.originators;
|
||||
});
|
||||
}
|
||||
|
||||
dettachLoanOriginator(loanOriginator: LoanOriginator): void {
|
||||
const dettachCodeDialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||
data: {
|
||||
heading: this.translateService.instant('labels.heading.Loan Originators'),
|
||||
dialogContext:
|
||||
this.translateService.instant('labels.buttons.Delete') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.inputs.Loan Originator') +
|
||||
' ' +
|
||||
loanOriginator.name
|
||||
}
|
||||
});
|
||||
dettachCodeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.loansService.dettachLoanOriginator(this.loanId, String(loanOriginator.id)).subscribe((response) => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refetches data fot the component
|
||||
*/
|
||||
private reload() {
|
||||
const url: string = this.router.url;
|
||||
this.router
|
||||
.navigateByUrl(`/clients/${this.clientId}/loans-accounts`, { skipLocationChange: true })
|
||||
.then(() => this.router.navigate([url]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,6 +658,16 @@ export class LoansService {
|
||||
return this.http.post('/loans?command=calculateLoanSchedule', payload);
|
||||
}
|
||||
|
||||
attachLoanOriginator(loanId: string, originatorId: string): Observable<any> {
|
||||
const emptyBody = {};
|
||||
return this.http.post(`/loans/${loanId}/originators/${originatorId}`, emptyBody);
|
||||
}
|
||||
|
||||
dettachLoanOriginator(loanId: string, originatorId: string): Observable<any> {
|
||||
const emptyBody = {};
|
||||
return this.http.delete(`/loans/${loanId}/originators/${originatorId}`, emptyBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loansAccount Loan account data used for the request
|
||||
* @param loansAccountTemplate Loan account template for getting product default values
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import { CodeValue, Currency } from 'app/shared/models/general.model';
|
||||
|
||||
export interface DelinquencyRange {
|
||||
id: number;
|
||||
@@ -178,6 +178,6 @@ export interface LoanOriginator {
|
||||
externalId: string;
|
||||
name: string;
|
||||
status: string;
|
||||
originatorTypeId: number;
|
||||
channelTypeId: number;
|
||||
originatorType: CodeValue;
|
||||
channelType: CodeValue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user