diff --git a/assets/images/circular-arrow-backwards.svg b/assets/images/circular-arrow-backwards.svg new file mode 100644 index 000000000000..209c0aea5fa7 --- /dev/null +++ b/assets/images/circular-arrow-backwards.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index 8ecdadefc4e9..2e2608d773ae 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -719,7 +719,7 @@ const CONST = { TASK_EDITED: 'TASKEDITED', TASK_REOPENED: 'TASKREOPENED', TRIPPREVIEW: 'TRIPPREVIEW', - UNAPPROVED: 'UNAPPROVED', // OldDot Action + UNAPPROVED: 'UNAPPROVED', UNHOLD: 'UNHOLD', UNSHARE: 'UNSHARE', // OldDot Action UPDATE_GROUP_CHAT_MEMBER_ROLE: 'UPDATEGROUPCHATMEMBERROLE', @@ -2335,6 +2335,7 @@ const CONST = { PRIVATE_NOTES: 'privateNotes', DELETE: 'delete', MARK_AS_INCOMPLETE: 'markAsIncomplete', + UNAPPROVE: 'unapprove', }, EDIT_REQUEST_FIELD: { AMOUNT: 'amount', diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index a0d7a5cb8883..487df5594212 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -42,6 +42,7 @@ import ChatBubbles from '@assets/images/chatbubbles.svg'; import CheckCircle from '@assets/images/check-circle.svg'; import CheckmarkCircle from '@assets/images/checkmark-circle.svg'; import Checkmark from '@assets/images/checkmark.svg'; +import CircularArrowBackwards from '@assets/images/circular-arrow-backwards.svg'; import Close from '@assets/images/close.svg'; import ClosedSign from '@assets/images/closed-sign.svg'; import Coins from '@assets/images/coins.svg'; @@ -201,6 +202,7 @@ export { Wrench, BackArrow, Bank, + CircularArrowBackwards, Bill, Bell, BellSlash, diff --git a/src/languages/en.ts b/src/languages/en.ts index e3e080f26201..a72e07701c8a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -814,6 +814,11 @@ export default { removed: 'removed', transactionPending: 'Transaction pending.', chooseARate: ({unit}: ReimbursementRateParams) => `Select a workspace reimbursement rate per ${unit}`, + unapprove: 'Unapprove', + unapproveReport: 'Unapprove report', + headsUp: 'Heads up!', + unapproveWithIntegrationWarning: (accountingIntegration: string) => + `This report has already been exported to ${accountingIntegration}. Changes to this report in Expensify may lead to data discrepancies and Expensify Card reconciliation issues. Are you sure you want to unapprove this report?`, }, notificationPreferencesPage: { header: 'Notification preferences', @@ -2759,6 +2764,21 @@ export default { xero: 'Xero', netsuite: 'NetSuite', intacct: 'Sage Intacct', + connectionName: (integration: ConnectionName) => { + switch (integration) { + case CONST.POLICY.CONNECTIONS.NAME.QBO: + return 'Quickbooks Online'; + case CONST.POLICY.CONNECTIONS.NAME.XERO: + return 'Xero'; + case CONST.POLICY.CONNECTIONS.NAME.NETSUITE: + return 'NetSuite'; + case CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT: + return 'Sage Intacct'; + default: { + return ''; + } + } + }, setup: 'Connect', lastSync: (relativeDate: string) => `Last synced ${relativeDate}`, import: 'Import', diff --git a/src/languages/es.ts b/src/languages/es.ts index 9e104ca9b1bb..c88b273ae05d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -817,6 +817,11 @@ export default { removed: 'eliminó', transactionPending: 'Transacción pendiente.', chooseARate: ({unit}: ReimbursementRateParams) => `Selecciona una tasa de reembolso por ${unit} del espacio de trabajo`, + unapprove: 'Desaprobar', + unapproveReport: 'Anular la aprobación del informe', + headsUp: 'Atención!', + unapproveWithIntegrationWarning: (accountingIntegration: string) => + `Este informe ya se ha exportado a ${accountingIntegration}. Los cambios realizados en este informe en Expensify pueden provocar discrepancias en los datos y problemas de conciliación de la tarjeta Expensify. ¿Está seguro de que desea anular la aprobación de este informe?`, }, notificationPreferencesPage: { header: 'Preferencias de avisos', @@ -2741,6 +2746,21 @@ export default { xero: 'Xero', netsuite: 'NetSuite', intacct: 'Sage Intacct', + connectionName: (integration: ConnectionName) => { + switch (integration) { + case CONST.POLICY.CONNECTIONS.NAME.QBO: + return 'Quickbooks Online'; + case CONST.POLICY.CONNECTIONS.NAME.XERO: + return 'Xero'; + case CONST.POLICY.CONNECTIONS.NAME.NETSUITE: + return 'NetSuite'; + case CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT: + return 'Sage Intacct'; + default: { + return ''; + } + } + }, setup: 'Configurar', lastSync: (relativeDate: string) => `Recién sincronizado ${relativeDate}`, import: 'Importar', diff --git a/src/libs/API/parameters/UnapproveExpenseReportParams.ts b/src/libs/API/parameters/UnapproveExpenseReportParams.ts new file mode 100644 index 000000000000..ba25424aeda6 --- /dev/null +++ b/src/libs/API/parameters/UnapproveExpenseReportParams.ts @@ -0,0 +1,6 @@ +type UnapproveExpenseReportParams = { + reportID: string; + reportActionID: string; +}; + +export default UnapproveExpenseReportParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index a49cb68fd04f..096e59f399f6 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -153,6 +153,7 @@ export type {default as CreateDistanceRequestParams} from './CreateDistanceReque export type {default as StartSplitBillParams} from './StartSplitBillParams'; export type {default as SendMoneyParams} from './SendMoneyParams'; export type {default as ApproveMoneyRequestParams} from './ApproveMoneyRequestParams'; +export type {default as UnapproveExpenseReportParams} from './UnapproveExpenseReportParams'; export type {default as EditMoneyRequestParams} from './EditMoneyRequestParams'; export type {default as ReplaceReceiptParams} from './ReplaceReceiptParams'; export type {default as SubmitReportParams} from './SubmitReportParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index dae65e7792bc..a1a5b91d7d99 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -172,6 +172,7 @@ const WRITE_COMMANDS = { SEND_MONEY_ELSEWHERE: 'SendMoneyElsewhere', SEND_MONEY_WITH_WALLET: 'SendMoneyWithWallet', APPROVE_MONEY_REQUEST: 'ApproveMoneyRequest', + UNAPPROVE_EXPENSE_REPORT: 'UnapproveExpenseReport', EDIT_MONEY_REQUEST: 'EditMoneyRequest', REPLACE_RECEIPT: 'ReplaceReceipt', SUBMIT_REPORT: 'SubmitReport', @@ -432,6 +433,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SEND_MONEY_ELSEWHERE]: Parameters.SendMoneyParams; [WRITE_COMMANDS.SEND_MONEY_WITH_WALLET]: Parameters.SendMoneyParams; [WRITE_COMMANDS.APPROVE_MONEY_REQUEST]: Parameters.ApproveMoneyRequestParams; + [WRITE_COMMANDS.UNAPPROVE_EXPENSE_REPORT]: Parameters.UnapproveExpenseReportParams; [WRITE_COMMANDS.EDIT_MONEY_REQUEST]: Parameters.EditMoneyRequestParams; [WRITE_COMMANDS.REPLACE_RECEIPT]: Parameters.ReplaceReceiptParams; [WRITE_COMMANDS.SUBMIT_REPORT]: Parameters.SubmitReportParams; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index e8e7b89e34f2..e8a6e8e58408 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1145,7 +1145,6 @@ function isOldDotReportAction(action: ReportAction | OldDotReportAction) { CONST.REPORT.ACTIONS.TYPE.SHARE, CONST.REPORT.ACTIONS.TYPE.STRIPE_PAID, CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL, - CONST.REPORT.ACTIONS.TYPE.UNAPPROVED, CONST.REPORT.ACTIONS.TYPE.UNSHARE, CONST.REPORT.ACTIONS.TYPE.DELETED_ACCOUNT, CONST.REPORT.ACTIONS.TYPE.DONATION, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 342e2439ed66..0493babdb806 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -187,6 +187,11 @@ type OptimisticApprovedReportAction = Pick< 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'isAttachment' | 'originalMessage' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction' >; +type OptimisticUnapprovedReportAction = Pick< + ReportAction, + 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'isAttachment' | 'originalMessage' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction' +>; + type OptimisticSubmittedReportAction = Pick< ReportAction, | 'actionName' @@ -778,6 +783,13 @@ function isReportApproved(reportOrID: OnyxInputOrEntry | string, parentR return report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && report?.statusNum === CONST.REPORT.STATUS_NUM.APPROVED; } +/** + * Checks if the supplied report has been manually reimbursed + */ +function isReportManuallyReimbursed(report: OnyxEntry): boolean { + return report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && report?.statusNum === CONST.REPORT.STATUS_NUM.REIMBURSED; +} + /** * Checks if the supplied report is an expense report in Open state and status. */ @@ -4039,6 +4051,9 @@ function getIOUReportActionMessage(iouReportID: string, type: string, total: num case CONST.REPORT.ACTIONS.TYPE.APPROVED: iouMessage = `approved ${amount}`; break; + case CONST.REPORT.ACTIONS.TYPE.UNAPPROVED: + iouMessage = `unapproved ${amount}`; + break; case CONST.IOU.REPORT_ACTION_TYPE.CREATE: iouMessage = `submitted ${amount}${comment && ` for ${comment}`}`; break; @@ -4201,6 +4216,36 @@ function buildOptimisticApprovedReportAction(amount: number, currency: string, e }; } +/** + * Builds an optimistic APPROVED report action with a randomly generated reportActionID. + */ +function buildOptimisticUnapprovedReportAction(amount: number, currency: string, expenseReportID: string): OptimisticUnapprovedReportAction { + return { + actionName: CONST.REPORT.ACTIONS.TYPE.UNAPPROVED, + actorAccountID: currentUserAccountID, + automatic: false, + avatar: getCurrentUserAvatar(), + isAttachment: false, + originalMessage: { + amount, + currency, + expenseReportID, + }, + message: getIOUReportActionMessage(expenseReportID, CONST.REPORT.ACTIONS.TYPE.UNAPPROVED, Math.abs(amount), '', currency), + person: [ + { + style: 'strong', + text: getCurrentUserDisplayNameOrEmail(), + type: 'TEXT', + }, + ], + reportActionID: NumberUtils.rand64(), + shouldShow: true, + created: DateUtils.getDBTime(), + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }; +} + /** * Builds an optimistic MOVED report action with a randomly generated reportActionID. * This action is used when we move reports across workspaces. @@ -7039,6 +7084,7 @@ export { areAllRequestsBeingSmartScanned, buildOptimisticAddCommentReportAction, buildOptimisticApprovedReportAction, + buildOptimisticUnapprovedReportAction, buildOptimisticCancelPaymentReportAction, buildOptimisticChangedTaskAssigneeReportAction, buildOptimisticChatReport, @@ -7251,6 +7297,7 @@ export { isPublicAnnounceRoom, isPublicRoom, isReportApproved, + isReportManuallyReimbursed, isReportDataReady, isReportFieldDisabled, isReportFieldOfTypeTitle, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 48c70021cacc..9fc8f40ae770 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -24,6 +24,7 @@ import type { StartSplitBillParams, SubmitReportParams, TrackExpenseParams, + UnapproveExpenseReportParams, UpdateMoneyRequestParams, } from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; @@ -6359,6 +6360,95 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?: API.write(WRITE_COMMANDS.APPROVE_MONEY_REQUEST, parameters, {optimisticData, successData, failureData}); } +function unapproveExpenseReport(expenseReport: OnyxEntry) { + if (isEmptyObject(expenseReport)) { + return; + } + + const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; + + const optimisticUnapprovedReportAction = ReportUtils.buildOptimisticUnapprovedReportAction(expenseReport.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID); + const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.SUBMITTED); + + const optimisticReportActionData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + value: { + [optimisticUnapprovedReportAction.reportActionID]: { + ...(optimisticUnapprovedReportAction as OnyxTypes.ReportAction), + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }; + const optimisticIOUReportData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, + value: { + ...expenseReport, + lastMessageText: ReportActionsUtils.getReportActionText(optimisticUnapprovedReportAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticUnapprovedReportAction), + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + pendingFields: { + partial: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }; + + const optimisticNextStepData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: optimisticNextStep, + }; + + const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionData, optimisticNextStepData]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + value: { + [optimisticUnapprovedReportAction.reportActionID]: { + pendingAction: null, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, + value: { + pendingFields: { + partial: null, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + value: { + [optimisticUnapprovedReportAction.reportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: currentNextStep, + }, + ]; + + const parameters: UnapproveExpenseReportParams = { + reportID: expenseReport.reportID, + reportActionID: optimisticUnapprovedReportAction.reportActionID, + }; + + API.write(WRITE_COMMANDS.UNAPPROVE_EXPENSE_REPORT, parameters, {optimisticData, successData, failureData}); +} + function submitReport(expenseReport: OnyxTypes.Report) { if (expenseReport.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(expenseReport.policyID)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(expenseReport.policyID)); @@ -7016,6 +7106,7 @@ function getIOURequestPolicyID(transaction: OnyxEntry, re export { approveMoneyRequest, + unapproveExpenseReport, canApproveIOU, canIOUBePaid, cancelPayment, diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 58a0fe1a80b8..c6e25bcaa70a 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -21,6 +21,7 @@ import PromotedActionsBar, {PromotedActions} from '@components/PromotedActionsBa import RoomHeaderAvatars from '@components/RoomHeaderAvatars'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -107,6 +108,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const [isUnapproveModalVisible, setIsUnapproveModalVisible] = useState(false); const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '-1'}`], [policies, report?.policyID]); const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]); const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID ?? '-1', policies), [report?.policyID, policies]); @@ -179,7 +181,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction); - const moneyRequestReport = useMemo(() => { + const moneyRequestReport: OnyxEntry = useMemo(() => { if (caseID === CASES.MONEY_REQUEST) { return parentReport; } @@ -197,6 +199,11 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD const canDeleteRequest = isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || isSelfDMTrackExpenseReport) && !isDeletedParentAction; const shouldShowDeleteButton = shouldShowTaskDeleteButton || canDeleteRequest; + const canUnapproveRequest = + ReportUtils.isMoneyRequestReport(moneyRequestReport) && + (ReportUtils.isReportManager(moneyRequestReport) || isPolicyAdmin) && + (ReportUtils.isReportApproved(moneyRequestReport) || ReportUtils.isReportManuallyReimbursed(moneyRequestReport)); + useEffect(() => { if (canDeleteRequest) { return; @@ -224,6 +231,15 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD Report.leaveGroupChat(report.reportID); }, [isChatRoom, isPolicyEmployee, isPolicyExpenseChat, report.reportID, report.visibility]); + const unapproveExpenseReportOrShowModal = useCallback(() => { + if (PolicyUtils.hasAccountingConnections(policy)) { + setIsUnapproveModalVisible(true); + return; + } + Navigation.dismissModal(); + IOU.unapproveExpenseReport(moneyRequestReport); + }, [moneyRequestReport, policy]); + const shouldShowLeaveButton = !isThread && (isGroupChat || (isChatRoom && ReportUtils.canLeaveChat(report, policy)) || (isPolicyExpenseChat && !report.isOwnPolicyExpenseChat && !isPolicyAdmin)); @@ -356,6 +372,16 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD }, }); } + + if (canUnapproveRequest) { + items.push({ + key: CONST.REPORT_DETAILS_MENU_ITEM.UNAPPROVE, + icon: Expensicons.CircularArrowBackwards, + translationKey: 'iou.unapprove', + isAnonymousAction: false, + action: () => unapproveExpenseReportOrShowModal(), + }); + } return items; }, [ isSelfDM, @@ -380,6 +406,8 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD isPolicyAdmin, session, leaveChat, + canUnapproveRequest, + unapproveExpenseReportOrShowModal, ]); const displayNamesWithTooltips = useMemo(() => { @@ -399,6 +427,14 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD /> ) : null; + const connectedIntegration = Object.values(CONST.POLICY.CONNECTIONS.NAME).find((integration) => !!policy?.connections?.[integration]); + const connectedIntegrationName = connectedIntegration ? translate('workspace.accounting.connectionName', connectedIntegration) : ''; + const unapproveWarningText = ( + + {translate('iou.headsUp')} {translate('iou.unapproveWithIntegrationWarning', connectedIntegrationName)} + + ); + const renderedAvatar = useMemo(() => { if (isMoneyRequestReport || isInvoiceReport) { return ( @@ -686,6 +722,20 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD danger shouldEnableNewFocusManagement /> + { + setIsUnapproveModalVisible(false); + Navigation.dismissModal(); + IOU.unapproveExpenseReport(moneyRequestReport); + }} + cancelText={translate('common.cancel')} + onCancel={() => setIsUnapproveModalVisible(false)} + prompt={unapproveWarningText} + /> ); diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx index 27227251b0b6..088ee9eb2b6e 100644 --- a/src/pages/home/report/ReportActionItemFragment.tsx +++ b/src/pages/home/report/ReportActionItemFragment.tsx @@ -69,6 +69,7 @@ const MUTED_ACTIONS = [ ...Object.values(CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG), CONST.REPORT.ACTIONS.TYPE.IOU, CONST.REPORT.ACTIONS.TYPE.APPROVED, + CONST.REPORT.ACTIONS.TYPE.UNAPPROVED, CONST.REPORT.ACTIONS.TYPE.MOVED, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST, ] as ReportActionName[]; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index c7d9397f3202..3d864523e418 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -402,6 +402,18 @@ type OriginalMessageApproved = { expenseReportID: string; }; +/** Model of `unapproved` report action */ +type OriginalMessageUnapproved = { + /** Unapproved expense amount */ + amount: number; + + /** Currency of the unapproved expense amount */ + currency: string; + + /** Report ID of the expense */ + expenseReportID: string; +}; + /** The map type of original message */ type OriginalMessageMap = { /** */ @@ -497,7 +509,7 @@ type OriginalMessageMap = { /** */ [CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL]: never; /** */ - [CONST.REPORT.ACTIONS.TYPE.UNAPPROVED]: never; + [CONST.REPORT.ACTIONS.TYPE.UNAPPROVED]: OriginalMessageUnapproved; /** */ [CONST.REPORT.ACTIONS.TYPE.UNHOLD]: never; /** */