From 2ab72914b3a96a698718326075fc8316b67b5587 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Mon, 13 May 2024 12:10:58 +0530 Subject: [PATCH 001/262] fix: prevent findByTxnId() api call from add edit expense form in add mode (#2960) --- src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts | 8 ++++---- src/app/fyle/add-edit-expense/add-edit-expense.page.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts index 43b334785b..90c7a68beb 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts @@ -1752,12 +1752,12 @@ export function TestCases5(getTestBed) { expect(expensesService.getExpenseById).not.toHaveBeenCalled(); component.attachments$.subscribe((res) => { - expect(res).toEqual(mockFileObject); + expect(res).toEqual([]); }); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(undefined); - expect(fileService.downloadUrl).toHaveBeenCalledOnceWith('fiV1gXpyCcbU'); - expect(component.getReceiptDetails).toHaveBeenCalledOnceWith(mockFileObject[0]); + expect(fileService.findByTransactionId).not.toHaveBeenCalled(); + expect(fileService.downloadUrl).not.toHaveBeenCalled(); + expect(component.getReceiptDetails).not.toHaveBeenCalled(); component.flightJourneyTravelClassOptions$.subscribe((res) => { expect(res).toBeUndefined(); diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts index c386bd1c18..1775e6723c 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts @@ -1627,7 +1627,7 @@ export class AddEditExpensePage implements OnInit { getReceiptCount(): Observable { return this.etxn$.pipe( - switchMap((etxn) => this.fileService.findByTransactionId(etxn.tx.id)), + switchMap((etxn) => (etxn.tx.id ? this.fileService.findByTransactionId(etxn.tx.id) : of([]))), map((fileObjs) => (fileObjs && fileObjs.length) || 0) ); } @@ -3096,7 +3096,7 @@ export class AddEditExpensePage implements OnInit { this.attachments$ = this.loadAttachments$.pipe( switchMap(() => this.etxn$.pipe( - switchMap((etxn) => this.fileService.findByTransactionId(etxn.tx.id)), + switchMap((etxn) => (etxn.tx.id ? this.fileService.findByTransactionId(etxn.tx.id) : of([]))), switchMap((fileObjs) => from(fileObjs)), concatMap((fileObj: FileObject) => this.fileService.downloadUrl(fileObj.id).pipe( @@ -4421,8 +4421,8 @@ export class AddEditExpensePage implements OnInit { }); } else { const editExpenseAttachments$ = this.etxn$.pipe( - switchMap((etxn) => this.fileService.findByTransactionId(etxn.tx.id)), - map((fileObjs) => (fileObjs && fileObjs.length) || 0) + switchMap((etxn) => (etxn.tx.id ? this.fileService.findByTransactionId(etxn.tx.id) : of([]))), + map((fileObjs) => fileObjs?.length || 0) ); this.attachmentUploadInProgress = true; From 53f20fb7700e350011e677e3304c0f23b8e0d16f Mon Sep 17 00:00:00 2001 From: SK027 Date: Mon, 13 May 2024 14:03:29 +0530 Subject: [PATCH 002/262] feat: Migrate getByParamsUnformatted -> spenderPlatformV1ApiService (#2954) --- .../platform/v1/platform-project.data.ts | 33 ++++++++++++ .../v1/platform-projects-params.data.ts | 13 +++++ .../v1/platform-project-params.model.ts | 13 +++++ .../core/services/projects.service.spec.ts | 15 +++--- src/app/core/services/projects.service.ts | 51 ++++++++----------- .../recently-used-items.service.spec.ts | 4 +- .../services/recently-used-items.service.ts | 4 +- src/app/core/test-data/projects.spec.data.ts | 31 ++--------- .../fy-select-project-modal.component.spec.ts | 42 ++++++++------- .../fy-select-project-modal.component.ts | 4 +- 10 files changed, 118 insertions(+), 92 deletions(-) create mode 100644 src/app/core/mock-data/platform/v1/platform-projects-params.data.ts create mode 100644 src/app/core/models/platform/v1/platform-project-params.model.ts diff --git a/src/app/core/mock-data/platform/v1/platform-project.data.ts b/src/app/core/mock-data/platform/v1/platform-project.data.ts index 77d0af81e8..39a6e36fd4 100644 --- a/src/app/core/mock-data/platform/v1/platform-project.data.ts +++ b/src/app/core/mock-data/platform/v1/platform-project.data.ts @@ -22,6 +22,39 @@ export const platformProjectSingleRes: PlatformApiResponse = offset: 0, }); +export const platformAPIResponseMultiple: PlatformApiResponse = deepFreeze({ + count: 2, + data: [ + { + is_enabled: true, + code: '1184', + created_at: new Date('2021-05-12T10:28:40.834844'), + description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', + id: 257528, + display_name: 'Customer Mapped Project', + category_ids: [122269, 122270, 122271, null], + org_id: 'orFdTTTNcyye', + updated_at: new Date('2021-07-08T10:28:27.686886'), + name: 'Customer Mapped Project', + sub_project: null, + }, + { + is_enabled: true, + code: '1182', + created_at: new Date('2021-05-12T10:28:40.834844'), + description: 'Sage Intacct Project - Fyle Engineering, Id - 1182', + id: 257529, + display_name: 'Fyle Engineering', + category_ids: [122269, 122270, 122271], + org_id: 'orFdTTTNcyye', + updated_at: new Date('2021-07-08T10:28:27.686886'), + name: 'Fyle Engineering', + sub_project: null, + }, + ], + offset: 0, +}); + export const platformAPIResponseActiveOnly: PlatformApiResponse = deepFreeze({ count: 4, data: [ diff --git a/src/app/core/mock-data/platform/v1/platform-projects-params.data.ts b/src/app/core/mock-data/platform/v1/platform-projects-params.data.ts new file mode 100644 index 0000000000..43eab3cb31 --- /dev/null +++ b/src/app/core/mock-data/platform/v1/platform-projects-params.data.ts @@ -0,0 +1,13 @@ +import { PlatformProjectParams } from 'src/app/core/models/platform/v1/platform-project-params.model'; +import deepFreeze from 'deep-freeze-strict'; + +export const ProjectPlatformParams: PlatformProjectParams = deepFreeze({ + org_id: 'eq.orNVthTo2Zyo', + order: 'name.asc', + limit: 10, + offset: 0, + is_enabled: 'eq.true', + category_ids: 'ov.{,122269,122270,122271,122272,122273}', + id: 'in.(3943,305792,148971,247936)', + name: 'ilike.%search%', +}); diff --git a/src/app/core/models/platform/v1/platform-project-params.model.ts b/src/app/core/models/platform/v1/platform-project-params.model.ts new file mode 100644 index 0000000000..d55325d0b3 --- /dev/null +++ b/src/app/core/models/platform/v1/platform-project-params.model.ts @@ -0,0 +1,13 @@ +export interface PlatformProjectParams { + limit: number; + offset: number; + order?: string; + sortDirection?: string; + sortOrder?: string; + searchNameText?: string; + is_enabled?: string; + id?: string; + category_ids?: string; + org_id?: string; + name?: string; +} diff --git a/src/app/core/services/projects.service.spec.ts b/src/app/core/services/projects.service.spec.ts index ba07aec396..d86892cbdc 100644 --- a/src/app/core/services/projects.service.spec.ts +++ b/src/app/core/services/projects.service.spec.ts @@ -17,10 +17,8 @@ import { } from '../test-data/projects.spec.data'; import { ProjectsService } from './projects.service'; import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service'; -import { - platformAPIResponseActiveOnly, - platformProjectSingleRes, -} from '../mock-data/platform/v1/platform-project.data'; +import { platformAPIResponseMultiple, platformProjectSingleRes, platformAPIResponseActiveOnly } from '../mock-data/platform/v1/platform-project.data'; +import { ProjectPlatformParams } from '../mock-data/platform/v1/platform-projects-params.data'; const fixDate = (data) => data.map((datum) => ({ @@ -101,7 +99,7 @@ describe('ProjectsService', () => { }); it('should be able to fetch data when no params provided', (done) => { - apiV2Service.get.and.returnValue(of(apiV2ResponseMultiple)); + spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseMultiple)); projectsService.getByParamsUnformatted({}).subscribe((res) => { expect(res).toEqual(fixDate(apiV2ResponseMultiple.data)); @@ -110,15 +108,18 @@ describe('ProjectsService', () => { }); it('should be able to fetch data when params are provided', (done) => { - apiV2Service.get.and.returnValue(of(apiV2ResponseMultiple)); + spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseMultiple)); + const params = ProjectPlatformParams; + const transformToV2ResponseSpy = spyOn(projectsService, 'transformToV2Response').and.callThrough(); const result = projectsService.getByParamsUnformatted(testProjectParams); result.subscribe((res) => { expect(res).toEqual(expectedProjectsResponse); - expect(apiV2Service.get).toHaveBeenCalledWith('/projects', { + expect(spenderPlatformV1ApiService.get).toHaveBeenCalledWith('/projects', { params, }); + expect(transformToV2ResponseSpy).toHaveBeenCalled(); done(); }); }); diff --git a/src/app/core/services/projects.service.ts b/src/app/core/services/projects.service.ts index 20942bd596..2e464f579a 100644 --- a/src/app/core/services/projects.service.ts +++ b/src/app/core/services/projects.service.ts @@ -12,6 +12,7 @@ import { OrgCategory } from '../models/v1/org-category.model'; import { PlatformProject } from '../models/platform/platform-project.model'; import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service'; import { PlatformApiResponse } from '../models/platform/platform-api-response.model'; +import { PlatformProjectParams } from '../models/platform/v1/platform-project-params.model'; @Injectable({ providedIn: 'root', @@ -27,7 +28,7 @@ export class ProjectsService { getByParamsUnformatted( projectParams: Partial<{ orgId: string; - active: boolean; + isEnabled: boolean; orgCategoryIds: string[]; searchNameText: string; limit: number; @@ -38,20 +39,20 @@ export class ProjectsService { }> ): Observable { // eslint-disable-next-line prefer-const - let { orgId, active, orgCategoryIds, searchNameText, limit, offset, sortOrder, sortDirection, projectIds } = + let { orgId, isEnabled, orgCategoryIds, searchNameText, limit, offset, sortOrder, sortDirection, projectIds } = projectParams; sortOrder = sortOrder || 'project_updated_at'; sortDirection = sortDirection || 'desc'; - const params: ProjectParams = { - project_org_id: 'eq.' + orgId, + const params: PlatformProjectParams = { + org_id: 'eq.' + orgId, order: sortOrder + '.' + sortDirection, limit: limit || 200, offset: offset || 0, }; // `active` can be optional - this.addActiveFilter(active, params); + this.addActiveFilter(isEnabled, params); // `orgCategoryIds` can be optional this.addOrgCategoryIdsFilter(orgCategoryIds, params); @@ -62,19 +63,11 @@ export class ProjectsService { // `searchNameText` can be optional this.addNameSearchFilter(searchNameText, params); - return this.apiV2Service - .get('/projects', { + return this.spenderPlatformV1ApiService + .get>('/projects', { params, }) - .pipe( - map((res) => - res.data.map((datum) => ({ - ...datum, - project_created_at: new Date(datum.project_created_at), - project_updated_at: new Date(datum.project_updated_at), - })) - ) - ); + .pipe(map((res) => this.transformToV2Response(res.data))); } @Cacheable() @@ -94,27 +87,27 @@ export class ProjectsService { ); } - addNameSearchFilter(searchNameText: string, params: ProjectParams): void { + addNameSearchFilter(searchNameText: string, params: PlatformProjectParams): void { if (typeof searchNameText !== 'undefined' && searchNameText !== null) { - params.project_name = 'ilike.%' + searchNameText + '%'; + params.name = 'ilike.%' + searchNameText + '%'; } } - addProjectIdsFilter(projectIds: number[], params: ProjectParams): void { + addProjectIdsFilter(projectIds: number[], params: PlatformProjectParams): void { if (typeof projectIds !== 'undefined' && projectIds !== null) { - params.project_id = 'in.(' + projectIds.join(',') + ')'; + params.id = 'in.(' + projectIds.join(',') + ')'; } } - addOrgCategoryIdsFilter(orgCategoryIds: string[], params: ProjectParams): void { + addOrgCategoryIdsFilter(orgCategoryIds: string[], params: PlatformProjectParams): void { if (typeof orgCategoryIds !== 'undefined' && orgCategoryIds !== null) { - params.project_org_category_ids = 'ov.{' + orgCategoryIds.join(',') + '}'; + params.category_ids = 'ov.{' + orgCategoryIds.join(',') + '}'; } } - addActiveFilter(active: boolean, params: ProjectParams): void { - if (typeof active !== 'undefined' && active !== null) { - params.project_active = 'eq.' + active; + addActiveFilter(isEnabled: boolean, params: PlatformProjectParams): void { + if (typeof isEnabled !== 'undefined' && isEnabled !== null) { + params.is_enabled = 'eq.' + isEnabled; } } @@ -158,8 +151,8 @@ export class ProjectsService { transformToV1Response(platformProject: PlatformProject[]): ProjectV1[] { const projectV1 = platformProject.map((platformProject) => ({ id: platformProject.id, - created_at: platformProject.created_at, - updated_at: platformProject.updated_at, + created_at: new Date(platformProject.created_at), + updated_at: new Date(platformProject.updated_at), name: platformProject.name, sub_project: platformProject.sub_project, code: platformProject.code, @@ -176,13 +169,13 @@ export class ProjectsService { const projectV2 = platformProject.map((platformProject) => ({ project_active: platformProject.is_enabled, project_code: platformProject.code, - project_created_at: platformProject.created_at, + project_created_at: new Date(platformProject.created_at), project_description: platformProject.description, project_id: platformProject.id, project_name: platformProject.display_name, project_org_category_ids: platformProject.category_ids, project_org_id: platformProject.org_id, - project_updated_at: platformProject.updated_at, + project_updated_at: new Date(platformProject.updated_at), projectv2_name: platformProject.name, sub_project_name: platformProject.sub_project, })); diff --git a/src/app/core/services/recently-used-items.service.spec.ts b/src/app/core/services/recently-used-items.service.spec.ts index 437ab4cde6..0a56aaa58c 100644 --- a/src/app/core/services/recently-used-items.service.spec.ts +++ b/src/app/core/services/recently-used-items.service.spec.ts @@ -65,9 +65,9 @@ describe('RecentlyUsedItemsService', () => { recentlyUsedItemsService.getRecentlyUsedProjects(config).subscribe((res) => { expect(projectsService.getByParamsUnformatted).toHaveBeenCalledOnceWith({ orgId: config.eou.ou.org_id, - active: true, + isEnabled: true, sortDirection: 'asc', - sortOrder: 'project_name', + sortOrder: 'name', orgCategoryIds: config.categoryIds, projectIds: config.recentValues.recent_project_ids, offset: 0, diff --git a/src/app/core/services/recently-used-items.service.ts b/src/app/core/services/recently-used-items.service.ts index 5a0bba0475..4a29a5d995 100644 --- a/src/app/core/services/recently-used-items.service.ts +++ b/src/app/core/services/recently-used-items.service.ts @@ -33,9 +33,9 @@ export class RecentlyUsedItemsService { return this.projectsService .getByParamsUnformatted({ orgId: config.eou.ou.org_id, - active: true, + isEnabled: true, sortDirection: 'asc', - sortOrder: 'project_name', + sortOrder: 'name', orgCategoryIds: config.categoryIds, projectIds: config.recentValues.recent_project_ids, offset: 0, diff --git a/src/app/core/test-data/projects.spec.data.ts b/src/app/core/test-data/projects.spec.data.ts index 135904f9c2..a63bae5b1c 100644 --- a/src/app/core/test-data/projects.spec.data.ts +++ b/src/app/core/test-data/projects.spec.data.ts @@ -1,5 +1,4 @@ import deepFreeze from 'deep-freeze-strict'; -import { ProjectParams } from '../models/project-params.model'; import { ProjectV1 } from '../models/v1/extended-project.model'; import { OrgCategory, OrgCategoryListItem } from '../models/v1/org-category.model'; import { ProjectV2 } from '../models/v2/project-v2.model'; @@ -86,13 +85,7 @@ export const apiV2ResponseMultiple = deepFreeze({ count: 2, data: [ { - ap1_email: null, - ap1_full_name: null, - ap2_email: null, - ap2_full_name: null, project_active: true, - project_approver1_id: null, - project_approver2_id: null, project_code: '1184', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', @@ -105,13 +98,7 @@ export const apiV2ResponseMultiple = deepFreeze({ sub_project_name: null, }, { - ap1_email: null, - ap1_full_name: null, - ap2_email: null, - ap2_full_name: null, project_active: true, - project_approver1_id: null, - project_approver2_id: null, project_code: '1182', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Fyle Engineering, Id - 1182', @@ -300,13 +287,7 @@ export const allowedActiveCategoriesListOptions: OrgCategoryListItem[] = deepFre export const expectedProjectsResponse: ProjectV2[] = deepFreeze([ { - ap1_email: null, - ap1_full_name: null, - ap2_email: null, - ap2_full_name: null, project_active: true, - project_approver1_id: null, - project_approver2_id: null, project_code: '1184', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', @@ -319,13 +300,7 @@ export const expectedProjectsResponse: ProjectV2[] = deepFreeze([ sub_project_name: null, }, { - ap1_email: null, - ap1_full_name: null, - ap2_email: null, - ap2_full_name: null, project_active: true, - project_approver1_id: null, - project_approver2_id: null, project_code: '1182', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Fyle Engineering, Id - 1182', @@ -339,11 +314,11 @@ export const expectedProjectsResponse: ProjectV2[] = deepFreeze([ }, ]); -export const testProjectParams: ProjectParams = deepFreeze({ +export const testProjectParams = deepFreeze({ orgId: 'orNVthTo2Zyo', - active: true, + isEnabled: true, sortDirection: 'asc', - sortOrder: 'project_name', + sortOrder: 'name', orgCategoryIds: [null, '122269', '122270', '122271', '122272', '122273'], projectIds: [3943, 305792, 148971, 247936], offset: 0, diff --git a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts index 0f3cd2eb22..b546517f11 100644 --- a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts +++ b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts @@ -117,7 +117,7 @@ describe('FyProjectSelectModalComponent', () => { projectsService.getbyId.and.returnValue(of(singleProjects1)); orgSettingsService.get.and.returnValue(of(orgSettingsData)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); orgUserSettingsService.get.and.returnValue(of(orgUserSettingsData)); projectsService.getByParamsUnformatted.and.returnValue(of([singleProject2])); @@ -125,7 +125,7 @@ describe('FyProjectSelectModalComponent', () => { component.cacheName = 'projects'; component.defaultValue = true; component.searchBarRef = fixture.debugElement.query(By.css('.selection-modal--search-input')); - recentLocalStorageItemsService.get.and.returnValue(Promise.resolve([testProjectV2])); + recentLocalStorageItemsService.get.and.resolveTo([testProjectV2]); utilityService.searchArrayStream.and.returnValue(() => of([{ label: '', value: '' }])); fixture.detectChanges(); @@ -140,7 +140,7 @@ describe('FyProjectSelectModalComponent', () => { it('should get projects when current selection is not defined', (done) => { projectsService.getByParamsUnformatted.and.returnValue(of(projects)); projectsService.getbyId.and.returnValue(of(expectedProjects[0].value)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); component.getProjects('projects').subscribe((res) => { expect(res).toEqual(expectedProjects); @@ -149,9 +149,9 @@ describe('FyProjectSelectModalComponent', () => { expect(orgUserSettingsService.get).toHaveBeenCalledTimes(4); expect(projectsService.getByParamsUnformatted).toHaveBeenCalledWith({ orgId: 'orNVthTo2Zyo', - active: true, + isEnabled: true, sortDirection: 'asc', - sortOrder: 'project_name', + sortOrder: 'name', orgCategoryIds: undefined, projectIds: null, searchNameText: '', @@ -175,9 +175,9 @@ describe('FyProjectSelectModalComponent', () => { expect(orgUserSettingsService.get).toHaveBeenCalledTimes(4); expect(projectsService.getByParamsUnformatted).toHaveBeenCalledWith({ orgId: 'orNVthTo2Zyo', - active: true, + isEnabled: true, sortDirection: 'asc', - sortOrder: 'project_name', + sortOrder: 'name', orgCategoryIds: undefined, projectIds: null, searchNameText: '', @@ -201,9 +201,9 @@ describe('FyProjectSelectModalComponent', () => { expect(orgUserSettingsService.get).toHaveBeenCalledTimes(4); expect(projectsService.getByParamsUnformatted).toHaveBeenCalledWith({ orgId: 'orNVthTo2Zyo', - active: true, + isEnabled: true, sortDirection: 'asc', - sortOrder: 'project_name', + sortOrder: 'name', orgCategoryIds: undefined, projectIds: null, searchNameText: '', @@ -251,14 +251,12 @@ describe('FyProjectSelectModalComponent', () => { }); it('should get project from recently used storage if not already present', (done) => { - recentLocalStorageItemsService.get.and.returnValue( - Promise.resolve([ - { - label: 'label', - value: testProjectV2, - }, - ]) - ); + recentLocalStorageItemsService.get.and.resolveTo([ + { + label: 'label', + value: testProjectV2, + }, + ]); component.recentlyUsed = null; component.cacheName = 'project'; fixture.detectChanges(); @@ -277,16 +275,16 @@ describe('FyProjectSelectModalComponent', () => { }); }); - it('onDoneClick(): should dimiss the modal on clicking the done CTA', () => { - modalController.dismiss.and.returnValue(Promise.resolve(true)); + it('onDoneClick(): should dimiss the modal on clicking the done CTA', async () => { + modalController.dismiss.and.resolveTo(true); - component.onDoneClick(); + await component.onDoneClick(); expect(modalController.dismiss).toHaveBeenCalledTimes(1); }); describe('onElementSelect():', () => { it('should dismiss the modal with selected option', () => { - modalController.dismiss.and.returnValue(Promise.resolve(true)); + modalController.dismiss.and.resolveTo(true); component.onElementSelect({ label: '', value: null }); expect(modalController.dismiss).toHaveBeenCalledWith({ label: '', value: null }); @@ -294,7 +292,7 @@ describe('FyProjectSelectModalComponent', () => { }); it('should cache the selected option and dismiss the modal', () => { - modalController.dismiss.and.returnValue(Promise.resolve(true)); + modalController.dismiss.and.resolveTo(true); recentLocalStorageItemsService.post.and.returnValue(null); component.cacheName = 'cache'; fixture.detectChanges(); diff --git a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts index a5581fca4b..a90a510e50 100644 --- a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts +++ b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts @@ -85,9 +85,9 @@ export class FyProjectSelectModalComponent implements AfterViewInit { switchMap((eou) => this.projectsService.getByParamsUnformatted({ orgId: eou.ou.org_id, - active: true, + isEnabled: true, sortDirection: 'asc', - sortOrder: 'project_name', + sortOrder: 'name', orgCategoryIds: this.categoryIds, projectIds: allowedProjectIds, searchNameText, From 6274a01e013571d8175d8e48e9331ae7db91c768 Mon Sep 17 00:00:00 2001 From: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Date: Mon, 13 May 2024 15:30:14 +0530 Subject: [PATCH 003/262] feat: changing order of stats to expenses->reports (#2981) --- .../fyle/dashboard/stats/stats.component.html | 63 +++++++++---------- .../fyle/dashboard/stats/stats.component.scss | 4 +- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/app/fyle/dashboard/stats/stats.component.html b/src/app/fyle/dashboard/stats/stats.component.html index 40220b269a..dac9aa7cb8 100644 --- a/src/app/fyle/dashboard/stats/stats.component.html +++ b/src/app/fyle/dashboard/stats/stats.component.html @@ -1,5 +1,36 @@
+ + +
ALL EXPENSES
+
+
+ + + + + + + +
EXPENSE REPORTS
@@ -102,38 +133,6 @@
- - - -
ALL EXPENSES
-
-
- - - - - - - -
diff --git a/src/app/fyle/dashboard/stats/stats.component.scss b/src/app/fyle/dashboard/stats/stats.component.scss index cd2e5ec19b..6de58c3d98 100644 --- a/src/app/fyle/dashboard/stats/stats.component.scss +++ b/src/app/fyle/dashboard/stats/stats.component.scss @@ -88,13 +88,13 @@ $ccc-bank-name: #c4cae8; &--report-stats-row { padding-bottom: 18px; - padding-top: 8px; + padding-top: 18px; --ion-grid-column-padding: 0; } &--expenses-stats-row { padding-bottom: 18px; - padding-top: 18px; + padding-top: 8px; --ion-grid-column-padding: 0; } } From 77e6f4cbc81be3ca38d139608fce1fa5f9adc32c Mon Sep 17 00:00:00 2001 From: SK027 Date: Mon, 13 May 2024 16:45:28 +0530 Subject: [PATCH 004/262] fix: Improved test cases ProjectsService (#2980) --- .../core/services/projects.service.spec.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/app/core/services/projects.service.spec.ts b/src/app/core/services/projects.service.spec.ts index d86892cbdc..e5a0c6dac8 100644 --- a/src/app/core/services/projects.service.spec.ts +++ b/src/app/core/services/projects.service.spec.ts @@ -17,7 +17,11 @@ import { } from '../test-data/projects.spec.data'; import { ProjectsService } from './projects.service'; import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service'; -import { platformAPIResponseMultiple, platformProjectSingleRes, platformAPIResponseActiveOnly } from '../mock-data/platform/v1/platform-project.data'; +import { + platformAPIResponseMultiple, + platformProjectSingleRes, + platformAPIResponseActiveOnly, +} from '../mock-data/platform/v1/platform-project.data'; import { ProjectPlatformParams } from '../mock-data/platform/v1/platform-projects-params.data'; const fixDate = (data) => @@ -69,8 +73,7 @@ describe('ProjectsService', () => { it('should be able to fetch project by id', (done) => { spenderPlatformV1ApiService.get.and.returnValue(of(platformProjectSingleRes)); - const transformToV2ResponseSpy = spyOn(projectsService, 'transformToV2Response').and.callThrough(); - + spyOn(projectsService, 'transformToV2Response').and.returnValue([apiV2ResponseSingle.data[0]]); projectsService.getbyId(257528).subscribe((res) => { expect(res).toEqual(apiV2ResponseSingle.data[0]); expect(spenderPlatformV1ApiService.get).toHaveBeenCalledOnceWith('/projects', { @@ -78,14 +81,14 @@ describe('ProjectsService', () => { id: 'eq.257528', }, }); - expect(transformToV2ResponseSpy).toHaveBeenCalled(); + expect(projectsService.transformToV2Response).toHaveBeenCalled(); done(); }); }); it('should be able to fetch all active projects', (done) => { spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseActiveOnly)); - const transformToV1ResponseSpy = spyOn(projectsService, 'transformToV1Response').and.callThrough(); + spyOn(projectsService, 'transformToV1Response').and.returnValue(expectedReponseActiveOnly); projectsService.getAllActive().subscribe((res) => { expect(res).toEqual(expectedReponseActiveOnly); expect(spenderPlatformV1ApiService.get).toHaveBeenCalledOnceWith('/projects', { @@ -93,7 +96,7 @@ describe('ProjectsService', () => { is_enabled: `eq.true`, }, }); - expect(transformToV1ResponseSpy).toHaveBeenCalled(); + expect(projectsService.transformToV1Response).toHaveBeenCalled(); done(); }); }); @@ -110,16 +113,15 @@ describe('ProjectsService', () => { it('should be able to fetch data when params are provided', (done) => { spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseMultiple)); const params = ProjectPlatformParams; - const transformToV2ResponseSpy = spyOn(projectsService, 'transformToV2Response').and.callThrough(); - const result = projectsService.getByParamsUnformatted(testProjectParams); + spyOn(projectsService, 'transformToV2Response').and.returnValue(expectedProjectsResponse); result.subscribe((res) => { expect(res).toEqual(expectedProjectsResponse); expect(spenderPlatformV1ApiService.get).toHaveBeenCalledWith('/projects', { params, }); - expect(transformToV2ResponseSpy).toHaveBeenCalled(); + expect(projectsService.transformToV2Response).toHaveBeenCalled(); done(); }); }); From ff2a8af9b2c3d4d20db43dbab72fb7ace970e3c6 Mon Sep 17 00:00:00 2001 From: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Date: Tue, 14 May 2024 10:03:11 +0530 Subject: [PATCH 005/262] test: fixing eslint issues for resolveTo (#2947) * test: fixing eslint issues for resolveTo * minor --- .../popup/popup.component.spec.ts | 2 +- .../request-invitation.page.spec.ts | 12 ++-- src/app/auth/sign-in/sign-in.page.spec.ts | 42 ++++++------- .../auth/switch-org/switch-org.page.spec.ts | 60 +++++++++---------- .../core/services/app-version.service.spec.ts | 2 +- src/app/core/services/auth.service.spec.ts | 24 ++++---- src/app/core/services/config.service.spec.ts | 4 +- ...porate-credit-card-expense.service.spec.ts | 2 +- .../core/services/currency.service.spec.ts | 2 +- .../services/custom-inputs.service.spec.ts | 4 +- .../core/services/google-auth.service.spec.ts | 6 +- .../services/launch-darkly.service.spec.ts | 4 +- src/app/core/services/loader.service.spec.ts | 8 +-- .../core/services/login-info.service.spec.ts | 8 +-- .../core/services/org-user.service.spec.ts | 8 +-- src/app/core/services/refiner.service.spec.ts | 4 +- .../core/services/router-auth.service.spec.ts | 20 +++---- src/app/core/services/storage.service.spec.ts | 10 ++-- src/app/core/services/tasks.service.spec.ts | 18 +++--- src/app/core/services/token.service.spec.ts | 18 +++--- .../deep-link-redirection.page.spec.ts | 10 ++-- .../add-edit-per-diem-5.page.spec.ts | 2 +- .../split-expense/split-expense.page.spec.ts | 2 +- .../fy-view-attachment.component.spec.ts | 4 +- 24 files changed, 135 insertions(+), 141 deletions(-) diff --git a/src/app/auth/new-password/popup/popup.component.spec.ts b/src/app/auth/new-password/popup/popup.component.spec.ts index 7830738183..755cd7f5d5 100644 --- a/src/app/auth/new-password/popup/popup.component.spec.ts +++ b/src/app/auth/new-password/popup/popup.component.spec.ts @@ -51,7 +51,7 @@ describe('ErrorComponent', () => { // @ts-ignore component.popoverController.dismiss.and.returnValue(of(null)); // @ts-ignore - component.router.navigate.and.returnValue(Promise.resolve(true)); + component.router.navigate.and.resolveTo(true); component.closeClicked(); // @ts-ignore expect(component.popoverController.dismiss).toHaveBeenCalledTimes(1); diff --git a/src/app/auth/request-invitation/request-invitation.page.spec.ts b/src/app/auth/request-invitation/request-invitation.page.spec.ts index acbfdf075b..d40f5ec2e9 100644 --- a/src/app/auth/request-invitation/request-invitation.page.spec.ts +++ b/src/app/auth/request-invitation/request-invitation.page.spec.ts @@ -65,8 +65,8 @@ describe('RequestInvitationPage', () => { describe('sendRequestInvitation', () => { it('should call the upsertRouter method with the correct email', fakeAsync(() => { invitationRequestsService.upsertRouter.and.returnValue(of(null)); - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); const ipEmail = 'ajain1234@fyle.in'; component.fg.controls.email.setValue(ipEmail); @@ -85,8 +85,8 @@ describe('RequestInvitationPage', () => { }; component.fg.controls.email.setValue(''); invitationRequestsService.upsertRouter.and.returnValue(throwError(() => error)); - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); component.sendRequestInvitation(); tick(500); @@ -103,8 +103,8 @@ describe('RequestInvitationPage', () => { }; component.fg.controls.email.setValue(''); invitationRequestsService.upsertRouter.and.returnValue(throwError(() => error)); - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); component.sendRequestInvitation(); tick(500); diff --git a/src/app/auth/sign-in/sign-in.page.spec.ts b/src/app/auth/sign-in/sign-in.page.spec.ts index 6150802007..3d437b9cde 100644 --- a/src/app/auth/sign-in/sign-in.page.spec.ts +++ b/src/app/auth/sign-in/sign-in.page.spec.ts @@ -149,7 +149,7 @@ describe('SignInPage', () => { loaderService.showLoader.and.returnValue(new Promise(() => {})); router.navigate.and.stub(); - routerAuthService.isLoggedIn.and.returnValue(Promise.resolve(false)); + routerAuthService.isLoggedIn.and.resolveTo(false); component.fg = formBuilder.group({ email: [Validators.compose([Validators.required, Validators.pattern('\\S+@\\S+\\.\\S{2,}')])], @@ -165,7 +165,7 @@ describe('SignInPage', () => { it('handleSamlSignIn(): should handle saml sign in ', fakeAsync(() => { const browserSpy = jasmine.createSpyObj('InAppBrowserObject', ['on', 'executeScript', 'close']); browserSpy.on.and.returnValue(of(new Event('event'))); - browserSpy.executeScript.and.returnValue(Promise.resolve([JSON.stringify(samlResData1)])); + browserSpy.executeScript.and.resolveTo([JSON.stringify(samlResData1)]); browserSpy.close.and.returnValue(null); spyOn(component, 'checkSAMLResponseAndSignInUser'); inAppBrowserService.create.and.returnValue(browserSpy); @@ -181,9 +181,9 @@ describe('SignInPage', () => { describe('checkSAMLResponseAndSignInUser():', () => { it('should check saml response and sign in user', async () => { - routerAuthService.handleSignInResponse.and.returnValue(Promise.resolve(authResData1)); + routerAuthService.handleSignInResponse.and.resolveTo(authResData1); spyOn(component, 'trackLoginInfo'); - router.navigate.and.returnValue(Promise.resolve(true)); + router.navigate.and.resolveTo(true); authService.refreshEou.and.returnValue(of(apiEouRes)); component.fg.controls.email.setValue('ajain@fyle.in'); @@ -351,7 +351,7 @@ describe('SignInPage', () => { routerAuthService.basicSignin.and.returnValue(of(authResData1)); authService.refreshEou.and.returnValue(of(apiEouRes)); trackingService.onSignin.and.callThrough(); - router.navigate.and.returnValue(Promise.resolve(true)); + router.navigate.and.resolveTo(true); component.fg.controls.password.setValue('password'); component.fg.controls.email.setValue('email'); fixture.detectChanges(); @@ -372,7 +372,7 @@ describe('SignInPage', () => { routerAuthService.basicSignin.and.returnValue(throwError(() => new HttpErrorResponse({ error: 'error' }))); authService.refreshEou.and.returnValue(of(apiEouRes)); trackingService.onSignin.and.callThrough(); - router.navigate.and.returnValue(Promise.resolve(true)); + router.navigate.and.resolveTo(true); spyOn(component, 'handleError'); fixture.detectChanges(); @@ -398,12 +398,12 @@ describe('SignInPage', () => { it('should sign in user with google', async () => { spyOn(component, 'trackLoginInfo'); - googleAuthService.login.and.returnValue(Promise.resolve(authResData2)); - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); + googleAuthService.login.and.resolveTo(authResData2); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); routerAuthService.googleSignin.and.returnValue(of(authResData2)); trackingService.onSignin.and.callThrough(); - router.navigate.and.returnValue(Promise.resolve(true)); + router.navigate.and.resolveTo(true); authService.refreshEou.and.returnValue(of(apiEouRes)); await component.googleSignIn(); @@ -421,7 +421,7 @@ describe('SignInPage', () => { }); it("should throw an error if google reponse doesn't contain access token", async () => { - googleAuthService.login.and.returnValue(Promise.resolve(authResData1)); + googleAuthService.login.and.resolveTo(authResData1); spyOn(component, 'handleError'); await component.googleSignIn(); @@ -443,20 +443,20 @@ describe('SignInPage', () => { }); it('ionViewWillEnter(): should set email', () => { - expect(component.emailSet).toEqual(false); + expect(component.emailSet).toBeFalse(); component.fg.controls.email.setValue('email'); component.ionViewWillEnter(); - expect(component.emailSet).toEqual(true); + expect(component.emailSet).toBeTrue(); }); describe('ngOnInit(): ', () => { it('should navigate to switch org page if logged in ', fakeAsync(() => { - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); - routerAuthService.isLoggedIn.and.returnValue(Promise.resolve(true)); - router.navigate.and.returnValue(Promise.resolve(true)); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); + routerAuthService.isLoggedIn.and.resolveTo(true); + router.navigate.and.resolveTo(true); component.ngOnInit(); tick(100); @@ -468,10 +468,10 @@ describe('SignInPage', () => { it('should set fg when email is not present in URl', fakeAsync(() => { activatedRoute.snapshot.params.email = null; - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); - routerAuthService.isLoggedIn.and.returnValue(Promise.resolve(true)); - router.navigate.and.returnValue(Promise.resolve(true)); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); + routerAuthService.isLoggedIn.and.resolveTo(true); + router.navigate.and.resolveTo(true); component.ngOnInit(); tick(1000); diff --git a/src/app/auth/switch-org/switch-org.page.spec.ts b/src/app/auth/switch-org/switch-org.page.spec.ts index b6af9c13f4..599f0ed2af 100644 --- a/src/app/auth/switch-org/switch-org.page.spec.ts +++ b/src/app/auth/switch-org/switch-org.page.spec.ts @@ -265,11 +265,11 @@ describe('SwitchOrgPage', () => { beforeEach(() => { orgService.getOrgs.and.returnValue(of(orgData1)); spyOn(component, 'getOrgsWhichContainSearchText').and.returnValue(orgData1); - spyOn(component, 'proceed').and.returnValue(Promise.resolve()); + spyOn(component, 'proceed').and.resolveTo(); spyOn(component, 'redirectToExpensePage').and.returnValue(); orgService.getCurrentOrg.and.returnValue(of(orgData1[0])); orgService.getPrimaryOrg.and.returnValue(of(orgData2[1])); - loaderService.showLoader.and.returnValue(Promise.resolve()); + loaderService.showLoader.and.resolveTo(); spyOn(component, 'trackSwitchOrgLaunchTime').and.returnValue(null); }); @@ -474,15 +474,13 @@ describe('SwitchOrgPage', () => { it('should show email not verified alert', fakeAsync(() => { spyOn(component, 'handleDismissPopup').and.returnValue(null); const popoverSpy = jasmine.createSpyObj('popover', ['present', 'onWillDismiss']); - popoverSpy.onWillDismiss.and.returnValue( - Promise.resolve({ - data: { - action: 'action', - }, - }) - ); - popoverController.create.and.returnValue(Promise.resolve(popoverSpy)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + popoverSpy.onWillDismiss.and.resolveTo({ + data: { + action: 'action', + }, + }); + popoverController.create.and.resolveTo(popoverSpy); + authService.getEou.and.resolveTo(apiEouRes); component.orgs$ = of(orgData1); fixture.detectChanges(); @@ -512,13 +510,11 @@ describe('SwitchOrgPage', () => { it('should show appropiate popup if action is not provided', fakeAsync(() => { spyOn(component, 'handleDismissPopup').and.returnValue(null); const popoverSpy = jasmine.createSpyObj('popover', ['present', 'onWillDismiss']); - popoverSpy.onWillDismiss.and.returnValue( - Promise.resolve({ - data: undefined, - }) - ); - popoverController.create.and.returnValue(Promise.resolve(popoverSpy)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + popoverSpy.onWillDismiss.and.resolveTo({ + data: undefined, + }); + popoverController.create.and.resolveTo(popoverSpy); + authService.getEou.and.resolveTo(apiEouRes); component.orgs$ = of(orgData1); fixture.detectChanges(); @@ -561,8 +557,8 @@ describe('SwitchOrgPage', () => { }); it('markUserActive(): should mark the user as active and return the org', (done) => { - loaderService.showLoader.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); + loaderService.showLoader.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); orgUserService.markActive.and.returnValue(of(apiEouRes)); component @@ -629,7 +625,7 @@ describe('SwitchOrgPage', () => { }); it('should show email verification alert if the user has not come through invite link', (done) => { - spyOn(component, 'showEmailNotVerifiedAlert').and.returnValue(Promise.resolve()); + spyOn(component, 'showEmailNotVerifiedAlert').and.resolveTo(); component.handlePendingDetails(roles, false).subscribe((res) => { expect(res).toBeNull(); @@ -687,7 +683,7 @@ describe('SwitchOrgPage', () => { it('proceed(): should proceed to other page as per user status', fakeAsync(() => { userService.isPendingDetails.and.returnValue(of(true)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); authService.getRoles.and.returnValue(of(roles)); spyOn(component, 'setSentryUser').and.callThrough(); spyOn(component, 'navigateBasedOnUserStatus').and.returnValue(of(apiEouRes)); @@ -731,7 +727,7 @@ describe('SwitchOrgPage', () => { })); it('trackSwitchOrg(): tracking switch orgs', fakeAsync(() => { - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); component.trackSwitchOrg(orgData1[0], apiEouRes); tick(500); @@ -753,12 +749,12 @@ describe('SwitchOrgPage', () => { describe('switchOrg(): ', () => { it('should catch error and clear all caches', fakeAsync(() => { - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); - loaderService.showLoader.and.returnValue(Promise.resolve()); + authService.getEou.and.resolveTo(apiEouRes); + loaderService.showLoader.and.resolveTo(); orgService.switchOrg.and.returnValue(throwError(() => {})); - secureStorageService.clearAll.and.returnValue(Promise.resolve({ value: true })); - storageService.clearAll.and.returnValue(Promise.resolve()); - loaderService.hideLoader.and.returnValue(Promise.resolve()); + secureStorageService.clearAll.and.resolveTo({ value: true }); + storageService.clearAll.and.resolveTo(); + loaderService.hideLoader.and.resolveTo(); userEventService.logout.and.returnValue(null); spyOn(globalCacheBusterNotifier, 'next'); @@ -777,11 +773,11 @@ describe('SwitchOrgPage', () => { })); it('should switch org', fakeAsync(() => { - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); - loaderService.showLoader.and.returnValue(Promise.resolve()); + authService.getEou.and.resolveTo(apiEouRes); + loaderService.showLoader.and.resolveTo(); orgService.switchOrg.and.returnValue(of(apiEouRes)); spyOn(component, 'trackSwitchOrg').and.returnValue(null); - spyOn(component, 'proceed').and.returnValue(Promise.resolve(null)); + spyOn(component, 'proceed').and.resolveTo(null); spyOn(globalCacheBusterNotifier, 'next'); component.switchOrg(orgData1[0]); @@ -800,7 +796,7 @@ describe('SwitchOrgPage', () => { describe('signOut(): ', () => { it('should sign out the user', fakeAsync(() => { deviceService.getDeviceInfo.and.returnValue(of(extendedDeviceInfoMockData)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); authService.logout.and.returnValue(of(null)); spyOn(globalCacheBusterNotifier, 'next'); diff --git a/src/app/core/services/app-version.service.spec.ts b/src/app/core/services/app-version.service.spec.ts index 1a878a97fd..27f2d203ad 100644 --- a/src/app/core/services/app-version.service.spec.ts +++ b/src/app/core/services/app-version.service.spec.ts @@ -161,7 +161,7 @@ describe('AppVersionService', () => { beforeEach(() => { spyOn(appVersionService, 'isSupported').and.returnValue(of({ supported: true })); loginInfoService.getLastLoggedInVersion.and.returnValue(of('5.50.0')); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); }); it("getUserAppVersionDetails(): should get user's app version details", (done) => { diff --git a/src/app/core/services/auth.service.spec.ts b/src/app/core/services/auth.service.spec.ts index 49639de762..40c3a7a31d 100644 --- a/src/app/core/services/auth.service.spec.ts +++ b/src/app/core/services/auth.service.spec.ts @@ -73,7 +73,7 @@ describe('AuthService', () => { }); it('getEou(): should get extended org user', (done) => { - storageService.get.and.returnValue(Promise.resolve(apiEouRes)); + storageService.get.and.resolveTo(apiEouRes); authService.getEou().then((res) => { expect(res).toEqual(apiEouRes); @@ -85,7 +85,7 @@ describe('AuthService', () => { it('refreshEou(): should refresh extended org user in memory', (done) => { apiService.get.and.returnValue(of(eouFlattended)); dataTransformService.unflatten.and.returnValue(eouRes3); - storageService.set.and.returnValue(Promise.resolve(null)); + storageService.set.and.resolveTo(null); authService.refreshEou().subscribe((res) => { expect(res).toEqual(eouRes3); @@ -99,7 +99,7 @@ describe('AuthService', () => { describe('getRoles():', () => { it('should get roles from access token', (done) => { const roles = ['ADMIN', 'APPROVER', 'FYLER', 'HOP', 'HOD', 'OWNER']; - tokenService.getAccessToken.and.returnValue(Promise.resolve(access_token)); + tokenService.getAccessToken.and.resolveTo(access_token); jwtHelperService.decodeToken.and.returnValue(apiAccessTokenRes); spyOn(JSON, 'parse').and.returnValue(roles); @@ -113,7 +113,7 @@ describe('AuthService', () => { }); it('return empty array if token no received', (done) => { - tokenService.getAccessToken.and.returnValue(Promise.resolve(null)); + tokenService.getAccessToken.and.resolveTo(null); authService.getRoles().subscribe((res) => { expect(res).toEqual([]); @@ -122,7 +122,7 @@ describe('AuthService', () => { }); it('should return empty array if roles not present', (done) => { - tokenService.getAccessToken.and.returnValue(Promise.resolve(access_token)); + tokenService.getAccessToken.and.resolveTo(access_token); jwtHelperService.decodeToken.and.returnValue(apiTokenWithoutRoles); authService.getRoles().subscribe((res) => { @@ -177,15 +177,13 @@ describe('AuthService', () => { }); it('newRefreshToken(): should refresh new token', (done) => { - storageService.delete.withArgs('user').and.returnValue(Promise.resolve(null)); - storageService.delete.withArgs('role').and.returnValue(Promise.resolve(null)); - tokenService.resetAccessToken.and.returnValue(Promise.resolve({ value: true })); - tokenService.setRefreshToken.withArgs(access_token_2).and.returnValue(Promise.resolve({ value: true })); - tokenService.getAccessToken.and.returnValue(Promise.resolve(access_token)); + storageService.delete.withArgs('user').and.resolveTo(null); + storageService.delete.withArgs('role').and.resolveTo(null); + tokenService.resetAccessToken.and.resolveTo({ value: true }); + tokenService.setRefreshToken.withArgs(access_token_2).and.resolveTo({ value: true }); + tokenService.getAccessToken.and.resolveTo(access_token); apiService.post.and.returnValue(of(apiAuthResponseRes)); - tokenService.setAccessToken - .withArgs(apiAuthResponseRes.access_token) - .and.returnValue(Promise.resolve({ value: true })); + tokenService.setAccessToken.withArgs(apiAuthResponseRes.access_token).and.resolveTo({ value: true }); spyOn(authService, 'refreshEou').and.returnValue(of(eouRes3)); authService.newRefreshToken(access_token_2).subscribe((res) => { diff --git a/src/app/core/services/config.service.spec.ts b/src/app/core/services/config.service.spec.ts index cdcf1464a1..b9717b40fb 100644 --- a/src/app/core/services/config.service.spec.ts +++ b/src/app/core/services/config.service.spec.ts @@ -40,14 +40,14 @@ describe('ConfigService', () => { describe('loadConfigurationData', () => { it('should call setClusterDomain if clusterDomain is present', async () => { const clusterDomain = 'https://staging.fyle.tech'; - tokenService.getClusterDomain.and.returnValue(Promise.resolve(clusterDomain)); + tokenService.getClusterDomain.and.resolveTo(clusterDomain); await configService.loadConfigurationData(); expect(routerAuthService.setClusterDomain).toHaveBeenCalledOnceWith(clusterDomain); expect(tokenService.getClusterDomain).toHaveBeenCalledTimes(1); }); it('should clear all stored data if clusterDomain is not present', async () => { - tokenService.getClusterDomain.and.returnValue(Promise.resolve(null)); + tokenService.getClusterDomain.and.resolveTo(null); await configService.loadConfigurationData(); expect(storageService.clearAll).toHaveBeenCalledTimes(1); expect(secureStorageService.clearAll).toHaveBeenCalledTimes(1); diff --git a/src/app/core/services/corporate-credit-card-expense.service.spec.ts b/src/app/core/services/corporate-credit-card-expense.service.spec.ts index b6b41c6128..dd07c8dfec 100644 --- a/src/app/core/services/corporate-credit-card-expense.service.spec.ts +++ b/src/app/core/services/corporate-credit-card-expense.service.spec.ts @@ -123,7 +123,7 @@ describe('CorporateCreditCardExpenseService', () => { it('getAssignedCards(): should get all assigned cards', (done) => { const queryParams = 'in.(COMPLETE,DRAFT)'; - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); apiV2Service.getStats.and.returnValue(of(new StatsResponse(apiAssignedCardDetailsRes))); spyOn(cccExpenseService, 'constructInQueryParamStringForV2').and.returnValue(queryParams); diff --git a/src/app/core/services/currency.service.spec.ts b/src/app/core/services/currency.service.spec.ts index a02537a1fb..cdae8ca576 100644 --- a/src/app/core/services/currency.service.spec.ts +++ b/src/app/core/services/currency.service.spec.ts @@ -48,7 +48,7 @@ describe('CurrencyService', () => { }); it('getAll(): should return all currencies', (done) => { - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); apiService.get.and.returnValue(of(apiAllCurrencies)); currencyService.getAll().subscribe((res) => { diff --git a/src/app/core/services/custom-inputs.service.spec.ts b/src/app/core/services/custom-inputs.service.spec.ts index 4086030ac1..541d7882dd 100644 --- a/src/app/core/services/custom-inputs.service.spec.ts +++ b/src/app/core/services/custom-inputs.service.spec.ts @@ -269,7 +269,7 @@ describe('CustomInputsService', () => { }); it('should fill dependent field properties', (done) => { - authService.getEou.and.returnValue(Promise.resolve(authRespone)); + authService.getEou.and.resolveTo(authRespone); spenderPlatformV1ApiService.get.and.returnValue(of(platformApiResponse)); const result = customInputsService.fillDependantFieldProperties(expensesWithDependentFields[0]); result.subscribe((res) => { @@ -279,7 +279,7 @@ describe('CustomInputsService', () => { }); it('should fill custom properties', (done) => { - authService.getEou.and.returnValue(Promise.resolve(authRespone)); + authService.getEou.and.resolveTo(authRespone); spenderPlatformV1ApiService.get.and.returnValue(of(platformApiResponse)); const result = customInputsService.fillCustomProperties(orgCatId, customProperties, false); result.subscribe((res) => { diff --git a/src/app/core/services/google-auth.service.spec.ts b/src/app/core/services/google-auth.service.spec.ts index 1a58aff4fc..2c1b6de2b6 100644 --- a/src/app/core/services/google-auth.service.spec.ts +++ b/src/app/core/services/google-auth.service.spec.ts @@ -29,8 +29,8 @@ describe('GoogleAuthService', () => { describe('login():', () => { it('should login in a user via goolge', (done) => { - googlePlus.login.and.returnValue(Promise.resolve(apiAuthRes)); - googlePlus.logout.and.returnValue(Promise.resolve(apiLogoutRes)); + googlePlus.login.and.resolveTo(apiAuthRes); + googlePlus.logout.and.resolveTo(apiLogoutRes); googleAuthService.login().then((res) => { expect(res).toEqual(apiAuthRes); @@ -44,7 +44,7 @@ describe('GoogleAuthService', () => { }); it('should throw an error', (done) => { - googlePlus.login.and.returnValue(Promise.reject(new Error())); + googlePlus.login.and.rejectWith(new Error()); googleAuthService.login().then((res) => { expect(res).toEqual(new Error()); diff --git a/src/app/core/services/launch-darkly.service.spec.ts b/src/app/core/services/launch-darkly.service.spec.ts index 3d378847e6..9a8b9eefad 100644 --- a/src/app/core/services/launch-darkly.service.spec.ts +++ b/src/app/core/services/launch-darkly.service.spec.ts @@ -46,7 +46,7 @@ describe('LaunchDarklyService', () => { describe('getVariation():', () => { it('should get variation', (done) => { const key = 'keyboard_plugin_enabled'; - storageService.get.and.returnValue(Promise.resolve(ldAllFlagsRes)); + storageService.get.and.resolveTo(ldAllFlagsRes); launchDarklyService.getVariation(key, true).subscribe((res) => { expect(res).toBeTrue(); @@ -56,7 +56,7 @@ describe('LaunchDarklyService', () => { it('should return default value when flags are not available', (done) => { const key = 'keyboard_plugin_enabled'; - storageService.get.and.returnValue(Promise.resolve(null)); + storageService.get.and.resolveTo(null); launchDarklyService.getVariation(key, true).subscribe((res) => { expect(res).toBeTrue(); diff --git a/src/app/core/services/loader.service.spec.ts b/src/app/core/services/loader.service.spec.ts index 72c89121a5..3b2ed63a5b 100644 --- a/src/app/core/services/loader.service.spec.ts +++ b/src/app/core/services/loader.service.spec.ts @@ -29,7 +29,7 @@ describe('LoaderService', () => { const message = 'Please wait...'; const duration = 1000; const loadingElementSpy = jasmine.createSpyObj('HTMLIonLoadingElement', ['present']); - loadingController.create.and.returnValue(Promise.resolve(loadingElementSpy)); + loadingController.create.and.resolveTo(loadingElementSpy); loaderService.showLoader(message, duration); tick(); @@ -42,7 +42,7 @@ describe('LoaderService', () => { it('showLoader(): should show loader without args', fakeAsync(() => { const loadingElementSpy = jasmine.createSpyObj('HTMLIonLoadingElement', ['present']); - loadingController.create.and.returnValue(Promise.resolve(loadingElementSpy)); + loadingController.create.and.resolveTo(loadingElementSpy); loaderService.showLoader(); tick(); @@ -53,7 +53,7 @@ describe('LoaderService', () => { it('hideLoader(): should hide loader', fakeAsync(() => { const loadingElementSpy = jasmine.createSpyObj('HTMLIonLoadingElement', ['dismiss']); - loadingController.dismiss.and.returnValue(Promise.resolve(loadingElementSpy)); + loadingController.dismiss.and.resolveTo(loadingElementSpy); loaderService.hideLoader(); tick(); @@ -62,7 +62,7 @@ describe('LoaderService', () => { it('hideLoader(): should catch errors in hide loader', fakeAsync(() => { const error = 'Something went wrong'; - loadingController.dismiss.and.returnValue(Promise.reject(error)); + loadingController.dismiss.and.rejectWith(error); loaderService.hideLoader(); tick(); diff --git a/src/app/core/services/login-info.service.spec.ts b/src/app/core/services/login-info.service.spec.ts index 5d5d85f8ed..e65c1213a8 100644 --- a/src/app/core/services/login-info.service.spec.ts +++ b/src/app/core/services/login-info.service.spec.ts @@ -35,7 +35,7 @@ describe('LoginInfoService', () => { describe('addLoginInfo():', () => { it('should add login info', async () => { - storageService.get.and.returnValue(Promise.resolve(null)); + storageService.get.and.resolveTo(null); storageService.set.and.callThrough(); loginInfoService.addLoginInfo(version, time); @@ -51,7 +51,7 @@ describe('LoginInfoService', () => { lastLoggedInTime: 'Fri, 17 Mar 2023 12:24:26 GMT', }; - storageService.get.and.returnValue(Promise.resolve(newLoginInfo)); + storageService.get.and.resolveTo(newLoginInfo); storageService.set.and.callThrough(); loginInfoService.addLoginInfo(version, newTime); @@ -62,7 +62,7 @@ describe('LoginInfoService', () => { describe('getLastLoggedInVersion():', () => { it('should get last logged in version', (done) => { - storageService.get.and.returnValue(Promise.resolve(null)); + storageService.get.and.resolveTo(null); loginInfoService.getLastLoggedInVersion().subscribe((res) => { expect(res).toBeUndefined(); @@ -72,7 +72,7 @@ describe('LoginInfoService', () => { }); it('should return null if last logged in version not present', (done) => { - storageService.get.and.returnValue(Promise.resolve(loginInfo)); + storageService.get.and.resolveTo(loginInfo); loginInfoService.getLastLoggedInVersion().subscribe((res) => { expect(res).toEqual(loginInfo.lastLoggedInVersion); diff --git a/src/app/core/services/org-user.service.spec.ts b/src/app/core/services/org-user.service.spec.ts index 255fed61d7..a983489fce 100644 --- a/src/app/core/services/org-user.service.spec.ts +++ b/src/app/core/services/org-user.service.spec.ts @@ -224,10 +224,10 @@ describe('OrgUserService', () => { // This token contains the user details such as user id, org id, org user id, roles, scopes, etc. const token = 'eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NzI5MTcyMDAsImlzcyI6IkZ5bGVBcHAiLCJ1c2VyX2lkIjoidXNNakxpYm15ZTdzIiwib3JnX3VzZXJfaWQiOiJvdXJ3N0hpNG1tcE8iLCJvcmdfaWQiOiJvck5WdGhUbzJaeW8iLCJyb2xlcyI6IltcIkZZTEVSXCIsXCJGSU5BTkNFXCIsXCJBRE1JTlwiLFwiQVBQUk9WRVJcIixcIlZFUklGSUVSXCIsXCJQQVlNRU5UX1BST0NFU1NPUlwiLFwiSE9QXCJdIiwic2NvcGVzIjoiW10iLCJhbGxvd2VkX0NJRFJzIjoiW10iLCJ2ZXJzaW9uIjoiMyIsImNsdXN0ZXJfZG9tYWluIjoiXCJodHRwczovL3N0YWdpbmcuZnlsZS50ZWNoXCIiLCJleHAiOjE2NzI5MjA4MDB9.hTMJ56cPH_HgKhZSKNCOIEGAzaAXCfIgbEYcaudhXwk'; - tokenService.getAccessToken.and.returnValue(Promise.resolve(token)); + tokenService.getAccessToken.and.resolveTo(token); orgUserService.isSwitchedToDelegator().then((res) => { - expect(res).toEqual(false); + expect(res).toBeFalse(); done(); }); }); @@ -237,10 +237,10 @@ describe('OrgUserService', () => { // This token contains the user details such as user id, org id, org user id, roles, scopes, etc. const token = 'eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NzI5MTcxNTgsImlzcyI6IkZ5bGVBcHAiLCJ1c2VyX2lkIjoidXNCa0pEMVVtMTc0Iiwib3JnX3VzZXJfaWQiOiJvdTVxclBKYkdmV00iLCJvcmdfaWQiOiJvck5WdGhUbzJaeW8iLCJyb2xlcyI6IltcIkZZTEVSXCIsXCJWRVJJRklFUlwiXSIsInNjb3BlcyI6IltdIiwicHJveHlfb3JnX3VzZXJfaWQiOiJvdXJ3N0hpNG1tcE8iLCJhbGxvd2VkX0NJRFJzIjoiW10iLCJ2ZXJzaW9uIjoiMyIsImNsdXN0ZXJfZG9tYWluIjoiXCJodHRwczovL3N0YWdpbmcuZnlsZS50ZWNoXCIiLCJleHAiOjE2NzI5MjA3NTh9.VqpiTmEd_Kp-fK11gBV-VfjEkPhCja-diu-TGDGPeKA'; - tokenService.getAccessToken.and.returnValue(Promise.resolve(token)); + tokenService.getAccessToken.and.resolveTo(token); orgUserService.isSwitchedToDelegator().then((res) => { - expect(res).toEqual(true); + expect(res).toBeTrue(); done(); }); }); diff --git a/src/app/core/services/refiner.service.spec.ts b/src/app/core/services/refiner.service.spec.ts index 8f6b99c709..c3dd41a675 100644 --- a/src/app/core/services/refiner.service.spec.ts +++ b/src/app/core/services/refiner.service.spec.ts @@ -75,7 +75,7 @@ describe('RefinerService', () => { it('should return true for non-demo orgs and when not switched to delegator', (done) => { spyOn(refinerService, 'isNonDemoOrg').and.returnValue(true); const switchedToDelegator = false; - orgUserService.isSwitchedToDelegator.and.returnValue(Promise.resolve(switchedToDelegator)); + orgUserService.isSwitchedToDelegator.and.resolveTo(switchedToDelegator); const homeCurrency = 'INR'; const eou = apiEouRes; refinerService.canStartSurvey(homeCurrency, eou).subscribe((res) => { @@ -96,7 +96,7 @@ describe('RefinerService', () => { }; spyOn(refinerService, 'isNonDemoOrg').and.returnValue(false); const switchedToDelegator = true; - orgUserService.isSwitchedToDelegator.and.returnValue(Promise.resolve(switchedToDelegator)); + orgUserService.isSwitchedToDelegator.and.resolveTo(switchedToDelegator); const homeCurrency = 'INR'; const eou = demoOrgRes; refinerService.canStartSurvey(homeCurrency, eou).subscribe((res) => { diff --git a/src/app/core/services/router-auth.service.spec.ts b/src/app/core/services/router-auth.service.spec.ts index 6c2b084070..51939ce897 100644 --- a/src/app/core/services/router-auth.service.spec.ts +++ b/src/app/core/services/router-auth.service.spec.ts @@ -174,8 +174,8 @@ describe('RouterAuthService', () => { }); it('isLoggedIn(): should check if user is logged in', (done) => { - tokenService.getAccessToken.and.returnValue(Promise.resolve(access_token)); - tokenService.getRefreshToken.and.returnValue(Promise.resolve(refresh_token)); + tokenService.getAccessToken.and.resolveTo(access_token); + tokenService.getRefreshToken.and.resolveTo(refresh_token); routerAuthService.isLoggedIn().then((res) => { expect(res).toBeTrue(); @@ -186,7 +186,7 @@ describe('RouterAuthService', () => { }); it('newRefreshToken(): should set new refresh token', async () => { - tokenService.setRefreshToken.and.returnValue(Promise.resolve(null)); + tokenService.setRefreshToken.and.resolveTo(null); const deleteUser = await storageService.delete('user'); const deleteRole = await storageService.delete('role'); @@ -198,7 +198,7 @@ describe('RouterAuthService', () => { }); it('newAccessToken(): should set new access token', fakeAsync(() => { - tokenService.setAccessToken.and.returnValue(Promise.resolve(null)); + tokenService.setAccessToken.and.resolveTo(null); tick(); routerAuthService.newAccessToken(access_token).then((res) => { @@ -211,7 +211,7 @@ describe('RouterAuthService', () => { it('fetchAccessToken(): should fetch access token', fakeAsync(() => { routerApiService.post.and.returnValue(of(apiAuthRes)); - tokenService.getAccessToken.and.returnValue(Promise.resolve(access_token)); + tokenService.getAccessToken.and.resolveTo(access_token); tick(); @@ -227,7 +227,7 @@ describe('RouterAuthService', () => { it('handleSignInResponse(): should handle sign in response', (done) => { spyOn(routerAuthService, 'newRefreshToken').and.callThrough(); spyOn(routerAuthService, 'setClusterDomain').and.callThrough(); - spyOn(routerAuthService, 'fetchAccessToken').and.returnValue(Promise.resolve(apiAuthRes)); + spyOn(routerAuthService, 'fetchAccessToken').and.resolveTo(apiAuthRes); spyOn(routerAuthService, 'newAccessToken').and.callThrough(); routerAuthService.handleSignInResponse(authResData1).then((res) => { @@ -258,7 +258,7 @@ describe('RouterAuthService', () => { it('basicSignin(): should sign in the user with email and password', (done) => { routerApiService.post.and.returnValue(of(authResData1)); - spyOn(routerAuthService, 'handleSignInResponse').and.returnValue(Promise.resolve(authResData1)); + spyOn(routerAuthService, 'handleSignInResponse').and.resolveTo(authResData1); const password = 'KalaChashma'; routerAuthService.basicSignin(email, password).subscribe((res) => { @@ -274,7 +274,7 @@ describe('RouterAuthService', () => { it('googleSignin(): should sign in the user with google', (done) => { routerApiService.post.and.returnValue(of(authResData1)); - spyOn(routerAuthService, 'handleSignInResponse').and.returnValue(Promise.resolve(authResData1)); + spyOn(routerAuthService, 'handleSignInResponse').and.resolveTo(authResData1); routerAuthService.googleSignin(access_token).subscribe((res) => { expect(res).toEqual(authResData1); @@ -288,7 +288,7 @@ describe('RouterAuthService', () => { it('resetPassword(): should reset user passord', (done) => { routerApiService.post.and.returnValue(of(authResData1)); - spyOn(routerAuthService, 'handleSignInResponse').and.returnValue(Promise.resolve(authResData1)); + spyOn(routerAuthService, 'handleSignInResponse').and.resolveTo(authResData1); const newPassword = 'New_Password'; @@ -305,7 +305,7 @@ describe('RouterAuthService', () => { it('emailVerify(): should verify email', (done) => { routerApiService.post.and.returnValue(of(authResData1)); - spyOn(routerAuthService, 'handleSignInResponse').and.returnValue(Promise.resolve(authResData1)); + spyOn(routerAuthService, 'handleSignInResponse').and.resolveTo(authResData1); const verification_code = 'orNVthTo2Zyo'; diff --git a/src/app/core/services/storage.service.spec.ts b/src/app/core/services/storage.service.spec.ts index 09cf0e08e3..c52336b3bc 100644 --- a/src/app/core/services/storage.service.spec.ts +++ b/src/app/core/services/storage.service.spec.ts @@ -21,7 +21,7 @@ describe('StorageService', () => { it('should set a key-value pair', async () => { const key = 'etxncCount'; const value = 150; - spyOn(Preferences, 'set').and.returnValue(Promise.resolve()); + spyOn(Preferences, 'set').and.resolveTo(); await storageService.set(key, value); @@ -39,10 +39,10 @@ describe('StorageService', () => { const key = 'isFirstReportCreated'; const value = true; // Setting the value to fetch it later - spyOn(Preferences, 'set').and.returnValue(Promise.resolve()); + spyOn(Preferences, 'set').and.resolveTo(); await storageService.set(key, value); - spyOn(Preferences, 'get').and.returnValue(Promise.resolve({ value: JSON.stringify(value) })); + spyOn(Preferences, 'get').and.resolveTo({ value: JSON.stringify(value) }); const result = await storageService.get(key); @@ -64,7 +64,7 @@ describe('StorageService', () => { describe('delete():', () => { it('should delete a value for a given key', async () => { const key = 'user'; - spyOn(Preferences, 'remove').and.returnValue(Promise.resolve()); + spyOn(Preferences, 'remove').and.resolveTo(); await storageService.delete(key); @@ -75,7 +75,7 @@ describe('StorageService', () => { describe('clearAll():', () => { it('should clear all key-value pairs', async () => { - spyOn(Preferences, 'clear').and.returnValue(Promise.resolve()); + spyOn(Preferences, 'clear').and.resolveTo(); await storageService.clearAll(); diff --git a/src/app/core/services/tasks.service.spec.ts b/src/app/core/services/tasks.service.spec.ts index c01832b8bc..95bff9679f 100644 --- a/src/app/core/services/tasks.service.spec.ts +++ b/src/app/core/services/tasks.service.spec.ts @@ -1016,7 +1016,7 @@ describe('TasksService', () => { describe('getMobileNumberVerificationTasks(): ', () => { it('should not return any task if user has verified mobile number', (done) => { - authService.getEou.and.returnValue(Promise.resolve(extendedOrgUserResponse)); + authService.getEou.and.resolveTo(extendedOrgUserResponse); corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([mastercardRTFCard])); const mapMobileNumberVerificationTaskSpy = spyOn(tasksService, 'mapMobileNumberVerificationTask'); tasksService.getMobileNumberVerificationTasks().subscribe((res) => { @@ -1031,7 +1031,7 @@ describe('TasksService', () => { it('should not return any task if user has not enrolled for RTF', (done) => { const eou = cloneDeep(extendedOrgUserResponse); eou.ou.mobile_verified = false; - authService.getEou.and.returnValue(Promise.resolve(eou)); + authService.getEou.and.resolveTo(eou); corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([])); const mapMobileNumberVerificationTaskSpy = spyOn(tasksService, 'mapMobileNumberVerificationTask'); tasksService.getMobileNumberVerificationTasks().subscribe((res) => { @@ -1047,7 +1047,7 @@ describe('TasksService', () => { const eou = cloneDeep(extendedOrgUserResponse); eou.ou.mobile_verified = false; eou.ou.mobile = null; - authService.getEou.and.returnValue(Promise.resolve(eou)); + authService.getEou.and.resolveTo(eou); corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([mastercardRTFCard])); const mapMobileNumberVerificationTaskSpy = spyOn(tasksService, 'mapMobileNumberVerificationTask').and.returnValue( [addMobileNumberTask] @@ -1063,7 +1063,7 @@ describe('TasksService', () => { it('should return verify number task if user has verified mobile number', (done) => { const eou = cloneDeep(extendedOrgUserResponse); eou.ou.mobile_verified = false; - authService.getEou.and.returnValue(Promise.resolve(eou)); + authService.getEou.and.resolveTo(eou); corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([mastercardRTFCard])); const mapMobileNumberVerificationTaskSpy = spyOn(tasksService, 'mapMobileNumberVerificationTask').and.returnValue( [addMobileNumberTask] @@ -1080,7 +1080,7 @@ describe('TasksService', () => { describe('getCommuteDetailsTasks():', () => { it('should return commute details task if commute details response data is not defined', (done) => { employeesService.getCommuteDetails.and.returnValue(of(commuteDetailsResponseData2)); - authService.getEou.and.returnValue(Promise.resolve(extendedOrgUserResponse)); + authService.getEou.and.resolveTo(extendedOrgUserResponse); orgSettingsService.get.and.returnValue(of(orgSettingsWithCommuteDeductionsEnabled)); tasksService.getCommuteDetailsTasks().subscribe((res) => { @@ -1094,7 +1094,7 @@ describe('TasksService', () => { it('should return commute details task if home location is not present', (done) => { employeesService.getCommuteDetails.and.returnValue(of(commuteDetailsResponseData3)); - authService.getEou.and.returnValue(Promise.resolve(extendedOrgUserResponse)); + authService.getEou.and.resolveTo(extendedOrgUserResponse); orgSettingsService.get.and.returnValue(of(orgSettingsWithCommuteDeductionsEnabled)); tasksService.getCommuteDetailsTasks().subscribe((res) => { @@ -1108,7 +1108,7 @@ describe('TasksService', () => { it('should not return commute details task if mileage is disabled for org', (done) => { employeesService.getCommuteDetails.and.returnValue(of(commuteDetailsResponseData3)); - authService.getEou.and.returnValue(Promise.resolve(extendedOrgUserResponse)); + authService.getEou.and.resolveTo(extendedOrgUserResponse); orgSettingsService.get.and.returnValue(of(orgSettingsWoMileage)); tasksService.getCommuteDetailsTasks().subscribe((res) => { @@ -1122,7 +1122,7 @@ describe('TasksService', () => { it('should not return commute details task if commute deduction settings is disabled for org', (done) => { employeesService.getCommuteDetails.and.returnValue(of(commuteDetailsResponseData3)); - authService.getEou.and.returnValue(Promise.resolve(extendedOrgUserResponse)); + authService.getEou.and.resolveTo(extendedOrgUserResponse); orgSettingsService.get.and.returnValue(of(orgSettingsRes)); tasksService.getCommuteDetailsTasks().subscribe((res) => { @@ -1136,7 +1136,7 @@ describe('TasksService', () => { it('should not return commute details task if home location is present in commute details', (done) => { employeesService.getCommuteDetails.and.returnValue(of(commuteDetailsResponseData)); - authService.getEou.and.returnValue(Promise.resolve(extendedOrgUserResponse)); + authService.getEou.and.resolveTo(extendedOrgUserResponse); orgSettingsService.get.and.returnValue(of(orgSettingsWithCommuteDeductionsDisabled)); tasksService.getCommuteDetailsTasks().subscribe((res) => { diff --git a/src/app/core/services/token.service.spec.ts b/src/app/core/services/token.service.spec.ts index 350b22d1e2..20d33d304b 100644 --- a/src/app/core/services/token.service.spec.ts +++ b/src/app/core/services/token.service.spec.ts @@ -30,7 +30,7 @@ describe('TokenService', () => { }); it('getAccessToken(): should get access token from secure storage', fakeAsync(() => { - secureStorageService.get.and.returnValue(Promise.resolve('token')); + secureStorageService.get.and.resolveTo('token'); tick(); tokenService.getAccessToken().then((token) => { expect(token).toEqual('token'); @@ -40,7 +40,7 @@ describe('TokenService', () => { it('setAccessToken(): should set access token to secure storage', fakeAsync(() => { userEventService.setToken.and.returnValue(null); - secureStorageService.set.and.returnValue(Promise.resolve({ value: true })); + secureStorageService.set.and.resolveTo({ value: true }); tick(); tokenService.setAccessToken('token').then(() => { expect(secureStorageService.set).toHaveBeenCalledOnceWith('X-AUTH-TOKEN', 'token'); @@ -48,7 +48,7 @@ describe('TokenService', () => { })); it('resetAccessToken(): should reset access token from secure storage', fakeAsync(() => { - secureStorageService.delete.and.returnValue(Promise.resolve({ value: true })); + secureStorageService.delete.and.resolveTo({ value: true }); tick(); tokenService.resetAccessToken().then(() => { expect(secureStorageService.delete).toHaveBeenCalledOnceWith('X-AUTH-TOKEN'); @@ -56,7 +56,7 @@ describe('TokenService', () => { })); it('getRefreshToken(): should get refresh token from secure storage', fakeAsync(() => { - secureStorageService.get.and.returnValue(Promise.resolve('token')); + secureStorageService.get.and.resolveTo('token'); tick(); tokenService.getRefreshToken().then((token) => { expect(token).toEqual('token'); @@ -65,7 +65,7 @@ describe('TokenService', () => { })); it('setRefreshToken(): should set refresh token to secure storage', fakeAsync(() => { - secureStorageService.set.and.returnValue(Promise.resolve({ value: true })); + secureStorageService.set.and.resolveTo({ value: true }); tick(); tokenService.setRefreshToken('token').then(() => { expect(secureStorageService.set).toHaveBeenCalledOnceWith('X-REFRESH-TOKEN', 'token'); @@ -73,7 +73,7 @@ describe('TokenService', () => { })); it('resetRefreshToken(): should reset refresh token from secure storage', fakeAsync(() => { - secureStorageService.delete.and.returnValue(Promise.resolve({ value: true })); + secureStorageService.delete.and.resolveTo({ value: true }); tick(); tokenService.resetRefreshToken().then(() => { expect(secureStorageService.delete).toHaveBeenCalledOnceWith('X-REFRESH-TOKEN'); @@ -81,7 +81,7 @@ describe('TokenService', () => { })); it('getClusterDomain(): should get cluster domain from secure storage', fakeAsync(() => { - secureStorageService.get.and.returnValue(Promise.resolve('domain')); + secureStorageService.get.and.resolveTo('domain'); tick(); tokenService.getClusterDomain().then((domain) => { expect(domain).toEqual('domain'); @@ -90,7 +90,7 @@ describe('TokenService', () => { })); it('setClusterDomain(): should set cluster domain to secure storage', fakeAsync(() => { - secureStorageService.set.and.returnValue(Promise.resolve({ value: true })); + secureStorageService.set.and.resolveTo({ value: true }); tick(); tokenService.setClusterDomain('domain').then(() => { expect(secureStorageService.set).toHaveBeenCalledOnceWith('CLUSTER-DOMAIN', 'domain'); @@ -98,7 +98,7 @@ describe('TokenService', () => { })); it('resetClusterDomain(): should reset cluster domain from secure storage', fakeAsync(() => { - secureStorageService.delete.and.returnValue(Promise.resolve({ value: true })); + secureStorageService.delete.and.resolveTo({ value: true }); tick(); tokenService.resetClusterDomain().then(() => { expect(secureStorageService.delete).toHaveBeenCalledOnceWith('CLUSTER-DOMAIN'); diff --git a/src/app/deep-link-redirection/deep-link-redirection.page.spec.ts b/src/app/deep-link-redirection/deep-link-redirection.page.spec.ts index 6fa18958b8..8af983b1c7 100644 --- a/src/app/deep-link-redirection/deep-link-redirection.page.spec.ts +++ b/src/app/deep-link-redirection/deep-link-redirection.page.spec.ts @@ -77,7 +77,7 @@ describe('DeepLinkRedirectionPage', () => { activeroutemock = TestBed.inject(ActivatedRoute); reportService.getERpt.and.returnValue(of(expectedSingleErpt)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); fixture = TestBed.createComponent(DeepLinkRedirectionPage); component = fixture.componentInstance; fixture.detectChanges(); @@ -122,7 +122,7 @@ describe('DeepLinkRedirectionPage', () => { }, }; - authService.getEou.and.returnValue(Promise.resolve(updatedApiEouRes)); + authService.getEou.and.resolveTo(updatedApiEouRes); const updatedErpt = { ...expectedSingleErpt, @@ -159,7 +159,7 @@ describe('DeepLinkRedirectionPage', () => { spyOn(component, 'switchOrg'); const error = 'Something went wrong'; - authService.getEou.and.returnValue(Promise.resolve(updatedApiEouRes)); + authService.getEou.and.resolveTo(updatedApiEouRes); reportService.getERpt.and.returnValue(throwError(() => error)); component.redirectToReportModule(); fixture.detectChanges(); @@ -273,7 +273,7 @@ describe('DeepLinkRedirectionPage', () => { }, }; - authService.getEou.and.returnValue(Promise.resolve(updatedApiEouRes)); + authService.getEou.and.resolveTo(updatedApiEouRes); activeroutemock.snapshot.params = { sub_module: 'advReq', @@ -299,7 +299,7 @@ describe('DeepLinkRedirectionPage', () => { })); it('should redirect to my_view_advance_request page if non of the conditions match', fakeAsync(() => { - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); activeroutemock.snapshot.params = { sub_module: 'advReq', diff --git a/src/app/fyle/add-edit-per-diem/add-edit-per-diem-5.page.spec.ts b/src/app/fyle/add-edit-per-diem/add-edit-per-diem-5.page.spec.ts index c6484db6d6..67a479e839 100644 --- a/src/app/fyle/add-edit-per-diem/add-edit-per-diem-5.page.spec.ts +++ b/src/app/fyle/add-edit-per-diem/add-edit-per-diem-5.page.spec.ts @@ -685,7 +685,7 @@ export function TestCases5(getTestBed) { from_dt: '2023-06-27', }); const tomorrow = new Date('2023-06-15'); - tomorrow.setDate(tomorrow.getDate() + 2); + tomorrow.setDate(tomorrow.getDate() + 3); const control = new FormControl(tomorrow.toDateString()); const result = component.customDateValidator(control); expect(result).toEqual({ invalidDateSelection: true }); diff --git a/src/app/fyle/split-expense/split-expense.page.spec.ts b/src/app/fyle/split-expense/split-expense.page.spec.ts index 97067e7893..c70962cc5c 100644 --- a/src/app/fyle/split-expense/split-expense.page.spec.ts +++ b/src/app/fyle/split-expense/split-expense.page.spec.ts @@ -1043,7 +1043,7 @@ describe('SplitExpensePage', () => { it('should return an error object when the date is after the upper bound of the valid range', () => { const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 2); + tomorrow.setDate(tomorrow.getDate() + 3); const control = new FormControl(tomorrow.toISOString().substring(0, 10)); const result = component.customDateValidator(control); expect(result).toEqual({ invalidDateSelection: true }); diff --git a/src/app/shared/components/fy-view-attachment/fy-view-attachment.component.spec.ts b/src/app/shared/components/fy-view-attachment/fy-view-attachment.component.spec.ts index 821afcbf34..5ad12b5ea5 100644 --- a/src/app/shared/components/fy-view-attachment/fy-view-attachment.component.spec.ts +++ b/src/app/shared/components/fy-view-attachment/fy-view-attachment.component.spec.ts @@ -85,8 +85,8 @@ describe('FyViewAttachmentComponent', () => { url: 'http://example.com/attachment3.pdf', }, ]; - loaderService.hideLoader.and.returnValue(Promise.resolve()); - loaderService.showLoader.and.returnValue(Promise.resolve()); + loaderService.hideLoader.and.resolveTo(); + loaderService.showLoader.and.resolveTo(); component.attachments = mockAttachments; fixture.detectChanges(); From 8bb06e0e54bb1934553a5ac13a0307e83106143f Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Tue, 14 May 2024 12:29:08 +0530 Subject: [PATCH 006/262] feat: use generateUrlsBulk() in merge expenses (#2974) --- .../generate-urls-bulk-response.data.ts | 12 +++++ src/app/core/mock-data/receipt-info.data.ts | 16 +++++++ src/app/core/models/receipt-info.model.ts | 1 + .../services/merge-expenses.service.spec.ts | 46 +++++++++++++++---- .../core/services/merge-expenses.service.ts | 43 +++++++++++------ 5 files changed, 94 insertions(+), 24 deletions(-) create mode 100644 src/app/core/mock-data/generate-urls-bulk-response.data.ts create mode 100644 src/app/core/mock-data/receipt-info.data.ts diff --git a/src/app/core/mock-data/generate-urls-bulk-response.data.ts b/src/app/core/mock-data/generate-urls-bulk-response.data.ts new file mode 100644 index 0000000000..bc80fdd47f --- /dev/null +++ b/src/app/core/mock-data/generate-urls-bulk-response.data.ts @@ -0,0 +1,12 @@ +import deepFreeze from 'deep-freeze-strict'; +import { PlatformFileGenerateUrlsResponse } from '../models/platform/platform-file-generate-urls-response.model'; + +export const generateUrlsBulkData1: PlatformFileGenerateUrlsResponse[] = deepFreeze([ + { + name: 'invoice.pdf', + id: '1', + content_type: 'application/pdf', + download_url: 'https://sampledownloadurl.com', + upload_url: 'https://sampleuploadurl.com', + }, +]); diff --git a/src/app/core/mock-data/receipt-info.data.ts b/src/app/core/mock-data/receipt-info.data.ts new file mode 100644 index 0000000000..2b9cc3146d --- /dev/null +++ b/src/app/core/mock-data/receipt-info.data.ts @@ -0,0 +1,16 @@ +import deepFreeze from 'deep-freeze-strict'; +import { ReceiptInfo } from '../models/receipt-info.model'; + +export const receiptInfoData1: ReceiptInfo = deepFreeze({ + type: 'pdf', + url: 'https://sampledownloadurl.com', + thumbnail: 'img/fy-pdf.svg', +}); + +export const receiptInfoData2: ReceiptInfo[] = deepFreeze([ + { + type: 'pdf', + url: 'https://sampledownloadurl.com', + thumbnail: 'img/fy-pdf.svg', + }, +]); diff --git a/src/app/core/models/receipt-info.model.ts b/src/app/core/models/receipt-info.model.ts index 5af26ad757..d85bcc413b 100644 --- a/src/app/core/models/receipt-info.model.ts +++ b/src/app/core/models/receipt-info.model.ts @@ -1,4 +1,5 @@ export interface ReceiptInfo { type: string; thumbnail: string; + url?: string; } diff --git a/src/app/core/services/merge-expenses.service.spec.ts b/src/app/core/services/merge-expenses.service.spec.ts index 13d5817c25..ee997d884f 100644 --- a/src/app/core/services/merge-expenses.service.spec.ts +++ b/src/app/core/services/merge-expenses.service.spec.ts @@ -97,6 +97,11 @@ import * as dayjs from 'dayjs'; import { expectedOrgCategoryByName2, orgCategoryData1 } from '../mock-data/org-category.data'; import { taxGroupData } from '../mock-data/tax-group.data'; import { cloneDeep } from 'lodash'; +import { ExpensesService } from './platform/v1/spender/expenses.service'; +import { SpenderFileService } from './platform/v1/spender/file.service'; +import { platformExpenseData, platformExpenseWithExtractedData } from '../mock-data/platform/v1/expense.data'; +import { receiptInfoData2 } from '../mock-data/receipt-info.data'; +import { generateUrlsBulkData1 } from '../mock-data/generate-urls-bulk-response.data'; describe('MergeExpensesService', () => { let mergeExpensesService: MergeExpensesService; @@ -109,6 +114,8 @@ describe('MergeExpensesService', () => { let categoriesService: jasmine.SpyObj; let dateService: jasmine.SpyObj; let taxGroupService: jasmine.SpyObj; + let expensesService: jasmine.SpyObj; + let spenderFileService: jasmine.SpyObj; beforeEach(() => { const apiServiceSpy = jasmine.createSpyObj('ApiService', ['post']); @@ -126,6 +133,8 @@ describe('MergeExpensesService', () => { const categoriesServiceSpy = jasmine.createSpyObj('CategoriesService', ['getAll', 'filterRequired']); const dateServiceSpy = jasmine.createSpyObj('DateService', ['isValidDate']); const taxGroupServiceSpy = jasmine.createSpyObj('TaxGroupService', ['get']); + const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById']); + const spenderFileServiceSpy = jasmine.createSpyObj('SpenderFileService', ['generateUrlsBulk']); TestBed.configureTestingModule({ providers: [ @@ -139,6 +148,8 @@ describe('MergeExpensesService', () => { { provide: CategoriesService, useValue: categoriesServiceSpy }, { provide: DateService, useValue: dateServiceSpy }, { provide: TaxGroupService, useValue: taxGroupServiceSpy }, + { provide: ExpensesService, useValue: expensesServiceSpy }, + { provide: SpenderFileService, useValue: spenderFileServiceSpy }, ], }); mergeExpensesService = TestBed.inject(MergeExpensesService); @@ -153,6 +164,8 @@ describe('MergeExpensesService', () => { categoriesService = TestBed.inject(CategoriesService) as jasmine.SpyObj; dateService = TestBed.inject(DateService) as jasmine.SpyObj; taxGroupService = TestBed.inject(TaxGroupService) as jasmine.SpyObj; + expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; + spenderFileService = TestBed.inject(SpenderFileService) as jasmine.SpyObj; }); it('should be created', () => { @@ -372,20 +385,35 @@ describe('MergeExpensesService', () => { }); it('getAttachements(): should return the attachments', (done) => { - const mockFileObject = cloneDeep(fileObject5); - fileService.findByTransactionId.and.returnValue(of(mockFileObject)); - fileService.downloadUrl.and.returnValue(of('mock-url')); + expensesService.getExpenseById.and.returnValue(of(platformExpenseWithExtractedData)); + spenderFileService.generateUrlsBulk.and.returnValue(of(generateUrlsBulkData1)); fileService.getReceiptsDetails.and.returnValue({ - thumbnail: mockFileObject[0].thumbnail, - type: mockFileObject[0].type, + type: 'pdf', + thumbnail: 'img/fy-pdf.svg', }); const transactionId = 'txz2vohKxBXu'; mergeExpensesService.getAttachements(transactionId).subscribe((res) => { - expect(res).toEqual(mockFileObject); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(transactionId); - expect(fileService.downloadUrl).toHaveBeenCalledOnceWith(mockFileObject[0].id); - expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith(mockFileObject[0].name, 'mock-url'); + expect(res).toEqual(receiptInfoData2); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(transactionId); + expect(spenderFileService.generateUrlsBulk).toHaveBeenCalledOnceWith(platformExpenseWithExtractedData.file_ids); + expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith( + generateUrlsBulkData1[0].name, + generateUrlsBulkData1[0].download_url + ); + done(); + }); + }); + + it('getAttachements(): should return empty array when there are no attachments', (done) => { + expensesService.getExpenseById.and.returnValue(of(platformExpenseData)); + + const transactionId = 'txz2vohKxBXu'; + mergeExpensesService.getAttachements(transactionId).subscribe((res) => { + expect(res).toEqual([]); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(transactionId); + expect(spenderFileService.generateUrlsBulk).not.toHaveBeenCalled(); + expect(fileService.getReceiptsDetails).not.toHaveBeenCalled(); done(); }); }); diff --git a/src/app/core/services/merge-expenses.service.ts b/src/app/core/services/merge-expenses.service.ts index c30e2e8225..390b1adb60 100644 --- a/src/app/core/services/merge-expenses.service.ts +++ b/src/app/core/services/merge-expenses.service.ts @@ -1,10 +1,12 @@ import { Injectable } from '@angular/core'; import { from, Observable, of } from 'rxjs'; -import { concatMap, filter, map, mergeMap, reduce, shareReplay, switchMap } from 'rxjs/operators'; +import { filter, map, mergeMap, reduce, shareReplay, switchMap } from 'rxjs/operators'; import { ApiService } from './api.service'; import { Expense } from '../models/expense.model'; +import { Expense as PlatformExpense } from '../models/platform/v1/expense.model'; import { ExpensesInfo } from './expenses-info.model'; import { FileService } from './file.service'; +import { SpenderFileService } from './platform/v1/spender/file.service'; import { CorporateCreditCardExpenseService } from './corporate-credit-card-expense.service'; import * as dayjs from 'dayjs'; import { HumanizeCurrencyPipe } from 'src/app/shared/pipes/humanize-currency.pipe'; @@ -24,6 +26,9 @@ import { GeneratedFormProperties } from '../models/generated-form-properties.mod import { Location } from '../models/location.model'; import { DependentFieldsMapping } from '../models/dependent-field-mapping.model'; import { CustomInput } from '../models/custom-input.model'; +import { ExpensesService } from './platform/v1/spender/expenses.service'; +import { PlatformFileGenerateUrlsResponse } from '../models/platform/platform-file-generate-urls-response.model'; +import { ReceiptInfo } from '../models/receipt-info.model'; type CardTransactionsConfig = { queryParams: { @@ -46,7 +51,9 @@ export class MergeExpensesService { private projectsService: ProjectsService, private categoriesService: CategoriesService, private dateService: DateService, - private taxGroupService: TaxGroupService + private taxGroupService: TaxGroupService, + private expensesService: ExpensesService, + private spenderFileService: SpenderFileService ) {} mergeExpenses( @@ -113,20 +120,26 @@ export class MergeExpensesService { } getAttachements(txnID: string): Observable { - return this.fileService.findByTransactionId(txnID).pipe( - switchMap((fileObjs) => from(fileObjs)), - concatMap((fileObj: FileObject) => - this.fileService.downloadUrl(fileObj.id).pipe( - map((downloadUrl) => { - fileObj.url = downloadUrl; - const details = this.fileService.getReceiptsDetails(fileObj.name, fileObj.url); - fileObj.type = details.type; - fileObj.thumbnail = details.thumbnail; - return fileObj; - }) - ) + return this.expensesService.getExpenseById(txnID).pipe( + switchMap((expense: PlatformExpense) => + expense.file_ids.length > 0 ? this.spenderFileService.generateUrlsBulk(expense.file_ids) : of([]) ), - reduce((acc: FileObject[], curr) => acc.concat(curr), []) + map((response: PlatformFileGenerateUrlsResponse[]) => { + const files = response.filter((file) => file.content_type !== 'text/html'); + const receiptObjs: ReceiptInfo[] = files.map((file) => { + const details = this.fileService.getReceiptsDetails(file.name, file.download_url); + + const receipt: ReceiptInfo = { + url: file.download_url, + type: details.type, + thumbnail: details.thumbnail, + }; + + return receipt; + }); + + return receiptObjs; + }) ); } From f8e57c362f7bbc06a86d4f653541355fba0ff86f Mon Sep 17 00:00:00 2001 From: Aastha Bist Date: Tue, 14 May 2024 12:34:51 +0530 Subject: [PATCH 007/262] feat: Move stats call in tasks to platform [1] (#2921) --- src/app/core/mock-data/report-stats.data.ts | 115 ++++++---- src/app/core/mock-data/task.data.ts | 22 +- .../v1/platform-stats-request-param.model.ts | 1 + .../v1/report-stats-response.model.ts | 2 +- src/app/core/models/report-stats.model.ts | 12 +- .../v1/approver/reports.service.spec.ts | 16 +- .../platform/v1/approver/reports.service.ts | 25 ++- .../platform/v1/spender/reports.service.ts | 6 +- src/app/core/services/tasks.service.spec.ts | 205 ++++++++++-------- src/app/core/services/tasks.service.ts | 154 ++++++------- .../core/test-data/tasks.service.spec.data.ts | 15 +- .../fyle/dashboard/stats/stats.component.ts | 10 +- 12 files changed, 327 insertions(+), 256 deletions(-) diff --git a/src/app/core/mock-data/report-stats.data.ts b/src/app/core/mock-data/report-stats.data.ts index 4f2edf25ac..d9e9933ff5 100644 --- a/src/app/core/mock-data/report-stats.data.ts +++ b/src/app/core/mock-data/report-stats.data.ts @@ -1,43 +1,44 @@ import deepFreeze from 'deep-freeze-strict'; import { ReportStats } from '../models/report-stats.model'; +import { PlatformReportsStatsResponse } from '../models/platform/v1/report-stats-response.model'; export const expectedReportStats: ReportStats = deepFreeze({ draft: { - count: 6, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + count: 2, total_amount: 93165.91, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, report: { - count: 45, + count: 2, total_amount: 5177243929.65219, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, approved: { count: 56, total_amount: 28758273650702.816, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, paymentPending: { count: 4, total_amount: 501602.12, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, processing: { count: 7, @@ -54,46 +55,66 @@ export const expectedEmptyReportStats: ReportStats = deepFreeze({ draft: { total_amount: 0, count: 0, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, report: { total_amount: 0, count: 0, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, approved: { total_amount: 0, count: 0, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, paymentPending: { total_amount: 0, count: 0, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, processing: { total_amount: 0, count: 0, - failed_amount: null, - failed_count: null, - processing_amount: null, - processing_count: null, - reimbursable_amount: null, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, }, }); + +export const expectedSentBackResponse: PlatformReportsStatsResponse = deepFreeze({ + total_amount: 4500, + count: 2, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, +}); + +export const expectedSentBackResponseSingularReport: PlatformReportsStatsResponse = deepFreeze({ + total_amount: 4500, + count: 1, + failed_amount: 0, + failed_count: 0, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, +}); diff --git a/src/app/core/mock-data/task.data.ts b/src/app/core/mock-data/task.data.ts index 41f5e32fdd..0eb61275d3 100644 --- a/src/app/core/mock-data/task.data.ts +++ b/src/app/core/mock-data/task.data.ts @@ -60,10 +60,24 @@ export const teamReportTaskSample = deepFreeze({ }); export const sentBackReportTaskSample = deepFreeze({ - amount: '44.53', + amount: '4.5K', + count: 2, + header: 'Reports sent back!', + subheader: '2 reports worth ₹4.5K were sent back by your approver', + icon: TaskIcon.REPORT, + ctas: [ + { + content: 'View Reports', + event: TASKEVENT.openSentBackReport, + }, + ], +}); + +export const sentBackReportTaskSingularSample = deepFreeze({ + amount: '4.5K', count: 1, header: 'Report sent back!', - subheader: '1 report worth ₹44.53 was sent back by your approver', + subheader: '1 report worth ₹4.5K was sent back by your approver', icon: TaskIcon.REPORT, ctas: [ { @@ -102,10 +116,10 @@ export const unreportedExpenseTaskSample2 = deepFreeze({ }); export const unsubmittedReportTaskSample = deepFreeze({ - amount: '0.00', + amount: '93.17K', count: 2, header: 'Unsubmitted reports', - subheader: '2 reports remain in draft state', + subheader: '2 reports worth ₹93.17K remain in draft state', icon: TaskIcon.REPORT, ctas: [ { diff --git a/src/app/core/models/platform/v1/platform-stats-request-param.model.ts b/src/app/core/models/platform/v1/platform-stats-request-param.model.ts index 484eb2c083..e0b4e058a1 100644 --- a/src/app/core/models/platform/v1/platform-stats-request-param.model.ts +++ b/src/app/core/models/platform/v1/platform-stats-request-param.model.ts @@ -1,3 +1,4 @@ export interface PlatformStatsRequestParams { state: string; + next_approver_user_ids?: string; } diff --git a/src/app/core/models/platform/v1/report-stats-response.model.ts b/src/app/core/models/platform/v1/report-stats-response.model.ts index 2648c910cd..3037e3fe3a 100644 --- a/src/app/core/models/platform/v1/report-stats-response.model.ts +++ b/src/app/core/models/platform/v1/report-stats-response.model.ts @@ -1,4 +1,4 @@ -export interface ReportsStatsResponsePlatform { +export interface PlatformReportsStatsResponse { count: number; failed_amount: number; failed_count: number; diff --git a/src/app/core/models/report-stats.model.ts b/src/app/core/models/report-stats.model.ts index fb8781fd4e..deb724f3bc 100644 --- a/src/app/core/models/report-stats.model.ts +++ b/src/app/core/models/report-stats.model.ts @@ -1,8 +1,8 @@ -import { ReportsStatsResponsePlatform } from './platform/v1/report-stats-response.model'; +import { PlatformReportsStatsResponse } from './platform/v1/report-stats-response.model'; export interface ReportStats { - draft: ReportsStatsResponsePlatform; - report: ReportsStatsResponsePlatform; - approved: ReportsStatsResponsePlatform; - paymentPending: ReportsStatsResponsePlatform; - processing: ReportsStatsResponsePlatform; + draft: PlatformReportsStatsResponse; + report: PlatformReportsStatsResponse; + approved: PlatformReportsStatsResponse; + paymentPending: PlatformReportsStatsResponse; + processing: PlatformReportsStatsResponse; } 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 ec93d16382..fc13ccd862 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 @@ -15,6 +15,7 @@ import { import { ReportsQueryParams } from 'src/app/core/models/platform/v1/reports-query-params.model'; import { StatsResponse } from 'src/app/core/models/platform/v1/stats-response.model'; import { expectedReportStats } from 'src/app/core/mock-data/report-stats.data'; +import { ReportState } from '../../../../models/platform/v1/report.model'; describe('ApproverReportsService', () => { let approverReportsService: ApproverReportsService; @@ -58,6 +59,15 @@ describe('ApproverReportsService', () => { }); }); + it('generateStatsQueryParams(): should generate stats query params', () => { + const queryParams = { + state: `eq.${ReportState.DRAFT}`, + }; + + const result = approverReportsService.generateStatsQueryParams(queryParams); + expect(result).toEqual('state=eq.DRAFT'); + }); + it('getReportsCount(): should get a count of reports', (done) => { // Mock the response of getReportsByParams spyOn(approverReportsService, 'getReportsByParams').and.returnValue(of(platformReportCountData)); @@ -158,14 +168,10 @@ describe('ApproverReportsService', () => { }); it('getReportsStats(): should get advance request stats', (done) => { - const statsResponse: StatsResponse = { - count: 2, - total_amount: 1200, - }; approverPlatformApiService.post.and.returnValue(of({ data: expectedReportStats.draft })); const params = { - state: 'eq.DRAFT', + state: `eq.${ReportState.DRAFT}`, }; approverReportsService.getReportsStats(params).subscribe((res) => { 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 99641f721b..a92d2151c8 100644 --- a/src/app/core/services/platform/v1/approver/reports.service.ts +++ b/src/app/core/services/platform/v1/approver/reports.service.ts @@ -6,7 +6,7 @@ import { ReportsQueryParams } from 'src/app/core/models/platform/v1/reports-quer import { PAGINATION_SIZE } from 'src/app/constants'; import { Report } from 'src/app/core/models/platform/v1/report.model'; import { PlatformStatsRequestParams } from 'src/app/core/models/platform/v1/platform-stats-request-param.model'; -import { StatsResponse } from 'src/app/core/models/platform/v1/stats-response.model'; +import { PlatformReportsStatsResponse } from 'src/app/core/models/platform/v1/report-stats-response.model'; @Injectable({ providedIn: 'root', @@ -53,14 +53,23 @@ export class ApproverReportsService { return this.approverPlatformApiService.get>('/reports', config); } - getReportsStats(params: PlatformStatsRequestParams): Observable { - const queryParams = { - data: { - query_params: `state=${params.state}`, - }, - }; + generateStatsQueryParams(params: PlatformStatsRequestParams): string { + const paramKeys = Object.keys(params); + const queryParams = []; + paramKeys.forEach((key) => { + queryParams.push(`${key}=${params[key]}`); + }); + + return queryParams.join('&'); + } + + getReportsStats(params: PlatformStatsRequestParams): Observable { return this.approverPlatformApiService - .post<{ data: StatsResponse }>('/reports/stats', queryParams) + .post<{ data: PlatformReportsStatsResponse }>('/reports/stats', { + data: { + query_params: this.generateStatsQueryParams(params), + }, + }) .pipe(map((res) => res.data)); } diff --git a/src/app/core/services/platform/v1/spender/reports.service.ts b/src/app/core/services/platform/v1/spender/reports.service.ts index 459d96ece9..8b81ac6b65 100644 --- a/src/app/core/services/platform/v1/spender/reports.service.ts +++ b/src/app/core/services/platform/v1/spender/reports.service.ts @@ -12,7 +12,7 @@ import { PlatformStatsRequestParams } from 'src/app/core/models/platform/v1/plat import { CacheBuster } from 'ts-cacheable'; import { UserEventService } from '../../../user-event.service'; import { TransactionService } from '../../../transaction.service'; -import { ReportsStatsResponsePlatform } from 'src/app/core/models/platform/v1/report-stats-response.model'; +import { PlatformReportsStatsResponse } from 'src/app/core/models/platform/v1/report-stats-response.model'; const reportsCacheBuster$ = new Subject(); @@ -121,14 +121,14 @@ export class SpenderReportsService { .pipe(map((res) => res.data)); } - getReportsStats(params: PlatformStatsRequestParams): Observable { + getReportsStats(params: PlatformStatsRequestParams): Observable { const queryParams = { data: { query_params: `state=${params.state}`, }, }; return this.spenderPlatformV1ApiService - .post<{ data: ReportsStatsResponsePlatform }>('/reports/stats', queryParams) + .post<{ data: PlatformReportsStatsResponse }>('/reports/stats', queryParams) .pipe(map((res) => res.data)); } } diff --git a/src/app/core/services/tasks.service.spec.ts b/src/app/core/services/tasks.service.spec.ts index 95bff9679f..df861913a6 100644 --- a/src/app/core/services/tasks.service.spec.ts +++ b/src/app/core/services/tasks.service.spec.ts @@ -7,6 +7,7 @@ import { TaskIcon } from '../models/task-icon.enum'; import { allExtendedReportsResponse, extendedOrgUserResponse, + extendedOrgUserResponseSpender, incompleteExpensesResponse, potentialDuplicatesApiResponse, sentBackAdvancesResponse, @@ -36,6 +37,7 @@ import { draftExpenseTaskSample2, unreportedExpenseTaskSample2, commuteDeductionTask, + sentBackReportTaskSingularSample, } from '../mock-data/task.data'; import { mastercardRTFCard } from '../mock-data/platform-corporate-card.data'; import { OrgSettingsService } from './org-settings.service'; @@ -55,6 +57,15 @@ import { commuteDetailsResponseData3, } from '../mock-data/commute-details-response.data'; import { orgSettingsPendingRestrictions } from '../mock-data/org-settings.data'; +import { SpenderReportsService } from './platform/v1/spender/reports.service'; +import { ApproverReportsService } from './platform/v1/approver/reports.service'; +import { PlatformReportsStatsResponse } from '../models/platform/v1/report-stats-response.model'; +import { + expectedReportStats, + expectedSentBackResponse, + expectedSentBackResponseSingularReport, +} from '../mock-data/report-stats.data'; +import { expectedReportsSinglePage } from '../mock-data/platform-report.data'; describe('TasksService', () => { let tasksService: TasksService; @@ -67,6 +78,8 @@ describe('TasksService', () => { let humanizeCurrencyPipe: jasmine.SpyObj; let expensesService: jasmine.SpyObj; let employeesService: jasmine.SpyObj; + let spenderReportsService: jasmine.SpyObj; + let approverReportsService: jasmine.SpyObj; let orgSettingsService: jasmine.SpyObj; const mockTaskClearSubject = new Subject(); const homeCurrency = 'INR'; @@ -74,7 +87,6 @@ describe('TasksService', () => { beforeEach(() => { const reportServiceSpy = jasmine.createSpyObj('ReportService', [ 'getReportAutoSubmissionDetails', - 'getReportStatsData', 'getAllExtendedReports', ]); const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseStats', 'getDuplicateSets']); @@ -88,6 +100,8 @@ describe('TasksService', () => { const humanizeCurrencyPipeSpy = jasmine.createSpyObj('HumanizeCurrencyPipe', ['transform']); const orgSettingsServiceSpy = jasmine.createSpyObj('OrgSettingsService', ['get']); const employeesServiceSpy = jasmine.createSpyObj('EmployeesService', ['getCommuteDetails']); + const spenderReportsServiceSpy = jasmine.createSpyObj('SpenderReportsService', ['getReportsStats']); + const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', ['getReportsStats']); TestBed.configureTestingModule({ providers: [ @@ -132,6 +146,14 @@ describe('TasksService', () => { provide: OrgSettingsService, useValue: orgSettingsServiceSpy, }, + { + provide: SpenderReportsService, + useValue: spenderReportsServiceSpy, + }, + { + provide: ApproverReportsService, + useValue: approverReportsServiceSpy, + }, ], }); tasksService = TestBed.inject(TasksService); @@ -148,6 +170,8 @@ describe('TasksService', () => { expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; employeesService = TestBed.inject(EmployeesService) as jasmine.SpyObj; orgSettingsService = TestBed.inject(OrgSettingsService) as jasmine.SpyObj; + spenderReportsService = TestBed.inject(SpenderReportsService) as jasmine.SpyObj; + approverReportsService = TestBed.inject(ApproverReportsService) as jasmine.SpyObj; orgSettingsService.get.and.returnValue(of(orgSettingsPendingRestrictions)); }); @@ -171,22 +195,16 @@ describe('TasksService', () => { }); }); - function setupUnsibmittedReportsResponse() { - reportService.getReportStatsData - .withArgs({ - scalar: true, - aggregates: 'count(rp_id),sum(rp_amount)', - rp_state: 'in.(DRAFT)', - }) - .and.returnValue(of(unsubmittedReportsResponse)); - } - it('should be able to fetch unsubmitted reports', (done) => { - setupUnsibmittedReportsResponse(); + spenderReportsService.getReportsStats.and.returnValue(of(expectedReportStats.draft)); currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); humanizeCurrencyPipe.transform - .withArgs(unsubmittedReportsResponse[0].aggregates[1].function_value, homeCurrency, true) - .and.returnValue('0.00'); + .withArgs(expectedReportStats.draft.total_amount, homeCurrency, true) + .and.returnValue('93.17K'); + humanizeCurrencyPipe.transform + .withArgs(expectedReportStats.draft.total_amount, homeCurrency) + .and.returnValue('₹93.17K'); + humanizeCurrencyPipe.transform.and.returnValue('93.1K'); tasksService.getUnsubmittedReportsTasks().subscribe((unsubmittedReportsTasks) => { expect(unsubmittedReportsTasks).toEqual([unsubmittedReportTaskSample]); done(); @@ -228,22 +246,20 @@ describe('TasksService', () => { }); it('should be able to fetch Sent Back Report Tasks', (done) => { - reportService.getReportStatsData + spenderReportsService.getReportsStats .withArgs({ - scalar: true, - aggregates: 'count(rp_id),sum(rp_amount)', - rp_state: 'in.(APPROVER_INQUIRY)', + state: 'eq.APPROVER_INQUIRY', }) - .and.returnValue(of(sentBackResponse)); + .and.returnValue(of(expectedSentBackResponse)); currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); humanizeCurrencyPipe.transform - .withArgs(sentBackResponse[0].aggregates[1].function_value, homeCurrency, true) - .and.returnValue('44.53'); + .withArgs(expectedSentBackResponse.total_amount, homeCurrency, true) + .and.returnValue('4.5K'); humanizeCurrencyPipe.transform - .withArgs(sentBackResponse[0].aggregates[1].function_value, homeCurrency) - .and.returnValue('₹44.53'); + .withArgs(expectedSentBackResponse.total_amount, homeCurrency) + .and.returnValue('₹4.5K'); tasksService.getSentBackReportTasks().subscribe((sentBackReportsTasks) => { expect(sentBackReportsTasks).toEqual([sentBackReportTaskSample]); @@ -251,30 +267,45 @@ describe('TasksService', () => { }); }); - it('should be able to fetch team reports tasks', (done) => { + it('should show proper content for singular sent back report', (done) => { + spenderReportsService.getReportsStats + .withArgs({ + state: 'eq.APPROVER_INQUIRY', + }) + .and.returnValue(of(expectedSentBackResponseSingularReport)); + + currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); + + humanizeCurrencyPipe.transform + .withArgs(expectedSentBackResponse.total_amount, homeCurrency, true) + .and.returnValue('4.5K'); + humanizeCurrencyPipe.transform + .withArgs(expectedSentBackResponse.total_amount, homeCurrency) + .and.returnValue('₹4.5K'); + + tasksService.getSentBackReportTasks().subscribe((sentBackReportsTasks) => { + expect(sentBackReportsTasks).toEqual([sentBackReportTaskSingularSample]); + done(); + }); + }); + + it('should be able to fetch team reports tasks is role is APPROVER', (done) => { authService.getEou.and.returnValue(new Promise((resolve) => resolve(extendedOrgUserResponse))); currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); humanizeCurrencyPipe.transform - .withArgs(teamReportResponse[0].aggregates[1].function_value, homeCurrency, true) + .withArgs(expectedReportStats.report.total_amount, homeCurrency, true) .and.returnValue('733.48K'); humanizeCurrencyPipe.transform - .withArgs(teamReportResponse[0].aggregates[1].function_value, homeCurrency) + .withArgs(expectedReportStats.report.total_amount, homeCurrency) .and.returnValue('₹733.48K'); - reportService.getReportStatsData - .withArgs( - { - approved_by: 'cs.{' + extendedOrgUserResponse.ou.id + '}', - rp_approval_state: ['in.(APPROVAL_PENDING)'], - rp_state: ['in.(APPROVER_PENDING)'], - sequential_approval_turn: ['in.(true)'], - aggregates: 'count(rp_id),sum(rp_amount)', - scalar: true, - }, - false - ) - .and.returnValue(of(teamReportResponse)); + approverReportsService.getReportsStats + .withArgs({ + next_approver_user_ids: `cs.[${extendedOrgUserResponse.us.id}]`, + state: 'eq.APPROVER_PENDING', + }) + .and.returnValue(of(expectedReportStats.report)); tasksService.getTeamReportsTasks().subscribe((teamReportsTasks) => { expect(teamReportsTasks).toEqual([teamReportTaskSample]); @@ -282,6 +313,23 @@ describe('TasksService', () => { }); }); + it('should be able to return dummy team reports tasks is role is not APPROVER', (done) => { + authService.getEou.and.returnValue(new Promise((resolve) => resolve(extendedOrgUserResponseSpender))); + currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); + + humanizeCurrencyPipe.transform + .withArgs(expectedReportStats.report.total_amount, homeCurrency, true) + .and.returnValue('733.48K'); + humanizeCurrencyPipe.transform + .withArgs(expectedReportStats.report.total_amount, homeCurrency) + .and.returnValue('₹733.48K'); + + tasksService.getTeamReportsTasks().subscribe((teamReportsTasks) => { + expect(teamReportsTasks).toEqual([]); + done(); + }); + }); + it('should be able to fetch potential duplicate tasks', (done) => { setupData(); expensesService.getDuplicateSets.and.returnValue(of(expenseDuplicateSets)); @@ -615,9 +663,9 @@ describe('TasksService', () => { const tasks2 = tasksService.mapAggregateToTeamReportTask( { - totalAmount: 0, - totalCount: 0, - }, + total_amount: 0, + count: 0, + } as PlatformReportsStatsResponse, homeCurrency ); @@ -625,9 +673,9 @@ describe('TasksService', () => { const tasks3 = tasksService.mapAggregateToUnsubmittedReportTask( { - totalAmount: 0, - totalCount: 0, - }, + total_amount: 0, + count: 0, + } as PlatformReportsStatsResponse, homeCurrency ); @@ -645,9 +693,9 @@ describe('TasksService', () => { const tasks5 = tasksService.mapSentBackReportsToTasks( { - totalAmount: 0, - totalCount: 0, - }, + total_amount: 0, + count: 0, + } as PlatformReportsStatsResponse, homeCurrency ); @@ -690,30 +738,21 @@ describe('TasksService', () => { function setupData() { currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); advanceRequestService.getAdvanceRequestStats.and.returnValue(of(sentBackAdvancesResponse)); - setupUnsibmittedReportsResponse(); + spenderReportsService.getReportsStats.and.returnValue(of(expectedReportStats.draft)); getUnreportedExpenses(); - reportService.getReportStatsData + spenderReportsService.getReportsStats .withArgs({ - scalar: true, - aggregates: 'count(rp_id),sum(rp_amount)', - rp_state: 'in.(APPROVER_INQUIRY)', + state: 'eq.APPROVER_INQUIRY', }) - .and.returnValue(of(sentBackResponse)); + .and.returnValue(of(expectedSentBackResponse)); authService.getEou.and.returnValue(new Promise((resolve) => resolve(extendedOrgUserResponse))); currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); - reportService.getReportStatsData - .withArgs( - { - approved_by: 'cs.{' + extendedOrgUserResponse.ou.id + '}', - rp_approval_state: ['in.(APPROVAL_PENDING)'], - rp_state: ['in.(APPROVER_PENDING)'], - sequential_approval_turn: ['in.(true)'], - aggregates: 'count(rp_id),sum(rp_amount)', - scalar: true, - }, - false - ) - .and.returnValue(of(teamReportResponse)); + approverReportsService.getReportsStats + .withArgs({ + next_approver_user_ids: `cs.[${extendedOrgUserResponse.us.id}]`, + state: 'eq.APPROVER_PENDING', + }) + .and.returnValue(of(expectedReportStats.report)); expensesService.getDuplicateSets.and.returnValue(of(expenseDuplicateSets)); expensesService.getExpenseStats .withArgs({ @@ -732,7 +771,7 @@ describe('TasksService', () => { tasksService.getTasks().subscribe((tasks) => { expect(tasks.map((task) => task.header)).toEqual([ '34 Potential Duplicates', - 'Report sent back!', + 'Reports sent back!', 'Incomplete expenses', 'Unsubmitted reports', 'Expenses are ready to report', @@ -766,7 +805,7 @@ describe('TasksService', () => { tasksService.getTasks(true).subscribe((tasks) => { expect(tasks.map((task) => task.header)).toEqual([ '34 Potential Duplicates', - 'Report sent back!', + 'Reports sent back!', 'Incomplete expenses', 'Reports to be approved', 'Advances sent back!', @@ -775,14 +814,6 @@ describe('TasksService', () => { }); }); - it('should make sure that stats dont fail even if aggregates are not present in response', () => { - const mappedStatsReponse = tasksService.getStatsFromResponse([], 'count(rp_id)', 'sum(rp_amount)'); - expect(mappedStatsReponse).toEqual({ - totalCount: 0, - totalAmount: 0, - }); - }); - it('should be able to refresh tasks on clearing task cache when automate report submission is scheduled', (done) => { const refreshCallback = () => {}; userEventService.onTaskCacheClear.and.callFake((refreshCallback) => @@ -859,9 +890,9 @@ describe('TasksService', () => { const sentBackReportTask = tasksService.mapSentBackReportsToTasks( { - totalCount: 2, - totalAmount: sentBackResponse[0].aggregates[1].function_value, - }, + count: 2, + total_amount: sentBackResponse[0].aggregates[1].function_value, + } as PlatformReportsStatsResponse, homeCurrency ); @@ -915,16 +946,16 @@ describe('TasksService', () => { ]); }); - it('should generate proper content in all cases of unsibmitted report tasks', () => { + it('should generate proper content in all cases of unsubmitted report tasks', () => { humanizeCurrencyPipe.transform .withArgs(unsubmittedReportsResponse[0].aggregates[1].function_value, homeCurrency, true) .and.returnValue('0.00'); const tasks = tasksService.mapAggregateToUnsubmittedReportTask( { - totalAmount: unsubmittedReportsResponse[0].aggregates[1].function_value, - totalCount: 1, - }, + total_amount: unsubmittedReportsResponse[0].aggregates[1].function_value, + count: 1, + } as PlatformReportsStatsResponse, homeCurrency ); @@ -955,9 +986,9 @@ describe('TasksService', () => { const tasks = tasksService.mapAggregateToTeamReportTask( { - totalAmount: teamReportResponse[0].aggregates[1].function_value, - totalCount: 1, - }, + total_amount: teamReportResponse[0].aggregates[1].function_value, + count: 1, + } as PlatformReportsStatsResponse, homeCurrency ); diff --git a/src/app/core/services/tasks.service.ts b/src/app/core/services/tasks.service.ts index c0ea13643b..3501738a78 100644 --- a/src/app/core/services/tasks.service.ts +++ b/src/app/core/services/tasks.service.ts @@ -15,11 +15,14 @@ import { UserEventService } from './user-event.service'; import { CurrencyService } from './currency.service'; import { TaskDictionary } from '../models/task-dictionary.model'; import { CorporateCreditCardExpenseService } from './corporate-credit-card-expense.service'; -import { Datum } from '../models/v2/stats-response.model'; import { ExpensesService } from './platform/v1/spender/expenses.service'; import { OrgSettingsService } from './org-settings.service'; import { EmployeesService } from './platform/v1/spender/employees.service'; import { StatsResponse } from '../models/platform/v1/stats-response.model'; +import { SpenderReportsService } from './platform/v1/spender/reports.service'; +import { PlatformReportsStatsResponse } from '../models/platform/v1/report-stats-response.model'; +import { ApproverReportsService } from './platform/v1/approver/reports.service'; +import { ReportState } from '../models/platform/v1/report.model'; @Injectable({ providedIn: 'root', @@ -45,7 +48,9 @@ export class TasksService { private corporateCreditCardExpenseService: CorporateCreditCardExpenseService, private expensesService: ExpensesService, private orgSettingsService: OrgSettingsService, - private employeesService: EmployeesService + private employeesService: EmployeesService, + private spenderReportsService: SpenderReportsService, + private approverReportsService: ApproverReportsService ) { this.refreshOnTaskClear(); } @@ -425,11 +430,9 @@ export class TasksService { ); } - getSentBackReports(): Observable { - return this.reportService.getReportStatsData({ - scalar: true, - aggregates: 'count(rp_id),sum(rp_amount)', - rp_state: 'in.(APPROVER_INQUIRY)', + getSentBackReports(): Observable { + return this.spenderReportsService.getReportsStats({ + state: `eq.${ReportState.APPROVER_INQUIRY}`, }); } @@ -438,23 +441,21 @@ export class TasksService { reportsStats: this.getSentBackReports(), homeCurrency: this.currencyService.getHomeCurrency(), }).pipe( - map(({ reportsStats, homeCurrency }: { reportsStats: Datum[]; homeCurrency: string }) => - this.mapSentBackReportsToTasks(this.mapScalarReportStatsResponse(reportsStats), homeCurrency) + map(({ reportsStats, homeCurrency }: { reportsStats: PlatformReportsStatsResponse; homeCurrency: string }) => + this.mapSentBackReportsToTasks(reportsStats, homeCurrency) ) ); } - getUnsubmittedReportsStats(): Observable { - return this.reportService.getReportStatsData({ - scalar: true, - aggregates: 'count(rp_id),sum(rp_amount)', - rp_state: 'in.(DRAFT)', + getUnsubmittedReportsStats(): Observable { + return this.spenderReportsService.getReportsStats({ + state: `eq.${ReportState.DRAFT}`, }); } getSentBackAdvancesStats(): Observable { return this.advancesRequestService.getAdvanceRequestStats({ - state: 'eq.SENT_BACK', + state: `eq.SENT_BACK`, }); } @@ -473,21 +474,26 @@ export class TasksService { ); } - getTeamReportsStats(): Observable { + getTeamReportsStats(): Observable { return from(this.authService.getEou()).pipe( - switchMap((eou) => - this.reportService.getReportStatsData( - { - approved_by: 'cs.{' + eou.ou.id + '}', - rp_approval_state: ['in.(APPROVAL_PENDING)'], - rp_state: ['in.(APPROVER_PENDING)'], - sequential_approval_turn: ['in.(true)'], - aggregates: 'count(rp_id),sum(rp_amount)', - scalar: true, - }, - false - ) - ) + switchMap((eou) => { + if (eou.ou.roles.includes('APPROVER')) { + return this.approverReportsService.getReportsStats({ + next_approver_user_ids: `cs.[${eou.us.id}]`, + state: `eq.${ReportState.APPROVER_PENDING}`, + }); + } + const zeroResponse: PlatformReportsStatsResponse = { + count: 0, + failed_amount: null, + failed_count: null, + processing_amount: 0, + processing_count: 0, + reimbursable_amount: 0, + total_amount: 0, + }; + return of(zeroResponse); + }) ); } @@ -496,8 +502,8 @@ export class TasksService { reportsStats: this.getTeamReportsStats(), homeCurrency: this.currencyService.getHomeCurrency(), }).pipe( - map(({ reportsStats, homeCurrency }: { reportsStats: Datum[]; homeCurrency: string }) => - this.mapAggregateToTeamReportTask(this.mapScalarReportStatsResponse(reportsStats), homeCurrency) + map(({ reportsStats, homeCurrency }: { reportsStats: PlatformReportsStatsResponse; homeCurrency: string }) => + this.mapAggregateToTeamReportTask(reportsStats, homeCurrency) ) ); } @@ -569,8 +575,8 @@ export class TasksService { reportsStats: this.getUnsubmittedReportsStats(), homeCurrency: this.currencyService.getHomeCurrency(), }).pipe( - map(({ reportsStats, homeCurrency }: { reportsStats: Datum[]; homeCurrency: string }) => - this.mapAggregateToUnsubmittedReportTask(this.mapScalarReportStatsResponse(reportsStats), homeCurrency) + map(({ reportsStats, homeCurrency }: { reportsStats: PlatformReportsStatsResponse; homeCurrency: string }) => + this.mapAggregateToUnsubmittedReportTask(reportsStats, homeCurrency) ) ); } @@ -585,7 +591,7 @@ export class TasksService { return this.orgSettingsService.get().pipe( map( (orgSetting) => - orgSetting?.corporate_credit_card_settings?.enabled && orgSetting?.pending_cct_expense_restriction?.enabled + orgSetting.corporate_credit_card_settings?.enabled && orgSetting.pending_cct_expense_restriction?.enabled ), switchMap((filterPendingTxn: boolean) => { if (filterPendingTxn) { @@ -661,24 +667,21 @@ export class TasksService { return amount > 0 ? ` worth ${this.humanizeCurrency.transform(amount, currency)} ` : ''; } - mapSentBackReportsToTasks( - aggregate: { totalCount: number; totalAmount: number }, - homeCurrency: string - ): DashboardTask[] { - if (aggregate.totalCount > 0) { + mapSentBackReportsToTasks(aggregate: PlatformReportsStatsResponse, homeCurrency: string): DashboardTask[] { + if (aggregate.count > 0) { return [ { - amount: this.humanizeCurrency.transform(aggregate.totalAmount, homeCurrency, true), - count: aggregate.totalCount, - header: `Report${aggregate.totalCount === 1 ? '' : 's'} sent back!`, - subheader: `${aggregate.totalCount} report${aggregate.totalCount === 1 ? '' : 's'}${this.getAmountString( - aggregate.totalAmount, + amount: this.humanizeCurrency.transform(aggregate.total_amount, homeCurrency, true), + count: aggregate.count, + header: `Report${aggregate.count === 1 ? '' : 's'} sent back!`, + subheader: `${aggregate.count} report${aggregate.count === 1 ? '' : 's'}${this.getAmountString( + aggregate.total_amount, homeCurrency - )} ${aggregate.totalCount === 1 ? 'was' : 'were'} sent back by your approver`, + )} ${aggregate.count === 1 ? 'was' : 'were'} sent back by your approver`, icon: TaskIcon.REPORT, ctas: [ { - content: `View Report${aggregate.totalCount === 1 ? '' : 's'}`, + content: `View Report${aggregate.count === 1 ? '' : 's'}`, event: TASKEVENT.openSentBackReport, }, ], @@ -720,24 +723,21 @@ export class TasksService { } } - mapAggregateToUnsubmittedReportTask( - aggregate: { totalCount: number; totalAmount: number }, - homeCurrency: string - ): DashboardTask[] { - if (aggregate.totalCount > 0) { + mapAggregateToUnsubmittedReportTask(aggregate: PlatformReportsStatsResponse, homeCurrency: string): DashboardTask[] { + if (aggregate.count > 0) { return [ { - amount: this.humanizeCurrency.transform(aggregate.totalAmount, homeCurrency, true), - count: aggregate.totalCount, - header: `Unsubmitted report${aggregate.totalCount === 1 ? '' : 's'}`, - subheader: `${aggregate.totalCount} report${aggregate.totalCount === 1 ? '' : 's'}${this.getAmountString( - aggregate.totalAmount, + amount: this.humanizeCurrency.transform(aggregate.total_amount, homeCurrency, true), + count: aggregate.count, + header: `Unsubmitted report${aggregate.count === 1 ? '' : 's'}`, + subheader: `${aggregate.count} report${aggregate.count === 1 ? '' : 's'}${this.getAmountString( + aggregate.total_amount, homeCurrency - )} remain${aggregate.totalCount === 1 ? 's' : ''} in draft state`, + )} remain${aggregate.count === 1 ? 's' : ''} in draft state`, icon: TaskIcon.REPORT, ctas: [ { - content: `Submit Report${aggregate.totalCount === 1 ? '' : 's'}`, + content: `Submit Report${aggregate.count === 1 ? '' : 's'}`, event: TASKEVENT.openDraftReports, }, ], @@ -748,24 +748,21 @@ export class TasksService { } } - mapAggregateToTeamReportTask( - aggregate: { totalCount: number; totalAmount: number }, - homeCurrency: string - ): DashboardTask[] { - if (aggregate.totalCount > 0) { + mapAggregateToTeamReportTask(aggregate: PlatformReportsStatsResponse, homeCurrency: string): DashboardTask[] { + if (aggregate.count > 0) { return [ { - amount: this.humanizeCurrency.transform(aggregate.totalAmount, homeCurrency, true), - count: aggregate.totalCount, - header: `Report${aggregate.totalCount === 1 ? '' : 's'} to be approved`, - subheader: `${aggregate.totalCount} report${aggregate.totalCount === 1 ? '' : 's'}${this.getAmountString( - aggregate.totalAmount, + amount: this.humanizeCurrency.transform(aggregate.total_amount, homeCurrency, true), + count: aggregate.count, + header: `Report${aggregate.count === 1 ? '' : 's'} to be approved`, + subheader: `${aggregate.count} report${aggregate.count === 1 ? '' : 's'}${this.getAmountString( + aggregate.total_amount, homeCurrency - )} require${aggregate.totalCount === 1 ? 's' : ''} your approval`, + )} require${aggregate.count === 1 ? 's' : ''} your approval`, icon: TaskIcon.REPORT, ctas: [ { - content: `Show Report${aggregate.totalCount === 1 ? '' : 's'}`, + content: `Show Report${aggregate.count === 1 ? '' : 's'}`, event: TASKEVENT.openTeamReport, }, ], @@ -831,23 +828,6 @@ export class TasksService { } } - getStatsFromResponse( - statsResponse: Datum[], - countName: string, - sumName: string - ): { totalCount: number; totalAmount: number } { - const countAggregate = statsResponse[0]?.aggregates.find((aggregate) => aggregate.function_name === countName) || 0; - const amountAggregate = statsResponse[0]?.aggregates.find((aggregate) => aggregate.function_name === sumName) || 0; - return { - totalCount: countAggregate && countAggregate.function_value, - totalAmount: amountAggregate && amountAggregate.function_value, - }; - } - - mapScalarReportStatsResponse(statsResponse: Datum[]): { totalCount: number; totalAmount: number } { - return this.getStatsFromResponse(statsResponse, 'count(rp_id)', 'sum(rp_amount)'); - } - getCommuteDetailsTasks(): Observable { const isCommuteDeductionEnabled$ = this.orgSettingsService .get() diff --git a/src/app/core/test-data/tasks.service.spec.data.ts b/src/app/core/test-data/tasks.service.spec.data.ts index 3c2d4a34e8..49250e447a 100644 --- a/src/app/core/test-data/tasks.service.spec.data.ts +++ b/src/app/core/test-data/tasks.service.spec.data.ts @@ -1,4 +1,5 @@ import deepFreeze from 'deep-freeze-strict'; +import { ExtendedOrgUser } from '../models/extended-org-user.model'; export const potentialDuplicatesApiResponse = deepFreeze([ { transaction_ids: ['tx3I0ccSGlhg', 'txvAmVCGZUZi'], fields: ['amount', 'currency', 'txn_dt'] }, @@ -22,16 +23,16 @@ export const potentialDuplicatesApiResponse = deepFreeze([ { transaction_ids: ['txT0ZmCrVOiD', 'txzjWIcqYxa9'], fields: ['amount', 'currency', 'txn_dt'] }, ]); -export const extendedOrgUserResponse = deepFreeze({ +export const extendedOrgUserResponse: ExtendedOrgUser = deepFreeze({ ou: { id: 'out3t2X258rd', created_at: new Date('2019-02-04T10:26:36.877Z'), org_id: 'orrjqbDbeP9p', user_id: 'usN0bYiJRI5V', - employee_id: null, + employee_id: 'ouE1vAIIxhaA', location: 'indiax', level: null, - level_id: null, + level_id: 'lvlPtroPaClQy', band: null, rank: null, business_unit: null, @@ -171,6 +172,14 @@ export const extendedOrgUserResponse = deepFreeze({ }, }); +export const extendedOrgUserResponseSpender: ExtendedOrgUser = deepFreeze({ + ...extendedOrgUserResponse, + ou: { + ...extendedOrgUserResponse.ou, + roles: ['FYLER'], + }, +}); + export const teamReportResponse = deepFreeze([ { aggregates: [ diff --git a/src/app/fyle/dashboard/stats/stats.component.ts b/src/app/fyle/dashboard/stats/stats.component.ts index 05ba1d7a99..a431b86e97 100644 --- a/src/app/fyle/dashboard/stats/stats.component.ts +++ b/src/app/fyle/dashboard/stats/stats.component.ts @@ -15,7 +15,7 @@ import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { OrgService } from 'src/app/core/services/org.service'; import { PaymentModesService } from 'src/app/core/services/payment-modes.service'; import { ReportStatsData } from 'src/app/core/models/report-stats-data.model'; -import { ReportsStatsResponsePlatform } from 'src/app/core/models/platform/v1/report-stats-response.model'; +import { PlatformReportsStatsResponse } from 'src/app/core/models/platform/v1/report-stats-response.model'; @Component({ selector: 'app-stats', @@ -23,13 +23,13 @@ import { ReportsStatsResponsePlatform } from 'src/app/core/models/platform/v1/re styleUrls: ['./stats.component.scss'], }) export class StatsComponent implements OnInit { - draftStats$: Observable; + draftStats$: Observable; - approvedStats$: Observable; + approvedStats$: Observable; - paymentPendingStats$: Observable; + paymentPendingStats$: Observable; - processingStats$: Observable; + processingStats$: Observable; homeCurrency$: Observable; From 0fca36933758073da8de04721506829e5cdd7bda Mon Sep 17 00:00:00 2001 From: Aastha Bist Date: Tue, 14 May 2024 13:07:19 +0530 Subject: [PATCH 008/262] feat: Move tasks, My Expenses, add expenses to report popup and create report to platform (#2952) --- .../core/mock-data/modal-controller.data.ts | 12 - src/app/core/mock-data/option.data.ts | 6 + .../core/mock-data/platform-report.data.ts | 406 +++++++++++++++++- .../platform/v1/reports-query-params.model.ts | 1 + .../v1/spender/reports.service.spec.ts | 6 +- .../platform/v1/spender/reports.service.ts | 18 +- src/app/core/services/report.service.spec.ts | 30 +- src/app/core/services/report.service.ts | 19 +- .../dashboard/tasks/tasks-2.component.spec.ts | 52 ++- .../dashboard/tasks/tasks-3.component.spec.ts | 97 +++-- .../tasks/tasks.component.setup.spec.ts | 9 +- .../fyle/dashboard/tasks/tasks.component.ts | 78 ++-- .../my-create-report.page.spec.ts | 34 +- .../my-create-report/my-create-report.page.ts | 10 +- .../add-txn-to-report-dialog.component.html | 10 +- ...add-txn-to-report-dialog.component.spec.ts | 19 +- .../add-txn-to-report-dialog.component.ts | 9 +- .../fyle/my-expenses/my-expenses.page.spec.ts | 87 ++-- src/app/fyle/my-expenses/my-expenses.page.ts | 34 +- .../create-new-report.component.spec.ts | 44 +- .../create-new-report.component.ts | 8 +- .../create-new-report.component.html | 99 ----- .../create-new-report.component.scss | 154 ------- .../create-new-report.component.spec.ts | 270 ------------ .../create-new-report.component.ts | 173 -------- src/app/shared/shared.module.ts | 3 - 26 files changed, 705 insertions(+), 983 deletions(-) delete mode 100644 src/app/shared/components/create-new-report/create-new-report.component.html delete mode 100644 src/app/shared/components/create-new-report/create-new-report.component.scss delete mode 100644 src/app/shared/components/create-new-report/create-new-report.component.spec.ts delete mode 100644 src/app/shared/components/create-new-report/create-new-report.component.ts diff --git a/src/app/core/mock-data/modal-controller.data.ts b/src/app/core/mock-data/modal-controller.data.ts index c68e7edc5e..a9b12a063a 100644 --- a/src/app/core/mock-data/modal-controller.data.ts +++ b/src/app/core/mock-data/modal-controller.data.ts @@ -4,7 +4,6 @@ import { filterOptions1 } from './filter.data'; import { selectedFilters1, selectedFilters4, taskSelectedFiltersData } from './selected-filters.data'; import { FilterOptionType } from 'src/app/shared/components/fy-filters/filter-option-type.enum'; import { CreateNewReportComponent as createReportV2 } from 'src/app/shared/components/create-new-report-v2/create-new-report.component'; -import { CreateNewReportComponent } from 'src/app/shared/components/create-new-report/create-new-report.component'; import { Mode } from '@ionic/core'; import { fyModalProperties } from './model-properties.data'; import { AddTxnToReportDialogComponent as v2 } from 'src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component'; @@ -21,8 +20,6 @@ import { advanceRequestFileUrlData2, fileObject4 } from './file-object.data'; import { ViewCommentComponent } from 'src/app/shared/components/comments-history/view-comment/view-comment.component'; import { FyPopoverComponent } from 'src/app/shared/components/fy-popover/fy-popover.component'; import { VirtualSelectModalComponent } from 'src/app/shared/components/virtual-select/virtual-select-modal/virtual-select-modal.component'; - -import { apiExpenseRes } from './expense.data'; import { apiExpenses1 } from './platform/v1/expense.data'; export const modalControllerParams = { @@ -57,15 +54,6 @@ export const modalControllerParams2 = { cssClass: 'dialog-popover', }; -export const newReportModalParams = { - component: CreateNewReportComponent, - componentProps: { - selectedExpensesToReport: apiExpenseRes, - }, - mode: 'ios', - ...fyModalProperties, -}; - export const newReportModalParams2 = { component: createReportV2, componentProps: { diff --git a/src/app/core/mock-data/option.data.ts b/src/app/core/mock-data/option.data.ts index dd17f97b43..0f01c6ff56 100644 --- a/src/app/core/mock-data/option.data.ts +++ b/src/app/core/mock-data/option.data.ts @@ -11,6 +11,9 @@ export const optionData1: Option[] = deepFreeze([ created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, @@ -77,6 +80,9 @@ export const optionData1: Option[] = deepFreeze([ created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, diff --git a/src/app/core/mock-data/platform-report.data.ts b/src/app/core/mock-data/platform-report.data.ts index e4497dfbb5..d4400f205c 100644 --- a/src/app/core/mock-data/platform-report.data.ts +++ b/src/app/core/mock-data/platform-report.data.ts @@ -3,6 +3,7 @@ import deepFreeze from 'deep-freeze-strict'; import { Report } from '../models/platform/v1/report.model'; import { ReportsQueryParams } from '../models/platform/v1/reports-query-params.model'; import { PlatformApiResponse } from '../models/platform/platform-api-response.model'; +import { ApprovalState } from '../models/platform/report-approvals.model'; export const mockQueryParams: ReportsQueryParams = deepFreeze({ state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', @@ -15,7 +16,26 @@ export const mockQueryParamsForCount: ReportsQueryParams = deepFreeze({ export const platformReportData: Report = deepFreeze({ amount: 0, - approvals: [], + approvals: [ + { + approver_user: { + email: 'aditya.b@fyle.in', + full_name: 'AB', + id: 'usJzTy7lqHSI', + }, + approver_user_id: 'usJzTy7lqHSI', + state: ApprovalState.APPROVAL_PENDING, + }, + { + approver_user: { + email: 'aastha.b@fyle.in', + full_name: 'Aastha', + id: 'usRjTPO4r69K', + }, + approver_user_id: 'usRjTPO4r69K', + state: ApprovalState.APPROVAL_DONE, + }, + ], created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { @@ -213,6 +233,225 @@ export const allReportsPaginated1: PlatformApiResponse = deepFreeze({ offset: 0, }); +export const allReportsPaginatedWithApproval: PlatformApiResponse = deepFreeze({ + count: 4, + data: [ + { + amount: 100, + approvals: [ + { + approver_user: { + email: 'aditya.b@fyle.in', + full_name: 'AB', + id: 'usJzTy7lqHSI', + }, + approver_user_id: 'usJzTy7lqHSI', + state: ApprovalState.APPROVAL_PENDING, + }, + { + approver_user: { + email: 'aastha.b@fyle.in', + full_name: 'Aastha', + id: 'usRjTPO4r69K', + }, + approver_user_id: 'usRjTPO4r69K', + state: ApprovalState.APPROVAL_DONE, + }, + ], + created_at: new Date('2023-07-11T06:19:28.260142+00:00'), + currency: 'USD', + employee: { + ach_account: { + added: true, + verified: null, + }, + business_unit: + 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', + code: null, + department: { + code: null, + display_name: '0000000 / arun', + id: 'dept7HJ9C4wvtX', + name: '0000000', + sub_department: 'arun', + }, + department_id: 'dept7HJ9C4wvtX', + id: 'ouX8dwsbLCLv', + location: 'Mumbai', + org_id: 'orNVthTo2Zyo', + title: 'director', + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + employee_id: 'ouX8dwsbLCLv', + id: 'rprAfNrce73O', + is_exported: false, + is_manually_flagged: false, + is_physical_bill_submitted: false, + is_policy_flagged: false, + is_verified: false, + last_approved_at: null, + last_paid_at: null, + last_resubmitted_at: null, + last_submitted_at: null, + next_approver_user_ids: null, + num_expenses: 0, + org_id: 'orNVthTo2Zyo', + physical_bill_submitted_at: null, + purpose: '#8: Jan 2023', + seq_num: 'C/2023/07/R/17', + settlement_id: null, + source: 'WEBAPP', + state: 'DRAFT', + state_display_name: 'Draft', + tax: null, + updated_at: new Date('2023-08-09T13:02:35.097839+00:00'), + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + { + amount: 200, + approvals: [], + created_at: new Date('2023-07-11T06:19:28.260142+00:00'), + currency: 'USD', + employee: { + ach_account: { + added: true, + verified: null, + }, + business_unit: + 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', + code: null, + department: { + code: null, + display_name: '0000000 / arun', + id: 'dept7HJ9C4wvtX', + name: '0000000', + sub_department: 'arun', + }, + department_id: 'dept7HJ9C4wvtX', + id: 'ouX8dwsbLCLv', + location: 'Mumbai', + org_id: 'orNVthTo2Zyo', + title: 'director', + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + employee_id: 'ouX8dwsbLCLv', + id: 'rpLMyvYSXgJy', + is_exported: false, + is_manually_flagged: false, + is_physical_bill_submitted: false, + is_policy_flagged: false, + is_verified: false, + last_approved_at: null, + last_paid_at: null, + last_resubmitted_at: null, + last_submitted_at: null, + next_approver_user_ids: null, + num_expenses: 0, + org_id: 'orNVthTo2Zyo', + physical_bill_submitted_at: null, + purpose: '#7: Jan 2023', + seq_num: 'C/2023/07/R/17', + settlement_id: null, + source: 'WEBAPP', + state: 'DRAFT', + state_display_name: 'Draft', + tax: null, + updated_at: new Date('2023-08-09T13:02:35.097839+00:00'), + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + ], + offset: 0, +}); + +export const filteredReportsData: PlatformApiResponse = deepFreeze({ + count: 4, + data: [ + { + amount: 200, + approvals: [], + created_at: new Date('2023-07-11T06:19:28.260142+00:00'), + currency: 'USD', + employee: { + ach_account: { + added: true, + verified: null, + }, + business_unit: + 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', + code: null, + department: { + code: null, + display_name: '0000000 / arun', + id: 'dept7HJ9C4wvtX', + name: '0000000', + sub_department: 'arun', + }, + department_id: 'dept7HJ9C4wvtX', + id: 'ouX8dwsbLCLv', + location: 'Mumbai', + org_id: 'orNVthTo2Zyo', + title: 'director', + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + employee_id: 'ouX8dwsbLCLv', + id: 'rpLMyvYSXgJy', + is_exported: false, + is_manually_flagged: false, + is_physical_bill_submitted: false, + is_policy_flagged: false, + is_verified: false, + last_approved_at: null, + last_paid_at: null, + last_resubmitted_at: null, + last_submitted_at: null, + next_approver_user_ids: null, + num_expenses: 0, + org_id: 'orNVthTo2Zyo', + physical_bill_submitted_at: null, + purpose: '#7: Jan 2023', + seq_num: 'C/2023/07/R/17', + settlement_id: null, + source: 'WEBAPP', + state: 'DRAFT', + state_display_name: 'Draft', + tax: null, + updated_at: new Date('2023-08-09T13:02:35.097839+00:00'), + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + ], + offset: 0, +}); + export const allReportsPaginated2: PlatformApiResponse = deepFreeze({ count: 4, data: [ @@ -344,11 +583,176 @@ export const allReportsPaginated2: PlatformApiResponse = deepFreeze({ offset: 2, }); +export const submittedReportData: Report = deepFreeze({ + amount: 300, + approvals: [], + created_at: new Date('2023-07-11T06:19:28.260142+00:00'), + currency: 'USD', + employee: { + ach_account: { + added: true, + verified: null, + }, + business_unit: + 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', + code: null, + department: { + code: null, + display_name: '0000000 / arun', + id: 'dept7HJ9C4wvtX', + name: '0000000', + sub_department: 'arun', + }, + department_id: 'dept7HJ9C4wvtX', + id: 'ouX8dwsbLCLv', + location: 'Mumbai', + org_id: 'orNVthTo2Zyo', + title: 'director', + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + employee_id: 'ouX8dwsbLCLv', + id: 'rpMvN0P10l6F', + is_exported: false, + is_manually_flagged: false, + is_physical_bill_submitted: false, + is_policy_flagged: false, + is_verified: false, + last_approved_at: null, + last_paid_at: null, + last_resubmitted_at: null, + last_submitted_at: null, + next_approver_user_ids: null, + num_expenses: 0, + org_id: 'orNVthTo2Zyo', + physical_bill_submitted_at: null, + purpose: '#6: Jan 2023', + seq_num: 'C/2023/07/R/17', + settlement_id: null, + source: 'WEBAPP', + state: 'APPROVER_PENDING', + state_display_name: 'Draft', + tax: null, + updated_at: new Date('2023-08-09T13:02:35.097839+00:00'), + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', +}); + +export const submittedReportDataWithApproval: Report = deepFreeze({ + amount: 300, + approvals: [ + { + approver_user: { + email: 'aditya.b@fyle.in', + full_name: 'AB', + id: 'usJzTy7lqHSI', + }, + approver_user_id: 'usJzTy7lqHSI', + state: ApprovalState.APPROVAL_PENDING, + }, + { + approver_user: { + email: 'aastha.b@fyle.in', + full_name: 'Aastha', + id: 'usRjTPO4r69K', + }, + approver_user_id: 'usRjTPO4r69K', + state: ApprovalState.APPROVAL_DONE, + }, + ], + created_at: new Date('2023-07-11T06:19:28.260142+00:00'), + currency: 'USD', + employee: { + ach_account: { + added: true, + verified: null, + }, + business_unit: + 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', + code: null, + department: { + code: null, + display_name: '0000000 / arun', + id: 'dept7HJ9C4wvtX', + name: '0000000', + sub_department: 'arun', + }, + department_id: 'dept7HJ9C4wvtX', + id: 'ouX8dwsbLCLv', + location: 'Mumbai', + org_id: 'orNVthTo2Zyo', + title: 'director', + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + employee_id: 'ouX8dwsbLCLv', + id: 'rpMvN0P10l6F', + is_exported: false, + is_manually_flagged: false, + is_physical_bill_submitted: false, + is_policy_flagged: false, + is_verified: false, + last_approved_at: null, + last_paid_at: null, + last_resubmitted_at: null, + last_submitted_at: null, + next_approver_user_ids: null, + num_expenses: 0, + org_id: 'orNVthTo2Zyo', + physical_bill_submitted_at: null, + purpose: '#6: Jan 2023', + seq_num: 'C/2023/07/R/17', + settlement_id: null, + source: 'WEBAPP', + state: 'APPROVER_PENDING', + state_display_name: 'Draft', + tax: null, + updated_at: new Date('2023-08-09T13:02:35.097839+00:00'), + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', +}); + +export const paidReportData: Report = deepFreeze({ + ...submittedReportDataWithApproval, + num_expenses: 1, + state: 'PAID', +}); + export const expectedSingleReport: Report[] = deepFreeze([allReportsPaginated1.data[0]]); export const expectedReportsSinglePage: Report[] = deepFreeze([...allReportsPaginated1.data]); +export const expectedReportsSinglePageWithApproval: Report[] = deepFreeze([...allReportsPaginatedWithApproval.data]); + +export const expectedReportsSinglePageFiltered: Report[] = deepFreeze([...filteredReportsData.data]); + export const expectedReportsPaginated: Report[] = deepFreeze([ ...allReportsPaginated1.data, ...allReportsPaginated2.data, ]); + +export const expectedReportsSinglePageSubmitted: Report[] = deepFreeze([ + ...allReportsPaginated1.data, + submittedReportData, +]); + +export const expectedReportsSinglePageSubmittedWithApproval: Report[] = deepFreeze([ + ...allReportsPaginated1.data, + submittedReportDataWithApproval, +]); diff --git a/src/app/core/models/platform/v1/reports-query-params.model.ts b/src/app/core/models/platform/v1/reports-query-params.model.ts index 5cac67b9f0..98d8f42567 100644 --- a/src/app/core/models/platform/v1/reports-query-params.model.ts +++ b/src/app/core/models/platform/v1/reports-query-params.model.ts @@ -4,4 +4,5 @@ export interface ReportsQueryParams { limit?: number; order?: string; id?: string; + next_approver_user_ids?: string; } diff --git a/src/app/core/services/platform/v1/spender/reports.service.spec.ts b/src/app/core/services/platform/v1/spender/reports.service.spec.ts index bb66513b2a..36f3b11130 100644 --- a/src/app/core/services/platform/v1/spender/reports.service.spec.ts +++ b/src/app/core/services/platform/v1/spender/reports.service.spec.ts @@ -42,6 +42,7 @@ describe('SpenderReportsService', () => { ) as jasmine.SpyObj; userEventService = TestBed.inject(UserEventService) as jasmine.SpyObj; transactionService = TestBed.inject(TransactionService) as jasmine.SpyObj; + spyOn(spenderReportsService, 'clearTransactionCache').and.returnValue(of(null)); }); it('should be created', () => { @@ -58,7 +59,7 @@ describe('SpenderReportsService', () => { offset: 0, }; - spenderReportsService.getReportsCount(mockQueryParams).subscribe((res) => { + spenderReportsService.getReportsCount(mockQueryParamsForCount).subscribe((res) => { // Verify expect(res).toEqual(4); // Check if the count is as expected expect(spenderReportsService.getReportsByParams).toHaveBeenCalledWith(expectedParams); // Check if the method is called with the expected params @@ -140,7 +141,6 @@ describe('SpenderReportsService', () => { it('addExpenses(): should add an expense to a report', (done) => { spenderPlatformV1ApiService.post.and.returnValue(of(null)); - spyOn(spenderReportsService, 'clearTransactionCache').and.returnValue(of(null)); const reportID = 'rpvcIMRMyM3A'; const txns = ['txTQVBx7W8EO']; @@ -200,7 +200,6 @@ describe('SpenderReportsService', () => { it('ejectExpenses(): should remove an expense from a report', (done) => { spenderPlatformV1ApiService.post.and.returnValue(of(null)); - spyOn(spenderReportsService, 'clearTransactionCache').and.returnValue(of(null)); const reportID = 'rpvcIMRMyM3A'; const txns = ['txTQVBx7W8EO']; @@ -232,6 +231,7 @@ describe('SpenderReportsService', () => { spenderReportsService.createDraft(reportParam).subscribe((res) => { expect(res).toEqual(allReportsPaginated1.data[0]); expect(spenderPlatformV1ApiService.post).toHaveBeenCalledOnceWith('/reports', reportParam); + expect(spenderReportsService.clearTransactionCache).toHaveBeenCalledTimes(1); done(); }); }); diff --git a/src/app/core/services/platform/v1/spender/reports.service.ts b/src/app/core/services/platform/v1/spender/reports.service.ts index 8b81ac6b65..1a0dfcb230 100644 --- a/src/app/core/services/platform/v1/spender/reports.service.ts +++ b/src/app/core/services/platform/v1/spender/reports.service.ts @@ -73,6 +73,16 @@ export class SpenderReportsService { ); } + @CacheBuster({ + cacheBusterNotifier: reportsCacheBuster$, + }) + createDraft(data: CreateDraftParams): Observable { + return this.spenderPlatformV1ApiService.post>('/reports', data).pipe( + tap(() => this.clearTransactionCache()), + map((res: PlatformApiPayload) => res.data) + ); + } + getAllReportsByParams(queryParams: ReportsQueryParams): Observable { return this.getReportsCount(queryParams).pipe( switchMap((count) => { @@ -94,7 +104,7 @@ export class SpenderReportsService { getReportsCount(queryParams: ReportsQueryParams): Observable { const params = { - state: queryParams.state, + ...queryParams, limit: 1, offset: 0, }; @@ -115,12 +125,6 @@ export class SpenderReportsService { return this.getReportsByParams(queryParams).pipe(map((res: PlatformApiResponse) => res.data[0])); } - createDraft(data: CreateDraftParams): Observable { - return this.spenderPlatformV1ApiService - .post>('/reports', data) - .pipe(map((res) => res.data)); - } - getReportsStats(params: PlatformStatsRequestParams): Observable { const queryParams = { data: { diff --git a/src/app/core/services/report.service.spec.ts b/src/app/core/services/report.service.spec.ts index 033d52fcc0..fbdb4e3967 100644 --- a/src/app/core/services/report.service.spec.ts +++ b/src/app/core/services/report.service.spec.ts @@ -70,10 +70,11 @@ import { StorageService } from './storage.service'; import { TransactionService } from './transaction.service'; import { UserEventService } from './user-event.service'; import { dataErtpTransformed, apiErptReporDataParam } from '../mock-data/data-transform.data'; -import { platformReportData } from '../mock-data/platform-report.data'; +import { expectedReportsSinglePage, platformReportData } from '../mock-data/platform-report.data'; import { ApproverPlatformApiService } from './approver-platform-api.service'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { cloneDeep } from 'lodash'; +import { SpenderReportsService } from './platform/v1/spender/reports.service'; describe('ReportService', () => { let reportService: ReportService; @@ -89,6 +90,7 @@ describe('ReportService', () => { let permissionsService: jasmine.SpyObj; let transactionService: jasmine.SpyObj; let networkService: jasmine.SpyObj; + let spenderReportsService: jasmine.SpyObj; let launchDarklyService: LaunchDarklyService; const apiReportStatParams: Partial = { @@ -196,6 +198,7 @@ describe('ReportService', () => { ) as jasmine.SpyObj; permissionsService = TestBed.inject(PermissionsService) as jasmine.SpyObj; launchDarklyService = TestBed.inject(LaunchDarklyService) as jasmine.SpyObj; + spenderReportsService = TestBed.inject(SpenderReportsService) as jasmine.SpyObj; }); it('should be created', () => { @@ -218,23 +221,6 @@ describe('ReportService', () => { }); }); - it('createDraft(): should create a draft report and return the report', (done) => { - apiService.post.and.returnValue(of(reportUnflattenedData)); - spyOn(reportService, 'clearTransactionCache').and.returnValue(of(null)); - - const reportParam = { - purpose: 'A draft Report', - source: 'MOBILE', - }; - - reportService.createDraft(reportParam).subscribe((res) => { - expect(res).toEqual(reportUnflattenedData); - expect(apiService.post).toHaveBeenCalledOnceWith('/reports', reportParam); - expect(reportService.clearTransactionCache).toHaveBeenCalledTimes(1); - done(); - }); - }); - it('submit(): should submit a report', (done) => { spyOn(reportService, 'clearTransactionCache').and.returnValue(of(null)); apiService.post.and.returnValue(of(null)); @@ -522,7 +508,7 @@ describe('ReportService', () => { }); it('create(): should create a new report', (done) => { - spyOn(reportService, 'createDraft').and.returnValue(of(reportUnflattenedData2)); + spyOn(spenderReportsService, 'createDraft').and.returnValue(of(expectedReportsSinglePage[0])); spenderPlatformV1ApiService.post.and.returnValue(of(null)); spyOn(reportService, 'submit').and.returnValue(of(null)); @@ -531,7 +517,7 @@ describe('ReportService', () => { source: 'MOBILE', }; const expenseIds = ['tx6Oe6FaYDZl']; - const reportID = 'rp5eUkeNm9wB'; + const reportID = 'rprAfNrce73O'; const payload = { data: { id: reportID, @@ -540,8 +526,8 @@ describe('ReportService', () => { }; reportService.create(reportPurpose, expenseIds).subscribe((res) => { - expect(res).toEqual(reportUnflattenedData2); - expect(reportService.createDraft).toHaveBeenCalledOnceWith(reportPurpose); + expect(res).toEqual(expectedReportsSinglePage[0]); + expect(spenderReportsService.createDraft).toHaveBeenCalledOnceWith({ data: reportPurpose }); expect(spenderPlatformV1ApiService.post).toHaveBeenCalledOnceWith('/reports/add_expenses', payload); expect(reportService.submit).toHaveBeenCalledOnceWith(reportID); done(); diff --git a/src/app/core/services/report.service.ts b/src/app/core/services/report.service.ts index 81d3fbbba1..cd295af2d8 100644 --- a/src/app/core/services/report.service.ts +++ b/src/app/core/services/report.service.ts @@ -33,6 +33,7 @@ import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service'; import { StorageService } from './storage.service'; import { TransactionService } from './transaction.service'; import { UserEventService } from './user-event.service'; +import { SpenderReportsService } from './platform/v1/spender/reports.service'; const reportsCacheBuster$ = new Subject(); @@ -55,7 +56,8 @@ export class ReportService { private approverPlatformApiService: ApproverPlatformApiService, private datePipe: DatePipe, private launchDarklyService: LaunchDarklyService, - private permissionsService: PermissionsService + private permissionsService: PermissionsService, + private spenderReportsService: SpenderReportsService ) { reportsCacheBuster$.subscribe(() => { this.userEventService.clearTaskCache(); @@ -130,18 +132,9 @@ export class ReportService { @CacheBuster({ cacheBusterNotifier: reportsCacheBuster$, }) - createDraft(report: ReportPurpose): Observable { - return this.apiService - .post('/reports', report) - .pipe(switchMap((res) => this.clearTransactionCache().pipe(map(() => res)))); - } - - @CacheBuster({ - cacheBusterNotifier: reportsCacheBuster$, - }) - create(report: ReportPurpose, expenseIds: string[]): Observable { - return this.createDraft(report).pipe( - switchMap((newReport: ReportV1) => { + create(report: ReportPurpose, expenseIds: string[]): Observable { + return this.spenderReportsService.createDraft({ data: report }).pipe( + switchMap((newReport: Report) => { const payload = { data: { id: newReport.id, diff --git a/src/app/fyle/dashboard/tasks/tasks-2.component.spec.ts b/src/app/fyle/dashboard/tasks/tasks-2.component.spec.ts index 452d9f79cb..879fd046d4 100644 --- a/src/app/fyle/dashboard/tasks/tasks-2.component.spec.ts +++ b/src/app/fyle/dashboard/tasks/tasks-2.component.spec.ts @@ -29,7 +29,6 @@ import { import { taskCtaData3, taskCtaData9 } from 'src/app/core/mock-data/task-cta.data'; import { expenseList } from 'src/app/core/mock-data/expense.data'; import { cloneDeep } from 'lodash'; -import { apiReportRes } from 'src/app/core/mock-data/api-reports.data'; import { publicAdvanceRequestRes, singleExtendedAdvReqRes } from 'src/app/core/mock-data/extended-advance-request.data'; import { expensesList, @@ -42,6 +41,10 @@ import { perDiemCategoryTransformedExpenseData, transformedExpenseData, } from 'src/app/core/mock-data/transformed-expense.data'; +import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { expectedReportsSinglePage } from 'src/app/core/mock-data/platform-report.data'; +import { apiEouRes } from 'src/app/core/mock-data/extended-org-user.data'; export function TestCases2(getTestBed) { return describe('test case set 2', () => { @@ -62,6 +65,8 @@ export function TestCases2(getTestBed) { let router: jasmine.SpyObj; let activatedRoute: jasmine.SpyObj; let networkService: jasmine.SpyObj; + let spenderReportsService: jasmine.SpyObj; + let approverReportsService: jasmine.SpyObj; beforeEach(waitForAsync(() => { const TestBed = getTestBed(); @@ -82,6 +87,8 @@ export function TestCases2(getTestBed) { router = TestBed.inject(Router) as jasmine.SpyObj; activatedRoute = TestBed.inject(ActivatedRoute) as jasmine.SpyObj; networkService = TestBed.inject(NetworkService) as jasmine.SpyObj; + spenderReportsService = TestBed.inject(SpenderReportsService) as jasmine.SpyObj; + approverReportsService = TestBed.inject(ApproverReportsService) as jasmine.SpyObj; })); describe('init():', () => { @@ -107,7 +114,6 @@ export function TestCases2(getTestBed) { expect(tasksService.getTasks).toHaveBeenCalledTimes(2); expect(tasksService.getTasks).toHaveBeenCalledWith(true, component.loadData$.getValue()); expect(component.trackTasks).toHaveBeenCalledTimes(2); - expect; expect(component.taskCount).toEqual(dashboardTasksData.length); expect(res).toEqual(dashboardTasksData); }); @@ -275,7 +281,7 @@ export function TestCases2(getTestBed) { beforeEach(() => { loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); - reportService.getMyReports.and.returnValue(of(apiReportRes)); + spenderReportsService.getAllReportsByParams.and.returnValue(of(expectedReportsSinglePage)); }); it('should get all reports and navigate to my view report page if task count is 1', fakeAsync(() => { @@ -284,10 +290,8 @@ export function TestCases2(getTestBed) { component.onSentBackReportTaskClick(taskCtaData3, mockDashboardTasksData[0]); tick(100); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Opening your report...'); - expect(reportService.getMyReports).toHaveBeenCalledOnceWith({ - queryParams: { - rp_state: 'in.(APPROVER_INQUIRY)', - }, + expect(spenderReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'eq.APPROVER_INQUIRY', offset: 0, limit: 1, }); @@ -296,7 +300,7 @@ export function TestCases2(getTestBed) { '/', 'enterprise', 'my_view_report', - { id: apiReportRes.data[0].rp_id }, + { id: expectedReportsSinglePage[0].id }, ]); })); @@ -304,7 +308,7 @@ export function TestCases2(getTestBed) { component.onSentBackReportTaskClick(taskCtaData3, dashboardTasksData[0]); tick(100); expect(loaderService.showLoader).not.toHaveBeenCalled(); - expect(reportService.getMyReports).not.toHaveBeenCalled(); + expect(spenderReportsService.getAllReportsByParams).not.toHaveBeenCalled(); expect(loaderService.hideLoader).not.toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_reports'], { queryParams: { filters: '{"state":["APPROVER_INQUIRY"]}' }, @@ -357,7 +361,8 @@ export function TestCases2(getTestBed) { beforeEach(() => { loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); - reportService.getTeamReports.and.returnValue(of(apiReportRes)); + authService.getEou.and.resolveTo(apiEouRes); + approverReportsService.getAllReportsByParams.and.returnValue(of(expectedReportsSinglePage)); }); it('should get all team reports and navigate to my view report page if task count is 1', fakeAsync(() => { @@ -366,21 +371,16 @@ export function TestCases2(getTestBed) { component.onTeamReportsTaskClick(taskCtaData3, mockDashboardTasksData[0]); tick(100); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Opening your report...'); - expect(reportService.getTeamReports).toHaveBeenCalledOnceWith({ - queryParams: { - rp_approval_state: ['in.(APPROVAL_PENDING)'], - rp_state: ['in.(APPROVER_PENDING)'], - sequential_approval_turn: ['in.(true)'], - }, - offset: 0, - limit: 1, + expect(approverReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'eq.APPROVER_PENDING', + next_approver_user_ids: `cs.[${apiEouRes.us.id}]`, }); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(router.navigate).toHaveBeenCalledOnceWith([ '/', 'enterprise', 'view_team_report', - { id: apiReportRes.data[0].rp_id, navigate_back: true }, + { id: expectedReportsSinglePage[0].id, navigate_back: true }, ]); })); @@ -388,7 +388,7 @@ export function TestCases2(getTestBed) { component.onTeamReportsTaskClick(taskCtaData3, dashboardTasksData[0]); tick(100); expect(loaderService.showLoader).not.toHaveBeenCalled(); - expect(reportService.getTeamReports).not.toHaveBeenCalled(); + expect(approverReportsService.getAllReportsByParams).not.toHaveBeenCalled(); expect(loaderService.hideLoader).not.toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'team_reports'], { queryParams: { filters: JSON.stringify({ state: ['APPROVER_PENDING'] }) }, @@ -400,7 +400,7 @@ export function TestCases2(getTestBed) { beforeEach(() => { loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); - reportService.getMyReports.and.returnValue(of(apiReportRes)); + spenderReportsService.getAllReportsByParams.and.returnValue(of(expectedReportsSinglePage)); }); it('should get all reports and navigate to my view report page if task count is 1', fakeAsync(() => { @@ -409,10 +409,8 @@ export function TestCases2(getTestBed) { component.onOpenDraftReportsTaskClick(taskCtaData3, mockDashboardTasksData[0]); tick(100); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Opening your report...'); - expect(reportService.getMyReports).toHaveBeenCalledOnceWith({ - queryParams: { - rp_state: 'in.(DRAFT)', - }, + expect(spenderReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'eq.DRAFT', offset: 0, limit: 1, }); @@ -421,7 +419,7 @@ export function TestCases2(getTestBed) { '/', 'enterprise', 'my_view_report', - { id: apiReportRes.data[0].rp_id }, + { id: expectedReportsSinglePage[0].id }, ]); })); @@ -429,7 +427,7 @@ export function TestCases2(getTestBed) { component.onOpenDraftReportsTaskClick(taskCtaData3, dashboardTasksData[0]); tick(100); expect(loaderService.showLoader).not.toHaveBeenCalled(); - expect(reportService.getMyReports).not.toHaveBeenCalled(); + expect(spenderReportsService.getAllReportsByParams).not.toHaveBeenCalled(); expect(loaderService.hideLoader).not.toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_reports'], { queryParams: { filters: JSON.stringify({ state: ['DRAFT'] }) }, diff --git a/src/app/fyle/dashboard/tasks/tasks-3.component.spec.ts b/src/app/fyle/dashboard/tasks/tasks-3.component.spec.ts index efd18d70b8..b5badd666b 100644 --- a/src/app/fyle/dashboard/tasks/tasks-3.component.spec.ts +++ b/src/app/fyle/dashboard/tasks/tasks-3.component.spec.ts @@ -14,7 +14,6 @@ import { ActivatedRoute, Router } from '@angular/router'; import { NetworkService } from 'src/app/core/services/network.service'; import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; import { finalize, of, tap } from 'rxjs'; -import { apiExtendedReportRes } from 'src/app/core/mock-data/report.data'; import { cloneDeep, noop } from 'lodash'; import { snackbarPropertiesRes2 } from 'src/app/core/mock-data/snackbar-properties.data'; import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; @@ -27,6 +26,14 @@ import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { orgSettingsPendingRestrictions } from 'src/app/core/mock-data/org-settings.data'; import { commuteDetailsResponseData } from 'src/app/core/mock-data/commute-details-response.data'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { + expectedReportsSinglePage, + expectedReportsSinglePageFiltered, + expectedReportsSinglePageSubmitted, + expectedReportsSinglePageSubmittedWithApproval, + expectedReportsSinglePageWithApproval, +} from 'src/app/core/mock-data/platform-report.data'; +import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; export function TestCases3(getTestBed) { return describe('test case set 3', () => { @@ -48,6 +55,7 @@ export function TestCases3(getTestBed) { let expensesService: jasmine.SpyObj; let orgSettingsService: jasmine.SpyObj; let spenderReportsService: jasmine.SpyObj; + let approverReportsService: jasmine.SpyObj; beforeEach(waitForAsync(() => { const TestBed = getTestBed(); @@ -70,6 +78,7 @@ export function TestCases3(getTestBed) { networkService = TestBed.inject(NetworkService) as jasmine.SpyObj; expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; spenderReportsService = TestBed.inject(SpenderReportsService) as jasmine.SpyObj; + approverReportsService = TestBed.inject(ApproverReportsService) as jasmine.SpyObj; orgSettingsService.get.and.returnValue(of(orgSettingsPendingRestrictions)); })); @@ -85,7 +94,7 @@ export function TestCases3(getTestBed) { spenderReportsService.addExpenses.and.returnValue(of(undefined)); component - .addTransactionsToReport(apiExtendedReportRes[0], ['tx5fBcPBAxLv']) + .addTransactionsToReport(expectedReportsSinglePage[0], ['tx5fBcPBAxLv']) .pipe( finalize(() => { expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); @@ -93,10 +102,10 @@ export function TestCases3(getTestBed) { ) .subscribe((res) => { expect(loaderService.showLoader).toHaveBeenCalledTimes(1); - expect(spenderReportsService.addExpenses).toHaveBeenCalledOnceWith(apiExtendedReportRes[0].rp_id, [ + expect(spenderReportsService.addExpenses).toHaveBeenCalledOnceWith(expectedReportsSinglePage[0].id, [ 'tx5fBcPBAxLv', ]); - expect(res).toEqual(apiExtendedReportRes[0]); + expect(res).toEqual(expectedReportsSinglePage[0]); done(); }); }); @@ -109,7 +118,7 @@ export function TestCases3(getTestBed) { snackbarProperties.setSnackbarProperties.and.returnValue(snackbarPropertiesRes2); const message = 'Expenses added to report successfully'; - component.showAddToReportSuccessToast({ message, report: apiExtendedReportRes[0] }); + component.showAddToReportSuccessToast({ message, report: expectedReportsSinglePage[0] }); expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { ...snackbarPropertiesRes2, panelClass: ['msb-success-with-camera-icon'], @@ -133,18 +142,18 @@ export function TestCases3(getTestBed) { describe('showOldReportsMatBottomSheet(): ', () => { beforeEach(() => { - reportService.getAllExtendedReports.and.returnValue(of(apiExtendedReportRes)); + spenderReportsService.getAllReportsByParams.and.returnValue(of(expectedReportsSinglePageWithApproval)); expensesService.getAllExpenses.and.returnValue(of([expenseData])); spyOn(component, 'showAddToReportSuccessToast'); orgSettingsService.get.and.returnValue(of(orgSettingsPendingRestrictions)); }); it('should call matBottomSheet.open and call showAddToReportSuccessToast if data.report is defined', fakeAsync(() => { - spyOn(component, 'addTransactionsToReport').and.returnValue(of(apiExtendedReportRes[0])); + spyOn(component, 'addTransactionsToReport').and.returnValue(of(expectedReportsSinglePage[0])); matBottomSheet.open.and.returnValue({ afterDismissed: () => of({ - report: apiExtendedReportRes[0], + report: expectedReportsSinglePageFiltered[0], }), } as MatBottomSheetRef); @@ -152,35 +161,31 @@ export function TestCases3(getTestBed) { tick(100); expect(matBottomSheet.open).toHaveBeenCalledOnceWith(AddTxnToReportDialogComponent as any, { - data: { openReports: apiExtendedReportRes }, + data: { openReports: expectedReportsSinglePageFiltered }, panelClass: ['mat-bottom-sheet-1'], }); expect(orgSettingsService.get).toHaveBeenCalledTimes(1); expect(expensesService.getAllExpenses).toHaveBeenCalledOnceWith(unreportedExpensesQueryParams); - expect(reportService.getAllExtendedReports).toHaveBeenCalledOnceWith({ - queryParams: { rp_state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)' }, + expect(spenderReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', }); - expect(component.addTransactionsToReport).toHaveBeenCalledOnceWith(apiExtendedReportRes[0], ['txcSFe6efB6R']); + expect(component.addTransactionsToReport).toHaveBeenCalledOnceWith(expectedReportsSinglePageFiltered[0], [ + 'txcSFe6efB6R', + ]); expect(component.showAddToReportSuccessToast).toHaveBeenCalledOnceWith({ - message: 'Expenses added to report successfully', - report: apiExtendedReportRes[0], + message: 'Expenses added to an existing draft report', + report: expectedReportsSinglePage[0], }); })); - it('should call matBottomSheet.open and call showAddToReportSuccessToast if report_approvals is defined and report is pending', fakeAsync(() => { - const mockReport = cloneDeep(apiExtendedReportRes); - mockReport[0].report_approvals = { - out3t2X258rd: { - rank: 0, - state: 'APPROVAL_PENDING', - }, - }; - reportService.getAllExtendedReports.and.returnValue(of(mockReport)); - spyOn(component, 'addTransactionsToReport').and.returnValue(of(mockReport[0])); + it('should call matBottomSheet.open and call showAddToReportSuccessToast if report.approvals is defined and report is pending', fakeAsync(() => { + const mockReport = cloneDeep(expectedReportsSinglePageSubmittedWithApproval); + spenderReportsService.getAllReportsByParams.and.returnValue(of(mockReport)); + spyOn(component, 'addTransactionsToReport').and.returnValue(of(mockReport[1])); matBottomSheet.open.and.returnValue({ afterDismissed: () => of({ - report: mockReport[0], + report: mockReport[1], }), } as MatBottomSheetRef); @@ -188,29 +193,29 @@ export function TestCases3(getTestBed) { tick(100); expect(matBottomSheet.open).toHaveBeenCalledOnceWith(AddTxnToReportDialogComponent as any, { - data: { openReports: mockReport }, + data: { openReports: expectedReportsSinglePage }, panelClass: ['mat-bottom-sheet-1'], }); expect(expensesService.getAllExpenses).toHaveBeenCalledOnceWith(unreportedExpensesQueryParams); - expect(reportService.getAllExtendedReports).toHaveBeenCalledOnceWith({ - queryParams: { rp_state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)' }, + expect(spenderReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', }); - expect(component.addTransactionsToReport).toHaveBeenCalledOnceWith(mockReport[0], ['txcSFe6efB6R']); + expect(component.addTransactionsToReport).toHaveBeenCalledOnceWith(mockReport[1], ['txcSFe6efB6R']); expect(component.showAddToReportSuccessToast).toHaveBeenCalledOnceWith({ - message: 'Expenses added to report successfully', - report: mockReport[0], + message: 'Expenses added to an existing draft report', + report: mockReport[1], }); })); - it('should call matBottomSheet.open and call showAddToReportSuccessToast if data.report is defined and rp_state is draft', fakeAsync(() => { - const mockExtendedReportRes = cloneDeep(apiExtendedReportRes); - mockExtendedReportRes[0].rp_state = 'DRAFT'; - reportService.getAllExtendedReports.and.returnValue(of(mockExtendedReportRes)); - spyOn(component, 'addTransactionsToReport').and.returnValue(of(mockExtendedReportRes[0])); + it('should call matBottomSheet.open and call showAddToReportSuccessToast if data.report is defined and report.state is draft', fakeAsync(() => { + const mockExtendedReportRes = cloneDeep(expectedReportsSinglePageWithApproval); + mockExtendedReportRes[0].state = 'DRAFT'; + spenderReportsService.getAllReportsByParams.and.returnValue(of(mockExtendedReportRes)); + spyOn(component, 'addTransactionsToReport').and.returnValue(of(mockExtendedReportRes[1])); matBottomSheet.open.and.returnValue({ afterDismissed: () => of({ - report: mockExtendedReportRes[0], + report: mockExtendedReportRes[1], }), } as MatBottomSheetRef); @@ -218,17 +223,19 @@ export function TestCases3(getTestBed) { tick(100); expect(matBottomSheet.open).toHaveBeenCalledOnceWith(AddTxnToReportDialogComponent as any, { - data: { openReports: mockExtendedReportRes }, + data: { openReports: expectedReportsSinglePageFiltered }, panelClass: ['mat-bottom-sheet-1'], }); expect(expensesService.getAllExpenses).toHaveBeenCalledOnceWith(unreportedExpensesQueryParams); - expect(reportService.getAllExtendedReports).toHaveBeenCalledOnceWith({ - queryParams: { rp_state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)' }, + expect(spenderReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', }); - expect(component.addTransactionsToReport).toHaveBeenCalledOnceWith(mockExtendedReportRes[0], ['txcSFe6efB6R']); + expect(component.addTransactionsToReport).toHaveBeenCalledOnceWith(expectedReportsSinglePage[1], [ + 'txcSFe6efB6R', + ]); expect(component.showAddToReportSuccessToast).toHaveBeenCalledOnceWith({ message: 'Expenses added to an existing draft report', - report: mockExtendedReportRes[0], + report: expectedReportsSinglePageFiltered[0], }); })); @@ -246,12 +253,12 @@ export function TestCases3(getTestBed) { tick(100); expect(matBottomSheet.open).toHaveBeenCalledOnceWith(AddTxnToReportDialogComponent as any, { - data: { openReports: apiExtendedReportRes }, + data: { openReports: expectedReportsSinglePageFiltered }, panelClass: ['mat-bottom-sheet-1'], }); expect(expensesService.getAllExpenses).not.toHaveBeenCalled(); - expect(reportService.getAllExtendedReports).toHaveBeenCalledOnceWith({ - queryParams: { rp_state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)' }, + expect(spenderReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', }); expect(component.addTransactionsToReport).not.toHaveBeenCalled(); expect(component.showAddToReportSuccessToast).not.toHaveBeenCalled(); diff --git a/src/app/fyle/dashboard/tasks/tasks.component.setup.spec.ts b/src/app/fyle/dashboard/tasks/tasks.component.setup.spec.ts index e4017c6562..0329894317 100644 --- a/src/app/fyle/dashboard/tasks/tasks.component.setup.spec.ts +++ b/src/app/fyle/dashboard/tasks/tasks.component.setup.spec.ts @@ -22,6 +22,7 @@ import { TestCases3 } from './tasks-3.component.spec'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; describe('TasksComponent', () => { const getTestBed = () => { @@ -45,7 +46,6 @@ describe('TasksComponent', () => { 'getMyReports', 'getTeamReports', 'addTransactions', - 'getAllExtendedReports', ]); const advanceRequestServiceSpy = jasmine.createSpyObj('AdvanceRequestService', ['getSpenderAdvanceRequests']); const modalControllerSpy = jasmine.createSpyObj('ModalController', ['create']); @@ -63,7 +63,11 @@ describe('TasksComponent', () => { ]); const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['showLoader', 'hideLoader']); const matBottomSheetSpy = jasmine.createSpyObj('MatBottomSheet', ['open']); - const spenderReportsServiceSpy = jasmine.createSpyObj('SpenderReportsService', ['addExpenses']); + const spenderReportsServiceSpy = jasmine.createSpyObj('SpenderReportsService', [ + 'addExpenses', + 'getAllReportsByParams', + ]); + const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', ['getAllReportsByParams']); const matSnackBarSpy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']); const snackbarPropertiesSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']); const authServiceSpy = jasmine.createSpyObj('AuthService', ['getEou']); @@ -98,6 +102,7 @@ describe('TasksComponent', () => { { provide: OrgSettingsService, useValue: orgSettingsServiceSpy }, { provide: ExpensesService, useValue: expensesServiceSpy }, { provide: SpenderReportsService, useValue: spenderReportsServiceSpy }, + { provide: ApproverReportsService, useValue: approverReportsServiceSpy }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/src/app/fyle/dashboard/tasks/tasks.component.ts b/src/app/fyle/dashboard/tasks/tasks.component.ts index ac65a01639..884951d47f 100644 --- a/src/app/fyle/dashboard/tasks/tasks.component.ts +++ b/src/app/fyle/dashboard/tasks/tasks.component.ts @@ -5,7 +5,6 @@ import { ActivatedRoute, Router } from '@angular/router'; import { ModalController, RefresherEventDetail } from '@ionic/angular'; import { Observable, BehaviorSubject, forkJoin, from, of, concat, combineLatest } from 'rxjs'; import { finalize, map, shareReplay, switchMap } from 'rxjs/operators'; -import { ExtendedReport } from 'src/app/core/models/report.model'; import { TaskCta } from 'src/app/core/models/task-cta.model'; import { TASKEVENT } from 'src/app/core/models/task-event.enum'; import { TaskFilters } from 'src/app/core/models/task-filters.model'; @@ -32,6 +31,9 @@ import { OverlayResponse } from 'src/app/core/models/overlay-response.modal'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { CommuteDetailsResponse } from 'src/app/core/models/platform/commute-details-response.model'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { Report, ReportState } from 'src/app/core/models/platform/v1/report.model'; +import { AuthService } from '../../../core/services/auth.service'; @Component({ selector: 'app-tasks', @@ -65,6 +67,8 @@ export class TasksComponent implements OnInit { private taskService: TasksService, private transactionService: TransactionService, private reportService: ReportService, + private spenderReportsService: SpenderReportsService, + private approverReportsService: ApproverReportsService, private expensesService: ExpensesService, private advanceRequestService: AdvanceRequestService, private modalController: ModalController, @@ -77,7 +81,7 @@ export class TasksComponent implements OnInit { private activatedRoute: ActivatedRoute, private networkService: NetworkService, private orgSettingsService: OrgSettingsService, - private spenderReportsService: SpenderReportsService + private authService: AuthService ) {} ngOnInit(): void { @@ -470,16 +474,18 @@ export class TasksComponent implements OnInit { onSentBackReportTaskClick(taskCta: TaskCta, task: DashboardTask): void { if (task.count === 1) { const queryParams = { - rp_state: 'in.(APPROVER_INQUIRY)', + state: `eq.${ReportState.APPROVER_INQUIRY}`, + offset: 0, + limit: 1, }; from(this.loaderService.showLoader('Opening your report...')) .pipe( - switchMap(() => this.reportService.getMyReports({ queryParams, offset: 0, limit: 1 })), + switchMap(() => this.spenderReportsService.getAllReportsByParams(queryParams)), finalize(() => this.loaderService.hideLoader()) ) .subscribe((res) => { - this.router.navigate(['/', 'enterprise', 'my_view_report', { id: res.data[0].rp_id }]); + this.router.navigate(['/', 'enterprise', 'my_view_report', { id: res[0].id }]); }); } else { this.router.navigate(['/', 'enterprise', 'my_reports'], { @@ -515,19 +521,20 @@ export class TasksComponent implements OnInit { onTeamReportsTaskClick(taskCta: TaskCta, task: DashboardTask): void { if (task.count === 1) { - const queryParams = { - rp_approval_state: ['in.(APPROVAL_PENDING)'], - rp_state: ['in.(APPROVER_PENDING)'], - sequential_approval_turn: ['in.(true)'], - }; - from(this.loaderService.showLoader('Opening your report...')) - .pipe( - switchMap(() => this.reportService.getTeamReports({ queryParams, offset: 0, limit: 1 })), - finalize(() => this.loaderService.hideLoader()) - ) - .subscribe((res) => { - this.router.navigate(['/', 'enterprise', 'view_team_report', { id: res.data[0].rp_id, navigate_back: true }]); - }); + from(this.authService.getEou()).subscribe((eou) => { + const queryParams = { + next_approver_user_ids: `cs.[${eou.us.id}]`, + state: `eq.${ReportState.APPROVER_PENDING}`, + }; + return from(this.loaderService.showLoader('Opening your report...')) + .pipe( + switchMap(() => this.approverReportsService.getAllReportsByParams(queryParams)), + finalize(() => this.loaderService.hideLoader()) + ) + .subscribe((res) => { + this.router.navigate(['/', 'enterprise', 'view_team_report', { id: res[0].id, navigate_back: true }]); + }); + }); } else { this.router.navigate(['/', 'enterprise', 'team_reports'], { queryParams: { @@ -540,16 +547,18 @@ export class TasksComponent implements OnInit { onOpenDraftReportsTaskClick(taskCta: TaskCta, task: DashboardTask): void { if (task.count === 1) { const queryParams = { - rp_state: 'in.(DRAFT)', + state: `eq.${ReportState.DRAFT}`, + offset: 0, + limit: 1, }; from(this.loaderService.showLoader('Opening your report...')) .pipe( - switchMap(() => this.reportService.getMyReports({ queryParams, offset: 0, limit: 1 })), + switchMap(() => this.spenderReportsService.getAllReportsByParams(queryParams)), finalize(() => this.loaderService.hideLoader()) ) .subscribe((res) => { - this.router.navigate(['/', 'enterprise', 'my_view_report', { id: res.data[0].rp_id }]); + this.router.navigate(['/', 'enterprise', 'my_view_report', { id: res[0].id }]); }); } else { this.router.navigate(['/', 'enterprise', 'my_reports'], { @@ -565,14 +574,14 @@ export class TasksComponent implements OnInit { this.router.navigate(['/', 'enterprise', 'potential-duplicates']); } - addTransactionsToReport(report: ExtendedReport, selectedExpensesId: string[]): Observable { + addTransactionsToReport(report: Report, selectedExpensesId: string[]): Observable { return from(this.loaderService.showLoader('Adding transaction to report')).pipe( - switchMap(() => this.spenderReportsService.addExpenses(report.rp_id, selectedExpensesId).pipe(map(() => report))), + switchMap(() => this.spenderReportsService.addExpenses(report.id, selectedExpensesId).pipe(map(() => report))), finalize(() => this.loaderService.hideLoader()) ); } - showAddToReportSuccessToast(config: { message: string; report: ExtendedReport }): void { + showAddToReportSuccessToast(config: { message: string; report: Report }): void { const toastMessageData = { message: config.message, redirectionText: 'View Report', @@ -586,7 +595,7 @@ export class TasksComponent implements OnInit { this.doRefresh(); expensesAddedToReportSnackBar.onAction().subscribe(() => { - this.router.navigate(['/', 'enterprise', 'my_view_report', { id: config.report.rp_id, navigateBack: true }]); + this.router.navigate(['/', 'enterprise', 'my_view_report', { id: config.report.id, navigateBack: true }]); }); } @@ -618,17 +627,16 @@ export class TasksComponent implements OnInit { }) ); - this.reportService - .getAllExtendedReports({ queryParams: { rp_state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)' } }) + this.spenderReportsService + .getAllReportsByParams({ state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)' }) .pipe( map((openReports) => openReports.filter( (openReport) => - // JSON.stringify(openReport.report_approvals).indexOf('APPROVAL_DONE') -> Filter report if any approver approved this report. - // Converting this object to string and checking If `APPROVAL_DONE` is present in the string, removing the report from the list - !openReport.report_approvals || - (openReport.report_approvals && - !(JSON.stringify(openReport.report_approvals).indexOf('APPROVAL_DONE') > -1)) + // (JSON.stringify(openReport.approvals.map((approval) => approval.state)) -> Filter report if any approver approved this report. + !openReport.approvals || + (openReport.approvals && + !(JSON.stringify(openReport.approvals.map((approval) => approval.state)).indexOf('APPROVAL_DONE') > -1)) ) ), switchMap((openReports) => { @@ -638,7 +646,7 @@ export class TasksComponent implements OnInit { }); return addTxnToReportDialog.afterDismissed(); }), - switchMap((data: { report: ExtendedReport }) => { + switchMap((data: { report: Report }) => { if (data && data.report) { return readyToReportExpenses$.pipe( switchMap((selectedExpensesId) => this.addTransactionsToReport(data.report, selectedExpensesId)) @@ -648,10 +656,10 @@ export class TasksComponent implements OnInit { } }) ) - .subscribe((report: ExtendedReport) => { + .subscribe((report: Report) => { if (report) { let message = ''; - if (report.rp_state.toLowerCase() === 'draft') { + if (report.state.toLowerCase() === 'draft') { message = 'Expenses added to an existing draft report'; } else { message = 'Expenses added to report successfully'; diff --git a/src/app/fyle/my-create-report/my-create-report.page.spec.ts b/src/app/fyle/my-create-report/my-create-report.page.spec.ts index e965d77d56..e28cb0f110 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.spec.ts +++ b/src/app/fyle/my-create-report/my-create-report.page.spec.ts @@ -31,6 +31,7 @@ import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expen import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { orgSettingsPendingRestrictions } from 'src/app/core/mock-data/org-settings.data'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { expectedReportsSinglePage } from '../../core/mock-data/platform-report.data'; describe('MyCreateReportPage', () => { let component: MyCreateReportPage; @@ -52,7 +53,6 @@ describe('MyCreateReportPage', () => { const orgSettingsServiceSpy = jasmine.createSpyObj('OrgSettingsService', ['get']); const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['getAllExpenses']); const reportServiceSpy = jasmine.createSpyObj('ReportService', [ - 'getMyReportsCount', 'createDraft', 'addTransactions', 'create', @@ -65,7 +65,11 @@ describe('MyCreateReportPage', () => { const storageServiceSpy = jasmine.createSpyObj('StorageService', ['get', 'set']); const refinerServiceSpy = jasmine.createSpyObj('RefinerService', ['startSurvey']); const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getAllExpenses']); - const spenderReportsServiceSpy = jasmine.createSpyObj('SpenderReportsService', ['addExpenses']); + const spenderReportsServiceSpy = jasmine.createSpyObj('SpenderReportsService', [ + 'addExpenses', + 'createDraft', + 'getReportsCount', + ]); TestBed.configureTestingModule({ declarations: [MyCreateReportPage, HumanizeCurrencyPipe], @@ -194,7 +198,7 @@ describe('MyCreateReportPage', () => { it('sendFirstReportCreated(): should set a new report if first report not created', fakeAsync(() => { storageService.get.and.resolveTo(false); - reportService.getMyReportsCount.and.returnValue(of(0)); + spenderReportsService.getReportsCount.and.returnValue(of(0)); spyOn(component, 'getTotalSelectedExpensesAmount').and.returnValue(150); component.readyToReportExpenses = [...readyToReportExpensesData, expenseData]; component.selectedElements = readyToReportExpensesData; @@ -203,7 +207,7 @@ describe('MyCreateReportPage', () => { component.sendFirstReportCreated(); tick(1000); - expect(reportService.getMyReportsCount).toHaveBeenCalledOnceWith({}); + expect(spenderReportsService.getReportsCount).toHaveBeenCalledOnceWith({}); expect(trackingService.createFirstReport).toHaveBeenCalledOnceWith({ Expense_Count: 2, Report_Value: 150, @@ -216,7 +220,7 @@ describe('MyCreateReportPage', () => { describe('ctaClickedEvent():', () => { beforeEach(() => { spyOn(component, 'sendFirstReportCreated'); - reportService.createDraft.and.returnValue(of(reportUnflattenedData)); + spenderReportsService.createDraft.and.returnValue(of(expectedReportsSinglePage[0])); }); it('should create a draft report and add transactions to it, if there are any selected expenses', () => { @@ -228,15 +232,17 @@ describe('MyCreateReportPage', () => { component.ctaClickedEvent('create_draft_report'); expect(component.sendFirstReportCreated).toHaveBeenCalledTimes(1); - expect(reportService.createDraft).toHaveBeenCalledOnceWith({ - purpose: component.reportTitle, - source: 'MOBILE', + expect(spenderReportsService.createDraft).toHaveBeenCalledOnceWith({ + data: { + purpose: component.reportTitle, + source: 'MOBILE', + }, }); expect(trackingService.createReport).toHaveBeenCalledOnceWith({ Expense_Count: 1, Report_Value: component.selectedTotalAmount, }); - expect(spenderReportsService.addExpenses).toHaveBeenCalledOnceWith(reportUnflattenedData.id, [ + expect(spenderReportsService.addExpenses).toHaveBeenCalledOnceWith(expectedReportsSinglePage[0].id, [ readyToReportExpensesData[0].id, ]); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_reports']); @@ -249,9 +255,11 @@ describe('MyCreateReportPage', () => { component.ctaClickedEvent('create_draft_report'); expect(component.sendFirstReportCreated).toHaveBeenCalledTimes(1); - expect(reportService.createDraft).toHaveBeenCalledOnceWith({ - purpose: component.reportTitle, - source: 'MOBILE', + expect(spenderReportsService.createDraft).toHaveBeenCalledOnceWith({ + data: { + purpose: component.reportTitle, + source: 'MOBILE', + }, }); expect(trackingService.createReport).toHaveBeenCalledOnceWith({ Expense_Count: 0, @@ -261,7 +269,7 @@ describe('MyCreateReportPage', () => { }); it('should create report', () => { - reportService.create.and.returnValue(of(reportUnflattenedData)); + reportService.create.and.returnValue(of(expectedReportsSinglePage[0])); component.selectedElements = cloneDeep(readyToReportExpensesData); fixture.detectChanges(); diff --git a/src/app/fyle/my-create-report/my-create-report.page.ts b/src/app/fyle/my-create-report/my-create-report.page.ts index 5da50dbd11..6a7de043db 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.ts +++ b/src/app/fyle/my-create-report/my-create-report.page.ts @@ -4,7 +4,6 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Observable, Subscription, from, noop, of } from 'rxjs'; import { finalize, map, shareReplay, switchMap, tap } from 'rxjs/operators'; import { Expense } from 'src/app/core/models/expense.model'; -import { ReportV1 } from 'src/app/core/models/report-v1.model'; import { CurrencyService } from 'src/app/core/services/currency.service'; import { LoaderService } from 'src/app/core/services/loader.service'; import { RefinerService } from 'src/app/core/services/refiner.service'; @@ -16,6 +15,7 @@ import { Expense as PlatformExpense } from '../../core/models/platform/v1/expens import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { Report } from '../../core/models/platform/v1/report.model'; @Component({ selector: 'app-my-create-report', templateUrl: './my-create-report.page.html', @@ -88,7 +88,7 @@ export class MyCreateReportPage implements OnInit { const isFirstReportCreated = await this.storageService.get('isFirstReportCreated'); if (!isFirstReportCreated) { - this.reportService.getMyReportsCount({}).subscribe(async (allReportsCount) => { + this.spenderReportsService.getReportsCount({}).subscribe(async (allReportsCount) => { if (allReportsCount === 0) { const expenses = this.readyToReportExpenses.filter((expense) => this.selectedElements.includes(expense)); const expenesIDs = expenses.map((expense) => expense.id); @@ -123,8 +123,8 @@ export class MyCreateReportPage implements OnInit { if (reportActionType === 'create_draft_report') { this.saveDraftReportLoading = true; - return this.reportService - .createDraft(report) + return this.spenderReportsService + .createDraft({ data: report }) .pipe( tap(() => this.trackingService.createReport({ @@ -132,7 +132,7 @@ export class MyCreateReportPage implements OnInit { Report_Value: this.selectedTotalAmount, }) ), - switchMap((report: ReportV1) => { + switchMap((report: Report) => { if (expenseIDs.length) { return this.spenderReportsService.addExpenses(report.id, expenseIDs).pipe(map(() => report)); } else { diff --git a/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.html b/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.html index f0f87e0172..8da31cda7a 100644 --- a/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.html +++ b/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.html @@ -30,21 +30,21 @@ -
{{ report.rp_purpose }}
+
{{ report.purpose }}
- {{ report.rp_num_transactions }} Expense{{ report.rp_num_transactions > 1 ? 's' : '' }} + {{ report.num_expenses }} Expense{{ report.num_expenses > 1 ? 's' : '' }}
{{ reportCurrencySymbol }} {{ - report.rp_amount || 0 | humanizeCurrency : report.rp_currency : true + report.amount || 0 | humanizeCurrency : report.currency : true }}
-
- {{ report.rp_state | reportState : data.isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }} +
+ {{ report.state | reportState : data.isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }}
diff --git a/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.spec.ts b/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.spec.ts index 0612a3497b..0ea0386428 100644 --- a/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.spec.ts +++ b/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.spec.ts @@ -14,6 +14,7 @@ import { HumanizeCurrencyPipe } from 'src/app/shared/pipes/humanize-currency.pip import { ReportState } from 'src/app/shared/pipes/report-state.pipe'; import { SnakeCaseToSpaceCase } from 'src/app/shared/pipes/snake-case-to-space-case.pipe'; import { AddTxnToReportDialogComponent } from './add-txn-to-report-dialog.component'; +import { expectedReportsSinglePage } from 'src/app/core/mock-data/platform-report.data'; describe('AddTxnToReportDialogComponent', () => { let component: AddTxnToReportDialogComponent; @@ -52,7 +53,7 @@ describe('AddTxnToReportDialogComponent', () => { }, { provide: MAT_BOTTOM_SHEET_DATA, - useValue: { openReports: apiExtendedReportRes, isNewReportsFlowEnabled: true }, + useValue: { openReports: expectedReportsSinglePage, isNewReportsFlowEnabled: true }, }, ], }).compileComponents(); @@ -80,8 +81,8 @@ describe('AddTxnToReportDialogComponent', () => { it('addTransactionToReport(): should add txn to report', () => { matBottomsheet.dismiss.and.callThrough(); - component.addTransactionToReport(apiExtendedReportRes[0]); - expect(matBottomsheet.dismiss).toHaveBeenCalledOnceWith({ report: apiExtendedReportRes[0] }); + component.addTransactionToReport(expectedReportsSinglePage[0]); + expect(matBottomsheet.dismiss).toHaveBeenCalledOnceWith({ report: expectedReportsSinglePage[0] }); }); it('onClickCreateReportTask(): should navigate to create report page', () => { @@ -94,23 +95,23 @@ describe('AddTxnToReportDialogComponent', () => { }); it('should display report information correctly', () => { - component.openReports = [apiExtendedReportRes[0]]; + component.openReports = [expectedReportsSinglePage[0]]; fixture.detectChanges(); expect(getTextContent(getElementBySelector(fixture, '.report-list--purpose'))).toEqual('#8: Jan 2023'); - expect(getTextContent(getElementBySelector(fixture, '.report-list--count'))).toEqual('1 Expense'); + expect(getTextContent(getElementBySelector(fixture, '.report-list--count'))).toEqual('0 Expense'); expect(getTextContent(getElementBySelector(fixture, '.report-list--currency'))).toEqual('$'); - expect(getTextContent(getElementBySelector(fixture, '.report-list--amount'))).toEqual('116.90'); - expect(getTextContent(getElementBySelector(fixture, '.report-list--state'))).toEqual('Submitted'); + expect(getTextContent(getElementBySelector(fixture, '.report-list--amount'))).toEqual('100.00'); + expect(getTextContent(getElementBySelector(fixture, '.report-list--state'))).toEqual('Draft'); }); it('should call addTransactionToReport() when clicked', () => { spyOn(component, 'addTransactionToReport'); - component.openReports = [apiExtendedReportRes[0]]; + component.openReports = [expectedReportsSinglePage]; const reportCard = getElementBySelector(fixture, '[data-testid="report"]') as HTMLElement; click(reportCard); - expect(component.addTransactionToReport).toHaveBeenCalledOnceWith(apiExtendedReportRes[0]); + expect(component.addTransactionToReport).toHaveBeenCalledOnceWith(expectedReportsSinglePage[0]); }); it('should call closeAddToReportModal() when clicked', () => { diff --git a/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.ts b/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.ts index 919be030a0..1eb4e95ba0 100644 --- a/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.ts +++ b/src/app/fyle/my-expenses/add-txn-to-report-dialog/add-txn-to-report-dialog.component.ts @@ -4,6 +4,7 @@ import { MatBottomSheet, MAT_BOTTOM_SHEET_DATA } from '@angular/material/bottom- import { ExtendedReport } from 'src/app/core/models/report.model'; import { CurrencyService } from 'src/app/core/services/currency.service'; import { Router } from '@angular/router'; +import { Report } from 'src/app/core/models/platform/v1/report.model'; @Component({ selector: 'app-add-txn-to-report-dialog', templateUrl: './add-txn-to-report-dialog.component.html', @@ -21,20 +22,20 @@ export class AddTxnToReportDialogComponent implements OnInit { private router: Router ) {} - closeAddToReportModal() { + closeAddToReportModal(): void { this.matBottomsheet.dismiss(); } - onClickCreateReportTask() { + onClickCreateReportTask(): void { this.matBottomsheet.dismiss(); this.router.navigate(['/', 'enterprise', 'my_create_report']); } - addTransactionToReport(report: ExtendedReport) { + addTransactionToReport(report: Report): void { this.matBottomsheet.dismiss({ report }); } - ngOnInit() { + ngOnInit(): void { this.currencyService.getHomeCurrency().subscribe((homeCurrency) => { this.reportCurrencySymbol = getCurrencySymbol(homeCurrency, 'wide'); }); diff --git a/src/app/fyle/my-expenses/my-expenses.page.spec.ts b/src/app/fyle/my-expenses/my-expenses.page.spec.ts index 3c9fa79ce9..6a4d6ee673 100644 --- a/src/app/fyle/my-expenses/my-expenses.page.spec.ts +++ b/src/app/fyle/my-expenses/my-expenses.page.spec.ts @@ -58,14 +58,17 @@ import { addExpenseToReportModalParams2, modalControllerParams, modalControllerParams2, - newReportModalParams, newReportModalParams2, openFromComponentConfig, popoverControllerParams, } from 'src/app/core/mock-data/modal-controller.data'; import { fyModalProperties } from 'src/app/core/mock-data/model-properties.data'; import { mileagePerDiemPlatformCategoryData } from 'src/app/core/mock-data/org-category.data'; -import { orgSettingsParamsWithSimplifiedReport, orgSettingsRes } from 'src/app/core/mock-data/org-settings.data'; +import { + orgSettingsParamsWithSimplifiedReport, + orgSettingsPendingRestrictions, + orgSettingsRes, +} from 'src/app/core/mock-data/org-settings.data'; import { orgUserSettingsData } from 'src/app/core/mock-data/org-user-settings.data'; import { apiExpenses1, @@ -75,7 +78,7 @@ import { perDiemExpenseWithSingleNumDays, } from 'src/app/core/mock-data/platform/v1/expense.data'; import { reportUnflattenedData } from 'src/app/core/mock-data/report-v1.data'; -import { apiExtendedReportRes, expectedReportSingleResponse } from 'src/app/core/mock-data/report.data'; +import { expectedReportSingleResponse } from 'src/app/core/mock-data/report.data'; import { selectedFilters1, selectedFilters2 } from 'src/app/core/mock-data/selected-filters.data'; import { snackbarPropertiesRes, @@ -125,6 +128,12 @@ import { MyExpensesPage } from './my-expenses.page'; import { MyExpensesService } from './my-expenses.service'; import { completeStats, incompleteStats } from 'src/app/core/mock-data/platform/v1/expenses-stats.data'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { + expectedReportsSinglePage, + expectedReportsSinglePageFiltered, + expectedReportsSinglePageSubmitted, + expectedReportsSinglePageWithApproval, +} from 'src/app/core/mock-data/platform-report.data'; describe('MyExpensesV2Page', () => { let component: MyExpensesPage; @@ -257,7 +266,10 @@ describe('MyExpensesV2Page', () => { const popupServiceSpy = jasmine.createSpyObj('PopupService', ['showPopup']); const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['create']); const snackbarPropertiesSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']); - const spenderReportsServiceSpy = jasmine.createSpyObj('SpenderReportsService', ['addExpenses']); + const spenderReportsServiceSpy = jasmine.createSpyObj('SpenderReportsService', [ + 'addExpenses', + 'getAllReportsByParams', + ]); const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', [ 'getExpensesCount', 'getExpenses', @@ -475,7 +487,7 @@ describe('MyExpensesV2Page', () => { expensesService.getExpensesCount.and.returnValue(of(10)); expensesService.getExpenses.and.returnValue(of(apiExpenses1)); - reportService.getAllExtendedReports.and.returnValue(of(apiExtendedReportRes)); + spenderReportsService.getAllReportsByParams.and.returnValue(of(expectedReportsSinglePageWithApproval)); spyOn(component, 'doRefresh'); spyOn(component, 'backButtonAction'); @@ -603,6 +615,15 @@ describe('MyExpensesV2Page', () => { expect(component.expensesTaskCount).toBe(10); })); + it('should set restrictPendingTransactionsEnabled to true when orgSettings.pending_cct_expense_restriction is true', fakeAsync(() => { + orgSettingsService.get.and.returnValue(of(orgSettingsPendingRestrictions)); + + component.ionViewWillEnter(); + tick(500); + + expect(component.restrictPendingTransactionsEnabled).toBeTrue(); + })); + it('should set isNewReportFlowEnabled to true if simplified_report_closure_settings is defined ', fakeAsync(() => { orgSettingsService.get.and.returnValue(of(orgSettingsParamsWithSimplifiedReport)); @@ -914,30 +935,25 @@ describe('MyExpensesV2Page', () => { component.ionViewWillEnter(); tick(500); - expect(reportService.getAllExtendedReports).toHaveBeenCalledOnceWith({ - queryParams: { - rp_state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', - }, + expect(spenderReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', }); component.openReports$.subscribe((openReports) => { - expect(openReports).toEqual(apiExtendedReportRes); + expect(openReports).toEqual(expectedReportsSinglePageFiltered); }); expect(component.doRefresh).toHaveBeenCalledTimes(1); })); - it('should set openReports$ and call doRefresh if report_approvals is defined', fakeAsync(() => { - const extendedReportResWithReportApproval = [expectedReportSingleResponse]; - reportService.getAllExtendedReports.and.returnValue(of(extendedReportResWithReportApproval)); + it('should set openReports$ and call doRefresh if report.approvals is defined', fakeAsync(() => { + spenderReportsService.getAllReportsByParams.and.returnValue(of(expectedReportsSinglePageWithApproval)); component.ionViewWillEnter(); tick(500); - expect(reportService.getAllExtendedReports).toHaveBeenCalledOnceWith({ - queryParams: { - rp_state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', - }, + expect(spenderReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ + state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)', }); component.openReports$.subscribe((openReports) => { - expect(openReports).toEqual(extendedReportResWithReportApproval); + expect(openReports).toEqual(expectedReportsSinglePageFiltered); }); expect(component.doRefresh).toHaveBeenCalledTimes(1); })); @@ -2237,7 +2253,7 @@ describe('MyExpensesV2Page', () => { 'onDidDismiss', ]); addExpenseToNewReportModalSpy.onDidDismiss.and.resolveTo({ - data: { report: apiExtendedReportRes[0], message: 'new report is created' }, + data: { report: expectedReportsSinglePage[0], message: 'new report is created' }, }); modalController.create.and.resolveTo(addExpenseToNewReportModalSpy); modalProperties.getModalDefaultProperties.and.returnValue(fyModalProperties); @@ -2247,7 +2263,7 @@ describe('MyExpensesV2Page', () => { tick(100); expect(modalController.create).toHaveBeenCalledOnceWith(newReportModalParams2); expect(component.showAddToReportSuccessToast).toHaveBeenCalledOnceWith({ - report: apiExtendedReportRes[0], + report: expectedReportsSinglePage[0], message: 'new report is created', }); })); @@ -2401,7 +2417,7 @@ describe('MyExpensesV2Page', () => { it('should navigate to my_view_report and open matSnackbar', () => { component.showAddToReportSuccessToast({ message: 'Expense added to report successfully', - report: apiExtendedReportRes[0], + report: expectedReportsSinglePage[0], }); expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { @@ -2431,7 +2447,7 @@ describe('MyExpensesV2Page', () => { it('should navigate to my_view_report with newly created report id in case of adding it to new report and open matSnackbar', () => { component.showAddToReportSuccessToast({ message: 'Expense added to report successfully', - report: reportUnflattenedData, + report: expectedReportsSinglePage[0], }); expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { @@ -2454,7 +2470,7 @@ describe('MyExpensesV2Page', () => { '/', 'enterprise', 'my_view_report', - { id: 'rp6LK3ghVatB', navigateBack: true }, + { id: 'rprAfNrce73O', navigateBack: true }, ]); }); }); @@ -2463,14 +2479,14 @@ describe('MyExpensesV2Page', () => { loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(true); - spenderReportsService.addExpenses.and.returnValue(of()); + spenderReportsService.addExpenses.and.returnValue(of(null)); component - .addTransactionsToReport(apiExtendedReportRes[0], ['tx5fBcPBAxLv']) + .addTransactionsToReport(expectedReportsSinglePage[0], ['tx5fBcPBAxLv']) .pipe( tap((updatedReport) => { expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Adding transaction to report'); expect(spenderReportsService.addExpenses).toHaveBeenCalledOnceWith('rprAfNrce73O', ['tx5fBcPBAxLv']); - expect(updatedReport).toEqual(apiExtendedReportRes[0]); + expect(updatedReport).toEqual(expectedReportsSinglePage[0]); }), finalize(() => { expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); @@ -2484,40 +2500,41 @@ describe('MyExpensesV2Page', () => { beforeEach(() => { component.selectedElements = apiExpenses1; component.isNewReportsFlowEnabled = true; - component.openReports$ = of(apiExtendedReportRes); + component.openReports$ = of(expectedReportsSinglePage); sharedExpenseService.getReportableExpenses.and.returnValue(apiExpenses1); spyOn(component, 'showAddToReportSuccessToast'); }); it('should call matBottomSheet.open and call showAddToReportSuccessToast if data.report is defined', () => { - spyOn(component, 'addTransactionsToReport').and.returnValue(of(apiExtendedReportRes[0])); + component.openReports$ = of(expectedReportsSinglePageSubmitted); + spyOn(component, 'addTransactionsToReport').and.returnValue(of(expectedReportsSinglePageSubmitted[2])); matBottomsheet.open.and.returnValue({ afterDismissed: () => of({ - report: apiExtendedReportRes[0], + report: expectedReportsSinglePageSubmitted[2], }), } as MatBottomSheetRef); component.showOldReportsMatBottomSheet(); expect(matBottomsheet.open).toHaveBeenCalledOnceWith(AddTxnToReportDialogComponent, { - data: { openReports: apiExtendedReportRes, isNewReportsFlowEnabled: true }, + data: { openReports: expectedReportsSinglePageSubmitted, isNewReportsFlowEnabled: true }, panelClass: ['mat-bottom-sheet-1'], }); - expect(component.addTransactionsToReport).toHaveBeenCalledOnceWith(apiExtendedReportRes[0], [ + expect(component.addTransactionsToReport).toHaveBeenCalledOnceWith(expectedReportsSinglePageSubmitted[2], [ 'txDDLtRaflUW', 'tx5WDG9lxBDT', ]); expect(component.showAddToReportSuccessToast).toHaveBeenCalledOnceWith({ message: 'Expenses added to report successfully', - report: apiExtendedReportRes[0], + report: expectedReportsSinglePageSubmitted[2], }); }); it('should call matBottomSheet.open and call showAddToReportSuccessToast if data.report is defined and rp_state is draft', () => { - const mockReportData = cloneDeep(apiExtendedReportRes); - mockReportData[0].rp_state = 'DRAFT'; + const mockReportData = cloneDeep(expectedReportsSinglePage); + mockReportData[0].state = 'DRAFT'; component.openReports$ = of(mockReportData); spyOn(component, 'addTransactionsToReport').and.returnValue(of(mockReportData[0])); matBottomsheet.open.and.returnValue({ @@ -2554,7 +2571,7 @@ describe('MyExpensesV2Page', () => { component.showOldReportsMatBottomSheet(); expect(matBottomsheet.open).toHaveBeenCalledOnceWith(AddTxnToReportDialogComponent, { - data: { openReports: apiExtendedReportRes, isNewReportsFlowEnabled: true }, + data: { openReports: expectedReportsSinglePage, isNewReportsFlowEnabled: true }, panelClass: ['mat-bottom-sheet-1'], }); diff --git a/src/app/fyle/my-expenses/my-expenses.page.ts b/src/app/fyle/my-expenses/my-expenses.page.ts index 6eeb3df55a..eff4a8e406 100644 --- a/src/app/fyle/my-expenses/my-expenses.page.ts +++ b/src/app/fyle/my-expenses/my-expenses.page.ts @@ -37,8 +37,6 @@ import { ExpenseFilters } from 'src/app/core/models/platform/expense-filters.mod import { PlatformCategory } from 'src/app/core/models/platform/platform-category.model'; import { Expense as PlatformExpense } from 'src/app/core/models/platform/v1/expense.model'; import { GetExpenseQueryParam } from 'src/app/core/models/platform/v1/get-expenses-query.model'; -import { ReportV1 } from 'src/app/core/models/report-v1.model'; -import { ExtendedReport } from 'src/app/core/models/report.model'; import { UniqueCardStats } from 'src/app/core/models/unique-cards-stats.model'; import { UniqueCards } from 'src/app/core/models/unique-cards.model'; import { Transaction } from 'src/app/core/models/v1/transaction.model'; @@ -78,6 +76,7 @@ import { HeaderState } from '../../shared/components/fy-header/header-state.enum import { AddTxnToReportDialogComponent } from './add-txn-to-report-dialog/add-txn-to-report-dialog.component'; import { MyExpensesService } from './my-expenses.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { Report } from 'src/app/core/models/platform/v1/report.model'; @Component({ selector: 'app-my-expenses', @@ -161,7 +160,7 @@ export class MyExpensesPage implements OnInit { isSearchBarFocused = false; - openReports$: Observable; + openReports$: Observable; homeCurrencySymbol: string; @@ -471,7 +470,7 @@ export class MyExpensesPage implements OnInit { this.isNewReportsFlowEnabled = orgSettings?.simplified_report_closure_settings?.enabled || false; this.restrictPendingTransactionsEnabled = (orgSettings?.corporate_credit_card_settings?.enabled && - orgSettings?.pending_cct_expense_restriction?.enabled) || + orgSettings.pending_cct_expense_restriction?.enabled) || false; }); @@ -693,17 +692,16 @@ export class MyExpensesPage implements OnInit { this.isLoading = false; }, 500); - const queryParams = { rp_state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)' }; + const queryParams = { state: 'in.(DRAFT,APPROVER_PENDING,APPROVER_INQUIRY)' }; - this.openReports$ = this.reportService.getAllExtendedReports({ queryParams }).pipe( + this.openReports$ = this.spenderReportsService.getAllReportsByParams(queryParams).pipe( map((openReports) => openReports.filter( (openReport) => // JSON.stringify(openReport.report_approvals).indexOf('APPROVAL_DONE') -> Filter report if any approver approved this report. - // Converting this object to string and checking If `APPROVAL_DONE` is present in the string, removing the report from the list - !openReport.report_approvals || - (openReport.report_approvals && - !(JSON.stringify(openReport.report_approvals).indexOf('APPROVAL_DONE') > -1)) + !openReport.approvals || + (openReport.approvals && + !(JSON.stringify(openReport.approvals.map((approval) => approval.state)).indexOf('APPROVAL_DONE') > -1)) ) ) ); @@ -1186,7 +1184,7 @@ export class MyExpensesPage implements OnInit { await addExpenseToNewReportModal.present(); const { data } = (await addExpenseToNewReportModal.onDidDismiss()) as { - data: { report: ExtendedReport; message: string }; + data: { report: Report; message: string }; }; if (data && data.report) { @@ -1320,7 +1318,7 @@ export class MyExpensesPage implements OnInit { } } - showAddToReportSuccessToast(config: { message: string; report: ExtendedReport | ReportV1 }): void { + showAddToReportSuccessToast(config: { message: string; report: Report }): void { const toastMessageData = { message: config.message, redirectionText: 'View Report', @@ -1338,14 +1336,14 @@ export class MyExpensesPage implements OnInit { expensesAddedToReportSnackBar.onAction().subscribe(() => { // Mixed data type as CREATE report and GET report API returns different responses - const reportId = (config.report as ExtendedReport).rp_id || (config.report as ReportV1).id; + const reportId = config.report.id; this.router.navigate(['/', 'enterprise', 'my_view_report', { id: reportId, navigateBack: true }]); }); } - addTransactionsToReport(report: ExtendedReport, selectedExpensesId: string[]): Observable { + addTransactionsToReport(report: Report, selectedExpensesId: string[]): Observable { return from(this.loaderService.showLoader('Adding transaction to report')).pipe( - switchMap(() => this.spenderReportsService.addExpenses(report.rp_id, selectedExpensesId).pipe(map(() => report))), + switchMap(() => this.spenderReportsService.addExpenses(report.id, selectedExpensesId).pipe(map(() => report))), finalize(() => this.loaderService.hideLoader()) ); } @@ -1364,7 +1362,7 @@ export class MyExpensesPage implements OnInit { data: { openReports, isNewReportsFlowEnabled: this.isNewReportsFlowEnabled }, panelClass: ['mat-bottom-sheet-1'], }); - return addTxnToReportDialog.afterDismissed() as Observable<{ report: ExtendedReport }>; + return addTxnToReportDialog.afterDismissed() as Observable<{ report: Report }>; }), switchMap((data) => { if (data && data.report) { @@ -1374,10 +1372,10 @@ export class MyExpensesPage implements OnInit { } }) ) - .subscribe((report: ExtendedReport) => { + .subscribe((report: Report) => { if (report) { let message = ''; - if (report.rp_state.toLowerCase() === 'draft') { + if (report.state.toLowerCase() === 'draft') { message = 'Expenses added to an existing draft report'; } else { message = 'Expenses added to report successfully'; diff --git a/src/app/shared/components/create-new-report-v2/create-new-report.component.spec.ts b/src/app/shared/components/create-new-report-v2/create-new-report.component.spec.ts index eda8331b9f..f0f92390a2 100644 --- a/src/app/shared/components/create-new-report-v2/create-new-report.component.spec.ts +++ b/src/app/shared/components/create-new-report-v2/create-new-report.component.spec.ts @@ -17,18 +17,10 @@ import { orgData1 } from 'src/app/core/mock-data/org.data'; import { expenseFieldsMapResponse2 } from 'src/app/core/mock-data/expense-fields-map.data'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FyCurrencyPipe } from '../../pipes/fy-currency.pipe'; -import { - apiExpenseRes, - expenseData1, - splitExpData, - expenseList2, - expenseList, -} from 'src/app/core/mock-data/expense.data'; import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core'; -import { Expense } from 'src/app/core/models/expense.model'; -import { reportUnflattenedData, reportUnflattenedData2 } from 'src/app/core/mock-data/report-v1.data'; import { apiExpenses1, nonReimbursableExpense } from 'src/app/core/mock-data/platform/v1/expense.data'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { expectedReportsSinglePage } from 'src/app/core/mock-data/platform-report.data'; describe('CreateNewReportComponent', () => { let component: CreateNewReportComponent; @@ -53,7 +45,7 @@ describe('CreateNewReportComponent', () => { refinerService = jasmine.createSpyObj('RefinerService', ['startSurvey']); currencyService = jasmine.createSpyObj('CurrencyService', ['getHomeCurrency']); expenseFieldsService = jasmine.createSpyObj('ExpenseFieldsService', ['getAllMap']); - spenderReportsService = jasmine.createSpyObj('SpenderReportsService', ['addExpenses']); + spenderReportsService = jasmine.createSpyObj('SpenderReportsService', ['addExpenses', 'createDraft']); const humanizeCurrencyPipeSpy = jasmine.createSpyObj('HumanizeCurrency', ['transform']); const fyCurrencyPipeSpy = jasmine.createSpyObj('FyCurrencyPipe', ['transform']); @@ -91,7 +83,7 @@ describe('CreateNewReportComponent', () => { currencyService.getHomeCurrency.and.returnValue(of(orgData1[0].currency)); expenseFieldsService.getAllMap.and.returnValue(of(expenseFieldsMapResponse2)); - reportService.createDraft.and.returnValue(of(reportUnflattenedData2)); + spenderReportsService.createDraft.and.returnValue(of(expectedReportsSinglePage[0])); fixture = TestBed.createComponent(CreateNewReportComponent); component = fixture.componentInstance; component.selectedElements = apiExpenses1; @@ -202,16 +194,18 @@ describe('CreateNewReportComponent', () => { it('should create a new draft report with the title and add transactions', fakeAsync(() => { component.reportTitle = '#3 : Mar 2023'; - const reportID = 'rp5eUkeNm9wB'; + const reportID = 'rprAfNrce73O'; const txns = ['txDDLtRaflUW', 'tx5WDG9lxBDT']; const reportParam = { - purpose: '#3 : Mar 2023', - source: 'MOBILE', + data: { + purpose: '#3 : Mar 2023', + source: 'MOBILE', + }, }; const Expense_Count = txns.length; const Report_Value = 0; - const report = reportUnflattenedData2; - reportService.createDraft.and.returnValue(of(reportUnflattenedData2)); + const report = expectedReportsSinglePage[0]; + spenderReportsService.createDraft.and.returnValue(of(expectedReportsSinglePage[0])); spenderReportsService.addExpenses.and.returnValue(of(undefined)); component.ctaClickedEvent('create_draft_report'); fixture.detectChanges(); @@ -219,7 +213,7 @@ describe('CreateNewReportComponent', () => { expect(component.saveDraftReportLoader).toBeFalse(); expect(component.showReportNameError).toBeFalse(); expect(trackingService.createReport).toHaveBeenCalledOnceWith({ Expense_Count, Report_Value }); - expect(reportService.createDraft).toHaveBeenCalledOnceWith(reportParam); + expect(spenderReportsService.createDraft).toHaveBeenCalledOnceWith(reportParam); expect(spenderReportsService.addExpenses).toHaveBeenCalledOnceWith(reportID, txns); expect(component.saveDraftReportLoader).toBeFalse(); expect(modalController.dismiss).toHaveBeenCalledOnceWith({ @@ -233,20 +227,22 @@ describe('CreateNewReportComponent', () => { component.reportTitle = '#3 : Mar 2023'; const tnxs = []; const reportParam = { - purpose: '#3 : Mar 2023', - source: 'MOBILE', + data: { + purpose: '#3 : Mar 2023', + source: 'MOBILE', + }, }; const Expense_Count = tnxs.length; const Report_Value = 0; - const report = reportUnflattenedData2; - reportService.createDraft.and.returnValue(of(reportUnflattenedData2)); + const report = expectedReportsSinglePage[0]; + spenderReportsService.createDraft.and.returnValue(of(expectedReportsSinglePage[0])); component.ctaClickedEvent('create_draft_report'); fixture.detectChanges(); tick(500); expect(component.saveDraftReportLoader).toBeFalse(); expect(component.showReportNameError).toBeFalse(); expect(trackingService.createReport).toHaveBeenCalledOnceWith({ Expense_Count, Report_Value }); - expect(reportService.createDraft).toHaveBeenCalledOnceWith(reportParam); + expect(spenderReportsService.createDraft).toHaveBeenCalledOnceWith(reportParam); expect(component.saveDraftReportLoader).toBeFalse(); expect(modalController.dismiss).toHaveBeenCalledOnceWith({ report, @@ -262,8 +258,8 @@ describe('CreateNewReportComponent', () => { }; const txnIds = ['txDDLtRaflUW', 'tx5WDG9lxBDT']; - const report = reportUnflattenedData2; - reportService.create.and.returnValue(of(reportUnflattenedData2)); + const report = expectedReportsSinglePage[0]; + reportService.create.and.returnValue(of(expectedReportsSinglePage[0])); component.ctaClickedEvent('submit_report'); fixture.detectChanges(); tick(500); diff --git a/src/app/shared/components/create-new-report-v2/create-new-report.component.ts b/src/app/shared/components/create-new-report-v2/create-new-report.component.ts index 1442fee016..63f42bea70 100644 --- a/src/app/shared/components/create-new-report-v2/create-new-report.component.ts +++ b/src/app/shared/components/create-new-report-v2/create-new-report.component.ts @@ -10,8 +10,8 @@ import { TrackingService } from 'src/app/core/services/tracking.service'; import { RefinerService } from 'src/app/core/services/refiner.service'; import { CurrencyService } from 'src/app/core/services/currency.service'; import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; -import { ReportV1 } from 'src/app/core/models/report-v1.model'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { Report } from 'src/app/core/models/platform/v1/report.model'; @Component({ selector: 'app-create-new-report', @@ -120,8 +120,8 @@ export class CreateNewReportComponent implements OnInit { const txnIds = this.selectedElements.map((expense) => expense.id); if (reportActionType === 'create_draft_report') { this.saveDraftReportLoader = true; - return this.reportService - .createDraft(report) + return this.spenderReportsService + .createDraft({ data: report }) .pipe( tap(() => this.trackingService.createReport({ @@ -129,7 +129,7 @@ export class CreateNewReportComponent implements OnInit { Report_Value: this.selectedTotalAmount, }) ), - switchMap((report: ReportV1) => { + switchMap((report: Report) => { if (txnIds.length > 0) { return this.spenderReportsService.addExpenses(report.id, txnIds).pipe(map(() => report)); } else { diff --git a/src/app/shared/components/create-new-report/create-new-report.component.html b/src/app/shared/components/create-new-report/create-new-report.component.html deleted file mode 100644 index d207db1458..0000000000 --- a/src/app/shared/components/create-new-report/create-new-report.component.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - -
-
Create new report
-
- {{ selectedElements.length }} {{ selectedElements.length > 1 ? 'Expenses' : 'Expense' }} - - {{ selectedTotalAmount || 0 | humanizeCurrency : homeCurrency }} -
-
-
- - - - - - -
-
- - -
-
-
-
- Report Name - * -
- -
-
Please enter report name.
-
- -
EXPENSES
-
- Select all -
- -
- - -
-
-
- - - -
- - -
-
-
diff --git a/src/app/shared/components/create-new-report/create-new-report.component.scss b/src/app/shared/components/create-new-report/create-new-report.component.scss deleted file mode 100644 index 5b8bd86520..0000000000 --- a/src/app/shared/components/create-new-report/create-new-report.component.scss +++ /dev/null @@ -1,154 +0,0 @@ -@import '../../../../theme/colors.scss'; - -.create-new-report { - &--title-container { - margin-left: -15%; - color: $black; - padding: 16px; - } - - &--title { - font-size: 20px; - line-height: 1.28; - } - - &--sub-title { - font-size: 14px; - line-height: 1.28; - margin-top: 4px; - } - - &--body { - background-color: $grey-4; - padding-bottom: 200px; - } - - &--name { - padding: 16px; - background-color: $pure-white; - } - - &--mandatory { - color: $brand-primary; - } - - &--text { - border-bottom: 1px solid $grey-lighter; - - &__invalid { - border-bottom: 1px solid $red-2; - } - } - - &--text-label { - margin: 0 8px 0 0; - max-width: 90%; - min-width: 120px; - font-size: 12px; - color: $black-light; - line-height: 1.35; - white-space: nowrap; - font-weight: 400; - } - - &--text-input { - border: 0; - font-size: 14px; - font-weight: 400; - height: 18px; - line-height: 1.3; - color: $blue-black; - width: 100%; - margin: 6px 0; - padding: 0; - } - - &--error-message { - margin-top: 2px; - color: $red; - } - - &--heading { - color: $grey-light; - font-size: 14px; - padding: 16px 16px; - background-color: $white; - font-weight: 500; - } - - &--select-all-checkbox { - font-size: 14px; - padding: 16px 16px; - background-color: $pure-white; - } - - &--add-more-container { - padding: 16px; - display: flex; - background-color: $grey-4; - } - - &--add-more-icon-container { - color: $brand-primary !important; - } - - &--add-more-icon { - font-size: 20px; - height: 20px; - width: 20px; - } - - &--add-more-text { - margin-left: 5px; - color: $black-light; - font-weight: 500; - font-size: 14px; - } - - &--footer { - height: 80px; - padding: 0; - } - - &--cta-container { - display: flex; - justify-content: space-between; - padding: 14px 35.5px; - bottom: 0; - width: 100%; - background-color: $pure-white; - box-shadow: 0 -2px 12px $pink-shadow; - z-index: 999; - } - - &--cta-button { - font-weight: 500; - } - - &--secondary-cta { - height: auto; - background-color: transparent; - color: $black; - font-size: 16px; - line-height: 1.25; - min-width: 40%; - - &__disabled { - opacity: 0.2; - } - } - - &--primary-cta { - min-height: 52px; - background: $pink-gradient; - color: $pure-white; - font-size: 14px; - line-height: 1.3; - border-radius: 4px; - padding: 10px 12px; - min-width: 50%; - &__disabled { - opacity: 0.8; - } - } -} diff --git a/src/app/shared/components/create-new-report/create-new-report.component.spec.ts b/src/app/shared/components/create-new-report/create-new-report.component.spec.ts deleted file mode 100644 index 8cc47f25f2..0000000000 --- a/src/app/shared/components/create-new-report/create-new-report.component.spec.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; -import { IonicModule } from '@ionic/angular'; -import { MatIconModule } from '@angular/material/icon'; -import { MatIconTestingModule } from '@angular/material/icon/testing'; -import { HumanizeCurrencyPipe } from '../../pipes/humanize-currency.pipe'; -import { ModalController } from '@ionic/angular'; -import { ReportService } from 'src/app/core/services/report.service'; -import { TrackingService } from 'src/app/core/services/tracking.service'; -import { RefinerService } from 'src/app/core/services/refiner.service'; -import { CurrencyService } from 'src/app/core/services/currency.service'; -import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; -import { CreateNewReportComponent } from './create-new-report.component'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { ExpensesCardComponent } from '../expenses-card/expenses-card.component'; -import { of } from 'rxjs'; -import { orgData1 } from 'src/app/core/mock-data/org.data'; -import { expenseFieldsMapResponse2 } from 'src/app/core/mock-data/expense-fields-map.data'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { FyCurrencyPipe } from '../../pipes/fy-currency.pipe'; -import { - apiExpenseRes, - expenseData1, - splitExpData, - expenseList2, - expenseList, -} from 'src/app/core/mock-data/expense.data'; -import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core'; -import { Expense } from 'src/app/core/models/expense.model'; -import { reportUnflattenedData, reportUnflattenedData2 } from 'src/app/core/mock-data/report-v1.data'; -import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; - -describe('CreateNewReportComponent', () => { - let component: CreateNewReportComponent; - let fixture: ComponentFixture; - let modalController: jasmine.SpyObj; - let reportService: jasmine.SpyObj; - let trackingService: jasmine.SpyObj; - let refinerService: jasmine.SpyObj; - let currencyService: jasmine.SpyObj; - let expenseFieldsService: jasmine.SpyObj; - let spenderReportsService: jasmine.SpyObj; - - beforeEach(waitForAsync(() => { - modalController = jasmine.createSpyObj('ModalController', ['dismiss']); - reportService = jasmine.createSpyObj('ReportService', [ - 'getReportPurpose', - 'createDraft', - 'addTransactions', - 'create', - ]); - trackingService = jasmine.createSpyObj('TrackingService', ['createReport']); - refinerService = jasmine.createSpyObj('RefinerService', ['startSurvey']); - currencyService = jasmine.createSpyObj('CurrencyService', ['getHomeCurrency']); - expenseFieldsService = jasmine.createSpyObj('ExpenseFieldsService', ['getAllMap']); - const spenderReportsServiceSpy = jasmine.createSpyObj('SpenderReportsService', ['addExpenses']); - const humanizeCurrencyPipeSpy = jasmine.createSpyObj('HumanizeCurrency', ['transform']); - const fyCurrencyPipeSpy = jasmine.createSpyObj('FyCurrencyPipe', ['transform']); - - TestBed.configureTestingModule({ - declarations: [CreateNewReportComponent, HumanizeCurrencyPipe, FyCurrencyPipe], - imports: [ - IonicModule.forRoot(), - MatIconModule, - MatIconTestingModule, - FormsModule, - ReactiveFormsModule, - MatCheckboxModule, - ], - providers: [ - { provide: ModalController, useValue: modalController }, - { provide: ReportService, useValue: reportService }, - { provide: TrackingService, useValue: trackingService }, - { provide: RefinerService, useValue: refinerService }, - { provide: CurrencyService, useValue: currencyService }, - { provide: ExpenseFieldsService, useValue: expenseFieldsService }, - { provide: HumanizeCurrencyPipe, useValue: humanizeCurrencyPipeSpy }, - { provide: FyCurrencyPipe, useValue: fyCurrencyPipeSpy }, - { provide: SpenderReportsService, useValue: spenderReportsServiceSpy }, - ], - schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA], - }).compileComponents(); - - reportService = TestBed.inject(ReportService) as jasmine.SpyObj; - trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj; - refinerService = TestBed.inject(RefinerService) as jasmine.SpyObj; - currencyService = TestBed.inject(CurrencyService) as jasmine.SpyObj; - expenseFieldsService = TestBed.inject(ExpenseFieldsService) as jasmine.SpyObj; - spenderReportsService = TestBed.inject(SpenderReportsService) as jasmine.SpyObj; - modalController = TestBed.inject(ModalController) as jasmine.SpyObj; - - currencyService.getHomeCurrency.and.returnValue(of(orgData1[0].currency)); - expenseFieldsService.getAllMap.and.returnValue(of(expenseFieldsMapResponse2)); - reportService.createDraft.and.returnValue(of(reportUnflattenedData2)); - fixture = TestBed.createComponent(CreateNewReportComponent); - component = fixture.componentInstance; - component.selectedElements = apiExpenseRes; - component.selectedExpensesToReport = apiExpenseRes; - - fixture.detectChanges(); - })); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('getReportTitle', () => { - it('should get the report title', () => { - const reportName = '#1: Jul 2021'; - component.selectedElements = apiExpenseRes; - reportService.getReportPurpose.and.returnValue(of(reportName)); - component.getReportTitle(); - fixture.detectChanges(); - expect(component.reportTitle).toEqual(reportName); - expect(reportService.getReportPurpose).toHaveBeenCalledOnceWith({ ids: ['tx3nHShG60zq'] }); - }); - - it('should not get the report title when the element is not in the selectedElements array', () => { - const reportName = '#1: Jul 2021'; - component.selectedElements = expenseList; - reportService.getReportPurpose.and.returnValue(of(reportName)); - component.getReportTitle(); - fixture.detectChanges(); - expect(component.reportTitle).toEqual(reportName); - expect(reportService.getReportPurpose).toHaveBeenCalledOnceWith({ ids: ['txBphgnCHHeO'] }); - }); - }); - - it('ionViewWillEnter: should call getReportTitle method', () => { - spyOn(component, 'getReportTitle'); - component.ionViewWillEnter(); - expect(component.getReportTitle).toHaveBeenCalledTimes(1); - }); - - it('closeEvent(): should dismiss the model ', () => { - component.closeEvent(); - expect(modalController.dismiss).toHaveBeenCalledTimes(1); - }); - - describe('toggleSelectAll():', () => { - it('should set report title when reportTitleInput is not dirty and txnIds length is greater than 0', () => { - const reportTitleSpy = spyOn(component, 'getReportTitle'); - component.toggleSelectAll(true); - expect(component.selectedElements).toEqual(component.selectedExpensesToReport); - expect(reportTitleSpy).toHaveBeenCalledTimes(1); - }); - - it('should deselect all the expenses to be added in the report when false is passed', () => { - const reportTitleSpy = spyOn(component, 'getReportTitle'); - component.selectedExpensesToReport = []; - component.toggleSelectAll(false); - expect(component.selectedElements).toEqual(component.selectedExpensesToReport); - expect(reportTitleSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe('selectExpense()', () => { - it('should add the expense to the array when if it is not already present ', fakeAsync(() => { - const reportTitleSpy = spyOn(component, 'getReportTitle'); - component.selectedElements = []; - const newExpense = apiExpenseRes[0]; - component.selectExpense(newExpense); - tick(500); - fixture.detectChanges(); - expect(component.selectedElements.length).toBe(component.selectedExpensesToReport.length); - expect(component.selectedElements).toContain(newExpense); - expect(reportTitleSpy).toHaveBeenCalledTimes(1); - expect(component.isSelectedAll).toBeTrue(); - })); - - it('should remove an expense from the selectedElements array', fakeAsync(() => { - component.selectedElements = expenseList2; - component.selectedExpensesToReport = expenseList2; - const reportTitleSpy = spyOn(component, 'getReportTitle'); - const existingExpense: Expense = component.selectedElements[0]; - component.selectExpense(existingExpense); - tick(500); - fixture.detectChanges(); - expect(component.selectedElements).not.toContain(existingExpense); - expect(component.selectedElements.length).toBe(1); - expect(reportTitleSpy).toHaveBeenCalledTimes(1); - expect(component.isSelectedAll).toBeFalse(); - })); - }); - - describe('ctaClickedEvent', () => { - it('should display error and exit if the report title is not present', () => { - component.reportTitle = ''; - component.ctaClickedEvent('create_draft_report'); - expect(component.showReportNameError).toBeTrue(); - }); - - it('should create a new draft report with the title and add transactions', fakeAsync(() => { - component.reportTitle = '#3 : Mar 2023'; - const reportID = 'rp5eUkeNm9wB'; - const tnxs = ['tx3nHShG60zq']; - const reportParam = { - purpose: '#3 : Mar 2023', - source: 'MOBILE', - }; - const Expense_Count = tnxs.length; - const Report_Value = 0; - const report = reportUnflattenedData2; - reportService.createDraft.and.returnValue(of(reportUnflattenedData2)); - spenderReportsService.addExpenses.and.returnValue(of(undefined)); - component.ctaClickedEvent('create_draft_report'); - fixture.detectChanges(); - tick(500); - expect(component.saveDraftReportLoader).toBeFalse(); - expect(component.showReportNameError).toBeFalse(); - expect(trackingService.createReport).toHaveBeenCalledOnceWith({ Expense_Count, Report_Value }); - expect(reportService.createDraft).toHaveBeenCalledOnceWith(reportParam); - expect(spenderReportsService.addExpenses).toHaveBeenCalledOnceWith(reportID, tnxs); - expect(component.saveDraftReportLoader).toBeFalse(); - expect(modalController.dismiss).toHaveBeenCalledOnceWith({ - report, - message: 'Expenses added to a new report', - }); - })); - - it('should create a new draft report with the title and return the report of no transactions ids are present', fakeAsync(() => { - component.selectedElements = []; - component.reportTitle = '#3 : Mar 2023'; - const tnxs = []; - const reportParam = { - purpose: '#3 : Mar 2023', - source: 'MOBILE', - }; - const Expense_Count = tnxs.length; - const Report_Value = 0; - const report = reportUnflattenedData2; - reportService.createDraft.and.returnValue(of(reportUnflattenedData2)); - component.ctaClickedEvent('create_draft_report'); - fixture.detectChanges(); - tick(500); - expect(component.saveDraftReportLoader).toBeFalse(); - expect(component.showReportNameError).toBeFalse(); - expect(trackingService.createReport).toHaveBeenCalledOnceWith({ Expense_Count, Report_Value }); - expect(reportService.createDraft).toHaveBeenCalledOnceWith(reportParam); - expect(component.saveDraftReportLoader).toBeFalse(); - expect(modalController.dismiss).toHaveBeenCalledOnceWith({ - report, - message: 'Expenses added to a new report', - }); - })); - - it('should create a new report with the title and submit the report', fakeAsync(() => { - component.reportTitle = '#3 : Mar 2023'; - const reportPurpose = { - purpose: '#3 : Mar 2023', - source: 'MOBILE', - }; - - const txnIds = ['tx3nHShG60zq']; - const report = reportUnflattenedData2; - reportService.create.and.returnValue(of(reportUnflattenedData2)); - component.ctaClickedEvent('submit_report'); - fixture.detectChanges(); - tick(500); - expect(component.submitReportLoader).toBeFalse(); - expect(component.showReportNameError).toBeFalse(); - expect(reportService.create).toHaveBeenCalledOnceWith(reportPurpose, txnIds); - expect(refinerService.startSurvey).toHaveBeenCalledOnceWith({ actionName: 'Submit Newly Created Report' }); - expect(component.submitReportLoader).toBeFalse(); - expect(modalController.dismiss).toHaveBeenCalledOnceWith({ - report, - message: 'Expenses submitted for approval', - }); - })); - }); -}); diff --git a/src/app/shared/components/create-new-report/create-new-report.component.ts b/src/app/shared/components/create-new-report/create-new-report.component.ts deleted file mode 100644 index 28f03925aa..0000000000 --- a/src/app/shared/components/create-new-report/create-new-report.component.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { NgModel } from '@angular/forms'; -import { ModalController } from '@ionic/angular'; -import { Observable, of, Subscription } from 'rxjs'; -import { finalize, map, switchMap, tap } from 'rxjs/operators'; -import { Expense } from 'src/app/core/models/expense.model'; -import { ExpenseFieldsMap } from 'src/app/core/models/v1/expense-fields-map.model'; -import { ReportService } from 'src/app/core/services/report.service'; -import { TrackingService } from 'src/app/core/services/tracking.service'; -import { RefinerService } from 'src/app/core/services/refiner.service'; -import { CurrencyService } from 'src/app/core/services/currency.service'; -import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; -import { ReportV1 } from 'src/app/core/models/report-v1.model'; -import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; - -@Component({ - selector: 'app-create-new-report', - templateUrl: './create-new-report.component.html', - styleUrls: ['./create-new-report.component.scss'], -}) -export class CreateNewReportComponent implements OnInit { - @Input() selectedExpensesToReport: Expense[]; - - @ViewChild('reportTitleInput') reportTitleInput: NgModel; - - expenseFields$: Observable>; - - selectedElements: Expense[]; - - selectedTotalAmount: number; - - reportTitle: string; - - submitReportLoader: boolean; - - saveDraftReportLoader: boolean; - - homeCurrency: string; - - isSelectedAll: boolean; - - showReportNameError: boolean; - - constructor( - private modalController: ModalController, - private reportService: ReportService, - private trackingService: TrackingService, - private refinerService: RefinerService, - private currencyService: CurrencyService, - private expenseFieldsService: ExpenseFieldsService, - private spenderReportsService: SpenderReportsService - ) {} - - getReportTitle(): Subscription { - const txnIds = this.selectedElements.map((etxn) => etxn.tx_id); - this.selectedTotalAmount = this.selectedElements.reduce( - (acc, obj) => acc + (obj.tx_skip_reimbursement ? 0 : obj.tx_amount), - 0 - ); - - if (this.reportTitleInput && !this.reportTitleInput.dirty && txnIds.length > 0) { - return this.reportService.getReportPurpose({ ids: txnIds }).subscribe((res) => { - this.reportTitle = res; - }); - } - } - - ngOnInit(): void { - this.selectedTotalAmount = 0; - this.submitReportLoader = false; - this.saveDraftReportLoader = false; - this.isSelectedAll = true; - this.expenseFields$ = this.expenseFieldsService.getAllMap(); - this.selectedElements = this.selectedExpensesToReport; - this.currencyService.getHomeCurrency().subscribe((homeCurrency) => { - this.homeCurrency = homeCurrency; - }); - } - - ionViewWillEnter(): void { - this.getReportTitle(); - } - - selectExpense(expense: Expense): void { - const isSelectedElementsIncludesExpense = this.selectedElements.some((txn) => expense.tx_id === txn.tx_id); - if (isSelectedElementsIncludesExpense) { - this.selectedElements = this.selectedElements.filter((txn) => txn.tx_id !== expense.tx_id); - } else { - this.selectedElements.push(expense); - } - this.getReportTitle(); - this.isSelectedAll = this.selectedElements.length === this.selectedExpensesToReport.length; - } - - toggleSelectAll(value: boolean): void { - if (value) { - this.selectedElements = this.selectedExpensesToReport; - } else { - this.selectedElements = []; - } - this.getReportTitle(); - } - - closeEvent(): void { - this.modalController.dismiss(); - } - - ctaClickedEvent(reportActionType): Subscription { - this.showReportNameError = false; - if (this.reportTitle?.trim().length <= 0) { - this.showReportNameError = true; - return; - } - - const report = { - purpose: this.reportTitle, - source: 'MOBILE', - }; - - const txnIds = this.selectedElements.map((expense) => expense.tx_id); - if (reportActionType === 'create_draft_report') { - this.saveDraftReportLoader = true; - return this.reportService - .createDraft(report) - .pipe( - tap(() => - this.trackingService.createReport({ - Expense_Count: txnIds.length, - Report_Value: this.selectedTotalAmount, - }) - ), - switchMap((report: ReportV1) => { - if (txnIds.length > 0) { - return this.spenderReportsService.addExpenses(report.id, txnIds).pipe(map(() => report)); - } else { - return of(report); - } - }), - finalize(() => { - this.saveDraftReportLoader = false; - }) - ) - .subscribe((report) => { - this.modalController.dismiss({ - report, - message: 'Expenses added to a new report', - }); - }); - } else { - this.submitReportLoader = true; - this.reportService - .create(report, txnIds) - .pipe( - tap(() => { - this.trackingService.createReport({ - Expense_Count: txnIds.length, - Report_Value: this.selectedTotalAmount, - }); - this.refinerService.startSurvey({ actionName: 'Submit Newly Created Report' }); - }), - finalize(() => { - this.submitReportLoader = false; - }) - ) - .subscribe((report) => { - this.modalController.dismiss({ - report, - message: 'Expenses submitted for approval', - }); - }); - } - } -} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index ff78c19fb5..26224e29cd 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -70,7 +70,6 @@ import { NavigationFooterComponent } from './components/navigation-footer/naviga import { FyConnectionComponent } from './components/fy-connection/fy-connection.component'; import { FyCriticalPolicyViolationComponent } from './components/fy-critical-policy-violation/fy-critical-policy-violation.component'; import { PopupAlertComponent } from './components/popup-alert/popup-alert.component'; -import { CreateNewReportComponent } from './components/create-new-report/create-new-report.component'; import { CreateNewReportComponent as CreateNewReportComponentV2 } from './components/create-new-report-v2/create-new-report.component'; import { ExpensesCardComponent } from './components/expenses-card/expenses-card.component'; import { ExpensesCardComponent as ExpensesCardComponentV2 } from './components/expenses-card-v2/expenses-card.component'; @@ -195,7 +194,6 @@ import { FySelectCommuteDetailsComponent } from './components/fy-select-commute- FyConnectionComponent, FyCriticalPolicyViolationComponent, PopupAlertComponent, - CreateNewReportComponent, CreateNewReportComponentV2, ExpensesCardComponent, ExpensesCardComponentV2, @@ -336,7 +334,6 @@ import { FySelectCommuteDetailsComponent } from './components/fy-select-commute- FyConnectionComponent, FyCriticalPolicyViolationComponent, PopupAlertComponent, - CreateNewReportComponent, CreateNewReportComponentV2, ExpensesCardComponent, ExpensesCardComponentV2, From ae0eab9e5c7c54b6db33647701a73bb33d302dbc Mon Sep 17 00:00:00 2001 From: Sumrender Singh <78428003+sumrender@users.noreply.github.com> Date: Tue, 14 May 2024 16:28:18 +0530 Subject: [PATCH 009/262] feat: lauchDarkly flag added for deprecating manual flag feature (#2966) * lauchDarkly flag added for deprecating manual flag feature * removed obs resolved in report presentational cmp * remove obs resolution from expenses presentational cmp * minor change --- .../core/services/launch-darkly.service.ts | 4 ++++ .../suggested-duplicates.component.html | 1 + .../suggested-duplicates.component.ts | 13 ++++++++++- .../my-create-report.page.html | 3 ++- .../my-create-report/my-create-report.page.ts | 13 ++++++++++- .../fyle/my-expenses/my-expenses.page.html | 1 + src/app/fyle/my-expenses/my-expenses.page.ts | 13 ++++++++++- src/app/fyle/my-reports/my-reports.page.html | 1 + src/app/fyle/my-reports/my-reports.page.ts | 13 ++++++++++- .../add-expenses-to-report.component.html | 3 ++- .../add-expenses-to-report.component.ts | 13 ++++++++++- .../my-view-report/my-view-report.page.html | 3 ++- .../my-view-report/my-view-report.page.ts | 13 ++++++++++- .../potential-duplicates.page.html | 1 + .../potential-duplicates.page.ts | 13 ++++++++++- .../fyle/team-reports/team-reports.page.html | 1 + .../fyle/team-reports/team-reports.page.ts | 13 ++++++++++- .../fyle/view-expense/view-expense.page.html | 22 +++++++++++-------- .../fyle/view-expense/view-expense.page.ts | 8 ++++++- .../view-team-report.page.html | 3 ++- .../view-team-report/view-team-report.page.ts | 13 ++++++++++- .../create-new-report.component.html | 1 + .../create-new-report.component.ts | 13 ++++++++++- .../expenses-card.component.ts | 9 +++++++- .../reports-card/reports-card.component.html | 2 +- .../reports-card/reports-card.component.ts | 2 ++ 26 files changed, 169 insertions(+), 26 deletions(-) diff --git a/src/app/core/services/launch-darkly.service.ts b/src/app/core/services/launch-darkly.service.ts index 2f4f7c3f2a..76add9b568 100644 --- a/src/app/core/services/launch-darkly.service.ts +++ b/src/app/core/services/launch-darkly.service.ts @@ -74,6 +74,10 @@ export class LaunchDarklyService { return this.getVariation('android-numeric-keypad', false); } + checkIfManualFlaggingFeatureIsEnabled(): Observable<{ value: boolean }> { + return this.getVariation('deprecate_manual_flagging', false).pipe(map((value) => ({ value }))); + } + // Checks if the passed in user is the same as the user which is initialized to LaunchDarkly (if any) private isTheSameUser(newUser: LDClient.LDUser): boolean { const previousUser = this.ldClient?.getUser(); diff --git a/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.html b/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.html index 3afd5a786a..1ca3d9fd7f 100644 --- a/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.html +++ b/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.html @@ -14,6 +14,7 @@ [previousExpenseTxnDate]="list[i - 1]?.spent_at" [previousExpenseCreatedAt]="list[i - 1]?.created_at" [expense]="expense" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" > diff --git a/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.ts b/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.ts index 83c5327d4f..b5eb0e3cbf 100644 --- a/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.ts +++ b/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.ts @@ -4,6 +4,7 @@ import { Router } from '@angular/router'; import { ModalController } from '@ionic/angular'; import { Observable, map, noop } from 'rxjs'; import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; @@ -19,13 +20,16 @@ export class SuggestedDuplicatesComponent { duplicateExpenses: Expense[] = []; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private modalController: ModalController, private expensesService: ExpensesService, private router: Router, private snackbarProperties: SnackbarPropertiesService, private matSnackBar: MatSnackBar, - private orgSettingsService: OrgSettingsService + private orgSettingsService: OrgSettingsService, + private launchDarklyService: LaunchDarklyService ) {} ionViewWillEnter(): void { @@ -34,12 +38,19 @@ export class SuggestedDuplicatesComponent { id: `in.(${txnIds})`, }; + this.setIsManualFlagFeatureEnabled(); this.expensesService .getExpenses({ offset: 0, limit: 10, ...queryParams }) .pipe(map((expenses) => (this.duplicateExpenses = expenses))) .subscribe(noop); } + setIsManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + dismissDuplicates(duplicateExpenseIds: string[], targetExpenseIds: string[]): Observable { return this.expensesService.dismissDuplicates(duplicateExpenseIds, targetExpenseIds); } diff --git a/src/app/fyle/my-create-report/my-create-report.page.html b/src/app/fyle/my-create-report/my-create-report.page.html index 20d0df9dd6..da0fd9e617 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.html +++ b/src/app/fyle/my-create-report/my-create-report.page.html @@ -67,8 +67,9 @@ [showDt]="checkShowDt(etxn,i)" [isSelectionModeEnabled]="true" [selectedElements]="selectedElements" - (cardClickedForSelection)="selectExpense($event)" [isFromReports]="true" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" + (cardClickedForSelection)="selectExpense($event)" >
diff --git a/src/app/fyle/my-create-report/my-create-report.page.ts b/src/app/fyle/my-create-report/my-create-report.page.ts index 6a7de043db..eaa6fa5976 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.ts +++ b/src/app/fyle/my-create-report/my-create-report.page.ts @@ -16,6 +16,7 @@ import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expen import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; import { Report } from '../../core/models/platform/v1/report.model'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-my-create-report', templateUrl: './my-create-report.page.html', @@ -50,6 +51,8 @@ export class MyCreateReportPage implements OnInit { emptyInput = false; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private transactionService: TransactionService, private activatedRoute: ActivatedRoute, @@ -62,7 +65,8 @@ export class MyCreateReportPage implements OnInit { private refinerService: RefinerService, private expensesService: ExpensesService, private orgSettingsService: OrgSettingsService, - private spenderReportsService: SpenderReportsService + private spenderReportsService: SpenderReportsService, + private launchDarklyService: LaunchDarklyService ) {} detectTitleChange(): void { @@ -209,11 +213,18 @@ export class MyCreateReportPage implements OnInit { this.selectedExpenseIDs = (expenseIDs ? JSON.parse(expenseIDs) : []) as string[]; } + setIsManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + ionViewWillEnter(): void { this.isSelectedAll = true; this.selectedElements = []; this.checkTxnIds(); + this.setIsManualFlagFeatureEnabled(); let queryParams = { report_id: 'is.null', diff --git a/src/app/fyle/my-expenses/my-expenses.page.html b/src/app/fyle/my-expenses/my-expenses.page.html index 1e2a7754c4..ce396c8dd8 100644 --- a/src/app/fyle/my-expenses/my-expenses.page.html +++ b/src/app/fyle/my-expenses/my-expenses.page.html @@ -192,6 +192,7 @@ [previousExpenseCreatedAt]="list[i-1]?.created_at" [isSelectionModeEnabled]="selectionMode" [selectedElements]="selectedElements" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (goToTransaction)="goToTransaction($event)" (setMultiselectMode)="switchSelectionMode($event)" (cardClickedForSelection)="selectExpense($event)" diff --git a/src/app/fyle/my-expenses/my-expenses.page.ts b/src/app/fyle/my-expenses/my-expenses.page.ts index eff4a8e406..d3910fd86a 100644 --- a/src/app/fyle/my-expenses/my-expenses.page.ts +++ b/src/app/fyle/my-expenses/my-expenses.page.ts @@ -77,6 +77,7 @@ import { AddTxnToReportDialogComponent } from './add-txn-to-report-dialog/add-tx import { MyExpensesService } from './my-expenses.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; import { Report } from 'src/app/core/models/platform/v1/report.model'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-my-expenses', @@ -194,6 +195,8 @@ export class MyExpensesPage implements OnInit { restrictPendingTransactionsEnabled = false; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private networkService: NetworkService, private loaderService: LoaderService, @@ -225,7 +228,8 @@ export class MyExpensesPage implements OnInit { private navController: NavController, private expenseService: ExpensesService, private sharedExpenseService: SharedExpenseService, - private spenderReportsService: SpenderReportsService + private spenderReportsService: SpenderReportsService, + private launchDarklyService: LaunchDarklyService ) {} get HeaderState(): typeof HeaderState { @@ -248,8 +252,15 @@ export class MyExpensesPage implements OnInit { this.isSearchBarFocused = true; } + setIsManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + ngOnInit(): void { this.setupNetworkWatcher(); + this.setIsManualFlagFeatureEnabled(); } formatTransactions(transactions: Partial[]): Partial[] { diff --git a/src/app/fyle/my-reports/my-reports.page.html b/src/app/fyle/my-reports/my-reports.page.html index 3f9375028e..1c13767cd2 100644 --- a/src/app/fyle/my-reports/my-reports.page.html +++ b/src/app/fyle/my-reports/my-reports.page.html @@ -100,6 +100,7 @@ [erpt]="erpt" [prevDate]="list[i-1]?.rp_created_at" [simplifyReportsEnabled]="simplifyReportsSettings.enabled" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (gotoReport)="onReportClick($event)" (deleteReport)="onDeleteReportClick($event)" (viewComments)="onViewCommentsClick()" diff --git a/src/app/fyle/my-reports/my-reports.page.ts b/src/app/fyle/my-reports/my-reports.page.ts index 90d372c612..5af4a0b095 100644 --- a/src/app/fyle/my-reports/my-reports.page.ts +++ b/src/app/fyle/my-reports/my-reports.page.ts @@ -37,6 +37,7 @@ import * as dayjs from 'dayjs'; import { AllowedPaymentModes } from 'src/app/core/models/allowed-payment-modes.enum'; import { DeletePopoverParams } from 'src/app/core/models/delete-popover-params.model'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; type Filters = Partial<{ state: string | string[]; @@ -110,6 +111,8 @@ export class MyReportsPage { nonReimbursableOrg$: Observable; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private networkService: NetworkService, private loaderService: LoaderService, @@ -125,7 +128,8 @@ export class MyReportsPage { private modalController: ModalController, private orgSettingsService: OrgSettingsService, private reportStatePipe: ReportState, - private expensesService: ExpensesService + private expensesService: ExpensesService, + private launchDarklyService: LaunchDarklyService ) {} get HeaderState(): typeof HeaderState { @@ -142,6 +146,7 @@ export class MyReportsPage { }); this.isLoading = true; + this.setManualFlagFeatureEnabled(); this.setupNetworkWatcher(); this.searchText = ''; @@ -315,6 +320,12 @@ export class MyReportsPage { }); } + setManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + loadData(event: { target?: { complete?: () => void } }): void { this.currentPageNumber = this.currentPageNumber + 1; const params = this.loadData$.getValue(); diff --git a/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.html b/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.html index 4d1afaac6f..d34c00fe2e 100644 --- a/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.html +++ b/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.html @@ -44,8 +44,9 @@ [previousExpenseCreatedAt]="list[i - 1]?.created_at" [isSelectionModeEnabled]="true" [selectedElements]="selectedElements" - (cardClickedForSelection)="toggleExpense(expense)" [isFromViewReports]="true" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" + (cardClickedForSelection)="toggleExpense(expense)" >
diff --git a/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.ts b/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.ts index da0387c7b6..34c5a57fdc 100644 --- a/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.ts +++ b/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.ts @@ -4,6 +4,7 @@ import { Observable } from 'rxjs'; import { CurrencyService } from 'src/app/core/services/currency.service'; import { Router } from '@angular/router'; import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-add-expenses-to-report', @@ -29,10 +30,13 @@ export class AddExpensesToReportComponent implements OnInit { homeCurrency: string; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private modalController: ModalController, private currencyService: CurrencyService, - private router: Router + private router: Router, + private launchDarklyService: LaunchDarklyService ) {} close() { @@ -82,6 +86,7 @@ export class AddExpensesToReportComponent implements OnInit { ionViewWillEnter() { this.isSelectedAll = true; this.homeCurrency$ = this.currencyService.getHomeCurrency(); + this.setIsManualFlagFeatureEnabled(); const selectedExpenses = []; this.unreportedExpenses.forEach((expense, i) => { selectedExpenses.push(this.unreportedExpenses[i]); @@ -90,6 +95,12 @@ export class AddExpensesToReportComponent implements OnInit { this.updateSelectedExpenses(); } + setIsManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + addNewExpense() { this.router.navigate([ '/', diff --git a/src/app/fyle/my-view-report/my-view-report.page.html b/src/app/fyle/my-view-report/my-view-report.page.html index abea74ae2e..549c270d5f 100644 --- a/src/app/fyle/my-view-report/my-view-report.page.html +++ b/src/app/fyle/my-view-report/my-view-report.page.html @@ -162,9 +162,10 @@ [expenseIndex]="i" [previousExpenseTxnDate]="list[i-1]?.spent_at" [previousExpenseCreatedAt]="list[i-1]?.created_at" - (goToTransaction)="goToTransaction($event)" [isFromReports]="true" [isFromViewReports]="true" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" + (goToTransaction)="goToTransaction($event)" > diff --git a/src/app/fyle/my-view-report/my-view-report.page.ts b/src/app/fyle/my-view-report/my-view-report.page.ts index 9de76546d2..dd61daf830 100644 --- a/src/app/fyle/my-view-report/my-view-report.page.ts +++ b/src/app/fyle/my-view-report/my-view-report.page.ts @@ -35,6 +35,7 @@ import { ShareReportComponent } from './share-report/share-report.component'; import { PlatformHandlerService } from 'src/app/core/services/platform-handler.service'; import { BackButtonActionPriority } from 'src/app/core/models/back-button-action-priority.enum'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-my-view-report', templateUrl: './my-view-report.page.html', @@ -111,6 +112,8 @@ export class MyViewReportPage { hardwareBackButtonAction: Subscription; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private activatedRoute: ActivatedRoute, private reportService: ReportService, @@ -129,7 +132,8 @@ export class MyViewReportPage { private refinerService: RefinerService, private orgSettingsService: OrgSettingsService, private platformHandlerService: PlatformHandlerService, - private spenderReportsService: SpenderReportsService + private spenderReportsService: SpenderReportsService, + private launchDarklyService: LaunchDarklyService ) {} get Segment(): typeof ReportPageSegment { @@ -160,8 +164,15 @@ export class MyViewReportPage { return orgSettings?.simplified_report_closure_settings?.enabled; } + setIsManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + ionViewWillEnter(): void { this.setupNetworkWatcher(); + this.setIsManualFlagFeatureEnabled(); this.reportId = this.activatedRoute.snapshot.params.id as string; this.navigateBack = !!this.activatedRoute.snapshot.params.navigateBack; diff --git a/src/app/fyle/potential-duplicates/potential-duplicates.page.html b/src/app/fyle/potential-duplicates/potential-duplicates.page.html index dc82d371be..7b538292cf 100644 --- a/src/app/fyle/potential-duplicates/potential-duplicates.page.html +++ b/src/app/fyle/potential-duplicates/potential-duplicates.page.html @@ -32,6 +32,7 @@ [isFromPotentialDuplicates]="true" [expense]="expense" [isDismissable]="duplicatesSet?.length > 2" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (dismissed)="dismiss($event)" (goToTransaction)="goToTransaction($event)" > diff --git a/src/app/fyle/potential-duplicates/potential-duplicates.page.ts b/src/app/fyle/potential-duplicates/potential-duplicates.page.ts index 065386fa85..15ef3e6782 100644 --- a/src/app/fyle/potential-duplicates/potential-duplicates.page.ts +++ b/src/app/fyle/potential-duplicates/potential-duplicates.page.ts @@ -8,6 +8,7 @@ import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-proper import { TrackingService } from 'src/app/core/services/tracking.service'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; type Expenses = Expense[]; @@ -29,16 +30,20 @@ export class PotentialDuplicatesPage { isLoading = true; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private expensesService: ExpensesService, private router: Router, private snackbarProperties: SnackbarPropertiesService, private matSnackBar: MatSnackBar, - private trackingService: TrackingService + private trackingService: TrackingService, + private launchDarklyService: LaunchDarklyService ) {} ionViewWillEnter(): void { this.selectedSet = 0; + this.setIsManualFlagFeatureEnabled(); this.duplicateSets$ = this.loadData$.pipe( switchMap(() => @@ -82,6 +87,12 @@ export class PotentialDuplicatesPage { }); } + setIsManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + getDuplicates(): Observable { return this.expensesService .getDuplicateSets() diff --git a/src/app/fyle/team-reports/team-reports.page.html b/src/app/fyle/team-reports/team-reports.page.html index 78c7c8f82c..fbbef178d1 100644 --- a/src/app/fyle/team-reports/team-reports.page.html +++ b/src/app/fyle/team-reports/team-reports.page.html @@ -79,6 +79,7 @@ [erpt]="erpt" [prevDate]="list[i-1]?.rp_created_at" [simplifyReportsEnabled]="simplifyReportsSettings.enabled" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (gotoReport)="onReportClick($event)" > diff --git a/src/app/fyle/team-reports/team-reports.page.ts b/src/app/fyle/team-reports/team-reports.page.ts index 1ada0e509f..41bc152fc7 100644 --- a/src/app/fyle/team-reports/team-reports.page.ts +++ b/src/app/fyle/team-reports/team-reports.page.ts @@ -26,6 +26,7 @@ import { ReportState } from 'src/app/shared/pipes/report-state.pipe'; import { GetTasksQueryParamsWithFilters } from 'src/app/core/models/get-tasks-query-params-with-filters.model'; import { GetTasksQueryParams } from 'src/app/core/models/get-tasks.query-params.model'; import { TeamReportsFilters } from 'src/app/core/models/team-reports-filters.model'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-team-reports', @@ -47,6 +48,8 @@ export class TeamReportsPage implements OnInit { isLoading = false; + isManualFlagFeatureEnabled: { value: boolean }; + isLoadingDataInInfiniteScroll: boolean; loadData$: BehaviorSubject>; @@ -95,7 +98,8 @@ export class TeamReportsPage implements OnInit { private apiV2Service: ApiV2Service, private tasksService: TasksService, private orgSettingsService: OrgSettingsService, - private reportStatePipe: ReportState + private reportStatePipe: ReportState, + private launchDarklyService: LaunchDarklyService ) {} get HeaderState() { @@ -104,6 +108,7 @@ export class TeamReportsPage implements OnInit { ngOnInit() { this.setupNetworkWatcher(); + this.setManualFlagFeatureEnabled(); } ionViewWillLeave() { @@ -233,6 +238,12 @@ export class TeamReportsPage implements OnInit { }); } + setManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + loadData(event: { target: HTMLIonInfiniteScrollElement }) { this.currentPageNumber = this.currentPageNumber + 1; const params = this.loadData$.getValue(); diff --git a/src/app/fyle/view-expense/view-expense.page.html b/src/app/fyle/view-expense/view-expense.page.html index 57675b1624..3a87918722 100644 --- a/src/app/fyle/view-expense/view-expense.page.html +++ b/src/app/fyle/view-expense/view-expense.page.html @@ -19,15 +19,19 @@ - - - - - + + + + + + + + + diff --git a/src/app/fyle/view-expense/view-expense.page.ts b/src/app/fyle/view-expense/view-expense.page.ts index 70666a9a3c..9ca3f5a479 100644 --- a/src/app/fyle/view-expense/view-expense.page.ts +++ b/src/app/fyle/view-expense/view-expense.page.ts @@ -36,6 +36,7 @@ import { AccountType } from 'src/app/core/models/platform/v1/account.model'; import { ExpenseState } from 'src/app/core/models/expense-state.enum'; import { TransactionStatusInfoPopoverComponent } from 'src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component'; import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-view-expense', @@ -59,6 +60,8 @@ export class ViewExpensePage { canFlagOrUnflag$: Observable; + isManualFlaggingFeatureEnabled$: Observable<{ value: boolean }>; + canDelete$: Observable; orgSettings: OrgSettings; @@ -152,7 +155,8 @@ export class ViewExpensePage { private dependentFieldsService: DependentFieldsService, private spenderExpensesService: SpenderExpensesService, private approverExpensesService: ApproverExpensesService, - private approverReportsService: ApproverReportsService + private approverReportsService: ApproverReportsService, + private launchDarklyService: LaunchDarklyService ) {} get ExpenseView(): typeof ExpenseView { @@ -360,6 +364,8 @@ export class ViewExpensePage { this.comments$ = this.statusService.find('transactions', this.expenseId); + this.isManualFlaggingFeatureEnabled$ = this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled(); + this.canFlagOrUnflag$ = this.expenseWithoutCustomProperties$.pipe( filter(() => this.view === ExpenseView.team), map((expense) => diff --git a/src/app/fyle/view-team-report/view-team-report.page.html b/src/app/fyle/view-team-report/view-team-report.page.html index 36799e44dc..d36bb3a8cb 100644 --- a/src/app/fyle/view-team-report/view-team-report.page.html +++ b/src/app/fyle/view-team-report/view-team-report.page.html @@ -166,9 +166,10 @@ [expenseIndex]="i" [previousExpenseTxnDate]="list[i-1]?.spent_at" [previousExpenseCreatedAt]="list[i-1]?.created_at" - (goToTransaction)="goToTransaction($event)" [isFromReports]="true" [isFromViewReports]="true" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" + (goToTransaction)="goToTransaction($event)" > diff --git a/src/app/fyle/view-team-report/view-team-report.page.ts b/src/app/fyle/view-team-report/view-team-report.page.ts index 89096b83a8..cfe2bac665 100644 --- a/src/app/fyle/view-team-report/view-team-report.page.ts +++ b/src/app/fyle/view-team-report/view-team-report.page.ts @@ -32,6 +32,7 @@ import { ExpensesService } from 'src/app/core/services/platform/v1/approver/expe import { Expense } from 'src/app/core/models/platform/v1/expense.model'; import { ShareReportComponent } from './share-report/share-report.component'; import { FyViewReportInfoComponent } from 'src/app/shared/components/fy-view-report-info/fy-view-report-info.component'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-view-team-report', templateUrl: './view-team-report.page.html', @@ -130,6 +131,8 @@ export class ViewTeamReportPage { timeSpentOnEditingReportName: number; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private activatedRoute: ActivatedRoute, private reportService: ReportService, @@ -148,7 +151,8 @@ export class ViewTeamReportPage { private refinerService: RefinerService, private statusService: StatusService, private humanizeCurrency: HumanizeCurrencyPipe, - private orgSettingsService: OrgSettingsService + private orgSettingsService: OrgSettingsService, + private launchDarklyService: LaunchDarklyService ) {} ionViewWillLeave() { @@ -197,6 +201,7 @@ export class ViewTeamReportPage { ionViewWillEnter() { this.isExpensesLoading = true; this.setupNetworkWatcher(); + this.setIsManualFlagFeatureEnabled(); this.navigateBack = this.activatedRoute.snapshot.params.navigate_back; @@ -333,6 +338,12 @@ export class ViewTeamReportPage { this.refreshApprovals$.next(null); } + setIsManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + toggleTooltip() { this.canShowTooltip = !this.canShowTooltip; } diff --git a/src/app/shared/components/create-new-report-v2/create-new-report.component.html b/src/app/shared/components/create-new-report-v2/create-new-report.component.html index 2777b65f82..1d0e751cf4 100644 --- a/src/app/shared/components/create-new-report-v2/create-new-report.component.html +++ b/src/app/shared/components/create-new-report-v2/create-new-report.component.html @@ -57,6 +57,7 @@ [previousExpenseCreatedAt]="selectedExpensesToReport[i - 1]?.created_at" [isSelectionModeEnabled]="true" [selectedElements]="selectedElements" + [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (cardClickedForSelection)="selectExpense($event)" > diff --git a/src/app/shared/components/create-new-report-v2/create-new-report.component.ts b/src/app/shared/components/create-new-report-v2/create-new-report.component.ts index 63f42bea70..2155ddef5b 100644 --- a/src/app/shared/components/create-new-report-v2/create-new-report.component.ts +++ b/src/app/shared/components/create-new-report-v2/create-new-report.component.ts @@ -12,6 +12,7 @@ import { CurrencyService } from 'src/app/core/services/currency.service'; import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; import { Report } from 'src/app/core/models/platform/v1/report.model'; +import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-create-new-report', @@ -41,6 +42,8 @@ export class CreateNewReportComponent implements OnInit { showReportNameError: boolean; + isManualFlagFeatureEnabled: { value: boolean }; + constructor( private modalController: ModalController, private reportService: ReportService, @@ -48,7 +51,8 @@ export class CreateNewReportComponent implements OnInit { private refinerService: RefinerService, private currencyService: CurrencyService, private expenseFieldsService: ExpenseFieldsService, - private spenderReportsService: SpenderReportsService + private spenderReportsService: SpenderReportsService, + private launchDarklyService: LaunchDarklyService ) {} getReportTitle(): Subscription { @@ -77,8 +81,15 @@ export class CreateNewReportComponent implements OnInit { }); } + setIsManualFlagFeatureEnabled() { + this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { + this.isManualFlagFeatureEnabled = ldFlag; + }); + } + ionViewWillEnter(): void { this.getReportTitle(); + this.setIsManualFlagFeatureEnabled(); } selectExpense(expense: Expense): void { diff --git a/src/app/shared/components/expenses-card-v2/expenses-card.component.ts b/src/app/shared/components/expenses-card-v2/expenses-card.component.ts index 3c163e9742..70576597f5 100644 --- a/src/app/shared/components/expenses-card-v2/expenses-card.component.ts +++ b/src/app/shared/components/expenses-card-v2/expenses-card.component.ts @@ -68,6 +68,8 @@ export class ExpensesCardComponent implements OnInit { @Input() showDt = true; + @Input() isManualFlagFeatureEnabled = false; + @Output() goToTransaction: EventEmitter<{ expense: Expense; expenseIndex: number }> = new EventEmitter<{ expense: Expense; expenseIndex: number; @@ -267,6 +269,11 @@ export class ExpensesCardComponent implements OnInit { this.expense.source_account?.type === AccountType.PERSONAL_CASH_ACCOUNT && this.expense.is_reimbursable; } + setIsPolicyViolated() { + const isManualFlagEnabledAndFlagged = this.isManualFlagFeatureEnabled && this.expense.is_manually_flagged; + this.isPolicyViolated = isManualFlagEnabledAndFlagged || this.expense.is_policy_flagged; + } + ngOnInit(): void { this.setupNetworkWatcher(); const orgSettings$ = this.orgSettingsService.get().pipe(shareReplay(1)); @@ -280,12 +287,12 @@ export class ExpensesCardComponent implements OnInit { this.isPerDiem = this.category === 'per diem'; this.isDraft = this.sharedExpenseService.isExpenseInDraft(this.expense); - this.isPolicyViolated = this.expense.is_manually_flagged || this.expense.is_policy_flagged; this.isCriticalPolicyViolated = this.sharedExpenseService.isCriticalPolicyViolatedExpense(this.expense); this.vendorDetails = this.sharedExpenseService.getVendorDetails(this.expense); this.expenseFieldsService.getAllMap().subscribe((expenseFields) => { this.expenseFields = expenseFields; }); + this.setIsPolicyViolated(); this.currencyService .getHomeCurrency() diff --git a/src/app/shared/components/reports-card/reports-card.component.html b/src/app/shared/components/reports-card/reports-card.component.html index 602f0d9257..4ea78f0fd9 100644 --- a/src/app/shared/components/reports-card/reports-card.component.html +++ b/src/app/shared/components/reports-card/reports-card.component.html @@ -13,7 +13,7 @@
{{ erpt.rp_purpose | ellipsis : 40 }} - +
diff --git a/src/app/shared/components/reports-card/reports-card.component.ts b/src/app/shared/components/reports-card/reports-card.component.ts index 9ef8eb7b74..65dae1997f 100644 --- a/src/app/shared/components/reports-card/reports-card.component.ts +++ b/src/app/shared/components/reports-card/reports-card.component.ts @@ -10,6 +10,8 @@ import { ExtendedReport } from 'src/app/core/models/report.model'; export class ReportsCardComponent implements OnInit { @Input() erpt: ExtendedReport; + @Input() isManualFlagFeatureEnabled: boolean; + @Input() prevDate: Date; @Input() simplifyReportsEnabled: boolean; From ff6a9cd196f6690363041dfb4359645b5827fd7d Mon Sep 17 00:00:00 2001 From: Sumrender Singh <78428003+sumrender@users.noreply.github.com> Date: Tue, 14 May 2024 17:26:27 +0530 Subject: [PATCH 010/262] Revert "feat: lauchDarkly flag added for deprecating manual flag feature (#2966)" (#2989) This reverts commit ae0eab9e5c7c54b6db33647701a73bb33d302dbc. --- .../core/services/launch-darkly.service.ts | 4 ---- .../suggested-duplicates.component.html | 1 - .../suggested-duplicates.component.ts | 13 +---------- .../my-create-report.page.html | 3 +-- .../my-create-report/my-create-report.page.ts | 13 +---------- .../fyle/my-expenses/my-expenses.page.html | 1 - src/app/fyle/my-expenses/my-expenses.page.ts | 13 +---------- src/app/fyle/my-reports/my-reports.page.html | 1 - src/app/fyle/my-reports/my-reports.page.ts | 13 +---------- .../add-expenses-to-report.component.html | 3 +-- .../add-expenses-to-report.component.ts | 13 +---------- .../my-view-report/my-view-report.page.html | 3 +-- .../my-view-report/my-view-report.page.ts | 13 +---------- .../potential-duplicates.page.html | 1 - .../potential-duplicates.page.ts | 13 +---------- .../fyle/team-reports/team-reports.page.html | 1 - .../fyle/team-reports/team-reports.page.ts | 13 +---------- .../fyle/view-expense/view-expense.page.html | 22 ++++++++----------- .../fyle/view-expense/view-expense.page.ts | 8 +------ .../view-team-report.page.html | 3 +-- .../view-team-report/view-team-report.page.ts | 13 +---------- .../create-new-report.component.html | 1 - .../create-new-report.component.ts | 13 +---------- .../expenses-card.component.ts | 9 +------- .../reports-card/reports-card.component.html | 2 +- .../reports-card/reports-card.component.ts | 2 -- 26 files changed, 26 insertions(+), 169 deletions(-) diff --git a/src/app/core/services/launch-darkly.service.ts b/src/app/core/services/launch-darkly.service.ts index 76add9b568..2f4f7c3f2a 100644 --- a/src/app/core/services/launch-darkly.service.ts +++ b/src/app/core/services/launch-darkly.service.ts @@ -74,10 +74,6 @@ export class LaunchDarklyService { return this.getVariation('android-numeric-keypad', false); } - checkIfManualFlaggingFeatureIsEnabled(): Observable<{ value: boolean }> { - return this.getVariation('deprecate_manual_flagging', false).pipe(map((value) => ({ value }))); - } - // Checks if the passed in user is the same as the user which is initialized to LaunchDarkly (if any) private isTheSameUser(newUser: LDClient.LDUser): boolean { const previousUser = this.ldClient?.getUser(); diff --git a/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.html b/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.html index 1ca3d9fd7f..3afd5a786a 100644 --- a/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.html +++ b/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.html @@ -14,7 +14,6 @@ [previousExpenseTxnDate]="list[i - 1]?.spent_at" [previousExpenseCreatedAt]="list[i - 1]?.created_at" [expense]="expense" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" > diff --git a/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.ts b/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.ts index b5eb0e3cbf..83c5327d4f 100644 --- a/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.ts +++ b/src/app/fyle/add-edit-expense/suggested-duplicates/suggested-duplicates.component.ts @@ -4,7 +4,6 @@ import { Router } from '@angular/router'; import { ModalController } from '@ionic/angular'; import { Observable, map, noop } from 'rxjs'; import { Expense } from 'src/app/core/models/platform/v1/expense.model'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; @@ -20,16 +19,13 @@ export class SuggestedDuplicatesComponent { duplicateExpenses: Expense[] = []; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private modalController: ModalController, private expensesService: ExpensesService, private router: Router, private snackbarProperties: SnackbarPropertiesService, private matSnackBar: MatSnackBar, - private orgSettingsService: OrgSettingsService, - private launchDarklyService: LaunchDarklyService + private orgSettingsService: OrgSettingsService ) {} ionViewWillEnter(): void { @@ -38,19 +34,12 @@ export class SuggestedDuplicatesComponent { id: `in.(${txnIds})`, }; - this.setIsManualFlagFeatureEnabled(); this.expensesService .getExpenses({ offset: 0, limit: 10, ...queryParams }) .pipe(map((expenses) => (this.duplicateExpenses = expenses))) .subscribe(noop); } - setIsManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - dismissDuplicates(duplicateExpenseIds: string[], targetExpenseIds: string[]): Observable { return this.expensesService.dismissDuplicates(duplicateExpenseIds, targetExpenseIds); } diff --git a/src/app/fyle/my-create-report/my-create-report.page.html b/src/app/fyle/my-create-report/my-create-report.page.html index da0fd9e617..20d0df9dd6 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.html +++ b/src/app/fyle/my-create-report/my-create-report.page.html @@ -67,9 +67,8 @@ [showDt]="checkShowDt(etxn,i)" [isSelectionModeEnabled]="true" [selectedElements]="selectedElements" - [isFromReports]="true" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (cardClickedForSelection)="selectExpense($event)" + [isFromReports]="true" >
diff --git a/src/app/fyle/my-create-report/my-create-report.page.ts b/src/app/fyle/my-create-report/my-create-report.page.ts index eaa6fa5976..6a7de043db 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.ts +++ b/src/app/fyle/my-create-report/my-create-report.page.ts @@ -16,7 +16,6 @@ import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expen import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; import { Report } from '../../core/models/platform/v1/report.model'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-my-create-report', templateUrl: './my-create-report.page.html', @@ -51,8 +50,6 @@ export class MyCreateReportPage implements OnInit { emptyInput = false; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private transactionService: TransactionService, private activatedRoute: ActivatedRoute, @@ -65,8 +62,7 @@ export class MyCreateReportPage implements OnInit { private refinerService: RefinerService, private expensesService: ExpensesService, private orgSettingsService: OrgSettingsService, - private spenderReportsService: SpenderReportsService, - private launchDarklyService: LaunchDarklyService + private spenderReportsService: SpenderReportsService ) {} detectTitleChange(): void { @@ -213,18 +209,11 @@ export class MyCreateReportPage implements OnInit { this.selectedExpenseIDs = (expenseIDs ? JSON.parse(expenseIDs) : []) as string[]; } - setIsManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - ionViewWillEnter(): void { this.isSelectedAll = true; this.selectedElements = []; this.checkTxnIds(); - this.setIsManualFlagFeatureEnabled(); let queryParams = { report_id: 'is.null', diff --git a/src/app/fyle/my-expenses/my-expenses.page.html b/src/app/fyle/my-expenses/my-expenses.page.html index ce396c8dd8..1e2a7754c4 100644 --- a/src/app/fyle/my-expenses/my-expenses.page.html +++ b/src/app/fyle/my-expenses/my-expenses.page.html @@ -192,7 +192,6 @@ [previousExpenseCreatedAt]="list[i-1]?.created_at" [isSelectionModeEnabled]="selectionMode" [selectedElements]="selectedElements" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (goToTransaction)="goToTransaction($event)" (setMultiselectMode)="switchSelectionMode($event)" (cardClickedForSelection)="selectExpense($event)" diff --git a/src/app/fyle/my-expenses/my-expenses.page.ts b/src/app/fyle/my-expenses/my-expenses.page.ts index d3910fd86a..eff4a8e406 100644 --- a/src/app/fyle/my-expenses/my-expenses.page.ts +++ b/src/app/fyle/my-expenses/my-expenses.page.ts @@ -77,7 +77,6 @@ import { AddTxnToReportDialogComponent } from './add-txn-to-report-dialog/add-tx import { MyExpensesService } from './my-expenses.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; import { Report } from 'src/app/core/models/platform/v1/report.model'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-my-expenses', @@ -195,8 +194,6 @@ export class MyExpensesPage implements OnInit { restrictPendingTransactionsEnabled = false; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private networkService: NetworkService, private loaderService: LoaderService, @@ -228,8 +225,7 @@ export class MyExpensesPage implements OnInit { private navController: NavController, private expenseService: ExpensesService, private sharedExpenseService: SharedExpenseService, - private spenderReportsService: SpenderReportsService, - private launchDarklyService: LaunchDarklyService + private spenderReportsService: SpenderReportsService ) {} get HeaderState(): typeof HeaderState { @@ -252,15 +248,8 @@ export class MyExpensesPage implements OnInit { this.isSearchBarFocused = true; } - setIsManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - ngOnInit(): void { this.setupNetworkWatcher(); - this.setIsManualFlagFeatureEnabled(); } formatTransactions(transactions: Partial[]): Partial[] { diff --git a/src/app/fyle/my-reports/my-reports.page.html b/src/app/fyle/my-reports/my-reports.page.html index 1c13767cd2..3f9375028e 100644 --- a/src/app/fyle/my-reports/my-reports.page.html +++ b/src/app/fyle/my-reports/my-reports.page.html @@ -100,7 +100,6 @@ [erpt]="erpt" [prevDate]="list[i-1]?.rp_created_at" [simplifyReportsEnabled]="simplifyReportsSettings.enabled" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (gotoReport)="onReportClick($event)" (deleteReport)="onDeleteReportClick($event)" (viewComments)="onViewCommentsClick()" diff --git a/src/app/fyle/my-reports/my-reports.page.ts b/src/app/fyle/my-reports/my-reports.page.ts index 5af4a0b095..90d372c612 100644 --- a/src/app/fyle/my-reports/my-reports.page.ts +++ b/src/app/fyle/my-reports/my-reports.page.ts @@ -37,7 +37,6 @@ import * as dayjs from 'dayjs'; import { AllowedPaymentModes } from 'src/app/core/models/allowed-payment-modes.enum'; import { DeletePopoverParams } from 'src/app/core/models/delete-popover-params.model'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; type Filters = Partial<{ state: string | string[]; @@ -111,8 +110,6 @@ export class MyReportsPage { nonReimbursableOrg$: Observable; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private networkService: NetworkService, private loaderService: LoaderService, @@ -128,8 +125,7 @@ export class MyReportsPage { private modalController: ModalController, private orgSettingsService: OrgSettingsService, private reportStatePipe: ReportState, - private expensesService: ExpensesService, - private launchDarklyService: LaunchDarklyService + private expensesService: ExpensesService ) {} get HeaderState(): typeof HeaderState { @@ -146,7 +142,6 @@ export class MyReportsPage { }); this.isLoading = true; - this.setManualFlagFeatureEnabled(); this.setupNetworkWatcher(); this.searchText = ''; @@ -320,12 +315,6 @@ export class MyReportsPage { }); } - setManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - loadData(event: { target?: { complete?: () => void } }): void { this.currentPageNumber = this.currentPageNumber + 1; const params = this.loadData$.getValue(); diff --git a/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.html b/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.html index d34c00fe2e..4d1afaac6f 100644 --- a/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.html +++ b/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.html @@ -44,9 +44,8 @@ [previousExpenseCreatedAt]="list[i - 1]?.created_at" [isSelectionModeEnabled]="true" [selectedElements]="selectedElements" - [isFromViewReports]="true" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (cardClickedForSelection)="toggleExpense(expense)" + [isFromViewReports]="true" > diff --git a/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.ts b/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.ts index 34c5a57fdc..da0387c7b6 100644 --- a/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.ts +++ b/src/app/fyle/my-view-report/add-expenses-to-report/add-expenses-to-report.component.ts @@ -4,7 +4,6 @@ import { Observable } from 'rxjs'; import { CurrencyService } from 'src/app/core/services/currency.service'; import { Router } from '@angular/router'; import { Expense } from 'src/app/core/models/platform/v1/expense.model'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-add-expenses-to-report', @@ -30,13 +29,10 @@ export class AddExpensesToReportComponent implements OnInit { homeCurrency: string; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private modalController: ModalController, private currencyService: CurrencyService, - private router: Router, - private launchDarklyService: LaunchDarklyService + private router: Router ) {} close() { @@ -86,7 +82,6 @@ export class AddExpensesToReportComponent implements OnInit { ionViewWillEnter() { this.isSelectedAll = true; this.homeCurrency$ = this.currencyService.getHomeCurrency(); - this.setIsManualFlagFeatureEnabled(); const selectedExpenses = []; this.unreportedExpenses.forEach((expense, i) => { selectedExpenses.push(this.unreportedExpenses[i]); @@ -95,12 +90,6 @@ export class AddExpensesToReportComponent implements OnInit { this.updateSelectedExpenses(); } - setIsManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - addNewExpense() { this.router.navigate([ '/', diff --git a/src/app/fyle/my-view-report/my-view-report.page.html b/src/app/fyle/my-view-report/my-view-report.page.html index 549c270d5f..abea74ae2e 100644 --- a/src/app/fyle/my-view-report/my-view-report.page.html +++ b/src/app/fyle/my-view-report/my-view-report.page.html @@ -162,10 +162,9 @@ [expenseIndex]="i" [previousExpenseTxnDate]="list[i-1]?.spent_at" [previousExpenseCreatedAt]="list[i-1]?.created_at" + (goToTransaction)="goToTransaction($event)" [isFromReports]="true" [isFromViewReports]="true" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" - (goToTransaction)="goToTransaction($event)" > diff --git a/src/app/fyle/my-view-report/my-view-report.page.ts b/src/app/fyle/my-view-report/my-view-report.page.ts index dd61daf830..9de76546d2 100644 --- a/src/app/fyle/my-view-report/my-view-report.page.ts +++ b/src/app/fyle/my-view-report/my-view-report.page.ts @@ -35,7 +35,6 @@ import { ShareReportComponent } from './share-report/share-report.component'; import { PlatformHandlerService } from 'src/app/core/services/platform-handler.service'; import { BackButtonActionPriority } from 'src/app/core/models/back-button-action-priority.enum'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-my-view-report', templateUrl: './my-view-report.page.html', @@ -112,8 +111,6 @@ export class MyViewReportPage { hardwareBackButtonAction: Subscription; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private activatedRoute: ActivatedRoute, private reportService: ReportService, @@ -132,8 +129,7 @@ export class MyViewReportPage { private refinerService: RefinerService, private orgSettingsService: OrgSettingsService, private platformHandlerService: PlatformHandlerService, - private spenderReportsService: SpenderReportsService, - private launchDarklyService: LaunchDarklyService + private spenderReportsService: SpenderReportsService ) {} get Segment(): typeof ReportPageSegment { @@ -164,15 +160,8 @@ export class MyViewReportPage { return orgSettings?.simplified_report_closure_settings?.enabled; } - setIsManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - ionViewWillEnter(): void { this.setupNetworkWatcher(); - this.setIsManualFlagFeatureEnabled(); this.reportId = this.activatedRoute.snapshot.params.id as string; this.navigateBack = !!this.activatedRoute.snapshot.params.navigateBack; diff --git a/src/app/fyle/potential-duplicates/potential-duplicates.page.html b/src/app/fyle/potential-duplicates/potential-duplicates.page.html index 7b538292cf..dc82d371be 100644 --- a/src/app/fyle/potential-duplicates/potential-duplicates.page.html +++ b/src/app/fyle/potential-duplicates/potential-duplicates.page.html @@ -32,7 +32,6 @@ [isFromPotentialDuplicates]="true" [expense]="expense" [isDismissable]="duplicatesSet?.length > 2" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (dismissed)="dismiss($event)" (goToTransaction)="goToTransaction($event)" > diff --git a/src/app/fyle/potential-duplicates/potential-duplicates.page.ts b/src/app/fyle/potential-duplicates/potential-duplicates.page.ts index 15ef3e6782..065386fa85 100644 --- a/src/app/fyle/potential-duplicates/potential-duplicates.page.ts +++ b/src/app/fyle/potential-duplicates/potential-duplicates.page.ts @@ -8,7 +8,6 @@ import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-proper import { TrackingService } from 'src/app/core/services/tracking.service'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; type Expenses = Expense[]; @@ -30,20 +29,16 @@ export class PotentialDuplicatesPage { isLoading = true; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private expensesService: ExpensesService, private router: Router, private snackbarProperties: SnackbarPropertiesService, private matSnackBar: MatSnackBar, - private trackingService: TrackingService, - private launchDarklyService: LaunchDarklyService + private trackingService: TrackingService ) {} ionViewWillEnter(): void { this.selectedSet = 0; - this.setIsManualFlagFeatureEnabled(); this.duplicateSets$ = this.loadData$.pipe( switchMap(() => @@ -87,12 +82,6 @@ export class PotentialDuplicatesPage { }); } - setIsManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - getDuplicates(): Observable { return this.expensesService .getDuplicateSets() diff --git a/src/app/fyle/team-reports/team-reports.page.html b/src/app/fyle/team-reports/team-reports.page.html index fbbef178d1..78c7c8f82c 100644 --- a/src/app/fyle/team-reports/team-reports.page.html +++ b/src/app/fyle/team-reports/team-reports.page.html @@ -79,7 +79,6 @@ [erpt]="erpt" [prevDate]="list[i-1]?.rp_created_at" [simplifyReportsEnabled]="simplifyReportsSettings.enabled" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (gotoReport)="onReportClick($event)" > diff --git a/src/app/fyle/team-reports/team-reports.page.ts b/src/app/fyle/team-reports/team-reports.page.ts index 41bc152fc7..1ada0e509f 100644 --- a/src/app/fyle/team-reports/team-reports.page.ts +++ b/src/app/fyle/team-reports/team-reports.page.ts @@ -26,7 +26,6 @@ import { ReportState } from 'src/app/shared/pipes/report-state.pipe'; import { GetTasksQueryParamsWithFilters } from 'src/app/core/models/get-tasks-query-params-with-filters.model'; import { GetTasksQueryParams } from 'src/app/core/models/get-tasks.query-params.model'; import { TeamReportsFilters } from 'src/app/core/models/team-reports-filters.model'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-team-reports', @@ -48,8 +47,6 @@ export class TeamReportsPage implements OnInit { isLoading = false; - isManualFlagFeatureEnabled: { value: boolean }; - isLoadingDataInInfiniteScroll: boolean; loadData$: BehaviorSubject>; @@ -98,8 +95,7 @@ export class TeamReportsPage implements OnInit { private apiV2Service: ApiV2Service, private tasksService: TasksService, private orgSettingsService: OrgSettingsService, - private reportStatePipe: ReportState, - private launchDarklyService: LaunchDarklyService + private reportStatePipe: ReportState ) {} get HeaderState() { @@ -108,7 +104,6 @@ export class TeamReportsPage implements OnInit { ngOnInit() { this.setupNetworkWatcher(); - this.setManualFlagFeatureEnabled(); } ionViewWillLeave() { @@ -238,12 +233,6 @@ export class TeamReportsPage implements OnInit { }); } - setManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - loadData(event: { target: HTMLIonInfiniteScrollElement }) { this.currentPageNumber = this.currentPageNumber + 1; const params = this.loadData$.getValue(); diff --git a/src/app/fyle/view-expense/view-expense.page.html b/src/app/fyle/view-expense/view-expense.page.html index 3a87918722..57675b1624 100644 --- a/src/app/fyle/view-expense/view-expense.page.html +++ b/src/app/fyle/view-expense/view-expense.page.html @@ -19,19 +19,15 @@ - - - - - - - - - + + + + + diff --git a/src/app/fyle/view-expense/view-expense.page.ts b/src/app/fyle/view-expense/view-expense.page.ts index 9ca3f5a479..70666a9a3c 100644 --- a/src/app/fyle/view-expense/view-expense.page.ts +++ b/src/app/fyle/view-expense/view-expense.page.ts @@ -36,7 +36,6 @@ import { AccountType } from 'src/app/core/models/platform/v1/account.model'; import { ExpenseState } from 'src/app/core/models/expense-state.enum'; import { TransactionStatusInfoPopoverComponent } from 'src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component'; import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-view-expense', @@ -60,8 +59,6 @@ export class ViewExpensePage { canFlagOrUnflag$: Observable; - isManualFlaggingFeatureEnabled$: Observable<{ value: boolean }>; - canDelete$: Observable; orgSettings: OrgSettings; @@ -155,8 +152,7 @@ export class ViewExpensePage { private dependentFieldsService: DependentFieldsService, private spenderExpensesService: SpenderExpensesService, private approverExpensesService: ApproverExpensesService, - private approverReportsService: ApproverReportsService, - private launchDarklyService: LaunchDarklyService + private approverReportsService: ApproverReportsService ) {} get ExpenseView(): typeof ExpenseView { @@ -364,8 +360,6 @@ export class ViewExpensePage { this.comments$ = this.statusService.find('transactions', this.expenseId); - this.isManualFlaggingFeatureEnabled$ = this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled(); - this.canFlagOrUnflag$ = this.expenseWithoutCustomProperties$.pipe( filter(() => this.view === ExpenseView.team), map((expense) => diff --git a/src/app/fyle/view-team-report/view-team-report.page.html b/src/app/fyle/view-team-report/view-team-report.page.html index d36bb3a8cb..36799e44dc 100644 --- a/src/app/fyle/view-team-report/view-team-report.page.html +++ b/src/app/fyle/view-team-report/view-team-report.page.html @@ -166,10 +166,9 @@ [expenseIndex]="i" [previousExpenseTxnDate]="list[i-1]?.spent_at" [previousExpenseCreatedAt]="list[i-1]?.created_at" + (goToTransaction)="goToTransaction($event)" [isFromReports]="true" [isFromViewReports]="true" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" - (goToTransaction)="goToTransaction($event)" > diff --git a/src/app/fyle/view-team-report/view-team-report.page.ts b/src/app/fyle/view-team-report/view-team-report.page.ts index cfe2bac665..89096b83a8 100644 --- a/src/app/fyle/view-team-report/view-team-report.page.ts +++ b/src/app/fyle/view-team-report/view-team-report.page.ts @@ -32,7 +32,6 @@ import { ExpensesService } from 'src/app/core/services/platform/v1/approver/expe import { Expense } from 'src/app/core/models/platform/v1/expense.model'; import { ShareReportComponent } from './share-report/share-report.component'; import { FyViewReportInfoComponent } from 'src/app/shared/components/fy-view-report-info/fy-view-report-info.component'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-view-team-report', templateUrl: './view-team-report.page.html', @@ -131,8 +130,6 @@ export class ViewTeamReportPage { timeSpentOnEditingReportName: number; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private activatedRoute: ActivatedRoute, private reportService: ReportService, @@ -151,8 +148,7 @@ export class ViewTeamReportPage { private refinerService: RefinerService, private statusService: StatusService, private humanizeCurrency: HumanizeCurrencyPipe, - private orgSettingsService: OrgSettingsService, - private launchDarklyService: LaunchDarklyService + private orgSettingsService: OrgSettingsService ) {} ionViewWillLeave() { @@ -201,7 +197,6 @@ export class ViewTeamReportPage { ionViewWillEnter() { this.isExpensesLoading = true; this.setupNetworkWatcher(); - this.setIsManualFlagFeatureEnabled(); this.navigateBack = this.activatedRoute.snapshot.params.navigate_back; @@ -338,12 +333,6 @@ export class ViewTeamReportPage { this.refreshApprovals$.next(null); } - setIsManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - toggleTooltip() { this.canShowTooltip = !this.canShowTooltip; } diff --git a/src/app/shared/components/create-new-report-v2/create-new-report.component.html b/src/app/shared/components/create-new-report-v2/create-new-report.component.html index 1d0e751cf4..2777b65f82 100644 --- a/src/app/shared/components/create-new-report-v2/create-new-report.component.html +++ b/src/app/shared/components/create-new-report-v2/create-new-report.component.html @@ -57,7 +57,6 @@ [previousExpenseCreatedAt]="selectedExpensesToReport[i - 1]?.created_at" [isSelectionModeEnabled]="true" [selectedElements]="selectedElements" - [isManualFlagFeatureEnabled]="isManualFlagFeatureEnabled.value" (cardClickedForSelection)="selectExpense($event)" > diff --git a/src/app/shared/components/create-new-report-v2/create-new-report.component.ts b/src/app/shared/components/create-new-report-v2/create-new-report.component.ts index 2155ddef5b..63f42bea70 100644 --- a/src/app/shared/components/create-new-report-v2/create-new-report.component.ts +++ b/src/app/shared/components/create-new-report-v2/create-new-report.component.ts @@ -12,7 +12,6 @@ import { CurrencyService } from 'src/app/core/services/currency.service'; import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; import { Report } from 'src/app/core/models/platform/v1/report.model'; -import { LaunchDarklyService } from 'src/app/core/services/launch-darkly.service'; @Component({ selector: 'app-create-new-report', @@ -42,8 +41,6 @@ export class CreateNewReportComponent implements OnInit { showReportNameError: boolean; - isManualFlagFeatureEnabled: { value: boolean }; - constructor( private modalController: ModalController, private reportService: ReportService, @@ -51,8 +48,7 @@ export class CreateNewReportComponent implements OnInit { private refinerService: RefinerService, private currencyService: CurrencyService, private expenseFieldsService: ExpenseFieldsService, - private spenderReportsService: SpenderReportsService, - private launchDarklyService: LaunchDarklyService + private spenderReportsService: SpenderReportsService ) {} getReportTitle(): Subscription { @@ -81,15 +77,8 @@ export class CreateNewReportComponent implements OnInit { }); } - setIsManualFlagFeatureEnabled() { - this.launchDarklyService.checkIfManualFlaggingFeatureIsEnabled().subscribe((ldFlag) => { - this.isManualFlagFeatureEnabled = ldFlag; - }); - } - ionViewWillEnter(): void { this.getReportTitle(); - this.setIsManualFlagFeatureEnabled(); } selectExpense(expense: Expense): void { diff --git a/src/app/shared/components/expenses-card-v2/expenses-card.component.ts b/src/app/shared/components/expenses-card-v2/expenses-card.component.ts index 70576597f5..3c163e9742 100644 --- a/src/app/shared/components/expenses-card-v2/expenses-card.component.ts +++ b/src/app/shared/components/expenses-card-v2/expenses-card.component.ts @@ -68,8 +68,6 @@ export class ExpensesCardComponent implements OnInit { @Input() showDt = true; - @Input() isManualFlagFeatureEnabled = false; - @Output() goToTransaction: EventEmitter<{ expense: Expense; expenseIndex: number }> = new EventEmitter<{ expense: Expense; expenseIndex: number; @@ -269,11 +267,6 @@ export class ExpensesCardComponent implements OnInit { this.expense.source_account?.type === AccountType.PERSONAL_CASH_ACCOUNT && this.expense.is_reimbursable; } - setIsPolicyViolated() { - const isManualFlagEnabledAndFlagged = this.isManualFlagFeatureEnabled && this.expense.is_manually_flagged; - this.isPolicyViolated = isManualFlagEnabledAndFlagged || this.expense.is_policy_flagged; - } - ngOnInit(): void { this.setupNetworkWatcher(); const orgSettings$ = this.orgSettingsService.get().pipe(shareReplay(1)); @@ -287,12 +280,12 @@ export class ExpensesCardComponent implements OnInit { this.isPerDiem = this.category === 'per diem'; this.isDraft = this.sharedExpenseService.isExpenseInDraft(this.expense); + this.isPolicyViolated = this.expense.is_manually_flagged || this.expense.is_policy_flagged; this.isCriticalPolicyViolated = this.sharedExpenseService.isCriticalPolicyViolatedExpense(this.expense); this.vendorDetails = this.sharedExpenseService.getVendorDetails(this.expense); this.expenseFieldsService.getAllMap().subscribe((expenseFields) => { this.expenseFields = expenseFields; }); - this.setIsPolicyViolated(); this.currencyService .getHomeCurrency() diff --git a/src/app/shared/components/reports-card/reports-card.component.html b/src/app/shared/components/reports-card/reports-card.component.html index 4ea78f0fd9..602f0d9257 100644 --- a/src/app/shared/components/reports-card/reports-card.component.html +++ b/src/app/shared/components/reports-card/reports-card.component.html @@ -13,7 +13,7 @@
{{ erpt.rp_purpose | ellipsis : 40 }} - +
diff --git a/src/app/shared/components/reports-card/reports-card.component.ts b/src/app/shared/components/reports-card/reports-card.component.ts index 65dae1997f..9ef8eb7b74 100644 --- a/src/app/shared/components/reports-card/reports-card.component.ts +++ b/src/app/shared/components/reports-card/reports-card.component.ts @@ -10,8 +10,6 @@ import { ExtendedReport } from 'src/app/core/models/report.model'; export class ReportsCardComponent implements OnInit { @Input() erpt: ExtendedReport; - @Input() isManualFlagFeatureEnabled: boolean; - @Input() prevDate: Date; @Input() simplifyReportsEnabled: boolean; From 38d80a4891ae38767b31b221d9b5721199bdb211 Mon Sep 17 00:00:00 2001 From: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Date: Tue, 14 May 2024 18:09:34 +0530 Subject: [PATCH 011/262] fix: sentry issue fix for add-edit-expense (#2987) * fix: sentry issue fix for add-edit-expense * minor --- src/app/fyle/add-edit-expense/add-edit-expense.page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts index 1775e6723c..fa409c1880 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts @@ -1785,7 +1785,7 @@ export class AddEditExpensePage implements OnInit { }); } - if (etxn.tx.tax_group_id) { + if (etxn.tx.tax_group_id && taxGroups) { const tg = taxGroups.find((tg) => tg.id === etxn.tx.tax_group_id); this.fg.patchValue({ tax_group: tg, From 54c065ec996064e3c70fc0d31f1a532506c00301 Mon Sep 17 00:00:00 2001 From: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Date: Tue, 14 May 2024 18:11:01 +0530 Subject: [PATCH 012/262] fix: fix mixpanel event for opt-in task click (#2990) --- src/app/core/services/tracking.service.ts | 16 ++++--------- .../fyle/dashboard/dashboard.service.spec.ts | 23 +++++++++++++++++++ src/app/fyle/dashboard/dashboard.service.ts | 16 +++++++++++++ .../dashboard/stats/stats.component.spec.ts | 19 +++++++++------ .../fyle/dashboard/stats/stats.component.ts | 15 ++++++++---- 5 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/app/core/services/tracking.service.ts b/src/app/core/services/tracking.service.ts index 8c4abee614..9ef423098a 100644 --- a/src/app/core/services/tracking.service.ts +++ b/src/app/core/services/tracking.service.ts @@ -386,14 +386,6 @@ export class TrackingService { this.eventTrack('dashboard action sheet button clicked', properties); } - dashboardOnUnreportedExpensesClick(properties = {}): void { - this.eventTrack('dashboard unreported expenses clicked', properties); - } - - dashboardOnIncompleteExpensesClick(properties = {}): void { - this.eventTrack('dashboard incomplete expenses clicked', properties); - } - dashboardOnIncompleteCardExpensesClick(properties = {}): void { this.eventTrack('dashboard incomplete corporate card expenses clicked', properties); } @@ -402,10 +394,6 @@ export class TrackingService { this.eventTrack('dashboard total corporate card expenses clicked', properties); } - dashboardOnReportPillClick(properties: { State: string }): void { - this.eventTrack('dashboard report pill clicked', properties); - } - //View expenses viewExpenseClicked(properties: ExpenseClickProperties): void { this.eventTrack('View expense clicked', properties); @@ -716,4 +704,8 @@ export class TrackingService { commuteDeductionDetailsError(properties: HttpErrorResponse): void { this.eventTrack('Commute Deduction - Details Error', properties); } + + statsClicked(properties: { event: string }): void { + this.eventTrack('Dashboard Stats Clicked', properties); + } } diff --git a/src/app/fyle/dashboard/dashboard.service.spec.ts b/src/app/fyle/dashboard/dashboard.service.spec.ts index 60bbdf1d44..e163be2aa3 100644 --- a/src/app/fyle/dashboard/dashboard.service.spec.ts +++ b/src/app/fyle/dashboard/dashboard.service.spec.ts @@ -17,6 +17,7 @@ import { } from '../../core/mock-data/stats.data'; import { DashboardService } from './dashboard.service'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { ReportStates } from './stat-badge/report-states'; describe('DashboardService', () => { let dashboardService: DashboardService; @@ -197,4 +198,26 @@ describe('DashboardService', () => { done(); }); }); + + describe('getReportStateMapping():', () => { + it('should return "Approved" if report state is APPROVED', () => { + expect(dashboardService.getReportStateMapping(ReportStates.APPROVED)).toEqual('Approved'); + }); + + it('should return "Draft" if report state is DRAFT', () => { + expect(dashboardService.getReportStateMapping(ReportStates.DRAFT)).toEqual('Draft'); + }); + + it('should return "Payment Pending" if report state is PAYMENT_PENDING', () => { + expect(dashboardService.getReportStateMapping(ReportStates.PAYMENT_PENDING)).toEqual('Payment Pending'); + }); + + it('should return "Processing" if report state is PAYMENT_PROCESSING', () => { + expect(dashboardService.getReportStateMapping(ReportStates.PAYMENT_PROCESSING)).toEqual('Processing'); + }); + + it('should return "Reported" if report state is APPROVER_PENDING', () => { + expect(dashboardService.getReportStateMapping(ReportStates.APPROVER_PENDING)).toEqual('Reported'); + }); + }); }); diff --git a/src/app/fyle/dashboard/dashboard.service.ts b/src/app/fyle/dashboard/dashboard.service.ts index 5847af7076..01fe797812 100644 --- a/src/app/fyle/dashboard/dashboard.service.ts +++ b/src/app/fyle/dashboard/dashboard.service.ts @@ -7,6 +7,7 @@ import { CorporateCreditCardExpenseService } from 'src/app/core/services/corpora import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { Stats } from '../../core/models/stats.model'; import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender/reports.service'; +import { ReportStates } from './stat-badge/report-states'; @Injectable() export class DashboardService { @@ -75,4 +76,19 @@ export class DashboardService { getCCCDetails(): Observable { return this.corporateCreditCardExpenseService.getAssignedCards(); } + + getReportStateMapping(state: ReportStates): string { + switch (state) { + case ReportStates.DRAFT: + return 'Draft'; + case ReportStates.APPROVER_PENDING: + return 'Reported'; + case ReportStates.APPROVED: + return 'Approved'; + case ReportStates.PAYMENT_PENDING: + return 'Payment Pending'; + case ReportStates.PAYMENT_PROCESSING: + return 'Processing'; + } + } } diff --git a/src/app/fyle/dashboard/stats/stats.component.spec.ts b/src/app/fyle/dashboard/stats/stats.component.spec.ts index ae385b4f72..4e1ed256b6 100644 --- a/src/app/fyle/dashboard/stats/stats.component.spec.ts +++ b/src/app/fyle/dashboard/stats/stats.component.spec.ts @@ -37,16 +37,15 @@ describe('StatsComponent', () => { 'getReportsStats', 'getUnreportedExpensesStats', 'getIncompleteExpensesStats', + 'getReportStateMapping', ]); const currencyServiceSpy = jasmine.createSpyObj('CurrencyService', ['getHomeCurrency']); const routerSpy = jasmine.createSpyObj('Router', ['navigate']); const networkServiceSpy = jasmine.createSpyObj('NetworkService', ['connectivityWatcher', 'isOnline']); const trackingServiceSpy = jasmine.createSpyObj('TrackingService', [ 'appLaunchTime', - 'dashboardOnReportPillClick', - 'dashboardOnUnreportedExpensesClick', - 'dashboardOnIncompleteExpensesClick', 'dashboardLaunchTime', + 'statsClicked', ]); const orgSettingsServiceSpy = jasmine.createSpyObj('OrgSettingsService', ['get']); const orgServiceSpy = jasmine.createSpyObj('OrgService', ['getOrgs']); @@ -342,6 +341,8 @@ describe('StatsComponent', () => { }); it('goToReportsPage(): should navigate to reports page with query params', () => { + dashboardService.getReportStateMapping.and.returnValue('Approved'); + component.goToReportsPage(ReportStates.APPROVED); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_reports'], { @@ -349,8 +350,8 @@ describe('StatsComponent', () => { filters: JSON.stringify({ state: [ReportStates.APPROVED.toString()] }), }, }); - expect(trackingService.dashboardOnReportPillClick).toHaveBeenCalledOnceWith({ - State: ReportStates.APPROVED.toString(), + expect(trackingService.statsClicked).toHaveBeenCalledOnceWith({ + event: 'Clicked On Approved Reports', }); }); @@ -363,7 +364,9 @@ describe('StatsComponent', () => { filters: JSON.stringify({ state: ['READY_TO_REPORT'] }), }, }); - expect(trackingService.dashboardOnUnreportedExpensesClick).toHaveBeenCalledTimes(1); + expect(trackingService.statsClicked).toHaveBeenCalledOnceWith({ + event: 'Clicked On Unreported Expenses', + }); }); it('goToExpensesPage(): should navigate to expenses page with query params', () => { @@ -374,7 +377,9 @@ describe('StatsComponent', () => { filters: JSON.stringify({ state: ['DRAFT'] }), }, }); - expect(trackingService.dashboardOnIncompleteExpensesClick).toHaveBeenCalledTimes(1); + expect(trackingService.statsClicked).toHaveBeenCalledOnceWith({ + event: 'Clicked On Incomplete Expenses', + }); }); }); diff --git a/src/app/fyle/dashboard/stats/stats.component.ts b/src/app/fyle/dashboard/stats/stats.component.ts index a431b86e97..d3a6e107fd 100644 --- a/src/app/fyle/dashboard/stats/stats.component.ts +++ b/src/app/fyle/dashboard/stats/stats.component.ts @@ -195,8 +195,10 @@ export class StatsComponent implements OnInit { }, }); - this.trackingService.dashboardOnReportPillClick({ - State: state.toString(), + const reportState = this.dashboardService.getReportStateMapping(state); + + this.trackingService.statsClicked({ + event: `Clicked On ${reportState} Reports`, }); } @@ -207,13 +209,18 @@ export class StatsComponent implements OnInit { queryParams, }); - this.trackingService.dashboardOnUnreportedExpensesClick(); + this.trackingService.statsClicked({ + event: 'Clicked On Unreported Expenses', + }); } else { const queryParams: Params = { filters: JSON.stringify({ state: ['DRAFT'] }) }; this.router.navigate(['/', 'enterprise', 'my_expenses'], { queryParams, }); - this.trackingService.dashboardOnIncompleteExpensesClick(); + + this.trackingService.statsClicked({ + event: 'Clicked On Incomplete Expenses', + }); } } From 2c79afccffb6b579696ed86c573e749e80c0886c Mon Sep 17 00:00:00 2001 From: Aastha Bist Date: Tue, 14 May 2024 19:18:51 +0530 Subject: [PATCH 013/262] fix: revert back approver stats to public (#2988) --- src/app/core/services/tasks.service.spec.ts | 83 ++++++++++--------- src/app/core/services/tasks.service.ts | 76 ++++++++++------- .../dashboard/tasks/tasks-2.component.spec.ts | 18 ++-- .../fyle/dashboard/tasks/tasks.component.ts | 27 +++--- 4 files changed, 116 insertions(+), 88 deletions(-) diff --git a/src/app/core/services/tasks.service.spec.ts b/src/app/core/services/tasks.service.spec.ts index df861913a6..257d639060 100644 --- a/src/app/core/services/tasks.service.spec.ts +++ b/src/app/core/services/tasks.service.spec.ts @@ -66,6 +66,7 @@ import { expectedSentBackResponseSingularReport, } from '../mock-data/report-stats.data'; import { expectedReportsSinglePage } from '../mock-data/platform-report.data'; +import { apiEouRes } from '../mock-data/extended-org-user.data'; describe('TasksService', () => { let tasksService: TasksService; @@ -88,6 +89,7 @@ describe('TasksService', () => { const reportServiceSpy = jasmine.createSpyObj('ReportService', [ 'getReportAutoSubmissionDetails', 'getAllExtendedReports', + 'getReportStatsData', ]); const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseStats', 'getDuplicateSets']); const userEventServiceSpy = jasmine.createSpyObj('UserEventService', ['onTaskCacheClear']); @@ -289,23 +291,30 @@ describe('TasksService', () => { }); }); - it('should be able to fetch team reports tasks is role is APPROVER', (done) => { + it('should be able to fetch team reports tasks', (done) => { authService.getEou.and.returnValue(new Promise((resolve) => resolve(extendedOrgUserResponse))); currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); humanizeCurrencyPipe.transform - .withArgs(expectedReportStats.report.total_amount, homeCurrency, true) + .withArgs(teamReportResponse[0].aggregates[1].function_value, homeCurrency, true) .and.returnValue('733.48K'); humanizeCurrencyPipe.transform - .withArgs(expectedReportStats.report.total_amount, homeCurrency) + .withArgs(teamReportResponse[0].aggregates[1].function_value, homeCurrency) .and.returnValue('₹733.48K'); - approverReportsService.getReportsStats - .withArgs({ - next_approver_user_ids: `cs.[${extendedOrgUserResponse.us.id}]`, - state: 'eq.APPROVER_PENDING', - }) - .and.returnValue(of(expectedReportStats.report)); + reportService.getReportStatsData + .withArgs( + { + approved_by: 'cs.{' + extendedOrgUserResponse.ou.id + '}', + rp_approval_state: ['in.(APPROVAL_PENDING)'], + rp_state: ['in.(APPROVER_PENDING)'], + sequential_approval_turn: ['in.(true)'], + aggregates: 'count(rp_id),sum(rp_amount)', + scalar: true, + }, + false + ) + .and.returnValue(of(teamReportResponse)); tasksService.getTeamReportsTasks().subscribe((teamReportsTasks) => { expect(teamReportsTasks).toEqual([teamReportTaskSample]); @@ -313,23 +322,6 @@ describe('TasksService', () => { }); }); - it('should be able to return dummy team reports tasks is role is not APPROVER', (done) => { - authService.getEou.and.returnValue(new Promise((resolve) => resolve(extendedOrgUserResponseSpender))); - currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); - - humanizeCurrencyPipe.transform - .withArgs(expectedReportStats.report.total_amount, homeCurrency, true) - .and.returnValue('733.48K'); - humanizeCurrencyPipe.transform - .withArgs(expectedReportStats.report.total_amount, homeCurrency) - .and.returnValue('₹733.48K'); - - tasksService.getTeamReportsTasks().subscribe((teamReportsTasks) => { - expect(teamReportsTasks).toEqual([]); - done(); - }); - }); - it('should be able to fetch potential duplicate tasks', (done) => { setupData(); expensesService.getDuplicateSets.and.returnValue(of(expenseDuplicateSets)); @@ -374,6 +366,14 @@ describe('TasksService', () => { }); }); + it('should make sure that stats dont fail even if aggregates are not present in response', () => { + const mappedStatsReponse = tasksService.getStatsFromResponse([], 'count(rp_id)', 'sum(rp_amount)'); + expect(mappedStatsReponse).toEqual({ + totalCount: 0, + totalAmount: 0, + }); + }); + it('should be able to fetch expensesTaskCount', (done) => { tasksService.expensesTaskCount$.next(10); tasksService @@ -663,9 +663,9 @@ describe('TasksService', () => { const tasks2 = tasksService.mapAggregateToTeamReportTask( { - total_amount: 0, - count: 0, - } as PlatformReportsStatsResponse, + totalAmount: 0, + totalCount: 0, + }, homeCurrency ); @@ -747,12 +747,19 @@ describe('TasksService', () => { .and.returnValue(of(expectedSentBackResponse)); authService.getEou.and.returnValue(new Promise((resolve) => resolve(extendedOrgUserResponse))); currencyService.getHomeCurrency.and.returnValue(of(homeCurrency)); - approverReportsService.getReportsStats - .withArgs({ - next_approver_user_ids: `cs.[${extendedOrgUserResponse.us.id}]`, - state: 'eq.APPROVER_PENDING', - }) - .and.returnValue(of(expectedReportStats.report)); + reportService.getReportStatsData + .withArgs( + { + approved_by: 'cs.{' + extendedOrgUserResponse.ou.id + '}', + rp_approval_state: ['in.(APPROVAL_PENDING)'], + rp_state: ['in.(APPROVER_PENDING)'], + sequential_approval_turn: ['in.(true)'], + aggregates: 'count(rp_id),sum(rp_amount)', + scalar: true, + }, + false + ) + .and.returnValue(of(teamReportResponse)); expensesService.getDuplicateSets.and.returnValue(of(expenseDuplicateSets)); expensesService.getExpenseStats .withArgs({ @@ -986,9 +993,9 @@ describe('TasksService', () => { const tasks = tasksService.mapAggregateToTeamReportTask( { - total_amount: teamReportResponse[0].aggregates[1].function_value, - count: 1, - } as PlatformReportsStatsResponse, + totalAmount: teamReportResponse[0].aggregates[1].function_value, + totalCount: 1, + }, homeCurrency ); diff --git a/src/app/core/services/tasks.service.ts b/src/app/core/services/tasks.service.ts index 3501738a78..bf91d4fd5e 100644 --- a/src/app/core/services/tasks.service.ts +++ b/src/app/core/services/tasks.service.ts @@ -23,6 +23,7 @@ import { SpenderReportsService } from './platform/v1/spender/reports.service'; import { PlatformReportsStatsResponse } from '../models/platform/v1/report-stats-response.model'; import { ApproverReportsService } from './platform/v1/approver/reports.service'; import { ReportState } from '../models/platform/v1/report.model'; +import { Datum } from '../models/v2/stats-response.model'; @Injectable({ providedIn: 'root', @@ -474,26 +475,21 @@ export class TasksService { ); } - getTeamReportsStats(): Observable { + getTeamReportsStats(): Observable { return from(this.authService.getEou()).pipe( - switchMap((eou) => { - if (eou.ou.roles.includes('APPROVER')) { - return this.approverReportsService.getReportsStats({ - next_approver_user_ids: `cs.[${eou.us.id}]`, - state: `eq.${ReportState.APPROVER_PENDING}`, - }); - } - const zeroResponse: PlatformReportsStatsResponse = { - count: 0, - failed_amount: null, - failed_count: null, - processing_amount: 0, - processing_count: 0, - reimbursable_amount: 0, - total_amount: 0, - }; - return of(zeroResponse); - }) + switchMap((eou) => + this.reportService.getReportStatsData( + { + approved_by: 'cs.{' + eou.ou.id + '}', + rp_approval_state: ['in.(APPROVAL_PENDING)'], + rp_state: ['in.(APPROVER_PENDING)'], + sequential_approval_turn: ['in.(true)'], + aggregates: 'count(rp_id),sum(rp_amount)', + scalar: true, + }, + false + ) + ) ); } @@ -502,8 +498,8 @@ export class TasksService { reportsStats: this.getTeamReportsStats(), homeCurrency: this.currencyService.getHomeCurrency(), }).pipe( - map(({ reportsStats, homeCurrency }: { reportsStats: PlatformReportsStatsResponse; homeCurrency: string }) => - this.mapAggregateToTeamReportTask(reportsStats, homeCurrency) + map(({ reportsStats, homeCurrency }: { reportsStats: Datum[]; homeCurrency: string }) => + this.mapAggregateToTeamReportTask(this.mapScalarReportStatsResponse(reportsStats), homeCurrency) ) ); } @@ -540,6 +536,23 @@ export class TasksService { return task; } + getStatsFromResponse( + statsResponse: Datum[], + countName: string, + sumName: string + ): { totalCount: number; totalAmount: number } { + const countAggregate = statsResponse[0]?.aggregates.find((aggregate) => aggregate.function_name === countName) || 0; + const amountAggregate = statsResponse[0]?.aggregates.find((aggregate) => aggregate.function_name === sumName) || 0; + return { + totalCount: countAggregate && countAggregate.function_value, + totalAmount: amountAggregate && amountAggregate.function_value, + }; + } + + mapScalarReportStatsResponse(statsResponse: Datum[]): { totalCount: number; totalAmount: number } { + return this.getStatsFromResponse(statsResponse, 'count(rp_id)', 'sum(rp_amount)'); + } + mapPotentialDuplicatesTasks(duplicateSets: string[][]): DashboardTask[] { if (duplicateSets.length > 0) { const duplicateIds = duplicateSets.reduce((acc, curVal) => acc.concat(curVal), []); @@ -748,21 +761,24 @@ export class TasksService { } } - mapAggregateToTeamReportTask(aggregate: PlatformReportsStatsResponse, homeCurrency: string): DashboardTask[] { - if (aggregate.count > 0) { + mapAggregateToTeamReportTask( + aggregate: { totalCount: number; totalAmount: number }, + homeCurrency: string + ): DashboardTask[] { + if (aggregate.totalCount > 0) { return [ { - amount: this.humanizeCurrency.transform(aggregate.total_amount, homeCurrency, true), - count: aggregate.count, - header: `Report${aggregate.count === 1 ? '' : 's'} to be approved`, - subheader: `${aggregate.count} report${aggregate.count === 1 ? '' : 's'}${this.getAmountString( - aggregate.total_amount, + amount: this.humanizeCurrency.transform(aggregate.totalAmount, homeCurrency, true), + count: aggregate.totalCount, + header: `Report${aggregate.totalCount === 1 ? '' : 's'} to be approved`, + subheader: `${aggregate.totalCount} report${aggregate.totalCount === 1 ? '' : 's'}${this.getAmountString( + aggregate.totalAmount, homeCurrency - )} require${aggregate.count === 1 ? 's' : ''} your approval`, + )} require${aggregate.totalCount === 1 ? 's' : ''} your approval`, icon: TaskIcon.REPORT, ctas: [ { - content: `Show Report${aggregate.count === 1 ? '' : 's'}`, + content: `Show Report${aggregate.totalCount === 1 ? '' : 's'}`, event: TASKEVENT.openTeamReport, }, ], diff --git a/src/app/fyle/dashboard/tasks/tasks-2.component.spec.ts b/src/app/fyle/dashboard/tasks/tasks-2.component.spec.ts index 879fd046d4..55bedabe67 100644 --- a/src/app/fyle/dashboard/tasks/tasks-2.component.spec.ts +++ b/src/app/fyle/dashboard/tasks/tasks-2.component.spec.ts @@ -45,6 +45,7 @@ import { SpenderReportsService } from 'src/app/core/services/platform/v1/spender import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; import { expectedReportsSinglePage } from 'src/app/core/mock-data/platform-report.data'; import { apiEouRes } from 'src/app/core/mock-data/extended-org-user.data'; +import { apiReportRes } from 'src/app/core/mock-data/api-reports.data'; export function TestCases2(getTestBed) { return describe('test case set 2', () => { @@ -362,7 +363,7 @@ export function TestCases2(getTestBed) { loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); authService.getEou.and.resolveTo(apiEouRes); - approverReportsService.getAllReportsByParams.and.returnValue(of(expectedReportsSinglePage)); + reportService.getTeamReports.and.returnValue(of(apiReportRes)); }); it('should get all team reports and navigate to my view report page if task count is 1', fakeAsync(() => { @@ -371,16 +372,21 @@ export function TestCases2(getTestBed) { component.onTeamReportsTaskClick(taskCtaData3, mockDashboardTasksData[0]); tick(100); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Opening your report...'); - expect(approverReportsService.getAllReportsByParams).toHaveBeenCalledOnceWith({ - state: 'eq.APPROVER_PENDING', - next_approver_user_ids: `cs.[${apiEouRes.us.id}]`, + expect(reportService.getTeamReports).toHaveBeenCalledOnceWith({ + queryParams: { + rp_approval_state: ['in.(APPROVAL_PENDING)'], + rp_state: ['in.(APPROVER_PENDING)'], + sequential_approval_turn: ['in.(true)'], + }, + offset: 0, + limit: 1, }); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(router.navigate).toHaveBeenCalledOnceWith([ '/', 'enterprise', 'view_team_report', - { id: expectedReportsSinglePage[0].id, navigate_back: true }, + { id: apiReportRes.data[0].rp_id, navigate_back: true }, ]); })); @@ -388,7 +394,7 @@ export function TestCases2(getTestBed) { component.onTeamReportsTaskClick(taskCtaData3, dashboardTasksData[0]); tick(100); expect(loaderService.showLoader).not.toHaveBeenCalled(); - expect(approverReportsService.getAllReportsByParams).not.toHaveBeenCalled(); + expect(reportService.getTeamReports).not.toHaveBeenCalled(); expect(loaderService.hideLoader).not.toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'team_reports'], { queryParams: { filters: JSON.stringify({ state: ['APPROVER_PENDING'] }) }, diff --git a/src/app/fyle/dashboard/tasks/tasks.component.ts b/src/app/fyle/dashboard/tasks/tasks.component.ts index 884951d47f..064d1a06df 100644 --- a/src/app/fyle/dashboard/tasks/tasks.component.ts +++ b/src/app/fyle/dashboard/tasks/tasks.component.ts @@ -521,20 +521,19 @@ export class TasksComponent implements OnInit { onTeamReportsTaskClick(taskCta: TaskCta, task: DashboardTask): void { if (task.count === 1) { - from(this.authService.getEou()).subscribe((eou) => { - const queryParams = { - next_approver_user_ids: `cs.[${eou.us.id}]`, - state: `eq.${ReportState.APPROVER_PENDING}`, - }; - return from(this.loaderService.showLoader('Opening your report...')) - .pipe( - switchMap(() => this.approverReportsService.getAllReportsByParams(queryParams)), - finalize(() => this.loaderService.hideLoader()) - ) - .subscribe((res) => { - this.router.navigate(['/', 'enterprise', 'view_team_report', { id: res[0].id, navigate_back: true }]); - }); - }); + const queryParams = { + rp_approval_state: ['in.(APPROVAL_PENDING)'], + rp_state: ['in.(APPROVER_PENDING)'], + sequential_approval_turn: ['in.(true)'], + }; + from(this.loaderService.showLoader('Opening your report...')) + .pipe( + switchMap(() => this.reportService.getTeamReports({ queryParams, offset: 0, limit: 1 })), + finalize(() => this.loaderService.hideLoader()) + ) + .subscribe((res) => { + this.router.navigate(['/', 'enterprise', 'view_team_report', { id: res.data[0].rp_id, navigate_back: true }]); + }); } else { this.router.navigate(['/', 'enterprise', 'team_reports'], { queryParams: { From 04da9c4821759659a3d6cb0f5a797b80eaa0bd4e Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Wed, 15 May 2024 08:24:39 +0530 Subject: [PATCH 014/262] feat: replace downloadUrl() with generateUrls() in view mileage page (#2975) --- .../mock-data/platform/v1/expense.data.ts | 2 +- .../view-mileage/view-mileage.page.spec.ts | 63 ++++++++++++++++++- .../fyle/view-mileage/view-mileage.page.ts | 49 ++++++++++----- 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/app/core/mock-data/platform/v1/expense.data.ts b/src/app/core/mock-data/platform/v1/expense.data.ts index faf0f5f8b7..96922690ee 100644 --- a/src/app/core/mock-data/platform/v1/expense.data.ts +++ b/src/app/core/mock-data/platform/v1/expense.data.ts @@ -497,7 +497,7 @@ export const mileageExpense: Expense = deepFreeze({ expense_rule_data: null, expense_rule_id: null, extracted_data: null, - file_ids: [], + file_ids: ['fi1w2IE6JeqS'], files: [], foreign_amount: null, foreign_currency: null, diff --git a/src/app/fyle/view-mileage/view-mileage.page.spec.ts b/src/app/fyle/view-mileage/view-mileage.page.spec.ts index 97fafff44b..10147c5b95 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.spec.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.spec.ts @@ -38,7 +38,7 @@ import { apiTeamRptSingleRes, expectedReports } from 'src/app/core/mock-data/api import { cloneDeep, slice } from 'lodash'; import { isEmpty } from 'rxjs/operators'; import { txnStatusData } from 'src/app/core/mock-data/transaction-status.data'; -import { mileageExpense } from 'src/app/core/mock-data/platform/v1/expense.data'; +import { mileageExpense, platformExpenseData } from 'src/app/core/mock-data/platform/v1/expense.data'; import { Expense } from 'src/app/core/models/platform/v1/expense.model'; import { ExpenseState } from 'src/app/core/models/expense-state.enum'; import { AccountType } from 'src/app/core/models/platform/v1/account.model'; @@ -48,6 +48,10 @@ import { MileageRatesService } from 'src/app/core/services/mileage-rates.service import { platformMileageRatesSingleData } from 'src/app/core/mock-data/platform-mileage-rate.data'; import { CustomInput } from 'src/app/core/models/custom-input.model'; import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { SpenderFileService } from 'src/app/core/services/platform/v1/spender/file.service'; +import { ApproverFileService } from 'src/app/core/services/platform/v1/approver/file.service'; +import { generateUrlsBulkData1 } from 'src/app/core/mock-data/generate-urls-bulk-response.data'; +import { receiptInfoData1 } from 'src/app/core/mock-data/receipt-info.data'; describe('ViewMileagePage', () => { let component: ViewMileagePage; @@ -73,6 +77,8 @@ describe('ViewMileagePage', () => { let mileageRatesService: jasmine.SpyObj; let activateRouteMock: ActivatedRoute; let approverReportsService: jasmine.SpyObj; + let spenderFileService: jasmine.SpyObj; + let approverFileService: jasmine.SpyObj; beforeEach(waitForAsync(() => { const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['hideLoader', 'showLoader']); @@ -107,7 +113,11 @@ describe('ViewMileagePage', () => { const dependentFieldsServiceSpy = jasmine.createSpyObj('DependentFieldsService', [ 'getDependentFieldValuesForBaseField', ]); - const fileServiceSpy = jasmine.createSpyObj('FileService', ['findByTransactionId', 'downloadUrl']); + const fileServiceSpy = jasmine.createSpyObj('FileService', [ + 'findByTransactionId', + 'downloadUrl', + 'getReceiptsDetails', + ]); const spenderExpensesServiceSpy = jasmine.createSpyObj('SpenderExpensesService', ['getExpenseById']); const approverExpensesServiceSpy = jasmine.createSpyObj('ApproverExpensesService', ['getExpenseById']); const mileageRatesServiceSpy = jasmine.createSpyObj('MileageRatesService', [ @@ -115,6 +125,8 @@ describe('ViewMileagePage', () => { 'getApproverMileageRateById', ]); const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', ['ejectExpenses']); + const spenderFileServiceSpy = jasmine.createSpyObj('SpenderFileService', ['generateUrls']); + const approverFileServiceSpy = jasmine.createSpyObj('ApproverFileService', ['generateUrls']); TestBed.configureTestingModule({ declarations: [ViewMileagePage], @@ -200,6 +212,14 @@ describe('ViewMileagePage', () => { provide: ApproverReportsService, useValue: approverReportsServiceSpy, }, + { + provide: SpenderFileService, + useValue: spenderFileServiceSpy, + }, + { + provide: ApproverFileService, + useValue: approverFileServiceSpy, + }, { provide: ActivatedRoute, useValue: { @@ -237,6 +257,8 @@ describe('ViewMileagePage', () => { approverExpensesService = TestBed.inject(ApproverExpensesService) as jasmine.SpyObj; mileageRatesService = TestBed.inject(MileageRatesService) as jasmine.SpyObj; approverReportsService = TestBed.inject(ApproverReportsService) as jasmine.SpyObj; + spenderFileService = TestBed.inject(SpenderFileService) as jasmine.SpyObj; + approverFileService = TestBed.inject(ApproverFileService) as jasmine.SpyObj; activateRouteMock = TestBed.inject(ActivatedRoute); fixture.detectChanges(); @@ -1050,6 +1072,43 @@ describe('ViewMileagePage', () => { expect(component.reportExpenseCount).toEqual(3); expect(component.activeExpenseIndex).toEqual(2); }); + + it('should call spender file service to get map attachment details', (done) => { + component.mileageExpense$ = of(mileageExpense); + component.view = ExpenseView.individual; + spenderFileService.generateUrls.and.returnValue(of(generateUrlsBulkData1[0])); + fileService.getReceiptsDetails.and.returnValue(receiptInfoData1); + + component.ionViewWillEnter(); + + component.mapAttachment$.subscribe((res) => { + expect(res).toEqual(receiptInfoData1); + expect(spenderFileService.generateUrls).toHaveBeenCalledOnceWith(mileageExpense.file_ids[0]); + expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith( + generateUrlsBulkData1[0].name, + generateUrlsBulkData1[0].download_url + ); + done(); + }); + }); + + it('should call approver file service to get map attachments', (done) => { + component.mileageExpense$ = of(mileageExpense); + activateRouteMock.snapshot.params.view = ExpenseView.team; + approverFileService.generateUrls.and.returnValue(of(generateUrlsBulkData1[0])); + fileService.getReceiptsDetails.and.returnValue(receiptInfoData1); + + component.ionViewWillEnter(); + component.mapAttachment$.subscribe((res) => { + expect(res).toEqual(receiptInfoData1); + expect(approverFileService.generateUrls).toHaveBeenCalledOnceWith(mileageExpense.file_ids[0]); + expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith( + generateUrlsBulkData1[0].name, + generateUrlsBulkData1[0].download_url + ); + done(); + }); + }); }); describe('getDisplayValue():', () => { diff --git a/src/app/fyle/view-mileage/view-mileage.page.ts b/src/app/fyle/view-mileage/view-mileage.page.ts index 320ed88b81..34b46d6117 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.ts @@ -38,6 +38,10 @@ import { ExpenseState } from 'src/app/core/models/expense-state.enum'; import { MileageRatesService } from 'src/app/core/services/mileage-rates.service'; import { PlatformMileageRates } from 'src/app/core/models/platform/platform-mileage-rates.model'; import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { SpenderFileService } from 'src/app/core/services/platform/v1/spender/file.service'; +import { ApproverFileService } from 'src/app/core/services/platform/v1/approver/file.service'; +import { Expense as PlatformExpense } from 'src/app/core/models/platform/v1/expense.model'; +import { PlatformFileGenerateUrlsResponse } from 'src/app/core/models/platform/platform-file-generate-urls-response.model'; @Component({ selector: 'app-view-mileage', @@ -132,7 +136,9 @@ export class ViewMileagePage { private approverExpensesService: ApproverExpensesService, private spenderExpensesService: SpenderExpensesService, private mileageRatesService: MileageRatesService, - private approverReportsService: ApproverReportsService + private approverReportsService: ApproverReportsService, + private spenderFileService: SpenderFileService, + private approverFileService: ApproverFileService ) {} get ExpenseView(): typeof ExpenseView { @@ -310,21 +316,32 @@ export class ViewMileagePage { this.mapAttachment$ = this.mileageExpense$.pipe( take(1), - switchMap((expense) => from(expense.files)), - concatMap((fileObj) => - this.fileService.downloadUrl(fileObj.id).pipe( - map((downloadUrl) => { - const details = this.fileService.getReceiptsDetails(fileObj.name, downloadUrl); - const fileObjWithDetails: FileObject = { - url: downloadUrl, - type: details.type, - thumbnail: details.thumbnail, - }; - - return fileObjWithDetails; - }) - ) - ) + switchMap((expense: PlatformExpense) => { + if (expense.file_ids.length > 0) { + if (this.view === ExpenseView.individual) { + return this.spenderFileService.generateUrls(expense.file_ids[0]); + } else { + return this.approverFileService.generateUrls(expense.file_ids[0]); + } + } else { + return of(null); + } + }), + map((response: PlatformFileGenerateUrlsResponse) => { + if (response !== null) { + const details = this.fileService.getReceiptsDetails(response.name, response.download_url); + + const receipt: FileObject = { + url: response.download_url, + type: details.type, + thumbnail: details.thumbnail, + }; + + return receipt; + } else { + return null; + } + }) ); this.expenseFields$ = this.expenseFieldsService.getAllMap().pipe(shareReplay(1)); From 898461cfe1cef361e4dea6ddd631d73ad2983443 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Wed, 15 May 2024 10:58:54 +0530 Subject: [PATCH 015/262] feat: Replace public files api with platform in expense card lite component (#2970) --- src/app/core/models/matched-expense.model.ts | 18 ++++++++++ .../expense-card-lite.component.spec.ts | 36 +++++++++---------- .../expense-card-lite.component.ts | 15 ++++---- 3 files changed, 44 insertions(+), 25 deletions(-) create mode 100644 src/app/core/models/matched-expense.model.ts diff --git a/src/app/core/models/matched-expense.model.ts b/src/app/core/models/matched-expense.model.ts new file mode 100644 index 0000000000..bf8610df4c --- /dev/null +++ b/src/app/core/models/matched-expense.model.ts @@ -0,0 +1,18 @@ +import { ExpenseState } from './expense-state.enum'; + +export interface MatchedExpense { + id: string; + currency: string; + amount: number; + spent_at: Date; + txn_dt: Date; + merchant: string; + vendor: string; + foreign_currency: string; + foreign_amount: number; + purpose: string; + state: ExpenseState; + seq_num: string; + no_of_files: number; + category_display_name: string; +} diff --git a/src/app/shared/components/expense-card-lite/expense-card-lite.component.spec.ts b/src/app/shared/components/expense-card-lite/expense-card-lite.component.spec.ts index bcb7a45cd2..28e1a0aee7 100644 --- a/src/app/shared/components/expense-card-lite/expense-card-lite.component.spec.ts +++ b/src/app/shared/components/expense-card-lite/expense-card-lite.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FileService } from 'src/app/core/services/file.service'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { ExpenseCardLiteComponent } from './expense-card-lite.component'; import { IonicModule } from '@ionic/angular'; import { MatIconModule } from '@angular/material/icon'; @@ -7,27 +7,27 @@ import { MatIconTestingModule } from '@angular/material/icon/testing'; import { CurrencySymbolPipe } from '../../pipes/currency-symbol.pipe'; import { getElementBySelector, getTextContent } from 'src/app/core/dom-helpers'; import { of } from 'rxjs'; -import { fileObjectData } from 'src/app/core/mock-data/file-object.data'; +import { platformExpenseData, platformExpenseWithExtractedData } from 'src/app/core/mock-data/platform/v1/expense.data'; describe('ExpenseCardLiteComponent', () => { let expenseCardLiteComponent: ExpenseCardLiteComponent; let fixture: ComponentFixture; - let fileService: jasmine.SpyObj; + let expensesService: jasmine.SpyObj; beforeEach(waitForAsync(() => { - const fileServiceSpy = jasmine.createSpyObj('FileService', ['findByTransactionId']); + const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById']); TestBed.configureTestingModule({ declarations: [ExpenseCardLiteComponent, CurrencySymbolPipe], imports: [IonicModule.forRoot(), MatIconModule, MatIconTestingModule], providers: [ { - provide: FileService, - useValue: fileServiceSpy, + provide: ExpensesService, + useValue: expensesServiceSpy, }, ], }).compileComponents(); - fileService = TestBed.inject(FileService) as jasmine.SpyObj; + expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; fixture = TestBed.createComponent(ExpenseCardLiteComponent); expenseCardLiteComponent = fixture.componentInstance; @@ -37,42 +37,42 @@ describe('ExpenseCardLiteComponent', () => { expect(expenseCardLiteComponent).toBeTruthy(); }); - const initialSetup = (fileData) => { - fileService.findByTransactionId.and.returnValue(of(fileData)); + const initialSetup = (expenseData) => { + expensesService.getExpenseById.and.returnValue(of(expenseData)); expenseCardLiteComponent.expense = { id: 'txn1234' }; fixture.detectChanges(); }; describe('getReceipt():', () => { it('should set isReceiptPresent to true when files are present', () => { - initialSetup([fileObjectData]); - expect(fileService.findByTransactionId).toHaveBeenCalledWith('txn1234'); - expect(expenseCardLiteComponent.isReceiptPresent).toBeTruthy(); + initialSetup(platformExpenseWithExtractedData); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith('txn1234'); + expect(expenseCardLiteComponent.isReceiptPresent).toBeTrue(); }); it('should set isReceiptPresent to false when no files are present', () => { - initialSetup([]); - expect(fileService.findByTransactionId).toHaveBeenCalledWith('txn1234'); - expect(expenseCardLiteComponent.isReceiptPresent).toBeFalsy(); + initialSetup(platformExpenseData); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith('txn1234'); + expect(expenseCardLiteComponent.isReceiptPresent).toBeFalse(); }); }); it('should display the receipt when available', () => { - initialSetup([fileObjectData]); + initialSetup(platformExpenseWithExtractedData); const element = fixture.nativeElement; const receiptContainer = element.querySelector('.expenses-card--receipt-image-container'); expect(receiptContainer).toBeTruthy(); }); it('should display a default icon when no receipt available', () => { - initialSetup([]); + initialSetup(platformExpenseData); const element = fixture.nativeElement; const icon = element.querySelector('.expenses-card--receipt-icon'); expect(icon).toBeTruthy(); }); it('should display "Unspecified" if purpose is not present', () => { - initialSetup([]); + initialSetup(platformExpenseWithExtractedData); const purpose = getElementBySelector(fixture, '.expenses-card--category'); expect(getTextContent(purpose)).toEqual('Unspecified'); }); diff --git a/src/app/shared/components/expense-card-lite/expense-card-lite.component.ts b/src/app/shared/components/expense-card-lite/expense-card-lite.component.ts index 22500e4ec2..b33bb34155 100644 --- a/src/app/shared/components/expense-card-lite/expense-card-lite.component.ts +++ b/src/app/shared/components/expense-card-lite/expense-card-lite.component.ts @@ -1,6 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; -import { FileObject } from 'src/app/core/models/file-obj.model'; -import { FileService } from 'src/app/core/services/file.service'; +import { MatchedExpense } from 'src/app/core/models/matched-expense.model'; +import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; @Component({ selector: 'app-expense-card-lite', @@ -8,19 +9,19 @@ import { FileService } from 'src/app/core/services/file.service'; styleUrls: ['./expense-card-lite.component.scss'], }) export class ExpenseCardLiteComponent implements OnInit { - @Input() expense; + @Input() expense: Partial; isReceiptPresent: boolean; - constructor(private fileService: FileService) {} + constructor(private expensesService: ExpensesService) {} ngOnInit(): void { this.getReceipt(); } - getReceipt() { - this.fileService.findByTransactionId(this.expense.id).subscribe((files: FileObject[]) => { - this.isReceiptPresent = files.length > 0; + getReceipt(): void { + this.expensesService.getExpenseById(this.expense.id).subscribe((expense: Expense) => { + this.isReceiptPresent = expense.file_ids?.length > 0; }); } } From fc7906ebf3e2749482c9880cce9bbae0da2f6867 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Wed, 15 May 2024 13:58:43 +0530 Subject: [PATCH 016/262] feat: use attach_receipt instead of public post api in expense card v2 (#2994) --- .../platform/v1/spender/expenses.service.ts | 13 +++++++++++++ .../expenses-card.component.spec.ts | 6 +++--- .../expenses-card-v2/expenses-card.component.ts | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/app/core/services/platform/v1/spender/expenses.service.ts b/src/app/core/services/platform/v1/spender/expenses.service.ts index a8e87ac3c4..bef74a78c7 100644 --- a/src/app/core/services/platform/v1/spender/expenses.service.ts +++ b/src/app/core/services/platform/v1/spender/expenses.service.ts @@ -164,4 +164,17 @@ export class ExpensesService { return this.getAllExpenses(params); } + + attachReceiptToExpense(expenseId: string, fileId: string): Observable { + const payload = { + data: { + id: expenseId, + file_id: fileId, + }, + }; + + return this.spenderService + .post>('/expenses/attach_receipt', payload) + .pipe(map((res) => res.data)); + } } diff --git a/src/app/shared/components/expenses-card-v2/expenses-card.component.spec.ts b/src/app/shared/components/expenses-card-v2/expenses-card.component.spec.ts index 1c77d3816b..aabf0100dd 100644 --- a/src/app/shared/components/expenses-card-v2/expenses-card.component.spec.ts +++ b/src/app/shared/components/expenses-card-v2/expenses-card.component.spec.ts @@ -68,7 +68,7 @@ describe('ExpensesCardComponent', () => { beforeEach(waitForAsync(() => { const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['transformExpense']); - const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById']); + const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById', 'attachReceiptToExpense']); const sharedExpenseServiceSpy = jasmine.createSpyObj('SharedExpenseService', [ 'isExpenseInDraft', 'isCriticalPolicyViolatedExpense', @@ -675,7 +675,7 @@ describe('ExpensesCardComponent', () => { fileService.getAttachmentType.and.returnValue(attachmentType); transactionsOutboxService.fileUpload.and.resolveTo(fileObj); - fileService.post.and.returnValue(of(fileObjectData)); + expensesService.attachReceiptToExpense.and.returnValue(of(platformExpenseData)); spyOn(component, 'matchReceiptWithEtxn').and.callThrough(); @@ -685,7 +685,7 @@ describe('ExpensesCardComponent', () => { expect(fileService.getAttachmentType).toHaveBeenCalledOnceWith(receiptDetailsaRes.type); expect(transactionsOutboxService.fileUpload).toHaveBeenCalledOnceWith(dataUrl, attachmentType); expect(component.matchReceiptWithEtxn).toHaveBeenCalledOnceWith(fileObj); - expect(fileService.post).toHaveBeenCalledOnceWith(fileObj); + expect(expensesService.attachReceiptToExpense).toHaveBeenCalledOnceWith(component.expense.id, fileObj.id); expect(component.attachmentUploadInProgress).toBeFalse(); tick(500); })); diff --git a/src/app/shared/components/expenses-card-v2/expenses-card.component.ts b/src/app/shared/components/expenses-card-v2/expenses-card.component.ts index 3c163e9742..91f458e149 100644 --- a/src/app/shared/components/expenses-card-v2/expenses-card.component.ts +++ b/src/app/shared/components/expenses-card-v2/expenses-card.component.ts @@ -464,7 +464,7 @@ export class ExpensesCardComponent implements OnInit { .pipe( switchMap((fileObj: FileObject) => { this.matchReceiptWithEtxn(fileObj); - return this.fileService.post(fileObj); + return this.expensesService.attachReceiptToExpense(this.expense.id, fileObj.id); }), finalize(() => { this.attachmentUploadInProgress = false; From ecd76b1a6e059aec05b67ffe96750981bc9903c5 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Wed, 15 May 2024 14:36:22 +0530 Subject: [PATCH 017/262] feat: use attach_receipt instead of public post api in expense card (#2996) --- .../expenses-card/expenses-card.component.spec.ts | 6 +++--- .../components/expenses-card/expenses-card.component.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/shared/components/expenses-card/expenses-card.component.spec.ts b/src/app/shared/components/expenses-card/expenses-card.component.spec.ts index d3ceed5401..e203098046 100644 --- a/src/app/shared/components/expenses-card/expenses-card.component.spec.ts +++ b/src/app/shared/components/expenses-card/expenses-card.component.spec.ts @@ -70,7 +70,7 @@ describe('ExpensesCardComponent', () => { 'getIsCriticalPolicyViolated', 'getVendorDetails', ]); - const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById']); + const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById', 'attachReceiptToExpense']); const orgUserSettingsServiceSpy = jasmine.createSpyObj('OrgUserSettingsService', ['get']); const fileServiceSpy = jasmine.createSpyObj('FileService', [ 'downloadUrl', @@ -648,7 +648,7 @@ describe('ExpensesCardComponent', () => { fileService.getAttachmentType.and.returnValue(attachmentType); transactionsOutboxService.fileUpload.and.resolveTo(fileObj); - fileService.post.and.returnValue(of(fileObjectData)); + expensesService.attachReceiptToExpense.and.returnValue(of(platformExpenseData)); spyOn(component, 'matchReceiptWithEtxn').and.callThrough(); @@ -658,7 +658,7 @@ describe('ExpensesCardComponent', () => { expect(fileService.getAttachmentType).toHaveBeenCalledOnceWith(receiptDetailsaRes.type); expect(transactionsOutboxService.fileUpload).toHaveBeenCalledOnceWith(dataUrl, attachmentType); expect(component.matchReceiptWithEtxn).toHaveBeenCalledOnceWith(fileObj); - expect(fileService.post).toHaveBeenCalledOnceWith(fileObj); + expect(expensesService.attachReceiptToExpense).toHaveBeenCalledOnceWith(component.expense.tx_id, fileObj.id); expect(component.attachmentUploadInProgress).toBeFalse(); tick(500); })); diff --git a/src/app/shared/components/expenses-card/expenses-card.component.ts b/src/app/shared/components/expenses-card/expenses-card.component.ts index 0159836eca..7eb6d204c9 100644 --- a/src/app/shared/components/expenses-card/expenses-card.component.ts +++ b/src/app/shared/components/expenses-card/expenses-card.component.ts @@ -451,7 +451,7 @@ export class ExpensesCardComponent implements OnInit { .pipe( switchMap((fileObj: FileObject) => { this.matchReceiptWithEtxn(fileObj); - return this.fileService.post(fileObj); + return this.expensesService.attachReceiptToExpense(this.expense.tx_id, fileObj.id); }), finalize(() => { this.attachmentUploadInProgress = false; From 5502f9cf7e8e8183a60759a85acdb5a243b37ea1 Mon Sep 17 00:00:00 2001 From: Aastha Bist Date: Wed, 15 May 2024 15:00:24 +0530 Subject: [PATCH 018/262] feat: View Expense, Mileage, per diem changes (#2993) --- .../core/mock-data/platform-report.data.ts | 4 +-- .../view-expense/view-expense.page.spec.ts | 23 ++++++++-------- .../fyle/view-expense/view-expense.page.ts | 6 ++--- .../view-mileage/view-mileage.page.spec.ts | 25 +++++++++-------- .../fyle/view-mileage/view-mileage.page.ts | 6 ++--- .../view-per-diem/view-per-diem.page.spec.ts | 27 ++++++++++--------- .../fyle/view-per-diem/view-per-diem.page.ts | 6 ++--- 7 files changed, 46 insertions(+), 51 deletions(-) diff --git a/src/app/core/mock-data/platform-report.data.ts b/src/app/core/mock-data/platform-report.data.ts index d4400f205c..791e9c5231 100644 --- a/src/app/core/mock-data/platform-report.data.ts +++ b/src/app/core/mock-data/platform-report.data.ts @@ -1,6 +1,6 @@ import deepFreeze from 'deep-freeze-strict'; -import { Report } from '../models/platform/v1/report.model'; +import { Report, ReportState } from '../models/platform/v1/report.model'; import { ReportsQueryParams } from '../models/platform/v1/reports-query-params.model'; import { PlatformApiResponse } from '../models/platform/platform-api-response.model'; import { ApprovalState } from '../models/platform/report-approvals.model'; @@ -731,7 +731,7 @@ export const submittedReportDataWithApproval: Report = deepFreeze({ export const paidReportData: Report = deepFreeze({ ...submittedReportDataWithApproval, num_expenses: 1, - state: 'PAID', + state: ReportState.PAID, }); export const expectedSingleReport: Report[] = deepFreeze([allReportsPaginated1.data[0]]); diff --git a/src/app/fyle/view-expense/view-expense.page.spec.ts b/src/app/fyle/view-expense/view-expense.page.spec.ts index e6c464b861..8caa2878db 100644 --- a/src/app/fyle/view-expense/view-expense.page.spec.ts +++ b/src/app/fyle/view-expense/view-expense.page.spec.ts @@ -50,13 +50,17 @@ import { TransactionStatusInfoPopoverComponent } from 'src/app/shared/components import { OrgSettings } from 'src/app/core/models/org-settings.model'; import { CustomInput } from 'src/app/core/models/custom-input.model'; import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { + expectedReportsSinglePage, + expectedReportsSinglePageSubmitted, + paidReportData, +} from 'src/app/core/mock-data/platform-report.data'; describe('ViewExpensePage', () => { let component: ViewExpensePage; let fixture: ComponentFixture; let loaderService: jasmine.SpyObj; let transactionService: jasmine.SpyObj; - let reportService: jasmine.SpyObj; let customInputsService: jasmine.SpyObj; let statusService: jasmine.SpyObj; let fileService: jasmine.SpyObj; @@ -79,7 +83,6 @@ describe('ViewExpensePage', () => { beforeEach(waitForAsync(() => { const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['hideLoader', 'showLoader']); const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['manualUnflag', 'manualFlag']); - const reportServiceSpy = jasmine.createSpyObj('ReportService', ['getTeamReport']); const customInputsServiceSpy = jasmine.createSpyObj('CustomInputsService', [ 'getCustomPropertyDisplayValue', 'fillCustomProperties', @@ -119,7 +122,10 @@ describe('ViewExpensePage', () => { ]); const approverExpensesServiceSpy = jasmine.createSpyObj('ApproverExpensesService', ['getExpenseById']); const spenderExpensesServiceSpy = jasmine.createSpyObj('SpenderExpensesService', ['getExpenseById']); - const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', ['ejectExpenses']); + const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', [ + 'ejectExpenses', + 'getReportById', + ]); TestBed.configureTestingModule({ declarations: [ViewExpensePage], @@ -133,10 +139,6 @@ describe('ViewExpensePage', () => { useValue: transactionServiceSpy, provide: TransactionService, }, - { - useValue: reportServiceSpy, - provide: ReportService, - }, { useValue: customInputsServiceSpy, provide: CustomInputsService, @@ -225,7 +227,6 @@ describe('ViewExpensePage', () => { fixture = TestBed.createComponent(ViewExpensePage); component = fixture.componentInstance; transactionService = TestBed.inject(TransactionService) as jasmine.SpyObj; - reportService = TestBed.inject(ReportService) as jasmine.SpyObj; customInputsService = TestBed.inject(CustomInputsService) as jasmine.SpyObj; statusService = TestBed.inject(StatusService) as jasmine.SpyObj; fileService = TestBed.inject(FileService) as jasmine.SpyObj; @@ -489,7 +490,7 @@ describe('ViewExpensePage', () => { type: 'image', thumbnail: 'mock-thumbnail', }); - reportService.getTeamReport.and.returnValue(of(apiTeamRptSingleRes.data[0])); + approverReportsService.getReportById.and.returnValue(of(expectedReportsSinglePage[0])); }); it('should get all the system categories and get the correct value of report is by subscribing to expenseWithoutCustomProperties$', fakeAsync(() => { @@ -680,7 +681,7 @@ describe('ViewExpensePage', () => { custom_fields: null, }; - reportService.getTeamReport.and.returnValue(of(apiTeamRptSingleRes.data[0])); + approverReportsService.getReportById.and.returnValue(of(paidReportData)); approverExpensesService.getExpenseById.and.returnValue(of(mockWithoutCustPropData)); component.expenseWithoutCustomProperties$ = of(mockWithoutCustPropData); activateRouteMock.snapshot.params.view = ExpenseView.team; @@ -699,7 +700,7 @@ describe('ViewExpensePage', () => { state: ExpenseState.DRAFT, custom_fields: null, }; - reportService.getTeamReport.and.returnValue(of(apiTeamReportPaginated1.data[3])); + approverReportsService.getReportById.and.returnValue(of(expectedReportsSinglePageSubmitted[2])); approverExpensesService.getExpenseById.and.returnValue(of(mockWithoutCustPropData)); component.expenseWithoutCustomProperties$ = of(mockWithoutCustPropData); activateRouteMock.snapshot.params.view = ExpenseView.team; diff --git a/src/app/fyle/view-expense/view-expense.page.ts b/src/app/fyle/view-expense/view-expense.page.ts index 70666a9a3c..44873773ba 100644 --- a/src/app/fyle/view-expense/view-expense.page.ts +++ b/src/app/fyle/view-expense/view-expense.page.ts @@ -6,7 +6,6 @@ import { ActivatedRoute, Router } from '@angular/router'; import { CustomInputsService } from 'src/app/core/services/custom-inputs.service'; import { switchMap, shareReplay, concatMap, map, finalize, reduce, takeUntil, take, filter } from 'rxjs/operators'; import { StatusService } from 'src/app/core/services/status.service'; -import { ReportService } from 'src/app/core/services/report.service'; import { FileService } from 'src/app/core/services/file.service'; import { ModalController, PopoverController } from '@ionic/angular'; import { NetworkService } from '../../core/services/network.service'; @@ -135,7 +134,6 @@ export class ViewExpensePage { private loaderService: LoaderService, private transactionService: TransactionService, private activatedRoute: ActivatedRoute, - private reportService: ReportService, private customInputsService: CustomInputsService, private statusService: StatusService, private fileService: FileService, @@ -375,10 +373,10 @@ export class ViewExpensePage { this.canDelete$ = this.expenseWithoutCustomProperties$.pipe( filter(() => this.view === ExpenseView.team), switchMap((expense) => - this.reportService.getTeamReport(expense.report_id).pipe(map((report) => ({ report, expense }))) + this.approverReportsService.getReportById(expense.report_id).pipe(map((report) => ({ report, expense }))) ), map(({ report, expense }) => - report?.rp_num_transactions === 1 + report.num_expenses === 1 ? false : ![ExpenseState.PAYMENT_PENDING, ExpenseState.PAYMENT_PROCESSING, ExpenseState.PAID].includes(expense.state) ) diff --git a/src/app/fyle/view-mileage/view-mileage.page.spec.ts b/src/app/fyle/view-mileage/view-mileage.page.spec.ts index 10147c5b95..779eedcf69 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.spec.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.spec.ts @@ -4,7 +4,6 @@ import { LoaderService } from 'src/app/core/services/loader.service'; import { TransactionService } from 'src/app/core/services/transaction.service'; import { CustomInputsService } from 'src/app/core/services/custom-inputs.service'; import { PolicyService } from 'src/app/core/services/policy.service'; -import { ReportService } from 'src/app/core/services/report.service'; import { NetworkService } from '../../core/services/network.service'; import { StatusService } from 'src/app/core/services/status.service'; import { ModalPropertiesService } from 'src/app/core/services/modal-properties.service'; @@ -34,7 +33,6 @@ import { expenseFieldsMapResponse, expenseFieldsMapResponse4 } from 'src/app/cor import { orgSettingsGetData } from 'src/app/core/test-data/org-settings.service.spec.data'; import { filledCustomProperties } from 'src/app/core/test-data/custom-inputs.spec.data'; import { getEstatusApiResponse } from 'src/app/core/test-data/status.service.spec.data'; -import { apiTeamRptSingleRes, expectedReports } from 'src/app/core/mock-data/api-reports.data'; import { cloneDeep, slice } from 'lodash'; import { isEmpty } from 'rxjs/operators'; import { txnStatusData } from 'src/app/core/mock-data/transaction-status.data'; @@ -48,6 +46,11 @@ import { MileageRatesService } from 'src/app/core/services/mileage-rates.service import { platformMileageRatesSingleData } from 'src/app/core/mock-data/platform-mileage-rate.data'; import { CustomInput } from 'src/app/core/models/custom-input.model'; import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { + expectedReportsSinglePage, + expectedReportsSinglePageSubmitted, + paidReportData, +} from '../../core/mock-data/platform-report.data'; import { SpenderFileService } from 'src/app/core/services/platform/v1/spender/file.service'; import { ApproverFileService } from 'src/app/core/services/platform/v1/approver/file.service'; import { generateUrlsBulkData1 } from 'src/app/core/mock-data/generate-urls-bulk-response.data'; @@ -60,7 +63,6 @@ describe('ViewMileagePage', () => { let transactionService: jasmine.SpyObj; let customInputsService: jasmine.SpyObj; let policyService: jasmine.SpyObj; - let reportService: jasmine.SpyObj; let popoverController: jasmine.SpyObj; let router: jasmine.SpyObj; let networkService: jasmine.SpyObj; @@ -83,7 +85,6 @@ describe('ViewMileagePage', () => { beforeEach(waitForAsync(() => { const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['hideLoader', 'showLoader']); const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['manualUnflag', 'manualFlag']); - const reportServiceSpy = jasmine.createSpyObj('ReportService', ['getTeamReport']); const customInputsServiceSpy = jasmine.createSpyObj('CustomInputsService', [ 'getCustomPropertyDisplayValue', 'fillCustomProperties', @@ -124,7 +125,10 @@ describe('ViewMileagePage', () => { 'getSpenderMileageRateById', 'getApproverMileageRateById', ]); - const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', ['ejectExpenses']); + const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', [ + 'ejectExpenses', + 'getReportById', + ]); const spenderFileServiceSpy = jasmine.createSpyObj('SpenderFileService', ['generateUrls']); const approverFileServiceSpy = jasmine.createSpyObj('ApproverFileService', ['generateUrls']); @@ -140,10 +144,6 @@ describe('ViewMileagePage', () => { useValue: transactionServiceSpy, provide: TransactionService, }, - { - useValue: reportServiceSpy, - provide: ReportService, - }, { useValue: customInputsServiceSpy, provide: CustomInputsService, @@ -239,7 +239,6 @@ describe('ViewMileagePage', () => { component = fixture.componentInstance; loaderService = TestBed.inject(LoaderService) as jasmine.SpyObj; transactionService = TestBed.inject(TransactionService) as jasmine.SpyObj; - reportService = TestBed.inject(ReportService) as jasmine.SpyObj; customInputsService = TestBed.inject(CustomInputsService) as jasmine.SpyObj; statusService = TestBed.inject(StatusService) as jasmine.SpyObj; modalController = TestBed.inject(ModalController) as jasmine.SpyObj; @@ -874,7 +873,7 @@ describe('ViewMileagePage', () => { report_id: 'rphNNUiCISkD', custom_fields: null, }; - reportService.getTeamReport.and.returnValue(of(apiTeamRptSingleRes.data[0])); + approverReportsService.getReportById.and.returnValue(of(paidReportData)); spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); component.mileageExpense$ = of(mockMileageExpense); component.expenseFields$ = of(expenseFieldsMapResponse4); @@ -895,7 +894,7 @@ describe('ViewMileagePage', () => { report_id: 'rphNNUiCISkD', custom_fields: null, }; - reportService.getTeamReport.and.returnValue(of(expectedReports.data[3])); + approverReportsService.getReportById.and.returnValue(of(expectedReportsSinglePageSubmitted[2])); approverExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); component.mileageExpense$ = of(mockMileageExpense); component.expenseFields$ = of(expenseFieldsMapResponse4); @@ -916,7 +915,7 @@ describe('ViewMileagePage', () => { report_id: 'rphNNUiCISkD', custom_fields: null, }; - reportService.getTeamReport.and.returnValue(of(expectedReports.data[3])); + approverReportsService.getReportById.and.returnValue(of(expectedReportsSinglePageSubmitted[2])); spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); component.mileageExpense$ = of(mockMileageExpense); component.expenseFields$ = of(expenseFieldsMapResponse4); diff --git a/src/app/fyle/view-mileage/view-mileage.page.ts b/src/app/fyle/view-mileage/view-mileage.page.ts index 34b46d6117..e6370b4977 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.ts @@ -7,7 +7,6 @@ import { TransactionService } from 'src/app/core/services/transaction.service'; import { CustomInputsService } from 'src/app/core/services/custom-inputs.service'; import { PolicyService } from 'src/app/core/services/policy.service'; import { switchMap, finalize, shareReplay, map, concatMap, takeUntil, take, filter } from 'rxjs/operators'; -import { ReportService } from 'src/app/core/services/report.service'; import { PopoverController, ModalController } from '@ionic/angular'; import { NetworkService } from '../../core/services/network.service'; import { StatusService } from 'src/app/core/services/status.service'; @@ -121,7 +120,6 @@ export class ViewMileagePage { private transactionService: TransactionService, private customInputsService: CustomInputsService, private policyService: PolicyService, - private reportService: ReportService, private popoverController: PopoverController, private router: Router, private networkService: NetworkService, @@ -456,10 +454,10 @@ export class ViewMileagePage { take(1), filter(() => this.view === ExpenseView.team), switchMap((expense) => - this.reportService.getTeamReport(expense.report_id).pipe(map((report) => ({ report, expense }))) + this.approverReportsService.getReportById(expense.report_id).pipe(map((report) => ({ report, expense }))) ), map(({ report, expense }) => - report.rp_num_transactions === 1 + report.num_expenses === 1 ? false : ![ExpenseState.PAYMENT_PENDING, ExpenseState.PAYMENT_PROCESSING, ExpenseState.PAID].includes(expense.state) ) diff --git a/src/app/fyle/view-per-diem/view-per-diem.page.spec.ts b/src/app/fyle/view-per-diem/view-per-diem.page.spec.ts index 415f5b4e6e..9bc77e94c7 100644 --- a/src/app/fyle/view-per-diem/view-per-diem.page.spec.ts +++ b/src/app/fyle/view-per-diem/view-per-diem.page.spec.ts @@ -7,7 +7,6 @@ import { LoaderService } from 'src/app/core/services/loader.service'; import { CustomInputsService } from 'src/app/core/services/custom-inputs.service'; import { PerDiemService } from 'src/app/core/services/per-diem.service'; import { PolicyService } from 'src/app/core/services/policy.service'; -import { ReportService } from 'src/app/core/services/report.service'; import { ActivatedRoute, Router } from '@angular/router'; import { StatusService } from 'src/app/core/services/status.service'; import { ModalPropertiesService } from 'src/app/core/services/modal-properties.service'; @@ -33,7 +32,6 @@ import { customInputData1 } from 'src/app/core/mock-data/custom-input.data'; import { orgSettingsData } from 'src/app/core/test-data/accounts.service.spec.data'; import { customFieldData1, customFields } from 'src/app/core/mock-data/custom-field.data'; import { perDiemRatesData1 } from 'src/app/core/mock-data/per-diem-rates.data'; -import { apiExtendedReportRes } from 'src/app/core/mock-data/report.data'; import { estatusData1 } from 'src/app/core/test-data/status.service.spec.data'; import { cloneDeep } from 'lodash'; import { FyPopoverComponent } from 'src/app/shared/components/fy-popover/fy-popover.component'; @@ -45,6 +43,7 @@ import { ExpenseState } from 'src/app/core/models/expense-state.enum'; import { AccountType } from 'src/app/core/models/platform/v1/account.model'; import { CustomInput } from 'src/app/core/models/custom-input.model'; import { ApproverReportsService } from 'src/app/core/services/platform/v1/approver/reports.service'; +import { expectedReportsSinglePage } from 'src/app/core/mock-data/platform-report.data'; describe('ViewPerDiemPage', () => { let component: ViewPerDiemPage; @@ -54,7 +53,6 @@ describe('ViewPerDiemPage', () => { let customInputsService: jasmine.SpyObj; let perDiemService: jasmine.SpyObj; let policyService: jasmine.SpyObj; - let reportService: jasmine.SpyObj; let approverReportsService: jasmine.SpyObj; let router: jasmine.SpyObj; let popoverController: jasmine.SpyObj; @@ -81,7 +79,6 @@ describe('ViewPerDiemPage', () => { 'getApproverExpensePolicyViolations', 'getSpenderExpensePolicyViolations', ]); - const reportServiceSpy = jasmine.createSpyObj('ReportService', ['getTeamReport']); const routerSpy = jasmine.createSpyObj('Router', ['navigate']); const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['create']); const statusServiceSpy = jasmine.createSpyObj('StatusService', ['find', 'post']); @@ -100,7 +97,10 @@ describe('ViewPerDiemPage', () => { ]); const spenderExpensesServiceSpy = jasmine.createSpyObj('SpenderExpensesService', ['getExpenseById']); const approverExpensesServiceSpy = jasmine.createSpyObj('ApproverExpensesService', ['getExpenseById']); - const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', ['ejectExpenses']); + const approverReportsServiceSpy = jasmine.createSpyObj('ApproverReportsService', [ + 'ejectExpenses', + 'getReportById', + ]); TestBed.configureTestingModule({ declarations: [ViewPerDiemPage], imports: [IonicModule.forRoot()], @@ -110,7 +110,6 @@ describe('ViewPerDiemPage', () => { { provide: CustomInputsService, useValue: customInputsServiceSpy }, { provide: PerDiemService, useValue: perDiemServiceSpy }, { provide: PolicyService, useValue: policyServiceSpy }, - { provide: ReportService, useValue: reportServiceSpy }, { provide: Router, useValue: routerSpy }, { provide: PopoverController, useValue: popoverControllerSpy }, { provide: StatusService, useValue: statusServiceSpy }, @@ -146,7 +145,6 @@ describe('ViewPerDiemPage', () => { customInputsService = TestBed.inject(CustomInputsService) as jasmine.SpyObj; perDiemService = TestBed.inject(PerDiemService) as jasmine.SpyObj; policyService = TestBed.inject(PolicyService) as jasmine.SpyObj; - reportService = TestBed.inject(ReportService) as jasmine.SpyObj; approverReportsService = TestBed.inject(ApproverReportsService) as jasmine.SpyObj; router = TestBed.inject(Router) as jasmine.SpyObj; popoverController = TestBed.inject(PopoverController) as jasmine.SpyObj; @@ -296,7 +294,7 @@ describe('ViewPerDiemPage', () => { customInputsService.fillCustomProperties.and.returnValue(of(mockCustomFields)); customInputsService.getCustomPropertyDisplayValue.and.returnValue('customPropertyDisplayValue'); perDiemService.getRate.and.returnValue(of(perDiemRatesData1)); - reportService.getTeamReport.and.returnValue(of(apiExtendedReportRes[0])); + approverReportsService.getReportById.and.returnValue(of(expectedReportsSinglePage[0])); policyService.getApproverExpensePolicyViolations.and.returnValue(of(individualExpPolicyStateData2)); policyService.getSpenderExpensePolicyViolations.and.returnValue(of(individualExpPolicyStateData3)); statusService.find.and.returnValue(of(estatusData1)); @@ -501,9 +499,12 @@ describe('ViewPerDiemPage', () => { activatedRoute.snapshot.params.view = ExpenseView.team; approverExpensesService.getExpenseById.and.returnValue(of(perDiemExpense)); + const mockReport = cloneDeep(expectedReportsSinglePage[0]); + mockReport.num_expenses = 1; + approverReportsService.getReportById.and.returnValue(of(mockReport)); component.ionViewWillEnter(); component.canDelete$.subscribe((canDelete) => { - expect(reportService.getTeamReport).toHaveBeenCalledOnceWith('rpFvmTgyeBjN'); + expect(approverReportsService.getReportById).toHaveBeenCalledOnceWith('rpFvmTgyeBjN'); expect(canDelete).toBeFalse(); done(); }); @@ -514,15 +515,15 @@ describe('ViewPerDiemPage', () => { const mockExpense = cloneDeep(perDiemExpense); mockExpense.state = ExpenseState.APPROVER_PENDING; - const mockReport = cloneDeep(apiExtendedReportRes[0]); - mockReport.rp_num_transactions = 2; + const mockReport = cloneDeep(expectedReportsSinglePage[0]); + mockReport.num_expenses = 2; - reportService.getTeamReport.and.returnValue(of(mockReport)); + approverReportsService.getReportById.and.returnValue(of(mockReport)); approverExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); component.canDelete$.subscribe((canDelete) => { - expect(reportService.getTeamReport).toHaveBeenCalledOnceWith('rpFvmTgyeBjN'); + expect(approverReportsService.getReportById).toHaveBeenCalledOnceWith('rpFvmTgyeBjN'); expect(canDelete).toBeTrue(); done(); }); diff --git a/src/app/fyle/view-per-diem/view-per-diem.page.ts b/src/app/fyle/view-per-diem/view-per-diem.page.ts index 5014945182..756a4ab405 100644 --- a/src/app/fyle/view-per-diem/view-per-diem.page.ts +++ b/src/app/fyle/view-per-diem/view-per-diem.page.ts @@ -8,7 +8,6 @@ import { CustomInputsService } from 'src/app/core/services/custom-inputs.service import { PerDiemService } from 'src/app/core/services/per-diem.service'; import { PolicyService } from 'src/app/core/services/policy.service'; import { switchMap, finalize, shareReplay, map, concatMap, filter, take } from 'rxjs/operators'; -import { ReportService } from 'src/app/core/services/report.service'; import { PopoverController, ModalController } from '@ionic/angular'; import { StatusService } from 'src/app/core/services/status.service'; import { ViewCommentComponent } from 'src/app/shared/components/comments-history/view-comment/view-comment.component'; @@ -106,7 +105,6 @@ export class ViewPerDiemPage { private customInputsService: CustomInputsService, private perDiemService: PerDiemService, private policyService: PolicyService, - private reportService: ReportService, private router: Router, private popoverController: PopoverController, private statusService: StatusService, @@ -300,10 +298,10 @@ export class ViewPerDiemPage { this.canDelete$ = this.perDiemExpense$.pipe( filter(() => this.view === ExpenseView.team), switchMap((expense) => - this.reportService.getTeamReport(expense.report_id).pipe(map((report) => ({ report, expense }))) + this.approverReportsService.getReportById(expense.report_id).pipe(map((report) => ({ report, expense }))) ), map(({ report, expense }) => - report.rp_num_transactions === 1 + report.num_expenses === 1 ? false : ![ExpenseState.PAYMENT_PENDING, ExpenseState.PAYMENT_PROCESSING, ExpenseState.PAID].includes(expense.state) ) From 8ec4f3ba8a3ca351a05b9f96b75ea9a2508b41ed Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Wed, 15 May 2024 16:18:50 +0530 Subject: [PATCH 019/262] feat: replace download_url with generate_urls/bulk api call in add edit expense page (#2965) --- .../add-edit-expense-3.spec.ts | 22 +++-- .../add-edit-expense-5.spec.ts | 23 ++++-- .../add-edit-expense/add-edit-expense.page.ts | 80 +++++++++++-------- .../add-edit-expense.setup.spec.ts | 7 ++ 4 files changed, 88 insertions(+), 44 deletions(-) diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts index 74ffc29fc4..0ce9a7a8e8 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts @@ -105,6 +105,9 @@ import { orgSettingsData, orgSettingsWithoutAutofill } from 'src/app/core/test-d import { FyViewAttachmentComponent } from 'src/app/shared/components/fy-view-attachment/fy-view-attachment.component'; import { AddEditExpensePage } from './add-edit-expense.page'; import { optionsData15, optionsData33 } from 'src/app/core/mock-data/merge-expenses-options-data.data'; +import { SpenderFileService } from 'src/app/core/services/platform/v1/spender/file.service'; +import { generateUrlsBulkData1 } from 'src/app/core/mock-data/generate-urls-bulk-response.data'; +import { receiptInfoData2 } from 'src/app/core/mock-data/receipt-info.data'; export function TestCases3(getTestBed) { return describe('AddEditExpensePage-3', () => { @@ -128,6 +131,7 @@ export function TestCases3(getTestBed) { let modalController: jasmine.SpyObj; let statusService: jasmine.SpyObj; let fileService: jasmine.SpyObj; + let spenderFileService: jasmine.SpyObj; let popoverController: jasmine.SpyObj; let currencyService: jasmine.SpyObj; let networkService: jasmine.SpyObj; @@ -179,6 +183,7 @@ export function TestCases3(getTestBed) { modalController = TestBed.inject(ModalController) as jasmine.SpyObj; statusService = TestBed.inject(StatusService) as jasmine.SpyObj; fileService = TestBed.inject(FileService) as jasmine.SpyObj; + spenderFileService = TestBed.inject(SpenderFileService) as jasmine.SpyObj; popoverController = TestBed.inject(PopoverController) as jasmine.SpyObj; currencyService = TestBed.inject(CurrencyService) as jasmine.SpyObj; networkService = TestBed.inject(NetworkService) as jasmine.SpyObj; @@ -407,17 +412,20 @@ export function TestCases3(getTestBed) { it('should return file observables in edit mode', (done) => { const mockFileObject = cloneDeep(fileObject4); fileService.findByTransactionId.and.returnValue(of(mockFileObject)); - fileService.downloadUrl.and.returnValue(of('url')); - spyOn(component, 'getReceiptDetails').and.returnValue({ - type: 'jpeg', - thumbnail: 'thumbnail', + fileService.getReceiptsDetails.and.returnValue({ + type: 'pdf', + thumbnail: 'img/fy-pdf.svg', }); + spenderFileService.generateUrlsBulk.and.returnValue(of(generateUrlsBulkData1)); component.getExpenseAttachments('edit', 'tx1vdITUXIzf').subscribe((res) => { - expect(res).toEqual(expectedFileData1); + expect(res).toEqual(receiptInfoData2); expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith('tx1vdITUXIzf'); - expect(fileService.downloadUrl).toHaveBeenCalledOnceWith('fiV1gXpyCcbU'); - expect(component.getReceiptDetails).toHaveBeenCalledOnceWith(mockFileObject[0]); + expect(spenderFileService.generateUrlsBulk).toHaveBeenCalledOnceWith(['fiV1gXpyCcbU']); + expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith( + 'invoice.pdf', + 'https://sampledownloadurl.com' + ); done(); }); }); diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts index 90c7a68beb..ef3d394e3e 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts @@ -109,6 +109,9 @@ import { apiExpenses2, expenseData, splitExpensesData } from 'src/app/core/mock- import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { matchedCCTransactionData } from 'src/app/core/mock-data/matchedCCTransaction.data'; import { cloneDeep } from 'lodash'; +import { SpenderFileService } from 'src/app/core/services/platform/v1/spender/file.service'; +import { generateUrlsBulkData1 } from 'src/app/core/mock-data/generate-urls-bulk-response.data'; +import { receiptInfoData2 } from 'src/app/core/mock-data/receipt-info.data'; export function TestCases5(getTestBed) { return describe('AddEditExpensePage-5', () => { @@ -133,6 +136,7 @@ export function TestCases5(getTestBed) { let modalController: jasmine.SpyObj; let statusService: jasmine.SpyObj; let fileService: jasmine.SpyObj; + let spenderFileService: jasmine.SpyObj; let popoverController: jasmine.SpyObj; let currencyService: jasmine.SpyObj; let networkService: jasmine.SpyObj; @@ -185,6 +189,7 @@ export function TestCases5(getTestBed) { modalController = TestBed.inject(ModalController) as jasmine.SpyObj; statusService = TestBed.inject(StatusService) as jasmine.SpyObj; fileService = TestBed.inject(FileService) as jasmine.SpyObj; + spenderFileService = TestBed.inject(SpenderFileService) as jasmine.SpyObj; popoverController = TestBed.inject(PopoverController) as jasmine.SpyObj; currencyService = TestBed.inject(CurrencyService) as jasmine.SpyObj; networkService = TestBed.inject(NetworkService) as jasmine.SpyObj; @@ -1347,6 +1352,11 @@ export function TestCases5(getTestBed) { const mockFileObject = cloneDeep(expectedFileData1); fileService.findByTransactionId.and.returnValue(of(mockFileObject)); fileService.downloadUrl.and.returnValue(of('url')); + fileService.getReceiptsDetails.and.returnValue({ + type: 'pdf', + thumbnail: 'img/fy-pdf.svg', + }); + spenderFileService.generateUrlsBulk.and.returnValue(of(generateUrlsBulkData1)); spyOn(component, 'getReceiptDetails').and.returnValue({ type: 'jpeg', thumbnail: 'thumbnail', @@ -1467,12 +1477,15 @@ export function TestCases5(getTestBed) { expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith('txyeiYbLDSOy'); component.attachments$.subscribe((res) => { - expect(res).toEqual(mockFileObject); + expect(res).toEqual(receiptInfoData2); }); expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith('tx3qHxFNgRcZ'); - expect(fileService.downloadUrl).toHaveBeenCalledOnceWith('fiV1gXpyCcbU'); - expect(component.getReceiptDetails).toHaveBeenCalledOnceWith(mockFileObject[0]); + expect(spenderFileService.generateUrlsBulk).toHaveBeenCalledOnceWith(['fiV1gXpyCcbU']); + expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith( + generateUrlsBulkData1[0].name, + generateUrlsBulkData1[0].download_url + ); component.flightJourneyTravelClassOptions$.subscribe((res) => { expect(res).toBeUndefined(); @@ -1756,8 +1769,8 @@ export function TestCases5(getTestBed) { }); expect(fileService.findByTransactionId).not.toHaveBeenCalled(); - expect(fileService.downloadUrl).not.toHaveBeenCalled(); - expect(component.getReceiptDetails).not.toHaveBeenCalled(); + expect(spenderFileService.generateUrlsBulk).not.toHaveBeenCalled(); + expect(fileService.getReceiptsDetails).not.toHaveBeenCalled(); component.flightJourneyTravelClassOptions$.subscribe((res) => { expect(res).toBeUndefined(); diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts index fa409c1880..1aebc01260 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts @@ -32,7 +32,6 @@ import { filter, finalize, map, - reduce, shareReplay, startWith, switchMap, @@ -135,6 +134,9 @@ import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expen import { TransactionStatusInfoPopoverComponent } from 'src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component'; import { CorporateCardTransactionRes } from 'src/app/core/models/platform/v1/corporate-card-transaction-res.model'; import { corporateCardTransaction } from 'src/app/core/models/platform/v1/cc-transaction.model'; +import { PlatformFileGenerateUrlsResponse } from 'src/app/core/models/platform/platform-file-generate-urls-response.model'; +import { SpenderFileService } from 'src/app/core/services/platform/v1/spender/file.service'; +import { ReceiptInfo } from 'src/app/core/models/receipt-info.model'; type FormValue = { currencyObj: { @@ -445,6 +447,7 @@ export class AddEditExpensePage implements OnInit { private modalController: ModalController, private statusService: StatusService, private fileService: FileService, + private spenderFileService: SpenderFileService, private popoverController: PopoverController, private currencyService: CurrencyService, private networkService: NetworkService, @@ -3072,21 +3075,20 @@ export class AddEditExpensePage implements OnInit { .pipe( map( (orgSetting) => - orgSetting?.corporate_credit_card_settings?.enabled && - orgSetting?.pending_cct_expense_restriction?.enabled + orgSetting.corporate_credit_card_settings?.enabled && orgSetting.pending_cct_expense_restriction?.enabled ) ); forkJoin({ - platformExpenses: this.platformExpense$, + platformExpense: this.platformExpense$, pendingTxnRestrictionEnabled: pendingTxnRestrictionEnabled$, }) .pipe(take(1)) .subscribe((config) => { if ( config.pendingTxnRestrictionEnabled && - config.platformExpenses.matched_corporate_card_transactions?.length && - config.platformExpenses.matched_corporate_card_transactions[0]?.status === TransactionStatus.PENDING + config.platformExpense.matched_corporate_card_transactions?.length && + config.platformExpense.matched_corporate_card_transactions[0]?.status === TransactionStatus.PENDING ) { this.pendingTransactionAllowedToReportAndSplit = false; } @@ -3097,19 +3099,26 @@ export class AddEditExpensePage implements OnInit { switchMap(() => this.etxn$.pipe( switchMap((etxn) => (etxn.tx.id ? this.fileService.findByTransactionId(etxn.tx.id) : of([]))), - switchMap((fileObjs) => from(fileObjs)), - concatMap((fileObj: FileObject) => - this.fileService.downloadUrl(fileObj.id).pipe( - map((downloadUrl) => { - fileObj.url = downloadUrl; - const details = this.getReceiptDetails(fileObj); - fileObj.type = details.type; - fileObj.thumbnail = details.thumbnail; - return fileObj; - }) - ) - ), - reduce((acc: FileObject[], curr) => acc.concat(curr), []) + switchMap((fileObjs: FileObject[]) => { + const fileIds: string[] = fileObjs.map((file) => file.id); + return fileIds.length > 0 ? this.spenderFileService.generateUrlsBulk(fileIds) : of([]); + }), + map((response: PlatformFileGenerateUrlsResponse[]) => { + const files = response.filter((file) => file.content_type !== 'text/html'); + const receiptObjs: ReceiptInfo[] = files.map((file) => { + const details = this.fileService.getReceiptsDetails(file.name, file.download_url); + + const receipt: ReceiptInfo = { + url: file.download_url, + type: details.type, + thumbnail: details.thumbnail, + }; + + return receipt; + }); + + return receiptObjs; + }) ) ) ); @@ -3218,19 +3227,26 @@ export class AddEditExpensePage implements OnInit { ); } else { return this.fileService.findByTransactionId(txnId).pipe( - switchMap((fileObjs: FileObject[]) => from(fileObjs)), - concatMap((fileObj: FileObject) => - this.fileService.downloadUrl(fileObj.id).pipe( - map((downloadUrl: string) => { - fileObj.url = downloadUrl; - const details = this.getReceiptDetails(fileObj); - fileObj.type = details.type; - fileObj.thumbnail = details.thumbnail; - return fileObj; - }) - ) - ), - reduce((acc: FileObject[], curr) => acc.concat(curr), []) + switchMap((fileObjs) => { + const fileIds = fileObjs.map((file) => file.id); + return fileIds?.length > 0 ? this.spenderFileService.generateUrlsBulk(fileIds) : of([]); + }), + map((response: PlatformFileGenerateUrlsResponse[]) => { + const files = response.filter((file) => file.content_type !== 'text/html'); + const receiptObjs: ReceiptInfo[] = files.map((file) => { + const details = this.fileService.getReceiptsDetails(file.name, file.download_url); + + const receipt: ReceiptInfo = { + url: file.download_url, + type: details.type, + thumbnail: details.thumbnail, + }; + + return receipt; + }); + + return receiptObjs; + }) ); } } diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts index 12ff17b78e..4a26d7038c 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts @@ -59,6 +59,7 @@ import { TestCases5 } from './add-edit-expense-5.spec'; import { TestCases6 } from './add-edit-expense-6.spec'; import { AddEditExpensePage } from './add-edit-expense.page'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { SpenderFileService } from 'src/app/core/services/platform/v1/spender/file.service'; export function setFormValid(component) { Object.defineProperty(component.fg, 'valid', { @@ -138,7 +139,9 @@ describe('AddEditExpensePage', () => { 'post', 'readFile', 'getImageTypeFromDataUrl', + 'getReceiptsDetails', ]); + const spenderFileServiceSpy = jasmine.createSpyObj('SpenderFileService', ['generateUrlsBulk']); const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['create']); const currencyServiceSpy = jasmine.createSpyObj('CurrencyService', [ 'getAmountWithCurrencyFraction', @@ -306,6 +309,10 @@ describe('AddEditExpensePage', () => { provide: FileService, useValue: fileServiceSpy, }, + { + provide: SpenderFileService, + useValue: spenderFileServiceSpy, + }, { provide: PopoverController, useValue: popoverControllerSpy, From 5e1adad75307eae12dea9cb447bd1210897f3062 Mon Sep 17 00:00:00 2001 From: Arjun Date: Wed, 15 May 2024 17:25:16 +0530 Subject: [PATCH 020/262] fix: Remove /api/auth/access_token call happening on reset password (#2984) * Remove /api/auth/access_token call happening on reset password * remove fdescribe --- src/app/auth/new-password/new-password.page.spec.ts | 9 ++++----- src/app/auth/new-password/new-password.page.ts | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/auth/new-password/new-password.page.spec.ts b/src/app/auth/new-password/new-password.page.spec.ts index c01688d742..f5db061720 100644 --- a/src/app/auth/new-password/new-password.page.spec.ts +++ b/src/app/auth/new-password/new-password.page.spec.ts @@ -30,7 +30,7 @@ describe('NewPasswordPage', () => { let loginInfoService: jasmine.SpyObj; beforeEach(waitForAsync(() => { - const authServiceSpy = jasmine.createSpyObj('AuthService', ['newRefreshToken']); + const authServiceSpy = jasmine.createSpyObj('AuthService', ['refreshEou']); const routerAuthServiceSpy = jasmine.createSpyObj('RouterAuthService', ['resetPassword']); const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['showLoader', 'hideLoader']); const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['create']); @@ -195,7 +195,7 @@ describe('NewPasswordPage', () => { it('should change the password and show success message on success', fakeAsync(() => { spyOn(component, 'trackLoginInfo'); routerAuthService.resetPassword.and.returnValue(of(resetPasswordRes)); - authService.newRefreshToken.and.returnValue(of(apiEouRes)); + authService.refreshEou.and.returnValue(of(apiEouRes)); const popoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present']); popoverController.create.and.returnValue(popoverSpy); deviceService.getDeviceInfo.and.returnValue(of(extendedDeviceInfoMockData)); @@ -211,7 +211,7 @@ describe('NewPasswordPage', () => { expect(loaderService.showLoader).toHaveBeenCalledTimes(1); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(routerAuthService.resetPassword).toHaveBeenCalledOnceWith(refreshToken, passwordValue); - expect(authService.newRefreshToken).toHaveBeenCalledOnceWith('token123'); + expect(authService.refreshEou).toHaveBeenCalledTimes(1); expect(trackingService.onSignin).toHaveBeenCalledOnceWith('ajain@fyle.in'); expect(trackingService.resetPassword).toHaveBeenCalledTimes(1); expect(component.trackLoginInfo).toHaveBeenCalledTimes(1); @@ -227,7 +227,6 @@ describe('NewPasswordPage', () => { it('should show error message on failure', fakeAsync(() => { spyOn(component, 'trackLoginInfo'); - authService.newRefreshToken.and.rejectWith(); routerAuthService.resetPassword.and.rejectWith(); const popoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present']); popoverController.create.and.returnValue(popoverSpy); @@ -243,7 +242,7 @@ describe('NewPasswordPage', () => { expect(loaderService.showLoader).toHaveBeenCalledTimes(1); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(routerAuthService.resetPassword).toHaveBeenCalledOnceWith(refreshToken, passwordValue); - expect(authService.newRefreshToken).not.toHaveBeenCalledOnceWith('token123'); + expect(authService.refreshEou).not.toHaveBeenCalled(); expect(trackingService.onSignin).not.toHaveBeenCalled(); expect(trackingService.resetPassword).not.toHaveBeenCalled(); expect(component.trackLoginInfo).not.toHaveBeenCalled(); diff --git a/src/app/auth/new-password/new-password.page.ts b/src/app/auth/new-password/new-password.page.ts index 9197c09698..0d5295e798 100644 --- a/src/app/auth/new-password/new-password.page.ts +++ b/src/app/auth/new-password/new-password.page.ts @@ -86,7 +86,7 @@ export class NewPasswordPage implements OnInit { from(this.loaderService.showLoader()) .pipe( switchMap(() => this.routerAuthService.resetPassword(refreshToken, this.fg.controls.password.value)), - switchMap((res) => this.authService.newRefreshToken(res.refresh_token)), + switchMap(() => this.authService.refreshEou()), tap(async (eou) => { const email = eou.us.email; this.trackingService.onSignin(email); From 1fe269743783b064b0c5293f27e70e5193832201 Mon Sep 17 00:00:00 2001 From: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Date: Thu, 16 May 2024 10:43:26 +0530 Subject: [PATCH 021/262] fix: issue while merging submitted expense tx_id instead of split_group_id (#2985) --- src/app/fyle/merge-expense/merge-expense-3.page.spec.ts | 4 ++-- src/app/fyle/merge-expense/merge-expense.page.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/fyle/merge-expense/merge-expense-3.page.spec.ts b/src/app/fyle/merge-expense/merge-expense-3.page.spec.ts index 0f5a83108d..b412cf5d9d 100644 --- a/src/app/fyle/merge-expense/merge-expense-3.page.spec.ts +++ b/src/app/fyle/merge-expense/merge-expense-3.page.spec.ts @@ -428,7 +428,7 @@ export function TestCases3(getTestBed) { expect(component.expenseToKeepInfoText).toEqual( 'You are required to keep the expense that has already been submitted.' ); - expect(component.fg.controls.target_txn_id.value).toEqual('txB1rVZJ4Pxl'); + expect(component.fg.controls.target_txn_id.value).toEqual('txLgXPnTDOGf'); }); it('should call mergeExpensesService.isMoreThanOneAdvancePresent once and modify showReceiptSelection and expenseToKeepInfoText', () => { @@ -466,7 +466,7 @@ export function TestCases3(getTestBed) { expect(component.expenseToKeepInfoText).toEqual( 'You are required to keep the expense paid from ‘advance’. Edit each expense separately if you wish to make any changes.' ); - expect(component.fg.controls.target_txn_id.value).toEqual('txB1rVZJ4Pxl'); + expect(component.fg.controls.target_txn_id.value).toEqual('txLgXPnTDOGf'); }); it('should call setAdvanceOrApprovedAndAbove once', () => { diff --git a/src/app/fyle/merge-expense/merge-expense.page.ts b/src/app/fyle/merge-expense/merge-expense.page.ts index 60ba0abe8d..ca74a186ca 100644 --- a/src/app/fyle/merge-expense/merge-expense.page.ts +++ b/src/app/fyle/merge-expense/merge-expense.page.ts @@ -788,7 +788,7 @@ export class MergeExpensePage implements OnInit, AfterViewChecked { this.disableExpenseToKeep = true; this.expenseToKeepInfoText = 'You are required to keep the expense that has already been submitted.'; this.fg.patchValue({ - target_txn_id: expensesInfo.defaultExpenses[0].tx_split_group_id, + target_txn_id: expensesInfo.defaultExpenses[0].tx_id, }); } else if (this.mergeExpensesService.isMoreThanOneAdvancePresent(expensesInfo, isAllAdvanceExpenses)) { this.showReceiptSelection = true; @@ -796,7 +796,7 @@ export class MergeExpensePage implements OnInit, AfterViewChecked { 'You cannot make changes to an expense paid from ‘advance’. Edit each expense separately if you wish to make any changes.'; } else if (this.mergeExpensesService.isAdvancePresent(expensesInfo)) { this.fg.patchValue({ - target_txn_id: expensesInfo.defaultExpenses[0].tx_split_group_id, + target_txn_id: expensesInfo.defaultExpenses[0].tx_id, }); this.disableExpenseToKeep = true; this.expenseToKeepInfoText = From 86f97724254511013c99b9e02536dd8dbba00a25 Mon Sep 17 00:00:00 2001 From: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Date: Thu, 16 May 2024 11:08:35 +0530 Subject: [PATCH 022/262] fix: added return types for update mobile number component (#2982) --- .../update-mobile-number.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/fyle/my-profile/update-mobile-number/update-mobile-number.component.ts b/src/app/fyle/my-profile/update-mobile-number/update-mobile-number.component.ts index c6048a363d..014c553315 100644 --- a/src/app/fyle/my-profile/update-mobile-number/update-mobile-number.component.ts +++ b/src/app/fyle/my-profile/update-mobile-number/update-mobile-number.component.ts @@ -11,7 +11,7 @@ import { OrgUserService } from 'src/app/core/services/org-user.service'; styleUrls: ['./update-mobile-number.component.scss'], }) export class UpdateMobileNumberComponent implements OnInit, AfterViewInit { - @ViewChild('input') inputEl: ElementRef; + @ViewChild('input') inputEl: ElementRef; @Input() title: string; @@ -39,15 +39,15 @@ export class UpdateMobileNumberComponent implements OnInit, AfterViewInit { this.inputValue = this.extendedOrgUser.ou.mobile || ''; } - ngAfterViewInit() { + ngAfterViewInit(): void { setTimeout(() => this.inputEl.nativeElement.focus(), 400); } - closePopover() { + closePopover(): void { this.popoverController.dismiss(); } - validateInput() { + validateInput(): void { if (!this.inputValue?.length) { this.error = 'Please enter a Mobile Number'; } else if (!this.inputValue.match(/[+]\d{7,}$/)) { @@ -55,11 +55,11 @@ export class UpdateMobileNumberComponent implements OnInit, AfterViewInit { } } - onFocus() { + onFocus(): void { this.error = null; } - saveValue() { + saveValue(): void { //If user has not changed the verified mobile number, close the popover if (this.inputValue === this.extendedOrgUser.ou.mobile && this.extendedOrgUser.ou.mobile_verified) { this.popoverController.dismiss(); From 64900fd3d205d56fc280f3a1b61c32c7aa8c7b50 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Thu, 16 May 2024 22:04:54 +0530 Subject: [PATCH 023/262] feat: use platform expense file_ids instead of public file objects in add edit expense page (#2998) --- .../add-edit-expense-3.spec.ts | 23 +++++++----- .../add-edit-expense-5.spec.ts | 36 +++++++++++++------ .../add-edit-expense/add-edit-expense.page.ts | 30 ++++++++-------- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts index 0ce9a7a8e8..c5d8166e12 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts @@ -108,6 +108,8 @@ import { optionsData15, optionsData33 } from 'src/app/core/mock-data/merge-expen import { SpenderFileService } from 'src/app/core/services/platform/v1/spender/file.service'; import { generateUrlsBulkData1 } from 'src/app/core/mock-data/generate-urls-bulk-response.data'; import { receiptInfoData2 } from 'src/app/core/mock-data/receipt-info.data'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { platformExpenseWithExtractedData } from 'src/app/core/mock-data/platform/v1/expense.data'; export function TestCases3(getTestBed) { return describe('AddEditExpensePage-3', () => { @@ -157,6 +159,7 @@ export function TestCases3(getTestBed) { let orgUserSettingsService: jasmine.SpyObj; let storageService: jasmine.SpyObj; let launchDarklyService: jasmine.SpyObj; + let expensesService: jasmine.SpyObj; beforeEach(() => { const TestBed = getTestBed(); @@ -213,6 +216,7 @@ export function TestCases3(getTestBed) { orgUserSettingsService = TestBed.inject(OrgUserSettingsService) as jasmine.SpyObj; storageService = TestBed.inject(StorageService) as jasmine.SpyObj; launchDarklyService = TestBed.inject(LaunchDarklyService) as jasmine.SpyObj; + expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; component.fg = formBuilder.group({ currencyObj: [, component.currencyObjValidator], @@ -410,18 +414,19 @@ export function TestCases3(getTestBed) { describe('getExpenseAttachments():', () => { it('should return file observables in edit mode', (done) => { - const mockFileObject = cloneDeep(fileObject4); - fileService.findByTransactionId.and.returnValue(of(mockFileObject)); + expensesService.getExpenseById.and.returnValue(of(platformExpenseWithExtractedData)); fileService.getReceiptsDetails.and.returnValue({ type: 'pdf', thumbnail: 'img/fy-pdf.svg', }); spenderFileService.generateUrlsBulk.and.returnValue(of(generateUrlsBulkData1)); - component.getExpenseAttachments('edit', 'tx1vdITUXIzf').subscribe((res) => { + component.getExpenseAttachments('edit', platformExpenseWithExtractedData.id).subscribe((res) => { expect(res).toEqual(receiptInfoData2); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith('tx1vdITUXIzf'); - expect(spenderFileService.generateUrlsBulk).toHaveBeenCalledOnceWith(['fiV1gXpyCcbU']); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(platformExpenseWithExtractedData.id); + expect(spenderFileService.generateUrlsBulk).toHaveBeenCalledOnceWith( + platformExpenseWithExtractedData.file_ids + ); expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith( 'invoice.pdf', 'https://sampledownloadurl.com' @@ -986,7 +991,7 @@ export function TestCases3(getTestBed) { component.mode = 'edit'; component.attachedReceiptsCount = 0; spyOn(component, 'getExpenseAttachments').and.returnValue(of(fileObject4)); - fileService.findByTransactionId.and.returnValue(of([fileObjectData])); + expensesService.getExpenseById.and.returnValue(of(platformExpenseWithExtractedData)); spyOn(component.loadAttachments$, 'next'); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); @@ -1005,7 +1010,7 @@ export function TestCases3(getTestBed) { tick(500); expect(component.getExpenseAttachments).toHaveBeenCalledOnceWith(component.mode, unflattenedTxnData.tx.id); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(unflattenedTxnData.tx.id); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(unflattenedTxnData.tx.id); expect(loaderService.showLoader).toHaveBeenCalledTimes(1); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(modalController.create).toHaveBeenCalledOnceWith({ @@ -1415,7 +1420,7 @@ export function TestCases3(getTestBed) { component.etxn$ = of(unflattenedExpData); component.isConnected$ = of(true); const mockFileData = cloneDeep(fileObjectData1); - fileService.findByTransactionId.and.returnValue(of(mockFileData)); + expensesService.getExpenseById.and.returnValue(of(platformExpenseWithExtractedData)); transactionOutboxService.fileUpload.and.resolveTo(mockFileData[0]); activatedRoute.snapshot.params.id = mockFileData[0].transaction_id; fileService.post.and.returnValue(of(fileData1[0])); @@ -1429,7 +1434,7 @@ export function TestCases3(getTestBed) { }); tick(1000); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(unflattenedExpData.tx.id); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(unflattenedExpData.tx.id); expect(transactionOutboxService.fileUpload).toHaveBeenCalledOnceWith('url', 'pdf'); expect(fileService.post).toHaveBeenCalledOnceWith(mockFileData[0]); expect(component.loadAttachments$.next).toHaveBeenCalledOnceWith(); diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts index ef3d394e3e..eebf429c10 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts @@ -105,7 +105,13 @@ import { apiV2ResponseMultiple, expectedProjectsResponse } from 'src/app/core/te import { getEstatusApiResponse } from 'src/app/core/test-data/status.service.spec.data'; import { AddEditExpensePage } from './add-edit-expense.page'; import { txnFieldsData2, txnFieldsFlightData } from 'src/app/core/mock-data/expense-fields-map.data'; -import { apiExpenses2, expenseData, splitExpensesData } from 'src/app/core/mock-data/platform/v1/expense.data'; +import { + apiExpenses2, + expenseData, + platformExpenseData, + platformExpenseWithExtractedData, + splitExpensesData, +} from 'src/app/core/mock-data/platform/v1/expense.data'; import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { matchedCCTransactionData } from 'src/app/core/mock-data/matchedCCTransaction.data'; import { cloneDeep } from 'lodash'; @@ -767,24 +773,35 @@ export function TestCases5(getTestBed) { describe('getReceiptCount():', () => { it('should get receipt count', (done) => { component.etxn$ = of(unflattenedTxnData); - fileService.findByTransactionId.and.returnValue(of(fileObject4)); + expensesService.getExpenseById.and.returnValue(of(platformExpenseWithExtractedData)); fixture.detectChanges(); component.getReceiptCount().subscribe((res) => { expect(res).toEqual(1); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(unflattenedTxnData.tx.id); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(unflattenedTxnData.tx.id); done(); }); }); it('should return 0 if no receipts are returned', (done) => { component.etxn$ = of(unflattenedTxnData); - fileService.findByTransactionId.and.returnValue(of(null)); + expensesService.getExpenseById.and.returnValue(of(platformExpenseData)); + fixture.detectChanges(); + + component.getReceiptCount().subscribe((res) => { + expect(res).toEqual(0); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(unflattenedTxnData.tx.id); + done(); + }); + }); + + it('should return 0 if new expense is being created', (done) => { + component.etxn$ = of({ tx: {} }); fixture.detectChanges(); component.getReceiptCount().subscribe((res) => { expect(res).toEqual(0); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(unflattenedTxnData.tx.id); + expect(expensesService.getExpenseById).not.toHaveBeenCalled(); done(); }); }); @@ -1349,9 +1366,6 @@ export function TestCases5(getTestBed) { spyOn(component, 'getNewExpenseObservable').and.returnValue(of(expectedExpenseObservable)); spyOn(component, 'getEditExpenseObservable').and.returnValue(of(expectedUnflattendedTxnData1)); expensesService.getExpenseById.and.returnValue(of(expenseData)); - const mockFileObject = cloneDeep(expectedFileData1); - fileService.findByTransactionId.and.returnValue(of(mockFileObject)); - fileService.downloadUrl.and.returnValue(of('url')); fileService.getReceiptsDetails.and.returnValue({ type: 'pdf', thumbnail: 'img/fy-pdf.svg', @@ -1474,14 +1488,14 @@ export function TestCases5(getTestBed) { expect(component.pendingTransactionAllowedToReportAndSplit).toBeTrue(); - expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith('txyeiYbLDSOy'); + expect(expensesService.getExpenseById).toHaveBeenCalledWith(activatedRoute.snapshot.params.id); component.attachments$.subscribe((res) => { expect(res).toEqual(receiptInfoData2); + expect(expensesService.getExpenseById).toHaveBeenCalledWith(unflattenedTxnData.tx.id); + expect(spenderFileService.generateUrlsBulk).toHaveBeenCalledOnceWith(expenseData.file_ids); }); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith('tx3qHxFNgRcZ'); - expect(spenderFileService.generateUrlsBulk).toHaveBeenCalledOnceWith(['fiV1gXpyCcbU']); expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith( generateUrlsBulkData1[0].name, generateUrlsBulkData1[0].download_url diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts index 1aebc01260..84e31886c5 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts @@ -1630,8 +1630,8 @@ export class AddEditExpensePage implements OnInit { getReceiptCount(): Observable { return this.etxn$.pipe( - switchMap((etxn) => (etxn.tx.id ? this.fileService.findByTransactionId(etxn.tx.id) : of([]))), - map((fileObjs) => (fileObjs && fileObjs.length) || 0) + switchMap((etxn) => (etxn.tx.id ? this.expensesService.getExpenseById(etxn.tx.id) : of({}))), + map((expense: PlatformExpense) => expense.file_ids?.length || 0) ); } @@ -3098,11 +3098,10 @@ export class AddEditExpensePage implements OnInit { this.attachments$ = this.loadAttachments$.pipe( switchMap(() => this.etxn$.pipe( - switchMap((etxn) => (etxn.tx.id ? this.fileService.findByTransactionId(etxn.tx.id) : of([]))), - switchMap((fileObjs: FileObject[]) => { - const fileIds: string[] = fileObjs.map((file) => file.id); - return fileIds.length > 0 ? this.spenderFileService.generateUrlsBulk(fileIds) : of([]); - }), + switchMap((etxn) => (etxn.tx.id ? this.expensesService.getExpenseById(etxn.tx.id) : of({}))), + switchMap((expense: PlatformExpense) => + expense.file_ids?.length > 0 ? this.spenderFileService.generateUrlsBulk(expense.file_ids) : of([]) + ), map((response: PlatformFileGenerateUrlsResponse[]) => { const files = response.filter((file) => file.content_type !== 'text/html'); const receiptObjs: ReceiptInfo[] = files.map((file) => { @@ -3226,11 +3225,10 @@ export class AddEditExpensePage implements OnInit { }) ); } else { - return this.fileService.findByTransactionId(txnId).pipe( - switchMap((fileObjs) => { - const fileIds = fileObjs.map((file) => file.id); - return fileIds?.length > 0 ? this.spenderFileService.generateUrlsBulk(fileIds) : of([]); - }), + return this.expensesService.getExpenseById(txnId).pipe( + switchMap((expense: PlatformExpense) => + expense.file_ids?.length > 0 ? this.spenderFileService.generateUrlsBulk(expense.file_ids) : of([]) + ), map((response: PlatformFileGenerateUrlsResponse[]) => { const files = response.filter((file) => file.content_type !== 'text/html'); const receiptObjs: ReceiptInfo[] = files.map((file) => { @@ -4437,8 +4435,8 @@ export class AddEditExpensePage implements OnInit { }); } else { const editExpenseAttachments$ = this.etxn$.pipe( - switchMap((etxn) => (etxn.tx.id ? this.fileService.findByTransactionId(etxn.tx.id) : of([]))), - map((fileObjs) => fileObjs?.length || 0) + switchMap((etxn) => (etxn.tx.id ? this.expensesService.getExpenseById(etxn.tx.id) : of({}))), + map((expense: PlatformExpense) => expense.file_ids?.length || 0) ); this.attachmentUploadInProgress = true; @@ -4654,8 +4652,8 @@ export class AddEditExpensePage implements OnInit { if ((data && data.attachments.length !== this.attachedReceiptsCount) || !data) { this.etxn$ .pipe( - switchMap((etxn) => this.fileService.findByTransactionId(etxn.tx.id)), - map((fileObjs) => (fileObjs && fileObjs.length) || 0) + switchMap((etxn) => this.expensesService.getExpenseById(etxn.tx.id)), + map((expense: PlatformExpense) => expense.file_ids?.length || 0) ) .subscribe((attachedReceipts) => { this.loadAttachments$.next(); From de14ad0a36fd513a5af68bd213c2ab3cc1452ad7 Mon Sep 17 00:00:00 2001 From: Dimple K H <31147415+Dimple16@users.noreply.github.com> Date: Thu, 16 May 2024 23:49:35 +0530 Subject: [PATCH 024/262] fix: Revert platform projects (#3000) * Revert "fix: Improved test cases ProjectsService (#2980)" This reverts commit 77e6f4cbc81be3ca38d139608fce1fa5f9adc32c. * Revert "feat: Migrate getByParamsUnformatted -> spenderPlatformV1ApiService (#2954)" This reverts commit 53f20fb7700e350011e677e3304c0f23b8e0d16f. * Revert "feat: Migrate getAllActive -> spenderPlatformV1ApiService (#2967)" This reverts commit b5c9e2e82850866adb718d92d1da1be17d512076. * Revert "feat: Migrate getbyId -> spenderPlatformV1ApiService (#2946)" This reverts commit e110648801a794dd1ecce8ebac27d55435ef8e9d. * Revert "feat: Add transformToV1Response & transformToV2Response for PlatformProject (#2942)" This reverts commit 5ca606e14a4d799dea79d59edb1530b5cfe5252c. --------- Co-authored-by: Dimple --- .../platform/v1/platform-project.data.ts | 102 --------------- .../v1/platform-projects-params.data.ts | 13 -- .../models/platform/platform-project.model.ts | 13 -- .../v1/platform-project-params.model.ts | 13 -- src/app/core/models/v2/project-v2.model.ts | 12 +- .../core/services/projects.service.spec.ts | 62 ++++----- src/app/core/services/projects.service.ts | 118 ++++++++---------- .../recently-used-items.service.spec.ts | 4 +- .../services/recently-used-items.service.ts | 4 +- src/app/core/test-data/projects.spec.data.ts | 50 +++++++- .../fy-select-project-modal.component.spec.ts | 42 ++++--- .../fy-select-project-modal.component.ts | 4 +- 12 files changed, 151 insertions(+), 286 deletions(-) delete mode 100644 src/app/core/mock-data/platform/v1/platform-project.data.ts delete mode 100644 src/app/core/mock-data/platform/v1/platform-projects-params.data.ts delete mode 100644 src/app/core/models/platform/platform-project.model.ts delete mode 100644 src/app/core/models/platform/v1/platform-project-params.model.ts diff --git a/src/app/core/mock-data/platform/v1/platform-project.data.ts b/src/app/core/mock-data/platform/v1/platform-project.data.ts deleted file mode 100644 index 39a6e36fd4..0000000000 --- a/src/app/core/mock-data/platform/v1/platform-project.data.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { PlatformApiResponse } from '../../../models/platform/platform-api-response.model'; -import { PlatformProject } from '../../../models/platform/platform-project.model'; -import deepFreeze from 'deep-freeze-strict'; - -export const platformProjectSingleRes: PlatformApiResponse = deepFreeze({ - count: 1, - data: [ - { - is_enabled: true, - code: '1184', - created_at: new Date('2021-05-12T10:28:40.834844'), - description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', - id: 257528, - name: 'Customer Mapped Project', - category_ids: [122269, 122270, 122271, null], - org_id: 'orFdTTTNcyye', - updated_at: new Date('2021-07-08T10:28:27.686886'), - display_name: 'Customer Mapped Project', - sub_project: null, - }, - ], - offset: 0, -}); - -export const platformAPIResponseMultiple: PlatformApiResponse = deepFreeze({ - count: 2, - data: [ - { - is_enabled: true, - code: '1184', - created_at: new Date('2021-05-12T10:28:40.834844'), - description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', - id: 257528, - display_name: 'Customer Mapped Project', - category_ids: [122269, 122270, 122271, null], - org_id: 'orFdTTTNcyye', - updated_at: new Date('2021-07-08T10:28:27.686886'), - name: 'Customer Mapped Project', - sub_project: null, - }, - { - is_enabled: true, - code: '1182', - created_at: new Date('2021-05-12T10:28:40.834844'), - description: 'Sage Intacct Project - Fyle Engineering, Id - 1182', - id: 257529, - display_name: 'Fyle Engineering', - category_ids: [122269, 122270, 122271], - org_id: 'orFdTTTNcyye', - updated_at: new Date('2021-07-08T10:28:27.686886'), - name: 'Fyle Engineering', - sub_project: null, - }, - ], - offset: 0, -}); - -export const platformAPIResponseActiveOnly: PlatformApiResponse = deepFreeze({ - count: 4, - data: [ - { - id: 257528, - created_at: new Date('2021-05-12T10:28:40.834Z'), - updated_at: new Date('2021-07-08T10:28:27.686Z'), - name: 'Customer Mapped Project', - sub_project: null, - code: '1184', - org_id: 'orFdTTTNcyye', - description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', - is_enabled: true, - category_ids: [null, 145429, 122269, 122271], - display_name: 'Customer Mapped Project', - }, - { - id: 257541, - created_at: new Date('2021-05-12T10:28:40.834Z'), - updated_at: new Date('2021-07-08T10:28:27.686Z'), - name: 'Sage Project 8', - sub_project: null, - code: '1178', - org_id: 'orFdTTTNcyye', - description: 'Sage Intacct Project - Sage Project 8, Id - 1178', - is_enabled: true, - category_ids: [null, 145429, 122269, 122271], - display_name: 'Customer Mapped Project', - }, - { - id: 257531, - created_at: new Date('2021-05-12T10:28:40.834Z'), - updated_at: new Date('2021-07-08T10:28:27.686Z'), - name: 'Fyle Team Integrations', - sub_project: null, - code: '1183', - org_id: 'orFdTTTNcyye', - description: 'Sage Intacct Project - Fyle Team Integrations, Id - 1183', - is_enabled: true, - category_ids: null, - display_name: 'Customer Mapped Project', - }, - ], - offset: 0, -}); diff --git a/src/app/core/mock-data/platform/v1/platform-projects-params.data.ts b/src/app/core/mock-data/platform/v1/platform-projects-params.data.ts deleted file mode 100644 index 43eab3cb31..0000000000 --- a/src/app/core/mock-data/platform/v1/platform-projects-params.data.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PlatformProjectParams } from 'src/app/core/models/platform/v1/platform-project-params.model'; -import deepFreeze from 'deep-freeze-strict'; - -export const ProjectPlatformParams: PlatformProjectParams = deepFreeze({ - org_id: 'eq.orNVthTo2Zyo', - order: 'name.asc', - limit: 10, - offset: 0, - is_enabled: 'eq.true', - category_ids: 'ov.{,122269,122270,122271,122272,122273}', - id: 'in.(3943,305792,148971,247936)', - name: 'ilike.%search%', -}); diff --git a/src/app/core/models/platform/platform-project.model.ts b/src/app/core/models/platform/platform-project.model.ts deleted file mode 100644 index e92b323356..0000000000 --- a/src/app/core/models/platform/platform-project.model.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface PlatformProject { - id: number; - org_id: string; - created_at: Date; - updated_at: Date; - name: string; - sub_project: string; - code: string; - display_name: string; - description: string; - is_enabled: boolean; - category_ids: number[]; -} diff --git a/src/app/core/models/platform/v1/platform-project-params.model.ts b/src/app/core/models/platform/v1/platform-project-params.model.ts deleted file mode 100644 index d55325d0b3..0000000000 --- a/src/app/core/models/platform/v1/platform-project-params.model.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface PlatformProjectParams { - limit: number; - offset: number; - order?: string; - sortDirection?: string; - sortOrder?: string; - searchNameText?: string; - is_enabled?: string; - id?: string; - category_ids?: string; - org_id?: string; - name?: string; -} diff --git a/src/app/core/models/v2/project-v2.model.ts b/src/app/core/models/v2/project-v2.model.ts index 8af239ae40..59edb030b5 100644 --- a/src/app/core/models/v2/project-v2.model.ts +++ b/src/app/core/models/v2/project-v2.model.ts @@ -1,11 +1,11 @@ export interface ProjectV2 { - ap1_email?: string; - ap1_full_name?: string; - ap2_email?: string; - ap2_full_name?: string; + ap1_email: string; + ap1_full_name: string; + ap2_email: string; + ap2_full_name: string; project_active: boolean; - project_approver1_id?: string; - project_approver2_id?: string; + project_approver1_id: string; + project_approver2_id: string; project_code: string; project_created_at: Date; project_description: string; diff --git a/src/app/core/services/projects.service.spec.ts b/src/app/core/services/projects.service.spec.ts index e5a0c6dac8..3152ac6a44 100644 --- a/src/app/core/services/projects.service.spec.ts +++ b/src/app/core/services/projects.service.spec.ts @@ -16,13 +16,6 @@ import { params, } from '../test-data/projects.spec.data'; import { ProjectsService } from './projects.service'; -import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service'; -import { - platformAPIResponseMultiple, - platformProjectSingleRes, - platformAPIResponseActiveOnly, -} from '../mock-data/platform/v1/platform-project.data'; -import { ProjectPlatformParams } from '../mock-data/platform/v1/platform-projects-params.data'; const fixDate = (data) => data.map((datum) => ({ @@ -35,12 +28,10 @@ describe('ProjectsService', () => { let projectsService: ProjectsService; let apiService: jasmine.SpyObj; let apiV2Service: jasmine.SpyObj; - let spenderPlatformV1ApiService: jasmine.SpyObj; beforeEach(() => { const apiServiceSpy = jasmine.createSpyObj('ApiService', ['get']); const apiv2ServiceSpy = jasmine.createSpyObj('ApiV2Service', ['get']); - const spenderPlatformApiServiceSpy = jasmine.createSpyObj('SpenderPlatformV1ApiService', ['get']); TestBed.configureTestingModule({ providers: [ @@ -53,18 +44,11 @@ describe('ProjectsService', () => { provide: ApiV2Service, useValue: apiv2ServiceSpy, }, - { - provide: SpenderPlatformV1ApiService, - useValue: spenderPlatformApiServiceSpy, - }, ], }); projectsService = TestBed.inject(ProjectsService); apiService = TestBed.inject(ApiService) as jasmine.SpyObj; apiV2Service = TestBed.inject(ApiV2Service) as jasmine.SpyObj; - spenderPlatformV1ApiService = TestBed.inject( - SpenderPlatformV1ApiService - ) as jasmine.SpyObj; }); it('should be created', () => { @@ -72,37 +56,35 @@ describe('ProjectsService', () => { }); it('should be able to fetch project by id', (done) => { - spenderPlatformV1ApiService.get.and.returnValue(of(platformProjectSingleRes)); - spyOn(projectsService, 'transformToV2Response').and.returnValue([apiV2ResponseSingle.data[0]]); + apiV2Service.get.and.returnValue(of(apiV2ResponseSingle)); projectsService.getbyId(257528).subscribe((res) => { - expect(res).toEqual(apiV2ResponseSingle.data[0]); - expect(spenderPlatformV1ApiService.get).toHaveBeenCalledOnceWith('/projects', { - params: { - id: 'eq.257528', - }, - }); - expect(projectsService.transformToV2Response).toHaveBeenCalled(); + expect(res).toEqual(fixDate(apiV2ResponseSingle.data)[0]); done(); }); + + expect(apiV2Service.get).toHaveBeenCalledWith('/projects', { + params: { + project_id: 'eq.257528', + }, + }); }); it('should be able to fetch all active projects', (done) => { - spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseActiveOnly)); - spyOn(projectsService, 'transformToV1Response').and.returnValue(expectedReponseActiveOnly); + apiService.get.and.returnValue(of(apiResponseActiveOnly)); projectsService.getAllActive().subscribe((res) => { expect(res).toEqual(expectedReponseActiveOnly); - expect(spenderPlatformV1ApiService.get).toHaveBeenCalledOnceWith('/projects', { - params: { - is_enabled: `eq.true`, - }, - }); - expect(projectsService.transformToV1Response).toHaveBeenCalled(); done(); }); + + expect(apiService.get).toHaveBeenCalledWith('/projects', { + params: { + active_only: true, + }, + }); }); it('should be able to fetch data when no params provided', (done) => { - spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseMultiple)); + apiV2Service.get.and.returnValue(of(apiV2ResponseMultiple)); projectsService.getByParamsUnformatted({}).subscribe((res) => { expect(res).toEqual(fixDate(apiV2ResponseMultiple.data)); @@ -111,17 +93,15 @@ describe('ProjectsService', () => { }); it('should be able to fetch data when params are provided', (done) => { - spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseMultiple)); - const params = ProjectPlatformParams; + apiV2Service.get.and.returnValue(of(apiV2ResponseMultiple)); + const result = projectsService.getByParamsUnformatted(testProjectParams); - spyOn(projectsService, 'transformToV2Response').and.returnValue(expectedProjectsResponse); result.subscribe((res) => { expect(res).toEqual(expectedProjectsResponse); - expect(spenderPlatformV1ApiService.get).toHaveBeenCalledWith('/projects', { + expect(apiV2Service.get).toHaveBeenCalledWith('/projects', { params, }); - expect(projectsService.transformToV2Response).toHaveBeenCalled(); done(); }); }); @@ -137,7 +117,7 @@ describe('ProjectsService', () => { }); it('should get project count restricted by a set of category IDs', (done) => { - spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseActiveOnly)); + apiService.get.and.returnValue(of(apiResponseActiveOnly)); const result = projectsService.getProjectCount({ categoryIds: testCategoryIds }); result.subscribe((res) => { @@ -147,7 +127,7 @@ describe('ProjectsService', () => { }); it('should get project count not restricted by a set of category IDs', (done) => { - spenderPlatformV1ApiService.get.and.returnValue(of(platformAPIResponseActiveOnly)); + apiService.get.and.returnValue(of(apiResponseActiveOnly)); const resultWithOutParam = projectsService.getProjectCount(); const resultWithParam = projectsService.getProjectCount({ categoryIds: null }); diff --git a/src/app/core/services/projects.service.ts b/src/app/core/services/projects.service.ts index 2e464f579a..7b4b8d7081 100644 --- a/src/app/core/services/projects.service.ts +++ b/src/app/core/services/projects.service.ts @@ -9,26 +9,18 @@ import { ProjectV1 } from '../models/v1/extended-project.model'; import { ProjectParams } from '../models/project-params.model'; import { intersection } from 'lodash'; import { OrgCategory } from '../models/v1/org-category.model'; -import { PlatformProject } from '../models/platform/platform-project.model'; -import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service'; -import { PlatformApiResponse } from '../models/platform/platform-api-response.model'; -import { PlatformProjectParams } from '../models/platform/v1/platform-project-params.model'; @Injectable({ providedIn: 'root', }) export class ProjectsService { - constructor( - private apiService: ApiService, - private apiV2Service: ApiV2Service, - private spenderPlatformV1ApiService: SpenderPlatformV1ApiService - ) {} + constructor(private apiService: ApiService, private apiV2Service: ApiV2Service) {} @Cacheable() getByParamsUnformatted( projectParams: Partial<{ orgId: string; - isEnabled: boolean; + active: boolean; orgCategoryIds: string[]; searchNameText: string; limit: number; @@ -39,20 +31,20 @@ export class ProjectsService { }> ): Observable { // eslint-disable-next-line prefer-const - let { orgId, isEnabled, orgCategoryIds, searchNameText, limit, offset, sortOrder, sortDirection, projectIds } = + let { orgId, active, orgCategoryIds, searchNameText, limit, offset, sortOrder, sortDirection, projectIds } = projectParams; sortOrder = sortOrder || 'project_updated_at'; sortDirection = sortDirection || 'desc'; - const params: PlatformProjectParams = { - org_id: 'eq.' + orgId, + const params: ProjectParams = { + project_org_id: 'eq.' + orgId, order: sortOrder + '.' + sortDirection, limit: limit || 200, offset: offset || 0, }; // `active` can be optional - this.addActiveFilter(isEnabled, params); + this.addActiveFilter(active, params); // `orgCategoryIds` can be optional this.addOrgCategoryIdsFilter(orgCategoryIds, params); @@ -63,11 +55,19 @@ export class ProjectsService { // `searchNameText` can be optional this.addNameSearchFilter(searchNameText, params); - return this.spenderPlatformV1ApiService - .get>('/projects', { + return this.apiV2Service + .get('/projects', { params, }) - .pipe(map((res) => this.transformToV2Response(res.data))); + .pipe( + map((res) => + res.data.map((datum) => ({ + ...datum, + project_created_at: new Date(datum.project_created_at), + project_updated_at: new Date(datum.project_updated_at), + })) + ) + ); } @Cacheable() @@ -87,27 +87,27 @@ export class ProjectsService { ); } - addNameSearchFilter(searchNameText: string, params: PlatformProjectParams): void { + addNameSearchFilter(searchNameText: string, params: ProjectParams): void { if (typeof searchNameText !== 'undefined' && searchNameText !== null) { - params.name = 'ilike.%' + searchNameText + '%'; + params.project_name = 'ilike.%' + searchNameText + '%'; } } - addProjectIdsFilter(projectIds: number[], params: PlatformProjectParams): void { + addProjectIdsFilter(projectIds: number[], params: ProjectParams): void { if (typeof projectIds !== 'undefined' && projectIds !== null) { - params.id = 'in.(' + projectIds.join(',') + ')'; + params.project_id = 'in.(' + projectIds.join(',') + ')'; } } - addOrgCategoryIdsFilter(orgCategoryIds: string[], params: PlatformProjectParams): void { + addOrgCategoryIdsFilter(orgCategoryIds: string[], params: ProjectParams): void { if (typeof orgCategoryIds !== 'undefined' && orgCategoryIds !== null) { - params.category_ids = 'ov.{' + orgCategoryIds.join(',') + '}'; + params.project_org_category_ids = 'ov.{' + orgCategoryIds.join(',') + '}'; } } - addActiveFilter(isEnabled: boolean, params: PlatformProjectParams): void { - if (typeof isEnabled !== 'undefined' && isEnabled !== null) { - params.is_enabled = 'eq.' + isEnabled; + addActiveFilter(active: boolean, params: ProjectParams): void { + if (typeof active !== 'undefined' && active !== null) { + params.project_active = 'eq.' + active; } } @@ -129,57 +129,37 @@ export class ProjectsService { getAllActive(): Observable { const data = { params: { - is_enabled: `eq.true`, + active_only: true, }, }; - return this.spenderPlatformV1ApiService - .get>('/projects', data) - .pipe(map((res) => this.transformToV1Response(res.data))); + return this.apiService.get('/projects', data).pipe( + map((res) => + res.map((datum) => ({ + ...datum, + created_at: new Date(datum.created_at), + updated_at: new Date(datum.updated_at), + })) + ) + ); } getbyId(projectId: number | string): Observable { - return this.spenderPlatformV1ApiService - .get>('/projects', { + return this.apiV2Service + .get('/projects', { params: { - id: `eq.${projectId}`, + project_id: `eq.${projectId}`, }, }) - .pipe(map((res) => this.transformToV2Response(res.data)[0])); - } - - transformToV1Response(platformProject: PlatformProject[]): ProjectV1[] { - const projectV1 = platformProject.map((platformProject) => ({ - id: platformProject.id, - created_at: new Date(platformProject.created_at), - updated_at: new Date(platformProject.updated_at), - name: platformProject.name, - sub_project: platformProject.sub_project, - code: platformProject.code, - org_id: platformProject.org_id, - description: platformProject.description, - active: platformProject.is_enabled, - org_category_ids: platformProject.category_ids, - })); - - return projectV1; - } - - transformToV2Response(platformProject: PlatformProject[]): ProjectV2[] { - const projectV2 = platformProject.map((platformProject) => ({ - project_active: platformProject.is_enabled, - project_code: platformProject.code, - project_created_at: new Date(platformProject.created_at), - project_description: platformProject.description, - project_id: platformProject.id, - project_name: platformProject.display_name, - project_org_category_ids: platformProject.category_ids, - project_org_id: platformProject.org_id, - project_updated_at: new Date(platformProject.updated_at), - projectv2_name: platformProject.name, - sub_project_name: platformProject.sub_project, - })); - - return projectV2; + .pipe( + map( + (res) => + res.data.map((datum) => ({ + ...datum, + project_created_at: new Date(datum.project_created_at), + project_updated_at: new Date(datum.project_updated_at), + }))[0] + ) + ); } } diff --git a/src/app/core/services/recently-used-items.service.spec.ts b/src/app/core/services/recently-used-items.service.spec.ts index 0a56aaa58c..437ab4cde6 100644 --- a/src/app/core/services/recently-used-items.service.spec.ts +++ b/src/app/core/services/recently-used-items.service.spec.ts @@ -65,9 +65,9 @@ describe('RecentlyUsedItemsService', () => { recentlyUsedItemsService.getRecentlyUsedProjects(config).subscribe((res) => { expect(projectsService.getByParamsUnformatted).toHaveBeenCalledOnceWith({ orgId: config.eou.ou.org_id, - isEnabled: true, + active: true, sortDirection: 'asc', - sortOrder: 'name', + sortOrder: 'project_name', orgCategoryIds: config.categoryIds, projectIds: config.recentValues.recent_project_ids, offset: 0, diff --git a/src/app/core/services/recently-used-items.service.ts b/src/app/core/services/recently-used-items.service.ts index 4a29a5d995..5a0bba0475 100644 --- a/src/app/core/services/recently-used-items.service.ts +++ b/src/app/core/services/recently-used-items.service.ts @@ -33,9 +33,9 @@ export class RecentlyUsedItemsService { return this.projectsService .getByParamsUnformatted({ orgId: config.eou.ou.org_id, - isEnabled: true, + active: true, sortDirection: 'asc', - sortOrder: 'name', + sortOrder: 'project_name', orgCategoryIds: config.categoryIds, projectIds: config.recentValues.recent_project_ids, offset: 0, diff --git a/src/app/core/test-data/projects.spec.data.ts b/src/app/core/test-data/projects.spec.data.ts index a63bae5b1c..64c42dcece 100644 --- a/src/app/core/test-data/projects.spec.data.ts +++ b/src/app/core/test-data/projects.spec.data.ts @@ -1,4 +1,6 @@ import deepFreeze from 'deep-freeze-strict'; + +import { ProjectParams } from '../models/project-params.model'; import { ProjectV1 } from '../models/v1/extended-project.model'; import { OrgCategory, OrgCategoryListItem } from '../models/v1/org-category.model'; import { ProjectV2 } from '../models/v2/project-v2.model'; @@ -14,6 +16,8 @@ export const apiResponseActiveOnly = deepFreeze([ org_id: 'orFdTTTNcyye', description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', active: true, + approver1_id: null, + approver2_id: null, org_category_ids: [null, 145429, 122269, 122271], }, { @@ -26,6 +30,8 @@ export const apiResponseActiveOnly = deepFreeze([ org_id: 'orFdTTTNcyye', description: 'Sage Intacct Project - Sage Project 8, Id - 1178', active: true, + approver1_id: null, + approver2_id: null, org_category_ids: [null, 145429, 122269, 122271], }, { @@ -38,6 +44,8 @@ export const apiResponseActiveOnly = deepFreeze([ org_id: 'orFdTTTNcyye', description: 'Sage Intacct Project - Fyle Team Integrations, Id - 1183', active: true, + approver1_id: null, + approver2_id: null, org_category_ids: null, }, ]); @@ -53,6 +61,8 @@ export const expectedReponseActiveOnly = deepFreeze([ org_id: 'orFdTTTNcyye', description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', active: true, + approver1_id: null, + approver2_id: null, org_category_ids: [null, 145429, 122269, 122271], }, { @@ -65,6 +75,8 @@ export const expectedReponseActiveOnly = deepFreeze([ org_id: 'orFdTTTNcyye', description: 'Sage Intacct Project - Sage Project 8, Id - 1178', active: true, + approver1_id: null, + approver2_id: null, org_category_ids: [null, 145429, 122269, 122271], }, { @@ -77,6 +89,8 @@ export const expectedReponseActiveOnly = deepFreeze([ org_id: 'orFdTTTNcyye', description: 'Sage Intacct Project - Fyle Team Integrations, Id - 1183', active: true, + approver1_id: null, + approver2_id: null, org_category_ids: null, }, ]); @@ -85,7 +99,13 @@ export const apiV2ResponseMultiple = deepFreeze({ count: 2, data: [ { + ap1_email: null, + ap1_full_name: null, + ap2_email: null, + ap2_full_name: null, project_active: true, + project_approver1_id: null, + project_approver2_id: null, project_code: '1184', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', @@ -98,7 +118,13 @@ export const apiV2ResponseMultiple = deepFreeze({ sub_project_name: null, }, { + ap1_email: null, + ap1_full_name: null, + ap2_email: null, + ap2_full_name: null, project_active: true, + project_approver1_id: null, + project_approver2_id: null, project_code: '1182', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Fyle Engineering, Id - 1182', @@ -120,7 +146,13 @@ export const apiV2ResponseSingle = deepFreeze({ count: 1, data: [ { + ap1_email: null, + ap1_full_name: null, + ap2_email: null, + ap2_full_name: null, project_active: true, + project_approver1_id: null, + project_approver2_id: null, project_code: '1184', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', @@ -287,7 +319,13 @@ export const allowedActiveCategoriesListOptions: OrgCategoryListItem[] = deepFre export const expectedProjectsResponse: ProjectV2[] = deepFreeze([ { + ap1_email: null, + ap1_full_name: null, + ap2_email: null, + ap2_full_name: null, project_active: true, + project_approver1_id: null, + project_approver2_id: null, project_code: '1184', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Customer Mapped Project, Id - 1184', @@ -300,7 +338,13 @@ export const expectedProjectsResponse: ProjectV2[] = deepFreeze([ sub_project_name: null, }, { + ap1_email: null, + ap1_full_name: null, + ap2_email: null, + ap2_full_name: null, project_active: true, + project_approver1_id: null, + project_approver2_id: null, project_code: '1182', project_created_at: new Date('2021-05-12T10:28:40.834844'), project_description: 'Sage Intacct Project - Fyle Engineering, Id - 1182', @@ -314,11 +358,11 @@ export const expectedProjectsResponse: ProjectV2[] = deepFreeze([ }, ]); -export const testProjectParams = deepFreeze({ +export const testProjectParams: ProjectParams = deepFreeze({ orgId: 'orNVthTo2Zyo', - isEnabled: true, + active: true, sortDirection: 'asc', - sortOrder: 'name', + sortOrder: 'project_name', orgCategoryIds: [null, '122269', '122270', '122271', '122272', '122273'], projectIds: [3943, 305792, 148971, 247936], offset: 0, diff --git a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts index b546517f11..0f3cd2eb22 100644 --- a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts +++ b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts @@ -117,7 +117,7 @@ describe('FyProjectSelectModalComponent', () => { projectsService.getbyId.and.returnValue(of(singleProjects1)); orgSettingsService.get.and.returnValue(of(orgSettingsData)); - authService.getEou.and.resolveTo(apiEouRes); + authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); orgUserSettingsService.get.and.returnValue(of(orgUserSettingsData)); projectsService.getByParamsUnformatted.and.returnValue(of([singleProject2])); @@ -125,7 +125,7 @@ describe('FyProjectSelectModalComponent', () => { component.cacheName = 'projects'; component.defaultValue = true; component.searchBarRef = fixture.debugElement.query(By.css('.selection-modal--search-input')); - recentLocalStorageItemsService.get.and.resolveTo([testProjectV2]); + recentLocalStorageItemsService.get.and.returnValue(Promise.resolve([testProjectV2])); utilityService.searchArrayStream.and.returnValue(() => of([{ label: '', value: '' }])); fixture.detectChanges(); @@ -140,7 +140,7 @@ describe('FyProjectSelectModalComponent', () => { it('should get projects when current selection is not defined', (done) => { projectsService.getByParamsUnformatted.and.returnValue(of(projects)); projectsService.getbyId.and.returnValue(of(expectedProjects[0].value)); - authService.getEou.and.resolveTo(apiEouRes); + authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); component.getProjects('projects').subscribe((res) => { expect(res).toEqual(expectedProjects); @@ -149,9 +149,9 @@ describe('FyProjectSelectModalComponent', () => { expect(orgUserSettingsService.get).toHaveBeenCalledTimes(4); expect(projectsService.getByParamsUnformatted).toHaveBeenCalledWith({ orgId: 'orNVthTo2Zyo', - isEnabled: true, + active: true, sortDirection: 'asc', - sortOrder: 'name', + sortOrder: 'project_name', orgCategoryIds: undefined, projectIds: null, searchNameText: '', @@ -175,9 +175,9 @@ describe('FyProjectSelectModalComponent', () => { expect(orgUserSettingsService.get).toHaveBeenCalledTimes(4); expect(projectsService.getByParamsUnformatted).toHaveBeenCalledWith({ orgId: 'orNVthTo2Zyo', - isEnabled: true, + active: true, sortDirection: 'asc', - sortOrder: 'name', + sortOrder: 'project_name', orgCategoryIds: undefined, projectIds: null, searchNameText: '', @@ -201,9 +201,9 @@ describe('FyProjectSelectModalComponent', () => { expect(orgUserSettingsService.get).toHaveBeenCalledTimes(4); expect(projectsService.getByParamsUnformatted).toHaveBeenCalledWith({ orgId: 'orNVthTo2Zyo', - isEnabled: true, + active: true, sortDirection: 'asc', - sortOrder: 'name', + sortOrder: 'project_name', orgCategoryIds: undefined, projectIds: null, searchNameText: '', @@ -251,12 +251,14 @@ describe('FyProjectSelectModalComponent', () => { }); it('should get project from recently used storage if not already present', (done) => { - recentLocalStorageItemsService.get.and.resolveTo([ - { - label: 'label', - value: testProjectV2, - }, - ]); + recentLocalStorageItemsService.get.and.returnValue( + Promise.resolve([ + { + label: 'label', + value: testProjectV2, + }, + ]) + ); component.recentlyUsed = null; component.cacheName = 'project'; fixture.detectChanges(); @@ -275,16 +277,16 @@ describe('FyProjectSelectModalComponent', () => { }); }); - it('onDoneClick(): should dimiss the modal on clicking the done CTA', async () => { - modalController.dismiss.and.resolveTo(true); + it('onDoneClick(): should dimiss the modal on clicking the done CTA', () => { + modalController.dismiss.and.returnValue(Promise.resolve(true)); - await component.onDoneClick(); + component.onDoneClick(); expect(modalController.dismiss).toHaveBeenCalledTimes(1); }); describe('onElementSelect():', () => { it('should dismiss the modal with selected option', () => { - modalController.dismiss.and.resolveTo(true); + modalController.dismiss.and.returnValue(Promise.resolve(true)); component.onElementSelect({ label: '', value: null }); expect(modalController.dismiss).toHaveBeenCalledWith({ label: '', value: null }); @@ -292,7 +294,7 @@ describe('FyProjectSelectModalComponent', () => { }); it('should cache the selected option and dismiss the modal', () => { - modalController.dismiss.and.resolveTo(true); + modalController.dismiss.and.returnValue(Promise.resolve(true)); recentLocalStorageItemsService.post.and.returnValue(null); component.cacheName = 'cache'; fixture.detectChanges(); diff --git a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts index a90a510e50..a5581fca4b 100644 --- a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts +++ b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts @@ -85,9 +85,9 @@ export class FyProjectSelectModalComponent implements AfterViewInit { switchMap((eou) => this.projectsService.getByParamsUnformatted({ orgId: eou.ou.org_id, - isEnabled: true, + active: true, sortDirection: 'asc', - sortOrder: 'name', + sortOrder: 'project_name', orgCategoryIds: this.categoryIds, projectIds: allowedProjectIds, searchNameText, From 96a429ac7afa439abb9efc5aad202c4873ce8654 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Sat, 18 May 2024 10:03:28 +0530 Subject: [PATCH 025/262] feat: use platform expense files key instead of findByTxnId() in split expense page (#3002) --- .../split-expense/split-expense.page.spec.ts | 18 ++++++++++++------ .../fyle/split-expense/split-expense.page.ts | 14 +++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/app/fyle/split-expense/split-expense.page.spec.ts b/src/app/fyle/split-expense/split-expense.page.spec.ts index c70962cc5c..50aa637212 100644 --- a/src/app/fyle/split-expense/split-expense.page.spec.ts +++ b/src/app/fyle/split-expense/split-expense.page.spec.ts @@ -17,6 +17,7 @@ import { CurrencyService } from 'src/app/core/services/currency.service'; import { OrgUserSettingsService } from 'src/app/core/services/org-user-settings.service'; import { DependentFieldsService } from 'src/app/core/services/dependent-fields.service'; import { SplitExpensePage } from './split-expense.page'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { AbstractControlOptions, FormArray, @@ -169,6 +170,7 @@ import { import { splitPolicyExp1 } from 'src/app/core/mock-data/split-expense-policy.data'; import { SplitExpenseMissingFieldsData } from 'src/app/core/models/split-expense-missing-fields.data'; import { splitPayloadData1 } from 'src/app/core/mock-data/split-payload.data'; +import { platformExpenseData, platformExpenseWithExtractedData } from 'src/app/core/mock-data/platform/v1/expense.data'; describe('SplitExpensePage', () => { let component: SplitExpensePage; @@ -180,6 +182,7 @@ describe('SplitExpensePage', () => { let currencyService: jasmine.SpyObj; let transactionService: jasmine.SpyObj; let fileService: jasmine.SpyObj; + let expensesService: jasmine.SpyObj; let navController: jasmine.SpyObj; let router: jasmine.SpyObj; let transactionsOutboxService: jasmine.SpyObj; @@ -218,6 +221,7 @@ describe('SplitExpensePage', () => { ]); const currencyServiceSpy = jasmine.createSpyObj('CurrencyService', ['getHomeCurrency']); const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['delete', 'matchCCCExpense']); + const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById']); const fileServiceSpy = jasmine.createSpyObj('FileService', ['findByTransactionId']); const routerSpy = jasmine.createSpyObj('Router', ['navigate']); const transactionsOutboxServiceSpy = jasmine.createSpyObj('TransactionsOutboxService', ['fileUpload']); @@ -274,6 +278,7 @@ describe('SplitExpensePage', () => { { provide: SplitExpenseService, useValue: splitExpenseServiceSpy }, { provide: CurrencyService, useValue: currencyServiceSpy }, { provide: TransactionService, useValue: transactionServiceSpy }, + { provide: ExpensesService, useValue: expensesServiceSpy }, { provide: FileService, useValue: fileServiceSpy }, { provide: Router, useValue: routerSpy }, { provide: TransactionsOutboxService, useValue: transactionsOutboxServiceSpy }, @@ -327,6 +332,7 @@ describe('SplitExpensePage', () => { splitExpenseService = TestBed.inject(SplitExpenseService) as jasmine.SpyObj; currencyService = TestBed.inject(CurrencyService) as jasmine.SpyObj; transactionService = TestBed.inject(TransactionService) as jasmine.SpyObj; + expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; fileService = TestBed.inject(FileService) as jasmine.SpyObj; router = TestBed.inject(Router) as jasmine.SpyObj; transactionsOutboxService = TestBed.inject(TransactionsOutboxService) as jasmine.SpyObj; @@ -744,12 +750,12 @@ describe('SplitExpensePage', () => { }); it('getAttachedFiles(): should get all the attached files', (done) => { - const transactionId = 'fizBwnXhyZTp'; - fileService.findByTransactionId.and.returnValue(of(fileObject8)); - component.getAttachedFiles(transactionId).subscribe((result) => { - expect(result).toEqual(fileObject8); - expect(component.fileObjs).toEqual(fileObject8); - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(transactionId); + const expenseId = 'fizBwnXhyZTp'; + expensesService.getExpenseById.and.returnValue(of(platformExpenseWithExtractedData)); + component.getAttachedFiles(expenseId).subscribe((result) => { + expect(result).toEqual(platformExpenseWithExtractedData.files); + expect(component.fileObjs).toEqual(platformExpenseWithExtractedData.files); + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(expenseId); done(); }); }); diff --git a/src/app/fyle/split-expense/split-expense.page.ts b/src/app/fyle/split-expense/split-expense.page.ts index c06e585050..e0a0ecfe74 100644 --- a/src/app/fyle/split-expense/split-expense.page.ts +++ b/src/app/fyle/split-expense/split-expense.page.ts @@ -48,6 +48,9 @@ import { TimezoneService } from 'src/app/core/services/timezone.service'; import { TxnCustomProperties } from 'src/app/core/models/txn-custom-properties.model'; import { SplittingExpenseProperties } from 'src/app/core/models/tracking-properties.model'; import { HttpErrorResponse } from '@angular/common/http'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { Expense as PlatformExpense } from 'src/app/core/models/platform/v1/expense.model'; +import { PlatformFile } from 'src/app/core/models/platform/platform-file.model'; @Component({ selector: 'app-split-expense', @@ -139,7 +142,8 @@ export class SplitExpensePage { private dependentFieldsService: DependentFieldsService, private launchDarklyService: LaunchDarklyService, private projectsService: ProjectsService, - private timezoneService: TimezoneService + private timezoneService: TimezoneService, + private expensesService: ExpensesService ) {} goBack(): void { @@ -540,10 +544,10 @@ export class SplitExpensePage { this.toastWithoutCTA(toastMessage, ToastType.SUCCESS, 'msb-success-with-camera-icon'); } - getAttachedFiles(transactionId: string): Observable { - return this.fileService.findByTransactionId(transactionId).pipe( - map((uploadedFiles) => { - this.fileObjs = uploadedFiles; + getAttachedFiles(expenseId: string): Observable[]> { + return this.expensesService.getExpenseById(expenseId).pipe( + map((expense: PlatformExpense) => { + this.fileObjs = expense.files; return this.fileObjs; }) ); From 363fe3f2088d2822ad49479b4668f95d90a1a4e6 Mon Sep 17 00:00:00 2001 From: SK027 Date: Mon, 20 May 2024 16:45:34 +0530 Subject: [PATCH 026/262] feat: Add debouncing in Search Project Modal (#2986) --- .../fy-select-project-modal.component.spec.ts | 37 ++++++++++--------- .../fy-select-project-modal.component.ts | 4 +- .../fy-select-project.component.spec.ts | 30 +++++++-------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts index 0f3cd2eb22..13005d416b 100644 --- a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts +++ b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed, discardPeriodicTasks, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { IonicModule } from '@ionic/angular'; import { ModalController } from '@ionic/angular'; import { ProjectsService } from 'src/app/core/services/projects.service'; @@ -117,7 +117,7 @@ describe('FyProjectSelectModalComponent', () => { projectsService.getbyId.and.returnValue(of(singleProjects1)); orgSettingsService.get.and.returnValue(of(orgSettingsData)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); orgUserSettingsService.get.and.returnValue(of(orgUserSettingsData)); projectsService.getByParamsUnformatted.and.returnValue(of([singleProject2])); @@ -125,7 +125,7 @@ describe('FyProjectSelectModalComponent', () => { component.cacheName = 'projects'; component.defaultValue = true; component.searchBarRef = fixture.debugElement.query(By.css('.selection-modal--search-input')); - recentLocalStorageItemsService.get.and.returnValue(Promise.resolve([testProjectV2])); + recentLocalStorageItemsService.get.and.resolveTo([testProjectV2]); utilityService.searchArrayStream.and.returnValue(() => of([{ label: '', value: '' }])); fixture.detectChanges(); @@ -140,7 +140,7 @@ describe('FyProjectSelectModalComponent', () => { it('should get projects when current selection is not defined', (done) => { projectsService.getByParamsUnformatted.and.returnValue(of(projects)); projectsService.getbyId.and.returnValue(of(expectedProjects[0].value)); - authService.getEou.and.returnValue(Promise.resolve(apiEouRes)); + authService.getEou.and.resolveTo(apiEouRes); component.getProjects('projects').subscribe((res) => { expect(res).toEqual(expectedProjects); @@ -251,14 +251,12 @@ describe('FyProjectSelectModalComponent', () => { }); it('should get project from recently used storage if not already present', (done) => { - recentLocalStorageItemsService.get.and.returnValue( - Promise.resolve([ - { - label: 'label', - value: testProjectV2, - }, - ]) - ); + recentLocalStorageItemsService.get.and.resolveTo([ + { + label: 'label', + value: testProjectV2, + }, + ]); component.recentlyUsed = null; component.cacheName = 'project'; fixture.detectChanges(); @@ -278,7 +276,7 @@ describe('FyProjectSelectModalComponent', () => { }); it('onDoneClick(): should dimiss the modal on clicking the done CTA', () => { - modalController.dismiss.and.returnValue(Promise.resolve(true)); + modalController.dismiss.and.resolveTo(true); component.onDoneClick(); expect(modalController.dismiss).toHaveBeenCalledTimes(1); @@ -286,7 +284,7 @@ describe('FyProjectSelectModalComponent', () => { describe('onElementSelect():', () => { it('should dismiss the modal with selected option', () => { - modalController.dismiss.and.returnValue(Promise.resolve(true)); + modalController.dismiss.and.resolveTo(true); component.onElementSelect({ label: '', value: null }); expect(modalController.dismiss).toHaveBeenCalledWith({ label: '', value: null }); @@ -294,7 +292,7 @@ describe('FyProjectSelectModalComponent', () => { }); it('should cache the selected option and dismiss the modal', () => { - modalController.dismiss.and.returnValue(Promise.resolve(true)); + modalController.dismiss.and.resolveTo(true); recentLocalStorageItemsService.post.and.returnValue(null); component.cacheName = 'cache'; fixture.detectChanges(); @@ -310,7 +308,7 @@ describe('FyProjectSelectModalComponent', () => { }); }); - it('ngAfterViewInit(): show filtered projects and recently used items', (done) => { + it('ngAfterViewInit(): show filtered projects and recently used items', fakeAsync((done) => { spyOn(component, 'getRecentlyUsedItems').and.returnValue(of(expectedProjects)); spyOn(component, 'getProjects').and.returnValue(of(labelledProjects)); @@ -321,10 +319,12 @@ describe('FyProjectSelectModalComponent', () => { inputElement.value = 'projects'; inputElement.dispatchEvent(new Event('keyup')); + tick(300); component.recentrecentlyUsedItems$.subscribe((res) => { expect(res).toEqual(expectedProjects4); }); + tick(300); component.filteredOptions$.subscribe((res) => { expect(res).toEqual(expectedLabelledProjects); }); @@ -332,8 +332,9 @@ describe('FyProjectSelectModalComponent', () => { expect(component.getProjects).toHaveBeenCalledWith('projects'); expect(component.getRecentlyUsedItems).toHaveBeenCalled(); expect(utilityService.searchArrayStream).toHaveBeenCalledWith('projects'); - done(); - }); + + discardPeriodicTasks(); + })); it('should show label on the screen', () => { component.label = 'Projects'; diff --git a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts index a5581fca4b..d61fea630f 100644 --- a/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts +++ b/src/app/shared/components/fy-select-project/fy-select-modal/fy-select-project-modal.component.ts @@ -1,7 +1,7 @@ import { Component, AfterViewInit, ViewChild, ElementRef, Input, ChangeDetectorRef, TemplateRef } from '@angular/core'; import { Observable, fromEvent, iif, of, from } from 'rxjs'; import { ModalController } from '@ionic/angular'; -import { map, startWith, distinctUntilChanged, switchMap, concatMap, finalize } from 'rxjs/operators'; +import { map, startWith, distinctUntilChanged, switchMap, concatMap, finalize, debounceTime } from 'rxjs/operators'; import { isEqual } from 'lodash'; import { ProjectsService } from 'src/app/core/services/projects.service'; import { AuthService } from 'src/app/core/services/auth.service'; @@ -165,6 +165,7 @@ export class FyProjectSelectModalComponent implements AfterViewInit { map((event) => event.target.value), startWith(''), distinctUntilChanged(), + debounceTime(300), switchMap((searchText: string) => this.getProjects(searchText)), map((projects) => projects.map((project: { label: string; value: ProjectV2; selected?: boolean }) => { @@ -183,6 +184,7 @@ export class FyProjectSelectModalComponent implements AfterViewInit { map((event) => event.target.value), startWith(''), distinctUntilChanged(), + debounceTime(300), switchMap((searchText: string) => this.getRecentlyUsedItems().pipe( // filtering of recently used items wrt searchText is taken care in service method diff --git a/src/app/shared/components/fy-select-project/fy-select-project.component.spec.ts b/src/app/shared/components/fy-select-project/fy-select-project.component.spec.ts index 10046a185b..466708a66a 100644 --- a/src/app/shared/components/fy-select-project/fy-select-project.component.spec.ts +++ b/src/app/shared/components/fy-select-project/fy-select-project.component.spec.ts @@ -61,7 +61,7 @@ describe('FySelectProjectComponent', () => { component.validInParent = false; const res = component.valid; - expect(res).toEqual(true); + expect(res).toBeTrue(); }); it('openModal(): should open select vendor modal', async () => { @@ -76,14 +76,12 @@ describe('FySelectProjectComponent', () => { handle: false, }); const projectModalSpy = jasmine.createSpyObj('projectModal', ['present', 'onWillDismiss']); - projectModalSpy.onWillDismiss.and.returnValue( - Promise.resolve({ - data: { - value: testProjectV2, - }, - }) - ); - modalController.create.and.returnValue(Promise.resolve(projectModalSpy)); + projectModalSpy.onWillDismiss.and.resolveTo({ + data: { + value: testProjectV2, + }, + }); + modalController.create.and.resolveTo(projectModalSpy); await component.openModal(); @@ -148,14 +146,12 @@ describe('FySelectProjectComponent', () => { component.registerOnChange(callbackFn); const projectModalSpy = jasmine.createSpyObj('projectModal', ['present', 'onWillDismiss']); - projectModalSpy.onWillDismiss.and.returnValue( - Promise.resolve({ - data: { - value: 'value1', - }, - }) - ); - modalController.create.and.returnValue(Promise.resolve(projectModalSpy)); + projectModalSpy.onWillDismiss.and.resolveTo({ + data: { + value: 'value1', + }, + }); + modalController.create.and.resolveTo(projectModalSpy); modalProperties.getModalDefaultProperties.and.callThrough(); From 7f88e218421103a1e33fa13f44c6ae0a15a507dc Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Mon, 20 May 2024 21:16:45 +0530 Subject: [PATCH 027/262] feat: replace files post api with platform attach receipt to expense api endpoint (#3005) --- src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts | 7 +++++-- src/app/fyle/add-edit-expense/add-edit-expense.page.ts | 4 ++-- .../fyle/add-edit-expense/add-edit-expense.setup.spec.ts | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts index c5d8166e12..af7baf5d8a 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts @@ -1423,7 +1423,7 @@ export function TestCases3(getTestBed) { expensesService.getExpenseById.and.returnValue(of(platformExpenseWithExtractedData)); transactionOutboxService.fileUpload.and.resolveTo(mockFileData[0]); activatedRoute.snapshot.params.id = mockFileData[0].transaction_id; - fileService.post.and.returnValue(of(fileData1[0])); + expensesService.attachReceiptToExpense.and.returnValue(of(platformExpenseWithExtractedData)); spyOn(component, 'parseFile').and.returnValue(null); spyOn(component.loadAttachments$, 'next'); fixture.detectChanges(); @@ -1436,7 +1436,10 @@ export function TestCases3(getTestBed) { expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(unflattenedExpData.tx.id); expect(transactionOutboxService.fileUpload).toHaveBeenCalledOnceWith('url', 'pdf'); - expect(fileService.post).toHaveBeenCalledOnceWith(mockFileData[0]); + expect(expensesService.attachReceiptToExpense).toHaveBeenCalledOnceWith( + mockFileData[0].transaction_id, + mockFileData[0].id + ); expect(component.loadAttachments$.next).toHaveBeenCalledOnceWith(); expect(trackingService.fileUploadComplete).toHaveBeenCalledOnceWith({ mode: 'edit', diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts index 84e31886c5..80879c4ba9 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts @@ -4448,13 +4448,13 @@ export class AddEditExpensePage implements OnInit { from(this.transactionOutboxService.fileUpload(data.dataUrl as string, attachmentType)) .pipe( switchMap((fileObj: FileObject) => { - fileObj.transaction_id = this.activatedRoute.snapshot.params.id as string; + const expenseId = this.activatedRoute.snapshot.params.id as string; this.trackingService.fileUploadComplete({ mode: 'edit', 'File ID': fileObj?.id, 'Txn ID': fileObj?.transaction_id, }); - return this.fileService.post(fileObj); + return this.expensesService.attachReceiptToExpense(expenseId, fileObj.id); }), switchMap(() => editExpenseAttachments$.pipe( diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts index 4a26d7038c..df9dfbb56f 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts @@ -222,6 +222,7 @@ describe('AddEditExpensePage', () => { 'getDuplicatesByExpense', 'getAllExpenses', 'getSplitExpenses', + 'attachReceiptToExpense', ]); TestBed.configureTestingModule({ From c518ed64938ffa01a06316b7089c038b6c91d9e3 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Mon, 20 May 2024 21:21:50 +0530 Subject: [PATCH 028/262] feat: replace files post api with platform attach receipt to expense api endpoint - part 2 (#3006) --- .../add-edit-expense-2.spec.ts | 24 +++++++------------ .../add-edit-expense/add-edit-expense.page.ts | 12 +++------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-2.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-2.spec.ts index 8d125e2a47..2430dcb6e1 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-2.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-2.spec.ts @@ -1455,35 +1455,27 @@ export function TestCases2(getTestBed) { }); }); - it('postToFileService(): should post files to file service', () => { - fileService.post.and.returnValue(of(null)); - - component.postToFileService(fileObjectData, 'tx5fBcPBAxLv'); - - expect(fileService.post).toHaveBeenCalledOnceWith({ ...fileObjectData, transaction_id: 'tx5fBcPBAxLv' }); - }); - - it('uploadFileAndPostToFileService(): should upload to file service', (done) => { + it('uploadFileAndAttachToExpense(): should upload to file service', (done) => { transactionOutboxService.fileUpload.and.resolveTo(fileObjectData); - spyOn(component, 'postToFileService').and.returnValue(of(fileObjectData)); + expensesService.attachReceiptToExpense.and.returnValue(of(platformExpenseWithExtractedData)); - component.uploadFileAndPostToFileService(fileObjectData, 'tx5fBcPBAxLv').subscribe(() => { + component.uploadFileAndAttachToExpense(fileObjectData, 'tx5fBcPBAxLv').subscribe(() => { expect(transactionOutboxService.fileUpload).toHaveBeenCalledOnceWith(fileObjectData.url, fileObjectData.type); - expect(component.postToFileService).toHaveBeenCalledOnceWith(fileObjectData, 'tx5fBcPBAxLv'); + expect(expensesService.attachReceiptToExpense).toHaveBeenCalledOnceWith('tx5fBcPBAxLv', fileObjectData.id); done(); }); }); it('uploadMultipleFiles(): should upload multiple files', (done) => { - const uploadSpy = spyOn(component, 'uploadFileAndPostToFileService'); + const uploadSpy = spyOn(component, 'uploadFileAndAttachToExpense'); uploadSpy.withArgs(fileObject7[0], 'tx5fBcPBAxLv').and.returnValue(of(fileObject7[0])); uploadSpy.withArgs(fileObject7[1], 'tx5fBcPBAxLv').and.returnValue(of(fileObject7[1])); component.uploadMultipleFiles(fileObject7, 'tx5fBcPBAxLv').subscribe((res) => { expect(res).toEqual(fileObject7); - expect(component.uploadFileAndPostToFileService).toHaveBeenCalledTimes(2); - expect(component.uploadFileAndPostToFileService).toHaveBeenCalledWith(fileObject7[0], 'tx5fBcPBAxLv'); - expect(component.uploadFileAndPostToFileService).toHaveBeenCalledWith(fileObject7[1], 'tx5fBcPBAxLv'); + expect(component.uploadFileAndAttachToExpense).toHaveBeenCalledTimes(2); + expect(component.uploadFileAndAttachToExpense).toHaveBeenCalledWith(fileObject7[0], 'tx5fBcPBAxLv'); + expect(component.uploadFileAndAttachToExpense).toHaveBeenCalledWith(fileObject7[1], 'tx5fBcPBAxLv'); done(); }); }); diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts index 80879c4ba9..041fbb3078 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts @@ -4920,18 +4920,12 @@ export class AddEditExpensePage implements OnInit { } uploadMultipleFiles(fileObjs: FileObject[], txnId: string): Observable { - return forkJoin(fileObjs.map((file) => this.uploadFileAndPostToFileService(file, txnId))); + return forkJoin(fileObjs.map((file) => this.uploadFileAndAttachToExpense(file, txnId))); } - postToFileService(fileObj: FileObject, txnId: string): Observable { - const fileObjCopy = cloneDeep(fileObj); - fileObjCopy.transaction_id = txnId; - return this.fileService.post(fileObjCopy); - } - - uploadFileAndPostToFileService(file: FileObject, txnId: string): Observable { + uploadFileAndAttachToExpense(file: FileObject, txnId: string): Observable { return from(this.transactionOutboxService.fileUpload(file.url, file.type)).pipe( - switchMap((fileObj: FileObject) => this.postToFileService(fileObj, txnId)) + switchMap((fileObj: FileObject) => this.expensesService.attachReceiptToExpense(txnId, fileObj.id)) ); } From 50c74e317425347674e1050cfe988fcc6f8fb174 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Tue, 21 May 2024 13:59:03 +0530 Subject: [PATCH 029/262] fix: for merge expenses attachments issue (#3009) --- src/app/core/mock-data/file-object.data.ts | 10 ++++++++++ .../core/services/merge-expenses.service.spec.ts | 5 ++--- src/app/core/services/merge-expenses.service.ts | 15 +++++++-------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/app/core/mock-data/file-object.data.ts b/src/app/core/mock-data/file-object.data.ts index d8404cb070..1d675fabc7 100644 --- a/src/app/core/mock-data/file-object.data.ts +++ b/src/app/core/mock-data/file-object.data.ts @@ -369,3 +369,13 @@ export const fileObject10: FileObject[] = deepFreeze([ thumbnail: 'src/assets/images/pdf-receipt-placeholder.png', }, ]); + +export const fileObject11: FileObject[] = deepFreeze([ + { + id: '1', + name: 'invoice.pdf', + type: 'pdf', + url: 'https://sampledownloadurl.com', + thumbnail: 'img/fy-pdf.svg', + }, +]); diff --git a/src/app/core/services/merge-expenses.service.spec.ts b/src/app/core/services/merge-expenses.service.spec.ts index ee997d884f..ceb5432d42 100644 --- a/src/app/core/services/merge-expenses.service.spec.ts +++ b/src/app/core/services/merge-expenses.service.spec.ts @@ -87,7 +87,7 @@ import { optionsData8, optionsData9, } from '../mock-data/merge-expenses-options-data.data'; -import { fileObject5 } from '../mock-data/file-object.data'; +import { fileObject11, fileObject5 } from '../mock-data/file-object.data'; import { mergeExpenesesCustomInputsData } from '../mock-data/merge-expenses-custom-inputs.data'; import * as lodash from 'lodash'; import { projectsV1Data } from '../test-data/projects.spec.data'; @@ -100,7 +100,6 @@ import { cloneDeep } from 'lodash'; import { ExpensesService } from './platform/v1/spender/expenses.service'; import { SpenderFileService } from './platform/v1/spender/file.service'; import { platformExpenseData, platformExpenseWithExtractedData } from '../mock-data/platform/v1/expense.data'; -import { receiptInfoData2 } from '../mock-data/receipt-info.data'; import { generateUrlsBulkData1 } from '../mock-data/generate-urls-bulk-response.data'; describe('MergeExpensesService', () => { @@ -394,7 +393,7 @@ describe('MergeExpensesService', () => { const transactionId = 'txz2vohKxBXu'; mergeExpensesService.getAttachements(transactionId).subscribe((res) => { - expect(res).toEqual(receiptInfoData2); + expect(res).toEqual(fileObject11); expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith(transactionId); expect(spenderFileService.generateUrlsBulk).toHaveBeenCalledOnceWith(platformExpenseWithExtractedData.file_ids); expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith( diff --git a/src/app/core/services/merge-expenses.service.ts b/src/app/core/services/merge-expenses.service.ts index 390b1adb60..fbb86fb625 100644 --- a/src/app/core/services/merge-expenses.service.ts +++ b/src/app/core/services/merge-expenses.service.ts @@ -28,7 +28,6 @@ import { DependentFieldsMapping } from '../models/dependent-field-mapping.model' import { CustomInput } from '../models/custom-input.model'; import { ExpensesService } from './platform/v1/spender/expenses.service'; import { PlatformFileGenerateUrlsResponse } from '../models/platform/platform-file-generate-urls-response.model'; -import { ReceiptInfo } from '../models/receipt-info.model'; type CardTransactionsConfig = { queryParams: { @@ -122,23 +121,23 @@ export class MergeExpensesService { getAttachements(txnID: string): Observable { return this.expensesService.getExpenseById(txnID).pipe( switchMap((expense: PlatformExpense) => - expense.file_ids.length > 0 ? this.spenderFileService.generateUrlsBulk(expense.file_ids) : of([]) + expense?.file_ids.length > 0 ? this.spenderFileService.generateUrlsBulk(expense.file_ids) : of([]) ), map((response: PlatformFileGenerateUrlsResponse[]) => { - const files = response.filter((file) => file.content_type !== 'text/html'); - const receiptObjs: ReceiptInfo[] = files.map((file) => { + const fileObjs: FileObject[] = response.map((file) => { const details = this.fileService.getReceiptsDetails(file.name, file.download_url); - - const receipt: ReceiptInfo = { + const fileObj: FileObject = { + id: file.id, + name: file.name, url: file.download_url, type: details.type, thumbnail: details.thumbnail, }; - return receipt; + return fileObj; }); - return receiptObjs; + return fileObjs; }) ); } From 7fde337584ae12b7ca028c64b3b9e64eb24efc7e Mon Sep 17 00:00:00 2001 From: Aryan Sethi <46134586+neohacker18@users.noreply.github.com> Date: Wed, 22 May 2024 18:44:15 +0530 Subject: [PATCH 030/262] fix: Fixing 400 error in POST /platform/v1/spender/expenses/stats (#3011) * Fixing expense/stats api error * Changing tests * Adding test for --- .../fyle/my-expenses/my-expenses.page.spec.ts | 28 +++++++++++++++++-- src/app/fyle/my-expenses/my-expenses.page.ts | 12 ++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/app/fyle/my-expenses/my-expenses.page.spec.ts b/src/app/fyle/my-expenses/my-expenses.page.spec.ts index 6a4d6ee673..8111f8a510 100644 --- a/src/app/fyle/my-expenses/my-expenses.page.spec.ts +++ b/src/app/fyle/my-expenses/my-expenses.page.spec.ts @@ -1153,7 +1153,7 @@ describe('MyExpensesV2Page', () => { expect(expensesService.getExpenseStats).toHaveBeenCalledOnceWith({ report_id: 'is.null', state: 'in.(COMPLETE,DRAFT)', - or: ['(matched_corporate_card_transactions->0->corporate_card_number.8698)'], + 'matched_corporate_card_transactions->0->corporate_card_number': '8698', }); expect(allExpenseStats).toEqual({ count: 3, @@ -1196,7 +1196,31 @@ describe('MyExpensesV2Page', () => { expect(expensesService.getExpenseStats).toHaveBeenCalledOnceWith({ report_id: 'is.null', state: 'in.(COMPLETE,DRAFT)', - or: ['(matched_corporate_card_transactions->0->corporate_card_number.8698)'], + 'matched_corporate_card_transactions->0->corporate_card_number': '8698', + }); + }); + + it('should delete queryParams.state if queryParams.or contains an element with state', () => { + component.loadExpenses$ = new BehaviorSubject({ + queryParams: { + or: ['state->DRAFT'], + 'matched_corporate_card_transactions->0->corporate_card_number': '8698', + state: 'in.(COMPLETE,DRAFT)', + }, + }); + + expensesService.getExpenseStats.and.returnValue(of(completeStats)); + component.setAllExpensesCountAndAmount(); + component.allExpensesStats$.subscribe((allExpenseStats) => { + expect(expensesService.getExpenseStats).toHaveBeenCalledOnceWith({ + report_id: 'is.null', + or: ['state->DRAFT'], + 'matched_corporate_card_transactions->0->corporate_card_number': '8698', + }); + expect(allExpenseStats).toEqual({ + count: 3, + amount: 30, + }); }); }); }); diff --git a/src/app/fyle/my-expenses/my-expenses.page.ts b/src/app/fyle/my-expenses/my-expenses.page.ts index eff4a8e406..fca88756a6 100644 --- a/src/app/fyle/my-expenses/my-expenses.page.ts +++ b/src/app/fyle/my-expenses/my-expenses.page.ts @@ -335,12 +335,12 @@ export class MyExpensesPage implements OnInit { queryParams.report_id = (queryParams.report_id || 'is.null') as string; queryParams.state = 'in.(COMPLETE,DRAFT)'; - if (queryParams['matched_corporate_card_transactions->0->corporate_card_number']) { - const cardParamsCopy = queryParams['matched_corporate_card_transactions->0->corporate_card_number'] as string; - - queryParams.or = (queryParams.or || []) as string[]; - queryParams.or.push('(matched_corporate_card_transactions->0->corporate_card_number.' + cardParamsCopy + ')'); - delete queryParams['matched_corporate_card_transactions->0->corporate_card_number']; + if (queryParams.or) { + const hasExpenseState = + Array.isArray(queryParams.or) && queryParams.or.some((element) => element.includes('state')); + if (hasExpenseState) { + delete queryParams.state; + } } return this.expenseService.getExpenseStats(queryParams).pipe( From c5cc4ac67b9031f99e7295640be3c93347b830d5 Mon Sep 17 00:00:00 2001 From: SK027 Date: Thu, 23 May 2024 11:20:30 +0530 Subject: [PATCH 031/262] feat: Update mobile app readme for code coverage (#3015) --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 9126b1508a..5f4ce72908 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,16 @@ ionic serve -c staging - Run `npm run test:no-parallel` to run tests without sharding (without parallel browsers). This is useful to avoid parallel execution and to prevent excessive CPU utilization and memory hogging. ## Viewing coverage report + After running the tests, you can view the test coverage report by following these steps: - Open generated `index.html` file present in the `app/coverage/index.html`. + - Metrics Explanation: + In this file you would see 4 metrics for the files you have changed + - Statements: Percentage of executed statements. + - Branches: Percentage of executed branches (e.g., conditions in if, else, switch statements, `&&`, `||`, `?` operators used). + - Functions: Percentage of executed functions. + - Lines: Percentage of executed lines of code. + - To increase code coverage write additional test cases to cover the missing Metrics. ## For running app directly in android device for staging From 2a3c7d8c183dec5ee6abba8be0dcbf75977b0bf2 Mon Sep 17 00:00:00 2001 From: Chethan-Fyle <93918979+Chethan-Fyle@users.noreply.github.com> Date: Thu, 23 May 2024 15:26:02 +0530 Subject: [PATCH 032/262] feat: replace files post call with attach_files/bulk in transaction service (#3008) --- .../core/cache-buster/expense-cache-buster.ts | 3 ++ .../v1/spender/expenses.service.spec.ts | 2 +- .../platform/v1/spender/expenses.service.ts | 17 +++++++++- .../core/services/transaction.service.spec.ts | 17 ++++++++-- src/app/core/services/transaction.service.ts | 34 +++++++------------ 5 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 src/app/core/cache-buster/expense-cache-buster.ts diff --git a/src/app/core/cache-buster/expense-cache-buster.ts b/src/app/core/cache-buster/expense-cache-buster.ts new file mode 100644 index 0000000000..6d72fa1740 --- /dev/null +++ b/src/app/core/cache-buster/expense-cache-buster.ts @@ -0,0 +1,3 @@ +import { Subject } from 'rxjs'; + +export const expensesCacheBuster$ = new Subject(); diff --git a/src/app/core/services/platform/v1/spender/expenses.service.spec.ts b/src/app/core/services/platform/v1/spender/expenses.service.spec.ts index d38347bd3b..5a85936951 100644 --- a/src/app/core/services/platform/v1/spender/expenses.service.spec.ts +++ b/src/app/core/services/platform/v1/spender/expenses.service.spec.ts @@ -10,10 +10,10 @@ import { import { PAGINATION_SIZE } from 'src/app/constants'; import { expensesResponse } from 'src/app/core/mock-data/platform/v1/expenses-response.data'; import { getExpensesQueryParams } from 'src/app/core/mock-data/platform/v1/expenses-query-params.data'; -import { expensesCacheBuster$ } from '../../../transaction.service'; import { expenseDuplicateSets } from 'src/app/core/mock-data/platform/v1/expense-duplicate-sets.data'; import { completeStats } from 'src/app/core/mock-data/platform/v1/expenses-stats.data'; import { ExpensesService as SharedExpenseService } from '../shared/expenses.service'; +import { expensesCacheBuster$ } from 'src/app/core/cache-buster/expense-cache-buster'; describe('ExpensesService', () => { let service: ExpensesService; diff --git a/src/app/core/services/platform/v1/spender/expenses.service.ts b/src/app/core/services/platform/v1/spender/expenses.service.ts index bef74a78c7..5e2a5de49b 100644 --- a/src/app/core/services/platform/v1/spender/expenses.service.ts +++ b/src/app/core/services/platform/v1/spender/expenses.service.ts @@ -6,7 +6,6 @@ import { Expense } from 'src/app/core/models/platform/v1/expense.model'; import { ExpensesQueryParams } from 'src/app/core/models/platform/v1/expenses-query-params.model'; import { PAGINATION_SIZE } from 'src/app/constants'; import { CacheBuster, Cacheable } from 'ts-cacheable'; -import { expensesCacheBuster$ } from '../../../transaction.service'; import { ExpenseDuplicateSet, ExpenseDuplicateSetsResponse, @@ -16,6 +15,7 @@ import { SplitPayload } from 'src/app/core/models/platform/v1/split-payload.mode import { Transaction } from 'src/app/core/models/v1/transaction.model'; import { SplitExpensePolicy } from 'src/app/core/models/platform/v1/split-expense-policy.model'; import { SplitExpenseMissingFields } from 'src/app/core/models/platform/v1/split-expense-missing-fields.model'; +import { expensesCacheBuster$ } from 'src/app/core/cache-buster/expense-cache-buster'; @Injectable({ providedIn: 'root', @@ -177,4 +177,19 @@ export class ExpensesService { .post>('/expenses/attach_receipt', payload) .pipe(map((res) => res.data)); } + + attachReceiptsToExpense(expenseId: string, fileIds: string[]): Observable { + const payload = { + data: [ + { + id: expenseId, + file_ids: fileIds, + }, + ], + }; + + return this.spenderService + .post>('/expenses/attach_files/bulk', payload) + .pipe(map((res) => res.data)); + } } diff --git a/src/app/core/services/transaction.service.spec.ts b/src/app/core/services/transaction.service.spec.ts index 06308a10f4..5f03e5d278 100644 --- a/src/app/core/services/transaction.service.spec.ts +++ b/src/app/core/services/transaction.service.spec.ts @@ -35,7 +35,6 @@ import { TimezoneService } from './timezone.service'; import { TransactionService } from './transaction.service'; import { UserEventService } from './user-event.service'; import { UtilityService } from './utility.service'; -import { expensesCacheBuster$ } from './transaction.service'; import * as dayjs from 'dayjs'; import { eouRes2 } from '../mock-data/extended-org-user.data'; import { txnStats } from '../mock-data/stats-response.data'; @@ -59,6 +58,9 @@ import { unmatchCCCExpenseResponseData, } from '../mock-data/corporate-card-transaction-response.data'; import { cloneDeep } from 'lodash'; +import { expensesCacheBuster$ } from '../cache-buster/expense-cache-buster'; +import { ExpensesService } from './platform/v1/spender/expenses.service'; +import { expenseData } from '../mock-data/platform/v1/expense.data'; describe('TransactionService', () => { let transactionService: TransactionService; @@ -78,6 +80,7 @@ describe('TransactionService', () => { let paymentModesService: jasmine.SpyObj; let orgSettingsService: jasmine.SpyObj; let accountsService: jasmine.SpyObj; + let expensesService: jasmine.SpyObj; beforeEach(() => { const networkServiceSpy = jasmine.createSpyObj('NetworkService', ['isOnline']); @@ -106,6 +109,7 @@ describe('TransactionService', () => { const paymentModesServiceSpy = jasmine.createSpyObj('PaymentModesService', ['getDefaultAccount']); const orgSettingsServiceSpy = jasmine.createSpyObj('OrgSettingsService', ['get']); const accountsServiceSpy = jasmine.createSpyObj('AccountsService', ['getEMyAccounts']); + const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['attachReceiptsToExpense']); TestBed.configureTestingModule({ providers: [ @@ -174,6 +178,10 @@ describe('TransactionService', () => { provide: AccountsService, useValue: accountsServiceSpy, }, + { + provide: ExpensesService, + useValue: expensesServiceSpy, + }, { provide: PAGINATION_SIZE, useValue: 2, @@ -200,6 +208,7 @@ describe('TransactionService', () => { paymentModesService = TestBed.inject(PaymentModesService) as jasmine.SpyObj; orgSettingsService = TestBed.inject(OrgSettingsService) as jasmine.SpyObj; accountsService = TestBed.inject(AccountsService) as jasmine.SpyObj; + expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; }); it('should be created', () => { @@ -1247,13 +1256,15 @@ describe('TransactionService', () => { it('createTxnWithFiles(): should create transaction with files', (done) => { spyOn(transactionService, 'upsert').and.returnValue(of(txnData2)); - fileService.post.and.returnValue(of(fileObjectData2)); + expensesService.attachReceiptsToExpense.and.returnValue(of([expenseData])); const mockFileObject = cloneDeep(fileObjectData1); transactionService.createTxnWithFiles(txnData, of(mockFileObject)).subscribe((res) => { expect(res).toEqual(txnData2); expect(transactionService.upsert).toHaveBeenCalledOnceWith(txnData); - expect(fileService.post).toHaveBeenCalledOnceWith(fileObjectData2); + expect(expensesService.attachReceiptsToExpense).toHaveBeenCalledOnceWith(mockFileObject[0].transaction_id, [ + mockFileObject[0].id, + ]); done(); }); }); diff --git a/src/app/core/services/transaction.service.ts b/src/app/core/services/transaction.service.ts index a8ff905066..15c6aad329 100644 --- a/src/app/core/services/transaction.service.ts +++ b/src/app/core/services/transaction.service.ts @@ -1,10 +1,10 @@ import { Inject, Injectable } from '@angular/core'; import { ApiService } from './api.service'; import { DateService } from './date.service'; -import { map, switchMap, tap, concatMap, reduce } from 'rxjs/operators'; +import { map, switchMap, concatMap, reduce } from 'rxjs/operators'; import { StorageService } from './storage.service'; import { NetworkService } from './network.service'; -import { from, Observable, range, forkJoin, Subject, of } from 'rxjs'; +import { from, Observable, range, forkJoin, of } from 'rxjs'; import { ApiV2Service } from './api-v2.service'; import { DataTransformService } from './data-transform.service'; import { AuthService } from './auth.service'; @@ -41,6 +41,8 @@ import { AccountType } from '../enums/account-type.enum'; import { Expense as PlatformExpense } from '../models/platform/v1/expense.model'; import { CorporateCardTransactionRes } from '../models/platform/v1/corporate-card-transaction-res.model'; import { ExpenseFilters } from '../models/expense-filters.model'; +import { ExpensesService } from './platform/v1/spender/expenses.service'; +import { expensesCacheBuster$ } from '../cache-buster/expense-cache-buster'; enum FilterState { READY_TO_REPORT = 'READY_TO_REPORT', @@ -49,8 +51,6 @@ enum FilterState { DRAFT = 'DRAFT', } -export const expensesCacheBuster$ = new Subject(); - type PaymentMode = { name: string; key: string; @@ -77,7 +77,8 @@ export class TransactionService { private userEventService: UserEventService, private paymentModesService: PaymentModesService, private orgSettingsService: OrgSettingsService, - private accountsService: AccountsService + private accountsService: AccountsService, + private expensesService: ExpensesService ) { expensesCacheBuster$.subscribe(() => { this.userEventService.clearTaskCache(); @@ -276,23 +277,12 @@ export class TransactionService { txn: Partial, fileUploads$: Observable ): Observable> { - return fileUploads$.pipe( - switchMap((fileObjs: FileObject[]) => - this.upsert(txn).pipe( - switchMap((transaction) => - from( - fileObjs.map((fileObj) => { - fileObj.transaction_id = transaction.id; - return fileObj; - }) - ).pipe( - concatMap((fileObj) => this.fileService.post(fileObj)), - reduce((acc: FileObject[], curr: FileObject) => acc.concat([curr]), []), - map(() => transaction) - ) - ) - ) - ) + const upsertTxn$ = this.upsert(txn); + return forkJoin([fileUploads$, upsertTxn$]).pipe( + switchMap(([fileObjs, transaction]) => { + const fileIds = fileObjs.map((fileObj) => fileObj.id); + return this.expensesService.attachReceiptsToExpense(transaction.id, fileIds).pipe(map(() => transaction)); + }) ); } From 1bcbf2e1e2ae71b6416574bc4311f579c0cc366b Mon Sep 17 00:00:00 2001 From: Aastha Bist Date: Mon, 27 May 2024 10:52:27 +0530 Subject: [PATCH 033/262] feat: add report platform API to View Report pages - spender and approver [1] (#2963) --- .../core/mock-data/platform-report.data.ts | 65 +++++-- .../core/models/platform/v1/level.model.ts | 12 +- .../core/models/platform/v1/report.model.ts | 6 +- src/app/core/services/report.service.spec.ts | 4 +- src/app/core/services/report.service.ts | 16 +- .../my-view-report/my-view-report.page.html | 54 +++--- .../my-view-report.page.spec.ts | 97 +++++----- .../my-view-report/my-view-report.page.ts | 64 +++--- .../view-team-report.page.html | 47 +++-- .../view-team-report.page.spec.ts | 131 ++++++------- .../view-team-report/view-team-report.page.ts | 172 ++++++++-------- .../fy-view-report-info.component.spec.ts | 183 +++++++++++------- .../fy-view-report-info.component.ts | 93 +++++---- 13 files changed, 493 insertions(+), 451 deletions(-) diff --git a/src/app/core/mock-data/platform-report.data.ts b/src/app/core/mock-data/platform-report.data.ts index 791e9c5231..f87773bb6c 100644 --- a/src/app/core/mock-data/platform-report.data.ts +++ b/src/app/core/mock-data/platform-report.data.ts @@ -36,16 +36,19 @@ export const platformReportData: Report = deepFreeze({ state: ApprovalState.APPROVAL_DONE, }, ], - created_at: new Date('2023-07-11T06:19:28.260142+00:00'), + created_at: new Date('2023-07-11T16:24:01.335Z'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: undefined, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -108,16 +111,22 @@ export const allReportsPaginated1: PlatformApiResponse = deepFreeze({ { amount: 100, approvals: [], + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -147,7 +156,7 @@ export const allReportsPaginated1: PlatformApiResponse = deepFreeze({ last_approved_at: null, last_paid_at: null, last_resubmitted_at: null, - last_submitted_at: null, + last_submitted_at: new Date('2023-02-01T13:02:35.097839+00:00'), next_approver_user_ids: null, num_expenses: 0, org_id: 'orNVthTo2Zyo', @@ -173,13 +182,16 @@ export const allReportsPaginated1: PlatformApiResponse = deepFreeze({ created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -261,13 +273,16 @@ export const allReportsPaginatedWithApproval: PlatformApiResponse = de created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -323,13 +338,16 @@ export const allReportsPaginatedWithApproval: PlatformApiResponse = de created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -392,13 +410,16 @@ export const filteredReportsData: PlatformApiResponse = deepFreeze({ created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -461,13 +482,16 @@ export const allReportsPaginated2: PlatformApiResponse = deepFreeze({ created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -523,13 +547,16 @@ export const allReportsPaginated2: PlatformApiResponse = deepFreeze({ created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -589,13 +616,16 @@ export const submittedReportData: Report = deepFreeze({ created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -671,13 +701,16 @@ export const submittedReportDataWithApproval: Report = deepFreeze({ created_at: new Date('2023-07-11T06:19:28.260142+00:00'), currency: 'USD', employee: { + org_name: 'Staging Loaded', + level: null, + mobile: '123456098', ach_account: { added: true, verified: null, }, business_unit: 'A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed A very long Business Unit indeed', - code: null, + code: '101', department: { code: null, display_name: '0000000 / arun', @@ -738,6 +771,14 @@ export const expectedSingleReport: Report[] = deepFreeze([allReportsPaginated1.d export const expectedReportsSinglePage: Report[] = deepFreeze([...allReportsPaginated1.data]); +export const sentBackReportData: Report = deepFreeze({ ...submittedReportData, state: 'APPROVER_INQUIRY' }); + +export const reportWithExpenses: Report = deepFreeze({ + ...platformReportData, + num_expenses: 3, + amount: 100, +}); + export const expectedReportsSinglePageWithApproval: Report[] = deepFreeze([...allReportsPaginatedWithApproval.data]); export const expectedReportsSinglePageFiltered: Report[] = deepFreeze([...filteredReportsData.data]); diff --git a/src/app/core/models/platform/v1/level.model.ts b/src/app/core/models/platform/v1/level.model.ts index 8507596fb0..39bd5b5d2f 100644 --- a/src/app/core/models/platform/v1/level.model.ts +++ b/src/app/core/models/platform/v1/level.model.ts @@ -1,11 +1,11 @@ export interface Level { id: string; - org_id: string; - created_at: Date; - updated_at: Date; + org_id?: string; + created_at?: Date; + updated_at?: Date; name: string; band: string; - code: string; - description: string; - is_enabled: boolean; + code?: string; + description?: string; + is_enabled?: boolean; } diff --git a/src/app/core/models/platform/v1/report.model.ts b/src/app/core/models/platform/v1/report.model.ts index a451898605..08e5b1bc6d 100644 --- a/src/app/core/models/platform/v1/report.model.ts +++ b/src/app/core/models/platform/v1/report.model.ts @@ -1,9 +1,10 @@ import { ReportApprovals } from '../report-approvals.model'; +import { Level } from './level.model'; export interface Report { id: string; org_id: string; - created_at: Date; + created_at: Date | string; updated_at: Date; user_id: string; user: { @@ -42,7 +43,10 @@ export interface Report { }; code: string; org_id: string; + org_name: string; department_id: string; + level: Level; + mobile: string; department: { id: string; code: string; diff --git a/src/app/core/services/report.service.spec.ts b/src/app/core/services/report.service.spec.ts index fbdb4e3967..e8813d49bb 100644 --- a/src/app/core/services/report.service.spec.ts +++ b/src/app/core/services/report.service.spec.ts @@ -655,7 +655,7 @@ describe('ReportService', () => { it('updateReportPurpose(): should update the report purpose', (done) => { spenderPlatformV1ApiService.post.and.returnValue(of(platformReportData)); - reportService.updateReportPurpose(reportData1).subscribe((res) => { + reportService.updateReportPurpose(platformReportData).subscribe((res) => { expect(res).toEqual(platformReportData); expect(spenderPlatformV1ApiService.post).toHaveBeenCalledOnceWith('/reports', { data: { @@ -670,7 +670,7 @@ describe('ReportService', () => { it('approverUpdateReportPurpose(): should update the report purpose for approver', (done) => { approverPlatformApiService.post.and.returnValue(of(platformReportData)); - reportService.approverUpdateReportPurpose(reportData1).subscribe((res) => { + reportService.approverUpdateReportPurpose(platformReportData).subscribe((res) => { expect(res).toEqual(platformReportData); expect(approverPlatformApiService.post).toHaveBeenCalledOnceWith('/reports', { data: { diff --git a/src/app/core/services/report.service.ts b/src/app/core/services/report.service.ts index cd295af2d8..037b2c8840 100644 --- a/src/app/core/services/report.service.ts +++ b/src/app/core/services/report.service.ts @@ -210,12 +210,12 @@ export class ReportService { @CacheBuster({ cacheBusterNotifier: reportsCacheBuster$, }) - updateReportPurpose(erpt: ExtendedReport): Observable { + updateReportPurpose(report: Report): Observable { const params = { data: { - id: erpt.rp_id, - source: erpt.rp_source, - purpose: erpt.rp_purpose, + id: report.id, + source: report.source, + purpose: report.purpose, }, }; return this.spenderPlatformV1ApiService.post('/reports', params); @@ -579,12 +579,12 @@ export class ReportService { ); } - approverUpdateReportPurpose(erpt: ExtendedReport): Observable { + approverUpdateReportPurpose(report: Report): Observable { const params: { data: Pick } = { data: { - id: erpt.rp_id, - source: erpt.rp_source, - purpose: erpt.rp_purpose, + id: report.id, + source: report.source, + purpose: report.purpose, }, }; return this.approverPlatformApiService.post('/reports', params); diff --git a/src/app/fyle/my-view-report/my-view-report.page.html b/src/app/fyle/my-view-report/my-view-report.page.html index abea74ae2e..076261f63f 100644 --- a/src/app/fyle/my-view-report/my-view-report.page.html +++ b/src/app/fyle/my-view-report/my-view-report.page.html @@ -8,8 +8,8 @@ View Report - - + + @@ -25,7 +25,7 @@ -
+
@@ -33,9 +33,9 @@
- {{erpt.rp_purpose | ellipsis: 35}} + {{report.purpose | ellipsis: 35}} {{ reportCurrencySymbol }} - {{ erpt.rp_amount || 0 | humanizeCurrency: erpt.rp_currency:true }} + {{ report.amount || 0 | humanizeCurrency: report.currency:true }}
@@ -58,8 +58,8 @@ -
- {{erpt.rp_state | reportState : simplifyReportsSettings.enabled | snakeCaseToSpaceCase}} +
+ {{report.state | reportState : simplifyReportsSettings.enabled | snakeCaseToSpaceCase}}
@@ -71,13 +71,13 @@ @@ -94,24 +94,18 @@
-
-
+
+
Approvers
-
+
-
{{ ap.approver_name }}
+
{{ approver.approver_user.full_name }}
-
{{ ap.state === 'APPROVAL_DONE' ? 'Approved' : (ap.state | titlecase | + >{{ approver.state === 'APPROVAL_DONE' ? 'Approved' : (approver.state | titlecase | snakeCaseToSpaceCase)}}
@@ -153,7 +147,7 @@ -
+
@@ -173,7 +167,7 @@
-
+
No expense in this report
Looks like this report is empty.
@@ -308,7 +302,7 @@
@@ -331,7 +325,7 @@ Submit Report @@ -342,7 +336,7 @@