Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Allow save expense with invalid fields after capture receipt #3308

Merged
merged 12 commits into from
Dec 9, 2024

Conversation

arjunaj5
Copy link
Contributor

@arjunaj5 arjunaj5 commented Dec 6, 2024

Clickup

https://app.clickup.com/t/86cx8j66c

Code Coverage

Please add code coverage here

UI Preview

Please add screenshots for UI changes

Summary by CodeRabbit

  • New Features

    • Enhanced transaction handling for file uploads, improving data integrity by filtering out invalid properties.
    • Improved error handling for policy violations during expense submissions.
  • Improvements

    • Streamlined logic for creating transactions, resulting in better readability and maintainability.
    • Simplified method signatures in transaction services, reducing complexity in entry management.
    • Refined handling of report IDs in expense submissions for better transaction management.
    • Added comprehensive test coverage for expense handling scenarios, particularly regarding policy violations and file uploads.
    • Enhanced clarity and consistency in test cases for adding and editing expenses, especially related to policy compliance.

Copy link

coderabbitai bot commented Dec 6, 2024

Walkthrough

The pull request modifies the TransactionService class, enhancing the createTxnWithFiles method's logic for file uploads. It retrieves file IDs first, checks their presence, and processes the transaction based on the source key. A new private method, cleanupExpensePayload, filters out invalid properties from the transaction object before upsertion. These changes improve the method's readability and maintainability without altering public method signatures or introducing new public methods.

Changes

File Path Change Summary
src/app/core/services/transaction.service.ts Restructured createTxnWithFiles method for better file handling; added cleanupExpensePayload to filter null/undefined properties.
src/app/core/services/transaction.service.spec.ts Updated createTxnWithFiles to include file handling; modified upsert method with duplicate definition.
src/app/core/services/transactions-outbox.service.ts Removed reportId parameter from addEntry and addEntryAndSync methods for simplified signatures.
src/app/fyle/add-edit-expense/add-edit-expense.page.ts Updated addExpense method to streamline expense addition; refined file upload handling and error management.
src/app/fyle/add-edit-mileage/add-edit-mileage.page.ts Simplified report ID assignment in addExpense method; enhanced error handling for policy violations.
src/app/fyle/add-edit-per-diem/add-edit-per-diem.page.ts Directly assigned report_id to transaction object; refined error handling for policy violations.
src/app/shared/components/capture-receipt/capture-receipt.component.ts Modified addExpenseToQueue method to reduce parameters passed to transactionsOutboxService.
src/app/core/mock-data/transaction.data.ts Introduced txnDataCleaned constant; updated txnData to include orig_currency and orig_amount.
src/app/core/services/transactions-outbox.service.spec.ts Updated test cases to reflect changes in addEntryAndSync method by removing null parameters.
src/app/fyle/add-edit-expense/add-edit-expense-4.spec.ts Added new test cases and modified existing ones for enhanced coverage of expense handling scenarios.
src/app/fyle/add-edit-mileage/add-edit-mileage-3.spec.ts Updated tests for addExpense and editExpense methods to ensure consistent handling of policy violations.
src/app/fyle/add-edit-per-diem/add-edit-per-diem-3.page.spec.ts Added new test cases and adjusted existing ones to improve coverage of policy violation handling.

Possibly related PRs

Suggested reviewers

  • Chethan-Fyle
  • Dimple16

🎉 In the realm of code where logic flows,
TransactionService shines, and progress grows.
With files in hand, we check and clean,
A streamlined process, efficient and keen.
Rajinikanth would say, "Let's make it right!"
In this dance of code, we reach new heights! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@arjunaj5 arjunaj5 requested a review from mvaishnavi December 6, 2024 11:33
@github-actions github-actions bot added the size/S Small PR label Dec 6, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between 33c45fd and ca1b84b.

📒 Files selected for processing (1)
  • src/app/core/services/transaction.service.ts (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/core/services/transaction.service.ts

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

🪛 eslint
src/app/core/services/transaction.service.ts

[error] 928-928: Missing return type on function.

(@typescript-eslint/explicit-function-return-type)

🪛 GitHub Check: Run linters
src/app/core/services/transaction.service.ts

[failure] 928-928:
Missing return type on function


[failure] 932-932:
Unsafe assignment of an any value

Comment on lines 928 to 936
private cleanupExpenseCreatePayload(txn: Partial<Transaction>) {
const newTxn = {};
for (const key in txn) {
if (txn[key] !== null && txn[key] !== undefined) {
newTxn[key] = txn[key];
}
}
return newTxn;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Mind it! Let's add some punch to this code with proper types!

The method needs some power-packed improvements:

  1. Add proper return type annotation
  2. Use type-safe implementation
  3. Consider using Object.entries for better readability

Here's the superstar way to do it:

- private cleanupExpenseCreatePayload(txn: Partial<Transaction>) {
-   const newTxn = {};
+ private cleanupExpenseCreatePayload(txn: Partial<Transaction>): Partial<Transaction> {
+   const newTxn: Partial<Transaction> = {};
    for (const key in txn) {
-     if (txn[key] !== null && txn[key] !== undefined) {
+     if (txn[key] != null) {
        newTxn[key] = txn[key];
      }
    }
    return newTxn;
  }

Alternative implementation using Object.entries:

private cleanupExpenseCreatePayload(txn: Partial<Transaction>): Partial<Transaction> {
  return Object.fromEntries(
    Object.entries(txn).filter(([_, value]) => value != null)
  ) as Partial<Transaction>;
}
🧰 Tools
🪛 eslint

[error] 928-928: Missing return type on function.

(@typescript-eslint/explicit-function-return-type)

🪛 GitHub Check: Run linters

[failure] 928-928:
Missing return type on function


[failure] 932-932:
Unsafe assignment of an any value

Comment on lines 198 to 212
const fileIds = fileObjs.map((fileObj) => fileObj.id);
if (fileIds.length > 0) {
return this.expensesService.createFromFile(fileIds[0], txn.source).pipe(
switchMap((result) => {
// txn contains only source key when capturing receipt
if (txn.hasOwnProperty('source') && Object.keys(txn).length === 1) {
return of(this.transformExpense(result.data[0]).tx);
} else {
const fileIds = fileObjs.map((fileObj) => fileObj.id);
txn.file_ids = fileIds;
txn.id = result.data[0].id;
return this.upsert(this.cleanupExpenseCreatePayload(txn));
}
})
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Listen up, partner! Let's make this code more stylish than my signature walk!

The code needs some style upgrades in the following areas:

  1. Replace hasOwnProperty with Object.hasOwn() for better safety
  2. Add proper type annotations for better type safety

Here's how to make it super:

  return fileUploads$.pipe(
    switchMap((fileObjs) => {
      const fileIds = fileObjs.map((fileObj) => fileObj.id);
      if (fileIds.length > 0) {
        return this.expensesService.createFromFile(fileIds[0], txn.source).pipe(
          switchMap((result) => {
-           if (txn.hasOwnProperty('source') && Object.keys(txn).length === 1) {
+           if (Object.hasOwn(txn, 'source') && Object.keys(txn).length === 1) {
              return of(this.transformExpense(result.data[0]).tx);
            } else {
              const fileIds = fileObjs.map((fileObj) => fileObj.id);
              txn.file_ids = fileIds;
              txn.id = result.data[0].id;
              return this.upsert(this.cleanupExpenseCreatePayload(txn));
            }
          })
        );
📝 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
const fileIds = fileObjs.map((fileObj) => fileObj.id);
if (fileIds.length > 0) {
return this.expensesService.createFromFile(fileIds[0], txn.source).pipe(
switchMap((result) => {
// txn contains only source key when capturing receipt
if (txn.hasOwnProperty('source') && Object.keys(txn).length === 1) {
return of(this.transformExpense(result.data[0]).tx);
} else {
const fileIds = fileObjs.map((fileObj) => fileObj.id);
txn.file_ids = fileIds;
txn.id = result.data[0].id;
return this.upsert(this.cleanupExpenseCreatePayload(txn));
}
})
);
const fileIds = fileObjs.map((fileObj) => fileObj.id);
if (fileIds.length > 0) {
return this.expensesService.createFromFile(fileIds[0], txn.source).pipe(
switchMap((result) => {
// txn contains only source key when capturing receipt
if (Object.hasOwn(txn, 'source') && Object.keys(txn).length === 1) {
return of(this.transformExpense(result.data[0]).tx);
} else {
const fileIds = fileObjs.map((fileObj) => fileObj.id);
txn.file_ids = fileIds;
txn.id = result.data[0].id;
return this.upsert(this.cleanupExpenseCreatePayload(txn));
}
})
);
🧰 Tools
🪛 Biome (1.9.4)

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between ca1b84b and 467025e.

📒 Files selected for processing (1)
  • src/app/core/services/transaction.service.ts (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/core/services/transaction.service.ts

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

🪛 eslint
src/app/core/services/transaction.service.ts

[error] 929-929: Missing return type on function.

(@typescript-eslint/explicit-function-return-type)

🪛 GitHub Check: Run linters
src/app/core/services/transaction.service.ts

[failure] 929-929:
Missing return type on function


[failure] 933-933:
Unsafe assignment of an any value

🔇 Additional comments (2)
src/app/core/services/transaction.service.ts (2)

Line range hint 198-937: Mind it! The changes look solid as a rock!

The implementation successfully achieves the goal of allowing expenses to be saved with invalid fields after quick fyle. The new cleanupExpensePayload method effectively filters out invalid properties, making the code more robust.

🧰 Tools
🪛 Biome (1.9.4)

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)


198-212: 🛠️ Refactor suggestion

Mind it! Let's make this code more powerful than a punch dialogue!

The code needs some style upgrades, macha! Here's how to make it super:

  1. Replace hasOwnProperty with Object.hasOwn() for better safety
  2. Add proper type annotations for better type safety
  return fileUploads$.pipe(
    switchMap((fileObjs) => {
      const fileIds = fileObjs.map((fileObj) => fileObj.id);
      if (fileIds.length > 0) {
        return this.expensesService.createFromFile(fileIds[0], txn.source).pipe(
          switchMap((result) => {
-           if (txn.hasOwnProperty('source') && Object.keys(txn).length === 1) {
+           if (Object.hasOwn(txn, 'source') && Object.keys(txn).length === 1) {
              return of(this.transformExpense(result.data[0]).tx);
            } else {
              const fileIds = fileObjs.map((fileObj) => fileObj.id);
              txn.file_ids = fileIds;
              txn.id = result.data[0].id;
              return this.upsert(this.cleanupExpensePayload(txn));
            }
          })
        );

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome (1.9.4)

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

Comment on lines 928 to 937
// to be used only when updating created expense with form values during capture recept flow
private cleanupExpensePayload(txn: Partial<Transaction>) {
const newTxn = {};
for (const key in txn) {
if (txn[key] !== null && txn[key] !== undefined) {
newTxn[key] = txn[key];
}
}
return newTxn;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Style ah? Let's add some punch to this code with proper types!

The method needs some power-packed improvements, thalaiva:

  1. Add proper return type annotation
  2. Use type-safe implementation
  3. Consider using Object.entries for better readability
- private cleanupExpensePayload(txn: Partial<Transaction>) {
-   const newTxn = {};
+ private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
+   const newTxn: Partial<Transaction> = {};
    for (const key in txn) {
-     if (txn[key] !== null && txn[key] !== undefined) {
+     if (txn[key] != null) {
        newTxn[key] = txn[key];
      }
    }
    return newTxn;
  }

Alternative implementation using Object.entries:

private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
  return Object.fromEntries(
    Object.entries(txn).filter(([_, value]) => value != null)
  ) as Partial<Transaction>;
}
🧰 Tools
🪛 eslint

[error] 929-929: Missing return type on function.

(@typescript-eslint/explicit-function-return-type)

🪛 GitHub Check: Run linters

[failure] 929-929:
Missing return type on function


[failure] 933-933:
Unsafe assignment of an any value

@github-actions github-actions bot added size/M Medium PR and removed size/S Small PR labels Dec 6, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Outside diff range comments (3)
src/app/fyle/add-edit-per-diem/add-edit-per-diem.page.ts (3)

Line range hint 984-989: Listen up! In checkIfInvalidPaymentMode, we're subscribing within a subscription.

This might lead to unexpected behaviors or memory leaks. Let's refactor it by using RxJS operators instead of nested subscriptions.

Consider refactoring like this:

984,989
- that
-   .checkIfInvalidPaymentMode()
-   .pipe(take(1))
-   .subscribe((invalidPaymentMode) => {
-     if (that.fg.valid && !invalidPaymentMode) {
+ this.checkIfInvalidPaymentMode().pipe(
+   take(1),
+   switchMap((invalidPaymentMode) => {
+     if (this.fg.valid && !invalidPaymentMode) {

And ensure to handle the rest of the logic within the switchMap.


Line range hint 1279-1284: Hold on! Let's be cautious with dependentFields$ initialization.

We're calling customInputsService.getAll(true) multiple times. It's better to share this observable.

You might want to use shareReplay(1) to avoid unnecessary calls:

1279,1281
- const customExpenseFields$ = this.customInputsService.getAll(true).pipe(shareReplay(1));
- 
- this.dependentFields$ = customExpenseFields$.pipe(
+ this.dependentFields$ = this.customInputs$.pipe(

Ensure that this.customInputs$ is initialized appropriately.


Line range hint 1490-1495: Heads up! The error handling in addExpense could be improved.

We're using multiple nested catchError. Let's simplify it for better readability.

Consider refactoring the catchError block to handle different error types more cleanly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between 467025e and 436e0ef.

📒 Files selected for processing (7)
  • src/app/core/services/transaction.service.spec.ts (1 hunks)
  • src/app/core/services/transaction.service.ts (2 hunks)
  • src/app/core/services/transactions-outbox.service.ts (2 hunks)
  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts (3 hunks)
  • src/app/fyle/add-edit-mileage/add-edit-mileage.page.ts (1 hunks)
  • src/app/fyle/add-edit-per-diem/add-edit-per-diem.page.ts (1 hunks)
  • src/app/shared/components/capture-receipt/capture-receipt.component.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/core/services/transaction.service.ts

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

🪛 GitHub Check: Run linters
src/app/core/services/transaction.service.ts

[failure] 933-933:
Unsafe assignment of an any value

🔇 Additional comments (7)
src/app/fyle/add-edit-per-diem/add-edit-per-diem.page.ts (1)

1984-1984: 🧹 Nitpick (assertive)

Watch out! It seems there's a misaligned closing brace here.

Alignment and indentation keep our code neat and tidy. Let's adjust it for better readability.

Here's the corrected indentation:

1984
-                 ).pipe(switchMap((txnData) => from(txnData)));
+               ).pipe(switchMap((txnData) => from(txnData)));

Likely invalid or redundant comment.

src/app/core/services/transaction.service.ts (2)

203-203: Boss, avoid using 'hasOwnProperty' directly on objects

Using hasOwnProperty directly can lead to unexpected issues. Better to use Object.hasOwn() for safety.

Apply this diff to fix the issue:

- if (txn.hasOwnProperty('source') && Object.keys(txn).length === 1) {
+ if (Object.hasOwn(txn, 'source') && Object.keys(txn).length === 1) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)


929-937: Let's make 'cleanupExpensePayload' more powerful

Add return type annotations and consider using Object.entries for cleaner code and better type safety.

Apply this diff to improve the method:

- private cleanupExpensePayload(txn: Partial<Transaction>) {
-   const newTxn = {};
+ private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
+   const newTxn: Partial<Transaction> = {};
    for (const key in txn) {
-     if (txn[key] !== null && txn[key] !== undefined) {
+     if (txn[key] != null) {
        newTxn[key] = txn[key];
      }
    }
    return newTxn;
  }

Alternatively, you could use Object.entries:

private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
  return Object.fromEntries(
    Object.entries(txn).filter(([_, value]) => value != null)
  ) as Partial<Transaction>;
}
🧰 Tools
🪛 GitHub Check: Run linters

[failure] 933-933:
Unsafe assignment of an any value

src/app/fyle/add-edit-mileage/add-edit-mileage.page.ts (2)

2917-2917: Heads up! Assigning report ID directly may cause issues.

At line 2917, you're setting etxn.tx.report_id = reportValue.report?.id;. Make sure reportValue.report is not null or undefined to avoid unexpected behaviors.


2923-2923: ⚠️ Potential issue

Wait a moment! Missing parameter in method call.

In this.transactionsOutboxService.addEntryAndSync, it seems you're missing a required parameter. Double-check the method definition to ensure all necessary arguments are provided.

Apply this diff to fix the issue:

- this.transactionsOutboxService.addEntryAndSync(
-   etxn.tx,
-   etxn.dataUrls as { url: string; type: string }[],
-   comments
- );
+ this.transactionsOutboxService.addEntryAndSync(
+   etxn.tx,
+   etxn.dataUrls as { url: string; type: string }[],
+   comments
+ );

Likely invalid or redundant comment.

src/app/fyle/add-edit-expense/add-edit-expense.page.ts (1)

4368-4368: Hold on! Direct assignment of report ID might cause issues.

At line 4368, assigning etxn.tx.report_id = formValues.report.id; could override existing report associations. Ensure this is intended and formValues.report is defined.

src/app/core/services/transactions-outbox.service.ts (1)

160-162: 🛠️ Refactor suggestion

Heads up! Update method calls to match new signature.

The addEntryAndSync method no longer requires reportId. Check all invocations to prevent runtime errors.

Update method calls like so:

- return this.addEntryAndSync(transaction, dataUrls, comments, reportId);
+ return this.addEntryAndSync(transaction, dataUrls, comments);

Likely invalid or redundant comment.

Comment on lines 1978 to 1984
etxn.tx.report_id = formValue.report.id;
}
return of(
this.transactionsOutboxService.addEntryAndSync(
etxn.tx,
etxn.dataUrls as { url: string; type: string }[],
comments,
reportId
comments
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hey there! Let's make sure we're handling formValue.report properly.

We're assigning formValue.report.id to etxn.tx.report_id without checking if formValue.report exists. It's always safer to check before we act.

Here's a suggestion:

1978,1980
- if (formValue.report && (etxn.tx.policy_amount === null || (etxn.tx.policy_amount && !(etxn.tx.policy_amount < 0.0001)))) {
-   etxn.tx.report_id = formValue.report.id;
- }
+ if (
+   formValue.report &&
+   formValue.report.id &&
+   (etxn.tx.policy_amount === null || (etxn.tx.policy_amount && !(etxn.tx.policy_amount < 0.0001)))
+ ) {
+   etxn.tx.report_id = formValue.report.id;
+ }
📝 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
etxn.tx.report_id = formValue.report.id;
}
return of(
this.transactionsOutboxService.addEntryAndSync(
etxn.tx,
etxn.dataUrls as { url: string; type: string }[],
comments,
reportId
comments
if (
formValue.report &&
formValue.report.id &&
(etxn.tx.policy_amount === null || (etxn.tx.policy_amount && !(etxn.tx.policy_amount < 0.0001)))
) {
etxn.tx.report_id = formValue.report.id;
}
return of(
this.transactionsOutboxService.addEntryAndSync(
etxn.tx,
etxn.dataUrls as { url: string; type: string }[],
comments

@@ -1100,6 +1100,7 @@ describe('TransactionService', () => {
const mockFileObject = cloneDeep(fileObjectData1);

spyOn(transactionService, 'upsert').and.returnValue(of(txnData2));
expensesService.createFromFile.and.returnValue(of({ data: [expenseData] }));
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Mind-blowing test case, but let's make it more robust!

The test case looks good, but in true Rajinikanth style, let's make it even more powerful! Consider adding assertions to verify that expensesService.createFromFile was called with the correct parameters.

Add this assertion after the existing expect statements:

 transactionService.createTxnWithFiles({ ...txnData }, of(mockFileObject)).subscribe((res) => {
   expect(res).toEqual(txnData2);
   expect(transactionService.upsert).toHaveBeenCalledOnceWith({ ...txnData, file_ids: [fileObjectData1[0].id] });
+  expect(expensesService.createFromFile).toHaveBeenCalledOnceWith(mockFileObject[0].id, txnData.source);
   done();
 });

Committable suggestion skipped: line range outside the PR's diff.

@@ -4387,8 +4385,7 @@ export class AddEditExpensePage implements OnInit {
this.transactionOutboxService.addEntryAndSync(
etxn.tx,
etxn.dataUrls as { url: string; type: string }[],
comments,
reportId
comments
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Watch out! Incorrect parameter passed to addEntryAndSync.

The method addEntryAndSync might have an updated signature. Confirm that all required parameters are correctly passed.

Here's a suggested change:

- this.transactionOutboxService.addEntryAndSync(
-   etxn.tx,
-   etxn.dataUrls as { url: string; type: string }[],
-   comments
- );
+ this.transactionOutboxService.addEntryAndSync(
+   etxn.tx,
+   etxn.dataUrls as { url: string; type: string }[],
+   comments
+ );

Committable suggestion skipped: line range outside the PR's diff.

@@ -141,14 +141,12 @@ export class TransactionsOutboxService {
addEntry(
transaction: Partial<Transaction>,
dataUrls: { url: string; type: string }[],
comments?: string[],
reportId?: string
comments?: string[]
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Mind this! Method signature updated but calls may be outdated.

You've removed the reportId parameter from addEntry. Ensure all calls to this method are updated accordingly.

@@ -127,7 +127,7 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
url: base64ImagesWithSource.base64Image,
},
];
return this.transactionsOutboxService.addEntry(transaction, attachmentUrls, null, null);
return this.transactionsOutboxService.addEntry(transaction, attachmentUrls, null);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wait a second! Missing argument in method call.

In this.transactionsOutboxService.addEntry, you've reduced parameters. Ensure this aligns with the updated method signature.

Here's how you might fix it:

- return this.transactionsOutboxService.addEntry(transaction, attachmentUrls, null);
+ return this.transactionsOutboxService.addEntry(transaction, attachmentUrls);

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between 436e0ef and 469dea5.

📒 Files selected for processing (7)
  • src/app/core/mock-data/transaction.data.ts (1 hunks)
  • src/app/core/services/transaction.service.spec.ts (2 hunks)
  • src/app/core/services/transaction.service.ts (2 hunks)
  • src/app/core/services/transactions-outbox.service.spec.ts (0 hunks)
  • src/app/fyle/add-edit-expense/add-edit-expense-4.spec.ts (3 hunks)
  • src/app/fyle/add-edit-mileage/add-edit-mileage-3.spec.ts (5 hunks)
  • src/app/fyle/add-edit-per-diem/add-edit-per-diem-3.page.spec.ts (8 hunks)
💤 Files with no reviewable changes (1)
  • src/app/core/services/transactions-outbox.service.spec.ts
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/core/services/transaction.service.ts

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

🔇 Additional comments (13)
src/app/fyle/add-edit-per-diem/add-edit-per-diem-3.page.spec.ts (2)

675-675: Mind it! The test cases are properly handling the no-comment scenarios, macha!

The empty array for the comments parameter in addEntryAndSync correctly reflects that no policy violation comments are needed when there are no violations. This maintains data consistency, thalaiva!

Also applies to: 885-885, 915-915, 955-955


717-717: Kabali da! The policy violation comments are being passed correctly!

The test cases are properly verifying that policy violation comments (['comment']) are passed to addEntryAndSync when violations occur. This ensures that important policy context is preserved during sync, what a style!

Also applies to: 762-762, 805-805, 844-844

src/app/fyle/add-edit-mileage/add-edit-mileage-3.spec.ts (6)

279-279: Mind it! The change to pass empty array for comments is spot on!

The modification to pass [] instead of undefined for the comments parameter in addEntryAndSync makes the code more robust and predictable.


320-320: Thalaiva approves this change!

Consistently using empty array for comments parameter maintains uniformity across test cases.


370-370: Another blockbuster change, passing empty array for critical policy violation test!

The modification aligns with other test cases and improves consistency.


422-422: What a powerful scene! Comments array with actual comment for policy violation.

The test correctly verifies that policy violation comments are properly passed through.


457-457: Even in offline mode, empty array is the style!

Maintaining consistency by using empty array for comments in offline mode test case as well.


Line range hint 1-900: Let me tell you one thing - these test cases need more verification!

While the changes look good, we should verify if there are any edge cases we're not testing.

Consider adding test cases for:

  1. Empty string in comments array
  2. Multiple comments in array
  3. Null values in comments array
src/app/fyle/add-edit-expense/add-edit-expense-4.spec.ts (1)

Line range hint 44-44: Superstar addition!

The introduction of txnDataCleaned provides clarity and ensures that only necessary transaction properties are used during testing. Well done!

src/app/core/services/transaction.service.ts (2)

203-203: ⚠️ Potential issue

Mind it! Replace hasOwnProperty for better safety.

Using Object.hasOwn(txn, 'source') is safer than txn.hasOwnProperty('source') as it avoids issues with objects that don't inherit from Object.prototype.

Apply this change:

-if (txn.hasOwnProperty('source') && Object.keys(txn).length === 1) {
+if (Object.hasOwn(txn, 'source') && Object.keys(txn).length === 1) {

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome (1.9.4)

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)


198-212: 🛠️ Refactor suggestion

Hold on! Let's make this code more robust.

You're using hasOwnProperty directly on the object txn, which can lead to unexpected results if the object has been created without a proper prototype chain. It's safer to use Object.hasOwn() instead.

Additionally, consider adding type annotations to improve type safety and maintainability.

Apply this diff to make the changes:

 const fileIds = fileObjs.map((fileObj) => fileObj.id);
 if (fileIds.length > 0) {
   return this.expensesService.createFromFile(fileIds[0], txn.source).pipe(
     switchMap((result) => {
-      if (txn.hasOwnProperty('source') && Object.keys(txn).length === 1) {
+      if (Object.hasOwn(txn, 'source') && Object.keys(txn).length === 1) {
         return of(this.transformExpense(result.data[0]).tx);
       } else {
         // capture receipt flow: patching the expense with any of the form values.
         txn.file_ids = fileIds;
         txn.id = result.data[0].id;
-        return this.upsert(this.cleanupExpensePayload(txn));
+        return this.upsert(this.cleanupExpensePayload(txn));
       }
     })
   );

Also, don't forget to import Object if needed.

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome (1.9.4)

[error] 203-203: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

src/app/core/services/transaction.service.spec.ts (1)

1107-1112: 🧹 Nitpick (assertive)

Add an assertion to strengthen the test!

To ensure that expensesService.createFromFile is called with the correct parameters, consider adding an assertion in your test case.

Apply this addition to your test:

 transactionService.createTxnWithFiles({ ...txnData }, of(mockFileObject)).subscribe((res) => {
   expect(res).toEqual(txnData2);
+  expect(expensesService.createFromFile).toHaveBeenCalledOnceWith(mockFileObject[0].id, txnData.source);
   expect(transactionService.upsert).toHaveBeenCalledOnceWith({
     ...txnDataCleaned,
     file_ids: [fileObjectData1[0].id],
     id: expenseData.id,
   });

This ensures that the createFromFile method is tested with the expected arguments.

Likely invalid or redundant comment.

src/app/core/mock-data/transaction.data.ts (1)

189-196: Well done! The addition brings clarity.

Introducing txnDataCleaned helps to ensure only necessary properties are included in transactions during tests, promoting better test practices.

Comment on lines +927 to +937

// to be used only when updating created expense with form values during capture recept flow
private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
const newTxn: Partial<Transaction> = {};
for (const key in txn) {
if (txn[key] !== null && txn[key] !== undefined) {
newTxn[key] = txn[key] as Transaction[keyof Transaction];
}
}
return newTxn;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Wait a minute! Let's tighten up this method with proper types.

The cleanupExpensePayload method lacks a return type annotation, and the implementation can be more type-safe and readable.

Here's how you can enhance it:

-private cleanupExpensePayload(txn: Partial<Transaction>) {
-  const newTxn = {};
+private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
+  const newTxn: Partial<Transaction> = {};
   for (const key in txn) {
-    if (txn[key] !== null && txn[key] !== undefined) {
+    if (txn[key] != null) {
       newTxn[key] = txn[key] as Transaction[keyof Transaction];
     }
   }
   return newTxn;
 }

Alternatively, you can use Object.entries for better readability:

private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
  return Object.fromEntries(
    Object.entries(txn).filter(([_, value]) => value != null)
  ) as Partial<Transaction>;
}

@arjunaj5 arjunaj5 changed the title fix: Allow save expense with invalid fields after quick fyle fix: Allow save expense with invalid fields after capture receipt Dec 8, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Outside diff range comments (2)
src/app/fyle/add-edit-mileage/add-edit-mileage-3.spec.ts (2)

Line range hint 250-286: Mind it! The test case needs proper cloning of data.

The test case now properly clones the unflattenedTxnData to prevent mutation, but there's a potential improvement.

Consider using a beforeEach block to set up the cloned data:

-spyOn(component, 'generateEtxnFromFg').and.returnValue(of(cloneDeep(unflattenedTxnData)));
+let clonedTxnData;
+beforeEach(() => {
+  clonedTxnData = cloneDeep(unflattenedTxnData);
+});
+spyOn(component, 'generateEtxnFromFg').and.returnValue(of(clonedTxnData));

Line range hint 1-1000: Thalaiva says: Add test coverage for edge cases!

The test suite could benefit from additional test cases covering edge scenarios.

Consider adding tests for:

  1. Network disconnection during expense submission
  2. Invalid report IDs
  3. Partial policy violation data
  4. Race conditions in async operations

Example test case structure:

it('should handle network disconnection during expense submission', (done) => {
  // Setup
  component.isConnected$ = new BehaviorSubject(true);
  
  // Simulate network disconnection
  setTimeout(() => {
    (component.isConnected$ as BehaviorSubject<boolean>).next(false);
  }, 100);
  
  // Test and assertions
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between 469dea5 and ce96a7f.

📒 Files selected for processing (5)
  • src/app/core/services/transaction.service.spec.ts (2 hunks)
  • src/app/core/services/transaction.service.ts (2 hunks)
  • src/app/fyle/add-edit-mileage/add-edit-mileage-3.spec.ts (8 hunks)
  • src/app/fyle/add-edit-per-diem/add-edit-per-diem-3.page.spec.ts (11 hunks)
  • src/app/fyle/add-edit-per-diem/add-edit-per-diem.page.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/core/services/transaction.service.ts

[error] 199-199: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

src/app/fyle/add-edit-per-diem/add-edit-per-diem-3.page.spec.ts

[error] 80-80: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🔇 Additional comments (4)
src/app/fyle/add-edit-per-diem/add-edit-per-diem.page.ts (2)

1983-1985: Kabali style! The transaction sync looks good.

The parameters for addEntryAndSync are properly structured with transaction, dataUrls, and comments. This is how you write clean code!


1974-1979: ⚠️ Potential issue

Mind it! Potential null reference issue in report ID assignment.

The condition formValue.report && (etxn.tx.policy_amount === null || (etxn.tx.policy_amount && !(etxn.tx.policy_amount < 0.0001))) is checking policy amount, but not verifying if formValue.report.id exists. When I say something exists, it better exist!

Here's how you fix it, like a boss:

-if (formValue.report && (etxn.tx.policy_amount === null || (etxn.tx.policy_amount && !(etxn.tx.policy_amount < 0.0001)))) {
-  transaction.report_id = formValue.report.id;
+if (
+  formValue.report?.id &&
+  (etxn.tx.policy_amount === null || (etxn.tx.policy_amount && !(etxn.tx.policy_amount < 0.0001)))
+) {
+  transaction.report_id = formValue.report.id;

Likely invalid or redundant comment.

src/app/core/services/transaction.service.ts (1)

198-212: 🛠️ Refactor suggestion

Listen up, partner! Let's make this code more stylish than my signature walk!

The code needs some style upgrades in the following areas:

  1. Replace hasOwnProperty with Object.hasOwn() for better safety
  2. Add proper type annotations for better type safety

Here's how to make it super:

  return fileUploads$.pipe(
    switchMap((fileObjs) => {
      const fileIds = fileObjs.map((fileObj) => fileObj.id);
      const isReceiptUpload = txn.hasOwnProperty('source') && Object.keys(txn).length === 1;
      const isAmountPresent = !!txn.amount;
      if ((isReceiptUpload || !isAmountPresent) && fileIds.length > 0) {
        return this.expensesService.createFromFile(fileIds[0], txn.source).pipe(
          switchMap((result) => {
            // capture receipt flow: patching the expense in case of amount not present
            if (!isReceiptUpload && !isAmountPresent) {
              txn.id = result.data[0].id;
              return this.upsert(this.cleanupExpensePayload(txn));
            } else {
              return of(this.transformExpense(result.data[0]).tx);
            }
          })
        );
      } else {
        return this.upsert(txn);
      }
    })
  );

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome (1.9.4)

[error] 199-199: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

src/app/core/services/transaction.service.spec.ts (1)

44-44: LGTM! Import statement added with style!

The import of txnDataCleaned is perfectly placed with other transaction data imports.

Comment on lines +327 to 329
[]
);
done();
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Don't disturb! The empty array parameter needs documentation.

The empty array parameter [] in addEntryAndSync calls appears to be for comments, but its purpose isn't immediately clear.

Add a comment to clarify the purpose of the empty array:

 expect(transactionOutboxService.addEntryAndSync).toHaveBeenCalledOnceWith(
   unflattenedMileageDataWithPolicyAmount.tx,
   unflattenedTxnData.dataUrls as any,
-  []
+  [] // comments array - empty when no policy violations
 );

Also applies to: 429-431

Comment on lines +439 to +452
spyOn(component, 'generateEtxnFromFg').and.returnValue(of(cloneDeep(unflattenedTxnData)));
spyOn(component, 'checkPolicyViolation').and.returnValue(of(expensePolicyDataWoData));
spyOn(component, 'trackCreateExpense');
authService.getEou.and.resolveTo(apiEouRes);
spyOn(component, 'getFormValues').and.returnValue({
report: expectedReportsPaginated[0],
});
const expectedEtxnData = {
...unflattenedTxnData,
tx: {
...unflattenedTxnData.tx,
report_id: expectedReportsPaginated[0].id,
},
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Kabali da! The offline mode test case needs better structure.

The offline mode test case duplicates the expectedEtxnData object construction. This could be moved to a shared setup.

Extract the expected data setup:

+let expectedOfflineEtxnData;
+beforeEach(() => {
+  expectedOfflineEtxnData = {
+    ...unflattenedTxnData,
+    tx: {
+      ...unflattenedTxnData.tx,
+      report_id: expectedReportsPaginated[0].id,
+    },
+  };
+});

 it('should add expense in offline mode', (done) => {
   component.isConnected$ = of(false);
   spyOn(component, 'getCustomFields').and.returnValue(of(txnCustomPropertiesData4));
   spyOn(component, 'getCalculatedDistance').and.returnValue(of('10'));
-  spyOn(component, 'generateEtxnFromFg').and.returnValue(of(cloneDeep(unflattenedTxnData)));
+  spyOn(component, 'generateEtxnFromFg').and.returnValue(of(expectedOfflineEtxnData));

Also applies to: 457-471

Comment on lines +928 to +937
// to be used only when updating created expense with form values during capture recept flow
private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
const newTxn: Partial<Transaction> = {};
for (const key in txn) {
if (txn[key] !== null && txn[key] !== undefined) {
newTxn[key] = txn[key] as Transaction[keyof Transaction];
}
}
return newTxn;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Mind it! Let's add some punch to this code with proper types!

The method needs some power-packed improvements:

  1. Add proper return type annotation
  2. Use type-safe implementation
  3. Consider using Object.entries for better readability

Here's the superstar way to do it:

- private cleanupExpensePayload(txn: Partial<Transaction>) {
-   const newTxn = {};
+ private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
+   const newTxn: Partial<Transaction> = {};
    for (const key in txn) {
-     if (txn[key] !== null && txn[key] !== undefined) {
+     if (txn[key] != null) {
        newTxn[key] = txn[key] as Transaction[keyof Transaction];
      }
    }
    return newTxn;
  }

Alternative implementation using Object.entries:

private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
  return Object.fromEntries(
    Object.entries(txn).filter(([_, value]) => value != null)
  ) as Partial<Transaction>;
}

@@ -77,7 +77,7 @@ import { expectedReportsPaginated } from 'src/app/core/mock-data/platform-report
import { PerDiemRedirectedFrom } from 'src/app/core/models/per-diem-redirected-from.enum';

export function TestCases3(getTestBed) {
return describe('add-edit-per-diem test cases set 3', () => {
return fdescribe('add-edit-per-diem test cases set 3', () => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hey! Don't focus on just one test, let all tests shine like stars!

Using fdescribe prevents other tests from running. This is typically used during development and should be removed before committing.

Replace fdescribe with describe:

- return fdescribe('add-edit-per-diem test cases set 3', () => {
+ return describe('add-edit-per-diem test cases set 3', () => {
📝 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
return fdescribe('add-edit-per-diem test cases set 3', () => {
return describe('add-edit-per-diem test cases set 3', () => {
🧰 Tools
🪛 Biome (1.9.4)

[error] 80-80: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

Comment on lines +1104 to +1111
expensesService.createFromFile.and.returnValue(of({ data: [expenseData] }));
transactionService.createTxnWithFiles({ ...txnData }, of(mockFileObject)).subscribe((res) => {
expect(res).toEqual(txnData2);
expect(transactionService.upsert).toHaveBeenCalledOnceWith({ ...txnData, file_ids: [fileObjectData1[0].id] });
expect(expensesService.createFromFile).toHaveBeenCalledOnceWith(mockFileObject[0].id, 'MOBILE_DASHCAM_BULK');
expect(transactionService.upsert).toHaveBeenCalledOnceWith({
...txnDataCleaned,
id: expenseData.id,
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Mind-blowing test case enhancement, but let's make it even more powerful!

The test case looks good with the new assertions, but in true Rajinikanth style, let's make it bulletproof! Consider adding error case scenarios.

Add an error test case to verify the error handling:

it('should handle errors when creating transaction with files', (done) => {
  const mockFileObject = cloneDeep(fileObjectData1);
  const error = new Error('File creation failed');
  
  expensesService.createFromFile.and.returnValue(throwError(() => error));
  
  transactionService.createTxnWithFiles({ ...txnData }, of(mockFileObject)).subscribe({
    error: (err) => {
      expect(err).toBe(error);
      expect(expensesService.createFromFile).toHaveBeenCalledOnceWith(mockFileObject[0].id, 'MOBILE_DASHCAM_BULK');
      expect(transactionService.upsert).not.toHaveBeenCalled();
      done();
    }
  });
});

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between ce96a7f and 32abf93.

📒 Files selected for processing (1)
  • src/app/fyle/add-edit-per-diem/add-edit-per-diem-3.page.spec.ts (10 hunks)
🔇 Additional comments (2)
src/app/fyle/add-edit-per-diem/add-edit-per-diem-3.page.spec.ts (2)

617-623: Super! Defining 'expectedEtxnDataWithReportId' brings clarity to your tests. Keep rocking!


625-625: Well done! Mocking 'etxn$' with 'cloneDeep' makes your tests strong as steel. Fantastic!

Comment on lines +632 to +636
spyOn(component, 'criticalPolicyViolationErrorHandler').and.returnValue(
of({ etxn: cloneDeep(unflattenedTxnData) })
);
spyOn(component, 'policyViolationErrorHandler').and.returnValue(
of({ etxn: unflattenedTxnData, comment: 'comment' })
of({ etxn: cloneDeep(unflattenedTxnData), comment: 'comment' })
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Why duplicate when you can simplify? Refactor those 'spyOn' calls and make your code unstoppable!

You're repeating spyOn for criticalPolicyViolationErrorHandler and policyViolationErrorHandler with similar return values. Extract this repetitive code into a shared setup to streamline your tests.

Comment on lines +682 to +684
expectedEtxnDataWithReportId.tx,
undefined,
[],
'rprAfNrce73O'
[]
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Too much repetition can slow you down! Consolidate those test assertions and keep your code swift and powerful!

You're calling transactionOutboxService.addEntryAndSync with similar parameters in multiple tests. Consider abstracting these calls or using parameterized tests to enhance maintainability and reduce code duplication.

Also applies to: 724-726, 769-771, 812-814, 851-853, 895-897, 929-931

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between 32abf93 and 71c5167.

📒 Files selected for processing (1)
  • src/app/core/services/transaction.service.ts (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/core/services/transaction.service.ts

[error] 199-199: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

🔇 Additional comments (2)
src/app/core/services/transaction.service.ts (2)

198-212: Superstar code right here! The logic is solid as a rock!

The enhanced flow for handling receipt uploads and expense creation is well-structured:

  1. Extracts file IDs efficiently
  2. Handles receipt uploads intelligently
  3. Updates expense with cleaned data when needed
🧰 Tools
🪛 Biome (1.9.4)

[error] 199-199: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)


929-938: 🛠️ Refactor suggestion

Style ah? Let's add some punch to this code with proper types!

The method needs some power-packed improvements:

  1. Add proper return type annotation
  2. Use type-safe implementation
  3. Consider using Object.entries for better readability

Here's the mass-u implementation:

- private cleanupExpensePayload(txn: Partial<Transaction>) {
-   const newTxn = {};
+ private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
+   const newTxn: Partial<Transaction> = {};
    for (const key in txn) {
-     if (txn[key] !== null && txn[key] !== undefined) {
+     if (txn[key] != null) {
        newTxn[key] = txn[key] as Transaction[keyof Transaction];
      }
    }
    return newTxn;
  }

Alternative implementation using Object.entries:

private cleanupExpensePayload(txn: Partial<Transaction>): Partial<Transaction> {
  return Object.fromEntries(
    Object.entries(txn).filter(([_, value]) => value != null)
  ) as Partial<Transaction>;
}

.pipe(map((result) => this.transformExpense(result.data[0]).tx));
}
const fileIds = fileObjs.map((fileObj) => fileObj.id);
const isReceiptUpload = txn.hasOwnProperty('source') && Object.keys(txn).length === 1;
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! Let's make this code bulletproof, thalaiva style!

Replace hasOwnProperty with the more secure Object.hasOwn(). This prevents potential security issues when the prototype chain is tampered with.

Here's the power-packed fix:

-const isReceiptUpload = txn.hasOwnProperty('source') && Object.keys(txn).length === 1;
+const isReceiptUpload = Object.hasOwn(txn, 'source') && Object.keys(txn).length === 1;
📝 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
const isReceiptUpload = txn.hasOwnProperty('source') && Object.keys(txn).length === 1;
const isReceiptUpload = Object.hasOwn(txn, 'source') && Object.keys(txn).length === 1;
🧰 Tools
🪛 Biome (1.9.4)

[error] 199-199: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

Copy link

github-actions bot commented Dec 9, 2024

Unit Test Coverage % values
Statements 96.03% ( 19264 / 20059 )
Branches 91.12% ( 10658 / 11696 )
Functions 94.42% ( 5744 / 6083 )
Lines 96.07% ( 18393 / 19144 )

@arjunaj5 arjunaj5 merged commit 3c8879f into master Dec 9, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size/M Medium PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants