Skip to content

Commit eac16f9

Browse files
committed
Merge branch 'main' of github.com:Expensify/App into jsenyitko-release-4-reimbursable-amt
2 parents d98cea5 + 2135cc0 commit eac16f9

1,923 files changed

Lines changed: 3295 additions & 4945 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/androidBump.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ jobs:
2121
with:
2222
bundler-cache: true
2323

24-
- name: Install 1Password CLI
25-
# v1
26-
uses: 1password/install-cli-action@143a85f84a90555d121cde2ff5872e393a47ab9f
24+
- name: Setup 1Password CLI and certificates
25+
uses: Expensify/GitHub-Actions/setup-certificate-1p@main
26+
with:
27+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
28+
SHOULD_LOAD_SSL_CERTIFICATES: 'false'
2729

2830
- name: Load files from 1Password
2931
working-directory: android/app

.github/workflows/buildAndroid.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ jobs:
9696
with:
9797
bundler-cache: true
9898

99-
- name: Install 1Password CLI
100-
# v1
101-
uses: 1password/install-cli-action@143a85f84a90555d121cde2ff5872e393a47ab9f
99+
- name: Setup 1Password CLI and certificates
100+
uses: Expensify/GitHub-Actions/setup-certificate-1p@main
101+
with:
102+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
103+
SHOULD_LOAD_SSL_CERTIFICATES: 'false'
102104

103105
- name: Load files from 1Password
104106
working-directory: android/app

.github/workflows/deploy.yml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,11 @@ jobs:
129129
- name: Install New Expensify Gems
130130
run: bundle install
131131

132-
- name: Install 1Password CLI
133-
# v1
134-
uses: 1password/install-cli-action@143a85f84a90555d121cde2ff5872e393a47ab9f
132+
- name: Setup 1Password CLI and certificates
133+
uses: Expensify/GitHub-Actions/setup-certificate-1p@main
134+
with:
135+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
136+
SHOULD_LOAD_SSL_CERTIFICATES: 'false'
135137

136138
- name: Load files from 1Password
137139
env:
@@ -408,9 +410,11 @@ jobs:
408410
max_attempts: 5
409411
command: npm run pod-install
410412

411-
- name: Install 1Password CLI
412-
# v1
413-
uses: 1password/install-cli-action@143a85f84a90555d121cde2ff5872e393a47ab9f
413+
- name: Setup 1Password CLI and certificates
414+
uses: Expensify/GitHub-Actions/setup-certificate-1p@main
415+
with:
416+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
417+
SHOULD_LOAD_SSL_CERTIFICATES: 'false'
414418

415419
- name: Load files from 1Password
416420
env:

.github/workflows/lint.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ jobs:
2626
with:
2727
IS_DESKTOP_BUILD: true
2828

29+
- name: Remove E/App version from package-lock.json (For cache invalidation)
30+
shell: bash
31+
# Remove App version from package-lock.json to avoid cache invalidation when the App version changes
32+
run: jq 'del(.version, .packages[""].version)' package-lock.json > normalized-package-lock.json
33+
34+
- name: Cache ESLint
35+
# v4
36+
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57
37+
with:
38+
path: node_modules/.cache/eslint
39+
# Only invalidate the cache if the eslint ruleset change (from eslint.config.mjs or the normalized-package-lock.json), we use --cache-strategy content to handle cache invalidation
40+
# See https://eslint.org/docs/latest/use/command-line-interface#--cache-strategy for more information
41+
key: ${{ runner.os }}-eslint-${{ hashFiles('eslint.config.*', 'normalized-package-lock.json') }}
42+
restore-keys: |
43+
${{ runner.os }}-eslint-
44+
2945
- name: Lint JavaScript and Typescript with ESLint
3046
run: npm run lint
3147
env:

.github/workflows/testBuild.yml

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ on:
44
workflow_dispatch:
55
inputs:
66
# If not specified, only build iOS and Android apps from the main branch of Expensify/App
7-
APP_PULL_REQUEST_NUMBER:
8-
description: Expensify/App PR number for correct placement of apps. Default to main.
7+
APP_PULL_REQUEST_URL:
8+
description: The Expensify/App pull request URL (e.g., https://github.com/Expensify/App/pull/12345). Defaults to main.
99
required: false
1010
default: ''
11-
# 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 are specified. If nothing is specified defaults to Mobile-Expensify's main
12-
MOBILE_EXPENSIFY_PULL_REQUEST_NUMBER:
13-
description: Expensify/Mobile-Expensify PR number. Defaults to main. Overrides MOBILE-EXPENSIFY set in App's PR description.
11+
# Pull Request URL from Mobile-Expensify repo for correct placement of OD app. It will take precedence over MOBILE-EXPENSIFY from App's PR description if both are specified. If nothing is specified defaults to Mobile-Expensify's main
12+
MOBILE_EXPENSIFY_PULL_REQUEST_URL:
13+
description: The Expensify/Mobile-Expensify pull request URL. Defaults to main. Overrides MOBILE-EXPENSIFY set in App's PR description.
1414
required: false
1515
default: ''
1616
REVIEWED_CODE:
@@ -35,15 +35,13 @@ on:
3535
type: boolean
3636
default: true
3737

38-
env:
39-
# This variable is needed for fastlane to construct correct path
40-
PULL_REQUEST_NUMBER: ${{ inputs.APP_PULL_REQUEST_NUMBER || github.event.number }}
41-
4238
jobs:
4339
prep:
4440
runs-on: ubuntu-latest
4541
outputs:
4642
APP_REF: ${{ steps.getHeadRef.outputs.REF || 'main' }}
43+
APP_PR_NUMBER: ${{ steps.extractAppPRNumber.outputs.PR_NUMBER }}
44+
MOBILE_PR_NUMBER: ${{ steps.extractMobilePRNumber.outputs.PR_NUMBER }}
4745
steps:
4846
- name: Checkout
4947
# v4
@@ -61,15 +59,37 @@ jobs:
6159
echo "::error::🕵️‍♀️ Please carefully review the pull request before running a test build to ensure it does not contain any malicious code"
6260
exit 1
6361
62+
- name: Extract App PR number from URL
63+
id: extractAppPRNumber
64+
if: ${{ inputs.APP_PULL_REQUEST_URL != '' }}
65+
run: |
66+
PR_NUMBER=$(echo '${{ inputs.APP_PULL_REQUEST_URL }}' | sed -E 's|.*/pull/([0-9]+).*|\1|')
67+
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
68+
echo "::error::❌ Could not extract PR number from URL. Please provide a valid GitHub PR URL (e.g., https://github.com/Expensify/App/pull/12345)"
69+
exit 1
70+
fi
71+
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_OUTPUT"
72+
73+
- name: Extract Mobile-Expensify PR number from URL
74+
id: extractMobilePRNumber
75+
if: ${{ inputs.MOBILE_EXPENSIFY_PULL_REQUEST_URL != '' }}
76+
run: |
77+
PR_NUMBER=$(echo '${{ inputs.MOBILE_EXPENSIFY_PULL_REQUEST_URL }}' | sed -E 's|.*/pull/([0-9]+).*|\1|')
78+
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
79+
echo "::error::❌ Could not extract PR number from URL. Please provide a valid GitHub PR URL (e.g., https://github.com/Expensify/Mobile-Expensify/pull/12345)"
80+
exit 1
81+
fi
82+
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_OUTPUT"
83+
6484
- name: Check if App pull request number is correct
6585
if: ${{ github.event_name == 'workflow_dispatch' }}
6686
id: getHeadRef
6787
run: |
6888
set -e
69-
if [ -z "${{ inputs.APP_PULL_REQUEST_NUMBER }}" ]; then
89+
if [ -z "${{ steps.extractAppPRNumber.outputs.PR_NUMBER }}" ]; then
7090
echo "REF=" >> "$GITHUB_OUTPUT"
7191
else
72-
echo "REF=$(gh pr view ${{ inputs.APP_PULL_REQUEST_NUMBER }} --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT"
92+
echo "REF=$(gh pr view ${{ steps.extractAppPRNumber.outputs.PR_NUMBER }} --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT"
7393
fi
7494
env:
7595
GITHUB_TOKEN: ${{ github.token }}
@@ -88,14 +108,14 @@ jobs:
88108
github-token: ${{ github.token }}
89109
result-encoding: string
90110
script: |
91-
if ('${{ inputs.MOBILE_EXPENSIFY_PULL_REQUEST_NUMBER}}') return '${{ inputs.MOBILE_EXPENSIFY_PULL_REQUEST_NUMBER}}';
111+
if ('${{ needs.prep.outputs.MOBILE_PR_NUMBER }}') return '${{ needs.prep.outputs.MOBILE_PR_NUMBER }}';
92112
93-
if (!'${{ inputs.APP_PULL_REQUEST_NUMBER }}') return '';
113+
if (!'${{ needs.prep.outputs.APP_PR_NUMBER }}') return '';
94114
95115
const pullRequest = await github.rest.pulls.get({
96116
owner: context.repo.owner,
97117
repo: context.repo.repo,
98-
pull_number: '${{ inputs.APP_PULL_REQUEST_NUMBER }}',
118+
pull_number: '${{ needs.prep.outputs.APP_PR_NUMBER }}',
99119
});
100120
101121
const body = pullRequest.data.body;
@@ -106,7 +126,7 @@ jobs:
106126
107127
getMobileExpensifyRef:
108128
runs-on: ubuntu-latest
109-
needs: [getMobileExpensifyPR]
129+
needs: [prep, getMobileExpensifyPR]
110130
outputs:
111131
MOBILE_EXPENSIFY_REF: ${{ steps.getHeadRef.outputs.REF || 'main' }}
112132
steps:
@@ -118,11 +138,11 @@ jobs:
118138
id: getHeadRef
119139
run: |
120140
set -e
121-
if [[ -z "${{ inputs.MOBILE_EXPENSIFY_PULL_REQUEST_NUMBER }}" && -z "${{ needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }}" ]]; then
141+
if [[ -z "${{ needs.prep.outputs.MOBILE_PR_NUMBER }}" && -z "${{ needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }}" ]]; then
122142
echo "REF=" >> "$GITHUB_OUTPUT"
123143
else
124-
echo "PR=${{ inputs.MOBILE_EXPENSIFY_PULL_REQUEST_NUMBER || needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }}" >> "$GITHUB_OUTPUT"
125-
echo "REF=$(gh pr view ${{ inputs.MOBILE_EXPENSIFY_PULL_REQUEST_NUMBER || needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }} -R Expensify/Mobile-Expensify --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT"
144+
echo "PR=${{ needs.prep.outputs.MOBILE_PR_NUMBER || needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }}" >> "$GITHUB_OUTPUT"
145+
echo "REF=$(gh pr view ${{ needs.prep.outputs.MOBILE_PR_NUMBER || needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }} -R Expensify/Mobile-Expensify --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT"
126146
fi
127147
env:
128148
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
@@ -133,7 +153,7 @@ jobs:
133153
needs: [prep, getMobileExpensifyPR, getMobileExpensifyRef]
134154
steps:
135155
- name: Add build start comment to Expensify/App PR
136-
if: ${{ inputs.APP_PULL_REQUEST_NUMBER != ''}}
156+
if: ${{ needs.prep.outputs.APP_PR_NUMBER != '' }}
137157
# v7
138158
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
139159
with:
@@ -143,7 +163,7 @@ jobs:
143163
github.rest.issues.createComment({
144164
owner: context.repo.owner,
145165
repo: context.repo.repo,
146-
issue_number: ${{ inputs.APP_PULL_REQUEST_NUMBER }},
166+
issue_number: ${{ needs.prep.outputs.APP_PR_NUMBER }},
147167
body: `🚧 @${{ github.actor }} has triggered a test Expensify/App build. You can view the [workflow run here](${workflowURL}).`
148168
});
149169
@@ -164,9 +184,11 @@ jobs:
164184
165185
desktop:
166186
name: Build and deploy Desktop for testing
167-
if: ${{ inputs.DESKTOP && inputs.APP_PULL_REQUEST_NUMBER }}
187+
if: ${{ inputs.DESKTOP && needs.prep.outputs.APP_PR_NUMBER }}
168188
needs: [prep]
169189
runs-on: macos-14-large
190+
env:
191+
PULL_REQUEST_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER }}
170192
steps:
171193
- name: Checkout
172194
# v4
@@ -231,9 +253,11 @@ jobs:
231253

232254
web:
233255
name: Build and deploy Web
234-
if: ${{ inputs.WEB && inputs.APP_PULL_REQUEST_NUMBER }}
256+
if: ${{ inputs.WEB && needs.prep.outputs.APP_PR_NUMBER }}
235257
needs: [prep]
236258
runs-on: ubuntu-latest-xl
259+
env:
260+
PULL_REQUEST_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER }}
237261
steps:
238262
- name: Checkout
239263
# v4
@@ -271,6 +295,8 @@ jobs:
271295
if: ${{ inputs.ANDROID }}
272296
needs: [prep, getMobileExpensifyPR, getMobileExpensifyRef]
273297
runs-on: ubuntu-latest-xl
298+
env:
299+
PULL_REQUEST_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER }}
274300
outputs:
275301
S3_APK_PATH: ${{ steps.exportAndroidS3Path.outputs.S3_APK_PATH }}
276302
steps:
@@ -327,9 +353,11 @@ jobs:
327353
with:
328354
bundler-cache: true
329355

330-
- name: Install 1Password CLI
331-
# v1
332-
uses: 1password/install-cli-action@143a85f84a90555d121cde2ff5872e393a47ab9f
356+
- name: Setup 1Password CLI and certificates
357+
uses: Expensify/GitHub-Actions/setup-certificate-1p@main
358+
with:
359+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
360+
SHOULD_LOAD_SSL_CERTIFICATES: 'false'
333361

334362
- name: Load files from 1Password
335363
env:
@@ -392,6 +420,7 @@ jobs:
392420
needs: [prep, getMobileExpensifyPR, getMobileExpensifyRef]
393421
env:
394422
DEVELOPER_DIR: /Applications/Xcode_26.0.app/Contents/Developer
423+
PULL_REQUEST_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER }}
395424
runs-on: macos-15-xlarge
396425
outputs:
397426
IOS_PATH: ${{ steps.export-ios-path.outputs.IOS_PATH }}
@@ -456,9 +485,11 @@ jobs:
456485
max_attempts: 5
457486
command: npm run pod-install
458487

459-
- name: Install 1Password CLI
460-
# v1
461-
uses: 1password/install-cli-action@143a85f84a90555d121cde2ff5872e393a47ab9f
488+
- name: Setup 1Password CLI and certificates
489+
uses: Expensify/GitHub-Actions/setup-certificate-1p@main
490+
with:
491+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
492+
SHOULD_LOAD_SSL_CERTIFICATES: 'false'
462493

463494
- name: Load files from 1Password
464495
env:
@@ -517,21 +548,21 @@ jobs:
517548
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
518549

519550
- name: Publish links to apps for download on Expensify/App PR
520-
if: ${{ inputs.APP_PULL_REQUEST_NUMBER || (!inputs.APP_PULL_REQUEST_NUMBER && !needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR) }}
551+
if: ${{ needs.prep.outputs.APP_PR_NUMBER || (!needs.prep.outputs.APP_PR_NUMBER && !needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR) }}
521552
uses: ./.github/actions/javascript/postTestBuildComment
522553
with:
523554
REPO: App
524-
APP_PR_NUMBER: ${{ inputs.APP_PULL_REQUEST_NUMBER }}
555+
APP_PR_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER }}
525556
MOBILE_EXPENSIFY_PR_NUMBER: ${{ needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }}
526557
GITHUB_TOKEN: ${{ github.token }}
527558
ANDROID: ${{ needs.androidHybrid.result }}
528559
DESKTOP: ${{ needs.desktop.result }}
529560
IOS: ${{ needs.iosHybrid.result }}
530561
WEB: ${{ needs.web.result }}
531562
ANDROID_LINK: ${{ needs.androidHybrid.outputs.S3_APK_PATH }}
532-
DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ inputs.APP_PULL_REQUEST_NUMBER }}/NewExpensify.dmg
563+
DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ needs.prep.outputs.APP_PR_NUMBER }}/NewExpensify.dmg
533564
IOS_LINK: ${{ needs.iosHybrid.outputs.IOS_PATH }}
534-
WEB_LINK: https://${{ inputs.APP_PULL_REQUEST_NUMBER }}.pr-testing.expensify.com
565+
WEB_LINK: https://${{ needs.prep.outputs.APP_PR_NUMBER }}.pr-testing.expensify.com
535566

536567
- name: Publish links to apps for download on Expensify/Mobile-Expensify PR
537568
if: ${{ needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }}

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ android {
114114
minSdkVersion rootProject.ext.minSdkVersion
115115
targetSdkVersion rootProject.ext.targetSdkVersion
116116
multiDexEnabled rootProject.ext.multiDexEnabled
117-
versionCode 1009027808
118-
versionName "9.2.78-8"
117+
versionCode 1009028101
118+
versionName "9.2.81-1"
119119
// Supported language variants must be declared here to avoid from being removed during the compilation.
120120
// This also helps us to not include unnecessary language variants in the APK.
121121
resConfigs "en", "es"

config/webpack/webpack.common.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment):
188188
project: 'app',
189189
release: {
190190
name: `${process.env.npm_package_name}@${process.env.npm_package_version}`,
191+
create: true,
192+
setCommits: {
193+
auto: true,
194+
},
191195
},
192196
sourcemaps: {
193197
// Use relative path from project root - works for both web (dist/) and desktop (desktop/dist/www/)

contributingGuides/STYLE.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ const modifiedArray = someArray.filter(filterFunc).map(mapFunc);
863863

864864
- Prefer `async/await` over `.then/.catch` across the codebase. Use raw `Promise` only when wrapping callbacks or implementing deferred signals.
865865
- Use `Promise.all(...)` (or `Promise.allSettled(...)`) for work that can run in parallel.
866-
- In UI code, start unrelated async tasks at the same time (dont wait for one to finish before starting another), and let the UI render immediately with loading states ([see data-binding](./philosophies/DATA-BINDING.md)).
866+
- In UI code, start unrelated async tasks at the same time (dont wait for one to finish before starting another), and let the UI render immediately with loading states ([see data-binding](./philosophies/DATA-BINDING.md)).
867867
- See the detailed [async philosophy document](./philosophies/ASYNC.md)
868868

869869
## Accessing Object Properties and Default Values
@@ -1130,14 +1130,18 @@ In React Native, one **must not** attempt to falsy-check a string for an inline
11301130
11311131
### Function component style
11321132
1133-
When writing a function component, you must ALWAYS add a `displayName` property and give it the same value as the name of the component (this is so it appears properly in the React dev tools)
1133+
When writing a function component do not explicitly add `displayName` property when component name matches with displayName. A few components such as higher-order components, may need the `displayName` property (this is so it appears properly in the React dev tools)
11341134
11351135
```tsx
1136-
function Avatar(props: AvatarProps) {...};
1136+
export default function <TProps extends WithCurrentUserPersonalDetailsProps>(WrappedComponent: ComponentType<TProps>): ComponentType<Omit<TProps, keyof HOCProps>> {
1137+
function WithCurrentUserPersonalDetails(props: Omit<TProps, keyof HOCProps>) {
1138+
...
1139+
}
11371140

1138-
Avatar.displayName = 'Avatar';
1141+
WithCurrentUserPersonalDetails.displayName = `WithCurrentUserPersonalDetails(${getComponentDisplayName(WrappedComponent)})`;
11391142

1140-
export default Avatar;
1143+
return WithCurrentUserPersonalDetails;
1144+
}
11411145
```
11421146
11431147
### Forwarding refs
@@ -1235,7 +1239,7 @@ The correct approach is avoid using `ScrollView`. You can add props like `listHe
12351239
</ScrollView>
12361240
```
12371241
1238-
### Correct Approach
1242+
### Correct Approach
12391243
The correct approach is to use the list component's built-in header and footer props instead of wrapping in a `ScrollView`:
12401244
12411245
- Using `SelectionList`

docs/articles/new-expensify/reports-and-expenses/Understanding-Report-Statuses-and-Actions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,5 @@ Yes—if the report is a **Draft** or is **Outstanding**. **Done** or **Paid** r
116116
## Can I view report details?
117117
Yes. Tap the report title or select **View Details** from the **More** menu.
118118

119+
## An employee sent an IOU before our company had a company workspace. How do we report their expenses on the workspace instead?
120+
The employee can move their IOU to the company workspace by selecting **More** > **Change Workspace**. This will convert the IOU into a regular expense report on the selected workspace.

0 commit comments

Comments
 (0)