From 91f305025d8d4c2cda93208a1558c4b078960435 Mon Sep 17 00:00:00 2001 From: Aastha Bist Date: Thu, 11 Jul 2024 23:28:23 +0530 Subject: [PATCH] feat: add approver platform API (#3131) --- src/app/core/models/employee-params.model.ts | 1 + .../v1/approver/reports.service.spec.ts | 19 +++++++ .../platform/v1/approver/reports.service.ts | 10 ++++ src/app/core/services/report.service.spec.ts | 16 ------ src/app/core/services/report.service.ts | 11 ---- .../add-approvers-popover.component.spec.ts | 48 +++++++++------- .../add-approvers-popover.component.ts | 34 ++++++----- .../approver-dialog.component.ts | 57 +++++++++---------- .../models/approver.model.ts | 4 ++ 9 files changed, 111 insertions(+), 89 deletions(-) create mode 100644 src/app/shared/components/fy-approver/add-approvers-popover/models/approver.model.ts diff --git a/src/app/core/models/employee-params.model.ts b/src/app/core/models/employee-params.model.ts index e954633196..f8c66c2a23 100644 --- a/src/app/core/models/employee-params.model.ts +++ b/src/app/core/models/employee-params.model.ts @@ -8,4 +8,5 @@ export interface EmployeeParams { ou_id: string; order: string; limit: number; + us_email?: string; } diff --git a/src/app/core/services/platform/v1/approver/reports.service.spec.ts b/src/app/core/services/platform/v1/approver/reports.service.spec.ts index d511838285..c906e257ef 100644 --- a/src/app/core/services/platform/v1/approver/reports.service.spec.ts +++ b/src/app/core/services/platform/v1/approver/reports.service.spec.ts @@ -200,6 +200,25 @@ describe('ApproverReportsService', () => { }); }); + it('addApprover(): should add approver to a report', (done) => { + approverPlatformApiService.post.and.returnValue(of(null)); + + const reportID = 'rprj1zHHpW2W'; + const approverEmail = 'asilk@akls.in'; + const comment = 'comment'; + + approverReportsService.addApprover(reportID, approverEmail, comment).subscribe(() => { + expect(approverPlatformApiService.post).toHaveBeenCalledOnceWith(`/reports/add_approver`, { + data: { + id: reportID, + approver_email: approverEmail, + comment, + }, + }); + done(); + }); + }); + it('getReportById(): should get a report by id', () => { spyOn(approverReportsService, 'getReportsByParams').and.returnValue(of(allReportsPaginated1)); const queryParams = { diff --git a/src/app/core/services/platform/v1/approver/reports.service.ts b/src/app/core/services/platform/v1/approver/reports.service.ts index 42bf744380..1398c9e333 100644 --- a/src/app/core/services/platform/v1/approver/reports.service.ts +++ b/src/app/core/services/platform/v1/approver/reports.service.ts @@ -85,6 +85,16 @@ export class ApproverReportsService { return this.approverPlatformApiService.post('/reports/send_back', { data: { id, comment } }); } + addApprover(rptId: string, approverEmail: string, comment: string): Observable { + const data = { + id: rptId, + approver_email: approverEmail, + comment, + }; + + return this.approverPlatformApiService.post('/reports/add_approver', { data }); + } + permissions(id: string): Observable { return this.approverPlatformApiService .post>('/reports/permissions', { data: { id } }) diff --git a/src/app/core/services/report.service.spec.ts b/src/app/core/services/report.service.spec.ts index 47706bb767..2a76faab0d 100644 --- a/src/app/core/services/report.service.spec.ts +++ b/src/app/core/services/report.service.spec.ts @@ -260,22 +260,6 @@ describe('ReportService', () => { }); }); - it('addApprover(): should add approver to a report', (done) => { - apiService.post.and.returnValue(of(null)); - - const reportID = 'rprj1zHHpW2W'; - const approverEmail = 'asilk@akls.in'; - const comment = 'comment'; - - reportService.addApprover(reportID, approverEmail, comment).subscribe(() => { - expect(apiService.post).toHaveBeenCalledOnceWith(`/reports/${reportID}/approvals`, { - approver_email: approverEmail, - comment, - }); - done(); - }); - }); - it('delete(): should delete a report', (done) => { apiService.delete.and.returnValue(of(null)); spyOn(reportService, 'clearTransactionCache').and.returnValue(of(null)); diff --git a/src/app/core/services/report.service.ts b/src/app/core/services/report.service.ts index e06c5eb1bc..e4eac0a087 100644 --- a/src/app/core/services/report.service.ts +++ b/src/app/core/services/report.service.ts @@ -74,17 +74,6 @@ export class ReportService { return this.apiService.post('/reports/' + rptId + '/approve'); } - @CacheBuster({ - cacheBusterNotifier: reportsCacheBuster$, - }) - addApprover(rptId: string, approverEmail: string, comment: string): Observable { - const data = { - approver_email: approverEmail, - comment, - }; - return this.apiService.post('/reports/' + rptId + '/approvals', data); - } - @CacheBuster({ cacheBusterNotifier: reportsCacheBuster$, }) diff --git a/src/app/shared/components/fy-approver/add-approvers-popover/add-approvers-popover.component.spec.ts b/src/app/shared/components/fy-approver/add-approvers-popover/add-approvers-popover.component.spec.ts index 2be7eeeca2..9d805de4c2 100644 --- a/src/app/shared/components/fy-approver/add-approvers-popover/add-approvers-popover.component.spec.ts +++ b/src/app/shared/components/fy-approver/add-approvers-popover/add-approvers-popover.component.spec.ts @@ -12,6 +12,7 @@ import { FormsModule } from '@angular/forms'; import { pullBackAdvancedRequests } from 'src/app/core/mock-data/advance-requests.data'; import { getElementBySelector, getTextContent } from 'src/app/core/dom-helpers'; import { of } from 'rxjs'; +import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; describe('AddApproversPopoverComponent', () => { let component: AddApproversPopoverComponent; @@ -20,7 +21,7 @@ describe('AddApproversPopoverComponent', () => { let modalProperties: jasmine.SpyObj; let popoverController: jasmine.SpyObj; let advanceRequestService: jasmine.SpyObj; - let reportService: jasmine.SpyObj; + let approverReportsService: jasmine.SpyObj; let loaderService: jasmine.SpyObj; beforeEach(waitForAsync(() => { @@ -28,7 +29,7 @@ describe('AddApproversPopoverComponent', () => { const modalPropertiesSpy = jasmine.createSpyObj('ModalPropertiesService', ['getModalDefaultProperties']); const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['dismiss']); const advanceRequestServiceSpy = jasmine.createSpyObj('AdvanceRequestService', ['addApprover']); - const reportServiceSpy = jasmine.createSpyObj('ReportService', ['addApprover']); + const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', ['addApprover']); const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['showLoader', 'hideLoader']); TestBed.configureTestingModule({ @@ -52,8 +53,8 @@ describe('AddApproversPopoverComponent', () => { useValue: advanceRequestServiceSpy, }, { - provide: ReportService, - useValue: reportServiceSpy, + provide: ApproverReportsService, + useValue: approverReportsServiceSpy, }, { provide: LoaderService, @@ -65,7 +66,7 @@ describe('AddApproversPopoverComponent', () => { modalProperties = TestBed.inject(ModalPropertiesService) as jasmine.SpyObj; popoverController = TestBed.inject(PopoverController) as jasmine.SpyObj; advanceRequestService = TestBed.inject(AdvanceRequestService) as jasmine.SpyObj; - reportService = TestBed.inject(ReportService) as jasmine.SpyObj; + approverReportsService = TestBed.inject(ApproverReportsService) as jasmine.SpyObj; loaderService = TestBed.inject(LoaderService) as jasmine.SpyObj; fixture = TestBed.createComponent(AddApproversPopoverComponent); @@ -84,9 +85,11 @@ describe('AddApproversPopoverComponent', () => { component.type = 'report'; component.ownerEmail = 'jay.b@fyle.in'; const selectedApproversList = ['ajain@fyle.in', 'aiyush.dhar@fylein', 'chethan.m+90@fyle.in', 'ashutosh.m@fyle.in']; - component.selectedApproversList = selectedApproversList; - modalController.create.and.returnValue(Promise.resolve(modalSpy)); - modalSpy.onWillDismiss.and.returnValue(Promise.resolve({ data: { selectedApproversList } } as any)); + component.selectedApproversList = selectedApproversList.map((email) => { + return { email }; + }); + modalController.create.and.resolveTo(modalSpy); + modalSpy.onWillDismiss.and.resolveTo({ data: { selectedApproversList } } as any); component.openModal(); tick(); @@ -94,7 +97,9 @@ describe('AddApproversPopoverComponent', () => { component: ApproverDialogComponent, componentProps: { approverEmailsList: component.approverEmailsList, - initialApproverList: selectedApproversList, + initialApproverList: selectedApproversList.map((email) => { + return { email }; + }), id: component.id, type: component.type, ownerEmail: component.ownerEmail, @@ -107,7 +112,7 @@ describe('AddApproversPopoverComponent', () => { })); it('closeAddApproversPopover(): should close popover', fakeAsync(() => { - popoverController.dismiss.and.returnValue(Promise.resolve(true)); + popoverController.dismiss.and.resolveTo(true); tick(); component.closeAddApproversPopover(); @@ -121,9 +126,9 @@ describe('AddApproversPopoverComponent', () => { component.confirmationMessage = 'The request is approved'; component.selectedApproversList = [{ email: 'john.doe@fyle.in' }]; advanceRequestService.addApprover.and.returnValue(of(pullBackAdvancedRequests)); - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); - popoverController.dismiss.and.returnValue(Promise.resolve(true)); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); + popoverController.dismiss.and.resolveTo(true); component.saveUpdatedApproversList(); @@ -138,22 +143,22 @@ describe('AddApproversPopoverComponent', () => { expect(popoverController.dismiss).toHaveBeenCalledOnceWith({ reload: true }); })); - it('should call reportService.addApprover() for other request types', fakeAsync(() => { + it('should call approverReportsService.addApprover() for other request types', fakeAsync(() => { fixture.detectChanges(); component.type = 'report'; component.id = 'repP09oaYXAf'; component.confirmationMessage = 'The request is approved'; component.selectedApproversList = [{ email: 'ajain@fyle.in' }]; - reportService.addApprover.and.returnValue(of(null)); - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); - popoverController.dismiss.and.returnValue(Promise.resolve(true)); + approverReportsService.addApprover.and.returnValue(of(null)); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); + popoverController.dismiss.and.resolveTo(true); component.saveUpdatedApproversList(); expect(loaderService.showLoader).toHaveBeenCalledTimes(1); tick(); - expect(reportService.addApprover).toHaveBeenCalledOnceWith( + expect(approverReportsService.addApprover).toHaveBeenCalledOnceWith( 'repP09oaYXAf', 'ajain@fyle.in', 'The request is approved' @@ -168,7 +173,10 @@ describe('AddApproversPopoverComponent', () => { }); it('should display the "+n more" chip when there are more than 3 selected approvers', () => { - component.selectedApproversList = ['ajain@fyle.in', 'aiyush.dhar@fyle.in', 'chetan.m@fyle.in', 'john.d@fyle.in']; + const selectedApproversList = ['ajain@fyle.in', 'aiyush.dhar@fyle.in', 'chetan.m@fyle.in', 'john.d@fyle.in']; + component.selectedApproversList = selectedApproversList.map((email) => { + return { email }; + }); fixture.detectChanges(); const moreChip = getElementBySelector(fixture, '.add-approvers-popover--input-container__chip'); expect(moreChip).toBeTruthy(); diff --git a/src/app/shared/components/fy-approver/add-approvers-popover/add-approvers-popover.component.ts b/src/app/shared/components/fy-approver/add-approvers-popover/add-approvers-popover.component.ts index 2c8635a989..33dcecae1b 100644 --- a/src/app/shared/components/fy-approver/add-approvers-popover/add-approvers-popover.component.ts +++ b/src/app/shared/components/fy-approver/add-approvers-popover/add-approvers-popover.component.ts @@ -6,23 +6,25 @@ import { LoaderService } from 'src/app/core/services/loader.service'; import { ApproverDialogComponent } from './approver-dialog/approver-dialog.component'; import { ModalPropertiesService } from 'src/app/core/services/modal-properties.service'; import { AdvanceRequestService } from 'src/app/core/services/advance-request.service'; -import { ReportService } from 'src/app/core/services/report.service'; - +import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { Approver } from './models/approver.model'; +import { AdvanceRequests } from 'src/app/core/models/advance-requests.model'; +import { Report } from 'src/app/core/models/platform/v1/report.model'; @Component({ selector: 'app-add-approvers-popover', templateUrl: './add-approvers-popover.component.html', styleUrls: ['./add-approvers-popover.component.scss'], }) export class AddApproversPopoverComponent { - @Input() approverEmailsList; + @Input() approverEmailsList: string[]; @Input() id: string; @Input() ownerEmail: string; - @Input() type; + @Input() type: string; - selectedApproversList = []; + selectedApproversList: Approver[] = []; displayValue: string; @@ -33,11 +35,11 @@ export class AddApproversPopoverComponent { private modalProperties: ModalPropertiesService, private popoverController: PopoverController, private advanceRequestService: AdvanceRequestService, - private reportService: ReportService, - private loaderService: LoaderService + private loaderService: LoaderService, + private approverReportsService: ApproverReportsService ) {} - async openModal() { + async openModal(): Promise { const approversListModal = await this.modalController.create({ component: ApproverDialogComponent, componentProps: { @@ -53,7 +55,7 @@ export class AddApproversPopoverComponent { await approversListModal.present(); - const { data } = await approversListModal.onWillDismiss(); + const { data } = await approversListModal.onWillDismiss<{ selectedApproversList: Approver[] }>(); if (data && data.selectedApproversList) { this.selectedApproversList = data.selectedApproversList; @@ -67,7 +69,7 @@ export class AddApproversPopoverComponent { } } - saveUpdatedApproversList() { + saveUpdatedApproversList(): void { from(this.loaderService.showLoader()) .pipe( switchMap(() => from(this.selectedApproversList.map((selectedApprover) => selectedApprover.email))), @@ -75,10 +77,16 @@ export class AddApproversPopoverComponent { if (this.type === 'ADVANCE_REQUEST') { return this.advanceRequestService.addApprover(this.id, approver, this.confirmationMessage); } else { - return this.reportService.addApprover(this.id, approver, this.confirmationMessage); + return this.approverReportsService.addApprover(this.id, approver, this.confirmationMessage); } }), - reduce((acc, curr) => acc.concat(curr), []), + reduce((acc: AdvanceRequests[] | Report[], curr: AdvanceRequests | Report) => { + if (this.type === 'ADVANCE_REQUEST') { + (acc as AdvanceRequests[]).concat(curr as AdvanceRequests); + } else { + (acc as Report[]).concat(curr as Report); + } + }, []), finalize(() => from(this.loaderService.hideLoader())) ) .subscribe(() => { @@ -86,7 +94,7 @@ export class AddApproversPopoverComponent { }); } - closeAddApproversPopover() { + closeAddApproversPopover(): void { this.popoverController.dismiss(); } } diff --git a/src/app/shared/components/fy-approver/add-approvers-popover/approver-dialog/approver-dialog.component.ts b/src/app/shared/components/fy-approver/add-approvers-popover/approver-dialog/approver-dialog.component.ts index 7d9170af01..2a86919696 100644 --- a/src/app/shared/components/fy-approver/add-approvers-popover/approver-dialog/approver-dialog.component.ts +++ b/src/app/shared/components/fy-approver/add-approvers-popover/approver-dialog/approver-dialog.component.ts @@ -7,11 +7,8 @@ import { ModalController } from '@ionic/angular'; import { Employee } from 'src/app/core/models/spender/employee.model'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { MatChipInputEvent } from '@angular/material/chips'; - -type Approver = { - name: string; - email: string; -}; +import { Approver } from '../models/approver.model'; +import { EmployeeParams } from 'src/app/core/models/employee-params.model'; @Component({ selector: 'app-approver-dialog', @@ -19,7 +16,7 @@ type Approver = { styleUrls: ['./approver-dialog.component.scss'], }) export class ApproverDialogComponent implements AfterViewInit, OnInit { - @ViewChild('searchBar') searchBarRef: ElementRef; + @ViewChild('searchBar') searchBarRef: ElementRef; @Input() approverEmailsList: string[]; @@ -33,9 +30,7 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { value: string; - approverList$: Observable; - - searchedApprovers$: Observable[]>; + searchedApprovers$: Observable[]>; selectedApproversList: Approver[] = []; @@ -59,19 +54,19 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { private modalController: ModalController ) {} - ngOnInit() { + ngOnInit(): void { this.selectedApproversList = this.initialApproverList; this.selectedApproversDict = this.getSelectedApproversDict(); } - getSelectedApproversDict() { + getSelectedApproversDict(): Record { return this.selectedApproversList.reduce((acc, curr) => { acc[curr.email] = true; return acc; }, {}); } - clearValue() { + clearValue(): void { this.value = ''; const searchInput = this.searchBarRef.nativeElement as HTMLInputElement; searchInput.value = ''; @@ -79,26 +74,26 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { this.getSearchedUsersList(); } - getSeparatorKeysCodes() { + getSeparatorKeysCodes(): number[] { return [ENTER, COMMA]; } - addChip(event: MatChipInputEvent) { + addChip(event: MatChipInputEvent): void { if (event && event.chipInput) { event.chipInput.clear(); } this.clearValue(); } - closeApproverModal() { + closeApproverModal(): void { this.modalController.dismiss(); } - async saveUpdatedApproveList() { + async saveUpdatedApproveList(): Promise { this.modalController.dismiss({ selectedApproversList: this.selectedApproversList }); } - onSelectApprover(approver: Employee, event: { checked: boolean }) { + onSelectApprover(approver: Employee, event: { checked: boolean }): void { if (event.checked) { this.selectedApproversList.push({ name: approver.us_full_name, email: approver.us_email }); } else { @@ -110,7 +105,7 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { this.selectedApproversDict = this.getSelectedApproversDict(); } - removeApprover(approver) { + removeApprover(approver: Approver): void { this.selectedApproversList = this.selectedApproversList.filter( (selectedApprover) => selectedApprover.email !== approver.email ); @@ -118,8 +113,8 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { this.selectedApproversDict = this.getSelectedApproversDict(); } - getDefaultUsersList() { - const params: any = { + getDefaultUsersList(): Observable[]> { + const params: Partial = { order: 'us_full_name.asc,us_email.asc,ou_id', }; @@ -130,6 +125,7 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { } return from(this.loaderService.showLoader('Loading...')).pipe( + // eslint-disable-next-line @typescript-eslint/no-unused-vars switchMap((_) => this.orgUserService.getEmployeesBySearch(params)), map((approvers) => approvers.map((approver) => { @@ -141,8 +137,8 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { ); } - getSearchedUsersList(searchText?: string) { - const params: any = { + getSearchedUsersList(searchText?: string): Observable[]> { + const params: Partial = { limit: 20, order: 'us_full_name.asc,us_email.asc,ou_id', }; @@ -164,7 +160,7 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { ); } - getUsersList(searchText) { + getUsersList(searchText: string): Employee[] | Observable[]> { if (searchText) { return this.getSearchedUsersList(searchText); } else { @@ -172,7 +168,7 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { switchMap((employees) => { employees = employees.filter((employee) => this.approverEmailsList.indexOf(employee.us_email) === -1); return this.getSearchedUsersList(null).pipe( - map((searchedEmployees) => { + map((searchedEmployees: Partial[]) => { searchedEmployees = this.getSearchedEmployees(searchedEmployees, employees); return employees.concat(searchedEmployees); }) @@ -182,19 +178,22 @@ export class ApproverDialogComponent implements AfterViewInit, OnInit { } } - getSearchedEmployees(searchedEmployees: Partial[], employees: Partial[]) { + getSearchedEmployees(searchedEmployees: Partial[], employees: Partial[]): Partial[] { searchedEmployees = searchedEmployees.filter( (searchedEmployee) => !employees.find((employee) => employee.us_email === searchedEmployee.us_email) ); return searchedEmployees; } - ngAfterViewInit() { - this.searchedApprovers$ = fromEvent(this.searchBarRef.nativeElement, 'keyup').pipe( - map((event: any) => event.srcElement.value), + ngAfterViewInit(): void { + this.searchedApprovers$ = fromEvent<{ srcElement: { value: string } }>( + this.searchBarRef.nativeElement, + 'keyup' + ).pipe( + map((event) => event.srcElement.value), startWith(''), distinctUntilChanged(), - switchMap((searchText: any) => this.getUsersList(searchText)) + switchMap((searchText: string) => this.getUsersList(searchText)) ); } } diff --git a/src/app/shared/components/fy-approver/add-approvers-popover/models/approver.model.ts b/src/app/shared/components/fy-approver/add-approvers-popover/models/approver.model.ts new file mode 100644 index 0000000000..d18fa595db --- /dev/null +++ b/src/app/shared/components/fy-approver/add-approvers-popover/models/approver.model.ts @@ -0,0 +1,4 @@ +export interface Approver { + name?: string; + email: string; +}