diff --git a/.env.example b/.env.example index 2bdda890b2ef..3f516bdab889 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,7 @@ EXPENSIFY_ACCOUNT_ID_RECEIPTS=-1 EXPENSIFY_ACCOUNT_ID_REWARDS=-1 EXPENSIFY_ACCOUNT_ID_STUDENT_AMBASSADOR=-1 EXPENSIFY_ACCOUNT_ID_SVFG=-1 +EXPENSIFY_ACCOUNT_ID_MANAGER_MCTEST=-1 FB_API_KEY=YOUR_API_KEY FB_APP_ID=YOUR_APP_ID diff --git a/.env.staging b/.env.staging index 17d82ac2d136..c789087ebded 100644 --- a/.env.staging +++ b/.env.staging @@ -6,4 +6,4 @@ EXPENSIFY_PARTNER_PASSWORD=e21965746fd75f82bb66 PUSHER_APP_KEY=268df511a204fbb60884 USE_WEB_PROXY=false ENVIRONMENT=staging -SEND_CRASH_REPORTS=true \ No newline at end of file +SEND_CRASH_REPORTS=true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2dfd9348d961..b8de634a489f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,9 +23,9 @@ PROPOSAL: diff --git a/.github/actions/composite/setupGitForOSBotify/action.yml b/.github/actions/composite/setupGitForOSBotify/action.yml index 456cef93676a..9495ba07b0ac 100644 --- a/.github/actions/composite/setupGitForOSBotify/action.yml +++ b/.github/actions/composite/setupGitForOSBotify/action.yml @@ -2,20 +2,25 @@ name: 'Setup Git for OSBotify' description: 'Setup Git for OSBotify' inputs: - GPG_PASSPHRASE: - description: 'Passphrase used to decrypt GPG key' + OP_SERVICE_ACCOUNT_TOKEN: + description: "1Password service account token" required: true runs: using: composite steps: - - name: Decrypt OSBotify GPG key - run: cd .github/workflows && gpg --quiet --batch --yes --decrypt --passphrase=${{ inputs.GPG_PASSPHRASE }} --output OSBotify-private-key.asc OSBotify-private-key.asc.gpg + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 + + - name: Load files from 1Password shell: bash + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ inputs.OP_SERVICE_ACCOUNT_TOKEN }} + run: op read "op://Mobile-Deploy-CI/OSBotify-private-key.asc/OSBotify-private-key.asc" --force --out-file ./OSBotify-private-key.asc - name: Import OSBotify GPG Key shell: bash - run: cd .github/workflows && gpg --import OSBotify-private-key.asc + run: gpg --import OSBotify-private-key.asc - name: Set up git for OSBotify shell: bash @@ -24,8 +29,3 @@ runs: git config --global commit.gpgsign true git config --global user.name OSBotify git config --global user.email infra+osbotify@expensify.com - - - name: Enable debug logs for git - shell: bash - if: runner.debug == '1' - run: echo "GIT_TRACE=true" >> "$GITHUB_ENV" diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml index 404ddc55e954..128dcf48e934 100644 --- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -5,8 +5,8 @@ name: "Setup Git for OSBotify" description: "Setup Git for OSBotify" inputs: - GPG_PASSPHRASE: - description: "Passphrase used to decrypt GPG key" + OP_SERVICE_ACCOUNT_TOKEN: + description: "1Password service account token" required: true OS_BOTIFY_APP_ID: description: "Application ID for OS Botify" @@ -24,28 +24,18 @@ outputs: runs: using: composite steps: - - name: Check if gpg encrypted private key is present - id: key_check - shell: bash - run: | - if [[ -f .github/workflows/OSBotify-private-key.asc.gpg ]]; then - echo "key_exists=true" >> "$GITHUB_OUTPUT" - fi - - - name: Checkout - uses: actions/checkout@v4 - if: steps.key_check.outputs.key_exists != 'true' - with: - sparse-checkout: | - .github + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 - - name: Decrypt OSBotify GPG key - run: cd .github/workflows && gpg --quiet --batch --yes --decrypt --passphrase=${{ inputs.GPG_PASSPHRASE }} --output OSBotify-private-key.asc OSBotify-private-key.asc.gpg + - name: Load files from 1Password shell: bash + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ inputs.OP_SERVICE_ACCOUNT_TOKEN }} + run: op read "op://Mobile-Deploy-CI/OSBotify-private-key.asc/OSBotify-private-key.asc" --force --out-file ./OSBotify-private-key.asc - name: Import OSBotify GPG Key shell: bash - run: cd .github/workflows && gpg --import OSBotify-private-key.asc + run: gpg --import OSBotify-private-key.asc - name: Set up git for OSBotify shell: bash @@ -55,11 +45,6 @@ runs: git config user.name OSBotify git config user.email infra+osbotify@expensify.com - - name: Enable debug logs for git - shell: bash - if: runner.debug == '1' - run: echo "GIT_TRACE=true" >> "$GITHUB_ENV" - - name: Sync clock shell: bash run: sudo sntp -sS time.windows.com diff --git a/.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.ts b/.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.ts index 3ff0de1b3bb2..8e285389e3f7 100644 --- a/.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.ts +++ b/.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.ts @@ -54,6 +54,7 @@ GithubUtils.octokit.pulls .then(({data: PR}) => { if (!isEmptyObject(PR)) { console.log(`Found matching pull request: ${PR.html_url}`); + console.log(`Pull request details: ${JSON.stringify(PR)}}`); core.setOutput('MERGE_COMMIT_SHA', PR.merge_commit_sha); core.setOutput('HEAD_COMMIT_SHA', PR.head?.sha); core.setOutput('IS_MERGED', PR.merged); diff --git a/.github/actions/javascript/getPullRequestDetails/index.js b/.github/actions/javascript/getPullRequestDetails/index.js index fa7e7e00e137..07baf462086c 100644 --- a/.github/actions/javascript/getPullRequestDetails/index.js +++ b/.github/actions/javascript/getPullRequestDetails/index.js @@ -11502,6 +11502,7 @@ GithubUtils_1.default.octokit.pulls .then(({ data: PR }) => { if (!(0, EmptyObject_1.isEmptyObject)(PR)) { console.log(`Found matching pull request: ${PR.html_url}`); + console.log(`Pull request details: ${JSON.stringify(PR)}}`); core.setOutput('MERGE_COMMIT_SHA', PR.merge_commit_sha); core.setOutput('HEAD_COMMIT_SHA', PR.head?.sha); core.setOutput('IS_MERGED', PR.merged); diff --git a/.github/scripts/checkParser.sh b/.github/scripts/checkParser.sh new file mode 100755 index 000000000000..d63f4a01452f --- /dev/null +++ b/.github/scripts/checkParser.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e + +ROOT_DIR=$(dirname "$(dirname "$(dirname "${BASH_SOURCE[0]}")")") +cd "$ROOT_DIR" || exit 1 + +autocomplete_parser_backup="src/libs/SearchParser/autocompleteParser.js.bak" +search_parser_backup="src/libs/SearchParser/searchParser.js.bak" + +#Copying the current .js parser files +cp src/libs/SearchParser/autocompleteParser.js "$autocomplete_parser_backup" 2>/dev/null +cp src/libs/SearchParser/searchParser.js "$search_parser_backup" 2>/dev/null + +#Running the scripts that generate the .js parser files +npm run generate-search-parser +npm run generate-autocomplete-parser + +#Checking if the saved files differ from the newly generated +if ! diff -q "$autocomplete_parser_backup" src/libs/SearchParser/autocompleteParser.js >/dev/null || + ! diff -q "$search_parser_backup" src/libs/SearchParser/searchParser.js >/dev/null; then + echo "The files generated from the .peggy files using the commands: generate-search-parser and generate-autocomplete-parser are not identical to those currently on this branch." + echo "The parser .js files should never be edited manually. Make sure you’ve run locally: npm run generate-search-parser and npm run generate-autocomplete-parser, and committed the changes." + exit 1 +else + echo "The files generated from the .peggy files using the commands: generate-search-parser and generate-autocomplete-parser are identical to those currently on this branch." + exit 0 +fi diff --git a/.github/workflows/OSBotify-private-key.asc.gpg b/.github/workflows/OSBotify-private-key.asc.gpg deleted file mode 100644 index 03f06222d0fe..000000000000 Binary files a/.github/workflows/OSBotify-private-key.asc.gpg and /dev/null differ diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 45dacacd0b16..3f32b2bfa1fe 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -80,14 +80,6 @@ git fetch origin tag 1.0.1-0 --no-tags --shallow-exclude=1.0.0-0 # This will fet ## Secrets The GitHub workflows require a large list of secrets to deploy, notify and test the code: -1. `LARGE_SECRET_PASSPHRASE` - decrypts secrets stored in various encrypted files stored in GitHub repository. To create updated versions of these encrypted files, refer to steps 1-4 of [this encrypted secrets help page](https://docs.github.com/en/actions/reference/encrypted-secrets#limits-for-secrets) using the `LARGE_SECRET_PASSPHRASE`. - 1. `android/app/my-upload-key.keystore.gpg` - 1. `android/app/android-fastlane-json-key.json.gpg` - 1. `ios/NewApp_AdHoc.mobileprovision` - 1. `ios/NewApp_AdHoc_Notification_Service.mobileprovision` - 1. `ios/NewApp_AppStore.mobileprovision.gpg` - 1. `ios/NewApp_AppStore_Notification_Service.mobileprovision.gpg` - 1. `ios/Certificates.p12.gpg` 1. `SLACK_WEBHOOK` - Sends Slack notifications via Slack WebHook https://expensify.slack.com/services/B01AX48D7MM 1. `OS_BOTIFY_TOKEN` - Personal access token for @OSBotify user in GitHub 1. `CLA_BOTIFY_TOKEN` - Personal access token for @CLABotify user in GitHub @@ -105,10 +97,15 @@ The GitHub workflows require a large list of secrets to deploy, notify and test 1. `APPLE_DEMO_PASSWORD` - Demo account password used for https://appstoreconnect.apple.com/ 1. `BROWSERSTACK` - Used to access Browserstack's API +We use 1Password for many secrets and in general use two different actions from 1Password to fetch secrets: + +1. `1password/install-cli-action` - This action is used to install 1Password cli `op` and is used to grab **files** using the `op read` command. +1. `1password/load-secrets-action` - This action is used to fetch **strings** from 1Password. + ### Important note about Secrets Secrets are available by default in most workflows. The exception to the rule is callable workflows. If a workflow is triggered by the `workflow_call` event, it will only have access to repo secrets if the workflow that called it passed in the secrets explicitly (for example, using `secrets: inherit`). -Furthermore, secrets are not accessible in actions. If you need to access a secret in an action, you must declare it as an input and pass it in. GitHub _should_ still obfuscate the value of the secret in workflow run logs. +Furthermore, secrets are not accessible in actions. If you need to access a secret in an action, _you must declare it as an input and pass it in_. GitHub _should_ still obfuscate the value of the secret in workflow run logs. ## Actions diff --git a/.github/workflows/androidBump.yml b/.github/workflows/androidBump.yml index e10304d1d922..5ea71c028e15 100644 --- a/.github/workflows/androidBump.yml +++ b/.github/workflows/androidBump.yml @@ -21,9 +21,14 @@ jobs: with: bundler-cache: true - - name: Decrypt json 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 + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 + + - name: Load files from 1Password working-directory: android/app + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + run: op read "op://Mobile-Deploy-CI/android-fastlane-json-key.json/android-fastlane-json-key.json" --force --out-file ./android-fastlane-json-key.json - name: Get status from Google Play and generate next rollout percentage id: checkAndroidStatus diff --git a/.github/workflows/buildAndroid.yml b/.github/workflows/buildAndroid.yml index d7784e2f610b..c4d33b00bef4 100644 --- a/.github/workflows/buildAndroid.yml +++ b/.github/workflows/buildAndroid.yml @@ -85,9 +85,14 @@ jobs: with: bundler-cache: true - - name: Decrypt keystore to sign the APK/AAB - run: gpg --batch --yes --decrypt --passphrase="${{ secrets.LARGE_SECRET_PASSPHRASE }}" --output my-upload-key.keystore my-upload-key.keystore.gpg + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 + + - name: Load files from 1Password working-directory: android/app + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + run: op read "op://Mobile-Deploy-CI/New Expensify my-upload-key.keystore/my-upload-key.keystore" --force --out-file ./my-upload-key.keystore - name: Get package version id: getPackageVersion diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml index 1772d5d309cc..b7dcf95294be 100644 --- a/.github/workflows/cherryPick.yml +++ b/.github/workflows/cherryPick.yml @@ -39,12 +39,13 @@ jobs: with: ref: staging token: ${{ secrets.OS_BOTIFY_TOKEN }} + submodules: true - name: Set up git for OSBotify id: setupGitForOSBotify uses: ./.github/actions/composite/setupGitForOSBotifyApp with: - GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} @@ -55,14 +56,19 @@ jobs: SEMVER_LEVEL: "PATCH" - name: Fetch history of relevant refs - run: | - git fetch origin main staging --no-tags --shallow-exclude ${{ steps.getPreviousVersion.outputs.PREVIOUS_VERSION }} + run: git fetch origin main staging --no-tags --shallow-exclude ${{ steps.getPreviousVersion.outputs.PREVIOUS_VERSION }} - name: Get version bump commit id: getVersionBumpCommit run: | git switch main VERSION_BUMP_COMMIT="$(git log --format='%H' --author='OSBotify' --grep 'Update version to ${{ needs.createNewVersion.outputs.NEW_VERSION }}')" + if [ -z "$VERSION_BUMP_COMMIT" ]; then + echo "::error::⌠Could not find version bump commit for ${{ needs.createNewVersion.outputs.NEW_VERSION }}" + git log --oneline + else + echo "::notice::👀 Found version bump commit $VERSION_BUMP_COMMIT" + fi echo "VERSION_BUMP_SHA=$VERSION_BUMP_COMMIT" >> "$GITHUB_OUTPUT" - name: Get merge commit for pull request to CP @@ -85,14 +91,13 @@ jobs: if git cherry-pick -S -x --mainline 1 ${{ steps.getCPMergeCommit.outputs.MERGE_COMMIT_SHA }}; then echo "🎉 No conflicts! CP was a success, PR can be automerged 🎉" echo "HAS_CONFLICTS=false" >> "$GITHUB_OUTPUT" + git commit --amend -m "$(git log -1 --pretty=%B)" -m "(CP triggered by ${{ github.actor }})" else echo "😞 PR can't be automerged, there are merge conflicts in the following files:" git --no-pager diff --name-only --diff-filter=U - git add . - GIT_MERGE_AUTOEDIT=no git cherry-pick --continue + git cherry-pick --abort echo "HAS_CONFLICTS=true" >> "$GITHUB_OUTPUT" fi - git commit --amend -m "$(git log -1 --pretty=%B)" -m "(CP triggered by ${{ github.actor }})" - name: Push changes run: | @@ -109,7 +114,25 @@ jobs: run: | gh pr create \ --title "💠Cherry pick PR #${{ github.event.inputs.PULL_REQUEST_NUMBER }} to staging ðŸ’" \ - --body "💠Cherry pick https://github.com/Expensify/App/pull/${{ github.event.inputs.PULL_REQUEST_NUMBER }} to staging ðŸ’" \ + --body \ + "💠Cherry pick https://github.com/Expensify/App/pull/${{ github.event.inputs.PULL_REQUEST_NUMBER }} to staging 💠+ This PR had conflicts when we tried to cherry-pick it to staging. You'll need to manually perform the cherry-pick, using the following steps: + + \`\`\`bash + git fetch + git checkout ${{ github.actor }}-cherry-pick-staging-${{ github.event.inputs.PULL_REQUEST_NUMBER }}-${{ github.run_attempt }} + git cherry-pick -S -x --mainline 1 ${{ steps.getCPMergeCommit.outputs.MERGE_COMMIT_SHA }} + \`\`\` + + Then manually resolve conflicts, and commit the change with \`git cherry-pick --continue\`. Lastly, please run: + + \`\`\`bash + git commit --amend -m \"$(git log -1 --pretty=%B)\" -m \"(CP triggered by ${{ github.actor }})\" + \`\`\` + + That will help us keep track of who triggered this CP. Once all that's done, push your changes with \`git push origin ${{ github.actor }}-cherry-pick-staging-${{ github.event.inputs.PULL_REQUEST_NUMBER }}-${{ github.run_attempt }}\`, and then open this PR for review. + + Note that you **must** test this PR, and both the author and reviewer checklist should be completed, just as if you were merging the PR to main." \ --label "Engineering,Hourly" \ --base "staging" sleep 5 @@ -117,11 +140,12 @@ jobs: "This pull request has merge conflicts and can not be automatically merged. :disappointed: Please manually resolve the conflicts, push your changes, and then request another reviewer to review and merge. **Important:** There may be conflicts that GitHub is not able to detect, so please _carefully_ review this pull request before approving." - gh pr edit --add-assignee "${{ github.actor }},${{ steps.getCPMergeCommit.outputs.MERGE_ACTOR }}" + ORIGINAL_PR_AUTHOR="$(gh pr view ${{ github.event.inputs.PULL_REQUEST_NUMBER }} --json author --jq .author.login)" + gh pr edit --add-assignee "${{ github.actor }},${{ steps.getCPMergeCommit.outputs.MERGE_ACTOR }},$ORIGINAL_PR_AUTHOR" env: GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} - - name: Label PR with CP Staging + - name: Label original PR with CP Staging run: gh pr edit ${{ inputs.PULL_REQUEST_NUMBER }} --add-label 'CP Staging' env: GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/commentOnNativeFileChanged.yml b/.github/workflows/commentOnNativeFileChanged.yml deleted file mode 100644 index 4590a80af707..000000000000 --- a/.github/workflows/commentOnNativeFileChanged.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Comment on native files changed - -on: - pull_request: - branches: - - 'main' - paths: - - '**.kt' - - '**.java' - - '**.swift' - - '**.mm' - - '**.h' - - '**.cpp' - - 'AndroidManifest.xml' - - 'project.pbxproj' - -jobs: - commentOnNativeFilesChanged: - name: Create comment - runs-on: ubuntu-latest - steps: - - name: Comment - run: | - gh pr comment ${{ github.event.number }} --body \ - ":warning: This PR is possibly changing native code. It may cause problems in Hybrid App. Please ask Hybrid App team to review those changes in the Slack open-source channel. The C+ can help you with that. :warning:" - env: - GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/compareNDandODbuilds.yml b/.github/workflows/compareNDandODbuilds.yml index 7314a72a5193..99a5de896501 100644 --- a/.github/workflows/compareNDandODbuilds.yml +++ b/.github/workflows/compareNDandODbuilds.yml @@ -4,42 +4,14 @@ on: workflow_dispatch: jobs: - validateActor: - runs-on: ubuntu-latest - outputs: - READY_TO_BUILD: ${{ fromJSON(steps.isExpensifyEmployee.outputs.IS_EXPENSIFY_EMPLOYEE) && fromJSON(steps.hasReadyToBuildLabel.outputs.HAS_READY_TO_BUILD_LABEL) }} - steps: - - name: Is Expensify employee - id: isExpensifyEmployee - run: | - if gh api /orgs/Expensify/teams/expensify-expensify/memberships/${{ github.actor }} --silent; then - echo "IS_EXPENSIFY_EMPLOYEE=true" >> "$GITHUB_OUTPUT" - else - echo "IS_EXPENSIFY_EMPLOYEE=false" >> "$GITHUB_OUTPUT" - fi - env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - - - id: hasReadyToBuildLabel - name: Set HAS_READY_TO_BUILD_LABEL flag - run: | - echo "HAS_READY_TO_BUILD_LABEL=$(gh pr view "${{ env.PULL_REQUEST_NUMBER }}" --repo Expensify/App --json labels --jq '.labels[].name' | grep -q 'Ready To Build' && echo 'true')" >> "$GITHUB_OUTPUT" - if [[ "$HAS_READY_TO_BUILD_LABEL" != 'true' ]]; then - echo "The 'Ready to Build' label is not attached to the PR #${{ env.PULL_REQUEST_NUMBER }}" - fi - env: - GITHUB_TOKEN: ${{ github.token }} - buildHybridAppAndroid: name: Build HybridApp Android - needs: [validateActor] - runs-on: macos-15-xlarge + runs-on: ubuntu-latest-xl steps: - name: Checkout uses: actions/checkout@v4 with: submodules: true - ref: ${{ github.event.pull_request.head.sha }} token: ${{ secrets.OS_BOTIFY_TOKEN }} - name: Update submodule to match main @@ -58,29 +30,71 @@ jobs: with: IS_HYBRID_BUILD: 'true' + - name: Run grunt build + run: | + cd Mobile-Expensify + npm run grunt:build:shared + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'oracle' + java-version: '17' + + - name: Setup Ruby + uses: ruby/setup-ruby@v1.190.0 + with: + bundler-cache: true + + - name: Install New Expensify Gems + run: bundle install + + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 + + - name: Load files from 1Password + working-directory: android/app + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + run: | + op read "op://Mobile-Deploy-CI/android-fastlane-json-key.json/android-fastlane-json-key.json" --force --out-file ./android-fastlane-json-key.json + op read "op://Mobile-Deploy-CI/New Expensify my-upload-key.keystore/my-upload-key.keystore" --force --out-file ./my-upload-key.keystore + + # Copy the keystore to the Android directory for Fullstory + cp ./upload-key.keystore Mobile-Expensify/Android + + - name: Load Android upload keystore credentials from 1Password + id: load-credentials + uses: 1password/load-secrets-action@v2 + with: + export-env: false + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + ANDROID_UPLOAD_KEYSTORE_PASSWORD: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEYSTORE_PASSWORD + ANDROID_UPLOAD_KEYSTORE_ALIAS: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEYSTORE_ALIAS + ANDROID_UPLOAD_KEY_PASSWORD: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEY_PASSWORD + - name: Build Android Release working-directory: Mobile-Expensify/Android run: | - if ! ./gradlew --profile assembleRelease - then - echo "⌠Android HybridApp failed to build: Please reach out to Contributor+ and/or Expensify engineers for help in #expensify-open-source to resolve." - exit 1 - else - echo "✅ Build succeeded. Printing Gradle profile report:" - # Print the latest generated profile report - PROFILE_REPORT=$(find build/reports/profile -maxdepth 1 -type f) - cat "$PROFILE_REPORT" - fi + ./gradlew --profile assembleRelease \ + -Pandroid.injected.signing.store.file="./upload-key.keystore" \ + -Pandroid.injected.signing.store.password=${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_PASSWORD }} \ + -Pandroid.injected.signing.key.alias=${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_ALIAS }} \ + -Pandroid.injected.signing.key.password=${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEY_PASSWORD }} + + echo "Printing Gradle profile report:" + # Print the latest generated profile report + PROFILE_REPORT=$(find build/reports/profile -maxdepth 1 -type f) + cat "$PROFILE_REPORT" buildStandaloneAndroid: name: Build Standalone Android - needs: [ validateActor ] - runs-on: macos-15-xlarge + runs-on: ubuntu-latest-xl steps: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha }} token: ${{ secrets.OS_BOTIFY_TOKEN }} - name: Configure MapBox SDK @@ -92,16 +106,24 @@ jobs: with: IS_HYBRID_BUILD: 'false' + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 + + - name: Load files from 1Password + working-directory: android/app + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + run: op read "op://Mobile-Deploy-CI/New Expensify my-upload-key.keystore/my-upload-key.keystore" --force --out-file ./my-upload-key.keystore + - name: Build Android Release working-directory: android + env: + MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }} + MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }} run: | - if ! ./gradlew --profile assembleProductionRelease - then - echo "⌠Android Standalone failed to build: Please reach out to Contributor+ and/or Expensify engineers for help in #expensify-open-source to resolve." - exit 1 - else - echo "✅ Build succeeded. Printing Gradle profile report:" - # Print the latest generated profile report - PROFILE_REPORT=$(find build/reports/profile -maxdepth 1 -type f) - cat "$PROFILE_REPORT" - fi + ./gradlew --profile assembleProductionRelease + + echo "Printing Gradle profile report:" + # Print the latest generated profile report + PROFILE_REPORT=$(find build/reports/profile -maxdepth 1 -type f) + cat "$PROFILE_REPORT" diff --git a/.github/workflows/createNewVersion.yml b/.github/workflows/createNewVersion.yml index 29dddbcd3151..a933c2f1686f 100644 --- a/.github/workflows/createNewVersion.yml +++ b/.github/workflows/createNewVersion.yml @@ -23,15 +23,15 @@ on: value: ${{ jobs.createNewVersion.outputs.NEW_VERSION }} secrets: - LARGE_SECRET_PASSPHRASE: - description: Passphrase used to decrypt GPG key - required: true SLACK_WEBHOOK: description: Webhook used to comment in slack required: true OS_BOTIFY_COMMIT_TOKEN: description: OSBotify personal access token, used to workaround committing to protected branch required: true + OP_SERVICE_ACCOUNT_TOKEN: + description: 1Password service account token + required: true jobs: validateActor: @@ -65,6 +65,7 @@ jobs: uses: actions/checkout@v4 with: ref: main + submodules: true # The OS_BOTIFY_COMMIT_TOKEN is a personal access token tied to osbotify # This is a workaround to allow pushes to a protected branch token: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} @@ -73,72 +74,26 @@ jobs: uses: ./.github/actions/composite/setupGitForOSBotify id: setupGitForOSBotify with: - GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - - name: Generate version + - name: Generate new E/App version id: bumpVersion uses: ./.github/actions/javascript/bumpVersion with: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} SEMVER_LEVEL: ${{ inputs.SEMVER_LEVEL }} - - name: Commit new version + - name: Update Mobile-Expensify submodule with the latest state of the Mobile-Expensify main branch run: | - git add \ - ./package.json \ - ./package-lock.json \ - ./android/app/build.gradle \ - ./ios/NewExpensify/Info.plist \ - ./ios/NewExpensifyTests/Info.plist \ - ./ios/NotificationServiceExtension/Info.plist - git commit -m "Update version to ${{ steps.bumpVersion.outputs.NEW_VERSION }}" - - - name: Update main branch - run: git push origin main - - - name: Announce failed workflow in Slack - if: ${{ failure() }} - uses: ./.github/actions/composite/announceFailedWorkflowInSlack - with: - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - - createNewHybridVersion: - runs-on: macos-latest - needs: [validateActor, createNewVersion] - if: ${{ fromJSON(needs.validateActor.outputs.HAS_WRITE_ACCESS) }} - steps: - - name: Run turnstyle - uses: softprops/turnstyle@49108bdfa571e62371bd2c3094893c547ab3fc03 - with: - poll-interval-seconds: 10 - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Check out `App` repo - uses: actions/checkout@v4 - with: - ref: main - submodules: true - # The OS_BOTIFY_COMMIT_TOKEN is a personal access token tied to osbotify - # This is a workaround to allow pushes to a protected branch - token: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} - - - name: Update submodule and checkout the main branch - run: | - git submodule update --init cd Mobile-Expensify + git fetch --depth=1 origin main git checkout main - git pull origin main - - - name: Setup git for OSBotify - uses: ./.github/actions/composite/setupGitForOSBotify - id: setupGitForOSBotify - with: - GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + git reset --hard origin/main - name: Generate HybridApp version run: | cd Mobile-Expensify + # Generate all flavors of the version SHORT_APP_VERSION=$(echo "$NEW_VERSION" | awk -F'-' '{print $1}') BUILD_NUMBER=$(echo "$NEW_VERSION" | awk -F'-' '{print $2}') @@ -167,9 +122,9 @@ jobs: # Update JS HybridApp Version sed -i .bak -E "s/\"version\": \"([0-9\.]*)\"/\"version\": \"$FULL_APP_VERSION\"/" $JS_CONFIG_FILE env: - NEW_VERSION: ${{ needs.createNewVersion.outputs.NEW_VERSION }} + NEW_VERSION: ${{ steps.bumpVersion.outputs.NEW_VERSION }} - - name: Commit new version + - name: Commit new Mobile-Expensify version run: | cd Mobile-Expensify git add \ @@ -178,16 +133,25 @@ jobs: ./iOS/Expensify/Expensify-Info.plist\ ./iOS/SmartScanExtension/Info.plist \ ./iOS/NotificationServiceExtension/Info.plist - git commit -m "Update version to ${{ needs.createNewVersion.outputs.NEW_VERSION }}" + git commit -m "Update version to ${{ steps.bumpVersion.outputs.NEW_VERSION }}" + git push origin main - - name: Update main branch on Mobile-Expensify and App + - name: Commit new E/App version + run: | + git add \ + ./package.json \ + ./package-lock.json \ + ./android/app/build.gradle \ + ./ios/NewExpensify/Info.plist \ + ./ios/NewExpensifyTests/Info.plist \ + ./ios/NotificationServiceExtension/Info.plist + git commit -m "Update version to ${{ steps.bumpVersion.outputs.NEW_VERSION }}" + + - name: Update Mobile-Expensify submodule in E/App run: | - cd Mobile-Expensify - git push origin main - cd .. git add Mobile-Expensify - git commit -m "Update Mobile-Expensify to ${{ needs.createNewVersion.outputs.NEW_VERSION }}" - git push origin main + git commit -m "Update Mobile-Expensify submodule version to ${{ steps.bumpVersion.outputs.NEW_VERSION }}" + git push origin main - name: Announce failed workflow in Slack if: ${{ failure() }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fab1604f1ee4..4b4ea2413ae5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -45,7 +45,7 @@ jobs: uses: ./.github/actions/composite/setupGitForOSBotifyApp id: setupGitForOSBotify with: - GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} @@ -97,12 +97,14 @@ jobs: pattern: android-*-artifact merge-multiple: true - - name: Log downloaded artifact paths - run: ls -R /tmp/artifacts + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 - - 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 + - name: Load files from 1Password working-directory: android/app + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + run: op read "op://Mobile-Deploy-CI/android-fastlane-json-key.json/android-fastlane-json-key.json" --force --out-file ./android-fastlane-json-key.json - name: Upload Android app to Google Play run: bundle exec fastlane android upload_google_play_internal @@ -166,9 +168,10 @@ jobs: env: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} run: | - op read op://Mobile-Deploy-CI/firebase.json/firebase.json --force --out-file ./firebase.json - op read op://Mobile-Deploy-CI/upload-key.keystore/upload-key.keystore --force --out-file ./upload-key.keystore - op read op://Mobile-Deploy-CI/android-fastlane-json-key.json/android-fastlane-json-key.json --force --out-file ./android-fastlane-json-key.json + op read "op://Mobile-Deploy-CI/firebase.json/firebase.json" --force --out-file ./firebase.json + op read "op://Mobile-Deploy-CI/upload-key.keystore/upload-key.keystore" --force --out-file ./upload-key.keystore + op read "op://Mobile-Deploy-CI/android-fastlane-json-key.json/android-fastlane-json-key.json" --force --out-file ./android-fastlane-json-key.json + # Copy the keystore to the Android directory for Fullstory cp ./upload-key.keystore Mobile-Expensify/Android @@ -252,10 +255,6 @@ jobs: name: android-hybrid-apk-artifact path: Expensify.apk - - name: Upload Android build to Firebase distribution - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane android upload_firebase_distribution - - name: Upload Android build artifact if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} uses: actions/upload-artifact@v4 @@ -302,10 +301,15 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode - - name: Decrypt Developer ID Certificate - run: cd desktop && gpg --quiet --batch --yes --decrypt --passphrase="$DEVELOPER_ID_SECRET_PASSPHRASE" --output developer_id.p12 developer_id.p12.gpg + - name: Load Desktop credentials from 1Password + id: load-credentials + uses: 1password/load-secrets-action@v2 + with: + export-env: false env: - DEVELOPER_ID_SECRET_PASSPHRASE: ${{ secrets.DEVELOPER_ID_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + DESKTOP_CERTIFICATE_BASE64: "op://Mobile-Deploy-CI/Desktop Certificates.p12/CSC_LINK" + DESKTOP_CERTIFICATE_PASSWORD: "op://Mobile-Deploy-CI/Desktop Certificates.p12/CSC_KEY_PASSWORD" - name: Build desktop app run: | @@ -315,14 +319,14 @@ jobs: npm run desktop-build-staging fi env: - CSC_LINK: ${{ secrets.CSC_LINK }} - CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + CSC_LINK: ${{ steps.load-credentials.outputs.DESKTOP_CERTIFICATE_BASE64 }} + CSC_KEY_PASSWORD: ${{ steps.load-credentials.outputs.DESKTOP_CERTIFICATE_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GCP_GEOLOCATION_API_KEY: $${{ secrets.GCP_GEOLOCATION_API_KEY_PRODUCTION }} + GCP_GEOLOCATION_API_KEY: ${{ secrets.GCP_GEOLOCATION_API_KEY_PRODUCTION }} - name: Upload desktop sourcemaps artifact uses: actions/upload-artifact@v4 @@ -377,25 +381,17 @@ jobs: max_attempts: 5 command: scripts/pod-install.sh - - name: Decrypt AppStore profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AppStore.mobileprovision NewApp_AppStore.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt AppStore Notification Service profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AppStore_Notification_Service.mobileprovision NewApp_AppStore_Notification_Service.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt certificate - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 - - name: Decrypt App Store Connect API key - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output ios-fastlane-json-key.json ios-fastlane-json-key.json.gpg + - name: Load files from 1Password env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + run: | + op read "op://Mobile-Deploy-CI/NewApp_AppStore/NewApp_AppStore.mobileprovision" --force --out-file ./NewApp_AppStore.mobileprovision + op read "op://Mobile-Deploy-CI/NewApp_AppStore_Notification_Service/NewApp_AppStore_Notification_Service.mobileprovision" --force --out-file ./NewApp_AppStore_Notification_Service.mobileprovision + op read "op://Mobile-Deploy-CI/New Expensify Distribution Certificate/Certificates.p12" --force --out-file ./Certificates.p12 + op read "op://Mobile-Deploy-CI/ios-fastlane-json-key.json/ios-fastlane-json-key.json" --force --out-file ./ios-fastlane-json-key.json - name: Get iOS native version id: getIOSVersion @@ -515,30 +511,12 @@ jobs: env: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} run: | - op read op://Mobile-Deploy-CI/firebase.json/firebase.json --force --out-file ./firebase.json - op read op://Mobile-Deploy-CI/OldApp_AppStore/OldApp_AppStore.mobileprovision --force --out-file ./OldApp_AppStore.mobileprovision - op read op://Mobile-Deploy-CI/OldApp_AppStore_Share_Extension/OldApp_AppStore_Share_Extension.mobileprovision --force --out-file ./OldApp_AppStore_Share_Extension.mobileprovision - op read op://Mobile-Deploy-CI/OldApp_AppStore_Notification_Service/OldApp_AppStore_Notification_Service.mobileprovision --force --out-file ./OldApp_AppStore_Notification_Service.mobileprovision - - - name: Decrypt AppStore profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AppStore.mobileprovision NewApp_AppStore.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt AppStore Notification Service profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AppStore_Notification_Service.mobileprovision NewApp_AppStore_Notification_Service.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt certificate - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt App Store Connect API key - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output ios-fastlane-json-key.json ios-fastlane-json-key.json.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + op read "op://Mobile-Deploy-CI/firebase.json/firebase.json" --force --out-file ./firebase.json + op read "op://Mobile-Deploy-CI/OldApp_AppStore/OldApp_AppStore.mobileprovision" --force --out-file ./OldApp_AppStore.mobileprovision + op read "op://Mobile-Deploy-CI/OldApp_AppStore_Share_Extension/OldApp_AppStore_Share_Extension.mobileprovision" --force --out-file ./OldApp_AppStore_Share_Extension.mobileprovision + op read "op://Mobile-Deploy-CI/OldApp_AppStore_Notification_Service/OldApp_AppStore_Notification_Service.mobileprovision" --force --out-file ./OldApp_AppStore_Notification_Service.mobileprovision + op read "op://Mobile-Deploy-CI/ios-fastlane-json-key.json/ios-fastlane-json-key.json" --force --out-file ./ios-fastlane-json-key.json + op read "op://Mobile-Deploy-CI/New Expensify Distribution Certificate/Certificates.p12" --force --out-file ./Certificates.p12 - name: Set current App version in Env run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV" @@ -577,10 +555,6 @@ jobs: env: BROWSERSTACK: ${{ secrets.BROWSERSTACK }} - - name: Upload iOS build to Firebase distribution - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane ios upload_firebase_distribution - - name: Upload iOS build artifact if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} uses: actions/upload-artifact@v4 diff --git a/.github/workflows/failureNotifier.yml b/.github/workflows/failureNotifier.yml index 39dfbe8e84a7..134ac0eff19f 100644 --- a/.github/workflows/failureNotifier.yml +++ b/.github/workflows/failureNotifier.yml @@ -25,7 +25,8 @@ jobs: repo: context.repo.repo, run_id: runId, }); - return jobsData.data; + const jobNamesToIgnore = ['confirmPassingBuild']; + return jobsData.data.filter(job => !jobNamesToIgnore.includes(job.name)); - name: Fetch Previous Workflow Run id: previous-workflow-run diff --git a/.github/workflows/finishReleaseCycle.yml b/.github/workflows/finishReleaseCycle.yml index 2285eec56065..ca030e95de1d 100644 --- a/.github/workflows/finishReleaseCycle.yml +++ b/.github/workflows/finishReleaseCycle.yml @@ -22,7 +22,7 @@ jobs: uses: ./.github/actions/composite/setupGitForOSBotifyApp id: setupGitForOSBotify with: - GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} @@ -87,7 +87,7 @@ jobs: id: setupGitForOSBotify uses: ./.github/actions/composite/setupGitForOSBotifyApp with: - GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} @@ -128,7 +128,7 @@ jobs: - name: Setup git for OSBotify uses: ./.github/actions/composite/setupGitForOSBotifyApp with: - GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index bfe860e60224..10ca10882464 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -104,7 +104,7 @@ jobs: - name: Setup Git for OSBotify uses: ./.github/actions/composite/setupGitForOSBotifyApp with: - GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index d00781fa2a32..80918d65462c 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -111,9 +111,6 @@ jobs: pattern: android-*-artifact merge-multiple: true - - name: Log downloaded artifact paths - run: ls -R /tmp/artifacts - - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: @@ -189,20 +186,17 @@ jobs: max_attempts: 5 command: scripts/pod-install.sh - - name: Decrypt AdHoc profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AdHoc.mobileprovision NewApp_AdHoc.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt AdHoc Notification Service profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AdHoc_Notification_Service.mobileprovision NewApp_AdHoc_Notification_Service.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + - name: Install 1Password CLI + uses: 1password/install-cli-action@v1 - - name: Decrypt certificate - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg + - name: Load files from 1Password env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + run: | + op read "op://Mobile-Deploy-CI/NewApp_AdHoc/NewApp_AdHoc.mobileprovision" --force --out-file ./NewApp_AdHoc.mobileprovision + op read "op://Mobile-Deploy-CI/NewApp_AdHoc_Notification_Service/NewApp_AdHoc_Notification_Service.mobileprovision" --force --out-file ./NewApp_AdHoc_Notification_Service.mobileprovision + op read "op://Mobile-Deploy-CI/NewApp_AdHoc_Share_Extension.mobileprovision/NewApp_AdHoc_Share_Extension.mobileprovision" --force --out-file ./NewApp_AdHoc_Share_Extension.mobileprovision + op read "op://Mobile-Deploy-CI/New Expensify Distribution Certificate/Certificates.p12" --force --out-file ./Certificates.p12 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 @@ -248,10 +242,15 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode - - name: Decrypt Developer ID Certificate - run: cd desktop && gpg --quiet --batch --yes --decrypt --passphrase="$DEVELOPER_ID_SECRET_PASSPHRASE" --output developer_id.p12 developer_id.p12.gpg + - name: Load Desktop credentials from 1Password + id: load-credentials + uses: 1password/load-secrets-action@v2 + with: + export-env: false env: - DEVELOPER_ID_SECRET_PASSPHRASE: ${{ secrets.DEVELOPER_ID_SECRET_PASSPHRASE }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + DESKTOP_CERTIFICATE_BASE64: "op://Mobile-Deploy-CI/Desktop Certificates.p12/CSC_LINK" + DESKTOP_CERTIFICATE_PASSWORD: "op://Mobile-Deploy-CI/Desktop Certificates.p12/CSC_KEY_PASSWORD" - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 @@ -263,14 +262,14 @@ jobs: - name: Build desktop app for testing run: npm run desktop-build-adhoc env: - CSC_LINK: ${{ secrets.CSC_LINK }} - CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + CSC_LINK: ${{ steps.load-credentials.outputs.DESKTOP_CERTIFICATE_BASE64 }} + CSC_KEY_PASSWORD: ${{ steps.load-credentials.outputs.DESKTOP_CERTIFICATE_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GCP_GEOLOCATION_API_KEY: $${{ secrets.GCP_GEOLOCATION_API_KEY_STAGING }} + GCP_GEOLOCATION_API_KEY: ${{ secrets.GCP_GEOLOCATION_API_KEY_STAGING }} web: name: Build and deploy Web diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index d958e0958083..9bd1b3b0f541 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -59,7 +59,7 @@ jobs: echo "REF=$(gh pr view ${{ github.event.inputs.PULL_REQUEST_NUMBER }} --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + getOldDotPR: runs-on: ubuntu-latest needs: validateActor @@ -81,7 +81,7 @@ jobs: }); const body = pullRequest.data.body; - const regex = /MOBILE-EXPENSIFY:(?\d+)/; + const regex = /MOBILE-EXPENSIFY:\s*https:\/\/github.com\/Expensify\/Mobile-Expensify\/pull\/(?\d+)/; const found = body.match(regex)?.groups?.prNumber || ""; return found.trim(); @@ -106,7 +106,7 @@ jobs: fi env: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - + postGitHubCommentBuildStarted: runs-on: ubuntu-latest @@ -153,16 +153,16 @@ jobs: cd Mobile-Expensify git fetch origin ${{ needs.getOldDotBranchRef.outputs.OLD_DOT_REF }} git checkout ${{ needs.getOldDotBranchRef.outputs.OLD_DOT_REF }} - + - name: Configure MapBox SDK run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} - name: Setup Node id: setup-node uses: ./.github/actions/composite/setupNode - with: + with: IS_HYBRID_BUILD: 'true' - + - name: Run grunt build run: | cd Mobile-Expensify @@ -192,10 +192,11 @@ jobs: env: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} run: | - op document get --output ./upload-key.keystore upload-key.keystore - op document get --output ./android-fastlane-json-key.json android-fastlane-json-key.json + op read "op://Mobile-Deploy-CI/upload-key.keystore/upload-key.keystore" --force --out-file ./upload-key.keystore + op read "op://Mobile-Deploy-CI/android-fastlane-json-key.json/android-fastlane-json-key.json" --force --out-file ./android-fastlane-json-key.json + # Copy the keystore to the Android directory for Fullstory - cp ./upload-key.keystore Mobile-Expensify/Android + cp ./upload-key.keystore Mobile-Expensify/Android - name: Load Android upload keystore credentials from 1Password id: load-credentials @@ -215,28 +216,28 @@ jobs: ANDROID_UPLOAD_KEYSTORE_ALIAS: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_ALIAS }} ANDROID_UPLOAD_KEY_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEY_PASSWORD }} run: bundle exec fastlane android build_adhoc_hybrid - + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - + - name: Upload Android AdHoc build to S3 run: bundle exec fastlane android upload_s3 env: S3_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }} S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} S3_BUCKET: ad-hoc-expensify-cash - S3_REGION: us-east-1 + S3_REGION: us-east-1 - name: Export S3 path id: exportAndroidS3Path run: | # $s3APKPath is set from within the Fastfile, android upload_s3 lane echo "S3_APK_PATH=$s3APKPath" >> "$GITHUB_OUTPUT" - + iosHybrid: name: Build and deploy iOS for testing needs: [validateActor, getBranchRef, getOldDotBranchRef] @@ -271,9 +272,9 @@ jobs: - name: Setup Node id: setup-node uses: ./.github/actions/composite/setupNode - with: + with: IS_HYBRID_BUILD: 'true' - + - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it run: | cp .env.staging .env.adhoc @@ -284,7 +285,7 @@ jobs: uses: ruby/setup-ruby@v1.204.0 with: bundler-cache: true - + - name: Install New Expensify Gems run: bundle install @@ -314,14 +315,10 @@ jobs: env: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} run: | - op read op://Mobile-Deploy-CI/OldApp_AdHoc/OldApp_AdHoc.mobileprovision --force --out-file ./OldApp_AdHoc.mobileprovision - op read op://Mobile-Deploy-CI/OldApp_AdHoc_Share_Extension/OldApp_AdHoc_Share_Extension.mobileprovision --force --out-file ./OldApp_AdHoc_Share_Extension.mobileprovision - op read op://Mobile-Deploy-CI/OldApp_AdHoc_Notification_Service/OldApp_AdHoc_Notification_Service.mobileprovision --force --out-file ./OldApp_AdHoc_Notification_Service.mobileprovision - - - name: Decrypt certificate - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + op read "op://Mobile-Deploy-CI/OldApp_AdHoc/OldApp_AdHoc.mobileprovision" --force --out-file ./OldApp_AdHoc.mobileprovision + op read "op://Mobile-Deploy-CI/OldApp_AdHoc_Share_Extension/OldApp_AdHoc_Share_Extension.mobileprovision" --force --out-file ./OldApp_AdHoc_Share_Extension.mobileprovision + op read "op://Mobile-Deploy-CI/OldApp_AdHoc_Notification_Service/OldApp_AdHoc_Notification_Service.mobileprovision" --force --out-file ./OldApp_AdHoc_Notification_Service.mobileprovision + op read "op://Mobile-Deploy-CI/New Expensify Distribution Certificate/Certificates.p12" --force --out-file ./Certificates.p12 - name: Build AdHoc app run: bundle exec fastlane ios build_adhoc_hybrid @@ -347,8 +344,6 @@ jobs: name: ios path: ./ios_paths.json - - postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing diff --git a/.github/workflows/verifyHybridApp.yml b/.github/workflows/verifyHybridApp.yml index b3db2c37d4d7..c69487a1b4e4 100644 --- a/.github/workflows/verifyHybridApp.yml +++ b/.github/workflows/verifyHybridApp.yml @@ -1,7 +1,6 @@ name: Verify HybridApp build on: - workflow_call: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] @@ -24,9 +23,23 @@ concurrency: cancel-in-progress: true jobs: + comment_on_fork: + name: Comment on all PRs that are forks + # Only run on pull requests that *are* a fork + if: ${{ github.event.pull_request.head.repo.fork }} + runs-on: ubuntu-latest + steps: + - name: Comment on forks + run: | + gh pr comment ${{github.event.pull_request.html_url }} --body \ + ":warning: This PR is possibly changing native code, it may cause problems with HybridApp. Please run an AdHoc build to verify that HybridApp will not break. :warning:" + env: + GITHUB_TOKEN: ${{ github.token }} verify_android: name: Verify Android HybridApp builds on main runs-on: ubuntu-latest-xl + # Only run on pull requests that are *not* on a fork + if: ${{ !github.event.pull_request.head.repo.fork }} steps: - name: Checkout uses: actions/checkout@v4 @@ -34,13 +47,11 @@ jobs: submodules: true ref: ${{ github.event.pull_request.head.sha }} token: ${{ secrets.OS_BOTIFY_TOKEN }} - # fetch-depth: 0 is required in order to fetch the correct submodule branch - fetch-depth: 0 - name: Update submodule to match main run: | - git submodule update --init --remote - git fetch + git submodule update --init --remote --depth 1 + cd Mobile-Expensify git checkout main - name: Configure MapBox SDK @@ -52,10 +63,14 @@ jobs: with: IS_HYBRID_BUILD: 'true' + - name: Setup Ruby + uses: ruby/setup-ruby@v1.204.0 + with: + bundler-cache: true + - name: Build Android Debug - working-directory: Mobile-Expensify/Android run: | - if ! ./gradlew assembleDebug + if ! npm run android-hybrid-build then echo "⌠Android HybridApp failed to build: Please reach out to Contributor+ and/or Expensify engineers for help in #expensify-open-source to resolve." exit 1 @@ -64,6 +79,8 @@ jobs: verify_ios: name: Verify iOS HybridApp builds on main runs-on: macos-15-xlarge + # Only run on pull requests that are *not* on a fork + if: ${{ !github.event.pull_request.head.repo.fork }} steps: - name: Checkout uses: actions/checkout@v4 @@ -71,13 +88,11 @@ jobs: submodules: true ref: ${{ github.event.pull_request.head.sha }} token: ${{ secrets.OS_BOTIFY_TOKEN }} - # fetch-depth: 0 is required in order to fetch the correct submodule branch - fetch-depth: 0 - name: Update submodule to match main run: | - git submodule update --init --remote - git fetch + git submodule update --init --remote --depth 1 + cd Mobile-Expensify git checkout main - name: Configure MapBox SDK @@ -94,9 +109,6 @@ jobs: with: bundler-cache: true - - name: Install New Expensify Gems - run: bundle install - - name: Cache Pod dependencies uses: actions/cache@v4 id: pods-cache @@ -125,16 +137,7 @@ jobs: export RCT_NO_LAUNCH_PACKAGER=1 # Build iOS using xcodebuild - if ! xcodebuild \ - -workspace Mobile-Expensify/iOS/Expensify.xcworkspace \ - -scheme Expensify \ - -configuration Debug \ - -sdk iphonesimulator \ - -arch x86_64 \ - CODE_SIGN_IDENTITY="" \ - CODE_SIGNING_REQUIRED=NO \ - CODE_SIGNING_ALLOWED=NO \ - build | xcpretty + if ! npm run ios-hybrid-build then echo "⌠iOS HybridApp failed to build: Please reach out to Contributor+ and/or Expensify engineers for help in #expensify-open-source to resolve." exit 1 diff --git a/.github/workflows/verifyParserFiles.yml b/.github/workflows/verifyParserFiles.yml new file mode 100644 index 000000000000..66fec63f40f8 --- /dev/null +++ b/.github/workflows/verifyParserFiles.yml @@ -0,0 +1,22 @@ +name: Check consistency of search parser files + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + paths: + - "src/libs/SearchParser/**" + +jobs: + verify: + if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + + - name: Verify parser files consistency + run: ./.github/scripts/checkParser.sh diff --git a/Mobile-Expensify b/Mobile-Expensify index 12b07e933d55..1e21bc34b7a1 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 12b07e933d55bb04d6446faa18287d40147aa0df +Subproject commit 1e21bc34b7a1aa98f92034d715244e1fbc885ac9 diff --git a/README.md b/README.md index 3b55f54bead2..1263b1e66b3a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ * [Running The Tests](#running-the-tests) * [Debugging](#debugging) * [App Structure and Conventions](#app-structure-and-conventions) +* [HybridApp](#HybridApp) * [Philosophy](#Philosophy) * [Security](#Security) * [Internationalization](#Internationalization) @@ -531,6 +532,15 @@ The primary difference is that the native code, which runs React Native, is loca The `Mobile-Expensify` directory is a **Git submodule**. This means it points to a specific commit on the `Mobile-Expensify` repository. +If you'd like to fetch the submodule while executing the `git pull` command in `Expensify/App` instead of updating it manually you can run this command in the root of the project: + +``` +git config submodule.recurse true +``` + +> [!WARNING] +> Please, remember that the submodule will get updated automatically only after executing the `git pull` command - if you switch between branches it is still recommended to execute `git submodule update` to make sure you're working on a compatible submodule version! + If you'd like to download the most recent changes from the `main` branch, please use the following command: ```bash git submodule update --remote diff --git a/android/app/android-fastlane-json-key.json.gpg b/android/app/android-fastlane-json-key.json.gpg deleted file mode 100644 index 386ee2b45f44..000000000000 Binary files a/android/app/android-fastlane-json-key.json.gpg and /dev/null differ diff --git a/android/app/build.gradle b/android/app/build.gradle index de0f512f1a03..c3fe780ab6d7 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 1009009005 - versionName "9.0.90-5" + versionCode 1009009500 + versionName "9.0.95-0" // 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/my-upload-key.keystore.gpg b/android/app/my-upload-key.keystore.gpg deleted file mode 100644 index e7ff57a171db..000000000000 Binary files a/android/app/my-upload-key.keystore.gpg and /dev/null differ diff --git a/android/app/src/main/res/font/expensify_mono.xml b/android/app/src/main/res/font/expensify_mono.xml index 346a34ba22b6..fe966e831e31 100644 --- a/android/app/src/main/res/font/expensify_mono.xml +++ b/android/app/src/main/res/font/expensify_mono.xml @@ -1,5 +1,7 @@ + + diff --git a/android/app/src/main/res/font/expensifymono_bolditalic.otf b/android/app/src/main/res/font/expensifymono_bolditalic.otf new file mode 100644 index 000000000000..4f3ac80d9119 Binary files /dev/null and b/android/app/src/main/res/font/expensifymono_bolditalic.otf differ diff --git a/android/app/src/main/res/font/expensifymono_italic.otf b/android/app/src/main/res/font/expensifymono_italic.otf new file mode 100644 index 000000000000..d2a7b8124b82 Binary files /dev/null and b/android/app/src/main/res/font/expensifymono_italic.otf differ diff --git a/android/gradle.properties b/android/gradle.properties index 038fb5c392e8..86ac4622e711 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -10,7 +10,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m -org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=512m +org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=2048m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit diff --git a/android/link-assets-manifest.json b/android/link-assets-manifest.json index 1c59d667fca1..9cf517a7fab4 100644 --- a/android/link-assets-manifest.json +++ b/android/link-assets-manifest.json @@ -5,6 +5,14 @@ "path": "assets/fonts/native/ExpensifyMono-Bold.otf", "sha1": "d70e12540200613e9e6ac9068bed57e4bf477bfe" }, + { + "path": "assets/fonts/native/ExpensifyMono-BoldItalic.otf", + "sha1": "ef9f92ba902942e232301c64bb55e79165435bcc" + }, + { + "path": "assets/fonts/native/ExpensifyMono-Italic.otf", + "sha1": "874301891972b3a2a6a8ece69b978e74ac9d10a2" + }, { "path": "assets/fonts/native/ExpensifyMono-Regular.otf", "sha1": "9bbd3795afea1b1136c5b6a8ecd7d470fd5ea1b2" diff --git a/assets/css/fonts.css b/assets/css/fonts.css index 7705be0b1140..7d24eb353189 100644 --- a/assets/css/fonts.css +++ b/assets/css/fonts.css @@ -40,6 +40,20 @@ src: url('/fonts/ExpensifyMono-Bold.woff2') format('woff2'), url('/fonts/ExpensifyMono-Bold.woff') format('woff'); } +@font-face { + font-family: Expensify Mono; + font-weight: 400; + font-style: italic; + src: url('/fonts/ExpensifyMono-Italic.woff2') format('woff2'), url('/fonts/ExpensifyMono-Italic.woff') format('woff'); +} + +@font-face { + font-family: Expensify Mono; + font-weight: 700; + font-style: italic; + src: url('/fonts/ExpensifyMono-BoldItalic.woff2') format('woff2'), url('/fonts/ExpensifyMono-BoldItalic.woff') format('woff'); +} + @font-face { font-family: Expensify New Kansas; font-weight: 500; diff --git a/assets/fonts/native/ExpensifyMono-BoldItalic.otf b/assets/fonts/native/ExpensifyMono-BoldItalic.otf new file mode 100644 index 000000000000..4f3ac80d9119 Binary files /dev/null and b/assets/fonts/native/ExpensifyMono-BoldItalic.otf differ diff --git a/assets/fonts/native/ExpensifyMono-Italic.otf b/assets/fonts/native/ExpensifyMono-Italic.otf new file mode 100644 index 000000000000..d2a7b8124b82 Binary files /dev/null and b/assets/fonts/native/ExpensifyMono-Italic.otf differ diff --git a/assets/fonts/web/ExpensifyMono-BoldItalic.woff b/assets/fonts/web/ExpensifyMono-BoldItalic.woff new file mode 100644 index 000000000000..898a2c908d02 Binary files /dev/null and b/assets/fonts/web/ExpensifyMono-BoldItalic.woff differ diff --git a/assets/fonts/web/ExpensifyMono-BoldItalic.woff2 b/assets/fonts/web/ExpensifyMono-BoldItalic.woff2 new file mode 100644 index 000000000000..27e67d312f3d Binary files /dev/null and b/assets/fonts/web/ExpensifyMono-BoldItalic.woff2 differ diff --git a/assets/fonts/web/ExpensifyMono-Italic.woff b/assets/fonts/web/ExpensifyMono-Italic.woff new file mode 100644 index 000000000000..8b823f33a4a6 Binary files /dev/null and b/assets/fonts/web/ExpensifyMono-Italic.woff differ diff --git a/assets/fonts/web/ExpensifyMono-Italic.woff2 b/assets/fonts/web/ExpensifyMono-Italic.woff2 new file mode 100644 index 000000000000..b7b7df6f5131 Binary files /dev/null and b/assets/fonts/web/ExpensifyMono-Italic.woff2 differ diff --git a/assets/images/export.svg b/assets/images/export.svg new file mode 100644 index 000000000000..ed6ae9897368 --- /dev/null +++ b/assets/images/export.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/images/integrationicons/netsuite-quickstart-icon-square.svg b/assets/images/integrationicons/netsuite-quickstart-icon-square.svg new file mode 100644 index 000000000000..5b8ddb542cf7 --- /dev/null +++ b/assets/images/integrationicons/netsuite-quickstart-icon-square.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/magnifying-glass-spy-mouth-closed.svg b/assets/images/magnifying-glass-spy-mouth-closed.svg new file mode 100644 index 000000000000..d5b46a70270f --- /dev/null +++ b/assets/images/magnifying-glass-spy-mouth-closed.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 9e942f21d918..d8bf79970006 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -24,6 +24,7 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `open_report_from_preview` | ✅ | Time taken to open a report from preview.

(previously `switch_report_from_preview`)

**Platforms:** All | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_thread` | ✅ | Time taken to open a thread in a report.

**Platforms:** All | Starts when user presses Report Action Item. | Stops when the `ReportActionsList` finishes laying out. | | `send_message` | ✅ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | +| `pusher_ping_pong` | ✅ | The time it takes to receive a PONG event through Pusher.

**Platforms:** All | Starts every minute and repeats on the minute. | Stops when the event is received from the server. | ## Documentation Maintenance diff --git a/desktop/developer_id.p12.gpg b/desktop/developer_id.p12.gpg deleted file mode 100644 index ad166e3f8334..000000000000 Binary files a/desktop/developer_id.p12.gpg and /dev/null differ diff --git a/docs/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards.md b/docs/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards.md deleted file mode 100644 index 54bd12ce5c49..000000000000 --- a/docs/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: Assign Company Cards -description: How to assign company cards to employees in Expensify once they have been connected or imported ---- - -After connecting or importing your company cards to Expensify, you can assign each card to its respective cardholder. - -# Assign new cards - -If you're assigning cards via CSV upload for the first time, - -1. Hover over **Settings** and click **Domains**. -2. Select the desired domain. -3. Click the card dropdown menu and select the desired feed from the list. -![Click the dropdown located right below the Imported Cards title near the top of the page. Then select a card from the list.](https://help.expensify.com/assets/images/csv-03.png){:width="100%"} - -{:start="4"} -4. Click **Assign New Cards**. - -![Under the Company Cards tab on the left, you'll use the dropdown menu to select a card and beneath that, you'll click Assign New Cards]({{site.url}}/assets/images/CompanyCards_Assign.png){:width="100%"} - -{:start="5"} -5. Enter the employee's email address and/or select it from the dropdown list. *Note: Employees must have an email address under this domain in order to assign a card to them.* -![Below the Assign a Card header, enter or select the employee's email address]({{site.url}}/assets/images/CompanyCards_EmailAssign.png){:width="100%"} - -{:start="6"} -6. Enter the last four digits of the card number and/or select it from the dropdown list. - - If no transactions have been posted on the card, the card number will not appear in the list and you'll need to enter the full card number into the field. Then press ENTER on your keyboard. The field may clear itself after you press ENTER, but you can disregard this and continue to the next step. -7. (Optional) Set the transaction start date. Any transactions that were posted before this date will not be imported into Expensify. If you do not make a selection, it will default to the earliest available transactions from the card. *Note: Expensify can only import data for the time period released by the bank. Most banks only provide a certain amount of historical data, averaging 30-90 days into the past. It's not possible to override the start date the bank has provided via this tool.* -8. Click **Assign**. - -Once assigned, you will see each cardholder associated with their card and the start date listed. The transactions will now be imported to the cardholder's account, where they can add receipts, code the expenses, and submit them for review and approval. - -![Expensify domain assigned cards](https://help.expensify.com/assets/images/ExpensifyHelp_AssignedCard.png){:width="100%"} - -# Upload new expenses for existing assigned cards - -To add new expenses to an existing uploaded and assigned card, - -1. Hover over **Settings** and click **Domains**. -2. Select the desired domain name. -3. Click **Manage/Import CSV**. -![Click Manage/Import CSV located in the top right between the Issue Virtual Card button and the Import Card button.](https://help.expensify.com/assets/images/csv-02.png){:width="100%"} - -{:start="4"} -4. Select the saved layout from the drop-down list. -5. Click **Upload CSV**. -6. Click **Update All Cards** to retrieve the new expenses for the assigned cards. - -# Unassign company cards - -{% include info.html %} -Unassigning a company card will delete any unsubmitted (Open or Unreported) expenses in the cardholder's account. -{% include end-info.html %} - -To unassign a specific card, click the Actions button to the right of the card and click **Unassign**. - -![Click the Actions button to the right of the card and select Unassign.]({{site.url}}/assets/images/CompanyCards_Unassign.png){:width="100%"} - -To completely remove the card connection, unassign every card from the list and then refresh the page. - -*Note: If expenses are Processing and then rejected, they will also be deleted when they're returned to an Open state, as the card they're linked to no longer exists.* - -{% include faq-begin.md %} - -**My Commercial Card Feed is set up. Why is a specific card not coming up when I try to assign it to an employee?** - -Cards will appear in the dropdown when they are activated and have at least one posted transaction. If the card is activated and has been used for a while and you're still not seeing it, reach out to your Account Manager or message concierge@expensify.com for further assistance. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings.md b/docs/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings.md deleted file mode 100644 index 75580b94f1ad..000000000000 --- a/docs/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: Configure Company Card Settings -description: How to customize your company card settings ---- - -Once you’ve imported your company cards via [commercial card feed](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Commercial-Card-Feeds), [direct bank feed](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Direct-Bank-Connections), or [CSV import](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import), the next step is to configure the card settings. - -{% include info.html %} -You must be a Domain Admin to complete this process. -{% include end-info.html %} - -# Configure company card settings - -1. Hover over **Settings** and click **Domains**. -2. Select the desired domain. -3. Click the **Settings** tab located at the top of the Company Cards tab. -![Near the top right, click the Settings tab that is located between the Card List and Reconciliation tabs.](https://help.expensify.com/assets/images/compcard-01.png){:width="100%"} -5. Set the following preferences, then click **Save**. - -## Preferred Workspace - -Setting a preferred Workspace for a company card feed ensures that the imported transactions are added to a report for that Workspace. This is useful when members are on multiple Workspaces and need to ensure their company card expenses are reported to a particular Workspace. - -## Reimbursable preference - -You can control how your employees' company card expenses are flagged for reimbursement: - -- **Force Yes**: All expenses will be marked as reimbursable. Employees cannot change this setting. -- **Force No**: All expenses will be marked as non-reimbursable. Employees cannot change this setting. -- **Do Not Force**: Expenses will default to either reimbursable or non-reimbursable (your choice), but employees can adjust if necessary. - -## Liability type - -Choose the liability type that suits your needs: - -- **Corporate Liability**: Users cannot delete company card expenses. -- **Personal Liability**: Users are allowed to delete company card expenses. - -If you update the settings on an existing company card feed, the changes will apply to expenses imported after the date that the setting is saved. The update will not affect previously imported expenses. - -# Use Scheduled Submit with company cards - -With Scheduled Submit, employees no longer have to create their expenses, add them to a report, and submit them manually. All they need to do is SmartScan their receipts and Concierge will take care of the rest using a variety of schedules that you can set according to your preferences. - -{% include info.html %} -Concierge won't automatically submit expenses on reports that have expense violations. These expenses will be moved to a new report for the current reporting period. -{% include end-info.html %} - -To enable Scheduled Submit, - -1. Hover over **Settings** and click **Workspaces**. -2. Select the desired Workspace. -3. Click the **Reports** tab on the left. -4. Enable the Scheduled Submit toggle. -5. Select the report submission frequency. -6. Select the date that reports will be submitted. - -# Connect company cards to an accounting integration - -If you're using a connected accounting system such as NetSuite, Xero, Intacct, Quickbooks Desktop, or QuickBooks Online, you can also connect the card to export to a specific credit card GL account. First, connect the card itself, and once completed, follow the steps below: - -1. Hover over **Settings** and click **Domains** -2. Select the desired domain. -3. Click **Edit Exports** near the top right and select the general ledger (GL) account you want to export expenses to. -![Find the desired card in the table. In that same row, click Edit Exports.](https://help.expensify.com/assets/images/cardfeeds-02.png){:width="100%"} - -Once the account is set, exported expenses will be mapped to the selected account when exported by a Domain Admin. - -# Export company card expenses to a connected accounting integration - -## Pooled GL account - -To export credit card expenses to a pooled GL account, - -1. Hover over **Settings** and click **Workspaces**. -2. Select the desired Workspace. -3. Click the **Connections** tab on the left. -4. Under Accounting Integrations, click **Configure** next to the desired accounting integration. -5. For Non-reimbursable export, select **Credit Card / Charge Card / Bank Transaction**. -6. Review the Export Settings page for exporting Expense Reports to NetSuite. -7. Select the Vendor/liability account you want to export all non-reimbursable expenses to. - -## Individual GL account - -1. Hover over **Settings** and click **Domains**. -2. Select the desired Domain. -3. Click the **Edit Exports** to the right of the desired card. Then select the general ledger (GL) account you want to export expenses to. -![Find the desired card in the table. In that same row, click Edit Exports.](https://help.expensify.com/assets/images/cardfeeds-02.png){:width="100%"} - -Once the account is set, exported expenses will be mapped to the selected account. - -# Identify company card transactions - -When you link your credit cards to Expensify, the transactions will appear in each user's account on the Expenses page as soon as they're posted. Transactions from centrally managed cards have a locked card icon next to them to indicate that they’re company card expenses. - -# Import historical transactions - -Once a card is connected via direct connection or via Approved! banks, Expensify will import 30-90 days of historical transactions to your account (based on your bank's discretion). Any historical expenses beyond that date range can be imported using the [CSV import](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import). - -# Use eReceipts - -Expensify eReceipts are digital substitutes for paper receipts, eliminating the need to keep physical receipts or use SmartScan for receipts. For Expensify Card transactions, eReceipts are automatically generated for all amounts in these categories: Airlines, Commuter expenses, Gas, Groceries, Mail, Meals, Car rental, Taxis, and Utilities. For other card programs, eReceipts are generated for USD purchases of $75 or less. - -{% include info.html %} -To ensure seamless automatic importation, it is key that you maintain your transactions in US Dollars. eReceipts can also be directly imported from your bank account. CSV/OFX imported files of bank transactions do not support eReceipts. eReceipts are not generated for lodging expenses. Due to incomplete or inaccurate category information from certain banks, there may be instances of invalid eReceipts being generated for hotel purchases. If you choose to re-categorize expenses, a similar situation may arise. It's crucial to remember that our Expensify eReceipt Guarantee excludes coverage for hotel and motel expenses. -{% include end-info.html %} - -{% include faq-begin.md %} - -**What plan/subscription is required in order to manage corporate cards?** - -A Group Workspace is required. - -**When do my company card transactions import to Expensify?** - -Credit card transactions are imported to Expensify once they’re posted to the bank account. This usually takes 1-3 business days between the point of purchase and when the transactions populate in your account. - -**Scheduled Submit is disabled. Why are reports still being submitted automatically?** - -If Scheduled Submit is disabled at the Group Workspace level or set to a manual frequency but expense reports are still being automatically submitted, Scheduled Submit is most likely enabled on the user’s Individual Workspace settings. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connect-credit-cards/Manage-Company-Cards.md b/docs/articles/expensify-classic/connect-credit-cards/Manage-Company-Cards.md new file mode 100644 index 000000000000..059cd8cf0cde --- /dev/null +++ b/docs/articles/expensify-classic/connect-credit-cards/Manage-Company-Cards.md @@ -0,0 +1,169 @@ +--- +title: Manage Company Credit Cards in Expensify +description: Learn how to assign, manage, and unassign company cards in Expensify to streamline expense tracking and maintain accurate transaction records. +--- +Assigning company cards in Expensify ensures employees can seamlessly track business expenses, while administrators maintain accurate transaction records. This guide explains how to assign, manage, and unassign company cards to keep your expense management streamlined + +## Main Uses +- **Streamlined Expense Tracking**: Automatically import credit card transactions to ensure accurate reporting and reconciliation. +- **Flexible Connection Options**: Connect cards via direct bank connections, commercial feeds, or CSV uploads. +- **Centralized Management**: Simplify administration of company cards and individual expenses within Expensify. + +## Core Users +- **Domain Admins**: Manage credit card connections and assignments across the organization. +- **Employees**: Track and manage their assigned card transactions. +- **Finance Teams**: Ensure accurate reporting and reconciliation of expenses. + +## Advantages +- **Automation**: Reduce manual entry with automatic imports for supported card programs. +- **Compatibility**: Support for major banks and custom CSV uploads for unsupported cards. +- **Control**: Assign, manage, and configure credit cards in one central place. + +--- +# Key Concepts + +## Direct Bank Connections +A direct bank connection links Expensify to your bank for automatic transaction imports. + +- **Supported Banks**: Includes major institutions like Chase, Amex, Citibank, Wells Fargo, and others. +- **Credentials**: Requires master administrative login credentials. +- **Import History**: Banks typically provide 30-90 days of historical transactions. +- **Troubleshooting**: If your connection fails, ensure your bank login credentials are correct and security questions are up to date. Disable two-factor authentication temporarily if necessary, as Expensify cannot bypass it. Use the "Fix Card" option in **Settings > Domains > [Domain Name] > Company Cards** to resolve issues. + +## Commercial Card Feed +A commercial card feed is a direct connection between Expensify and your bank for reliable daily transaction imports. + +- **Supported Networks**: Visa, Mastercard, and American Express. +- **Setup**: Initiated by your bank and configured in Expensify. +- **Advantages**: More stable and not affected by changes to login credentials. +- **Deep Dive**: For Mastercard, use the Common Data File (CDF) format; for Visa, enable the Variant Call Format (VCF). American Express requires specific forms. Ensure you provide accurate distribution IDs and start dates during setup to avoid data gaps. + +## CSV Upload +A CSV upload is a manual method for importing credit card transactions into Expensify. + +- **File Formats**: Accepts CSV, OFX, QFX, or XLS files. +- **Minimum Required Fields**: Includes card number, date, merchant, amount, and currency. +- **Use Case**: Ideal for unsupported banks or importing older transactions. +- **Common Issues**: If you encounter errors such as "Attribute value mapping is missing," ensure all required fields are included and formatted correctly. Use Expensify’s [CSV template](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1594908368712-Best+Example+CSV+for+Domains.csv) as a guide. + +--- +# Direct Connection: How do I connect credit cards via direct bank connection? +1. Go to **Settings > Domains > [Domain Name] > Company Cards**. +2. Click **Import Card**. +3. Select your card issuer and enter the master administrative login credentials. +4. Assign accounts to cardholders. +5. Set a start date to determine which transactions will appear. + +--- +# Commercial Card Feed: How do I set up a commercial card feed? + +## Mastercard +1. Contact your bank and request a Common Data File (CDF) feed for Expensify. +2. Provide the desired start date for transaction imports. +3. Submit the distribution ID to Expensify via the [submission form](https://expensify.typeform.com/to/cGlCAz). +4. Expensify will notify you once the feed is enabled. + +## Visa +1. Request a Variant Call Format (VCF) feed for Expensify from your bank. +2. Obtain the feed file name or raw file information. +3. Submit the file details via the [submission form](https://expensify.typeform.com/to/cGlCAz). + +## American Express +1. Complete the required [Amex forms](https://drive.google.com/file/d/1zqDA_MCk06jk_fWjzx2y0r4gOyAMqKJe/view?usp=sharing). +2. Email the forms to American Express. +3. Submit the provided production file information to Expensify. + +--- +# CSV Upload: How do I import credit card transactions using a CSV? +1. Download your card transactions from your bank in CSV, OFX, QFX, or XLS format. +2. Format the CSV for upload using [this template](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1594908368712-Best+Example+CSV+for+Domains.csv) as a guide -- be sure to include the card number, date, merchant, amount, and currency. + - You can also add mapping for Categories and Tags, but those parameters are optional. + +![Your CSV template should include, at a minimum, a column for the card number, posted date, merchant, posted amount, and posted currency.](https://help.expensify.com/assets/images/csv-01.png){:width="100%"} + +3. Go to **Settings > Domains > [Domain Name] > Company Cards > Manage/Import CSV**. +4. Upload the file and map the fields to Expensify’s requirements. +5. Review the Output Preview for errors and submit the file. + + +![Click Manage/Import CSV located in the top right between the Issue Virtual Card button and the Import Card button.](https://help.expensify.com/assets/images/csv-02.png){:width="100%"} + +--- +# Assign Cards: How do I assign cards to employees? +1. Go to **Settings > Domains > [Domain Name] > Company Cards**. +2. Select the card feed from the dropdown list. +3. Click **Assign New Cards** and select the employee’s email and card number. +4. (Optional) Set a transaction start date. +5. Click **Assign** to complete the process. + +![Click the dropdown located right below the Imported Cards title near the top of the page. Then select a card from the list.](https://help.expensify.com/assets/images/csv-03.png){:width="100%"} + +![Under the Company Cards tab on the left, you'll use the dropdown menu to select a card and beneath that, you'll click Assign New Cards]({{site.url}}/assets/images/CompanyCards_Assign.png){:width="100%"} + +--- +# Unassign Cards: How do I unassign cards? +1. Go to **Settings > Domains > [Domain Name] > Company Cards**. +2. Locate the card and click **Actions > Unassign**. +3. (Optional) To remove the card feed, unassign all associated cards and refresh the page. + +_**Note: Unassigning a card deletes all open or unreported expenses linked to it.**_ + +![Click the Actions button to the right of the card and select Unassign.]({{site.url}}/assets/images/CompanyCards_Unassign.png){:width="100%"} + +--- +# Configure Company Card Settings +1. Go to **Settings > Domains > [Domain Name] > Company Cards > Settings**. +2. Adjust preferences for: + - **Preferred Workspace**: Ensures transactions are reported to a specific workspace. + - **Reimbursable Preference**: Controls whether expenses are flagged as reimbursable or non-reimbursable. + - **Liability Type**: Set the corporate or personal liability settings for company card expenses. Corporate liability prevents users from deleting company card expenses, while personal liability allows users to manage and delete their company card expenses. +3. Save the settings to apply changes. + +![Near the top right, click the Settings tab that is located between the Card List and Reconciliation tabs.](https://help.expensify.com/assets/images/compcard-01.png){:width="100%"} + +## Export Company Card Expenses to a Connected Accounting Integration: Centralized General Ledger Account + +For businesses using accounting integrations like QuickBooks or NetSuite, connect the cards to export expenses to specific general ledger (GL) accounts via the "Edit Exports" option. + +![Find the desired card in the table. In that same row, click Edit Exports.](https://help.expensify.com/assets/images/cardfeeds-02.png){:width="100%"} + +**To export credit card expenses to a pooled GL account:** +1. Navigate to **Settings > Workspaces**. +2. Select the appropriate **Workspace**. +3. Open the **Connections** tab. +4. Under **Accounting Integrations**, click **Configure** next to the relevant integration. +5. In the **Non-reimbursable Export** section, select **Credit Card / Charge Card / Bank Transaction**. +6. Review the **Export Settings** page to ensure proper expense report export to NetSuite. +7. Choose the **Vendor/Liability Account** for exporting all non-reimbursable expenses. + +## Exporting to Individual General Ledger Accounts + +1. Navigate to **Settings > Domains**. +2. Select the appropriate **Domain**. +3. Click **Edit Exports** next to the relevant card. +4. Select the **General Ledger (GL) Account** for expense exports. + +--- + +# FAQ + +## Missing Transactions: Why aren’t all transactions appearing? +Transactions may take up to 24 hours to post. Update the card feed or check the Reconciliation Dashboard to review transaction data. + +- **Reconciliation Dashboard**: Navigate to **Settings > Domains > [Domain Name] > Company Cards > Reconciliation** to identify missing or unapproved expenses within a specific timeframe. +- **Common Issues**: Ensure the transaction date range is correct. If gaps persist, contact Concierge to request a historical data update. + +## Direct vs. Commercial Feeds: Which option is better? +Commercial feeds are more stable and reliable but require setup by your bank. Direct connections are easier to set up but may experience occasional disruptions. + +## CSV Upload Errors: What should I do if my upload fails? +Ensure the file includes the required fields and matches the formatting guidelines. Use Expensify’s [CSV template](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1594908368712-Best+Example+CSV+for+Domains.csv) for reference. + +## Unassigning Cards: What happens when I unassign a card? +Unassigning a card deletes all open or unreported expenses linked to it. Submitted or processed expenses remain unaffected. + +## Connection Issues: Why is my card connection broken? +Check for changes to bank credentials, security questions, or login details. Update the information in Expensify and re-establish the connection via **Fix Card**. + +## Historical Data: How far back can I import transactions? +Most banks provide 30-90 days of historical data. Older transactions can be imported using the CSV upload method. diff --git a/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md index 02baa7f30570..089b06750053 100644 --- a/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md +++ b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md @@ -59,7 +59,9 @@ This dictates when reimbursable expenses will export, according to your preferre **Journal Entries:** Non-reimbursable expenses will be posted to the Journal Entries posting account selected in your workspace's connection settings. If you centrally manage your company cards through Domains, you can export expenses from each card to a specific account in NetSuite. -- Expensify Card expenses will always export as Journal Entries, even if you have Expense Reports or Vendor Bills configured for non-reimbursable expenses on the Export tab +- When [automatic reconciliation](https://help.expensify.com/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation) is enabled, Expensify Card expenses will always export as individual, itemized Journal Entries, regardless of Expense Reports or Vendor Bills settings configured for non-reimbursable expenses on the Export tab. +- Without automatic reconciliation, Expensify Card expenses will export using the export type configured for non-reimbursable expenses on the Export tab. +- Expensify Card expenses exported as Journal Entries will always export as individual, itemized Journal Entries, regardless of whether the "one journal entry for all items on report" setting is enabled. - Journal entry forms do not contain a customer column, so it is not possible to export customers or projects with this export option - The credit line and header level classifications are pulled from the employee record diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md index ed390e928fa4..ad02afc4747b 100644 --- a/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md @@ -1,112 +1,95 @@ --- title: Configure QuickBooks Desktop -description: Configure QuickBooks Desktop +description: Learn how to configure QuickBooks Desktop with Expensify, including export, import, and advanced settings. --- -Our new QuickBooks Desktop integration allows you to automate the import and export process with Expensify. -# Step 1: Configure export settings -The following steps will determine how data will be exported from Expensify to QuickBooks Desktop. +Our QuickBooks Desktop integration automates the import and export process between Expensify and QuickBooks Desktop. -![Expensify export settings page for the QuickBooks Desktop integration](https://help.expensify.com/assets/images/quickbooks-desktop-export-settings.png){:width="100%"} +# Step 1: Configure Export Settings +The following steps determine how data is exported from Expensify to QuickBooks Desktop. + +![QuickBooks Desktop export settings](https://help.expensify.com/assets/images/quickbooks-desktop-export-settings.png){:width="100%"} 1. In Expensify, hover over **Settings** and click **Workspaces**. 2. Select the Workspace you want to connect to QuickBooks Desktop. -3. Click the **Connections** tab. +3. Click the **Connections** tab. 4. Click **Export** under the QuickBooks Desktop connection. -5. Review each of the following export settings: -- **Preferred Exporter**: This person is used in QuickBooks Desktop as the export user. They will receive notifications for errors, as well as prompts to export reports via the Home page of their Expensify account. -- **Date**: You can choose either the report’s submitted date, the report’s exported date, or the date of the last expense on the report when exporting reports to QuickBooks Desktop. -- **Unique reference numbers**: Enable this to allow the use of a unique reference number for each transaction. Disable this to use the same Report ID for all expenses from a certain report. -- **Reimbursable expenses**: Reimbursable options include: - - **Vendor Bill (recommended)**: A single itemized vendor bill for each Expensify report. An A/P account is required to export to a vendor bill. - - **Check**: A single itemized check for each Expensify report. - - **Journal Entry**: A single itemized journal entry for each Expensify report. -- **Non-reimbursable expenses**: Non-reimbursable options include: - - **Credit Card**: Each expense appears as a separate credit card transaction with a post date that matches your credit card statement. If you centrally manage company cards through your domain, you can export expenses from each card to a specific QuickBooks account by clicking Edit Exports next to each user’s card. To display the merchant name in the payee field in QuickBooks Desktop, ensure that a matching Vendor exists in QuickBooks. Expensify searches for an exact match during export. If no match is found, the payee is mapped to a Credit Card Misc. Vendor created by Expensify. - - **Check**: Expenses are exported as individual itemized checks for each Expensify report. The check is written to the “vendor,†which is the person who created or submitted the report in Expensify. - - **Vendor Bill**: Each Expensify report results in a single itemized vendor bill. The bill is associated with the “vendor,†which is the individual responsible for creating or submitting the report in Expensify. - -# Step 2: Configure coding/import settings - -The following steps help you determine how data will be imported from QuickBooks Desktop to Expensify: - -![Expensify coding settings page for the QuickBooks Desktop integration](https://help.expensify.com/assets/images/quickbooks-desktop-coding-settings.png){:width="100%"} - -1. Click Import under the QuickBooks Desktop connection. -2. Review each of the following import settings: -- **Chart of Accounts**: The Chart of Accounts is automatically imported from QuickBooks Desktop as categories. This cannot be amended. -- **Classes**: Choose whether to import classes, which will be shown in Expensify as tags for expense-level coding. -- **Customers/Projects**: Choose whether to import customers/projects, which will be shown in Expensify as tags for expense-level coding. -- **Locations**: Choose whether to import locations, which will be shown in Expensify as tags for expense-level coding. - -# Step 3: Configure advanced settings - -The following steps help you determine the advanced settings for your connection, like auto-sync and employee invitation settings. - -![Expensify advanced settings page for the QuickBooks Desktop integration](https://help.expensify.com/assets/images/quickbooks-desktop-advanced-settings.png){:width="100%"} +5. Review the following export settings: + - **Preferred Exporter**: The designated user in QuickBooks Desktop who receives error notifications and export prompts. + - **Date**: Choose whether to use the report’s submitted date, exported date, or last expense date. + - **Unique Reference Numbers**: Enable to generate a unique reference number for each transaction. + - **Reimbursable Expenses**: + - **Vendor Bill (recommended)**: Creates a single itemized vendor bill per Expensify report. + - **Check**: Creates a single itemized check per Expensify report. + - **Journal Entry**: Creates a single itemized journal entry per Expensify report. + - **Non-Reimbursable Expenses**: + - **Credit Card**: Each expense appears as a separate transaction with a matching post date. Ensure the merchant exists in QuickBooks for accurate payee mapping. + - **Check**: Creates an itemized check for each report. + - **Vendor Bill**: Creates a vendor bill associated with the report submitter. + +# Step 2: Configure Coding/Import Settings +These settings determine how data is imported from QuickBooks Desktop into Expensify. + +![QuickBooks Desktop coding settings](https://help.expensify.com/assets/images/quickbooks-desktop-coding-settings.png){:width="100%"} + +1. Click **Import** under the QuickBooks Desktop connection. +2. Review the following import settings: + - **Chart of Accounts**: Automatically imported as categories. + - **Classes**: Optional import as tags for expense coding. + - **Customers/Projects**: Optional import as tags for expense coding. + - **Locations**: Optional import as tags for expense coding. + +# Step 3: Configure Advanced Settings +These settings help manage auto-sync and employee invitation preferences. + +![QuickBooks Desktop advanced settings](https://help.expensify.com/assets/images/quickbooks-desktop-advanced-settings.png){:width="100%"} 1. Click **Advanced** under the QuickBooks Desktop connection. -2. **Enable or disable Auto-Sync**: If enabled, QuickBooks Desktop automatically communicates changes with Expensify to ensure that the data shared between the two systems is up to date. New report approvals/reimbursements will be synced during the next auto-sync period. +2. **Enable or Disable Auto-Sync**: When enabled, changes are automatically synced between Expensify and QuickBooks Desktop. {% include info.html %} -Please note that Auto-Sync will queue information to be added to your QuickBooks Company File the next time both your QuickBooks Company File and QuickBooks Web Connector are open. +Auto-Sync queues updates to be added to your QuickBooks Company File the next time both QuickBooks Desktop and QuickBooks Web Connector are open. {% include end-info.html %} # FAQ -## **How do I manually sync my QuickBooks Desktop if I have Auto-Sync disabled?** - -To manually sync your connection: - -1. In Expensify, hover over **Settings** and select **Workspaces**. -2. Click the Workspace name that is connected to QuickBooks Desktop. -3. Click the **Connections** tab on the left. +## How do I manually sync QuickBooks Desktop if Auto-Sync is disabled? +1. In Expensify, go to **Settings** > **Workspaces**. +2. Click the Workspace connected to QuickBooks Desktop. +3. Click the **Connections** tab. 4. Click **Sync Now** under QuickBooks Desktop. {% include info.html %} -For manual syncing, we recommend completing this process at least once a week and/or after making changes in QuickBooks Desktop that could impact how reports export from Expensify. Changes may include adjustments to your chart of accounts, vendors, employees, customers/jobs, or items. Remember: Both the Web Connector and QuickBooks Desktop need to be running for syncing or exporting to work. +We recommend manually syncing at least once a week or after making changes in QuickBooks Desktop that impact report exports. {% include end-info.html %} -## **Can I sync Expensify and QuickBooks Desktop and use the platforms at the same time?** - -When syncing Expensify to QuickBooks Desktop, we recommend waiting until the sync finishes to access either Expensify and/or QuickBooks Desktop, as performance may vary during this process. You cannot open an instance of QuickBooks Desktop while a program is syncing - this may cause QuickBooks Desktop to behave unexpectedly. - -## **What are the different types of accounts that can be imported from QuickBooks Desktop?** +## Can I use Expensify and QuickBooks Desktop at the same time while syncing? +We recommend waiting for the sync to complete before using either platform, as performance may be affected. -Here is the list of accounts from QuickBooks Desktop and how they are pulled into Expensify: +## What types of accounts can be imported from QuickBooks Desktop? -| QuickBooks Desktop account type | How it imports to Expensify | -| ------------- | ------------- | -| Accounts payable | Vendor bill or journal entry export options | -| Accounts receivable | Do not import | -| Accumulated adjustment | Do not import | -| Bank | Debit card or check export options | -| Credit card | Credit card export options | -| Equity | Do not import | -| Fixed assets | Categories | -| Income | Do not import | -| Long-term liabilities | Do not import | -| Other assets | Do not import | -| Other current assets | Categories or journal entry export options | -| Other current liabilities | Journal Entry export options if the report creator is set up as an Employee within QuickBooks | -| Other expense | All detail types except Exchange Gain or Loss import as categories; Exchange Gain or Loss does not import | -| Other income | Do not import | +| QuickBooks Desktop Account Type | How it Imports to Expensify | +|----------------------------|----------------------------------| +| Accounts Payable | Vendor bill or journal entry | +| Accounts Receivable | Not imported | +| Bank | Debit card or check | +| Credit Card | Credit card export options | +| Fixed Assets | Categories | +| Other Current Assets | Categories or journal entry | +| Other Current Liabilities | Journal entry (if report creator is an Employee) | +| Other Expense | All detail types import except Exchange Gain/Loss | -## **Why are exports showing as “Credit Card Misc.â€?** +## Why are exports showing as "Credit Card Misc."? +Expensify checks for an exact vendor match when exporting credit or debit card expenses. If no match is found, expenses are mapped to a "Credit Card Misc." vendor. -When exporting as credit or debit card expenses, Expensify checks for an exact vendor match. If none are found, the payee will be mapped to a vendor that Expensify will automatically create and label as Credit Card Misc. or Debit Card Misc. - -If you centrally manage your company cards through domains, you can export expenses from each card to a specific account in QuickBooks: - -1. In Expensify, hover over Settings and click Domains. +To export expenses to a specific QuickBooks account: +1. In Expensify, go to **Settings** > **Domains**. 2. Select the desired domain. -3. Click the **Company Cards** tab. +3. Click the **Company Cards** tab. 4. Click **Export**. -## **How does multi-currency work with QuickBooks Desktop?** - -When using QuickBooks Desktop Multi-Currency, there are some limitations to consider based on your export options: +## How does multi-currency work with QuickBooks Desktop? -- **Vendor Bills and Checks**: The currency of the vendor and the currency of the account must match, but they do not have to be in the home currency. -- **Credit Card**: If an expense doesn’t match an existing vendor in QuickBooks, it exports to the Credit Card Misc. vendor created by Expensify. When exporting a report in a currency other than your home currency, the transaction will be created under the vendor’s currency with a 1:1 conversion. For example, a transaction in Expensify for $50 CAD will appear in QuickBooks as $50 USD. -- **Journal Entries**: Multi-currency exports will fail because the account currency must match both the vendor currency and the home currency. +- **Vendor Bills and Checks**: The vendor and account currencies must match but do not need to be in the home currency. +- **Credit Card**: If a matching vendor is not found, the expense exports to the "Credit Card Misc." vendor. Foreign currency transactions are converted 1:1. +- **Journal Entries**: Multi-currency exports fail unless the account, vendor, and home currency match. diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md index c832667080d5..f12005725ca3 100644 --- a/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md @@ -1,114 +1,114 @@ --- -title: Quickbooks Desktop Troubleshooting -description: Quickbooks Desktop Troubleshooting +title: QuickBooks Desktop Troubleshooting +description: Troubleshoot common QuickBooks Desktop issues, including connection problems, import/export errors, and sync failures. --- -# The Web Connector cannot be reached +# The Web Connector Cannot Be Reached -Generally, these errors indicate that there is a connection issue, where there’s a breakdown between Expensify and QuickBooks. +These errors indicate a connection issue between Expensify and QuickBooks. -## How to resolve +## How to Resolve -1. Make sure that the Web Connector and QuickBooks Desktop are both running. -2. Make sure that the Web Connector is installed in the same location as your QuickBooks application. For example, if QuickBooks is installed on your local desktop, the Web Connector should be too. Or if QuickBooks is installed on a remote server, the Web Connector should be installed there as well. +1. Ensure both the Web Connector and QuickBooks Desktop are running. +2. Verify that the Web Connector is installed in the same location as QuickBooks: + - If QuickBooks is on your local desktop, the Web Connector should be too. + - If QuickBooks is on a remote server, install the Web Connector there as well. If the error persists: - -1. Close the Web Connector completely (you may want to use Task Manager to do this). -2. Right-click the Web Connector icon on your desktop and select **Run as administrator**. -3. Sync your Workspace again. -If this doesn’t work, the final troubleshooting steps should be: +1. Close the Web Connector completely using Task Manager if needed. +2. Right-click the Web Connector icon and select **Run as administrator**. +3. Sync your Workspace again. -1. Quit QuickBooks Desktop, then reopen it. -2. In Expensify, hover over **Settings** and select **Workspaces**. -3. Click the workspace name that is connected to QuickBooks Desktop. -4. Click the **Connections** tab on the left. -5. Click **QuickBooks Desktop**. -6. Click **Sync Now**. -7. If this still doesn’t resolve the issue, use the link to reinstall the Web Connector. +Final troubleshooting steps: -# Connection and/or authentication issue +1. Restart QuickBooks Desktop. +2. In Expensify, go to **Settings** > **Workspaces**. +3. Select the connected Workspace. +4. Click the **Connections** tab and select **QuickBooks Desktop**. +5. Click **Sync Now**. +6. If the issue persists, reinstall the Web Connector. -Generally, these errors indicate that there is a credentials issue. +# Connection or Authentication Issues -## How to resolve +These errors indicate a credentials issue. -1. Make sure QuickBooks Desktop is open with the correct company file. This must be the same company file that you have connected to Expensify. -2. Make sure the QuickBooks Web Connector is open and the connector is online. -3. Make sure that there are no dialogue boxes open in QuickBooks that are interfering with attempts to sync or export. To resolve this, close any open windows in QuickBooks Desktop so that you only see a gray screen, then try exporting or syncing again. -4. Check that you have the correct permissions. -5. Log in to QuickBooks Desktop as an Admin (in single-user mode). -6. Go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**. +## How to Resolve -![Company Preferences page of QuickBooks Desktop](https://help.expensify.com/assets/images/quickbooks-desktop-company-preferences.png){:width="100%"} +1. Ensure QuickBooks Desktop is open with the correct company file. +2. Confirm that the QuickBooks Web Connector is online. +3. Close any open dialogue boxes in QuickBooks that may interfere with syncing. +4. Check that you have the correct permissions: + - Log in to QuickBooks Desktop as an Admin in single-user mode. + - Go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**. -7. Select the Web Connector and click **Properties**. + ![QuickBooks Desktop Company Preferences](https://help.expensify.com/assets/images/quickbooks-desktop-company-preferences.png){:width="100%"} -![Web Connector Properties page in QuickBooks Desktop](https://help.expensify.com/assets/images/quickbooks-desktop-access-rights.png){:width="100%"} +5. Select the Web Connector and click **Properties**. -8. Make sure that the "Allow this application to login automatically" checkbox is selected and click **OK**. -9. Close all windows in QuickBooks. + ![QuickBooks Desktop Web Connector Access Rights](https://help.expensify.com/assets/images/quickbooks-desktop-access-rights.png){:width="100%"} -If these general troubleshooting steps don’t work, reach out to Concierge and have the following information ready to provide: +6. Check **Allow this application to login automatically** and click **OK**. -1. What version of QuickBooks Desktop do you have (Enterprise 2016, Pro 2014, etc.)? -2. Is your QuickBooks program installed on your computer or a remote network/drive? -3. Is your QuickBooks company file installed on your computer or a remote network/drive? -4. Is your Web Connector installed on your computer or a remote network/drive? -5. If any of the above are on a remote option, is there a company that runs that remote environment? If so, who (ie: RightNetworks, SwissNet, Cloud9, etc.)? +If the issue persists, contact Concierge with: +- QuickBooks Desktop version (e.g., Enterprise 2016, Pro 2014) +- Installation details (local or remote) +- Web Connector installation location +- Remote environment provider (if applicable) -# Import issue or missing categories and/or tags +# Import Issues or Missing Categories/Tags -Generally, if you are having issues importing data from QuickBooks to Expensify, this indicates that the integration needs to be updated or your version of QuickBooks may not support a specific configuration. +If data is not importing, the integration may need updating. -## How to resolve +## How to Resolve -1. Re-sync the connection between Expensify and QuickBooks Desktop. A fresh sync can often resolve any issues, especially if you have recently updated your chart of accounts or projects, customers, or jobs in QuickBooks Desktop. -2. Check your configuration in QuickBooks Desktop. Expensify will import the chart of accounts to be utilized either as categories or export account options, while projects, customers, and tags will be imported as tags. +1. Re-sync Expensify and QuickBooks Desktop. +2. Check your QuickBooks Desktop configuration: + - The Chart of Accounts imports as categories. + - Projects, customers, and jobs import as tags. -If these general troubleshooting steps don’t work, reach out to Concierge with context on what is specifically missing in Expensify, as well as screenshots from your QuickBooks Desktop setup. +If the issue persists, contact Concierge with missing data details and screenshots. -# Export or "can't find category/class/location/account" issue +# Export or "Can't Find Category/Class/Location/Account" Issues -Generally, when an export error occurs, we’ll share the reason in the Report Comments section at the bottom of the report. This will give you an indication of how to resolve the error. +Errors during export are noted in the **Report Comments** section. -## How to resolve +## How to Resolve -1. Re-sync the connection between Expensify and QuickBooks Desktop. A fresh sync can often resolve any issues, especially if you have recently updated your chart of accounts or projects, customers, or jobs in QuickBooks Desktop. -2. Re-apply coding to expenses and re-export the report. If you’ve recently synced Expensify and QuickBooks or recently made changes to your Workspace category or tags settings, you may need to re-apply coding to expenses. -3. Make sure that your current version of QuickBooks Desktop supports the selected export option. Different versions of QuickBooks Desktop support different export options and the [version that you own](https://quickbooks.intuit.com/desktop/) may not be compatible with the export type. +1. Re-sync Expensify and QuickBooks Desktop. +2. Re-apply coding to expenses and re-export the report. +3. Verify that your QuickBooks Desktop version supports the selected export option. -If these general troubleshooting steps don’t work, reach out to Concierge with the Report ID, some context on what you’re trying to do, and a screenshot of the Expensify error message. +If the issue persists, contact Concierge with the Report ID and a screenshot of the error message. -# “Oops!†error when syncing or exporting +# "Oops!" Error When Syncing or Exporting -Generally, an “Oops!†error can often be temporary or a false error. Although you will see a message pop up, there may actually not be an actual issue. +This error may be temporary or a false flag. -## How to resolve +## How to Resolve -1. Check to see if the sync or export was successful. -2. If it wasn't, please attempt to sync or export the connection again. +1. Check if the sync or export was successful. +2. If not, attempt to sync or export again. -If the problem persists, download the QuickBooks Desktop log file via the Web Connector (click View Logs to download them) and reach out to Concierge for further assistance. +If the problem persists, download the QuickBooks Desktop log file from the Web Connector and contact Concierge. {% include info.html %} -If you’re using a remote server (e.g. RightNetworks), you may need to contact that support team to request your logs. +If you use a remote server (e.g., RightNetworks), you may need to contact their support team for logs. {% include end-info.html %} -# Reports not exporting to QuickBooks Desktop +# Reports Not Exporting to QuickBooks Desktop -Generally, this is the result of not having both the QuickBooks Web Connector and the Company File open, since the Report was exported. +This usually occurs when the QuickBooks Web Connector or Company File is not open during export. -## How to resolve +## How to Resolve -1. Make sure that the Web Connector and QuickBooks Desktop Company File are both open. -2. In the Web Connector, check that the Last Status is “Okâ€. +1. Ensure both the Web Connector and QuickBooks Desktop Company File are open. +2. In the Web Connector, check that the **Last Status** is "Ok". -![QuickBooks Web Connector showing status "OK"](https://help.expensify.com/assets/images/quickbooks-desktop-web-connector.png){:width="100%"} + ![QuickBooks Web Connector Status OK](https://help.expensify.com/assets/images/quickbooks-desktop-web-connector.png){:width="100%"} -3. Check the Report Comments in Expensify to confirm that the report has been successfully exported to QuickBooks Desktop. +3. Check **Report Comments** in Expensify to confirm successful export. -![Expensify report showing the report was exported](https://help.expensify.com/assets/images/quickbooks-desktop-exported-report-comments.png){:width="100%"} + ![Expensify Exported Report](https://help.expensify.com/assets/images/quickbooks-desktop-exported-report-comments.png){:width="100%"} -If these general troubleshooting steps don’t work, reach out to Concierge with your Expensify Report ID and a screenshot of your QuickBooks Web Connector. +If the issue persists, contact Concierge with your Expensify Report ID and a screenshot of your QuickBooks Web Connector. diff --git a/docs/articles/expensify-classic/connections/xero/Configure-Xero.md b/docs/articles/expensify-classic/connections/xero/Configure-Xero.md index 170e8d0b6974..907223c72ca9 100644 --- a/docs/articles/expensify-classic/connections/xero/Configure-Xero.md +++ b/docs/articles/expensify-classic/connections/xero/Configure-Xero.md @@ -1,104 +1,107 @@ --- title: Configure Xero -description: Configure Xero +description: Learn how to configure Xero in Expensify, including best practices, export settings, coding configurations, and advanced options. --- -**Best Practices Using Xero** +## Best Practices Using Xero -A connection to Xero lets you combine the power of Expensify's expense management features with Xero's accounting capabilities. By following the recommended best practices below, your finances will be automatically categorized correctly and accounted for in the right place. +A connection to Xero lets you combine Expensify's expense management features with Xero's accounting capabilities. Follow these best practices to ensure your finances are correctly categorized and accounted for: -- Configure your setup immediately after making the connection, and review each settings tab thoroughly. -- Keep Auto Sync enabled. - - The daily auto sync will update Expensify with any changes to your chart of accounts, customers/projects, or bank accounts in Xero. - - Finalized reports will be exported to Xero automatically, saving your admin team time with every report. -- Set your preferred exporter to a user who is both a workspace and domain admin. -- Configure your coding settings and enforce them by requiring categories and tags on expenses. +- Configure your setup immediately after connecting and review each settings tab thoroughly. +- Keep **Auto Sync** enabled to: + - Update Expensify daily with changes to your chart of accounts, customers/projects, or bank accounts in Xero. + - Automatically export finalized reports to Xero, saving your admin team time. +- Set the **Preferred Exporter** to a user who is both a Workspace and Domain Admin. +- Configure **coding settings** and enforce them by requiring categories and tags on expenses. -# Accessing the Xero Configuration Settings -Xero is connected at the workspace level, and each workspace can have a unique configuration that dictates how the connection functions. To access the configuration: +## Accessing the Xero Configuration Settings + +Xero is connected at the workspace level, and each workspace has a unique configuration. To access the settings: 1. Click **Settings** near the bottom of the left-hand menu. -2. Navigate to Workspaces > Groups > [workspace Name] > Connections. -3. Scroll down to the Xero connection and click the **Configure** button to open the settings menu. - -# Step 1: Configure Export Settings -The following steps help you determine how data will be exported from Expensify to Xero. - -1. Click the **Configure** button under the Xero connection to open the settings menu. -2. Under the Export tab, review each of the following export settings: - - **Preferred Exporter**: Choose a Workspace Admin to set as the Preferred Exporter. - - Concierge exports reports automatically on behalf of the preferred exporter. - - Other Workspace Admins will still be able to export to Xero manually. - - If you set different export bank accounts for individual company cards under your Domain > Company Cards, then your Preferred Exporter must be a Domain Admin in addition to Workspace Admin. - - **Export reimbursable expenses and bills as**: Reimbursable expenses export as a Purchase Bill. This setting cannot be amended. - - **Purchase Bill Date**: Choose whether to use the date of the last expense on the report, export date, or submitted date. - - **Export invoices as**: All invoices exported to Xero will be as sales invoices. Sales invoices always display the date on which the invoice was sent. This setting cannot be amended. - - **Export non-reimbursable expenses as**: Each exported expense posts as a bank transaction to the Xero bank account you select below, and transaction dates will match the dates on your bank statement. - - **Xero Bank Account**: Select which bank account will be used to post bank transactions when non-reimbursable expenses are exported. - -## Step 1B: Optional configuration when company cards are connected -1. Click **Settings** near the bottom of the left-hand menu. -2. Navigate to Domains > [domain name] > Company Cards. -3. If you have more than one company card connection, select the connection first. -4. Locate the cardholder you want to configure in the list, -5. Click the **Edit Exports** button and assign the account the card expenses should export to in Xero. - -# Step 2: Configure Coding settings -The following steps help you determine how data will be imported from Xero to Expensify. - -1. Click the **Configure** button under the Xero connection to open the settings menu. -2. Under the Coding tab, review each of the following settings and configure the options to determine what information will be imported: - - **Chart of Accounts**: Your Xero Chart of Accounts is imported into Expensify as expense categories. _This is enabled by default and cannot be disabled._ - - **Tax Rates**: When Enabled, your tax rates in Xero will be imported into your workspace. After being imported, you can find them on the [Tax](https://expensify.com/policy?param=%7B%22policyID%22:%22B936DE4542E9E78B%22%7D#tax) page of your workspace settings. - - **Tracking Categories**: When Enabled, you can configure how Xero Cost Centres and Xero Regions import. - - Xero contact default (applies the Xero contact default during export to Xero) - - Tag (line-item level) - - Report Field (header level) - - **Billable Expenses**: When enabled, your Xero customer contacts will be imported as tags. Xero requires all billable expenses to have a customer tag to be able to be exported to Xero. - -# Step 3: Configure advanced settings -The following steps help you determine the advanced settings for your connection, like auto-sync. - -1. Click the **Configure** button under the Xero connection to open the settings menu. -2. Under the Advanced tab, review each of the following settings and configure the options you wish to use: - - **Auto Sync**: When enabled, the connection will sync daily to ensure that the data shared between the two systems is up-to-date. - - New report approvals/reimbursements will be synced during the next auto-sync period. -Reimbursable expenses will export after reimbursement occurs or the report is marked as reimbursed outside Expensify when using Direct or Indirect reimbursement. - - Non-reimbursable expenses will export automatically after the report is final approved. - - **Newly Imported Categories Should Be**: When a new account is created in the Xero chart of accounts, this setting controls whether the new category in Expensify is enabled or disabled by default. Disabled categories are not visible to employees when coding expenses. - - **Set purchase bill status** (optional): Reimbursable expenses are exported as purchase bills with the status selected. The options available are: - - Awaiting Payment (default) - - Draft - - Awaiting Approval - - **Sync Reimbursed Reports**: When enabled, you can configure the Bill Payment and Invoice Collections accounts to be used when reimbursing reports and paying invoices. - - Anytime a report is reimbursed, or an invoice is paid through Expensify, the corresponding purchase bill or sales invoice in Xero will be marked as paid. - - Similarly, if a purchase bill or sales invoice is marked as paid in Xero, the related Expensify report or invoice will be automatically marked as reimbursed/paid. - - **Xero Bill Payment Account**: Once the expense report is paid, your reimbursements will appear under this Xero Bill Payment account. - - **Xero Invoice Collections Account**: If you are exporting invoices from Expensify, select the invoice collection account under which you want invoices to appear once they are marked as paid. +2. Navigate to **Workspaces > Groups > [Workspace Name] > Connections**. +3. Scroll to the Xero connection and click **Configure**. -{% include faq-begin.md %} +--- + +## Step 1: Configure Export Settings -## I have multiple organizations in Xero. Can I connect them all to Expensify? +Define how data will be exported from Expensify to Xero: -Yes, you can connect each organization you have to Expensify. Here are some essential things to keep in mind: +1. Click **Configure** under the Xero connection. +2. Under the **Export** tab, review and set up: + - **Preferred Exporter**: Assign a Workspace Admin. + - Concierge exports reports automatically for the preferred exporter. + - Other Workspace Admins can export manually. + - If you set different export bank accounts for company cards, the Preferred Exporter must also be a Domain Admin. + - **Export reimbursable expenses and bills as**: Always exported as a **Purchase Bill**. + - **Purchase Bill Date**: Choose **last expense date, export date, or submitted date**. + - **Export invoices as**: Always exported as **Sales Invoices**. + - **Export non-reimbursable expenses as**: Posted as **bank transactions** to a Xero bank account. + - **Xero Bank Account**: Select the bank account for posting non-reimbursable expenses. -- Organization Selection in the Workspace > Connections > Xero Configuration > Export settings tab: This option is available only if multiple organizations are configured in Xero. -- One Workspace, One Organization: Each Workspace can connect to just one organization at a time. It’s a one-to-one connection. -- Adding New Organizations: If you create a new organization in Xero after your initial connection, you’ll need to disconnect and then reconnect it to Xero. Don’t forget to take a screenshot of your current settings by clicking Configure and checking the Export, Coding, and Advanced tabs. This way, you can easily set everything up again. +### Step 1B: Configure Company Card Exports (If Applicable) -## How can I view the purchase bills exported to Xero? +1. Click **Settings**. +2. Navigate to **Domains > [Domain Name] > Company Cards**. +3. If multiple company card connections exist, select the relevant one. +4. Locate the cardholder and click **Edit Exports**. +5. Assign the correct Xero account for the card expenses. + +--- + +## Step 2: Configure Coding Settings + +Define how data is imported from Xero to Expensify: + +1. Click **Configure** under the Xero connection. +2. Under the **Coding** tab, configure: + - **Chart of Accounts**: Imported as **expense categories** (enabled by default). + - **Tax Rates**: Enabled to import Xero tax rates, visible under [Tax Settings](https://expensify.com/policy?param=%7B%22policyID%22:%22B936DE4542E9E78B%22%7D#tax). + - **Tracking Categories**: Choose import method: + - **Xero contact default** (applies during export) + - **Tag** (line-item level) + - **Report Field** (header level) + - **Billable Expenses**: Enables importing Xero customer contacts as tags. All billable expenses require a customer tag for export. + +--- + +## Step 3: Configure Advanced Settings + +1. Click **Configure** under the Xero connection. +2. Under the **Advanced** tab, configure: + - **Auto Sync**: Ensures daily synchronization. + - Reimbursable expenses export after reimbursement. + - Non-reimbursable expenses export after final approval. + - **Newly Imported Categories Should Be**: Controls default visibility of new Xero accounts in Expensify. + - **Set Purchase Bill Status** (optional): + - **Awaiting Payment** (default) + - **Draft** + - **Awaiting Approval** + - **Sync Reimbursed Reports**: Ensures paid reports and invoices sync across Expensify and Xero. + - **Xero Bill Payment Account**: Specifies where reimbursements appear in Xero. + - **Xero Invoice Collections Account**: Defines the invoice collection account for exported invoices. + +--- + +{% include faq-begin.md %} + +## Can I connect multiple Xero organizations to Expensify? + +Yes, but each workspace can connect to only one Xero organization at a time. If you add a new organization in Xero, you'll need to disconnect and reconnect the integration. Take screenshots of your settings beforehand to reconfigure quickly. + +## How can I view purchase bills exported to Xero? -**To view the bills in Xero:** 1. Log into Xero. -2. Navigate to Business > Purchase Overview > Awaiting Payments. - - Bills will be payable to the individual who created and submitted the report in Expensify. +2. Navigate to **Business > Purchase Overview > Awaiting Payments**. + - Bills are payable to the user who created and submitted the report in Expensify. -## How can I view the bank transactions in Xero? +## How can I view bank transactions in Xero? -**To view the transactions in Xero:** 1. Log into Xero. -2. Head over to your Dashboard. -3. Select your company card. -4. Locate the specific expense you’re interested in. +2. Open your **Dashboard**. +3. Select your **Company Card**. +4. Locate the relevant expense. {% include faq-end.md %} + diff --git a/docs/articles/expensify-classic/connections/xero/Connect-To-Xero.md b/docs/articles/expensify-classic/connections/xero/Connect-To-Xero.md index 2fb71a780e41..fc79d1a595f6 100644 --- a/docs/articles/expensify-classic/connections/xero/Connect-To-Xero.md +++ b/docs/articles/expensify-classic/connections/xero/Connect-To-Xero.md @@ -1,28 +1,36 @@ --- title: Connect to Xero -description: Everything you need to know about Expensify's direct integration with Xero +description: Learn how to integrate Expensify with Xero for seamless expense management order: 1 --- -**Prerequisites** +## Prerequisites -You must be a Workspace Admin in Expensify using a Collect or Control Workspace to connect your Xero account to Expensify. +To connect Xero with Expensify, you must: +- Be a **Workspace Admin** in Expensify. +- Use a **Collect** or **Control** Workspace. ## Step 1: Connect Expensify to Xero -1. Click **Settings** near the bottom of the left-hand menu. -2. Navigate to Workspaces > Groups > [workspace Name] > Connections. -3. Click on **Connect to Xero**. -4. Click the **Create a New Xero Connection** button. + +Follow these steps to set up the Xero integration: + +1. Click **Settings** in the bottom left menu. +2. Navigate to **Workspaces** > **Groups** > *[Workspace Name]* > **Connections**. +3. Click **Connect to Xero**. +4. Click **Create a New Xero Connection**. 5. Enter your Xero login credentials. -6. Review the access information and click Allow Access. -7. You will be redirected back to Expensify and the connection will import some initial settings from Xero to Expensify. -8. Once the sync is complete, the configuration window for Xero will open automatically so you can configure your export, import, and advanced settings. -9. Click the **Save** button when you’re done configuring to finalize the connection. +6. Review the access permissions and click **Allow Access**. +7. You will be redirected back to Expensify, where the connection will begin syncing initial settings from Xero. +8. Once the sync is complete, the **Xero Configuration** window will open automatically. +9. Configure your **export, import, and advanced settings** as needed. +10. Click **Save** to finalize the connection. {% include faq-begin.md %} -## I use a Cashbook or Ledger Xero account, can I still connect in Expensify? +## FAQ + +### Can I connect a Cashbook or Ledger Xero account to Expensify? -Starting in September 2021, there’s a chance for Cashbook and Ledger-type organizations in Xero. Apps like Expensify won’t be able to create invoices and bills for these accounts using the Xero API. So, if you’re using a Cashbook or Ledger Xero account, please be aware that this might affect your Expensify integration. +Starting in September 2021, **Cashbook and Ledger** Xero accounts may experience limitations. Apps like Expensify cannot create invoices or bills for these account types using the Xero API. If you are using a **Cashbook or Ledger** Xero account, please note that this may affect your Expensify integration. {% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connections/xero/Xero-Troubleshooting.md b/docs/articles/expensify-classic/connections/xero/Xero-Troubleshooting.md index f23ec515cde4..6d34d87d4f73 100644 --- a/docs/articles/expensify-classic/connections/xero/Xero-Troubleshooting.md +++ b/docs/articles/expensify-classic/connections/xero/Xero-Troubleshooting.md @@ -1,155 +1,142 @@ --- title: Xero Troubleshooting -description: Xero Troubleshooting +description: Troubleshooting common Xero integration errors in Expensify, including sync and export issues. --- # Overview of Xero Troubleshooting -Synchronizing and exporting data between Expensify and Xero 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 Xero or Expensify. +Synchronizing and exporting data between Expensify and Xero can streamline your financial processes, but occasionally, errors may occur due to discrepancies in settings, missing data, or configuration issues. -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. +This guide provides step-by-step solutions to common Xero-related errors to ensure a seamless connection and accurate expense reporting. -# ExpensiError XRO014 Billable Expenses Require A Customer - -**Why does this happen?** - -This happens because Xero requires all billable expenses exported from Expensify to have a customer associated with it. This error occurs when one or more expenses on a report have been marked "billable," and do not have a customer associated with them. - -## How to fix it - -1. Navigate to your Settings > Workspaces > [click workspace] > Connections > Configure button > Coding tab. -2. Click the **toggle** to enable Billable Expenses. -3. Click the **Save** button to save the change and sync the connection. -4. Open the report in question and apply a _Customer_ tag to each billable expense. - - _Note: A Contact in Xero is not imported as a Customer until they have had some kind of bill raised against them. If you can't see your Customer imported as a tag, you may need to raise a dummy invoice at the Xero end and then delete/void it. Don’t forget to sync the connection again after taking this step._ -5. Try to export the report again by clicking the **Export to** button and select the **Xero** option. - -# ExpensiError XRO027: Expense on this report is categorized with a category no longer in Xero - -**Why does this happen?** - -When exporting expense data, Xero will not accept a category on an expense that no longer exists in the chart of accounts. This error occurs when one or more expenses on the report are categorized with a category that no longer exists in Xero. - -## How to fix it - -1. Log into Xero. -2. Navigate to Settings > Chart of Accounts. -3. Confirm that each category used on an expense in the report in Expensify is still an active account in Xero. -4. If the account doesn’t exist, add it again and sync the connection in Expensify. -5. If the account still exists, open the details, check “Show in Expense Claims,†and then sync the connection in Expensify. -6. After syncing, open the report and re-categorize any expenses showing a red workspace violation for out-of-workspace categories. -7. After recategorizing, click the **Export to** button and select the **Xero** option. - -# ExpensiError XRO031: Payment has already been allocated to reimbursable expenses - -**Why does this happen?** - -Xero does not allow for paid expenses to be modified. When you attempt to export the reimbursable expenses again, Xero considers that a modification and rejects the export. This error occurs when the report contains reimbursable expenses that have already been exported to Xero where a payment was issued on the purchase bill. - -## How to fix it +--- -1. Log into Xero. -2. Click on Business > Bills to Pay > and then the Paid tab. -3. Locate the report from the error and click on it to open it. -4. Click on the blue text that says Payment. -5. Click the Options dropdown and then Remove and Redo to delete the payment. - - _Note: Do not void the bill in Xero._ -6. Head back to Expensify and open the report again. -7. Click the **Export to** button and select the **Xero** option. +## ExpensiError XRO014: Billable Expenses Require a Customer -The new export will override the current report in Xero and retain the same report ID. +### Why does this happen? +Xero requires all billable expenses exported from Expensify to have a customer assigned. This error occurs when one or more expenses are marked "billable" but lack an associated customer. -# ExpensiError XRO087 No Bank Account or Incorrect Bank Account +### How to fix it +1. Navigate to **Settings > Workspaces > [workspace] > Connections > Configure > Coding tab**. +2. Enable **Billable Expenses** by toggling the setting. +3. Click **Save** to sync the connection. +4. Open the report and apply a **Customer** tag to each billable expense. + - *Note: A Xero Contact becomes a Customer only after an invoice has been raised against them. If the Customer is missing, create a dummy invoice in Xero, then delete/void it and sync again.* +5. Retry the export by clicking **Export to > Xero**. -**Why does this happen?** +--- -Xero requires all bank transactions created from non-reimbursable expenses in Expensify to be posted to an active bank account. This error occurs when the destination account in Xero doesn’t exist, isn’t set, or is not the right type. +## ExpensiError XRO027: Category No Longer Exists in Xero -## How to fix it +### Why does this happen? +Xero does not accept expenses categorized under accounts that no longer exist in the Chart of Accounts. -1. Navigate to Settings > Workspaces > [workspace name] > Connections > click the **Configure** button. -2. Select a Xero Bank Account from the dropdown that will apply to all non-reimbursable expenses exported to Xero. -3. Click the **Save** button to sync the connection. -4. Open the report again and click the **Export to** button and then the **Xero** option. +### How to fix it +1. Log into Xero and navigate to **Settings > Chart of Accounts**. +2. Ensure all expense categories in Expensify are active in Xero. +3. If a category is missing, add it back in Xero and sync Expensify. +4. If the category exists, ensure **Show in Expense Claims** is enabled. +5. Sync Expensify, open the report, and recategorize expenses flagged with a red violation. +6. Click **Export to > Xero**. -# ExpensiError XRO052: Expenses Are Not Categorized With A Xero Account +--- -**Why does this happen?** +## ExpensiError XRO031: Payment Already Allocated to Reimbursable Expenses -Xero requires all expenses exported from Expensify to use a category matching an account in your chart of accounts. If a category from another source is used, Xero will reject the expense. This error occurs when an expense on the report has a category applied that is not valid. +### Why does this happen? +Xero does not allow modifications to paid expenses. If a reimbursable expense is re-exported, Xero rejects it as a modification. -## How to fix it +### How to fix it +1. In Xero, go to **Business > Bills to Pay > Paid tab**. +2. Locate and open the report with the error. +3. Click on the blue **Payment** link. +4. Click **Options > Remove and Redo** (*Do not void the bill*). +5. In Expensify, open the report and click **Export to > Xero**. + - The new export will override the previous report while retaining the same ID. -1. Sync your Xero connection in Expensify from Settings > Workspaces > [click workspace] > Connections, and click the **Sync Now** button. -2. Review the expenses on the report. If any appear with a red _Category no longer valid_ violation, recategorize the expense until all expenses are violation-free. -3. Click the **Export to** button and then the **Xero** option. -4. If you receive the same error, continue. - - _Note the categories used on the expenses and check the Settings > Workspaces > [click workspace] > Categories page to confirm the exact categories used on the report are enabled and connected to Xero (you'll see a blue icon next to all connected categories)._ -5. Confirm that the categories used for expenses in the report match exactly the accounts in your Xero chart of accounts. -6. If you make any changes in Xero or in Expensify, always sync the connection and then try to export again. +--- -# ExpensiError XRO068: Organization is not subscribed to currency x +## ExpensiError XRO087: No or Incorrect Bank Account -**Why does this happen?** +### Why does this happen? +Xero requires bank transactions from Expensify to post to an active bank account. This error occurs when the destination account is missing or incorrect. -Xero requires the currencies you’re using in Expensify to be added to your account before you can export expenses in that currency. For example, if your workspace is set to Canadian currency, all expenses submitted on that workspace will be converted to CAD. You must also have the Canadian currency added to your Xero account to export successfully. This error occurs when your Xero account does not have the currency mentioned in the error added. +### How to fix it +1. In Expensify, go to **Settings > Workspaces > [workspace] > Connections > Configure**. +2. Select a **Xero Bank Account** for non-reimbursable expenses. +3. Click **Save** to sync the connection. +4. Open the report and retry the export. -## How to fix it -_Note: Not all versions of Xero allow adding currencies. To add currencies, please upgrade your Xero account to the Established [plan](https://www.xero.com/us/pricing-plans/)._ +--- -1. Log into Xero. -2. Navigate to Settings > General Settings. -3. Under the heading Features, select Currencies. -4. Click **Add Currency** to add the currency listed in the error message. -5. Sync your Xero connection in Settings > Workspaces > [click workspace] > Connections. -6. Open the report and click the **Export to** button and then the **Xero** option. +## ExpensiError XRO052: Expenses Not Categorized with a Xero Account -# ExpensiError XRO076: This report has already been exported once to Xero, but has been voided +### Why does this happen? +Xero requires all expenses to be categorized under valid accounts in the Chart of Accounts. -**Why does this happen?** +### How to fix it +1. Sync Expensify with Xero (**Settings > Workspaces > [workspace] > Connections > Sync Now**). +2. Review expenses for red category violations and recategorize them. +3. Click **Export to > Xero**. +4. If errors persist: + - Verify category settings under **Settings > Workspaces > Categories**. + - Ensure categories match exactly with Xero’s Chart of Accounts. +5. Sync again and retry the export. -Xero does not allow Expensify to modify a purchase bill created from a previous export if the bill has been voided. This error occurs when the report has already been exported to Xero, and the purchase bill has been voided. +--- -## How to fix it -_Note: Xero does not support “unvoiding†a bill, it is an irreversible action._ +## ExpensiError XRO068: Currency Not Subscribed in Xero -1. From the Reports page in Expensify, locate the report associated with the voided bill. -2. Check the box to the left of the report and click **Copy**. -3. Open the new report and submit it through the approval workflow, then confirm it exports to Xero successfully. +### Why does this happen? +Xero requires all currencies used in Expensify to be added before exporting expenses in that currency. -# ExpensiError XRO099: You have reached the limit of invoices you can approve with your Xero account. +### How to fix it +1. In Xero, go to **Settings > General Settings > Features > Currencies**. +2. Click **Add Currency** and select the required currency. +3. Sync Expensify with Xero (**Settings > Workspaces > Connections**). +4. Open the report and click **Export to > Xero**. + - *Note: Adding currencies requires the Established Xero plan. [Upgrade if necessary](https://www.xero.com/us/pricing-plans/).* -**Why does this happen?** +--- -The Early plan only allows you to enter 5 bills per month. This error occurs when you are on a trial account of Xero and have run out of your allowable exports. +## ExpensiError XRO076: Report Previously Exported and Voided -## How to fix it -Please upgrade your Xero account to a Growing or Established [plan](https://www.xero.com/us/pricing-plans/) so you can continue to use the integration and export reports without error. +### Why does this happen? +Xero does not allow modifications to voided purchase bills. -# Why are company card expenses exported to the wrong account? +### How to fix it +1. In Expensify, locate the report on the **Reports** page. +2. Select the report and click **Copy**. +3. Submit the copied report for approval and export it to Xero. -Multiple factors could be causing your company card transactions to export to the wrong place in your accounting system, but the best place to start is always the same. +--- -- First, confirm that the company cards have been mapped to the correct accounts in Settings > Domains > Company Cards > click the **Edit Export** button for the card to view the account. -- Next, confirm the expenses in question have been imported from the company card? - - Only expenses that have the Card+Lock icon next to them will export according to the mapping settings that you configure in the domain settings. +## ExpensiError XRO099: Xero Invoice Approval Limit Reached -It’s important to note that expenses imported from a card linked at the individual account level, expenses created from a SmartScanned receipt, and manually created cash expenses will export to the default bank account selected in your connection's configuration settings. +### Why does this happen? +The Early plan in Xero allows only 5 bills per month. This error occurs when the limit is reached. -**Is the report exporter a domain admin?** +### How to fix it +Upgrade your Xero account to a **Growing or Established plan**. [See Xero pricing](https://www.xero.com/us/pricing-plans/). -The user exporting the report must be a domain admin. You can check the history and comment section at the bottom of the report to see who exported the report. +--- -If your reports are being exported automatically by Concierge, the user listed as the Preferred Exporter under Settings > Workspaces > [workspaces name] > Connections > click **Configure** must be a domain admin as well. +## Why Are Company Card Expenses Exported to the Wrong Account? -If the report exporter is not a domain admin, all company card expenses will export to the bank account set in Settings > Workspaces > [workspace name] > Connections > click **Configure** for non-reimbursable expenses. +1. Confirm that **company cards** are mapped correctly: + - **Settings > Domains > Company Cards > Edit Export**. +2. Verify that expenses have the **Card+Lock icon** (indicating they were imported from a company card). +3. Ensure the exporter is a **Domain Admin**: + - Check the **Preferred Exporter** setting under **Settings > Workspaces > Connections > Configure**. +4. Verify company card mapping under the correct workspace. -**Has the company card been mapped under the correct workspace?** +--- -If you have multiple workspaces connected to Xero, each connected workspace will have a separate list of accounts to assign the card to. Unless you choose an account listed under the same workspace as the report you are exporting, expenses will export to the default bank account. +## Why Do Non-Reimbursable Expenses Show 'Credit Card Misc' Instead of the Merchant? -# Why do non-reimbursable expenses say 'Credit Card Misc,' instead of the merchant? +If a merchant in Expensify **matches** a contact in Xero, expenses will reflect the vendor name. Otherwise, they default to \"Expensify Credit Card Misc\" to prevent duplicates. -Where the merchant in Expensify is an exact match to a contact you have set up in Xero then exported credit card expenses will show the vendor name. If not we use the the default name Expensify Credit Card Misc. This is done to prevent multiple variations of the same contact (e.g. Starbucks and Starbucks #1234 as is often seen in credit card statements) being created in Xero. +### How to fix it +Use **Expense Rules** in Expensify to standardize merchant names. Learn more [here](https://help.expensify.com/articles/expensify-classic/expenses/Create-Expense-Rules). -To change merchant names to match your vendor list in Xero, we recommend using our Expense Rules feature. More information on this can be found [here](https://community.expensify.com/discussion/5654/deep-dive-using-expense-rules-to-vendor-match-when-exporting-to-an-accounting-package/p1?new=1). +--- diff --git a/docs/articles/expensify-classic/expensify-billing/Consolidated-Domain-Billing.md b/docs/articles/expensify-classic/expensify-billing/Consolidated-Domain-Billing.md index 671d0c41e772..f084e3b01cd9 100644 --- a/docs/articles/expensify-classic/expensify-billing/Consolidated-Domain-Billing.md +++ b/docs/articles/expensify-classic/expensify-billing/Consolidated-Domain-Billing.md @@ -1,28 +1,53 @@ --- title: Consolidated Domain Billing -description: Consolidated Domain Billing allows organizations to have different billing owners with only one person being billed for all paid workspaces. +description: Learn how to enable and manage Consolidated Domain Billing, allowing one billing owner to cover all paid workspaces under a verified domain. --- - + # Overview -If your organization requires that different workspaces have different billing owners, but only one person should pay the Expensify bill each month, you can enable Consolidated Domain Billing. -# How to enable Consolidated Domain Billing -Consolidated Domain Billing is a domain-level feature, so to access this setting, you’ll first need to claim and verify your domain. You can do this by heading to **Settings > Domains > Domain Name** > clicking on a setting such as **Groups** > and then clicking **Verify**. +Consolidated Domain Billing allows organizations to have different billing owners for workspaces while ensuring that only one person is billed for all paid workspaces within a verified domain. + +--- + +# Enabling Consolidated Domain Billing +To enable this feature, you must first claim and verify your domain: + +1. Go to **Settings > Domains**. +2. Select your domain. +3. Click on a setting such as **Groups**. +4. Click **Verify**. + +Once your domain is verified, enable Consolidated Domain Billing: + +1. Navigate to **Settings > Domains > [Your Domain]**. +2. Select **Domain Admins > Primary Contact and Billing**. +3. Toggle **Consolidated Domain Billing** on. -Once the domain is verified, you can enable Consolidated Domain Billing under **Settings > Domains > Domain Name > Domain Admins > Primary Contact and Billing**. -# How to use Consolidated Domain Billing -When a Domain Admin enables Consolidated Domain Billing, all Group workspaces owned by any user with an email address matching the domain will get billed to the Consolidated Domain Billing owner’s account. -# Deep Dive -## Consolidated Domain Billing best practices -If you don’t have multiple billing owners across your organization, or if you want to keep billing separate for any reason, then this feature isn’t necessary. +--- + +# How It Works +When a **Domain Admin** enables Consolidated Domain Billing: -If you have an Annual Subscription and enable Consolidated Domain Billing, the Consolidated Domain Billing feature will gather the amounts due for each Group workspace Billing Owner (listed under **Settings > Workspaces > Group**). To make full use of the Annual Subscription for all workspaces in your domain, you should also be the billing owner for all Group workspaces. +- All **Group Workspaces** owned by users with an email address matching the domain will be billed to the **Primary Contact** listed under **Domain Admins**. +- Individual workspace billing owners will no longer receive separate charges. + +--- + +# Best Practices +- **When to Use It**: If multiple billing owners exist in your organization but you want a single, consolidated bill. +- **When to Avoid It**: If you need to keep workspace billing separate for accounting or financial tracking purposes. +- **Annual Subscription Considerations**: + - If you have an **Annual Subscription**, Consolidated Domain Billing will combine the amounts due for each Group workspace billing owner. + - To maximize savings, the **Primary Contact** should also be the billing owner for all Group Workspaces. + +--- -{% include faq-begin.md %} +# FAQ -## How do I take over the billing of a workspace with Consolidated Domain Billing enabled? -You’ll have to toggle off Consolidated Domain Billing, take over ownership of the workspace, and then toggle it back on. +## How do I take over the billing of a workspace with Consolidated Domain Billing enabled? +1. Toggle off **Consolidated Domain Billing**. +2. Take ownership of the workspace. +3. Toggle **Consolidated Domain Billing** back on. -## Can I use Consolidated Domain Billing to cover the bill for some workspaces, but not others? -No, this feature means that you’ll be paying the bill for all domain members who choose a subscription. +## Can I cover the bill for some workspaces but not others? +No, enabling this feature means you will be billed for **all** domain members who choose a subscription. -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md b/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md index 38686462a1c2..ee03a18033ea 100644 --- a/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md +++ b/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md @@ -54,8 +54,6 @@ To add your Expensify Card to a digital wallet, follow the steps below: - **Restricted Country**: Transactions from restricted countries will be declined. {% include faq-begin.md %} -## Can I use Smart Limits with a free Expensify account? -If you're on the Free plan, you won't have the option to use Smart Limits. Your card limit will simply reset at the end of each calendar month. ## I still haven't received my Expensify Card. What should I do? For more information on why your card hasn't arrived, you can check out this resource on [Requesting a Card](https://help.expensify.com/articles/expensify-classic/expensify-card/Request-the-Card#what-if-i-havent-received-my-card-after-multiple-weeks). diff --git a/docs/articles/expensify-classic/settings/Email-Notifications.md b/docs/articles/expensify-classic/settings/Email-Notifications.md index ec87aac95229..bb4776cde8fc 100644 --- a/docs/articles/expensify-classic/settings/Email-Notifications.md +++ b/docs/articles/expensify-classic/settings/Email-Notifications.md @@ -1,71 +1,78 @@ --- -title: Expensify Email notifications -description: Troubleshooting steps for receiving emails and notifications from Expensify. ---- +title: Expensify Email Notifications +description: Troubleshooting steps for receiving emails and notifications from Expensify. +--- + +Occasionally, members may have trouble receiving email notifications from Expensify, including Magic Code emails, account validation emails, secondary login validations, integration emails, or report action notifications. + +# Troubleshooting Missing Expensify Notifications + +## Issue: The Email or Notification Is Never Received, and No Error Message Appears + +Emails can sometimes be delayed and may take up to 30–60 minutes to arrive. If an expected notification has not arrived: + +- Check your **Email Preferences** via **Settings > Account > Preferences**. In the **Contact Preferences** section, ensure the relevant notification types are enabled. +- Check your email spam and trash folders, as Expensify messages might be filtered incorrectly. +- Ensure that Expensify emails are not blocked. Add the domain **expensify.com** to your email provider’s allowlist. + +## Issue: A Banner Says “We’re Having Trouble Emailing You†-Occasionally, members may have trouble receiving email notifications from Expensify, such as Expensify Magic Code emails, account validation emails, secondary login validations, integration emails, or report action notifications. +Confirm that the email address on your Expensify account is valid, then click the link in the banner labeled **"here."** If successful, you will see a confirmation that your email was unblocked. -# Troubleshooting missing Expensify notifications +![Expensify Email Error](https://help.expensify.com/assets/images/ExpensifyHelp_EmailError.png){:width="100%"} -## Issue: The email or notification is never received, and no message, banner, or additional context is provided -Emails can sometimes be delayed and could take up to 30-60 minutes to arrive in your inbox. If you're expecting a notification that still hasn't arrived after waiting: - - Check your **Email Preferences** on the web via **Settings > Account > Preferences**. In the **Contact Preferences** section, ensure that the relevant boxes are checked for the email type you're missing. - - Check your email spam and trash folders, as Expensify messages might end up there inadvertently. - - Check to make sure you haven't unintentionally blocked Expensify emails. allowlist the domain expensify.com with your email provider. +**If unsuccessful, another error may appear:** -## Issue: A banner that says “We’re having trouble emailing you†shows the top of your screen. -Confirm that the email address on your Expensify account is deliverable, and then click the link in the banner that says "here." If successful, you will see a confirmation that your email was unblocked. +- If the error or SMTP message includes a URL, follow the provided link for further instructions. +- If the message references **"mimecast.com"**, consult your IT team. +- If the message mentions **"blacklist"**, your company may use a third-party email filtering service. Contact your IT team for assistance. - ![ExpensifyHelp_EmailError]({{site.url}}/assets/images/ExpensifyHelp_EmailError.png){:width="100%"} - - **If unsuccessful, you will see another error:** - - If the new error or SMTP message includes a URL, navigate to that URL for further instructions. - - If the new error or SMTP message includes "mimecast.com", consult with your company's IT team. - - If the new error or SMTP message includes "blacklist," it means your company has configured its email servers to use a third-party email reputation or blocklisting service. Consult with your company's IT team. - -![ExpensifyHelp_SMTPError]({{site.url}}/assets/images/ExpensifyHelp_SMTPError.png){:width="100%"} +![Expensify SMTP Error](https://help.expensify.com/assets/images/ExpensifyHelp_SMTPError.png){:width="100%"} -# Further troubleshooting for public domains +# Further Troubleshooting for Public Domains -If you are still not receiving Expensify notifications and have an email address on a public domain such as gmail.com or yahoo.com, you may need to add Expensify's domain expensify.com to your email's allowlist by taking the following steps: +If you use a public email provider (e.g., **gmail.com** or **yahoo.com**), try the following steps: - - Search for messages from expensify.com in your spam folder, open them, and click “Not Spam†at the top of each message. - Configure an email filter that identifies Expensify's email domain as expensify.com and directs all incoming messages to your inbox to prevent messages from going to spam. - - Add specific known Expensify email addresses, such as concierge@expensify.com, to your email contacts list. +- Search for messages from **expensify.com** in your spam folder, open them, and mark them as **Not Spam**. +- Configure an email filter to direct all messages from **expensify.com** to your inbox. +- Add known Expensify email addresses (e.g., **concierge@expensify.com**) to your contacts list. -# Further troubleshooting for private domains +# Further Troubleshooting for Private Domains -If your organization uses a private domain, Expensify emails may be blocked at the server level. This can sometimes happen unexpectedly due to broader changes in email provider's handling or filtering of incoming messages. Consult your internal IT team to assist with the following: +If your organization uses a private domain, Expensify emails may be blocked at the server level. This may happen due to changes in your email provider’s filtering settings. Work with your IT team to: - - Ensure that the domain expensify.com is allowlisted on the domain email servers. This domain is the source of various notification emails, so it's important it's recognized by your company's servers. - - Confirm there is no server-level email blocking - - Make sure spam filters are not blocking Expensify emails. +- Ensure **expensify.com** is allowlisted on your domain email servers. +- Confirm that there is no server-level email blocking. +- Verify that spam filters are not blocking Expensify emails. -Even if you have received messages from our Concierge support in the past, ensure that expensify.com is allowlisted. +Even if you have received Expensify emails in the past, confirm that **expensify.com** is still allowlisted. -## Companies using Outlook +## Companies Using Outlook -- Add Expensify to your personal Safe Senders list by following these steps: [Outlook email client](https://support.microsoft.com/en-us/office/add-recipients-of-my-email-messages-to-the-safe-senders-list-be1baea0-beab-4a30-b968-9004332336ce) / [Outlook.com](https://support.microsoft.com/en-us/office/safe-senders-in-outlook-com-470d4ee6-e3b6-402b-8cd9-a6f00eda7339) -- **Company IT administrators:** Add Expensify to your domain's Safe Sender list by following the steps here: [Create safe sender lists in EOP](https://learn.microsoft.com/en-us/defender-office-365/create-safe-sender-lists-in-office-365) -**Company IT administrators:** Add expensify.com to the domain's explicit allowlist. As each company's setup varies, you may need to contact Outlook support for specific instructions. -- **Company administrators:** Contact Outlook support to see if there are additional steps to take based on your domain's email configuration. +- Add Expensify to your personal Safe Senders list: + - [Outlook Email Client](https://support.microsoft.com/en-us/office/add-recipients-of-my-email-messages-to-the-safe-senders-list-be1baea0-beab-4a30-b968-9004332336ce) + - [Outlook.com](https://support.microsoft.com/en-us/office/safe-senders-in-outlook-com-470d4ee6-e3b6-402b-8cd9-a6f00eda7339) +- **IT Administrators:** Add Expensify to the domain’s Safe Senders list: + - [Create Safe Sender Lists in EOP](https://learn.microsoft.com/en-us/defender-office-365/create-safe-sender-lists-in-office-365) +- **Company Administrators:** Contact Outlook support for domain-specific configurations. -## Companies using Google Workspaces: +## Companies Using Google Workspaces -- **Company IT administrators:** Adjust your domain's email allowlist and safe senders lists to include expensify.com by following these steps: [Allowlists, denylists, and approved senders](https://support.google.com/a/answer/60752) +- **IT Administrators:** Adjust email allowlist and safe senders settings to include **expensify.com**: + - [Allowlists, Denylists, and Approved Senders](https://support.google.com/a/answer/60752) {% include faq-begin.md %} -## How can I be sure that emails from Expensify are legitimate and not spam? +## How Can I Be Sure That Emails from Expensify Are Legitimate and Not Spam? -Expensify's emails are SPF and DKIM-signed, meaning they are cryptographically signed and encrypted to prevent spoofing. +Expensify emails are **SPF** and **DKIM-signed**, meaning they are cryptographically signed and encrypted to prevent spoofing. -## Why do legitimate emails from Expensify sometimes end up marked as spam? +## Why Do Legitimate Emails from Expensify Sometimes End Up Marked as Spam? -The problem typically arises when a third-party domain or IP reputation service erroneously flags our domain or one of our sending IP addresses. Many IT departments use lists published by such services to filter email for the entire company. +This typically happens when a third-party domain or IP reputation service mistakenly flags our domain or one of our sending IP addresses. Many IT departments use these lists to filter company-wide email. -## What is the best way to ensure emails are not accidentally marked as Spam? +## What Is the Best Way to Ensure Expensify Emails Are Not Marked as Spam? -For server-level spam detection, the safest approach to allow emails from Expensify is to verify DKIM and SPF, rather than solely relying on the third-party reputation of the sending IP address. +For server-level spam detection, the best approach is to verify **DKIM** and **SPF** rather than relying solely on the reputation of the sending IP address. {% include faq-end.md %} diff --git a/docs/articles/expensify-classic/settings/Enable-two-factor-authentication.md b/docs/articles/expensify-classic/settings/Enable-two-factor-authentication.md index abbd51f3efb7..129da9603110 100644 --- a/docs/articles/expensify-classic/settings/Enable-two-factor-authentication.md +++ b/docs/articles/expensify-classic/settings/Enable-two-factor-authentication.md @@ -1,62 +1,77 @@ --- -title: Enable Two-factor authentication -description: Use 2FA for extra login security +title: Enable Two-Factor Authentication +description: Use 2FA for extra login security. --- -
-Add an extra layer of security to help keep your financial data safe and secure by enabling two-factor authentication (2FA). This will require you to enter a code generated by your preferred authenticator app (like Google Authenticator or Microsoft Authenticator) when you log in. +Add an extra layer of security to protect your financial data by enabling two-factor authentication (2FA). This requires you to enter a code generated by your preferred authenticator app (such as Google Authenticator or Microsoft Authenticator) when you log in. -Expensify's Two-Factor Authentication (2FA) is implemented via a Time-based One-Time Password (TOTP) algorithm. This requires you to use an Authenticator app to generate a unique code each time you log in, adding a second “factor†to your login. +Expensify's 2FA is implemented via a Time-based One-Time Password (TOTP) algorithm. This means that each time you log in, you must use an authenticator app to generate a unique 6-digit code, adding a second “factor†to your login. + +## Recommended Authenticator Apps + +You can use any authenticator app, but here are a few we recommend: -You can choose to use whichever authenticator you prefer, but here are a few we recommend: - [1Password](https://support.1password.com/one-time-passwords/) - [Authy](https://authy.com/) - [Google Authenticator](https://support.google.com/accounts/answer/1066447) - [Microsoft Authenticator](https://www.microsoft.com/en-us/security/mobile-authenticator-app) -You will need to select an authenticator app to use before proceeding. +Ensure you have an authenticator app installed before proceeding. -## Enable and Set Up Two-factor authentication +# Enable and Set Up Two-Factor Authentication -1. Hover over Settings, then click **Account**. -2. Under the Account Details tab, scroll down to the Two Factor Authentication section and enable the toggle. -3. Save a copy of your backup codes. - - Click **Download** to save a copy of your backup codes to your computer. - - Click **Copy** to paste the codes into a document or other secure location. +1. Hover over **Settings**, then click **Account**. +2. Under the **Account Details** tab, scroll to the **Two-Factor Authentication** section and enable the toggle. +3. Save a copy of your backup codes: + - Click **Download** to save a copy to your computer. + - Click **Copy** to store the codes in a secure location. {% include info.html %} -This step is critical—You will lose access to your account if you cannot use your authenticator app and do not have your recovery codes. +This step is critical—If you lose access to your authenticator app and do not have your recovery codes, you will lose access to your account. {% include end-info.html %} -4. Click **Continue**. -5. Download or open your authenticator app and either: - - Scan the QR code shown on your computer screen. +4. Click **Continue**. +5. Open your authenticator app and either: + - Scan the QR code displayed on your screen. - Enter the 6-digit code from your authenticator app into Expensify and click **Verify**. -When you log in to Expensify in the future, you’ll be emailed a magic code that you’ll use to log in with. Then you’ll be prompted to open your authenticator app to get the 6-digit code and enter it into Expensify. A new code regenerates every few seconds, so the code is always different. If the code time runs out, you can generate a new code as needed. +Once set up, when logging into Expensify, you will: +- Receive a Magic Code email to initiate login. +- Be prompted to enter a 6-digit code from your authenticator app. + +New codes regenerate every few seconds. If the code expires, generate a new one. + +# Lost Recovery Codes or Authenticator App + +If you lose your mobile device and recovery codes, a **Domain Admin** can reset your 2FA **only if**: -## Lost recovery codes and authenticator app +- You use a company email or a domain you own. +- The Domain Admin also has 2FA enabled. -If you have lost your mobile device and can’t find your recovery codes, your Domain Admin can complete the steps below to reset your 2FA **only if (1) you use a company email address or email address on a domain that you own and (2) the Domain Admin also has 2FA enabled**: +## Reset 2FA as a Domain Admin -If your domain has 2FA enabled, a domain admin can follow Settings > Domains > Domain Members and click **Edit Settings** for your email address. -They can then click **Reset** to reset two-factor authentication (2FA) on your account. This will allow you to gain access to your account on the web or mobile app and configure 2FA again. +1. Navigate to **Settings > Domains > Domain Members**. +2. Click **Edit Settings** for the affected email address. +3. Click **Reset** to disable 2FA. +4. The user can now log in and reconfigure 2FA. -If your domain does not have 2FA enabled, a domain admin can follow Settings > Domains > Domain Members and enable Two Factor Authentication. Then they can follow the previously mentioned steps to reset 2FA for your account. +If your domain does not have 2FA enabled: +1. Go to **Settings > Domains > Domain Members**. +2. Enable **Two-Factor Authentication**. +3. Follow the previous steps to reset 2FA for the user. {% include info.html %} -If you use a public email address such as gmail, hotmail, or yahoo, we unfortunately can’t help you disable your 2FA setting. If you are unable to find your recovery codes, you may need to create a new Expensify account with a different email address. +If you use a public email (e.g., Gmail, Yahoo, Hotmail), Expensify cannot disable 2FA. If recovery codes are lost, you may need to create a new account with a different email. {% include end-info.html %} -If you don’t have a Domain Admin, follow the steps in this [guide](https://help.expensify.com/articles/expensify-classic/domains/Claim-And-Verify-A-Domain) to verify the domain. +If no Domain Admin is available, follow [this guide](https://help.expensify.com/articles/expensify-classic/domains/Claim-And-Verify-A-Domain) to verify your domain. -## General troubleshooting +# General Troubleshooting -Make sure your phone’s time is set to automatically update (a manual time that’s fractionally different can cause issues). -Try disabling 2FA using a device that you are still logged into. For example, if you’re having trouble logging in with your computer, try to see if your mobile device is still logged in. If so, -Hover over Settings, then click Account. -Under the Account Details tab, scroll down to the Two Factor Authentication section and disable the toggle. -Try logging in with your other device. -Once you’ve logged in again, you can re-enable 2FA. +- Ensure your phone’s time is set to **automatic update**. A manual time difference can cause issues. +- If you are still logged in on another device: + 1. Hover over **Settings**, then click **Account**. + 2. Under the **Account Details** tab, scroll to **Two-Factor Authentication** and disable the toggle. + 3. Try logging in again, then re-enable 2FA. -
+Following these steps ensures your account remains secure while preventing access issues. diff --git a/docs/articles/expensify-classic/settings/General-product-troubleshooting.md b/docs/articles/expensify-classic/settings/General-product-troubleshooting.md index 57126628e04f..9fe6d65e319b 100644 --- a/docs/articles/expensify-classic/settings/General-product-troubleshooting.md +++ b/docs/articles/expensify-classic/settings/General-product-troubleshooting.md @@ -1,48 +1,58 @@ --- title: General Product Troubleshooting -description: How to troubleshoot a website issue +description: How to troubleshoot a website issue. --- - -# Issues with a specific feature -If you're having issues with a specific feature, please reffer to the corresponding section of the help docs for detailed explinations of common errors and troubleshooting steps. If you cannot find an answer to your question, please reach out to Concierge via in-product chat or by emailing us at concierge@expensify.com. +# Issues with a Specific Feature + +If you're experiencing issues with a specific feature, refer to the corresponding section of the help docs for detailed explanations of common errors and troubleshooting steps. If you cannot find an answer, reach out to **Concierge** via in-product chat or email us at **concierge@expensify.com**. + +# Troubleshooting Local Issues -# Troubleshooting local issues Is your webpage not loading? Try these steps: -- Try clicking [here](https://www.expensify.com/signout.php?clean=true), which will force a clean sign-out from the site, which can be very helpful in removing any stale data that can cause issues. -- Clear cookies & cache on your browser. -- Try using an Incognito or Private browsing window. -- Try on a different browser. + +- Click [here](https://www.expensify.com/signout.php?clean=true) to force a clean sign-out from the site. This removes any stale data that may cause issues. +- Clear your browser's **cookies and cache**. +- Open an **Incognito** or **Private browsing** window. +- Try using a **different browser**. # JavaScript Console -A developer console is a tool that logs information about the backend operations of the sites you visit and the applications you run. This information can help our developers solve any issue that you may experience. -If you've been asked to provide a screenshot of your developer console, scroll down to find the instructions for the browser or application you're using. +A developer console logs information about backend operations on websites and applications. This information helps developers diagnose issues. + +If you've been asked to provide a screenshot of your developer console, follow the instructions below based on your browser. -## Chrome +## Chrome -- Keyboard shortcut - - Mac: Cmd + Option + J - - Windows: Ctrl + Shift + J -- From the menu: View > Developer > JavaScript Console +- **Keyboard Shortcut**: + - **Mac**: Cmd + Option + J + - **Windows**: Ctrl + Shift + J +- **From the Menu**: View > Developer > JavaScript Console ## Firefox -- Keyboard shortcut: - - Mac: Cmd + Option + K - - Windows: Ctrl + Shift + J -- From the menu: Menu Bar > More Tools > Web Developer Tools > Console tab +- **Keyboard Shortcut**: + - **Mac**: Cmd + Option + K + - **Windows**: Ctrl + Shift + J +- **From the Menu**: Menu Bar > More Tools > Web Developer Tools > Console tab ## Safari - -Before opening the console you will need to enable it in Safari by clicking the Safari Menu > Settings > Advanced > and selecting the "Show features for web developers" checkbox. Once enabled, you can locate the console in the developer menu or open it using the keyboard shortcut: -- Keyboard shortcut: Cmd + Option + C -- From the menu: Develop Menu > Show JavaScript Console +Before opening the console, enable it in Safari: + +1. Click **Safari Menu** > **Settings** > **Advanced**. +2. Check **Show features for web developers**. + +Once enabled, open the console: + +- **Keyboard Shortcut**: Cmd + Option + C +- **From the Menu**: Develop Menu > Show JavaScript Console + +## Microsoft Edge -## Microsoft Edge +- **Keyboard Shortcut**: + - **Mac**: Cmd + Option + J + - **Windows**: Ctrl + Shift + J +- **From the Menu**: Right-click a webpage > Inspect > Console -- Keyboard shortcut: - - Mac: Cmd + Option + J - - Windows: Ctrl + Shift + J -- From the menu: Right-click a webpage > Inspect > Console +Following these steps will help diagnose and resolve common website issues effectively. diff --git a/docs/articles/expensify-classic/settings/Merge-accounts.md b/docs/articles/expensify-classic/settings/Merge-accounts.md index b2e3b0cdf55b..e9e90accc459 100644 --- a/docs/articles/expensify-classic/settings/Merge-accounts.md +++ b/docs/articles/expensify-classic/settings/Merge-accounts.md @@ -1,30 +1,43 @@ --- -title: Merge accounts -description: Merge two Expensify accounts into one +title: Merge Accounts +description: Learn how to merge two Expensify accounts into one. --- -
-If you have two Expensify accounts (for example, a personal account and a separate account for your company), you can combine the two accounts by merging them. Once merged, all receipts, expenses, expense reports, invoices, bills, imported cards, secondary logins, co-pilots, and group workspace settings from both accounts will be combined into one account. +If you have two Expensify accounts (e.g., a personal account and a separate company account), you can combine them by merging. This process consolidates all receipts, expenses, reports, invoices, bills, imported cards, secondary logins, co-pilots, and group workspace settings into one account. {% include info.html %} -Merging two accounts is a permanent action that cannot be reversed. To merge a company and personal account, you must sign in to your company account and merge your personal account with it. You cannot merge a company account into a personal account, nor can you merge two different company accounts together if they are private domains. +Merging accounts is **permanent** and **cannot be undone**. To merge a company and personal account, log in to your **company account** and merge your **personal account** with it. + +- You **cannot** merge a company account into a personal account. +- You **cannot** merge two company accounts if they belong to private domains. {% include end-info.html %} -*Note: This process is currently not available from the mobile app and must be completed from the Expensify website.* +*Note: This process must be completed from the Expensify website and is not available in the mobile app.* + +--- + +## How to Merge Accounts -1. Log in to Expensify using the account you want to keep as the primary. -2. Hover over Settings and click **Account**. -3. Under Account Details, scroll down to the Merge Accounts section. -4. Enter the email address or phone number associated with the account that you want to merge into this account. -5. Select the “Yes, I understand this is not reversible†checkbox to agree. +1. Log in to Expensify using the **account you want to keep** as the primary. +2. Hover over **Settings** and click **Account**. +3. Scroll down to the **Merge Accounts** section under Account Details. +4. Enter the **email address or phone number** associated with the account you want to merge. +5. Select the **“Yes, I understand this is not reversibleâ€** checkbox. 6. Click **Merge Accounts**. -7. Check your email for the magic code sent from Expensify and copy the code. -8. Paste the code into the field and click **Merge**. +7. Check your email for the **magic code** sent from Expensify. +8. Copy and paste the code into the field, then click **Merge**. -# FAQ +--- -**What information merges into my new account?** +# FAQ -All receipts, expenses, expense reports, invoices, bills, imported cards, secondary logins, co-pilots, and group workspace settings will be merged into your new account. +**What happens to my data when I merge accounts?** +All of the following will be transferred into your new account: +- Receipts and expenses +- Expense reports +- Invoices and bills +- Imported cards +- Secondary logins +- Co-pilots +- Group workspace settings -
diff --git a/docs/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing.md b/docs/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing.md index bc39e33bab4a..59314be96584 100644 --- a/docs/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing.md +++ b/docs/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing.md @@ -1,48 +1,46 @@ --- -title: Expensify plan types and pricing +title: Expensify Plan Types and Pricing +description: An overview of Expensify's plan types and pricing description: An overview of plan types and pricing --- -
-Expensify offers plans and flexible pricing to cater to different business sizes and needs, whether you’re self-employed, part of a large organization, or anything in between. +Expensify offers flexible pricing plans designed to suit different business sizes and needs, whether you’re self-employed, part of a large organization, or somewhere in between. # Choosing the Right Plan -Expensify offers two pricing plans: +Expensify provides two main pricing plans: +| Feature | **Collect Plan** | **Control Plan** | +|----------------------|--------------------------------------------------|--------------------------------------------------| +| **Ideal for** | Small teams or businesses with 1-10 employees | Larger companies with 10-1000 employees | +| **Pricing*** | $5 USD per user/month | $9 USD per user/month | +| **SmartScans** | ✔ Unlimited | ✔ Unlimited | +| **Expensify Card** | ✔ Smart Limits & 1-2% cash back | ✔ Smart Limits & 1-2% cash back | +| **Expense Approvals** | ✔ Yes | ✔ Multiple approvers | +| **ACH Reimbursement** | ✔ Unlimited | ✔ Unlimited | +| **Bank Feed Support** | ⌠Not available | ✔ Third-party card feeds & reconciliation | +| **Accounting Sync** | ✔ QuickBooks Online & Xero | ✔ NetSuite, Sage Intacct, QuickBooks Desktop | +| **HR & Payroll Sync** | ⌠Not available | ✔ Gusto, Zenefits, Certinia, Workday | +| **Security & Control**| ⌠Not available | ✔ SAML/SSO & admin-enforced controls | -| | Collect Plan | Control Plan | -|--------------------|---------------------------|---------------------------------------------------------| -| **Ideal for:** | Sole proprietors and small teams or businesses with 1-10 employees | Larger companies with 10-1000 employees and more complex expense management needs | -| **Pricing starts at:** | $5 USD per user/month on an annual subscription (Non-USD prices available in FAQ) | $9 USD per user/month on an annual subscription (Non-USD prices available in FAQ) | -| | ✔ Unlimited SmartScans and distance tracking | ✔ All Collect Plan features | -| | ✔ Expensify Cards with Smart Limits and cash back | ✔ Third-party card feeds and reconciliation | -| | ✔ Expense approvals | ✔ Integration with NetSuite, Sage Intacct, and QuickBooks Desktop | -| | ✔ Unlimited ACH reimbursement | ✔ Gusto, Zenefits, Certinia, and Workday sync | -| | ✔ Integration with QuickBooks Online and Xero | ✔ Multiple expense approvers | -| | | ✔ SAML/SSO for added security | -| | | ✔ Admin-enforced controls | +***Note**: This price is available if you have an **Annual Subscription** and your team adopts the **Expensify Card**. Expensify Card usage on both plans generates 1% cash back with every swipe on US purchases—no minimums necessary—and 2% back if you spend $250k+/month across cards. -Expensify Card usage on both plans generates 1% cash back with every swipe on US purchases --- no minimums necessary --- and 2% back if you spend $250k+/month across cards. +--- # FAQ ## How much does Expensify cost? - -The cost depends on your plan and subscription type. Expensify offers a 50% discount for annual subscriptions and up to another 50% discount for using Expensify Cards. Try out our [savings calculator](https://use.expensify.com/savings-calculator) for an easy estimate based on your numbers. +The cost depends on your plan and subscription type. Expensify offers a 50% discount for annual subscriptions and up to another 50% discount for using Expensify Cards. Try out our [savings calculator](https://use.expensify.com/savings-calculator) to estimate your cost. ## Does Expensify bill in non-USD currencies? +Yes! Customers can pay in AUD, GBP, or NZD in addition to USD. -Yes! Customers can pay for Expensify in AUD, GBP, or NZD in addition to USD. -- The Collect plan begins at A$14, £8, or NZ$16 per user/month on an annual subscription -- The Control plan begins at A$30, £14, or NZ$32 per user/month on an annual subscription +- **Collect Plan:** A$14, £8, or NZ$16 per user/month (Annual subscription + Expensify Cards) +- **Control Plan:** A$30, £14, or NZ$32 per user/month (Annual subscription + Expensify Cards) ## Is Expensify free for individuals? - -Yes! Individuals can use Expensify for free to track expenses. +Yes! Individuals can use Expensify for free to track expenses. The steps in this [help article](https://help.expensify.com/articles/expensify-classic/getting-started/Create-a-workspace-for-yourself) will walk you through creating a personal workspace to track your expenses. ## How do I get more info about pricing? +For customized information or help choosing the right plan, reach out to Expensify Concierge or email **concierge@expensify.com**. -For customized information or help choosing the right plan, reach out to Expensify Concierge or email concierge@expensify.com. - -
diff --git a/docs/articles/new-expensify/connect-credit-cards/Personal-Cards.md b/docs/articles/new-expensify/connect-credit-cards/Personal-Cards.md new file mode 100644 index 000000000000..70fc5cdffb52 --- /dev/null +++ b/docs/articles/new-expensify/connect-credit-cards/Personal-Cards.md @@ -0,0 +1,12 @@ +--- +title: Personal Cards +description: Learn how to track and manage your personal credit card expenses in Expensify through automatic imports or manual uploads. +--- + +# Overview + +Expensify makes it easy to track expenses and get reimbursed by linking your personal credit card. Once connected, transactions can be imported automatically, or you can upload a CSV file for manual entry. These transactions will be merged with SmartScanned receipts and, if enabled, can generate IRS-compliant eReceipts. + +--- + +We are currently developing the personal card connection feature for New Expensify. Once available, we will update this article with step-by-step instructions on how to connect your card. Stay tuned! diff --git a/docs/articles/new-expensify/connect-credit-cards/company-cards/Commercial-feeds.md b/docs/articles/new-expensify/connect-credit-cards/company-cards/Commercial-feeds.md index 2dbe47d3b178..bd94e2ccff54 100644 --- a/docs/articles/new-expensify/connect-credit-cards/company-cards/Commercial-feeds.md +++ b/docs/articles/new-expensify/connect-credit-cards/company-cards/Commercial-feeds.md @@ -2,115 +2,111 @@ title: Commercial-feeds.md description: Commercial feeds --- + # Overview Commercial feeds are the most reliable way to import company card expenses. They remain unaffected by changes to bank login credentials or UI updates, making them highly recommended for those eligible. + The easiest way to confirm your eligibility for a commercial feed is to ask your bank directly. -# Prerequisites for enabling a commercial feed -If you haven't already, you need to create a workspace before setting up a commercial feed. Go to Settings > Workspaces > New workspace to create one. -Additionally, you’ll need to enable company cards on your workspace by navigating to Settings > Workspaces > [your workspace] > More features, and toggling on Company cards. Note that upgrading to the Control plan is required to access this feature. -# How to set up a Mastercard commercial feed + +# Prerequisites for Enabling a Commercial Feed +If you haven't already, you need to create a workspace before setting up a commercial feed. Go to **Settings > Workspaces > New workspace** to create one. + +Additionally, you’ll need to enable company cards on your workspace by navigating to **Settings > Workspaces > [your workspace] > More features**, and toggling on **Company cards**. Note that upgrading to the Control plan is required to access this feature. + +# How to Set Up a Mastercard Commercial Feed Your bank must access Mastercard's SmartData portal to complete the process. Expensify is a registered vendor in the portal, so no additional Mastercard forms are required. Your bank may, however, have its own forms. -## Steps to add a Mastercard commercial feed: -Contact your banking relationship manager and request that your CDF (Common Data File) feed be sent directly to Expensify in the Mastercard SmartData Portal (file type: CDF version 3 Release 11.01). Specify the earliest transaction date you need in the feed. -The bank will initiate feed delivery by selecting Expensify in Mastercard's portal and will email you the distribution ID. -While waiting for your bank, ensure your Control plan workspace in Expensify is set up. -Submit the distribution ID in Expensify by navigating to Settings > Workspaces > [your workspace] > Company cards > Add cards, selecting your bank (choose "Other" if not listed), and then selecting Mastercard Commercial Cards. -Once submitted, Expensify will connect the feed and notify you when it’s enabled. -# How to set up a Visa commercial feed -## Steps to add a Visa commercial feed: -Contact your banking relationship manager and request that your VCF (Variant Call Format) feed be sent directly to Expensify. Share this with your bank: "There’s a checkbox in your Visa Subscription Management portal that can be selected to enable the feed, eliminating the need for a test file." -Request the feed filename or raw file information, including the Processor ID, Financial Institution (bank) ID, and Company ID. -While waiting for your bank, ensure your Control plan workspace in Expensify is set up. -Submit the required IDs in Expensify by navigating to Settings > Workspaces > [your workspace] > Company cards > Add cards, selecting your bank (choose "Other" if not listed), and then selecting Visa Commercial Cards. -Once submitted, Expensify will connect the feed and notify you when it’s enabled. - -# How to set up an American Express corporate feed + +## Steps to Add a Mastercard Commercial Feed: +1. Contact your banking relationship manager and request that your CDF (Common Data File) feed be sent directly to Expensify in the Mastercard SmartData Portal (file type: CDF version 3 Release 11.01). Specify the earliest transaction date you need in the feed. +2. The bank will initiate feed delivery by selecting Expensify in Mastercard's portal and will email you the distribution ID. +3. While waiting for your bank, ensure your Control workspace in Expensify is set up. +4. Submit the distribution ID in Expensify by navigating to **Settings > Workspaces > [your workspace] > Company cards > Add cards**, selecting your bank (choose "Other" if not listed), and then selecting **Mastercard Commercial Cards**. +5. Once submitted, Expensify will connect the feed and notify you when it’s enabled. + +# How to Set Up a Visa Commercial Feed +## Steps to Add a Visa Commercial Feed: +1. Contact your banking relationship manager and request that your VCF (Variant Call Format) feed be sent directly to Expensify. Share this with your bank: "There’s a checkbox in your Visa Subscription Management portal that can be selected to enable the feed, eliminating the need for a test file." +2. Request the feed filename or raw file information, including the Processor ID, Financial Institution (bank) ID, and Company ID. +3. While waiting for your bank, ensure your Control workspace in Expensify is set up. +4. Submit the required IDs in Expensify by navigating to **Settings > Workspaces > [your workspace] > Company cards > Add cards**, selecting your bank (choose "Other" if not listed), and then selecting **Visa Commercial Cards**. +5. Once submitted, Expensify will connect the feed and notify you when it’s enabled. + +# How to Set Up an American Express Corporate Feed To begin, fill out Amex's required forms and send them to Amex for processing. Download the forms [here](https://drive.google.com/file/d/1zqDA_MCk06jk_fWjzx2y0r4gOyAMqKJe/view?usp=sharing). -## Instructions for filling out the Amex forms: -PAGE 1 -Corporation Name: The legal name of your company on file with American Express -Corporation Address: The legal address of your company -Requested Feed Start Date: The earliest transaction date you want in Expensify (use international date format: DD/MM/YY or spelled out, e.g., January 1, 1900). -Requestor Contact: Name of the person completing the request -Email Address: Email of the person completing the request -Control Account Number: The master or basic control account number for the cards you’d like to add (not a credit card number). Contact Amex if you need assistance identifying the correct number. -PAGE 2 +## Instructions for Filling Out the Amex Forms: +**PAGE 1** +- **Corporation Name:** The legal name of your company on file with American Express +- **Corporation Address:** The legal address of your company +- **Requested Feed Start Date:** The earliest transaction date you want in Expensify (use international date format: DD/MM/YY or spelled out, e.g., January 1, 1900). +- **Requestor Contact:** Name of the person completing the request +- **Email Address:** Email of the person completing the request +- **Control Account Number:** The master or basic control account number for the cards you’d like to add (not a credit card number). Contact Amex if you need assistance identifying the correct number. + +**PAGE 2** No information required -PAGE 3 -Client Registered Name: The legal name of your company on file with American Express -Master Control Account or Basic Control Account: Same as the control account number on page 1 -PAGE 4 -Country List: The country where the account originates -Client Authorization: Complete your full name, job title, and date (use international date format i.e., DD/MM/YY). Sign where indicated. +**PAGE 3** +- **Client Registered Name:** The legal name of your company on file with American Express +- **Master Control Account or Basic Control Account:** Same as the control account number on page 1 + +**PAGE 4** +- **Country List:** The country where the account originates +- **Client Authorization:** Complete your full name, job title, and date (use international date format i.e., DD/MM/YY). Sign where indicated. -## Steps to add an American Express corporate feed: -Send the completed forms to electronictransmissionsteam@aexp.com and request they send your corporate card feed to Expensify. You should receive a confirmation email within a few days. -While waiting, ensure your Control plan workspace in Expensify is set up. -Amex will send a Production Letter with delivery file name information (e.g., R123456_B123456789_GL1025_001_$DATE$$TIME$_$SEQ$). -Submit the delivery file name in Expensify by navigating to Settings > Workspaces > [your workspace] > Company cards > Add cards > American Express > American Express Corporate Cards. -Once submitted, Expensify will connect the feed and notify you when it’s enabled. +## Steps to Add an American Express Corporate Feed: +1. Send the completed forms to **electronictransmissionsteam@aexp.com** and request they send your corporate card feed to Expensify. You should receive a confirmation email within a few days. +2. While waiting, ensure your Control workspace in Expensify is set up. +3. Amex will send a Production Letter with delivery file name information (e.g., `R123456_B123456789_GL1025_001_$DATE$$TIME$_$SEQ$`). +4. Submit the delivery file name in Expensify by navigating to **Settings > Workspaces > [your workspace] > Company cards > Add cards > American Express > American Express Corporate Cards**. +5. Once submitted, Expensify will connect the feed and notify you when it’s enabled. -# How to assign company cards -Once your feed is connected, you can assign cards to employees. To do this, navigate to Settings > Workspaces > [your workspace] > Company cards. +# How to Assign Company Cards +Once your feed is connected, you can assign cards to employees. To do this, navigate to **Settings > Workspaces > [your workspace] > Company cards**. -![Click the feed name to view the feed selector]({{site.url}}/assets/images/commfeed/commfeed-01.png){:width="100%"} +![Click the feed name to view the feed selector]({{site.url}}/assets/images/commfeed/commfeed-01-updated.png){:width="100%"} If you have multiple feeds, click the feed name at the top left to select the appropriate one. -![Select a feed from the feed selector to view it]({{site.url}}/assets/images/commfeed/commfeed-02.png){:width="100%"} +![Select a feed from the feed selector to view it]({{site.url}}/assets/images/commfeed/commfeed-02-updated.png){:width="100%"} -Click Assign card to select an employee. All workspace members appear in the list. +Click **Assign card** to select an employee. All workspace members appear in the list. -![Click assign card and select an employee from the list]({{site.url}}/assets/images/commfeed-03.png){:width="100%"} +![Click assign card and select an employee from the list]({{site.url}}/assets/images/commfeed/commfeed-03-updated.png){:width="100%"} Select the card you want to assign. Cards only appear if they have recent transactions. -![Select a card from the list]({{site.url}}/assets/images/commfeed/commfeed-04.png){:width="100%"} +![Select a card from the list]({{site.url}}/assets/images/commfeed/commfeed-04-updated.png){:width="100%"} Choose a start date: -From the beginning: Imports all available transactions (typically 30-90 days). -Custom start date: Allows you to specify a date. -![Select your transaction start date]({{site.url}}/assets/images/commfeed/commfeed-05.png){:width="100%"} -Review the details and click Assign card. Transactions will import immediately. -![Double check the selections and assign the card]({{site.url}}/assets/images/commfeed/commfeed-06.png){:width="100%"} - -# Managing cards -Clicking an assigned card opens the Card details page, where you can: -Change the card name. -Select a card-specific export account (if connected to accounting software like QuickBooks, NetSuite, Xero, etc.). -Update the card to pull recent transactions. -Unassign the card (note: unassigning deletes unsubmitted expenses on draft reports in the cardholder’s account). -![Manage the card on the card details page]({{site.url}}/assets/images/commfeed/commfeed-07.png){:width="100%"} - -{% include faq-begin.md %} +- **From the beginning:** Imports all available transactions (typically 30-90 days). +- **Custom start date:** Allows you to specify a date. + +![Select your transaction start date]({{site.url}}/assets/images/commfeed/commfeed-05-updated.png){:width="100%"} -## My commercial feed is connected. Why is a specific card not appearing for assignment? -Cards appear for assignment if they’re active and have at least one recent transaction. If a card meeting these criteria doesn’t appear, contact your account manager or message concierge@expensify.com. +Review the details and click **Assign card**. Transactions will import immediately. -## Is there an extra fee for using commercial feeds? -No, commercial feed setup is included in the Control plan. +![Double check the selections and assign the card]({{site.url}}/assets/images/commfeed/commfeed-06-updated.png){:width="100%"} -## What’s the difference between a direct feed and commercial feed? -Direct feeds use login credentials for quick setup, but can require re-authenticating from time to time. Commercial feeds require bank involvement for setup but offer the most reliable connection. +# Managing Cards +Once a card is assigned, you can manage its settings by navigating to **Settings > Workspaces > [your workspace] > Company cards** and selecting the assigned card. -## I have a Small Business Amex account. Am I eligible to set up a commercial feed? -Small Business or Triumph Amex accounts may not be eligible for a commercial feed and might need to use an Amex direct feed. +## Available Card Management Actions: +- **Rename the Card**: Change the card name for easier identification. +- **Set a Specific Export Account**: If connected to accounting software like QuickBooks, NetSuite, or Xero, you can assign a unique export account for this card. +- **Update Transactions**: Manually refresh the card feed to pull in the latest transactions. +- **Unassign the Card**: Removing a card unassigns it from the employee and deletes unsubmitted expenses from draft reports in their account. -## Are commercial feeds the best option if my bank isn’t one where Expensify supports direct feeds? -Yes. If direct feeds are not available for your bank, commercial feeds are the best option for importing company card transactions. Currently, Expensify supports direct feeds for: -American Express -Bank of America -Brex -Capital One -Chase -Citibank -Stripe -Wells Fargo +![Manage the card on the card details page]({{site.url}}/assets/images/commfeed/commfeed-07-updated.png){:width="100%"} +# FAQ -{% include faq-end.md %} +## My commercial feed is connected. Why is a specific card not appearing for assignment? +Cards appear for assignment if they’re active and have at least one recent transaction. If a card meeting these criteria doesn’t appear, contact your account manager or message concierge@expensify.com. +## Is there an extra fee for using commercial feeds? +No, commercial feed setup is included in the Control plan. +## What’s the difference between a direct feed and commercial feed? +Direct feeds use login credentials for quick setup, but can require re-authenticating from time to time. Commercial feeds require bank involvement for setup but offer the most reliable connection. diff --git a/docs/articles/new-expensify/connect-credit-cards/company-cards/Company-Card-Settings.md b/docs/articles/new-expensify/connect-credit-cards/company-cards/Company-Card-Settings.md new file mode 100644 index 000000000000..52bbf32921a2 --- /dev/null +++ b/docs/articles/new-expensify/connect-credit-cards/company-cards/Company-Card-Settings.md @@ -0,0 +1,54 @@ +--- +title: Company Card Settings +description: Learn how to manage company card settings, export transactions to accounting software, and enable eReceipts for efficient expense tracking in Expensify +--- + +Workspace admins can easily manage the company card settings at the workspace level. This guide walks you through adjusting the settings, exporting transactions to your accounting system, and enabling eReceipts for efficient expense tracking. + +--- +# Managing Company Card Settings +To make changes to the settings: + +1. Go to **Settings > Workspaces > [Workspace Name] > Company cards**. +2. Click **Settings**, where you can: + - Change the name of the company card connection. + - Adjust whether cardholders can delete transactions (changes to this setting only apply to new card transactions). + - Remove the card connection (removing the connection unassigns all its cards and deletes unsubmitted expenses on draft reports in cardholder accounts). + +![Tap settings to open the card feed settings page]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_12.png){:width="100%"} + +--- +# Exporting Transactions to an Accounting System +If you’ve connected accounting software (e.g., QuickBooks, NetSuite, Xero), you can export card transactions to either: + +- A central account (default setting). +- Separate individual accounts. + +## Exporting to a Central Account +To adjust the export type for the central account head to **Settings > Workspaces > [Workspace Name] > Accounting > Connections > Export > Export company card expenses as**. + +## Exporting to Individual Accounts +1. Go to **Settings > Workspaces > [Workspace Name] > Company cards**. +2. Click an assigned card to open the **Card details** page. +3. Select an individual card account manually to override the central export account. + - If left as **Default card**, the central account is used. +--- +# Using eReceipts +Expensify provides eReceipts as digital substitutes for paper receipts, eliminating the need for SmartScanned physical receipts. eReceipts are automatically generated for many USD purchases of $75 or less on both commercial and direct credit card connections. + +To enable eReceipts: +1. Go to **Settings > Workspaces > [Workspace Name] > More features**, and enable **Rules**. +2. Navigate to **Rules**, and enable **eReceipts**. + +**Note**: +- eReceipts are not generated for some expense categories, such as lodging. +- Incomplete or inaccurate category information from some banks or re-categorizing expenses may invalidate eReceipts. + +--- +# FAQ + +## Are company cards limited to a certain plan? +Yes, company cards are limited to the Control plan. However, the [Expensify Card](https://use.expensify.com/company-credit-card) is available on both the Collect and Control plans. + +## When do card transactions import into Expensify? +Card transactions start importing after a card is assigned. Expensify imports transactions as soon as they post, typically within 1-3 business days. Pending transactions will not appear in your account. diff --git a/docs/articles/new-expensify/connect-credit-cards/company-cards/Direct-feeds.md b/docs/articles/new-expensify/connect-credit-cards/company-cards/Direct-feeds.md new file mode 100644 index 000000000000..9388ba3d8aa9 --- /dev/null +++ b/docs/articles/new-expensify/connect-credit-cards/company-cards/Direct-feeds.md @@ -0,0 +1,101 @@ +--- +title: Direct-feeds.md +description: Direct feeds +--- +# Overview +Direct feeds are a quick and reliable way to import company card expenses. Connect your bank to Expensify with your login credentials to be up and running in minutes! + +# Prerequisites for enabling a direct feed +If you haven't already, you need to create a workspace before setting up a direct feed. Go to **Settings > Workspaces > New workspace** to create one. +Additionally, you’ll need to enable company cards on your workspace by navigating to **Settings > Workspaces > [your workspace] > More features**, and toggling on **Company cards**. Note that upgrading to the Control plan is required to access this feature. +# How to set up a direct feed +After creating a Control workspace, you can add a direct feed by going to **Settings > Workspaces > [your workspace] > Company cards** and selecting **Add cards**. + +![Click add cards to add a card feed]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_01.png){:width="100%"} + +Select your bank from the list. If your bank isn’t there, check out [commercial feeds](https://help.expensify.com/articles/new-expensify/connect-credit-cards/company-cards/Commercial-feeds). + +![Select your bank and click Next]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_02.png){:width="100%"} + +Select **Direct feed**. + +![Select your feed type and click next]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_03.png){:width="100%"} + +Log into your bank’s website using the master credentials (typically those of the account owner with the highest-level access), then follow the steps to select your account(s) and connect them to Expensify. + +![Login to your bank]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_04.png){:width="100%"} + +# How to assign company cards +Once your feed is connected, you can assign cards to employees. To do this, navigate to **Settings > Workspaces > [your workspace] > Company cards**. + +![Click company cards in the workspace editor to open the feed]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_05.png){:width="100%"} + +If you have multiple feeds, click the feed name at the top left to select the appropriate one. + +![Click the feed name in the top left to open the feed selector where you can select a feed from the list]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_06.png){:width="100%"} + +Click **Assign card** to begin the process. Often, you’ll be prompted to log into your bank before you can assign cards. Select an employee. All workspace members appear in the list. + +![Click assign card to begin the flow. Start by selecting a member from the list]({{site.url}}/assets/images/Direct Feed HelpDot Images +/directfeeds_07.png){:width="100%"} + +Select the card you want to assign. Cards only appear if they have recent transactions. + +![Select a card from the list]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_08.png){:width="100%"} + +Choose a start date: +- **From the beginning**: Imports all available transactions (typically 30-90 days). +- **Custom start date**: Allows you to specify a date. + +![Choose a transaction start date]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_09.png){:width="100%"} + +Review the details and click **Assign card**. Transactions will import immediately. + +![Check your selections and assign the card]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_10.png){:width="100%"} + +# Managing cards +Clicking an assigned card opens the **Card details** page, where you can: + +- Change the card name. +- Select a card-specific export account (if connected to accounting software like QuickBooks, NetSuite, Xero, etc.). +- Update the card to pull recent transactions. +- Unassign the card (note: unassigning deletes unsubmitted expenses on draft reports in the cardholder’s account). + +![Tap the assigned card to open the card details page where you can manage the card]({{site.url}}/assets/images/Direct Feed HelpDot Images/directfeeds_11.png){:width="100%"} + +{% include faq-begin.md %} +## My direct feed is connected. Why is a specific card not appearing for assignment? +Make sure you used your bank's master credentials when connecting. Also, note that cards will only appear for assignment if they’re active and have at least one recent transaction. If a card meeting these conditions doesn’t appear, reach out to your account manager or email concierge@expensify.com. + +## Are direct bank connections the best option for connecting credit cards to Expensify? +Direct bank connections are a great option if Expensify supports your bank. For enhanced stability and added functionality, consider setting up a [commercial feed](https://help.expensify.com/articles/new-expensify/connect-credit-cards/company-cards/Commercial-feeds) with your bank or using the [Expensify Card](https://use.expensify.com/company-credit-card). + +## Is there an extra fee for using direct feeds? +No, direct feeds are included with the Control plan at no additional cost. + +## What’s the difference between a direct feed and commercial feed? +Direct feeds use login credentials for quick setup, but can require re-authentication from time to time. Commercial feeds require bank involvement for setup but offer the most reliable connection. + +## What if Expensify doesn’t support direct feeds for my bank? +If direct feeds aren’t available for your bank, commercial feeds are the best option for importing company card transactions. Currently, Expensify supports direct feeds for: +- American Express +- Bank of America +- Brex +- Capital One +- Chase +- Citibank +- Stripe +- Wells Fargo + +## Can direct feeds have maintenance/downtime? +Yes, occasionally. Banks may update their systems to enhance security, which can temporarily affect connections. Expensify’s engineering team works closely with banks to monitor and promptly update connections as needed. + +## My direct feed connection is broken, how do I fix it? +Direct feed connections may break if your bank login credentials, card numbers, or security questions change. To fix this, go to **Settings > Workspaces > [your workspace] > Company cards** > select **log into your bank** on the error message and follow the steps to fix the connection. + +## Can I connect several direct feeds with the same bank on one workspace? +No, only one direct feed per bank can be connected to a workspace. If you have multiple card programs with the same bank under different credentials, request that your bank consolidate them under one set of credentials. This allows you to connect all card programs through a single direct feed. + +You can, however, connect multiple direct feeds to a workspace if they’re from different banks. +{% include faq-end.md %} + diff --git a/docs/articles/new-expensify/connections/netsuite/Connect-To-NetSuite.md b/docs/articles/new-expensify/connections/netsuite/Connect-To-NetSuite.md new file mode 100644 index 000000000000..99a67a577500 --- /dev/null +++ b/docs/articles/new-expensify/connections/netsuite/Connect-To-NetSuite.md @@ -0,0 +1,162 @@ +--- +title: Connect To NetSuite +description: Connect NetSuite to New Expensify for streamlined expense reporting and accounting integration. +order: 1 +--- + +{% include info.html %} +To use the NetSuite connection, you must have a NetSuite account and an Expensify Control plan. +{% include end-info.html %} + +Expensify’s integration with NetSuite supports syncing data between the two systems. Before you start connecting Expensify with NetSuite, there are a few things to note: + +- You must be able to login to NetSuite as an administrator to initiate the connection. +- A Control Plan in Expensify is required to integrate with NetSuite. +- Employees don’t need NetSuite access or a NetSuite license to submit expense reports and sync them to NetSuite. +- Each NetSuite subsidiary must be connected to a separate Expensify workspace. +- The workspace currency in Expensify must match the NetSuite subsidiary's default currency. + +# Step 1: Install the Expensify Bundle in NetSuite + +1. While logged into NetSuite as an administrator, go to _Customization > SuiteBundler > Search & Install Bundles_, then search for “Expensifyâ€. +2. Click on the Expensify Connect bundle (Bundle ID 283395). +3. Click **Install**. +4. If you already have the Expensify Connect bundle installed, head to _Customization > SuiteBundler > Search & Install Bundles > List_, and update it to the latest version. +5. Select **Show on Existing Custom Forms** for all available fields. + + +# Step 2: Enable Token-Based Authentication + +1. In NetSuite, go to _Setup > Company > Enable Features > SuiteCloud > Manage Authentication_. +2. Make sure “Token Based Authentication†is enabled. +3. Click **Save**. + + +# Step 3: Add Expensify Integration Role to a User + +1. In NetSuite, head to _Lists > Employees_, and find the user to who you would like to add the Expensify Integration role. The user you select must have access to at least the permissions included in the Expensify Integration Role, but they’re not required to be a NetSuite admin. +2. Click _Edit > Access_, then find the Expensify Integration role in the dropdown and add it to the user. +3. Click **Save**. + + +{% include info.html %} +Remember that Tokens are linked to a **User** and a **Role**, not solely to a User. It’s important to note that you cannot establish a connection with tokens using one role and then switch to another role afterward. Once you’ve initiated a connection with tokens, you must continue using the same token/user/role combination for all subsequent sync or export actions. +{% include end-info.html %} + + +# Step 4: Create Access Tokens + +1. In NetSuite, enter “page: tokens†in the Global Search. +2. Click **New Access Token**. +3. Select Expensify as the application (this must be the original Expensify integration from the bundle). +4. Select the role Expensify Integration. +5. Click **Save**. +6. Copy and paste the token and token ID to a saved location on your computer (this is the only time you will see these details.) + + +# Step 5: Confirm Expense Reports are enabled in NetSuite + +{% include info.html %} +Expense Reports must be enabled in order to use Expensify’s integration with NetSuite. +{% include end-info.html %} + + +1. In NetSuite, go to _Setup > Company > Enable Features > Employees_. +2. Confirm the checkbox next to Expense Reports is checked. +3. If not, click the checkbox and then click **Save** to enable Expense Reports. + + +# Step 6: Confirm Expense Categories are set up in NetSuite + +{% include info.html %} +Once Expense Reports are enabled, Expense Categories can be set up in NetSuite. Expense Categories are synced to Expensify as Categories. Each Expense Category is an alias mapped to a General Ledger account so that employees can more easily categorize expenses. +{% include end-info.html %} + +1. In NetSuite, go to _Setup > Accounting > Expense Categories_ (a list of Expense Categories should show.) +2. If no Expense Categories are visible, click **New** to create new ones. + + +# Step 7: Confirm Journal Entry Transaction Forms are Configured Properly + +1. In NetSuite, go to _Customization > Forms > Transaction Forms_. +2. Click **Customize** or **Edit** next to the Standard Journal Entry form. +3. Click _Screen Fields > Main_. Please verify the “Created From†label has “Show†checked and the "Display Type" is set to "Normal." +4. Click the sub-header **Lines** and verify that the “Show†column for “Receipt URL†is checked. +5. Go to _Customization > Forms > Transaction Forms_ and ensure that all other transaction forms with the journal type have this same configuration. + + +# Step 8: Confirm Expense Report Transaction Forms are Configured Properly + +1. In NetSuite, go to _Customization > Forms > Transaction Forms_. +2. Click **Customize** or **Edit** next to the Standard Expense Report form, then click _Screen Fields > Main_. +3. Verify the “Created From†label has “Show†checked and the "Display Type" is set to "Normal." +4. Click the second sub-header, **Expenses**, and verify that the "Show" column for "Receipt URL" is checked. +5. Go to _Customization > Forms > Transaction Forms_ and ensure that all other transaction forms with the expense report type have this same configuration. + + +# Step 9: Confirm Vendor Bill Transactions Forms are Configured Properly + +1. In NetSuite, go to _Customization > Forms > Transaction Forms_. +2. Click **Customize** or **Edit** next to your preferred Vendor Bill form. +3. Click _Screen Fields > Main_ and verify that the “Created From†label has “Show†checked and that Departments, Classes, and Locations have the “Show†label unchecked. +4. Under the **Expenses** sub-header (make sure to click the “Expenses†sub-header at the very bottom and not “Expenses & Itemsâ€), ensure “Show†is checked for Receipt URL, Department, Location, and Class. +5. Go to _Customization > Forms > Transaction Forms_ and ensure that all other transaction forms with the vendor bill type have this same configuration. + + +# Step 10: Confirm Vendor Credit Transactions Forms are Configured Properly + +1. In NetSuite, go to _Customization > Forms > Transaction Forms_. +2. Click **Customize** or **Edit** next to your preferred Vendor Credit form, then click _Screen Fields > Main_ and verify that the “Created From†label has “Show†checked and that Departments, Classes, and Locations have the “Show†label unchecked. +3. Under the Expenses sub-header (make sure to click the “Expenses†sub-header at the very bottom and not “Expenses & Itemsâ€), ensure “Show†is checked for Receipt URL, Department, Location, and Class. +4. Go to _Customization > Forms > Transaction Forms_ and ensure that all other transaction forms with the vendor credit type have this same configuration. + + +# Step 11: Set up Tax Groups (only applicable if tracking taxes) + +{% include info.html %} +**Things to note about tax.** +Expensify imports NetSuite Tax Groups (not Tax Codes). To ensure Tax Groups can be applied to expenses go to _Setup > Accounting > Set Up Taxes_ and set the _Tax Code Lists Include_ preference to “Tax Groups And Tax Codes†or “Tax Groups Only.†If this field does not display, it’s not needed for that specific country. +Tax Groups are an alias for Tax Codes in NetSuite and can contain one or more Tax Codes (Please note: for UK and Ireland subsidiaries, please ensure your Tax Groups do not have more than one Tax Code). We recommend naming Tax Groups so your employees can easily understand them, as the name and rate will be displayed in Expensify. +{% include end-info.html %} + +1. Go to _Setup > Accounting > Tax Groups_. +2. Click **New**. +3. Select the country for your Tax Group. +4. Enter the Tax Name (this is what employees will see in Expensify.) +5. Select the subsidiary for this Tax Group. +6. Select the Tax Code from the table you wish to include in this Tax Group. +7. Click **Add**. +8. Click **Save**. +9. Create one NetSuite Tax Group for each tax rate you want to show in Expensify. + +# Step 12: Connect Expensify to NetSuite + +1. Click your profile image or icon in the bottom left menu. +2. Scroll down and click **Workspaces** in the left menu. +3. Select the workspace you want to connect to NetSuite. +4. Click **More features** in the left menu. +5. Scroll down to the Integrate section and enable the **Accounting** toggle. +6. Click **Accounting** in the left menu. +7. Click **Connect** next to NetSuite. +8. Click **Next** until you reach setup step 5 (If you followed the instructions above, then the first four setup steps will already be complete.) +9. On setup step 5, enter your NetSuite Account ID, Token ID, and Token Secret (the NetSuite Account ID can be found in NetSuite by going to Setup > Integration > Web Services Preferences_.) +10. Click **Confirm** to complete the setup. + + +![The New Expensify workspace setting is open and the More Features tab is selected and visible. The toggle to enable Accounting is highlighted with an orange call out and is currently in the grey disabled position.]({{site.url}}/assets/images/ExpensifyHelp-Xero-1.png) + +![The New Expensify workspace settings > More features tab is open with the toggle to enable Accounting enabled and green. The Accounting tab is now visible in the left-hand menu and is highlighted with an orange call out.]({{site.url}}/assets/images/ExpensifyHelp-Xero-2.png){:width="100%"} + +After completing the setup, the NetSuite connection will sync. It can take 1-2 minutes to sync with NetSuite. + +Once connected, all newly approved and paid reports exported from Expensify will be generated in NetSuite using SOAP Web Services (the term NetSuite employs when records are created through the integration). You can then move forward with [configuring the NetSuite settings](https://help.expensify.com/articles/new-expensify/connections/netsuite/Configure-Netsuite) in Expensify. + +{% include faq-begin.md %} + +## If I have a lot of customer and vendor data in NetSuite, how can I help ensure that importing them all is seamless? + +For importing your customers and vendors, make sure your page size is set to 1000 in NetSuite. + +Go to **Setup > Integration > Web Services Preferences** and search **Page Size** to determine your page size. + +{% include faq-end.md %} diff --git a/docs/articles/new-expensify/connections/netsuite/Connect-to-NetSuite.md b/docs/articles/new-expensify/connections/netsuite/Connect-to-NetSuite.md deleted file mode 100644 index 990217523743..000000000000 --- a/docs/articles/new-expensify/connections/netsuite/Connect-to-NetSuite.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: Connect to NetSuite -description: Integrate NetSuite with Expensify -order: 1 ---- - -{% include info.html %} -To use the NetSuite connection, you must have a NetSuite account and an Expensify Control plan. -{% include end-info.html %} - -Expensify’s integration with NetSuite supports syncing data between the two systems. Before you start connecting Expensify with NetSuite, there are a few things to note: - -- You must use NetSuite administrator credentials to initiate the connection. -- A Control Plan in Expensify is required to integrate with NetSuite. -- Employees don’t need NetSuite access or a NetSuite license to submit expense reports and sync them to NetSuite. -- Each NetSuite subsidiary must be connected to a separate Expensify workspace. -- The workspace currency in Expensify must match the NetSuite subsidiary's default currency. - -# Step 1: Install the Expensify Bundle in NetSuite - -While logged into NetSuite as an administrator, go to **Customization > SuiteBundler > Search & Install Bundles**, then search for “Expensifyâ€. -Click on the Expensify Connect bundle (Bundle ID 283395). -Click **Install**. -If you already have the Expensify Connect bundle installed, head to **Customization > SuiteBundler > Search & Install Bundles > List**, and update it to the latest version. -Select "Show on Existing Custom Forms" for all available fields. - -# Step 2: Enable Token-Based Authentication - -In NetSuite, go to **Setup > Company > Enable Features > SuiteCloud > Manage Authentication**. -Make sure “Token Based Authentication†is enabled. -Click **Save**. - -# Step 3: Add Expensify Integration Role to a User - -In NetSuite, head to **Lists > Employees**, and find the user to who you would like to add the Expensify Integration role. The user you select must at least have access to the permissions included in the Expensify Integration Role, and Admin access works too, but Admin access is not required. -Click **Edit > Access**, then find the Expensify Integration role in the dropdown and add it to the user. -Click **Save**. - - -{% include info.html %} -Remember that Tokens are linked to a **User** and a **Role**, not solely to a User. It’s important to note that you cannot establish a connection with tokens using one role and then switch to another role afterward. Once you’ve initiated a connection with tokens, you must continue using the same token/user/role combination for all subsequent sync or export actions. -{% include end-info.html %} - -# Step 4: Create Access Tokens - - -In NetSuite, enter “page: tokens†in the Global Search. -Click **New Access Token**. -Select Expensify as the application (this must be the original Expensify integration from the bundle). -Select the role Expensify Integration. -Click **Save**. -Copy and paste the token and token ID to a saved location on your computer (this is the only time you will see these details.) - - -# Step 5: Confirm Expense Reports are enabled in NetSuite - -{% include info.html %} -Expense Reports must be enabled in order to use Expensify’s integration with NetSuite. -{% include end-info.html %} - - -In NetSuite, go to **Setup > Company > Enable Features > Employees**. -Confirm the checkbox next to "Expense Reports" is checked. -If not, click the checkbox and then click **Save** to enable Expense Reports. - - -# Step 6: Confirm Expense Categories are set up in NetSuite - -{% include info.html %} -Once Expense Reports are enabled, Expense Categories can be set up in NetSuite. Expense Categories are synced to Expensify as Categories. Each Expense Category is an alias mapped to a General Ledger account so that employees can more easily categorize expenses. -{% include end-info.html %} - - -In NetSuite, go to **Setup > Accounting > Expense Categories** (a list of Expense Categories should show.) -If no Expense Categories are visible, click **New** to create new ones. - -# Step 7: Confirm Journal Entry Transaction Forms are Configured Properly - -In NetSuite, go to **Customization > Forms > Transaction Forms.** -Click **Customize** or **Edit** next to the Standard Journal Entry form. -Click **Screen Fields > Main**. Please verify the “Created From†label has “Show†checked and the "Display Type" is set to "Normal." -Click the sub-header **Lines** and verify that the “Show†column for “Receipt URL†is checked. -Go to **Customization > Forms > Transaction Forms** and ensure that all other transaction forms with the journal type have this same configuration. - - -# Step 8: Confirm Expense Report Transaction Forms are Configured Properly - - -In NetSuite, go to **Customization > Forms > Transaction Forms.** -Click **Customize** or **Edit** next to the Standard Expense Report form, then click **Screen Fields > Main.** -Verify the “Created From†label has “Show†checked and the "Display Type" is set to "Normal." -Click the second sub-header, **Expenses**, and verify that the "Show" column for "Receipt URL" is checked. -Go to **Customization > Forms > Transaction Forms** and ensure that all other transaction forms with the expense report type have this same configuration. - - -# Step 9: Confirm Vendor Bill Transactions Forms are Configured Properly - - -In NetSuite, go to **Customization > Forms > Transaction Forms.** -Click **Customize** or **Edit** next to your preferred Vendor Bill form. -Click **Screen Fields > Main** and verify that the “Created From†label has “Show†checked and that Departments, Classes, and Locations have the “Show†label unchecked. -Under the **Expenses** sub-header (make sure to click the “Expenses†sub-header at the very bottom and not “Expenses & Itemsâ€), ensure “Show†is checked for Receipt URL, Department, Location, and Class. -Go to **Customization > Forms > Transaction Forms** and ensure that all other transaction forms with the vendor bill type have this same configuration. - - -# Step 10: Confirm Vendor Credit Transactions Forms are Configured Properly - - -In NetSuite, go to **Customization > Forms > Transaction Forms**. -Click **Customize** or **Edit** next to your preferred Vendor Credit form, then click **Screen Fields > Main** and verify that the “Created From†label has “Show†checked and that Departments, Classes, and Locations have the “Show†label unchecked. -Under the **Expenses** sub-header (make sure to click the “Expenses†sub-header at the very bottom and not “Expenses & Itemsâ€), ensure “Show†is checked for Receipt URL, Department, Location, and Class. -Go to **Customization > Forms > Transaction Forms** and ensure that all other transaction forms with the vendor credit type have this same configuration. - - -# Step 11: Set up Tax Groups (only applicable if tracking taxes) - -{% include info.html %} -**Things to note about tax.** -Expensify imports NetSuite Tax Groups (not Tax Codes). To ensure Tax Groups can be applied to expenses go to **Setup > Accounting > Set Up Taxes** and set the _Tax Code Lists Include_ preference to “Tax Groups And Tax Codes†or “Tax Groups Only.†If this field does not display, it’s not needed for that specific country. -Tax Groups are an alias for Tax Codes in NetSuite and can contain one or more Tax Codes (Please note: for UK and Ireland subsidiaries, please ensure your Tax Groups do not have more than one Tax Code). We recommend naming Tax Groups so your employees can easily understand them, as the name and rate will be displayed in Expensify. -{% include end-info.html %} - -Go to **Setup > Accounting > Tax Groups**. -Click **New**. -Select the country for your Tax Group. -Enter the Tax Name (this is what employees will see in Expensify.) -Select the subsidiary for this Tax Group. -Select the Tax Code from the table you wish to include in this Tax Group. -Click **Add**. -Click **Save**. -Create one NetSuite Tax Group for each tax rate you want to show in Expensify. - -# Step 12: Connect Expensify to NetSuite - -Click your profile image or icon in the bottom left menu. -Scroll down and click **Workspaces** in the left menu. -Select the workspace you want to connect to NetSuite. -Click **More features** in the left menu. -Click **More features** in the left menu. -Scroll down to the Integrate section and enable the Accounting toggle. -Click **Accounting** in the left menu. -Click **Connect** next to NetSuite. -Click **Next** until you reach setup step 5 (If you followed the instructions above, then the first four setup steps will already be complete.) -On setup step 5, enter your NetSuite Account ID, Token ID, and Token Secret (the NetSuite Account ID can be found in NetSuite by going to **Setup > Integration > Web Services Preferences**.) -Click **Confirm** to complete the setup. - - -![The New Expensify workspace setting is open and the More Features tab is selected and visible. The toggle to enable Accounting is highlighted with an orange call out and is currently in the grey disabled position.]({{site.url}}/assets/images/ExpensifyHelp-Xero-1.png) - -![The New Expensify workspace settings > More features tab is open with the toggle to enable Accounting enabled and green. The Accounting tab is now visible in the left-hand menu and is highlighted with an orange call out.]({{site.url}}/assets/images/ExpensifyHelp-Xero-2.png){:width="100%"} - -After completing the setup, the NetSuite connection will sync. It can take 1-2 minutes to sync with NetSuite. - -Once connected, all newly approved and paid reports exported from Expensify will be generated in NetSuite using SOAP Web Services (the term NetSuite employs when records are created through the integration). - -{% include faq-begin.md %} - -## If I have a lot of customer and vendor data in NetSuite, how can I help ensure that importing them all is seamless? - -For importing your customers and vendors, make sure your page size is set to 1000 in NetSuite. - -Go to **Setup > Integration > Web Services Preferences** and search **Page Size** to determine your page size. - -{% 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 2157e05aa377..23c1bc58e5fc 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md +++ b/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md @@ -56,6 +56,10 @@ When an expense is submitted to a workspace, your approver will receive an email ![Click Scan]({{site.url}}/assets/images/ExpensifyHelp-CreateExpenseUpdate-3.png){:width="100%"} ![Enter workspace or individual's name]({{site.url}}/assets/images/ExpensifyHelp-CreateExpenseUpdate-4.png){:width="100%"} +{% include info.html %} +SmartScan can only detect and process text written in the Latin alphabet. +{% include end-info.html %} + {% include info.html %} 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 %} diff --git a/docs/articles/new-expensify/expenses-&-payments/Track-expenses.md b/docs/articles/new-expensify/expenses-&-payments/Track-expenses.md index 77256279b1d7..37d522673dcd 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Track-expenses.md +++ b/docs/articles/new-expensify/expenses-&-payments/Track-expenses.md @@ -1,45 +1,33 @@ --- title: Track Expenses -description: Create, store, or share non-reimbursable expenses +description: Easily learn how to track, manage, and take action on your expenses in Expensify with this step-by-step guide --- -
- -Create, store, or share non-reimbursable expenses with the Track Expenses feature. - -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click the + icon in the bottom left menu and select **Track Expense**. -2. Create the expense manually, scan the receipt, or add a distance expense. - -{% include info.html %} -For an in-depth walkthrough on how to create an expense, check out the [create an expense](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Create-an-expense) article. -{% include end-info.html %} - -3. Choose the next steps for the expense: - - **Submit it to someone**: Select this option to request payment from other members of your Expensify workspace. - - **Categorize it**: Select this option to choose a category and additional details to code the expense for a specific workspace. The expense will then be placed on a report and can be submitted to the workspace for approval. - - **Share it with my accountant**: Select this option to share the expense with your accountant. The expense will then be placed on a report under the workspace for your accountant to review. - - **Nothing for now**: Select this option to store the expense. Expensify will keep the expense until you are ready to take action on it—it won’t expire. When you’re ready, you can then select one of the above options for the expense at a later time. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap the + icon in the bottom menu and select **Track Expense**. -2. Create the expense manually, scan the receipt, or add a distance expense. - -{% include info.html %} -For an in-depth walkthrough on how to create an expense, check out the [create an expense](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Create-an-expense) article. -{% include end-info.html %} - -3. Choose the next steps for the expense: - - **Submit it to someone**: Select this option to request payment from a contact in your phone’s contact list or from other members of your Expensify workspace. - - **Categorize it**: Select this option to choose a category and additional details to code the expense for a specific workspace. The expense will then be placed on a report and can be submitted to the workspace for approval. - - **Share it with my accountant**: Select this option to share the expense with your accountant. The expense will then be placed on a report under the workspace for your accountant to review. - - **Nothing for now**: Select this option to store the expense. Expensify will keep the expense until you are ready to take action on it—it won’t expire. When you’re ready, you can then select one of the above options for the expense at a later time. -{% include end-option.html %} - -{% include end-selector.html %} - -![The New Expensify page is open with the FAB (big + button) clicked and the option to Track Expenses is highlighted.]({{site.url}}/assets/images/FAB_track_expense.png){:width="100%"} - -
+Tracking an expense in Expensify is simple and can be done in just a few steps. Follow the guide below to track an expense without submitting it. + +## Steps to Track an Expense + +1. Press the **big green + button** +2. Choose **Create Expense** +3. Select how you would like to create the expense: + - Scan a receipt + - Enter mileage + - Enter manually +4. Follow the prompts to enter the required information (this will vary depending on your selection in step 3) +5. Click **Next** +6. Choose **Just track it (don’t submit it)** +7. Enter or modify details in the fields shown (these fields will vary depending on how your Workspace is configured) +8. Click **Create Expense** + +## Where to Find Tracked Expenses + +Once the expense is created, it will appear in your **Workspace Chat** in the left-hand navigation. Within the workspace chat, you can take the following further actions on the expense at any time: + +- **Submit it to someone** – Send the expense for approval or processing. +- **Categorize it** – Assign the expense to a category for better organization. +- **Share it with my accountant** – Provide access to relevant financial contacts. +- **Nothing for now** – Leave the expense in your records for future action. + +You can also view your tracked expenses by doing the following: +1. Click on Reports +2. Click on Expenses +3. Use the filters to help you locate your expenses diff --git a/docs/articles/new-expensify/settings/Add-personal-information.md b/docs/articles/new-expensify/settings/Add-personal-information.md deleted file mode 100644 index 492d349357ec..000000000000 --- a/docs/articles/new-expensify/settings/Add-personal-information.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Add personal information -description: Add your legal name, DOB, and/or address for travel and payments ---- -
- -You can add private details to your Expensify account that are only visible to you, such as your legal name, date of birth, and/or address. This information is useful for booking travel and for payment purposes. - -To add or update your private account details, - -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click **Profile** in the left menu. -3. Scroll down to the Private details section and click the Legal Name, Date of Birth, and/or Address fields to update them. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon at the bottom of the screen. -2. Tap **Profile** in the left menu. -3. Scroll down to the Private details section and tap the Legal Name, Date of Birth, and/or Address fields to update them. -{% include end-option.html %} - -{% include end-selector.html %} - -
diff --git a/docs/articles/new-expensify/settings/Add-profile-photo.md b/docs/articles/new-expensify/settings/Add-profile-photo.md deleted file mode 100644 index 60e56deaafbc..000000000000 --- a/docs/articles/new-expensify/settings/Add-profile-photo.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Add profile photo -description: Add an image to your profile ---- -
- -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click the Edit pencil icon next to your profile image or icon and select **Upload Image** to choose a new image from your saved files. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon at the bottom of the screen. -2. Tap the Edit pencil icon next to your profile image or icon and select **Upload Image** to choose a new image from your saved files. -{% include end-option.html %} - -{% include end-selector.html %} - -
diff --git a/docs/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences.md b/docs/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences.md new file mode 100644 index 000000000000..6161b853cc26 --- /dev/null +++ b/docs/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences.md @@ -0,0 +1,134 @@ +--- +title: Manage Profile and Account Preferences +description: Learn how to update your profile settings, preferences, and notifications in Expensify. +--- + +Expensify allows users to customize their profile and preferences to enhance their experience. This guide covers how to update your profile photo, set your timezone, change your language, manage notification settings, update your pronouns, switch themes, share your status, and update your personal information. + +## Main Uses +- Customize your profile settings to match your preferences. +- Manage notifications and language settings for better communication. +- Adjust appearance settings for improved user experience. +- Provide personal details for travel and payment purposes. + +## Core Users +- Expensify members managing their profile. +- Admins assisting employees with profile settings. +- Users looking to personalize their account. + +## Key Advantages +- Easy-to-use profile customization features. +- Syncs settings across all Expensify platforms. +- Enhances communication, accessibility, and travel and payment processes. + +--- +# Profile Customization: What profile settings can I update? +Expensify allows you to personalize your account with the following options: + +- **Profile Photo** – Add or update your profile image. +- **Status** – Display a custom status message for your team. +- **Pronouns** – Choose pronouns to be displayed on your account. +- **Language** – Change your account language to Spanish or another supported language. +- **Timezone** – Set your correct timezone for accurate timestamps. +- **Theme** – Switch between light mode and dark mode, or match your device settings. +- **Notifications** – Manage email and in-app notification preferences. +- **Personal Information** – Add or update your legal name, date of birth, and address. + +--- +# How to Access and Update + +## Profile Photo: How do I add or update my profile photo? +To upload a new profile picture: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select the **Edit** (pencil) icon next to your profile image. +3. Choose **Upload Image** and select a new image from your files. + +## Timezone: How do I set my timezone? +To update your timezone settings: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select **Profile** from the left menu. +3. Click **Timezone** and select your preferred timezone. + +## Language: How do I change my account language? +To switch your account language to Spanish: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select **Preferences**. +3. Click the **Language** option and choose **Spanish**. + +## Notifications: How do I manage my notification settings? +To customize the notifications you receive: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select **Preferences**. +3. Adjust the toggles under **Notifications**: + - **Receive relevant feature updates and Expensify news**: Enable to receive emails and in-app notifications about new features. + - **Mute all sounds from Expensify**: Enable to silence all in-app notification sounds. + +## Status: How do I update my profile status? +To share your status with your team: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select **Profile**. +3. Click **Status** and enter a custom status message. +4. (Optional) Click the **emoji icon** to add an emoji. +5. Set an expiration time under **Clear After** (e.g., 30 minutes, 1 hour, etc.). +6. Click **Save**. + +## Pronouns: How do I update my pronouns? +To set your preferred pronouns: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select **Profile**. +3. Click **Pronouns** and choose from the available options. + +## Theme: How do I switch between light and dark mode? +To change Expensify’s appearance: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select **Preferences**. +3. Click **Theme** and choose: + - **Dark mode**: A dark background theme. + - **Light mode**: A light background theme. + - **Use Device Settings**: Matches your device’s default theme. + +## Personal Information: How do I add or update my personal information? +To update your legal name, date of birth, or address: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select **Profile** from the left menu. +3. Scroll to the **Private details** section and update the **Legal Name**, **Date of Birth**, and/or **Address** fields. + +## Name: How do I update my display or legal name? +To change your display or legal name: + +1. Press your **profile image or icon** in the bottom left menu. +2. Select **Profile** from the left menu. +3. Edit your name: + - **Display name**: Click **Display Name**, enter your preferred name, and click **Save**. + - **Legal name**: Scroll to the **Private Details** section, click **Legal Name**, update the fields, and click **Save**. + +--- +# FAQ + +## Profile: Why should I update my profile photo? +Updating your profile photo helps colleagues recognize you in chats and improves account personalization. + +## Language: Will changing my language setting affect my reports? +No, changing your language setting only updates the app’s interface and does not alter any report contents. + +## Notifications: Can I disable all Expensify notifications? +Yes, you can mute all sounds and opt out of feature updates by adjusting the notification preferences under **Preferences**. + +## Theme: Will my theme preference sync across devices? +Yes, your theme preference applies across all Expensify apps, including mobile, web, and desktop. + +## Status: Can I set a permanent status message? +Yes, after updating your status, choose **Never** under _**When should we clear your status?**_ + +## Personal Information: Why should I add my legal name and address? +Providing your legal name, date of birth, and address is useful for booking travel and securely receiving payments. + +--- diff --git a/docs/articles/new-expensify/settings/Preferences.md b/docs/articles/new-expensify/settings/Preferences.md deleted file mode 100644 index b94c9d35c1a1..000000000000 --- a/docs/articles/new-expensify/settings/Preferences.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Preferences -description: How to manage your Expensify Preferences ---- -# Overview -Your Preferences in Expensify allow you to customize how you use New Expensify. - -- Set your theme preference - -# How to set your theme preference in New Expensify - -To set or update your theme preference in New Expensify: -1. Go to **Settings > Preferences** -2. Tap on **Theme** -3. You can choose between the _Dark_ theme, the _Light_ theme, or _Use Device Settings_ - -_Use Device Settings_ is the default setting. - -Selecting _Use Device Settings_ will use your device's theme settings. For example, if your device is set to adjust the appearance from light to dark during the day, we'll match that. - -Your theme preference will sync across all your New Expensify apps (mobile, web, or OSX desktop apps). diff --git a/docs/articles/new-expensify/settings/Set-timezone.md b/docs/articles/new-expensify/settings/Set-timezone.md deleted file mode 100644 index 11ce1340c7bb..000000000000 --- a/docs/articles/new-expensify/settings/Set-timezone.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Set timezone -description: Set your timezone ---- -
- -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click **Profile** in the left menu. -3. Click **Timezone** to select your timezone. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon at the bottom of the screen. -2. Tap **Profile** in the left menu -3. Tap **Timezone** to select your timezone. -{% include end-option.html %} - -{% include end-selector.html %} - -
diff --git a/docs/articles/new-expensify/settings/Switch-account-language-to-Spanish.md b/docs/articles/new-expensify/settings/Switch-account-language-to-Spanish.md deleted file mode 100644 index a431d34fbc0f..000000000000 --- a/docs/articles/new-expensify/settings/Switch-account-language-to-Spanish.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Switch account language to Spanish -description: Change your account language ---- -
- -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click **Preferences** in the left menu. -3. Click the Language option and select **Spanish**. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon in the bottom menu. -2. Tap **Preferences**. -3. Tap the Language option and select **Spanish**. -{% include end-option.html %} - -{% include end-selector.html %} - -
diff --git a/docs/articles/new-expensify/settings/Switch-to-light-or-dark-mode.md b/docs/articles/new-expensify/settings/Switch-to-light-or-dark-mode.md deleted file mode 100644 index 34f96f9f5f7d..000000000000 --- a/docs/articles/new-expensify/settings/Switch-to-light-or-dark-mode.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: Switch to light or dark mode -description: Change the appearance of Expensify ---- -
- -Expensify has three theme options that determine how the app looks: -- **Dark mode**: The app appears with a dark background -- **Light mode**: The app appears with a light background -- **Use Device settings**: Expensify will automatically use your device’s default theme - -To change your Expensify theme, - -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click **Preferences** in the left menu. -3. Click the **Theme** option and select the desired theme. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon in the bottom menu. -2. Tap **Preferences**. -3. Tap the **Theme** option and select the desired theme. -{% include end-option.html %} - -{% include end-selector.html %} - -
diff --git a/docs/articles/new-expensify/settings/Update-Notification-Preferences.md b/docs/articles/new-expensify/settings/Update-Notification-Preferences.md deleted file mode 100644 index e4111b3d02d3..000000000000 --- a/docs/articles/new-expensify/settings/Update-Notification-Preferences.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Update notification preferences -description: Determine how you want to receive Expensify notifications ---- -
- -To customize the email and in-app notifications you receive from Expensify, - -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click **Preferences** in the left menu. -3. Enable or disable the toggles under Notifications: - - **Receive relevant feature updates and Expensify news**: If enabled, you will receive emails and in-app notifications from Expensify about new product and company updates. - - **Mute all sounds from Expensify**: If enabled, all in-app notification sounds will be silenced. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon in the bottom menu. -2. Tap **Preferences**. -3. Enable or disable the toggles under Notifications: - - **Receive relevant feature updates and Expensify news**: If enabled, you will receive emails and in-app notifications from Expensify about new product and company updates. - - **Mute all sounds from Expensify**: If enabled, all in-app notification sounds will be silenced. -{% include end-option.html %} - -{% include end-selector.html %} - -
diff --git a/docs/articles/new-expensify/settings/Update-your-name.md b/docs/articles/new-expensify/settings/Update-your-name.md deleted file mode 100644 index d6b65def12ac..000000000000 --- a/docs/articles/new-expensify/settings/Update-your-name.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Update your name -description: Update your display or legal name ---- -
- -Your Expensify account includes two names: -- Your display name that everyone can see (which can include a nickname) -- Your legal name that only you can see (for booking travel and for payment purposes) - -To update your display or legal name, - -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click **Profile** in the left menu. -3. Edit your name. - - **Display name**: Click **Display Name** and enter your first name (or nickname) and last name into the fields and click **Save**. This name will be visible to anyone in your company workspace. - - **Legal name**: Scroll down to the Private Details section and click **Legal name**. Then enter your legal first and last name and click **Save**. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon at the bottom of the screen. -2. Tap Profile in the left menu. -3. Edit your name. - - **Display name**: Tap **Display Name** and enter your first name (or nickname) and last name into the fields and tap **Save**. This name will be visible to anyone in your company workspace. - - **Legal name**: Scroll down to the Private Details section and tap **Legal name**. Then enter your legal first and last name and tap **Save**. -{% include end-option.html %} - -{% include end-selector.html %} - -
diff --git a/docs/articles/new-expensify/settings/Update-your-profile-status.md b/docs/articles/new-expensify/settings/Update-your-profile-status.md deleted file mode 100644 index 5e5130f69cd5..000000000000 --- a/docs/articles/new-expensify/settings/Update-your-profile-status.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Update your profile status -description: Share your status with your team ---- -
- -You can update your status in Expensify to let your coworkers know if you are out of the office, in a meeting, or even list your work hours or a different message. This message will appear when someone clicks on your profile or in a chat conversation. - -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click **Profile** in the left menu. -3. Click **Status**. -4. (Optional) Click the emoji icon to add an emoji. -5. Click the message field and enter a status. For example, out of office, in a meeting, at lunch, etc. -6. Click **Clear After** to select an expiration for the status. For example, if you select 30 minutes, the status will be automatically cleared after 30 minutes. -7. Click **Save**. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon at the bottom of the screen. -2. Tap **Profile** in the left menu. -3. Tap **Status**. -4. (Optional) Tap the emoji icon to add an emoji. -5. Tap the message field and enter a status. For example, out of office, in a meeting, at lunch, Office Hours: M-F 8-5 PT, etc. -6. Tap **Clear After** to select an expiration for the status. For example, if you select 30 minutes, the status will be automatically cleared after 30 minutes. -7. Tap **Save**. -{% include end-option.html %} - -{% include end-selector.html %} - -
- diff --git a/docs/articles/new-expensify/settings/Update-your-pronouns.md b/docs/articles/new-expensify/settings/Update-your-pronouns.md deleted file mode 100644 index bf0e902092ff..000000000000 --- a/docs/articles/new-expensify/settings/Update-your-pronouns.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Update your pronouns -description: Display your pronouns on your account ---- -
- -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Click **Profile** in the left menu. -3. Click **Pronouns** to select your pronouns. Type any letter into the field to see a list of available options. -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon at the bottom of the screen. -2. Tap **Profile** in the left menu. -3. Tap **Pronouns** to select your pronouns. Type any letter into the field to see a list of available options. -{% include end-option.html %} - -{% include end-selector.html %} - -
diff --git a/docs/articles/new-expensify/travel/Manage-Travel-Member-Roles.md b/docs/articles/new-expensify/travel/Manage-Travel-Member-Roles.md index c0a467bd220e..eab24598b162 100644 --- a/docs/articles/new-expensify/travel/Manage-Travel-Member-Roles.md +++ b/docs/articles/new-expensify/travel/Manage-Travel-Member-Roles.md @@ -12,16 +12,14 @@ To assign a role to a travel member, 2. Click **Book or manage travel**. 3. Click the **Program** tab at the top and select **Users**. 4. Click the name of the member whose role you wish to update. -5. Click the **Roles** tab and select a role. - - **Traveler**: Can only book travel for themselves. - - **Travel Arranger**: Can book travel for themselves and for other workspace members. Arrangers can be set to arrange travel for everyone in the workspace or for specific individuals only. - - **Company Admin**: Can book travel for themselves as well as any other workspace members. They can also access administrative features to: - - Define travel policies - - Add Users - - Remove Users - - Add and configure corporate cards as payment methods - - View analytics and metrics - - Use the Safety feature +5. Click the **Roles** tab and select a role. + - **Traveler**: Can only book travel for themselves. + - **Travel Arranger**: Can book travel for themselves and for other workspace members. Arrangers can be set to arrange travel for everyone in the workspace or for specific individuals only. + - **Company Admin**: Can book travel for themselves as well as any other workspace members. They can also access administrative features to: + - Define travel policies + - Add and configure corporate cards as payment methods + - View analytics and metrics + - Use the Safety feature 6. Click **Save**. diff --git a/docs/articles/new-expensify/travel/Track-Travel-Analytics.md b/docs/articles/new-expensify/travel/Track-Travel-Analytics.md new file mode 100644 index 000000000000..c50fa11c7a15 --- /dev/null +++ b/docs/articles/new-expensify/travel/Track-Travel-Analytics.md @@ -0,0 +1,87 @@ +--- +title: Track Travel Analytics +description: Get insight into company travel bookings to ensure real-time duty of care reporting and travel policy compliance. +--- +
+ +Expensify Travel provides insights into company travel bookings to ensure real-time duty of care reporting and travel policy compliance. These analytics help Workspace Admins: + +- See global employee locations with a real-time employee location map +- Analyze travel spend based on details such as trip, traveler, or carrier +- Monitor booking trends and adherence to travel policy compliance +- Generate environmental, social, and governance (ESG) reporting + +To view your analytics, + +1. Click Travel in the left menu. +2. Click **Book or Manage Travel**. +3. Click the **Analytics** tab at the top of the screen. + +From here, you can see a variety of reports, including the Duty of Care report, Spend, and ESG metrics. + +## Duty of Care report + +Duty of care is a legal obligation for employers to safeguard the health, safety, and well-being of their employees both in the office and during business trips. With Expensify’s Duty of Care analytics, you can view a global map showing real-time employee locations. + +1. Click the **Analytics** tab at the top and select Travelers. +2. Use the map to see employee locations. If desired, you can use the filters above the map to show past and future trips, or travel booked to specific locations. + +## Spend and compliance report + +Workspace Admins can analyze travel data based on a variety of trip, traveler, and compliance attributes. + +1. Click the **Analytics** tab at the top and select Company Reports. +2. Review the overview data, or select a specific report from the left menu. +3. Click the three dot menu on the right of the screen to download the report as a PDF. + +## ESG report + +Expensify Travel provides various ESG metrics, including carbon footprint analysis, sustainability scores, and ethical travel spending. + +1. Click the **Analytics** tab at the top and select Company Reports. +2. Click **Air Manifest** in the left menu. +3. Review the CO2 Emissions column in the table. + +
+ +
+ +Expensify Travel provides insights into company travel bookings to ensure real-time duty of care reporting and travel policy compliance. These analytics help Workspace Admins: + +- See global employee locations with a real-time employee location map +- Analyze travel spend based on details such as trip, traveler, or carrier +- Monitor booking trends and adherence to travel policy compliance +- Generate environmental, social, and governance (ESG) reporting + +To view your analytics, + +1. Click the + icon in the bottom left menu and select **Book travel**. +2. Click **Book Travel**. +3. Click the **Analytics** tab at the top of the screen. + +From here, you can see a variety of reports, including the Duty of Care report, Spend, and ESG metrics. + +## Duty of Care report + +Duty of care is a legal obligation for employers to safeguard the health, safety, and well-being of their employees both in the office and during business trips. With Expensify’s Duty of Care analytics, you can view a global map showing real-time employee locations. + +1. Click the **Analytics** tab at the top and select Travelers. +2. Use the map to see employee locations. If desired, you can use the filters above the map to show past and future trips, or travel booked to specific locations. + +## Spend and compliance report + +Workspace Admins can analyze travel data based on a variety of trip, traveler, and compliance attributes. + +1. Click the **Analytics** tab at the top and select Company Reports. +2. Review the overview data, or select a specific report from the left menu. +3. Click the three dot menu on the right of the screen to download the report as a PDF. + +## ESG report + +Expensify Travel provides various ESG metrics, including carbon footprint analysis, sustainability scores, and ethical travel spending. + +1. Click the **Analytics** tab at the top and select Company Reports. +2. Click **Air Manifest** in the left menu. +3. Review the CO2 Emissions column in the table. + +
diff --git a/docs/articles/new-expensify/workspaces/Managing-Workspace-Members.md b/docs/articles/new-expensify/workspaces/Managing-Workspace-Members.md new file mode 100644 index 000000000000..d3b07a2fe222 --- /dev/null +++ b/docs/articles/new-expensify/workspaces/Managing-Workspace-Members.md @@ -0,0 +1,98 @@ +--- +title: Managing Workspace Members in New Expensify +description: Learn how to add, remove, and manage user roles in a New Expensify Workspace. +--- + +# Managing Workspace Members in New Expensify + +Managing users in a New Expensify Workspace is essential for maintaining efficient expense tracking and ensuring the right individuals have appropriate access. This guide covers how to add, remove, and update user roles to streamline Workspace management. + +## Overview + +Managing users in a New Expensify Workspace allows administrators to control access, assign roles, and ensure the right individuals can submit and approve expenses efficiently. + +### Key Advantages + +- **Seamless user management** – Easily add, remove, or modify user roles. +- **Role-based permissions** – Assign different access levels to users for better control. +- **Efficient expense tracking** – Ensure only authorized users can interact with Workspace expenses. + +## What Is a Workspace User? + +A Workspace user is anyone added to a New Expensify Workspace. Their assigned role determines their permissions: + +- **Member** – Users who submit and/or approve expenses. +- **Admin** – Users responsible for managing users, roles, and Workspace settings. +- **Auditor** – Users who need to review and comment on expense reports. + +## How Do I Invite Users to a Workspace? + +To add users to your Workspace: + +1. From the **Workspace** view, press **Members** on the left. +2. Press **Invite Member**. +3. Use the search field to find the individual by name, email, or phone number. (You can select multiple people.) +4. Click **Next**. +5. *(Optional)* Click **Role** to change the user’s role type from the default (which is set to **Member**). +6. *(Optional)* Enter a custom message into the **Message** field. +7. Click **Invite**. + +**Note:** You can also invite members from the Workspace’s **Profile** page by pressing **Share** to share the Workspace’s URL or QR code. + +## How Do I Remove a User From a Workspace? + +### Method One - Removing Members Individually + +1. From the **Workspace** view, press **Members** on the left. +2. Click the member you want to remove. +3. Click **Remove from Workspace** on the right panel. +4. Confirm the removal by pressing **Remove** on the pop-up message. + +### Method Two - Removing Multiple Workspace Members at Once + +1. From the **Workspace** view, press **Members** on the left. +2. Select the checkbox next to each member you want to remove. +3. Click the **green dropdown arrow** on the top right. +4. Select **Remove Member/s**. +5. Confirm the removal by pressing **Remove** on the pop-up message. + +## How Do I Change a User's Role in a Workspace? + +To change an existing user’s role: + +1. From the **Workspace** view, press **Members** on the left. +2. Click the member whose role you want to update. +3. Click **Roles** on the right-hand panel. +4. Select the new role for the user. + +## How do I change the Workspace Owner? + +To change the ownership of a workspace: + +1. From the Workspace view, press Members on the left. +2. Click the member with the “Owner†tag next to their name. +3. Click Transfer Owner on the right-hand panel. +4. Click Continue. + +# FAQ + +## Why Can't I Invite a New User? + +If you are unable to invite a new user, check the following: + +- You must be an **Admin** to add new users. +- The email address entered must be valid and properly formatted. + +## Can I Invite Multiple Users at Once? + +Yes! To invite multiple users at once, you can: + +- Use the search field to select multiple people (covered in detail in the **How do I invite users to a Workspace?** section). +- Upload them via spreadsheet: + +1. From the **Workspace** view, press **Members** on the left. +2. Click the **three vertical dots** on the top right. +3. Select **Import via Spreadsheet**. +4. Drag and drop your spreadsheet into the right-hand panel or press **Upload File** to select your file for upload. + +**Note:** You can use the **[spreadsheet import template](https://docs.google.com/spreadsheets/d/19fjknN-KOS74RjXDccXZGUNTm-utdV7Gvveo5EyrJLE/edit?gid=0#gid=0)** for formatting. diff --git a/docs/assets/images/commfeed/commfeed-01-updated.png b/docs/assets/images/commfeed/commfeed-01-updated.png new file mode 100644 index 000000000000..347847089abe Binary files /dev/null and b/docs/assets/images/commfeed/commfeed-01-updated.png differ diff --git a/docs/assets/images/commfeed/commfeed-02-updated.png b/docs/assets/images/commfeed/commfeed-02-updated.png new file mode 100644 index 000000000000..00e30982d92d Binary files /dev/null and b/docs/assets/images/commfeed/commfeed-02-updated.png differ diff --git a/docs/assets/images/commfeed/commfeed-03-updated.png b/docs/assets/images/commfeed/commfeed-03-updated.png new file mode 100644 index 000000000000..172ae62da80c Binary files /dev/null and b/docs/assets/images/commfeed/commfeed-03-updated.png differ diff --git a/docs/assets/images/commfeed/commfeed-04-updated.png b/docs/assets/images/commfeed/commfeed-04-updated.png new file mode 100644 index 000000000000..6eb5daeed399 Binary files /dev/null and b/docs/assets/images/commfeed/commfeed-04-updated.png differ diff --git a/docs/assets/images/commfeed/commfeed-05-updated.png b/docs/assets/images/commfeed/commfeed-05-updated.png new file mode 100644 index 000000000000..a1831587d559 Binary files /dev/null and b/docs/assets/images/commfeed/commfeed-05-updated.png differ diff --git a/docs/assets/images/commfeed/commfeed-06-updated.png b/docs/assets/images/commfeed/commfeed-06-updated.png new file mode 100644 index 000000000000..918d6301eb6d Binary files /dev/null and b/docs/assets/images/commfeed/commfeed-06-updated.png differ diff --git a/docs/assets/images/commfeed/commfeed-07-updated.png b/docs/assets/images/commfeed/commfeed-07-updated.png new file mode 100644 index 000000000000..63aaae0b1620 Binary files /dev/null and b/docs/assets/images/commfeed/commfeed-07-updated.png differ diff --git a/docs/assets/images/perdiem_01.png b/docs/assets/images/perdiem_01.png new file mode 100644 index 000000000000..3d2f304ec997 Binary files /dev/null and b/docs/assets/images/perdiem_01.png differ diff --git a/docs/assets/images/perdiem_02.png b/docs/assets/images/perdiem_02.png new file mode 100644 index 000000000000..b58ad9002177 Binary files /dev/null and b/docs/assets/images/perdiem_02.png differ diff --git a/docs/assets/images/perdiem_03.png b/docs/assets/images/perdiem_03.png new file mode 100644 index 000000000000..88fef731a09c Binary files /dev/null and b/docs/assets/images/perdiem_03.png differ diff --git a/docs/assets/images/perdiem_04.png b/docs/assets/images/perdiem_04.png new file mode 100644 index 000000000000..c44dab235ab5 Binary files /dev/null and b/docs/assets/images/perdiem_04.png differ diff --git a/docs/assets/images/perdiem_05.png b/docs/assets/images/perdiem_05.png new file mode 100644 index 000000000000..823bf030e404 Binary files /dev/null and b/docs/assets/images/perdiem_05.png differ diff --git a/docs/redirects.csv b/docs/redirects.csv index 9ccef010ec96..f4970373fb7a 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -620,7 +620,20 @@ https://help.expensify.com/articles/expensify-classic/connect-credit-cards/compa https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Connect-Company-Cards/ https://help.expensify.com/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa,https://help.expensify.com/new-expensify/hubs/expensify-card/ https://help.expensify.com/articles/new-expensify/expensify-card/Dispute-Expensify-Card-transaction,https://help.expensify.com/articles/new-expensify/expensify-card/Disputing-Expensify-Card-Transactions +https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite,https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-To-NetSuite https://help.expensify.com/articles/expensify-classic/expensify-card/Request-the-Card,https://help.expensify.com/articles/expensify-classic/expensify-card/Request-the-Expensify-Card https://help.expensify.com/articles/expensify-classic/settings/Change-or-add-email-address,https://help.expensify.com/articles/expensify-classic/settings/Managing-Primary-and-Secondary-Logins-in-Expensify https://help.expensify.com/articles/expensify-classic/domains/SAML-SSO,https://help.expensify.com/articles/expensify-classic/domains/Managing-Single-Sign-On-(SSO)-in-Expensify https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Direct-Bank-Connections,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Connect-Company-Cards +https://help.expensify.com/articles/new-expensify/settings/Add-profile-photo,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Set-timezone,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Switch-account-language-to-Spanish,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Update-Notification-Preferences,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Update-your-profile-status,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Update-your-pronouns,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Preferences,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Switch-to-light-or-dark-mode,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Add-personal-information,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/new-expensify/settings/Update-your-name,https://help.expensify.com/articles/new-expensify/settings/Manage-Profile-and-Account-Preferences +https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Manage-Company-Cards +https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Manage-Company-Cards \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7efd6d5ebe1b..b9930ca92324 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -120,8 +120,7 @@ platform :android do gradle( project_dir: 'Mobile-Expensify/Android', task: 'assemble', - flavor: 'Production', - build_type: 'Release', + build_type: 'Debug', ) setGradleOutputsInEnv() end @@ -197,17 +196,6 @@ platform :android do ) end - desc "Upload app to Firebase distribution" - lane :upload_firebase_distribution do - firebase_app_distribution( - app: "1:1008697809946:android:2e48f9ffe8d0b6a2", - service_credentials_file: "./firebase.json", - groups: "applause", - android_artifact_path: ENV[KEY_GRADLE_AAB_PATH], - android_artifact_type: "AAB" - ) - end - desc "Upload HybridApp to Google Play for internal testing" lane :upload_google_play_internal_hybrid do # Google is very unreliable, so we retry a few times @@ -344,7 +332,7 @@ def setupIOSSigningCertificate() ) import_certificate( - certificate_path: "./ios/Certificates.p12", + certificate_path: "./Certificates.p12", keychain_name: "ios-build.keychain", keychain_password: keychain_password ) @@ -358,11 +346,11 @@ platform :ios do setupIOSSigningCertificate() install_provisioning_profile( - path: "./ios/NewApp_AppStore.mobileprovision" + path: "./NewApp_AppStore.mobileprovision" ) install_provisioning_profile( - path: "./ios/NewApp_AppStore_Notification_Service.mobileprovision" + path: "./NewApp_AppStore_Notification_Service.mobileprovision" ) build_app( @@ -458,7 +446,12 @@ platform :ios do ENV["ENVFILE"]=".env.production" build_app( workspace: "./ios/NewExpensify.xcworkspace", - scheme: "New Expensify" + scheme: "New Expensify", + configuration: "Debug", + sdk: "iphonesimulator", + skip_codesigning: true, + skip_archive: true, + export_method: "development" ) setIOSBuildOutputsInEnv() end @@ -468,7 +461,12 @@ platform :ios do ENV["ENVFILE"]="./Mobile-Expensify/.env.production.hybridapp.ios" build_app( workspace: "./Mobile-Expensify/iOS/Expensify.xcworkspace", - scheme: "Expensify" + scheme: "Expensify", + configuration: "Debug", + sdk: "iphonesimulator", + skip_codesigning: true, + skip_archive: true, + export_method: "development" ) setIOSBuildOutputsInEnv() end @@ -480,11 +478,11 @@ platform :ios do setupIOSSigningCertificate() install_provisioning_profile( - path: "./ios/NewApp_AdHoc.mobileprovision" + path: "./NewApp_AdHoc.mobileprovision" ) install_provisioning_profile( - path: "./ios/NewApp_AdHoc_Notification_Service.mobileprovision" + path: "./NewApp_AdHoc_Notification_Service.mobileprovision" ) build_app( @@ -518,21 +516,11 @@ platform :ios do sh("echo '{\"ipa_path\": \"#{lane_context[SharedValues::S3_IPA_OUTPUT_PATH]}\",\"html_path\": \"#{lane_context[SharedValues::S3_HTML_OUTPUT_PATH]}\"}' > ../ios_paths.json") end - desc "Upload app to Firebase distribution" - lane :upload_firebase_distribution do - firebase_app_distribution( - app: "1:1008697809946:ios:3ffad71f664f2886", - service_credentials_file: "./firebase.json", - groups: "applause", - ipa_path: ENV[KEY_IPA_PATH], - ) - end - desc "Upload app to TestFlight" lane :upload_testflight do upload_to_testflight( app_identifier: "com.chat.expensify.chat", - api_key_path: "./ios/ios-fastlane-json-key.json", + api_key_path: "./ios-fastlane-json-key.json", distribute_external: true, notify_external_testers: true, changelog: "Thank you for beta testing New Expensify, this version includes bug fixes and improvements.", @@ -566,7 +554,7 @@ platform :ios do lane :upload_testflight_hybrid do upload_to_testflight( app_identifier: "com.expensify.expensifylite", - api_key_path: "./ios/ios-fastlane-json-key.json", + api_key_path: "./ios-fastlane-json-key.json", distribute_external: true, notify_external_testers: true, reject_build_waiting_for_review: true, @@ -602,7 +590,7 @@ platform :ios do lane :submit_for_review do deliver( app_identifier: "com.chat.expensify.chat", - api_key_path: "./ios/ios-fastlane-json-key.json", + api_key_path: "./ios-fastlane-json-key.json", # Skip HTMl report verification force: true, @@ -686,7 +674,7 @@ platform :ios do lane :submit_hybrid_for_rollout do deliver( app_identifier: "com.expensify.expensifylite", - api_key_path: "./ios/ios-fastlane-json-key.json", + api_key_path: "./ios-fastlane-json-key.json", # Skip HTML report verification force: true, diff --git a/ios/Certificates.p12.gpg b/ios/Certificates.p12.gpg deleted file mode 100644 index 91f827416367..000000000000 Binary files a/ios/Certificates.p12.gpg and /dev/null differ diff --git a/ios/NewApp_AdHoc.mobileprovision.gpg b/ios/NewApp_AdHoc.mobileprovision.gpg deleted file mode 100644 index 567a867981e6..000000000000 Binary files a/ios/NewApp_AdHoc.mobileprovision.gpg and /dev/null differ diff --git a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg deleted file mode 100644 index 6437d0a3f096..000000000000 Binary files a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg and /dev/null differ diff --git a/ios/NewApp_AppStore.mobileprovision.gpg b/ios/NewApp_AppStore.mobileprovision.gpg deleted file mode 100644 index 22624c6f41d6..000000000000 Binary files a/ios/NewApp_AppStore.mobileprovision.gpg and /dev/null differ diff --git a/ios/NewApp_AppStore_Notification_Service.mobileprovision.gpg b/ios/NewApp_AppStore_Notification_Service.mobileprovision.gpg deleted file mode 100644 index 503a096f1726..000000000000 Binary files a/ios/NewApp_AppStore_Notification_Service.mobileprovision.gpg and /dev/null differ diff --git a/ios/NewApp_Development.mobileprovision.gpg b/ios/NewApp_Development.mobileprovision.gpg deleted file mode 100644 index 34f034752b7f..000000000000 Binary files a/ios/NewApp_Development.mobileprovision.gpg and /dev/null differ diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index c8d825589bfb..14866da313d5 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 0DFC45952C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */; }; 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */; }; 0F5E5351263B73FD004CA14F /* EnvironmentChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */; }; + 0F749C2B3B8F4562B816DEAB /* ExpensifyMono-Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = ED64768FC6254E4D8FCD12BC /* ExpensifyMono-Italic.otf */; }; 1246A3EF20E54E7A9494C8B9 /* ExpensifyNeue-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 18D050E0262400AF000D658B /* BridgingFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D050DF262400AF000D658B /* BridgingFile.swift */; }; @@ -28,6 +29,7 @@ 30581EA8AAFD4FCE88C5D191 /* ExpensifyNeue-Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = BF6A4C5167244B9FB8E4D4E3 /* ExpensifyNeue-Italic.otf */; }; 374FB8D728A133FE000D84EF /* OriginImageRequestHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */; }; 383643682B6D4AE2005BB9AE /* DeviceCheck.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 383643672B6D4AE2005BB9AE /* DeviceCheck.framework */; }; + 524F95D57E75496EBD14B0AA /* ExpensifyMono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A96F65C6624044318D21DAB1 /* ExpensifyMono-BoldItalic.otf */; }; 7041848526A8E47D00E09F4D /* RCTStartupTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */; }; 7041848626A8E47D00E09F4D /* RCTStartupTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */; }; 70CF6E82262E297300711ADC /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70CF6E81262E297300711ADC /* BootSplash.storyboard */; }; @@ -142,6 +144,7 @@ 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyNeue-BoldItalic.otf"; sourceTree = ""; }; 8EFE0319D586C1078DB926FD /* Pods-NewExpensify.releaseadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.releaseadhoc.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.releaseadhoc.xcconfig"; sourceTree = ""; }; 9196A72C11B91A52A43D6E8A /* libPods-NotificationServiceExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NotificationServiceExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A96F65C6624044318D21DAB1 /* ExpensifyMono-BoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyMono-BoldItalic.otf"; sourceTree = ""; }; AC131FBA2CF634F20010CE80 /* BackgroundTasks.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BackgroundTasks.framework; path = System/Library/Frameworks/BackgroundTasks.framework; sourceTree = SDKROOT; }; BBE493797E97F2995E627244 /* Pods-NotificationServiceExtension.debugadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debugadhoc.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debugadhoc.xcconfig"; sourceTree = ""; }; BCD444BEDDB0AF1745B39049 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-NewExpensify-NewExpensifyTests/ExpoModulesProvider.swift"; sourceTree = ""; }; @@ -159,6 +162,7 @@ E9DF872C2525201700607FDC /* AirshipConfig.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = AirshipConfig.plist; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; + ED64768FC6254E4D8FCD12BC /* ExpensifyMono-Italic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Italic.otf"; path = "../assets/fonts/native/ExpensifyMono-Italic.otf"; sourceTree = ""; }; EF33B19FC6A7FE676839430D /* libPods-NewExpensify-NewExpensifyTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NewExpensify-NewExpensifyTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; EFA5CA89CC675CA3370CF89E /* Pods-NewExpensify.debugproduction.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debugproduction.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debugproduction.xcconfig"; sourceTree = ""; }; F0C450E92705020500FD2970 /* colors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = colors.json; path = ../colors.json; sourceTree = ""; }; @@ -345,6 +349,8 @@ 44BF435285B94E5B95F90994 /* ExpensifyNewKansas-Medium.otf */, D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */, DCF33E34FFEC48128CDD41D4 /* ExpensifyMono-Bold.otf */, + A96F65C6624044318D21DAB1 /* ExpensifyMono-BoldItalic.otf */, + ED64768FC6254E4D8FCD12BC /* ExpensifyMono-Italic.otf */, E704648954784DDFBAADF568 /* ExpensifyMono-Regular.otf */, 52796131E6554494B2DDB056 /* ExpensifyNeue-Bold.otf */, 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */, @@ -532,6 +538,8 @@ 30581EA8AAFD4FCE88C5D191 /* ExpensifyNeue-Italic.otf in Resources */, 1246A3EF20E54E7A9494C8B9 /* ExpensifyNeue-Regular.otf in Resources */, D27CE6B77196EF3EF450EEAC /* PrivacyInfo.xcprivacy in Resources */, + 524F95D57E75496EBD14B0AA /* ExpensifyMono-BoldItalic.otf in Resources */, + 0F749C2B3B8F4562B816DEAB /* ExpensifyMono-Italic.otf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 6725c5258a8f..adbebe3553d4 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -23,7 +23,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.90 + 9.0.95 CFBundleSignature ???? CFBundleURLTypes @@ -44,7 +44,7 @@ CFBundleVersion - 9.0.90.5 + 9.0.95.0 FullStory OrgId @@ -92,6 +92,8 @@ ExpensifyNewKansas-Medium.otf ExpensifyNewKansas-MediumItalic.otf ExpensifyMono-Bold.otf + ExpensifyMono-BoldItalic.otf + ExpensifyMono-Italic.otf ExpensifyMono-Regular.otf ExpensifyNeue-Bold.otf ExpensifyNeue-BoldItalic.otf diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 5fceec77c360..275d21039a11 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.90 + 9.0.95 CFBundleSignature ???? CFBundleVersion - 9.0.90.5 + 9.0.95.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 46e8942c5f90..9bebb3c1868e 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.90 + 9.0.95 CFBundleVersion - 9.0.90.5 + 9.0.95.0 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b0e84d3d033f..a7347a4c5097 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -166,6 +166,7 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - fmt (9.1.0) + - ForkInputMask (7.3.3) - FullStory (1.52.0) - fullstory_react-native (1.7.2): - DoubleConversion @@ -1604,6 +1605,28 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-advanced-input-mask (1.2.1): + - DoubleConversion + - ForkInputMask (~> 7.3.2) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-airship (19.2.1): - AirshipFrameworkProxy (= 7.1.2) - DoubleConversion @@ -2491,7 +2514,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.221): + - RNLiveMarkdown (0.1.230): - DoubleConversion - glog - hermes-engine @@ -2511,10 +2534,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.221) + - RNLiveMarkdown/newarch (= 0.1.230) - RNReanimated/worklets - Yoga - - RNLiveMarkdown/newarch (0.1.221): + - RNLiveMarkdown/newarch (0.1.230): - DoubleConversion - glog - hermes-engine @@ -2880,6 +2903,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-advanced-input-mask (from `../node_modules/react-native-advanced-input-mask`) - "react-native-airship (from `../node_modules/@ua/react-native-airship`)" - react-native-app-logs (from `../node_modules/react-native-app-logs`) - react-native-blob-util (from `../node_modules/react-native-blob-util`) @@ -2968,6 +2992,7 @@ SPEC REPOS: - FirebaseInstallations - FirebasePerformance - FirebaseRemoteConfig + - ForkInputMask - GoogleAppMeasurement - GoogleDataTransport - GoogleSignIn @@ -3093,6 +3118,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-advanced-input-mask: + :path: "../node_modules/react-native-advanced-input-mask" react-native-airship: :path: "../node_modules/@ua/react-native-airship" react-native-app-logs: @@ -3270,6 +3297,7 @@ SPEC CHECKSUMS: FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be + ForkInputMask: 55e3fbab504b22da98483e9f9a6514b98fdd2f3c FullStory: c8a10b2358c0d33c57be84d16e4c440b0434b33d fullstory_react-native: 63a803cca04b0447a71daa73e4df3f7b56e1919d glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a @@ -3323,6 +3351,7 @@ SPEC CHECKSUMS: React-logger: 26155dc23db5c9038794db915f80bd2044512c2e React-Mapbuffer: ad1ba0205205a16dbff11b8ade6d1b3959451658 React-microtasksnativemodule: e771eb9eb6ace5884ee40a293a0e14a9d7a4343c + react-native-advanced-input-mask: 22e3bd2a0f38fada50b475c98bf39d39053097a3 react-native-airship: e10f6823d8da49bbcb2db4bdb16ff954188afccc react-native-app-logs: ee32b6e80bf8d1b883dfc5ac96efa7c1bd9a06a5 react-native-blob-util: 221c61c98ae507b758472ac4d2d489119d1a6c44 @@ -3383,7 +3412,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 364e6862a112045bb5c5d35601f0bdb0304af979 RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: 9940212ca19bf54101b585178e691ee040b82c35 + RNLiveMarkdown: 3887f27df3e002a4d008488df0ef62d7bcc526eb RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: d184c8d3213acf4c97ec71fbbb6f9d4954552d80 RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28 diff --git a/ios/ios-fastlane-json-key.json.gpg b/ios/ios-fastlane-json-key.json.gpg deleted file mode 100644 index 06d2109da080..000000000000 --- a/ios/ios-fastlane-json-key.json.gpg +++ /dev/null @@ -1,2 +0,0 @@ -Œ  H46 )‰ÕëÒÀ¯€E$é¨R `L¤IuÌ0Ø„ç¹<;ä\ôÏÕ F•I™ßιíú¾{[›·‡ 3¢üñEįL?ʼëö-VŒÿ¢6vÉW¶6Œ}뾆Àc¢kýž¶ö†)×â>Oå¥#ôƒý#E:AÖAîÝx|ÄßüFQeŽäõ"6õQ Єâ 7ØôY¿Q”• µöö´Ó+Ãp³cöáÃ8~÷’‰ó—麜Ǔb‚D‡Y›·žRA¶ŒœÀ!ˆ\T åí`¢Èñàá¶-ÊØyGþy>I»G‰ÞêÿñÕU ¹Ö e·¶¡jYC¤<¿î H}„—ÿ?µ9J`Î2 ½T -‹ýè,ža¸½á> "J(,«}ž€v;ÝF©>ià„9›Ó£ó©êŠüìÑ aQŸÒÛr˜Nð¨;m¼M­ÝùMŠ_ýD3LÍ–êsVDa1nä™h½£®Æ9¡ÉXœ†º9 \ No newline at end of file diff --git a/ios/link-assets-manifest.json b/ios/link-assets-manifest.json index 1c59d667fca1..9cf517a7fab4 100644 --- a/ios/link-assets-manifest.json +++ b/ios/link-assets-manifest.json @@ -5,6 +5,14 @@ "path": "assets/fonts/native/ExpensifyMono-Bold.otf", "sha1": "d70e12540200613e9e6ac9068bed57e4bf477bfe" }, + { + "path": "assets/fonts/native/ExpensifyMono-BoldItalic.otf", + "sha1": "ef9f92ba902942e232301c64bb55e79165435bcc" + }, + { + "path": "assets/fonts/native/ExpensifyMono-Italic.otf", + "sha1": "874301891972b3a2a6a8ece69b978e74ac9d10a2" + }, { "path": "assets/fonts/native/ExpensifyMono-Regular.otf", "sha1": "9bbd3795afea1b1136c5b6a8ecd7d470fd5ea1b2" diff --git a/jest/setup.ts b/jest/setup.ts index de616c752b15..1e4dff5a9199 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -124,3 +124,23 @@ jest.mock( cancel() {} }, ); + +jest.mock('@libs/prepareRequestPayload/index.native.ts', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + __esModule: true, + default: jest.fn((command: string, data: Record) => { + const formData = new FormData(); + + Object.keys(data).forEach((key) => { + const value = data[key]; + + if (value === undefined) { + return; + } + + formData.append(key, value as string | Blob); + }); + + return Promise.resolve(formData); + }), +})); diff --git a/package-lock.json b/package-lock.json index 1bdbb454694f..79c088f27400 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "new.expensify", - "version": "9.0.90-5", + "version": "9.0.95-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.90-5", + "version": "9.0.95-0", "hasInstallScript": true, "license": "MIT", "dependencies": { "@dotlottie/react-player": "^1.6.3", "@expensify/react-native-background-task": "file:./modules/background-task", - "@expensify/react-native-live-markdown": "0.1.221", + "@expensify/react-native-live-markdown": "0.1.230", "@expo/metro-runtime": "^4.0.0", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -79,6 +79,7 @@ "react-fast-pdf": "^1.0.22", "react-map-gl": "^7.1.3", "react-native": "0.76.3", + "react-native-advanced-input-mask": "1.2.1", "react-native-android-location-enabler": "^2.0.1", "react-native-app-logs": "0.3.1", "react-native-blob-util": "0.19.4", @@ -98,7 +99,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.89", + "react-native-onyx": "2.0.92", "react-native-pager-view": "6.5.1", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -3641,9 +3642,9 @@ "link": true }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.221", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.221.tgz", - "integrity": "sha512-2CeBE1LsNvslaqYmPlf1hsl5gqG3eMsn+7jUSAZ4YmQqz1iLKJn+ryQVE4Rl0eLeeikWDlKvqX9isQHgKofLgw==", + "version": "0.1.230", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.230.tgz", + "integrity": "sha512-1b+sVJRvHPpnSFhGtS4IHD03vzMEz79NUyokVd+coWNnLOgb4/3ZlC9U58Dymc9+/eqq191lhALSDJMTm2DWow==", "license": "MIT", "workspaces": [ "./example", @@ -3653,7 +3654,7 @@ "node": ">= 18.0.0" }, "peerDependencies": { - "expensify-common": ">=2.0.108", + "expensify-common": ">=2.0.115", "react": "*", "react-native": "*", "react-native-reanimated": ">=3.16.4" @@ -32027,6 +32028,23 @@ } } }, + "node_modules/react-native-advanced-input-mask": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-advanced-input-mask/-/react-native-advanced-input-mask-1.2.1.tgz", + "integrity": "sha512-qXK6l8f5zOLrWxhrtA2od4R2UsV8OEcvFlZlX5VTp3sB/JlHW/iJd15m8Rgn/mcJFfvnKlHmVVHJefDrUOJFvA==", + "license": "MIT", + "workspaces": [ + "example", + "WebExample" + ], + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-android-location-enabler": { "version": "2.0.1", "license": "MIT", @@ -32318,9 +32336,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.89", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.89.tgz", - "integrity": "sha512-JzXjas0UNnYqTH4XD2Qfs64kBJBvHQ7HIIglieL1+Gto7eANyFRUpr0uRM3BlONinSPD/1xWZIurYAJtHuM5dg==", + "version": "2.0.92", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.92.tgz", + "integrity": "sha512-6StFOp3j4DC3gsY5Cl1qcbZ8mXL1RUMyzDf4l4im/4QlF6+bSpOHdYDZZjrUddbO/i1PA5ktUnAK4NM/JQ+BZg==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index 0adad02eb41d..b79a9e8318ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.90-5", + "version": "9.0.95-0", "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.", @@ -39,7 +39,9 @@ "detectRedirectCycle": "ts-node .github/scripts/detectRedirectCycle.ts", "desktop-build-adhoc": "./scripts/build-desktop.sh adhoc", "ios-build": "bundle exec fastlane ios build_unsigned", + "ios-hybrid-build": "bundle exec fastlane ios build_unsigned_hybrid", "android-build": "bundle exec fastlane android build_local", + "android-hybrid-build": "bundle exec fastlane android build_local_hybrid", "test": "TZ=utc NODE_OPTIONS=--experimental-vm-modules jest", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", @@ -71,13 +73,13 @@ "react-compiler-healthcheck": "react-compiler-healthcheck --verbose", "react-compiler-healthcheck-test": "react-compiler-healthcheck --verbose &> react-compiler-output.txt", "generate-search-parser": "peggy --format es -o src/libs/SearchParser/searchParser.js src/libs/SearchParser/searchParser.peggy src/libs/SearchParser/baseRules.peggy", - "generate-autocomplete-parser": "peggy --format es -o src/libs/SearchParser/autocompleteParser.js src/libs/SearchParser/autocompleteParser.peggy src/libs/SearchParser/baseRules.peggy", + "generate-autocomplete-parser": "peggy --format es -o src/libs/SearchParser/autocompleteParser.js src/libs/SearchParser/autocompleteParser.peggy src/libs/SearchParser/baseRules.peggy && ./scripts/parser-workletization.sh src/libs/SearchParser/autocompleteParser.js", "web:prod": "http-server ./dist --cors" }, "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.221", "@expensify/react-native-background-task": "file:./modules/background-task", + "@expensify/react-native-live-markdown": "0.1.230", "@expo/metro-runtime": "^4.0.0", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -144,6 +146,7 @@ "react-fast-pdf": "^1.0.22", "react-map-gl": "^7.1.3", "react-native": "0.76.3", + "react-native-advanced-input-mask": "1.2.1", "react-native-android-location-enabler": "^2.0.1", "react-native-app-logs": "0.3.1", "react-native-blob-util": "0.19.4", @@ -163,7 +166,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.89", + "react-native-onyx": "2.0.92", "react-native-pager-view": "6.5.1", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", diff --git a/scripts/parser-workletization.sh b/scripts/parser-workletization.sh new file mode 100755 index 000000000000..ab048e407de3 --- /dev/null +++ b/scripts/parser-workletization.sh @@ -0,0 +1,22 @@ +#!/bin/bash +### +# This script modifies the autocompleteParser.js file to be compatible with worklets. +# autocompleteParser.js is generated by PeggyJS and uses some parts of syntax not supported by worklets. +# This script runs each time the parser is generated by the `generate-autocomplete-parser` command. +### + +filePath=$1 + +if [ ! -f "$filePath" ]; then + echo "$filePath does not exist." + exit 1 +fi +# shellcheck disable=SC2016 +if awk 'BEGIN { print "\47worklet\47\n\nclass peg\$SyntaxError{}" } 1' "$filePath" | sed 's/function peg\$SyntaxError/function temporary/g' | sed 's/peg$subclass(peg$SyntaxError, Error);//g' > tmp.txt; then + mv tmp.txt "$filePath" + echo "Successfully updated $filePath" +else + echo "An error occurred while modifying the file." + rm -f tmp.txt + exit 1 +fi diff --git a/scripts/run-build.sh b/scripts/run-build.sh index 70e0dcf7c586..0abbd4530adf 100755 --- a/scripts/run-build.sh +++ b/scripts/run-build.sh @@ -38,6 +38,9 @@ NEW_DOT_FLAG="${STANDALONE_NEW_DOT:-false}" SCHEME="Expensify Dev" APP_ID="org.me.mobiexpensifyg.dev" + # Build Yapl JS + cd Mobile-Expensify && npm run grunt:build:shared && cd .. + echo -e "\n${GREEN}Starting a HybridApp build!${NC}" PROJECT_ROOT_PATH="Mobile-Expensify/" export CUSTOM_APK_NAME="Expensify-debug.apk" diff --git a/scripts/shellCheck.sh b/scripts/shellCheck.sh index d148958900d4..4d8155eeca88 100755 --- a/scripts/shellCheck.sh +++ b/scripts/shellCheck.sh @@ -9,9 +9,11 @@ source scripts/shellUtils.sh declare -r DIRECTORIES_TO_IGNORE=( './node_modules' + './desktop/node_modules' './vendor' './ios/Pods' './.husky' + './docs/vendor' ) # This lists all shell scripts in this repo except those in directories we want to ignore diff --git a/src/App.tsx b/src/App.tsx index f9403e258af1..3513cb23953b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,8 +43,9 @@ import type {Route} from './ROUTES'; import './setup/backgroundTask'; import {SplashScreenStateContextProvider} from './SplashScreenStateContext'; +/** Values passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */ type AppProps = { - /** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */ + /** URL containing all necessary data to run React Native app (e.g. login data) */ url?: Route; }; diff --git a/src/CONST.ts b/src/CONST.ts index fad349c53276..285d3ed2e13d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ import {add as dateAdd} from 'date-fns'; import {sub as dateSubtract} from 'date-fns/sub'; +// eslint-disable-next-line lodash/import-scope +import type {Dictionary} from 'lodash'; +import invertBy from 'lodash/invertBy'; import Config from 'react-native-config'; import * as KeyCommand from 'react-native-key-command'; import type {ValueOf} from 'type-fest'; @@ -16,6 +19,8 @@ import type PlaidBankAccount from './types/onyx/PlaidBankAccount'; const EMPTY_ARRAY = Object.freeze([]); const EMPTY_OBJECT = Object.freeze({}); +const DEFAULT_NUMBER_ID = 0; + const CLOUDFRONT_DOMAIN = 'cloudfront.net'; const CLOUDFRONT_URL = `https://d2k5nsl2zxldvw.${CLOUDFRONT_DOMAIN}`; const ACTIVE_EXPENSIFY_URL = addTrailingForwardSlash(Config?.NEW_EXPENSIFY_URL ?? 'https://new.expensify.com'); @@ -75,6 +80,7 @@ const selectableOnboardingChoices = { const backendOnboardingChoices = { ADMIN: 'newDotAdmin', SUBMIT: 'newDotSubmit', + TRACK_WORKSPACE: 'newDotTrackWorkspace', } as const; const onboardingChoices = { @@ -101,15 +107,52 @@ const selfGuidedTourTask: OnboardingTask = { description: ({navatticURL}) => `[Take a self-guided product tour](${navatticURL}) and learn about everything Expensify has to offer.`, }; +const createWorkspaceTask: OnboardingTask = { + type: 'createWorkspace', + autoCompleted: true, + title: 'Create a workspace', + description: ({workspaceSettingsLink}) => + '*Create a workspace* to track expenses, scan receipts, chat, and more.\n' + + '\n' + + 'Here’s how to create a workspace:\n' + + '\n' + + '1. Click *Settings*.\n' + + '2. Click *Workspaces* > *New workspace*.\n' + + '\n' + + `*Your new workspace is ready!* [Check it out](${workspaceSettingsLink}).`, +}; + +const meetGuideTask: OnboardingTask = { + type: 'meetGuide', + autoCompleted: false, + title: 'Meet your setup specialist', + description: ({adminsRoomLink}) => + `Meet your setup specialist, who can answer any questions as you get started with Expensify. Yes, a real human!\n` + + '\n' + + `Chat with the specialist in your [#admins room](${adminsRoomLink}).`, +}; + +const setupCategoriesTask: OnboardingTask = { + type: 'setupCategories', + autoCompleted: false, + title: 'Set up categories', + description: ({workspaceCategoriesLink}) => + '*Set up categories* so your team can code expenses for easy reporting.\n' + + '\n' + + 'Here’s how to set up categories:\n' + + '\n' + + '1. Click *Settings*.\n' + + '2. Go to *Workspaces*.\n' + + '3. Select your workspace.\n' + + '4. Click *Categories*.\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}).`, +}; + const onboardingEmployerOrSubmitMessage: OnboardingMessage = { message: 'Getting paid back is as easy as sending a message. Let’s go over the basics.', - video: { - url: `${CLOUDFRONT_URL}/videos/guided-setup-get-paid-back-v3.mp4`, - thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-get-paid-back.jpg`, - duration: 26, - width: 1280, - height: 960, - }, tasks: [ selfGuidedTourTask, { @@ -157,13 +200,6 @@ const combinedTrackSubmitOnboardingEmployerOrSubmitMessage: OnboardingMessage = const onboardingPersonalSpendMessage: OnboardingMessage = { message: 'Here’s how to track your spend in a few clicks.', - video: { - url: `${CLOUDFRONT_URL}/videos/guided-setup-track-personal-v2.mp4`, - thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-track-personal.jpg`, - duration: 55, - width: 1280, - height: 960, - }, tasks: [ selfGuidedTourTask, { @@ -296,6 +332,7 @@ const EMAIL = { SVFG: 'svfg@expensify.com', EXPENSIFY_EMAIL_DOMAIN: '@expensify.com', EXPENSIFY_TEAM_EMAIL_DOMAIN: '@team.expensify.com', + MANAGER_MCTEST: 'manager_mctest@expensify.com', }; const CONST = { @@ -911,7 +948,7 @@ const CONST = { CLOUDFRONT_URL, EMPTY_ARRAY, EMPTY_OBJECT, - DEFAULT_NUMBER_ID: 0, + DEFAULT_NUMBER_ID, USE_EXPENSIFY_URL, EXPENSIFY_URL, GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com', @@ -1373,6 +1410,7 @@ const CONST = { SEARCH_FILTER_OPTIONS: 'search_filter_options', USE_DEBOUNCED_STATE_DELAY: 300, LIST_SCROLLING_DEBOUNCE_TIME: 200, + PUSHER_PING_PONG: 'pusher_ping_pong', }, PRIORITY_MODE: { GSD: 'gsd', @@ -2138,6 +2176,31 @@ const CONST = { '_vietNam', ] as string[], + NSQS_EXPORT_DATE: { + LAST_EXPENSE: 'LAST_EXPENSE', + EXPORTED: 'EXPORTED', + SUBMITTED: 'SUBMITTED', + }, + + NSQS_INTEGRATION_ENTITY_MAP_TYPES: { + NETSUITE_DEFAULT: 'NETSUITE_DEFAULT', + REPORT_FIELD: 'REPORT_FIELD', + TAG: 'TAG', + }, + + NSQS_CONFIG: { + AUTO_SYNC: 'autoSync', + SYNC_OPTIONS: { + MAPPING: { + CUSTOMERS: 'syncOptions.mapping.customers', + PROJECTS: 'syncOptions.mapping.projects', + }, + }, + EXPORTER: 'exporter', + EXPORT_DATE: 'exportDate', + APPROVAL_ACCOUNT: 'approvalAccount', + }, + QUICKBOOKS_EXPORT_DATE: { LAST_EXPENSE: 'LAST_EXPENSE', REPORT_EXPORTED: 'REPORT_EXPORTED', @@ -2186,6 +2249,7 @@ const CONST = { REWARDS: Number(Config?.EXPENSIFY_ACCOUNT_ID_REWARDS ?? 11023767), // rewards@expensify.com STUDENT_AMBASSADOR: Number(Config?.EXPENSIFY_ACCOUNT_ID_STUDENT_AMBASSADOR ?? 10476956), SVFG: Number(Config?.EXPENSIFY_ACCOUNT_ID_SVFG ?? 2012843), + MANAGER_MCTEST: Number(Config?.EXPENSIFY_ACCOUNT_ID_MANAGER_MCTEST ?? 18964612), }, ENVIRONMENT: { @@ -2623,17 +2687,20 @@ const CONST = { QBD: 'quickbooksDesktop', XERO: 'xero', NETSUITE: 'netsuite', + NSQS: 'netsuiteQuickStart', SAGE_INTACCT: 'intacct', }, ROUTE: { QBO: 'quickbooks-online', XERO: 'xero', NETSUITE: 'netsuite', + NSQS: 'nsqs', SAGE_INTACCT: 'sage-intacct', QBD: 'quickbooks-desktop', }, NAME_USER_FRIENDLY: { netsuite: 'NetSuite', + netsuiteQuickStart: 'NSQS', quickbooksOnline: 'QuickBooks Online', quickbooksDesktop: 'QuickBooks Desktop', xero: 'Xero', @@ -2711,6 +2778,12 @@ const CONST = { NETSUITE_SYNC_EXPENSIFY_REIMBURSED_REPORTS: 'netSuiteSyncExpensifyReimbursedReports', NETSUITE_SYNC_IMPORT_VENDORS_TITLE: 'netSuiteImportVendorsTitle', NETSUITE_SYNC_IMPORT_CUSTOM_LISTS_TITLE: 'netSuiteImportCustomListsTitle', + NSQS_SYNC_CONNECTION: 'nsqsSyncConnection', + NSQS_SYNC_ACCOUNTS: 'nsqsSyncAccounts', + NSQS_SYNC_EMPLOYEES: 'nsqsSyncEmployees', + NSQS_SYNC_CUSTOMERS: 'nsqsSyncCustomers', + NSQS_SYNC_PROJECTS: 'nsqsSyncProjects', + NSQS_SYNC_CURRENCY: 'nsqsSyncCurrency', SAGE_INTACCT_SYNC_CHECK_CONNECTION: 'intacctCheckConnection', SAGE_INTACCT_SYNC_IMPORT_TITLE: 'intacctImportTitle', SAGE_INTACCT_SYNC_IMPORT_DATA: 'intacctImportData', @@ -2719,6 +2792,19 @@ const CONST = { SAGE_INTACCT_SYNC_IMPORT_SYNC_REIMBURSED_REPORTS: 'intacctImportSyncBillPayments', }, SYNC_STAGE_TIMEOUT_MINUTES: 20, + + // Map each connection to its designated display connection + get MULTI_CONNECTIONS_MAPPING() { + return { + [this.NAME.NETSUITE]: this.NAME.NETSUITE, + [this.NAME.NSQS]: this.NAME.NETSUITE, + } as Record, ValueOf | undefined>; + }, + + // Get linked connections by the designated display connection + get MULTI_CONNECTIONS_MAPPING_INVERTED() { + return invertBy(this.MULTI_CONNECTIONS_MAPPING) as Dictionary> | undefined>; + }, }, ACCESS_VARIANTS: { PAID: 'paid', @@ -2744,7 +2830,7 @@ const CONST = { NAME_PER_DIEM_INTERNATIONAL: 'Per Diem International', DISTANCE_UNIT_MILES: 'mi', DISTANCE_UNIT_KILOMETERS: 'km', - MILEAGE_IRS_RATE: 0.67, + MILEAGE_IRS_RATE: 0.7, DEFAULT_RATE: 'Default Rate', RATE_DECIMALS: 3, FAKE_P2P_ID: '_FAKE_P2P_ID_', @@ -3204,6 +3290,7 @@ const CONST = { EMAIL.RECEIPTS, EMAIL.STUDENT_AMBASSADOR, EMAIL.SVFG, + EMAIL.MANAGER_MCTEST, ] as string[], get EXPENSIFY_ACCOUNT_IDS() { return [ @@ -3224,6 +3311,7 @@ const CONST = { this.ACCOUNT_ID.REWARDS, this.ACCOUNT_ID.STUDENT_AMBASSADOR, this.ACCOUNT_ID.SVFG, + this.ACCOUNT_ID.MANAGER_MCTEST, ].filter((id) => id !== -1); }, @@ -5007,6 +5095,7 @@ const CONST = { quickbooksOnline: 'QuickBooks Online', xero: 'Xero', netsuite: 'NetSuite', + netsuiteQuickStart: 'NSQS', intacct: 'Sage Intacct', quickbooksDesktop: 'QuickBooks Desktop', }, @@ -5015,38 +5104,10 @@ const CONST = { [onboardingChoices.SUBMIT]: onboardingEmployerOrSubmitMessage, [onboardingChoices.MANAGE_TEAM]: { message: 'Here are some important tasks to help get your team’s expenses under control.', - video: { - url: `${CLOUDFRONT_URL}/videos/guided-setup-manage-team-v2.mp4`, - thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-manage-team.jpg`, - duration: 55, - width: 1280, - height: 960, - }, tasks: [ - { - type: 'createWorkspace', - autoCompleted: true, - title: 'Create a workspace', - description: ({workspaceSettingsLink}) => - '*Create a workspace* to track expenses, scan receipts, chat, and more.\n' + - '\n' + - 'Here’s how to create a workspace:\n' + - '\n' + - '1. Click *Settings*.\n' + - '2. Click *Workspaces* > *New workspace*.\n' + - '\n' + - `*Your new workspace is ready!* [Check it out](${workspaceSettingsLink}).`, - }, + createWorkspaceTask, selfGuidedTourTask, - { - type: 'meetGuide', - autoCompleted: false, - title: 'Meet your setup specialist', - description: ({adminsRoomLink}) => - `Meet your setup specialist, who can answer any questions as you get started with Expensify. Yes, a real human!\n` + - '\n' + - `Chat with the specialist in your [#admins room](${adminsRoomLink}).`, - }, + meetGuideTask, { type: 'setupCategoriesAndTags', autoCompleted: false, @@ -5056,24 +5117,7 @@ const CONST = { '\n' + `Import them automatically by [connecting your accounting software](${workspaceAccountingLink}), or set them up manually in your [workspace settings](${workspaceSettingsLink}).`, }, - { - type: 'setupCategories', - autoCompleted: false, - title: 'Set up categories', - description: ({workspaceCategoriesLink}) => - '*Set up categories* so your team can code expenses for easy reporting.\n' + - '\n' + - 'Here’s how to set up categories:\n' + - '\n' + - '1. Click *Settings*.\n' + - '2. Go to *Workspaces*.\n' + - '3. Select your workspace.\n' + - '4. Click *Categories*.\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}).`, - }, + setupCategoriesTask, { type: 'setupTags', autoCompleted: false, @@ -5151,16 +5195,45 @@ const CONST = { }, ], }, - [onboardingChoices.PERSONAL_SPEND]: onboardingPersonalSpendMessage, - [onboardingChoices.CHAT_SPLIT]: { - message: 'Splitting bills with friends is as easy as sending a message. Here’s how.', + [onboardingChoices.TRACK_WORKSPACE]: { + message: 'Here are some important tasks to help get your workspace set up.', video: { - url: `${CLOUDFRONT_URL}/videos/guided-setup-chat-split-bills-v2.mp4`, - thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-chat-split-bills.jpg`, + url: `${CLOUDFRONT_URL}/videos/guided-setup-manage-team-v2.mp4`, + thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-manage-team.jpg`, duration: 55, width: 1280, height: 960, }, + tasks: [ + createWorkspaceTask, + meetGuideTask, + setupCategoriesTask, + { + type: 'inviteAccountant', + autoCompleted: false, + title: 'Invite your accountant', + description: ({workspaceMembersLink}) => + '*Invite your accountant* to Expensify and share your expenses with them to make tax time easier.\n' + + '\n' + + 'Here’s how to invite your accountant:\n' + + '\n' + + '1. Click your profile picture.\n' + + '2. Go to *Workspaces*.\n' + + '3. Select your workspace.\n' + + '4. Click *Members* > Invite member.\n' + + '5. Enter their email or phone number.\n' + + '6. Add an invite message if you’d like.\n' + + '7. You’ll be set as the expense approver. You can change this to any admin once you invite your team.\n' + + '\n' + + 'That’s it, happy expensing! 😄\n' + + '\n' + + `[View your workspace members](${workspaceMembersLink}).`, + }, + ], + }, + [onboardingChoices.PERSONAL_SPEND]: onboardingPersonalSpendMessage, + [onboardingChoices.CHAT_SPLIT]: { + message: 'Splitting bills with friends is as easy as sending a message. Here’s how.', tasks: [ selfGuidedTourTask, { @@ -5201,23 +5274,8 @@ const CONST = { }, [onboardingChoices.ADMIN]: { message: "As an admin, learn how to manage your team's workspace and submit expenses yourself.", - video: { - url: `${CLOUDFRONT_URL}/videos/guided-setup-manage-team-v2.mp4`, - thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-manage-team.jpg`, - duration: 55, - width: 1280, - height: 960, - }, tasks: [ - { - type: 'meetSetupSpecialist', - autoCompleted: false, - title: 'Meet your setup specialist', - description: - '*Meet your setup specialist* who can answer any questions as you get started with Expensify. Yes, a real human!' + - '\n' + - 'Chat with them in your #admins room or schedule a call today.', - }, + meetGuideTask, { type: 'reviewWorkspaceSettings', autoCompleted: false, @@ -5321,183 +5379,183 @@ const CONST = { }, CURRENCY_TO_DEFAULT_MILEAGE_RATE: JSON.parse(`{ "AED": { - "rate": 396, + "rate": 414, "unit": "km" }, "AFN": { - "rate": 8369, + "rate": 8851, "unit": "km" }, "ALL": { - "rate": 11104, + "rate": 10783, "unit": "km" }, "AMD": { - "rate": 56842, + "rate": 45116, "unit": "km" }, "ANG": { - "rate": 193, + "rate": 203, "unit": "km" }, "AOA": { - "rate": 67518, + "rate": 102929, "unit": "km" }, "ARS": { - "rate": 9873, + "rate": 118428, "unit": "km" }, "AUD": { - "rate": 85, + "rate": 88, "unit": "km" }, "AWG": { - "rate": 195, + "rate": 203, "unit": "km" }, "AZN": { - "rate": 183, + "rate": 192, "unit": "km" }, "BAM": { - "rate": 177, + "rate": 212, "unit": "km" }, "BBD": { - "rate": 216, + "rate": 225, "unit": "km" }, "BDT": { - "rate": 9130, + "rate": 13697, "unit": "km" }, "BGN": { - "rate": 177, + "rate": 211, "unit": "km" }, "BHD": { - "rate": 40, + "rate": 42, "unit": "km" }, "BIF": { - "rate": 210824, + "rate": 331847, "unit": "km" }, "BMD": { - "rate": 108, + "rate": 113, "unit": "km" }, "BND": { - "rate": 145, + "rate": 153, "unit": "km" }, "BOB": { - "rate": 745, + "rate": 779, "unit": "km" }, "BRL": { - "rate": 594, + "rate": 660, "unit": "km" }, "BSD": { - "rate": 108, + "rate": 113, "unit": "km" }, "BTN": { - "rate": 7796, + "rate": 9761, "unit": "km" }, "BWP": { - "rate": 1180, + "rate": 1569, "unit": "km" }, "BYN": { - "rate": 280, + "rate": 369, "unit": "km" }, "BYR": { - "rate": 2159418, + "rate": 2255979, "unit": "km" }, "BZD": { - "rate": 217, + "rate": 227, "unit": "km" }, "CAD": { - "rate": 70, + "rate": 72, "unit": "km" }, "CDF": { - "rate": 213674, + "rate": 321167, "unit": "km" }, "CHF": { - "rate": 70, + "rate": 76, "unit": "km" }, "CLP": { - "rate": 77249, + "rate": 111689, "unit": "km" }, "CNY": { - "rate": 702, + "rate": 808, "unit": "km" }, "COP": { - "rate": 383668, + "rate": 473791, "unit": "km" }, "CRC": { - "rate": 65899, + "rate": 57190, "unit": "km" }, "CUC": { - "rate": 108, + "rate": 113, "unit": "km" }, "CUP": { - "rate": 2776, + "rate": 2902, "unit": "km" }, "CVE": { - "rate": 6112, + "rate": 11961, "unit": "km" }, "CZK": { - "rate": 2356, + "rate": 2715, "unit": "km" }, "DJF": { - "rate": 19151, + "rate": 19956, "unit": "km" }, "DKK": { - "rate": 379, + "rate": 381, "unit": "km" }, "DOP": { - "rate": 6144, + "rate": 6948, "unit": "km" }, "DZD": { - "rate": 14375, + "rate": 15226, "unit": "km" }, "EEK": { - "rate": 1576, + "rate": 1646, "unit": "km" }, "EGP": { - "rate": 1696, + "rate": 5657, "unit": "km" }, "ERN": { - "rate": 1617, + "rate": 1690, "unit": "km" }, "ETB": { - "rate": 4382, + "rate": 14326, "unit": "km" }, "EUR": { @@ -5505,11 +5563,11 @@ const CONST = { "unit": "km" }, "FJD": { - "rate": 220, + "rate": 264, "unit": "km" }, "FKP": { - "rate": 77, + "rate": 90, "unit": "km" }, "GBP": { @@ -5517,55 +5575,55 @@ const CONST = { "unit": "mi" }, "GEL": { - "rate": 359, + "rate": 323, "unit": "km" }, "GHS": { - "rate": 620, + "rate": 1724, "unit": "km" }, "GIP": { - "rate": 77, + "rate": 90, "unit": "km" }, "GMD": { - "rate": 5526, + "rate": 8111, "unit": "km" }, "GNF": { - "rate": 1081319, + "rate": 974619, "unit": "km" }, "GTQ": { - "rate": 832, + "rate": 872, "unit": "km" }, "GYD": { - "rate": 22537, + "rate": 23585, "unit": "km" }, "HKD": { - "rate": 837, + "rate": 877, "unit": "km" }, "HNL": { - "rate": 2606, + "rate": 2881, "unit": "km" }, "HRK": { - "rate": 684, + "rate": 814, "unit": "km" }, "HTG": { - "rate": 8563, + "rate": 14734, "unit": "km" }, "HUF": { - "rate": 33091, + "rate": 44127, "unit": "km" }, "IDR": { - "rate": 1555279, + "rate": 1830066, "unit": "km" }, "ILS": { @@ -5573,147 +5631,147 @@ const CONST = { "unit": "km" }, "INR": { - "rate": 7805, + "rate": 9761, "unit": "km" }, "IQD": { - "rate": 157394, + "rate": 147577, "unit": "km" }, "IRR": { - "rate": 4539961, + "rate": 4741290, "unit": "km" }, "ISK": { - "rate": 13518, + "rate": 15772, "unit": "km" }, "JMD": { - "rate": 15794, + "rate": 17738, "unit": "km" }, "JOD": { - "rate": 77, + "rate": 80, "unit": "km" }, "JPY": { - "rate": 11748, + "rate": 17542, "unit": "km" }, "KES": { - "rate": 11845, + "rate": 14589, "unit": "km" }, "KGS": { - "rate": 9144, + "rate": 9852, "unit": "km" }, "KHR": { - "rate": 437658, + "rate": 453066, "unit": "km" }, "KMF": { - "rate": 44418, + "rate": 53269, "unit": "km" }, "KPW": { - "rate": 97043, + "rate": 101389, "unit": "km" }, "KRW": { - "rate": 121345, + "rate": 162705, "unit": "km" }, "KWD": { - "rate": 32, + "rate": 35, "unit": "km" }, "KYD": { - "rate": 90, + "rate": 93, "unit": "km" }, "KZT": { - "rate": 45396, + "rate": 58319, "unit": "km" }, "LAK": { - "rate": 1010829, + "rate": 2452802, "unit": "km" }, "LBP": { - "rate": 164153, + "rate": 10093809, "unit": "km" }, "LKR": { - "rate": 21377, + "rate": 33423, "unit": "km" }, "LRD": { - "rate": 18709, + "rate": 22185, "unit": "km" }, "LSL": { - "rate": 1587, + "rate": 2099, "unit": "km" }, "LTL": { - "rate": 348, + "rate": 364, "unit": "km" }, "LVL": { - "rate": 71, + "rate": 74, "unit": "km" }, "LYD": { - "rate": 486, + "rate": 554, "unit": "km" }, "MAD": { - "rate": 967, + "rate": 1127, "unit": "km" }, "MDL": { - "rate": 1910, + "rate": 2084, "unit": "km" }, "MGA": { - "rate": 406520, + "rate": 529635, "unit": "km" }, "MKD": { - "rate": 5570, + "rate": 6650, "unit": "km" }, "MMK": { - "rate": 152083, + "rate": 236413, "unit": "km" }, "MNT": { - "rate": 306788, + "rate": 382799, "unit": "km" }, "MOP": { - "rate": 863, + "rate": 904, "unit": "km" }, "MRO": { - "rate": 38463, + "rate": 40234, "unit": "km" }, "MRU": { - "rate": 3862, + "rate": 4506, "unit": "km" }, "MUR": { - "rate": 4340, + "rate": 5226, "unit": "km" }, "MVR": { - "rate": 1667, + "rate": 1735, "unit": "km" }, "MWK": { - "rate": 84643, + "rate": 195485, "unit": "km" }, "MXN": { @@ -5721,23 +5779,23 @@ const CONST = { "unit": "km" }, "MYR": { - "rate": 444, + "rate": 494, "unit": "km" }, "MZN": { - "rate": 7772, + "rate": 7199, "unit": "km" }, "NAD": { - "rate": 1587, + "rate": 2099, "unit": "km" }, "NGN": { - "rate": 42688, + "rate": 174979, "unit": "km" }, "NIO": { - "rate": 3772, + "rate": 4147, "unit": "km" }, "NOK": { @@ -5745,35 +5803,35 @@ const CONST = { "unit": "km" }, "NPR": { - "rate": 12474, + "rate": 15617, "unit": "km" }, "NZD": { - "rate": 95, + "rate": 104, "unit": "km" }, "OMR": { - "rate": 42, + "rate": 43, "unit": "km" }, "PAB": { - "rate": 108, + "rate": 113, "unit": "km" }, "PEN": { - "rate": 401, + "rate": 420, "unit": "km" }, "PGK": { - "rate": 380, + "rate": 455, "unit": "km" }, "PHP": { - "rate": 5234, + "rate": 6582, "unit": "km" }, "PKR": { - "rate": 16785, + "rate": 31411, "unit": "km" }, "PLN": { @@ -5781,43 +5839,43 @@ const CONST = { "unit": "km" }, "PYG": { - "rate": 704732, + "rate": 890772, "unit": "km" }, "QAR": { - "rate": 393, + "rate": 410, "unit": "km" }, "RON": { - "rate": 443, + "rate": 538, "unit": "km" }, "RSD": { - "rate": 10630, + "rate": 12656, "unit": "km" }, "RUB": { - "rate": 8074, + "rate": 11182, "unit": "km" }, "RWF": { - "rate": 107182, + "rate": 156589, "unit": "km" }, "SAR": { - "rate": 404, + "rate": 423, "unit": "km" }, "SBD": { - "rate": 859, + "rate": 951, "unit": "km" }, "SCR": { - "rate": 2287, + "rate": 1611, "unit": "km" }, "SDG": { - "rate": 41029, + "rate": 67705, "unit": "km" }, "SEK": { @@ -5825,155 +5883,159 @@ const CONST = { "unit": "km" }, "SGD": { - "rate": 145, + "rate": 151, "unit": "km" }, "SHP": { - "rate": 77, + "rate": 90, "unit": "km" }, "SLL": { - "rate": 1102723, + "rate": 2362357, + "unit": "km" + }, + "SLE": { + "rate": 2363, "unit": "km" }, "SOS": { - "rate": 62604, + "rate": 64374, "unit": "km" }, "SRD": { - "rate": 1526, + "rate": 3954, "unit": "km" }, "STD": { - "rate": 2223309, + "rate": 2510095, "unit": "km" }, "STN": { - "rate": 2232, + "rate": 2683, "unit": "km" }, "SVC": { - "rate": 943, + "rate": 987, "unit": "km" }, "SYP": { - "rate": 82077, + "rate": 1464664, "unit": "km" }, "SZL": { - "rate": 1585, + "rate": 2099, "unit": "km" }, "THB": { - "rate": 3328, + "rate": 3801, "unit": "km" }, "TJS": { - "rate": 1230, + "rate": 1228, "unit": "km" }, "TMT": { - "rate": 378, + "rate": 394, "unit": "km" }, "TND": { - "rate": 295, + "rate": 360, "unit": "km" }, "TOP": { - "rate": 245, + "rate": 274, "unit": "km" }, "TRY": { - "rate": 845, + "rate": 4035, "unit": "km" }, "TTD": { - "rate": 732, + "rate": 763, "unit": "km" }, "TWD": { - "rate": 3055, + "rate": 3703, "unit": "km" }, "TZS": { - "rate": 250116, + "rate": 286235, "unit": "km" }, "UAH": { - "rate": 2985, + "rate": 4725, "unit": "km" }, "UGX": { - "rate": 395255, + "rate": 416016, "unit": "km" }, "USD": { - "rate": 67, + "rate": 70, "unit": "mi" }, "UYU": { - "rate": 4777, + "rate": 4888, "unit": "km" }, "UZS": { - "rate": 1131331, + "rate": 1462038, "unit": "km" }, "VEB": { - "rate": 679346, + "rate": 709737, "unit": "km" }, "VEF": { - "rate": 26793449, + "rate": 27993155, "unit": "km" }, "VES": { - "rate": 194381905, + "rate": 6457, "unit": "km" }, "VND": { - "rate": 2487242, + "rate": 2825526, "unit": "km" }, "VUV": { - "rate": 11748, + "rate": 13358, "unit": "km" }, "WST": { - "rate": 272, + "rate": 315, "unit": "km" }, "XAF": { - "rate": 59224, + "rate": 70811, "unit": "km" }, "XCD": { - "rate": 291, + "rate": 304, "unit": "km" }, "XOF": { - "rate": 59224, + "rate": 70811, "unit": "km" }, "XPF": { - "rate": 10783, + "rate": 12875, "unit": "km" }, "YER": { - "rate": 27037, + "rate": 28003, "unit": "km" }, "ZAR": { - "rate": 464, + "rate": 484, "unit": "km" }, "ZMK": { - "rate": 566489, + "rate": 591756, "unit": "km" }, "ZMW": { - "rate": 2377, + "rate": 3148, "unit": "km" } }`) as Record, @@ -6006,6 +6068,13 @@ const CONST = { TRAIN: 'train', }, + CANCELLATION_POLICY: { + UNKNOWN: 'UNKNOWN', + NON_REFUNDABLE: 'NON_REFUNDABLE', + FREE_CANCELLATION_UNTIL: 'FREE_CANCELLATION_UNTIL', + PARTIALLY_REFUNDABLE: 'PARTIALLY_REFUNDABLE', + }, + DOT_SEPARATOR: '•', DEFAULT_TAX: { @@ -6124,6 +6193,7 @@ const CONST = { LOWER_THAN: 'lt', LOWER_THAN_OR_EQUAL_TO: 'lte', }, + SYNTAX_RANGE_NAME: 'syntax', SYNTAX_ROOT_KEYS: { TYPE: 'type', STATUS: 'status', @@ -6356,6 +6426,14 @@ const CONST = { description: 'workspace.upgrade.perDiem.description' as const, icon: 'PerDiem', }, + travel: { + id: 'travel' as const, + alias: 'travel', + name: 'Travel', + title: 'workspace.upgrade.travel.title' as const, + description: 'workspace.upgrade.travel.description' as const, + icon: 'Luggage', + }, }; }, REPORT_FIELD_TYPES: { @@ -6555,6 +6633,8 @@ const CONST = { ERROR_PERMISSION_DENIED: 'permissionDenied', }, }, + SKIPPABLE_COLLECTION_MEMBER_IDS: [String(DEFAULT_NUMBER_ID), '-1', 'undefined', 'null', 'NaN'] as string[], + SETUP_SPECIALIST_LOGIN: 'Setup Specialist', } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b89d23973d1a..1fb84c3dd9cf 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -228,6 +228,9 @@ const ONYXKEYS = { /** The NVP containing the target url to navigate to when deleting a transaction */ NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL: 'nvp_deleteTransactionNavigateBackURL', + /** A timestamp of when the last full reconnect should have been done */ + NVP_RECONNECT_APP_IF_FULL_RECONNECT_BEFORE: 'nvp_reconnectAppIfFullReconnectBefore', + /** Plaid data (access tokens, bank accounts ...) */ PLAID_DATA: 'plaidData', @@ -321,7 +324,7 @@ const ONYXKEYS = { // The theme setting set by the user in preferences. // This can be either "light", "dark" or "system" - PREFERRED_THEME: 'preferredTheme', + PREFERRED_THEME: 'nvp_preferredTheme', // Information about the onyx updates IDs that were received from the server ONYX_UPDATES_FROM_SERVER: 'onyxUpdatesFromServer', @@ -467,6 +470,9 @@ const ONYXKEYS = { /** Corpay onboarding fields used in steps 3-5 in the global reimbursements */ CORPAY_ONBOARDING_FIELDS: 'corpayOnboardingFields', + /** Timestamp of when the last full reconnect was done on this client */ + LAST_FULL_RECONNECT_TIME: 'lastFullReconnectTime', + /** Information about travel provisioning process */ TRAVEL_PROVISIONING: 'travelProvisioning', @@ -542,6 +548,9 @@ const ONYXKEYS = { /** Expensify cards settings */ PRIVATE_EXPENSIFY_CARD_SETTINGS: 'private_expensifyCardSettings_', + /** Expensify cards manual billing setting */ + PRIVATE_EXPENSIFY_CARD_MANUAL_BILLING: 'private_expensifyCardManualBilling_', + /** Stores which connection is set up to use Continuous Reconciliation */ EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION: 'expensifyCard_continuousReconciliationConnection_', @@ -718,6 +727,8 @@ const ONYXKEYS = { NETSUITE_TOKEN_INPUT_FORM_DRAFT: 'netsuiteTokenInputFormDraft', NETSUITE_CUSTOM_FORM_ID_FORM: 'netsuiteCustomFormIDForm', NETSUITE_CUSTOM_FORM_ID_FORM_DRAFT: 'netsuiteCustomFormIDFormDraft', + NSQS_OAUTH2_FORM: 'nsqsOAuth2Form', + NSQS_OAUTH2_FORM_DRAFT: 'nsqsOAuth2FormDraft', SAGE_INTACCT_DIMENSION_TYPE_FORM: 'sageIntacctDimensionTypeForm', SAGE_INTACCT_DIMENSION_TYPE_FORM_DRAFT: 'sageIntacctDimensionTypeFormDraft', SEARCH_ADVANCED_FILTERS_FORM: 'searchAdvancedFiltersForm', @@ -828,6 +839,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.NETSUITE_CUSTOM_SEGMENT_ADD_FORM]: FormTypes.NetSuiteCustomFieldForm; [ONYXKEYS.FORMS.NETSUITE_TOKEN_INPUT_FORM]: FormTypes.NetSuiteTokenInputForm; [ONYXKEYS.FORMS.NETSUITE_CUSTOM_FORM_ID_FORM]: FormTypes.NetSuiteCustomFormIDForm; + [ONYXKEYS.FORMS.NSQS_OAUTH2_FORM]: FormTypes.NSQSOAuth2Form; [ONYXKEYS.FORMS.SAGE_INTACCT_DIMENSION_TYPE_FORM]: FormTypes.SageIntacctDimensionForm; [ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM]: FormTypes.SearchAdvancedFiltersForm; [ONYXKEYS.FORMS.TEXT_PICKER_MODAL_FORM]: FormTypes.TextPickerModalForm; @@ -892,6 +904,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END]: OnyxTypes.BillingGraceEndPeriod; [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER]: OnyxTypes.CardFeeds; [ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS]: OnyxTypes.ExpensifyCardSettings; + [ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_MANUAL_BILLING]: boolean; [ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList; [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName; [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean; @@ -1045,6 +1058,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: number; [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: number; [ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL]: string | undefined; + [ONYXKEYS.NVP_RECONNECT_APP_IF_FULL_RECONNECT_BEFORE]: string; [ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[]; [ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE]: string; [ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx; @@ -1058,6 +1072,7 @@ type OnyxValuesMapping = { [ONYXKEYS.PRESERVED_USER_SESSION]: OnyxTypes.Session; [ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING]: OnyxTypes.DismissedProductTraining; [ONYXKEYS.CORPAY_ONBOARDING_FIELDS]: OnyxTypes.CorpayOnboardingFields; + [ONYXKEYS.LAST_FULL_RECONNECT_TIME]: string; [ONYXKEYS.TRAVEL_PROVISIONING]: OnyxTypes.TravelProvisioning; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9972c03d7b68..b01ecf9058b7 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -68,7 +68,10 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_POSTED: 'search/filters/posted', SEARCH_REPORT: { route: 'search/view/:reportID/:reportActionID?', - getRoute: ({reportID, reportActionID, backTo}: {reportID: string; reportActionID?: string; backTo?: string}) => { + getRoute: ({reportID, reportActionID, backTo}: {reportID: string | undefined; reportActionID?: string; backTo?: string}) => { + if (!reportID) { + Log.warn('Invalid reportID is used to build the SEARCH_REPORT route'); + } const baseRoute = reportActionID ? (`search/view/${reportID}/${reportActionID}` as const) : (`search/view/${reportID}` as const); return getUrlWithBackToParam(baseRoute, backTo); }, @@ -186,6 +189,10 @@ const ROUTES = { route: 'settings/wallet/card/:cardID/report-virtual-fraud', getRoute: (cardID: string) => `settings/wallet/card/${cardID}/report-virtual-fraud` as const, }, + SETTINGS_REPORT_FRAUD_CONFIRMATION: { + route: 'settings/wallet/card/:cardID/report-virtual-fraud-confirm', + getRoute: (cardID: string) => `settings/wallet/card/${cardID}/report-virtual-fraud-confirm` as const, + }, SETTINGS_DOMAINCARD_REPORT_FRAUD: { route: 'settings/card/:cardID/report-virtual-fraud', getRoute: (cardID: string) => `settings/card/${cardID}/report-virtual-fraud` as const, @@ -337,7 +344,12 @@ const ROUTES = { }, REPORT_WITH_ID_DETAILS_SHARE_CODE: { route: 'r/:reportID/details/shareCode', - getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/details/shareCode` as const, backTo), + getRoute: (reportID: string | undefined, backTo?: string) => { + if (!reportID) { + Log.warn('Invalid reportID is used to build the REPORT_WITH_ID_DETAILS_SHARE_CODE route'); + } + return getUrlWithBackToParam(`r/${reportID}/details/shareCode` as const, backTo); + }, }, ATTACHMENTS: { route: 'attachment', @@ -514,7 +526,7 @@ const ROUTES = { }, MONEY_REQUEST_STEP_CATEGORY: { route: ':action/:iouType/category/:transactionID/:reportID/:reportActionID?', - getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string | undefined, backTo = '', reportActionID?: string) => getUrlWithBackToParam(`${action as string}/${iouType as string}/category/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_ATTENDEE: { @@ -833,7 +845,12 @@ const ROUTES = { }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export', - getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export` as const, backTo, false), + getRoute: (policyID: string | undefined, backTo?: string) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT route'); + } + return getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export` as const, backTo, false); + }, }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/company-card-expense-account', @@ -841,15 +858,30 @@ const ROUTES = { }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/company-card-expense-account/account-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/account-select` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT route'); + } + return `settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/account-select` as const; + }, }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/company-card-expense-account/default-vendor-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/default-vendor-select` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT route'); + } + return `settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/default-vendor-select` as const; + }, }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/company-card-expense-account/card-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/card-select` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_SELECT route'); + } + return `settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/card-select` as const; + }, }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/invoice-account-select', @@ -1102,6 +1134,28 @@ const ROUTES = { getRoute: (policyID: string, connection?: ValueOf) => `settings/workspaces/${policyID}/accounting/${connection as string}/card-reconciliation/account` as const, }, + WORKSPACE_ACCOUNTING_MULTI_CONNECTION_SELECTOR: { + route: 'settings/workspaces/:policyID/accounting/:connection/connection-selector', + getRoute: ( + policyID: string, + connection: ValueOf, + integrationToDisconnect?: ConnectionName, + shouldDisconnectIntegrationBeforeConnecting?: boolean, + ) => { + const searchParams = new URLSearchParams(); + + if (integrationToDisconnect) { + searchParams.append('integrationToDisconnect', integrationToDisconnect); + } + if (shouldDisconnectIntegrationBeforeConnecting !== undefined) { + searchParams.append('shouldDisconnectIntegrationBeforeConnecting', shouldDisconnectIntegrationBeforeConnecting.toString()); + } + + const queryParams = searchParams.size ? `?${searchParams.toString()}` : ''; + + return `settings/workspaces/${policyID}/accounting/${connection}/connection-selector${queryParams}` as const; + }, + }, WORKSPACE_CATEGORIES: { route: 'settings/workspaces/:policyID/categories', getRoute: (policyID: string | undefined) => { @@ -1584,6 +1638,7 @@ const ROUTES = { route: 'travel/terms/:domain/accept', getRoute: (domain: string, backTo?: string) => getUrlWithBackToParam(`travel/terms/${domain}/accept`, backTo), }, + TRAVEL_UPGRADE: 'travel/upgrade', TRACK_TRAINING_MODAL: 'track-training', TRAVEL_TRIP_SUMMARY: { route: 'r/:reportID/trip/:transactionID', @@ -1930,6 +1985,50 @@ const ROUTES = { route: 'settings/workspaces/:policyID/connections/netsuite/advanced/autosync/accounting-method', getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/autosync/accounting-method` as const, }, + POLICY_ACCOUNTING_NSQS_SETUP: { + route: 'settings/workspaces/:policyID/accounting/nsqs/setup', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/setup` as const, + }, + POLICY_ACCOUNTING_NSQS_IMPORT: { + route: 'settings/workspaces/:policyID/accounting/nsqs/import', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/import` as const, + }, + POLICY_ACCOUNTING_NSQS_IMPORT_CUSTOMERS: { + route: 'settings/workspaces/:policyID/accounting/nsqs/import/customers', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/import/customers` as const, + }, + POLICY_ACCOUNTING_NSQS_IMPORT_CUSTOMERS_DISPLAYED_AS: { + route: 'settings/workspaces/:policyID/accounting/nsqs/import/customers/displayed-as', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/import/customers/displayed-as` as const, + }, + POLICY_ACCOUNTING_NSQS_IMPORT_PROJECTS: { + route: 'settings/workspaces/:policyID/accounting/nsqs/import/projects', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/import/projects` as const, + }, + POLICY_ACCOUNTING_NSQS_IMPORT_PROJECTS_DISPLAYED_AS: { + route: 'settings/workspaces/:policyID/accounting/nsqs/import/projects/displayed-as', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/import/projects/displayed-as` as const, + }, + POLICY_ACCOUNTING_NSQS_EXPORT: { + route: 'settings/workspaces/:policyID/accounting/nsqs/export', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/export` as const, + }, + POLICY_ACCOUNTING_NSQS_EXPORT_PREFERRED_EXPORTER: { + route: 'settings/workspaces/:policyID/accounting/nsqs/export/preferred-exporter', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/export/preferred-exporter` as const, + }, + POLICY_ACCOUNTING_NSQS_EXPORT_DATE: { + route: 'settings/workspaces/:policyID/accounting/nsqs/export/date', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/export/date` as const, + }, + POLICY_ACCOUNTING_NSQS_ADVANCED: { + route: 'settings/workspaces/:policyID/accounting/nsqs/advanced', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/advanced` as const, + }, + POLICY_ACCOUNTING_NSQS_ADVANCED_APPROVAL_ACCOUNT: { + route: 'settings/workspaces/:policyID/accounting/nsqs/advanced/approval-account', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/nsqs/advanced/approval-account` as const, + }, POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/prerequisites', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/prerequisites` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 362758c7a843..712f549d7595 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -30,6 +30,7 @@ const SCREENS = { TCS: 'Travel_TCS', TRIP_SUMMARY: 'Travel_TripSummary', TRIP_DETAILS: 'Travel_TripDetails', + UPGRADE: 'Travel_Upgrade', DOMAIN_SELECTOR: 'Travel_DomainSelector', DOMAIN_PERMISSION_INFO: 'Travel_DomainPermissionInfo', PUBLIC_DOMAIN_ERROR: 'Travel_PublicDomainError', @@ -123,6 +124,7 @@ const SCREENS = { ENABLE_PAYMENTS: 'Settings_Wallet_EnablePayments', CARD_ACTIVATE: 'Settings_Wallet_Card_Activate', REPORT_VIRTUAL_CARD_FRAUD: 'Settings_Wallet_ReportVirtualCardFraud', + REPORT_VIRTUAL_CARD_FRAUD_CONFIRMATION: 'Settings_Wallet_ReportVirtualCardFraudConfirmation', CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS: 'Settings_Wallet_Cards_Digital_Details_Update_Address', VERIFY_ACCOUNT: 'Settings_Wallet_Verify_Account', }, @@ -430,6 +432,17 @@ const SCREENS = { NETSUITE_CUSTOM_FORM_ID: 'Policy_Accounting_NetSuite_Custom_Form_ID', NETSUITE_AUTO_SYNC: 'Policy_Accounting_NetSuite_Auto_Sync', NETSUITE_ACCOUNTING_METHOD: 'Policy_Accounting_NetSuite_Accounting_Method', + NSQS_SETUP: 'Policy_Accounting_NSQS_Setup', + NSQS_IMPORT: 'Policy_Accounting_NSQS_Import', + NSQS_IMPORT_CUSTOMERS: 'Policy_Accounting_NSQS_Import_Customers', + NSQS_IMPORT_CUSTOMERS_DISPLAYED_AS: 'Policy_Accounting_NSQS_Import_Customers_Displayed_As', + NSQS_IMPORT_PROJECTS: 'Policy_Accounting_NSQS_Import_Projects', + NSQS_IMPORT_PROJECTS_DISPLAYED_AS: 'Policy_Accounting_NSQS_Import_Projects_Displayed_As', + NSQS_EXPORT: 'Policy_Accounting_NSQS_Export', + NSQS_EXPORT_PREFERRED_EXPORTER: 'Policy_Accounting_NSQS_Export_Preferred_Exporter', + NSQS_EXPORT_DATE: 'Policy_Accounting_NSQS_Export_Date', + NSQS_ADVANCED: 'Policy_Accounting_NSQS_Advanced', + NSQS_ADVANCED_APPROVAL_ACCOUNT: 'Policy_Accounting_NSQS_Advanced_Approval_Account', SAGE_INTACCT_PREREQUISITES: 'Policy_Accounting_Sage_Intacct_Prerequisites', ENTER_SAGE_INTACCT_CREDENTIALS: 'Policy_Enter_Sage_Intacct_Credentials', EXISTING_SAGE_INTACCT_CONNECTIONS: 'Policy_Existing_Sage_Intacct_Connections', @@ -453,6 +466,7 @@ const SCREENS = { SAGE_INTACCT_PAYMENT_ACCOUNT: 'Policy_Accounting_Sage_Intacct_Payment_Account', CARD_RECONCILIATION: 'Policy_Accounting_Card_Reconciliation', RECONCILIATION_ACCOUNT_SETTINGS: 'Policy_Accounting_Reconciliation_Account_Settings', + MULTI_CONNECTION_SELECTOR: 'Policy_Accounting_Multi_Connection_Selector', }, INITIAL: 'Workspace_Initial', PROFILE: 'Workspace_Profile', diff --git a/src/components/Accordion/index.native.tsx b/src/components/Accordion/index.native.tsx new file mode 100644 index 000000000000..0fcaa7294dd7 --- /dev/null +++ b/src/components/Accordion/index.native.tsx @@ -0,0 +1,81 @@ +import type {ReactNode} from 'react'; +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {SharedValue} from 'react-native-reanimated'; +import Animated, {Easing, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type AccordionProps = { + /** Giving information whether the component is open */ + isExpanded: SharedValue; + + /** Element that is inside Accordion */ + children: ReactNode; + + /** Duration of expansion animation */ + duration?: number; + + /** Additional external style */ + style?: StyleProp; + + /** Was toggle triggered */ + isToggleTriggered: SharedValue; +}; + +function Accordion({isExpanded, children, duration = 300, isToggleTriggered, style}: AccordionProps) { + const height = useSharedValue(0); + const styles = useThemeStyles(); + + const derivedHeight = useDerivedValue(() => { + if (!isToggleTriggered.get()) { + return isExpanded.get() ? height.get() : 0; + } + + return withTiming(height.get() * Number(isExpanded.get()), { + duration, + easing: Easing.inOut(Easing.quad), + }); + }); + + const derivedOpacity = useDerivedValue(() => { + if (!isToggleTriggered.get()) { + return isExpanded.get() ? 1 : 0; + } + + return withTiming(isExpanded.get() ? 1 : 0, { + duration, + easing: Easing.inOut(Easing.quad), + }); + }); + + const animatedStyle = useAnimatedStyle(() => { + if (!isToggleTriggered.get() && !isExpanded.get()) { + return { + height: 0, + opacity: 0, + }; + } + return { + height: !isToggleTriggered.get() ? height.get() : derivedHeight.get(), + opacity: derivedOpacity.get(), + }; + }); + + return ( + + { + height.set(e.nativeEvent.layout.height); + }} + style={[styles.pAbsolute, styles.l0, styles.r0, styles.t0]} + > + {children} + + + ); +} + +Accordion.displayName = 'Accordion'; + +export default Accordion; diff --git a/src/components/Accordion/index.tsx b/src/components/Accordion/index.tsx new file mode 100644 index 000000000000..9715f3902c03 --- /dev/null +++ b/src/components/Accordion/index.tsx @@ -0,0 +1,78 @@ +import type {ReactNode} from 'react'; +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {SharedValue} from 'react-native-reanimated'; +import Animated, {Easing, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; + +type AccordionProps = { + /** Giving information whether the component is open */ + isExpanded: SharedValue; + + /** Element that is inside Accordion */ + children: ReactNode; + + /** Duration of expansion animation */ + duration?: number; + + /** Additional external style */ + style?: StyleProp; + + /** Was toggle triggered */ + isToggleTriggered: SharedValue; +}; + +function Accordion({isExpanded, children, duration = 300, isToggleTriggered, style}: AccordionProps) { + const height = useSharedValue(0); + + const derivedHeight = useDerivedValue(() => { + if (!isToggleTriggered.get()) { + return isExpanded.get() ? height.get() : 0; + } + + return withTiming(height.get() * Number(isExpanded.get()), { + duration, + easing: Easing.inOut(Easing.quad), + }); + }); + + const derivedOpacity = useDerivedValue(() => { + if (!isToggleTriggered.get()) { + return isExpanded.get() ? 1 : 0; + } + + return withTiming(isExpanded.get() ? 1 : 0, { + duration, + easing: Easing.inOut(Easing.quad), + }); + }); + + const animatedStyle = useAnimatedStyle(() => { + if (!isToggleTriggered.get() && !isExpanded.get()) { + return { + height: 0, + opacity: 0, + }; + } + + return { + height: !isToggleTriggered.get() ? undefined : derivedHeight.get(), + opacity: derivedOpacity.get(), + }; + }); + + return ( + + { + height.set(e.nativeEvent.layout.height); + }} + > + {children} + + + ); +} +Accordion.displayName = 'Accordion'; + +export default Accordion; diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index 009bccb68b73..5590561289eb 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -24,6 +24,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/AddPaymentCardForm'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; type PaymentCardFormProps = { shouldShowPaymentCardForm?: boolean; @@ -132,7 +133,7 @@ function PaymentCardForm({ currencySelectorRoute, }: PaymentCardFormProps) { const styles = useThemeStyles(); - const [data] = useOnyx(ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM); + const [data, metadata] = useOnyx(ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM); const {translate} = useLocalize(); const route = useRoute(); @@ -204,7 +205,7 @@ function PaymentCardForm({ setCardNumber(validCardNumber); }, []); - if (!shouldShowPaymentCardForm) { + if (!shouldShowPaymentCardForm || isLoadingOnyxValue(metadata)) { return null; } diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index 0feabf9b6092..d4c56c43835f 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -162,7 +162,13 @@ function AddressSearch( // Make sure that the order of keys remains such that the country is always set above the state. // Refer to https://github.com/Expensify/App/issues/15633 for more information. - const {country: countryFallbackLongName = '', state: stateAutoCompleteFallback = '', city: cityAutocompleteFallback = ''} = getPlaceAutocompleteTerms(autocompleteData?.terms ?? []); + const { + country: countryFallbackLongName = '', + state: stateAutoCompleteFallback = '', + city: cityAutocompleteFallback = '', + street: streetAutocompleteFallback = '', + streetNumber: streetNumberAutocompleteFallback = '', + } = getPlaceAutocompleteTerms(autocompleteData?.terms ?? []); const countryFallback = Object.keys(CONST.ALL_COUNTRIES).find((country) => country === countryFallbackLongName); @@ -170,7 +176,7 @@ function AddressSearch( const country = countryPrimary || countryFallback || ''; const values = { - street: `${streetNumber} ${streetName}`.trim(), + street: `${streetNumber || streetNumberAutocompleteFallback} ${streetName || streetAutocompleteFallback}`.trim(), name: details.name ?? '', // Autocomplete returns any additional valid address fragments (e.g. Apt #) as subpremise. street2: subpremise, diff --git a/src/components/AmountWithoutCurrencyInput.tsx b/src/components/AmountWithoutCurrencyInput.tsx new file mode 100644 index 000000000000..448357188b45 --- /dev/null +++ b/src/components/AmountWithoutCurrencyInput.tsx @@ -0,0 +1,73 @@ +import React, {useCallback, useMemo} from 'react'; +import type {ForwardedRef} from 'react'; +import useLocalize from '@hooks/useLocalize'; +import {replaceAllDigits, replaceCommasWithPeriod, stripSpacesFromAmount} from '@libs/MoneyRequestUtils'; +import CONST from '@src/CONST'; +import TextInput from './TextInput'; +import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; + +type AmountFormProps = { + /** Amount supplied by the FormProvider */ + value?: string; + + /** Callback to update the amount in the FormProvider */ + onInputChange?: (value: string) => void; + + /** Should we allow negative number as valid input */ + shouldAllowNegative?: boolean; +} & Partial; + +function AmountWithoutCurrencyInput( + {value: amount, shouldAllowNegative = false, inputID, name, defaultValue, accessibilityLabel, role, label, onInputChange, ...rest}: AmountFormProps, + ref: ForwardedRef, +) { + const {toLocaleDigit} = useLocalize(); + const separator = useMemo( + () => + replaceAllDigits('1.1', toLocaleDigit) + .split('') + .filter((char) => char !== '1') + .join(''), + [toLocaleDigit], + ); + /** + * Sets the selection and the amount accordingly to the value passed to the input + * @param newAmount - Changed amount from user input + */ + const setNewAmount = useCallback( + (newAmount: string) => { + // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value + // More info: https://github.com/Expensify/App/issues/16974 + const newAmountWithoutSpaces = stripSpacesFromAmount(newAmount); + const replacedCommasAmount = replaceCommasWithPeriod(newAmountWithoutSpaces); + onInputChange?.(replacedCommasAmount); + }, + [onInputChange], + ); + + return ( + + ); +} + +AmountWithoutCurrencyInput.displayName = 'AmountWithoutCurrencyForm'; + +export default React.forwardRef(AmountWithoutCurrencyInput); diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index fc5c77958635..75f9ac985966 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -38,7 +38,7 @@ function ArchivedReportFooter({report}: ArchivedReportFooterProps) { const shouldRenderHTML = archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT && archiveReason !== CONST.REPORT.ARCHIVE_REASON.BOOKING_END_DATE_HAS_PASSED; - let policyName = ReportUtils.getPolicyName(report); + let policyName = ReportUtils.getPolicyName({report}); if (archiveReason === CONST.REPORT.ARCHIVE_REASON.INVOICE_RECEIVER_POLICY_DELETED) { policyName = originalMessage?.receiverPolicyName ?? ''; diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index 74ff19f21a46..4286c6c834eb 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -133,6 +133,8 @@ type AttachmentModalProps = { canEditReceipt?: boolean; + canDeleteReceipt?: boolean; + shouldDisableSendButton?: boolean; attachmentLink?: string; @@ -157,6 +159,7 @@ function AttachmentModal({ children, fallbackSource, canEditReceipt = false, + canDeleteReceipt = false, onModalClose = () => {}, isLoading = false, shouldShowNotFoundPage = false, @@ -447,7 +450,7 @@ function AttachmentModal({ } const hasOnlyEReceipt = hasEReceipt(transaction) && !hasReceiptSource(transaction); - if (!hasOnlyEReceipt && hasReceipt(transaction) && !isReceiptBeingScanned(transaction) && canEditReceipt && !hasMissingSmartscanFields(transaction)) { + if (!hasOnlyEReceipt && hasReceipt(transaction) && !isReceiptBeingScanned(transaction) && canDeleteReceipt && !hasMissingSmartscanFields(transaction)) { menuItems.push({ icon: Expensicons.Trashcan, text: translate('receipt.deleteReceipt'), diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 98e6dd626883..e9b3b555385d 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -1,18 +1,33 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx, withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; +import { + getChatRoomSubtitle, + getDisplayNamesWithTooltips, + getIcons, + getParentNavigationSubtitle, + getReportName, + isChatThread, + isExpenseReport, + isInvoiceReport, + isIOUReport, + isMoneyRequest, + isMoneyRequestReport, + isTrackExpenseReport, + navigateToDetailsPage, + shouldReportShowSubscript, +} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Policy, Report, ReportActions} from '@src/types/onyx'; +import type {Policy, Report} from '@src/types/onyx'; import type {Icon} from '@src/types/onyx/OnyxCommon'; import CaretWrapper from './CaretWrapper'; import DisplayNames from './DisplayNames'; @@ -23,15 +38,7 @@ import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import SubscriptAvatar from './SubscriptAvatar'; import Text from './Text'; -type AvatarWithDisplayNamePropsWithOnyx = { - /** All of the actions of the report */ - parentReportActions: OnyxEntry; - - /** Personal details of all users */ - personalDetails: OnyxEntry; -}; - -type AvatarWithDisplayNameProps = AvatarWithDisplayNamePropsWithOnyx & { +type AvatarWithDisplayNameProps = { /** The report currently being looked at */ report: OnyxEntry; @@ -55,41 +62,38 @@ const fallbackIcon: Icon = { id: -1, }; -function AvatarWithDisplayName({ - policy, - report, - parentReportActions, - isAnonymous = false, - size = CONST.AVATAR_SIZE.DEFAULT, - shouldEnableDetailPageNavigation = false, - personalDetails = CONST.EMPTY_OBJECT, -}: AvatarWithDisplayNameProps) { +function AvatarWithDisplayName({policy, report, isAnonymous = false, size = CONST.AVATAR_SIZE.DEFAULT, shouldEnableDetailPageNavigation = false}: AvatarWithDisplayNameProps) { + const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`, {canEvict: false}); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST) ?? CONST.EMPTY_OBJECT; + const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`); const [invoiceReceiverPolicy] = useOnyx( - `${ONYXKEYS.COLLECTION.POLICY}${parentReport?.invoiceReceiver && 'policyID' in parentReport.invoiceReceiver ? parentReport.invoiceReceiver.policyID : -1}`, + `${ONYXKEYS.COLLECTION.POLICY}${parentReport?.invoiceReceiver && 'policyID' in parentReport.invoiceReceiver ? parentReport.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`, ); - const title = ReportUtils.getReportName(report, undefined, undefined, undefined, invoiceReceiverPolicy); - const subtitle = ReportUtils.getChatRoomSubtitle(report); - const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); - const isMoneyRequestOrReport = - ReportUtils.isMoneyRequestReport(report) || ReportUtils.isMoneyRequest(report) || ReportUtils.isTrackExpenseReport(report) || ReportUtils.isInvoiceReport(report); - const icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy, invoiceReceiverPolicy); - const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails), false); - const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(report); + const title = getReportName(report, undefined, undefined, undefined, invoiceReceiverPolicy); + const subtitle = getChatRoomSubtitle(report, {isCreateExpenseFlow: true}); + const parentNavigationSubtitleData = getParentNavigationSubtitle(report); + const isMoneyRequestOrReport = isMoneyRequestReport(report) || isMoneyRequest(report) || isTrackExpenseReport(report) || isInvoiceReport(report); + const icons = getIcons(report, personalDetails, null, '', -1, policy, invoiceReceiverPolicy); + const ownerPersonalDetails = getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); + const displayNamesWithTooltips = getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails), false); + const shouldShowSubscriptAvatar = shouldReportShowSubscript(report); const avatarBorderColor = isAnonymous ? theme.highlightBG : theme.componentBG; const actorAccountID = useRef(null); useEffect(() => { - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; - actorAccountID.current = parentReportAction?.actorAccountID ?? -1; + if (!report?.parentReportActionID) { + return; + } + const parentReportAction = parentReportActions?.[report?.parentReportActionID]; + actorAccountID.current = parentReportAction?.actorAccountID ?? CONST.DEFAULT_NUMBER_ID; }, [parentReportActions, report]); const goToDetailsPage = useCallback(() => { - ReportUtils.navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute()); + navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute()); }, [report]); const showActorDetails = useCallback(() => { @@ -99,17 +103,17 @@ function AvatarWithDisplayName({ return; } - if (ReportUtils.isExpenseReport(report) && report?.ownerAccountID) { + if (isExpenseReport(report) && report?.ownerAccountID) { Navigation.navigate(ROUTES.PROFILE.getRoute(report.ownerAccountID)); return; } - if (ReportUtils.isIOUReport(report) && report?.reportID) { + if (isIOUReport(report) && report?.reportID) { Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(report.reportID)); return; } - if (ReportUtils.isChatThread(report)) { + if (isChatThread(report)) { // In an ideal situation account ID won't be 0 if (actorAccountID.current && actorAccountID.current > 0) { Navigation.navigate(ROUTES.PROFILE.getRoute(actorAccountID.current)); @@ -198,12 +202,4 @@ function AvatarWithDisplayName({ AvatarWithDisplayName.displayName = 'AvatarWithDisplayName'; -export default withOnyx({ - parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '-1'}`, - canEvict: false, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, -})(AvatarWithDisplayName); +export default AvatarWithDisplayName; diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx index ad1a659e6d9f..7c4ce77c8a99 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.tsx +++ b/src/components/BlockingViews/FullPageNotFoundView.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import type {StyleProp, TextStyle} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; import useLocalize from '@hooks/useLocalize'; @@ -43,6 +44,12 @@ type FullPageNotFoundViewProps = { /** Whether we should force the full page view */ shouldForceFullScreen?: boolean; + + /** The style of the subtitle message */ + subtitleStyle?: StyleProp; + + /** Whether we should display the button that opens new SearchRouter */ + shouldDisplaySearchRouter?: boolean; }; // eslint-disable-next-line rulesdir/no-negated-variables @@ -58,6 +65,8 @@ function FullPageNotFoundView({ shouldShowBackButton = true, onLinkPress = () => Navigation.dismissModal(), shouldForceFullScreen = false, + subtitleStyle, + shouldDisplaySearchRouter, }: FullPageNotFoundViewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -68,6 +77,7 @@ function FullPageNotFoundView({ diff --git a/src/components/BookTravelButton.tsx b/src/components/BookTravelButton.tsx new file mode 100644 index 000000000000..edffb8315808 --- /dev/null +++ b/src/components/BookTravelButton.tsx @@ -0,0 +1,125 @@ +import {Str} from 'expensify-common'; +import React, {useCallback, useContext, useState} from 'react'; +import {NativeModules} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {openTravelDotLink} from '@libs/actions/Link'; +import {cleanupTravelProvisioningSession} from '@libs/actions/Travel'; +import Log from '@libs/Log'; +import Navigation from '@libs/Navigation/Navigation'; +import {getAdminsPrivateEmailDomains, isPaidGroupPolicy} from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import Button from './Button'; +import CustomStatusBarAndBackgroundContext from './CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext'; +import DotIndicatorMessage from './DotIndicatorMessage'; + +type BookTravelButtonProps = { + text: string; +}; + +const navigateToAcceptTerms = (domain: string) => { + // Remove the previous provision session infromation if any is cached. + cleanupTravelProvisioningSession(); + Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain)); +}; + +function BookTravelButton({text}: BookTravelButtonProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); + const policy = usePolicy(activePolicyID); + const [errorMessage, setErrorMessage] = useState(''); + const [travelSettings] = useOnyx(ONYXKEYS.NVP_TRAVEL_SETTINGS); + const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const primaryLogin = account?.primaryLogin; + const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext); + + // Flag indicating whether NewDot was launched exclusively for Travel, + // e.g., when the user selects "Trips" from the Expensify Classic menu in HybridApp. + const [wasNewDotLaunchedJustForTravel] = useOnyx(ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY); + + const bookATrip = useCallback(() => { + setErrorMessage(''); + + // The primary login of the user is where Spotnana sends the emails with booking confirmations, itinerary etc. It can't be a phone number. + if (!primaryLogin || Str.isSMSLogin(primaryLogin)) { + setErrorMessage(translate('travel.phoneError')); + return; + } + + if (!isPaidGroupPolicy(policy)) { + Navigation.navigate(ROUTES.TRAVEL_UPGRADE); + return; + } + + // Spotnana requires an address anytime an entity is created for a policy + if (isEmptyObject(policy?.address)) { + Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(policy?.id, Navigation.getActiveRoute())); + return; + } + + const isPolicyProvisioned = policy?.travelSettings?.spotnanaCompanyID ?? policy?.travelSettings?.associatedTravelDomainAccountID; + if (policy?.travelSettings?.hasAcceptedTerms ?? (travelSettings?.hasAcceptedTerms && isPolicyProvisioned)) { + openTravelDotLink(policy?.id) + ?.then(() => { + // When a user selects "Trips" in the Expensify Classic menu, the HybridApp opens the ManageTrips page in NewDot. + // The wasNewDotLaunchedJustForTravel flag indicates if NewDot was launched solely for this purpose. + if (!NativeModules.HybridAppModule || !wasNewDotLaunchedJustForTravel) { + return; + } + + // Close NewDot if it was opened only for Travel, as its purpose is now fulfilled. + Log.info('[HybridApp] Returning to OldDot after opening TravelDot'); + NativeModules.HybridAppModule.closeReactNativeApp(false, false); + setRootStatusBarEnabled(false); + }) + ?.catch(() => { + setErrorMessage(translate('travel.errorMessage')); + }); + } else if (isPolicyProvisioned) { + navigateToAcceptTerms(CONST.TRAVEL.DEFAULT_DOMAIN); + } else { + // Determine the domain to associate with the workspace during provisioning in Spotnana. + // - If all admins share the same private domain, the workspace is tied to it automatically. + // - If admins have multiple private domains, the user must select one. + // - Public domains are not allowed; an error page is shown in that case. + const adminDomains = getAdminsPrivateEmailDomains(policy); + if (adminDomains.length === 0) { + Navigation.navigate(ROUTES.TRAVEL_PUBLIC_DOMAIN_ERROR); + } else if (adminDomains.length === 1) { + navigateToAcceptTerms(adminDomains.at(0) ?? CONST.TRAVEL.DEFAULT_DOMAIN); + } else { + Navigation.navigate(ROUTES.TRAVEL_DOMAIN_SELECTOR); + } + } + }, [policy, wasNewDotLaunchedJustForTravel, travelSettings, translate, primaryLogin, setRootStatusBarEnabled]); + + return ( + <> + {!!errorMessage && ( + + )} +