diff --git a/src/app/clients/clients-routing.module.ts b/src/app/clients/clients-routing.module.ts index 3e60e4774..d035123d3 100644 --- a/src/app/clients/clients-routing.module.ts +++ b/src/app/clients/clients-routing.module.ts @@ -78,6 +78,11 @@ const routes: Routes = [ clientDatatables: ClientDatatablesResolver }, children: [ + { + path: '', + redirectTo: 'general', + pathMatch: 'full' + }, { path: 'general', component: GeneralTabComponent, diff --git a/src/app/loans/common-resolvers/loan-reschedules.resolver.spec.ts b/src/app/loans/common-resolvers/loan-reschedules.resolver.spec.ts new file mode 100644 index 000000000..92b706c0e --- /dev/null +++ b/src/app/loans/common-resolvers/loan-reschedules.resolver.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { LoanReschedulesResolver } from './loan-reschedules.resolver'; + +describe('LoanReschedulesResolver', () => { + let resolver: LoanReschedulesResolver; + + beforeEach(() => { + TestBed.configureTestingModule({}); + resolver = TestBed.inject(LoanReschedulesResolver); + }); + + it('should be created', () => { + expect(resolver).toBeTruthy(); + }); +}); diff --git a/src/app/loans/common-resolvers/loan-reschedules.resolver.ts b/src/app/loans/common-resolvers/loan-reschedules.resolver.ts new file mode 100644 index 000000000..f596c6936 --- /dev/null +++ b/src/app/loans/common-resolvers/loan-reschedules.resolver.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { + Router, Resolve, + RouterStateSnapshot, + ActivatedRouteSnapshot +} from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { LoansService } from '../loans.service'; + +@Injectable({ + providedIn: 'root' +}) +export class LoanReschedulesResolver implements Resolve { + + /** + * @param {LoansService} LoansService Loans service. + */ + constructor(private loansService: LoansService) { } + + /** + * Returns the Loans data. + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const loanId = route.parent.paramMap.get('loanId'); + return this.loansService.loanRescheduleRequests(loanId); + } + +} diff --git a/src/app/loans/loans-routing.module.ts b/src/app/loans/loans-routing.module.ts index d6fcde635..29e43a6cc 100644 --- a/src/app/loans/loans-routing.module.ts +++ b/src/app/loans/loans-routing.module.ts @@ -53,6 +53,9 @@ import { GLIMLoanTemplateResolver } from './common-resolvers/glim-loan-template. import { GroupViewResolver } from 'app/groups/common-resolvers/group-view.resolver'; import { LoanDelinquencyTagsResolver } from './common-resolvers/loan-delinquency-tags.resolver'; import { LoanDelinquencyTagsTabComponent } from './loans-view/loan-delinquency-tags-tab/loan-delinquency-tags-tab.component'; +import { RescheduleLoanComponent } from 'app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component'; +import { LoanReschedulesResolver } from './common-resolvers/loan-reschedules.resolver'; +import { RescheduleLoanTabComponent } from './loans-view/reschedule-loan-tab/reschedule-loan-tab.component'; /** Loans Route. */ const routes: Routes = [ @@ -131,6 +134,19 @@ const routes: Routes = [ }, ] }, + { + path: 'loan-reschedules', + data: {}, + resolve: { + loanRescheduleData: LoanReschedulesResolver + }, + children: [ + { + path: '', + component: RescheduleLoanTabComponent + } + ] + }, { path: 'loan-collateral', component: LoanCollateralTabComponent, diff --git a/src/app/loans/loans-view/loans-view.component.html b/src/app/loans/loans-view/loans-view.component.html index 282d1f4ab..e67dd4770 100644 --- a/src/app/loans/loans-view/loans-view.component.html +++ b/src/app/loans/loans-view/loans-view.component.html @@ -19,11 +19,12 @@

- Loan Product: {{loanDetailsData.loanProductName}}({{loanDetailsData.accountNo}}) + Loan Product: {{loanDetailsData.loanProductName}}({{loanDetailsData.accountNo}})

{{entityType}} Name: {{loanDetailsData.clientName || - loanDetailsData.groupName}}({{loanDetailsData.clientAccountNo}})
+ loanDetailsData.groupName}}({{loanDetailsData.clientAccountNo}})
Loan Account Classification : {{loanDetailsData?.delinquencyRange.classification}}
@@ -165,6 +166,10 @@ Charges + + Loan Reschedules + Loan Documents diff --git a/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.html b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.html new file mode 100644 index 000000000..8c100bcdf --- /dev/null +++ b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.html @@ -0,0 +1,79 @@ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
# {{ item.id }} Status +
+ + + {{ item.statusEnum.value | translate }} + +
+
From Date + {{ item.rescheduleFromDate | dateFormat }} + Reason + {{ item.rescheduleReasonCodeValue.name }} + Updated On + + {{ item.timeline.submittedOnDate | dateFormat }} + + + {{ item.timeline.approvedOnDate | dateFormat }} + + + {{ item.timeline.rejectedOnDate | dateFormat }} + + Actions + + + + + +
+ +
diff --git a/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.scss b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.scss new file mode 100644 index 000000000..f8b494af1 --- /dev/null +++ b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.scss @@ -0,0 +1,19 @@ +table { + width: 100%; + .action-button { + min-width: 26px; + padding: 0 0 3px 0; + margin: 0 2%; + line-height: 25px; + } +} + +.container { + padding-top: 1%; + padding-bottom: 2%; + + .action-buttons { + padding-bottom: 1rem; + padding-top: 1rem; + } +} diff --git a/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.spec.ts b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.spec.ts new file mode 100644 index 000000000..0313206b9 --- /dev/null +++ b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RescheduleLoanTabComponent } from './reschedule-loan-tab.component'; + +describe('RescheduleLoanTabComponent', () => { + let component: RescheduleLoanTabComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RescheduleLoanTabComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RescheduleLoanTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.ts b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.ts new file mode 100644 index 000000000..923d74a28 --- /dev/null +++ b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Dates } from 'app/core/utils/dates'; +import { LoansService } from 'app/loans/loans.service'; +import { SettingsService } from 'app/settings/settings.service'; +import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component'; + +@Component({ + selector: 'mifosx-reschedule-loan-tab', + templateUrl: './reschedule-loan-tab.component.html', + styleUrls: ['./reschedule-loan-tab.component.scss'] +}) +export class RescheduleLoanTabComponent implements OnInit { + + loanRescheduleData: any; + loanRescheduleDataColumns: string[] = ['id', 'rescheduleFromDate', 'reason', 'status', 'updatedOnDate', 'actions']; + clientId: any; + + constructor(private route: ActivatedRoute, + private router: Router, + private loansServices: LoansService, + private settingsService: SettingsService, + private dateUtils: Dates, + private dialog: MatDialog) { + this.clientId = this.route.parent.parent.snapshot.paramMap.get('clientId'); + this.route.parent.data.subscribe((data: { loanRescheduleData: any }) => { + this.loanRescheduleData = data.loanRescheduleData; + }); + } + + ngOnInit(): void { + } + + manageRequest(request: any, command: string): void { + const approveLoanRescheduleDialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { heading: `${command} Loan Reschedule`, dialogContext: `Are you sure you want ${command} the Loan Reschedule ${request.id}` } + }); + approveLoanRescheduleDialogRef.afterClosed().subscribe((response: { confirm: any }) => { + if (response.confirm) { + const locale = this.settingsService.language.code; + const dateFormat = this.settingsService.dateFormat; + const payload = { + dateFormat, + locale + }; + if (command === 'Approve') { + payload['approvedOnDate'] = this.dateUtils.formatDate(this.settingsService.businessDate, dateFormat); + } else { + payload['rejectedOnDate'] = this.dateUtils.formatDate(this.settingsService.businessDate, dateFormat); + } + this.loansServices.applyCommandLoanRescheduleRequests(request.id, command.toLowerCase(), payload).subscribe((result: any) => { + 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])); + } +} diff --git a/src/app/loans/loans.module.ts b/src/app/loans/loans.module.ts index 70e9d348f..bb01829c1 100644 --- a/src/app/loans/loans.module.ts +++ b/src/app/loans/loans.module.ts @@ -74,6 +74,7 @@ import { LoansAccountScheduleStepComponent } from './loans-account-stepper/loans import { EditRepaymentScheduleComponent } from './loans-view/loan-account-actions/edit-repayment-schedule/edit-repayment-schedule.component'; import { DisburseToSavingsAccountComponent } from './loans-view/loan-account-actions/disburse-to-savings-account/disburse-to-savings-account.component'; import { LoansAccountDatatableStepComponent } from './loans-account-stepper/loans-account-datatable-step/loans-account-datatable-step.component'; +import { RescheduleLoanTabComponent } from './loans-view/reschedule-loan-tab/reschedule-loan-tab.component'; /** * Loans Module @@ -146,7 +147,8 @@ import { LoansAccountDatatableStepComponent } from './loans-account-stepper/loan LoansAccountScheduleStepComponent, EditRepaymentScheduleComponent, DisburseToSavingsAccountComponent, - LoansAccountDatatableStepComponent + LoansAccountDatatableStepComponent, + RescheduleLoanTabComponent ], providers: [ ], }) diff --git a/src/app/loans/loans.service.ts b/src/app/loans/loans.service.ts index fa043dd6d..440ca2714 100644 --- a/src/app/loans/loans.service.ts +++ b/src/app/loans/loans.service.ts @@ -267,6 +267,22 @@ export class LoansService { return this.http.get('/rescheduleloans/template'); } + /** + * Returns the Loan Reschedule request + */ + loanRescheduleRequests(loanId: any) { + const httpParams = new HttpParams().set('loanId', loanId); + return this.http.get('/rescheduleloans', { params: httpParams }); + } + + /** + * Returns the Loan Reschedule request + */ + applyCommandLoanRescheduleRequests(rescheduleId: any, command: string, data: any) { + const httpParams = new HttpParams().set('command', command); + return this.http.post(`/rescheduleloans/${rescheduleId}`, data, { params: httpParams }); + } + /** * Submits Reschedule Data * @param {any} data Data diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html index 05129e10c..f4f81ede0 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html @@ -8,13 +8,13 @@
diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.html index 56d6e173f..2c6c07a24 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.html +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.html @@ -8,8 +8,12 @@
+
@@ -68,4 +72,4 @@ No pending rescheduled loan available for this account. - \ No newline at end of file + diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.ts index ab2d74cbb..390050867 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.ts +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.ts @@ -79,37 +79,40 @@ export class RescheduleLoanComponent implements OnInit { return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.position + 1}`; } - approveBulkLoanReschedule() { + bulkLoanReschedule(action: string) { const rescheduleLoanDialogRef = this.dialog.open(ConfirmationDialogComponent, { - data: { heading: 'Reschedule Loan', dialogContext: 'Are you sure you want to Reschedule Loan' } + data: { heading: 'Reschedule Loan', dialogContext: 'Are you sure you want to ' + action + ' the Reschedule Loan' } }); rescheduleLoanDialogRef.afterClosed().subscribe((response: { confirm: any }) => { if (response.confirm) { - this.bulkLoanRescheduleApproval(); + this.bulkLoanRescheduleRequest(action.toLowerCase()); } }); } - bulkLoanRescheduleApproval() { + bulkLoanRescheduleRequest(command: string) { const dateFormat = this.settingsService.dateFormat; - const approvedOnDate = this.dateUtils.formatDate(new Date(), dateFormat); + const transactionDate = this.dateUtils.formatDate(this.settingsService.businessDate, dateFormat); const locale = this.settingsService.language.code; const formData = { dateFormat, - approvedOnDate, locale }; + if (command === 'approve') { + formData['approvedOnDate'] = transactionDate; + } else { + formData['rejectedOnDate'] = transactionDate; + } const listSelectedAccounts = this.selection.selected; this.batchRequests = []; let reqId = 1; listSelectedAccounts.forEach((element: any) => { - const url = 'rescheduleloans/' + element.id + '?command=approve'; + const url = 'rescheduleloans/' + element.id + '?command=' + command; const bodyData = JSON.stringify(formData); const batchData = { requestId: reqId++, relativeUrl: url, method: 'POST', body: bodyData }; this.batchRequests.push(batchData); }); this.tasksService.submitBatchData(this.batchRequests).subscribe((response: any) => { - console.log(response); this.reload(); }); } diff --git a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html index 2297fe523..cd82a574a 100644 --- a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html +++ b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html @@ -1,12 +1,12 @@