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