mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
WIP(multi-assign): functioning multi
This commit is contained in:
@@ -1469,11 +1469,10 @@ export default {
|
|||||||
tasks: [this.task],
|
tasks: [this.task],
|
||||||
});
|
});
|
||||||
Object.assign(this.task, response);
|
Object.assign(this.task, response);
|
||||||
const promises = this.assignedMembers.map(memberId => this.$store.dispatch('tasks:assignTask', {
|
await this.$store.dispatch('tasks:assignTask', {
|
||||||
taskId: this.task._id,
|
taskId: this.task._id,
|
||||||
userId: memberId,
|
assignedUserIds: this.assignedMembers,
|
||||||
}));
|
});
|
||||||
Promise.all(promises);
|
|
||||||
this.assignedMembers.forEach(memberId => {
|
this.assignedMembers.forEach(memberId => {
|
||||||
if (!this.task.assignedUsers) this.task.assignedUsers = {};
|
if (!this.task.assignedUsers) this.task.assignedUsers = {};
|
||||||
this.task.assignedUsers[memberId] = {
|
this.task.assignedUsers[memberId] = {
|
||||||
@@ -1520,7 +1519,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
await this.$store.dispatch('tasks:assignTask', {
|
await this.$store.dispatch('tasks:assignTask', {
|
||||||
taskId: this.task._id,
|
taskId: this.task._id,
|
||||||
userId: memberId,
|
assignedUserIds: [memberId],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export async function createGroupTasks (store, payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function assignTask (store, payload) {
|
export async function assignTask (store, payload) {
|
||||||
const response = await axios.post(`/api/v4/tasks/${payload.taskId}/assign/${payload.userId}`);
|
const response = await axios.post(`/api/v4/tasks/${payload.taskId}/assign`, payload.assignedUserIds);
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import isUUID from 'validator/lib/isUUID';
|
||||||
import { authWithHeaders } from '../../../middlewares/auth';
|
import { authWithHeaders } from '../../../middlewares/auth';
|
||||||
import * as Tasks from '../../../models/task';
|
import * as Tasks from '../../../models/task';
|
||||||
import { model as Group } from '../../../models/group';
|
import { model as Group } from '../../../models/group';
|
||||||
@@ -169,30 +170,31 @@ api.groupMoveTask = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /api/v3/tasks/:taskId/assign/:assignedUserId Assign a group task to a user
|
* @api {post} /api/v3/tasks/:taskId/assign Assign a group task to a user or users
|
||||||
* @apiDescription Assigns a user to a group task
|
* @apiDescription Assign users to a group task
|
||||||
* @apiName AssignTask
|
* @apiName AssignTask
|
||||||
* @apiGroup Task
|
* @apiGroup Task
|
||||||
*
|
*
|
||||||
* @apiParam (Path) {UUID} taskId The id of the task that will be assigned
|
* @apiParam (Path) {UUID} taskId The id of the task that will be assigned
|
||||||
* @apiParam (Path) {UUID} assignedUserId The id of the user that will be assigned to the task
|
* @apiParam (Body) {UUID[]} [assignedUserIds] Array of user IDs to be assigned to the task
|
||||||
*
|
*
|
||||||
* @apiSuccess data The assigned task
|
* @apiSuccess data The assigned task
|
||||||
*/
|
*/
|
||||||
api.assignTask = {
|
api.assignTask = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/tasks/:taskId/assign/:assignedUserId',
|
url: '/tasks/:taskId/assign',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
||||||
req.checkParams('assignedUserId', res.t('userIdRequired')).notEmpty().isUUID();
|
|
||||||
|
|
||||||
const reqValidationErrors = req.validationErrors();
|
const reqValidationErrors = req.validationErrors();
|
||||||
if (reqValidationErrors) throw reqValidationErrors;
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { assignedUserId } = req.params;
|
const assignedUserIds = req.body;
|
||||||
const assignedUser = await User.findById(assignedUserId).exec();
|
for (const userId of assignedUserIds) {
|
||||||
|
if (!isUUID(userId)) throw new BadRequest('Assigned users must be UUIDs');
|
||||||
|
}
|
||||||
|
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||||
@@ -211,18 +213,21 @@ api.assignTask = {
|
|||||||
|
|
||||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||||
|
|
||||||
|
const assignedUsers = await User.find({ _id: { $in: assignedUserIds } }).exec();
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const taskText = task.text;
|
const taskText = task.text;
|
||||||
const userName = `@${user.auth.local.username}`;
|
const userName = `@${user.auth.local.username}`;
|
||||||
|
|
||||||
if (user._id !== assignedUserId) {
|
for (const userToAssign of assignedUsers) {
|
||||||
assignedUser.addNotification('GROUP_TASK_ASSIGNED', {
|
if (user._id !== userToAssign._id) {
|
||||||
message: res.t('youHaveBeenAssignedTask', { managerName: userName, taskText }),
|
userToAssign.addNotification('GROUP_TASK_ASSIGNED', {
|
||||||
taskId: task._id,
|
message: res.t('youHaveBeenAssignedTask', { managerName: userName, taskText }),
|
||||||
});
|
taskId: task._id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
promises.push(group.syncTask(task, assignedUser, user));
|
promises.push(group.syncTask(task, assignedUsers, user));
|
||||||
promises.push(group.save());
|
promises.push(group.save());
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
|||||||
@@ -1499,82 +1499,84 @@ schema.methods.updateTask = async function updateTask (taskToSync, options = {})
|
|||||||
await taskSchema.update(updateQuery, updateCmd, { multi: true }).exec();
|
await taskSchema.update(updateQuery, updateCmd, { multi: true }).exec();
|
||||||
};
|
};
|
||||||
|
|
||||||
schema.methods.syncTask = async function groupSyncTask (taskToSync, user, assigningUser) {
|
schema.methods.syncTask = async function groupSyncTask (taskToSync, users, assigningUser) {
|
||||||
const group = this;
|
const group = this;
|
||||||
const toSave = [];
|
const toSave = [];
|
||||||
const assignmentData = {
|
for (const user of users) {
|
||||||
assignedDate: new Date(),
|
const assignmentData = {
|
||||||
assigningUsername: assigningUser && assigningUser._id !== user._id
|
assignedDate: new Date(),
|
||||||
? assigningUser.auth.local.username : null,
|
assigningUsername: assigningUser && assigningUser._id !== user._id
|
||||||
completed: false,
|
? assigningUser.auth.local.username : null,
|
||||||
};
|
completed: false,
|
||||||
|
};
|
||||||
|
|
||||||
if (!taskToSync.group.assignedUsers) {
|
if (!taskToSync.group.assignedUsers) {
|
||||||
taskToSync.group.assignedUsers = {};
|
taskToSync.group.assignedUsers = {};
|
||||||
}
|
|
||||||
if (!taskToSync.group.assignedUsers[user._id]) {
|
|
||||||
taskToSync.group.assignedUsers[user._id] = assignmentData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync tags
|
|
||||||
const userTags = user.tags;
|
|
||||||
const i = _.findIndex(userTags, { id: group._id });
|
|
||||||
|
|
||||||
if (i !== -1) {
|
|
||||||
if (userTags[i].name !== group.name) {
|
|
||||||
// update the name - it's been changed since
|
|
||||||
userTags[i].name = group.name;
|
|
||||||
userTags[i].group = group._id;
|
|
||||||
}
|
}
|
||||||
} else {
|
if (!taskToSync.group.assignedUsers[user._id]) {
|
||||||
userTags.push({
|
taskToSync.group.assignedUsers[user._id] = assignmentData;
|
||||||
id: group._id,
|
}
|
||||||
name: group.name,
|
|
||||||
group: group._id,
|
// Sync tags
|
||||||
});
|
const userTags = user.tags;
|
||||||
|
const i = _.findIndex(userTags, { id: group._id });
|
||||||
|
|
||||||
|
if (i !== -1) {
|
||||||
|
if (userTags[i].name !== group.name) {
|
||||||
|
// update the name - it's been changed since
|
||||||
|
userTags[i].name = group.name;
|
||||||
|
userTags[i].group = group._id;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userTags.push({
|
||||||
|
id: group._id,
|
||||||
|
name: group.name,
|
||||||
|
group: group._id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const findQuery = {
|
||||||
|
'group.taskId': taskToSync._id,
|
||||||
|
userId: user._id,
|
||||||
|
'group.id': group._id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let matchingTask = await Tasks.Task.findOne(findQuery).exec(); // eslint-disable-line
|
||||||
|
|
||||||
|
if (!matchingTask) { // If the task is new, create it
|
||||||
|
matchingTask = new Tasks[taskToSync.type](Tasks.Task.sanitize(syncableAttrs(taskToSync)));
|
||||||
|
matchingTask.group.id = taskToSync.group.id;
|
||||||
|
matchingTask.userId = user._id;
|
||||||
|
matchingTask.group.taskId = taskToSync._id;
|
||||||
|
user.tasksOrder[`${taskToSync.type}s`].unshift(matchingTask._id);
|
||||||
|
} else {
|
||||||
|
_.merge(matchingTask, syncableAttrs(taskToSync));
|
||||||
|
// Make sure the task is in user.tasksOrder
|
||||||
|
const orderList = user.tasksOrder[`${taskToSync.type}s`];
|
||||||
|
if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id);
|
||||||
|
}
|
||||||
|
matchingTask.group.assignedUsers = taskToSync.group.assignedUsers;
|
||||||
|
matchingTask.group.sharedCompletion = taskToSync.group.sharedCompletion;
|
||||||
|
matchingTask.group.managerNotes = taskToSync.group.managerNotes;
|
||||||
|
|
||||||
|
// sync checklist
|
||||||
|
if (taskToSync.checklist) {
|
||||||
|
taskToSync.checklist.forEach(element => {
|
||||||
|
const newCheckList = { completed: false };
|
||||||
|
newCheckList.linkId = element.id;
|
||||||
|
newCheckList.text = element.text;
|
||||||
|
matchingTask.checklist.push(newCheckList);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't override the notes, but provide it if not provided
|
||||||
|
if (!matchingTask.notes) matchingTask.notes = taskToSync.notes;
|
||||||
|
// add tag if missing
|
||||||
|
if (matchingTask.tags.indexOf(group._id) === -1) matchingTask.tags.push(group._id);
|
||||||
|
|
||||||
|
toSave.push(matchingTask.save(), user.save());
|
||||||
}
|
}
|
||||||
|
toSave.push(taskToSync.save());
|
||||||
const findQuery = {
|
|
||||||
'group.taskId': taskToSync._id,
|
|
||||||
userId: user._id,
|
|
||||||
'group.id': group._id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let matchingTask = await Tasks.Task.findOne(findQuery).exec();
|
|
||||||
|
|
||||||
if (!matchingTask) { // If the task is new, create it
|
|
||||||
matchingTask = new Tasks[taskToSync.type](Tasks.Task.sanitize(syncableAttrs(taskToSync)));
|
|
||||||
matchingTask.group.id = taskToSync.group.id;
|
|
||||||
matchingTask.userId = user._id;
|
|
||||||
matchingTask.group.taskId = taskToSync._id;
|
|
||||||
user.tasksOrder[`${taskToSync.type}s`].unshift(matchingTask._id);
|
|
||||||
} else {
|
|
||||||
_.merge(matchingTask, syncableAttrs(taskToSync));
|
|
||||||
// Make sure the task is in user.tasksOrder
|
|
||||||
const orderList = user.tasksOrder[`${taskToSync.type}s`];
|
|
||||||
if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id);
|
|
||||||
}
|
|
||||||
|
|
||||||
matchingTask.group.assignedUsers = taskToSync.group.assignedUsers;
|
|
||||||
matchingTask.group.sharedCompletion = taskToSync.group.sharedCompletion;
|
|
||||||
matchingTask.group.managerNotes = taskToSync.group.managerNotes;
|
|
||||||
|
|
||||||
// sync checklist
|
|
||||||
if (taskToSync.checklist) {
|
|
||||||
taskToSync.checklist.forEach(element => {
|
|
||||||
const newCheckList = { completed: false };
|
|
||||||
newCheckList.linkId = element.id;
|
|
||||||
newCheckList.text = element.text;
|
|
||||||
matchingTask.checklist.push(newCheckList);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't override the notes, but provide it if not provided
|
|
||||||
if (!matchingTask.notes) matchingTask.notes = taskToSync.notes;
|
|
||||||
// add tag if missing
|
|
||||||
if (matchingTask.tags.indexOf(group._id) === -1) matchingTask.tags.push(group._id);
|
|
||||||
|
|
||||||
toSave.push(matchingTask.save(), taskToSync.save(), user.save());
|
|
||||||
return Promise.all(toSave);
|
return Promise.all(toSave);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,10 @@ export const TaskSchema = new Schema({
|
|||||||
$type: String, default: 'singleCompletion', // legacy data
|
$type: String, default: 'singleCompletion', // legacy data
|
||||||
},
|
},
|
||||||
managerNotes: { $type: String },
|
managerNotes: { $type: String },
|
||||||
completedBy: { $type: String, ref: 'User', validate: [v => validator.isUUID(v), 'Invalid uuid for group completing user.'] },
|
completedBy: {
|
||||||
|
$type: Schema.Types.Mixed,
|
||||||
|
default: () => ({}), // { 'UUID': Date }
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
reminders: [reminderSchema],
|
reminders: [reminderSchema],
|
||||||
|
|||||||
Reference in New Issue
Block a user