Repository navigation
feat: add Auto report option for expenses spanning multiple submitters #99688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f1008c7
8d7742f
f43a117
26b7671
4c1c461
5f18072
c5052d3
202cbd2
ccefcc2
4a7ed69
4ace428
0742500
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ import type { | |
| import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; | ||
| import DateUtils from '@libs/DateUtils'; | ||
| import DistanceRequestUtils from '@libs/DistanceRequestUtils'; | ||
| import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; | ||
| import {toLocaleDigit} from '@libs/LocaleDigitUtils'; | ||
| import {translateLocal} from '@libs/Localize'; | ||
| import {buildOptimisticNextStep} from '@libs/NextStepUtils'; | ||
|
|
@@ -2038,6 +2039,39 @@ function changeTransactionsReport(props: ChangeTransactionsReportProps) { | |
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Reports expenses without naming a destination: the backend puts each one in its owner's latest draft report, or a new | ||
| * report when they have none. Needed when a selection spans submitters, since any report the App picked would belong to | ||
| * just one of them. Carries no optimistic data because every update the normal move builds hangs off a destination only | ||
| * the backend knows. | ||
| */ | ||
| function autoReportTransactions(transactionIDs: string[]) { | ||
| if (transactionIDs.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| // There is no optimistic move to roll back, but a failure still has to reach the admin: the screen has closed and | ||
| // the selection is gone by then, so the error is surfaced on the expenses themselves via their red brick road. | ||
| const successData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.TRANSACTION>> = transactionIDs.map((transactionID) => ({ | ||
| onyxMethod: Onyx.METHOD.MERGE, | ||
| key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, | ||
| value: {errors: null}, | ||
| })); | ||
| const failureData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.TRANSACTION>> = transactionIDs.map((transactionID) => ({ | ||
| onyxMethod: Onyx.METHOD.MERGE, | ||
| key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, | ||
| value: {errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage')}, | ||
| })); | ||
|
Comment on lines
+2060
to
+2064
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Auto report failures don't show in Search
Expected: The affected expenses show an error in the list. |
||
|
|
||
| const parameters: ChangeTransactionsReportParams = { | ||
| transactionList: transactionIDs.join(','), | ||
| reportID: CONST.REPORT.AUTOMATIC_REPORT_ID, | ||
| transactionIDToReportActionAndThreadData: '{}', | ||
| }; | ||
|
|
||
| API.write(WRITE_COMMANDS.CHANGE_TRANSACTIONS_REPORT, parameters, {successData, failureData}); | ||
| } | ||
|
|
||
| function getDefaultP2PMileageRate() { | ||
| API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, null); | ||
| } | ||
|
|
@@ -2059,6 +2093,7 @@ function getDuplicateTransactionDetails(transactionID?: string) { | |
| } | ||
|
|
||
| export { | ||
| autoReportTransactions, | ||
| saveWaypoint, | ||
| removeWaypoint, | ||
| getRoute, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,7 @@ import usePersonalPolicy from '@hooks/usePersonalPolicy'; | |
| import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses'; | ||
|
|
||
| import {createNewReport} from '@libs/actions/Report'; | ||
| import {changeTransactionsReport} from '@libs/actions/Transaction'; | ||
| import {autoReportTransactions, changeTransactionsReport} from '@libs/actions/Transaction'; | ||
| import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; | ||
| import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; | ||
| import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNavigationActionToMicrotaskQueue'; | ||
|
|
@@ -23,6 +23,7 @@ import {generateReportID, getPersonalDetailsForAccountID, getReportOrDraftReport | |
| import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; | ||
| import { | ||
| isDistanceRequest as isDistanceRequestUtil, | ||
| isManagedCardTransaction, | ||
| isManualDistanceRequest as isManualDistanceRequestUtil, | ||
| isOdometerDistanceRequest as isOdometerDistanceRequestUtil, | ||
| isUnreportedManagedCardTransaction, | ||
|
|
@@ -119,6 +120,29 @@ function SearchTransactionsChangeReport() { | |
| return report?.ownerAccountID; | ||
| }, [selectedTransactions, selectedTransactionsKeys, allReports]); | ||
| const targetOwnerPersonalDetails = useMemo(() => getPersonalDetailsForAccountID(targetOwnerAccountID, personalDetails) as PersonalDetails, [personalDetails, targetOwnerAccountID]); | ||
| // Kept separate from `targetOwnerAccountID`, which stops at the first owner it finds. Counting needs them all. | ||
| // Only distinct resolved owners count. An owner we cannot resolve must not stand in for a second submitter: for an | ||
| // unreported expense the report lookup can never resolve one (its reportID is `0`), so a search snapshot missing | ||
| // the money-request action would otherwise file one cardholder's bulk selection as mixed and strip its report list. | ||
| // "Auto report" has the backend resolve each destination through the expense's card, so one expense without a card | ||
| // fails the whole request with "404 Card not found". | ||
|
Comment on lines
+123
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please simplify all inline comments which are too long. |
||
| const areAllManagedCardTransactions = selectedTransactionsKeys.length > 0 && transactions.length === selectedTransactionsKeys.length && transactions.every(isManagedCardTransaction); | ||
| const hasMultipleSubmitters = useMemo(() => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One cardholder's selection with unresolved owners gets no Move to report - expected? This now ignores unresolved owners, but the entry gate at
Should Move to report be offered (with the cardholder's reports and Create report) or not? |
||
| const ownerAccountIDs = new Set<number>(); | ||
|
|
||
| for (const transactionKey of selectedTransactionsKeys) { | ||
| const selection = selectedTransactions[transactionKey]; | ||
| const reportID = selection?.reportID; | ||
| const ownerAccountID = | ||
| selection?.ownerAccountID ?? getReportOrDraftReport(reportID, undefined, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`])?.ownerAccountID; | ||
|
|
||
| if (typeof ownerAccountID === 'number') { | ||
| ownerAccountIDs.add(ownerAccountID); | ||
| } | ||
| } | ||
|
|
||
| return ownerAccountIDs.size > 1; | ||
| }, [selectedTransactions, selectedTransactionsKeys, allReports]); | ||
|
|
||
| useHydrateReportsFromSnapshot(currentSearchResults, allReports); | ||
|
|
||
|
|
@@ -258,6 +282,14 @@ function SearchTransactionsChangeReport() { | |
| Navigation.goBack(undefined, {afterTransition: clearSelectedTransactions}); | ||
| }; | ||
|
|
||
| const autoReport = () => { | ||
| if (selectedTransactionsKeys.length === 0) { | ||
| return; | ||
| } | ||
| autoReportTransactions(selectedTransactionsKeys); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a multi-submitter selection includes expenses from a workspace whose owner is past due, this path sends Useful? React with 👍 / 👎. |
||
| Navigation.goBack(undefined, {afterTransition: clearSelectedTransactions}); | ||
| }; | ||
|
|
||
| const removeFromReport = () => { | ||
| if (selectedTransactionsKeys.length === 0) { | ||
| return; | ||
|
|
@@ -302,6 +334,9 @@ function SearchTransactionsChangeReport() { | |
| transactionPolicyID={selectedReportPolicyID} | ||
| isPerDiemRequest={hasPerDiemTransactions} | ||
| isUnreportedManagedCardTransaction={hasUnreportedManagedCardTransactions} | ||
| hasMultipleSubmitters={hasMultipleSubmitters} | ||
| areAllManagedCardTransactions={areAllManagedCardTransactions} | ||
| autoReport={autoReport} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auto report reaches expenses on Processing reports - Is this expected?
canAutoReportAcrossSubmitterschecks owners, managed cards and per diem/distance, but not report state.canChangeReportis true for an admin on any outstanding report, including Processing, so submitted company-card expenses from two employees now reach Move to report → Auto report.On
maina mixed-owner selection never reaches the move flow.Should Move to report be offered (with Auto report) or not?