Skip to content

Commit

Permalink
fix: data extraction not working in case of bulk upload (#3010)
Browse files Browse the repository at this point in the history
* fix: data extraction not working in case of bulk upload

* written test

* test: fixed eslint issues (#3025)
  • Loading branch information
suyashpatil78 authored Jun 1, 2024
1 parent 655c990 commit 2e009f4
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 39 deletions.
11 changes: 11 additions & 0 deletions src/app/core/mock-data/parsed-response.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import deepFreeze from 'deep-freeze-strict';
import { ParsedResponse } from '../models/parsed_response.model';

export const parsedResponseData1: ParsedResponse = deepFreeze({
category: 'SYSTEM',
currency: 'USD',
amount: 100,
date: new Date('2023-02-15T06:30:00.000Z'),
invoice_dt: new Date('2023-02-24T12:03:57.680Z'),
vendor_name: 'vendor',
});
7 changes: 7 additions & 0 deletions src/app/core/models/receipt-preview-data.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ReceiptPreviewData {
base64ImagesWithSource: Partial<{
source: string;
base64Image: string;
}>[];
continueCaptureReceipt?: boolean;
}
29 changes: 28 additions & 1 deletion src/app/core/services/transactions-outbox.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import { TransactionsOutboxService } from './transactions-outbox.service';
import { outboxQueueData1 } from '../mock-data/outbox-queue.data';
import { cloneDeep } from 'lodash';
import { of } from 'rxjs';
import { parsedReceiptData1, parsedReceiptData2 } from '../mock-data/parsed-receipt.data';
import { extractedData, parsedReceiptData1, parsedReceiptData2 } from '../mock-data/parsed-receipt.data';
import { fileData1 } from '../mock-data/file.data';
import { SpenderReportsService } from './platform/v1/spender/reports.service';
import { parsedResponseData1 } from '../mock-data/parsed-response.data';

describe('TransactionsOutboxService', () => {
const rootUrl = 'https://staging.fyle.tech';
Expand Down Expand Up @@ -353,4 +354,30 @@ describe('TransactionsOutboxService', () => {
expect(transactionService.upsert).toHaveBeenCalledTimes(2);
expect(transactionsOutboxService.removeDataExtractionEntry).toHaveBeenCalledTimes(3);
}));

describe('getExpenseDate():', () => {
it('should return transaction date if txn_dt is present', () => {
const txnDate = new Date('2023-02-15T06:30:00.000Z');
const mockQueue = cloneDeep(outboxQueueData1[0]);
mockQueue.transaction.txn_dt = txnDate;
const res = transactionsOutboxService.getExpenseDate(mockQueue, parsedResponseData1);
expect(res).toEqual(txnDate);
});

it('should return extracted date if txn_dt is not present', () => {
const mockQueue = cloneDeep(outboxQueueData1[0]);
mockQueue.transaction.txn_dt = null;
const res = transactionsOutboxService.getExpenseDate(mockQueue, parsedResponseData1);
expect(res).toEqual(parsedResponseData1.date);
});

it('should return today date if txn_dt and extracted date is not present', () => {
const mockQueue = cloneDeep(outboxQueueData1[0]);
mockQueue.transaction.txn_dt = null;
const mockParsedResponse = cloneDeep(parsedResponseData1);
mockParsedResponse.date = null;
const res = transactionsOutboxService.getExpenseDate(mockQueue, mockParsedResponse);
expect(res).toEqual(new Date());
});
});
});
13 changes: 12 additions & 1 deletion src/app/core/services/transactions-outbox.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { FileObject } from '../models/file-obj.model';
import { OutboxQueue } from '../models/outbox-queue.model';
import { ExpensesService } from './platform/v1/spender/expenses.service';
import { SpenderReportsService } from './platform/v1/spender/reports.service';
import { ParsedResponse } from '../models/parsed_response.model';
import { SpenderFileService } from './platform/v1/spender/file.service';
import { PlatformFile } from '../models/platform/platform-file.model';

Expand Down Expand Up @@ -131,6 +132,16 @@ export class TransactionsOutboxService {
await this.saveDataExtractionQueue();
}

getExpenseDate(entry: OutboxQueue, extractedData: ParsedResponse): Date {
if (entry.transaction.txn_dt) {
return new Date(entry.transaction.txn_dt);
} else if (extractedData.date) {
return new Date(extractedData.date);
} else {
return new Date();
}
}

async processDataExtractionEntry(): Promise<void> {
const that = this;
const clonedQueue = cloneDeep(this.dataExtractionQueue);
Expand Down Expand Up @@ -159,7 +170,7 @@ export class TransactionsOutboxService {
};

entry.transaction.extracted_data = extractedData;
entry.transaction.txn_dt = new Date();
entry.transaction.txn_dt = this.getExpenseDate(entry, parsedResponse);

// TODO: add this to allow amout addtion to extracted expense
// let transactionUpsertPromise;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-proper
import { AuthService } from 'src/app/core/services/auth.service';
import { CameraService } from 'src/app/core/services/camera.service';
import { CameraPreviewService } from 'src/app/core/services/camera-preview.service';
import { ReceiptPreviewData } from 'src/app/core/models/receipt-preview-data.model';

type Image = Partial<{
source: string;
Expand Down Expand Up @@ -79,7 +80,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
@Inject(DEVICE_PLATFORM) private devicePlatform: 'android' | 'ios' | 'web'
) {}

setupNetworkWatcher() {
setupNetworkWatcher(): void {
const networkWatcherEmitter = new EventEmitter<boolean>();
this.networkService.connectivityWatcher(networkWatcherEmitter);
this.isOffline$ = concat(this.networkService.isOnline(), networkWatcherEmitter.asObservable()).pipe(
Expand All @@ -88,21 +89,23 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
);
}

ngOnInit() {
ngOnInit(): void {
this.setupNetworkWatcher();
this.isBulkMode = false;
this.base64ImagesWithSource = [];
this.noOfReceipts = 0;
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
addMultipleExpensesToQueue(base64ImagesWithSource: Image[]) {
return from(base64ImagesWithSource).pipe(
concatMap((res: Image) => this.addExpenseToQueue(res)),
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
reduce((acc, curr) => acc.concat(curr), [])
);
}

addExpenseToQueue(base64ImagesWithSource: Image) {
addExpenseToQueue(base64ImagesWithSource: Image): Observable<void> {
let source = base64ImagesWithSource.source;

return forkJoin({
Expand All @@ -115,7 +118,6 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
}
const transaction = {
source,
txn_dt: new Date(),
currency: eou?.org?.currency,
};

Expand All @@ -131,21 +133,21 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
);
}

onDismissCameraPreview() {
onDismissCameraPreview(): void {
if (this.isModal) {
this.modalController.dismiss();
} else {
this.navController.back();
}
}

onToggleFlashMode(flashMode: 'on' | 'off') {
onToggleFlashMode(flashMode: 'on' | 'off'): void {
this.trackingService.flashModeSet({
FlashMode: flashMode,
});
}

showBulkModeToastMessage() {
showBulkModeToastMessage(): void {
const message =
'If you have multiple receipts to upload, please use <b>BULK MODE</b> to upload all the receipts at once.';
this.bulkModeToastMessageRef = this.matSnackBar.openFromComponent(ToastMessageComponent, {
Expand All @@ -157,7 +159,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
this.trackingService.showToastMessage({ ToastContent: message });
}

onSwitchMode() {
onSwitchMode(): void {
this.isBulkMode = !this.isBulkMode;

if (this.isBulkMode) {
Expand All @@ -167,7 +169,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
}
}

onSingleCaptureOffline() {
onSingleCaptureOffline(): void {
this.loaderService.showLoader();
this.addMultipleExpensesToQueue(this.base64ImagesWithSource)
.pipe(finalize(() => this.loaderService.hideLoader()))
Expand All @@ -176,7 +178,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});
}

navigateToExpenseForm() {
navigateToExpenseForm(): void {
const isInstafyleEnabled$ = this.orgUserSettingsService
.get()
.pipe(
Expand All @@ -199,7 +201,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});
}

saveSingleCapture() {
saveSingleCapture(): void {
this.isOffline$.pipe(take(1)).subscribe((isOffline) => {
if (isOffline) {
this.onSingleCaptureOffline();
Expand All @@ -210,13 +212,13 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
this.transactionsOutboxService.incrementSingleCaptureCount();
}

onSingleCapture() {
onSingleCapture(): void {
const receiptPreviewModal = this.createReceiptPreviewModal('single');

const receiptPreviewDetails$ = from(receiptPreviewModal).pipe(
shareReplay(1),
tap((receiptPreviewModal) => receiptPreviewModal.present()),
switchMap((receiptPreviewModal) => receiptPreviewModal.onWillDismiss()),
switchMap((receiptPreviewModal) => receiptPreviewModal.onWillDismiss<ReceiptPreviewData>()),
map((receiptPreviewData) => receiptPreviewData?.data),
filter((receiptPreviewDetails) => !!receiptPreviewDetails)
);
Expand Down Expand Up @@ -258,7 +260,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
saveReceipt$.pipe(filter((isModal) => !isModal)).subscribe(() => this.saveSingleCapture());
}

addPerformanceTrackers() {
addPerformanceTrackers(): void {
this.orgService.getOrgs().subscribe((orgs) => {
const isMultiOrg = orgs.length > 1;

Expand All @@ -275,7 +277,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
const measureLaunchTime = performance.getEntriesByName(PerfTrackers.appLaunchTime);

// eslint-disable-next-line @typescript-eslint/dot-notation
const isLoggedIn = performance.getEntriesByName(PerfTrackers.appLaunchStartTime)[0]['detail'];
const isLoggedIn = performance.getEntriesByName(PerfTrackers.appLaunchStartTime)[0]['detail'] as boolean;

// Converting the duration to seconds and fix it to 3 decimal places
const launchTimeDuration = (measureLaunchTime[0]?.duration / 1000)?.toFixed(3);
Expand All @@ -289,7 +291,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});
}

openReceiptPreviewModal() {
openReceiptPreviewModal(): void {
const receiptPreviewDetails$ = this.showReceiptPreview().pipe(filter((data) => !!data));

receiptPreviewDetails$
Expand All @@ -313,7 +315,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
.pipe(
filter(
(receiptPreviewDetails) =>
!receiptPreviewDetails.continueCaptureReceipt && receiptPreviewDetails.base64ImagesWithSource.length
!receiptPreviewDetails.continueCaptureReceipt && !!receiptPreviewDetails.base64ImagesWithSource.length
),
switchMap(() => {
this.loaderService.showLoader('Please wait...', 10000);
Expand All @@ -326,7 +328,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});
}

createReceiptPreviewModal(mode: 'single' | 'bulk') {
createReceiptPreviewModal(mode: 'single' | 'bulk'): Promise<HTMLIonModalElement> {
return this.modalController.create({
component: ReceiptPreviewComponent,
componentProps: {
Expand All @@ -336,19 +338,19 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});
}

showReceiptPreview() {
showReceiptPreview(): Observable<ReceiptPreviewData> {
return from(this.createReceiptPreviewModal('bulk')).pipe(
tap((receiptPreviewModal) => receiptPreviewModal.present()),
switchMap((receiptPreviewModal) => receiptPreviewModal.onWillDismiss()),
switchMap((receiptPreviewModal) => receiptPreviewModal.onWillDismiss<ReceiptPreviewData>()),
map((receiptPreviewDetails) => receiptPreviewDetails?.data)
);
}

onBulkCapture() {
onBulkCapture(): void {
this.noOfReceipts += 1;
}

showLimitReachedPopover() {
showLimitReachedPopover(): Observable<HTMLIonPopoverElement> {
const limitReachedPopover = this.popoverController.create({
component: PopupAlertComponent,
componentProps: {
Expand All @@ -365,7 +367,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
return from(limitReachedPopover).pipe(tap((limitReachedPopover) => limitReachedPopover.present()));
}

onCaptureReceipt() {
onCaptureReceipt(): void {
if (this.noOfReceipts >= 20) {
this.trackingService.receiptLimitReached();
this.showLimitReachedPopover().subscribe(noop);
Expand Down Expand Up @@ -395,7 +397,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
}
}

setupPermissionDeniedPopover(permissionType: 'CAMERA' | 'GALLERY') {
setupPermissionDeniedPopover(permissionType: 'CAMERA' | 'GALLERY'): Promise<HTMLIonPopoverElement> {
const isIos = this.devicePlatform === 'ios';

const galleryPermissionName = isIos ? 'Photos' : 'Storage';
Expand Down Expand Up @@ -428,11 +430,11 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});
}

showPermissionDeniedPopover(permissionType: 'CAMERA' | 'GALLERY') {
showPermissionDeniedPopover(permissionType: 'CAMERA' | 'GALLERY'): void {
from(this.setupPermissionDeniedPopover(permissionType))
.pipe(
tap((permissionDeniedPopover) => permissionDeniedPopover.present()),
switchMap((permissionDeniedPopover) => permissionDeniedPopover.onWillDismiss())
switchMap((permissionDeniedPopover) => permissionDeniedPopover.onWillDismiss<{ action: string }>())
)
.subscribe(({ data }) => {
if (data?.action === 'OPEN_SETTINGS') {
Expand All @@ -445,7 +447,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});
}

onGalleryUpload() {
onGalleryUpload(): void {
this.trackingService.instafyleGalleryUploadOpened({});

const checkPermission$ = from(this.imagePicker.hasReadPermission()).pipe(shareReplay(1));
Expand Down Expand Up @@ -476,7 +478,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});

receiptsFromGallery$
.pipe(filter((receiptsFromGallery) => receiptsFromGallery.length > 0))
.pipe(filter((receiptsFromGallery: string[]) => receiptsFromGallery.length > 0))
.subscribe((receiptsFromGallery) => {
receiptsFromGallery.forEach((receiptBase64) => {
const receiptBase64Data = 'data:image/jpeg;base64,' + receiptBase64;
Expand All @@ -489,32 +491,32 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
});

receiptsFromGallery$
.pipe(filter((receiptsFromGallery) => !receiptsFromGallery.length))
.pipe(filter((receiptsFromGallery: string[]) => !receiptsFromGallery.length))
.subscribe(() => this.setUpAndStartCamera());
}

ngAfterViewInit() {
ngAfterViewInit(): void {
if (this.isModal) {
this.setUpAndStartCamera();
}
}

ngOnDestroy() {
ngOnDestroy(): void {
if (this.isModal) {
this.stopCamera();
}
this.bulkModeToastMessageRef?.dismiss?.();
}

setUpAndStartCamera() {
setUpAndStartCamera(): void {
this.cameraPreview.setUpAndStartCamera();
if (this.transactionsOutboxService.singleCaptureCount === 3) {
this.showBulkModeToastMessage();
this.isBulkModePromptShown = true;
}
}

stopCamera() {
stopCamera(): void {
this.cameraPreview.stopCamera();
}
}
Loading

0 comments on commit 2e009f4

Please sign in to comment.