[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>
This commit is contained in:
parent
eb144dc3ea
commit
f1a9ea3d1f
402
src/services/api/crm.api.ts
Normal file
402
src/services/api/crm.api.ts
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
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 Lead extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
contactName?: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
mobile?: string;
|
||||||
|
companyName?: string;
|
||||||
|
title?: string;
|
||||||
|
street?: string;
|
||||||
|
city?: string;
|
||||||
|
stateId?: string;
|
||||||
|
countryId?: string;
|
||||||
|
website?: string;
|
||||||
|
description?: string;
|
||||||
|
source?: string;
|
||||||
|
medium?: string;
|
||||||
|
campaign?: string;
|
||||||
|
priority: 'low' | 'medium' | 'high' | 'very_high';
|
||||||
|
status: 'new' | 'contacted' | 'qualified' | 'proposition' | 'won' | 'lost';
|
||||||
|
stageId?: string;
|
||||||
|
stageName?: string;
|
||||||
|
userId?: string;
|
||||||
|
userName?: string;
|
||||||
|
teamId?: string;
|
||||||
|
expectedRevenue?: number;
|
||||||
|
probability?: number;
|
||||||
|
lostReason?: string;
|
||||||
|
tags?: string[];
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Opportunity extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
partnerId: string;
|
||||||
|
partnerName?: string;
|
||||||
|
contactName?: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
description?: string;
|
||||||
|
priority: 'low' | 'medium' | 'high' | 'very_high';
|
||||||
|
status: 'new' | 'qualified' | 'proposition' | 'negotiation' | 'won' | 'lost';
|
||||||
|
stageId: string;
|
||||||
|
stageName?: string;
|
||||||
|
userId?: string;
|
||||||
|
userName?: string;
|
||||||
|
teamId?: string;
|
||||||
|
expectedRevenue: number;
|
||||||
|
probability: number;
|
||||||
|
expectedCloseDate?: string;
|
||||||
|
lostReason?: string;
|
||||||
|
tags?: string[];
|
||||||
|
leadId?: string;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CrmStage extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
sequence: number;
|
||||||
|
probability: number;
|
||||||
|
isFolded: boolean;
|
||||||
|
requirements?: string;
|
||||||
|
teamId?: string;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PipelineSummary {
|
||||||
|
stages: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
sequence: number;
|
||||||
|
count: number;
|
||||||
|
totalRevenue: number;
|
||||||
|
weightedRevenue: number;
|
||||||
|
}[];
|
||||||
|
totals: {
|
||||||
|
leads: number;
|
||||||
|
opportunities: number;
|
||||||
|
totalRevenue: number;
|
||||||
|
weightedRevenue: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DTOs
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface CreateLeadDto {
|
||||||
|
name: string;
|
||||||
|
contactName?: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
mobile?: string;
|
||||||
|
companyName?: string;
|
||||||
|
title?: string;
|
||||||
|
street?: string;
|
||||||
|
city?: string;
|
||||||
|
stateId?: string;
|
||||||
|
countryId?: string;
|
||||||
|
website?: string;
|
||||||
|
description?: string;
|
||||||
|
source?: string;
|
||||||
|
medium?: string;
|
||||||
|
campaign?: string;
|
||||||
|
priority?: Lead['priority'];
|
||||||
|
stageId?: string;
|
||||||
|
userId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
expectedRevenue?: number;
|
||||||
|
probability?: number;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateLeadDto {
|
||||||
|
name?: string;
|
||||||
|
contactName?: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
mobile?: string;
|
||||||
|
companyName?: string;
|
||||||
|
title?: string;
|
||||||
|
street?: string;
|
||||||
|
city?: string;
|
||||||
|
stateId?: string;
|
||||||
|
countryId?: string;
|
||||||
|
website?: string;
|
||||||
|
description?: string;
|
||||||
|
source?: string;
|
||||||
|
medium?: string;
|
||||||
|
campaign?: string;
|
||||||
|
priority?: Lead['priority'];
|
||||||
|
status?: Lead['status'];
|
||||||
|
stageId?: string;
|
||||||
|
userId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
expectedRevenue?: number;
|
||||||
|
probability?: number;
|
||||||
|
lostReason?: string;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateOpportunityDto {
|
||||||
|
name: string;
|
||||||
|
partnerId: string;
|
||||||
|
contactName?: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
description?: string;
|
||||||
|
priority?: Opportunity['priority'];
|
||||||
|
stageId: string;
|
||||||
|
userId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
expectedRevenue: number;
|
||||||
|
probability?: number;
|
||||||
|
expectedCloseDate?: string;
|
||||||
|
tags?: string[];
|
||||||
|
leadId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateOpportunityDto {
|
||||||
|
name?: string;
|
||||||
|
partnerId?: string;
|
||||||
|
contactName?: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
description?: string;
|
||||||
|
priority?: Opportunity['priority'];
|
||||||
|
status?: Opportunity['status'];
|
||||||
|
stageId?: string;
|
||||||
|
userId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
expectedRevenue?: number;
|
||||||
|
probability?: number;
|
||||||
|
expectedCloseDate?: string;
|
||||||
|
lostReason?: string;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConvertLeadDto {
|
||||||
|
createPartner: boolean;
|
||||||
|
partnerId?: string;
|
||||||
|
createOpportunity: boolean;
|
||||||
|
opportunityName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Filters
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface LeadFilters extends PaginationParams {
|
||||||
|
status?: Lead['status'];
|
||||||
|
priority?: Lead['priority'];
|
||||||
|
stageId?: string;
|
||||||
|
userId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
source?: string;
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OpportunityFilters extends PaginationParams {
|
||||||
|
status?: Opportunity['status'];
|
||||||
|
priority?: Opportunity['priority'];
|
||||||
|
stageId?: string;
|
||||||
|
partnerId?: string;
|
||||||
|
userId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
minRevenue?: number;
|
||||||
|
maxRevenue?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// API Client
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const crmApi = {
|
||||||
|
// Leads
|
||||||
|
getLeads: async (filters?: LeadFilters): Promise<PaginatedResponse<Lead>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<Lead>>(
|
||||||
|
API_ENDPOINTS.CRM.LEADS,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getLeadById: async (id: string): Promise<Lead> => {
|
||||||
|
const response = await api.get<ApiResponse<Lead>>(
|
||||||
|
`${API_ENDPOINTS.CRM.LEADS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Lead no encontrado');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createLead: async (data: CreateLeadDto): Promise<Lead> => {
|
||||||
|
const response = await api.post<ApiResponse<Lead>>(
|
||||||
|
API_ENDPOINTS.CRM.LEADS,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear lead');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateLead: async (id: string, data: UpdateLeadDto): Promise<Lead> => {
|
||||||
|
const response = await api.patch<ApiResponse<Lead>>(
|
||||||
|
`${API_ENDPOINTS.CRM.LEADS}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar lead');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
convertLead: async (id: string, data: ConvertLeadDto): Promise<{ partner?: { id: string }; opportunity?: Opportunity }> => {
|
||||||
|
const response = await api.post<ApiResponse<{ partner?: { id: string }; opportunity?: Opportunity }>>(
|
||||||
|
`${API_ENDPOINTS.CRM.LEADS}/${id}/convert`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al convertir lead');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
markLeadAsWon: async (id: string): Promise<Lead> => {
|
||||||
|
const response = await api.post<ApiResponse<Lead>>(
|
||||||
|
`${API_ENDPOINTS.CRM.LEADS}/${id}/won`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al marcar lead como ganado');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
markLeadAsLost: async (id: string, reason?: string): Promise<Lead> => {
|
||||||
|
const response = await api.post<ApiResponse<Lead>>(
|
||||||
|
`${API_ENDPOINTS.CRM.LEADS}/${id}/lost`,
|
||||||
|
{ reason }
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al marcar lead como perdido');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteLead: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.CRM.LEADS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar lead');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Opportunities
|
||||||
|
getOpportunities: async (filters?: OpportunityFilters): Promise<PaginatedResponse<Opportunity>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<Opportunity>>(
|
||||||
|
API_ENDPOINTS.CRM.OPPORTUNITIES,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getOpportunityById: async (id: string): Promise<Opportunity> => {
|
||||||
|
const response = await api.get<ApiResponse<Opportunity>>(
|
||||||
|
`${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Oportunidad no encontrada');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createOpportunity: async (data: CreateOpportunityDto): Promise<Opportunity> => {
|
||||||
|
const response = await api.post<ApiResponse<Opportunity>>(
|
||||||
|
API_ENDPOINTS.CRM.OPPORTUNITIES,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear oportunidad');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOpportunity: async (id: string, data: UpdateOpportunityDto): Promise<Opportunity> => {
|
||||||
|
const response = await api.patch<ApiResponse<Opportunity>>(
|
||||||
|
`${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar oportunidad');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
markOpportunityAsWon: async (id: string): Promise<Opportunity> => {
|
||||||
|
const response = await api.post<ApiResponse<Opportunity>>(
|
||||||
|
`${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}/won`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al marcar oportunidad como ganada');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
markOpportunityAsLost: async (id: string, reason?: string): Promise<Opportunity> => {
|
||||||
|
const response = await api.post<ApiResponse<Opportunity>>(
|
||||||
|
`${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}/lost`,
|
||||||
|
{ reason }
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al marcar oportunidad como perdida');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteOpportunity: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar oportunidad');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Pipeline
|
||||||
|
getPipeline: async (teamId?: string): Promise<PipelineSummary> => {
|
||||||
|
const response = await api.get<ApiResponse<PipelineSummary>>(
|
||||||
|
API_ENDPOINTS.CRM.PIPELINE,
|
||||||
|
{ params: teamId ? { teamId } : undefined }
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al obtener pipeline');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stages
|
||||||
|
getStages: async (): Promise<CrmStage[]> => {
|
||||||
|
const response = await api.get<ApiResponse<CrmStage[]>>(
|
||||||
|
API_ENDPOINTS.CRM.STAGES
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al obtener etapas');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
559
src/services/api/financial.api.ts
Normal file
559
src/services/api/financial.api.ts
Normal file
@ -0,0 +1,559 @@
|
|||||||
|
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 Account extends BaseEntity {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: 'asset' | 'liability' | 'equity' | 'income' | 'expense';
|
||||||
|
accountType: string;
|
||||||
|
parentId?: string;
|
||||||
|
currencyId?: string;
|
||||||
|
reconcile: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
balance?: number;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Journal extends BaseEntity {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: 'sale' | 'purchase' | 'cash' | 'bank' | 'general';
|
||||||
|
defaultDebitAccountId?: string;
|
||||||
|
defaultCreditAccountId?: string;
|
||||||
|
currencyId?: string;
|
||||||
|
isActive: boolean;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JournalEntryLine {
|
||||||
|
id?: string;
|
||||||
|
accountId: string;
|
||||||
|
accountName?: string;
|
||||||
|
partnerId?: string;
|
||||||
|
partnerName?: string;
|
||||||
|
name?: string;
|
||||||
|
debit: number;
|
||||||
|
credit: number;
|
||||||
|
currencyId?: string;
|
||||||
|
amountCurrency?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JournalEntry extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
journalId: string;
|
||||||
|
journalName?: string;
|
||||||
|
date: string;
|
||||||
|
ref?: string;
|
||||||
|
status: 'draft' | 'posted' | 'cancelled';
|
||||||
|
lines: JournalEntryLine[];
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvoiceLine {
|
||||||
|
id?: string;
|
||||||
|
productId?: string;
|
||||||
|
productName?: string;
|
||||||
|
accountId: string;
|
||||||
|
name: string;
|
||||||
|
quantity: number;
|
||||||
|
unitPrice: number;
|
||||||
|
discount?: number;
|
||||||
|
taxIds?: string[];
|
||||||
|
subtotal?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Invoice extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
type: 'out_invoice' | 'out_refund' | 'in_invoice' | 'in_refund';
|
||||||
|
partnerId: string;
|
||||||
|
partnerName?: string;
|
||||||
|
dateInvoice: string;
|
||||||
|
dateDue?: string;
|
||||||
|
journalId: string;
|
||||||
|
status: 'draft' | 'open' | 'paid' | 'cancelled';
|
||||||
|
paymentState?: 'not_paid' | 'partial' | 'paid';
|
||||||
|
lines: InvoiceLine[];
|
||||||
|
amountUntaxed: number;
|
||||||
|
amountTax: number;
|
||||||
|
amountTotal: number;
|
||||||
|
amountResidual: number;
|
||||||
|
currencyId?: string;
|
||||||
|
notes?: string;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Payment extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
partnerId: string;
|
||||||
|
partnerName?: string;
|
||||||
|
paymentType: 'inbound' | 'outbound' | 'transfer';
|
||||||
|
paymentMethod: 'manual' | 'check' | 'bank_transfer' | 'electronic';
|
||||||
|
journalId: string;
|
||||||
|
journalName?: string;
|
||||||
|
date: string;
|
||||||
|
amount: number;
|
||||||
|
currencyId?: string;
|
||||||
|
memo?: string;
|
||||||
|
status: 'draft' | 'posted' | 'reconciled' | 'cancelled';
|
||||||
|
invoiceIds?: string[];
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DTOs
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface CreateAccountDto {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: Account['type'];
|
||||||
|
accountType: string;
|
||||||
|
parentId?: string;
|
||||||
|
currencyId?: string;
|
||||||
|
reconcile?: boolean;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateAccountDto {
|
||||||
|
code?: string;
|
||||||
|
name?: string;
|
||||||
|
type?: Account['type'];
|
||||||
|
accountType?: string;
|
||||||
|
parentId?: string;
|
||||||
|
currencyId?: string;
|
||||||
|
reconcile?: boolean;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateJournalDto {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: Journal['type'];
|
||||||
|
defaultDebitAccountId?: string;
|
||||||
|
defaultCreditAccountId?: string;
|
||||||
|
currencyId?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateJournalDto {
|
||||||
|
code?: string;
|
||||||
|
name?: string;
|
||||||
|
type?: Journal['type'];
|
||||||
|
defaultDebitAccountId?: string;
|
||||||
|
defaultCreditAccountId?: string;
|
||||||
|
currencyId?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateJournalEntryDto {
|
||||||
|
journalId: string;
|
||||||
|
date: string;
|
||||||
|
ref?: string;
|
||||||
|
lines: Omit<JournalEntryLine, 'id' | 'accountName' | 'partnerName'>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateInvoiceDto {
|
||||||
|
type: Invoice['type'];
|
||||||
|
partnerId: string;
|
||||||
|
dateInvoice?: string;
|
||||||
|
dateDue?: string;
|
||||||
|
journalId: string;
|
||||||
|
lines: Omit<InvoiceLine, 'id' | 'productName' | 'subtotal'>[];
|
||||||
|
currencyId?: string;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateInvoiceDto {
|
||||||
|
partnerId?: string;
|
||||||
|
dateInvoice?: string;
|
||||||
|
dateDue?: string;
|
||||||
|
journalId?: string;
|
||||||
|
lines?: Omit<InvoiceLine, 'productName' | 'subtotal'>[];
|
||||||
|
currencyId?: string;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePaymentDto {
|
||||||
|
partnerId: string;
|
||||||
|
paymentType: Payment['paymentType'];
|
||||||
|
paymentMethod: Payment['paymentMethod'];
|
||||||
|
journalId: string;
|
||||||
|
date?: string;
|
||||||
|
amount: number;
|
||||||
|
currencyId?: string;
|
||||||
|
memo?: string;
|
||||||
|
invoiceIds?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePaymentDto {
|
||||||
|
partnerId?: string;
|
||||||
|
paymentMethod?: Payment['paymentMethod'];
|
||||||
|
journalId?: string;
|
||||||
|
date?: string;
|
||||||
|
amount?: number;
|
||||||
|
currencyId?: string;
|
||||||
|
memo?: string;
|
||||||
|
invoiceIds?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Filters
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface AccountFilters extends PaginationParams {
|
||||||
|
type?: Account['type'];
|
||||||
|
isActive?: boolean;
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JournalFilters extends PaginationParams {
|
||||||
|
type?: Journal['type'];
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JournalEntryFilters extends PaginationParams {
|
||||||
|
journalId?: string;
|
||||||
|
status?: JournalEntry['status'];
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvoiceFilters extends PaginationParams {
|
||||||
|
type?: Invoice['type'];
|
||||||
|
partnerId?: string;
|
||||||
|
status?: Invoice['status'];
|
||||||
|
paymentState?: Invoice['paymentState'];
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentFilters extends PaginationParams {
|
||||||
|
partnerId?: string;
|
||||||
|
paymentType?: Payment['paymentType'];
|
||||||
|
status?: Payment['status'];
|
||||||
|
journalId?: string;
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// API Client
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const financialApi = {
|
||||||
|
// Accounts
|
||||||
|
getAccounts: async (filters?: AccountFilters): Promise<PaginatedResponse<Account>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<Account>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.ACCOUNTS,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getAccountById: async (id: string): Promise<Account> => {
|
||||||
|
const response = await api.get<ApiResponse<Account>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.ACCOUNTS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Cuenta contable no encontrada');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createAccount: async (data: CreateAccountDto): Promise<Account> => {
|
||||||
|
const response = await api.post<ApiResponse<Account>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.ACCOUNTS,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear cuenta contable');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAccount: async (id: string, data: UpdateAccountDto): Promise<Account> => {
|
||||||
|
const response = await api.patch<ApiResponse<Account>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.ACCOUNTS}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar cuenta contable');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteAccount: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.ACCOUNTS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar cuenta contable');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Journals
|
||||||
|
getJournals: async (filters?: JournalFilters): Promise<PaginatedResponse<Journal>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<Journal>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.JOURNALS,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getJournalById: async (id: string): Promise<Journal> => {
|
||||||
|
const response = await api.get<ApiResponse<Journal>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.JOURNALS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Diario contable no encontrado');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createJournal: async (data: CreateJournalDto): Promise<Journal> => {
|
||||||
|
const response = await api.post<ApiResponse<Journal>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.JOURNALS,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear diario contable');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateJournal: async (id: string, data: UpdateJournalDto): Promise<Journal> => {
|
||||||
|
const response = await api.patch<ApiResponse<Journal>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.JOURNALS}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar diario contable');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteJournal: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.JOURNALS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar diario contable');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Journal Entries (Moves)
|
||||||
|
getJournalEntries: async (filters?: JournalEntryFilters): Promise<PaginatedResponse<JournalEntry>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<JournalEntry>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.MOVES,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getJournalEntryById: async (id: string): Promise<JournalEntry> => {
|
||||||
|
const response = await api.get<ApiResponse<JournalEntry>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.MOVES}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Asiento contable no encontrado');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createJournalEntry: async (data: CreateJournalEntryDto): Promise<JournalEntry> => {
|
||||||
|
const response = await api.post<ApiResponse<JournalEntry>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.MOVES,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear asiento contable');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
postJournalEntry: async (id: string): Promise<JournalEntry> => {
|
||||||
|
const response = await api.post<ApiResponse<JournalEntry>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.MOVES}/${id}/post`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al publicar asiento contable');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelJournalEntry: async (id: string): Promise<JournalEntry> => {
|
||||||
|
const response = await api.post<ApiResponse<JournalEntry>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.MOVES}/${id}/cancel`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al cancelar asiento contable');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteJournalEntry: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.MOVES}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar asiento contable');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Invoices
|
||||||
|
getInvoices: async (filters?: InvoiceFilters): Promise<PaginatedResponse<Invoice>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<Invoice>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.INVOICES,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getInvoiceById: async (id: string): Promise<Invoice> => {
|
||||||
|
const response = await api.get<ApiResponse<Invoice>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Factura no encontrada');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createInvoice: async (data: CreateInvoiceDto): Promise<Invoice> => {
|
||||||
|
const response = await api.post<ApiResponse<Invoice>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.INVOICES,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear factura');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateInvoice: async (id: string, data: UpdateInvoiceDto): Promise<Invoice> => {
|
||||||
|
const response = await api.patch<ApiResponse<Invoice>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar factura');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
validateInvoice: async (id: string): Promise<Invoice> => {
|
||||||
|
const response = await api.post<ApiResponse<Invoice>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}/validate`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al validar factura');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelInvoice: async (id: string): Promise<Invoice> => {
|
||||||
|
const response = await api.post<ApiResponse<Invoice>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}/cancel`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al cancelar factura');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteInvoice: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar factura');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Payments
|
||||||
|
getPayments: async (filters?: PaymentFilters): Promise<PaginatedResponse<Payment>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<Payment>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.PAYMENTS,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPaymentById: async (id: string): Promise<Payment> => {
|
||||||
|
const response = await api.get<ApiResponse<Payment>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Pago no encontrado');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createPayment: async (data: CreatePaymentDto): Promise<Payment> => {
|
||||||
|
const response = await api.post<ApiResponse<Payment>>(
|
||||||
|
API_ENDPOINTS.FINANCIAL.PAYMENTS,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear pago');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePayment: async (id: string, data: UpdatePaymentDto): Promise<Payment> => {
|
||||||
|
const response = await api.patch<ApiResponse<Payment>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar pago');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
postPayment: async (id: string): Promise<Payment> => {
|
||||||
|
const response = await api.post<ApiResponse<Payment>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}/post`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al publicar pago');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
reconcilePayment: async (id: string, invoiceIds: string[]): Promise<Payment> => {
|
||||||
|
const response = await api.post<ApiResponse<Payment>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}/reconcile`,
|
||||||
|
{ invoiceIds }
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al reconciliar pago');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelPayment: async (id: string): Promise<Payment> => {
|
||||||
|
const response = await api.post<ApiResponse<Payment>>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}/cancel`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al cancelar pago');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deletePayment: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar pago');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -5,3 +5,8 @@ export * from './products.api';
|
|||||||
export * from './inventory.api';
|
export * from './inventory.api';
|
||||||
export * from './warehouses.api';
|
export * from './warehouses.api';
|
||||||
export * from './billing.api';
|
export * from './billing.api';
|
||||||
|
export * from './sales.api';
|
||||||
|
export * from './purchases.api';
|
||||||
|
export * from './financial.api';
|
||||||
|
export * from './crm.api';
|
||||||
|
export * from './projects.api';
|
||||||
|
|||||||
493
src/services/api/projects.api.ts
Normal file
493
src/services/api/projects.api.ts
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
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');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
279
src/services/api/purchases.api.ts
Normal file
279
src/services/api/purchases.api.ts
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
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 PurchaseOrderLine {
|
||||||
|
id?: string;
|
||||||
|
productId: string;
|
||||||
|
productName?: string;
|
||||||
|
quantity: number;
|
||||||
|
unitPrice: number;
|
||||||
|
discount?: number;
|
||||||
|
taxIds?: string[];
|
||||||
|
subtotal?: number;
|
||||||
|
dateExpected?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseOrder extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
partnerId: string;
|
||||||
|
partnerName?: string;
|
||||||
|
dateOrder: string;
|
||||||
|
dateExpected?: string;
|
||||||
|
status: 'draft' | 'confirmed' | 'received' | 'done' | 'cancelled';
|
||||||
|
paymentTermId?: string;
|
||||||
|
warehouseId?: string;
|
||||||
|
lines: PurchaseOrderLine[];
|
||||||
|
amountUntaxed: number;
|
||||||
|
amountTax: number;
|
||||||
|
amountTotal: number;
|
||||||
|
notes?: string;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseRfq extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
partnerId: string;
|
||||||
|
partnerName?: string;
|
||||||
|
dateRfq: string;
|
||||||
|
dateExpiry?: string;
|
||||||
|
status: 'draft' | 'sent' | 'confirmed' | 'cancelled' | 'expired';
|
||||||
|
paymentTermId?: string;
|
||||||
|
lines: PurchaseOrderLine[];
|
||||||
|
amountUntaxed: number;
|
||||||
|
amountTax: number;
|
||||||
|
amountTotal: number;
|
||||||
|
notes?: string;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DTOs
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface CreatePurchaseOrderDto {
|
||||||
|
partnerId: string;
|
||||||
|
dateOrder?: string;
|
||||||
|
dateExpected?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
warehouseId?: string;
|
||||||
|
lines: Omit<PurchaseOrderLine, 'id' | 'productName' | 'subtotal'>[];
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePurchaseOrderDto {
|
||||||
|
partnerId?: string;
|
||||||
|
dateOrder?: string;
|
||||||
|
dateExpected?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
warehouseId?: string;
|
||||||
|
lines?: Omit<PurchaseOrderLine, 'productName' | 'subtotal'>[];
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePurchaseRfqDto {
|
||||||
|
partnerId: string;
|
||||||
|
dateRfq?: string;
|
||||||
|
dateExpiry?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
lines: Omit<PurchaseOrderLine, 'id' | 'productName' | 'subtotal'>[];
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePurchaseRfqDto {
|
||||||
|
partnerId?: string;
|
||||||
|
dateRfq?: string;
|
||||||
|
dateExpiry?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
lines?: Omit<PurchaseOrderLine, 'productName' | 'subtotal'>[];
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Filters
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface PurchaseOrderFilters extends PaginationParams {
|
||||||
|
partnerId?: string;
|
||||||
|
status?: PurchaseOrder['status'];
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseRfqFilters extends PaginationParams {
|
||||||
|
partnerId?: string;
|
||||||
|
status?: PurchaseRfq['status'];
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// API Client
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const purchasesApi = {
|
||||||
|
// Orders
|
||||||
|
getOrders: async (filters?: PurchaseOrderFilters): Promise<PaginatedResponse<PurchaseOrder>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<PurchaseOrder>>(
|
||||||
|
API_ENDPOINTS.PURCHASES.ORDERS,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getOrderById: async (id: string): Promise<PurchaseOrder> => {
|
||||||
|
const response = await api.get<ApiResponse<PurchaseOrder>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Orden de compra no encontrada');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createOrder: async (data: CreatePurchaseOrderDto): Promise<PurchaseOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<PurchaseOrder>>(
|
||||||
|
API_ENDPOINTS.PURCHASES.ORDERS,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear orden de compra');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOrder: async (id: string, data: UpdatePurchaseOrderDto): Promise<PurchaseOrder> => {
|
||||||
|
const response = await api.patch<ApiResponse<PurchaseOrder>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar orden de compra');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmOrder: async (id: string): Promise<PurchaseOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<PurchaseOrder>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}/confirm`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al confirmar orden de compra');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
receiveOrder: async (id: string): Promise<PurchaseOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<PurchaseOrder>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}/receive`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al recibir orden de compra');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelOrder: async (id: string): Promise<PurchaseOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<PurchaseOrder>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}/cancel`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al cancelar orden de compra');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteOrder: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar orden de compra');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// RFQs (Request for Quotation)
|
||||||
|
getRfqs: async (filters?: PurchaseRfqFilters): Promise<PaginatedResponse<PurchaseRfq>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<PurchaseRfq>>(
|
||||||
|
API_ENDPOINTS.PURCHASES.RFQS,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getRfqById: async (id: string): Promise<PurchaseRfq> => {
|
||||||
|
const response = await api.get<ApiResponse<PurchaseRfq>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Solicitud de cotizacion no encontrada');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createRfq: async (data: CreatePurchaseRfqDto): Promise<PurchaseRfq> => {
|
||||||
|
const response = await api.post<ApiResponse<PurchaseRfq>>(
|
||||||
|
API_ENDPOINTS.PURCHASES.RFQS,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear solicitud de cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRfq: async (id: string, data: UpdatePurchaseRfqDto): Promise<PurchaseRfq> => {
|
||||||
|
const response = await api.patch<ApiResponse<PurchaseRfq>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar solicitud de cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
sendRfq: async (id: string): Promise<PurchaseRfq> => {
|
||||||
|
const response = await api.post<ApiResponse<PurchaseRfq>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}/send`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al enviar solicitud de cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmRfq: async (id: string): Promise<PurchaseOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<PurchaseOrder>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}/confirm`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al confirmar solicitud de cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelRfq: async (id: string): Promise<PurchaseRfq> => {
|
||||||
|
const response = await api.post<ApiResponse<PurchaseRfq>>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}/cancel`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al cancelar solicitud de cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRfq: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar solicitud de cotizacion');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
274
src/services/api/sales.api.ts
Normal file
274
src/services/api/sales.api.ts
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
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 SalesOrderLine {
|
||||||
|
id?: string;
|
||||||
|
productId: string;
|
||||||
|
productName?: string;
|
||||||
|
quantity: number;
|
||||||
|
unitPrice: number;
|
||||||
|
discount?: number;
|
||||||
|
taxIds?: string[];
|
||||||
|
subtotal?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SalesOrder extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
partnerId: string;
|
||||||
|
partnerName?: string;
|
||||||
|
dateOrder: string;
|
||||||
|
dateDelivery?: string;
|
||||||
|
status: 'draft' | 'confirmed' | 'done' | 'cancelled';
|
||||||
|
pricelistId?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
warehouseId?: string;
|
||||||
|
lines: SalesOrderLine[];
|
||||||
|
amountUntaxed: number;
|
||||||
|
amountTax: number;
|
||||||
|
amountTotal: number;
|
||||||
|
notes?: string;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Quotation extends BaseEntity {
|
||||||
|
name: string;
|
||||||
|
partnerId: string;
|
||||||
|
partnerName?: string;
|
||||||
|
dateQuotation: string;
|
||||||
|
dateExpiry?: string;
|
||||||
|
status: 'draft' | 'sent' | 'confirmed' | 'cancelled' | 'expired';
|
||||||
|
pricelistId?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
lines: SalesOrderLine[];
|
||||||
|
amountUntaxed: number;
|
||||||
|
amountTax: number;
|
||||||
|
amountTotal: number;
|
||||||
|
notes?: string;
|
||||||
|
tenantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DTOs
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface CreateSalesOrderDto {
|
||||||
|
partnerId: string;
|
||||||
|
dateOrder?: string;
|
||||||
|
dateDelivery?: string;
|
||||||
|
pricelistId?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
warehouseId?: string;
|
||||||
|
lines: Omit<SalesOrderLine, 'id' | 'productName' | 'subtotal'>[];
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateSalesOrderDto {
|
||||||
|
partnerId?: string;
|
||||||
|
dateOrder?: string;
|
||||||
|
dateDelivery?: string;
|
||||||
|
pricelistId?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
warehouseId?: string;
|
||||||
|
lines?: Omit<SalesOrderLine, 'productName' | 'subtotal'>[];
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateQuotationDto {
|
||||||
|
partnerId: string;
|
||||||
|
dateQuotation?: string;
|
||||||
|
dateExpiry?: string;
|
||||||
|
pricelistId?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
lines: Omit<SalesOrderLine, 'id' | 'productName' | 'subtotal'>[];
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateQuotationDto {
|
||||||
|
partnerId?: string;
|
||||||
|
dateQuotation?: string;
|
||||||
|
dateExpiry?: string;
|
||||||
|
pricelistId?: string;
|
||||||
|
paymentTermId?: string;
|
||||||
|
lines?: Omit<SalesOrderLine, 'productName' | 'subtotal'>[];
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Filters
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface SalesOrderFilters extends PaginationParams {
|
||||||
|
partnerId?: string;
|
||||||
|
status?: SalesOrder['status'];
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuotationFilters extends PaginationParams {
|
||||||
|
partnerId?: string;
|
||||||
|
status?: Quotation['status'];
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// API Client
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const salesApi = {
|
||||||
|
// Orders
|
||||||
|
getOrders: async (filters?: SalesOrderFilters): Promise<PaginatedResponse<SalesOrder>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<SalesOrder>>(
|
||||||
|
API_ENDPOINTS.SALES.ORDERS,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getOrderById: async (id: string): Promise<SalesOrder> => {
|
||||||
|
const response = await api.get<ApiResponse<SalesOrder>>(
|
||||||
|
`${API_ENDPOINTS.SALES.ORDERS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Orden de venta no encontrada');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createOrder: async (data: CreateSalesOrderDto): Promise<SalesOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<SalesOrder>>(
|
||||||
|
API_ENDPOINTS.SALES.ORDERS,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear orden de venta');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOrder: async (id: string, data: UpdateSalesOrderDto): Promise<SalesOrder> => {
|
||||||
|
const response = await api.patch<ApiResponse<SalesOrder>>(
|
||||||
|
`${API_ENDPOINTS.SALES.ORDERS}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar orden de venta');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmOrder: async (id: string): Promise<SalesOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<SalesOrder>>(
|
||||||
|
`${API_ENDPOINTS.SALES.ORDERS}/${id}/confirm`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al confirmar orden de venta');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelOrder: async (id: string): Promise<SalesOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<SalesOrder>>(
|
||||||
|
`${API_ENDPOINTS.SALES.ORDERS}/${id}/cancel`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al cancelar orden de venta');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteOrder: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.SALES.ORDERS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar orden de venta');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quotations
|
||||||
|
getQuotations: async (filters?: QuotationFilters): Promise<PaginatedResponse<Quotation>> => {
|
||||||
|
const response = await api.get<PaginatedResponse<Quotation>>(
|
||||||
|
API_ENDPOINTS.SALES.QUOTATIONS,
|
||||||
|
{ params: filters }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getQuotationById: async (id: string): Promise<Quotation> => {
|
||||||
|
const response = await api.get<ApiResponse<Quotation>>(
|
||||||
|
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Cotizacion no encontrada');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createQuotation: async (data: CreateQuotationDto): Promise<Quotation> => {
|
||||||
|
const response = await api.post<ApiResponse<Quotation>>(
|
||||||
|
API_ENDPOINTS.SALES.QUOTATIONS,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al crear cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateQuotation: async (id: string, data: UpdateQuotationDto): Promise<Quotation> => {
|
||||||
|
const response = await api.patch<ApiResponse<Quotation>>(
|
||||||
|
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al actualizar cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmQuotation: async (id: string): Promise<SalesOrder> => {
|
||||||
|
const response = await api.post<ApiResponse<SalesOrder>>(
|
||||||
|
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}/confirm`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al confirmar cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
sendQuotation: async (id: string): Promise<Quotation> => {
|
||||||
|
const response = await api.post<ApiResponse<Quotation>>(
|
||||||
|
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}/send`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al enviar cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelQuotation: async (id: string): Promise<Quotation> => {
|
||||||
|
const response = await api.post<ApiResponse<Quotation>>(
|
||||||
|
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}/cancel`
|
||||||
|
);
|
||||||
|
if (!response.data.success || !response.data.data) {
|
||||||
|
throw new Error(response.data.error || 'Error al cancelar cotizacion');
|
||||||
|
}
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteQuotation: async (id: string): Promise<void> => {
|
||||||
|
const response = await api.delete<ApiResponse>(
|
||||||
|
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}`
|
||||||
|
);
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new Error(response.data.error || 'Error al eliminar cotizacion');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user