You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
329 lines
8.3 KiB
329 lines
8.3 KiB
// task.js
|
|
// Copyright (C) 2024 DTP Technologies, LLC
|
|
// All Rights Reserved
|
|
|
|
'use strict';
|
|
|
|
import mongoose from 'mongoose';
|
|
const Task = mongoose.model('Task');
|
|
const TaskSession = mongoose.model('TaskSession');
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
import { SiteService, SiteError } from '../../lib/site-lib.js';
|
|
|
|
export default class TaskService extends SiteService {
|
|
|
|
static get name ( ) { return 'TaskService'; }
|
|
static get slug () { return 'task'; }
|
|
|
|
constructor (dtp) {
|
|
super(dtp, TaskService);
|
|
}
|
|
|
|
async start ( ) {
|
|
const { client: clientService, user: userService } = this.dtp.services;
|
|
|
|
// const { jobQueue: jobQueueService } = this.dtp.services;
|
|
// this.linksQueue = jobQueueService.getJobQueue('links', this.dtp.config.jobQueues.links);
|
|
|
|
this.populateTask = [
|
|
{
|
|
path: 'user',
|
|
select: userService.USER_SELECT,
|
|
},
|
|
{
|
|
path: 'client',
|
|
populate: clientService.populateClient,
|
|
},
|
|
{
|
|
path: 'project',
|
|
populate: clientService.populateClientProject,
|
|
},
|
|
];
|
|
|
|
this.populateTaskSession = [
|
|
{
|
|
path: 'user',
|
|
select: userService.USER_SELECT,
|
|
},
|
|
{
|
|
path: 'client',
|
|
populate: clientService.populateClient,
|
|
},
|
|
{
|
|
path: 'project',
|
|
populate: clientService.populateClientProject,
|
|
},
|
|
{
|
|
path: 'task',
|
|
populate: this.populateTask,
|
|
},
|
|
{
|
|
path: 'screenshots.image',
|
|
}
|
|
];
|
|
}
|
|
|
|
async createTask (user, taskDefinition) {
|
|
const { text: textService } = this.dtp.services;
|
|
const NOW = new Date();
|
|
|
|
const task = new Task();
|
|
task.created = NOW;
|
|
task.user = user._id;
|
|
task.client = mongoose.Types.ObjectId.createFromHexString(taskDefinition.clientId);
|
|
task.project = mongoose.Types.ObjectId.createFromHexString(taskDefinition.projectId);
|
|
task.status = 'pending';
|
|
task.note = textService.filter(taskDefinition.note);
|
|
|
|
await task.save();
|
|
|
|
return task.toObject();
|
|
}
|
|
|
|
async startTask (task) {
|
|
if (task.status !== 'pending') {
|
|
throw new SiteError(400, 'The task is not in the pending state');
|
|
}
|
|
await Task.updateOne(
|
|
{ _id: task._id },
|
|
{ $set: { status: 'active' } },
|
|
);
|
|
}
|
|
|
|
async closeTask (task) {
|
|
await TaskSession
|
|
.find({ task: task._id, status: 'active' })
|
|
.cursor()
|
|
.eachAsync(async (session) => {
|
|
await this.closeTaskSession(session);
|
|
});
|
|
|
|
await Task.updateOne(
|
|
{ _id: task._id },
|
|
{ $set: { status: 'finished' } },
|
|
);
|
|
}
|
|
|
|
async getTasksForUser (user, options, pagination) {
|
|
const search = { user: user._id };
|
|
|
|
options = options || { };
|
|
if (options.pending) {
|
|
search.status = 'pending';
|
|
} else if (options.active) {
|
|
search.status = 'active';
|
|
} else if (options.finished) {
|
|
search.status = 'finished';
|
|
}
|
|
|
|
const tasks = await Task
|
|
.find(search)
|
|
.sort({ created: -1 })
|
|
.skip(pagination.skip)
|
|
.limit(pagination.cpp)
|
|
.populate(this.populateTask)
|
|
.lean();
|
|
|
|
return tasks.reverse();
|
|
}
|
|
|
|
async getTasksForProject (project, options, pagination) {
|
|
const search = { project: project._id };
|
|
|
|
options = options || { };
|
|
if (options.pending) {
|
|
search.status = 'pending';
|
|
} else if (options.active) {
|
|
search.status = 'active';
|
|
} else if (options.finished) {
|
|
search.status = 'finished';
|
|
}
|
|
|
|
const tasks = await Task
|
|
.find(search)
|
|
.sort({ created: -1 })
|
|
.skip(pagination.skip)
|
|
.limit(pagination.cpp)
|
|
.populate(this.populateTask)
|
|
.lean();
|
|
|
|
return tasks.reverse();
|
|
}
|
|
|
|
async getTaskGridForUser (user) {
|
|
const pagination = { skip: 0, cpp: 10 };
|
|
const pendingTasks = await this.getTasksForUser(user, { pending: true }, pagination);
|
|
const activeTasks = await this.getTasksForUser(user, { active: true }, pagination);
|
|
const finishedTasks = await this.getTasksForUser(user, { finished: true }, pagination);
|
|
return { pendingTasks, activeTasks, finishedTasks };
|
|
}
|
|
|
|
async getTaskGridForProject (project) {
|
|
const pagination = { skip: 0, cpp: 10 };
|
|
const pendingTasks = await this.getTasksForProject(project, { pending: true }, pagination);
|
|
const activeTasks = await this.getTasksForProject(project, { active: true }, pagination);
|
|
const finishedTasks = await this.getTasksForProject(project, { finished: true }, pagination);
|
|
return { pendingTasks, activeTasks, finishedTasks };
|
|
}
|
|
|
|
async getTaskById (taskId) {
|
|
const task = await Task
|
|
.findOne({ _id: taskId })
|
|
.populate(this.populateTask)
|
|
.lean();
|
|
return task;
|
|
}
|
|
|
|
async createTaskSession (task) {
|
|
const NOW = new Date();
|
|
|
|
if (await TaskSession.findOne({ user: task.user._id, status: 'active' })) {
|
|
throw new SiteError(401, "Can't start new session with a currently active session.");
|
|
}
|
|
|
|
let session = new TaskSession();
|
|
session.created = NOW;
|
|
session.lastUpdated = NOW;
|
|
session.hourlyRate = task.project.hourlyRate;
|
|
session.user = task.user._id;
|
|
session.client = task.client._id;
|
|
session.project = task.project._id;
|
|
session.task = task._id;
|
|
session.status = 'active';
|
|
|
|
await session.save();
|
|
session = await TaskSession.populate(session, this.populateTaskSession);
|
|
|
|
this.log.info('task session created', {
|
|
user: {
|
|
_id: task.user._id,
|
|
username: task.user.username,
|
|
},
|
|
session: {
|
|
_id: session._id,
|
|
},
|
|
});
|
|
|
|
return session.toObject();
|
|
}
|
|
|
|
async addTaskSessionScreenshot (session, file) {
|
|
const NOW = new Date();
|
|
const { image: imageService } = this.dtp.services;
|
|
const image = await imageService.create(session.user._id, { }, file);
|
|
await TaskSession.updateOne(
|
|
{ _id: session._id },
|
|
{
|
|
$push: {
|
|
screenshots: {
|
|
created: NOW,
|
|
image: image._id,
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
const displayList = this.createDisplayList('screenshot-accepted');
|
|
displayList.showNotification(
|
|
'Screenshot accepted',
|
|
'success',
|
|
'bottom-center',
|
|
3000,
|
|
);
|
|
|
|
this.dtp.emitter
|
|
.to(session.task._id.toString())
|
|
.emit('session-control', {
|
|
displayList,
|
|
});
|
|
}
|
|
|
|
async setTaskSessionStatus (session, status) {
|
|
if (status === session.status) {
|
|
return; // do nothing
|
|
}
|
|
if (!['active', 'reconnecting'].includes(status)) {
|
|
throw new SiteError(400, 'Can only set status to active or reconnecting');
|
|
}
|
|
|
|
this.log.info('updating task session status', {
|
|
user: {
|
|
_id: session.user._id,
|
|
username: session.user.username,
|
|
},
|
|
session: { _id: session._id },
|
|
status,
|
|
});
|
|
|
|
await TaskSession.updateOne({ _id: session._id }, { $set: { status } });
|
|
}
|
|
|
|
async closeTaskSession (session, status = 'finished') {
|
|
const { client: clientService } = this.dtp.services;
|
|
const NOW = new Date();
|
|
|
|
if (session.status !== 'active') {
|
|
throw new SiteError(400, 'The session is not currently active');
|
|
}
|
|
|
|
const duration = dayjs(NOW).diff(session.created, 'second');
|
|
await TaskSession.updateOne(
|
|
{ _id: session._id },
|
|
{
|
|
$set: {
|
|
finished: NOW,
|
|
status,
|
|
duration,
|
|
},
|
|
},
|
|
);
|
|
|
|
await Task.updateOne(
|
|
{ _id: session.task._id },
|
|
{
|
|
$inc: { duration },
|
|
},
|
|
);
|
|
|
|
await clientService.addTimeWorked(session.client, duration);
|
|
|
|
this.log.info('task session closed', {
|
|
user: {
|
|
_id: session.user._id,
|
|
username: session.user.username,
|
|
},
|
|
duration,
|
|
});
|
|
}
|
|
|
|
async getSessionsForTask (task, pagination) {
|
|
const sessions = TaskSession
|
|
.find({ task: task._id })
|
|
.sort({ created: 1 })
|
|
.skip(pagination.skip)
|
|
.limit(pagination.cpp)
|
|
.populate(this.populateTaskSession)
|
|
.lean();
|
|
return sessions;
|
|
}
|
|
|
|
async getTaskSessionById (sessionId) {
|
|
const session = await TaskSession
|
|
.findOne({ _id: sessionId })
|
|
.populate(this.populateTaskSession)
|
|
.lean();
|
|
return session;
|
|
}
|
|
|
|
async closeTaskSessionForUser (user) {
|
|
await TaskSession
|
|
.find({ user: user._id, status: 'active' })
|
|
.populate(this.populateTaskSession)
|
|
.cursor()
|
|
.eachAsync(async (session) => {
|
|
await this.closeTaskSession(session);
|
|
});
|
|
}
|
|
}
|