erp-core-frontend-v2/src/services/api/projects.api.ts
rckrdmrd f1a9ea3d1f [TASK-2026-01-20-004] feat: Add API clients for sales, purchases, financial, CRM, projects
EPIC-P1-004: Frontend API clients
- Add sales.api.ts (orders, quotations, returns)
- Add purchases.api.ts (purchase orders, suppliers, receipts)
- Add financial.api.ts (accounts, journals, payments)
- Add crm.api.ts (leads, opportunities, campaigns)
- Add projects.api.ts (projects, tasks, timesheets)
- Update api/index.ts with new exports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 04:06:32 -06:00

494 lines
14 KiB
TypeScript

import api from './axios-instance';
import { API_ENDPOINTS } from '@shared/constants/api-endpoints';
import type { BaseEntity } from '@shared/types/entities.types';
import type { ApiResponse, PaginatedResponse, PaginationParams } from '@shared/types/api.types';
// ============================================================================
// Interfaces
// ============================================================================
export interface Project extends BaseEntity {
name: string;
code?: string;
description?: string;
partnerId?: string;
partnerName?: string;
userId?: string;
userName?: string;
teamId?: string;
status: 'draft' | 'active' | 'on_hold' | 'completed' | 'cancelled';
priority: 'low' | 'medium' | 'high' | 'critical';
dateStart?: string;
dateEnd?: string;
plannedHours?: number;
effectiveHours?: number;
progress?: number;
color?: string;
isTemplate?: boolean;
allowTimesheets: boolean;
privacyVisibility: 'employees' | 'followers' | 'portal';
tags?: string[];
tenantId: string;
}
export interface Task extends BaseEntity {
name: string;
projectId: string;
projectName?: string;
description?: string;
parentId?: string;
userId?: string;
userName?: string;
assigneeIds?: string[];
stageId?: string;
stageName?: string;
status: 'new' | 'in_progress' | 'done' | 'cancelled';
priority: 'low' | 'medium' | 'high' | 'critical';
dateStart?: string;
dateDeadline?: string;
dateEnd?: string;
plannedHours?: number;
effectiveHours?: number;
remainingHours?: number;
progress?: number;
sequence?: number;
color?: string;
tags?: string[];
milestoneId?: string;
milestoneName?: string;
tenantId: string;
}
export interface Timesheet extends BaseEntity {
projectId: string;
projectName?: string;
taskId?: string;
taskName?: string;
userId: string;
userName?: string;
date: string;
hours: number;
description?: string;
isBillable: boolean;
unitAmount?: number;
tenantId: string;
}
export interface ProjectStage extends BaseEntity {
name: string;
sequence: number;
isFolded: boolean;
projectIds?: string[];
tenantId: string;
}
export interface Milestone extends BaseEntity {
name: string;
projectId: string;
projectName?: string;
dateDeadline?: string;
isReached: boolean;
reachedDate?: string;
description?: string;
tenantId: string;
}
// ============================================================================
// DTOs
// ============================================================================
export interface CreateProjectDto {
name: string;
code?: string;
description?: string;
partnerId?: string;
userId?: string;
teamId?: string;
priority?: Project['priority'];
dateStart?: string;
dateEnd?: string;
plannedHours?: number;
color?: string;
isTemplate?: boolean;
allowTimesheets?: boolean;
privacyVisibility?: Project['privacyVisibility'];
tags?: string[];
}
export interface UpdateProjectDto {
name?: string;
code?: string;
description?: string;
partnerId?: string;
userId?: string;
teamId?: string;
status?: Project['status'];
priority?: Project['priority'];
dateStart?: string;
dateEnd?: string;
plannedHours?: number;
color?: string;
allowTimesheets?: boolean;
privacyVisibility?: Project['privacyVisibility'];
tags?: string[];
}
export interface CreateTaskDto {
name: string;
projectId: string;
description?: string;
parentId?: string;
userId?: string;
assigneeIds?: string[];
stageId?: string;
priority?: Task['priority'];
dateStart?: string;
dateDeadline?: string;
plannedHours?: number;
sequence?: number;
color?: string;
tags?: string[];
milestoneId?: string;
}
export interface UpdateTaskDto {
name?: string;
description?: string;
parentId?: string;
userId?: string;
assigneeIds?: string[];
stageId?: string;
status?: Task['status'];
priority?: Task['priority'];
dateStart?: string;
dateDeadline?: string;
dateEnd?: string;
plannedHours?: number;
sequence?: number;
color?: string;
tags?: string[];
milestoneId?: string;
}
export interface CreateTimesheetDto {
projectId: string;
taskId?: string;
date: string;
hours: number;
description?: string;
isBillable?: boolean;
unitAmount?: number;
}
export interface UpdateTimesheetDto {
projectId?: string;
taskId?: string;
date?: string;
hours?: number;
description?: string;
isBillable?: boolean;
unitAmount?: number;
}
export interface CreateMilestoneDto {
name: string;
projectId: string;
dateDeadline?: string;
description?: string;
}
export interface UpdateMilestoneDto {
name?: string;
dateDeadline?: string;
description?: string;
isReached?: boolean;
}
// ============================================================================
// Filters
// ============================================================================
export interface ProjectFilters extends PaginationParams {
status?: Project['status'];
priority?: Project['priority'];
partnerId?: string;
userId?: string;
teamId?: string;
isTemplate?: boolean;
dateFrom?: string;
dateTo?: string;
}
export interface TaskFilters extends PaginationParams {
projectId?: string;
status?: Task['status'];
priority?: Task['priority'];
stageId?: string;
userId?: string;
assigneeId?: string;
milestoneId?: string;
dateFrom?: string;
dateTo?: string;
}
export interface TimesheetFilters extends PaginationParams {
projectId?: string;
taskId?: string;
userId?: string;
isBillable?: boolean;
dateFrom?: string;
dateTo?: string;
}
// ============================================================================
// API Client
// ============================================================================
export const projectsApi = {
// Projects
getProjects: async (filters?: ProjectFilters): Promise<PaginatedResponse<Project>> => {
const response = await api.get<PaginatedResponse<Project>>(
API_ENDPOINTS.PROJECTS.PROJECTS,
{ params: filters }
);
return response.data;
},
getProjectById: async (id: string): Promise<Project> => {
const response = await api.get<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Proyecto no encontrado');
}
return response.data.data;
},
createProject: async (data: CreateProjectDto): Promise<Project> => {
const response = await api.post<ApiResponse<Project>>(
API_ENDPOINTS.PROJECTS.PROJECTS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear proyecto');
}
return response.data.data;
},
updateProject: async (id: string, data: UpdateProjectDto): Promise<Project> => {
const response = await api.patch<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar proyecto');
}
return response.data.data;
},
archiveProject: async (id: string): Promise<Project> => {
const response = await api.post<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/archive`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al archivar proyecto');
}
return response.data.data;
},
unarchiveProject: async (id: string): Promise<Project> => {
const response = await api.post<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/unarchive`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al desarchivar proyecto');
}
return response.data.data;
},
duplicateProject: async (id: string, name?: string): Promise<Project> => {
const response = await api.post<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/duplicate`,
{ name }
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al duplicar proyecto');
}
return response.data.data;
},
deleteProject: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar proyecto');
}
},
// Tasks
getTasks: async (filters?: TaskFilters): Promise<PaginatedResponse<Task>> => {
const response = await api.get<PaginatedResponse<Task>>(
API_ENDPOINTS.PROJECTS.TASKS,
{ params: filters }
);
return response.data;
},
getTaskById: async (id: string): Promise<Task> => {
const response = await api.get<ApiResponse<Task>>(
`${API_ENDPOINTS.PROJECTS.TASKS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Tarea no encontrada');
}
return response.data.data;
},
createTask: async (data: CreateTaskDto): Promise<Task> => {
const response = await api.post<ApiResponse<Task>>(
API_ENDPOINTS.PROJECTS.TASKS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear tarea');
}
return response.data.data;
},
updateTask: async (id: string, data: UpdateTaskDto): Promise<Task> => {
const response = await api.patch<ApiResponse<Task>>(
`${API_ENDPOINTS.PROJECTS.TASKS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar tarea');
}
return response.data.data;
},
moveTask: async (id: string, stageId: string, sequence?: number): Promise<Task> => {
const response = await api.post<ApiResponse<Task>>(
`${API_ENDPOINTS.PROJECTS.TASKS}/${id}/move`,
{ stageId, sequence }
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al mover tarea');
}
return response.data.data;
},
deleteTask: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PROJECTS.TASKS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar tarea');
}
},
// Timesheets
getTimesheets: async (filters?: TimesheetFilters): Promise<PaginatedResponse<Timesheet>> => {
const response = await api.get<PaginatedResponse<Timesheet>>(
API_ENDPOINTS.PROJECTS.TIMESHEETS,
{ params: filters }
);
return response.data;
},
getTimesheetById: async (id: string): Promise<Timesheet> => {
const response = await api.get<ApiResponse<Timesheet>>(
`${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Registro de tiempo no encontrado');
}
return response.data.data;
},
createTimesheet: async (data: CreateTimesheetDto): Promise<Timesheet> => {
const response = await api.post<ApiResponse<Timesheet>>(
API_ENDPOINTS.PROJECTS.TIMESHEETS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear registro de tiempo');
}
return response.data.data;
},
updateTimesheet: async (id: string, data: UpdateTimesheetDto): Promise<Timesheet> => {
const response = await api.patch<ApiResponse<Timesheet>>(
`${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar registro de tiempo');
}
return response.data.data;
},
deleteTimesheet: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar registro de tiempo');
}
},
// Stages
getStages: async (): Promise<ProjectStage[]> => {
const response = await api.get<ApiResponse<ProjectStage[]>>(
API_ENDPOINTS.PROJECTS.STAGES
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al obtener etapas');
}
return response.data.data;
},
// Milestones
getMilestones: async (projectId: string): Promise<Milestone[]> => {
const response = await api.get<ApiResponse<Milestone[]>>(
API_ENDPOINTS.PROJECTS.MILESTONES,
{ params: { projectId } }
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al obtener hitos');
}
return response.data.data;
},
createMilestone: async (data: CreateMilestoneDto): Promise<Milestone> => {
const response = await api.post<ApiResponse<Milestone>>(
API_ENDPOINTS.PROJECTS.MILESTONES,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear hito');
}
return response.data.data;
},
updateMilestone: async (id: string, data: UpdateMilestoneDto): Promise<Milestone> => {
const response = await api.patch<ApiResponse<Milestone>>(
`${API_ENDPOINTS.PROJECTS.MILESTONES}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar hito');
}
return response.data.data;
},
deleteMilestone: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PROJECTS.MILESTONES}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar hito');
}
},
};