Skip to content

Commit bbedf30

Browse files
committed
Merge branch 'main' into fix/45733-design-improvements
2 parents aca41e1 + 1daaabf commit bbedf30

File tree

11 files changed

+126
-11
lines changed

11 files changed

+126
-11
lines changed

docs/articles/expensify-classic/reports/Assign-report-approvers-to-specific-employees.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ To assign a report approver to a specific member of your workspace,
2626
- Approves To: Determines who must approve a report after this user has approved it. This creates an approval chain. When added, a note is visible in the Details column of the Workspace Members table. If blank, the user is a “final approver.”
2727
- If Report Total is Over $X then Approves To: These two fields add an extra approver if the report total exceeds the set amount. When added, a note is visible in the Details column of the Workspace Members table.
2828

29-
[Image coming soon]
29+
![Image of user approval settings]({{site.url}}/assets/images/Approves_To.png){:width="100%"}
3030

3131
## Example
3232

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type StartIssueNewCardFlowParams = {
2+
policyID: string;
3+
};
4+
5+
export default StartIssueNewCardFlowParams;

src/libs/API/parameters/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,4 @@ export type {default as CopyExistingPolicyConnectionParams} from './CopyExisting
268268
export type {default as ExportSearchItemsToCSVParams} from './ExportSearchItemsToCSVParams';
269269
export type {default as UpdateExpensifyCardLimitParams} from './UpdateExpensifyCardLimitParams';
270270
export type {CreateWorkspaceApprovalParams, UpdateWorkspaceApprovalParams, RemoveWorkspaceApprovalParams} from './WorkspaceApprovalParams';
271+
export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFlowParams';

src/libs/API/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ const READ_COMMANDS = {
703703
SEARCH: 'Search',
704704
OPEN_SUBSCRIPTION_PAGE: 'OpenSubscriptionPage',
705705
OPEN_DRAFT_DISTANCE_EXPENSE: 'OpenDraftDistanceExpense',
706+
START_ISSUE_NEW_CARD_FLOW: 'StartIssueNewCardFlow',
706707
} as const;
707708

708709
type ReadCommand = ValueOf<typeof READ_COMMANDS>;
@@ -758,6 +759,7 @@ type ReadCommandParameters = {
758759
[READ_COMMANDS.SEARCH]: Parameters.SearchParams;
759760
[READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE]: null;
760761
[READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE]: null;
762+
[READ_COMMANDS.START_ISSUE_NEW_CARD_FLOW]: Parameters.StartIssueNewCardFlowParams;
761763
};
762764

763765
const SIDE_EFFECT_REQUEST_COMMANDS = {

src/libs/ReportUtils.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1897,6 +1897,13 @@ function getPersonalDetailsForAccountID(accountID: number): Partial<PersonalDeta
18971897
return allPersonalDetails?.[accountID] ?? defaultDetails;
18981898
}
18991899

1900+
/**
1901+
* Returns the personal details or a default object if the personal details are not available.
1902+
*/
1903+
function getPersonalDetailsOrDefault(personalDetails: Partial<PersonalDetails> | undefined | null): Partial<PersonalDetails> {
1904+
return personalDetails ?? {isOptimisticPersonalDetail: true};
1905+
}
1906+
19001907
const hiddenTranslation = Localize.translateLocal('common.hidden');
19011908

19021909
const phoneNumberCache: Record<string, string> = {};
@@ -1909,7 +1916,7 @@ function getDisplayNameForParticipant(accountID?: number, shouldUseShortForm = f
19091916
return '';
19101917
}
19111918

1912-
const personalDetails = getPersonalDetailsForAccountID(accountID);
1919+
const personalDetails = getPersonalDetailsOrDefault(allPersonalDetails?.[accountID]);
19131920
if (!personalDetails) {
19141921
return '';
19151922
}

src/libs/ValidationUtils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ function isValidCompanyName(name: string) {
338338
}
339339

340340
function isValidReportName(name: string) {
341-
return name.trim().length <= CONST.REPORT_NAME_LIMIT;
341+
return new Blob([name.trim()]).size <= CONST.REPORT_NAME_LIMIT;
342342
}
343343

344344
/**

src/libs/actions/Card.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import type {
77
ReportVirtualExpensifyCardFraudParams,
88
RequestReplacementExpensifyCardParams,
99
RevealExpensifyCardDetailsParams,
10+
StartIssueNewCardFlowParams,
1011
UpdateExpensifyCardLimitParams,
1112
} from '@libs/API/parameters';
12-
import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
13+
import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
1314
import * as ErrorUtils from '@libs/ErrorUtils';
1415
import * as NetworkStore from '@libs/Network/NetworkStore';
1516
import CONST from '@src/CONST';
@@ -372,6 +373,14 @@ function updateExpensifyCardLimit(policyID: string, cardID: number, newLimit: nu
372373
API.write(WRITE_COMMANDS.UPDATE_EXPENSIFY_CARD_LIMIT, parameters, {optimisticData, successData, failureData});
373374
}
374375

376+
function startIssueNewCardFlow(policyID: string) {
377+
const parameters: StartIssueNewCardFlowParams = {
378+
policyID,
379+
};
380+
381+
API.read(READ_COMMANDS.START_ISSUE_NEW_CARD_FLOW, parameters);
382+
}
383+
375384
export {
376385
requestReplacementExpensifyCard,
377386
activatePhysicalExpensifyCard,
@@ -383,5 +392,6 @@ export {
383392
clearIssueNewCardFlow,
384393
updateExpensifyCardLimit,
385394
updateSettlementAccount,
395+
startIssueNewCardFlow,
386396
};
387397
export type {ReplacementReason};

src/libs/actions/IOU.ts

+90-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {format} from 'date-fns';
22
import {fastMerge, Str} from 'expensify-common';
33
import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxInputValue, OnyxUpdate} from 'react-native-onyx';
44
import Onyx from 'react-native-onyx';
5-
import type {ValueOf} from 'type-fest';
5+
import type {PartialDeep, ValueOf} from 'type-fest';
66
import ReceiptGeneric from '@assets/images/receipt-generic.png';
77
import * as API from '@libs/API';
88
import type {
@@ -7677,11 +7677,98 @@ function mergeDuplicates(params: TransactionMergeParams) {
76777677
};
76787678
});
76797679

7680+
const duplicateTransactionTotals = params.transactionIDList.reduce((total, id) => {
7681+
const duplicateTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`];
7682+
if (!duplicateTransaction) {
7683+
return total;
7684+
}
7685+
return total + duplicateTransaction.amount;
7686+
}, 0);
7687+
7688+
const expenseReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${params.reportID}`];
7689+
const expenseReportOptimisticData: OnyxUpdate = {
7690+
onyxMethod: Onyx.METHOD.MERGE,
7691+
key: `${ONYXKEYS.COLLECTION.REPORT}${params.reportID}`,
7692+
value: {
7693+
total: (expenseReport?.total ?? 0) - duplicateTransactionTotals,
7694+
},
7695+
};
7696+
const expenseReportFailureData: OnyxUpdate = {
7697+
onyxMethod: Onyx.METHOD.MERGE,
7698+
key: `${ONYXKEYS.COLLECTION.REPORT}${params.reportID}`,
7699+
value: {
7700+
total: expenseReport?.total,
7701+
},
7702+
};
7703+
7704+
const iouActionsToDelete = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`] ?? {})?.filter(
7705+
(reportAction): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU> => {
7706+
if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) {
7707+
return false;
7708+
}
7709+
const message = ReportActionsUtils.getOriginalMessage(reportAction);
7710+
if (!message?.IOUTransactionID) {
7711+
return false;
7712+
}
7713+
return params.transactionIDList.includes(message.IOUTransactionID);
7714+
},
7715+
);
7716+
7717+
const deletedTime = DateUtils.getDBTime();
7718+
const expenseReportActionsOptimisticData: OnyxUpdate = {
7719+
onyxMethod: Onyx.METHOD.MERGE,
7720+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`,
7721+
value: iouActionsToDelete.reduce<Record<string, PartialDeep<ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU>>>>((val, reportAction) => {
7722+
// eslint-disable-next-line no-param-reassign
7723+
val[reportAction.reportActionID] = {
7724+
originalMessage: {
7725+
deleted: deletedTime,
7726+
},
7727+
...(Array.isArray(reportAction.message) &&
7728+
!!reportAction.message[0] && {
7729+
message: [
7730+
{
7731+
...reportAction.message[0],
7732+
deleted: deletedTime,
7733+
},
7734+
...reportAction.message.slice(1),
7735+
],
7736+
}),
7737+
...(!Array.isArray(reportAction.message) && {
7738+
message: {
7739+
deleted: deletedTime,
7740+
},
7741+
}),
7742+
};
7743+
return val;
7744+
}, {}),
7745+
};
7746+
const expenseReportActionsFailureData: OnyxUpdate = {
7747+
onyxMethod: Onyx.METHOD.MERGE,
7748+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`,
7749+
value: iouActionsToDelete.reduce<Record<string, NullishDeep<PartialDeep<ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU>>>>>((val, reportAction) => {
7750+
// eslint-disable-next-line no-param-reassign
7751+
val[reportAction.reportActionID] = {
7752+
originalMessage: {
7753+
deleted: null,
7754+
},
7755+
message: reportAction.message,
7756+
};
7757+
return val;
7758+
}, {}),
7759+
};
7760+
76807761
const optimisticData: OnyxUpdate[] = [];
76817762
const failureData: OnyxUpdate[] = [];
76827763

7683-
optimisticData.push(optimisticTransactionData, ...optimisticTransactionDuplicatesData, ...optimisticTransactionViolations);
7684-
failureData.push(failureTransactionData, ...failureTransactionDuplicatesData, ...failureTransactionViolations);
7764+
optimisticData.push(
7765+
optimisticTransactionData,
7766+
...optimisticTransactionDuplicatesData,
7767+
...optimisticTransactionViolations,
7768+
expenseReportOptimisticData,
7769+
expenseReportActionsOptimisticData,
7770+
);
7771+
failureData.push(failureTransactionData, ...failureTransactionDuplicatesData, ...failureTransactionViolations, expenseReportFailureData, expenseReportActionsFailureData);
76857772

76867773
API.write(WRITE_COMMANDS.TRANSACTION_MERGE, params, {optimisticData, failureData});
76877774
}

src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) {
7373

7474
const submit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.EDIT_EXPENSIFY_CARD_LIMIT_FORM>) => {
7575
const currentLimit = card.nameValuePairs?.limit ?? 0;
76-
const currentSpend = currentLimit - card.availableSpend;
76+
const currentSpend = currentLimit - (card.availableSpend ?? 0);
7777
const newLimit = Number(values[INPUT_IDS.LIMIT]) * 100;
7878
const newAvailableSpend = newLimit - currentSpend;
7979

src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import React from 'react';
1+
import React, {useEffect} from 'react';
22
import {useOnyx} from 'react-native-onyx';
33
import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
44
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
5+
import * as Card from '@userActions/Card';
56
import CONST from '@src/CONST';
67
import ONYXKEYS from '@src/ONYXKEYS';
78
import AssigneeStep from './AssigneeStep';
@@ -17,7 +18,9 @@ function IssueNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) {
1718
const {currentStep} = issueNewCard ?? {};
1819

1920
// TODO: add logic to skip Assignee step when the flow is started from the member's profile page
20-
// TODO: StartIssueNewCardFlow call to API
21+
useEffect(() => {
22+
Card.startIssueNewCardFlow(policy?.id ?? '-1');
23+
}, [policy?.id]);
2124

2225
switch (currentStep) {
2326
case CONST.EXPENSIFY_CARD.STEP.ASSIGNEE:

src/types/onyx/Card.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type Card = {
1414
bank: string;
1515

1616
/** Available amount to spend */
17-
availableSpend: number;
17+
availableSpend?: number;
1818

1919
/** Domain name */
2020
domainName: string;

0 commit comments

Comments
 (0)