Skip to content

Commit 4c47976

Browse files
authored
Merge pull request #154 from aliansoftwareteam/feature/editable-task-created-by
feat: allow editing the "Created by" user on a task
2 parents 9c7fa18 + 5af9f29 commit 4c47976

4 files changed

Lines changed: 206 additions & 17 deletions

File tree

Modules/Tasks/helpers/task_class_Mongo.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,76 @@ class Task {
831831
})
832832
}
833833

834+
/* -------------- UPDATE TASK LEADER (CREATED BY) FUNCTION FOR TASK -----------------*/
835+
836+
updateTaskLeader({firebaseObj, projectData, taskData, employeeName, userData, isUpdateTask}) {
837+
return new Promise((resolve, reject) => {
838+
try {
839+
const newLeaderId = firebaseObj && firebaseObj.Task_Leader;
840+
if (!newLeaderId) {
841+
reject(new Error("Task_Leader is required"));
842+
return;
843+
}
844+
845+
// SIDE-EFFECTS ONLY (history) — parity with updateAssignee/updateStatus/updatePriority
846+
if (isUpdateTask === false) {
847+
const historyObj = {
848+
key: "TaskLeader_Changed",
849+
message: `<b>${userData.Employee_Name}</b> has changed <b>Created by</b> to <b>${employeeName}</b>.`,
850+
sprintId: taskData.sprintId,
851+
};
852+
HandleHistory('task', projectData.CompanyId, projectData._id, taskData._id, historyObj, userData)
853+
.catch((error) => {
854+
logger.error(`ERROR in history: ${error.message}`);
855+
});
856+
resolve({status: true, statusText: "Task leader updated successfully"});
857+
return;
858+
}
859+
860+
// DB-WRITE PATH
861+
const findObj = {
862+
type: dbCollections.TASKS,
863+
data: [{ _id: taskData._id }]
864+
};
865+
MongoDbCrudOpration(projectData.CompanyId, findObj, "findOne").then((response) => {
866+
if (!response) {
867+
throw new Error("Task document does not exist!");
868+
}
869+
870+
const mongoUpdateObj = { $set: { Task_Leader: newLeaderId } };
871+
const updateObj = {
872+
type: SCHEMA_TYPE.TASKS,
873+
data: [
874+
{ _id: new mongoose.Types.ObjectId(taskData._id) },
875+
mongoUpdateObj,
876+
{ returnDocument: "after" }
877+
]
878+
};
879+
MongoDbCrudOpration(projectData.CompanyId, updateObj, "findOneAndUpdate").then((result) => {
880+
socketEmitter.emit('update', { type: "update", data: result, updatedFields: { Task_Leader: newLeaderId }, module: 'task' });
881+
resolve({status: true, statusText: "Task leader updated successfully"});
882+
883+
const historyObj = {
884+
key: "TaskLeader_Changed",
885+
message: `<b>${userData.Employee_Name}</b> has changed <b>Created by</b> to <b>${employeeName}</b>.`,
886+
sprintId: taskData.sprintId,
887+
};
888+
HandleHistory('task', projectData.CompanyId, projectData._id, taskData._id, historyObj, userData)
889+
.catch((error) => {
890+
logger.error(`ERROR in history: ${error.message}`);
891+
});
892+
}).catch((error) => {
893+
reject(error);
894+
});
895+
}).catch((error) => {
896+
reject(error);
897+
});
898+
} catch (error) {
899+
reject(error);
900+
}
901+
});
902+
}
903+
834904
/* -------------- (AUTO RUN ON TASK CREATE) UPDATE TASK KEY -----------------*/
835905
updateTaskKey({companyId, projectCode, projectId, taskId, taskTypeKey, sprintId, mainChat = false,isParentTask=true , indexObj = {}}) {
836906
return new Promise((resolve, reject) => {

frontend/src/components/organisms/TaskDetailRightSide/TaskDetailRightSide.vue

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,50 @@
5454
<div class="d-flex task-detail-right-side-label">
5555
<h4>{{$t('Comment.created_by')}}</h4>
5656
<Skelaton v-if="!task?.Task_Leader && isMainSpinner" style="height: 30px;" class="w-30px border-radius-50-per"/>
57-
<div v-else class="d-flex align-items-center">
58-
<UserProfile
59-
:data="{
60-
image: taskLeaderData.Employee_profileImageURL,
61-
title: taskLeaderData.Employee_Name
62-
}"
63-
:showDot="false"
64-
width="30px"
65-
:thumbnail="'30x30'"
66-
/>
67-
<span
68-
class="black text-ellipsis task-created-by"
69-
:class="{'font-size-13 font-weight-400' : clientWidth > 767, 'font-size-16' : clientWidth <=767}"
70-
:title="taskLeaderData?.Employee_Name || 'N/A'">
71-
{{ taskLeaderData?.Employee_Name || 'N/A' }}
72-
</span>
73-
</div>
57+
<template v-else>
58+
<!-- Editable picker — gated by the same permission as Assignee. -->
59+
<div
60+
v-if="checkPermission('task.task_assignee',project?.isGlobalPermission) === true && checkPermission('task.task_list',project?.isGlobalPermission) == true"
61+
class="d-flex align-items-center"
62+
>
63+
<Assignee
64+
:numOfUsers="1"
65+
:users="task.Task_Leader ? [task.Task_Leader] : []"
66+
:addUser="true"
67+
:options="permittedOptions"
68+
@selected="updateTaskLeader($event)"
69+
imageWidth="30px"
70+
:showAddUser="false"
71+
:zIndexAssigne="props.zIndexAssigne"
72+
:isDisplayTeam="false"
73+
:multiSelect="false"
74+
/>
75+
<span
76+
class="black text-ellipsis task-created-by ml-5px"
77+
:class="{'font-size-13 font-weight-400' : clientWidth > 767, 'font-size-16' : clientWidth <=767}"
78+
:title="taskLeaderData?.Employee_Name || 'N/A'">
79+
{{ taskLeaderData?.Employee_Name || 'N/A' }}
80+
</span>
81+
</div>
82+
<!-- Read-only display — original behaviour for users without permission. -->
83+
<div v-else class="d-flex align-items-center">
84+
<UserProfile
85+
:data="{
86+
image: taskLeaderData.Employee_profileImageURL,
87+
title: taskLeaderData.Employee_Name
88+
}"
89+
:showDot="false"
90+
width="30px"
91+
:thumbnail="'30x30'"
92+
/>
93+
<span
94+
class="black text-ellipsis task-created-by"
95+
:class="{'font-size-13 font-weight-400' : clientWidth > 767, 'font-size-16' : clientWidth <=767}"
96+
:title="taskLeaderData?.Employee_Name || 'N/A'">
97+
{{ taskLeaderData?.Employee_Name || 'N/A' }}
98+
</span>
99+
</div>
100+
</template>
74101
</div>
75102
<div class="d-flex task-detail-right-side-label" v-if="checkPermission('task.task_priority',project?.isGlobalPermission) !== null && checkApps('Priority')">
76103
<h4>{{$t('Projects.priority')}}</h4>
@@ -357,6 +384,44 @@ const updateAssignee = (event, type) =>{
357384
}
358385
}
359386
387+
const updateTaskLeader = (event) => {
388+
try {
389+
if (!event || !event.id) return;
390+
if (event.id === props.task.Task_Leader) return;
391+
const userData = getUserData();
392+
393+
const updateObject = {
394+
Task_Leader: event.id
395+
}
396+
397+
const projectData = {
398+
_id: project.value._id,
399+
CompanyId: project.value.CompanyId,
400+
lastTaskId: project.value.lastTaskId,
401+
ProjectName: project.value.ProjectName,
402+
ProjectCode: project.value.ProjectCode
403+
}
404+
405+
taskClass.updateTaskLeader({
406+
firebaseObj: updateObject,
407+
projectData: projectData,
408+
taskData: props.task,
409+
employeeName: getUser(event.id).Employee_Name,
410+
userData
411+
})
412+
.then(() => {
413+
$toast.success(t('Toast.Created_by_updated_successfully'), { position: 'top-right' });
414+
})
415+
.catch((error) => {
416+
console.error("ERROR in updateTaskLeader: ", error);
417+
$toast.error(t('Toast.Created_by_not_updated'), { position: 'top-right' });
418+
})
419+
} catch (error) {
420+
console.error(error);
421+
$toast.error(t('Toast.Created_by_not_updated'), { position: 'top-right' });
422+
}
423+
}
424+
360425
const updatePriority = async(val) => {
361426
try {
362427
const userData = getUserData();

frontend/src/locales/en.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,6 +1707,8 @@ export default {
17071707
"Assignee added successfully": "Assignee added successfully",
17081708
"Assignee removed successfully": "Assignee removed successfully",
17091709
"Assignee not updated": "Assignee not updated",
1710+
Created_by_updated_successfully: "Created by updated successfully",
1711+
Created_by_not_updated: "Created by not updated",
17101712
Task_updated_successfully: "Task updated successfully",
17111713
Task_restored_successfully: "Task restored successfully",
17121714
Task_archived_successfully: "Task archived successfully",

frontend/src/utils/TaskOperations/index.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,58 @@ class Task {
327327
})
328328
}
329329

330+
/* -------------- UPDATE TASK LEADER (CREATED BY) -----------------*/
331+
332+
updateTaskLeader({ firebaseObj, projectData, taskData, employeeName, userData, isUpdateTask = true }) {
333+
return new Promise((resolve, reject) => {
334+
try {
335+
const newLeaderId = firebaseObj && firebaseObj.Task_Leader;
336+
if (!newLeaderId) {
337+
reject({ status: false, error: new Error("Task_Leader is required") });
338+
return;
339+
}
340+
341+
const { sprintId, ProjectID } = taskData;
342+
343+
// Optimistic UI: mirror what updateAssignee does for AssigneeUserId
344+
Store.commit('projectData/mutateUpdateFirebaseTasks', {
345+
snap: null,
346+
op: "modified",
347+
pid: ProjectID,
348+
sprintId,
349+
data: { ...taskData, Task_Leader: newLeaderId },
350+
updatedFields: { ...firebaseObj }
351+
});
352+
353+
apiRequest("patch", env.V2_TASKS, {
354+
action: "updateTaskLeader",
355+
firebaseObj,
356+
projectData,
357+
taskData,
358+
employeeName,
359+
isUpdateTask,
360+
userData: {
361+
"Employee_Name": userData.Employee_Name,
362+
"id": userData.id,
363+
"companyOwnerId": userData.companyOwnerId
364+
}
365+
})
366+
.then((response) => {
367+
if (response.data.status) {
368+
resolve({ status: true, statusText: "Task leader updated successfully" });
369+
} else {
370+
reject({ status: false, error: response.data.error });
371+
}
372+
})
373+
.catch((error) => {
374+
reject({ status: false, error: error });
375+
});
376+
} catch (error) {
377+
reject({ status: false, error: error });
378+
}
379+
});
380+
}
381+
330382
/* -------------- UPDATE TASK WATCHER -----------------*/
331383
updateWatcher({companyId, projectId, sprintId, taskId, userId, add,userData,employeeName, watchers: watchersArr}) {
332384
return new Promise((resolve,reject) => {

0 commit comments

Comments
 (0)