trello_fake/lib/tasksService.ts

233 lines
6.4 KiB
TypeScript

// lib/tasksService.ts
import { dolibarrFetch } from "./dolibarrClient";
import {
DolibarrTask,
Task,
mapDolibarrTask,
CreateTaskData,
UpdateTaskData
} from "@/types/task";
/**
* Obtener todas las tareas de un proyecto específico
*
* Nota: El endpoint /projects/{id}/tasks de Dolibarr no devuelve tareas correctamente,
* por lo que obtenemos todas las tareas y filtramos por fk_project en el cliente.
* Se usa limit=10000 para evitar problemas de paginación por defecto (100).
*/
export async function getTasksByProjectId(projectId: number): Promise<Task[]> {
try {
// Usar limit alto + sortfield para obtener todas las tareas y evitar paginación
const dolibarrTasks: DolibarrTask[] = await dolibarrFetch(
`tasks?limit=10000&sortfield=t.rowid&sortorder=ASC`
);
// Si no hay tareas, devolver array vacío
if (!dolibarrTasks || !Array.isArray(dolibarrTasks)) {
return [];
}
// Filtrar tareas que pertenecen a este proyecto
const projectTasks = dolibarrTasks.filter(
task => String(task.fk_project) === String(projectId)
);
// Mapear las tareas al formato de la UI
return projectTasks.map(mapDolibarrTask);
} catch (error) {
console.error('Error fetching tasks for project:', projectId, error);
// Si el error es 404 (no hay tareas), devolver array vacío
if (error instanceof Error && error.message.includes('404')) {
return [];
}
throw error;
}
}
/**
* Obtener todas las tareas (sin filtro de proyecto)
* Endpoint: /tasks
*/
export async function getAllTasks(): Promise<Task[]> {
try {
const dolibarrTasks: DolibarrTask[] = await dolibarrFetch(
'tasks?limit=10000&sortfield=t.rowid&sortorder=ASC'
);
if (!dolibarrTasks || !Array.isArray(dolibarrTasks)) {
return [];
}
return dolibarrTasks.map(mapDolibarrTask);
} catch (error) {
console.error('Error fetching all tasks:', error);
throw error;
}
}
/**
* Obtener una tarea específica por ID
* Endpoint: /tasks/{id}
*/
export async function getTaskById(taskId: number): Promise<Task | null> {
try {
const dolibarrTask: DolibarrTask = await dolibarrFetch(`tasks/${taskId}`);
return mapDolibarrTask(dolibarrTask);
} catch (error) {
console.error('Error fetching task:', taskId, error);
return null;
}
}
/**
* Obtener datos crudos de Dolibarr para una tarea
*/
export async function getDolibarrTaskById(taskId: number): Promise<DolibarrTask | null> {
try {
return await dolibarrFetch(`tasks/${taskId}`);
} catch (error) {
console.error('Error fetching Dolibarr task:', taskId, error);
return null;
}
}
/**
* Calcular estadísticas de las tareas de un proyecto
*/
export function calculateTaskStats(tasks: Task[]) {
const total = tasks.length;
const completed = tasks.filter(t => t.status === '2').length;
const inProgress = tasks.filter(t => t.status === '1').length;
const draft = tasks.filter(t => t.status === '0').length;
const totalPlannedHours = tasks.reduce((sum, t) => sum + t.plannedHours, 0);
const totalWorkedHours = tasks.reduce((sum, t) => sum + t.workedHours, 0);
const avgProgress = total > 0
? Math.round(tasks.reduce((sum, t) => sum + t.progress, 0) / total)
: 0;
const highPriority = tasks.filter(t => t.priority === '3').length;
const overdue = tasks.filter(t => {
if (!t.endDate && !t.plannedEndDate) return false;
const endDate = t.endDate || t.plannedEndDate;
return endDate && new Date(endDate) < new Date() && t.status !== '2';
}).length;
return {
total,
completed,
inProgress,
draft,
totalPlannedHours,
totalWorkedHours,
avgProgress,
highPriority,
overdue,
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0,
};
}
// ==================== FUNCIONES DE ESCRITURA (CREATE/UPDATE/DELETE) ====================
/**
* Crear una nueva tarea
* Endpoint: POST /tasks
* @returns La tarea creada con su ID
*/
export async function createTask(data: CreateTaskData): Promise<Task> {
try {
// Limpiar datos undefined/null para la API
const cleanData = Object.fromEntries(
Object.entries(data).filter(([, v]) => v !== undefined && v !== null && v !== '')
);
const response = await dolibarrFetch('tasks', {
method: 'POST',
body: JSON.stringify(cleanData),
});
// Dolibarr devuelve el ID de la tarea creada
const taskId = typeof response === 'object' ? response.id || response : response;
// Obtener la tarea completa
const createdTask = await getTaskById(Number(taskId));
if (!createdTask) {
throw new Error('No se pudo obtener la tarea creada');
}
return createdTask;
} catch (error) {
console.error('Error creating task:', error);
throw error;
}
}
/**
* Actualizar una tarea existente
* Endpoint: PUT /tasks/{id}
* @returns La tarea actualizada
*/
export async function updateTask(taskId: number, data: UpdateTaskData): Promise<Task> {
try {
// Limpiar datos undefined/null para la API
const cleanData = Object.fromEntries(
Object.entries(data).filter(([, v]) => v !== undefined && v !== null)
);
await dolibarrFetch(`tasks/${taskId}`, {
method: 'PUT',
body: JSON.stringify(cleanData),
});
// Obtener la tarea actualizada
const updatedTask = await getTaskById(taskId);
if (!updatedTask) {
throw new Error('No se pudo obtener la tarea actualizada');
}
return updatedTask;
} catch (error) {
console.error('Error updating task:', taskId, error);
throw error;
}
}
/**
* Eliminar una tarea
* Endpoint: DELETE /tasks/{id}
*/
export async function deleteTask(taskId: number): Promise<void> {
try {
await dolibarrFetch(`tasks/${taskId}`, {
method: 'DELETE',
});
} catch (error) {
console.error('Error deleting task:', taskId, error);
throw error;
}
}
/**
* Validar (cambiar estado a 1) una tarea
* Endpoint: POST /tasks/{id}/validate
*/
export async function validateTask(taskId: number): Promise<Task> {
try {
await dolibarrFetch(`tasks/${taskId}/validate`, {
method: 'POST',
body: JSON.stringify({ notrigger: 0 }),
});
const task = await getTaskById(taskId);
if (!task) {
throw new Error('No se pudo obtener la tarea validada');
}
return task;
} catch (error) {
console.error('Error validating task:', taskId, error);
throw error;
}
}