diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f4f6a90ae6db..1446f1e4d851 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -94,7 +94,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] I followed the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md) - [ ] I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like `Avatar`, I verified the components using `Avatar` are working as expected) - [ ] I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests) -- [ ] I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such +- [ ] I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such - [ ] I verified that if a function's arguments changed that all usages have also been updated correctly - [ ] If any new file was added I verified that: - [ ] The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory @@ -109,6 +109,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] I verified that all the inputs inside a form are aligned with each other. - [ ] I added `Design` label and/or tagged `@Expensify/design` so the design team can review the changes. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. +- [ ] I added [unit tests](https://github.com/Expensify/App/blob/main/tests/README.md) for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow. - [ ] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps. ### Screenshots/Videos diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1ceb12a30af5..2cacdf557560 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -114,51 +114,6 @@ jobs: env: BROWSERSTACK: ${{ secrets.BROWSERSTACK }} - submitAndroid: - name: Submit Android app for production review - needs: prep - if: ${{ github.ref == 'refs/heads/production' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Ruby - uses: ruby/setup-ruby@v1.190.0 - with: - bundler-cache: true - - - name: Get Android native version - id: getAndroidVersion - run: echo "VERSION_CODE=$(grep -o 'versionCode\s\+[0-9]\+' android/app/build.gradle | awk '{ print $2 }')" >> "$GITHUB_OUTPUT" - - - name: Decrypt json w/ Google Play credentials - run: gpg --batch --yes --decrypt --passphrase="${{ secrets.LARGE_SECRET_PASSPHRASE }}" --output android-fastlane-json-key.json android-fastlane-json-key.json.gpg - working-directory: android/app - - - name: Submit Android build for review - run: bundle exec fastlane android upload_google_play_production - env: - VERSION: ${{ steps.getAndroidVersion.outputs.VERSION_CODE }} - - - name: Warn deployers if Android production deploy failed - if: ${{ failure() }} - uses: 8398a7/action-slack@v3 - with: - status: custom - custom_payload: | - { - channel: '#deployer', - attachments: [{ - color: "#DB4545", - pretext: ``, - text: `💥 Android production deploy failed. Please manually submit ${{ needs.prep.outputs.APP_VERSION }} in the . 💥`, - }] - } - env: - GITHUB_TOKEN: ${{ github.token }} - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} - android_hybrid: name: Build and deploy Android HybridApp needs: prep @@ -431,12 +386,6 @@ jobs: APPLE_DEMO_EMAIL: ${{ secrets.APPLE_DEMO_EMAIL }} APPLE_DEMO_PASSWORD: ${{ secrets.APPLE_DEMO_PASSWORD }} - - name: Submit build for App Store review - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane ios submit_for_review - env: - VERSION: ${{ steps.getIOSVersion.outputs.IOS_VERSION }} - - name: Upload iOS build to Browser Stack if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@/Users/runner/work/App/App/New Expensify.ipa" @@ -730,7 +679,7 @@ jobs: name: Post a Slack message when any platform fails to build or deploy runs-on: ubuntu-latest if: ${{ failure() }} - needs: [buildAndroid, uploadAndroid, submitAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web] + needs: [buildAndroid, uploadAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web] steps: - name: Checkout uses: actions/checkout@v4 @@ -745,21 +694,15 @@ jobs: outputs: IS_AT_LEAST_ONE_PLATFORM_DEPLOYED: ${{ steps.checkDeploymentSuccessOnAtLeastOnePlatform.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED }} IS_ALL_PLATFORMS_DEPLOYED: ${{ steps.checkDeploymentSuccessOnAllPlatforms.outputs.IS_ALL_PLATFORMS_DEPLOYED }} - needs: [buildAndroid, uploadAndroid, submitAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web] + needs: [buildAndroid, uploadAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web] if: ${{ always() }} steps: - name: Check deployment success on at least one platform id: checkDeploymentSuccessOnAtLeastOnePlatform run: | isAtLeastOnePlatformDeployed="false" - if [ ${{ github.ref }} == 'refs/heads/production' ]; then - if [ "${{ needs.submitAndroid.result }}" == "success" ]; then - isAtLeastOnePlatformDeployed="true" - fi - else - if [ "${{ needs.uploadAndroid.result }}" == "success" ]; then - isAtLeastOnePlatformDeployed="true" - fi + if [ "${{ needs.uploadAndroid.result }}" == "success" ]; then + isAtLeastOnePlatformDeployed="true" fi if [ "${{ needs.iOS.result }}" == "success" ] || \ @@ -784,14 +727,8 @@ jobs: isAllPlatformsDeployed="true" fi - if [ ${{ github.ref }} == 'refs/heads/production' ]; then - if [ "${{ needs.submitAndroid.result }}" != "success" ]; then - isAllPlatformsDeployed="false" - fi - else - if [ "${{ needs.uploadAndroid.result }}" != "success" ]; then - isAllPlatformsDeployed="false" - fi + if [ "${{ needs.uploadAndroid.result }}" != "success" ]; then + isAllPlatformsDeployed="false" fi echo "IS_ALL_PLATFORMS_DEPLOYED=$isAllPlatformsDeployed" >> "$GITHUB_OUTPUT" @@ -939,7 +876,7 @@ jobs: name: Post a Slack message when all platforms deploy successfully runs-on: ubuntu-latest if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_ALL_PLATFORMS_DEPLOYED) }} - needs: [prep, buildAndroid, uploadAndroid, submitAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] + needs: [prep, buildAndroid, uploadAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] steps: - name: 'Announces the deploy in the #announce Slack room' uses: 8398a7/action-slack@v3 @@ -993,11 +930,11 @@ jobs: postGithubComments: uses: ./.github/workflows/postDeployComments.yml if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} - needs: [prep, buildAndroid, uploadAndroid, submitAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] + needs: [prep, buildAndroid, uploadAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] with: version: ${{ needs.prep.outputs.APP_VERSION }} env: ${{ github.ref == 'refs/heads/production' && 'production' || 'staging' }} - android: ${{ github.ref == 'refs/heads/production' && needs.submitAndroid.result || needs.uploadAndroid.result }} + android: ${{ github.ref == 'refs/heads/production' && needs.uploadAndroid.result }} android_hybrid: ${{ needs.android_hybrid.result }} ios: ${{ needs.iOS.result }} ios_hybrid: ${{ needs.iOS_hybrid.result }} diff --git a/README.md b/README.md index 6b75fbed1b2c..9f73a0012bef 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,7 @@ Often times in order to write a unit test, you may need to mock data, a componen to help run our Unit tests. * To run the **Jest unit tests**: `npm run test` +* UI tests guidelines can be found [here](tests/ui/README.md) ## Performance tests We use Reassure for monitoring performance regression. More detailed information can be found [here](tests/perf-test/README.md): diff --git a/android/app/build.gradle b/android/app/build.gradle index ce5927fc2ad9..901ef0ccbbbf 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009006504 - versionName "9.0.65-4" + versionCode 1009006701 + versionName "9.0.67-1" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.kt b/android/app/src/main/java/com/expensify/chat/MainApplication.kt index 942304c80445..ec3ac41c76c4 100644 --- a/android/app/src/main/java/com/expensify/chat/MainApplication.kt +++ b/android/app/src/main/java/com/expensify/chat/MainApplication.kt @@ -8,6 +8,7 @@ import android.database.CursorWindow import android.os.Process import androidx.multidex.MultiDexApplication import com.expensify.chat.bootsplash.BootSplashPackage +import com.expensify.chat.navbar.NavBarManagerPackage import com.expensify.chat.shortcutManagerModule.ShortcutManagerPackage import com.facebook.react.PackageList import com.facebook.react.ReactApplication @@ -36,6 +37,7 @@ class MainApplication : MultiDexApplication(), ReactApplication { add(BootSplashPackage()) add(ExpensifyAppPackage()) add(RNTextInputResetPackage()) + add(NavBarManagerPackage()) } override fun getJSMainModuleName() = ".expo/.virtual-metro-entry" diff --git a/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerModule.kt b/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerModule.kt new file mode 100644 index 000000000000..5c566df606eb --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerModule.kt @@ -0,0 +1,27 @@ +package com.expensify.chat.navbar + +import androidx.core.view.WindowInsetsControllerCompat +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.UiThreadUtil; + +class NavBarManagerModule( + private val mReactContext: ReactApplicationContext, +) : ReactContextBaseJavaModule(mReactContext) { + override fun getName(): String = "RNNavBarManager" + + @ReactMethod + fun setButtonStyle(style: String) { + UiThreadUtil.runOnUiThread { + mReactContext.currentActivity?.window?.let { + WindowInsetsControllerCompat(it, it.decorView).let { controller -> + when (style) { + "light" -> controller.isAppearanceLightNavigationBars = false + "dark" -> controller.isAppearanceLightNavigationBars = true + } + } + } + } + } +} diff --git a/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerPackage.kt b/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerPackage.kt new file mode 100644 index 000000000000..33ee64d17769 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerPackage.kt @@ -0,0 +1,18 @@ +package com.expensify.chat.navbar + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class NavBarManagerPackage : ReactPackage { + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return emptyList() + } + + override fun createNativeModules(reactContext: ReactApplicationContext): List { + val modules: MutableList = ArrayList() + modules.add(NavBarManagerModule(reactContext)) + return modules + } +} diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 75126afbd407..42da35d7a493 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -7,6 +7,8 @@ diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index 0a9417820190..14b571308bb5 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -9,9 +9,10 @@ You can create as many accounts as needed in order to test your changes directly **Notes**: -1. When testing chat functionality in the app please do this between accounts you or your fellow contributors own - **do not test chatting with Concierge**, as this diverts to our customer support team. Thank you. -2. A member of our customer onboarding team gets auto-assigned to every new policy created by a non-paying account to help them set up. Please **do not interact with these teams, ask for calls, or support on your issues.** If you do need to test functionality inside the defaultRooms (#admins & #announce) for any issues you’re working on, please let them know that you are a contributor and don’t need assistance. They will proceed to ignore the chat. -3. Please **do not post in any Expensify owned public room for testing** (e.g #exfy-roadmap, #new-expensify-feedback). These rooms include real customers and investors. You can create your own public rooms, or [use this test public room](https://staging.new.expensify.com/r/2091104345528462) on either staging or production. Thanks! +1. When creating test accounts, include a `+` (plus sign) in the email address (e.g., matt+1@gmail.com). This marks the account and their associated workspaces as test accounts in Expensify, ensuring Expensify Guides are not assigned to help with account setup. +2. When testing chat functionality in the app please do this between accounts you or your fellow contributors own - **do not test chatting with Concierge**, as this diverts to our customer support team. Thank you. +3. A member of our customer onboarding team gets auto-assigned to every new policy created by a non-paying account to help them set up. Please **do not interact with these teams, ask for calls, or support on your issues.** If you do need to test functionality inside the defaultRooms (#admins & #announce) for any issues you’re working on, please let them know that you are a contributor and don’t need assistance. They will proceed to ignore the chat. +4. Please **do not post in any Expensify owned public room for testing** (e.g #exfy-roadmap, #new-expensify-feedback). These rooms include real customers and investors. You can create your own public rooms, or [use this test public room](https://staging.new.expensify.com/r/2091104345528462) on either staging or production. Thanks! #### Generating Multiple Test Accounts You can generate multiple test accounts by using a `+` postfix, for example if your email is test@test.com, you can create multiple New Expensify accounts connected to the same email address by using test+123@test.com, test+456@test.com, etc. diff --git a/contributingGuides/PROPOSAL_TEMPLATE.md b/contributingGuides/PROPOSAL_TEMPLATE.md index 8c9fa7968fe2..d5ab0bf4a864 100644 --- a/contributingGuides/PROPOSAL_TEMPLATE.md +++ b/contributingGuides/PROPOSAL_TEMPLATE.md @@ -7,6 +7,9 @@ ### What changes do you think we should make in order to solve the problem? +### What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future? + + ### What alternative solutions did you explore? (Optional) **Reminder:** Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job. diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index 5fc14328f3b4..545c79a95af1 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -30,7 +30,7 @@ - [ ] I verified that this PR follows the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md) - [ ] I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like `Avatar`, I verified the components using `Avatar` have been tested & I retested again) - [ ] I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests) -- [ ] I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such +- [ ] I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such - [ ] If a new component is created I verified that: - [ ] A similar component doesn't exist in the codebase - [ ] All props are defined accurately and each prop has a `/** comment above it */` @@ -54,6 +54,7 @@ - [ ] I verified that all the inputs inside a form are aligned with each other. - [ ] I added `Design` label and/or tagged `@Expensify/design` so the design team can review the changes. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. +- [ ] For any bug fix or new feature in this PR, I verified that sufficient [unit tests](https://github.com/Expensify/App/blob/main/tests/README.md) are included to prevent regressions in this flow. - [ ] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps. - [ ] I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR. diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md deleted file mode 100644 index b231984f61e2..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Create and Pay Bills -description: Expensify bill management and payment methods. ---- -Streamline your operations by receiving and paying vendor or supplier bills directly in Expensify. Vendors can send bills even if they don't have an Expensify account, and you can manage payments seamlessly. - -# Receive Bills in Expensify -You can receive bills in three ways: -- Directly from Vendors: Provide your Expensify billing email to vendors. -- Forwarding Emails: Forward bills received in your email to Expensify. -- Manual Upload: For physical bills, create a Bill in Expensify from the Reports page. - -# Bill Pay Workflow -1. When a vendor or supplier sends a bill to Expensify, the document is automatically SmartScanned, and a Bill is created. This Bill is managed by the primary domain contact, who can view it on the Reports page within their default group workspace. - -2. Once the Bill is ready for processing, it follows the established approval workflow. As each person approves it, the Bill appears in the next approver’s Inbox. The final approver will pay the Bill using one of the available payment methods. - -3. During this process, the Bill is coded with the appropriate GL codes from your connected accounting software. After completing the approval workflow, the Bill can be exported back to your accounting system. - -# Payment Methods -There are multiple ways to pay Bills in Expensify. Let’s go over each method below. - -## ACH bank-to-bank transfer - -To use this payment method, you must have a [business bank account connected to your Expensify account](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account). - -**To pay with an ACH bank-to-bank transfer:** -1. Sign in to your [Expensify web account](www.expensify.com). -2. Go to the Home or Reports page and locate the Bill that needs to be paid. -3. Click the Pay button to be redirected to the Bill. -4. Choose the ACH option from the drop-down list. - -**Fees:** None - -## Credit or Debit Card -This option is available to all US and International customers receiving a bill from a US vendor with a US business bank account. - -**To pay with a credit or debit card:** -1. Sign in to your [Expensify web account](www.expensify.com). -2. Click on the Bill you’d like to pay to see the details. -3. Click the Pay button. -4. Enter your credit card or debit card details. - -**Fees:** 2.9% of the total amount paid. - -## Venmo -If both you and the vendor must have Venmo connected to Expensify, you can pay the bill by following the steps outlined [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Third-Party-Payments#setting-up-third-party-payments). - -**Fees:** Venmo charges a 3% sender’s fee. - - -## Pay outside of Expensify -If you are unable to pay using one of the above methods, you can still mark the Bill as paid. This will update its status to indicate that the payment was made outside Expensify. - -**To mark a Bill as paid outside of Expensify:** -1. Sign in to your [Expensify web account](www.expensify.com). -2. Click on the Bill you’d like to pay to see the details. -3. Click the Reimburse button. -4. Choose **I’ll do it manually**. - -**Fees:** None. - -{% include faq-begin.md %} - -## Who receives vendor bills in Expensify? -Bills are sent to the Primary Contact listed under **Settings > Domains > [Domain Name] > Domain Admins**. - -## Who can view and pay a Bill? -Only the primary domain contact can view and pay a Bill. - -## How can others access Bills? -The primary contact can share Bills or grant Copilot access for others to manage payments. - -## Is Bill Pay supported internationally? -Currently, payments are only supported in USD. - -## What's the difference between a Bill and an Invoice in Expensify? -A Bill represents a payable amount owed to a vendor, while an Invoice is a receivable amount owed to you. -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-and-Pay-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-and-Pay-Bills.md new file mode 100644 index 000000000000..328b7f2051bc --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-and-Pay-Bills.md @@ -0,0 +1,111 @@ +--- +title: Receive and Pay Bills +description: Expensify bill management and payment methods. +--- + +Easily receive and pay vendor or supplier bills directly in Expensify. Your vendors don’t even need an Expensify account! Manage everything seamlessly in one place. + +# Receiving Bills + +Expensify makes it easy to receive bills in three simple ways: + +### 1. Directly from Vendors +Share your Expensify billing email with vendors to receive bills automatically. + +- Set a Primary Contact under **Settings > Domains > Domain Admins**. +- Ask vendors to email bills to your billing address: `domainname@expensify.cash` (e.g., for *expensify.com*, use `expensify@expensify.cash`). +- Once emailed, the bill is automatically created in Expensify, ready for payment. + +### 2. Forwarding Emails +Received a bill in your email? Forward it to Expensify. + +- Ensure your Primary Contact is set under **Settings > Domains > Domain Admins**. +- Forward bills to `domainname@expensify.cash`. Example: `domainname@expensify.cash` (e.g., for *expensify.com*, use `expensify@expensify.cash`). +- Expensify will create a bill automatically, ready for payment. + +### 3. Manual Upload +Got a paper bill? Create a bill manually in [Expensify](https://www.expensify.com/): + +1. Log in to [Expensify](https://www.expensify.com). +2. Go to **Reports > New Report > Bill**. +3. Enter the invoice details: sender’s email, merchant name, amount, and date. +4. Upload the invoice as a receipt. + + +# Paying Bills in Expensify + +Expensify makes it easy to manage and pay vendor bills with a straightforward workflow and flexible payment options. Here’s how it works: + +## Bill Pay Workflow + +1. **SmartScan & Create**: When a vendor sends a bill, Expensify automatically SmartScans the document and creates a bill. +2. **Submission to Primary Contact**: The bill is submitted to the primary contact, who can review it on the Reports page under their default group policy. +3. **Communication**: If the approver needs clarification, they can communicate directly with the sender via the invoice linked to the bill. +4. **Approval Workflow**: Once reviewed, the bill follows your workspace’s approval process. The final approver handles the payment. +5. **Accounting Integration**: During approval, the bill is coded with the correct GL codes from your connected accounting software. Once approved, it can be exported back to your accounting system. + +## Payment Methods + +Expensify offers several ways to pay bills. Choose the method that works best for you: + +### 1. ACH Bank-to-Bank Transfer + +Fast and fee-free, this method requires a connected [business bank account](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account). + +**How to Pay via ACH:** +1. Log in to your [Expensify web account](https://www.expensify.com/). +2. Find the bill on the Home or Reports page. +3. Click **Pay** and select the ACH option. + +**Fees:** None. + +--- + +### 2. Credit or Debit Card + +Pay vendors using a credit or debit card. This option is available for US and international customers paying US vendors with a US business bank account. + +**How to Pay with a Card:** +1. Log in to your [Expensify web account](https://www.expensify.com/). +2. Open the bill details and click **Pay**. +3. Enter your card information to complete the payment. + +**Fees:** 2.9% of the total amount paid. + +--- + +### 3. Venmo + +If both you and the vendor have Venmo accounts connected to Expensify, you can pay through Venmo. Learn how to set up Venmo [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Third-Party-Payments#setting-up-third-party-payments). + +**Fees:** Venmo charges a 3% sender’s fee. + +--- + +### 4. Pay Outside Expensify + +If you prefer to pay outside Expensify, you can still track the payment within the platform. + +**How to Mark as Paid Outside Expensify:** +1. Log in to your [Expensify web account](https://www.expensify.com/). +2. Open the bill details and click **Pay**. +3. Select **Mark as Paid** to update its status. + +**Fees:** None. +{% include faq-begin.md %} + +## Who receives vendor bills in Expensify? +bills are sent to the Primary Contact listed under **Settings > Domains > [Domain Name] > Domain Admins**. + +## Who can view and pay a bill? +Only the primary domain contact can view and pay a bill. + +## How can others access bills? +The primary contact can share bills or grant Copilot access for others to manage payments. + +## Is bill Pay supported internationally? +Currently, payments are only supported in USD. + +## What's the difference between a bill and an Invoice in Expensify? +A bill represents a payable amount owed to a vendor, while an Invoice is a receivable amount owed to you. +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/domains/Claim-And-Verify-A-Domain.md b/docs/articles/expensify-classic/domains/Claim-And-Verify-A-Domain.md index ed74224c622e..ecb0b938aa8e 100644 --- a/docs/articles/expensify-classic/domains/Claim-And-Verify-A-Domain.md +++ b/docs/articles/expensify-classic/domains/Claim-And-Verify-A-Domain.md @@ -46,6 +46,6 @@ After successful verification, an email will be sent to all members of the Expen # Add another domain -To add an additional domain, you’ll have to first add your email address that is connected with your domain as your [primary or secondary email] (https://help.expensify.com/articles/expensify-classic/settings/account-settings/Change-or-add-email-address) (for example, if your domain is yourcompany.com, then you want to add and verify your email address @yourcompany.com as your primary or secondary email address). Then you can complete the steps above to add the domain. +To add an additional domain, you’ll have to first add your email address that is connected with your domain as your [primary or secondary email](https://help.expensify.com/articles/expensify-classic/settings/account-settings/Change-or-add-email-address) (for example, if your domain is yourcompany.com, then you want to add and verify your email address @yourcompany.com as your primary or secondary email address). Then you can complete the steps above to add the domain. diff --git a/docs/articles/expensify-classic/expenses/Apply-Tax.md b/docs/articles/expensify-classic/expenses/Apply-Tax.md index c89176bcc0e8..9360962cb2ba 100644 --- a/docs/articles/expensify-classic/expenses/Apply-Tax.md +++ b/docs/articles/expensify-classic/expenses/Apply-Tax.md @@ -28,6 +28,21 @@ To handle these, you can create a single tax that combines both taxes into a sin From the Reports page, you can select Reports and then click **Export To > Tax Report** to generate a CSV containing all the expense information, including the split-out taxes. +## Why is the tax amount different than I expect? + +In Expensify, tax is *inclusive*, meaning it's already part of the total amount shown. + +To determine the inclusive tax from a total price that already includes tax, you can use the following formula: + +### **Tax amount = (Total price x Tax rate) ÷ (1 + Tax Rate)** + +For example, if an item costs $100 and the tax rate is 20%: +Tax amount = (**$100** x .20) ÷ (1 + .**20**) = **$16.67** +This means the tax amount $16.67 is included in the total. + +If you are simply trying to calculate the price before tax, you can use the formula: + +### **Price before tax = (Total price) ÷ (1 + Tax rate)** # Deep Dive diff --git a/docs/articles/expensify-classic/settings/Change-or-add-email-address.md b/docs/articles/expensify-classic/settings/Change-or-add-email-address.md index 754b9a7f9ac0..f6fe3d8e13b4 100644 --- a/docs/articles/expensify-classic/settings/Change-or-add-email-address.md +++ b/docs/articles/expensify-classic/settings/Change-or-add-email-address.md @@ -12,13 +12,34 @@ The primary email address on your Expensify account is the email that receives e Before you can remove a primary email address, you must add a new one to your Expensify account and make it the primary using the steps below. Email addresses must be added as a secondary login before they can be made the primary. {% include end-info.html %} +# Adding a new Secondary Login *Note: This process is currently not available from the mobile app and must be completed from the Expensify website.* 1. Hover over Settings, then click **Account**. -2. Under the Account Details tab, scroll down to the Secondary Logins section and click **Add Secondary Login**. +2. Under the Account Details > Secondary Logins > click **Add Secondary Login**. 3. Enter the email address or phone number you wish to use as a secondary login. For phone numbers, be sure to include the international code, if applicable. 4. Find the email or text message from Expensify containing the Magic Code and enter it into the field. -5. To make the new email address the primary address for your account, click **Make Primary**. + +# Changing your Primary Login +If you already have multiple email addresses linked to your account, you can change which one is listed as the Primary Login. + +1. Settings > Account > Secondary Logins. +2. Click **Make Primary** next to the email address you want to appear on your account. You can keep both logins, or you can click **Remove** next to the old email address to delete it from your account. + +# Unlinking an email from your old account +If you at one point added your personal email address as a Secondary Login to your account, and then the account was closed - for example if you had a company account and then left the company - you may want to unlink your personal email to use it with a new Expensify account. You can do this with the following steps: + +1. Navigate to the sign in page at expensify.com. +2. Enter your personal email address into the email field. +3. Click **Unlink Accounts**. +4. You will recieve a verification email to complete the unlinking of your personal address. + +# FAQ +**What does changing the primary login do?** +When you change your primary login this will update the email address that appears on your reports (old and new), in workspace account settings, and on your account. + +**Can I have multiple Seconary Logins?** +Yes, you can have an unlimited number of logins attached to your account. diff --git a/docs/articles/new-expensify/connections/netsuite/Netsuite-Troubleshooting.md b/docs/articles/new-expensify/connections/netsuite/Netsuite-Troubleshooting.md index 2ac1aaadbef4..15a74cf925fa 100644 --- a/docs/articles/new-expensify/connections/netsuite/Netsuite-Troubleshooting.md +++ b/docs/articles/new-expensify/connections/netsuite/Netsuite-Troubleshooting.md @@ -1,6 +1,442 @@ --- title: Netsuite Troubleshooting -description: Coming soon +description: Troubleshoot common NetSuite sync and export errors. --- -# Coming soon +Synchronizing and exporting data between Expensify and NetSuite can streamline your financial processes, but occasionally, users may encounter errors that prevent a smooth integration. These errors often arise from discrepancies in settings, missing data, or configuration issues within NetSuite or Expensify. + +This troubleshooting guide aims to help you identify and resolve common sync and export errors, ensuring a seamless connection between your financial management systems. By following the step-by-step solutions provided for each specific error, you can quickly address issues and maintain accurate and efficient expense reporting and data management. + +# ExpensiError NS0005: Please enter value(s) for Department, Location or Class + +**Why does this happen?** + +This error occurs when the classification (like Location) is required at the header level of your transaction form in NetSuite. + +For expense reports and journal entries, NetSuite uses classifications from the employee record default. Expensify only exports this information at the line item level. + +For vendor bills, these classifications can't be mandatory because we use the vendor record instead of the employee record, and vendor records don’t have default classifications. + +## How to fix it for vendor bills + +Note: When exporting as a Vendor Bill, we pull from the vendor record, not the employee. Therefore, employee defaults don’t apply at the header ("main") level. This error appears if your NetSuite transaction form requires those fields. + +1. Go to **Customization > Forms > Transaction Forms**. +2. Click **"Edit"** on your preferred vendor bill form. +3. Go to **Screen Fields > Main**. +4. Uncheck both **"Show"** and **"Mandatory"** for the listed fields in your error message. +5. Sync the NetSuite connection in Expensify (**Settings > Workspaces > Workspace Name > Accounting > three-dot menu > Sync Now**.) +6. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +## How to fix it for journal entries and expense reports + +Note: If you see this error when exporting a Journal Entry or Expense Report, it might be because the report submitter doesn’t have default settings for Departments, Classes, or Locations. + +1. Go to **Lists > Employees** in NetSuite. +2. Click **"Edit"** next to the employee's name who submitted the report. +3. Scroll down to the **Classification** section. +4. Select a default **Department**, **Class**, and **Location** for the employee. +5. Click **Save**. +6. Sync the NetSuite connection in Expensify (**Settings > Workspaces > Workspace Name > Accounting > three-dot menu > Sync Now**.) +7. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + + +# ExpensiError NS0012: Currency Does Not Exist In NetSuite + +**Why does this happen? (scenario 1)** + +When dealing with foreign transactions, Expensify sends the conversion rate and currency of the original expense to NetSuite. If the currency isn't listed in your NetSuite subsidiary, you'll see an error message saying the currency does not exist in NetSuite. + +## How to fix it + +1. Ensure the currency in Expensify matches what's in your NetSuite subsidiary. +2. If you see an error saying 'The currency X does not exist in NetSuite', re-sync your connection to NetSuite through the workspace admin section in Expensify. +3. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +**Why does this happen? (scenario 2)** + +This error can happen if you’re using a non-OneWorld NetSuite instance and exporting a currency other than EUR, GBP, USD, or CAD. + +## How to fix it + +1. Head to NetSuite. +2. Go to **Setup > Enable Features**. +3. Check the **Multiple Currencies** box. + +Once you've done this, you can add the offending currency by searching **New Currencies** in the NetSuite global search. + +# ExpensiError NS0021: Invalid tax code reference key + +**Why does this happen?** + +This error usually indicates an issue with the Tax Group settings in NetSuite, which can arise from several sources. + +## How to fix it + +If a Tax Code on Sales Transactions is mapped to a Tax Group, an error will occur. To fix this, the Tax Code must be mapped to a Tax Code on Purchase Transactions instead. + +To verify if a Tax Code is for Sales or Purchase transactions, view the relevant Tax Code(s). + +**For Australian Taxes:** + +Ensure your Tax Groups are mapped correctly: +- **GST 10%** to **NCT-AU** (not the Sales Transaction Tax Code TS-AU) +- **No GST 0%** to **NCF-AU** (not the Sales Transaction Tax Code TFS-AU) + +### Tax Group Type +Tax Groups can represent different types of taxes. For compatibility with Expensify, ensure the tax type is set to GST/VAT. + +### Enable Tax Groups +Some subsidiaries require you to enable Tax Groups. Go to **Set Up Taxes** for the subsidiary's country and ensure the Tax Code lists include both Tax Codes and Tax Groups. + +# ExpensiError NS0023: Employee Does Not Exist in NetSuite (Invalid Employee) + +**Why does this happen?** + +This can happen if the employee’s subsidiary in NetSuite doesn’t match the subsidiary selected for the connection in Expensify. + +## How to fix it + +1. **Check the Employee's Subsidiary** + - Go to the employee record in NetSuite. + - Confirm the employee's subsidiary matches what’s listed as the subsidiary at the workspace level. + - To find this in Expensify navigate to **Settings > Workspaces > click workspace name > Accounting > Subsidiary**. + - If the subsidiaries don’t match, update the subsidiary in Expensify to match what’s listed in NetSuite. + - Sync the NetSuite connection in Expensify (**Settings > Workspaces > click workspace name > Accounting > three-dot menu > Sync Now**.) +2. **Verify Access Restrictions:** + - Go to **Lists > Employees > Employees > [Select Employee] > Edit > Access**. + - Uncheck **Restrict Access to Expensify**. +3. **Additional Checks:** + - Ensure the email on the employee record in NetSuite matches the email address of the report submitter in Expensify. + - In NetSuite, make sure the employee's hire date is in the past and/or the termination date is in the future. +4. **Currency Match for Journal Entries:** + - If exporting as Journal Entries, ensure the currency for the NetSuite employee record, NetSuite subsidiary, and Expensify workspace all match. + - In NetSuite, go to the **Human Resources** tab > **Expense Report Currencies**, and add the subsidiary/policy currency if necessary. + +# ExpensiError NS0085: Expense Does Not Have Appropriate Permissions for Settings an Exchange Rate in NetSuite + +**Why does this happen?** + +This error occurs when the exchange rate settings in NetSuite aren't updated correctly. + +## How to fix it + +1. In NetSuite, go to Customization > Forms > Transaction Forms. +2. Search for the form type that the report is being exported as (Expense Report, Journal Entry, or Vendor Bill) and click Edit next to the form that has the Preferred checkbox checked. + - **For Expense Reports:** + - Go to Screen Fields > Expenses (the Expenses tab farthest to the right). + - Ensure the Exchange Rate field under the Description column has the Show checkbox checked. + - **For Vendor Bills:** + - Go to Screen Fields > Main. + - Ensure the Exchange Rate field under the Description column has the Show checkbox checked. + - **For Journal Entries:** + - Go to Screen Fields > Lines. + - Ensure the Exchange Rate field under the Description column has the Show checkbox checked. + - Go to Screen Fields > Main and ensure the Show checkbox is checked in the Exchange Rate field under the Description column. +3. Sync the NetSuite connection in Expensify (**Settings > Workspaces > Workspace Name > Accounting > three-dot menu > Sync Now**.) +4. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +# ExpensiError NS0079: The Transaction Date is Not Within the Date Range of Your Accounting Period + +**Why does this happen?** + +The transaction date you specified is not within the date range of your accounting period. When the posting period settings in NetSuite are not configured to allow a transaction date outside the posting period, you can't export a report to the next open period, which is why you’ll run into this error. + +## How to fix it + +1. In NetSuite, navigate to Setup > Accounting > Accounting Preferences. +2. Under the General Ledger section, ensure the field Allow Transaction Date Outside of the Posting Period is set to Warn. +3. Then, choose whether to export your reports to the First Open Period or the Current Period. + +**Additionally, ensure the Export to Next Open Period feature is enabled within Expensify:** +1. Navigate to **Settings > Workspaces > Workspace Name > Accounting > Export. +2. Scroll down and confirm that the toggle for **Export to next open period** is enabled. + +If any configuration settings are updated on the NetSuite connection, be sure to sync the connection before trying the export again. + +# ExpensiError NS0055: The Vendor You are Trying to Export to Does Not Have Access to the Currency X + +**Why does this happen?** + +This error occurs when a vendor tied to a report in Expensify does not have access to a currency on the report in NetSuite. The vendor used in NetSuite depends on the type of expenses on the report you're exporting. + +- For **reimbursable** (out-of-pocket) expenses, this is the employee who submitted the report. +- For **non-reimbursable** (e.g., company card) expenses, this is the default vendor set via the Settings > Workspaces > click workspace name > Accounting > Export settings. + +## How to fix it + +To fix this, the vendor needs to be given access to the applicable currency: +1. In NetSuite, navigate to Lists > Relationships > Vendors to access the list of Vendors. +2. Click Edit next to the Vendor tied to the report: + - For reimbursable (out-of-pocket) expenses, this is the report's submitter. + - For non-reimbursable (e.g., company card) expenses, this is the default vendor set via **Settings > Workspaces > click workspace name > Accounting > Export > click Export company card expenses as > Default vendor.** +3. Navigate to the Financial tab. +4. Scroll down to the Currencies section and add all the currencies that are on the report you are trying to export. +5. Click Save. + +# ExpensiError NS0068: You do not have permission to set a value for element - “Created From” + +**Why does this happen?** + +This error typically occurs due to insufficient permissions or misconfigured settings in NetSuite on the preferred transaction form for your export type. + +## How to fix it + +1. In NetSuite, go to Customization > Forms > Transaction Forms. +2. Search for the form type that the report is being exported as in NetSuite (Expense Report, Journal Entry, Vendor Bill, or if the report total is negative, Vendor Credit). +3. Click Edit next to the form that has the Preferred checkbox checked. +4. Go to Screen Fields > Main and ensure the field Created From has the Show checkbox checked. +5. Sync the NetSuite connection in Expensify (**Settings > Workspaces > Workspace Name > Accounting > three-dot menu > Sync Now**.) +6. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +## ExpensiError NS0068: Reports with Expensify Card expenses + +**Why does this happen?** + +Expensify Card expenses export as Journal Entries. If you encounter this error when exporting a report with Expensify Card non-reimbursable expenses, ensure the field Created From has the Show checkbox checked for Journal Entries in NetSuite. + +## How to fix it +1. In NetSuite, go to Customization > Forms > Transaction Forms. +2. Click Edit next to the journal entry form that has the Preferred checkbox checked. +3. Ensure the field Created From has the Show checkbox checked. +4. Sync the NetSuite connection in Expensify (**Settings > Workspaces > Workspace Name > Accounting > three-dot menu > Sync Now**.) +5. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +# ExpensiError NS0037: You do not have permission to set a value for element - “Receipt URL” + +**Why does this happen?** + +This error typically occurs due to insufficient permissions or misconfigured settings in NetSuite on the preferred transaction form for your export type. + +## How to fix it + +1. In NetSuite, go to Customization > Forms > Transaction Forms. +2. Search for the form type that the report is being exported as in NetSuite (Expense Report, Journal Entry, or Vendor Bill). +3. Click Edit next to the form that has the Preferred checkbox checked. + - If the report is being exported as an Expense Report: + - Go to Screen Fields > Expenses (the Expenses tab farthest to the right). + - Ensure the field ReceiptURL has the Show checkbox checked. + - If the report is being exported as a Journal Entry: + - Go to Screen Fields > Lines. + - Ensure the field ReceiptURL has the Show checkbox checked. + - If the report is being exported as a Vendor Bill: + - Go to Screen Fields > Main. + - Ensure the field ReceiptURL has the Show checkbox checked. +4. Sync the NetSuite connection in Expensify (**Settings > Workspaces > click workspace name > Accounting > three-dot menu > Sync Now**.) +5. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +# ExpensiError NS0042: Error creating vendor - this entity already exists + +**Why does this happen?** + +This error occurs when a vendor record already exists in NetSuite, but Expensify is still attempting to create a new one. This typically means that Expensify cannot find the existing vendor during export. +- The vendor record already exists in NetSuite, but there may be discrepancies preventing Expensify from recognizing it. +- The email on the NetSuite vendor record does not match the email of the report submitter in Expensify. +- The vendor record might not be associated with the correct subsidiary in NetSuite. + +## How to fix it + +1. **Check Email Matching:** + - Ensure the email on the NetSuite vendor record matches the email of the report submitter in Expensify. + - If it doesn’t match update the existing vendor record in NetSuite to match the report submitter's email and name. + - If there is no email listed, add the email address of the report’s submitter to the existing vendor record in NetSuite. +2. **Check Subsidiary Association:** + - Ensure the vendor record is associated with the same subsidiary selected in the connection configurations + - You can review this under **Settings > Workspaces > click workspace name > Accounting > Subsidiary.** +3. **Automatic Vendor Creation:** + - If you want Expensify to automatically create vendors, ensure the "Auto-create employees/vendors" option is enabled under **Settings > Workspaces > click workspace name > Accounting > Advanced.** + - If appropriate, delete the existing vendor record in NetSuite to allow Expensify to create a new one. +4. After making the necessary changes, sync the NetSuite connection in Expensify (**Settings > Workspaces > click workspace name > Accounting > three-dot menu > Sync Now**.) +5. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +# ExpensiError NS0109: Failed to login to NetSuite, please verify your credentials + +**Why does this happen?** + +This error indicates a problem with the tokens created for the connection between Expensify and NetSuite. The error message will say, "Login Error. Please check your credentials." + +## How to fix it + +1. Review the [Connect to NetSuite](https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite) guide and follow steps 1 and 2 exactly as outlined. +2. If you're using an existing token and encounter a problem, you may need to create a new token. + +# ExpensiError NS0123 Login Error: Please make sure that the Expensify integration is enabled + +**Why does this happen?** + +This error indicates that the Expensify integration is not enabled in NetSuite. + +## How to fix it + +1. **Enable the Expensify Integration:** + - In NetSuite, navigate to Setup > Integrations > Manage Integrations. + - Ensure that the Expensify Integration is listed and that the State is Enabled. +2. **If you can't find the Expensify integration:** + - Click "Show Inactives" to see if Expensify is listed as inactive. + - If Expensify is listed, update its state to Enabled. +3. Once the Expensify integration is enabled, sync the NetSuite connection in Expensify (**Settings > Workspaces > Workspace Name > Accounting > three-dot menu > Sync Now**.) + +# ExpensiError NS0045: Expenses Not Categorized with a NetSuite Account + +**Why does this happen?** + +This happens when approved expenses are categorized with an option that didn’t import from NetSuite. For NetSuite to accept expense coding, it must first exits and be imported into Expensify from NetSuite. + +## How to fix it + +1. Log into NetSuite +2. Do a global search for the missing record. + - Ensure the expense category is active and correctly named. + - Ensure the category is associated with the correct subsidiary that the Expensify workspace is linked to. +3. Sync the NetSuite connection in Expensify (**Settings > Workspaces > click workspace name > Accounting > three-dot menu > Sync Now**.) +4. Go back to the report, click on the offending expense(s), and re-apply the category in question. +5. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + + +# ExpensiError NS0061: Please Enter Value(s) for: Tax Code + +**Why does this happen?** + +This error typically occurs when attempting to export expense reports to a Canadian subsidiary in NetSuite for the first time and/or if your subsidiary in NetSuite has Tax enabled. + +## How to fix it + +To fix this, you need to enable Tax in the NetSuite configuration settings. + +1. Go to **Settings > Workspaces > click workspace name > Accounting > Export**. + - Select a Journal Entry tax posting account if you plan on exporting any expenses with taxes. +2. Wait for the connection to sync, it will automatically do so after you make a change. +3. Attempt the export again. + +**Note:** Expenses created before Tax was enabled might need to have the newly imported taxes applied to them retroactively to be exported. + +# Error creating employee: Your current role does not have permission to access this record. + +**Why does this happen?** + +This error indicates that the credentials or role used to connect NetSuite to Expensify do not have the necessary permissions within NetSuite. You can find setup instructions for configuring permissions in NetSuite [here](https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite). + +## How to fix it + +1. If permissions are configured correctly, confirm the report submitter exists in the subsidiary set for the workspace connection and that their Expensify email address matches the email on the NetSuite Employee Record. +2. If the above is true, try toggling off _Auto create employees/vendors_ under the **Settings > Workspaces > Group > click workspace name > Accounting > Advanced tab of the NetSuite configuration window. +3. Sync the NetSuite connection in Expensify (**Settings > Workspaces > click workspace name > Accounting > three-dot menu > Sync Now**.) +4. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +# Elimination Settings for X Do Not Match + +**Why does this happen?** + +This error occurs when an Intercompany Payable account is set as the default in the Default Payable Account field in the NetSuite subsidiary preferences, and the Accounting Approval option is enabled for Expense Reports. + +## How to fix it + +Set the Default Payable Account for Expense Reports on each subsidiary in NetSuite to ensure the correct payable account is active. + +1. Navigate to Subsidiaries: + - Go to Setup > Company > Subsidiaries. +2. Edit Subsidiary Preferences: + - Click Edit for the desired subsidiary. + - Go to the Preferences tab. +3. Set Default Payable Account: + - Choose the preferred account for Default Payable Account for Expense Reports. + +Repeat these steps for each subsidiary to ensure the settings are correct, and then sync the NetSuite connection in Expensify (**Settings > Workspaces > click workspace name > Accounting > three-dot menu > Sync Now**.) + +# ExpensiError NS0046: Billable Expenses Not Coded with a NetSuite Customer or Billable Project + +**Why does this happen?** + +NetSuite requires billable expenses to be assigned to a Customer or a Project that is configured as billable to a Customer. If this is not set up correctly in NetSuite, this error can occur. + +## How to fix it + +1. Check the billable expenses and confirm that a Customer or Project tag is selected. +2. Make any necessary adjustments to the billable expense. +3. Attempt the export again by clicking on Search, then clicking the Approved (company card expenses) or Paid (reimbursable expenses) filter. +Click on the report in question and it will open in the right-hand panel. +Click on Export to NetSuite to try to export again. + +{% include faq-begin.md %} +## Why are reports exporting as _Accounting Approved_ instead of _Paid in Full_? + +**This can occur for two reasons:** +- Missing Locations, Classes, or Departments in the Bill Payment Form +- Incorrect Settings in Expensify Workspace Configuration + +**Missing Locations, Classes, or Departments in Bill Payment Form:** If locations, classes, or departments are required in your accounting classifications but are not marked as 'Show' on the preferred bill payment form, this error can occur, and you will need to update the bill payment form in NetSuite: + +1. Go to Customization > Forms > Transaction Forms. +2. Find your preferred (checkmarked) Bill Payment form. +3. Click Edit or Customize. +4. Under the Screen Fields > Main tab, check 'Show' near the department, class, and location options. + +**Incorrect Settings in Expensify Workspace Configuration:** To fix this, you'll want to confirm the NetSuite connection settings are set up correctly in Expensify: + +1. Head to **Settings > Workspaces > click workspace name > Accounting > Advanced.** +2. **Ensure the following settings are correct:** + - Sync Reimbursed Reports: Enabled and payment account chosen. + - Journal Entry Approval Level: Approved for Posting. + - A/P Approval Account: This must match the current account being used for bill payment. +3. **Verify A/P Approval Account:** + - To ensure the A/P Approval Account matches the account in NetSuite: + - Go to your bill/expense report causing the error. + - Click Make Payment. + - This account needs to match the account selected in your Expensify configuration. +4. **Check Expense Report List:** + - Make sure this is also the account selected on the expense report by looking at the expense report list. + +Following these steps will help ensure that reports are exported as "Paid in Full" instead of "Accounting Approved." + +## Why are reports exporting as _Pending Approval_? +If reports are exporting as "Pending Approval" instead of "Approved," you'll need to adjust the approval preferences in NetSuite. + +**Exporting as Journal Entries/Vendor Bills:** +1. In NetSuite, go to Setup > Accounting > Accounting Preferences. +2. On the **General** tab, uncheck **Require Approvals on Journal Entries**. +3. On the **Approval Routing** tab, uncheck Journal Entries/Vendor Bills to remove the approval requirement for Journal Entries created in NetSuite. + +**Note:** This change affects all Journal Entries, not just those created by Expensify. + +**Exporting as Expense Reports:** +1. In NetSuite, navigate to Setup > Company > Enable Features. +2. On the "Employee" tab, uncheck "Approval Routing" to remove the approval requirement for Expense Reports created in NetSuite. Please note that this setting also applies to purchase orders. + +## How do I Change the Default Payable Account for Reimbursable Expenses in NetSuite? + +NetSuite is set up with a default payable account that is credited each time reimbursable expenses are exported as Expense Reports to NetSuite (once approved by the supervisor and accounting). If you need to change this to credit a different account, follow the below steps: + +**For OneWorld Accounts:** +1. Navigate to Setup > Company > Subsidiaries in NetSuite. +2. Next to the subsidiary you want to update, click Edit. +3. Click the Preferences tab. +4. In the Default Payable Account for Expense Reports field, select the desired payable account. +5. Click Save. + +**For Non-OneWorld Accounts:** +1. Navigate to Setup > Accounting > Accounting Preferences in NetSuite. +2. Click the Time & Expenses tab. +3. Under the Expenses section, locate the Default Payable Account for Expense Reports field and choose the preferred account. +4. Click Save. + +{% include faq-end.md %} diff --git a/docs/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account.md b/docs/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account.md index 516497c9dce7..4a4ec72a671a 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account.md +++ b/docs/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account.md @@ -80,6 +80,9 @@ Wait until the end of the second business day. If you still don’t see them, pl Once that's all set, make sure to contact your account manager or concierge, and our team will be able to re-trigger those three test transactions! +## Is my data safe? + +We take security seriously. Our measures align with what banks use to protect sensitive financial data. We regularly test and update our security to stay ahead of any threats. Plus, we’re checked daily by McAfee for extra reassurance against hackers. You can verify our security strength below or on the McAfee SECURE site. Discover how Expensify safeguards your information [here](https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security). {% include faq-end.md %} diff --git a/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md b/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md index 38f1e0fdd466..2ae14e822a12 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md +++ b/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md @@ -25,7 +25,7 @@ When an expense is submitted to a workspace, your approver will receive an email # How to Create an Expense -# SmartScan a receipt +## SmartScan a receipt {% include selector.html values="desktop, mobile" %} @@ -55,7 +55,7 @@ When an expense is submitted to a workspace, your approver will receive an email You can also forward receipts to receipts@expensify.com using your primary or secondary email address. SmartScan will automatically extract all the details from the receipt and add them to your expenses. {% include end-info.html %} -# Manually add an expense +## Manually add an expense {% include selector.html values="desktop, mobile" %} @@ -83,7 +83,7 @@ You can also forward receipts to receipts@expensify.com using your primary or se {% include end-selector.html %} -# Create a distance expense +## Create a distance expense {% include selector.html values="desktop, mobile" %} @@ -115,6 +115,28 @@ You can also forward receipts to receipts@expensify.com using your primary or se {% include end-selector.html %} +# How to Delete an Expense + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop or WebApp" %} +1. Click **Search > Expenses** and locate your expense. +2. Click the checkbox next to the expense(s) you wish to delete. +3. Click **# selected** in the top right corner. +4. Choose **Delete**. +5. Confirm that you wish to delete it by clicking the red **Delete** button in the popup. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap **Search**. +2. Tap and hold on the expense you wish to delete. +3. Tap **# selected**. +4. Tap **Delete**. +5. Confirm that you wish to delete it by clicking the red **Delete** button in the popup. +{% include end-option.html %} + +{% include end-selector.html %} + # Next Steps for expenses sent to an Individual - Expenses submitted to an individual are instantly sent. diff --git a/docs/redirects.csv b/docs/redirects.csv index 5c83d510ccb8..e1c0e12eb070 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -603,3 +603,4 @@ https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-T https://help.expensify.com/articles/expensify-classic/spending-insights/Default-Export-Templates,https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports/ https://help.expensify.com/articles/expensify-classic/spending-insights/Other-Export-Options,https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports/ https://help.expensify.com/articles/expensify-classic/travel/Edit-or-cancel-travel-arrangements,https://help.expensify.com/articles/expensify-classic/travel/Book-with-Expensify-Travel +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-and-Pay-Bills diff --git a/help/README.md b/help/README.md index d62513f07f53..c0fb4dbf524a 100644 --- a/help/README.md +++ b/help/README.md @@ -48,7 +48,7 @@ Every PR pushed by an authorized Expensify employee or representative will autom 3. Install Ruby and Jekyll 4. Build the entire site using Jekyll 5. Create a "preview" of the newly built site in Cloudflare -6. Record a link to that preview in the PR. +6. Record a link to that preview in the PR ## How to deploy the site for real Whenever a PR that touches the `/help` directory is merged, it will re-run the build just like before. However, it will detect that this build is being run from the `main` branch, and thus push the changes to the `production` Cloudflare environment -- meaning, it will replace the contents hosted at https://newhelp.expensify.com diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index cd38fcaaaf6c..cd2598608a0f 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -638,6 +638,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", "${PODS_ROOT}/../../node_modules/@expensify/react-native-live-markdown/parser/react-native-live-markdown-parser.js", + "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", @@ -658,6 +659,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/react-native-live-markdown-parser.js", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", @@ -842,6 +844,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", "${PODS_ROOT}/../../node_modules/@expensify/react-native-live-markdown/parser/react-native-live-markdown-parser.js", + "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", @@ -862,6 +865,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/react-native-live-markdown-parser.js", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 91cb6a7f745b..2b1de6fa329f 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.65 + 9.0.67 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.65.4 + 9.0.67.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 38b36ea381f6..998f520e3128 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.65 + 9.0.67 CFBundleSignature ???? CFBundleVersion - 9.0.65.4 + 9.0.67.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index def1f5fd29dd..55a083959f45 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.65 + 9.0.67 CFBundleVersion - 9.0.65.4 + 9.0.67.1 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5ea5b19896e4..21633b432c12 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1722,7 +1722,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-keyboard-controller (1.14.1): + - react-native-keyboard-controller (1.14.4): - DoubleConversion - glog - hermes-engine @@ -2391,7 +2391,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.183): + - RNLiveMarkdown (0.1.187): - DoubleConversion - glog - hermes-engine @@ -2411,9 +2411,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.183) + - RNLiveMarkdown/newarch (= 0.1.187) - Yoga - - RNLiveMarkdown/newarch (0.1.183): + - RNLiveMarkdown/newarch (0.1.187): - DoubleConversion - glog - hermes-engine @@ -3236,7 +3236,7 @@ SPEC CHECKSUMS: react-native-geolocation: b9bd12beaf0ebca61a01514517ca8455bd26fa06 react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: aae312752fcdfaa2240be9a015fc41ce54087546 - react-native-keyboard-controller: 902c07f41a415b632583b384427a71770a8b02a3 + react-native-keyboard-controller: 97bb7b48fa427c7455afdc8870c2978efd9bfa3a react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: fb5112b1fa754975485884ae85a3fb6a684f49d5 react-native-pager-view: c64a744211a46202619a77509f802765d1659dba @@ -3286,7 +3286,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 8781e2529230a1bc3ea8d75e5c3cd071b6c6aed7 RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: fa9c6451960d09209bb5698745a0a66330ec53cc + RNLiveMarkdown: 8338447b39fcd86596c74b9e0e9509e365a2dd3b RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 460d6ff97ae49c7d5708c3212c6521697c36a0c4 RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28 diff --git a/package-lock.json b/package-lock.json index 90868fbb3a55..468d451e4b6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "new.expensify", - "version": "9.0.65-4", + "version": "9.0.67-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.65-4", + "version": "9.0.67-1", "hasInstallScript": true, "license": "MIT", "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.183", + "@expensify/react-native-live-markdown": "0.1.187", "@expo/metro-runtime": "~3.2.3", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -95,7 +95,7 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.81", + "react-native-onyx": "2.0.82", "react-native-pager-view": "6.5.0", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -103,7 +103,7 @@ "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", "react-native-plaid-link-sdk": "11.11.0", "react-native-qrcode-svg": "6.3.11", - "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-quick-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", + "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-nitro-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", "react-native-reanimated": "3.16.1", "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", @@ -3632,14 +3632,14 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.183", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.183.tgz", - "integrity": "sha512-egxknos7ghe4M5Z2rK7DvphcaxQBdxyppu5N2tdCVc/3oPO2ZtBNjDjtksqywC12wPtIYgHSgxrzvLEfbh5skw==", + "version": "0.1.187", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.187.tgz", + "integrity": "sha512-bw+dfhRN31u2xfG8LCI3e28g5EG/BfkyX1EqjPBRQlDZo4fZsdA61UFW6P8Y4rHlqspjYXJ0vk4ctECRWYl4Yg==", "license": "MIT", "workspaces": [ - "parser", - "example", - "WebExample" + "./parser", + "./example", + "./WebExample" ], "engines": { "node": ">= 18.0.0" @@ -35765,9 +35765,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.81", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.81.tgz", - "integrity": "sha512-EwBqruX4lLnlk3KyZp4bst/voekLJFus7UhtvKmDuqR2Iz/FremwE04JW6YxGyc7C6KpbQrCFdWg/oF9ptRAtg==", + "version": "2.0.82", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.82.tgz", + "integrity": "sha512-12+NgkC4fOeGu2J6s985NKUuLHP4aijBhpE6Us5IfVL+9dwxr/KqUVgV00OzXtYAABcWcpMC5PrvESqe8T5Iyw==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", @@ -35885,8 +35885,9 @@ }, "node_modules/react-native-quick-sqlite": { "version": "8.1.0", - "resolved": "git+ssh://git@github.com/margelo/react-native-quick-sqlite.git#99f34ebefa91698945f3ed26622e002bd79489e0", + "resolved": "git+ssh://git@github.com/margelo/react-native-nitro-sqlite.git#99f34ebefa91698945f3ed26622e002bd79489e0", "integrity": "sha512-7uuHmOEnc6SOAVoAdvkQhvaYhUZMORM75qo+v6PZoH6Qk21j5CmrcxJE3gNh0FhMfxK73hQ3ZtugC/NI2jVhrw==", + "license": "MIT", "peerDependencies": { "react": "*", "react-native": "*" diff --git a/package.json b/package.json index 5be8ade75636..6671a935a68c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.65-4", + "version": "9.0.67-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -68,7 +68,7 @@ }, "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.183", + "@expensify/react-native-live-markdown": "0.1.187", "@expo/metro-runtime": "~3.2.3", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -152,7 +152,7 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.81", + "react-native-onyx": "2.0.82", "react-native-pager-view": "6.5.0", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -160,7 +160,7 @@ "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", "react-native-plaid-link-sdk": "11.11.0", "react-native-qrcode-svg": "6.3.11", - "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-quick-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", + "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-nitro-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", "react-native-reanimated": "3.16.1", "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", diff --git a/patches/react-native+0.75.2+023+modal-navigation-bar-translucent.patch b/patches/react-native+0.75.2+023+modal-navigation-bar-translucent.patch new file mode 100644 index 000000000000..f8a98760b389 --- /dev/null +++ b/patches/react-native+0.75.2+023+modal-navigation-bar-translucent.patch @@ -0,0 +1,214 @@ +diff --git a/node_modules/react-native/Libraries/Modal/Modal.d.ts b/node_modules/react-native/Libraries/Modal/Modal.d.ts +index 4cc2df2..a501b27 100644 +--- a/node_modules/react-native/Libraries/Modal/Modal.d.ts ++++ b/node_modules/react-native/Libraries/Modal/Modal.d.ts +@@ -94,6 +94,11 @@ export interface ModalPropsAndroid { + * Determines whether your modal should go under the system statusbar. + */ + statusBarTranslucent?: boolean | undefined; ++ ++ /** ++ * Determines whether your modal should go under the system navigationbar. ++ */ ++ navigationBarTranslucent?: boolean | undefined; + } + + export type ModalProps = ModalBaseProps & +diff --git a/node_modules/react-native/Libraries/Modal/Modal.js b/node_modules/react-native/Libraries/Modal/Modal.js +index 1942d9e..1ffbe4c 100644 +--- a/node_modules/react-native/Libraries/Modal/Modal.js ++++ b/node_modules/react-native/Libraries/Modal/Modal.js +@@ -95,6 +95,14 @@ export type Props = $ReadOnly<{| + */ + statusBarTranslucent?: ?boolean, + ++ /** ++ * The `navigationBarTranslucent` prop determines whether your modal should go under ++ * the system navigationbar. ++ * ++ * See https://reactnative.dev/docs/modal.html#navigationbartranslucent-android ++ */ ++ navigationBarTranslucent?: ?boolean, ++ + /** + * The `hardwareAccelerated` prop controls whether to force hardware + * acceleration for the underlying window. +@@ -170,6 +178,14 @@ function confirmProps(props: Props) { + `Modal with '${props.presentationStyle}' presentation style and 'transparent' value is not supported.`, + ); + } ++ if ( ++ props.navigationBarTranslucent === true && ++ props.statusBarTranslucent !== true ++ ) { ++ console.warn( ++ 'Modal with translucent navigation bar and without translucent status bar is not supported.', ++ ); ++ } + } + } + +@@ -291,6 +307,7 @@ class Modal extends React.Component { + onDismiss={onDismiss} + visible={this.props.visible} + statusBarTranslucent={this.props.statusBarTranslucent} ++ navigationBarTranslucent={this.props.navigationBarTranslucent} + identifier={this._identifier} + style={styles.modal} + // $FlowFixMe[method-unbinding] added when improving typing for this parameters +diff --git a/node_modules/react-native/React/Views/RCTModalHostView.h b/node_modules/react-native/React/Views/RCTModalHostView.h +index 2fcdcae..0469c23 100644 +--- a/node_modules/react-native/React/Views/RCTModalHostView.h ++++ b/node_modules/react-native/React/Views/RCTModalHostView.h +@@ -27,6 +27,7 @@ + + // Android only + @property (nonatomic, assign) BOOL statusBarTranslucent; ++@property (nonatomic, assign) BOOL navigationBarTranslucent; + @property (nonatomic, assign) BOOL hardwareAccelerated; + @property (nonatomic, assign) BOOL animated; + +diff --git a/node_modules/react-native/React/Views/RCTModalHostViewManager.m b/node_modules/react-native/React/Views/RCTModalHostViewManager.m +index e2ae7e2..a694008 100644 +--- a/node_modules/react-native/React/Views/RCTModalHostViewManager.m ++++ b/node_modules/react-native/React/Views/RCTModalHostViewManager.m +@@ -118,6 +118,7 @@ - (void)invalidate + RCT_EXPORT_VIEW_PROPERTY(presentationStyle, UIModalPresentationStyle) + RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL) + RCT_EXPORT_VIEW_PROPERTY(statusBarTranslucent, BOOL) ++RCT_EXPORT_VIEW_PROPERTY(navigationBarTranslucent, BOOL) + RCT_EXPORT_VIEW_PROPERTY(hardwareAccelerated, BOOL) + RCT_EXPORT_VIEW_PROPERTY(animated, BOOL) + RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock) +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt +index d5e053c..fddda45 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt +@@ -59,6 +59,15 @@ public class ReactModalHostManager : + view.statusBarTranslucent = statusBarTranslucent + } + ++ ++ @ReactProp(name = "navigationBarTranslucent") ++ public override fun setNavigationBarTranslucent( ++ view: ReactModalHostView, ++ navigationBarTranslucent: Boolean ++ ) { ++ view.navigationBarTranslucent = navigationBarTranslucent ++ } ++ + @ReactProp(name = "hardwareAccelerated") + public override fun setHardwareAccelerated( + view: ReactModalHostView, +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +index f6e0d82..03380cb 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +@@ -46,6 +46,7 @@ import com.facebook.react.uimanager.UIManagerModule + import com.facebook.react.uimanager.events.EventDispatcher + import com.facebook.react.views.common.ContextUtils + import com.facebook.react.views.view.ReactViewGroup ++import com.facebook.react.views.view.setSystemBarsTranslucency + import java.util.Objects + import kotlin.math.abs + +@@ -78,6 +79,12 @@ public class ReactModalHostView(context: ThemedReactContext) : + createNewDialog = true + } + ++ public var navigationBarTranslucent: Boolean = false ++ set(value) { ++ field = value ++ createNewDialog = true ++ } ++ + public var animationType: String? = null + set(value) { + field = value +@@ -296,6 +303,7 @@ public class ReactModalHostView(context: ThemedReactContext) : + } else { + frameLayout.fitsSystemWindows = true + } ++ dialog?.window?.setSystemBarsTranslucency(navigationBarTranslucent) + return frameLayout + } + +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +new file mode 100644 +index 0000000..24057c4 +--- /dev/null ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +@@ -0,0 +1,53 @@ ++/* ++ * Copyright (c) Meta Platforms, Inc. and affiliates. ++ * ++ * This source code is licensed under the MIT license found in the ++ * LICENSE file in the root directory of this source tree. ++ */ ++ ++package com.facebook.react.views.view ++ ++import android.content.res.Configuration ++import android.graphics.Color ++import android.os.Build ++import android.view.Window ++import android.view.WindowManager ++import androidx.core.view.ViewCompat ++import androidx.core.view.WindowCompat ++import androidx.core.view.WindowInsetsControllerCompat ++ ++@Suppress("DEPRECATION") ++public fun Window.setSystemBarsTranslucency(isTranslucent: Boolean) { ++ WindowCompat.setDecorFitsSystemWindows(this, !isTranslucent) ++ ++ if (isTranslucent) { ++ val isDarkMode = ++ context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == ++ Configuration.UI_MODE_NIGHT_YES ++ ++ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ++ isStatusBarContrastEnforced = false ++ isNavigationBarContrastEnforced = true ++ } ++ ++ statusBarColor = Color.TRANSPARENT ++ navigationBarColor = when { ++ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Color.TRANSPARENT ++ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && !isDarkMode -> ++ Color.argb(0xe6, 0xFF, 0xFF, 0xFF) ++ else -> Color.argb(0x80, 0x1b, 0x1b, 0x1b) ++ } ++ ++ WindowInsetsControllerCompat(this, this.decorView).run { ++ isAppearanceLightNavigationBars = !isDarkMode ++ } ++ ++ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { ++ attributes.layoutInDisplayCutoutMode = when { ++ Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> ++ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS ++ else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/node_modules/react-native/src/private/specs/components/RCTModalHostViewNativeComponent.js b/node_modules/react-native/src/private/specs/components/RCTModalHostViewNativeComponent.js +index 86bf895..58ec294 100644 +--- a/node_modules/react-native/src/private/specs/components/RCTModalHostViewNativeComponent.js ++++ b/node_modules/react-native/src/private/specs/components/RCTModalHostViewNativeComponent.js +@@ -58,6 +58,14 @@ type NativeProps = $ReadOnly<{| + */ + statusBarTranslucent?: WithDefault, + ++ /** ++ * The `navigationBarTranslucent` prop determines whether your modal should go under ++ * the system navigationbar. ++ * ++ * See https://reactnative.dev/docs/modal#navigationBarTranslucent ++ */ ++ navigationBarTranslucent?: WithDefault, ++ + /** + * The `hardwareAccelerated` prop controls whether to force hardware + * acceleration for the underlying window. diff --git a/patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch b/patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch deleted file mode 100644 index 8d2d81aab40a..000000000000 --- a/patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -index 93c20d3..df1e846 100644 ---- a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -+++ b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -@@ -74,7 +74,7 @@ class EdgeToEdgeReactViewGroup( - } - - override fun onConfigurationChanged(newConfig: Configuration?) { -- this.reApplyWindowInsets() -+ // this.reApplyWindowInsets() - } - // endregion - -@@ -124,12 +124,12 @@ class EdgeToEdgeReactViewGroup( - } - - private fun goToEdgeToEdge(edgeToEdge: Boolean) { -- reactContext.currentActivity?.let { -- WindowCompat.setDecorFitsSystemWindows( -- it.window, -- !edgeToEdge, -- ) -- } -+ // reactContext.currentActivity?.let { -+ // WindowCompat.setDecorFitsSystemWindows( -+ // it.window, -+ // !edgeToEdge, -+ // ) -+ // } - } - - private fun setupKeyboardCallbacks() { -@@ -182,16 +182,16 @@ class EdgeToEdgeReactViewGroup( - // region State managers - private fun enable() { - this.goToEdgeToEdge(true) -- this.setupWindowInsets() -+ // this.setupWindowInsets() - this.setupKeyboardCallbacks() -- modalAttachedWatcher.enable() -+ // modalAttachedWatcher.enable() - } - - private fun disable() { - this.goToEdgeToEdge(false) -- this.setupWindowInsets() -+ // this.setupWindowInsets() - this.removeKeyboardCallbacks() -- modalAttachedWatcher.disable() -+ // modalAttachedWatcher.disable() - } - // endregion - -@@ -223,7 +223,7 @@ class EdgeToEdgeReactViewGroup( - fun forceStatusBarTranslucent(isStatusBarTranslucent: Boolean) { - if (active && this.isStatusBarTranslucent != isStatusBarTranslucent) { - this.isStatusBarTranslucent = isStatusBarTranslucent -- this.reApplyWindowInsets() -+ // this.reApplyWindowInsets() - } - } - // endregion diff --git a/patches/react-native-modal+13.0.1.patch b/patches/react-native-modal+13.0.1+001+initial.patch similarity index 100% rename from patches/react-native-modal+13.0.1.patch rename to patches/react-native-modal+13.0.1+001+initial.patch diff --git a/patches/react-native-modal+13.0.1+002+modal-navigation-bar-translucent.patch b/patches/react-native-modal+13.0.1+002+modal-navigation-bar-translucent.patch new file mode 100644 index 000000000000..a318627af02c --- /dev/null +++ b/patches/react-native-modal+13.0.1+002+modal-navigation-bar-translucent.patch @@ -0,0 +1,32 @@ +diff --git a/node_modules/react-native-modal/dist/modal.d.ts b/node_modules/react-native-modal/dist/modal.d.ts +index bd6419e..029762c 100644 +--- a/node_modules/react-native-modal/dist/modal.d.ts ++++ b/node_modules/react-native-modal/dist/modal.d.ts +@@ -46,6 +46,7 @@ declare const defaultProps: { + scrollOffsetMax: number; + scrollHorizontal: boolean; + statusBarTranslucent: boolean; ++ navigationBarTranslucent: boolean; + supportedOrientations: ("landscape" | "portrait" | "portrait-upside-down" | "landscape-left" | "landscape-right")[]; + }; + export declare type ModalProps = ViewProps & { +@@ -137,6 +138,7 @@ export declare class ReactNativeModal extends React.Component + scrollOffsetMax: number; + scrollHorizontal: boolean; + statusBarTranslucent: boolean; ++ navigationBarTranslucent: boolean; + supportedOrientations: ("landscape" | "portrait" | "portrait-upside-down" | "landscape-left" | "landscape-right")[]; + }; + state: State; +diff --git a/node_modules/react-native-modal/dist/modal.js b/node_modules/react-native-modal/dist/modal.js +index 46277ea..feec991 100644 +--- a/node_modules/react-native-modal/dist/modal.js ++++ b/node_modules/react-native-modal/dist/modal.js +@@ -38,6 +38,7 @@ const defaultProps = { + scrollOffsetMax: 0, + scrollHorizontal: false, + statusBarTranslucent: false, ++ navigationBarTranslucent: false, + supportedOrientations: ['portrait', 'landscape'], + }; + const extractAnimationFromProps = (props) => ({ diff --git a/src/App.tsx b/src/App.tsx index 643e2146e501..52904e0a06c4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,6 @@ import {PortalProvider} from '@gorhom/portal'; import React from 'react'; import {LogBox} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; -import {KeyboardProvider} from 'react-native-keyboard-controller'; import {PickerStateProvider} from 'react-native-picker-select'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; @@ -15,6 +14,7 @@ import CustomStatusBarAndBackgroundContextProvider from './components/CustomStat import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import InitialURLContextProvider from './components/InitialURLContextProvider'; +import KeyboardProvider from './components/KeyboardProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; diff --git a/src/CONST.ts b/src/CONST.ts index 60d61e8ffc7e..d3c595d709d3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -212,6 +212,7 @@ const onboardingPersonalSpendMessage: OnboardingMessage = { const combinedTrackSubmitOnboardingPersonalSpendMessage: OnboardingMessage = { ...onboardingPersonalSpendMessage, tasks: [ + selfGuidedTourTask, { type: 'trackExpense', autoCompleted: false, @@ -1338,6 +1339,10 @@ const CONST = { LIGHT_CONTENT: 'light-content', DARK_CONTENT: 'dark-content', }, + NAVIGATION_BAR_BUTTONS_STYLE: { + LIGHT: 'light', + DARK: 'dark', + }, TRANSACTION: { DEFAULT_MERCHANT: 'Expense', UNKNOWN_MERCHANT: 'Unknown Merchant', @@ -2850,6 +2855,7 @@ const CONST = { ALLOW: 'personal', }, CARD_LIST_THRESHOLD: 8, + DEFAULT_EXPORT_TYPE: 'default', EXPORT_CARD_TYPES: { /** * Name of Card NVP for QBO custom export accounts @@ -3809,8 +3815,8 @@ const CONST = { }, GA: {}, GB: { - regex: /^[A-Z]{1,2}[0-9R][0-9A-Z]?\s*[0-9][A-Z-CIKMOV]{2}$/, - samples: 'LA102UX, BL2F8FX, BD1S9LU, WR4G 6LH', + regex: /^[A-Z]{1,2}[0-9R][0-9A-Z]?\s*([0-9][ABD-HJLNP-UW-Z]{2})?$/, + samples: 'LA102UX, BL2F8FX, BD1S9LU, WR4G 6LH, W1U', }, GD: {}, GE: { @@ -4968,9 +4974,8 @@ const CONST = { '2. Go to *Workspaces*.\n' + '3. Select your workspace.\n' + '4. Click *Categories*.\n' + - '5. Add or import your own categories.\n' + - "6. Disable any default categories you don't need.\n" + - '7. Require a category for every expense in *Settings*.\n' + + "5. Disable any categories you don't need.\n" + + '6. Add your own categories in the top right.\n' + '\n' + `[Take me to workspace category settings](${workspaceCategoriesLink}).`, }, @@ -5943,6 +5948,7 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, + MIN_TAX_RATE_DECIMAL_PLACES: 2, DOWNLOADS_PATH: '/Downloads', DOWNLOADS_TIMEOUT: 5000, @@ -5964,6 +5970,9 @@ const CONST = { ACTION_TYPES: { VIEW: 'view', REVIEW: 'review', + SUBMIT: 'submit', + APPROVE: 'approve', + PAY: 'pay', DONE: 'done', PAID: 'paid', }, @@ -6304,10 +6313,17 @@ const CONST = { }, DEBUG: { + FORMS: { + REPORT: 'report', + REPORT_ACTION: 'reportAction', + TRANSACTION: 'transaction', + TRANSACTION_VIOLATION: 'transactionViolation', + }, DETAILS: 'details', JSON: 'json', REPORT_ACTIONS: 'actions', REPORT_ACTION_PREVIEW: 'preview', + TRANSACTION_VIOLATIONS: 'violations', }, REPORT_IN_LHN_REASONS: { @@ -6344,6 +6360,10 @@ const CONST = { PAID_ADOPTION: 'paid_adoption', }, }, + + HYBRID_APP: { + REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b4510a2faeed..f97edbd744eb 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -716,10 +716,6 @@ const ONYXKEYS = { RULES_MAX_EXPENSE_AMOUNT_FORM_DRAFT: 'rulesMaxExpenseAmountFormDraft', RULES_MAX_EXPENSE_AGE_FORM: 'rulesMaxExpenseAgeForm', RULES_MAX_EXPENSE_AGE_FORM_DRAFT: 'rulesMaxExpenseAgeFormDraft', - DEBUG_REPORT_PAGE_FORM: 'debugReportPageForm', - DEBUG_REPORT_PAGE_FORM_DRAFT: 'debugReportPageFormDraft', - DEBUG_REPORT_ACTION_PAGE_FORM: 'debugReportActionPageForm', - DEBUG_REPORT_ACTION_PAGE_FORM_DRAFT: 'debugReportActionPageFormDraft', DEBUG_DETAILS_FORM: 'debugDetailsForm', DEBUG_DETAILS_FORM_DRAFT: 'debugDetailsFormDraft', }, @@ -814,9 +810,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AMOUNT_FORM]: FormTypes.RulesMaxExpenseAmountForm; [ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AGE_FORM]: FormTypes.RulesMaxExpenseAgeForm; [ONYXKEYS.FORMS.SEARCH_SAVED_SEARCH_RENAME_FORM]: FormTypes.SearchSavedSearchRenameForm; - [ONYXKEYS.FORMS.DEBUG_REPORT_PAGE_FORM]: FormTypes.DebugReportForm; - [ONYXKEYS.FORMS.DEBUG_REPORT_ACTION_PAGE_FORM]: FormTypes.DebugReportActionForm; - [ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm; + [ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm | FormTypes.DebugTransactionForm | FormTypes.DebugTransactionViolationForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2710523fb9b9..d24f0480e136 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1300,6 +1300,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/per-diem', getRoute: (policyID: string) => `settings/workspaces/${policyID}/per-diem` as const, }, + WORKSPACE_PER_DIEM_SETTINGS: { + route: 'settings/workspaces/:policyID/per-diem/settings', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/per-diem/settings` as const, + }, RULES_CUSTOM_NAME: { route: 'settings/workspaces/:policyID/rules/name', getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/name` as const, @@ -1782,13 +1786,46 @@ const ROUTES = { getRoute: (reportID: string, reportActionID: string) => `debug/report/${reportID}/actions/${reportActionID}/preview` as const, }, DETAILS_CONSTANT_PICKER_PAGE: { - route: 'debug/details/constant/:fieldName', - getRoute: (fieldName: string, fieldValue?: string, backTo?: string) => getUrlWithBackToParam(`debug/details/constant/${fieldName}?fieldValue=${fieldValue}`, backTo), + route: 'debug/:formType/details/constant/:fieldName', + getRoute: (formType: string, fieldName: string, fieldValue?: string, policyID?: string, backTo?: string) => + getUrlWithBackToParam(`debug/${formType}/details/constant/${fieldName}?fieldValue=${fieldValue}&policyID=${policyID}`, backTo), }, DETAILS_DATE_TIME_PICKER_PAGE: { route: 'debug/details/datetime/:fieldName', getRoute: (fieldName: string, fieldValue?: string, backTo?: string) => getUrlWithBackToParam(`debug/details/datetime/${fieldName}?fieldValue=${fieldValue}`, backTo), }, + DEBUG_TRANSACTION: { + route: 'debug/transaction/:transactionID', + getRoute: (transactionID: string) => `debug/transaction/${transactionID}` as const, + }, + DEBUG_TRANSACTION_TAB_DETAILS: { + route: 'debug/transaction/:transactionID/details', + getRoute: (transactionID: string) => `debug/transaction/${transactionID}/details` as const, + }, + DEBUG_TRANSACTION_TAB_JSON: { + route: 'debug/transaction/:transactionID/json', + getRoute: (transactionID: string) => `debug/transaction/${transactionID}/json` as const, + }, + DEBUG_TRANSACTION_TAB_VIOLATIONS: { + route: 'debug/transaction/:transactionID/violations', + getRoute: (transactionID: string) => `debug/transaction/${transactionID}/violations` as const, + }, + DEBUG_TRANSACTION_VIOLATION_CREATE: { + route: 'debug/transaction/:transactionID/violations/create', + getRoute: (transactionID: string) => `debug/transaction/${transactionID}/violations/create` as const, + }, + DEBUG_TRANSACTION_VIOLATION: { + route: 'debug/transaction/:transactionID/violations/:index', + getRoute: (transactionID: string, index: string) => `debug/transaction/${transactionID}/violations/${index}` as const, + }, + DEBUG_TRANSACTION_VIOLATION_TAB_DETAILS: { + route: 'debug/transaction/:transactionID/violations/:index/details', + getRoute: (transactionID: string, index: string) => `debug/transaction/${transactionID}/violations/${index}/details` as const, + }, + DEBUG_TRANSACTION_VIOLATION_TAB_JSON: { + route: 'debug/transaction/:transactionID/violations/:index/json', + getRoute: (transactionID: string, index: string) => `debug/transaction/${transactionID}/violations/${index}/json` as const, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index dfa13405f58b..05eae56d5c21 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -551,6 +551,7 @@ const SCREENS = { RULES_MAX_EXPENSE_AGE: 'Rules_Max_Expense_Age', RULES_BILLABLE_DEFAULT: 'Rules_Billable_Default', PER_DIEM: 'Per_Diem', + PER_DIEM_SETTINGS: 'Per_Diem_Settings', }, EDIT_REQUEST: { @@ -625,6 +626,9 @@ const SCREENS = { REPORT_ACTION_CREATE: 'Debug_Report_Action_Create', DETAILS_CONSTANT_PICKER_PAGE: 'Debug_Details_Constant_Picker_Page', DETAILS_DATE_TIME_PICKER_PAGE: 'Debug_Details_Date_Time_Picker_Page', + TRANSACTION: 'Debug_Transaction', + TRANSACTION_VIOLATION_CREATE: 'Debug_Transaction_Violation_Create', + TRANSACTION_VIOLATION: 'Debug_Transaction_Violation', }, } as const; diff --git a/src/components/AmountPicker/AmountSelectorModal.tsx b/src/components/AmountPicker/AmountSelectorModal.tsx index b54f6301b798..d8510aef0499 100644 --- a/src/components/AmountPicker/AmountSelectorModal.tsx +++ b/src/components/AmountPicker/AmountSelectorModal.tsx @@ -1,4 +1,5 @@ -import React, {useState} from 'react'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useRef, useState} from 'react'; import {View} from 'react-native'; import AmountForm from '@components/AmountForm'; import Button from '@components/Button'; @@ -6,6 +7,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Modal from '@components/Modal'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; @@ -16,6 +18,28 @@ function AmountSelectorModal({value, description = '', onValueSelected, isVisibl const styles = useThemeStyles(); const [currentValue, setValue] = useState(value); + const inputRef = useRef(null); + const focusTimeoutRef = useRef(null); + + const inputCallbackRef = (ref: BaseTextInputRef | null) => { + inputRef.current = ref; + }; + + useFocusEffect( + useCallback(() => { + focusTimeoutRef.current = setTimeout(() => { + if (inputRef.current && isVisible) { + inputRef.current.focus(); + } + return () => { + if (!focusTimeoutRef.current || !isVisible) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, CONST.ANIMATED_TRANSITION); + }, [isVisible, inputRef]), + ); return ( inputCallbackRef(ref)} />