diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js
index 740ab345fa74..d05ebd4bfd09 100644
--- a/src/components/ReportActionItem/TaskPreview.js
+++ b/src/components/ReportActionItem/TaskPreview.js
@@ -88,9 +88,9 @@ function TaskPreview(props) {
disabled={ReportUtils.isCanceledTaskReport(props.taskReport)}
onPress={() => {
if (isTaskCompleted) {
- Task.reopenTask(props.taskReportID, taskTitle);
+ Task.reopenTask(props.taskReport, taskTitle);
} else {
- Task.completeTask(props.taskReportID, taskTitle);
+ Task.completeTask(props.taskReport, taskTitle);
}
}}
accessibilityLabel={props.translate('task.task')}
diff --git a/src/components/ReportActionItem/TaskView.js b/src/components/ReportActionItem/TaskView.js
index 7fd04d8544c7..83c15acee79f 100644
--- a/src/components/ReportActionItem/TaskView.js
+++ b/src/components/ReportActionItem/TaskView.js
@@ -79,7 +79,7 @@ function TaskView(props) {
{props.translate('task.title')}
(isCompleted ? Task.reopenTask(props.report.reportID, taskTitle) : Task.completeTask(props.report.reportID, taskTitle))}
+ onPress={() => (isCompleted ? Task.reopenTask(props.report, taskTitle) : Task.completeTask(props.report, taskTitle))}
isChecked={isCompleted}
style={styles.taskMenuItemCheckbox}
containerSize={24}
diff --git a/src/components/TaskHeaderActionButton.js b/src/components/TaskHeaderActionButton.js
index e0a2549b2f0c..148cf81ce672 100644
--- a/src/components/TaskHeaderActionButton.js
+++ b/src/components/TaskHeaderActionButton.js
@@ -50,9 +50,7 @@ function TaskHeaderActionButton(props) {
medium
text={props.translate(ReportUtils.isCompletedTaskReport(props.report) ? 'task.markAsIncomplete' : 'task.markAsDone')}
onPress={() =>
- ReportUtils.isCompletedTaskReport(props.report)
- ? Task.reopenTask(props.report.reportID, props.report.reportName)
- : Task.completeTask(props.report.reportID, props.report.reportName)
+ ReportUtils.isCompletedTaskReport(props.report) ? Task.reopenTask(props.report, props.report.reportName) : Task.completeTask(props.report, props.report.reportName)
}
style={[styles.flex1]}
/>
diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js
index 3cd5621e5e32..6d777533360d 100644
--- a/src/libs/ReportActionsUtils.js
+++ b/src/libs/ReportActionsUtils.js
@@ -119,6 +119,17 @@ function getParentReportAction(report, allReportActionsParam = undefined) {
return lodashGet(allReportActionsParam || allReportActions, [report.parentReportID, report.parentReportActionID], {});
}
+/**
+ * Find the reportAction having the given childReportID in parent report actions
+ *
+ * @param {String} childReportID
+ * @param {String} parentReportID
+ * @returns {Object}
+ */
+function getParentReportActionInReport(childReportID, parentReportID) {
+ return _.find(allReportActions[parentReportID], (reportAction) => reportAction && `${reportAction.childReportID}` === `${childReportID}`);
+}
+
/**
* Determines if the given report action is sent money report action by checking for 'pay' type and presence of IOUDetails object.
*
@@ -583,6 +594,7 @@ export {
getReportPreviewAction,
isCreatedTaskReportAction,
getParentReportAction,
+ getParentReportActionInReport,
isTransactionThread,
getFormattedAmount,
isSentMoneyReportAction,
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 3fd1236e26ae..e3b6cb2ccc52 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -1592,24 +1592,26 @@ function updateOptimisticParentReportAction(parentReportAction, lastVisibleActio
* @param {String} reportID The reportID of the report that is updated
* @param {String} lastVisibleActionCreated Last visible action created of the child report
* @param {String} type The type of action in the child report
+ * @param {String} parentReportID Custom reportID to be updated
+ * @param {String} parentReportActionID Custom reportActionID to be updated
* @returns {Object}
*/
-const getOptimisticDataForParentReportAction = (reportID, lastVisibleActionCreated, type) => {
+function getOptimisticDataForParentReportAction(reportID, lastVisibleActionCreated, type, parentReportID = '', parentReportActionID = '') {
const report = getReport(reportID);
- if (report && report.parentReportActionID) {
- const parentReportAction = ReportActionsUtils.getParentReportAction(report);
- if (parentReportAction && parentReportAction.reportActionID) {
- return {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`,
- value: {
- [parentReportAction.reportActionID]: updateOptimisticParentReportAction(parentReportAction, lastVisibleActionCreated, type),
- },
- };
- }
+ const parentReportAction = ReportActionsUtils.getParentReportAction(report);
+ if (_.isEmpty(parentReportAction)) {
+ return {};
}
- return {};
-};
+
+ const optimisticParentReportAction = updateOptimisticParentReportAction(parentReportAction, lastVisibleActionCreated, type);
+ return {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID || report.parentReportID}`,
+ value: {
+ [parentReportActionID || report.parentReportActionID]: optimisticParentReportAction,
+ },
+ };
+}
/**
* Builds an optimistic reportAction for the parent report when a task is created
@@ -2788,6 +2790,30 @@ function getParentReport(report) {
return lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, {});
}
+/**
+ * Find the parent report action in assignee report for a task report
+ * Returns an empty object if assignee report is the same as the share destination report
+ *
+ * @param {Object} taskReport
+ * @returns {Object}
+ */
+function getTaskParentReportActionIDInAssigneeReport(taskReport) {
+ const assigneeChatReportID = lodashGet(getChatByParticipants(isTaskAssignee(taskReport) ? [taskReport.ownerAccountID] : [taskReport.managerID]), 'reportID');
+ if (!assigneeChatReportID || assigneeChatReportID === taskReport.parentReportID) {
+ return {};
+ }
+
+ const clonedParentReportActionID = lodashGet(ReportActionsUtils.getParentReportActionInReport(taskReport.reportID, assigneeChatReportID), 'reportActionID');
+ if (!clonedParentReportActionID) {
+ return {};
+ }
+
+ return {
+ reportID: assigneeChatReportID,
+ reportActionID: clonedParentReportActionID,
+ };
+}
+
/**
* Return true if the composer should be hidden
* @param {Object} report
@@ -2969,6 +2995,7 @@ export {
getMoneyRequestAction,
getBankAccountRoute,
getParentReport,
+ getTaskParentReportActionIDInAssigneeReport,
getReportPreviewMessage,
shouldHideComposer,
getOriginalReportID,
diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js
index 36eabd05cece..5cb186415406 100644
--- a/src/libs/actions/Task.js
+++ b/src/libs/actions/Task.js
@@ -213,7 +213,13 @@ function createTaskAndNavigate(parentReportID, title, description, assignee, ass
Navigation.dismissModal(optimisticTaskReport.reportID);
}
-function completeTask(taskReportID, taskTitle) {
+/**
+ * Complete a task
+ * @param {Object} taskReport task report
+ * @param {String} taskTitle Title of the task
+ */
+function completeTask(taskReport, taskTitle) {
+ const taskReportID = taskReport.reportID;
const message = `completed task: ${taskTitle}`;
const completedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED, message);
@@ -266,9 +272,25 @@ function completeTask(taskReportID, taskTitle) {
];
// Update optimistic data for parent report action
- const optimisticParentReportData = ReportUtils.getOptimisticDataForParentReportAction(taskReportID, completedTaskReportAction.created, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- if (!_.isEmpty(optimisticParentReportData)) {
- optimisticData.push(optimisticParentReportData);
+ const optimisticDataForParentReportAction = ReportUtils.getOptimisticDataForParentReportAction(taskReportID, completedTaskReportAction.created, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ if (!_.isEmpty(optimisticDataForParentReportAction)) {
+ optimisticData.push(optimisticDataForParentReportAction);
+ }
+
+ // Multiple report actions can link to the same child. Both share destination (task parent) and assignee report link to the same report action.
+ // We need to find and update the other parent report action (in assignee report). More info https://github.com/Expensify/App/issues/23920#issuecomment-1663092717
+ const assigneeReportAction = ReportUtils.getTaskParentReportActionIDInAssigneeReport(taskReport);
+ if (!_.isEmpty(assigneeReportAction)) {
+ const optimisticDataForClonedParentReportAction = ReportUtils.getOptimisticDataForParentReportAction(
+ taskReportID,
+ completedTaskReportAction.created,
+ CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ assigneeReportAction.reportID,
+ assigneeReportAction.reportActionID,
+ );
+ if (!_.isEmpty(optimisticDataForClonedParentReportAction)) {
+ optimisticData.push(optimisticDataForClonedParentReportAction);
+ }
}
API.write(
@@ -282,11 +304,12 @@ function completeTask(taskReportID, taskTitle) {
}
/**
- * Reopens a closed task
- * @param {string} taskReportID ReportID of the task
- * @param {string} taskTitle Title of the task
+ * Reopen a closed task
+ * @param {Object} taskReport task report
+ * @param {String} taskTitle Title of the task
*/
-function reopenTask(taskReportID, taskTitle) {
+function reopenTask(taskReport, taskTitle) {
+ const taskReportID = taskReport.reportID;
const message = `reopened task: ${taskTitle}`;
const reopenedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKREOPENED, message);
@@ -342,9 +365,25 @@ function reopenTask(taskReportID, taskTitle) {
];
// Update optimistic data for parent report action
- const optimisticParentReportData = ReportUtils.getOptimisticDataForParentReportAction(taskReportID, reopenedTaskReportAction.created, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- if (!_.isEmpty(optimisticParentReportData)) {
- optimisticData.push(optimisticParentReportData);
+ const optimisticDataForParentReportAction = ReportUtils.getOptimisticDataForParentReportAction(taskReportID, reopenedTaskReportAction.created, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ if (!_.isEmpty(optimisticDataForParentReportAction)) {
+ optimisticData.push(optimisticDataForParentReportAction);
+ }
+
+ // Multiple report actions can link to the same child. Both share destination (task parent) and assignee report link to the same report action.
+ // We need to find and update the other parent report action (in assignee report). More info https://github.com/Expensify/App/issues/23920#issuecomment-1663092717
+ const assigneeReportAction = ReportUtils.getTaskParentReportActionIDInAssigneeReport(taskReport);
+ if (!_.isEmpty(assigneeReportAction)) {
+ const optimisticDataForClonedParentReportAction = ReportUtils.getOptimisticDataForParentReportAction(
+ taskReportID,
+ reopenedTaskReportAction.created,
+ CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ assigneeReportAction.reportID,
+ assigneeReportAction.reportActionID,
+ );
+ if (!_.isEmpty(optimisticDataForClonedParentReportAction)) {
+ optimisticData.push(optimisticDataForClonedParentReportAction);
+ }
}
API.write(
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index 85cdd239dae7..fafd6caf4f1f 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -104,7 +104,7 @@ function HeaderView(props) {
threeDotMenuItems.push({
icon: Expensicons.Checkmark,
text: props.translate('task.markAsDone'),
- onSelected: () => Task.completeTask(props.report.reportID, title),
+ onSelected: () => Task.completeTask(props.report, title),
});
}
@@ -113,7 +113,7 @@ function HeaderView(props) {
threeDotMenuItems.push({
icon: Expensicons.Checkmark,
text: props.translate('task.markAsIncomplete'),
- onSelected: () => Task.reopenTask(props.report.reportID, title),
+ onSelected: () => Task.reopenTask(props.report, title),
});
}