From c4812e9e9365e266c9b6e4fe39d6883f6fbcb0ee Mon Sep 17 00:00:00 2001 From: Karan Takalkar Date: Thu, 6 Aug 2020 17:03:52 +0530 Subject: [PATCH] feat: loans advanced search (fund-mapping) component (#1180) Fixes: #1179 --- .../advance-search-template.resolver.ts | 30 +++ .../fund-mapping/fund-mapping.component.html | 223 ++++++++++++++++++ .../fund-mapping/fund-mapping.component.scss | 17 ++ .../fund-mapping.component.spec.ts | 25 ++ .../fund-mapping/fund-mapping.component.ts | 164 +++++++++++++ .../organization-routing.module.ts | 13 +- .../organization/organization.component.html | 2 +- src/app/organization/organization.module.ts | 2 + src/app/organization/organization.service.ts | 14 ++ 9 files changed, 488 insertions(+), 2 deletions(-) create mode 100644 src/app/organization/fund-mapping/advance-search-template.resolver.ts create mode 100644 src/app/organization/fund-mapping/fund-mapping.component.html create mode 100644 src/app/organization/fund-mapping/fund-mapping.component.scss create mode 100644 src/app/organization/fund-mapping/fund-mapping.component.spec.ts create mode 100644 src/app/organization/fund-mapping/fund-mapping.component.ts diff --git a/src/app/organization/fund-mapping/advance-search-template.resolver.ts b/src/app/organization/fund-mapping/advance-search-template.resolver.ts new file mode 100644 index 000000000..97ad06346 --- /dev/null +++ b/src/app/organization/fund-mapping/advance-search-template.resolver.ts @@ -0,0 +1,30 @@ +/** Angular Imports */ +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; + +/** rxjs Imports */ +import { Observable } from 'rxjs'; + +/** Custom Services */ +import { OrganizationService } from '../organization.service'; + +/** + * Advance Search Template resolver. + */ +@Injectable() +export class AdvanceSearchTemplateResolver implements Resolve { + + /** + * @param {OrganizationService} organizationService Organization service. + */ + constructor(private organizationService: OrganizationService) {} + + /** + * Returns the Advance Search template. + * @returns {Observable} + */ + resolve(): Observable { + return this.organizationService.getAdvanceSearchTemplate(); + } + +} diff --git a/src/app/organization/fund-mapping/fund-mapping.component.html b/src/app/organization/fund-mapping/fund-mapping.component.html new file mode 100644 index 000000000..416d0c012 --- /dev/null +++ b/src/app/organization/fund-mapping/fund-mapping.component.html @@ -0,0 +1,223 @@ + + +
+ +
+ + + Loan Status + + All + Active + Overpaid + Closed (obligations met) + Closed (written-off) + + + + + + + Product + + + {{ product.name }} + + + + + + Office + + + {{ office.name }} + + + + + + Date Type + + Approval Date + Creation Date + Disbursement Date + + + + + From Date + + + + + From Date is required + + + + + To Date + + + + + To Date is required + + + + + Loan Outstanding Percentage + + +
+ + + Comparison Condition + + between + <= + >= + < + > + = + + + Comparison Condition is required + + + + + Minimum Value + + + Minimum Value is required + + + + + Comparison Value + + + Comparison Value is required + + + + + Maximum Value + + + Maximum Value is required + + + +
+ + + Loan Outstanding Amount + + +
+ + + Comparison Condition + + between + <= + >= + < + > + = + + + Comparison Condition is required + + + + + Minimum Value + + + Minimum Value is required + + + + + Comparison Value + + + Comparison Value is required + + + + + Maximum Value + + + Maximum Value is required + + + +
+ +
+ + + + + + +
+ +
+ + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Office Name {{ loan.officeName }} Product Name {{ loan.loanProductName }} Count {{ loan.count }} Outstanding {{ loan.loanOutStanding }} Percentage {{ loan.percentage }}
+ + + +
+ +
diff --git a/src/app/organization/fund-mapping/fund-mapping.component.scss b/src/app/organization/fund-mapping/fund-mapping.component.scss new file mode 100644 index 000000000..2aaa09a5a --- /dev/null +++ b/src/app/organization/fund-mapping/fund-mapping.component.scss @@ -0,0 +1,17 @@ +.container { + + max-width: 73rem; + + .margin-v { + margin: 1em 0; + } + + .margin-b { + margin: 0 0 1em 0; + } + + .margin-t { + margin-top: 1em; + } + +} diff --git a/src/app/organization/fund-mapping/fund-mapping.component.spec.ts b/src/app/organization/fund-mapping/fund-mapping.component.spec.ts new file mode 100644 index 000000000..56905b3a2 --- /dev/null +++ b/src/app/organization/fund-mapping/fund-mapping.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FundMappingComponent } from './fund-mapping.component'; + +describe('FundMappingComponent', () => { + let component: FundMappingComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FundMappingComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FundMappingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/organization/fund-mapping/fund-mapping.component.ts b/src/app/organization/fund-mapping/fund-mapping.component.ts new file mode 100644 index 000000000..f45f2fdb3 --- /dev/null +++ b/src/app/organization/fund-mapping/fund-mapping.component.ts @@ -0,0 +1,164 @@ +/** Angular Imports */ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatTableDataSource, MatPaginator, MatSort } from '@angular/material'; +import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { DatePipe } from '@angular/common'; + +/** Custom Services */ +import { OrganizationService } from '../organization.service'; + +/** + * Fund Mapping Component. + */ +@Component({ + selector: 'mifosx-fund-mapping', + templateUrl: './fund-mapping.component.html', + styleUrls: ['./fund-mapping.component.scss'] +}) +export class FundMappingComponent implements OnInit { + + /** Minimum Date allowed. */ + minDate = new Date(2000, 0, 1); + /** Maximum Date allowed. */ + maxDate = new Date(); + /** Fund mapping form. */ + fundMappingForm: FormGroup; + /** Advance Search Template */ + advanceSearchTemplate: any; + /** Toggles b/w form and table */ + isCollapsed = false; + + /** Columns to be displayed in loans table. */ + displayedColumns: string[] = ['officeName', 'productName', 'count', 'outstanding', 'percentage']; + /** Data source for loans table. */ + dataSource: MatTableDataSource; + + /** Paginator for loans table. */ + @ViewChild(MatPaginator) paginator: MatPaginator; + /** Sorter for loans table. */ + @ViewChild(MatSort) sort: MatSort; + + /** + * Retrieves the advance search template from `resolve`. + * @param {FormBuilder} formBuilder Form Builder. + * @param {OrganizationService} organizationService Organization Service. + * @param {Router} router Router for navigation. + * @param {DatePipe} datePipe Date Pipe to format date. + */ + constructor(private formBuilder: FormBuilder, + private organizationService: OrganizationService, + private route: ActivatedRoute, + private datePipe: DatePipe) { + this.route.data.subscribe((data: { advanceSearchTemplate: any }) => { + this.advanceSearchTemplate = data.advanceSearchTemplate; + }); + } + + ngOnInit() { + this.createFundMappingForm(); + this.buildDependencies(); + } + + /** + * Creates the Fund Mapping Form + */ + createFundMappingForm() { + this.fundMappingForm = this.formBuilder.group({ + 'loanStatus': [''], + 'loanProducts': [''], + 'offices': [''], + 'loanDateOption': ['', Validators.required], + 'loanFromDate': ['', Validators.required], + 'loanToDate': ['', Validators.required], + 'includeOutStandingAmountPercentage': [false], + 'includeOutstandingAmount': [false] + }); + } + + /** + * Sets conditional child controls. + */ + buildDependencies() { + this.fundMappingForm.get('includeOutStandingAmountPercentage').valueChanges.subscribe((value: boolean) => { + if (value) { + this.fundMappingForm.addControl('outStandingAmountPercentageCondition', new FormControl('', Validators.required)); + this.fundMappingForm.get('outStandingAmountPercentageCondition').valueChanges.subscribe((_value: string) => { + if (_value === 'between') { + this.fundMappingForm.addControl('minOutStandingAmountPercentage', new FormControl('', Validators.required)); + this.fundMappingForm.addControl('maxOutStandingAmountPercentage', new FormControl('', Validators.required)); + this.fundMappingForm.removeControl('outStandingAmountPercentage'); + } else { + this.fundMappingForm.addControl('outStandingAmountPercentage', new FormControl('', Validators.required)); + this.fundMappingForm.removeControl('minOutStandingAmountPercentage'); + this.fundMappingForm.removeControl('maxOutStandingAmountPercentage'); + } + }); + this.fundMappingForm.get('outStandingAmountPercentageCondition').patchValue('between'); + } else { + this.fundMappingForm.removeControl('outStandingAmountPercentageCondition'); + this.fundMappingForm.removeControl('minOutStandingAmountPercentage'); + this.fundMappingForm.removeControl('maxOutStandingAmountPercentage'); + this.fundMappingForm.removeControl('outStandingAmountPercentage'); + } + }); + this.fundMappingForm.get('includeOutStandingAmountPercentage').patchValue(true); + this.fundMappingForm.get('includeOutstandingAmount').valueChanges.subscribe((value: boolean) => { + if (value) { + this.fundMappingForm.addControl('outstandingAmountCondition', new FormControl('', Validators.required)); + this.fundMappingForm.get('outstandingAmountCondition').valueChanges.subscribe((_value: string) => { + if (_value === 'between') { + this.fundMappingForm.addControl('minOutstandingAmount', new FormControl('', Validators.required)); + this.fundMappingForm.addControl('maxOutstandingAmount', new FormControl('', Validators.required)); + this.fundMappingForm.removeControl('outstandingAmount'); + } else { + this.fundMappingForm.addControl('outstandingAmount', new FormControl('', Validators.required)); + this.fundMappingForm.removeControl('minOutstandingAmount'); + this.fundMappingForm.removeControl('maxOutstandingAmount'); + } + }); + this.fundMappingForm.get('outstandingAmountCondition').patchValue('between'); + } else { + this.fundMappingForm.removeControl('outstandingAmountCondition'); + this.fundMappingForm.removeControl('minOutstandingAmount'); + this.fundMappingForm.removeControl('maxOutstandingAmount'); + this.fundMappingForm.removeControl('outstandingAmount'); + } + }); + this.fundMappingForm.get('includeOutstandingAmount').patchValue(true); + } + + /** + * Initializes the data source, paginator and sorter for loans table. + * @param {any} data + */ + setLoans(data: any) { + this.dataSource = new MatTableDataSource(data); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + } + + /** + * Searches standing loans. + */ + submit() { + this.isCollapsed = true; + // TODO: Update once language and date settings are setup + const locale = 'en'; + const dateFormat = 'yyyy-MM-dd'; + this.fundMappingForm.patchValue({ + 'loanFromDate': this.datePipe.transform(this.fundMappingForm.value.loanFromDate, dateFormat), + 'loanToDate': this.datePipe.transform(this.fundMappingForm.value.loanToDate, dateFormat) + }); + const fundMapping = { + ...this.fundMappingForm.value, + entities: ['loans'], + dateFormat, + locale + }; + this.organizationService.retrieveAdvanceSearchResults(fundMapping).subscribe((response: any) => { + this.setLoans(response); + }); + } + +} diff --git a/src/app/organization/organization-routing.module.ts b/src/app/organization/organization-routing.module.ts index 2cfb51daa..a872488e8 100644 --- a/src/app/organization/organization-routing.module.ts +++ b/src/app/organization/organization-routing.module.ts @@ -60,6 +60,7 @@ import { CreateLoanProvisioningCriteriaComponent } from './loan-provisioning-cri import { BulkLoanReassignmnetComponent } from './bulk-loan-reassignmnet/bulk-loan-reassignmnet.component'; import { EditLoanProvisioningCriteriaComponent } from './loan-provisioning-criteria/edit-loan-provisioning-criteria/edit-loan-provisioning-criteria.component'; import { StandingInstructionsHistoryComponent } from './standing-instructions-history/standing-instructions-history.component'; +import { FundMappingComponent } from './fund-mapping/fund-mapping.component'; /** Custom Resolvers */ import { LoanProvisioningCriteriaResolver } from './loan-provisioning-criteria/common-resolvers/loan-provisioning-criteria.resolver'; @@ -99,6 +100,7 @@ import { EntityDataTableChecksTemplateResolver } from './entity-data-table-check import { LoanProvisioningCriteriaTemplateResolver } from './loan-provisioning-criteria/common-resolvers/loan-provisioning-criteria-template.resolver'; import { LoanProvisioningCriteriaAndTemplateResolver } from './loan-provisioning-criteria/common-resolvers/loan-provisioning-criteria-and-template.resolver'; import { StandingInstructionsTemplateResolver } from './standing-instructions-history/standing-instructions-template.resolver'; +import { AdvanceSearchTemplateResolver } from './fund-mapping/advance-search-template.resolver'; /** Organization Routes */ const routes: Routes = [ @@ -329,6 +331,14 @@ const routes: Routes = [ standingInstructionsTemplate: StandingInstructionsTemplateResolver } }, + { + path: 'fund-mapping', + component: FundMappingComponent, + data: { title: extract('Advance Search'), breadcrumb: 'Advance Search' }, + resolve: { + advanceSearchTemplate: AdvanceSearchTemplateResolver + } + }, { path: 'adhoc-query', data: { title: extract('Adhoc Query'), breadcrumb: 'Adhoc Query' }, @@ -674,7 +684,8 @@ const routes: Routes = [ LoanProvisioningCriteriasResolver, LoanProvisioningCriteriaTemplateResolver, LoanProvisioningCriteriaAndTemplateResolver, - StandingInstructionsTemplateResolver + StandingInstructionsTemplateResolver, + AdvanceSearchTemplateResolver ] }) export class OrganizationRoutingModule { } diff --git a/src/app/organization/organization.component.html b/src/app/organization/organization.component.html index 50385a574..4ceb67a05 100644 --- a/src/app/organization/organization.component.html +++ b/src/app/organization/organization.component.html @@ -40,7 +40,7 @@

View logged history of standing instructions

- + diff --git a/src/app/organization/organization.module.ts b/src/app/organization/organization.module.ts index 3eb39fbac..cff8c19ca 100644 --- a/src/app/organization/organization.module.ts +++ b/src/app/organization/organization.module.ts @@ -67,6 +67,7 @@ import { BulkLoanReassignmnetComponent } from './bulk-loan-reassignmnet/bulk-loa import { CreateLoanProvisioningCriteriaComponent } from './loan-provisioning-criteria/create-loan-provisioning-criteria/create-loan-provisioning-criteria.component'; import { EditLoanProvisioningCriteriaComponent } from './loan-provisioning-criteria/edit-loan-provisioning-criteria/edit-loan-provisioning-criteria.component'; import { StandingInstructionsHistoryComponent } from './standing-instructions-history/standing-instructions-history.component'; +import { FundMappingComponent } from './fund-mapping/fund-mapping.component'; /** * Organization Module @@ -140,6 +141,7 @@ import { StandingInstructionsHistoryComponent } from './standing-instructions-hi CreateLoanProvisioningCriteriaComponent, EditLoanProvisioningCriteriaComponent, StandingInstructionsHistoryComponent, + FundMappingComponent, ] }) export class OrganizationModule { } diff --git a/src/app/organization/organization.service.ts b/src/app/organization/organization.service.ts index 338dd7b24..5ed1473fa 100644 --- a/src/app/organization/organization.service.ts +++ b/src/app/organization/organization.service.ts @@ -686,6 +686,20 @@ export class OrganizationService { return this.http.get('/imports', { params: httpParams } ); } + /** + * @returns {Observable} + */ + getAdvanceSearchTemplate(): Observable { + return this.http.get('/search/template'); + } + + /** + * @returns {Observable} + */ + retrieveAdvanceSearchResults(queryObject: any): Observable { + return this.http.post('/search/advance', queryObject); + } + /** * @returns {Observable} */