From 36b7e060f670d9ccc5bf9de0a39f98c411a4ed10 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 4 Sep 2024 12:08:34 +0700 Subject: [PATCH 01/15] fix: Use new ResolveDuplicates when approver is resolving duplicates --- src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/IOU.ts | 115 ++++++++++++++++++ .../TransactionDuplicate/Confirmation.tsx | 15 +++ 4 files changed, 133 insertions(+) diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index d25dce103c9e..72e7c0cd273c 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -239,6 +239,7 @@ export type {default as SendInvoiceParams} from './SendInvoiceParams'; export type {default as PayInvoiceParams} from './PayInvoiceParams'; export type {default as MarkAsCashParams} from './MarkAsCashParams'; export type {default as TransactionMergeParams} from './TransactionMergeParams'; +export type {default as TransactionResolveParams} from './TransactionResolveParams'; export type {default as UpdateSubscriptionTypeParams} from './UpdateSubscriptionTypeParams'; export type {default as SignUpUserParams} from './SignUpUserParams'; export type {default as UpdateSubscriptionAutoRenewParams} from './UpdateSubscriptionAutoRenewParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index babdf6ddfdb3..a6aef122e421 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -256,6 +256,7 @@ const WRITE_COMMANDS = { PAY_INVOICE: 'PayInvoice', MARK_AS_CASH: 'MarkAsCash', TRANSACTION_MERGE: 'Transaction_Merge', + TRANSACTION_RESOLVE: 'ResolveDuplicates', UPDATE_SUBSCRIPTION_TYPE: 'UpdateSubscriptionType', SIGN_UP_USER: 'SignUpUser', UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew', @@ -621,6 +622,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.PAY_INVOICE]: Parameters.PayInvoiceParams; [WRITE_COMMANDS.MARK_AS_CASH]: Parameters.MarkAsCashParams; [WRITE_COMMANDS.TRANSACTION_MERGE]: Parameters.TransactionMergeParams; + [WRITE_COMMANDS.TRANSACTION_RESOLVE]: Parameters.TransactionResolveParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_TYPE]: Parameters.UpdateSubscriptionTypeParams; [WRITE_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_AUTO_RENEW]: Parameters.UpdateSubscriptionAutoRenewParams; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 29d481737790..512d90e4b949 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -25,6 +25,7 @@ import type { SubmitReportParams, TrackExpenseParams, TransactionMergeParams, + TransactionResolveParams, UnapproveExpenseReportParams, UpdateMoneyRequestParams, } from '@libs/API/parameters'; @@ -7990,6 +7991,119 @@ function mergeDuplicates(params: TransactionMergeParams) { API.write(WRITE_COMMANDS.TRANSACTION_MERGE, params, {optimisticData, failureData}); } +function resolveDuplicates(params: TransactionMergeParams) { + const originalSelectedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`]; + + const optimisticTransactionData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`, + value: { + ...originalSelectedTransaction, + billable: params.billable, + comment: { + comment: params.comment, + }, + category: params.category, + created: params.created, + currency: params.currency, + modifiedMerchant: params.merchant, + reimbursable: params.reimbursable, + tag: params.tag, + }, + }; + + const failureTransactionData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`, + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style + value: originalSelectedTransaction as OnyxTypes.Transaction, + }; + + const optimisticTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => { + const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? []; + return { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, + value: violations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION), + }; + }); + + const failureTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => { + const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? []; + return { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, + value: violations, + }; + }); + + const reportIDList = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`] ?? {}) + ?.filter((reportAction): reportAction is ReportAction => { + if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { + return false; + } + const message = ReportActionsUtils.getOriginalMessage(reportAction); + if (!message?.IOUTransactionID) { + return false; + } + return params.transactionIDList.includes(message.IOUTransactionID); + }) + .map((action) => action?.childReportID); + + const transactionReportID = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`] ?? {})?.find( + (reportAction): reportAction is ReportAction => { + if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { + return false; + } + const message = ReportActionsUtils.getOriginalMessage(reportAction); + if (!message?.IOUTransactionID) { + return false; + } + return params.transactionID === message.IOUTransactionID; + }, + )?.childReportID; + + const optimisticReportAction = ReportUtils.buildOptimisticDismissedViolationReportAction({ + reason: 'manual', + violationName: CONST.VIOLATIONS.RTER, + }); + + const optimisticReportActionData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionReportID}`, + value: { + [optimisticReportAction.reportActionID]: optimisticReportAction, + }, + }; + + const failureReportActionData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionReportID}`, + value: { + [optimisticReportAction.reportActionID]: null, + }, + }; + + const optimisticData: OnyxUpdate[] = []; + const failureData: OnyxUpdate[] = []; + + optimisticData.push(optimisticTransactionData, ...optimisticTransactionViolations, optimisticReportActionData); + failureData.push(failureTransactionData, ...failureTransactionViolations, failureReportActionData); + + // get reportID from params.transactionIDList + // add optimistic data like putonhold for each reportID + // const reportActionIDList = + + const parameters: TransactionResolveParams = { + ...params, + transactionIDToKeep: params.transactionID, + reportActionIDList: [], + optimisticReportActionID: optimisticReportAction.reportActionID, + }; + + API.write(WRITE_COMMANDS.TRANSACTION_RESOLVE, parameters, {optimisticData, failureData}); +} + export { adjustRemainingSplitShares, approveMoneyRequest, @@ -8059,5 +8173,6 @@ export { updateMoneyRequestTaxAmount, updateMoneyRequestTaxRate, mergeDuplicates, + resolveDuplicates, }; export type {GPSPoint as GpsPoint, IOURequestType}; diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 96b32006675e..79ab7f4dd0ee 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -14,6 +14,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; @@ -33,6 +34,8 @@ import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; function Confirmation() { const styles = useThemeStyles(); const {translate} = useLocalize(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const route = useRoute>(); const [reviewDuplicates, reviewDuplicatesResult] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); const transaction = useMemo(() => TransactionUtils.buildNewTransactionAfterReviewingDuplicates(reviewDuplicates), [reviewDuplicates]); @@ -42,12 +45,24 @@ function Confirmation() { (action) => ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID === reviewDuplicates?.transactionID, ); + const isActionOwner = + typeof reportAction?.actorAccountID === 'number' && + typeof currentUserPersonalDetails?.accountID === 'number' && + reportAction.actorAccountID === currentUserPersonalDetails?.accountID; + const isApprover = ReportUtils.isMoneyRequestReport(report) && report?.managerID !== null && currentUserPersonalDetails?.accountID === report?.managerID; + const isAdmin = ReportUtils.isPolicyAdmin(report?.policyID ?? '-1', allPolicies); + const transactionsMergeParams = useMemo(() => TransactionUtils.buildTransactionsMergeParams(reviewDuplicates, transaction), [reviewDuplicates, transaction]); + const mergeDuplicates = useCallback(() => { IOU.mergeDuplicates(transactionsMergeParams); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportAction?.childReportID ?? '-1')); }, [reportAction?.childReportID, transactionsMergeParams]); + const resolveDuplicates = useCallback(() => { + IOU.resolveDuplicates(transactionsMergeParams); + }, [transactionsMergeParams]); + const contextValue = useMemo( () => ({ transactionThreadReport: report, From 2d1d4ca596347f205eb6cc771afa51d0c0d89c63 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 4 Sep 2024 14:32:47 +0700 Subject: [PATCH 02/15] add new api params --- .../API/parameters/TransactionResolveParams.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/libs/API/parameters/TransactionResolveParams.ts diff --git a/src/libs/API/parameters/TransactionResolveParams.ts b/src/libs/API/parameters/TransactionResolveParams.ts new file mode 100644 index 000000000000..8af9e71302e1 --- /dev/null +++ b/src/libs/API/parameters/TransactionResolveParams.ts @@ -0,0 +1,17 @@ +type TransactionResolveParams = { + transactionIDToKeep: string; + transactionIDList: string[]; + created: string; + merchant: string; + amount: number; + currency: string; + category: string; + comment: string; + billable: boolean; + reimbursable: boolean; + tag: string; + optimisticReportActionID: string; + reportActionIDList: string[]; +}; + +export default TransactionResolveParams; From 8086b115def452c0568dfadb64b24eaf9d6ea13b Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 9 Sep 2024 11:39:26 +0700 Subject: [PATCH 03/15] complete parameter for API --- src/libs/actions/IOU.ts | 43 ++++++++++++++----- .../TransactionDuplicate/Confirmation.tsx | 16 ++++--- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index fd79bcf29260..09a3b23e6025 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -8072,10 +8072,12 @@ function resolveDuplicates(params: TransactionMergeParams) { const optimisticTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => { const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? []; + const newViolation = {name: CONST.VIOLATIONS.HOLD, type: CONST.VIOLATION_TYPES.VIOLATION}; + const updatedViolations = id === params.transactionID ? violations : [...violations, newViolation]; return { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, - value: violations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION), + value: updatedViolations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION), }; }); @@ -8101,6 +8103,30 @@ function resolveDuplicates(params: TransactionMergeParams) { }) .map((action) => action?.childReportID); + const optimisticHoldActions: OnyxUpdate[] = []; + const failureHoldActions: OnyxUpdate[] = []; + const reportActionIDList: string[] = []; + reportIDList.forEach((transactionThreadReportID) => { + const createdReportAction = ReportUtils.buildOptimisticHoldReportAction(); + reportActionIDList.push(createdReportAction.reportActionID); + optimisticHoldActions.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`, + value: { + [createdReportAction.reportActionID]: createdReportAction, + }, + }); + failureHoldActions.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`, + value: { + [createdReportAction.reportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericHoldExpenseFailureMessage'), + }, + }, + }); + }); + const transactionReportID = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${params.reportID}`] ?? {})?.find( (reportAction): reportAction is ReportAction => { if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { @@ -8116,7 +8142,7 @@ function resolveDuplicates(params: TransactionMergeParams) { const optimisticReportAction = ReportUtils.buildOptimisticDismissedViolationReportAction({ reason: 'manual', - violationName: CONST.VIOLATIONS.RTER, + violationName: CONST.VIOLATIONS.DUPLICATED_TRANSACTION, }); const optimisticReportActionData: OnyxUpdate = { @@ -8138,17 +8164,14 @@ function resolveDuplicates(params: TransactionMergeParams) { const optimisticData: OnyxUpdate[] = []; const failureData: OnyxUpdate[] = []; - optimisticData.push(optimisticTransactionData, ...optimisticTransactionViolations, optimisticReportActionData); - failureData.push(failureTransactionData, ...failureTransactionViolations, failureReportActionData); - - // get reportID from params.transactionIDList - // add optimistic data like putonhold for each reportID - // const reportActionIDList = + optimisticData.push(optimisticTransactionData, ...optimisticTransactionViolations, ...optimisticHoldActions, optimisticReportActionData); + failureData.push(failureTransactionData, ...failureTransactionViolations, ...failureHoldActions, failureReportActionData); + const {transactionID, reportID, receiptID, ...otherParams} = params; const parameters: TransactionResolveParams = { - ...params, + ...otherParams, transactionIDToKeep: params.transactionID, - reportActionIDList: [], + reportActionIDList, optimisticReportActionID: optimisticReportAction.reportActionID, }; diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 79ab7f4dd0ee..98caa4cae3fe 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -45,10 +45,7 @@ function Confirmation() { (action) => ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID === reviewDuplicates?.transactionID, ); - const isActionOwner = - typeof reportAction?.actorAccountID === 'number' && - typeof currentUserPersonalDetails?.accountID === 'number' && - reportAction.actorAccountID === currentUserPersonalDetails?.accountID; + const isActionOwner = report?.ownerAccountID === currentUserPersonalDetails?.accountID; const isApprover = ReportUtils.isMoneyRequestReport(report) && report?.managerID !== null && currentUserPersonalDetails?.accountID === report?.managerID; const isAdmin = ReportUtils.isPolicyAdmin(report?.policyID ?? '-1', allPolicies); @@ -61,7 +58,8 @@ function Confirmation() { const resolveDuplicates = useCallback(() => { IOU.resolveDuplicates(transactionsMergeParams); - }, [transactionsMergeParams]); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportAction?.childReportID ?? '-1')); + }, [transactionsMergeParams, reportAction?.childReportID]); const contextValue = useMemo( () => ({ @@ -123,7 +121,13 @@ function Confirmation() {