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,