diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 7bdf2592bb99..a242c7a52479 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -5,7 +5,7 @@ self-hosted-runner: - macos-15-large - macos-15-xlarge - macos-12 - - ubuntu-20.04-v4 + - ubuntu-24.04-v4 paths: '**/*': diff --git a/.github/actions/javascript/postTestBuildComment/action.yml b/.github/actions/javascript/postTestBuildComment/action.yml index 538a8f9bfe3a..1e0ae76baace 100644 --- a/.github/actions/javascript/postTestBuildComment/action.yml +++ b/.github/actions/javascript/postTestBuildComment/action.yml @@ -13,24 +13,36 @@ inputs: ANDROID: description: "Android job result ('success', 'failure', 'cancelled', or 'skipped')" required: false + ANDROID_HYBRID: + description: "Android Hybrid job result ('success', 'failure', 'cancelled', or 'skipped')" + required: false DESKTOP: description: "Desktop job result ('success', 'failure', 'cancelled', or 'skipped')" required: false IOS: description: "iOS job result ('success', 'failure', 'cancelled', or 'skipped')" required: false + IOS_Hybrid: + description: "iOS Hybrid job result ('success', 'failure', 'cancelled', or 'skipped')" + required: false WEB: description: "Web job result ('success', 'failure', 'cancelled', or 'skipped')" required: false ANDROID_LINK: description: "Link for the Android build" required: false + ANDROID_HYBRID_LINK: + description: "Link for the Android Hybrid build" + required: false DESKTOP_LINK: description: "Link for the desktop build" required: false IOS_LINK: description: "Link for the iOS build" required: false + IOS_HYBRID_LINK: + description: "Link for the iOS Hybrid build" + required: false WEB_LINK: description: "Link for the web build" required: false diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index d30aa17886e4..98293b84a2b4 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -11502,12 +11502,14 @@ const github_1 = __nccwpck_require__(5438); const CONST_1 = __importDefault(__nccwpck_require__(9873)); const GithubUtils_1 = __importDefault(__nccwpck_require__(9296)); function getTestBuildMessage() { - const inputs = ['ANDROID', 'DESKTOP', 'IOS', 'WEB']; + const inputs = ['ANDROID', 'ANDROID_HYBRID', 'DESKTOP', 'IOS', 'IOS_HYBRID', 'WEB']; const names = { [inputs[0]]: 'Android', - [inputs[1]]: 'Desktop', - [inputs[2]]: 'iOS', - [inputs[3]]: 'Web', + [inputs[1]]: 'Android Hybrid', + [inputs[2]]: 'Desktop', + [inputs[3]]: 'iOS', + [inputs[4]]: 'iOS Hybrid', + [inputs[5]]: 'Web', }; const result = inputs.reduce((acc, platform) => { const input = core.getInput(platform, { required: false }); @@ -11531,6 +11533,9 @@ function getTestBuildMessage() { | ------------- | ------------- | | ${result.ANDROID.link} | ${result.IOS.link} | | ${result.ANDROID.qrCode} | ${result.IOS.qrCode} | +| Android Hybrid :robot::arrows_counterclockwise: | iOS Hybrid :apple::arrows_counterclockwise: | +| ${result.ANDROID_HYBRID.link} | ${result.IOS_HYBRID.link} | +| ${result.ANDROID_HYBRID.qrCode} | ${result.IOS_HYBRID.qrCode} | | Desktop :computer: | Web :spider_web: | | ${result.DESKTOP.link} | ${result.WEB.link} | | ${result.DESKTOP.qrCode} | ${result.WEB.qrCode} | diff --git a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts index 4a49eaf67eb1..9d1e68164c7e 100644 --- a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts +++ b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts @@ -5,12 +5,14 @@ import CONST from '@github/libs/CONST'; import GithubUtils from '@github/libs/GithubUtils'; function getTestBuildMessage(): string { - const inputs = ['ANDROID', 'DESKTOP', 'IOS', 'WEB'] as const; + const inputs = ['ANDROID', 'ANDROID_HYBRID', 'DESKTOP', 'IOS', 'IOS_HYBRID', 'WEB'] as const; const names = { [inputs[0]]: 'Android', - [inputs[1]]: 'Desktop', - [inputs[2]]: 'iOS', - [inputs[3]]: 'Web', + [inputs[1]]: 'Android Hybrid', + [inputs[2]]: 'Desktop', + [inputs[3]]: 'iOS', + [inputs[4]]: 'iOS Hybrid', + [inputs[5]]: 'Web', }; const result = inputs.reduce((acc, platform) => { @@ -40,6 +42,9 @@ function getTestBuildMessage(): string { | ------------- | ------------- | | ${result.ANDROID.link} | ${result.IOS.link} | | ${result.ANDROID.qrCode} | ${result.IOS.qrCode} | +| Android Hybrid :robot::arrows_counterclockwise: | iOS Hybrid :apple::arrows_counterclockwise: | +| ${result.ANDROID_HYBRID.link} | ${result.IOS_HYBRID.link} | +| ${result.ANDROID_HYBRID.qrCode} | ${result.IOS_HYBRID.qrCode} | | Desktop :computer: | Web :spider_web: | | ${result.DESKTOP.link} | ${result.WEB.link} | | ${result.DESKTOP.qrCode} | ${result.WEB.qrCode} | diff --git a/.github/workflows/authorChecklist.yml b/.github/workflows/authorChecklist.yml index 2bc94cffefd8..3118d8ee9c18 100644 --- a/.github/workflows/authorChecklist.yml +++ b/.github/workflows/authorChecklist.yml @@ -5,6 +5,7 @@ on: # Warning: – when using the pull_request_target event, DO NOT checkout code from an untrusted branch: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ pull_request_target: types: [opened, edited, reopened, synchronize] + branches: [main] jobs: # Note: PHP specifically looks for the name of this job, "checklist", so if the name of the job is changed, diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml index b2a55876e19b..37990320aa08 100644 --- a/.github/workflows/cherryPick.yml +++ b/.github/workflows/cherryPick.yml @@ -95,34 +95,42 @@ jobs: if: fromJSON(steps.cherryPick.outputs.HAS_CONFLICTS) id: createPullRequest 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 🍒 - 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: + AUTHOR_CHECKLIST=$(sed -n '/### PR Author Checklist/,$p' .github/PULL_REQUEST_TEMPLATE.md) + + PR_DESCRIPTION=$(cat <> "$GITHUB_OUTPUT" - else - echo "::error::❌ PR ${{ env.PULL_REQUEST_NUMBER }} does not have 'Ready to Build' label" - echo "READY_TO_BUILD=false" >> "$GITHUB_OUTPUT" - exit 1 - fi - env: - GITHUB_TOKEN: ${{ github.token }} - - name: Check if pull request number is correct if: ${{ github.event_name == 'workflow_dispatch' }} id: getHeadRef @@ -64,13 +69,14 @@ jobs: buildAndroid: name: Build Android app for testing + if: ${{ inputs.ANDROID }} uses: ./.github/workflows/buildAndroid.yml needs: [prep] secrets: inherit with: type: adhoc ref: ${{ needs.prep.outputs.REF }} - pull_request_number: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} + pull_request_number: ${{ github.event.inputs.PULL_REQUEST_NUMBER }} uploadAndroid: name: Upload Android app to S3 @@ -118,6 +124,7 @@ jobs: iOS: name: Build and deploy iOS for testing + if: ${{ inputs.IOS }} needs: [prep] env: DEVELOPER_DIR: /Applications/Xcode_16.2.0.app/Contents/Developer @@ -206,6 +213,7 @@ jobs: desktop: name: Build and deploy Desktop for testing + if: ${{ inputs.DESKTOP }} needs: [prep] runs-on: macos-14-large steps: @@ -256,6 +264,7 @@ jobs: web: name: Build and deploy Web + if: ${{ inputs.WEB }} needs: [prep] runs-on: ubuntu-latest-xl steps: @@ -286,11 +295,21 @@ jobs: - name: Deploy to S3 for internal testing run: aws s3 cp --recursive --acl public-read "$GITHUB_WORKSPACE"/dist s3://ad-hoc-expensify-cash/web/"$PULL_REQUEST_NUMBER" + buildHybridApps: + name: Build hybrid adhoc apps + uses: ./.github/workflows/testBuildHybrid.yml + needs: [prep] + secrets: inherit + with: + APP_REF: ${{ needs.prep.outputs.REF }} + IOS_HYBRID: ${{ inputs.IOS_HYBRID }} + ANDROID_HYBRID: ${{ inputs.ANDROID_HYBRID }} + postGithubComment: runs-on: ubuntu-latest + if: always() name: Post a GitHub comment with app download links for testing - needs: [prep, uploadAndroid, iOS, desktop, web] - if: ${{ always() && needs.prep.outputs.READY_TO_BUILD == 'true' }} + needs: [prep, uploadAndroid, iOS, desktop, web, buildHybridApps] steps: - name: Checkout uses: actions/checkout@v4 @@ -314,13 +333,17 @@ jobs: - name: Publish links to apps for download uses: ./.github/actions/javascript/postTestBuildComment with: - PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} + PR_NUMBER: ${{ github.event.inputs.PULL_REQUEST_NUMBER }} GITHUB_TOKEN: ${{ github.token }} ANDROID: ${{ needs.uploadAndroid.result }} + ANDROID_HYBRID: ${{ needs.buildHybridApps.result }} DESKTOP: ${{ needs.desktop.result }} IOS: ${{ needs.iOS.result }} + IOS_HYBRID: ${{ needs.buildHybridApps.result }} WEB: ${{ needs.web.result }} ANDROID_LINK: ${{ needs.uploadAndroid.outputs.S3_APK_PATH }} - DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ env.PULL_REQUEST_NUMBER }}/NewExpensify.dmg + ANDROID_HYBRID_LINK: ${{ needs.buildHybridApps.outputs.ANDROID_LINK }} + DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ github.event.inputs.PULL_REQUEST_NUMBER }}/NewExpensify.dmg IOS_LINK: ${{ steps.get_ios_path.outputs.ios_path }} - WEB_LINK: https://${{ env.PULL_REQUEST_NUMBER }}.pr-testing.expensify.com + IOS_HYBRID_LINK: ${{ needs.buildHybridApps.outputs.IOS_LINK }} + WEB_LINK: https://${{ github.event.inputs.PULL_REQUEST_NUMBER }}.pr-testing.expensify.com diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index 3f3b8c147671..8a165cc4e988 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -11,6 +11,27 @@ on: description: Pull Request number from Mobile-Expensify repo for correct placement of OD app. It will take precedence over MOBILE-EXPENSIFY from App's PR description if both specified. If nothing is specified defaults to Mobile-Expensify's main required: false default: '' + workflow_call: + inputs: + APP_REF: + description: Git ref to checkout in App + type: string + required: true + IOS_HYBRID: + description: Should build iOS app? + type: boolean + default: true + ANDROID_HYBRID: + description: Should build Android app? + type: boolean + default: true + + outputs: + ANDROID_LINK: + value: ${{ jobs.androidHybrid.outputs.S3_APK_PATH }} + IOS_LINK: + value: ${{ jobs.iosHybrid.outputs.IOS_PATH }} + env: # This variable is needed for fastlane to construct correct path @@ -24,6 +45,7 @@ jobs: uses: actions/checkout@v4 - name: Validate that user is an Expensify employee + if: ${{ github.event_name == 'workflow_dispatch' }} uses: ./.github/actions/composite/validateActor with: REQUIRE_APP_DEPLOYER: false @@ -31,6 +53,10 @@ jobs: - name: Validate input run: | + if [[ "${{ github.event_name }}" == "workflow_call" ]]; then + exit 0 + fi + if [[ -z "${{ github.event.inputs.APP_PULL_REQUEST_NUMBER }}" && -z "${{ github.event.inputs.HYBRIDAPP_PULL_REQUEST_NUMBER }}" ]]; then echo "Invalid input. You have to pass at least one PR number" exit 1 @@ -49,6 +75,12 @@ jobs: id: getHeadRef run: | set -e + + if [[ "${{ github.event_name }}" == "workflow_call " ]]; then + echo "REF=${{ inputs.APP_REF }}" >> "$GITHUB_OUTPUT" + exit 0 + fi + if [[ -z "${{ github.event.inputs.APP_PULL_REQUEST_NUMBER }}" ]]; then echo "REF=" >> "$GITHUB_OUTPUT" else @@ -120,6 +152,7 @@ jobs: androidHybrid: name: Build Android HybridApp + if: ${{ github.event_name == 'workflow_dispatch' || inputs.ANDROID_HYBRID }} needs: [getNewDotRef, getOldDotPR, getOldDotRef] runs-on: ubuntu-latest-xl outputs: @@ -235,10 +268,13 @@ jobs: iosHybrid: name: Build and deploy iOS for testing + if: ${{ github.event_name == 'workflow_dispatch' || inputs.IOS_HYBRID }} needs: [getNewDotRef, getOldDotPR, getOldDotRef] env: DEVELOPER_DIR: /Applications/Xcode_16.2.0.app/Contents/Developer runs-on: macos-15-xlarge + outputs: + IOS_PATH: ${{ steps.export-ios-path.outputs.IOS_PATH }} steps: - name: Checkout uses: actions/checkout@v4 @@ -336,15 +372,20 @@ jobs: S3_BUCKET: ad-hoc-expensify-cash S3_REGION: us-east-1 - - name: Upload Artifact - uses: actions/upload-artifact@v4 - with: - name: ios - path: ./ios_paths.json + - name: Export iOS path + id: export-ios-path + run: | + content_ios="$(cat ./ios_paths.json)" + content_ios="${content_ios//'%'/'%25'}" + content_ios="${content_ios//$'\n'/'%0A'}" + content_ios="${content_ios//$'\r'/'%0D'}" + ios_path=$(echo "$content_ios" | jq -r '.html_path') + echo "IOS_PATH=$ios_path" >> "$GITHUB_OUTPUT" postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing + if: github.event_name == 'workflow_dispatch' needs: [getNewDotRef, getOldDotPR, androidHybrid, iosHybrid] steps: - name: Checkout @@ -352,20 +393,6 @@ jobs: with: ref: ${{ needs.getNewDotRef.outputs.REF }} - - name: Download Artifact - uses: actions/download-artifact@v4 - - - name: Read JSONs with iOS paths - id: get_ios_path - if: ${{ needs.iosHybrid.result == 'success' }} - run: | - content_ios="$(cat ./ios/ios_paths.json)" - content_ios="${content_ios//'%'/'%25'}" - content_ios="${content_ios//$'\n'/'%0A'}" - content_ios="${content_ios//$'\r'/'%0D'}" - ios_path=$(echo "$content_ios" | jq -r '.html_path') - echo "ios_path=$ios_path" >> "$GITHUB_OUTPUT" - - name: Publish links to apps for download on NewDot PR if: github.event.inputs.APP_PULL_REQUEST_NUMBER != '' uses: ./.github/actions/javascript/postTestBuildComment @@ -376,7 +403,7 @@ jobs: ANDROID: ${{ needs.androidHybrid.result }} IOS: ${{ needs.iosHybrid.result }} ANDROID_LINK: ${{ needs.androidHybrid.outputs.S3_APK_PATH }} - IOS_LINK: ${{ steps.get_ios_path.outputs.ios_path }} + IOS_LINK: ${{ needs.iosHybrid.outputs.IOS_PATH }} - name: Publish links to apps for download on OldDot PR if: needs.getOldDotPR.outputs.OLD_DOT_PR != '' @@ -388,4 +415,4 @@ jobs: ANDROID: ${{ needs.androidHybrid.result }} IOS: ${{ needs.iosHybrid.result }} ANDROID_LINK: ${{ needs.androidHybrid.outputs.S3_APK_PATH }} - IOS_LINK: ${{ steps.get_ios_path.outputs.ios_path }} + IOS_LINK: ${{ needs.iosHybrid.outputs.IOS_PATH }} diff --git a/.github/workflows/verifyHybridApp.yml b/.github/workflows/verifyHybridApp.yml index 876d3b1834fb..69b0c329947f 100644 --- a/.github/workflows/verifyHybridApp.yml +++ b/.github/workflows/verifyHybridApp.yml @@ -19,7 +19,7 @@ on: - 'ios/project.pbxproj' pull_request_target: types: [opened, synchronize] - branches-ignore: [staging, production] + branches: [main] paths: - '**.kt' - '**.java' @@ -52,7 +52,7 @@ jobs: - 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:" + ":warning: This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. :warning:" env: GITHUB_TOKEN: ${{ github.token }} verify_android: diff --git a/.gitignore b/.gitignore index 430962a17709..459d8b1d1271 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,9 @@ android/app/src/main/java/com/expensify/chat/generated/ # Vscode .vscode +# Fleet +.fleet + # node.js # node_modules/ diff --git a/Mobile-Expensify b/Mobile-Expensify index 80f59d5dc24a..20e1d9c9ddb8 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 80f59d5dc24a2159897f814c8017eff313970873 +Subproject commit 20e1d9c9ddb828c40c07e9ca28f2eb29820adde9 diff --git a/README.md b/README.md index ee32c9a1c8e7..833ccc7f43ed 100644 --- a/README.md +++ b/README.md @@ -849,3 +849,14 @@ In order to compile a production iOS build, run `npm run ios-build`, this will g #### Local production build the Android app To build an APK to share run (e.g. via Slack), run `npm run android-build`, this will generate a new APK in the `android/app` folder. + +# Onyx derived values +Onyx derived values are special Onyx keys which contain values derived from other Onyx values. These are available as a performance optimization, so that if the result of a common computation of Onyx values is needed in many places across the app, the computation can be done only as needed in a centralized location, and then shared across the app. Once created, Onyx derived values are stored and consumed just like any other Onyx value. + +## Creating new Onyx derived values +1. Add the new Onyx key. The keys for Onyx derived values are stored in `ONYXKEYS.ts`, in the `ONYXKEYS.DERIVED` object. +2. Declare the type for the derived value in `ONYXKEYS.ts`, in the `OnyxDerivedValuesMapping` type. +3. Add the derived value config to `ONYX_DERIVED_VALUES` in `src/libs/OnyxDerived.ts`. A derived value config is defined by: + 1. The Onyx key for the derived value + 2. An array of dependent Onyx keys (which can be any keys, not including the one from the previous step. Including other derived values!) + 3. A `compute` function, which takes an array of dependent Onyx values (in the same order as the array of keys from the previous step), and returns a value matching the type you declared in `OnyxDerivedValuesMapping` diff --git a/__mocks__/Illustrations.ts b/__mocks__/Illustrations.ts new file mode 100644 index 000000000000..8b2b033a8ff8 --- /dev/null +++ b/__mocks__/Illustrations.ts @@ -0,0 +1,307 @@ +const Abracadabra = 'Abracadabra'; +const BankArrowPink = 'BankArrowPink'; +const BankMouseGreen = 'BankMouseGreen'; +const BankUserGreen = 'BankUserGreen'; +const BigRocket = 'BigRocket'; +const BrokenMagnifyingGlass = 'BrokenMagnifyingGlass'; +const ChatBubbles = 'ChatBubbles'; +const CoffeeMug = 'CoffeeMug'; +const ConciergeBlue = 'ConciergeBlue'; +const ConciergeExclamation = 'ConciergeExclamation'; +const CreditCardsBlue = 'CreditCardsBlue'; +const EmailAddress = 'EmailAddress'; +const EmptyCardState = 'EmptyCardState'; +const EmptyStateExpenses = 'EmptyStateExpenses'; +const EnvelopeReceipt = 'EnvelopeReceipt'; +const FolderOpen = 'FolderOpen'; +const HandCard = 'HandCard'; +const HotDogStand = 'HotDogStand'; +const InvoiceOrange = 'InvoiceOrange'; +const JewelBoxBlue = 'JewelBoxBlue'; +const JewelBoxGreen = 'JewelBoxGreen'; +const PaymentHands = 'PaymentHands'; +const JewelBoxPink = 'JewelBoxPink'; +const JewelBoxYellow = 'JewelBoxYellow'; +const MagicCode = 'MagicCode'; +const Mailbox = 'Mailbox'; +const MoneyEnvelopeBlue = 'MoneyEnvelopeBlue'; +const MoneyMousePink = 'MoneyMousePink'; +const MushroomTopHat = 'MushroomTopHat'; +const ReceiptsSearchYellow = 'ReceiptsSearchYellow'; +const ReceiptYellow = 'ReceiptYellow'; +const ReceiptWrangler = 'ReceiptWrangler'; +const RocketBlue = 'RocketBlue'; +const RocketOrange = 'RocketOrange'; +const SanFrancisco = 'SanFrancisco'; +const SafeBlue = 'SafeBlue'; +const SmallRocket = 'SmallRocket'; +const TadaYellow = 'TadaYellow'; +const TadaBlue = 'TadaBlue'; +const ToddBehindCloud = 'ToddBehindCloud'; +const ToddWithPhones = 'ToddWithPhones'; +const GpsTrackOrange = 'GpsTrackOrange'; +const ShieldYellow = 'ShieldYellow'; +const MoneyReceipts = 'MoneyReceipts'; +const PinkBill = 'PinkBill'; +const CreditCardsNew = 'CreditCardsNew'; +const CreditCardsNewGreen = 'CreditCardsNewGreen'; +const InvoiceBlue = 'InvoiceBlue'; +const LaptopwithSecondScreenandHourglass = 'LaptopwithSecondScreenandHourglass'; +const LockOpen = 'LockOpen'; +const Luggage = 'Luggage'; +const MoneyIntoWallet = 'MoneyIntoWallet'; +const MoneyWings = 'MoneyWings'; +const OpenSafe = 'OpenSafe'; +const TrackShoe = 'TrackShoe'; +const BankArrow = 'BankArrow'; +const ConciergeBubble = 'ConciergeBubble'; +const ConciergeNew = 'ConciergeNew'; +const MoneyBadge = 'MoneyBadge'; +const TreasureChest = 'TreasureChest'; +const ThumbsUpStars = 'ThumbsUpStars'; +const Hands = 'Hands'; +const HandEarth = 'HandEarth'; +const SmartScan = 'SmartScan'; +const Hourglass = 'Hourglass'; +const CommentBubbles = 'CommentBubbles'; +const CommentBubblesBlue = 'CommentBubblesBlue'; +const TrashCan = 'TrashCan'; +const TeleScope = 'TeleScope'; +const Profile = 'Profile'; +const Puzzle = 'Puzzle'; +const PalmTree = 'PalmTree'; +const LockClosed = 'LockClosed'; +const Gears = 'Gears'; +const QRCode = 'QRCode'; +const RealtimeReport = 'RealtimeReport'; +const HoldExpense = 'HoldExpense'; +const ReceiptEnvelope = 'ReceiptEnvelope'; +const Approval = 'Approval'; +const WalletAlt = 'WalletAlt'; +const Workflows = 'Workflows'; +const PendingBank = 'PendingBank'; +const ThreeLeggedLaptopWoman = 'ThreeLeggedLaptopWoman'; +const House = 'House'; +const Building = 'Building'; +const Buildings = 'Buildings'; +const Alert = 'Alert'; +const TeachersUnite = 'TeachersUnite'; +const Abacus = 'Abacus'; +const Binoculars = 'Binoculars'; +const CompanyCard = 'CompanyCard'; +const ReceiptUpload = 'ReceiptUpload'; +const ExpensifyCardIllustration = 'ExpensifyCardIllustration'; +const SplitBill = 'SplitBill'; +const PiggyBank = 'PiggyBank'; +const Pillow = 'Pillow'; +const Accounting = 'Accounting'; +const Car = 'Car'; +const Coins = 'Coins'; +const Pencil = 'Pencil'; +const Tag = 'Tag'; +const CarIce = 'CarIce'; +const ReceiptLocationMarker = 'ReceiptLocationMarker'; +const Lightbulb = 'Lightbulb'; +const EmptyStateTravel = 'EmptyStateTravel'; +const Stopwatch = 'Stopwatch'; +const SubscriptionAnnual = 'SubscriptionAnnual'; +const SubscriptionPPU = 'SubscriptionPPU'; +const ExpensifyApprovedLogo = 'ExpensifyApprovedLogo'; +const ExpensifyApprovedLogoLight = 'ExpensifyApprovedLogoLight'; +const SendMoney = 'SendMoney'; +const CheckmarkCircle = 'CheckmarkCircle'; +const CreditCardEyes = 'CreditCardEyes'; +const LockClosedOrange = 'LockClosedOrange'; +const EmptyState = 'EmptyState'; +const FolderWithPapers = 'FolderWithPapers'; +const VirtualCard = 'VirtualCard'; +const Tire = 'Tire'; +const BigVault = 'BigVault'; +const Filters = 'Filters'; +const MagnifyingGlassMoney = 'MagnifyingGlassMoney'; +const Rules = 'Rules'; +const CompanyCardsEmptyState = 'CompanyCardsEmptyState'; +const AmexCompanyCards = 'AmexCompanyCards'; +const MasterCardCompanyCards = 'MasterCardCompanyCards'; +const VisaCompanyCards = 'VisaCompanyCards'; +const CompanyCardsPendingState = 'CompanyCardsPendingState'; +const VisaCompanyCardDetail = 'VisaCompanyCardDetail'; +const MasterCardCompanyCardDetail = 'MasterCardCompanyCardDetail'; +const AmexCardCompanyCardDetail = 'AmexCardCompanyCardDetail'; +const TurtleInShell = 'TurtleInShell'; +const BankOfAmericaCompanyCardDetail = 'BankOfAmericaCompanyCardDetail'; +const BrexCompanyCardDetail = 'BrexCompanyCardDetail'; +const CapitalOneCompanyCardDetail = 'CapitalOneCompanyCardDetail'; +const ChaseCompanyCardDetail = 'ChaseCompanyCardDetail'; +const CitibankCompanyCardDetail = 'CitibankCompanyCardDetail'; +const StripeCompanyCardDetail = 'StripeCompanyCardDetail'; +const WellsFargoCompanyCardDetail = 'WellsFargoCompanyCardDetail'; +const PerDiem = 'PerDiem'; +const AmexCardCompanyCardDetailLarge = 'AmexCardCompanyCardDetailLarge'; +const BankOfAmericaCompanyCardDetailLarge = 'BankOfAmericaCompanyCardDetailLarge'; +const BrexCompanyCardDetailLarge = 'BrexCompanyCardDetailLarge'; +const CapitalOneCompanyCardDetailLarge = 'CapitalOneCompanyCardDetailLarge'; +const ChaseCompanyCardDetailLarge = 'ChaseCompanyCardDetailLarge'; +const CitibankCompanyCardDetailLarge = 'CitibankCompanyCardDetailLarge'; +const MasterCardCompanyCardDetailLarge = 'MasterCardCompanyCardDetailLarge'; +const StripeCompanyCardDetailLarge = 'StripeCompanyCardDetailLarge'; +const VisaCompanyCardDetailLarge = 'VisaCompanyCardDetailLarge'; +const WellsFargoCompanyCardDetailLarge = 'WellsFargoCompanyCardDetailLarge'; +const Flash = 'Flash'; +const ExpensifyMobileApp = 'ExpensifyMobileApp'; +const ReportReceipt = 'ReportReceipt'; + +export { + Abracadabra, + BankArrowPink, + BankMouseGreen, + BankUserGreen, + BigRocket, + BrokenMagnifyingGlass, + ChatBubbles, + CoffeeMug, + ConciergeBlue, + ConciergeExclamation, + CreditCardsBlue, + EmailAddress, + EmptyCardState, + EmptyStateExpenses, + EnvelopeReceipt, + FolderOpen, + HandCard, + HotDogStand, + InvoiceOrange, + JewelBoxBlue, + JewelBoxGreen, + PaymentHands, + JewelBoxPink, + JewelBoxYellow, + MagicCode, + Mailbox, + MoneyEnvelopeBlue, + MoneyMousePink, + MushroomTopHat, + ReceiptsSearchYellow, + ReceiptYellow, + ReceiptWrangler, + RocketBlue, + RocketOrange, + SanFrancisco, + SafeBlue, + SmallRocket, + TadaYellow, + TadaBlue, + ToddBehindCloud, + ToddWithPhones, + GpsTrackOrange, + ShieldYellow, + MoneyReceipts, + PinkBill, + CreditCardsNew, + CreditCardsNewGreen, + InvoiceBlue, + LaptopwithSecondScreenandHourglass, + LockOpen, + Luggage, + MoneyIntoWallet, + MoneyWings, + OpenSafe, + TrackShoe, + BankArrow, + ConciergeBubble, + ConciergeNew, + MoneyBadge, + TreasureChest, + ThumbsUpStars, + Hands, + HandEarth, + SmartScan, + Hourglass, + CommentBubbles, + CommentBubblesBlue, + TrashCan, + TeleScope, + Profile, + Puzzle, + PalmTree, + LockClosed, + Gears, + QRCode, + RealtimeReport, + HoldExpense, + ReceiptEnvelope, + Approval, + WalletAlt, + Workflows, + PendingBank, + ThreeLeggedLaptopWoman, + House, + Building, + Buildings, + Alert, + TeachersUnite, + Abacus, + Binoculars, + CompanyCard, + ReceiptUpload, + ExpensifyCardIllustration, + SplitBill, + PiggyBank, + Pillow, + Accounting, + Car, + Coins, + Pencil, + Tag, + CarIce, + ReceiptLocationMarker, + Lightbulb, + EmptyStateTravel, + Stopwatch, + SubscriptionAnnual, + SubscriptionPPU, + ExpensifyApprovedLogo, + ExpensifyApprovedLogoLight, + SendMoney, + CheckmarkCircle, + CreditCardEyes, + LockClosedOrange, + EmptyState, + FolderWithPapers, + VirtualCard, + Tire, + BigVault, + Filters, + MagnifyingGlassMoney, + Rules, + CompanyCardsEmptyState, + AmexCompanyCards, + MasterCardCompanyCards, + VisaCompanyCards, + CompanyCardsPendingState, + VisaCompanyCardDetail, + MasterCardCompanyCardDetail, + AmexCardCompanyCardDetail, + TurtleInShell, + BankOfAmericaCompanyCardDetail, + BrexCompanyCardDetail, + CapitalOneCompanyCardDetail, + ChaseCompanyCardDetail, + CitibankCompanyCardDetail, + StripeCompanyCardDetail, + WellsFargoCompanyCardDetail, + PerDiem, + AmexCardCompanyCardDetailLarge, + BankOfAmericaCompanyCardDetailLarge, + BrexCompanyCardDetailLarge, + CapitalOneCompanyCardDetailLarge, + ChaseCompanyCardDetailLarge, + CitibankCompanyCardDetailLarge, + MasterCardCompanyCardDetailLarge, + StripeCompanyCardDetailLarge, + VisaCompanyCardDetailLarge, + WellsFargoCompanyCardDetailLarge, + Flash, + ExpensifyMobileApp, + ReportReceipt, +}; diff --git a/android/app/build.gradle b/android/app/build.gradle index 49fcdbb4fa68..f34acdd2855f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -114,8 +114,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009010600 - versionName "9.1.6-0" + versionCode 1009011000 + versionName "9.1.10-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/assets/css/pdf.css b/assets/css/pdf.css index 26c80a5baf27..1d8c686efd76 100644 --- a/assets/css/pdf.css +++ b/assets/css/pdf.css @@ -8,8 +8,7 @@ overflow: visible; border: 9px solid transparent; background-clip: content-box; - border-image: url(../images/shadow.png) 9 9 repeat; - background-color: rgba(255, 255, 255, 1); + background-color: transparent !important; } .react-pdf__Page__annotations { @@ -24,3 +23,12 @@ .react-pdf__Page__textContent span{ color: transparent; } +.react-pdf__Page__canvas { + box-shadow: 0px 4px 12px 0px rgba(2, 18, 4, 0.06); +} +@media (prefers-color-scheme: dark) { + .react-pdf__Page__canvas { + color-scheme: dark; + box-shadow: 0px 4px 12px 0px rgba(2, 18, 4, 0.24); + } +} diff --git a/assets/images/companyCards/card=-generic.svg b/assets/images/companyCards/card=-generic.svg deleted file mode 100644 index 192c194da9e7..000000000000 --- a/assets/images/companyCards/card=-generic.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/companyCards/generic-csv-dark.svg b/assets/images/companyCards/generic-csv-dark.svg new file mode 100644 index 000000000000..0e450b8db437 --- /dev/null +++ b/assets/images/companyCards/generic-csv-dark.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/companyCards/generic-csv-light.svg b/assets/images/companyCards/generic-csv-light.svg new file mode 100644 index 000000000000..0075d5a35f79 --- /dev/null +++ b/assets/images/companyCards/generic-csv-light.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/companyCards/generic-dark.svg b/assets/images/companyCards/generic-dark.svg new file mode 100644 index 000000000000..58ee197a1107 --- /dev/null +++ b/assets/images/companyCards/generic-dark.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/companyCards/generic-light.svg b/assets/images/companyCards/generic-light.svg new file mode 100644 index 000000000000..e7e8d192c74f --- /dev/null +++ b/assets/images/companyCards/generic-light.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/companyCards/large/card-generic-large.svg b/assets/images/companyCards/large/card-generic-large.svg deleted file mode 100644 index 0979107526e6..000000000000 --- a/assets/images/companyCards/large/card-generic-large.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/companyCards/large/generic-csv-dark-large.svg b/assets/images/companyCards/large/generic-csv-dark-large.svg new file mode 100644 index 000000000000..6afc916636bc --- /dev/null +++ b/assets/images/companyCards/large/generic-csv-dark-large.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/companyCards/large/generic-csv-light-large.svg b/assets/images/companyCards/large/generic-csv-light-large.svg new file mode 100644 index 000000000000..6d60cffc1c61 --- /dev/null +++ b/assets/images/companyCards/large/generic-csv-light-large.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/companyCards/large/generic-dark-large.svg b/assets/images/companyCards/large/generic-dark-large.svg new file mode 100644 index 000000000000..de2fd514569e --- /dev/null +++ b/assets/images/companyCards/large/generic-dark-large.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/companyCards/large/generic-light-large.svg b/assets/images/companyCards/large/generic-light-large.svg new file mode 100644 index 000000000000..b5559951d34e --- /dev/null +++ b/assets/images/companyCards/large/generic-light-large.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/product-illustrations/rocket-dude.svg b/assets/images/product-illustrations/rocket-dude.svg new file mode 100644 index 000000000000..0321d4bf3e93 --- /dev/null +++ b/assets/images/product-illustrations/rocket-dude.svg @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/shadow.png b/assets/images/shadow.png deleted file mode 100644 index a00061ac7e12..000000000000 Binary files a/assets/images/shadow.png and /dev/null differ diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index 8ad26ac31e01..c58ee143fc14 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -121,7 +121,6 @@ const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment): {from: 'assets/sounds', to: 'sounds'}, {from: 'node_modules/react-pdf/dist/esm/Page/AnnotationLayer.css', to: 'css/AnnotationLayer.css'}, {from: 'node_modules/react-pdf/dist/esm/Page/TextLayer.css', to: 'css/TextLayer.css'}, - {from: 'assets/images/shadow.png', to: 'images/shadow.png'}, {from: '.well-known/apple-app-site-association', to: '.well-known/apple-app-site-association', toType: 'file'}, {from: '.well-known/assetlinks.json', to: '.well-known/assetlinks.json'}, diff --git a/contributingGuides/FORMS.md b/contributingGuides/FORMS.md index 2cfd24f13ab7..469281d3e86b 100644 --- a/contributingGuides/FORMS.md +++ b/contributingGuides/FORMS.md @@ -35,23 +35,6 @@ Labels and hints are enabled by passing the appropriate props to each input: /> ``` -### Character Limits - -If a field has a character limit, we should give that field a max limit. This is done by passing the maxLength prop to TextInput. - -```jsx - -``` -Note: We shouldn't place a max limit on a field if the entered value can be formatted. eg: Phone number. -The phone number can be formatted in different ways. - -- 2109400803 -- +12109400803 -- (210)-940-0803 - ### Native Keyboards We should always set people up for success on native platforms by enabling the best keyboard for the type of input we’re asking them to provide. See [inputMode](https://reactnative.dev/docs/textinput#inputmode) in the React Native documentation. @@ -176,6 +159,34 @@ function validate(values) { For a working example, check [Form story](https://github.com/Expensify/App/blob/aa1f0f34eeba5d761657168255a1ae9aebdbd95e/src/stories/Form.stories.js#L63-L72) +### Character Limits + +If a field has a character limit, we should give that field a max limit. This is done by passing the character limit validation in the validate function. + +Here's an example for a form that has one input `name`, and has character limit of 100: + +```js +function validate(values) { + const errors = {}; + if (values.name.length > 100) { + ErrorUtils.addErrorMessage(errors, 'name', translate('common.error.characterLimitExceedCounter', {length: values.name.length, limit: 100})); + } + return errors; +} +``` + +> [!NOTE] +> We shouldn't place a max limit on a field if the entered value can be formatted. eg: Phone number. +> The phone number can be formatted in different ways. +> +> - 2109400803 +> - +12109400803 +> - (210)-940-0803 + +> [!NOTE] +> If we want to count number of Unicode code points instead of the number of UTF-16 code units, we should use the spread syntax. +> Example - `[...newCategoryName].length` + ### Highlight Fields and Inline Errors Individual form fields should be highlighted with a red error outline and present supporting inline error text below the field. Error text will be required for all required fields and optional fields that require validation. This will keep our error handling consistent and ensure we put in a good effort to help the user fix the problem by providing more information than less. @@ -340,3 +351,7 @@ In case there's a nested Picker in Form, we should pass the props below to Form, #### Enable ScrollContext Pass the `scrollContextEnabled` prop to enable scrolling up when Picker is pressed, making sure the Picker is always in view and doesn't get covered by virtual keyboards for example. + +#### Enable Form to Scroll to the End + +Pass the `shouldScrollToEnd` prop to automatically scroll to the bottom when the form is opened. Ensure that the scrolling stops at the appropriate limit so that the button remains visible above the keypad. diff --git a/contributingGuides/HYBRID_APP.md b/contributingGuides/HYBRID_APP.md index cfdfc8ad8757..9c0ba4cb3f20 100644 --- a/contributingGuides/HYBRID_APP.md +++ b/contributingGuides/HYBRID_APP.md @@ -45,6 +45,7 @@ In that case, what are the differences in each script? - `./Mobile-Expensify/iOS/build` The scripts will also remove some deeper hidden cache, but crucial thing to understand is that cache for HybridApp are *mostly* located in `Mobile-Expensify` submodule, alongside the native code. +As the final step in both standalone and HybridApp scenarios, it runs `npm ci` (clean install), so an explicit `node_modules` installation is not necessary after cleaning. ## `npm run android` and `npm run ios` - **Without access to HybridApp**: diff --git a/contributingGuides/REASSURE_PERFORMANCE_TEST.md b/contributingGuides/REASSURE_PERFORMANCE_TEST.md index 0de450b78875..0e3f856819a9 100644 --- a/contributingGuides/REASSURE_PERFORMANCE_TEST.md +++ b/contributingGuides/REASSURE_PERFORMANCE_TEST.md @@ -26,9 +26,9 @@ We use Reassure for monitoring performance regression. It helps us check if our ## Running tests locally - Checkout your base environment, eg. `git checkout main`. -- Collect baseline metrics with `npx reassure --baseline`. +- Collect baseline metrics with `npm run perf-test -- --baseline`. - Apply any desired changes (for testing purposes you can eg. try to slow down a list). -- Collect current metrics with `npx reassure`. +- Collect current metrics with `npm run perf-test`. - Open up the resulting `output.md` / `output.json` (see console output) to compare the results. - With all that information, Reassure can present the render duration times as statistically significant or meaningless. diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index 545c79a95af1..f47746c62f98 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -9,6 +9,7 @@ - [ ] I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline) - [ ] I checked that screenshots or videos are included for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms) - [ ] I included screenshots or videos for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms) +- [ ] I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard. - [ ] I verified tests pass on **all platforms** & I tested again on: - [ ] Android: Native - [ ] Android: mWeb Chrome diff --git a/desktop/ELECTRON_EVENTS.ts b/desktop/ELECTRON_EVENTS.ts index b06794567c7d..99f275ab996b 100644 --- a/desktop/ELECTRON_EVENTS.ts +++ b/desktop/ELECTRON_EVENTS.ts @@ -14,6 +14,7 @@ const ELECTRON_EVENTS = { DOWNLOAD_FAILED: 'download-started', DOWNLOAD_CANCELED: 'download-canceled', SILENT_UPDATE: 'silent-update', + OPEN_LOCATION_SETTING: 'open-location-setting', } as const; export default ELECTRON_EVENTS; diff --git a/desktop/contextBridge.ts b/desktop/contextBridge.ts index 74b91c4634a1..13a752e0d6aa 100644 --- a/desktop/contextBridge.ts +++ b/desktop/contextBridge.ts @@ -18,6 +18,7 @@ const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ ELECTRON_EVENTS.LOCALE_UPDATED, ELECTRON_EVENTS.DOWNLOAD, ELECTRON_EVENTS.SILENT_UPDATE, + ELECTRON_EVENTS.OPEN_LOCATION_SETTING, ] as const; const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ diff --git a/desktop/main.ts b/desktop/main.ts index 4f642d90da51..2b296d84f6a3 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -1,3 +1,4 @@ +import {exec} from 'child_process'; import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, shell} from 'electron'; import type {BaseWindow, BrowserView, MenuItem, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron'; import contextMenu from 'electron-context-menu'; @@ -6,7 +7,7 @@ import type {ElectronLog} from 'electron-log'; import {autoUpdater} from 'electron-updater'; import {machineId} from 'node-machine-id'; import checkForUpdates from '@libs/checkForUpdates'; -import * as Localize from '@libs/Localize'; +import {translate} from '@libs/Localize'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -71,9 +72,9 @@ function pasteAsPlainText(browserWindow: BrowserWindow | BrowserView | WebviewTa function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => void { return contextMenu({ labels: { - cut: Localize.translate(preferredLocale, 'desktopApplicationMenu.cut'), - paste: Localize.translate(preferredLocale, 'desktopApplicationMenu.paste'), - copy: Localize.translate(preferredLocale, 'desktopApplicationMenu.copy'), + cut: translate(preferredLocale, 'desktopApplicationMenu.cut'), + paste: translate(preferredLocale, 'desktopApplicationMenu.paste'), + copy: translate(preferredLocale, 'desktopApplicationMenu.copy'), }, append: (defaultActions, parameters, browserWindow) => [ { @@ -81,10 +82,10 @@ function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => voi visible: parameters.isEditable && parameters.editFlags.canPaste, role: 'pasteAndMatchStyle', accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), + label: translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), }, { - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), + label: translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, click: () => pasteAsPlainText(browserWindow), @@ -126,7 +127,7 @@ let hasUpdate = false; let downloadedVersion: string; let isSilentUpdating = false; -// Note that we have to subscribe to this separately and cannot use Localize.translateLocal, +// Note that we have to subscribe to this separately and cannot use translateLocal, // because the only way code can be shared between the main and renderer processes at runtime is via the context bridge // So we track preferredLocale separately via ELECTRON_EVENTS.LOCALE_UPDATED const preferredLocale: Locale = CONST.LOCALES.DEFAULT; @@ -165,23 +166,23 @@ const manuallyCheckForUpdates = (menuItem?: MenuItem, browserWindow?: BaseWindow if (downloadPromise) { dialog.showMessageBox(browserWindow, { type: 'info', - message: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.title'), - detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.message', {isSilentUpdating}), - buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')], + message: translate(preferredLocale, 'checkForUpdatesModal.available.title'), + detail: translate(preferredLocale, 'checkForUpdatesModal.available.message', {isSilentUpdating}), + buttons: [translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')], }); } else if (result && 'error' in result && result.error) { dialog.showMessageBox(browserWindow, { type: 'error', - message: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.title'), - detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.message'), - buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')], + message: translate(preferredLocale, 'checkForUpdatesModal.error.title'), + detail: translate(preferredLocale, 'checkForUpdatesModal.error.message'), + buttons: [translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')], }); } else { dialog.showMessageBox(browserWindow, { type: 'info', - message: Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.title'), - detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.message'), - buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')], + message: translate(preferredLocale, 'checkForUpdatesModal.notAvailable.title'), + detail: translate(preferredLocale, 'checkForUpdatesModal.notAvailable.message'), + buttons: [translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')], cancelId: 2, }); } @@ -242,7 +243,7 @@ const localizeMenuItems = (submenu: MenuItemConstructorOptions[], updatedLocale: submenu.map((menu) => { const newMenu: MenuItemConstructorOptions = {...menu}; if (menu.id) { - const labelTranslation = Localize.translate(updatedLocale, `desktopApplicationMenu.${menu.id}` as TranslationPaths); + const labelTranslation = translate(updatedLocale, `desktopApplicationMenu.${menu.id}` as TranslationPaths); if (labelTranslation) { newMenu.label = labelTranslation; } @@ -310,7 +311,25 @@ const mainWindow = (): Promise => { }); ipcMain.handle(ELECTRON_EVENTS.REQUEST_DEVICE_ID, () => machineId()); + ipcMain.handle(ELECTRON_EVENTS.OPEN_LOCATION_SETTING, () => { + if (process.platform !== 'darwin') { + // Platform not supported for location settings + return Promise.resolve(undefined); + } + return new Promise((resolve, reject) => { + const command = 'open x-apple.systempreferences:com.apple.preference.security?Privacy_Location'; + + exec(command, (error) => { + if (error) { + console.error('Error opening location settings:', error); + reject(error); + return; + } + resolve(undefined); + }); + }); + }); /* * The default origin of our Electron app is app://- instead of https://new.expensify.com or https://staging.new.expensify.com * This causes CORS errors because the referer and origin headers are wrong and the API responds with an Access-Control-Allow-Origin that doesn't match app://- @@ -353,14 +372,14 @@ const mainWindow = (): Promise => { const initialMenuTemplate: MenuItemConstructorOptions[] = [ { id: 'mainMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.mainMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.mainMenu`), submenu: [ {id: 'about', role: 'about'}, - {id: 'update', label: Localize.translate(preferredLocale, `desktopApplicationMenu.update`), click: quitAndInstallWithUpdate, visible: false}, - {id: 'checkForUpdates', label: Localize.translate(preferredLocale, `desktopApplicationMenu.checkForUpdates`), click: manuallyCheckForUpdates}, + {id: 'update', label: translate(preferredLocale, `desktopApplicationMenu.update`), click: quitAndInstallWithUpdate, visible: false}, + {id: 'checkForUpdates', label: translate(preferredLocale, `desktopApplicationMenu.checkForUpdates`), click: manuallyCheckForUpdates}, { id: 'viewShortcuts', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.viewShortcuts`), + label: translate(preferredLocale, `desktopApplicationMenu.viewShortcuts`), accelerator: 'CmdOrCtrl+J', click: () => { showKeyboardShortcutsPage(browserWindow); @@ -378,12 +397,12 @@ const mainWindow = (): Promise => { }, { id: 'fileMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.fileMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.fileMenu`), submenu: [{id: 'closeWindow', role: 'close', accelerator: 'Cmd+w'}], }, { id: 'editMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.editMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.editMenu`), submenu: [ {id: 'undo', role: 'undo'}, {id: 'redo', role: 'redo'}, @@ -406,7 +425,7 @@ const mainWindow = (): Promise => { {type: 'separator'}, { id: 'speechSubmenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.speechSubmenu`), + label: translate(preferredLocale, `desktopApplicationMenu.speechSubmenu`), submenu: [ {id: 'startSpeaking', role: 'startSpeaking'}, {id: 'stopSpeaking', role: 'stopSpeaking'}, @@ -416,7 +435,7 @@ const mainWindow = (): Promise => { }, { id: 'viewMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.viewMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.viewMenu`), submenu: [ {id: 'reload', role: 'reload'}, {id: 'forceReload', role: 'forceReload'}, @@ -431,7 +450,7 @@ const mainWindow = (): Promise => { }, { id: 'historyMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.historyMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.historyMenu`), submenu: [ { id: 'back', @@ -472,33 +491,33 @@ const mainWindow = (): Promise => { }, { id: 'helpMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.helpMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.helpMenu`), role: 'help', submenu: [ { id: 'learnMore', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.learnMore`), + label: translate(preferredLocale, `desktopApplicationMenu.learnMore`), click: () => { shell.openExternal(CONST.MENU_HELP_URLS.LEARN_MORE); }, }, { id: 'documentation', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.documentation`), + label: translate(preferredLocale, `desktopApplicationMenu.documentation`), click: () => { shell.openExternal(CONST.MENU_HELP_URLS.DOCUMENTATION); }, }, { id: 'communityDiscussions', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.communityDiscussions`), + label: translate(preferredLocale, `desktopApplicationMenu.communityDiscussions`), click: () => { shell.openExternal(CONST.MENU_HELP_URLS.COMMUNITY_DISCUSSIONS); }, }, { id: 'searchIssues', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.searchIssues`), + label: translate(preferredLocale, `desktopApplicationMenu.searchIssues`), click: () => { shell.openExternal(CONST.MENU_HELP_URLS.SEARCH_ISSUES); }, diff --git a/desktop/package-lock.json b/desktop/package-lock.json index ec97e4db7eb1..3008cb66d7c9 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -9,7 +9,7 @@ "dependencies": { "electron-context-menu": "^2.3.0", "electron-log": "^4.4.8", - "electron-updater": "^6.4.1", + "electron-updater": "^6.5.0", "mime-types": "^2.1.35", "node-machine-id": "^1.1.12" }, @@ -168,9 +168,9 @@ "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" }, "node_modules/electron-updater": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.4.1.tgz", - "integrity": "sha512-copPdR81tUcUEFrtI19rz07dVG55B1FkURjEet3KxOC/PZ1ueEIpFRqJXzeW6S0SoHrZFCbAuYfxjDcJrwOBbg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.5.0.tgz", + "integrity": "sha512-04l7lKtJQV0gHuw0GjVVSUiuRwwW2Fhj6HUjxC4mtkDz12/1cd1hqa8+sJCy5iYdLpN+fwkBmlPiI90enUNQmA==", "license": "MIT", "dependencies": { "builder-util-runtime": "9.3.1", @@ -565,9 +565,9 @@ "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" }, "electron-updater": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.4.1.tgz", - "integrity": "sha512-copPdR81tUcUEFrtI19rz07dVG55B1FkURjEet3KxOC/PZ1ueEIpFRqJXzeW6S0SoHrZFCbAuYfxjDcJrwOBbg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.5.0.tgz", + "integrity": "sha512-04l7lKtJQV0gHuw0GjVVSUiuRwwW2Fhj6HUjxC4mtkDz12/1cd1hqa8+sJCy5iYdLpN+fwkBmlPiI90enUNQmA==", "requires": { "builder-util-runtime": "9.3.1", "fs-extra": "^10.1.0", diff --git a/desktop/package.json b/desktop/package.json index 54e81daeecc0..248dd4e7d328 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -6,7 +6,7 @@ "dependencies": { "electron-context-menu": "^2.3.0", "electron-log": "^4.4.8", - "electron-updater": "^6.4.1", + "electron-updater": "^6.5.0", "mime-types": "^2.1.35", "node-machine-id": "^1.1.12" }, diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 472887177e74..d5745942d1cc 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -36,7 +36,9 @@ diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 9307b3e712d8..f4dd6276c279 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -986,3 +986,11 @@ button { .hidden { display: none; } + +.expensifyhelp-logo__expensify { + fill: var(--color-text); +} + +.expensifyhelp-logo__help { + fill: var(--color-success); +} \ No newline at end of file diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-an-Invoice.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-an-Invoice.md deleted file mode 100644 index 18020402f7de..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-an-Invoice.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Pay an Invoice -description: A guide to different methods of paying an invoice ---- - -
- -There are multiple ways to pay Invoices in Expensify. Let’s go over each method below. - -# How to Pay Invoices - -1. Sign in to your [Expensify web account](www.expensify.com). -2. Click on **Home** and find the pending Invoice payment -3. Click **Pay** to be redirected to the Invoice -4. Review the Invoice -5. When you are ready to pay, click the **Pay** button at the top of the Invoice -6. Follow the prompts to pay through one of the following methods. - -![Click Home and Pay on the invoice](https://help.expensify.com/assets/images/ExpensifyHelp_OldDot_PayInvoice_1.png){:width="100%"} - -![Click Pay on Invoice and choose a method of payment](https://help.expensify.com/assets/images/ExpensifyHelp_OldDot_PayInvoice_2.png){:width="100%"} - -### ACH bank-to-bank transfer - -To use this payment method, you must have a [business bank account connected to your Expensify account](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account). - -**To pay with an ACH bank-to-bank transfer:** -1. Sign in to your [Expensify web account](www.expensify.com). -2. Go to the **Home** or **Reports** page and locate the Invoice that needs to be paid. -3. Click the **Pay** button to be redirected to the Invoice. -4. Choose the ACH option from the drop-down list. - -**Fees:** None - -### Credit or Debit Card -This option is available to all US and International customers receiving an invoice from a US vendor with a US business bank account. - -**To pay with a credit or debit card:** -1. Sign in to your [Expensify web account](www.expensify.com). -2. Click on the Invoice you’d like to pay to see the details. -3. Click the **Pay** button. -4. Enter your credit card or debit card details. - -**Fees:** 2.9% credit card payment fee. - -### Venmo -If both you and the vendor must have Venmo connected to Expensify, you can pay the Invoice by following the steps outlined [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Third-Party-Payments#setting-up-third-party-payments). - -**Fees:** Venmo and Paypal.me charges may apply. - - -### Pay outside of Expensify -If you are unable to pay using one of the above methods, you can still mark the Invoice as paid. This will update its status to indicate that the payment was made outside Expensify. - -**To mark an Invoice as paid outside of Expensify:** -1. Sign in to your [Expensify web account](www.expensify.com). -2. Click on the Invoice you’d like to pay to see the details. -3. Click the Pay button. -4. Choose **I’ll do it manually**. - -**Fees:** None. - -{% include faq-begin.md %} - -## What’s the difference between an Invoice and an Expense Report in Expensify? -An invoice is an expense submitted to a client or contractor for payment. An expense report is an expense/group of expenses submitted to an employer for reimbursement. - -## What’s the difference between a Bill and an Invoice in Expensify? -A Bill is an amount owed to a payee (usually a vendor or supplier) and is usually created from a vendor invoice. An Invoice is a receivable and indicates an amount owed to you by someone else. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Send-and-Pay-Invoices.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Send-and-Pay-Invoices.md new file mode 100644 index 000000000000..f360172c4328 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Send-and-Pay-Invoices.md @@ -0,0 +1,140 @@ +--- +title: Invoice Payments +description: A guide to sending, receiving, and paying invoices in Expensify. +--- + +Managing invoices is easier than ever with Expensify. This guide walks you through sending invoices to vendors and suppliers, bulk importing invoices, and receiving and sending invoice payments. + +--- + +# Send an Invoice + +1. Sign in to your [Expensify account](https://www.expensify.com). +2. (Optional) Customize your company invoices using this [guide](https://help.expensify.com/articles/expensify-classic/workspaces/Set-Up-Invoicing). +3. Go to the **Reports** page, click the drop-down, and select **Invoice**. +4. Click **Add Expense** to upload an invoice or drag and drop the invoice PDF for SmartScan. +5. Once SmartScan completes, the invoice PDF will be added as a receipt to the expense. +6. Add applicable tags and categories based on workspace settings. +7. Click **Send**. +8. Enter the recipient’s email address. +9. (Optional) Add a memo, due date, and attach a PDF. +10. Click **Send**. +11. The recipient will receive an email with payment instructions. They can pay through Expensify by following [these steps](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-an-Invoice). + +![From the Reports page, click New Report and select Invoice](https://help.expensify.com/assets/images/ExpensifyHelp_OldDot_SendInvoice.png){:width="100%"} + +![Click Send and enter the recipients email address](https://help.expensify.com/assets/images/ExpensifyHelp_OldDot_SendInvoice_02.png){:width="100%"} + +--- + +# Upload Invoices in Bulk + +1. Go to the **Reports** tab. +2. Click the **New Report** drop-down. +3. Select **Bulk Import Invoices**. +4. Download the sample CSV template. +5. Fill in the invoice details (see **CSV Formatting Guide** below). +6. Click **Upload CSV**. + +![Click Reports, New Reports, choose Bulk Import Invoices](https://help.expensify.com/assets/images/invoice-bulk-01.png){:width="100%"} + +![Download Sample CSV](https://help.expensify.com/assets/images/invoice-bulk-02.png){:width="100%"} + +![Format CSV following our guidelines](https://help.expensify.com/assets/images/invoice-bulk-03.png){:width="100%"} + +## CSV Formatting Guide + +- **Send To**: Recipient’s email (e.g., john.smith@company.com) +- **Share**: Additional email recipients (e.g., julie.clarke@company.com) +- **Report Name**: Invoice report title +- **Merchant**: Business name of invoice sender +- **Amount**: Numeric format (negative amounts not allowed) +- **Date**: YYYY-MM-DD format +- **Due Date**: YYYY-MM-DD format + +## After Uploading Invoices + +- Invoices appear on the **Reports** page. +- Filter by **Invoices** to locate them easily. +- The **Send To** contact receives an email notification. +- You can manually edit invoice details. +- A PDF can be manually attached to the report. + +![Search for Invoices on Reports page](https://help.expensify.com/assets/images/invoice-bulk-04.png){:width="100%"} + +![Invoices will indicate next steps at the top of each report](https://help.expensify.com/assets/images/invoice-bulk-05.png){:width="100%"} + +--- + +# Receive an Invoice Payment + +1. Ensure your [business bank account is connected](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account). +2. Include your payment details on the invoice. +3. The recipient will receive an invoice notification. +4. They can pay the invoice via Expensify. + +--- + +# Pay Invoices + +You can pay an invoice in Expensify using various methods, including ACH bank transfers, credit or debit cards, and Venmo. If you’ve already paid outside of Expensify, you can mark the invoice as paid manually. + +## Paying an Invoice +1. Sign in to your [Expensify web account](www.expensify.com). +2. Go to Home or Reports and locate the invoice. +3. Click Pay. +4. Select a payment method from the options below. + +![Click Home and Pay on the invoice](https://help.expensify.com/assets/images/ExpensifyHelp_OldDot_PayInvoice_1.png){:width="100%"} + +![Click Pay on Invoice and choose a method of payment](https://help.expensify.com/assets/images/ExpensifyHelp_OldDot_PayInvoice_2.png){:width="100%"} + +--- + +# Payment Methods + +## ACH Bank-to-Bank Transfer +To use this method, you must have a [business bank account connected to Expensify](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account): +1. Follow the steps under **Paying an Invoice**. +2. Select ACH from the payment options. + +**Fees: None**. + +## Credit or Debit Card +This option is available for US and international customers paying a US vendor with a US business bank account: +1. Follow the steps under **Paying an Invoice**. +2. Enter your credit or debit card details. + +**Fees: 2.9% credit card processing fee**. + +## Venmo +If both you and the vendor must have Venmo connected to Expensify, you can pay the Invoice by following the steps outlined [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Third-Party-Payments#setting-up-third-party-payments). + +**Fees:** Venmo and Paypal.me charges may apply. + +## Marking an Invoice as Paid Outside Expensify +If you paid outside Expensify, you can update the invoice status: +1. Follow the steps under **Paying an Invoice**. +2. Select **I’ll do it manually**. + +**Fees: None**. + +--- + +# FAQ + +## Are there any fees for sending invoices in Expensify? +No, invoices are included in the [Control Plan](https://help.expensify.com/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription#change-group-plan). + +## Can invoices be revised after sending? +Yes, click **Undo Send** to revoke an invoice. To add details, use [Report Comments](https://help.expensify.com/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report). + +## How do I communicate with the payor? +Use [Report Comments](https://help.expensify.com/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report) for direct communication. + +## What’s the difference between an invoice and an expense report? +- **Invoice**: Sent to clients or contractors for payment. +- **Expense Report**: Submitted to an employer for reimbursement. + +## What’s the difference between a bill and an invoice in Expensify? +A Bill is an amount owed to a payee (usually a vendor or supplier) and is usually created from a vendor invoice. An Invoice is a receivable and indicates an amount owed to you by someone else. diff --git a/docs/articles/expensify-classic/domains/Create-A-Group.md b/docs/articles/expensify-classic/domains/Create-A-Group.md deleted file mode 100644 index fb70faffa27e..000000000000 --- a/docs/articles/expensify-classic/domains/Create-A-Group.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Create a group -description: How to set different rules for different members of your domain ---- -
- -To set different domain rules for different members, you can place them into groups. For example, many organizations create different groups for employees and managers since they generally need different domain permissions. - -To create a group, - -1. Hover over Settings, then click **Domains**. -2. Click the name of the domain. -3. Click the **Groups** tab on the left. -4. Click **Create Group**. -5. Select all of the group settings and permissions. - - **Permission Group Name**: Enter a name for the group - - **Default Group**: Determine if new domain members will be automatically added to this group. - - **Strictly enforce expense workspace rules**: Determine if all expense rules must be met before people in this group can submit a report. - - **Restrict primary login selection**: Determine if members of this group will be restricted from using a personal email address to access their Expensify account. - - **Restrict expense workspace creation/removal**: Determine if members of this group will be allowed to create new workspaces. - - **Preferred workspace**: Determine if this group will automatically have their expenses and reports posted to a specific workspace. - - **Set preferred workspace to**: If preferred workspace is enabled, select which workspace members of this group will have set as their preferred workspace. - - **Expensify Card Preferred Workspace**: If preferred workspace is enabled, determine if Expensify Card transactions for this group will be posted to the preferred workspace listed for the Expensify Card instead of the preferred workspace listed in the above settings. -6. Click **Save**. - -
\ No newline at end of file diff --git a/docs/articles/expensify-classic/domains/Domain-Groups.md b/docs/articles/expensify-classic/domains/Domain-Groups.md new file mode 100644 index 000000000000..97f67427ece3 --- /dev/null +++ b/docs/articles/expensify-classic/domains/Domain-Groups.md @@ -0,0 +1,42 @@ +--- +title: Domain Groups +description: How to set different rules for different members of your domain +--- + +To set different domain rules for different members, you can place them into groups. This allows organizations to customize permissions based on roles, such as employees and managers, ensuring they have the appropriate access and settings. + +--- +# Configuring Domain Groups + +1. Hover over **Settings**, then click **Domains**. +2. Click the name of the domain. +3. Click the **Groups** tab. +4. Click **Create Group**. +5. Configure the group settings and permissions: + - **Permission Group Name**: Enter a name for the group. + - **Default Group**: Choose if new domain members will be automatically added to this group. + - **Strictly enforce expense workspace rules**: Decide if all expense rules must be met before group members can submit a report. + - **Restrict primary login selection**: Choose whether members can access their Expensify account using a personal email. + - **Restrict expense workspace creation/removal**: Set whether members can create or remove workspaces. + - **Preferred workspace**: Select a default workspace for group members' expenses and reports. + - **Set preferred workspace to**: If enabled, specify which workspace will be set as preferred. + - **Expensify Card Preferred Workspace**: If the preferred workspace is enabled, check whether Expensify Card transactions for this group will be posted to the preferred workspace for the Expensify Card instead of the one in the above settings. +6. Click **Save**. + +--- +# When to Use Different Domain Group Permissions + +## Strictly enforce expense workspace rules +This setting ensures all workspace-level rules are followed before an expense report is submitted. This prevents expense reports from being submitted with missing receipts, incorrect categories, or violations. + +## Restrict primary login selection +Enable this setting to ensure employees use their company email instead of a personal email account when submitting expense reports. This helps to maintain security and compliance within your organization. + +## Restrict expense workspace creation/removal +Set this to prevent employees from creating additional workspaces outside of the company workspace they're already a member of. This will ensure expense reports are always routed through the correct company-approved channels. + +## Preferred workspace +If you have multiple workspaces across several teams, use this setting to assign an employee to their corresponding workspace. For instance, use this for sales teams so their expenses automatically post to the "Sales Department" workspace, reducing the need for manual adjustments. + +## Expensify Card Preferred Workspace +Enable this if your team uses the Expensify Cards for business expenses. This will ensure that all transactions are posted directly to the correct workspace without additional setup. diff --git a/docs/articles/expensify-classic/expenses/Add-Invoices-in-Bulk.md b/docs/articles/expensify-classic/expenses/Add-Invoices-in-Bulk.md deleted file mode 100644 index 3e9b6c0397db..000000000000 --- a/docs/articles/expensify-classic/expenses/Add-Invoices-in-Bulk.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Bulk Upload Invoices -description: How to Bulk Upload Invoices ---- - -Expensify offers importing multiple invoices (bulk import) via CSV to save you from manually creating individual invoices. - -## Uploading Invoices into Expensify - -1. Click the **Reports** tab. -2. Click the **New Report** drop-down. -3. Select **Bulk Import Invoices**. -4. Click the sample CSV link to download your custom CSV template to your browser or computer. -5. Add the invoice details following the formatting rules (see below **CSV formatting guide** section) -6. Click **Upload CSV** - -![Click Reports, New Reports, choose Bulk Import Invoices](https://help.expensify.com/assets/images/invoice-bulk-01.png){:width="100%"} - -![Download Sample CSV](https://help.expensify.com/assets/images/invoice-bulk-02.png){:width="100%"} - -![Format CSV following our guidelines](https://help.expensify.com/assets/images/invoice-bulk-03.png){:width="100%"} - - -## CSV formatting guide - -- Send to: recipient's email address (ex: john.smith@companydomain.com) -- Share: email address (ex: julie.clarke@companydomain.com) -- Report Name: this will be the name of the Invoice report -- Merchant: business name of invoice sender -- Amount: use the number format in this column. Negative amounts cannot be invoiced. -- Date: YYYY-MM-DD formatting -- Due Date: YYYY-MM-DD formatting - -## After the Invoices are uploaded - -- After you click **Upload**, the invoices will automatically be created and viewable on the **Reports** page. -- Set the **Reports page** filter to Invoices to narrow down your search. -- The **Send To** contact will get an email notifying them of the invoice you sent. -- You can manually edit the invoice details. -- You can manually upload a PDF of the invoice to the report. - -![Search for Invoices on Reports page](https://help.expensify.com/assets/images/invoice-bulk-04.png){:width="100%"} - -![Invoices will indicate next steps at the top of each report](https://help.expensify.com/assets/images/invoice-bulk-05.png){:width="100%"} - -{% include faq-begin.md %} - -## Are there any fees associated with Invoices in Expensify? -No, Invoices are part of the [Control Plan](https://help.expensify.com/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription#change-group-plan). - -## Can Invoices be revised once they are sent? -If you sent an invoice by mistake, you can click **Undo Send** on the invoice to revoke it. If you’d like to add more details to a sent invoice, you can add those as a [Report comment](https://help.expensify.com/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report) for everyone to view. - -## How do I communicate with the payor -You can communicate with the payor through [Report comments](https://help.expensify.com/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report). - -## What’s the difference between an Invoice and an Expense Report in Expensify? -An invoice is an expense submitted to a client or contractor for payment. An expense report is an expense or group of expenses submitted to an employer for reimbursement. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md deleted file mode 100644 index e83640403ce4..000000000000 --- a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: Create Expense Rules -description: Automatically categorize, tag, and report expenses based on the merchant's name ---- - -Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant’s name. - -# Create expense rules - -1. Hover over **Settings** and click **Account**. -2. Click **Expense Rules**. -2. Click **New Rule**. -3. Add what the merchant name should contain in order for the rule to be applied. *Note: If you enter just a period, the rule will apply to all expenses regardless of the merchant name. Universal Rules will always take precedence over all other expense rules.* -4. Choose from the following rules: -- **Merchant:** Updates the merchant name (e.g., “Starbucks #238” could be changed to “Starbucks”) -- **Category:** Applies a workspace category to the expense -- **Tag:** Applies a tag to the expense (e.g., a Department or Location) -- **Description:** Adds a description to the description field on the expense -- **Reimbursability:** Determines whether the expense will be marked as reimbursable or non-reimbursable -- **Billable**: Determines whether the expense is billable -- **Add to a report named:** Adds the expense to a report with the name you type into the field. If no report with that name exists, a new report will be created if the "Create report if necessary" checkbox is selected. - -![Fields to create a new expense rule, including the characters a merchant's name should contain for the rule to apply, as well as what changes should be applied to the expense including the merchant name, category, tag, description, reimbursability, whether it is billable, and what report it will be added to.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} - -{:start="6"} -6. (Optional) To apply the rule to previously entered expenses, select the **Apply to existing matching expenses** checkbox. You can also click **Preview Matching Expenses** to see if your rule matches the intended expenses. - -# How rules are applied - -In general, your expense rules will be applied in order, from **top to bottom**, (i.e., from the first rule). However, other settings can impact how expense rules are applied. Here is the hierarchy that determines how these are applied: - -1. A Universal Rule will **always** be applied over any other expense category rules. Rules that would otherwise change the expense category will **not** override the Universal Rule. -2. If Scheduled Submit and the setting “Enforce Default Report Title” are enabled on the workspace, this will take precedence over any rules trying to add the expense to a report. -3. If the expense is from a company card that is forced to a workspace with strict rule enforcement, those rules will take precedence over individual expense rules. -4. If you belong to a workspace that is tied to an accounting integration, the configuration settings for this connection may update your expense details upon export, even if the expense rules were successfully applied to the expense. - -# Create an expense rule from changes made to an expense - -If you open an expense and change it, you can then create an expense rule based on those changes by selecting the “Create a rule based on your changes" checkbox. *Note: The expense must be saved, reopened, and edited for this option to appear.* - -![The "Create a rule based on your changes" checkbox is located in the bottom right corner of the popup window, to the left of the Save button.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"} - -# Delete an expense rule - -To delete an expense rule, - -1. Hover over **Settings** and click **Account**. -2. Click **Expense Rules**. -3. Scroll down to the rule you’d like to remove and click the trash can icon. - -![The Trash icon to delete an expense rule is located at the top right of the box containing the expense rule, to the left of the Edit icon.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"} - -{% include faq-begin.md %} - -## How can I use expense rules to vendor match when exporting to an accounting package? - -When exporting non-reimbursable expenses to your connected accounting package, the payee field will list "Credit Card Misc." if the merchant name on the expense in Expensify is not an exact match to a vendor in the accounting package. When an exact match is unavailable, "Credit Card Misc." prevents multiple variations of the same vendor (e.g., Starbucks and Starbucks #1234, as is often seen in credit card statements) from being created in your accounting package. - -For repeated expenses, the best practice is to use Expense Rules, which will automatically update the merchant name without having to do it manually each time. This only works for connections to QuickBooks Online, Desktop, and Xero. Vendor matching cannot be performed in this manner for NetSuite or Sage Intacct due to limitations in the API of the accounting package. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expenses/Expense-Rules.md b/docs/articles/expensify-classic/expenses/Expense-Rules.md new file mode 100644 index 000000000000..190c0f70c486 --- /dev/null +++ b/docs/articles/expensify-classic/expenses/Expense-Rules.md @@ -0,0 +1,76 @@ +--- +title: Expense Rules +description: Automatically categorize, tag, and report expenses based on the merchant's name. +--- + +Expense rules in Expensify help automate the categorization, tagging, and reporting of expenses based on merchant names, reducing manual work. By setting up these rules at the account level, employees can streamline expense management and ensure consistency across reports. + +--- + +# Create an Expense Rule + +1. Hover over **Settings** and click **Account**. +2. Click **Expense Rules**. +3. Click **New Rule**. +4. In the **Merchant Name Contains** field, enter part of the merchant name that should trigger the rule. + - **Note:** If you enter only a period (`.`), the rule applies to all expenses. Universal Rules take precedence over all other expense rules. +5. Select the rules to apply when a matching expense is detected: + - **Merchant:** Standardizes the merchant name (e.g., "Starbucks #238" → "Starbucks"). + - **Category:** Assigns a workspace category to the expense. + - **Tag:** Adds a tag (e.g., Department or Location). + - **Description:** Updates the description field of the expense. + - **Reimbursability:** Marks the expense as reimbursable or non-reimbursable. + - **Billable:** Flags the expense as billable. + - **Add to a report named:** Assigns the expense to a specific report. If **Create report if necessary** is selected, a new one is created if the report does not exist. + +![Fields to create a new expense rule, including the characters a merchant's name should contain for the rule to apply, as well as what changes should be applied to the expense including the merchant name, category, tag, description, reimbursability, whether it is billable, and what report it will be added to.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} + + +6. (Optional) Select **Apply to existing matching expenses** to update past expenses. +7. Click **Preview Matching Expenses** to check if the rule applies correctly. + +--- + +# How Rules Are Applied + +Expense rules are processed from **top to bottom** in the list. However, other settings may override them. The rule hierarchy is: + +1. **Universal Rules** always override other expense category rules. +2. **Scheduled Submit** with **Enforce Default Report Title** enabled takes precedence over expense rule-based report assignments. +3. **Company Card Rules** for enforced workspaces take priority over individual expense rules. +4. **Accounting Integrations** may override expense rule settings when expenses are exported. + +--- + +# Create an Expense Rule from an Edited Expense + +If you modify an expense manually, you can create a rule based on those changes: + +1. Open the expense. +2. Make the necessary edits. +3. Select **Create a rule based on your changes** before saving. + - **Note:** The option appears only after saving, reopening, and editing an expense. + +![The "Create a rule based on your changes" checkbox is located in the bottom right corner of the popup window, to the left of the Save button.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"} + +--- + +# Delete an Expense Rule + +1. Hover over **Settings** and click **Account**. +2. Click **Expense Rules**. +3. Find the rule you want to remove and click the **Trash** icon. + +![The Trash icon to delete an expense rule is located at the top right of the box containing the expense rule, to the left of the Edit icon.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"} + +--- + +# FAQ + +## How can I use expense rules for vendor matching in an accounting integration? + +When exporting non-reimbursable expenses, the **Payee** field in the accounting software will show "Credit Card Misc." if there is no exact match for the merchant name. This prevents multiple variations of the same vendor (e.g., "Starbucks" vs. "Starbucks #1234") from being created. + +To avoid this, use **Expense Rules** to standardize vendor names before export. +- **Supported integrations:** QuickBooks Online, QuickBooks Desktop, Xero. +- **Not supported for:** NetSuite, Sage Intacct (due to API limitations). diff --git a/docs/articles/expensify-classic/expenses/Expense-Types.md b/docs/articles/expensify-classic/expenses/Expense-Types.md index faf670469362..7f8d822585c0 100644 --- a/docs/articles/expensify-classic/expenses/Expense-Types.md +++ b/docs/articles/expensify-classic/expenses/Expense-Types.md @@ -1,43 +1,101 @@ --- title: Expense Types -description: Details of the different Expense filters and Expense Types +description: Learn how to organize reports by expense type and identify different expense categories in Expensify. --- -## Organize a Report by Expense Type -Organizing a report by expense type can make it easier to review expenses on a report. +Understanding expense types in Expensify helps you track and categorize business spending more effectively. This guide covers how to organize reports by expense type and explains the differences between reimbursable, non-reimbursable, and billable expenses. + +# Organize a Report by Expense Type + +Organizing reports by expense type helps streamline expense review: 1. Open the desired report. -2. Click Details in the upper right corner of the report. -3. Click the View dropdown and select Detailed. -4. Click the Split by dropdown and select Reimbursable or Billable. +2. Click **Details** in the upper-right corner. +3. Click the **View** dropdown and select **Detailed**. +4. Click the **Split by** dropdown and select **Reimbursable** or **Billable**. +5. To group expenses further, use the **Group by** dropdown to select **Category** or **Tags**. + +--- + +# Identify Expense Types +The right side of every report displays total expenses, broken down by **reimbursable**, **billable**, and **non-reimbursable** amounts. -To group the expenses by category or tag, you can also click the Group by dropdown and select Category or Tags. +## Reimbursable Expenses +Expenses paid by employees on behalf of the business, including: +- **Cash & Personal Card:** Out-of-pocket business expenses. +- **Per Diem:** Daily expense allowances configured in your [workspace settings](https://help.expensify.com/articles/expensify-classic/workspaces/Enable-per-diem-expenses). +- **Time:** Hourly wages for jobs, typically used for contractor invoicing. Configure rates [here](https://help.expensify.com/articles/expensify-classic/workspaces/Set-time-and-distance-rates). +- **Distance:** Mileage-related expenses. -## Identify Expense Types -The right side of every report provides the total for all the expenses. Under the total, there is a breakdown of reimbursable, billable, and non-reimbursable amounts (depending on the expense types that exist on the report). +## Non-Reimbursable Expenses +Expenses that are directly covered by the business, usually on company cards. -- Reimbursable expenses: Expenses paid to the employee, including: - - Cash & personal card: Expenses paid for by the employee on behalf of the business. - - Per diem: Expenses for a daily or partial daily rate [configured in your Workspace](https://help.expensify.com/articles/expensify-classic/workspaces/Enable-per-diem-expenses). - - Time: An hourly rate for your employees or jobs as [set for your workspace](https://help.expensify.com/articles/expensify-classic/workspaces/Set-time-and-distance-rates). This expense type is usually used by contractors or small businesses billing the customer via [Expensify Invoicing](https://help.expensify.com/articles/expensify-classic/workspaces/Set-Up-Invoicing). - - Distance: Expenses related to business travel. -- Non-reimbursable expenses: Expenses directly covered by the business, typically on company cards. -- Billable expenses: Business or employee expenses that must be billed to a specific client or vendor. This option is for tracking expenses for invoicing to customers, clients, or other departments. Any kind of expense can be billable, in _addition_ to being either reimbursable or non-reimbursable. +## Billable Expenses +Expenses billed to a client or vendor. Any expense—reimbursable or non-reimbursable—can also be billable. ![Image of a report showing multiple expense totals]({{site.url}}/assets/images/amounts.png){:width="100%"} -{% include faq-begin.md %} +--- + +# Deleting Expenses + +Expensify does not permanently delete expenses in open accounts to prevent lost funds or reconciliation issues. Instead, expenses are **soft deleted**, meaning they are moved to a **Deleted** folder. This ensures that: + +- Accidental deletions can be easily reversed. +- No financial data is permanently lost within an active Expensify account. +- Users maintain better control over expense records. + +## Recovering Deleted Expenses + +If you need to restore a deleted expense: + +1. Navigate to **Expenses** in your Expensify account. +2. Click the **Filters** icon and select **Deleted** to view all soft-deleted expenses. +3. Locate the expense you want to recover. +4. Click **Restore** to move the expense back to its original state. + +## Managing Deleted Expenses + +- **Deleted expenses remain in the system** for reference, ensuring no financial data is lost. +- **Expenses can be restored at any time** unless permanently removed by an admin. +- If an expense is deleted in error, you can restore it using the steps above. + +--- + +# Filtering Expenses + +Use filters to narrow down the data: +1. Click the **Expenses** tab. +2. Adjust the filters at the top of the page: + - **Date Range** – Select a specific time frame. + - **Merchant Name** – Find expenses from a particular vendor (partial searches work). + - **Workspace** – View expenses for a specific Group or Individual Workspace. + - **Categories** – Filter by category to refine your search. + - **Tags** – Locate expenses based on assigned tags. + - **Submitters** – Find expenses by employee or vendor. + - **Personal Expenses** – Show expenses that have not yet been added to a report. + - **Open, Processing, Approved, Reimbursed, Closed** – View expenses at different reporting stages. + +**Note:** Some filters adjust dynamically based on your current selections. If results aren’t as expected, click **Reset** to clear all filters. + +--- -**What’s the difference between an expense, a receipt, and a report attachment?** +# FAQ -- **Expense:** Created when you SmartScan or manually upload a receipt from a purchase. -- **Receipt:** A picture file that is automatically attached to the expense during the SmartScan process. -- **Report Attachments:** Additional documents that need to be submitted to your approver (e.g., supplemental documents to the purchase) can be added to a report any time by clicking the paperclip icon in the comments at the bottom of the report. +## What’s the difference between an expense, a receipt, and a report attachment? +- **Expense:** Created when you SmartScan or manually upload a receipt. +- **Receipt:** Image file automatically attached to an expense via SmartScan. +- **Report Attachment:** Additional documents (e.g., supporting documents) added via the paperclip icon in report comments. -**How are credits or refunds displayed in Expensify?** +## How are credits or refunds displayed in Expensify? +Credits appear as **negative expenses** (e.g., -$1.00). They offset the total report amount. -In Expensify, a credit is displayed as an expense with a minus in front of it (e.g., -$1.00). Expensify defaults all expenses as something that needs to be paid by the company. So a credit that is returned to the company is displayed as a negative expense. +For example: +- A report with **$400** and **$500** reimbursable expenses shows a total of **$900**. +- A report with **-$400** and **$500** expenses results in a **$100** total. -If a report includes a credit or a refund expense, it will offset the total amount on the report. For example, if the report has two reimbursable expenses, one for $400 and one for $500, then the total reimbursable amount is $900. Conversely, an expense for -$400 and one for $500 will be a total reimbursable amount of $500. +## Can I permanently delete a cash expense? +Permanently deleting cash expenses is restricted to ensure accurate financial records. It is possible to delete company card expenses, as long as the expenses have not been submitted, by un-assigning the employee's card at the domain level. -{% include faq-end.md %} +## How do I find my deleted expenses? +Use the **Filters** option in the **Expenses** tab and select **Deleted** to view all soft-deleted expenses. diff --git a/docs/articles/expensify-classic/expenses/Send-and-Receive-Payment-for-Invoices.md b/docs/articles/expensify-classic/expenses/Send-and-Receive-Payment-for-Invoices.md deleted file mode 100644 index fde2c43e9d95..000000000000 --- a/docs/articles/expensify-classic/expenses/Send-and-Receive-Payment-for-Invoices.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Send and Receive Payment for Invoices -description: How to Send and Receive Payments for Invoices ---- - -Simplify your back office by sending invoices to vendors and suppliers in Expensify. -Invoices can be sent to anyone with or without an Expensify account and paid directly to your business bank account through Expensify. - -## How to send an invoice in Expensify - -1. Sign in to your [Expensify web account](www.expensify.com) -2. Customize your company invoices following the steps in this [help article](https://help.expensify.com/articles/expensify-classic/workspaces/Set-Up-Invoicing). (Optional) -3. From the **Reports** page, click the drop-down and select **Invoice**. -4. Click **Add Expense** to upload an invoice or drag and drop the invoice as a pdf into the report to start the SmartScan process. -5. Once the SmartScan process is complete, the invoice PDF will be added as a receipt to the expense -6. Add applicable tags and categories based on your workspace settings. -7. Click **Send** -8. Enter the recipient's email address -9. Add a memo, due date, attach a PDF of the invoice (Optional) -10. Click **Send** -11. The recipient will receive an email about the invoice and can pay through Expensify following these [steps](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-an-Invoice). - -![From the Reports page, click New Report and select Invoice](https://help.expensify.com/assets/images/ExpensifyHelp_OldDot_SendInvoice.png){:width="100%"} - -![Click Send and enter the recipients email address](https://help.expensify.com/assets/images/ExpensifyHelp_OldDot_SendInvoice_02.png){:width="100%"} - -## How to Receive an Invoice Payment in Expensify - -1. To use Expensify payments, you must have a [business bank account connected to your Expensify account](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account). -2. Ensure the payment details are on the invoice sent to the payor. -3. The payor will receive a notification of the submitted invoice. -4. They will have the option to pay the invoice through Expensify. - -{% include faq-begin.md %} - -## Are there any fees associated with Invoices in Expensify? -No, Invoices are part of the [Control Plan](https://help.expensify.com/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription#change-group-plan). - -## Can Invoices be revised once they are sent? -If you sent an invoice by mistake, you can click **Undo Send** on the invoice to revoke it. If you’d like to add more details to a sent invoice, you can add those as a [Report comment](https://help.expensify.com/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report) for everyone to view. - -## How do I communicate with the payor -You can communicate with the payor through [Report comments](https://help.expensify.com/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report). - -## What’s the difference between an Invoice and an Expense Report in Expensify? -An invoice is an expense submitted to a client or contractor for payment. An expense report is an expense or group of expenses submitted to an employer for reimbursement. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription.md b/docs/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription.md index 0c0153522af3..f27c883ad82c 100644 --- a/docs/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription.md +++ b/docs/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription.md @@ -1,86 +1,106 @@ --- -title: Changing your workspace plan -description: How to change your plan or subscription +title: Changing Your Workspace Plan +description: How to change your Expensify plan or subscription --- -# Overview -Expensify offers various plans depending on your needs: Track, Submit, Collect, Control, and Free. Your choice of plan depends on whether you want to manage your expenses individually or for a group or company. You may need to upgrade from an individual plan to a group plan if you recently hired additional employees that need should be added to a Group Workspace, or you need access to Expensify's features that are only available on a paid plan. - -# How to change a subscription on an Individual Plan -## Change Individual Plan -### Web -1. Go to **Settings > Workspaces > Individual > [Your Individual Workspace]** -1. Click on **Plan** and select **Switch** under the plan you want to switch to -### Mobile -Open the Expensify app and: -1. Tap the hamburger icon (three lines) on the top left -1. Tap **Settings** -1. Tap **View All** under your Workspace -1. Select the Workspace you want to change under the "Individual" tab -1. Tap **Current Plan** under **Plan** -1. Find the **Switch** option under the plan you're not currently using + +Expensify offers several plans based on your needs: **Track, Submit, Collect, Control,** and **Free**. Your choice depends on whether you manage expenses individually or for a group or company. You may need to upgrade if you hire employees who need access to a **Group Workspace** or require features exclusive to paid plans. + +--- + +# Changing a Subscription on an Individual Plan + +**Web:** +1. Go to **Settings > Workspaces > Individual > [Your Individual Workspace]**. +2. Click **Plan** and select **Switch** under your desired plan. + +**Mobile:** +1. Open the Expensify app. +2. Tap the **hamburger menu** (three lines) on the top left. +3. Tap **Settings**. +4. Tap **View All** under your Workspace. +5. Select the Workspace under the **Individual** tab. +6. Tap **Current Plan** under **Plan**. +7. Tap **Switch** under the plan you're not currently using. + ## Upgrade to a Group Plan -To upgrade to a group plan, you will need to create a Group Workspace by heading to **Settings > Workspaces > Group** and choosing a Collect or Control plan. +1. Go to **Settings > Workspaces > Group**. +2. Select a **Collect** or **Control** plan. -# How to change a subscription on a Group Plan -## Change Group Plan -## Web -1. Go to **Settings > Workspaces > Group > [Your Group Workspace]** -1. Click on **Plan** and select **Switch** under the plan you want to switch to. +--- -## Mobile -1. In the Expensify mobile app, navigate to **Settings > Workspaces > [Your Workspace] > Current Plan > Switch**. +# Changing a Subscription on a Group Plan -## Adjust subscription size -When you first create a subscription, you can manually set your size by entering a number in the Subscription Size field of your subscription settings by heading to **Settings > Workspaces > Group > Subscription**. +**Web:** +1. Go to **Settings > Workspaces > Group > [Your Group Workspace]**. +2. Click **Plan** and select **Switch** under your desired plan. -If you choose not to set a size yourself, it will be calculated automatically for your first bill based on your depending on which scenario below fits your use case: -- If you’ve never had activity in Expensify, your subscription size is set automatically to match the number of active users you had your first month of using Expensify on your Annual Subscription. This means you’ll see the number update automatically after your first billing. -- For existing Workspaces switching to an annual subscription, the subscription size is set to the number of active users on your last month’s billing history. +**Mobile:** +1. Open the Expensify app. +2. Navigate to **Settings > Workspaces > [Your Workspace] > Current Plan > Switch**. -## Auto increase subscription size -This feature manages your subscription by automatically increasing the count whenever there is activity that exceeds your subscription size. Whenever your subscription size is increased, you will start a new 12-month commitment for the new subscription size in full. +--- -To enable automatically increasing your subscription size, head to **Settings > Workspaces > Group > Subscription** and toggle this feature on. +## Adjust Subscription Size +1. Go to **Settings > Workspaces > Group > Subscription**. +2. Enter the desired number in the **Subscription Size** field. + - If left blank, your subscription size will be set automatically: + - **New Workspaces**: Based on active users in the first month. + - **Existing Workspaces Switching to Annual**: Based on the last month's active users. -## Auto renew -By default, your subscription is set to automatically renew after a year. To disable this, head to **Settings > Workspaces > Subscription** and use the toggle to turn this feature off before your current subscription ends. +## Auto-Increase Subscription Size +1. Go to **Settings > Workspaces > Group > Subscription**. +2. Toggle **Auto Increase Subscription Size** on. +3. When enabled, your subscription size will adjust automatically based on usage, triggering a new 12-month commitment for the updated size. -If Auto Renew is disabled then the last bill at the annual rate will be issued on the date listed under the Auto Renew settings. +## Auto-Renew Subscription +1. Go to **Settings > Workspaces > Subscription**. +2. Toggle **Auto Renew** off before your current subscription ends if you do not want it to renew. + - If **Auto Renew** is disabled, your final bill at the annual rate will be issued on the date listed under **Auto Renew Settings**. -# How to downgrade to a free account from an Individual Plan -## Web -1. Log in to your account through a web browser. -1. Go to **Settings > Workspaces > Individual > Subscription**. -1. Click "Cancel Subscription" to end your Monthly Subscription. +--- -Note: Your subscription is a pre-purchase for 30 days of unlimited SmartScanning. This means that when you cancel, you do not get a refund and instead get to use the remainder of the month of unlimited SmartScanning you purchased. +# Downgrading to a Free Account from an Individual Plan -## App Store -If you subscribed via iOS, you must cancel your monthly subscription through the App Store by heading to App Store > click on your ID > Subscriptions. You can't cancel it directly in Expensify. +**Web:** +1. Log in via a web browser. +2. Go to **Settings > Workspaces > Individual > Subscription**. +3. Click **Cancel Subscription**. + - **Note**: The subscription is prepaid for 30 days of unlimited **SmartScanning**. No refunds are issued, but you retain access until the period ends. -# How to downgrade to a free account from a Group Plan -## Pay-per-use -If you have a Group Workspace and use Pay-Per-Use billing, you can downgrade by going to **Settings > Workspaces > Group** and clicking the cog button next to your Workspace name, then choosing **Delete**. +**App Store (iOS Users):** +1. Go to the **App Store**. +2. Tap your **Apple ID** > **Subscriptions**. +3. Cancel your Expensify subscription. + - **Note**: This cannot be done within Expensify. -Note: Deleting a Workspace removes its configurations and Workspace members but not their Expensify accounts. +--- -When deleting your final paid Workspace, if any Workspace members have been active that month (this means anybody who created, edited, submitted, approved, exported, or deleted a report) you will be billed for their activity as part of the downgrade flow. +# Downgrading to a Free Account from a Group Plan -## Annual subscription -If you recently started an annual subscription, you can downgrade for a full refund before the second bill. If you meet the criteria below, you can request a refund by going to **Settings > Your Account > Billing** in the web app: -- Own Collect or Control Group Workspaces -- Have only been billed for a single month -- Have not cleared a balance in the past +## Pay-Per-Use Plan +1. Go to **Settings > Workspaces > Group**. +2. Click the **cog icon** next to your Workspace name. +3. Select **Delete**. + - **Note**: Deleting a Workspace removes its settings and members but does not delete their Expensify accounts. + - If any members were active that month (submitted, approved, or edited reports), you will be billed for their usage. -Note: Refunds apply to Collect or Control Group Workspaces with one month of billing and no previous balance. +## Annual Subscription +1. If eligible for a refund, go to **Settings > Your Account > Billing**. +2. Click **Request a Refund** if: + - You own a **Collect** or **Control** Group Workspace. + - You have only been billed once. + - You have no outstanding balance. -Once you’ve successfully downgraded to a free Expensify account, your Workspace will be deleted and you will see a refund line item added to your Billing History. +Once downgraded, your Workspace will be deleted, and a refund line item will appear in your **Billing History**. + +--- + +# FAQ -{% include faq-begin.md %} ## Will I be charged for a monthly subscription even if I don't use SmartScans? -Yes, the Monthly Subscription is prepaid and not based on activity, so you'll be charged regardless of usage. -## I'm on a group workspace; do I need the monthly subscription too? -Probably not. Group workspace members already have unlimited SmartScans, so there's usually no need to buy the subscription. However, you can use it for personal use if you leave your company's Workspace. +Yes, monthly subscriptions are prepaid and not usage-based, so you will be charged regardless of activity. + +## I'm on a Group Workspace. Do I need the monthly subscription too? +No, Group Workspace members already have unlimited **SmartScans**. However, you can keep a subscription for personal use if you leave your company's Workspace. -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expensify-billing/Out-of-Date-Billing.md b/docs/articles/expensify-classic/expensify-billing/Out-of-Date-Billing.md new file mode 100644 index 000000000000..6affb34cc90e --- /dev/null +++ b/docs/articles/expensify-classic/expensify-billing/Out-of-Date-Billing.md @@ -0,0 +1,33 @@ +--- +title: Out-of-Date Billing +description: How to resolve an out-of-date billing notification for your Expensify Workspace. +--- + +**A notification about out-of-date billing appears when:** + +- The payment method for your workspace is expired, invalid, or has insufficient funds. +- Your company’s Expensify trial has ended, and it’s time to [upgrade your subscription](https://help.expensify.com/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription). + +--- + +# Identify the Billing Owner + +1. Hover over **Settings** and click **Workspaces**. +2. Select the workspace with the `!` symbol. +3. Locate the **Billing Owner** listed. +4. Ask the Billing Owner to follow the next steps. If you need to take over billing, click **Take Over Billing**. + +--- + +# Update Payment and Retry Billing + +**Note:** Only the **Billing Owner** can complete this step. + +1. Ensure the linked card or bank account has sufficient funds. +2. Hover over **Settings** and click **Account**. +3. Select the **Payments** tab. +4. Choose one of the following: + - Click **Retry Billing** if the previous payment failed due to insufficient funds. + - Click **Add Payment Card** to enter a new payment method. + +Once the payment is processed, the out-of-date billing notification will disappear. diff --git a/docs/articles/expensify-classic/expensify-billing/Out-of-date-Billing.md b/docs/articles/expensify-classic/expensify-billing/Out-of-date-Billing.md deleted file mode 100644 index d6529dfd8e1c..000000000000 --- a/docs/articles/expensify-classic/expensify-billing/Out-of-date-Billing.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Out-of-date Billing -description: What to do if you receive an out-of-date billing notification ---- - -A notification that your workspace has out-of-date billing will appear for one of the following reasons: - -- A workspace you’re an Admin for has an expired/invalid payment card or insufficient funds. -- Your company’s Expensify trial has ended and it’s time to [upgrade your subscription](https://help.expensify.com/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription). - -## Step 1: Determine who the billing owner is - -1. Hover over **Settings** and click **Workspaces**. -2. Click the name of the workspace that has the `!` symbol next to it. -3. Review who is listed as the Billing Owner. -4. Have this person complete the steps below, or you can click **Take Over Billing** if you will take over handling payments for the Expensify workspace. - -## Step 2: Retry payment or update the payment card - -{% include info.html %} -This step must be completed by the Billing Owner. -{% include end-info.html %} - -1. Ensure that the card or bank account has sufficient funds for the payment. -2. Hover over **Settings** and click **Account**. -3. Click the **Payments** tab on the left. -4. Click **Retry Billing** if there were originally insufficient funds in the payment account, or click **Add Payment Card** to add a payment method. - -Once the payment is processed, the out-of-date billing notification will disappear. diff --git a/docs/articles/expensify-classic/expensify-billing/Receipt-Breakdown.md b/docs/articles/expensify-classic/expensify-billing/Receipt-Breakdown.md index 8f512fb71512..92229f4aa8a5 100644 --- a/docs/articles/expensify-classic/expensify-billing/Receipt-Breakdown.md +++ b/docs/articles/expensify-classic/expensify-billing/Receipt-Breakdown.md @@ -2,36 +2,55 @@ title: Receipts Breakdown description: This article goes over the Expensify receipt for billing owners. --- -**Your receipt is broken up into the following sections:** -- A high-level summary of your total Expensify bill -- Ways to reduce your bill and get paid to use Expensify -- A billing breakdown that covers all activity and discounts -- An activity breakdown by workspace - -## A high-level summary - -- The top section will show the total amount you paid as the billing owner of Expensify Workspaces and give you a breakdown of the price per member. -- Every member of your workspace(s) can store data, review data, and access free features like Expensify Chat. -- We show the total price and then calculate the price per member by using the number of members across all of the workspaces you own. -- Further down in the receipt, there's a breakdown of the members who generated billable activity. - -## Reduce your bill and get paid to use Expensify -Chances are you can actually get paid to use Expensify with the Expensify Card. In this section of the receipt, we outline how much money you're leaving on the table by not using the Expensify Card. You can click `Get started` to connect with your account manager (if you have one) or Concierge, both of whom can help get you started with the card. - -_Note: Currently, we offer Expensify Cards to companies with US bank accounts._ - -## The billing breakdown -Your receipt will have a detailed breakdown of activity and discounts across all workspaces. Here's a description of items that may appear on your bill: -- **[Number of] Inactive workspace members @ $0.00:** All inactive members from any of your workspaces. -- **[Number of] Chat-only members @ $0.00:** Any workspace members who chatted but didn't generate any other billable activity. Learn more about [chatting for free.](https://help.expensify.com/new-expensify/hubs/chat/) -- **[Number of] Annual Control members @ $18.00:** Any members included in your annual subscription on the Control plan. -- **[Number of] Pay-per-use Control members @ $36.00:** Any members above your annual subscription size on the Control plan. They're billed at the pay-per-use rate. -- **[Number of] Annual Collect members @ $10.00:** Any members included in your annual subscription on the Collect plan. -- **[Number of] Pay-per-use Collect members @ $20.00:** Any members above your annual subscription size on the Collect plan. These members are billed at the pay-per-use rate. -- **X% Expensify Card discount with $Y spend:** The % discount you're getting based on total settled US purchases across your Expensify Cards. -- **X% Expensify Card cash back credit for $Y spend:** The amount of cash back you've earned based on total settled US purchases across your Expensify Cards. -- **50% ExpensifyApproved! partner discount:** If you're part of an accounting firm, you get an additional discount for being our partner -- learn more about our ExpensifyApproved! accountant program [here](https://use.expensify.com/accountants-program). -- **Total:** The sum of all the line items above. - -## The activity breakdown -This section will list all of your workspaces alongside their IDs and break down the billing for each of them. + +Your Expensify receipt clearly shows your total bill, member activity, and any applicable discounts. This guide explains each section to help you understand your charges and identify potential savings. + +## Receipt Sections + +Your receipt includes the following details: + +- **High-level summary** – Total Expensify bill and per-member pricing. +- **Ways to reduce costs** – Potential savings with the Expensify Card. +- **Billing breakdown** – Detailed charges, discounts, and member activity. +- **Activity breakdown** – Workspace-specific billing details. + +--- + +## High-Level Summary + +- Displays the **total amount paid** for all Expensify Workspaces under your billing ownership. +- Shows the **price per member**, calculated based on the total number of members across your workspaces. +- Provides a **member activity breakdown** to show which members generated billable activity. + +--- + +## Reduce Your Bill & Get Paid to Use Expensify + +- Expensify Card users may qualify for **cash back** and **discounts** on their bill. +- This section shows **potential savings** if you use the Expensify Card for business expenses. +- Click **Get started** to connect with your account manager or Concierge for setup assistance. + +**Note:** [Expensify Cards]([url](https://use.expensify.com/company-credit-card)) are currently available to companies with **US business bank accounts**. + +--- + +## Billing Breakdown + +Your receipt provides a **line-by-line breakdown** of charges and discounts: + +- **Inactive workspace members @ $0.00** – Users who did not generate billable activity. +- **Chat-only members @ $0.00** – Users who only used Expensify Chat. [Learn more](https://help.expensify.com/new-expensify/hubs/chat/). +- **Annual Control members @ $18.00** – Members covered under an annual **Control** plan subscription. +- **Pay-per-use Control members @ $36.00** – Additional **Control** plan members billed at a per-use rate. +- **Annual Collect members @ $10.00** – Members covered under an annual **Collect** plan subscription. +- **Pay-per-use Collect members @ $20.00** – Additional **Collect** plan members billed at a per-use rate. +- **X% Expensify Card discount with $Y spend** – Discount applied for total settled US purchases. +- **X% Expensify Card cash back credit for $Y spend** – Cash back earned on total settled US purchases. +- **50% ExpensifyApproved! partner discount** – Discount for approved **accounting firms**. [Learn more](https://use.expensify.com/accountants-program). +- **Total** – The final amount after applying all charges and discounts. + +--- + +## Activity Breakdown + +This section displays a detailed list of workspaces with their unique IDs and shows billing details for each workspace. diff --git a/docs/articles/expensify-classic/expensify-billing/Tax-Exempt.md b/docs/articles/expensify-classic/expensify-billing/Tax-Exempt.md index a91454b4965b..808b88b0440a 100644 --- a/docs/articles/expensify-classic/expensify-billing/Tax-Exempt.md +++ b/docs/articles/expensify-classic/expensify-billing/Tax-Exempt.md @@ -1,24 +1,31 @@ --- title: Tax Exempt -description: Tax-exempt status in Expensify for organizations recognized by the IRS or local tax authorities. +description: How to request tax-exempt status for your Expensify account if your organization is recognized by the IRS or other local tax authorities. --- -# Overview -If your organization is recognized by the IRS or other local tax authorities as tax-exempt, that means you don’t need to pay any tax on your Expensify monthly bill. Please follow these instructions to request tax-exempt status. -# How to request tax-exempt status in Expensify -1. Go to **Settings > Account > Payments**. -1. Click on the option that says **Request Tax-Exempt Status**. -1. After you've requested tax-exempt status, Concierge (our support service) will start a conversation with you. They will ask you to upload a PDF of your tax-exempt documentation. This document should include your VAT number (or "RUT" in Chile). You can use one of the following documents: 501(c), ST-119, or a foreign tax-exempt declaration. -1. Our team will review your document and let you know if we need any more information. -1. Once everything is verified, we'll update your account accordingly. + +If your organization is recognized as **tax-exempt** by the IRS or other local tax authorities, you can request tax-exempt status in Expensify to avoid tax charges on your monthly bill. Follow these steps to submit your request. + +--- + +# Request Tax-Exempt Status + +1. Go to **Settings > Account > Payments**. +2. Click **Request Tax-Exempt Status**. +3. Concierge will reach out to you for documentation. Upload a PDF of your tax-exempt certification. Your document should include your **VAT number** (or **RUT** in Chile). Accepted documents include: + - **501(c)** (for U.S. organizations) + - **ST-119** + - **Foreign tax-exempt declaration** +4. Our team will review your submission and request any additional information if needed. +5. Once verified, your account will be updated, and tax will no longer be applied to future billing. ![Click the request tax exempt status button]({{site.url}}/assets/images/Tax Exempt - Classic.png){:width="100%"} -Once your account is marked as tax-exempt, the corresponding state tax will no longer be applied to future billing. +If you need to **remove** your tax-exempt status, contact your **Account Manager** or **Concierge**. + +--- -If you need to remove your tax-exempt status, let your Account Manager know or contact Concierge. +# FAQ -{% include faq-begin.md %} -## What happens to my past Expensify bills that incorrectly had tax added to them? -Expensify can provide a refund for the tax you were charged on your previous bills. Please let your Account Manager know or contact Concierge if this is the case. +## Will Expensify refund past tax charges? -{% include faq-end.md %} +Yes! If tax was incorrectly applied to your past bills, Expensify can issue a refund. Contact your **Account Manager** or **Concierge** to request a refund. diff --git a/docs/articles/expensify-classic/expensify-card/Admin-Card-Settings-and-Features.md b/docs/articles/expensify-classic/expensify-card/Admin-Card-Settings-and-Features.md index b9938b058ef6..1b656e274d89 100644 --- a/docs/articles/expensify-classic/expensify-card/Admin-Card-Settings-and-Features.md +++ b/docs/articles/expensify-classic/expensify-card/Admin-Card-Settings-and-Features.md @@ -1,134 +1,147 @@ --- -title: Admin Card Settings and Features +title: Admin Card Settings and Features description: A deep dive into the available controls and settings for the Expensify Card. --- -# Expensify Visa® Commercial Card Overview -The Expensify Visa® Commercial Card offers various settings to help admins manage expenses and card usage efficiently. Here’s how to use these features: - -## Smart Limits -Smart Limits allow you to set custom spending limits for each Expensify cardholder or default limits for groups. Setting a Smart Limit activates an Expensify card for your user and issues a virtual card for immediate use. -#### Set Limits for Individual Cardholders -As a Domain Admin, you can set or edit Custom Smart Limits for a card: -1. Go to _**Settings > Domains > Domain Name > Company Cards**_. -2. Click **Edit Limit** to set the limit. +# Expensify Visa® Commercial Card Overview +The Expensify Visa® Commercial Card includes various settings to help admins manage expenses and card usage efficiently. -This limit restricts the amount of unapproved (unsubmitted and processing) expenses a cardholder can incur. Once the limit is reached, the cardholder cannot use their card until they submit outstanding expenses and have their card spend approved. If you set the Smart Limit to $0, the user’s card cannot be used. +## Set and Manage Smart Limits +Smart Limits allow you to control spending for each Expensify cardholder or set default limits for groups. Enabling a Smart Limit activates an Expensify Card and issues a virtual card for immediate use. -#### Set Default Group Limits -Domain Admins can set or edit custom Smart Limits for a domain group: +### Set Limits for Individual Cardholders +As a Domain Admin, you can set or edit Custom Smart Limits: +1. Go to **Settings > Domains > [Domain Name] > Company Cards**. +2. Click **Edit Limit** and enter the desired amount. -1. Go to _**Settings > Domains > Domain Name > Groups**_. -2. Click on the limit in-line for your chosen group and amend the value. +- The limit controls how much unapproved spending a cardholder can accumulate. +- Once the limit is reached, the cardholder must submit expenses for approval before making new transactions. +- Setting a Smart Limit of $0 disables the card. -This limit applies to all members of the Domain Group who do not have an individual limit set via _**Settings > Domains > Domain Name > Company Cards**_. +### Set Default Group Limits +To set or update default Smart Limits for a domain group: +1. Go to **Settings > Domains > [Domain Name] > Groups**. +2. Click the in-line limit for your chosen group and adjust the value. -#### Refreshing Smart Limits -To let cardholders continue spending, you can approve their pending expenses via the Reconciliation tab. This frees up their limit, allowing them to use their card again. +This applies to all group members who do not have an individual limit. -To check an unapproved card balance and approve expenses: -1. Click on **Reconciliation** and enter a date range. -2. Click on the Unapproved total to see what needs approval. -3. You can add to a new report or approve an existing report from here. +### Refreshing Smart Limits +To enable further spending, approve pending expenses: +1. Go to **Reconciliation**, enter a date range, and click **Run**. +2. Click the **Unapproved Total** to review pending expenses. +3. Approve expenses or add them to a report. You can also increase a Smart Limit at any time by clicking **Edit Limit**. -### Understanding Your Domain Limit -To ensure you have the most accurate Domain Limit for your company, follow these steps: +--- -1. **Connect Your Bank Account:** Go to _**Settings > Account > Payments > Add Verified Bank Account**_ and connect via Plaid. +## Check and Adjust Your Domain Limit +Ensure your Domain Limit is accurate by following these steps: -2. **Request a Custom Limit:** If your bank isn’t supported or you’re experiencing connection issues, you can request a custom limit at _**Settings > Domains > Domain Name > Company Cards > Request Limit Increase**_. Note that you’ll need to provide three months of unredacted bank statements for review by our risk management team. +1. **Connect Your Bank Account:** Go to **Settings > Account > Payments > Add Verified Bank Account** and connect via Plaid. +2. **Request a Custom Limit:** If Plaid does not support your bank, request a limit increase at **Settings > Domains > [Domain Name] > Company Cards > Request Limit Increase**. + - You’ll need to upload three months of unredacted bank statements. ### Factors Affecting Your Domain Limit -Your Domain Limit may fluctuate due to several factors: +Your Domain Limit may change due to: +- **Available Funds:** Expensify monitors balances via Plaid. A sudden drop in funds within 24 hours can lower your limit. +- **Pending Expenses:** Large pending transactions reduce your available balance. +- **Processing Settlements:** Settlements take about three business days, dynamically adjusting your Domain Limit. -- **Available Funds in Your Verified Business Bank Account:** We regularly monitor balances via Plaid. A sudden decrease in balance within the last 24 hours may impact your limit. For accounts with 'sweep' functionality, maintain a sufficient balance even when sweeping daily. +**Note:** If your Domain Limit is $0, cardholders cannot make purchases. -- **Pending Expenses:** Check the Reconciliation Dashboard for large pending expenses that could affect your available balance. Your Domain Limit automatically adjusts to include pending expenses. - -- **Processing Settlements:** Settlements typically take about three business days to process and clear. Multiple large settlements over consecutive days may affect your Domain Limit, which updates dynamically once settlements are cleared. - -Please note: If your Domain Limit is reduced to $0, cardholders cannot make purchases, even if they have higher Smart Limits set on their individual cards. +--- -## Reconciling Expenses and Settlements -Reconciling expenses ensures your financial records are accurate and up-to-date. Follow these steps to review and reconcile expenses associated with your Expensify Cards: +## Reconcile Expenses and Settlements +### Reconcile Expenses +1. Go to **Settings > Domains > [Domain Name] > Company Cards > Reconciliation > Expenses**. +2. Enter start and end dates, then click **Run**. +3. Review the **Imported Total**, which shows all Expensify Card transactions for the period. +4. Click totals to view associated expenses. + +### Reconcile Settlements +1. Log into Expensify. +2. Click **Settings > Domains > [Domain Name] > Company Cards > Reconciliation > Settlements**. +3. Use **Search** to generate a statement for a specific period. +4. Click **Download CSV** to review settlement details, including: + - **Date & Posted Date** + - **Entry ID & Transaction ID** + - **Amount & Merchant** + - **Cardholder & Business Account** +5. To reconcile pre-authorizations, use the **Transaction ID** column in the CSV file. -#### How to Reconcile Expenses: -1. Go to _**Settings > Domains > Domain Name > Company Cards > Reconciliation > Expenses**_. -2. Enter your start and end dates, then click *Run*. -3. The Imported Total will display all Expensify Card transactions for the period. -4. You'll see a list of all Expensify Cards, the total spend on each card, and a snapshot of expenses that have been approved and have not been approved (Approved Total and Unapproved Total, respectively). -5. Click on the amounts to view the associated expenses. +--- -#### How to Reconcile Settlements: -A settlement is the payment to Expensify for purchases made using the Expensify Cards. The program can settle on either a daily or monthly basis. Note that not all transactions in a settlement will be approved when running reconciliation. +## Manage Settlement Settings +### Set a Preferred Workspace +Create a dedicated workspace for card expenses and use **Scheduled Submit** to automatically add expenses to reports. -1. Log into the Expensify web app. -2. Click _**Settings > Domains > Domain Name > Company Cards > Reconciliation > `Settlements**_. -3. Use the Search function to generate a statement for the specific period you need. +### Change the Settlement Account +1. Go to **Settings > Domains > [Domain Name] > Company Cards > Settings**. +2. Select a new verified business bank account from the **Settlement Account** dropdown. +3. Click **Save**. -The search results will include the following info for each entry: -- **Date:** When a purchase was made or funds were debited for payments. -- **Posted Date:** When the purchase transaction is posted. -- **Entry ID:** A unique number grouping card payments and transactions settled by those payments. -- **Amount:** The amount debited from the Business Bank Account for payments. -- **Merchant:** The business where a purchase was made. -- **Card:** Refers to the Expensify Card number and cardholder’s email address. -- **Business Account:** The business bank account connected to Expensify that the settlement is paid from. -- **Transaction ID:** A special ID that helps Expensify support locate transactions if there’s an issue. +### Change the Settlement Frequency +By default, settlements occur daily. You can switch to monthly settlements if needed. -Review the individual transactions (debits) and the payments (credits) that settled them. Each cardholder will have a virtual and a physical card listed, handled the same way for settlements, reconciliation, and exporting. +#### Monthly Settlement Requirements: +- The account must not have had a negative balance in the last 90 days. +- An initial settlement will process any outstanding spending before switching. +- The settlement date will be the day you switch moving forward. -4. Click **Download CSV** for reconciliation. This will list everything you see on the screen. -5. To reconcile pre-authorizations, use the Transaction ID column in the CSV file to locate the original purchase. -6. Review account payments: You’ll see payments made from the accounts listed under _**Settings > Account > Payments > Bank Accounts**_. Payment data won’t show for deleted accounts. +#### Steps to Change the Settlement Frequency: +1. Go to **Settings > Domains > [Domain Name] > Company Cards > Settings**. +2. Click **Settlement Frequency** and select **Monthly**. +3. Click **Save**. -Use the Reconciliation Dashboard to confirm the status of expenses missing from your accounting system. It allows you to view both approved and unapproved expenses within your selected date range that haven’t been exported yet. +--- -### Set a Preferred Workspace -Many customers find it helpful to separate their company card expenses from other types of expenses for easier coding. To do this, create a separate workspace specifically for card expenses. +## Declined Expensify Card Transactions +Enable **Receive real-time alerts** to get notified about declined transactions: +1. Open the mobile app. +2. Tap the menu icon (**≡**) and go to **Settings**. +3. Toggle **Receive real-time alerts** on. -**Using a Preferred Workspace:** -Combine this feature with Scheduled Submit to automatically add new card expenses to reports connected to your card-specific workspace. +### Common Reasons for Declines +#### Insufficient Card Limit +The transaction exceeds the card's limit. Check your balance under **Settings > Account > Credit Card Import**. -### Change the Settlement Account -You can change your settlement account to any verified business bank account in Expensify. If your current bank account is closing, make sure to set up a replacement as soon as possible. +#### Card Not Activated or Canceled +Ensure the card is active. -#### Steps to Select a Different Settlement Account: -1. Go to _**Settings > Domains > Domain Name > Company Cards > Settings**_ tab. -2. Use the Expensify Card settlement account dropdown to select a new account. -3. Click **Save**. +#### Incorrect Card Information +Mistyped CVC, ZIP, or expiration date will result in a decline. -### Change the Settlement Frequency -By default, Expensify Cards settle daily. However, you can switch to monthly settlements. +#### Suspicious Activity +Expensify may block flagged transactions. -#### Monthly Settlement Requirements: - - The settlement account must not have had a negative balance in the last 90 days. - - There will be an initial settlement for any outstanding spending before the switch. - - The settlement date going forward will be the date you switch (e.g., if you switch on September 15th, future settlements will be on the 15th of each month). +#### Merchant in a Restricted Country +Transactions from restricted countries will be declined. -#### Steps to Change the Settlement Frequency: -1. Go to _**Settings > Domains > Domain Name > Company Cards > Settings**_ tab. -2. Click the **Settlement Frequency** dropdown and select **Monthly**. -3. Click **Save** to confirm the change. +--- -### Declined Expensify Card Transactions -If you have 'Receive real-time alerts' enabled, you'll get a notification explaining why a transaction was declined. To enable alerts: -1. Open the mobile app. -2. Click the three-bar icon in the upper-left corner. -3. Go to Settings. -4. Toggle 'Receive real-time alerts' on. +# FAQ -If you or your employees notice any unfamiliar purchases or need a new card, go to _**Settings > Account > Credit Card Import**_ and click on **Request a New Card**. +## Who can access the Reconciliation tab? +Only **Domain Admins** can access the Reconciliation tool. -#### Common Reasons for Declines: -- **Insufficient Card Limit:** If a transaction exceeds your card's limit, it will be declined. Always check your balance under _**Settings > Account > Credit Card Import**_ on the web or mobile app. Approve pending expenses to free up your limit. +## Who can view and process company card transactions? +- **Domain Admins** can view all company card transactions, including unreported ones, via the Reconciliation tool. +- **Workspace Admins** can only view reported expenses in a workspace. If they lack domain access, they cannot see transactions that haven’t been added to a report. -- **Card Not Activated or Canceled:** Transactions won't process if the card hasn't been activated or has been canceled. +## What do I do if company card expenses are missing? +1. Use the **Reconciliation** tool to locate the missing expense: + - Select the date range for the expense. + - View the specific card to check the data. +2. If the expense isn’t listed, click **Update** next to the card under the **Card List** tab to pull in missing transactions. +3. If the expense still doesn’t appear, contact Concierge with these details: + - Merchant name + - Date + - Amount + - Last four digits of the card number -- **Incorrect Card Information:** Entering incorrect card details, such as the CVC, ZIP, or expiration date, will lead to declines. +**Note:** Only posted transactions will be imported. -- **Suspicious Activity:** Expensify may block transactions if unusual activity is detected. This could be due to irregular spending patterns, risky vendors, or multiple rapid transactions. Check your Expensify Home page to approve unusual merchants. If further review is needed, Expensify will perform a manual due diligence check and lock your cards temporarily. +--- -- **Merchant in a Restricted Country:** Transactions will be declined if the merchant is in a restricted country. +**Still have questions?** Reach out to Concierge for further assistance. 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 ee03a18033ea..2c2302916bc1 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 @@ -6,56 +6,79 @@ description: Expensify Card Settings for Employees # Using Your Expensify Visa® Commercial Card ### Activate Your Card -You can start using your card immediately upon receipt by logging into your Expensify account, heading to your Home tab, and following the prompts on the _**Activate your Expensify Card**_ task. +To activate your card: +1. Log into your Expensify account. +2. Go to your **Home tab**. +3. Follow the prompts on the **Activate your Expensify Card** task. +Once activated, you can start using your card immediately! -### Review your Card's Smart Limit -Check your card’s Smart Limit via _**Settings > Account > Credit Card Import**_: -- This limit is the total amount of unapproved expenses you can have on the card. -- If a purchase is more than your card's Smart Limit, it will be declined. +### Review Your Card's Smart Limit +You can check your card’s **Smart Limit** by navigating to **Settings > Account > Credit Card Import**. +- The **Smart Limit** is the total amount of unapproved expenses you can have on your card. +- If a purchase exceeds your card's Smart Limit, it will be declined. -## Managing Expenses -- **Submit Expenses Promptly**: Submit your expenses regularly to restore your full limit. Contact your admin if you need a limit adjustment. -- **Using Your Card**: Swipe your Expensify Card like any other card. You’ll receive instant alerts on your phone for SmartScan receipts. SmartScanned receipts will merge automatically with card expenses. -- **eReceipts**: If your organization doesn’t require itemized receipts, Expensify will generate IRS-compliant eReceipts for all non-lodging transactions. -- **Reporting Expenses**: Report and submit Expensify Card expenses as usual. Approved expenses refresh your Smart Limit. +--- + +## Managing Your Expenses + +- **Submit Expenses Promptly**: Regularly submit your expenses to restore your full limit. If needed, contact your admin for a limit adjustment. +- **Using Your Card**: Swipe your Expensify Card like any other. You'll get instant alerts for **SmartScan receipts**, which will automatically merge with your card expenses. +- **eReceipts**: If your organization doesn't require itemized receipts, Expensify will generate IRS-compliant **eReceipts** for all non-lodging transactions. +- **Reporting Expenses**: Submit your **Expensify Card expenses** as usual. Once approved, your **Smart Limit** will be refreshed. + +--- ## Enabling Notifications -Download the Expensify mobile app and enable push notifications to stay updated on spending activity and potential fraud. -#### For iPhone: -1. Open the Expensify app and tap the three-bar icon in the upper-left corner. -2. Tap _**Settings > enable Receive real-time alerts**_. -3. Accept the confirmation to access your iPhone’s notification settings for Expensify. -4. Turn on **Allow Notifications** and select your notification types. +Stay updated on your spending activity and potential fraud by enabling **push notifications** in the Expensify mobile app. + +### For iPhone: +1. Open the Expensify app. +2. Tap the **three-bar icon** in the top-left corner. +3. Go to **Settings > Enable Receive real-time alerts**. +4. Confirm the prompt to access your iPhone's notification settings. +5. Turn on **Allow Notifications** and choose your preferred notification types. -#### For Android: -1. Go to _**Settings > Apps and Notifications**_. -2. Find and open Expensify and enable notifications. -3. Customize your alerts based on your phone model. +### For Android: +1. Go to **Settings > Apps and Notifications**. +2. Find and open **Expensify**. +3. Enable notifications and customize the alerts based on your device model. + +--- ## Using Your Virtual Card -- **Access Details**: You can view your virtual card details (card number, expiration date, CVC) via _**Settings > Account > Credit Card Import > Show Details**_. The virtual and physical cards share the same limit. -- **Purchases**: Use the virtual card for online, in-app, and in-person payments when linked to a mobile wallet (Apple Pay or Google Pay). -#### Adding to a Digital Wallet -To add your Expensify Card to a digital wallet, follow the steps below: - 1. Tap the three-bar icon in the upper-left corner. - 2. Tap _**Settings > Connected Cards**_. - 3. Tap **Add to Apple Wallet** or **Add to Gpay**, depending on your device. - 4. Complete the steps as prompted. +### Accessing Virtual Card Details +To view your virtual card details (card number, expiration date, CVC), go to **Settings > Account > Credit Card Import > Show Details**. +Note: The virtual and physical cards share the same **Smart Limit**. + +### Making Purchases +Use the virtual card for **online, in-app, or in-person** payments when linked to a mobile wallet (e.g., **Apple Pay** or **Google Pay**). + +### Adding to a Digital Wallet +1. Tap the **three-bar icon** in the top-left corner. +2. Go to **Settings > Connected Cards**. +3. Tap **Add to Apple Wallet** or **Add to Google Pay**, depending on your device. +4. Follow the steps to complete the setup. + +--- ## Handling Declines -- **Real-Time Alerts**: Enable real-time alerts in the mobile app (_**Settings > toggle Receive real-time alerts**_) to get notifications for declines. -- **Common Decline Reasons**: - - **Insufficient Limit**: Transactions exceeding the available limit will be declined. You can check your limit in _**Settings > Connected Cards**_ or _**Settings > Account > Credit Card Import**_. - - **Inactive or Disabled Card**: Ensure your card is active and not disabled by your Domain Admin. - - **Incorrect Information**: Entering incorrect card details (CVC, ZIP, expiration date) will result in declines. - - **Suspicious Activity**: Transactions may be blocked for unusual or suspicious activity. Check the Expensify Home page to approve unusual merchants. Suspicious spending may prompt a manual due diligence check, during which your cards will be locked. - - **Restricted Country**: Transactions from restricted countries will be declined. -{% include faq-begin.md %} +### Real-Time Decline Alerts +To get alerts for declined transactions: +1. Go to **Settings > Toggle Receive real-time alerts** in the mobile app. + +### Common Decline Reasons +- **Insufficient Limit**: Transactions exceeding your available limit will be declined. Check your limit in **Settings > Connected Cards** or **Settings > Account > Credit Card Import**. +- **Inactive or Disabled Card**: Ensure your card is active and not disabled by your Domain Admin. +- **Incorrect Information**: Declines may occur due to incorrect card details (e.g., CVC, ZIP code, expiration date). +- **Suspicious Activity**: Transactions may be blocked if flagged as suspicious. You can approve unusual merchants via your **Expensify Home page**. Suspicious activity may result in a manual review, and your cards could be locked temporarily. +- **Restricted Country**: Transactions from restricted countries will be declined. + +--- -## 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). +## FAQ -{% include faq-end.md %} +## I haven’t received my Expensify Card. What should I do? +For help with a missing card, visit our guide 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/expensify-card/Change-Expensify-Card-limit.md b/docs/articles/expensify-classic/expensify-card/Change-Expensify-Card-limit.md index 81ce761f84f4..e04fb2cdff77 100644 --- a/docs/articles/expensify-classic/expensify-card/Change-Expensify-Card-limit.md +++ b/docs/articles/expensify-classic/expensify-card/Change-Expensify-Card-limit.md @@ -2,24 +2,30 @@ title: Change Expensify Card limit description: Increase or decrease the limit for an Expensify Card or for a group --- -
-You can set Expensify Card limits for each group in your organization, or you can set the limit per card. +You can set a limit for each individual **Expensify Card** or for a group within your organization. Here's how to do both: -# Set a limit per card +## Set a Limit per Card -1. Hover over Settings, then click **Domains**. -2. Click the name of the domain. +1. Hover over **Settings** and click **Domains**. +2. Select the name of the domain you want to modify. 3. Next to the card, click **Edit Limit**. -4. Ensure the Custom Smart Limit toggle is enabled to be able to set a specific card limit. Otherwise, the card limit will be determined by the limit set for the group that the employee is in. -5. In the Limit Amount field, enter the desired limit. If set to $0, the card will be disabled for use until the limit is increased. -6. Click **Save**. +4. Make sure the **Custom Smart Limit** toggle is ON. This allows you to set a specific limit for the card. If it's OFF, the card’s limit will default to the group limit. +5. In the **Limit Amount** field, enter the desired limit: + - **$0** disables the card until the limit is increased. +6. Click **Save**. -# Set a limit per group +## Set a Limit per Group -1. Hover over Settings, then click **Domains**. -2. Click the name of the domain. -3. Click the **Groups** tab on the left. -4. Click the Expensify Card Smart Limit field for the card and enter the desired limit. +1. Hover over **Settings** and click **Domains**. +2. Select the name of the domain. +3. Click the **Groups** tab on the left side. +4. In the **Expensify Card Smart Limit** field, enter the desired limit for the group. -
+# FAQ + +**What happens if I set the limit to $0?** +Setting the limit to $0 will disable the card, meaning it cannot be used until the limit is increased. + +**Can I set different limits for different employees?** +Yes, you can set individual card limits by enabling the **Custom Smart Limit** toggle. Without this, the employee's card will follow the group’s limit. diff --git a/docs/articles/expensify-classic/expensify-card/Deactivate-or-cancel-an-Expensify-Card.md b/docs/articles/expensify-classic/expensify-card/Deactivate-or-cancel-an-Expensify-Card.md index 7a704f024ce7..1056929c4726 100644 --- a/docs/articles/expensify-classic/expensify-card/Deactivate-or-cancel-an-Expensify-Card.md +++ b/docs/articles/expensify-classic/expensify-card/Deactivate-or-cancel-an-Expensify-Card.md @@ -2,43 +2,46 @@ title: Deactivate or cancel an Expensify Card description: Close an Expensify Card --- -
A cardholder can cancel an Expensify Card themselves, or a Domain Admin can deactivate it. You may want to cancel or deactivate a card: - After a fraudulent or suspicious charge - When an Expensify Card is lost or damaged - After an employee leaves the company -# Cardholders +--- + +## For Cardholders -To cancel an Expensify Card assigned to you, +To cancel your Expensify Card: -1. Hover over Settings, then click **Account**. -2. Click the **Credit Card Import** tab. -3. Click **Request a New Card** next to the card. -4. Choose a reason. -5. Confirm your address details for shipping a new card. -6. Consult this [guide](https://help.expensify.com/articles/expensify-classic/expensify-card/Dispute-A-Transaction) for how to dispute fraudulent transactions (where relevant). - -# Domain Admins +1. Hover over **Settings**, then click **Account**. +2. Click the **Credit Card Import** tab. +3. Click **Request a New Card** next to your Expensify Card. +4. Select a reason for the cancellation. +5. Confirm your address for shipping the new card. +6. If needed, follow this [guide](https://help.expensify.com/articles/expensify-classic/expensify-card/Dispute-A-Transaction) to dispute any fraudulent charges. -To deactivate an employee's Expensify Card as a Domain Admin, +--- -1. Hover over Settings, then click **Domains**. -2. Click the name of the domain. -3. Next to the card, click **Edit Limit**. -4. Ensure the Custom Smart Limit toggle is enabled to be able to set a specific card limit. Otherwise, the card limit will be determined by the limit set for the group that the employee is in. -5. In the Limit Amount field, set the limit to $0. The card will be disabled for use until the limit is increased. -6. Click **Save**. +## For Domain Admins -Note: If you have concerns about fraudulent access to a Domain Admin's user account, please message Concierge or email concierge@expensify.com immediately. If necessary, our support team can manually suspend Expensify cards outside of the Expensify Domain as a temporary measure if your account is compromised. +To deactivate an employee's Expensify Card: -# Terminating an old Expensify Card after upgrading to the new Expensify Visa® Commercial Card +1. Hover over **Settings**, then click **Domains**. +2. Click the name of the domain. +3. Next to the card, click **Edit Limit**. +4. Enable the **Custom Smart Limit** toggle to set a specific card limit. If not enabled, the card limit will match the group limit. +5. Set the **Limit Amount** to $0. This disables the card until the limit is increased. +6. Click **Save**. -To terminate old Expensify Cards that have since been upgraded, +**Important:** If you suspect fraudulent access to a Domain Admin account, immediately contact Concierge via the in-app chat or email at concierge@expensify.com. If necessary, we can manually suspend cards outside of the Expensify Domain temporarily. -1. Hover over Settings, then click **Domains**. -2. Click the name of the domain. -3. Next to the card, click **Terminate**. +--- + +## Terminating an Old Expensify Card After Upgrading -
+To terminate old Expensify Cards after upgrading to the new Expensify Visa® Commercial Card: + +1. Hover over **Settings**, then click **Domains**. +2. Click the name of the domain. +3. Next to the card, click **Terminate**. diff --git a/docs/articles/expensify-classic/expensify-card/Dispute-A-Transaction.md b/docs/articles/expensify-classic/expensify-card/Dispute-A-Transaction.md index cb86c340dc81..1a585a73be1c 100644 --- a/docs/articles/expensify-classic/expensify-card/Dispute-A-Transaction.md +++ b/docs/articles/expensify-classic/expensify-card/Dispute-A-Transaction.md @@ -1,62 +1,80 @@ --- -title: Expensify Card - Transaction Disputes & Fraud -description: Understand how to dispute an Expensify Card transaction. +title: Expensify Card - Transaction Disputes & Fraud +description: Learn how to dispute a transaction on your Expensify Card and how to protect yourself from fraud. --- + # Disputing Expensify Card Transactions -While using your Expensify Visa® Commercial Card, you might encounter transaction errors, such as: -- Unauthorized transaction activity -- Incorrect transaction amounts. -- Duplicate charges for a single transaction. -- Missing merchant refunds. -When that happens, you may need to file a dispute for one or more transactions. +If you notice issues with a transaction on your Expensify Visa® Commercial Card, you may need to file a dispute. Common transaction errors include: + +- Unauthorized charges +- Incorrect transaction amounts +- Duplicate charges +- Missing merchant refunds + +When a dispute is necessary, follow the steps below to resolve it. + +## How to Dispute a Transaction + +If you find an error on your Expensify Card transaction, contact us right away at [concierge@expensify.com](mailto:concierge@expensify.com). We’ll ask you a few questions to understand the situation, then start a dispute on your behalf with our card processor. -## Disputing a Transaction -If you notice a transaction error on your Expensify Card, contact us immediately at concierge@expensify.com. We will ask a few questions to understand the situation better, and file a dispute with our card processor on your behalf. +## Common Types of Disputes -## Types of Disputes -The most common types of disputes are: -- Unauthorized or fraudulent disputes +Here are the most common reasons for disputes: + +- Unauthorized or fraudulent transactions - Service disputes -### Unauthorized or fraudulent disputes -- Charges made after your card was lost or stolen. -- Unauthorized charges while your card is in your possession (indicating compromised information). -- Continued charges for a canceled recurring subscription. +### Unauthorized or Fraudulent Transactions -**If there are transactions made with your Expensify Card you don't recognize, you'll want to do the following right away:** -1. Cancel your card by going to _**Settings > Account > Credit Card Import > Request A New Card**_. -2. Enable Two-Factor Authentication (2FA) for added security under _**Settings > Account > Account Details > Two Factor Authentication**_. +These types of disputes occur when: -### Service Disputes -- Received damaged or defective merchandise. -- Charged for merchandise that was never received. -- Double-charged for a purchase made with another method (e.g., cash). -- Made a return but didn't receive a refund. -- Multiple charges for a single transaction. -- Charges settled for an incorrect amount. - -For service disputes, contacting the merchant is often the quickest way to resolve the dispute. - -## Simplifying the Dispute Process -To ensure a smooth dispute process, please: -- Provide detailed information about the disputed charge, including why you're disputing it and any steps you've taken to address the issue. -- If you recognize the merchant but not the charge, contact the merchant directly. -- Include supporting documentation (e.g., receipts, cancellation confirmations) when submitting your dispute to increase the chances of a favorable resolution (recommended but not required). -- Make sure the transaction isn't pending (pending transactions cannot be disputed). +- Charges are made after your card is lost or stolen +- There are unauthorized charges while your card is still in your possession (indicating possible compromised card information) +- Recurring charges continue after a subscription is canceled + +**To protect your account from further unauthorized charges:** + +1. **Cancel your card**: + Go to **Settings** > **Account** > **Credit Card Import** > **Request A New Card**. +2. **Enable Two-Factor Authentication (2FA)**: + Activate 2FA for added security under **Settings** > **Account** > **Account Details** > **Two-Factor Authentication**. + +### Service Disputes + +These disputes involve issues with the product or service, such as: + +- Damaged or defective merchandise +- Merchandise that was never received +- Double charges (e.g., you paid with another method, like cash) +- No refund after returning an item +- Multiple charges for a single transaction +- Charges made for the wrong amount + +For service-related disputes, contact the merchant first. This is usually the fastest way to resolve the issue. + +## Tips for a Smooth Dispute Process + +To ensure your dispute is resolved quickly: + +- Provide detailed information about the transaction and the issue. Explain why you're disputing the charge and any steps you’ve taken to address it. +- If you recognize the merchant but not the charge, contact the merchant first. +- Include supporting documents, like receipts or cancellation confirmations. While not required, they can help increase the chances of a successful resolution. +- Ensure the transaction isn’t pending, as pending charges cannot be disputed. + +## FAQ + +### **How does Expensify protect me from fraud?** -{% include faq-begin.md %} +You’ll receive real-time push notifications for every card charge, allowing you to spot any issues immediately. Expensify also uses advanced algorithms to detect and block suspicious activity. -## **How am I protected from fraud using the Expensify Card?** -Real-time push notifications alert you of every card charge upfront, helping identify potential issues immediately. Expensify also leverages sophisticated algorithms to detect and/or block unusual card activity. +If you spot any suspicious transactions, you can dispute them by contacting Concierge via the Expensify app or by emailing [concierge@expensify.com](mailto:concierge@expensify.com). You can also cancel your Expensify Card directly within our platform. -Expensify cardholders can dispute suspicious transactions directly via Concierge, either within the Expensify app or by emailing [concierge@expensify.com](mailto:concierge@expensify.com). Cardholders can also cancel their Expensify Card anytime within our platform. +### **How long does the dispute process take?** -## **How long does the dispute process take?** -The dispute process generally takes up to 90 days. It depends on the type of dispute. +The dispute process typically takes up to 90 days, depending on the type of dispute. -## **Can I cancel a dispute?** -Contact Concierge if you've filed a dispute and want to cancel it. You might do this if you recognize a previously reported unauthorized charge or if the merchant has already resolved the issue. +### **Can I cancel a dispute?** -{% include faq-end.md %} +Yes. If you've filed a dispute and wish to cancel it, contact Concierge. This may happen if you recognize a previously unauthorized charge or if the merchant has already resolved the issue. diff --git a/docs/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation.md b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation.md index 9f32e8d2e361..d131f4648f4f 100644 --- a/docs/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation.md +++ b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation.md @@ -1,18 +1,18 @@ --- -title: Expensify Card reconciliation -description: Learn how to reconcile expenses from Expensify Cards through auto-reconciliation or manual methods. +title: Expensify Card Reconciliation +description: Learn how to reconcile expenses from Expensify Cards through Continuous Reconciliation or manual methods. --- -To manage unapproved Expensify Card expenses after closing your books for the month, you can set up **auto-reconciliation** with an accounting integration or **manually reconcile** the expenses. +To manage unapproved Expensify Card expenses after closing your books for the month, you can set up **Continuous Reconciliation** with an accounting integration or **manually reconcile** the expenses. --- -# Set Up Automatic Reconciliation +# Set Up Continuous Reconciliation -Auto-reconciliation deducts Expensify Card purchases from your company's settlement account on a daily or monthly basis. +Continuous Reconciliation deducts Expensify Card purchases from your company's settlement account on a daily or monthly basis. _**Note: A business bank account must be linked as your settlement account to complete this process.**_ -## Steps to set up auto-reconciliation: +## Steps to set up Continuous Reconciliation: 1. Go to **Settings > Domains**. 2. Click your desired domain name. 3. Under the **Company Cards** tab, find the **Imported Cards** section and select the desired Expensify Card from the dropdown. @@ -104,7 +104,11 @@ The filtered list will show expenses with settlement entries but no correspondin - Run the settlements report in the Reconciliation Dashboard. - Compare **Approved** activity to posted activity in your accounting system. -## What if Auto-Reconciliation is disabled for Expensify Cards? +## What if Continuous Reconciliation is disabled for Expensify Cards? A Domain Admin can set export accounts for individual cards via: **Settings > Domains > [Domain Name] > Company Cards > Edit Exports**. + +## How can I use Expensify's Continuous Reconciliation with Sage Intacct Smart Rules, and why are there issues? + +Due to the highly customizable nature of Sage Intacct Smart Rules, Continuous Reconciliation may encounter conflicts, especially when Expensify attempts to create vendor accounts during the reconciliation process. To resolve this, you can temporarily disable all Smart Rules in Sage Intacct, allow Expensify to create the necessary vendor accounts, and then re-enable the Smart Rules. However, if some Smart Rules are implemented via a Sage Intacct Package and cannot be easily disabled, you may need to manually adjust the rules after account creation. This process might need to be repeated if new employees submit reports in the future. Expensify creates vendor accounts to associate reports with the email addresses that submitted them, and the "vendor" field is included in the journal entries posted via Continuous Reconciliation. diff --git a/docs/articles/expensify-classic/expensify-card/Expensify-Card-Statements.md b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Statements.md new file mode 100644 index 000000000000..6b8aa740e6e0 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Statements.md @@ -0,0 +1,90 @@ +--- +title: Expensify Card Statements +description: Learn how to access and manage your Expensify Card statements and settlements. +--- + +The Expensify Card provides detailed statements that help you track transactions and settlements with ease. This guide explains how to access, export, and manage your statements while understanding key details like settlement frequency and outstanding balances. + +# Accessing Your Statement +To view your Expensify Card statement: +1. Ensure your domain uses the Expensify Card and has a validated Business Bank Account. +2. Navigate to **Settings > Domains > [Your Domain Name] > Company Cards**. +3. Click the **Reconciliation** tab and select **Settlements**. +4. Your statement will display: + - **Transactions (Debits)**: Individual card purchases (transactions may take up to 1-2 business days to appear). + - **Settlements (Credits)**: Payments made to cover transactions. + +--- + +# Key Information in the Statement +Each statement includes the following details: +- **Date**: The posted date of each transaction and payment. +- **Entry ID**: A unique identifier grouping payments and transactions. +- **Withdrawn Amount**: The total debited from your Business Bank Account. +- **Transaction Amount**: The purchase amount for each transaction. +- **User Email**: The email address of the cardholder. +- **Transaction ID**: A unique identifier for each transaction. + +![Expanded card settlement that shows the various items that make up each card settlement.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExpanded.png){:width="100%"} + +**Note:** Statements only include payments from active Business Bank Accounts under **Settings > Account > Payments > Business Accounts**. Payments from deleted accounts will not appear. + +--- +# Exporting Statements +To download a statement: +1. Log in to Expensify. +2. Go to **Settings > Domains > Company Cards**. +3. Click the **Reconciliation** tab and select **Settlements**. +4. Enter the start and end dates. +5. Click **Search** to view the statement. +6. Click **Download** to export it as a CSV file. + +![Click the Download CSV button in the middle of the page to export your card settlements.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExport.png){:width="100%"} + +--- +# Expensify Card Settlement Frequency +You can choose between two settlement options: +- **Daily Settlement**: Your balance is paid in full every business day. +- **Monthly Settlement**: Your balance is settled once per month on a predetermined date (available for Plaid-connected accounts with no recent negative balances). + +**Updating Settlement Frequency:** +1. Go to **Settings > Domains > [Your Domain Name] > Company Cards**. +2. Click the **Settings** tab. +3. Select either **Daily** or **Monthly** from the dropdown menu. +4. Click **Save** to confirm. + +**Note:** You cannot choose a specific settlement date beyond the available daily or monthly options. + +--- +# How Settlement Works +- On your scheduled settlement date, Expensify calculates the total of all posted transactions. +- The total settlement amount is withdrawn from your Verified Business Bank Account, resetting your card balance to $0. +- To change your settlement frequency or bank account: + 1. Go to **Settings > Domains > [Your Domain Name] > Company Cards**. + 2. Click the **Settings** tab. + 3. Select new options from the dropdown menu. + 4. Click **Save** to confirm. + +![Change your card settlement account or settlement frequency via the dropdown menus in the middle of the screen.](https://help.expensify.com/assets/images/ExpensifyHelp_CardSettings.png){:width="100%"} + +--- +# FAQ + +## Can I pay my balance early if I’ve reached my Domain Limit? +- **Monthly Settlement**: Click **Settle Now** to manually initiate payment. +- **Daily Settlement**: Balances settle automatically. + +## Will our Domain Limit change if our Verified Bank Account balance increases? +Yes, your limit may adjust based on cash balance, spending patterns, and Expensify usage history. If your bank account is connected via Plaid, updates occur within 24 hours of a fund transfer. + +## How is the "Amount Owed" on the card list calculated? +It includes all pending and posted transactions since the last settlement. Only posted transactions are included in the actual settlement withdrawal. + +## How can I view unsettled expenses? +1. Check the date of your last settlement. +2. Go to **Settings > Domains > Company Cards > Reconciliation**. +3. Click the **Expenses** tab. +4. Set the start date to the day after the last settled expenses and the end date to today. +5. The **Imported Total** will display the outstanding amount, with a breakdown of individual expenses available. + +For additional support, contact Concierge via Expensify. diff --git "a/docs/articles/expensify-classic/expensify-card/Set-Up-the-Expensify-Visa\302\256-Commercial-Card-for-your-Company.md" "b/docs/articles/expensify-classic/expensify-card/Set-Up-the-Expensify-Visa\302\256-Commercial-Card-for-your-Company.md" index 8f6a3f1a908d..d5b9f3febf7e 100644 --- "a/docs/articles/expensify-classic/expensify-card/Set-Up-the-Expensify-Visa\302\256-Commercial-Card-for-your-Company.md" +++ "b/docs/articles/expensify-classic/expensify-card/Set-Up-the-Expensify-Visa\302\256-Commercial-Card-for-your-Company.md" @@ -1,69 +1,83 @@ --- -title: Set Up the Expensify Visa® Commercial Card for your Company -description: Details on setting up the Expensify Card for your company as an admin +title: Set Up the Expensify Visa® Commercial Card for Your Company +description: Step-by-step guide for admins to set up the Expensify Card for their organization. --- + # Overview -If you’re an admin interested in rolling out the Expensify Visa® Commercial Card for your organization, you’re in the right place. This article will cover how to qualify and apply for the Expensify Card program and begin issuing cards to your employees. +If you're an admin looking to roll out the Expensify Visa® Commercial Card for your organization, you're in the right place. This guide will walk you through the steps to qualify, apply for, and issue the Expensify Card to your employees. + +# How to Qualify for the Expensify Card Program + +Before you can apply for the Expensify Card, make sure you meet the following prerequisites: + +1. **Email Address**: The email associated with your Expensify account must be from a private domain. +2. **Claim Your Domain**: You must claim your private domain in Expensify. +3. **US Business Bank Account**: You need to add and verify a US business bank account to your Expensify account. + +### Claim Your Domain + +To claim a domain, you must be a workspace admin and use a company email address matching the domain you want to claim. After setting up your account and workspace, go to **Settings > Domains** to claim your domain. + +### Add and Verify Your US Business Bank Account -# How to qualify for the Expensify Card program +To add your business bank account, go to **Settings > Account > Payments** and click **Add Verified Bank Account**. Follow the setup steps and complete the verification process. -There are three prerequisites to consider before applying for the Expensify Card: +# How to Apply for the Expensify Card -1. The email address associated with your account must be on a private domain -2. You must claim your private domain in Expensify -3. You must add and verify a US business bank account to your Expensify account - -To claim a domain, you must be a workspace admin with a company email address matching the domain you want to claim. After you create an account and set up a workspace, head to **Settings > Domains** to claim your domain. +Once your domain is claimed and your business bank account is verified, you can apply for the Expensify Card. You can apply in multiple ways via the web: -You can add a business bank account by navigating to **Settings > Account > Payments** and clicking Add Verified Bank Account. Follow the setup steps and complete the verification process as required. +### From the Home Page -# How to apply for the Expensify Card +1. Log into your Expensify account. +2. Go to your account's home page. +3. Find the task titled “Introducing the Expensify Card” and click **Enable my Expensify Cards** to start the application. -Once you’ve claimed your domain and added a verified US business bank account, you can apply for the Expensify Card. There are multiple ways to apply for the card from the web: +### From the Company Cards Page -## From the home page +1. Log into your Expensify account. +2. Go to **Settings > Domains > _Domain Name_ > Company Cards**. +3. Click **Get the Card**. -1. Log into your Expensify account using your preferred web browser -2. Head to your account’s home page -3. On the task that says “Introducing the Expensify Card,” click **Enable my Expensify Cards** to get started +Once your application is received, we'll review it and send you a confirmation email with next steps. -## From the Company Cards page +# How to Issue Cards -1. Log into your Expensify account using your preferred web browser -2. Head to **Settings > Domains > _Domain Name_ > Company Cards** -3. Click **Get the Card** +After approval, it’s time to set card limits for your employees. When you set a limit, the employee will receive an email and see a task on their home page asking for their shipping address. Once they enter the details, their physical card will be shipped, and a virtual card will be created for immediate use. -After we receive your application, we’ll review it ASAP and send you a confirmation email with the next steps once we have them. +### Set a Card Limit for an Employee -# How to issue cards +1. Go to **Settings > Domains > _Domain Name_ > Company Cards**. +2. Click **Edit Limit** next to the employee who needs a card. +3. Set a limit greater than $0 to issue the card. -After you’ve been approved, it’s time to set limits for your employees. Setting a limit triggers an email and task on the home page requesting the employee’s shipping address. Once they enter their details, a card will be shipped to them. We’ll also create a virtual card for the employee that can be used immediately. +### Set Limits for a Group of Employees -To set a limit, head over to the Company Cards UI via **Settings > Domains > _Domain Name_ > Company Cards**. Click the **Edit Limit** button next to members who need a card assigned, and set a non-$0 to issue them a card. +If you have a validated domain, you can set limits for multiple employees at once: -If you have a validated domain, you can set a limit for multiple members by setting a limit for an entire domain group via **Settings > Domains > _Domain Name_ > Groups**. Keep in mind that custom limits that are set on an individual basis will override the group limit. +1. Go to **Settings > Domains > _Domain Name_ > Groups**. +2. Set a limit for the entire group. -The Company Cards page will act as a hub to view all employees who have been issued a card and where you can view and edit the individual card limits. You’ll also be able to see anyone who has requested a card but doesn’t have one yet. +Keep in mind that individual limits will override group limits. -{% include faq-begin.md %} +The **Company Cards** page serves as a hub for managing card limits and viewing all employees with cards. You can also see who has requested a card but hasn’t received one yet. -## Are there foreign transaction fees? +# FAQ -There are no foreign transaction fees when using your Expensify Card for international purchases. +## Are There Foreign Transaction Fees? -## How does the Expensify Card affect my or my company's credit score? +No, there are no foreign transaction fees for international purchases made with your Expensify Card. -Applying for or using the Expensify Card will never have any positive or negative effect on your personal credit score or your business's credit score. We do not consider your or your business' credit score when determining approval and your card limit. +## Does the Expensify Card Affect My Credit Score? -## How much does the Expensify Card cost? +Using the Expensify Card has no impact on your personal or business credit score. Your credit score is not considered when determining card approval or limit. -The Expensify Card is a free corporate card, and no fees are associated with it. In addition, if you use the Expensify Card, you can save money on your Expensify subscription (based on how much of your approved spend occurs on the Expensify Card, compared with other approved spend, in each month). +## What Are the Costs of the Expensify Card? -## If I have staff outside the US, can they use the Expensify Card? +The Expensify Card is free. There are no fees associated with it. Additionally, using the card can help you save money on your Expensify subscription based on your approved spend. -As long as the verified bank account used to apply for the Expensify Card is a US bank account, your cardholders can be anywhere in the world. +## Can My Staff Outside the US Use the Expensify Card? -Otherwise, the Expensify Card is not available for customers using non-US banks. With that said, launching international support is a top priority for us. Let us know if you’re interested in contacting support, and we’ll reach out as soon as the Expensify Card is available outside the United States. +Yes, as long as your business bank account is US-based, your cardholders can be located anywhere in the world. -{% include faq-end.md %} +Currently, the Expensify Card is not available for customers with non-US bank accounts, but international support is a priority. Contact support if you're interested in being notified when it becomes available. diff --git a/docs/articles/expensify-classic/expensify-card/Statements.md b/docs/articles/expensify-classic/expensify-card/Statements.md deleted file mode 100644 index eb797f0cee4b..000000000000 --- a/docs/articles/expensify-classic/expensify-card/Statements.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: — Expensify Card Statements and Settlements -description: Understand how to access your Expensify Card Statement ---- - -## Expensify Card Statements -Expensify offers several settlement types and a detailed statement of transactions and settlements. - -### Accessing the Statement -- If your domain uses the Expensify Card and you have a validated Business Bank Account, access the statement at _**Settings > Domains > Company Cards > Reconciliation Tab > Settlements**_. -- The statement shows individual transactions (debits) and their corresponding settlements (credits). - -### Key Information in the Statement -- **Date:** Debit date for card payments; purchase date for transactions. -- **Entry ID:** Unique ID grouping card payments and transactions. -- **Withdrawn Amount:** Amount debited from the Business Bank Account for card payments. -- **Transaction Amount:** Expense purchase amount for card transactions. -- **User Email:** Cardholder’s Expensify email address. -- **Transaction ID:** Unique ID for locating transactions and assisting support. - -![Expanded card settlement that shows the various items that make up each card settlement.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExpanded.png){:width="100%"} - -**Note:** The statement only includes payments from existing Business Bank Accounts under **Settings > Account > Payments > Business Accounts**. Deleted accounts' payments won't appear. - -## Exporting Statements -1. Log in to the web app and go to **Settings > Domains > Company Cards**. -2. Click the **Reconciliation** tab and select **Settlements**. -3. Enter the start and end dates for your statement. -4. Click **Search** to view the statement. -5. Click **Download** to export it as a CSV. - -![Click the Download CSV button in the middle of the page to export your card settlements.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExport.png){:width="100%"} - -## Expensify Card Settlement Frequency -- **Daily Settlement:** Balance paid in full every business day with an itemized debit each day. -- **Monthly Settlement:** Balance settled monthly on a predetermined date with one itemized debit per month (available for Plaid-connected accounts with no recent negative balance). - -## How Settlement Works -- Each business day or on your monthly settlement date, the total of posted transactions is calculated. -- The settlement amount is withdrawn from the Verified Business Bank Account linked to the primary domain admin, resetting your card balance to $0. -- To change your settlement frequency or bank account, go to _**Settings > Domains > [Domain Name] > Company Cards**_, click the **Settings** tab, and select the new options from the dropdown menu. Click **Save** to confirm. - -![Change your card settlement account or settlement frequency via the dropdown menus in the middle of the screen.](https://help.expensify.com/assets/images/ExpensifyHelp_CardSettings.png){:width="100%"} - - -# FAQ - -## Can you pay your balance early if you’ve reached your Domain Limit? -- For Monthly Settlement, use the “Settle Now” button to manually initiate settlement. -- For Daily Settlement, balances settle automatically with no additional action required. - -## Will our domain limit change if our Verified Bank Account has a higher balance? -Domain limits may change based on cash balance, spending patterns, and history with Expensify. If your bank account is connected through Plaid, expect changes within 24 hours of transferring funds. - -## How is the “Amount Owed” figure on the card list calculated? -It includes all pending and posted transactions since the last settlement date. The settlement amount withdrawn only includes posted transactions. - -## How do I view all unsettled expenses? -1. Note the dates of expenses in your last settlement. -2. Go to the **Expenses** tab on the Reconciliation Dashboard. -3. Set the start date after the last settled expenses and the end date to today. -4. The **Imported Total** shows the outstanding amount, and you can click to view individual expenses. diff --git a/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md b/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md index 5b0f0c1c2879..d2fb3a558926 100644 --- a/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md +++ b/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md @@ -1,87 +1,87 @@ --- -title: Unlimited Virtual Cards -description: Learn more about virtual cards and how they can help your business gain efficiency and insight into company spending. +title: Unlimited Virtual Cards +description: Learn about virtual cards and how they help businesses improve efficiency and gain better control over company spending. --- # Overview -For admins to issue virtual cards, your company **must upgrade to Expensify’s new Expensify Visa® Commercial Card.** +To issue virtual cards, your company must upgrade to the **Expensify Visa® Commercial Card**. -Once upgraded to the new Expensify Card, admins can issue an unlimited number of virtual cards with a fixed or monthly limit for specific company purchases or recurring subscription payments _(e.g., Marketing purchases, Advertising, Travel, Amazon Web Services, etc.)._ +Once upgraded, admins can issue an **unlimited number of virtual cards** with either a fixed or monthly limit for specific purchases or recurring subscriptions (e.g., Marketing, Advertising, Travel, AWS, etc.). -This feature supports businesses that require tighter controls on company spending. Customers can set fixed or monthly spending limits for each virtual card. +Virtual cards are ideal for businesses that need **tight control over spending**. You can set individual spending limits for each virtual card. -Use virtual cards if your company needs or wants: +Use virtual cards if your company needs: -- To use one card per vendor or subscription, -- To issue cards for one-time purchases with a fixed amount, -- To issue cards for events or trips, -- To issue cards with a low limit that renews monthly, +- One card per vendor or subscription +- Cards for one-time purchases with a fixed amount +- Cards for events or trips +- Cards with a low limit that renews monthly -Admins can also name each virtual card, making it easy to categorize and assign them to specific accounts upon creation. Naming the card ensures a clear and organized overview of expenses within the Expensify platform. +Admins can name each card for easy tracking and organization. This makes it simple to categorize expenses in the Expensify platform. -# Set up virtual cards +# Set up Virtual Cards -After adopting the new Expensify Card, domain admins can issue virtual cards to any employee using an email matching your domain. Once created and assigned, the card will be visible under the name given to the card. +After upgrading to the Expensify Card, domain admins can issue virtual cards to any employee with an email matching your company domain. Once assigned, the virtual card will appear under the card's assigned name. **To assign a virtual card:** -Head to **Settings** > **Domains** > [**Company Cards**](https://www.expensify.com/domain_companycards) and click the **Issue Virtual Cards** button. From there: +1. Go to **Settings** > **Domains** > [**Company Cards**](https://www.expensify.com/domain_companycards). +2. Click **Issue Virtual Cards**. +3. Enter a card name (e.g., "Google Ads"). +4. Select a domain member to assign the card to. +5. Set a card limit. +6. Choose **Limit Type** (Fixed or Monthly). +7. Click **Issue Card**. -1. Enter a card name (i.e., "Google Ads"). -2. Select a domain member to assign the card to. -3. Enter a card limit. -4. Select a **Limit Type** of _Fixed_ or _Monthly_. -5. Click **Issue Card**. +![The Issue Virtual Cards modal is open in the middle of the screen. There are four options to set; Card Name, Assignee, Card Limit, and Limit type. A cancel (left) and save (right) button are at the bottom right of the modal.]({{site.url}}/assets/images/AdminissuedVirtualCards.png) -![The Issue Virtual Cards modal is open in the middle of the screen. There are four options to set; Card Name, Assignee, Card Limit, and Limit type. A cancel (left) and save (right) button are at the bottom right of the modal.]({{site.url}}/assets/images/AdminissuedVirtualCards.png){:width="100%"} +# Edit Virtual Cards -# Edit virtual cards - -Domain admin can update the details of a virtual card on the [Company Cards](https://www.expensify.com/domain_companycards) page. +Domain admins can edit the details of any virtual card on the [Company Cards](https://www.expensify.com/domain_companycards) page. **To edit a virtual card:** -1. Click the **Edit** button to the right of the card. -2. Change the editable details. -3. Click **Edit Card** to save the changes. +1. Click the **Edit** button next to the card. +2. Update the details as needed. +3. Click **Edit Card** to save changes. -# Terminate a virtual card +# Terminate a Virtual Card -Domain admin can also terminate a virtual card on the [Company Cards](https://www.expensify.com/domain_companycards) page by setting the limit to $0. +Admins can terminate a virtual card by setting the limit to **$0**. **To terminate a virtual card:** -1. Click the **Edit** button to the right of the card. -2. Set the limit to $0. +1. Click the **Edit** button next to the card. +2. Set the limit to **$0**. 3. Click **Save**. -4. Refresh your web page, and the card will be removed from the list. +4. Refresh the page, and the card will be removed from the list. -{% include faq-begin.md %} +# FAQ -## What is the difference between a fixed limit and a monthly limit? +## What’s the difference between a fixed limit and a monthly limit? -There are two different limit types that are best suited for their intended purpose. -- _Fixed-limit_ spend cards are ideal for one-time expenses or providing employees with access to a card for a designated purchase. -- _Monthly_ limit spend cards are perfect for managing recurring expenses such as subscriptions and memberships. +- **Fixed-limit cards** are ideal for one-time purchases or specific purchases with a set amount. +- **Monthly-limit cards** are best for recurring expenses like subscriptions or memberships. -A virtual card with either of these limit types doesn't share its limit with any other cards, including the cardholder's smart limit cards. +Both limit types are independent of other cards, including any smart limit cards. ## Where can employees see their virtual cards? -Employees can see their assigned virtual cards by navigating to **Settings** > **Account** > [**Credit Cards Import**](https://www.expensify.com/settings?param=%7B%22section%22:%22creditcards%22%7D) in their account. - -On this page, employees can see the remaining card limit, the type of card (e.g., fixed or monthly), and the name given to the card. +Employees can view their assigned virtual cards by going to **Settings** > **Account** > [**Credit Cards Import**](https://www.expensify.com/settings?param=%7B%22section%22:%22creditcards%22%7D) in their account. -When the employee needs to use the card, they’ll click the **Show Details** button to expose the card details for making purchases. +Here, they can see: -_Note: If the employee doesn’t have Two-Factor Authentication (2FA) enabled when they display the card details, they’ll be prompted to enable it. Enabling 2FA for their account provides the best protection from fraud and is **required** to dispute virtual card expenses._ +- Remaining card limit +- Card type (e.g., fixed or monthly) +- The name of the card -## What do I do when there is fraud on one of our virtual cards? +To view card details, they’ll click **Show Details**. -If you or an employee loses their virtual card, experiences fraud, or suspects the card details are no longer secure, please [request a new card](https://help.expensify.com/articles/expensify-classic/expensify-card/Request-the-Card) immediately. A domain admin can also set the limit for the card to $0 to terminate the specific card immediately if the employee cannot take action. +_Note: If the employee hasn’t enabled Two-Factor Authentication (2FA), they will be prompted to enable it before displaying card details. 2FA is required for disputing virtual card charges._ -When the employee requests a new card, the compromised card will be terminated immediately. This is best practice for any Expensify Card, and if fraud is suspected, action should be taken as soon as possible to reduce the company's financial impact. +## What should I do if there’s fraud on one of our virtual cards? -{% include faq-end.md %} +If a virtual card is lost, compromised, or you suspect fraud, request a new card immediately. The domain admin can set the card limit to **$0** to terminate it if the employee is unavailable. +When a new card is requested, the old card is immediately deactivated. Always act quickly in the case of fraud to minimize financial impact. diff --git a/docs/articles/expensify-classic/getting-started/Join-Your-Company's-Workspace.md b/docs/articles/expensify-classic/getting-started/Join-Your-Company's-Workspace.md new file mode 100644 index 000000000000..cd58d5d6b64d --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Join-Your-Company's-Workspace.md @@ -0,0 +1,132 @@ +--- +title: Join Your Company's Workspace +description: Get started with Expensify as an employee or company member. +--- + +Getting started with Expensify is quick and easy, whether you're a new employee or an existing team member joining a company workspace. This guide walks you through downloading the app, setting up your profile, managing expenses, and securing your account. + +--- + +# 1. Download the Mobile App + +Upload expenses and check reports from your phone by downloading the Expensify mobile app. + +- [iOS](https://apps.apple.com/us/app/expensify-expense-tracker/id471713959) +- [Android](https://play.google.com/store/apps/details?id=org.me.mobiexpensifyg&hl=en_US&gl=US) + +For a full walkthrough on creating and submitting expenses via the mobile app, click [here](https://expensify.navattic.com/fl150n1n)! + +--- + +# 2. Add Your Name and Photo + +**Desktop:** +1. Click your profile image in the main menu. +2. Hover over the profile picture and click **Change**. +3. Update your profile: + - **Name**: Enter your first and last name, then click **Update**. + - **Photo**: Click **Add Photo** to upload an image. + +**Mobile:** +1. Tap the ☰ menu icon in the top left. +2. Tap your profile picture. +3. Tap the **Edit** icon to update your name or photo. + - **Name**: Enter your first and/or last name, then tap **Update**. + - **Photo**: Tap **Upload Photo**, then: + - Tap the camera button to take a new photo. + - Tap the photo icon to select an existing image. + +--- + +# 3. Meet Concierge + +Concierge is your personal assistant, available on both desktop and mobile. It helps by: + +- Reminding you to submit expenses. +- Alerting you when more information is needed. +- Providing updates on new features. + +For support, click the green chat bubble at the bottom of the screen to chat with Concierge. + +--- + +# 4. Learn How to Add an Expense + +Employees can document reimbursable and non-reimbursable expenses using SmartScan or manual entry. + +## SmartScan a Receipt + +**Desktop:** +1. Click the **Expenses** tab. +2. Click the **+** icon and select **Scan Receipt**. +3. Upload a saved receipt image. + +**Mobile:** +1. Tap the camera icon in the bottom right. +2. Upload or take a receipt photo: + - **Upload**: Tap the photo icon and select an image. + - **Take a photo**: Ensure details are clear, then capture the image. + +*You can also email receipts to receipts@expensify.com from any email address associated with your Expensify account.* + +## Manually Enter an Expense + +Desktop: +1. Click the **Expenses** tab. +2. Click the **+** icon. +3. Select the expense type and enter details. +4. Click **Save**. + +Mobile: +1. Tap the ☰ menu icon and select **Expenses**. +2. Tap the **+** icon. +3. Select the expense type and enter details. +4. Tap **Save**. + +--- + +# 5. Create & Submit an Expense Report + +Your expenses may be automatically added to a report. If not, follow these steps to create and submit one. + +**Desktop:** +1. Click the **Reports** tab. +2. Click **New Report** > **Expense Report**. +3. Click **Add Expenses** and select expenses. +4. Click **Submit**, enter approver details, and click **Send**. + +**Mobile:** +1. Tap ☰ > **Reports**. +2. Tap the **+** icon and select **Expense Report**. +3. Tap **Add Expenses**, then select expenses. +4. Tap **Submit Report**, add approver details, and tap **Submit**. + +--- + +# 6. Add a Secondary Login + +Connect a personal email to ensure access to Expensify, even if your employer changes. + +*Setting up this feature is available only on the Expensify website.* + +1. Go to **Settings** > **Account**. +2. Under **Account Details**, click **Add Secondary Login**. +3. Enter your email or phone number. +4. Verify your new login with the Magic Code sent to you. + +--- + +# 7. Secure Your Account + +Add an extra layer of security to help keep your financial data safe and secure by enabling two-factor authentication (2FA). Setting this up requires you to enter a code generated by your preferred authenticator app (like Google Authenticator or Microsoft Authenticator) to log in. + +*Setting up this feature is available only on the Expensify website.* + +1. Go to **Settings** > **Account**. +2. Under **Two-Factor Authentication**, enable the toggle. +3. Save a copy of your backup codes. +4. Use an authenticator app to scan the QR code. +5. Enter the 6-digit code from the app and click **Verify**. + +--- + diff --git a/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md b/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md deleted file mode 100644 index 6067df6874d4..000000000000 --- a/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md +++ /dev/null @@ -1,259 +0,0 @@ ---- -title: Join your company's workspace -description: Get started with Expensify as an employee or other company member ---- -
- -# Overview - -Welcome to Expensify! If you received an invitation to join your company’s Expensify workspace, follow the steps below to get started. - -# 1. Download the mobile app - -Upload your expenses and check your reports right from your phone by downloading the Expensify mobile app. You can search for “Expensify” in the app store, or tap one of the links below. - -[iOS](https://apps.apple.com/us/app/expensify-expense-tracker/id471713959) -| [Android](https://play.google.com/store/apps/details?id=org.me.mobiexpensifyg&hl=en_US&gl=US) - -For a full walkthrough on creating and submitting expenses via the mobile app, click [here](https://expensify.navattic.com/fl150n1n)! - -# 2. Add your name and photo - -{% include selector.html values="desktop, mobile" %} -{% include option.html value="desktop" %} -
    -
  1. Click the profile image at the top of the main menu.
  2. -
  3. Hover over the profile picture and click Change.
  4. -
  5. Update your profile picture and name. -
      -
    • Name: Enter your first and last name into the fields and click Update. Note that this name will be visible to anyone in your company workspace.
    • -
    • Photo: Click Add Photo.
    • -
    -
  6. -
- -{% include end-option.html %} - -{% include option.html value="mobile" %} - -
    -
  1. Tap the ☰ menu icon in the top left.
  2. -
  3. Tap the profile picture icon.
  4. -
  5. Tap the Edit icon next to your name and update your name or photo. -
      -
    • Name: Enter your first and/or last name into the fields and tap Update. Note that this name will be visible to anyone in your company workspace.
    • -
    • Photo: Tap Upload Photo and either:
    • -
        -
      • Tap the capture button to take a new photo.
      • -
      • Tap the photo icon on the left to select a saved photo.
      • -
      -
    -
  6. -
- -{% include end-option.html %} -{% include end-selector.html %} - - -# 3. Meet Concierge -Your personal assistant, Concierge, lives on your Expensify Home page on both desktop and the mobile app. - -Concierge will walk you through setting up your account and also provide: -
    -
  • Reminders to do things like submit your expenses
  • -
  • Alerts when more information is needed on an expense report
  • -
  • Updates on new and improved account features
  • -
- -You can also get support at any time by clicking the green chat bubble in the right corner. This will open a chat with Concierge where you can ask questions and receive direct support. - -# 4. Learn how to add an expense -As an employee, you may need to document reimbursable expenses (like business travel paid for with personal funds) or non-reimbursable expenses (like a lunch paid for with a company card). You can create an expense automatically by SmartScanning a receipt, or you can enter it manually. - -## SmartScan a receipt - -You can upload pictures of your receipts to Expensify, and SmartScan will automatically capture the receipt details, including the merchant, date, total, and currency. - -{% include selector.html values="desktop, mobile" %} -{% include option.html value="desktop" %} -
    -
  1. Click the Expenses tab.
  2. -
  3. Click the + icon in the top right and select Scan Receipt.
  4. -
  5. Upload a saved image of a receipt.
  6. -
- -{% include end-option.html %} - -{% include option.html value="mobile" %} -
    -
  1. Open the mobile app and tap the camera icon in the bottom right corner.
  2. -
  3. Upload or take a photo of your receipt.
  4. -
      -
    • Upload a photo: Click the photo icon in the left corner and select the image from your device.
    • -
    • Take a photo: Click the camera icon in the right corner to select the mode, make sure all of the transaction details are clearly visible, and then take the photo.
    • -
    -
  5. Normal Mode: Upload one receipt.
  6. -
  7. Rapid Fire Mode: Upload multiple receipts at once.
  8. -
-{% include end-option.html %} -{% include end-selector.html %} - -You can open any receipt and select **Fill out details myself** to add or edit the merchant, date, total, description, category, or add attendees who took part in the expense. You can also check that the expense is correctly labeled as reimbursable or non-reimbursable and split the expense if multiple expenses are included on one receipt. - -*Note: You can also email receipts to SmartScan by sending them to receipts@expensify.com from an email address tied to your Expensify account (either a primary or secondary email). SmartScan will automatically pull all of the details from the receipt, fill them in for you, and add the receipt to the Expenses tab on your account.* - -## Manually enter an expense - -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -
    -
  1. Click the Expenses tab.
  2. -
  3. Click the + icon in the top right.
  4. -
  5. Select the type of expense and enter the expense details.
  6. -
      -
    • Manually create: Manually enter receipt details.
    • -
    • Scan receipt: Upload a saved image of a receipt.
    • -
    • Create multiple: Upload expenses in bulk.
    • -
    • Time: Create an expense based on hours.
    • -
    • Distance: Create an expense based on distance.
    • -
        -
      • Manually Create: Manually enter the distance details for the expense.
      • -
      • Create from Map: Enter the start and end destination and Expensify will help you create a receipt for the trip.
      • -
      -
    -
  7. Click Save.
  8. -
-{% include end-option.html %} - -{% include option.html value="mobile" %} -
    -
  1. Tap the ☰ menu icon in the top left.
  2. -
  3. Tap Expenses.
  4. -
  5. Tap the + icon in the top right.
  6. -
  7. Tap the correct expense type and enter the expense details.
  8. -
      -
    • Manually create: Manually enter receipt details.
    • -
    • Time: Enter work time and rate.
    • -
    • Manually create (Distance): Manually enter trip details by total distance.
    • -
    • Odometer: Manually enter trip details by start and end odometer readings.
    • -
    • Start GPS: Track distance while using the Expensify app to automatically calculate the distance in real time during the trip.
    • -
    -
  9. Tap Save.
  10. -
-{% include end-option.html %} - -{% include end-selector.html %} - -# 5. Learn how to create & submit an expense report - -Once you’ve created your expenses, they may be automatically added to an expense report if your company has this feature enabled. If not, your next step will be to add your expenses to a report and submit them for payment. - -{% include selector.html values="Desktop, Mobile" %} - -{% include option.html value="desktop" %} - -
    -
  1. Click the Reports tab.
  2. -
      -
    • If a report has been automatically created for your most recently submitted expense, then you don’t have to do anything else—your report is already created and will also be automatically submitted.
    • -
    • If a report has not been automatically created, follow the steps below.
    • -
    -
  3. Click New Report, or click the New Report dropdown and select Expense Report.
  4. -
  5. Click Add Expenses.
  6. -
  7. Click an expense to add it to the report.
  8. -
      -
    • If an expense you already added does not appear in the list, use the filter on the left to search by the merchant name or change the date range. Note: Only expenses that are not already on a report will appear.
    • -
    -
  9. Once all your expenses are added to the report, click the X to close the pop-up.
  10. -
  11. (Optional) Make any desired changes to the report and/or expenses.
  12. -
      -
    • Click the Edit icon next to the report name to change it. If this icon is not visible, the option has been disabled by your workspace.
    • -
    • Click the X icon next to an expense to remove it from the report.
    • -
    • Click the Expense Details icon to review or edit the expense details.
    • -
    • At the bottom of the report, add comments to include more information.
    • -
    • Click the Attachments icon to add additional attachments.
    • -
    -
  13. When the report is ready to send for approval, click Submit.
  14. -
  15. Enter the details for who will receive a notification email about your report and what they will receive.
  16. -
      -
    • To: Enter the name(s) who will be approving your report (if they are not already listed).
    • -
    • CC: Enter the email address of anyone else who should be notified that your expense report has been submitted. Add a comma between each email address if adding more than one.
    • -
    • Memo: Enter any relevant notes.
    • -
    • Attach PDF: Select this checkbox to attach a copy of your report to the email.
    • -
    -
  17. Click Send.
  18. -
- -{% include end-option.html %} - -{% include option.html value="mobile" %} -
    -
  1. Tap the ☰ menu icon in the top left.
  2. -
  3. Tap Reports.
  4. -
      -
    • If a report has been automatically created for your most recently submitted expense, then you don’t have to do anything else—your report is already created and will also be automatically submitted.
    • -
    • If a report has not been automatically created, follow the steps below.
    • -
    -
  5. Tap the + icon and tap Expense Report.
  6. -
  7. Tap Add Expenses, then tap an expense to add it to the report. Repeat this step until all desired expenses are added. Note: Only expenses that are not already on a report will appear.
  8. -
  9. (Optional) Make any desired changes to the report and/or expenses.
  10. -
      -
    • Tap the report name to change it.
    • -
    • Tap an expense to review or edit the expense details.
    • -
    • At the bottom of the report, add comments to include more information.
    • -
    • Tap the Attachments icon to add additional attachments.
    • -
    -
  11. When the report is ready to send for approval, tap Submit Report.
  12. -
  13. Add any additional sending details and tap Submit.
  14. -
  15. Enter the details for who will receive a notification email about your report and what they will receive.
  16. -
      -
    • To: Enter the name(s) who will be approving your report (if they are not already listed).
    • -
    • CC: Enter the email address of anyone else who should be notified that your expense report has been submitted. Add a comma between each email address if adding more than one.
    • -
    • Memo: Enter any relevant notes.
    • -
    • Attach PDF: Select this checkbox to attach a copy of your report to the email.
    • -
    -
  17. Tap Submit.
  18. -
-{% include end-option.html %} - -{% include end-selector.html %} - -# 6. Add a secondary login - -Connect your personal email address as a secondary login so you always have access to your Expensify account, even if your employer changes. - -*Note: This process is currently not available from the mobile app and must be completed from the Expensify website.* - -
    -
  1. Hover over Settings, then click Account.
  2. -
  3. Under the Account Details tab, scroll down to the Secondary Logins section and click Add Secondary Login.
  4. -
  5. Enter the email address or phone number you wish to use as a secondary login. For phone numbers, be sure to include the international code, if applicable.
  6. -
  7. Find the email or text message from Expensify containing the Magic Code and enter it into the field to add the secondary login.
  8. -
- -# 7. Secure your account - -Add an extra layer of security to help keep your financial data safe and secure by enabling two-factor authentication. This will require you to enter a code generated by your preferred authenticator app (like Google Authenticator or Microsoft Authenticator) when you log in. - -*Note: This process is currently not available from the mobile app and must be completed from the Expensify website.* - -
    -
  1. Hover over Settings, then click Account.
  2. -
  3. Under the Account Details tab, scroll down to the Two Factor Authentication section and enable the toggle.
  4. -
  5. Save a copy of your backup codes. 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.
  6. -
      -
    • 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.
    • -
    -
  7. Click Continue.
  8. -
  9. Download or open your authenticator app and either:
  10. -
      -
    • Scan the QR code shown on your computer 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 open your authenticator app to get the 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. - -
diff --git a/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md b/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md index e3c6c5fa2a46..b5bdf167a908 100644 --- a/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md +++ b/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md @@ -33,6 +33,25 @@ There may be restrictions on your ability to create reports depending on your wo {% include end-selector.html %} +# Move expenses to another report + +{% include selector.html values="desktop" %} + +{% include option.html value="desktop" %} + +To move open expenses from one report to another, follow these steps +1. Select Expenses: +On the Expenses page, select the open expenses you want to move by checking the box next to each expense. +2. Add to Report: +Click the Add to Report button in the top right corner. +3. Choose Report: +Select the report you want to consolidate the expenses into from the drop-down list. + - Only open expenses can be moved between reports. *Note: Submitted expenses in the Processing, Approved, Reimbursed, or Closed state cannot be moved.* + +{% include end-option.html %} + +{% include end-selector.html %} + # Create a new report {% include selector.html values="desktop, mobile" %} diff --git a/docs/articles/expensify-classic/settings/Set-Time-Zone.md b/docs/articles/expensify-classic/settings/Set-Time-Zone.md new file mode 100644 index 000000000000..8136ffb7b5a2 --- /dev/null +++ b/docs/articles/expensify-classic/settings/Set-Time-Zone.md @@ -0,0 +1,24 @@ +--- +title: Set Time Zone +description: Learn how to set your time zone in Expensify for accurate timestamps. +--- + +You can set your time zone manually or allow Expensify to adjust it automatically based on your location. + +Some actions in Expensify are timestamped. To ensure accuracy, make sure your time zone matches your current location. +- **Automatic time zone**: Expensify updates your time zone based on your location. +- **Manual time zone**: You must manually update your time zone when traveling. + +**Note:** This process is only available on the Expensify website and cannot be completed from the mobile app. + +--- + +## Set Your Time Zone + +1. Hover over **Settings**, then click **Account**. +2. Click the **Preferences** tab. +3. Scroll to the **Time Zone** section and choose your preference: + - **Automatic**: Check the **Set my time zone automatically** box to allow Expensify to adjust your time zone based on your location. + - **Manual**: Select your time zone from the list. + +Your time zone settings will now be applied to your Expensify account. diff --git a/docs/articles/expensify-classic/settings/Set-time-zone.md b/docs/articles/expensify-classic/settings/Set-time-zone.md deleted file mode 100644 index 7d4842f936b9..000000000000 --- a/docs/articles/expensify-classic/settings/Set-time-zone.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Set time zone -description: Set your time zone in Expensify ---- -
- -You can manually set your time zone or allow Expensify to automatically set your time zone based on your location. - -{% include info.html %} -Some actions you take in Expensify are timestamped. To ensure the most accurate time is captured, you’ll want to make sure your time zone matches your current location. If you select the automatic time zone option, your time zone will automatically reflect your current location. If you manually set your time zone, you must manually update the time zone when traveling. -{% include end-info.html %} - -*Note: This process is currently not available from the mobile app and must be completed from the Expensify website.* - -1. Hover over Settings, then click **Account**. -2. Click the **Preferences** tab. -3. Scroll down to the Time Zone section and select your time zone preferences. - - Automatic time zone: To allow Expensify to automatically set your time zone based on your location, select the Set my time zone automatically checkbox. - - Manual time zone: To manually select your time zone, select your time zone from the list. - -
diff --git a/docs/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports.md b/docs/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports.md index 5636d1e49f4c..7eec775a1911 100644 --- a/docs/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports.md +++ b/docs/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports.md @@ -1,13 +1,15 @@ --- title: Export Expenses and Reports -description: How to export expenses and reports using custom reports, PDF files, CSVs, and more +description: How to export expenses and reports using custom reports, PDF files, CSVs, and more --- -There are several methods you can use to export your expenses and reports, including: +There are several methods you can use to export your expenses and reports on the Expensify Classic web app, including: - Export as a PDF - Export as a CSV or to an accounting integration - Export using a default or custom export template +Please note that it is currently not possible to export these files using the Expensify Classic mobile app. + # Export PDF 1. Click the **Reports** tab. @@ -33,19 +35,19 @@ The PDF will be downloaded with all expenses, any attached receipts, and all rep # Create custom export templates -If you don't have a direct connection to your accounting system, you can export your expense data to the system for upload as long as the system accepts a CSV file. You can then analyze the data in your favorite spreadsheet program. +If you don't have a direct connection to your accounting system, you can export your expense data to the system for upload as long as the system accepts a CSV file. You can then analyze the data in your favorite spreadsheet program. -Custom export templates can be created and made available to all Workspace Admins for your workspace, or you can create a template that is just for your own use. +Custom export templates can be created and made available to all Workspace Admins for your workspace, or you can create a template that is just for your own use. ## For a workspace {% include info.html %} -Must be a Group Workspace Admin to complete this process. +Must be a Group Workspace Admin to complete this process. {% include end-info.html %} 1. Hover over **Settings** and click **Workspaces**. 2. Select the desired workspace. -3. Click the **Export Formats** tab on the left. +3. Click the **Export Formats** tab on the left. 4. Click **New Export Format**. 5. Enter a name for the export format. 6. Select the format type (e.g., CSV, XLS for Excel, or CSV without BOM for MS Access) @@ -59,7 +61,7 @@ Must be a Group Workspace Admin to complete this process. ## For personal use 1. Hover over **Settings** and click **Account**. -2. Click **Preferences**. +2. Click **Preferences**. 3. Under CSV Export Formats, click **New Export Format**. 4. Enter a name for the export format. 5. Select the format type (e.g., CSV, XLS for Excel, or CSV without BOM for MS Access) @@ -68,86 +70,78 @@ Must be a Group Workspace Admin to complete this process. - Click **Add Column** to add a new column. - Drag and drop the columns into a different order. - Hover over a column and click the red X in the right corner to delete it. -8. Check the Example Output at the bottom and click **Save Export Format** when all the columns are complete. +8. Check the Example Output at the bottom and click **Save Export Format** when all the columns are complete. ## Formulas -Enter any of the following formulas into the Formula field for each column. Be sure to also include both brackets around the formula as shown in the table below. +Enter any of the following formulas into the Formula field for each column. Be sure to also include both brackets around the formula as shown in the table below. ### Report level | Formula | Description | | -- | -- | -| Report title | The title of the report the expense is part of. | -| {report:title} | Would output "Expense Expenses to 2019-11-05" assuming that is the report's title.| +| Report title | The title of the report the expense is part of. | +| {report:title} | Would output "Expense Expenses to 2019-11-05" assuming that is the report's title.| | Report ID | Number is a unique number per report and can be used to identify specific reports.| -| {report:id} | Would output R00I7J3xs5fn assuming that is the report's ID.| -| Old Report ID | A unique number per report and can be used to identify specific reports as well. Every report has both an ID and an old ID - they're simply different ways of showing the same information in either base10 or base62. | -| {report:oldID} | Would output R3513250790654885 assuming that is the report's old ID.| -| Reimbursement ID | The unique number for a report that's been reimbursed via ACH in Expensify. The reimbursement ID is searchable on the Reports page and is found on your bank statement in the line-item detail for the reimbursed amount.| -| {report:reimbursementid} | Would output 123456789109876 assuming that is the ID on the line-item detail for the reimbursed amount in your business bank account.| -| Report Total | The total amount of the expense report.| -| {report:total} | Would output $325.34 assuming that is the report's total.| -| Type | Is the type of report (either Expense Report, Invoice or Bill)| -| {report:type} | Would output "Expense Report" assuming that is the report's type.| -| Reimbursable Total | Is the total amount that is reimbursable on the report.| -| {report:reimbursable} | Would output $143.43 assuming the report's reimbursable total was 143.43 US Dollars.| -| Currency | Is the currency to which all expenses on the report are being converted.| -| {report:currency} | Would output USD assuming that the report total was calculated in US dollars.| +| {report:id} | Would output R00I7J3xs5fn assuming that is the report's ID.| +| Old Report ID | A unique number per report and can be used to identify specific reports as well. Every report has both an ID and an old ID - they're simply different ways of showing the same information in either base10 or base62. | +| {report:oldID} | Would output R3513250790654885 assuming that is the report's old ID.| +| Reimbursement ID | The unique number for a report that's been reimbursed via ACH in Expensify. The reimbursement ID is searchable on the Reports page and is found on your bank statement in the line-item detail for the reimbursed amount.| +| {report:reimbursementid} | Would output 123456789109876 assuming that is the ID on the line-item detail for the reimbursed amount in your business bank account.| +| Report Total | The total amount of the expense report.| +| {report:total} | Would output $325.34 assuming that is the report's total.| +| Type | Is the type of report (either Expense Report, Invoice or Bill)| +| {report:type} | Would output "Expense Report" assuming that is the report's type.| +| Reimbursable Total | Is the total amount that is reimbursable on the report.| +| {report:reimbursable} | Would output $143.43 assuming the report's reimbursable total was 143.43 US Dollars.| +| Currency | Is the currency to which all expenses on the report are being converted.| +| {report:currency} | Would output USD assuming that the report total was calculated in US dollars.| || Note - Currency accepts an optional three character currency code or NONE. If you want to do any math operations on the report total, you should use {report:total:nosymbol} to avoid an error. Please see Expense:Amount for more information on currencies.| -| Report Field | Formula will output the value for a given Report Field which is created in the workspace settings.| -| {field:Employee ID} | Would output 12456 , assuming "Employee ID" is the name of the Report Field and "123456" is the value of that field on the report.| -| Created date | The expense report was originally created by the user.| -| {report:created} | Would output 2010-09-15 12:00:00 assuming the expense report was created on September 15th, 2010 at noon.| -| {report:created:yyyy-MM-dd} | Would output 2010-09-15 assuming the expense report was created on September 15, 2010.| -| | Note - All Date Formulas accept an optional format string. The default if one is not provided is yyyy-MM-dd hh:mm:ss. For a full breakdown, check out the Date Formatting [here](https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports#date-formats).| -| StartDate | Is the date of the earliest expense on the report.| -| {report:startdate} | Would output 2010-09-15 assuming that is the date of the earliest expense on the report.| -| EndDate| Is the date of the last expense on the report.| -| {report:enddate} | Would output 2010-09-26 assuming that is the date of the last expense on the report.| -| Scheduled Submit Dates | The start and end dates of the Scheduled Submit reporting cycle.| -| {report:autoReporting:start} | Would output 2010-09-15 assuming that is the start date of the automatic reporting cycle, when the automatic reporting frequency is not set to daily.| -| {report:autoReporting:end} | Would output 2010-09-26 assuming that is the end date of the automatic reporting cycle, when the automatic reporting frequency is not set to daily.| -| Submission Date | Is the date that the report was submitted.| -| {report:submit:date} | Would output 1986-09-15 12:00:00 assuming that the report was submitted on September 15, 1986, at noon.| -| {report:submit:date:yyyy-MM-dd} | Would output 1986-09-15 assuming that the report was submitted on September 15, 1986.| -| | Note - All Date Formulas accept an optional format string. The default if one is not provided is yyyy-MM-dd hh:mm:ss. For a full breakdown, check out the Date Formatting.| -| Approval Date | The date the report was approved. This formula can be used for export templates, but not for report titles.| -| {report:approve:date} | Would output 2011-09-25 12:00:00 assuming that the report was approved on September 25, 2011, at noon.| -| {report:approve:date:yyyy-MM-dd} | Would output 2011-09-25 assuming that the report was approved on September 25, 2011.| -| Reimbursement Date | The date an expense report was reimbursed. This formula can be used for export templates, but not for report titles.| -| {report:achreimburse} | Would output 2011-09-25 assuming that is the date the report was reimbursed via ACH Direct Deposit.| -| {report:manualreimburse} | Would output 2011-09-25 assuming that is the date the report was marked as reimbursed. | -| Export Date | Is the date when the report is exported. This formula can be used for export templates, but not for report titles.| -| {report:dateexported} | Would output 2013-09-15 12:00 assuming that the report was exported on September 15, 2013, at noon.| -| {report:dateexported:yyyy-MM-dd} | Would output 2013-09-15 assuming that the report was exported on September 15, 2013.| -| Expenses Count | Is the number of total expenses on the report of this specific expense.| -| {report:expensescount} | Would output 10 assuming that there were 10 expenses on the given report for this expense.| -| Workspace Name | Is the name of the workspace applied to the report.| -| {report:policyname} | Would output Sales assuming that the given report was under a workspace named Sales.| -| Status | Is the current state of the report when it was exported.| -| {report:status} | Would output Approved assuming that the report has been approved and not yet reimbursed.| -| Custom Fields | | +| Report Field | Formula will output the value for a given Report Field which is created in the workspace settings.| +| {field:Employee ID} | Would output 12456 , assuming "Employee ID" is the name of the Report Field and "123456" is the value of that field on the report.| +| Created date | The expense report was originally created by the user.| +| {report:created} | Would output 2010-09-15 12:00:00 assuming the expense report was created on September 15th, 2010 at noon.| +| {report:created:yyyy-MM-dd} | Would output 2010-09-15 assuming the expense report was created on September 15, 2010.| +| | Note - All Date Formulas accept an optional format string. The default if one is not provided is yyyy-MM-dd hh:mm:ss. For a full breakdown, check out the Date Formatting [here](https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports#date-formats).| +| StartDate | Is the date of the earliest expense on the report.| +| {report:startdate} | Would output 2010-09-15 assuming that is the date of the earliest expense on the report.| +| EndDate| Is the date of the last expense on the report.| +| {report:enddate} | Would output 2010-09-26 assuming that is the date of the last expense on the report.| +| Scheduled Submit Dates | The start and end dates of the Scheduled Submit reporting cycle.| +| {report:autoReporting:start} | Would output 2010-09-15 assuming that is the start date of the automatic reporting cycle, when the automatic reporting frequency is not set to daily.| +| {report:autoReporting:end} | Would output 2010-09-26 assuming that is the end date of the automatic reporting cycle, when the automatic reporting frequency is not set to daily.| +| Submission Date | Is the date that the report was submitted.| +| {report:submit:date} | Would output 1986-09-15 12:00:00 assuming that the report was submitted on September 15, 1986, at noon.| +| {report:submit:date:yyyy-MM-dd} | Would output 1986-09-15 assuming that the report was submitted on September 15, 1986.| +| | Note - All Date Formulas accept an optional format string. The default if one is not provided is yyyy-MM-dd hh:mm:ss. For a full breakdown, check out the Date Formatting.| +| Approval Date | The date the report was approved. This formula can be used for export templates, but not for report titles.| +| {report:approve:date} | Would output 2011-09-25 12:00:00 assuming that the report was approved on September 25, 2011, at noon.| +| {report:approve:date:yyyy-MM-dd} | Would output 2011-09-25 assuming that the report was approved on September 25, 2011.| +| Reimbursement Date | The date an expense report was reimbursed. This formula can be used for export templates, but not for report titles.| +| {report:achreimburse} | Would output 2011-09-25 assuming that is the date the report was reimbursed via ACH Direct Deposit.| +| {report:manualreimburse} | Would output 2011-09-25 assuming that is the date the report was marked as reimbursed. | +| Export Date | Is the date when the report is exported. This formula can be used for export templates, but not for report titles.| +| {report:dateexported} | Would output 2013-09-15 12:00 assuming that the report was exported on September 15, 2013, at noon.| +| {report:dateexported:yyyy-MM-dd} | Would output 2013-09-15 assuming that the report was exported on September 15, 2013.| +| Expenses Count | Is the number of total expenses on the report of this specific expense.| +| {report:expensescount} | Would output 10 assuming that there were 10 expenses on the given report for this expense.| +| Workspace Name | Is the name of the workspace applied to the report.| +| {report:policyname} | Would output Sales assuming that the given report was under a workspace named Sales.| +| Status | Is the current state of the report when it was exported.| +| {report:status} | Would output Approved assuming that the report has been approved and not yet reimbursed.| +| Custom Fields | | | {report:submit:from:customfield1} | Would output the custom field 1 entry associated with the user who submitted the report. If John Smith’s Custom Field 1 contains 100, then this formula would output 100.| | {report:submit:from:customfield2} | Would output the custom field 2 entry associated with the user who submitted the report. If John Smith’s Custom Field 2 contains 1234, then this formula would output 1234. | -| To | Is the email address of the last person who the report was submitted to.| -| {report:submit:to} | Would output alice@email.com if they are the current approver.| -| {report:submit:to:email\|frontPart} | Would output alice.| -| Current user | To export the email of the currently logged in Expensify user.| -| {user:email} | Would output bob@example.com assuming that is the currently logged in Expensify user's email.| +| To | Is the email address of the last person who the report was submitted to.| +| {report:submit:to} | Would output alice@email.com if they are the current approver.| +| Current user | To export the email of the currently logged in Expensify user.| +| {user:email} | Would output bob@example.com assuming that is the currently logged in Expensify user's email.| | Submitter | "Sally Ride" with email "sride@email.com" is the submitter for the following examples.| -| {report:submit:from:email}| sride@email.com| -| {report:submit:from}| Sally Ride| -| {report:submit:from:firstname}| Sally| -| {report:submit:from:lastname}| Ride| -| {report:submit:from:fullname}| Sally Ride | -| | Note - If user's name is blank, then {report:submit:from} and {report:submit:from:email\|frontPart} will print the user's whole email.| - -`{report:submit:from:email|frontPart}` sride - -`{report:submit:from:email|domain}` email.com - -`{user:email|frontPart}` would output bob assuming that is the currently logged in Expensify user's email. +| {report:submit:from}| Would output Sally Ride. If the user's name is blank, then it will print the user's whole email.| +| {report:submit:from:firstname}| Sally| +| {report:submit:from:lastname}| Ride| +| {report:submit:from:fullname}| Sally Ride | +| {report:submit:from:email} | sride@email.com| ### Expense level @@ -162,101 +156,109 @@ Enter any of the following formulas into the Formula field for each column. Be s | {expense:created:yyyy-MM-dd} | Would output 2019-11-05 assuming the expense was created on November 5th, 2019. | | {expense:posted:yyyy-MM-dd} | Would output 2023-07-24 assuming the expense was posted on July 24th, 2023. | | Tax | The tax type and amount applied to the expense line item. | -| {expense:tax:field} | Would output VAT assuming this is the name of the tax field.| -| {expense:tax:ratename} | Would output the name of the tax rate that was used (ex: Standard). This will show custom if the chosen tax amount is manually entered and not chosen from the list of given options.| -| {expense:tax:amount} | Would output $2.00 assuming that is the amount of the tax on the expense.| -| {expense:tax:percentage} | Would output 20% assuming this is the amount of tax that was applied to the subtotal.| -| {expense:tax:net} | would output $18.66 assuming this is the amount of the expense before tax was applied.| -| {expense:tax:code} | would output the tax code that was set in the workspace settings.| +| {expense:tax:field} | Would output VAT assuming this is the name of the tax field.| +| {expense:tax:ratename} | Would output the name of the tax rate that was used (ex: Standard). This will show custom if the chosen tax amount is manually entered and not chosen from the list of given options.| +| {expense:tax:amount} | Would output $2.00 assuming that is the amount of the tax on the expense.| +| {expense:tax:percentage} | Would output 20% assuming this is the amount of tax that was applied to the subtotal.| +| {expense:tax:net} | would output $18.66 assuming this is the amount of the expense before tax was applied.| +| {expense:tax:code} | would output the tax code that was set in the workspace settings.| | Expense Amount | Related to the currency type and amount of the expense. | -| {expense:amount} | Would output $3.95 assuming the expense was for three dollars and ninety-five cents.| -| {expense:amount:isk} | Would output Íkr3.95 assuming the expense was for 3.95 Icelandic króna.| -| {expense:amount:nosymbol} | Would output 3.95. Notice that there is no currency symbol in front of the expense amount because we designated none.| -| {expense:exchrate} | Would output the currency conversion rate used to convert the expense amount| +| {expense:amount} | Would output $3.95 assuming the expense was for three dollars and ninety-five cents.| +| {expense:amount:isk} | Would output Íkr3.95 assuming the expense was for 3.95 Icelandic króna.| +| {expense:amount:nosymbol} | Would output 3.95. Notice that there is no currency symbol in front of the expense amount because we designated none.| +| {expense:exchrate} | Would output the currency conversion rate used to convert the expense amount| | | Add an optional extra input that is either a three-letter currency code or nosymbol to denote the output's currency. The default if one isn't provided is USD.| -| {expense:amount:originalcurrency} | This gives the amount of the expense in the currency in which it occurred before currency conversion | +| {expense:amount:originalcurrency} | This gives the amount of the expense in the currency in which it occurred before currency conversion | | {expense:amount:originalcurrency:nosymbol} | Will export the expense in its original currency without the currency symbol. | -| {expense:amount:negsign} | displays negative expenses with a minus sign in front rather wrapped in parenthesis. It would output -$3.95 assuming the expense was already a negative expense for three dollars and ninety-five cents. This formula does not convert a positive expense to a negative value.| -| {expense:amount:unformatted} | Displays expense amounts without commas. This removes commas from expenses that have an amount of more than 1000. It would output $10000 assuming the expense was for ten thousand dollars.| -| {expense:debitamount} | Displays the amount of the expense if the expense is positive. Nothing will be displayed in this column if the expense is negative. It would output $3.95 assuming the expense was for three dollars and ninety-five cents.| -| {expense:creditamount} | Displays the amount of the expense if the expense is negative. Nothing will be displayed in this column if the expense is positive. It would output -$3.95 assuming the expense was for negative three dollars and ninety-five cents.| +| {expense:amount:negsign} | displays negative expenses with a minus sign in front rather wrapped in parenthesis. It would output -$3.95 assuming the expense was already a negative expense for three dollars and ninety-five cents. This formula does not convert a positive expense to a negative value.| +| {expense:amount:unformatted} | Displays expense amounts without commas. This removes commas from expenses that have an amount of more than 1000. It would output $10000 assuming the expense was for ten thousand dollars.| +| {expense:debitamount} | Displays the amount of the expense if the expense is positive. Nothing will be displayed in this column if the expense is negative. It would output $3.95 assuming the expense was for three dollars and ninety-five cents.| +| {expense:creditamount} | Displays the amount of the expense if the expense is negative. Nothing will be displayed in this column if the expense is positive. It would output -$3.95 assuming the expense was for negative three dollars and ninety-five cents.| | For expenses imported via CDF/VCF feed only || -| {expense:purchaseamount} | Is the amount of the original purchase in the currency it was purchased in. Control plan users only.| -| {expense:purchaseamount} | Would output Irk 3.95 assuming the expense was for 3.95 Icelandic krónur, no matter what currency your bank has translated it to.| -| {expense:purchasecurrency} | Would output Irk assuming the expense was incurred in Icelandic krónur (before your bank converted it back to your home currency).| +| {expense:purchaseamount} | Is the amount of the original purchase in the currency it was purchased in. Control plan users only.| +| {expense:purchaseamount} | Would output Irk 3.95 assuming the expense was for 3.95 Icelandic krónur, no matter what currency your bank has translated it to.| +| {expense:purchasecurrency} | Would output Irk assuming the expense was incurred in Icelandic krónur (before your bank converted it back to your home currency).| | Original Amount | When import with a connected bank.| -| {expense:originalamount} | Is the amount of the expense imported from your bank or credit card feed. It would output $3.95 assuming the expense equated to $3.95 and you use US-based bank. You may add an optional extra input that is either a three-letter currency code or NONE to denote the output's currency.| +| {expense:originalamount} | Is the amount of the expense imported from your bank or credit card feed. It would output $3.95 assuming the expense equated to $3.95 and you use US-based bank. You may add an optional extra input that is either a three-letter currency code or NONE to denote the output's currency.| | Category | The category of the expense. | -| {expense:category} | Would output Employee Moral assuming that is the expenses' category.| -| {expense:category:glcode} | Would output the category gl code of the category selected.| -| {expense:category:payrollcode} | Outputs the payroll code information entered for the category that is applied to the expense. If the payroll code for the Mileage category was 39847, this would output simply 39847.| -| Attendees | Persons listed as attendees on the expense.| -| {expense:attendees} | Would output the name or email address entered in the Attendee field within the expense (ex. guest@domain.com). | -| {expense:attendees:count} | Would output the number of attendees that were added to the expense (ex. 2).8.  Attendees - persons listed as attendees on the expense.| +| {expense:category} | Would output Employee Moral assuming that is the expenses' category.| +| {expense:category:glcode} | Would output the category gl code of the category selected.| +| {expense:category:payrollcode} | Outputs the payroll code information entered for the category that is applied to the expense. If the payroll code for the Mileage category was 39847, this would output simply 39847.| +| Attendees | Persons listed as attendees on the expense.| +| {expense:attendees} | Would output the name or email address entered in the Attendee field within the expense (ex. guest@domain.com). | +| {expense:attendees:count} | Would output the number of attendees that were added to the expense (ex. 2).8.  Attendees - persons listed as attendees on the expense.| | Tags | Tags of the expense - in this example the name of the tag is "Department." | -| {expense:tag} | Would output Henry at Example Co. assuming that is the expenses' tag. | -| Multiple Tags | Tags for companies that have multiple tags setup. | -| {expense:tag:ntag-1} | Outputs the first tag on the expense, if one is selected. | -| {expense:tag:ntag-3} | Outputs the third tag on the expense, if one is selected. | -| Description | The description on the expense. | +| {expense:tag} | Would output Henry at Example Co. assuming that is the expenses' tag. | +| Multiple Tags | Tags for companies that have multiple tags setup. | +| {expense:tag:ntag-1} | Outputs the first tag on the expense, if one is selected. | +| {expense:tag:ntag-3} | Outputs the third tag on the expense, if one is selected. | +| Description | The description on the expense. | | {expense:comment} | Would output "office lunch" assuming that is the expenses' description.| -| Receipt | | -| {expense:receipt:type} | Would output eReceipt if the receipt is an Expensify Guaranteed eReceipt.| -| {expense:receipt:url} | Would output a link to the receipt image page that anyone with access to the receipt in Expensify could view.| -| {expense:receipt:url:direct} | Would show the direct receipt image url for download. | -| {expense:mcc} | Would output 3351 assuming that is the expenses' MCC (Merchant Category Code of the expense).| -| | Note, we only have the MCC for expenses that are automatically imported or imported from an OFX/QFX file. For those we don't have an MCC for the output would be (an empty string).| -| Card name/number expense type | | -| {expense:card} | Manual/Cash Expenses — would output Cash assuming the expense was manually entered using either the website or the mobile app.| -| {expense:card} | Bank Card Expenses — would output user@company.com – 1234 assuming the expense was imported from a credit card feed.| -| | Note - If you do not have access to the card that the expense was created on 'Unknown' will be displayed. If cards are assigned to users under Domain, then you'll need to be a Domain Admin to export the card number.| -| Expense ID | | -| {expense:id} | Would output the unique number associated with each individual expense "4294967579".| -| Reimbursable state | | -| {expense:reimbursable} | Would output "yes" or "no" depending on whether the expense is reimbursable or not.| -| Billable state | | +| Receipt | | +| {expense:receipt:type} | Would output eReceipt if the receipt is an Expensify Guaranteed eReceipt.| +| {expense:receipt:url} | Would output a link to the receipt image page that anyone with access to the receipt in Expensify could view.| +| {expense:receipt:url:direct} | Would show the direct receipt image url for download. | +| {expense:mcc} | Would output 3351 assuming that is the expenses' MCC (Merchant Category Code of the expense).| +| | Note, we only have the MCC for expenses that are automatically imported or imported from an OFX/QFX file. For those we don't have an MCC for the output would be (an empty string).| +| Card name/number expense type | | +| {expense:card} | Manual/Cash Expenses — would output Cash assuming the expense was manually entered using either the website or the mobile app.| +| {expense:card} | Bank Card Expenses — would output user@company.com – 1234 assuming the expense was imported from a credit card feed.| +| | Note - If you do not have access to the card that the expense was created on 'Unknown' will be displayed. If cards are assigned to users under Domain, then you'll need to be a Domain Admin to export the card number.| +| Expense ID | | +| {expense:id} | Would output the unique number associated with each individual expense "4294967579".| +| Reimbursable state | | +| {expense:reimbursable} | Would output "yes" or "no" depending on whether the expense is reimbursable or not.| +| Billable state | | | {expense:billable} | Would output "yes" or "no" depending on whether the expense is billable or not. -| Expense Number | Is the ordinal number of the expense on its expense report.| -| {report:expense:number} | Would output 2 assuming that the given expense was the second expense on its report.| -| GL codes | | -| {expense:category:glcode} | Would output the GL code associated with the category of the expense. If the GL code for Meals is 45256 this would output simply 45256.| -| {expense:tag:glcode} | Would output the GL code associated with the tag of the expense. If the GL code for Client X is 08294 this would output simply 08294.| -| {expense:tag:ntag-3:glcode} | Would output the GL code associated with the third tag the user chooses. This is only for companies that have multiple tags setup.| +| Expense Number | Is the ordinal number of the expense on its expense report.| +| {report:expense:number} | Would output 2 assuming that the given expense was the second expense on its report.| +| GL codes | | +| {expense:category:glcode} | Would output the GL code associated with the category of the expense. If the GL code for Meals is 45256 this would output simply 45256.| +| {expense:tag:glcode} | Would output the GL code associated with the tag of the expense. If the GL code for Client X is 08294 this would output simply 08294.| +| {expense:tag:ntag-3:glcode} | Would output the GL code associated with the third tag the user chooses. This is only for companies that have multiple tags setup.| -### Date formats +### Date formats | Formula | Description | | -- | -- | -| M/dd/yyyy | 5/23/2019| -|MMMM dd, yyyy| May 23, 2019| -|dd MMM yyyy| 23 May 2019| -|yyyy/MM/dd| 2019/05/23| -|dd MMM yyyy| 23 May 2019| -|yyyy/MM/dd| 2019/05/23| -|MMMM, yyyy| May, 2019| -|yy/MM/dd| 19/05/23| -|dd/MM/yy| 23/05/19| -|yyyy| 2019| +| M/dd/yyyy | 5/23/2019| +|MMMM dd, yyyy| May 23, 2019| +|dd MMM yyyy| 23 May 2019| +|yyyy/MM/dd| 2019/05/23| +|dd MMM yyyy| 23 May 2019| +|yyyy/MM/dd| 2019/05/23| +|MMMM, yyyy| May, 2019| +|yy/MM/dd| 19/05/23| +|dd/MM/yy| 23/05/19| +|yyyy| 2019| ### Math formulas | Formula | Description | | -- | -- | -| * | Multiplication {math: 3 * 4} output 12| -| / | Division {math: 3 / 4 }output 0.75| -| + | Addition {math: 3 + 4 }output | -| - | Subtraction {math: 3 - 4 }output -1| -| ^ | Exponent {math: 3 ^ 4 } output 81| -| sqrt | The square root of a number. {sqrt:64} output 8| +| * | Multiplication {math: 3 * 4} output 12| +| / | Division {math: 3 / 4 }output 0.75| +| + | Addition {math: 3 + 4 }output | +| - | Subtraction {math: 3 - 4 }output -1| +| ^ | Exponent {math: 3 ^ 4 } output 81| +| sqrt | The square root of a number. {sqrt:64} output 8| || Note - You can also combine the value of any two numeric fields. For example, you can use {math: {expense:tag:glcode} + {expense:category:glcode}} to add the value of the Tag GL code with the Category GL code.| -### Substring formulas - -This formula will output a subset of the string in question. It is important to remember that the count starts at 0 not 1. +## Functions +Functions can be applied to any formula using the `|` symbol and the function name. They can be chained together and are case insensitive. -`{expense:merchant|substr:0:4}` would output "Star" for a merchant named Starbucks. This is because we are telling it to start at position 0 and be of 4 character length. - -`{expense:merchant|substr:4:5}` would output "bucks" for a merchant named Starbucks. This is because we are telling it to start at position 4 and be of 5 character length. +| Function | Description +| -- | -- | +| frontpart | Get the front part of an email or the first word of the value| +| {report:submit:from:email\|frontPart} | would output alice if alice@email.com was the submitter| +| {report:submit:from:fullname\|frontpart} | would output Sally if the submitter's full name is Sally Ride| +| substr | Outputs a substring of the string (value) it operates on. It takes 1 or 2 parameters: offset and length. An offset of 0 starts at the first character of the value, 1 at the second, etc. The length is how many characters to return.| +| {expense:merchant\|substr:0:4} | would output "Star" for a merchant named Starbucks.| +| {expense:merchant\|substr:4:5} | would output "bucks" for a merchant named Starbucks.| +| {report:policyname\|substr:20} | would output "Sally's Expenses" for a report on a workspace named "Control Workspace - Sally's Expenses"| +| {report:policyname\|substr:20\|frontpart} | would output "Sally's"| +| domain | Get the domain name from an email address; the part after the `@` sign.| +| {report:submit:from:email\|domain} | email.com if alice@email.com was the submitter| {% include faq-begin.md %} @@ -264,6 +266,10 @@ This formula will output a subset of the string in question. It is important to No, the custom template always exports one line per *expense*. At the moment, it is not possible to create a template that will export one line per report. +**Can I export to CSV or PDF on my mobile app?** + +No, expenses can only be exported using the web app. + **How do I print a report?** 1. Click the **Reports** tab. diff --git a/docs/articles/expensify-classic/spending-insights/Fringe-Benefits.md b/docs/articles/expensify-classic/spending-insights/Fringe-Benefits.md index 4db5ad1f27b9..aa134a6e246f 100644 --- a/docs/articles/expensify-classic/spending-insights/Fringe-Benefits.md +++ b/docs/articles/expensify-classic/spending-insights/Fringe-Benefits.md @@ -2,42 +2,65 @@ title: Fringe Benefits description: How to track your Fringe Benefits --- -# Overview -If you’re looking to track and report expense data to calculate Fringe Benefits Tax (FBT), you can use Expensify’s special workflow that allows you to capture extra information and use a template to export to a spreadsheet. -# How to set up Fringe Benefit Tax +If you need to track and report expense data for **Fringe Benefits Tax (FBT)**, Expensify offers a workflow to capture the required information and export it to a spreadsheet. -## Add Attendee Count Tags -First, you’ll need to add these two tags to your Workspace: -1) Number of Internal Attendees -2) Number of External Attendees +--- -These tags must be named exactly as written above, ensuring there are no extra spaces at the beginning or at the end. You’ll need to set the tags to be numbers 00 - 10 or whatever number you wish to go up to (up to the maximum number of attendees you would expect at any one time), one tag per number i.e. “01”, “02”, “03” etc. These tags can be added in addition to those that are pulled in from your accounting solution. Follow these [instructions](https://help.expensify.com/articles/expensify-classic/workspaces/Tags) to add tags. +# Set Up Fringe Benefits Tax -## Add Payroll Code -Go to **Settings > Workspaces > Group > _Workspace Name_ > Categories** and within the categories you wish to track FBT against, select **Edit Category** and add the code “TAG”: +## Add Attendee Count Tags +To start tracking FBT, add the following tags to your **Workspace**: +- **Number of Internal Attendees** +- **Number of External Attendees** -## Enable Workflow -Once you’ve added both tags (Internal Attendees and External Attendees) and added the payroll code “TAG” to FBT categories, you can send a request to Expensify at concierge@expensify.com to enable the FBT workflow. Please send the following request: ->“Can you please add the custom workflow/DEW named FRINGE_BENEFIT_TAX to my company workspace named ?” -Once the FBT workflow is enabled, it will require anything with the code “TAG” to include the two attendee count tags in order to be submitted. +These tags **must be named exactly** as written above, without extra spaces. +## Setting up Tags: +1. Go to **Settings > Workspaces > Group > _Workspace Name_ > Tags**. +2. Create tags with numeric values (e.g., **"01", "02", "03"**) up to your expected maximum number of attendees. +3. These tags can be used alongside existing accounting solution tags. +4. Follow [these instructions](https://help.expensify.com/articles/expensify-classic/workspaces/Tags) to add tags. -# For Users -Once these steps are completed, users who create expenses coded with any category that has the payroll code “TAG” (e.g. Entertainment Expenses) but don’t add the internal and external attendee counts, will not be able to submit their expenses. -# For Admins -You are now able to create and run a report, which shows all expenses under these categories and also shows the number of internal and external attendees. Because we don’t presume to know all of the data points you wish to capture, you’ll need to create a Custom CSV export. -Here are a couple of examples of Excel formulas to use to report on attendees: -- `{expense:tag:ntag-1}` outputs the first tag the user chooses. -- `{expense:tag:ntag-3}` outputs the third tag the user chooses. +## Add Payroll Code +1. Navigate to **Settings > Workspaces > Group > _Workspace Name_ > Categories**. +2. Select **Edit Category** for the relevant expense categories. +3. Add the payroll code **“TAG”**. -Your expenses may have multiple levels of coding, i.e.: -- GL Code (Category) -- Department (Tag 1) -- Location (Tag 2) -- Number of Internal Attendees (Tag 3) -- Number of External Attendees (Tag 4) +## Enable the FBT Workflow +Once you've added the attendee count tags and payroll code, email **concierge@expensify.com** with this request: -In the above case, you’ll want to use `{expense:tag:ntag-3}` and `{expense:tag:ntag-4}` as formulas to report on the number of internal and external attendees. +> **Subject:** Enable Fringe Benefits Tax Workflow +> **Message:** Can you please add the custom workflow/DEW named **FRINGE_BENEFIT_TAX** to my company workspace named ****? -Our article on [Custom Templates](https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates) shows how to create a custom CSV. +Once enabled, expenses coded with **“TAG”** will require the attendee count tags before they can be submitted. + +--- + +# For Workspace Members + +- When submitting expenses under **FBT-tracked categories**, users **must** include internal and external attendee counts. +- If these fields are missing, the expense **cannot be submitted**. + +--- + +# For Workspace Admins + +## Running Reports +You can generate reports that include FBT-tracked expenses and attendee counts. + +To extract the data: +1. Use a **Custom CSV Export** to format your report. +2. Apply these formulas to reference attendee counts: + - `{expense:tag:ntag-3}` → **Internal Attendees** + - `{expense:tag:ntag-4}` → **External Attendees** + +## Example: Expense Coding Levels +If your expenses have multiple coding levels, your report might include: +- **GL Code** (Category) +- **Department** (Tag 1) +- **Location** (Tag 2) +- **Number of Internal Attendees** (Tag 3) +- **Number of External Attendees** (Tag 4) + +For more details, visit our [Custom Templates guide](https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates). diff --git a/docs/articles/expensify-classic/spending-insights/Insights.md b/docs/articles/expensify-classic/spending-insights/Insights.md index 6eb96b039285..88111c1aaded 100644 --- a/docs/articles/expensify-classic/spending-insights/Insights.md +++ b/docs/articles/expensify-classic/spending-insights/Insights.md @@ -12,9 +12,9 @@ The Insights dashboard in Expensify provides a real-time overview of company spe ## Review Your Insights Data -1. Navigate to your [Insights page](https://www.expensify.com/expenses?param={"fromInsightsTab":true,"viewMode":"charts"}) in the left-hand menu. -2. Select a date range (default is the current month). -3. Apply filters for categories, tags, employees, or other parameters. +1. Open your [Insights page](https://www.expensify.com/expenses?param={"fromInsightsTab":true,"viewMode":"charts"}) from the left-hand menu. +2. Choose a date range (default is the current month). +3. Apply filters to narrow down data by categories, tags, employees, or other criteria. 4. Click **View Raw Data** to see a detailed dataset. --- @@ -23,7 +23,7 @@ The Insights dashboard in Expensify provides a real-time overview of company spe 1. Click **View Raw Data** next to the dataset you want to export. 2. Select individual expenses or use the **Select All** checkbox. -3. Click **Export To** in the top-right corner to download the data as a CSV file. +3. Click **Export To** in the top-right corner, then choose CSV to download the file. --- @@ -38,7 +38,8 @@ The Insights dashboard in Expensify provides a real-time overview of company spe 1. Navigate to **Settings > Workspaces > Group > [Workspace Name] > Export Formats**. 2. Build a report using these [report-level formulas](https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates#report-level). -3. Need help? Click **Support** to contact Concierge or your Account Manager. + +If you need help creating a custom export report, click **Support** to contact the Concierge or your Account Manager. --- @@ -65,7 +66,7 @@ Yes! We offer [seven default reports](https://help.expensify.com/articles/expens - **Per Diem Export**: Exports Per Diem details. - **Tag Export**: Similar to Category Export, but for Tags. -*Note: These reports will be emailed to you rather than automatically downloaded.* +**Note:** These reports will be emailed to you rather than automatically downloaded. ## How many expenses can I export in one report? @@ -74,8 +75,7 @@ Yes! We offer [seven default reports](https://help.expensify.com/articles/expens ## What types of custom reports can my Account Manager help create? -We offer various custom reports, including: - +**We offer various custom reports, including:** - Accrual Reports - Aged Approval Reports - Audit Reports @@ -85,4 +85,4 @@ We offer various custom reports, including: - Invoice Reconciliation Reports - Vendor Spend Analysis -Need something specific? Contact your Account Manager for assistance! +Looking for a custom report? Reach out to your Account Manager for assistance! diff --git a/docs/articles/new-expensify/connections/netsuite/Configure-Netsuite.md b/docs/articles/new-expensify/connections/netsuite/Configure-Netsuite.md index 26db42df9e5b..4734a85bf67f 100644 --- a/docs/articles/new-expensify/connections/netsuite/Configure-Netsuite.md +++ b/docs/articles/new-expensify/connections/netsuite/Configure-Netsuite.md @@ -1,162 +1,138 @@ --- title: Configure NetSuite -description: Configure the Import, Export, and Advanced settings for Expensify's integration with NetSuite +description: Learn how to configure the Import, Export, and Advanced settings for Expensify's integration with NetSuite. order: 2 --- -# Best Practices Using NetSuite +Expensify’s integration with NetSuite streamlines expense management by automatically syncing expense reports. This reduces manual entry, minimizes errors, and provides real-time visibility into spending. With this integration, you can: -Using Expensify with NetSuite brings a seamless, efficient approach to managing expenses. With automatic syncing, expense reports flow directly into NetSuite, reducing manual entry and errors while giving real-time visibility into spending. This integration speeds up approvals, simplifies reimbursements, and provides clear insights for smarter budgeting and compliance. Together, Expensify and NetSuite make expense management faster, more accurate, and stress-free. +- Speed up approvals and reimbursements. +- Gain clear insights for budgeting and compliance. +- Ensure seamless data flow between Expensify and NetSuite. -# Accessing the NetSuite Configuration Settings +# Accessing NetSuite Configuration Settings -NetSuite is connected at the workspace level, and each workspace can have a unique configuration that dictates how the connection functions. To access the connection settings: +NetSuite is connected at the workspace level, and each workspace can have a unique configuration. -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 access settings for. +To access the configuration settings: + +1. Click **Settings** in the bottom left menu. +2. Select **Workspaces** from the left menu. +3. Choose the workspace you want to configure. 4. Click **Accounting** in the left menu. -# Step 1: Configure Import Settings - -The following steps help you determine how data will be imported from NetSuite to Expensify. - -1. From the Accounting tab of your workspace settings, click on **Import**. -2. In the right-hand menu, review each of the following import settings: - - _Categories_: Your NetSuite Expense Categories are automatically imported into Expensify as categories. This is enabled by default and cannot be disabled. - - _Department, Classes, and Locations_: The NetSuite connection allows you to import each independently and utilize tags, report fields, or employee defaults as the coding method. - - Tags are applied at the expense level and apply to single expense. - - Report Fields are applied at the report header level and apply to all expenses on the report. - - The employee default is applied when the expense is exported to NetSuite and comes from the default on the submitter’s employee record in NetSuite. - - _Customers and Projects_: The NetSuite connections allows you to import customers and projects into Expensify as Tags or Report Fields. - -_Cross-subsidiary customers/projects_: Enable to import Customers and Projects across all NetSuite subsidiaries to a single Expensify workspace. This setting requires you to enable “Intercompany Time and Expense” in NetSuite. To enable that feature in NetSuite, go to **Setup > Company > Setup Tasks: Enable Features > Advanced Features**. - -_Tax_: Enable to import NetSuite Tax Groups and configure further on the Taxes tab of your workspace settings menu. - -_Custom Segments and Records_: Enable to import segments and records are tags or report fields. - - If configuring Custom Records as Report Fields, use the Field ID on the Transactions tab (under **Custom Segments > Transactions**). - - If configuring Custom Records as Tags, use the Field ID on the Transaction Columns tab (under **Custom Segments > Transaction Columns**). - - Don’t use the “Filtered by” feature available for Custom Segments. Expensify can’t make these dependent on other fields. If you do have a filter selected, we suggest switching that filter in NetSuite to “Subsidiary” and enabling all subsidiaries to ensure you don’t receive any errors upon exporting reports. - -_Custom Lists_: Enable to import lists as tags or reports fields. -3. Sync the connection by closing the right-hand menu and clicking the three-dot icon > Sync Now option. Once the sync completes, you should see the values for any enabled tags or report fields in the corresponding Tag or Report Field tabs in the workspace settings menu. - -{% include info.html %} -When you’re done configuring the settings, or anytime you make changes in the future, sync the NetSuite connection. This will ensure changes are saved and updated across both systems. -{% include end-info.html %} - -# Step 2: Configure Export Settings - -The following steps help you determine how data will be exported from Expensify to NetSuite. +--- + +## Step 1: Configure Import Settings + +To determine how data is imported from NetSuite to Expensify: -1. From the Accounting tab of your workspace settings, click on **Export**. -2. In the right-hand menu, review each of the following export settings: - - _Preferred exporter_: Any workspace admin can export reports to NetSuite. For automatic export, Concierge will export on behalf of the preferred exporter. The preferred exporter will also be notified of any expense reports that fail to export to NetSuite due to an error. - - _Export date_: You can choose which date to use for the records created in NetSuite. There are three date options: - - _Date of last expense_: This will use the date of the most recent expense on the report. - - _Submitted date_: The date the employee submitted the report. - - _Exported date_: The date you export the report to NetSuite. - - _Export out-of-pocket expenses as_: - - _Expense Reports_: Out-of-pocket expenses will be exported as expense reports, which will be posted to the payables account designated in NetSuite. - - _Vendor Bills_: Out-of-pocket expenses will be exported to NetSuite as vendor bills. Each report will be posted as payable to the vendor associated with the employee who submitted the report. You can also set an approval level in NetSuite for vendor bills. - - _Journal Entries_: Out-of-pocket expenses will be exported to NetSuite as journal entries. All the transactions will be posted to the payable account specified in the workspace. You can also set an approval level in NetSuite for the journal entries. - - By default, journal entry forms do not contain a customer column, so it is not possible to export customers or projects with this export option. Also, the credit line and header-level classifications are pulled from the employee record. - - _Export company card expenses as_: - - _Expense Reports_:To export company card expenses as expense reports, you will need to configure your default corporate cards in NetSuite. - - _Vendor Bills_: Company card expenses will be posted as a vendor bill payable to the default vendor specified in your workspace Accounting settings. You can also set an approval level in NetSuite for the bills. - - _Journal Entries_: Company Card expenses will be posted to the Journal Entries posting account selected in your workspace Accounting settings. - - Important Notes: - - 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 - - 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 - - _Export invoices to_: Select the Accounts Receivable account where you want your Invoice reports to export. In NetSuite, the invoices are linked to the customer, corresponding to the email address where the invoice was sent. - - _Invoice item_: Choose whether Expensify creates an "Expensify invoice line item" for you upon export (if one doesn’t exist already) or select an existing invoice item. - - _Export foreign currency amount_: Enabling this feature allows you to send the original amount of the expense rather than the converted total when exporting to NetSuite. This option is only available when exporting out-of-pocket expenses as Expense Reports. - - _Export to next open period_: When this feature is enabled and you try exporting an expense report to a closed NetSuite period, we will automatically export to the next open period instead of returning an error. -3. Sync the connection by closing the right-hand menu and clicking the three-dot icon > Sync Now option. +1. Go to **Accounting** > **Import**. +2. Review and configure the following settings: -# Step 3: Configure Advanced Settings + - **Categories**: NetSuite Expense Categories are automatically imported. + - **Departments, Classes, and Locations**: Import options include Tags, Report Fields, or Employee Defaults. + - **Customers and Projects**: Choose to import them as Tags or Report Fields. + - *Cross-Subsidiary Customers/Projects*: Enable this to import data across all NetSuite subsidiaries. Ensure "Intercompany Time and Expense" is enabled in NetSuite under **Setup > Company > Enable Features > Advanced Features**. + - **Tax**: Import NetSuite Tax Groups and configure in the Taxes tab. + - **Custom Segments and Records**: Import segments and records as Tags or Report Fields. + - Use the correct Field ID from NetSuite’s Transactions tab. + - Avoid using the “Filtered by” feature in Custom Segments. + - **Custom Lists**: Enable to import lists as Tags or Report Fields. -The following steps help you determine the advanced settings for your NetSuite connection. +3. Click the **three-dot icon** > **Sync Now** to update settings. +4. After configuring settings, sync the NetSuite connection to apply changes. -1. From the Accounting tab of your workspace settings, click on **Advanced**. -2. In the right-hand menu, review each of the following advanced settings: - - _Auto-sync_: When enabled, the connection will sync daily to ensure that the data shared between the two systems is up-to-date. We strongly recommend keeping auto-sync enabled. The following will occur when auto-sync is enabled: - - When an expense report reaches its final state in Expensify, it will be automatically exported to NetSuite. The final state will either be reimbursement (if you reimburse members through Expensify) or final approval (if you reimburse members outside of Expensify). - - If Sync Reimbursed Reports is enabled, then we will sync the reimbursement status of reports between Expensify and NetSuite. - - _Sync reimbursed reports_: Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the NetSuite. - - _Reimbursments account_: Select the account that matches the default account for Bill Payments in your NetSuite account. - - _Collections account_: When exporting invoices, once marked as Paid, the payment is marked against the account selected. - - _Invite employees and set approvals_: Enabling this feature will invite all employees from the connected NetSuite subsidiary to your Expensify workspace. Once imported, Expensify will send them an email letting them know they’ve been added to a workspace. - - In addition to inviting employees, this feature enables a custom set of approval workflow options, which you can manage in Expensify Classic. (Click Switch to Expensify Classic from the Settings menu.) - - _Auto create employees/vendors_: With this feature enabled, Expensify will automatically create a new employee or vendor in NetSuite (if one doesn’t already exist) using the name and email of the report submitter. - - _Enable newly imported categories_: Toggle to enable this feature and anytime a new Expense Category is created in NetSuite, it will be imported into Expensify as an enabled category. Otherwise, it will import disabled and employees will be unable to see it as an option to code to an expense. - - _Setting approval levels_: You can set the NetSuite approval level for each different export type; Expense report, Vendor bill, and Journal entry. - - Note: If you have Approval Routing selected in your accounting preference, this will override the selections in Expensify. If you do not wish to use Approval Routing in NetSuite, go to **Setup > Accounting > Accounting Preferences > Approval Routing** and ensure Vendor Bills and Journal Entries are not selected. - - _Custom form ID_: By default, Expensify creates entries using the preferred transaction form set in NetSuite. Enabling this setting allows you to designate a specific transaction form. - - _Out-of-pocket expense_: - - _Company card expense_: -3. Sync the connection by closing the right-hand menu and clicking the three-dot icon > Sync Now option. +--- + +## Step 2: Configure Export Settings -{% include faq-begin.md %} +To determine how data is exported from Expensify to NetSuite: -## I added tags in NetSuite (departments, classes, or locations) how do I get them into my workspace? +1. Go to **Accounting** > **Export**. +2. Review and configure the following settings: -New departments, classes, and locations must be added in NetSuite first before they can be added as options to code to expenses in Expensify. After adding them in NetSuite, sync your connection to import the new options. + - **Preferred Exporter**: Select an admin to handle exports. + - **Export Date Options**: + - *Date of Last Expense* + - *Submitted Date* + - *Exported Date* + - **Export Out-of-Pocket Expenses As**: + - *Expense Reports* + - *Vendor Bills* + - *Journal Entries* (Note: Customers/projects cannot be exported with this option.) + - **Export Company Card Expenses As**: + - *Expense Reports*, *Vendor Bills*, or *Journal Entries*. + - Expensify Card expenses always export as Journal Entries. + - **Export Invoices To**: Choose an Accounts Receivable account. + - **Invoice Item**: Select or create an invoice line item. + - **Export Foreign Currency Amount**: Enable to export original expense amounts. + - **Export to Next Open Period**: Automatically exports expenses to the next open NetSuite period if the current one is closed. -Once imported, you can turn specific tags on or off under **Settings > Workspaces > [Workspace Name] > Tags**. You can turn specific report fields on or off under **Settings > Workspaces > [Workspace Name] > Report Fields**. +3. Click the **three-dot icon** > **Sync Now** to update settings. -## Is it possible to automate inviting my employees and their approver from NetSuite into Expensify? +--- -Yes, you can automatically import your employees and set their approval workflow with your connection between NetSuite and Expensify. +## Step 3: Configure Advanced Settings -Enabling this feature will invite all employees from the connected NetSuite subsidiary to your Expensify workspace. Once imported, Expensify will send them an email letting them know they’ve been added to a workspace. +To manage additional integration features: -In addition to inviting employees, this feature enables a custom set of approval workflow options, which you can manage in Expensify Classic. (Click Switch to Expensify Classic from the Settings menu.) Your options for approval include: +1. Go to **Accounting** > **Advanced**. +2. Review and configure the following settings: -- **Basic Approval:** A single level of approval, where all users submit directly to a Final Approver. The Final Approver defaults to the workspace owner but can be edited on the people page. -- **Manager Approval (default):** Two levels of approval route reports first to an employee’s NetSuite expense approver or supervisor, and second to a workspace-wide Final Approver. By NetSuite convention, Expensify will map to the supervisor if no expense approver exists. The Final Approver defaults to the workspace owner but can be edited on the people page. -- **Configure Manually:** Employees will be imported, but all levels of approval must be manually configured on the workspace’s People settings page. If you enable this setting, it’s recommended you review the newly imported employees and managers on the **Settings > Workspaces > Group > [Workspace Name] > People** page. + - **Auto-Sync**: Enable for daily syncing. + - **Sync Reimbursed Reports**: Automatically updates reimbursement status between Expensify and NetSuite. + - **Invite Employees & Set Approvals**: Imports employees and sets approval workflows. + - **Auto Create Employees/Vendors**: Creates a NetSuite employee/vendor record if one doesn’t exist. + - **Enable Newly Imported Categories**: Automatically enables new Expense Categories from NetSuite. + - **Approval Levels**: Set NetSuite approval levels for Expense Reports, Vendor Bills, and Journal Entries. + - **Custom Form ID**: Use a specific NetSuite transaction form instead of the default. +3. Click the **three-dot icon** > **Sync Now** to apply changes. -## I notice that company card expenses export to NetSuite right away when I approve a report, but reimbursable expenses don’t, why is that? +--- -When Auto Sync is enabled and you reimburse employees through Expensify, we help to automatically send finalized expenses to NetSuite. The timing of the export depends on the type of expense it is. - - **If you reimburse members through Expensify:** Reimbursing an expense report will trigger auto-export to NetSuite. When the expense report is exported to NetSuite, a corresponding bill payment will also be created in NetSuite. - - **If you reimburse members outside of Expensify:** Expense reports will be exported to NetSuite at the time of final approval. After you mark the report as paid in NetSuite, the reimbursed status will be synced back to Expensify the next time the integration syncs. +# FAQ -## How do I configure my default corporate cards in NetSuite? +## How do I import new tags (departments, classes, locations) from NetSuite? -To export company card expenses as expense reports, you must configure your default corporate cards in NetSuite. +1. Add new tags in NetSuite. +2. Sync the NetSuite connection in Expensify. +3. Enable or disable tags under **Settings > Workspaces > [Workspace Name] > Tags**. -To do this, you must select the correct card on the NetSuite employee records (for individual accounts) or the subsidiary record (If you use a non-One World account, the default is found in your accounting preferences). +## Can I automate employee invitations and approval workflows from NetSuite? -To update your expense report transaction form in NetSuite: +Yes, enabling the feature will: +- Import employees from the connected NetSuite subsidiary. +- Notify them via email. +- Allow workflow configuration in Expensify Classic (**Settings > Workspaces > People**). -1. Go to **Customization > Forms > Transaction Forms.** -2. Click **Edit** next to the preferred expense report form. -3. Go to the **Screen Fields > Main** tab. -4. Check “Show” for "Account for Corporate Card Expenses." -5. Go to the **Screen Fields > Expenses** tab. -6. Check “Show” for "Corporate Card." +## Why do company card expenses export immediately, but reimbursable expenses don’t? -You can also select the default account on your employee record to use individual corporate cards for each employee. Make sure you add this field to your employee entity form in NetSuite. If you have multiple cards assigned to a single employee, you cannot export to each account. You can only have a single default per employee record. +- **Reimbursable expenses** export upon reimbursement in Expensify. +- **Company card expenses** export upon approval. +- If Auto-Sync is enabled, reimbursement status updates automatically. -## My custom segments created before 2019.1 weren’t created with a unified ID, what change can I make to import them into Expensify?” +## How do I configure corporate card exports in NetSuite? - Note that as of 2019.1, any new custom segments that you create automatically use the unified ID, and the "Use as Field ID" box is not visible. If you are editing a custom segment definition that was created before 2019.1, the "Use as Field ID" box is available. To use a unified ID for the entire custom segment definition, check the "Use as Field ID" box. When the box is checked, no field ID fields or columns are shown on the Application & Sourcing subtabs because one ID is used for all fields. +1. Select the correct corporate card under **Customization > Forms > Transaction Forms**. +2. Enable "Show" for "Account for Corporate Card Expenses" and "Corporate Card" fields. +3. Set the default card account on employee records. -## How does Auto-sync work with reimbursed reports? +## Why can’t I import custom segments created before 2019.1? -If a report is reimbursed via ACH or marked as reimbursed in Expensify and then exported to NetSuite, the report is automatically marked as paid in NetSuite. +Check the "Use as Field ID" box in NetSuite. This assigns a unified ID to older segments. -If a report is exported to NetSuite, and then marked as paid in NetSuite, the report will automatically be marked as reimbursed in Expensify during the next sync. +## How does Auto-Sync work with reimbursed reports? -## Will enabling auto-sync affect existing approved and reimbursed reports? +- If a report is reimbursed in Expensify, NetSuite marks it as paid. +- If a report is marked as paid in NetSuite, Expensify updates the status during the next sync. -Auto-sync will only export newly approved reports to NetSuite. Reports that were approved or reimbursed before enabling auto-sync will need to be manually exported to sync them to NetSuite. +## Will enabling Auto-Sync affect existing reports? -## When using multi-currency features in NetSuite, can expenses be exported with any currency? +No, only newly approved reports will auto-export. Manually export older reports if needed. -When using multi-currency features with NetSuite, remember these points: +## How does multi-currency exporting work in NetSuite? -**Employee/Vendor currency:** The currency set for a NetSuite vendor or employee record must match the subsidiary currency for whichever subsidiary you export that user's reports to. A currency mismatch will cause export errors. -**Bank Account Currency:** When synchronizing bill payments, your bank account’s currency must match the subsidiary’s currency. Failure to do so will result in an “Invalid Account” error. -{% include faq-end.md %} +- Employee/vendor currency must match the subsidiary currency. +- The bank account currency must match the subsidiary currency for bill payments. diff --git a/docs/articles/new-expensify/connections/quickbooks-desktop/Connect-to-QuickBooks-Desktop.md b/docs/articles/new-expensify/connections/quickbooks-desktop/Connect-to-QuickBooks-Desktop.md new file mode 100644 index 000000000000..b5f13d353bea --- /dev/null +++ b/docs/articles/new-expensify/connections/quickbooks-desktop/Connect-to-QuickBooks-Desktop.md @@ -0,0 +1,86 @@ +--- +title: QuickBooks Desktop +description: Easily connect Expensify to QuickBooks Desktop for streamlined expense management and accounting. +order: 1 +--- + +QuickBooks Desktop is accounting software developed by Intuit, designed for small and medium-sized businesses to manage financial tasks. Connecting Expensify to QuickBooks Desktop makes expense management seamless. + +This guide walks you through connecting Expensify to QuickBooks Desktop, ensuring a smooth integration for managing your business expenses efficiently. + +--- + +# Connect to QuickBooks Desktop + +{% include info.html %} +To connect QuickBooks Desktop to Expensify, you must log into QuickBooks Desktop as an Admin. The company file you want to connect must be the only one open. +{% include end-info.html %} + +1. In Expensify, 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 to connect to QuickBooks Desktop. +4. Click **More features** in the left menu. +5. In the **Integrate** section, enable the **Accounting** toggle. +6. Click **Accounting** in the left menu. +7. Click **Set up** next to QuickBooks Desktop. +8. Click **Copy** to copy the link. Paste this link into the computer running QuickBooks Desktop. +9. Select your QuickBooks Desktop version. + + ![QuickBooks Desktop version selection](https://help.expensify.com/assets/images/QBO_desktop_02.png){:width="100%"} + +10. Download the Web Connector and follow the installation instructions. +11. Open the Web Connector. +12. When prompted during setup, download the config file and open it using File Explorer. This will automatically load the application into the QuickBooks Web Connector. + +{% include info.html %} +Ensure the correct company file is open in QuickBooks Desktop and is the only one open. +{% include end-info.html %} + +13. In QuickBooks Desktop, select **Yes, always allow access, even when QuickBooks is not running**, then click **Continue**. + + ![QuickBooks Desktop access permission](https://help.expensify.com/assets/images/QBO_desktop_04.png){:width="100%"} + +14. Click **OK**, then click **Yes**. + + ![QuickBooks Desktop confirmation](https://help.expensify.com/assets/images/QBO_desktop_05.png){:width="100%"} + +15. Click **Copy** to copy the password. + + ![Copy Web Connector password](https://help.expensify.com/assets/images/QBO_desktop_06.png){:width="100%"} + +16. Paste the password into the Password field of the Web Connector and press **Enter**. + + ![Paste password in Web Connector](https://help.expensify.com/assets/images/QBO_desktop_08.png){:width="100%"} + +17. Click **Yes** to save the password. The new connection will appear in the Web Connector. + + ![Save Web Connector password](https://help.expensify.com/assets/images/QBO_desktop_07.png){:width="100%"} + +{% include info.html %} +Securely save this password in a trusted password manager. You'll need it for future configuration updates or troubleshooting. +{% include end-info.html %} + +--- + +# FAQ + +## What are the hardware and software requirements for QuickBooks Desktop connector? + +- **Hardware requirements**: Ensure the host machine meets [Intuit's recommended specifications](https://quickbooks.intuit.com/learn-support/en-us/help-article/install-products/system-requirements-quickbooks-desktop-2022/L9664spDA_US_en_US). +- **Software requirements**: Windows 10 or 11 with the latest service packs installed. Users have run the connector on older Windows versions, but we don't officially support them. The Web Connector doesn't run on Mac OS. + +## What versions of QuickBooks Desktop are supported? + +Expensify follows [Intuit’s service discontinuation policy](https://quickbooks.intuit.com/learn-support/en-us/help-article/feature-preferences/quickbooks-desktop-service-discontinuation-policy/L17cXxlie_US_en_US) and supports these versions: + +- **Latest three versions** of QuickBooks Desktop (US, Canada) +- **Version tiers**: Accountant, Pro, Pro Plus, Premier, Premier Plus, Enterprise +- **Special editions**: Contractor, Manufacturing and Wholesale, Accountant, Professional Services, Nonprofit + +## Can multiple QuickBooks Desktop Connectors be installed on the same machine? + +Yes. Install one connector per company file. You can install multiple connectors to sync multiple company files to Expensify from one computer. Ensure you're logged into the correct QuickBooks company file when syncing. + +## Can I export negative expenses? + +Generally, yes. However, if you select **Check** as your export option, the report’s total cannot be negative. This also applies to non-reimbursable expenses exported as debit card transactions. Because QuickBooks Desktop doesn't support debit cards, transactions export as a non-reimbursable check, which must have a positive total amount. diff --git a/docs/articles/new-expensify/connections/quickbooks-desktop/QuickBooks-Desktop-Troubleshooting.md b/docs/articles/new-expensify/connections/quickbooks-desktop/QuickBooks-Desktop-Troubleshooting.md new file mode 100644 index 000000000000..67cf43373ffd --- /dev/null +++ b/docs/articles/new-expensify/connections/quickbooks-desktop/QuickBooks-Desktop-Troubleshooting.md @@ -0,0 +1,125 @@ +--- +title: QuickBooks Desktop troubleshooting +description: Resolve common QuickBooks Desktop integration issues with Expensify, including Web Connector, authentication, import, and export errors. +order: 3 +--- + +This article provides step-by-step solutions for common QuickBooks Desktop issues encountered when syncing with Expensify. Follow these troubleshooting steps to quickly address and resolve connectivity, authentication, import, and export problems. + +--- + +# The Web Connector cannot be reached + +These errors usually indicate a connection issue between Expensify and QuickBooks. + +## How to resolve + +1. Ensure QuickBooks Desktop and the Web Connector are running. +2. Install the Web Connector in the same location as QuickBooks (local desktop or remote server). + +If the error persists: + +1. Completely close the Web Connector (use Task Manager if needed). +2. Right-click the Web Connector icon and select **Run as administrator**. +3. Sync your Expensify workspace again. + +If issues continue: + +1. Quit and reopen QuickBooks Desktop. +2. In Expensify, go to **Settings > Workspaces**. +3. Select your workspace connected to QuickBooks Desktop. +4. Click **Accounting**. +5. Click the three vertical dots next to **QuickBooks Desktop**. +6. Click **Sync now**. +7. If unresolved, reinstall the Web Connector using the provided link. + +--- + +# Connection or authentication issues + +These errors usually indicate a credential issue. + +## How to resolve + +1. Ensure QuickBooks Desktop is open with the correct company file. +2. Ensure the QuickBooks Web Connector is open and online. +3. Close any open dialogue boxes in QuickBooks Desktop and retry syncing or exporting. +4. Check permissions: log in to QuickBooks Desktop as Admin (single-user mode). +5. Go to **Edit > Preferences > Integrated Applications > Company Preferences**. + +![Company Preferences](https://help.expensify.com/assets/images/quickbooks-desktop-company-preferences.png){:width="100%"} + +6. Select the Web Connector and click **Properties**. + +![Web Connector Properties](https://help.expensify.com/assets/images/quickbooks-desktop-access-rights.png){:width="100%"} + +7. Check **Allow this application to login automatically** and click **OK**. +8. Close all QuickBooks windows. + +If unresolved, contact Concierge with: + +- QuickBooks Desktop version. +- Location of QuickBooks and company file (local or remote). +- Location of Web Connector (local or remote). +- Provider of remote environment (if applicable, e.g., RightNetworks). + +--- + +# Import issues or missing categories/tags + +These issues indicate the integration needs updating or QuickBooks version incompatibility. + +## How to resolve + +1. Re-sync the connection from **Workspace Accounting** settings. +2. Verify configuration in QuickBooks. Expensify imports chart of accounts as categories or export account options, and imports projects, customers, and tags as tags. + +If unresolved, contact Concierge with details and QuickBooks screenshots. + +--- + +# Export or "can't find category/class/location/account" issues + +These errors usually generate a system message in Workspace Chat indicating the issue. + +## How to resolve + +1. Re-sync the connection from **Workspace Accounting** settings. +2. Re-apply coding to expenses and re-export reports. +3. Verify your QuickBooks Desktop version supports the selected export option ([check compatibility](https://quickbooks.intuit.com/desktop/)). + +If unresolved, contact Concierge with Report ID, context, and Expensify error screenshot. + +--- + +# “Oops!” error when syncing or exporting + +These errors can often be temporary or false alarms. + +## How to resolve + +1. Check if the sync/export was successful. +2. Retry syncing or exporting if unsuccessful. + +If persistent, download QuickBooks Desktop logs via Web Connector (**View Logs**) and contact Concierge. + +{% include info.html %} +If using a remote server (e.g., RightNetworks), contact their support for logs. +{% include end-info.html %} + +--- + +# Reports not exporting to QuickBooks Desktop + +Usually caused by the Web Connector or QuickBooks company file being closed during export. + +## How to resolve + +1. Ensure the Web Connector and QuickBooks Desktop company file are open. +2. In Web Connector, verify **Last Status** is "Ok". + +![Web Connector Status](https://help.expensify.com/assets/images/quickbooks-desktop-web-connector.png){:width="100%"} + +3. Check Workspace Chat in Expensify to confirm successful export. + +If unresolved, contact Concierge with Expensify Report ID and Web Connector screenshot. diff --git a/docs/articles/new-expensify/expenses-&-payments/create-per-diem-expense.md b/docs/articles/new-expensify/expenses-&-payments/Create-Per-Diem-expenses.md similarity index 62% rename from docs/articles/new-expensify/expenses-&-payments/create-per-diem-expense.md rename to docs/articles/new-expensify/expenses-&-payments/Create-Per-Diem-expenses.md index 2a0677969417..9b1174cffb9e 100644 --- a/docs/articles/new-expensify/expenses-&-payments/create-per-diem-expense.md +++ b/docs/articles/new-expensify/expenses-&-payments/Create-Per-Diem-expenses.md @@ -1,46 +1,9 @@ --- -title: Configure and submit Per Diem expenses +title: How to create a Per Diem expense description: Learn how to create and submit a Per Diem expense, including selecting a workspace, destination, time details, and sub-rates. ---
-# Configuring Per Diem in a workspace - -Per Diem is available as a feature under the **Spend** section within **More Features** in the workspace settings. Once enabled, it will appear as a dedicated menu item in the workspace settings LHN. - -## Uploading and exporting Per Diem rates - -Admins can manage Per Diem rates by uploading or exporting data. - -- To upload rates, use the **Import spreadsheet** option. -- To download existing rates, use the **Download CSV** option. - -Both options are accessible from the **three-dot menu** in the page header. - -## Editing or deleting Per Diem rates - -Each Per Diem rate is listed as an individual line item. Admins can: - -- Select a **single** rate or **multiple** rates. -- Edit a rate by clicking on it and adjusting the details. -- Delete rates using the **"X selected" drop-down menu**. - -## Setting the default Per Diem category - -Admins can assign a default category to Per Diem expenses: - -1. Click the **Settings** button in the top-right corner. -2. In the right-hand panel, select **Default category**. -3. Choose from the available categories. - -# FAQ - -## Why don’t I see the Per Diem option when submitting an expense? -The Per Diem option is only available if you are a member of a workspace with Per Diem enabled. If you are submitting an expense outside a workspace (such as in a group chat or DM), the option will not appear. - -## Can I bulk-edit or delete Per Diem rates? -Yes, you can select multiple rates at once and apply bulk actions such as editing or deleting. - # How to create a Per Diem expense If your workspace has **Per Diem** enabled, you can create a Per Diem expense directly from the **Submit Expense** flow. Follow the steps below to complete the process. diff --git a/docs/articles/new-expensify/expensify-card/Check-Expensify-Card-limit.md b/docs/articles/new-expensify/expensify-card/Check-Expensify-Card-limit.md index 803211c873ec..9cc45dd0e79b 100644 --- a/docs/articles/new-expensify/expensify-card/Check-Expensify-Card-limit.md +++ b/docs/articles/new-expensify/expensify-card/Check-Expensify-Card-limit.md @@ -25,3 +25,7 @@ To check your Smart Limit, {% include end-selector.html %}
+ +![Insert alt text for accessibility here]({{site.url}}/assets/images/wallet-01.png){:width=“100%“} + +![Insert alt text for accessibility here]({{site.url}}/assets/images/wallet-02.png){:width=“100%“} diff --git a/docs/articles/new-expensify/getting-started/Track-Expenses-on-Personal-Workspace.md b/docs/articles/new-expensify/getting-started/Track-Expenses-on-Personal-Workspace.md index 919e2e7ef2f7..29756eddbd93 100644 --- a/docs/articles/new-expensify/getting-started/Track-Expenses-on-Personal-Workspace.md +++ b/docs/articles/new-expensify/getting-started/Track-Expenses-on-Personal-Workspace.md @@ -6,7 +6,7 @@ description: Get started with Expensify and start tracking personal expenses # Overview -Welcome to Expensify! If you are a freelancer, sole proprietor or simply tracking your personal expenses, follow the steps below to get started and enjoy a free 7-day trial. The trial will begin when workspace activity begins. +Welcome to Expensify! If you are a freelancer, sole proprietor or simply tracking your personal expenses, follow the steps below to get started and enjoy a free 30-day trial. The trial will begin when workspace activity begins. {% include info.html %} After you create your new workspace, you can schedule a free private onboarding session with one of our Setup Specialists. After you complete the steps below, check your email and notifications in Expensify for your unique signup link. diff --git a/docs/articles/new-expensify/getting-started/Using-Reports-in-New-Expensify.md b/docs/articles/new-expensify/getting-started/Using-Reports-in-New-Expensify.md index 7c2016b4e212..bc4afbe957a7 100644 --- a/docs/articles/new-expensify/getting-started/Using-Reports-in-New-Expensify.md +++ b/docs/articles/new-expensify/getting-started/Using-Reports-in-New-Expensify.md @@ -24,6 +24,8 @@ Expensify's Reports feature introduces a powerful way to access and manage finan - **Cross-platform consistency** - Enjoy a seamless experience across desktop and mobile platforms. - **Saved reports** - Save and revisit frequently used report queries for recurring tasks. +![A photo of the Reports page]({{site.url}}/assets/images/ExpensifyHelp-Reports-1-v2.png){:width="100%"} + --- # Report Filters @@ -37,6 +39,8 @@ Expensify’s report filters help users narrow down results to find specific dat - Trips: Drafts, Upcoming, In Progress, Past - **Advanced filters** - Enable precise reports using query syntax (e.g., `type:expenses status:approved`). +![A photo of common report filter]({{site.url}}/assets/images/ExpensifyHelp-SearchFormat.png){:width="100%"} + --- # Search Format diff --git a/docs/articles/new-expensify/settings/Merge-Accounts.md b/docs/articles/new-expensify/settings/Merge-Accounts.md new file mode 100644 index 000000000000..dafe9a28dd0b --- /dev/null +++ b/docs/articles/new-expensify/settings/Merge-Accounts.md @@ -0,0 +1,36 @@ +# Merge Accounts + +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. + +> **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. + +### Restrictions +- You cannot merge a company account into a personal account. +- You cannot merge two company accounts if they belong to private domains. +- If your company has SAML enabled, you’ll be prompted to **Switch to Classic** for your domain admin to approve the request. + +## How to Merge Accounts + +1. **Log in** to Expensify using the account you want to keep as the primary. +2. Click your profile icon in the bottom left corner to open **Settings** +3. Click **Security** +3. Click the row for **Merge Accounts** +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. +8. Copy and paste the code into the field, then click **Merge**. +![Image shows Merge Accounts page]({{site.url}}/assets/images/ExpensifyHelp-MergeAccounts-FixedForever.png){:width="100%"} + +## FAQ + +### 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/workspaces/Configure-Per-Diem-in-a-workspace.md b/docs/articles/new-expensify/workspaces/Configure-Per-Diem-in-a-workspace.md new file mode 100644 index 000000000000..4e626f154efc --- /dev/null +++ b/docs/articles/new-expensify/workspaces/Configure-Per-Diem-in-a-workspace.md @@ -0,0 +1,52 @@ +--- +title: Configure Per Diem expenses within a workspace +description: Learn how to set up and edit per diem rates within a workspace +--- +
+ +# Configuring Per Diem in a workspace + +Per Diem is available as a feature under the **Spend** section within **More Features** in the workspace settings. Once enabled, it will appear as a dedicated menu item in the workspace settings LHN. + +![Enable Per Diem under the Spend section of the workspace settings]({{site.url}}/assets/images/perdiem_01.png){:width="100%"} + +## Uploading and exporting Per Diem rates + +Admins can manage Per Diem rates by uploading or exporting data. + +- To upload rates, use the **Import spreadsheet** option. +- To download existing rates, use the **Download CSV** option. + +Both options are accessible from the **three-dot menu** in the page header. + +![Use the three dot menu to upload or export per diem rates]({{site.url}}/assets/images/perdiem_02.png){:width="100%"} + +## Editing or deleting Per Diem rates + +Each Per Diem rate is listed as an individual line item. Admins can: + +- Select a **single** rate or **multiple** rates. +- Edit a rate by clicking on it and adjusting the details. +- Delete rates using the **"X selected" drop-down menu**. + +![Edit rates using the right-hand panel]({{site.url}}/assets/images/perdiem_03.png){:width="100%"} + +## Setting the default Per Diem category + +Admins can assign a default category to Per Diem expenses: + +1. Click the **Settings** button in the top-right corner. +2. In the right-hand panel, select **Default category**. +3. Choose from the available categories. + +![Assign the default category in the right-hand panel]({{site.url}}/assets/images/perdiem_04.png){:width="100%"} + + +# FAQ + +## Why don’t I see the Per Diem option when submitting an expense? +The Per Diem option is only available if you are a member of a workspace with Per Diem enabled. If you are submitting an expense outside a workspace (such as in a group chat or DM), the option will not appear. + +## Can I bulk-edit or delete Per Diem rates? +Yes, you can select multiple rates at once and apply bulk actions such as editing or deleting. + diff --git a/docs/articles/new-expensify/workspaces/Managing-Workspace-Members.md b/docs/articles/new-expensify/workspaces/Managing-Workspace-Members.md index d3b07a2fe222..3ee596b22630 100644 --- a/docs/articles/new-expensify/workspaces/Managing-Workspace-Members.md +++ b/docs/articles/new-expensify/workspaces/Managing-Workspace-Members.md @@ -67,12 +67,17 @@ To change an existing user’s role: ## How do I change the Workspace Owner? -To change the ownership of a workspace: +To change the ownership of a workspace (as the new owner): -1. From the Workspace view, press Members on the left. +1. From the Workspace view, click 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. +5. The user who took the action is now the owner, and should have the "Owner" tag next to their name. + +![Workspace list showing Owner tag]({{site.url}}/assets/images/transfer-ownership.png){:width="100%"} + +![Workspace admin role showing transfer owner button]({{site.url}}/assets/images/transfer-ownership_02.png){:width="100%"} # FAQ diff --git a/docs/articles/new-expensify/workspaces/Require-tags-and-categories-for-expenses.md b/docs/articles/new-expensify/workspaces/Require-tags-and-categories-for-expenses.md index f2fd6970f5af..ebeec8f609c8 100644 --- a/docs/articles/new-expensify/workspaces/Require-tags-and-categories-for-expenses.md +++ b/docs/articles/new-expensify/workspaces/Require-tags-and-categories-for-expenses.md @@ -1,41 +1,46 @@ --- -title: Require tags and categories for expenses -description: Make tags and/or categories required for all expenses +title: Require Tags and Categories for Expenses +description: Learn how to make Tags and Categories mandatory for all expenses in a Workspace. --- -
- -To require workspace members to add tags and/or categories to their expenses, - -{% include selector.html values="desktop, mobile" %} - -{% include option.html value="desktop" %} -1. Click your profile image or icon in the bottom left menu. -2. Scroll down and click **Workspaces** in the left menu. -3. Select a workspace. -4. Click **Tags** or **Categories** in the left menu. -5. Click **Settings** at the top right of the page. -6. Enable the “Members must tag/categorize all expenses" toggle. -7. If desired, repeat steps 4-6 for tags or categories (whichever you haven’t done yet). -{% include end-option.html %} - -{% include option.html value="mobile" %} -1. Tap your profile image or icon in the bottom menu. -2. Tap **Workspaces**. -3. Select a workspace. + +To ensure expenses are properly categorized, you can require Workspace members to add tags and categories before submitting them. This guide walks you through enabling this setting on both desktop and mobile and managing default spend categories for smarter expense tracking. + +# Require Tags or Categories for Workspace Expenses + +**Desktop:** +1. Click **Settings** in the bottom left menu. +2. Scroll down and click **Workspaces**. +3. Select a Workspace. +4. Click **Tags** or **Categories**. +5. Click **Settings** at the top right. +6. Enable the **“Members must tag or categorize all expenses”** toggle. +7. If needed, repeat steps 4-6 for the other option (tags or categories). + +**Mobile:** +1. Tap **Settings** in the bottom menu. +2. Tap **Workspaces**. +3. Select a Workspace. 4. Tap **Tags** or **Categories**. -5. Tap **Settings** at the top right of the page. -6. Enable the “Members must tag/categorize all expenses" toggle. -7. If desired, repeat steps 4-6 for tags or categories (whichever you haven’t done yet). -{% include end-option.html %} +5. Tap **Settings** at the top right. +6. Enable the **“Members must tag or categorize all expenses”** toggle. +7. If needed, repeat steps 4-6 for the other option (tags or categories). -{% include end-selector.html %} +![Workspace Categories Setting with Required Toggle Highlighted]({{site.url}}/assets/images/Workspace_category_toggle.png){:width="100%"} + +Once enabled, the **Tag** or **Category** field will be marked as **required** on all expenses. + +> **Note:** If Tags or Categories are required, expenses can still be submitted without them. However, the submitter and approver will see an orange dot on the expense, indicating that the required Tag or Category is missing. + +--- -![In the Workspace, Categories setting, the right-hand panel is open and the toggle to require categories on expenses is highlighted.]({{site.url}}/assets/images/Workspace_category_toggle.png){:width="100%"} - -This will highlight the tag and/or category field as required on all expenses. +# Default Spend Categories +Expensify learns how you categorize certain merchants over time and automatically applies that category to future expenses from the same merchant. You can always change the category, and Expensify will learn your corrections over time. -{% include info.html %} -Expenses will still be able to be submitted without a tag and/or category even if they are set as required. The submitter and approver will see an orange dot on the expense details alerting them that the tag/category is missing. -{% include end-info.html %} +## Manage Default Spend Categories +1. Click **Settings** in the bottom menu. +2. Click **Workspaces**. +3. Select a Workspace. +4. Click **Categories**. +5. Click **Settings** at the top right. +6. Click on any of the default categories and select one of your categories from there. -
diff --git a/docs/assets/images/ExpensifyHelp-Reports-1-v2.png b/docs/assets/images/ExpensifyHelp-Reports-1-v2.png new file mode 100644 index 000000000000..8b312be171a9 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-Reports-1-v2.png differ diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index 63ab6f79c737..d6e266c5b376 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -121,7 +121,7 @@ function openSidebar() { // Function to adapt & fix cropped SVG viewBox from Google based on viewport (Mobile or Tablet-Desktop) function changeSVGViewBoxGoogle() { // Get all inline Google SVG elements on the page - const svgsGoogle = document.querySelectorAll('svg'); + const svgsGoogle = document.querySelectorAll('svg:([data-source])'); Array.from(svgsGoogle).forEach((svg) => { // Set the viewBox attribute to '0 0 13 13' to make the svg fit in the mobile view diff --git a/docs/new-expensify/hubs/connections/quickbooks-desktop.html b/docs/new-expensify/hubs/connections/quickbooks-desktop.html new file mode 100644 index 000000000000..86641ee60b7d --- /dev/null +++ b/docs/new-expensify/hubs/connections/quickbooks-desktop.html @@ -0,0 +1,5 @@ +--- +layout: default +--- + +{% include section.html %} diff --git a/docs/redirects.csv b/docs/redirects.csv index 7775a05a1411..fcf7782eb528 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -126,7 +126,6 @@ https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-c https://help.expensify.com/articles/expensify-classic/bank-accounts-and-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/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting -https://help.expensify.com/articles/expensify-classic/reports/Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency https://help.expensify.com/articles/expensify-classic/reports/The-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/The-Expenses-Page https://help.expensify.com/articles/expensify-classic/reports/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses @@ -145,7 +144,6 @@ https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/App https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members,https://help.expensify.com/articles/expensify-classic/workspaces/Invite-members-and-assign-roles https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency -https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Expense-Types,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Types https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Report-Audit-Log-and-Comments,https://help.expensify.com/articles/expensify-classic/reports/Report-Audit-Log-and-Comments https://help.expensify.com/articles/expensify-classic/expense-and-report-features/The-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/The-Expenses-Page @@ -204,7 +202,7 @@ https://help.expensify.com/articles/expensify-classic/settings/account-settings/ https://help.expensify.com/articles/expensify-classic/settings/account-settings/Manage-devices,https://help.expensify.com/articles/expensify-classic/settings/Manage-devices https://help.expensify.com/articles/expensify-classic/settings/account-settings/Merge-accounts,https://help.expensify.com/articles/expensify-classic/settings/Merge-accounts https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-Notifications,https://help.expensify.com/articles/expensify-classic/settings/Set-Notifications -https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-time-zone,https://help.expensify.com/articles/expensify-classic/settings/Set-time-zone +https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-time-zone,https://help.expensify.com/articles/expensify-classic/settings/Set-Time-Zone https://help.expensify.com/articles/expensify-classic/travel/Coming-Soon,https://help.expensify.com/expensify-classic/hubs/travel/ https://help.expensify.com/articles/new-expensify/expenses/Manually-submit-reports-for-approval,https://help.expensify.com/new-expensify/hubs/expenses/ https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.html,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments @@ -384,7 +382,7 @@ https://community.expensify.com/discussion/5539/deep-dive-what-is-a-copilot-what https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Vacation-Delegate,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Delegate-when-out-of-office https://community.expensify.com/discussion/5678/deep-dive-secondary-login-merge-accounts-what-does-this-mean,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Merge-accounts https://community.expensify.com/discussion/5103/how-to-create-and-use-custom-units/,https://help.expensify.com/ -https://community.expensify.com/discussion/6530/how-to-set-your-time-zone-for-report-history-comments,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-time-zone +https://community.expensify.com/discussion/6530/how-to-set-your-time-zone-for-report-history-comments,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-Time-Zone https://community.expensify.com/discussion/5651/deep-dive-best-practices-when-youre-running-into-trouble-receiving-emails-from-expensify,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-Notifications https://community.expensify.com/discussion/5793/how-to-connect-your-personal-card-to-import-expenses/,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards https://community.expensify.com/discussion/5677/deep-dive-security-how-expensify-protects-your-information,https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security @@ -576,7 +574,7 @@ https://help.expensify.com/articles/new-expensify/getting-started/Upgrade-to-a-C https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports https://help.expensify.com/articles/new-expensify/expenses-&-payments/pay-an-invoice.html,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Pay-an-invoice https://community.expensify.com/discussion/4707/how-to-set-up-your-mobile-app,https://help.expensify.com/articles/expensify-classic/getting-started/Join-your-company's-workspace#download-the-mobile-app -https://community.expensify.com/discussion/5179/faq-what-does-a-policy-for-which-you-are-an-admin-has-out-of-date-billing-information-mean,https://help.expensify.com/articles/expensify-classic/expensify-billing/Out-of-date-Billing +https://community.expensify.com/discussion/5179/faq-what-does-a-policy-for-which-you-are-an-admin-has-out-of-date-billing-information-mean,https://help.expensify.com/articles/expensify-classic/expensify-billing/Out-of-Date-Billing https://community.expensify.com/discussion/6179/setting-up-a-receipt-or-travel-integration-with-expensify,https://help.expensify.com/articles/expensify-classic/connections/Additional-Travel-Integrations https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-Personal-US-Bank-Account,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-Personal-Bank-Account https://community.expensify.com//discussion/6927/deep-dive-how-can-i-estimate-the-savings-applied-to-my-bill,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview#savings-calculator @@ -592,7 +590,6 @@ https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/add-a-payment-card-and-view-your-subscription,https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Add-a-payment-card-and-view-your-subscription https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Billing-page-coming-soon,https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Billing-page https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Billing-page,https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Billing-Overview -https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Create-Expense-Rules https://help.expensify.com/articles/expensify-classic/expenses/The-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/Navigate-the-Expenses-Page https://help.expensify.com/articles/expensify-classic/expenses/Add-expenses-in-bulk,https://help.expensify.com/articles/expensify-classic/expenses/Add-an-expense https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses,https://help.expensify.com/articles/expensify-classic/expenses/Add-an-expense @@ -626,7 +623,6 @@ https://help.expensify.com/articles/expensify-classic/settings/Change-or-add-ema 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 @@ -642,6 +638,7 @@ https://help.expensify.com/articles/expensify-classic/travel/Approve-travel-expe https://help.expensify.com/articles/expensify-classic/travel/Book-with-Expensify-Travel.md,https://help.expensify.com/articles/new-expensify/travel/Book-with-Expensify-Travel https://help.expensify.com/articles/expensify-classic/travel/Configure-travel-policy-and-preferences.md,https://help.expensify.com/articles/new-expensify/travel/Configure-travel-policy-and-preferences https://help.expensify.com/articles/expensify-classic/travel/Track-Travel-Analytics.md,https://help.expensify.com/articles/new-expensify/travel/Track-Travel-Analytics +https://help.expensify.com/articles/new-expensify/expenses-&-payments/create-per-diem-expense,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Create-Per-Diem-expense https://help.expensify.com/articles/expensify-classic/connections/ADP,https://help.expensify.com/articles/expensify-classic/connections/Connect-to-ADP https://help.expensify.com/articles/new-expensify/connect-credit-cards/company-cards/Commercial-feeds,https://help.expensify.com/articles/new-expensify/connect-credit-cards/Commercial-feeds https://help.expensify.com/articles/new-expensify/connect-credit-cards/company-cards/Company-Card-Settings,https://help.expensify.com/articles/new-expensify/connect-credit-cards/Company-Card-Settings @@ -651,8 +648,17 @@ https://help.expensify.com/articles/expensify-classic/workspaces/Create-a-group- https://help.expensify.com/articles/expensify-classic/getting-started/Create-a-company-workspace,https://help.expensify.com/articles/expensify-classic/getting-started/Create-a-Company-Workspace https://help.expensify.com/articles/expensify-classic/workspaces/Set-up-your-individual-workspace,https://help.expensify.com/articles/expensify-classic/getting-started/Create-a-workspace-for-yourself https://help.expensify.com/articles/expensify-classic/travel/Book-with-Expensify-Travel,https://help.expensify.com/articles/new-expensify/travel/Book-with-Expensify-Travel +https://help.expensify.com/articles/expensify-classic/getting-started/Join-your-company's-workspace,https://help.expensify.com/articles/expensify-classic/getting-started/Join-Your-Company's-Workspace https://help.expensify.com/articles/expensify-classic/domains/Add-Domain-Members-and-Admins,https://help.expensify.com/articles/expensify-classic/domains/Domain-Members https://help.expensify.com/articles/expensify-classic/domains/Switch-Domain-Member-to-Admin,https://help.expensify.com/articles/expensify-classic/domains/Domain-Members +https://help.expensify.com/articles/expensify-classic/domains/Create-A-Group,https://help.expensify.com/articles/expensify-classic/domains/Domain-Groups https://help.expensify.com/articles/expensify-classic/expenses/Navigate-the-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/Export-Expenses-from-the-Expenses-Page https://help.expensify.com/articles/expensify-classic/expenses/Export-expenses,https://help.expensify.com/articles/expensify-classic/expenses/Export-Expenses-from-the-Expenses-Page https://help.expensify.com/articles/expensify-classic/reports/Print-or-download-a-report,https://help.expensify.com/articles/expensify-classic/expenses/Export-Expenses-from-the-Expenses-Page +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-an-Invoice,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Send-and-Pay-Invoices +https://help.expensify.com/articles/expensify-classic/expenses/Add-Invoices-in-Bulk,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Send-and-Pay-Invoices +https://help.expensify.com/articles/expensify-classic/expenses/Send-and-Receive-Payment-for-Invoices,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Send-and-Pay-Invoices +https://help.expensify.com/articles/expensify-classic/expenses/Create-Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules +https://help.expensify.com/articles/expensify-classic/expensify-card/Statements,https://help.expensify.com/articles/expensify-classic/expensify-card/Expensify-Card-Statements +https://help.expensify.com/articles/expensify-classic/expensify-billing/Out-of-date-Billing,https://help.expensify.com/articles/expensify-classic/expensify-billing/Out-of-Date-Billing +https://help.expensify.com/articles/expensify-classic/settings/Set-time-zone,https://help.expensify.com/articles/expensify-classic/settings/Set-Time-Zone diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 754aad26679e..a7b59e173424 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -23,7 +23,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.1.6 + 9.1.10 CFBundleSignature ???? CFBundleURLTypes @@ -44,7 +44,7 @@ CFBundleVersion - 9.1.6.0 + 9.1.10.0 FullStory OrgId diff --git a/ios/NewExpensify/RCTBootSplash.h b/ios/NewExpensify/RCTBootSplash.h index f25f3e28f561..f499fe59f916 100644 --- a/ios/NewExpensify/RCTBootSplash.h +++ b/ios/NewExpensify/RCTBootSplash.h @@ -2,8 +2,9 @@ @interface RCTBootSplash : NSObject -+ (void)invalidateBootSplash; + (void)initWithStoryboard:(NSString * _Nonnull)storyboardName rootView:(UIView * _Nullable)rootView; ++ (void)hide:(BOOL)fade; ++ (void)bringSubviewToFrontIfInitialized; @end diff --git a/ios/NewExpensify/RCTBootSplash.mm b/ios/NewExpensify/RCTBootSplash.mm index ddb3f2d047ce..fe2dcd9ba646 100644 --- a/ios/NewExpensify/RCTBootSplash.mm +++ b/ios/NewExpensify/RCTBootSplash.mm @@ -25,16 +25,14 @@ + (BOOL)requiresMainQueueSetup { return NO; } -+ (void)invalidateBootSplash { - _resolveQueue = nil; - _rootView = nil; - _nativeHidden = false; -} - + (bool)isLoadingViewVisible { return _loadingView != nil && ![_loadingView isHidden]; } ++ (BOOL)isInitialized { + return _loadingView && _rootView; +} + + (bool)hasResolveQueue { return _resolveQueue != nil; } @@ -42,7 +40,7 @@ + (bool)hasResolveQueue { + (void)clearResolveQueue { if (![self hasResolveQueue]) return; - + while ([_resolveQueue count] > 0) { RCTPromiseResolveBlock resolve = [_resolveQueue objectAtIndex:0]; [_resolveQueue removeObjectAtIndex:0]; @@ -81,7 +79,7 @@ + (void)hideAndClearPromiseQueue { + (void)initWithStoryboard:(NSString * _Nonnull)storyboardName rootView:(UIView * _Nullable)rootView { - if (RCTRunningInAppExtension()) { + if (RCTRunningInAppExtension() || [self isInitialized]) { return; } @@ -108,8 +106,14 @@ + (void)initWithStoryboard:(NSString * _Nonnull)storyboardName _loadingView.center = (CGPoint){CGRectGetMidX(_rootView.bounds), CGRectGetMidY(_rootView.bounds)}; _loadingView.hidden = NO; - [_rootView disableActivityIndicatorAutoHide:YES]; - [_rootView setLoadingView:_loadingView]; + [_rootView addSubview:_loadingView]; + + if ([_rootView respondsToSelector:@selector(disableActivityIndicatorAutoHide:)]) { + [_rootView disableActivityIndicatorAutoHide:YES]; + } + if ([_rootView respondsToSelector:@selector(setLoadingView:)]) { + [_rootView setLoadingView:_loadingView]; + } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onJavaScriptDidLoad) @@ -145,24 +149,31 @@ - (NSDictionary *)constantsToExport { }; } -- (void)hideImpl:(BOOL)fade - resolve:(RCTPromiseResolveBlock)resolve { - if (_resolveQueue == nil) - _resolveQueue = [[NSMutableArray alloc] init]; ++ (void)bringSubviewToFrontIfInitialized { + if(![self isInitialized]) { + return; + } - [_resolveQueue addObject:resolve]; + [_rootView bringSubviewToFront:_loadingView]; +} - if (![RCTBootSplash isLoadingViewVisible] || RCTRunningInAppExtension()) - return [RCTBootSplash clearResolveQueue]; ++ (void)hide:(BOOL)fade { + if (![RCTBootSplash isLoadingViewVisible] || RCTRunningInAppExtension()) + return [RCTBootSplash clearResolveQueue]; - _fade = fade; + _fade = fade; - if (_nativeHidden) - return [RCTBootSplash hideAndClearPromiseQueue]; + return [RCTBootSplash hideAndClearPromiseQueue]; } -- (void)isVisibleImpl:(RCTPromiseResolveBlock)resolve { - resolve(@([RCTBootSplash isLoadingViewVisible])); +- (void)hideImpl:(BOOL)fade + resolve:(RCTPromiseResolveBlock)resolve { + if (_resolveQueue == nil) + _resolveQueue = [[NSMutableArray alloc] init]; + + [_resolveQueue addObject:resolve]; + + [RCTBootSplash hide:fade]; } RCT_EXPORT_METHOD(hide:(RCTPromiseResolveBlock)resolve @@ -170,9 +181,4 @@ - (void)isVisibleImpl:(RCTPromiseResolveBlock)resolve { [self hideImpl:0 resolve:resolve]; } -RCT_EXPORT_METHOD(isVisible:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { - [self isVisibleImpl:resolve]; -} - @end diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 3b820bfc9d5a..18597086467e 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.1.6 + 9.1.10 CFBundleSignature ???? CFBundleVersion - 9.1.6.0 + 9.1.10.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 8db6346d0067..323f5b5d62cc 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.1.6 + 9.1.10 CFBundleVersion - 9.1.6.0 + 9.1.10.0 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 28a5d50f8001..b2df1bbc6ae4 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2522,7 +2522,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.233): + - RNLiveMarkdown (0.1.238): - DoubleConversion - glog - hermes-engine @@ -2542,10 +2542,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.233) + - RNLiveMarkdown/newarch (= 0.1.238) - RNReanimated/worklets - Yoga - - RNLiveMarkdown/newarch (0.1.233): + - RNLiveMarkdown/newarch (0.1.238): - DoubleConversion - glog - hermes-engine @@ -3292,8 +3292,8 @@ SPEC CHECKSUMS: AirshipServiceExtension: 9c73369f426396d9fb9ff222d86d842fac76ba46 AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa AppLogs: 3bc4e9b141dbf265b9464409caaa40416a9ee0e0 - boost: 26992d1adf73c1c7676360643e687aee6dda994b - DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + boost: d7090b1a93a9798c029277a8288114f2948f471c + DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 EXAV: 9773c9799767c9925547b05e41a26a0240bb8ef2 EXImageLoader: 759063a65ab016b836f73972d3bb25404888713d expensify-react-native-background-task: 6f797cf470b627912c246514b1631a205794775d @@ -3313,11 +3313,11 @@ SPEC CHECKSUMS: FirebaseInstallations: 40bd9054049b2eae9a2c38ef1c3dd213df3605cd FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b - fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 + fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be ForkInputMask: 55e3fbab504b22da98483e9f9a6514b98fdd2f3c FullStory: c8a10b2358c0d33c57be84d16e4c440b0434b33d fullstory_react-native: 63a803cca04b0447a71daa73e4df3f7b56e1919d - glog: 69ef571f3de08433d766d614c73a9838a06bf7eb + glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db @@ -3343,7 +3343,7 @@ SPEC CHECKSUMS: PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 pusher-websocket-react-native: e40c49a1e4ec96d4157375aebcf44943f0f8f62f PusherSwift: cad631bad86cfff4b8458dce1310a7774e469b1f - RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 + RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648 RCTDeprecation: 2c5e1000b04ab70b53956aa498bf7442c3c6e497 RCTRequired: 5f785a001cf68a551c5f5040fb4c415672dbb481 RCTTypeSafety: 6b98db8965005d32449605c0d005ecb4fee8a0f7 @@ -3433,7 +3433,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 364e6862a112045bb5c5d35601f0bdb0304af979 RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: 763a9c03a90435770c2d49f9e99a7fc72ea49552 + RNLiveMarkdown: a06be8b281567f9c2cd16c7f7c6b4f5f9d0ff68b RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: d184c8d3213acf4c97ec71fbbb6f9d4954552d80 RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28 diff --git a/jest.config.js b/jest.config.js index ea9a23f01362..124473525992 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,7 +12,11 @@ module.exports = { '^.+\\.[jt]sx?$': 'babel-jest', '^.+\\.svg?$': 'jest-transformer-svg', }, - transformIgnorePatterns: ['/node_modules/(?!react-native)/'], + transformIgnorePatterns: [ + '/node_modules/(?!react-native)/', + // Prevent Babel from transforming worklets in this file so they are treated as normal functions, otherwise FormatSelectionUtilsTest won't run. + '/node_modules/@expensify/react-native-live-markdown/lib/commonjs/parseExpensiMark.js', + ], testPathIgnorePatterns: ['/node_modules'], globals: { __DEV__: true, diff --git a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt index 47e4196f37c1..19d3e31d83c1 100644 --- a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt +++ b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt @@ -41,6 +41,16 @@ class ReactNativeBackgroundTaskModule internal constructor(context: ReactApplica return NAME } + override fun invalidate() { + super.invalidate() + try { + reactApplicationContext.unregisterReceiver(taskReceiver) + Log.d("ReactNativeBackgroundTaskModule", "BroadcastReceiver unregistered") + } catch (e: IllegalArgumentException) { + Log.w("ReactNativeBackgroundTaskModule", "Receiver not registered or already unregistered") + } + } + @ReactMethod override fun defineTask(taskName: String, taskExecutor: Callback, promise: Promise) { try { diff --git a/modules/background-task/ios/ReactNativeBackgroundTask.h b/modules/background-task/ios/ReactNativeBackgroundTask.h index 93d1e83a6878..8f3f0e114501 100644 --- a/modules/background-task/ios/ReactNativeBackgroundTask.h +++ b/modules/background-task/ios/ReactNativeBackgroundTask.h @@ -1,5 +1,5 @@ #ifdef RCT_NEW_ARCH_ENABLED -#import "RNReactNativeBackgroundTaskSpec.h" +#import #import @interface ReactNativeBackgroundTask : NativeReactNativeBackgroundTaskSpecBase diff --git a/package-lock.json b/package-lock.json index 3f04969ba44b..4052598cf49e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "new.expensify", - "version": "9.1.6-0", + "version": "9.1.10-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.1.6-0", + "version": "9.1.10-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.233", + "@expensify/react-native-live-markdown": "0.1.238", "@expo/metro-runtime": "^4.0.0", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -79,7 +79,7 @@ "react-content-loader": "^7.0.0", "react-dom": "18.3.1", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "^1.0.26", + "react-fast-pdf": "^1.0.27", "react-map-gl": "^7.1.3", "react-native": "0.76.3", "react-native-advanced-input-mask": "1.3.1", @@ -274,7 +274,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "type-fest": "4.20.0", + "type-fest": "4.35.0", "typescript": "^5.4.5", "wait-port": "^0.2.9", "webpack": "^5.94.0", @@ -3644,9 +3644,9 @@ "link": true }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.233", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.233.tgz", - "integrity": "sha512-U0hz4YE67SLi5O2YXzGahsqEq700saQIH7Spg/5rHNfJmJeTaJTs5loOcIWHlkcU9NAVG/5sSFSVv1UxldywJg==", + "version": "0.1.238", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.238.tgz", + "integrity": "sha512-JtNlxSNyq0bORBlfIlLwYlJom6X4BMVy+8QKvlWlvu7FDAzbpBU/RBQwp/n3AxYfu6KczHwRkY5ozbVcRj1rYA==", "license": "MIT", "workspaces": [ "./example", @@ -32064,9 +32064,9 @@ } }, "node_modules/react-fast-pdf": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/react-fast-pdf/-/react-fast-pdf-1.0.26.tgz", - "integrity": "sha512-90ZzyfYtJYLLNV782kOrRRZt2C0M6p0DoCL80kIdhq5/63Y+6+/tzpF5aO0tmnA2G0uM6Pm+plwPsG0bWUjmJg==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/react-fast-pdf/-/react-fast-pdf-1.0.27.tgz", + "integrity": "sha512-+lb5pABLnN2dAq4rOWLmEz00wIesL/KjCo/fgbkvJmdz5nuTof1kks4Qymi29OHga7LzKzox8oqyQivcBOUKXQ==", "license": "MIT", "dependencies": { "react-pdf": "9.2.0", @@ -36332,7 +36332,9 @@ } }, "node_modules/type-fest": { - "version": "4.20.0", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.35.0.tgz", + "integrity": "sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { diff --git a/package.json b/package.json index 20efe78933bc..af82ad720618 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.1.6-0", + "version": "9.1.10-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.", @@ -79,7 +79,7 @@ "dependencies": { "@dotlottie/react-player": "^1.6.3", "@expensify/react-native-background-task": "file:./modules/background-task", - "@expensify/react-native-live-markdown": "0.1.233", + "@expensify/react-native-live-markdown": "0.1.238", "@expo/metro-runtime": "^4.0.0", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -146,7 +146,7 @@ "react-content-loader": "^7.0.0", "react-dom": "18.3.1", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "^1.0.26", + "react-fast-pdf": "^1.0.27", "react-map-gl": "^7.1.3", "react-native": "0.76.3", "react-native-advanced-input-mask": "1.3.1", @@ -341,7 +341,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "type-fest": "4.20.0", + "type-fest": "4.35.0", "typescript": "^5.4.5", "wait-port": "^0.2.9", "webpack": "^5.94.0", diff --git a/patches/@react-navigation+core+6.4.11+001+getStateFromPath-getPathFromState-configs-caching.patch b/patches/@react-navigation+core+6.4.11+002+getStateFromPath-getPathFromState-configs-caching.patch similarity index 100% rename from patches/@react-navigation+core+6.4.11+001+getStateFromPath-getPathFromState-configs-caching.patch rename to patches/@react-navigation+core+6.4.11+002+getStateFromPath-getPathFromState-configs-caching.patch diff --git a/patches/@react-navigation+core+6.4.11+002+platform-navigation-stack-types.patch b/patches/@react-navigation+core+6.4.11+003+platform-navigation-stack-types.patch similarity index 100% rename from patches/@react-navigation+core+6.4.11+002+platform-navigation-stack-types.patch rename to patches/@react-navigation+core+6.4.11+003+platform-navigation-stack-types.patch diff --git a/patches/@react-navigation+core+6.4.11+004+side-pane.patch b/patches/@react-navigation+core+6.4.11+004+side-pane.patch new file mode 100644 index 000000000000..995c37eedd04 --- /dev/null +++ b/patches/@react-navigation+core+6.4.11+004+side-pane.patch @@ -0,0 +1,92 @@ +diff --git a/node_modules/@react-navigation/core/lib/module/useDescriptors.js b/node_modules/@react-navigation/core/lib/module/useDescriptors.js +index 76fdab1..75f315c 100644 +--- a/node_modules/@react-navigation/core/lib/module/useDescriptors.js ++++ b/node_modules/@react-navigation/core/lib/module/useDescriptors.js +@@ -112,6 +112,19 @@ export default function useDescriptors(_ref, convertCustomScreenOptions) { + } + return o; + }); ++ const SidePane = customOptions.sidePane; ++ let element = /*#__PURE__*/React.createElement(React.Fragment, { ++ children: [/*#__PURE__*/React.createElement(SceneView, { ++ navigation: navigation, ++ route: route, ++ screen: screen, ++ routeState: state.routes[i].state, ++ getState: getState, ++ setState: setState, ++ options: customOptions, ++ clearOptions: clearOptions ++ }), SidePane && /*#__PURE__*/React.createElement(SidePane, {})] ++ }); + acc[route.key] = { + route, + // @ts-expect-error: it's missing action helpers, fix later +@@ -123,17 +136,10 @@ export default function useDescriptors(_ref, convertCustomScreenOptions) { + }, /*#__PURE__*/React.createElement(NavigationContext.Provider, { + value: navigation + }, /*#__PURE__*/React.createElement(NavigationRouteContext.Provider, { +- value: route +- }, /*#__PURE__*/React.createElement(SceneView, { +- navigation: navigation, +- route: route, +- screen: screen, +- routeState: state.routes[i].state, +- getState: getState, +- setState: setState, +- options: mergedOptions, +- clearOptions: clearOptions +- })))); ++ value: route, ++ children: element ++ }, ++ ))); + }, + options: mergedOptions + }; +diff --git a/node_modules/@react-navigation/core/src/useDescriptors.tsx b/node_modules/@react-navigation/core/src/useDescriptors.tsx +index 2e4ee0f..11ece43 100644 +--- a/node_modules/@react-navigation/core/src/useDescriptors.tsx ++++ b/node_modules/@react-navigation/core/src/useDescriptors.tsx +@@ -238,6 +238,23 @@ export default function useDescriptors< + return o; + }); + ++ const SidePane = (customOptions as any).sidePane; ++ let element = ( ++ <> ++ ++ {SidePane && } ++ ++ ); ++ + acc[route.key] = { + route, + // @ts-expect-error: it's missing action helpers, fix later +@@ -247,16 +264,7 @@ export default function useDescriptors< + + + +- ++ {element} + + + diff --git a/scripts/is-hybrid-app.sh b/scripts/is-hybrid-app.sh index 32ca190ac832..0894a3aeaff9 100755 --- a/scripts/is-hybrid-app.sh +++ b/scripts/is-hybrid-app.sh @@ -1,11 +1,18 @@ #!/bin/bash -set -e + +source scripts/shellUtils.sh + +# Check if jq is installed +if ! jq --version > /dev/null 2>&1; then + error 'jq is not installed. Please install jq and try again' >&2 + exit 1 +fi if [[ ! -d Mobile-Expensify ]]; then echo false exit 0 else - cd Mobile-Expensify + cd Mobile-Expensify || exit 1 fi # Check if 'package.json' exists @@ -17,6 +24,10 @@ if [[ -f package.json ]]; then if [[ "$package_name" == "mobile-expensify" ]]; then echo true exit 0 + else + echo "The package name is incorrect. It should be 'mobile-expensify'. Script will assume the standalone NewDot app." + echo false + exit 0 fi else echo "package.json not found in Mobile-Expensify" diff --git a/src/App.tsx b/src/App.tsx index f3d37fe87c0b..8dd2631a6b7d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,10 +42,17 @@ 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. */ +/** + * Properties passed to the top-level React Native component by HybridApp. + * These will always be `undefined` in "pure" NewDot builds. + */ type AppProps = { - /** URL containing all necessary data to run React Native app (e.g. login data) */ + /** The URL specifying the initial navigation destination when the app opens */ url?: Route; + /** Serialized configuration data required to initialize the React Native app (e.g. authentication details) */ + hybridAppSettings?: string; + /** A timestamp indicating when the initial properties were last updated, used to detect changes */ + timestamp?: string; }; LogBox.ignoreLogs([ @@ -61,14 +68,18 @@ const fill = {flex: 1}; const StrictModeWrapper = CONFIG.USE_REACT_STRICT_MODE_IN_DEV ? React.StrictMode : ({children}: {children: React.ReactElement}) => children; -function App({url}: AppProps) { +function App({url, hybridAppSettings, timestamp}: AppProps) { useDefaultDragAndDrop(); OnyxUpdateManager(); return ( - + - `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, @@ -386,7 +376,7 @@ const CONST = { ANIMATION_PAID_CHECKMARK_DELAY: 300, ANIMATION_THUMBSUP_DURATION: 250, ANIMATION_THUMBSUP_DELAY: 200, - ANIMATION_PAID_BUTTON_HIDE_DELAY: 1000, + ANIMATION_PAID_BUTTON_HIDE_DELAY: 300, BACKGROUND_IMAGE_TRANSITION_DURATION: 1000, SCREEN_TRANSITION_END_TIMEOUT: 1000, ARROW_HIDE_DELAY: 3000, @@ -681,6 +671,14 @@ const CONST = { AGREEMENTS: 'AgreementsStep', FINISH: 'FinishStep', }, + BANK_INFO_STEP_ACH_DATA_INPUT_IDS: { + ACCOUNT_HOLDER_NAME: 'addressName', + ACCOUNT_HOLDER_REGION: 'addressState', + ACCOUNT_HOLDER_CITY: 'addressCity', + ACCOUNT_HOLDER_ADDRESS: 'addressStreet', + ACCOUNT_HOLDER_POSTAL_CODE: 'addressZipCode', + ROUTING_CODE: 'routingNumber', + }, BUSINESS_INFO_STEP: { PICKLIST: { ANNUAL_VOLUME_RANGE: 'AnnualVolumeRange', @@ -748,7 +746,6 @@ const CONST = { PREVENT_SPOTNANA_TRAVEL: 'preventSpotnanaTravel', REPORT_FIELDS_FEATURE: 'reportFieldsFeature', NETSUITE_USA_TAX: 'netsuiteUsaTax', - COMBINED_TRACK_SUBMIT: 'combinedTrackSubmit', PER_DIEM: 'newDotPerDiem', NEWDOT_MERGE_ACCOUNTS: 'newDotMergeAccounts', NEWDOT_MANAGER_MCTEST: 'newDotManagerMcTest', @@ -774,6 +771,12 @@ const CONST = { GB: 'GB', IT: 'IT', }, + SWIPE_DIRECTION: { + DOWN: 'down', + LEFT: 'left', + RIGHT: 'right', + UP: 'up', + }, DESKTOP_DEEPLINK_APP_STATE: { CHECKING: 'checking', INSTALLED: 'installed', @@ -1059,6 +1062,7 @@ const CONST = { ADMIN_DOMAINS_URL: 'admin_domains', INBOX: 'inbox', POLICY_CONNECTIONS_URL: (policyID: string) => `policy?param={"policyID":"${policyID}"}#connections`, + SIGN_OUT: 'signout', }, EXPENSIFY_POLICY_DOMAIN, @@ -1112,6 +1116,15 @@ const CONST = { MIN_INITIAL_REPORT_ACTION_COUNT: 15, UNREPORTED_REPORTID: '0', SPLIT_REPORTID: '-2', + PRIMARY_ACTIONS: { + SUBMIT: 'submit', + APPROVE: 'approve', + PAY: 'pay', + EXPORT_TO_ACCOUNTING: 'exportToAccounting', + REMOVE_HOLD: 'removeHold', + REVIEW_DUPLICATES: 'reviewDuplicates', + MARK_AS_CASH: 'markAsCash', + }, ACTIONS: { LIMIT: 50, // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts @@ -1451,6 +1464,7 @@ const CONST = { USE_DEBOUNCED_STATE_DELAY: 300, LIST_SCROLLING_DEBOUNCE_TIME: 200, PUSHER_PING_PONG: 'pusher_ping_pong', + LOCATION_UPDATE_INTERVAL: 5000, }, PRIORITY_MODE: { GSD: 'gsd', @@ -1491,6 +1505,10 @@ const CONST = { DRAFT: 'draft', BACKUP: 'backup', }, + LIABILITY_TYPE: { + RESTRICT: 'corporate', + ALLOW: 'personal', + }, }, MCC_GROUPS: { @@ -1651,6 +1669,7 @@ const CONST = { EMOJI_NUM_PER_ROW: 8, EMOJI_DEFAULT_SKIN_TONE: -1, + DISPLAY_PARTICIPANTS_LIMIT: 5, // Amount of emojis to render ahead at the end of the update cycle EMOJI_DRAW_AMOUNT: 250, @@ -2953,6 +2972,7 @@ const CONST = { BREX: 'oauth.brex.com', WELLS_FARGO: 'oauth.wellsfargo.com', AMEX_DIRECT: 'oauth.americanexpressfdx.com', + CSV: '_ccupload', }, STEP_NAMES: ['1', '2', '3', '4'], STEP: { @@ -3033,6 +3053,7 @@ const CONST = { VISA: 'visa', MASTERCARD: 'mastercard', STRIPE: 'stripe', + CSV: 'CSV', }, FEED_TYPE: { CUSTOM: 'customFeed', @@ -3229,6 +3250,7 @@ const CONST = { CARD_SECURITY_CODE: /^[0-9]{3,4}$/, CARD_EXPIRATION_DATE: /^(0[1-9]|1[0-2])([^0-9])?([0-9]{4}|([0-9]{2}))$/, ROOM_NAME: /^#[\p{Ll}0-9-]{1,100}$/u, + ROOM_NAME_WITHOUT_LIMIT: /^#[\p{Ll}0-9-]+$/u, DOMAIN_BASE: '^(?:https?:\\/\\/)?(?:www\\.)?([^\\/]+)', ALPHANUMERIC_WITH_SPACE_AND_HYPHEN: /^[A-Za-z0-9 -]+$/, @@ -3249,7 +3271,7 @@ const CONST = { EMOJI_NAME: /:[\p{L}0-9_+-]+:/gu, EMOJI_SUGGESTIONS: /:[\p{L}0-9_+-]{1,40}$/u, AFTER_FIRST_LINE_BREAK: /\n.*/g, - LINE_BREAK: /\r\n|\r|\n/g, + LINE_BREAK: /\r\n|\r|\n|\u2028/g, CODE_2FA: /^\d{6}$/, ATTACHMENT_ID: /chat-attachments\/(\d+)/, HAS_COLON_ONLY_AT_THE_BEGINNING: /^:[^:]+$/, @@ -3322,7 +3344,7 @@ const CONST = { GUIDES_CALL_TASK_IDS: { CONCIERGE_DM: 'NewExpensifyConciergeDM', WORKSPACE_INITIAL: 'WorkspaceHome', - WORKSPACE_PROFILE: 'WorkspaceProfile', + WORKSPACE_OVERVIEW: 'WorkspaceOverview', WORKSPACE_INVOICES: 'WorkspaceSendInvoices', WORKSPACE_MEMBERS: 'WorkspaceManageMembers', WORKSPACE_EXPENSIFY_CARD: 'WorkspaceExpensifyCard', @@ -3449,6 +3471,11 @@ const CONST = { UNAPPROVE: 'unapprove', DEBUG: 'debug', GO_TO_WORKSPACE: 'goToWorkspace', + TRACK: { + SUBMIT: 'submit', + CATEGORIZE: 'categorize', + SHARE: 'share', + }, }, EDIT_REQUEST_FIELD: { AMOUNT: 'amount', @@ -5155,7 +5182,7 @@ const CONST = { ONBOARDING_CHOICES: {...onboardingChoices}, SELECTABLE_ONBOARDING_CHOICES: {...selectableOnboardingChoices}, - COMBINED_TRACK_SUBMIT_ONBOARDING_CHOICES: {...combinedTrackSubmitOnboardingChoices}, + CREATE_EXPENSE_ONBOARDING_CHOICES: {...createExpenseOnboardingChoices}, ONBOARDING_SIGNUP_QUALIFIERS: {...signupQualifiers}, ONBOARDING_INVITE_TYPES: {...onboardingInviteTypes}, ONBOARDING_COMPANY_SIZE: {...onboardingCompanySize}, @@ -5176,7 +5203,6 @@ const CONST = { tasks: [ createWorkspaceTask, selfGuidedTourTask, - meetGuideTask, { type: 'setupCategoriesAndTags', autoCompleted: false, @@ -5275,7 +5301,6 @@ const CONST = { }, tasks: [ createWorkspaceTask, - meetGuideTask, setupCategoriesTask, { type: 'inviteAccountant', @@ -5344,7 +5369,6 @@ const CONST = { [onboardingChoices.ADMIN]: { message: "As an admin, learn how to manage your team's workspace and submit expenses yourself.", tasks: [ - meetGuideTask, { type: 'reviewWorkspaceSettings', autoCompleted: false, @@ -5382,11 +5406,11 @@ const CONST = { }, } satisfies Record, - COMBINED_TRACK_SUBMIT_ONBOARDING_MESSAGES: { - [combinedTrackSubmitOnboardingChoices.PERSONAL_SPEND]: combinedTrackSubmitOnboardingPersonalSpendMessage, - [combinedTrackSubmitOnboardingChoices.EMPLOYER]: combinedTrackSubmitOnboardingEmployerOrSubmitMessage, - [combinedTrackSubmitOnboardingChoices.SUBMIT]: combinedTrackSubmitOnboardingEmployerOrSubmitMessage, - } satisfies Record, OnboardingMessage>, + CREATE_EXPENSE_ONBOARDING_MESSAGES: { + [createExpenseOnboardingChoices.PERSONAL_SPEND]: combinedTrackSubmitOnboardingPersonalSpendMessage, + [createExpenseOnboardingChoices.EMPLOYER]: combinedTrackSubmitOnboardingEmployerOrSubmitMessage, + [createExpenseOnboardingChoices.SUBMIT]: combinedTrackSubmitOnboardingEmployerOrSubmitMessage, + } satisfies Record, OnboardingMessage>, REPORT_FIELD_TITLE_FIELD_ID: 'text_title', @@ -6286,6 +6310,7 @@ const CONST = { TAG: 'tag', TAX_RATE: 'taxRate', CARD_ID: 'cardID', + FEED: 'feed', REPORT_ID: 'reportID', KEYWORD: 'keyword', IN: 'in', @@ -6319,6 +6344,7 @@ const CONST = { TAG: 'tag', TAX_RATE: 'tax-rate', CARD_ID: 'card', + FEED: 'feed', REPORT_ID: 'reportid', KEYWORD: 'keyword', IN: 'in', @@ -6680,10 +6706,6 @@ const CONST = { }, }, - HYBRID_APP: { - REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront', - }, - MIGRATED_USER_WELCOME_MODAL: 'migratedUserWelcomeModal', BASE_LIST_ITEM_TEST_ID: 'base-list-item-', @@ -6697,6 +6719,8 @@ const CONST = { LHN_WORKSPACE_CHAT_TOOLTIP: 'workspaceChatLHNTooltip', GLOBAL_CREATE_TOOLTIP: 'globalCreateTooltip', SCAN_TEST_TOOLTIP: 'scanTestTooltip', + SCAN_TEST_TOOLTIP_MANAGER: 'scanTestTooltipManager', + SCAN_TEST_CONFIRMATION: 'scanTestConfirmation', }, SMART_BANNER_HEIGHT: 152, diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts index 6014ab22e953..de3a66e1f441 100644 --- a/src/NAVIGATORS.ts +++ b/src/NAVIGATORS.ts @@ -14,4 +14,5 @@ export default { REPORTS_SPLIT_NAVIGATOR: 'ReportsSplitNavigator', SETTINGS_SPLIT_NAVIGATOR: 'SettingsSplitNavigator', WORKSPACE_SPLIT_NAVIGATOR: 'WorkspaceSplitNavigator', + SEARCH_FULLSCREEN_NAVIGATOR: 'SearchFullscreenNavigator', } as const; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a66f00a87dd6..72cbcee20e34 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -479,6 +479,9 @@ const ONYXKEYS = { /** Information about travel provisioning process */ TRAVEL_PROVISIONING: 'travelProvisioning', + /** Stores the information about the state of side panel */ + NVP_SIDE_PANE: 'nvp_sidePaneExpanded', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -759,6 +762,9 @@ const ONYXKEYS = { WORKSPACE_PER_DIEM_FORM: 'workspacePerDiemForm', WORKSPACE_PER_DIEM_FORM_DRAFT: 'workspacePerDiemFormDraft', }, + DERIVED: { + CONCIERGE_CHAT_REPORT_ID: 'conciergeChatReportID', + }, } as const; type AllOnyxKeys = DeepValueOf; @@ -1078,15 +1084,22 @@ type OnyxValuesMapping = { [ONYXKEYS.CORPAY_ONBOARDING_FIELDS]: OnyxTypes.CorpayOnboardingFields; [ONYXKEYS.LAST_FULL_RECONNECT_TIME]: string; [ONYXKEYS.TRAVEL_PROVISIONING]: OnyxTypes.TravelProvisioning; + [ONYXKEYS.NVP_SIDE_PANE]: OnyxTypes.SidePane; +}; + +type OnyxDerivedValuesMapping = { + [ONYXKEYS.DERIVED.CONCIERGE_CHAT_REPORT_ID]: string | undefined; }; -type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; + +type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping & OnyxDerivedValuesMapping; type OnyxCollectionKey = keyof OnyxCollectionValuesMapping; type OnyxFormKey = keyof OnyxFormValuesMapping; type OnyxFormDraftKey = keyof OnyxFormDraftValuesMapping; type OnyxValueKey = keyof OnyxValuesMapping; +type OnyxDerivedKey = keyof OnyxDerivedValuesMapping; -type OnyxKey = OnyxValueKey | OnyxCollectionKey | OnyxFormKey | OnyxFormDraftKey; +type OnyxKey = OnyxValueKey | OnyxCollectionKey | OnyxFormKey | OnyxFormDraftKey | OnyxDerivedKey; type OnyxPagesKey = typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES; type MissingOnyxKeysError = `Error: Types don't match, OnyxKey type is missing: ${Exclude}`; @@ -1095,4 +1108,16 @@ type MissingOnyxKeysError = `Error: Types don't match, OnyxKey type is missing: type AssertOnyxKeys = AssertTypesEqual; export default ONYXKEYS; -export type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxFormDraftKey, OnyxFormKey, OnyxFormValuesMapping, OnyxKey, OnyxPagesKey, OnyxValueKey, OnyxValues}; +export type { + OnyxCollectionKey, + OnyxCollectionValuesMapping, + OnyxFormDraftKey, + OnyxFormKey, + OnyxFormValuesMapping, + OnyxKey, + OnyxPagesKey, + OnyxValueKey, + OnyxValues, + OnyxDerivedKey, + OnyxDerivedValuesMapping, +}; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 50a768955b25..41fd4b830f12 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -78,6 +78,13 @@ const ROUTES = { return getUrlWithBackToParam(baseRoute, backTo); }, }, + SEARCH_MONEY_REQUEST_REPORT: { + route: 'search/report/:reportID', + getRoute: ({reportID, backTo}: {reportID: string; backTo?: string}) => { + const baseRoute = `search/view/${reportID}` as const; + return getUrlWithBackToParam(baseRoute, backTo); + }, + }, TRANSACTION_HOLD_REASON_RHP: 'search/hold', // This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated @@ -135,7 +142,10 @@ const ROUTES = { SETTINGS_TIMEZONE_SELECT: 'settings/profile/timezone/select', SETTINGS_PRONOUNS: 'settings/profile/pronouns', SETTINGS_PREFERENCES: 'settings/preferences', - SETTINGS_SUBSCRIPTION: {route: 'settings/subscription', getRoute: (backTo?: string) => getUrlWithBackToParam('settings/subscription', backTo)}, + SETTINGS_SUBSCRIPTION: { + route: 'settings/subscription', + getRoute: (backTo?: string) => getUrlWithBackToParam('settings/subscription', backTo), + }, SETTINGS_SUBSCRIPTION_SIZE: { route: 'settings/subscription/subscription-size', getRoute: (canChangeSize: 0 | 1) => `settings/subscription/subscription-size?canChangeSize=${canChangeSize as number}` as const, @@ -148,7 +158,7 @@ const ROUTES = { SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode', SETTINGS_LANGUAGE: 'settings/preferences/language', SETTINGS_THEME: 'settings/preferences/theme', - SETTINGS_WORKSPACES: 'settings/workspaces', + SETTINGS_WORKSPACES: {route: 'settings/workspaces', getRoute: (backTo?: string) => getUrlWithBackToParam('settings/workspaces', backTo)}, SETTINGS_SECURITY: 'settings/security', SETTINGS_CLOSE: 'settings/security/closeAccount', SETTINGS_ADD_DELEGATE: 'settings/security/delegate', @@ -333,7 +343,10 @@ const ROUTES = { route: 'r/:reportID/edit/policyField/:policyID/:fieldID', getRoute: (reportID: string | undefined, policyID: string | undefined, fieldID: string, backTo?: string) => { if (!policyID || !reportID) { - Log.warn('Invalid policyID or reportID is used to build the EDIT_REPORT_FIELD_REQUEST route', {policyID, reportID}); + Log.warn('Invalid policyID or reportID is used to build the EDIT_REPORT_FIELD_REQUEST route', { + policyID, + reportID, + }); } return getUrlWithBackToParam(`r/${reportID}/edit/policyField/${policyID}/${encodeURIComponent(fieldID)}` as const, backTo); }, @@ -809,35 +822,35 @@ const ROUTES = { route: 'settings/workspaces/:policyID/invite-message', getRoute: (policyID: string, backTo?: string) => `${getUrlWithBackToParam(`settings/workspaces/${policyID}/invite-message`, backTo)}` as const, }, - WORKSPACE_PROFILE: { - route: 'settings/workspaces/:policyID/profile', + WORKSPACE_OVERVIEW: { + route: 'settings/workspaces/:policyID/overview', getRoute: (policyID: string | undefined, backTo?: string) => { if (!policyID) { - Log.warn('Invalid policyID is used to build the WORKSPACE_PROFILE route'); + Log.warn('Invalid policyID is used to build the WORKSPACE_OVERVIEW route'); } - return getUrlWithBackToParam(`settings/workspaces/${policyID}/profile` as const, backTo); + return getUrlWithBackToParam(`settings/workspaces/${policyID}/overview` as const, backTo); }, }, - WORKSPACE_PROFILE_ADDRESS: { - route: 'settings/workspaces/:policyID/profile/address', + WORKSPACE_OVERVIEW_ADDRESS: { + route: 'settings/workspaces/:policyID/overview/address', getRoute: (policyID: string | undefined, backTo?: string) => { if (!policyID) { - Log.warn('Invalid policyID is used to build the WORKSPACE_PROFILE_ADDRESS route'); + Log.warn('Invalid policyID is used to build the WORKSPACE_OVERVIEW_ADDRESS route'); } - return getUrlWithBackToParam(`settings/workspaces/${policyID}/profile/address` as const, backTo); + return getUrlWithBackToParam(`settings/workspaces/${policyID}/overview/address` as const, backTo); }, }, - WORKSPACE_PROFILE_PLAN: { - route: 'settings/workspaces/:policyID/profile/plan', - getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/profile/plan` as const, backTo), + WORKSPACE_OVERVIEW_PLAN: { + route: 'settings/workspaces/:policyID/overview/plan', + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/overview/plan` as const, backTo), }, WORKSPACE_ACCOUNTING: { route: 'settings/workspaces/:policyID/accounting', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting` as const, }, - WORKSPACE_PROFILE_CURRENCY: { - route: 'settings/workspaces/:policyID/profile/currency', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/currency` as const, + WORKSPACE_OVERVIEW_CURRENCY: { + route: 'settings/workspaces/:policyID/overview/currency', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/overview/currency` as const, }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export', @@ -850,15 +863,16 @@ const ROUTES = { }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/company-card-expense-account', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account` as const, + getRoute: (policyID: string, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account` as const, backTo), }, 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 | undefined) => { + getRoute: (policyID: string | undefined, backTo?: string) => { 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; + return getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/account-select` as const, backTo); }, }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT: { @@ -872,36 +886,38 @@ const ROUTES = { }, 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 | undefined) => { + getRoute: (policyID: string | undefined, backTo?: string) => { 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; + return getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/card-select` as const, backTo); }, }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/invoice-account-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/invoice-account-select` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/invoice-account-select` as const, backTo), }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_PREFERRED_EXPORTER: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/preferred-exporter', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/preferred-exporter` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/preferred-exporter` as const, backTo), }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/out-of-pocket-expense', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/out-of-pocket-expense` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/out-of-pocket-expense` as const, backTo), }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/out-of-pocket-expense/account-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/out-of-pocket-expense/account-select` as const, + getRoute: (policyID: string, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/out-of-pocket-expense/account-select` as const, backTo), }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/out-of-pocket-expense/entity-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/out-of-pocket-expense/entity-select` as const, + getRoute: (policyID: string, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/out-of-pocket-expense/entity-select` as const, backTo), }, POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/date-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/date-select` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/quickbooks-online/export/date-select` as const, backTo), }, POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/export/company-card-expense-account/account-select', @@ -987,22 +1003,22 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/import/items', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-desktop/import/items` as const, }, - WORKSPACE_PROFILE_NAME: { - route: 'settings/workspaces/:policyID/profile/name', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/name` as const, + WORKSPACE_OVERVIEW_NAME: { + route: 'settings/workspaces/:policyID/overview/name', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/overview/name` as const, }, - WORKSPACE_PROFILE_DESCRIPTION: { - route: 'settings/workspaces/:policyID/profile/description', + WORKSPACE_OVERVIEW_DESCRIPTION: { + route: 'settings/workspaces/:policyID/overview/description', getRoute: (policyID: string | undefined) => { if (!policyID) { - Log.warn('Invalid policyID is used to build the WORKSPACE_PROFILE_DESCRIPTION route'); + Log.warn('Invalid policyID is used to build the WORKSPACE_OVERVIEW_DESCRIPTION route'); } - return `settings/workspaces/${policyID}/profile/description` as const; + return `settings/workspaces/${policyID}/overview/description` as const; }, }, - WORKSPACE_PROFILE_SHARE: { - route: 'settings/workspaces/:policyID/profile/share', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/share` as const, + WORKSPACE_OVERVIEW_SHARE: { + route: 'settings/workspaces/:policyID/overview/share', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/overview/share` as const, }, WORKSPACE_AVATAR: { route: 'settings/workspaces/:policyID/avatar', @@ -1110,7 +1126,12 @@ const ROUTES = { }, WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/advanced', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/advanced` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ADVANCED route'); + } + return `settings/workspaces/${policyID}/accounting/quickbooks-online/advanced` as const; + }, }, WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ACCOUNT_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/account-selector', @@ -1127,8 +1148,12 @@ const ROUTES = { }, WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS: { route: 'settings/workspaces/:policyID/accounting/:connection/card-reconciliation/account', - getRoute: (policyID: string, connection?: ValueOf) => - `settings/workspaces/${policyID}/accounting/${connection as string}/card-reconciliation/account` as const, + getRoute: (policyID: string | undefined, connection?: ValueOf) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS route'); + } + return `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', @@ -1150,11 +1175,15 @@ const ROUTES = { WORKSPACE_UPGRADE: { route: 'settings/workspaces/:policyID?/upgrade/:featureName?', getRoute: (policyID?: string, featureName?: string, backTo?: string) => - policyID ? getUrlWithBackToParam(`settings/workspaces/${policyID}/upgrade/${encodeURIComponent(featureName ?? '')}` as const, backTo) : (`settings/workspaces/upgrade` as const), + getUrlWithBackToParam( + policyID ? (`settings/workspaces/${policyID}/upgrade/${encodeURIComponent(featureName ?? '')}` as const) : (`settings/workspaces/upgrade` as const), + backTo, + ), }, WORKSPACE_DOWNGRADE: { route: 'settings/workspaces/:policyID?/downgrade/', - getRoute: (policyID?: string) => (policyID ? (`settings/workspaces/${policyID}/downgrade/` as const) : `settings/workspaces/downgrade`), + getRoute: (policyID?: string, backTo?: string) => + getUrlWithBackToParam(policyID ? (`settings/workspaces/${policyID}/downgrade/` as const) : (`settings/workspaces/downgrade` as const), backTo), }, WORKSPACE_CATEGORIES_SETTINGS: { route: 'settings/workspaces/:policyID/categories/settings', @@ -1473,7 +1502,7 @@ const ROUTES = { }, WORKSPACE_EXPENSIFY_CARD_SETTINGS_ACCOUNT: { route: 'settings/workspaces/:policyID/expensify-card/settings/account', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/settings/account` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/expensify-card/settings/account`, backTo), }, WORKSPACE_EXPENSIFY_CARD_SETTINGS_FREQUENCY: { route: 'settings/workspaces/:policyID/expensify-card/settings/frequency', @@ -1755,23 +1784,28 @@ const ROUTES = { }, POLICY_ACCOUNTING_XERO_PREFERRED_EXPORTER_SELECT: { route: 'settings/workspaces/:policyID/connections/xero/export/preferred-exporter/select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/xero/export/preferred-exporter/select` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/xero/export/preferred-exporter/select` as const, backTo), }, POLICY_ACCOUNTING_XERO_EXPORT_PURCHASE_BILL_DATE_SELECT: { route: 'settings/workspaces/:policyID/accounting/xero/export/purchase-bill-date-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/export/purchase-bill-date-select` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/xero/export/purchase-bill-date-select` as const, backTo), }, POLICY_ACCOUNTING_XERO_EXPORT_BANK_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/accounting/xero/export/bank-account-select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/export/bank-account-select` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/xero/export/bank-account-select` as const, backTo), }, POLICY_ACCOUNTING_XERO_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/xero/advanced', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/advanced` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_XERO_ADVANCED route'); + } + return `settings/workspaces/${policyID}/accounting/xero/advanced` as const; + }, }, POLICY_ACCOUNTING_XERO_BILL_STATUS_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/xero/export/purchase-bill-status-selector', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/export/purchase-bill-status-selector` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/xero/export/purchase-bill-status-selector` as const, backTo), }, POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/xero/advanced/invoice-account-selector', @@ -1890,52 +1924,52 @@ const ROUTES = { }, POLICY_ACCOUNTING_NETSUITE_PREFERRED_EXPORTER_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/export/preferred-exporter/select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/preferred-exporter/select` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/preferred-exporter/select` as const, backTo), }, POLICY_ACCOUNTING_NETSUITE_DATE_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/export/date/select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/date/select` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/date/select` as const, backTo), }, POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES: { route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType', - getRoute: (policyID: string | undefined, expenseType: ValueOf) => { + getRoute: (policyID: string | undefined, expenseType: ValueOf, backTo?: string) => { if (!policyID) { Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES route'); } - return `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}` as const; + return getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}` as const, backTo); }, }, POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES_DESTINATION_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType/destination/select', - getRoute: (policyID: string, expenseType: ValueOf) => - `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}/destination/select` as const, + getRoute: (policyID: string, expenseType: ValueOf, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}/destination/select` as const, backTo), }, POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES_VENDOR_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType/vendor/select', - getRoute: (policyID: string, expenseType: ValueOf) => - `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}/vendor/select` as const, + getRoute: (policyID: string, expenseType: ValueOf, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}/vendor/select` as const, backTo), }, POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES_PAYABLE_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType/payable-account/select', - getRoute: (policyID: string, expenseType: ValueOf) => - `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}/payable-account/select` as const, + getRoute: (policyID: string, expenseType: ValueOf, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}/payable-account/select` as const, backTo), }, POLICY_ACCOUNTING_NETSUITE_EXPORT_EXPENSES_JOURNAL_POSTING_PREFERENCE_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/export/expenses/:expenseType/journal-posting-preference/select', - getRoute: (policyID: string, expenseType: ValueOf) => - `settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}/journal-posting-preference/select` as const, + getRoute: (policyID: string, expenseType: ValueOf, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/expenses/${expenseType as string}/journal-posting-preference/select` as const, backTo), }, POLICY_ACCOUNTING_NETSUITE_RECEIVABLE_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/export/receivable-account/select', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/receivable-account/select` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/receivable-account/select` as const, backTo), }, POLICY_ACCOUNTING_NETSUITE_INVOICE_ITEM_PREFERENCE_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/export/invoice-item-preference/select', - getRoute: (policyID: string | undefined) => { + getRoute: (policyID: string | undefined, backTo?: string) => { if (!policyID) { Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_NETSUITE_INVOICE_ITEM_PREFERENCE_SELECT route'); } - return `settings/workspaces/${policyID}/connections/netsuite/export/invoice-item-preference/select` as const; + return getUrlWithBackToParam(`settings/workspaces/${policyID}/connections/netsuite/export/invoice-item-preference/select` as const, backTo); }, }, POLICY_ACCOUNTING_NETSUITE_INVOICE_ITEM_SELECT: { @@ -2096,39 +2130,46 @@ const ROUTES = { }, POLICY_ACCOUNTING_SAGE_INTACCT_PREFERRED_EXPORTER: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/preferred-exporter', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/preferred-exporter` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/sage-intacct/export/preferred-exporter` as const, backTo), }, POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT_DATE: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/date', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/date` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/sage-intacct/export/date` as const, backTo), }, POLICY_ACCOUNTING_SAGE_INTACCT_REIMBURSABLE_EXPENSES: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/reimbursable', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/reimbursable` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/sage-intacct/export/reimbursable` as const, backTo), }, POLICY_ACCOUNTING_SAGE_INTACCT_NON_REIMBURSABLE_EXPENSES: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/nonreimbursable', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/nonreimbursable` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/sage-intacct/export/nonreimbursable` as const, backTo), }, POLICY_ACCOUNTING_SAGE_INTACCT_REIMBURSABLE_DESTINATION: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/reimbursable/destination', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/reimbursable/destination` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/sage-intacct/export/reimbursable/destination` as const, backTo), }, POLICY_ACCOUNTING_SAGE_INTACCT_NON_REIMBURSABLE_DESTINATION: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/nonreimbursable/destination', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/nonreimbursable/destination` as const, + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/sage-intacct/export/nonreimbursable/destination` as const, backTo), }, POLICY_ACCOUNTING_SAGE_INTACCT_DEFAULT_VENDOR: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/:reimbursable/default-vendor', - getRoute: (policyID: string, reimbursable: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/${reimbursable}/default-vendor` as const, + getRoute: (policyID: string, reimbursable: string, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/sage-intacct/export/${reimbursable}/default-vendor` as const, backTo), }, POLICY_ACCOUNTING_SAGE_INTACCT_NON_REIMBURSABLE_CREDIT_CARD_ACCOUNT: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/nonreimbursable/credit-card-account', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/nonreimbursable/credit-card-account` as const, + getRoute: (policyID: string, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/accounting/sage-intacct/export/nonreimbursable/credit-card-account` as const, backTo), }, POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/advanced` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED route'); + } + return `settings/workspaces/${policyID}/accounting/sage-intacct/advanced` as const; + }, }, POLICY_ACCOUNTING_SAGE_INTACCT_PAYMENT_ACCOUNT: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced/payment-account', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index d950fb1cd5db..7e09c0277fe4 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -37,6 +37,7 @@ const SCREENS = { }, SEARCH: { ROOT: 'Search_Root', + MONEY_REQUEST_REPORT: 'Search_Money_Request_Report', REPORT_RHP: 'Search_Report_RHP', ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP', ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP', @@ -468,7 +469,7 @@ const SCREENS = { MULTI_CONNECTION_SELECTOR: 'Policy_Accounting_Multi_Connection_Selector', }, INITIAL: 'Workspace_Initial', - PROFILE: 'Workspace_Profile', + PROFILE: 'Workspace_Overview', COMPANY_CARDS: 'Workspace_CompanyCards', COMPANY_CARDS_ASSIGN_CARD: 'Workspace_CompanyCards_AssignCard', COMPANY_CARDS_SELECT_FEED: 'Workspace_CompanyCards_Select_Feed', @@ -531,9 +532,9 @@ const SCREENS = { TAG_APPROVER: 'Tag_Approver', TAG_LIST_VIEW: 'Tag_List_View', TAG_GL_CODE: 'Tag_GL_Code', - CURRENCY: 'Workspace_Profile_Currency', - ADDRESS: 'Workspace_Profile_Address', - PLAN: 'Workspace_Profile_Plan_Type', + CURRENCY: 'Workspace_Overview_Currency', + ADDRESS: 'Workspace_Overview_Address', + PLAN: 'Workspace_Overview_Plan_Type', WORKFLOWS: 'Workspace_Workflows', WORKFLOWS_PAYER: 'Workspace_Workflows_Payer', WORKFLOWS_APPROVALS_NEW: 'Workspace_Approvals_New', @@ -542,9 +543,9 @@ const SCREENS = { WORKFLOWS_APPROVALS_APPROVER: 'Workspace_Workflows_Approvals_Approver', WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency', WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET: 'Workspace_Workflows_Auto_Reporting_Monthly_Offset', - DESCRIPTION: 'Workspace_Profile_Description', - SHARE: 'Workspace_Profile_Share', - NAME: 'Workspace_Profile_Name', + DESCRIPTION: 'Workspace_Overview_Description', + SHARE: 'Workspace_Overview_Share', + NAME: 'Workspace_Overview_Name', CATEGORY_CREATE: 'Category_Create', CATEGORY_EDIT: 'Category_Edit', CATEGORY_PAYROLL_CODE: 'Category_Payroll_Code', diff --git a/src/components/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx b/src/components/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx index f967272ac63c..8ccee64f1118 100644 --- a/src/components/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx @@ -10,7 +10,7 @@ import RadioListItem from '@components/SelectionList/RadioListItem'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ValidationUtils from '@libs/ValidationUtils'; +import {getFieldRequiredErrors, isValidSecurityCode} from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ChangeBillingCurrencyForm'; @@ -33,9 +33,9 @@ function PaymentCardChangeCurrencyForm({changeBillingCurrency, isSecurityCodeReq const [currency, setCurrency] = useState>(initialCurrency ?? CONST.PAYMENT_CARD_CURRENCY.USD); const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS); + const errors = getFieldRequiredErrors(values, REQUIRED_FIELDS); - if (values.securityCode && !ValidationUtils.isValidSecurityCode(values.securityCode)) { + if (values.securityCode && !isValidSecurityCode(values.securityCode)) { errors.securityCode = translate('addPaymentCardPage.error.securityCode'); } @@ -102,7 +102,6 @@ function PaymentCardChangeCurrencyForm({changeBillingCurrency, isSecurityCodeReq label={translate('addDebitCardPage.cvv')} aria-label={translate('addDebitCardPage.cvv')} role={CONST.ROLE.PRESENTATION} - maxLength={4} containerStyles={[styles.mt5]} inputMode={CONST.INPUT_MODE.NUMERIC} /> diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index 5590561289eb..6aa7ac7d4eeb 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -171,6 +171,11 @@ function PaymentCardForm({ // See issue: https://github.com/Expensify/App/issues/55493#issuecomment-2616349754 if (values.addressZipCode && !isValidPaymentZipCode(values.addressZipCode)) { errors.addressZipCode = translate('addPaymentCardPage.error.addressZipCode'); + } else if (values.addressZipCode.length > CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE) { + errors.addressZipCode = translate('common.error.characterLimitExceedCounter', { + length: values.addressZipCode.length, + limit: CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE, + }); } if (!values.acceptTerms) { @@ -290,7 +295,6 @@ function PaymentCardForm({ label={translate('common.zipPostCode')} aria-label={translate('common.zipPostCode')} role={CONST.ROLE.PRESENTATION} - maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} containerStyles={[styles.mt5]} /> {!!showStateSelector && ( diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index ffeea4ab3a02..908cfcbb9164 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -2,7 +2,6 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import {addErrorMessage} from '@libs/ErrorUtils'; import {isRequiredFulfilled} from '@libs/ValidationUtils'; import type {Country} from '@src/CONST'; import CONST from '@src/CONST'; @@ -109,6 +108,27 @@ function AddressForm({ errors[fieldKey] = translate('common.error.fieldRequired'); }); + if (values.addressLine2.length > CONST.FORM_CHARACTER_LIMIT) { + errors.addressLine2 = translate('common.error.characterLimitExceedCounter', { + length: values.addressLine2.length, + limit: CONST.FORM_CHARACTER_LIMIT, + }); + } + + if (values.city.length > CONST.FORM_CHARACTER_LIMIT) { + errors.city = translate('common.error.characterLimitExceedCounter', { + length: values.city.length, + limit: CONST.FORM_CHARACTER_LIMIT, + }); + } + + if (values.country !== CONST.COUNTRY.US && values.state.length > CONST.STATE_CHARACTER_LIMIT) { + errors.state = translate('common.error.characterLimitExceedCounter', { + length: values.state.length, + limit: CONST.STATE_CHARACTER_LIMIT, + }); + } + // If no country is selected, default value is an empty string and there's no related regex data so we default to an empty object const countryRegexDetails = (values.country ? CONST.COUNTRY_ZIP_REGEX_DATA?.[values.country] : {}) as CountryZipRegex; @@ -116,8 +136,6 @@ function AddressForm({ const countrySpecificZipRegex = countryRegexDetails?.regex; const countryZipFormat = countryRegexDetails?.samples ?? ''; - addErrorMessage(errors, 'firstName', translate('bankAccount.error.firstName')); - if (countrySpecificZipRegex) { if (!countrySpecificZipRegex.test(values.zipPostCode?.trim().toUpperCase())) { if (isRequiredFulfilled(values.zipPostCode?.trim())) { @@ -173,7 +191,6 @@ function AddressForm({ aria-label={translate('common.addressLine', {lineNumber: 2})} role={CONST.ROLE.PRESENTATION} defaultValue={street2} - maxLength={CONST.FORM_CHARACTER_LIMIT} spellCheck={false} shouldSaveDraft={shouldSaveDraft} /> @@ -206,7 +223,6 @@ function AddressForm({ aria-label={translate('common.stateOrProvince')} role={CONST.ROLE.PRESENTATION} value={state} - maxLength={CONST.STATE_CHARACTER_LIMIT} spellCheck={false} onValueChange={onAddressChanged} shouldSaveDraft={shouldSaveDraft} @@ -220,7 +236,6 @@ function AddressForm({ aria-label={translate('common.city')} role={CONST.ROLE.PRESENTATION} defaultValue={city} - maxLength={CONST.FORM_CHARACTER_LIMIT} spellCheck={false} onValueChange={onAddressChanged} shouldSaveDraft={shouldSaveDraft} @@ -234,7 +249,6 @@ function AddressForm({ role={CONST.ROLE.PRESENTATION} autoCapitalize="characters" defaultValue={zip} - maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} hint={zipFormat} onValueChange={onAddressChanged} shouldSaveDraft={shouldSaveDraft} diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index e8da2b56cab9..4373dae8ac73 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -577,7 +577,7 @@ function AttachmentModal({ onToggleKeyboard={setIsConfirmButtonDisabled} onPDFLoadError={() => { isPDFLoadError.current = true; - setIsModalOpen(false); + closeModal(); }} isWorkspaceAvatar={isWorkspaceAvatar} maybeIcon={maybeIcon} diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 2fbe69a120a0..cafcf827cff6 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -5,9 +5,9 @@ import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getDefaultWorkspaceAvatar, getDefaultWorkspaceAvatarTestID} from '@libs/ReportUtils'; import type {AvatarSource} from '@libs/UserUtils'; -import * as UserUtils from '@libs/UserUtils'; +import {getAvatar} from '@libs/UserUtils'; import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; import type {AvatarType} from '@src/types/onyx/OnyxCommon'; @@ -82,10 +82,10 @@ function Avatar({ const isWorkspace = type === CONST.ICON_TYPE_WORKSPACE; const userAccountID = isWorkspace ? undefined : (avatarID as number); - const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, userAccountID); + const source = isWorkspace ? originalSource : getAvatar(originalSource, userAccountID); const useFallBackAvatar = imageError || !source || source === Expensicons.FallbackAvatar; - const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; - const fallbackAvatarTestID = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; + const fallbackAvatar = isWorkspace ? getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; + const fallbackAvatarTestID = isWorkspace ? getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; const avatarSource = useFallBackAvatar ? fallbackAvatar : source; // We pass the color styles down to the SVG for the workspace and fallback avatar. @@ -110,6 +110,7 @@ function Avatar({ source={{uri: avatarSource}} style={imageStyle} onError={() => setImageError(true)} + cachePolicy="memory-disk" /> ) : ( diff --git a/src/components/BookTravelButton.tsx b/src/components/BookTravelButton.tsx index 846cf23e4c8a..01d512e9b2be 100644 --- a/src/components/BookTravelButton.tsx +++ b/src/components/BookTravelButton.tsx @@ -3,14 +3,16 @@ import React, {useCallback, useContext, useState} from 'react'; import {NativeModules} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import usePolicy from '@hooks/usePolicy'; +import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {openTravelDotLink} from '@libs/actions/Link'; import {cleanupTravelProvisioningSession} from '@libs/actions/Travel'; -import DateUtils from '@libs/DateUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getAdminsPrivateEmailDomains, isPaidGroupPolicy} from '@libs/PolicyUtils'; +import colors from '@styles/theme/colors'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -19,6 +21,7 @@ import Button from './Button'; import ConfirmModal from './ConfirmModal'; import CustomStatusBarAndBackgroundContext from './CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext'; import DotIndicatorMessage from './DotIndicatorMessage'; +import {RocketDude} from './Icon/Illustrations'; type BookTravelButtonProps = { text: string; @@ -30,38 +33,37 @@ const navigateToAcceptTerms = (domain: string) => { Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain)); }; -// Spotnana has scheduled maintenance from February 23 at 7 AM EST (12 PM UTC) to February 24 at 12 PM EST (5 PM UTC). -const SPOTNANA_BLACKOUT_PERIOD_START = '2025-02-23T11:59:00Z'; -const SPOTNANA_BLACKOUT_PERIOD_END = '2025-02-24T17:01:00Z'; - function BookTravelButton({text}: BookTravelButtonProps) { const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); 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 [primaryLogin] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.primaryLogin}); + const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); + const primaryContactMethod = primaryLogin ?? sessionEmail ?? ''; const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext); - const [isMaintenanceModalVisible, setMaintenanceModalVisibility] = useState(false); + const {isBlockedFromSpotnanaTravel} = usePermissions(); + const [isPreventionModalVisible, setPreventionModalVisibility] = useState(false); // 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 hideMaintenanceModal = () => setMaintenanceModalVisibility(false); + const hidePreventionModal = () => setPreventionModalVisibility(false); const bookATrip = useCallback(() => { setErrorMessage(''); - if (DateUtils.isCurrentTimeWithinRange(SPOTNANA_BLACKOUT_PERIOD_START, SPOTNANA_BLACKOUT_PERIOD_END)) { - setMaintenanceModalVisibility(true); + if (isBlockedFromSpotnanaTravel) { + setPreventionModalVisibility(true); return; } // 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)) { + if (!primaryContactMethod || Str.isSMSLogin(primaryContactMethod)) { setErrorMessage(translate('travel.phoneError')); return; } @@ -71,12 +73,6 @@ function BookTravelButton({text}: BookTravelButtonProps) { 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) @@ -89,7 +85,7 @@ function BookTravelButton({text}: BookTravelButtonProps) { // 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); + NativeModules.HybridAppModule.closeReactNativeApp({shouldSignOut: false, shouldSetNVP: false}); setRootStatusBarEnabled(false); }) ?.catch(() => { @@ -105,13 +101,16 @@ function BookTravelButton({text}: BookTravelButtonProps) { const adminDomains = getAdminsPrivateEmailDomains(policy); if (adminDomains.length === 0) { Navigation.navigate(ROUTES.TRAVEL_PUBLIC_DOMAIN_ERROR); + } else if (isEmptyObject(policy?.address)) { + // Spotnana requires an address anytime an entity is created for a policy + Navigation.navigate(ROUTES.WORKSPACE_OVERVIEW_ADDRESS.getRoute(policy?.id, Navigation.getActiveRoute())); } 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]); + }, [policy, wasNewDotLaunchedJustForTravel, travelSettings, translate, primaryContactMethod, setRootStatusBarEnabled, isBlockedFromSpotnanaTravel]); return ( <> @@ -131,11 +130,16 @@ function BookTravelButton({text}: BookTravelButtonProps) { large /> diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 0bc3dba642cf..bffac55007fa 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -3,8 +3,8 @@ import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useTransactionViolations from '@hooks/useTransactionViolations'; -import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminPolicyUtils} from '@libs/PolicyUtils'; -import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils'; +import {isPolicyAdmin as isPolicyAdminPolicyUtils} from '@libs/PolicyUtils'; +import {isCurrentUserSubmitter, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -52,7 +52,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn ); } - if (isReportApproved({report}) || isReportManuallyReimbursed(report) || (isProcessingReport(report) && !isInstantSubmitEnabled(policy))) { + if (isReportApproved({report}) || isReportManuallyReimbursed(report)) { return translate('violations.memberBrokenConnectionError'); } diff --git a/src/components/Composer/implementation/index.tsx b/src/components/Composer/implementation/index.tsx index 08d07b549a46..7732ef8680d9 100755 --- a/src/components/Composer/implementation/index.tsx +++ b/src/components/Composer/implementation/index.tsx @@ -47,6 +47,7 @@ function Composer( end: 0, }, isComposerFullSize = false, + onContentSizeChange, shouldContainScroll = true, isGroupPolicyReport = false, ...props @@ -362,6 +363,7 @@ function Composer( onSelectionChange={addCursorPositionToSelectionChange} onContentSizeChange={(e) => { setPrevHeight(e.nativeEvent.contentSize.height); + onContentSizeChange?.(e); }} disabled={isDisabled} onKeyPress={handleKeyPress} diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index 3bfb5a146d05..22e7ec76c5b5 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -4,10 +4,8 @@ import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import colors from '@styles/theme/colors'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -96,8 +94,14 @@ type ConfirmContentProps = { /** Image to display with content */ image?: IconAsset; + /** Styles for the image */ + imageStyles?: StyleProp; + /** Whether the modal is visibile */ isVisible: boolean; + + /** Whether the confirm button is loading */ + isConfirmLoading?: boolean; }; function ConfirmContent({ @@ -124,22 +128,23 @@ function ConfirmContent({ shouldCenterIcon = false, shouldShowDismissIcon = false, image, + imageStyles, titleContainerStyles, shouldReverseStackedButtons = false, isVisible, + isConfirmLoading, }: ConfirmContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const theme = useTheme(); const {isOffline} = useNetwork(); - const StyleUtils = useStyleUtils(); const isCentered = shouldCenterContent; return ( <> {!!image && ( - + {shouldShowCancelButton && !shouldReverseStackedButtons && (