Skip to content

Commit d4f4e53

Browse files
authored
Merge pull request Expensify#64724 from DylanDylann/allow-remove-demo-transaction
Allow removing demo transactions
2 parents 1d14859 + b632a7c commit d4f4e53

11 files changed

Lines changed: 116 additions & 8 deletions

File tree

src/components/OfflineWithFeedback.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type ChildrenProps from '@src/types/utils/ChildrenProps';
1414
import {isEmptyObject} from '@src/types/utils/EmptyObject';
1515
import CustomStylesForChildrenProvider from './CustomStylesForChildrenProvider';
1616
import ErrorMessageRow from './ErrorMessageRow';
17+
import ImageSVG from './ImageSVG';
1718

1819
/**
1920
* This component should be used when we are using the offline pattern B (offline with feedback).
@@ -108,7 +109,7 @@ function OfflineWithFeedback({
108109
const applyStrikeThrough = useCallback(
109110
(childrenProp: React.ReactNode): React.ReactNode => {
110111
const strikeThroughChildren = mapChildrenFlat(childrenProp, (child) => {
111-
if (!React.isValidElement(child)) {
112+
if (!React.isValidElement(child) || child.type === ImageSVG) {
112113
return child;
113114
}
114115

src/libs/DebugUtils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
10691069
inserted: CONST.RED_BRICK_ROAD_PENDING_ACTION,
10701070
accountant: CONST.RED_BRICK_ROAD_PENDING_ACTION,
10711071
splitExpenses: CONST.RED_BRICK_ROAD_PENDING_ACTION,
1072+
isDemoTransaction: CONST.RED_BRICK_ROAD_PENDING_ACTION,
10721073
},
10731074
'string',
10741075
);
@@ -1107,6 +1108,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
11071108
splits: 'array',
11081109
dismissedViolations: 'object',
11091110
splitExpenses: 'array',
1111+
isDemoTransaction: 'boolean',
11101112
});
11111113
case 'accountant':
11121114
return validateObject<ObjectElement<Transaction, 'accountant'>>(value, {

src/libs/ReportSecondaryActionUtils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
getOriginalTransactionWithSplitInfo,
4848
hasReceipt as hasReceiptTransactionUtils,
4949
isCardTransaction as isCardTransactionUtils,
50+
isDemoTransaction,
5051
isDuplicate,
5152
isOnHold as isOnHoldTransactionUtils,
5253
isPending,
@@ -425,6 +426,10 @@ function isDeleteAction(report: Report, reportTransactions: Transaction[], repor
425426
const isSingleTransaction = reportTransactions.length === 1;
426427
const isInvoiceReport = isInvoiceReportUtils(report);
427428

429+
if (reportTransactions.length > 0 && reportTransactions.every((t) => isDemoTransaction(t))) {
430+
return true;
431+
}
432+
428433
if (isUnreported) {
429434
return isOwner;
430435
}

src/libs/ReportUtils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ import {
240240
hasViolation,
241241
hasWarningTypeViolation,
242242
isCardTransaction as isCardTransactionTransactionUtils,
243+
isDemoTransaction,
243244
isDistanceRequest,
244245
isExpensifyCardTransaction,
245246
isFetchingWaypointsFromServer,
@@ -372,6 +373,7 @@ type BuildOptimisticIOUReportActionParams = {
372373
created?: string;
373374
linkedExpenseReportAction?: OnyxEntry<ReportAction>;
374375
isPersonalTrackingExpense?: boolean;
376+
reportActionID?: string;
375377
};
376378

377379
type OptimisticIOUReportAction = Pick<
@@ -2500,13 +2502,17 @@ function canDeleteCardTransactionByLiabilityType(transaction: OnyxEntry<Transact
25002502
* Can only delete if the author is this user and the action is an ADD_COMMENT action or an IOU action in an unsettled report, or if the user is a
25012503
* policy admin
25022504
*/
2503-
function canDeleteReportAction(reportAction: OnyxInputOrEntry<ReportAction>, reportID: string | undefined, iouTransaction?: OnyxEntry<Transaction>): boolean {
2505+
function canDeleteReportAction(reportAction: OnyxInputOrEntry<ReportAction>, reportID: string | undefined, transaction: OnyxEntry<Transaction> | undefined): boolean {
25042506
const report = getReportOrDraftReport(reportID);
25052507
const isActionOwner = reportAction?.actorAccountID === currentUserAccountID;
25062508
const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`] ?? null;
25072509

2510+
if (isDemoTransaction(transaction)) {
2511+
return true;
2512+
}
2513+
25082514
if (isMoneyRequestAction(reportAction)) {
2509-
const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(iouTransaction);
2515+
const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(transaction);
25102516
// For now, users cannot delete split actions
25112517
const isSplitAction = getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT;
25122518

@@ -6247,6 +6253,7 @@ function buildOptimisticIOUReportAction(params: BuildOptimisticIOUReportActionPa
62476253
created = DateUtils.getDBTime(),
62486254
linkedExpenseReportAction,
62496255
isPersonalTrackingExpense = false,
6256+
reportActionID,
62506257
} = params;
62516258

62526259
const IOUReportID = isPersonalTrackingExpense ? undefined : iouReportID || generateReportID();
@@ -6306,7 +6313,7 @@ function buildOptimisticIOUReportAction(params: BuildOptimisticIOUReportActionPa
63066313
automatic: false,
63076314
isAttachmentOnly: false,
63086315
originalMessage,
6309-
reportActionID: rand64(),
6316+
reportActionID: reportActionID ?? rand64(),
63106317
shouldShow: true,
63116318
created,
63126319
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,

src/libs/TransactionUtils/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ type BuildOptimisticTransactionParams = {
9595
existingTransaction?: OnyxEntry<Transaction>;
9696
policy?: OnyxEntry<Policy>;
9797
transactionParams: TransactionParams;
98+
isDemoTransactionParam?: boolean;
9899
};
99100

100101
let allTransactions: OnyxCollection<Transaction> = {};
@@ -247,7 +248,7 @@ function isPendingCardOrScanningTransaction(transaction: OnyxEntry<Transaction>)
247248
* it's transactionID match what was already generated.
248249
*/
249250
function buildOptimisticTransaction(params: BuildOptimisticTransactionParams): Transaction {
250-
const {originalTransactionID = '', existingTransactionID, existingTransaction, policy, transactionParams} = params;
251+
const {originalTransactionID = '', existingTransactionID, existingTransaction, policy, transactionParams, isDemoTransactionParam} = params;
251252
const {
252253
amount,
253254
currency,
@@ -275,6 +276,9 @@ function buildOptimisticTransaction(params: BuildOptimisticTransactionParams): T
275276
const transactionID = existingTransactionID ?? rand64();
276277

277278
const commentJSON: Comment = {comment, attendees};
279+
if (isDemoTransactionParam) {
280+
commentJSON.isDemoTransaction = true;
281+
}
278282
if (source) {
279283
commentJSON.source = source;
280284
}
@@ -337,6 +341,10 @@ function hasReceiptSource(transaction: OnyxInputOrEntry<Transaction>): boolean {
337341
return !!transaction?.receipt?.source;
338342
}
339343

344+
function isDemoTransaction(transaction: OnyxInputOrEntry<Transaction>): boolean {
345+
return transaction?.comment?.isDemoTransaction ?? false;
346+
}
347+
340348
function isMerchantMissing(transaction: OnyxEntry<Transaction>) {
341349
if (transaction?.modifiedMerchant && transaction.modifiedMerchant !== '') {
342350
return transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
@@ -1801,6 +1809,7 @@ export {
18011809
getTransactionPendingAction,
18021810
isTransactionPendingDelete,
18031811
createUnreportedExpenseSections,
1812+
isDemoTransaction,
18041813
shouldShowViolation,
18051814
isUnreportedAndHasInvalidDistanceRateTransaction,
18061815
};

src/libs/actions/IOU.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,7 @@ function buildOnyxDataForTestDriveIOU(testDriveIOUParams: BuildOnyxDataForTestDr
13451345
paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE,
13461346
iouReportID: testDriveIOUParams.iouOptimisticParams.report.reportID,
13471347
transactionID: testDriveIOUParams.transaction.transactionID,
1348+
reportActionID: testDriveIOUParams.iouOptimisticParams.action.reportActionID,
13481349
});
13491350

13501351
const text = Localize.translateLocal('testDrive.employeeInviteMessage', {name: personalDetailsList?.[userAccountID]?.firstName ?? ''});
@@ -1605,6 +1606,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR
16051606
paymentType: isScanRequest && !isTestReceipt ? undefined : CONST.IOU.PAYMENT_TYPE.ELSEWHERE,
16061607
iouReportID: iou.report.reportID,
16071608
transactionID: transaction.transactionID,
1609+
reportActionID: iou.action.reportActionID,
16081610
});
16091611

16101612
optimisticData.push(
@@ -3387,6 +3389,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma
33873389
billable,
33883390
pendingFields: isDistanceRequest ? {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : undefined,
33893391
},
3392+
isDemoTransactionParam: isSelectedManagerMcTest(participant.login) || transactionParams.receipt?.isTestDriveReceipt,
33903393
});
33913394

33923395
const optimisticPolicyRecentlyUsedCategories = buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, category);

src/pages/ReportDetailsPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import {
9393
shouldUseFullTitleToDisplay,
9494
} from '@libs/ReportUtils';
9595
import StringUtils from '@libs/StringUtils';
96+
import {isDemoTransaction} from '@libs/TransactionUtils';
9697
import {deleteMoneyRequest, deleteTrackExpense, getNavigationUrlAfterTrackExpenseDelete, getNavigationUrlOnMoneyRequestDelete} from '@userActions/IOU';
9798
import {
9899
clearAvatarErrors,
@@ -245,7 +246,6 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
245246
});
246247

247248
const isPrivateNotesFetchTriggered = reportMetadata?.isLoadingPrivateNotes !== undefined;
248-
249249
const requestParentReportAction = useMemo(() => {
250250
// 2. MoneyReport case
251251
if (caseID === CASES.MONEY_REPORT) {
@@ -281,7 +281,8 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail
281281
const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : undefined;
282282
const [iouTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${iouTransactionID}`, {canBeMissing: true});
283283
const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(iouTransaction);
284-
const shouldShowDeleteButton = shouldShowTaskDeleteButton || (canDeleteRequest && isCardTransactionCanBeDeleted);
284+
const shouldShowDeleteButton = shouldShowTaskDeleteButton || (canDeleteRequest && isCardTransactionCanBeDeleted) || isDemoTransaction(iouTransaction);
285+
285286
useEffect(() => {
286287
if (canDeleteRequest) {
287288
return;

src/types/onyx/Transaction.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ type Comment = {
6969
/** Whether the transaction comment is loading */
7070
isLoading?: boolean;
7171

72+
/** Whether the transaction comment is a demo transaction */
73+
isDemoTransaction?: boolean;
74+
7275
/** Type of the transaction */
7376
type?: ValueOf<typeof CONST.TRANSACTION.TYPE>;
7477

tests/perf-test/ReportUtils.perf-test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,11 @@ describe('ReportUtils', () => {
8989

9090
test('[ReportUtils] canDeleteReportAction on 1k reports and policies', async () => {
9191
const reportID = '1';
92+
const transaction = createRandomTransaction(1);
9293
const reportAction = {...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT} as unknown as ReportAction;
9394

9495
await waitForBatchedUpdates();
95-
await measureFunction(() => canDeleteReportAction(reportAction, reportID));
96+
await measureFunction(() => canDeleteReportAction(reportAction, reportID, transaction));
9697
});
9798

9899
test('[ReportUtils] getReportRecipientAccountID on 1k participants', async () => {

tests/unit/ReportSecondaryActionUtilsTest.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,38 @@ describe('getSecondaryAction', () => {
10691069
expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DELETE)).toBe(false);
10701070
});
10711071

1072+
it('include DELETE option for demo transaction', async () => {
1073+
const report = {
1074+
reportID: REPORT_ID,
1075+
type: CONST.REPORT.TYPE.EXPENSE,
1076+
ownerAccountID: EMPLOYEE_ACCOUNT_ID,
1077+
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
1078+
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
1079+
} as unknown as Report;
1080+
1081+
const TRANSACTION_ID = 'TRANSACTION_ID';
1082+
1083+
const transaction = {
1084+
transactionID: TRANSACTION_ID,
1085+
reportID: REPORT_ID,
1086+
comment: {
1087+
isDemoTransaction: true,
1088+
},
1089+
} as unknown as Transaction;
1090+
1091+
const policy = {
1092+
id: POLICY_ID,
1093+
approvalMode: CONST.POLICY.APPROVAL_MODE.BASIC,
1094+
approver: APPROVER_EMAIL,
1095+
} as unknown as Policy;
1096+
1097+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
1098+
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy);
1099+
1100+
const result = getSecondaryReportActions({report, chatReport, reportTransactions: [transaction], violations: {}, policy});
1101+
expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DELETE)).toBe(true);
1102+
});
1103+
10721104
it('include EXPORT_TO_ACCOUNTING option for settled expense report', async () => {
10731105
const report = {
10741106
reportID: REPORT_ID,

0 commit comments

Comments
 (0)