From f07344be3a83bd25e0713dfdbd15efdd9952649e Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 18:16:23 +0100 Subject: [PATCH 01/15] Add some default policy values --- src/libs/actions/Policy.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 3c34e823ac9a..8dc2a3cd0b3d 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1938,6 +1938,11 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, customUnits, makeMeAdmin, + autoReporting: true, + approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, + harvesting: { + enabled: true, + } }, }, { From 0860477bea7f31d39e314f5f0ee183d206546c99 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 18:33:35 +0100 Subject: [PATCH 02/15] Make sure we create the workspace with correct harvesting and approval mode defaults --- src/libs/actions/Policy.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 8dc2a3cd0b3d..dabc1b0890e4 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2001,6 +2001,11 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName isPolicyExpenseChatEnabled: true, outputCurrency, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + autoReporting: true, + approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, + harvesting: { + enabled: true, + }, customUnits, areCategoriesEnabled: true, areTagsEnabled: false, @@ -2489,6 +2494,11 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string // Setting the currency to USD as we can only add the VBBA for this policy currency right now outputCurrency: CONST.CURRENCY.USD, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + autoReporting: true, + approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, + harvesting: { + enabled: true, + }, customUnits, areCategoriesEnabled: true, areTagsEnabled: false, From 3131c5d66431fdf5bc4a00cdaa7c24a3dbca8e91 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 18:35:51 +0100 Subject: [PATCH 03/15] Remove deprecated isPreventSelfApprovalEnabled policy property --- src/libs/NextStepUtils.ts | 4 ++-- src/types/onyx/Policy.ts | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 5a19e68afe72..7a0b638ddce1 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -73,7 +73,7 @@ function buildNextStep( const {policyID = '', ownerAccountID = -1, managerID = -1} = report; const policy = ReportUtils.getPolicy(policyID); - const {submitsTo, harvesting, isPreventSelfApprovalEnabled, preventSelfApproval, autoReportingFrequency, autoReportingOffset} = policy; + const {submitsTo, harvesting, preventSelfApproval, autoReportingFrequency, autoReportingOffset} = policy; const isOwner = currentUserAccountID === ownerAccountID; const isManager = currentUserAccountID === managerID; const isSelfApproval = currentUserAccountID === submitsTo; @@ -164,7 +164,7 @@ function buildNextStep( } // Prevented self submitting - if ((isPreventSelfApprovalEnabled ?? preventSelfApproval) && isSelfApproval) { + if (preventSelfApproval && isSelfApproval) { optimisticNextStep.message = [ { text: "Oops! Looks like you're submitting to ", diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 247eb64f48e9..d33190d9cb71 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -316,9 +316,6 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< enabled: boolean; }; - /** @deprecated Whether the scheduled submit is enabled */ - isPreventSelfApprovalEnabled?: boolean; - /** Whether the self approval or submitting is enabled */ preventSelfApproval?: boolean; From ebf0eaa25c0ce9bc235db9968caa182e3eb38680 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 22:37:58 +0100 Subject: [PATCH 04/15] Create correct next steps when the report is submitted in the submit and close approval flow --- src/libs/NextStepUtils.ts | 14 ++++++++++++++ src/libs/actions/IOU.ts | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 50a424bcee8d..0e76596ba8fa 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -255,6 +255,20 @@ function buildNextStep( break; } + // Generates an optimistic nextStep once a report has been closed for example in the case of Submit and Close approval flow + case CONST.REPORT.STATUS_NUM.CLOSED: + optimisticNextStep = { + type, + title: 'Finished!', + message: [ + { + text: 'No further action required!', + }, + ], + }; + + break; + // Generates an optimistic nextStep once a report has been approved case CONST.REPORT.STATUS_NUM.APPROVED: // Self review diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5c92cd87a2bc..51a91e03622e 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4782,8 +4782,8 @@ function submitReport(expenseReport: OnyxTypes.Report) { const parentReport = ReportUtils.getReport(expenseReport.parentReportID); const policy = getPolicy(expenseReport.policyID); const isCurrentUserManager = currentUserPersonalDetails.accountID === expenseReport.managerID; - const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.SUBMITTED); const isSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy); + const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, isSubmitAndClosePolicy ? CONST.REPORT.STATUS_NUM.CLOSED : CONST.REPORT.STATUS_NUM.SUBMITTED); const optimisticData: OnyxUpdate[] = !isSubmitAndClosePolicy ? [ From 26e41dc9fa5f46ac1593bfa1100ca6516f9d5c4b Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 22:42:11 +0100 Subject: [PATCH 05/15] Add unit test for the submit and close next steps --- src/libs/actions/IOU.ts | 5 ----- tests/unit/NextStepUtilsTest.ts | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 51a91e03622e..30c9bca9ca7d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4808,11 +4808,6 @@ function submitReport(expenseReport: OnyxTypes.Report) { statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, - value: optimisticNextStep, - }, ] : [ { diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 200a8f52349e..a5bdda802315 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -442,6 +442,24 @@ describe('libs/NextStepUtils', () => { expect(result).toMatchObject(optimisticNextStep); }); + + test('submit and close approval mode', () => { + report.ownerAccountID = strangeAccountID; + optimisticNextStep.title = 'Finished:'; + optimisticNextStep.message = [ + { + text: 'No further action required!', + }, + ]; + + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, + }).then(() => { + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.CLOSED); + + expect(result).toMatchObject(optimisticNextStep); + }); + }); }); describe('it generates an optimistic nextStep once a report has been approved', () => { From bcb3110cc69b238fea606159e265464f27fc2237 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 22:43:32 +0100 Subject: [PATCH 06/15] Make sure the next steps are included in the optimistic data when submitting --- src/libs/actions/IOU.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 30c9bca9ca7d..333c869f4be4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4821,6 +4821,12 @@ function submitReport(expenseReport: OnyxTypes.Report) { }, ]; + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: optimisticNextStep, + }); + if (parentReport?.reportID) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, From 36c91d2365bd179e9829602c4da95df8ebf89413 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 22:44:39 +0100 Subject: [PATCH 07/15] Include the next steps in failure data when submitting no matter what approval mode the policy has --- src/libs/actions/IOU.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 333c869f4be4..344ec53568cc 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4862,6 +4862,11 @@ function submitReport(expenseReport: OnyxTypes.Report) { stateNum: CONST.REPORT.STATE_NUM.OPEN, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: currentNextStep, + }, ]; if (!isSubmitAndClosePolicy) { failureData.push( @@ -4874,11 +4879,6 @@ function submitReport(expenseReport: OnyxTypes.Report) { }, }, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, - value: currentNextStep, - }, ); } From 67c18cea42d8404072739bcfef770499e0cd13af Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 23:11:15 +0100 Subject: [PATCH 08/15] Update the unit tests --- tests/unit/NextStepUtilsTest.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index a5bdda802315..072a06748da9 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -445,7 +445,7 @@ describe('libs/NextStepUtils', () => { test('submit and close approval mode', () => { report.ownerAccountID = strangeAccountID; - optimisticNextStep.title = 'Finished:'; + optimisticNextStep.title = 'Finished!'; optimisticNextStep.message = [ { text: 'No further action required!', @@ -571,13 +571,5 @@ describe('libs/NextStepUtils', () => { expect(result).toMatchObject(optimisticNextStep); }); }); - - describe('it generates a nullable optimistic nextStep', () => { - test('closed status', () => { - const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.CLOSED); - - expect(result).toBeNull(); - }); - }); }); }); From fc36c7c0769aaa189a24fe25a8a6790d6dc42338 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 23:12:26 +0100 Subject: [PATCH 09/15] Fix style --- src/libs/actions/IOU.ts | 16 +++++++--------- src/libs/actions/Policy.ts | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 344ec53568cc..d8c388045aa6 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4869,17 +4869,15 @@ function submitReport(expenseReport: OnyxTypes.Report) { }, ]; if (!isSubmitAndClosePolicy) { - failureData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, - value: { - [optimisticSubmittedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), - }, + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + value: { + [optimisticSubmittedReportAction.reportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), }, }, - ); + }); } if (parentReport?.reportID) { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index e11cef47303b..27ed99d00868 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1954,7 +1954,7 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, harvesting: { enabled: true, - } + }, }, }, { From 356bbd0f563f46b62510cd1c846ede02da3de526 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 23:13:02 +0100 Subject: [PATCH 10/15] Revert podfile lock changes --- ios/Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9e6a52543c92..24ef0704be25 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1921,8 +1921,8 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: b6b6f46949eae83b71429c971162af337ef34fa3 - Yoga: 13c8ef87792450193e117976337b8527b49e8c03 + Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 PODFILE CHECKSUM: a431c146e1501391834a2f299a74093bac53b530 -COCOAPODS: 1.14.3 +COCOAPODS: 1.13.0 From eb24a0a7dddf05ea5185c45cf0f730cd22fe72b4 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 23:34:02 +0100 Subject: [PATCH 11/15] Update IOU tests --- tests/actions/IOUTest.ts | 108 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 2ce72a58aead..cfa37d4e6299 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -3051,7 +3051,9 @@ describe('actions/IOU', () => { let chatReport: OnyxEntry; return waitForBatchedUpdates() .then(() => { - PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace"); + const policyID = PolicyActions.generatePolicyID(); + PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace", policyID); + PolicyActions.setWorkspaceApprovalMode(policyID, CARLOS_EMAIL, CONST.POLICY.APPROVAL_MODE.BASIC); return waitForBatchedUpdates(); }) .then( @@ -3147,6 +3149,110 @@ describe('actions/IOU', () => { }), ); }); + it('correctly submits a report with Submit and Close approval mode', () => { + const amount = 10000; + const comment = '💸💸💸💸'; + const merchant = 'NASDAQ'; + let expenseReport: OnyxEntry; + let chatReport: OnyxEntry; + return waitForBatchedUpdates() + .then(() => { + PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace"); + return waitForBatchedUpdates(); + }) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + chatReport = Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT) ?? null; + + resolve(); + }, + }); + }), + ) + .then(() => { + if (chatReport) { + IOU.requestMoney( + chatReport, + amount, + CONST.CURRENCY.USD, + '', + merchant, + RORY_EMAIL, + RORY_ACCOUNT_ID, + {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID, isPolicyExpenseChat: true, reportID: chatReport.reportID}, + comment, + {}, + ); + } + return waitForBatchedUpdates(); + }) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null; + Onyx.merge(`report_${expenseReport?.reportID}`, { + statusNum: 0, + stateNum: 0, + }); + resolve(); + }, + }); + }), + ) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null; + + // Verify report is a draft + expect(expenseReport?.stateNum).toBe(0); + expect(expenseReport?.statusNum).toBe(0); + resolve(); + }, + }); + }), + ) + .then(() => { + if (expenseReport) { + IOU.submitReport(expenseReport); + } + return waitForBatchedUpdates(); + }) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null; + + // Report is closed since the default policy settings is Submit and Close + expect(expenseReport?.stateNum).toBe(2); + expect(expenseReport?.statusNum).toBe(2); + resolve(); + }, + }); + }), + ); + }); it('correctly implements error handling', () => { const amount = 10000; const comment = '💸💸💸💸'; From 737788b6ffb4e4a2c2e00bff6c705638c015992e Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 3 Apr 2024 23:37:19 +0100 Subject: [PATCH 12/15] Add comment to explain test --- tests/actions/IOUTest.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index cfa37d4e6299..803ef9ba6be2 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -3053,6 +3053,8 @@ describe('actions/IOU', () => { .then(() => { const policyID = PolicyActions.generatePolicyID(); PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace", policyID); + + // Change the approval mode for the policy since default is Submit and Close PolicyActions.setWorkspaceApprovalMode(policyID, CARLOS_EMAIL, CONST.POLICY.APPROVAL_MODE.BASIC); return waitForBatchedUpdates(); }) From 8643d4105e2e74ee14b9b820b89d6176aaef212b Mon Sep 17 00:00:00 2001 From: Lauren Schurr <33293730+lschurr@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:02:24 -0700 Subject: [PATCH 13/15] Delete docs/articles/expensify-classic/reports/Expense-Rules.md --- .../reports/Expense-Rules.md | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 docs/articles/expensify-classic/reports/Expense-Rules.md diff --git a/docs/articles/expensify-classic/reports/Expense-Rules.md b/docs/articles/expensify-classic/reports/Expense-Rules.md deleted file mode 100644 index 295aa8d00cc9..000000000000 --- a/docs/articles/expensify-classic/reports/Expense-Rules.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Expense Rules -description: Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant's name. - ---- -# Overview -Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant’s name. - -# How to use Expense Rules -**To create an expense rule, follow these steps:** -1. Navigate to **Settings > Account > Expense Rules** -2. Click on **New Rule** -3. Fill in the required information to set up your rule - -When creating an expense rule, you will be able to apply the following rules to expenses: - -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} - -- **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 - -## Tips on using Expense Rules -- If you'd like to apply a rule to all expenses (“Universal Rule”) rather than just one merchant, simply enter a period [.] and nothing else into the **“When the merchant name contains:”** field. **Note:** Universal Rules will always take precedence over all other rules for category (more on this below). -- You can apply a rule to previously entered expenses by checking the **Apply to existing matching expenses** checkbox. Click “Preview Matching Expenses” to see if your rule matches the intended expenses. -- You can create expense rules while editing an expense. To do this, simply check the box **“Create a rule based on your changes"** at the time of editing. Note that the expense must be saved, reopened, and edited for this option to appear. - - -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"} - - -To delete an expense rule, go to **Settings > Account > Expense Rules**, scroll down to the rule you’d like to remove, and then click the trash can icon in the upper right corner of the rule: - -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"} - -# Deep Dive -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** precede 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. - - -{% 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 %} From 4a0e8220ebdfdb68ac2de44e22a5787c4e2d60c0 Mon Sep 17 00:00:00 2001 From: Lauren Schurr <33293730+lschurr@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:07:36 -0700 Subject: [PATCH 14/15] Create Currency.md --- .../expensify-classic/workspaces/Currency.md | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/articles/expensify-classic/workspaces/Currency.md diff --git a/docs/articles/expensify-classic/workspaces/Currency.md b/docs/articles/expensify-classic/workspaces/Currency.md new file mode 100644 index 000000000000..77b5fbbb3ebc --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Currency.md @@ -0,0 +1,63 @@ +--- +title: Report Currency +description: Understanding expense and report currency +--- + +# Overview +As a workspace admin, you can choose a default currency for your employees' expense reports, and we’ll automatically convert any expenses into that currency. + +Here are a few essential things to remember: + +- Currency settings for a workspace apply to all expenses under that workspace. If you need different default currencies for certain employees, creating separate workspaces and configuring the currency settings is best. +- As an admin, the currency settings you establish in the workspace will take precedence over any currency settings individual users may have in their accounts. +- Currency is a workspace-level setting, meaning the currency you set will determine the currency for all expenses submitted on that workspace. + +# How to select the currency on a workspace + +## As an admin on a group workspace + +1. Sign into your Expensify web account +2. Go to **Settings > Workspaces > Group > _[Workspace Name]_> Reports > Report Basics** +3. Adjust the **Report Output Currency** + +## On an individual workspace + +1. Sign into your Expensify web account +2. Go to **Settings > Workspaces > Individual >_[Workspace Name]_> Reports > Report Basics** +3. Adjust the **Report Output Currency** + +Please note the currency setting on an individual workspace is overridden when you submit a report on a group workspace. + +# Deep Dive + +## Conversion Rates + +Using data from Open Exchange Rates, Expensify takes the average rate on the day the expense occurred to convert an expense from one currency to another. The conversion rate can vary depending on when the expense happened since the rate is determined after the market closes on that specific date. + +If the markets aren’t open on the day the expense takes place (i.e., on a Saturday), Expensify will use the daily average rate from the last available market day before the purchase took place. + +When an expense is logged for a future date, possibly to anticipate a purchase that has yet to occur, we'll use the most recent available data. This means the report's value may change up to the day of that expense. + +## Managing expenses for employees in several different countries + +Suppose you have employees scattered across the globe who submit expense reports in various currencies. The best way to manage those expenses is to create separate group workspaces for each location or region where your employees are based. + +Then, set the default currency for that workspace to match the currency in which the employees are reimbursed. + +For example, if you have employees in the US, France, Japan, and India, you’d want to create four separate workspaces, add the employees to each, and then set the corresponding currency for each workspace. + +{% include faq-begin.md %} + +## I have expenses in several different currencies. How will this show up on a report? + +If you're traveling to foreign countries during a reporting period and making purchases in various currencies, each expense is imported with the currency of the purchase. + +On your expense report, Expensify will automatically convert each expense to the default currency set for the group workspace. + +## How does the currency of an expense impact the conversion rate? + +Expenses entered in a foreign currency are automatically converted to the default currency on your workspace. The conversion uses the day’s average trading rate pulled from [Open Exchange Rates](https://openexchangerates.org/). + +If you want to bypass the exchange rate conversion, you can manually enter an expense in your default currency instead. + +{% include faq-end.md %} From 1371acf9ebae128cd836e7f7487e2ca126ec24e7 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 8 Apr 2024 10:45:09 +0000 Subject: [PATCH 15/15] Update version to 1.4.61-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- ios/NotificationServiceExtension/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index bc08cbbeed21..b1538d22de8f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001046013 - versionName "1.4.60-13" + versionCode 1001046100 + versionName "1.4.61-0" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 0db15a68744f..ad29816ca2d4 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.60 + 1.4.61 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.60.13 + 1.4.61.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index e133f93aa125..30ebc444e07c 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.60 + 1.4.61 CFBundleSignature ???? CFBundleVersion - 1.4.60.13 + 1.4.61.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 12e153cd1f3b..5cfad9bf91a6 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.60 + 1.4.61 CFBundleVersion - 1.4.60.13 + 1.4.61.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 391b13e99305..484fd990a918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.60-13", + "version": "1.4.61-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.60-13", + "version": "1.4.61-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 5e00815f1e3e..3bdfcdeb2424 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.60-13", + "version": "1.4.61-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.",