Skip to content
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

Create optimistic report when approving/paying for report with hold expenses #42573

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d4b8cfd
wip
war-in May 17, 2024
d2a24ba
wip, but preview is showing and actions are moved
war-in May 17, 2024
e48f86c
fix linter
war-in May 17, 2024
5ae1de0
create optimistic data only when not `full` report
war-in May 20, 2024
4af7687
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in May 24, 2024
c04ce34
add optimisticHoldReportID api parameter
war-in May 24, 2024
a321cc3
add failureData
war-in May 24, 2024
827b020
fix mistakes
war-in May 24, 2024
af47d1b
move code to separate function
war-in May 24, 2024
26ace8c
add logic to approve flow
war-in May 24, 2024
2161c41
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in May 28, 2024
5c8a544
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in Jun 3, 2024
04d0d5e
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in Jun 7, 2024
039a123
add parentReportActionID to optimistic expense report
war-in Jun 7, 2024
245b715
mark as deleted and create new reportActions
war-in Jun 10, 2024
a49af7b
non-clickable optimistic report preview
war-in Jun 10, 2024
ec55722
Revert "non-clickable optimistic report preview"
war-in Jun 11, 2024
63fed38
clean the code, prepare optimisticHoldReportExpenseActionIDs
war-in Jun 11, 2024
6eb0efe
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in Jun 11, 2024
3b04956
pass optimisticHoldActionID to API
war-in Jun 14, 2024
20c6cb6
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in Jun 14, 2024
31daa15
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in Jul 5, 2024
acdc7ad
clean up code, remove lint and type errors
war-in Jul 5, 2024
ba4b50e
add `optimisticHoldReportExpenseActionIDs` to API commands
war-in Jul 8, 2024
12c8a8f
fix wrong hold transaction condition and update reports
war-in Jul 8, 2024
8eb5e0f
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in Jul 8, 2024
50b7808
fix first transaction extraction
war-in Jul 8, 2024
ac983cb
Merge branch 'refs/heads/main' into war-in/delay-in-creating-report-w…
war-in Jul 24, 2024
5b11c4b
use newest API
war-in Jul 24, 2024
238270f
update transactions to see correct expenses number
war-in Jul 24, 2024
6002cfb
bring moved transactions back on failure
war-in Jul 24, 2024
110921d
add pending action to all created previews
war-in Jul 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/libs/API/parameters/ApproveMoneyRequestParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ type ApproveMoneyRequestParams = {
reportID: string;
approvedReportActionID: string;
full?: boolean;
optimisticHoldReportID?: string;
optimisticHoldActionID?: string;
/**
* Stringified JSON object with type of following structure:
* Array<{
* optimisticReportActionID: string;
* oldReportActionID: string;
* }>
*/
optimisticHoldReportExpenseActionIDs?: string;
};

export default ApproveMoneyRequestParams;
10 changes: 10 additions & 0 deletions src/libs/API/parameters/PayMoneyRequestParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ type PayMoneyRequestParams = {
paymentMethodType: PaymentMethodType;
full: boolean;
amount?: number;
optimisticHoldReportID?: string;
optimisticHoldActionID?: string;
/**
* Stringified JSON object with type of following structure:
* Array<{
* optimisticReportActionID: string;
* oldReportActionID: string;
* }>
*/
optimisticHoldReportExpenseActionIDs?: string;
};

export default PayMoneyRequestParams;
231 changes: 231 additions & 0 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import * as Localize from '@libs/Localize';
import Navigation from '@libs/Navigation/Navigation';
import * as NextStepUtils from '@libs/NextStepUtils';
import {rand64} from '@libs/NumberUtils';
import Permissions from '@libs/Permissions';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as PolicyUtils from '@libs/PolicyUtils';
Expand All @@ -49,6 +50,7 @@ import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUR
import * as ReportUtils from '@libs/ReportUtils';
import * as SubscriptionUtils from '@libs/SubscriptionUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import {getCurrency, getTransaction} from '@libs/TransactionUtils';
import ViolationsUtils from '@libs/Violations/ViolationsUtils';
import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -6195,6 +6197,203 @@ function getSendMoneyParams(
};
}

type OptimisticHoldReportExpenseActionID = {
optimisticReportActionID: string;
oldReportActionID: string;
};

function getHoldReportActionsAndTransactions(reportID: string) {
const iouReportActions = ReportActionsUtils.getAllReportActions(reportID);
const holdReportActions: Array<OnyxTypes.ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU>> = [];
const holdTransactions: OnyxTypes.Transaction[] = [];

Object.values(iouReportActions).forEach((action) => {
const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? null : null;
const transaction = getTransaction(transactionID ?? '-1');

if (transaction?.comment?.hold) {
holdReportActions.push(action as OnyxTypes.ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU>);
holdTransactions.push(transaction);
}
});

return {holdReportActions, holdTransactions};
}

function getReportFromHoldRequestsOnyxData(
chatReport: OnyxTypes.Report,
iouReport: OnyxTypes.Report,
recipient: Participant,
): {
optimisticHoldReportID: string;
optimisticHoldActionID: string;
optimisticHoldReportExpenseActionIDs: OptimisticHoldReportExpenseActionID[];
optimisticData: OnyxUpdate[];
failureData: OnyxUpdate[];
} {
const {holdReportActions, holdTransactions} = getHoldReportActionsAndTransactions(iouReport.reportID);
const firstHoldTransaction = holdTransactions[0];

const optimisticExpenseReport = ReportUtils.buildOptimisticExpenseReport(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should create the optimistic data that matches with the data from our BE.
This caused #49754 more detailed:
#49754 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should cover a case where it's an IOU report. Otherwise, it will build a wrong optimistic report, causing LHN to display a wrong message. More details here #49274 (comment)

chatReport.reportID,
chatReport.policyID ?? iouReport.policyID ?? '',
recipient.accountID ?? 1,
(firstHoldTransaction?.amount ?? 0) * -1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This argument is for the total. We should have summed up the hold transactions. (Coming from #48760)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we edit the amount, the new amount is stored in modifiedAmount. This caused #49269

getCurrency(firstHoldTransaction),
false,
);
const optimisticExpenseReportPreview = ReportUtils.buildOptimisticReportPreview(chatReport, optimisticExpenseReport, '', firstHoldTransaction, optimisticExpenseReport.reportID);

const updateHeldReports: Record<string, Pick<OnyxTypes.Report, 'parentReportActionID' | 'parentReportID' | 'chatReportID'>> = {};
const addHoldReportActions: OnyxTypes.ReportActions = {};
const deleteHoldReportActions: Record<string, Pick<OnyxTypes.ReportAction, 'message'>> = {};
const optimisticHoldReportExpenseActionIDs: OptimisticHoldReportExpenseActionID[] = [];

holdReportActions.forEach((holdReportAction) => {
const originalMessage = ReportActionsUtils.getOriginalMessage(holdReportAction);

deleteHoldReportActions[holdReportAction.reportActionID] = {
message: [
{
deleted: DateUtils.getDBTime(),
type: CONST.REPORT.MESSAGE.TYPE.TEXT,
text: '',
},
],
};

const reportActionID = rand64();
addHoldReportActions[reportActionID] = {
...holdReportAction,
reportActionID,
originalMessage: {
...originalMessage,
IOUReportID: optimisticExpenseReport.reportID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update the lastVisibleActionCreated optimistically. Ref: #54655

},
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
};

const heldReport = getReportOrDraftReport(holdReportAction.childReportID);
if (heldReport) {
optimisticHoldReportExpenseActionIDs.push({optimisticReportActionID: reportActionID, oldReportActionID: holdReportAction.reportActionID});

updateHeldReports[`${ONYXKEYS.COLLECTION.REPORT}${heldReport.reportID}`] = {
parentReportActionID: reportActionID,
parentReportID: optimisticExpenseReport.reportID,
chatReportID: optimisticExpenseReport.reportID,
};
}
});

const updateHeldTransactions: Record<string, Pick<OnyxTypes.Transaction, 'reportID'>> = {};
holdTransactions.forEach((transaction) => {
updateHeldTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`] = {
reportID: optimisticExpenseReport.reportID,
};
});

const optimisticData: OnyxUpdate[] = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #52696, We need to reset pending action in all places if we use it in optimistic data.

{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
value: {
iouReportID: optimisticExpenseReport.reportID,
},
},
// add new optimistic expense report
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticExpenseReport.reportID}`,
value: optimisticExpenseReport,
},
// add preview report action to main chat
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
value: {
[optimisticExpenseReportPreview.reportActionID]: optimisticExpenseReportPreview,
},
},
// remove hold report actions from old iou report
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
value: deleteHoldReportActions,
},
// add hold report actions to new iou report
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticExpenseReport.reportID}`,
value: addHoldReportActions,
},
// update held reports with new parentReportActionID
{
onyxMethod: Onyx.METHOD.MERGE_COLLECTION,
key: `${ONYXKEYS.COLLECTION.REPORT}`,
value: updateHeldReports,
},
// update transactions with new iouReportID
{
onyxMethod: Onyx.METHOD.MERGE_COLLECTION,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}`,
value: updateHeldTransactions,
},
];

const bringReportActionsBack: Record<string, OnyxTypes.ReportAction> = {};
holdReportActions.forEach((reportAction) => {
bringReportActionsBack[reportAction.reportActionID] = reportAction;
});

const bringHeldTransactionsBack: Record<string, OnyxTypes.Transaction> = {};
holdTransactions.forEach((transaction) => {
bringHeldTransactionsBack[`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`] = transaction;
});

const failureData: OnyxUpdate[] = [
// remove added optimistic expense report
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticExpenseReport.reportID}`,
value: null,
},
// remove preview report action from the main chat
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
value: {
[optimisticExpenseReportPreview.reportActionID]: null,
},
},
// add hold report actions back to old iou report
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
value: bringReportActionsBack,
},
// remove hold report actions from the new iou report
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticExpenseReport.reportID}`,
value: null,
},
// add hold transactions back to old iou report
{
onyxMethod: Onyx.METHOD.MERGE_COLLECTION,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}`,
value: bringHeldTransactionsBack,
},
];

return {
optimisticData,
optimisticHoldActionID: optimisticExpenseReportPreview.reportActionID,
failureData,
optimisticHoldReportID: optimisticExpenseReport.reportID,
optimisticHoldReportExpenseActionIDs,
};
}

function getPayMoneyRequestParams(
chatReport: OnyxTypes.Report,
iouReport: OnyxTypes.Report,
Expand Down Expand Up @@ -6350,6 +6549,19 @@ function getPayMoneyRequestParams(
});
}

let optimisticHoldReportID;
let optimisticHoldActionID;
let optimisticHoldReportExpenseActionIDs;
if (!full) {
const holdReportOnyxData = getReportFromHoldRequestsOnyxData(chatReport, iouReport, recipient);

optimisticData.push(...holdReportOnyxData.optimisticData);
failureData.push(...holdReportOnyxData.failureData);
optimisticHoldReportID = holdReportOnyxData.optimisticHoldReportID;
optimisticHoldActionID = holdReportOnyxData.optimisticHoldActionID;
optimisticHoldReportExpenseActionIDs = JSON.stringify(holdReportOnyxData.optimisticHoldReportExpenseActionIDs);
}

return {
params: {
iouReportID: iouReport.reportID,
Expand All @@ -6358,6 +6570,9 @@ function getPayMoneyRequestParams(
paymentMethodType,
full,
amount: Math.abs(total),
optimisticHoldReportID,
optimisticHoldActionID,
optimisticHoldReportExpenseActionIDs,
},
optimisticData,
successData,
Expand Down Expand Up @@ -6610,10 +6825,26 @@ function approveMoneyRequest(expenseReport: OnyxEntry<OnyxTypes.Report>, full?:
});
}

let optimisticHoldReportID;
let optimisticHoldActionID;
let optimisticHoldReportExpenseActionIDs;
if (!full && !!chatReport && !!expenseReport) {
const holdReportOnyxData = getReportFromHoldRequestsOnyxData(chatReport, expenseReport, {accountID: expenseReport.ownerAccountID});

optimisticData.push(...holdReportOnyxData.optimisticData);
failureData.push(...holdReportOnyxData.failureData);
optimisticHoldReportID = holdReportOnyxData.optimisticHoldReportID;
optimisticHoldActionID = holdReportOnyxData.optimisticHoldActionID;
optimisticHoldReportExpenseActionIDs = JSON.stringify(holdReportOnyxData.optimisticHoldReportExpenseActionIDs);
}

const parameters: ApproveMoneyRequestParams = {
reportID: expenseReport?.reportID ?? '-1',
approvedReportActionID: optimisticApprovedReportAction.reportActionID,
full,
optimisticHoldReportID,
optimisticHoldActionID,
optimisticHoldReportExpenseActionIDs,
};

API.write(WRITE_COMMANDS.APPROVE_MONEY_REQUEST, parameters, {optimisticData, successData, failureData});
Expand Down
Loading