Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@awesome-cordova-plugins/smartlook": "^6.14.0",
"@awesome-cordova-plugins/spinner-dialog": "^6.2.0",
"@capacitor-community/camera-preview": "^7.0.0",
"@capacitor-mlkit/document-scanner": "^7.3.0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Mind it! These new packages need my blessing for security!

The new dependencies look stylish, but let me verify they're the latest and safest versions for my fans to use.

Also applies to: 54-54


🌐 Web query:

What are the latest versions of @capacitor-mlkit/document-scanner and @capacitor/filesystem packages on npm?

💡 Result:

As of August 7, 2025, the latest versions of the specified npm packages are:

  • @capacitor-mlkit/document-scanner: The most recent version is 7.2.1, released on April 7, 2025. (classic.yarnpkg.com)

  • @capacitor/filesystem: The latest stable version is 7.0.1, published 18 days ago. (npmjs.com)

Please note that @capacitor/filesystem has a nightly build version 7.0.2-nightly-20250527T150614.0, released 19 days ago. (npmjs.com)

For the most current information, you can visit the npm package pages:

Citations:


I will verify the version of @capacitor/filesystem in package.json now, mighty developer!


🏁 Script executed:

#!/bin/bash
rg "@capacitor/filesystem" -n package.json

Length of output: 84


Double-check published versions of new dependencies, Superstar!

I see two package versions that leap beyond the latest npm releases:

  • package.json:46 – “@capacitor-mlkit/document-scanner”: “^7.3.0”
    • Latest published is 7.2.1 (yarnpkg.com)
  • package.json:54 – “@capacitor/filesystem”: “^7.1.4”
    • Latest stable is 7.0.1 (npmjs.com)

Please confirm these versions actually exist on npm or adjust them down to the current stable releases. Rajini says: get this right at once!

🤖 Prompt for AI Agents
In package.json at line 46, the version "^7.3.0" for
"@capacitor-mlkit/document-scanner" is higher than the latest published version
7.2.1 on npm. Verify the actual published version on npm and if "^7.3.0" does
not exist, change it to "^7.2.1" or the latest stable version available.
Similarly, check the version for "@capacitor/filesystem" at line 54 and adjust
it to "^7.0.1" if "^7.1.4" is not published. Ensure all dependency versions
match the latest stable releases on npm before updating package.json.

"@capacitor/android": "^7.0.0",
"@capacitor/app": "^7.0.0",
"@capacitor/browser": "^7.0.0",
Expand Down
46 changes: 46 additions & 0 deletions src/app/core/services/mlkit-document-scanner.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Injectable } from '@angular/core';
import { DocumentScanner, ScanResult } from '@capacitor-mlkit/document-scanner';
import { from, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';

@Injectable({
providedIn: 'root',
})
export class MLKitDocumentScannerService {
scanDocument(isBulkMode = false): Observable<string> {
return from(
DocumentScanner.scanDocument({
resultFormats: 'JPEG',
galleryImportAllowed: false, // Disable gallery import to prevent additional UI
pageLimit: isBulkMode ? 20 : 1, // Allow up to 20 pages in bulk mode, 1 in single mode
scannerMode: 'BASE', // Use basic mode to minimize editing UI
}),
).pipe(
switchMap((result: ScanResult) => {
if (!result || !result.scannedImages || result.scannedImages.length === 0) {
throw new Error('No document scanned or scan was cancelled');
}
// In bulk mode, we still return one image at a time to maintain compatibility
return from(this.convertFileToBase64(result.scannedImages[0]));
}),
);
}

private async convertFileToBase64(fileUrl: string): Promise<string> {
try {
const response = await fetch(fileUrl);
const blob = await response.blob();
return new Promise<string>((resolve, reject): void => {
const reader = new FileReader();
reader.onloadend = (): void => {
const base64data = reader.result as string;
resolve(base64data);
};
reader.onerror = (): void => reject(new Error('Failed to convert file to base64'));
reader.readAsDataURL(blob);
});
} catch {
throw new Error('Failed to convert file to base64');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Mind it! The error details are vanishing like a villain in my movies!

The generic catch block swallows the original error information, making debugging difficult. Preserve the error details for better troubleshooting.

-    } catch {
-      throw new Error('Failed to convert file to base64');
+    } catch (error) {
+      throw new Error(`Failed to convert file to base64: ${error.message || 'Unknown error'}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch {
throw new Error('Failed to convert file to base64');
}
} catch (error) {
throw new Error(`Failed to convert file to base64: ${error.message || 'Unknown error'}`);
}
🤖 Prompt for AI Agents
In src/app/core/services/mlkit-document-scanner.service.ts around lines 35 to
37, the catch block throws a new error without preserving the original error
details, which hinders debugging. Modify the catch block to capture the caught
error object and include its message or stack trace when throwing the new error,
so the original error information is preserved and visible for troubleshooting.

}
}
21 changes: 13 additions & 8 deletions src/app/fyle/dashboard/dashboard.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -704,14 +704,13 @@ export class DashboardPage {
this.trackingService.dashboardActionSheetButtonClicked({
Action: action,
});
this.router.navigate([
'/',
'enterprise',
route,
{
navigate_back: true,
},
]);
const autoCaptureExpenseParams: Params = {
navigate_back: true,
};
if (action === 'Auto capture expenses') {
autoCaptureExpenseParams.auto_capture_expenses = true;
}
this.router.navigate(['/', 'enterprise', route, autoCaptureExpenseParams]);
};
}

Expand All @@ -732,6 +731,12 @@ export class DashboardPage {
cssClass: 'capture-receipt',
handler: this.actionSheetButtonsHandler('Add manually', 'add_edit_expense'),
},
{
text: 'Auto capture expenses',
icon: 'assets/svg/camera.svg',
cssClass: 'capture-receipt',
handler: this.actionSheetButtonsHandler('Auto capture expenses', 'camera_overlay'),
},
Comment on lines +734 to +739
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Style matters, even in code! Same icon for different actions is like wearing the same costume in every scene!

Both "Capture receipt" and "Auto capture expenses" buttons use the same icon 'assets/svg/camera.svg'. Users might get confused between the two actions.

Consider using a different icon for the auto capture feature, perhaps one that indicates automation or bulk scanning.

🤖 Prompt for AI Agents
In src/app/fyle/dashboard/dashboard.page.ts around lines 734 to 739, the "Auto
capture expenses" button uses the same camera.svg icon as the "Capture receipt"
button, which can confuse users. Change the icon for the "Auto capture expenses"
button to a different SVG that visually represents automation or bulk scanning
to clearly differentiate the two actions.

];

if (mileageEnabled) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Component, EventEmitter, OnDestroy, OnInit, Input, AfterViewInit, ViewChild, Inject } from '@angular/core';
import { Component, EventEmitter, OnDestroy, OnInit, Input, AfterViewInit, ViewChild } from '@angular/core';
import { inject } from '@angular/core';
import { CameraPreviewPictureOptions } from '@capacitor-community/camera-preview';
import { ModalController, NavController, PopoverController } from '@ionic/angular';
import { ReceiptPreviewComponent } from './receipt-preview/receipt-preview.component';
import { TrackingService } from 'src/app/core/services/tracking.service';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
import { TransactionsOutboxService } from 'src/app/core/services/transactions-outbox.service';
import { ImagePicker } from '@awesome-cordova-plugins/image-picker/ngx';
import { concat, forkJoin, from, noop, Observable } from 'rxjs';
Expand All @@ -23,6 +24,7 @@
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 { MLKitDocumentScannerService } from 'src/app/core/services/mlkit-document-scanner.service';
import { ReceiptPreviewData } from 'src/app/core/models/receipt-preview-data.model';
import { TranslocoService } from '@jsverse/transloco';

Expand Down Expand Up @@ -65,26 +67,45 @@

nativeSettings = NativeSettings;

constructor(
private modalController: ModalController,
private trackingService: TrackingService,
private router: Router,
private navController: NavController,
private transactionsOutboxService: TransactionsOutboxService,
private imagePicker: ImagePicker,
private networkService: NetworkService,
private popoverController: PopoverController,
private loaderService: LoaderService,
private orgService: OrgService,
private platformEmployeeSettingsService: PlatformEmployeeSettingsService,
private matSnackBar: MatSnackBar,
private snackbarProperties: SnackbarPropertiesService,
private authService: AuthService,
private cameraService: CameraService,
private cameraPreviewService: CameraPreviewService,
@Inject(DEVICE_PLATFORM) private devicePlatform: 'android' | 'ios' | 'web',
private translocoService: TranslocoService,
) {}
private readonly modalController = inject(ModalController);

private readonly trackingService = inject(TrackingService);

private readonly router = inject(Router);

private readonly route = inject(ActivatedRoute);

private readonly navController = inject(NavController);

private readonly transactionsOutboxService = inject(TransactionsOutboxService);

private readonly imagePicker = inject(ImagePicker);

private readonly networkService = inject(NetworkService);

private readonly popoverController = inject(PopoverController);

private readonly loaderService = inject(LoaderService);

private readonly orgService = inject(OrgService);

private readonly platformEmployeeSettingsService = inject(PlatformEmployeeSettingsService);

private readonly matSnackBar = inject(MatSnackBar);

private readonly snackbarProperties = inject(SnackbarPropertiesService);

private readonly authService = inject(AuthService);

private readonly cameraService = inject(CameraService);

private readonly cameraPreviewService = inject(CameraPreviewService);

private readonly mlkitScanner = inject(MLKitDocumentScannerService);

private readonly devicePlatform = inject<'android' | 'ios' | 'web'>(DEVICE_PLATFORM);

private readonly translocoService = inject(TranslocoService);

setupNetworkWatcher(): void {
const networkWatcherEmitter = new EventEmitter<boolean>();
Expand Down Expand Up @@ -515,6 +536,13 @@
}

setUpAndStartCamera(): void {
// Check if the URL contains auto_capture_expenses=true
const autoCaptureParam = this.route.snapshot.params?.['auto_capture_expenses'] as string | undefined;
if (autoCaptureParam === 'true') {
Comment on lines +539 to +541
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Dei! The parameter reading is like looking for keys in the wrong pocket!

You're reading auto_capture_expenses from route params using this.route.snapshot.params, but the dashboard sends it as a query parameter. This will always be undefined.

-    const autoCaptureParam = this.route.snapshot.params?.['auto_capture_expenses'] as string | undefined;
+    const autoCaptureParam = this.route.snapshot.queryParams?.['auto_capture_expenses'] as string | undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Check if the URL contains auto_capture_expenses=true
const autoCaptureParam = this.route.snapshot.params?.['auto_capture_expenses'] as string | undefined;
if (autoCaptureParam === 'true') {
// Check if the URL contains auto_capture_expenses=true
const autoCaptureParam = this.route.snapshot.queryParams?.['auto_capture_expenses'] as string | undefined;
if (autoCaptureParam === 'true') {
🤖 Prompt for AI Agents
In src/app/shared/components/capture-receipt/capture-receipt.component.ts around
lines 539 to 541, the code incorrectly reads the 'auto_capture_expenses' value
from route params instead of query parameters. Change the code to read
'auto_capture_expenses' from this.route.snapshot.queryParams instead of
this.route.snapshot.params to correctly capture the query parameter sent by the
dashboard.

this.startDirectDocumentScan();
return;
}

this.cameraPreview.setUpAndStartCamera();
if (this.transactionsOutboxService.singleCaptureCount === 3) {
this.showBulkModeToastMessage();
Expand All @@ -525,4 +553,77 @@
stopCamera(): void {
this.cameraPreview.stopCamera();
}

// New direct document scanning functions
startDirectDocumentScan(): void {
this.mlkitScanner.scanDocument(false).subscribe({
next: (base64PictureData) => {
// Navigate directly to expense form with the scanned image
const isInstafyleEnabled$ = this.platformEmployeeSettingsService
.get()
.pipe(
map(
(employeeSettings) =>
employeeSettings.insta_fyle_settings.allowed && employeeSettings.insta_fyle_settings.enabled,
),
);

isInstafyleEnabled$.subscribe((isInstafyleEnabled) => {
this.router.navigate([
'/',
'enterprise',
'add_edit_expense',
{
dataUrl: base64PictureData,
canExtractData: isInstafyleEnabled,
},
]);
});
},
error: (error: Error) => {
// Only show error if it's not a user cancellation
if (error?.message !== 'No document scanned or scan was cancelled') {
console.log('error', error);

Check failure on line 586 in src/app/shared/components/capture-receipt/capture-receipt.component.ts

View workflow job for this annotation

GitHub Actions / Run linters

Unexpected console statement. Only these console methods are allowed: .
this.matSnackBar.openFromComponent(ToastMessageComponent, {
...this.snackbarProperties.setSnackbarProperties('failure', {
message: 'Document scanning failed. Please try again.' + error.message,
}),
});
}
},
});
}
Comment on lines +558 to +595
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Style matters! Code duplication is like repeating my punch dialogues - powerful once, boring twice!

The navigation logic (lines 562-581) duplicates the navigateToExpenseForm method. Also, there's a console statement that violates linting rules.

Refactor to reuse existing method and fix the console statement:

  startDirectDocumentScan(): void {
    this.mlkitScanner.scanDocument(false).subscribe({
      next: (base64PictureData) => {
-        // Navigate directly to expense form with the scanned image
-        const isInstafyleEnabled$ = this.platformEmployeeSettingsService
-          .get()
-          .pipe(
-            map(
-              (employeeSettings) =>
-                employeeSettings.insta_fyle_settings.allowed && employeeSettings.insta_fyle_settings.enabled,
-            ),
-          );
-
-        isInstafyleEnabled$.subscribe((isInstafyleEnabled) => {
-          this.router.navigate([
-            '/',
-            'enterprise',
-            'add_edit_expense',
-            {
-              dataUrl: base64PictureData,
-              canExtractData: isInstafyleEnabled,
-            },
-          ]);
-        });
+        this.base64ImagesWithSource = [{
+          source: 'MOBILE_DASHCAM_SINGLE',
+          base64Image: base64PictureData,
+        }];
+        this.navigateToExpenseForm();
      },
      error: (error: Error) => {
        // Only show error if it's not a user cancellation
        if (error?.message !== 'No document scanned or scan was cancelled') {
-          console.log('error', error);
          this.matSnackBar.openFromComponent(ToastMessageComponent, {
            ...this.snackbarProperties.setSnackbarProperties('failure', {
-              message: 'Document scanning failed. Please try again.' + error.message,
+              message: this.translocoService.translate('captureReceipt.documentScanFailed'),
            }),
          });
        }
      },
    });
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
startDirectDocumentScan(): void {
this.mlkitScanner.scanDocument(false).subscribe({
next: (base64PictureData) => {
// Navigate directly to expense form with the scanned image
const isInstafyleEnabled$ = this.platformEmployeeSettingsService
.get()
.pipe(
map(
(employeeSettings) =>
employeeSettings.insta_fyle_settings.allowed && employeeSettings.insta_fyle_settings.enabled,
),
);
isInstafyleEnabled$.subscribe((isInstafyleEnabled) => {
this.router.navigate([
'/',
'enterprise',
'add_edit_expense',
{
dataUrl: base64PictureData,
canExtractData: isInstafyleEnabled,
},
]);
});
},
error: (error: Error) => {
// Only show error if it's not a user cancellation
if (error?.message !== 'No document scanned or scan was cancelled') {
console.log('error', error);
this.matSnackBar.openFromComponent(ToastMessageComponent, {
...this.snackbarProperties.setSnackbarProperties('failure', {
message: 'Document scanning failed. Please try again.' + error.message,
}),
});
}
},
});
}
startDirectDocumentScan(): void {
this.mlkitScanner.scanDocument(false).subscribe({
next: (base64PictureData) => {
this.base64ImagesWithSource = [{
source: 'MOBILE_DASHCAM_SINGLE',
base64Image: base64PictureData,
}];
this.navigateToExpenseForm();
},
error: (error: Error) => {
// Only show error if it's not a user cancellation
if (error?.message !== 'No document scanned or scan was cancelled') {
this.matSnackBar.openFromComponent(ToastMessageComponent, {
...this.snackbarProperties.setSnackbarProperties('failure', {
message: this.translocoService.translate('captureReceipt.documentScanFailed'),
}),
});
}
},
});
}
🧰 Tools
🪛 GitHub Check: Run linters

[failure] 586-586:
Unexpected console statement. Only these console methods are allowed: .

🪛 GitHub Actions: Lint

[error] 586-586: ESLint: Unexpected console statement. Only these console methods are allowed. (no-console)

🤖 Prompt for AI Agents
In src/app/shared/components/capture-receipt/capture-receipt.component.ts
between lines 558 and 595, the navigation logic inside startDirectDocumentScan
duplicates the existing navigateToExpenseForm method and includes a console.log
statement which violates linting rules. Refactor by replacing the duplicated
navigation code with a call to navigateToExpenseForm, passing the necessary
parameters, and remove or replace the console.log with proper error handling or
logging consistent with the project's standards.


startDirectBulkDocumentScan(): void {
if (this.noOfReceipts >= 20) {
this.trackingService.receiptLimitReached();
this.showLimitReachedPopover().subscribe(noop);
return;
}

this.mlkitScanner.scanDocument(true).subscribe({
next: (base64PictureData) => {
this.base64ImagesWithSource.push({
source: 'MOBILE_DASHCAM_BULK',
base64Image: base64PictureData,
});
this.noOfReceipts += 1;

// Add to queue and navigate to my expenses
this.loaderService.showLoader(this.translocoService.translate('captureReceipt.pleaseWait'), 10000);
this.addMultipleExpensesToQueue(this.base64ImagesWithSource)
.pipe(finalize(() => this.loaderService.hideLoader()))
.subscribe(() => {
this.router.navigate(['/', 'enterprise', 'my_expenses']);
});
},
error: () => {
this.matSnackBar.openFromComponent(ToastMessageComponent, {
...this.snackbarProperties.setSnackbarProperties('failure', {
message: this.translocoService.translate('captureReceipt.documentScanFailed'),
}),
});
},
});
}
Comment on lines +597 to +628
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Kabali says: Incomplete implementation is worse than no implementation!

The startDirectBulkDocumentScan() method doesn't actually enable bulk mode in the scanner - it calls scanDocument(true) but immediately processes and navigates after the first scan, defeating the bulk purpose.

The method should either:

  1. Loop to capture multiple documents, or
  2. Pass the correct bulk flag to actually scan multiple pages at once

Currently, it's misleading as it behaves like single capture with extra steps.

🤖 Prompt for AI Agents
In src/app/shared/components/capture-receipt/capture-receipt.component.ts around
lines 593 to 624, the startDirectBulkDocumentScan() method calls
scanDocument(true) but processes only one scanned image before navigating away,
so it does not actually perform bulk scanning. To fix this, modify the method to
either loop and accumulate multiple scans before processing or ensure the
scanner is invoked in a mode that captures multiple pages in one call, then
process all images together before navigating. This will align the method
behavior with its intended bulk scanning purpose.

}
Loading