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>
494 lines
14 KiB
TypeScript
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');
|
|
}
|
|
},
|
|
};
|