// Task Repository Implementation following Hexagonal Architecture import { formatDate, getRangeValue, removeLocalOffsetFromDate, } from "@/lib/utils"; import { and, gte, ilike, eq, count, asc, desc, ne, lte, or, isNotNull, sql, countDistinct, lt, isNull, } from "drizzle-orm"; import moment from "moment"; import { jsonAggBuildObject, jsonBuildObject } from "@/helpers/query"; import { dbGetAll, dbGetOne } from "@/lib/db/drizzle-client"; import { areas, project, task, user } from "@/database/schema"; import { db } from "@/database/db"; import { FilterTasks, MonthlyActivity, ProjectTaskQueryResult, Task, TaskId, TasksByDate, WeeklyActivity, } from "../domain/models"; import { TaskRepository } from "../domain/repositories"; import { createLogRecord } from "@/server/log"; import { UserId } from "@/server/user/domain/models"; import { Area, AreaId, AreaStatusEnum } from "@/server/project/domain/models"; class TaskRepositoryImpl implements TaskRepository { async getProjectTasks( filter: FilterTasks ): Promise { const { searchQuery, status, range } = filter; const rangeValue = range ? getRangeValue(range!) : undefined; const results = await dbGetAll("project", { where: and( searchQuery ? ilike(project.name, `%${searchQuery}%`) : undefined, !searchQuery ? ne(project.status, "completada"):undefined, !searchQuery ? ne(project.status, "cancelada"):undefined, rangeValue ? and( gte(project.updatedAt, rangeValue.start), lte(project.updatedAt, rangeValue.end) ) : undefined ), columns: { id: true, name: true, status: true, }, with: { task: { columns: { id: true, status: true, priority: true, startDate: true, floorName: true, areaId: true, userId: true, projectName: true, projectId: true, areaName: true, startedTime: true, endedTime: true, expireDate: true, comments: true, createdAT: true, updatedAt: true, }, with: { user: { columns: { id: true, firstName: true, lastName: true, }, }, }, }, }, orderBy: [asc(project.name), task.id], }); const filteredResults = !searchQuery ? results.filter( (project) => project.task.length > 0 ):results return filteredResults as ProjectTaskQueryResult[]; } async getRecentModified(): Promise { const date = moment().startOf("day").toDate(); const todayDate = removeLocalOffsetFromDate(date); const data = await db .select({ id: task.id, startDate: task.startDate, expireDate: task.expireDate, startedTime: task.startedTime, endedTime: task.endedTime, status: task.status, createdAt: task.createdAT, comments: task.comments, projectName: task.projectName, areaName: task.areaName, floorName: task.floorName, projectId: task.projectId, priority: task.priority, updatedAt: task.updatedAt, user: jsonBuildObject({ id: user.id, firstName: user.firstName, lastName: user.lastName, }), }) .from(task) .limit(5) .leftJoin(user, eq(task.userId, user.id)) .orderBy(desc(task.updatedAt)) .where(and(gte(task.updatedAt, todayDate))); return data as Task[]; } async getAll(filter: FilterTasks, userId?: UserId): Promise { const { status, searchQuery, limit, onlyUserTask, onlyRecentModifiedTask, range, } = filter; const date = moment().startOf("day").toDate(); const todayDate = removeLocalOffsetFromDate(date); const rangeValue = range ? getRangeValue(range!) : undefined; const tasksItems = await db .select({ startDate: task.startDate, tasks: jsonAggBuildObject({ id: task.id, areaId: task.areaId, userId: task.userId, startDate: task.startDate, expireDate: task.expireDate, startedTime: task.startedTime, endedTime: task.endedTime, status: task.status, createdAt: task.createdAT, comments: task.comments, projectName: task.projectName, areaName: task.areaName, floorName: task.floorName, projectId: task.projectId, priority: task.priority, updatedAt: task.updatedAt, user: jsonBuildObject({ id: user.id, firstName: user.firstName, lastName: user.lastName, }), }), }) .from(task) .limit(limit!) .innerJoin(user, eq(task.userId, user.id)) .groupBy(task.startDate) .where( and( onlyUserTask && userId?.value ? eq(user.id, userId.value) : undefined, rangeValue ? and( gte(task.startDate, formatDate(rangeValue.start)), lte(task.startDate, formatDate(rangeValue.end)) ) : undefined, searchQuery ? ilike(task.projectName, `%${searchQuery}%`) : undefined, onlyRecentModifiedTask ? gte(task.updatedAt, todayDate) : undefined, status === "pendiente" ? and( gte(task.startDate, formatDate(todayDate)), or( eq(task.status, "en proceso"), eq(task.status, "no iniciada"), eq(task.status, "reabierta") ), or( isNull(task.expireDate), gte(task.expireDate, formatDate(todayDate)) ) ) : undefined, status === "expirada" ? and( or( and( isNotNull(task.expireDate), lt(task.expireDate, formatDate(todayDate)) ), lt(task.startDate, formatDate(todayDate)) ), ne(task.status, "cancelada"), ne(task.status, "completada") ) : undefined, status === "completada" ? or(eq(task.status, "revisada"), eq(task.status, "completada")) : undefined ) ); return tasksItems as TasksByDate[]; } async create(data: Task, userId: UserId): Promise { await db.transaction(async (tx) => { const createPromise = tx.insert(task).values(data as any); const logRecordPromise = createLogRecord(tx, { userId: userId.value, modifiedItem: `Tarea-${data.areaName}/${data.floorName}/${data.projectName}`, eventType: "crear", tableName: "tool", item: data, }); await Promise.all([createPromise, logRecordPromise]); }); return data; } async updateAreaStatus( status: AreaStatusEnum, areaId: AreaId ): Promise { return db.update(areas).set({ status }).where(eq(areas.id, areaId.value)).returning(); } async update(data: Partial, userId: UserId): Promise> { const { projectId, projectName, areaId, areaName, id, floorName, userId: assignedUserId, user, ...rest } = data; const logValues = user ? { ...rest, assignedUser: `${user.firstName} ${user.lastName}` } : rest; const todayDate= removeLocalOffsetFromDate(new Date()) await db.transaction(async (tx) => { const createPromise = tx .update(task) .set({ userId: assignedUserId, ...(rest as any), ...(data?.status === 'en proceso' && {startedTime:todayDate,endedTime:null} ), ...(data?.status === 'completada' || data?.status === 'revisada' ? {endedTime:todayDate}:null ), }) .where(eq(task.id, data.id!)); const logRecordPromise = createLogRecord(tx as any, { userId: userId.value, modifiedItem: `Tarea = area:${areaName},piso:${floorName},projecto:${projectName} modificada`, eventType: "modificar", tableName: "task", item: logValues, }); const promises: any = [createPromise, logRecordPromise]; if (data?.status && data?.areaId) { const updateArePromise = this.updateAreaStatus(data.status as any, { value: data.areaId, }); promises.push(updateArePromise); } await Promise.all(promises); }); return data; } async isTaskAreaAssigned(areaId: AreaId): Promise { const result = await dbGetOne("task", { where: and(eq(task.areaId, areaId.value), ne(task.status, "cancelada")), }); return result ? true : false; } async delete(taskId: TaskId, userId: UserId): Promise { return await db.transaction(async (tx) => { const [deletedTask] = await tx .delete(task) .where(eq(task.id, taskId.value)) .returning(); if (deletedTask) { await createLogRecord(tx, { userId: userId.value, modifiedItem: `Tarea-${deletedTask.areaName}/${deletedTask.floorName}/${deletedTask.projectName}`, eventType: "eliminar", tableName: "task", item: deletedTask, }); } return deletedTask as Task; }); } async getUserWeeklyActivity(userId: UserId): Promise { const data = await db .select({ count: count(task.id), day: sql`EXTRACT(DOW FROM ${task.updatedAt})`, }) .from(task) .where( and( or(eq(task.status, "completada"), eq(task.status, "revisada")), eq(task.userId, userId.value) ) ) .groupBy(sql`EXTRACT(DOW FROM ${task.updatedAt})`); const daysOfWeek: string[] = [ "domingo", "lunes", "martes", "miercoles", "jueves", "viernes", "sabado", ]; const daysList = daysOfWeek.map((day) => { return { day, count: 0 }; }); data.forEach(({ count, day }: any) => { if (day >= 0 && day < daysList.length) { daysList[day].count = count; } }); return daysList; } async userMonthActivity(userId: UserId): Promise { const [data] = await db .select({ compleatedTasks: countDistinct(task.id), floors: countDistinct(task.floorName), projects: countDistinct(task.projectName), }) .from(task) .where( and( or(eq(task.status, "completada"), eq(task.status, "revisada")), eq(task.userId, userId.value) ) ) .groupBy(task.userId); return data; } } export const defaultTaskRepository = new TaskRepositoryImpl();