feat(frontend): Add API services for invoices, inventory, sales, purchases

- Add invoices feature: types, API service for CRUD, validation, payments, PDF
- Add inventory feature: types, API service for stock, movements, warehouses, locations
- Add sales feature: types, API service for orders, quotations, PDF
- Add purchases feature: types, API service for orders, receipts

Part of FASE 2: P0 Frontend API Services
This commit is contained in:
rckrdmrd 2026-01-18 02:47:54 -06:00
parent d3fdc0b9c0
commit 6cb8e99f50
16 changed files with 1498 additions and 0 deletions

View File

@ -0,0 +1,204 @@
import { api } from '@services/api/axios-instance';
import type {
StockLevel,
StockMovement,
StockSearchParams,
MovementSearchParams,
CreateStockMovementDto,
AdjustStockDto,
TransferStockDto,
ReserveStockDto,
StockLevelsResponse,
MovementsResponse,
Warehouse,
CreateWarehouseDto,
UpdateWarehouseDto,
WarehousesResponse,
Location,
CreateLocationDto,
UpdateLocationDto,
LocationsResponse,
} from '../types';
const STOCK_URL = '/api/v1/inventory/stock';
const MOVEMENTS_URL = '/api/v1/inventory/movements';
const WAREHOUSES_URL = '/api/v1/warehouses';
const LOCATIONS_URL = '/api/v1/inventory/locations';
export const inventoryApi = {
// ==================== Stock Levels ====================
// Get stock levels with filters
getStockLevels: async (params?: StockSearchParams): Promise<StockLevelsResponse> => {
const searchParams = new URLSearchParams();
if (params?.productId) searchParams.append('productId', params.productId);
if (params?.warehouseId) searchParams.append('warehouseId', params.warehouseId);
if (params?.locationId) searchParams.append('locationId', params.locationId);
if (params?.lotNumber) searchParams.append('lotNumber', params.lotNumber);
if (params?.hasStock !== undefined) searchParams.append('hasStock', String(params.hasStock));
if (params?.lowStock !== undefined) searchParams.append('lowStock', String(params.lowStock));
if (params?.page) searchParams.append('page', String(params.page));
if (params?.limit) searchParams.append('limit', String(params.limit));
const response = await api.get<StockLevelsResponse>(`${STOCK_URL}?${searchParams.toString()}`);
return response.data;
},
// Get stock by product
getStockByProduct: async (productId: string): Promise<StockLevel[]> => {
const response = await api.get<StockLevel[]>(`${STOCK_URL}/product/${productId}`);
return response.data;
},
// Get stock by warehouse
getStockByWarehouse: async (warehouseId: string): Promise<StockLevel[]> => {
const response = await api.get<StockLevel[]>(`${STOCK_URL}/warehouse/${warehouseId}`);
return response.data;
},
// Get available stock for a product in a warehouse
getAvailableStock: async (productId: string, warehouseId: string): Promise<number> => {
const response = await api.get<{ available: number }>(`${STOCK_URL}/available/${productId}/${warehouseId}`);
return response.data.available;
},
// ==================== Stock Movements ====================
// Get movements with filters
getMovements: async (params?: MovementSearchParams): Promise<MovementsResponse> => {
const searchParams = new URLSearchParams();
if (params?.movementType) searchParams.append('movementType', params.movementType);
if (params?.productId) searchParams.append('productId', params.productId);
if (params?.warehouseId) searchParams.append('warehouseId', params.warehouseId);
if (params?.status) searchParams.append('status', params.status);
if (params?.referenceType) searchParams.append('referenceType', params.referenceType);
if (params?.referenceId) searchParams.append('referenceId', params.referenceId);
if (params?.fromDate) searchParams.append('fromDate', params.fromDate);
if (params?.toDate) searchParams.append('toDate', params.toDate);
if (params?.page) searchParams.append('page', String(params.page));
if (params?.limit) searchParams.append('limit', String(params.limit));
const response = await api.get<MovementsResponse>(`${MOVEMENTS_URL}?${searchParams.toString()}`);
return response.data;
},
// Get movement by ID
getMovementById: async (id: string): Promise<StockMovement> => {
const response = await api.get<StockMovement>(`${MOVEMENTS_URL}/${id}`);
return response.data;
},
// Create movement
createMovement: async (data: CreateStockMovementDto): Promise<StockMovement> => {
const response = await api.post<StockMovement>(MOVEMENTS_URL, data);
return response.data;
},
// Confirm movement
confirmMovement: async (id: string): Promise<StockMovement> => {
const response = await api.post<StockMovement>(`${MOVEMENTS_URL}/${id}/confirm`);
return response.data;
},
// Cancel movement
cancelMovement: async (id: string): Promise<StockMovement> => {
const response = await api.post<StockMovement>(`${MOVEMENTS_URL}/${id}/cancel`);
return response.data;
},
// ==================== Stock Operations ====================
// Adjust stock
adjustStock: async (data: AdjustStockDto): Promise<StockMovement> => {
const response = await api.post<StockMovement>(`${STOCK_URL}/adjust`, data);
return response.data;
},
// Transfer stock between warehouses
transferStock: async (data: TransferStockDto): Promise<StockMovement> => {
const response = await api.post<StockMovement>(`${STOCK_URL}/transfer`, data);
return response.data;
},
// Reserve stock
reserveStock: async (data: ReserveStockDto): Promise<{ success: boolean }> => {
const response = await api.post<{ success: boolean }>(`${STOCK_URL}/reserve`, data);
return response.data;
},
// Release reservation
releaseReservation: async (productId: string, warehouseId: string, quantity: number): Promise<{ success: boolean }> => {
const response = await api.post<{ success: boolean }>(`${STOCK_URL}/release`, {
productId,
warehouseId,
quantity,
});
return response.data;
},
// ==================== Warehouses ====================
// Get all warehouses
getWarehouses: async (page?: number, limit?: number): Promise<WarehousesResponse> => {
const params = new URLSearchParams();
if (page) params.append('page', String(page));
if (limit) params.append('limit', String(limit));
const response = await api.get<WarehousesResponse>(`${WAREHOUSES_URL}?${params.toString()}`);
return response.data;
},
// Get warehouse by ID
getWarehouseById: async (id: string): Promise<Warehouse> => {
const response = await api.get<Warehouse>(`${WAREHOUSES_URL}/${id}`);
return response.data;
},
// Create warehouse
createWarehouse: async (data: CreateWarehouseDto): Promise<Warehouse> => {
const response = await api.post<Warehouse>(WAREHOUSES_URL, data);
return response.data;
},
// Update warehouse
updateWarehouse: async (id: string, data: UpdateWarehouseDto): Promise<Warehouse> => {
const response = await api.patch<Warehouse>(`${WAREHOUSES_URL}/${id}`, data);
return response.data;
},
// Delete warehouse
deleteWarehouse: async (id: string): Promise<void> => {
await api.delete(`${WAREHOUSES_URL}/${id}`);
},
// ==================== Locations ====================
// Get locations by warehouse
getLocationsByWarehouse: async (warehouseId: string): Promise<LocationsResponse> => {
const response = await api.get<LocationsResponse>(`${LOCATIONS_URL}?warehouseId=${warehouseId}`);
return response.data;
},
// Get location by ID
getLocationById: async (id: string): Promise<Location> => {
const response = await api.get<Location>(`${LOCATIONS_URL}/${id}`);
return response.data;
},
// Create location
createLocation: async (data: CreateLocationDto): Promise<Location> => {
const response = await api.post<Location>(LOCATIONS_URL, data);
return response.data;
},
// Update location
updateLocation: async (id: string, data: UpdateLocationDto): Promise<Location> => {
const response = await api.patch<Location>(`${LOCATIONS_URL}/${id}`, data);
return response.data;
},
// Delete location
deleteLocation: async (id: string): Promise<void> => {
await api.delete(`${LOCATIONS_URL}/${id}`);
},
};

View File

@ -0,0 +1,2 @@
export * from './api/inventory.api';
export * from './types';

View File

@ -0,0 +1 @@
export * from './inventory.types';

View File

@ -0,0 +1,223 @@
export type MovementType = 'receipt' | 'shipment' | 'transfer' | 'adjustment' | 'return' | 'production' | 'consumption';
export type MovementStatus = 'draft' | 'confirmed' | 'cancelled';
export interface StockLevel {
id: string;
tenantId: string;
productId: string;
warehouseId: string;
locationId?: string | null;
lotNumber?: string | null;
serialNumber?: string | null;
quantityOnHand: number;
quantityReserved: number;
quantityAvailable: number;
quantityIncoming: number;
quantityOutgoing: number;
unitCost?: number | null;
totalCost?: number | null;
lastMovementAt?: string | null;
createdAt: string;
updatedAt: string;
}
export interface StockMovement {
id: string;
tenantId: string;
movementNumber: string;
movementType: MovementType;
productId: string;
sourceWarehouseId?: string | null;
sourceLocationId?: string | null;
destWarehouseId?: string | null;
destLocationId?: string | null;
quantity: number;
unitCost?: number | null;
totalCost?: number | null;
lotNumber?: string | null;
serialNumber?: string | null;
expiryDate?: string | null;
reason?: string | null;
notes?: string | null;
referenceType?: string | null;
referenceId?: string | null;
status: MovementStatus;
confirmedAt?: string | null;
confirmedBy?: string | null;
createdBy?: string | null;
createdAt: string;
updatedAt: string;
}
export interface StockSearchParams {
productId?: string;
warehouseId?: string;
locationId?: string;
lotNumber?: string;
hasStock?: boolean;
lowStock?: boolean;
page?: number;
limit?: number;
}
export interface MovementSearchParams {
movementType?: MovementType;
productId?: string;
warehouseId?: string;
status?: MovementStatus;
referenceType?: string;
referenceId?: string;
fromDate?: string;
toDate?: string;
page?: number;
limit?: number;
}
export interface CreateStockMovementDto {
movementType: MovementType;
productId: string;
sourceWarehouseId?: string;
sourceLocationId?: string;
destWarehouseId?: string;
destLocationId?: string;
quantity: number;
unitCost?: number;
lotNumber?: string;
serialNumber?: string;
expiryDate?: string;
reason?: string;
notes?: string;
referenceType?: string;
referenceId?: string;
}
export interface AdjustStockDto {
productId: string;
warehouseId: string;
locationId?: string;
lotNumber?: string;
serialNumber?: string;
newQuantity: number;
reason?: string;
notes?: string;
}
export interface TransferStockDto {
productId: string;
sourceWarehouseId: string;
sourceLocationId?: string;
destWarehouseId: string;
destLocationId?: string;
quantity: number;
lotNumber?: string;
serialNumber?: string;
notes?: string;
}
export interface ReserveStockDto {
productId: string;
warehouseId: string;
locationId?: string;
lotNumber?: string;
quantity: number;
referenceType?: string;
referenceId?: string;
}
export interface StockLevelsResponse {
data: StockLevel[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
export interface MovementsResponse {
data: StockMovement[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
// Warehouse types
export interface Warehouse {
id: string;
tenantId: string;
code: string;
name: string;
warehouseType: 'main' | 'transit' | 'customer' | 'supplier' | 'virtual';
address?: Record<string, any> | null;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
export interface CreateWarehouseDto {
code: string;
name: string;
warehouseType?: 'main' | 'transit' | 'customer' | 'supplier' | 'virtual';
address?: Record<string, any>;
}
export interface UpdateWarehouseDto {
code?: string;
name?: string;
warehouseType?: 'main' | 'transit' | 'customer' | 'supplier' | 'virtual';
address?: Record<string, any> | null;
isActive?: boolean;
}
export interface WarehousesResponse {
data: Warehouse[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
// Location types
export interface Location {
id: string;
tenantId: string;
warehouseId: string;
code: string;
name: string;
locationType: 'internal' | 'view' | 'supplier' | 'customer' | 'inventory' | 'production' | 'transit';
parentId?: string | null;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
export interface CreateLocationDto {
warehouseId: string;
code: string;
name: string;
locationType?: 'internal' | 'view' | 'supplier' | 'customer' | 'inventory' | 'production' | 'transit';
parentId?: string;
}
export interface UpdateLocationDto {
code?: string;
name?: string;
locationType?: 'internal' | 'view' | 'supplier' | 'customer' | 'inventory' | 'production' | 'transit';
parentId?: string | null;
isActive?: boolean;
}
export interface LocationsResponse {
data: Location[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}

View File

@ -0,0 +1,138 @@
import { api } from '@services/api/axios-instance';
import type {
Invoice,
CreateInvoiceDto,
UpdateInvoiceDto,
InvoiceFilters,
InvoicesResponse,
InvoiceStats,
} from '../types';
const BASE_URL = '/api/v1/invoices';
export const invoicesApi = {
// Get all invoices with filters
getAll: async (filters?: InvoiceFilters): Promise<InvoicesResponse> => {
const params = new URLSearchParams();
if (filters?.search) params.append('search', filters.search);
if (filters?.invoiceType) params.append('invoiceType', filters.invoiceType);
if (filters?.invoiceContext) params.append('invoiceContext', filters.invoiceContext);
if (filters?.status) params.append('status', filters.status);
if (filters?.partnerId) params.append('partnerId', filters.partnerId);
if (filters?.dateFrom) params.append('dateFrom', filters.dateFrom);
if (filters?.dateTo) params.append('dateTo', filters.dateTo);
if (filters?.page) params.append('page', String(filters.page));
if (filters?.limit) params.append('limit', String(filters.limit));
if (filters?.sortBy) params.append('sortBy', filters.sortBy);
if (filters?.sortOrder) params.append('sortOrder', filters.sortOrder);
const response = await api.get<InvoicesResponse>(`${BASE_URL}?${params.toString()}`);
return response.data;
},
// Get invoice by ID
getById: async (id: string): Promise<Invoice> => {
const response = await api.get<Invoice>(`${BASE_URL}/${id}`);
return response.data;
},
// Create invoice
create: async (data: CreateInvoiceDto): Promise<Invoice> => {
const response = await api.post<Invoice>(BASE_URL, data);
return response.data;
},
// Update invoice
update: async (id: string, data: UpdateInvoiceDto): Promise<Invoice> => {
const response = await api.patch<Invoice>(`${BASE_URL}/${id}`, data);
return response.data;
},
// Delete invoice
delete: async (id: string): Promise<void> => {
await api.delete(`${BASE_URL}/${id}`);
},
// Validate invoice (change status from draft to validated)
validate: async (id: string): Promise<Invoice> => {
const response = await api.post<Invoice>(`${BASE_URL}/${id}/validate`);
return response.data;
},
// Send invoice
send: async (id: string): Promise<Invoice> => {
const response = await api.post<Invoice>(`${BASE_URL}/${id}/send`);
return response.data;
},
// Record payment
recordPayment: async (id: string, data: {
amount: number;
paymentMethod: string;
paymentReference?: string;
paymentDate?: string;
}): Promise<Invoice> => {
const response = await api.post<Invoice>(`${BASE_URL}/${id}/payment`, data);
return response.data;
},
// Cancel invoice
cancel: async (id: string, reason?: string): Promise<Invoice> => {
const response = await api.post<Invoice>(`${BASE_URL}/${id}/cancel`, { reason });
return response.data;
},
// Void invoice
void: async (id: string, reason?: string): Promise<Invoice> => {
const response = await api.post<Invoice>(`${BASE_URL}/${id}/void`, { reason });
return response.data;
},
// Get invoice stats
getStats: async (tenantId?: string): Promise<InvoiceStats> => {
const params = tenantId ? `?tenantId=${tenantId}` : '';
const response = await api.get<InvoiceStats>(`${BASE_URL}/stats${params}`);
return response.data;
},
// Generate PDF
generatePdf: async (id: string): Promise<Blob> => {
const response = await api.get(`${BASE_URL}/${id}/pdf`, {
responseType: 'blob',
});
return response.data;
},
// Download PDF
downloadPdf: async (id: string, fileName?: string): Promise<void> => {
const blob = await invoicesApi.generatePdf(id);
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName || `invoice-${id}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
},
// Get sales invoices
getSales: async (filters?: Omit<InvoiceFilters, 'invoiceType'>): Promise<InvoicesResponse> => {
return invoicesApi.getAll({ ...filters, invoiceType: 'sale' });
},
// Get purchase invoices
getPurchases: async (filters?: Omit<InvoiceFilters, 'invoiceType'>): Promise<InvoicesResponse> => {
return invoicesApi.getAll({ ...filters, invoiceType: 'purchase' });
},
// Get draft invoices
getDrafts: async (filters?: Omit<InvoiceFilters, 'status'>): Promise<InvoicesResponse> => {
return invoicesApi.getAll({ ...filters, status: 'draft' });
},
// Get overdue invoices
getOverdue: async (filters?: Omit<InvoiceFilters, 'status'>): Promise<InvoicesResponse> => {
return invoicesApi.getAll({ ...filters, status: 'overdue' });
},
};

View File

@ -0,0 +1,2 @@
export * from './api/invoices.api';
export * from './types';

View File

@ -0,0 +1 @@
export * from './invoice.types';

View File

@ -0,0 +1,189 @@
export type InvoiceType = 'sale' | 'purchase' | 'credit_note' | 'debit_note';
export type InvoiceStatus = 'draft' | 'validated' | 'sent' | 'partial' | 'paid' | 'overdue' | 'void' | 'refunded' | 'cancelled' | 'voided';
export type InvoiceContext = 'commercial' | 'saas';
export interface Invoice {
id: string;
tenantId: string;
invoiceNumber: string;
invoiceType: InvoiceType;
invoiceContext: InvoiceContext;
// Commercial fields
salesOrderId?: string | null;
purchaseOrderId?: string | null;
partnerId?: string | null;
partnerName?: string | null;
partnerTaxId?: string | null;
// SaaS fields
subscriptionId?: string | null;
periodStart?: string | null;
periodEnd?: string | null;
// Billing info
billingName?: string | null;
billingEmail?: string | null;
billingAddress?: Record<string, any> | null;
taxId?: string | null;
// Dates
invoiceDate: string;
dueDate?: string | null;
paymentDate?: string | null;
paidAt?: string | null;
// Amounts
currency: string;
exchangeRate: number;
subtotal: number;
taxAmount: number;
withholdingTax: number;
discountAmount: number;
total: number;
amountPaid: number;
amountDue?: number | null;
paidAmount: number;
// Payment
paymentTermDays: number;
paymentMethod?: string | null;
paymentReference?: string | null;
// Status
status: InvoiceStatus;
// CFDI
cfdiUuid?: string | null;
cfdiStatus?: string | null;
cfdiXml?: string | null;
cfdiPdfUrl?: string | null;
// Notes
notes?: string | null;
internalNotes?: string | null;
// Audit
createdAt: string;
createdBy?: string | null;
updatedAt: string;
updatedBy?: string | null;
deletedAt?: string | null;
// Relations
items?: InvoiceItem[];
}
export interface InvoiceItem {
id: string;
invoiceId: string;
description: string;
itemType: string;
quantity: number;
unitPrice: number;
subtotal: number;
profileCode?: string | null;
platform?: string | null;
periodStart?: string | null;
periodEnd?: string | null;
metadata?: Record<string, any>;
createdAt: string;
}
export interface CreateInvoiceDto {
invoiceType?: InvoiceType;
invoiceContext?: InvoiceContext;
// Commercial
partnerId?: string;
partnerName?: string;
partnerTaxId?: string;
salesOrderId?: string;
purchaseOrderId?: string;
// SaaS
subscriptionId?: string;
periodStart?: string;
periodEnd?: string;
// Billing
billingName?: string;
billingEmail?: string;
billingAddress?: Record<string, any>;
taxId?: string;
// Dates
invoiceDate?: string;
dueDate?: string;
// Amounts
currency?: string;
exchangeRate?: number;
paymentTermDays?: number;
// Notes
notes?: string;
internalNotes?: string;
// Items
items?: CreateInvoiceItemDto[];
}
export interface CreateInvoiceItemDto {
description: string;
itemType?: string;
quantity: number;
unitPrice: number;
metadata?: Record<string, any>;
}
export interface UpdateInvoiceDto {
partnerId?: string;
partnerName?: string;
partnerTaxId?: string;
billingName?: string;
billingEmail?: string;
billingAddress?: Record<string, any>;
taxId?: string;
invoiceDate?: string;
dueDate?: string | null;
currency?: string;
exchangeRate?: number;
paymentTermDays?: number;
paymentMethod?: string;
paymentReference?: string;
notes?: string | null;
internalNotes?: string | null;
}
export interface InvoiceFilters {
search?: string;
invoiceType?: InvoiceType;
invoiceContext?: InvoiceContext;
status?: InvoiceStatus;
partnerId?: string;
dateFrom?: string;
dateTo?: string;
page?: number;
limit?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
export interface InvoicesResponse {
data: Invoice[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
export interface InvoiceStats {
total: number;
byStatus: Record<InvoiceStatus, number>;
totalRevenue: number;
pendingAmount: number;
overdueAmount: number;
}

View File

@ -0,0 +1,157 @@
import { api } from '@services/api/axios-instance';
import type {
PurchaseOrder,
CreatePurchaseOrderDto,
UpdatePurchaseOrderDto,
PurchaseOrderFilters,
PurchaseOrdersResponse,
PurchaseReceipt,
CreatePurchaseReceiptDto,
PurchaseReceiptFilters,
PurchaseReceiptsResponse,
} from '../types';
const ORDERS_URL = '/api/v1/purchases/orders';
const RECEIPTS_URL = '/api/v1/purchases/receipts';
export const purchasesApi = {
// ==================== Purchase Orders ====================
// Get all purchase orders with filters
getOrders: async (filters?: PurchaseOrderFilters): Promise<PurchaseOrdersResponse> => {
const params = new URLSearchParams();
if (filters?.companyId) params.append('companyId', filters.companyId);
if (filters?.partnerId) params.append('partnerId', filters.partnerId);
if (filters?.status) params.append('status', filters.status);
if (filters?.dateFrom) params.append('dateFrom', filters.dateFrom);
if (filters?.dateTo) params.append('dateTo', filters.dateTo);
if (filters?.search) params.append('search', filters.search);
if (filters?.page) params.append('page', String(filters.page));
if (filters?.limit) params.append('limit', String(filters.limit));
if (filters?.sortBy) params.append('sortBy', filters.sortBy);
if (filters?.sortOrder) params.append('sortOrder', filters.sortOrder);
const response = await api.get<PurchaseOrdersResponse>(`${ORDERS_URL}?${params.toString()}`);
return response.data;
},
// Get purchase order by ID
getOrderById: async (id: string): Promise<PurchaseOrder> => {
const response = await api.get<PurchaseOrder>(`${ORDERS_URL}/${id}`);
return response.data;
},
// Create purchase order
createOrder: async (data: CreatePurchaseOrderDto): Promise<PurchaseOrder> => {
const response = await api.post<PurchaseOrder>(ORDERS_URL, data);
return response.data;
},
// Update purchase order
updateOrder: async (id: string, data: UpdatePurchaseOrderDto): Promise<PurchaseOrder> => {
const response = await api.patch<PurchaseOrder>(`${ORDERS_URL}/${id}`, data);
return response.data;
},
// Delete purchase order
deleteOrder: async (id: string): Promise<void> => {
await api.delete(`${ORDERS_URL}/${id}`);
},
// ==================== Order Actions ====================
// Confirm purchase order
confirmOrder: async (id: string): Promise<PurchaseOrder> => {
const response = await api.post<PurchaseOrder>(`${ORDERS_URL}/${id}/confirm`);
return response.data;
},
// Cancel purchase order
cancelOrder: async (id: string): Promise<PurchaseOrder> => {
const response = await api.post<PurchaseOrder>(`${ORDERS_URL}/${id}/cancel`);
return response.data;
},
// Get draft orders
getDrafts: async (filters?: Omit<PurchaseOrderFilters, 'status'>): Promise<PurchaseOrdersResponse> => {
return purchasesApi.getOrders({ ...filters, status: 'draft' });
},
// Get confirmed orders
getConfirmed: async (filters?: Omit<PurchaseOrderFilters, 'status'>): Promise<PurchaseOrdersResponse> => {
return purchasesApi.getOrders({ ...filters, status: 'confirmed' });
},
// Get orders by supplier
getBySupplier: async (partnerId: string, filters?: Omit<PurchaseOrderFilters, 'partnerId'>): Promise<PurchaseOrdersResponse> => {
return purchasesApi.getOrders({ ...filters, partnerId });
},
// ==================== Purchase Receipts ====================
// Get all receipts
getReceipts: async (filters?: PurchaseReceiptFilters): Promise<PurchaseReceiptsResponse> => {
const params = new URLSearchParams();
if (filters?.purchaseOrderId) params.append('purchaseOrderId', filters.purchaseOrderId);
if (filters?.partnerId) params.append('partnerId', filters.partnerId);
if (filters?.status) params.append('status', filters.status);
if (filters?.dateFrom) params.append('dateFrom', filters.dateFrom);
if (filters?.dateTo) params.append('dateTo', filters.dateTo);
if (filters?.search) params.append('search', filters.search);
if (filters?.page) params.append('page', String(filters.page));
if (filters?.limit) params.append('limit', String(filters.limit));
const response = await api.get<PurchaseReceiptsResponse>(`${RECEIPTS_URL}?${params.toString()}`);
return response.data;
},
// Get receipt by ID
getReceiptById: async (id: string): Promise<PurchaseReceipt> => {
const response = await api.get<PurchaseReceipt>(`${RECEIPTS_URL}/${id}`);
return response.data;
},
// Create receipt (receive goods)
createReceipt: async (data: CreatePurchaseReceiptDto): Promise<PurchaseReceipt> => {
const response = await api.post<PurchaseReceipt>(RECEIPTS_URL, data);
return response.data;
},
// Confirm receipt
confirmReceipt: async (id: string): Promise<PurchaseReceipt> => {
const response = await api.post<PurchaseReceipt>(`${RECEIPTS_URL}/${id}/confirm`);
return response.data;
},
// Cancel receipt
cancelReceipt: async (id: string): Promise<PurchaseReceipt> => {
const response = await api.post<PurchaseReceipt>(`${RECEIPTS_URL}/${id}/cancel`);
return response.data;
},
// Get receipts by order
getReceiptsByOrder: async (purchaseOrderId: string): Promise<PurchaseReceiptsResponse> => {
return purchasesApi.getReceipts({ purchaseOrderId });
},
// Generate purchase order PDF
generateOrderPdf: async (id: string): Promise<Blob> => {
const response = await api.get(`${ORDERS_URL}/${id}/pdf`, {
responseType: 'blob',
});
return response.data;
},
// Download purchase order PDF
downloadOrderPdf: async (id: string, fileName?: string): Promise<void> => {
const blob = await purchasesApi.generateOrderPdf(id);
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName || `purchase-order-${id}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
},
};

View File

@ -0,0 +1,2 @@
export * from './api/purchases.api';
export * from './types';

View File

@ -0,0 +1 @@
export * from './purchase.types';

View File

@ -0,0 +1,174 @@
export type PurchaseOrderStatus = 'draft' | 'sent' | 'confirmed' | 'done' | 'cancelled';
export interface PurchaseOrderLine {
id: string;
orderId: string;
productId: string;
productName?: string;
productCode?: string;
description: string;
quantity: number;
qtyReceived: number;
qtyInvoiced: number;
uomId: string;
uomName?: string;
priceUnit: number;
discount: number;
amountUntaxed: number;
amountTax: number;
amountTotal: number;
expectedDate?: string | null;
}
export interface PurchaseOrder {
id: string;
tenantId: string;
companyId: string;
companyName?: string;
name: string;
ref?: string | null;
partnerId: string;
partnerName?: string;
orderDate: string;
expectedDate?: string | null;
effectiveDate?: string | null;
currencyId: string;
currencyCode?: string;
paymentTermId?: string | null;
amountUntaxed: number;
amountTax: number;
amountTotal: number;
status: PurchaseOrderStatus;
receiptStatus?: string | null;
invoiceStatus?: string | null;
notes?: string | null;
lines?: PurchaseOrderLine[];
createdAt: string;
confirmedAt?: string | null;
}
export interface CreatePurchaseOrderLineDto {
productId: string;
description: string;
quantity: number;
uomId: string;
priceUnit: number;
discount?: number;
expectedDate?: string;
}
export interface CreatePurchaseOrderDto {
companyId: string;
name: string;
ref?: string;
partnerId: string;
orderDate: string;
expectedDate?: string;
currencyId: string;
paymentTermId?: string;
notes?: string;
lines: CreatePurchaseOrderLineDto[];
}
export interface UpdatePurchaseOrderDto {
ref?: string | null;
partnerId?: string;
orderDate?: string;
expectedDate?: string | null;
currencyId?: string;
paymentTermId?: string | null;
notes?: string | null;
lines?: CreatePurchaseOrderLineDto[];
}
export interface PurchaseOrderFilters {
companyId?: string;
partnerId?: string;
status?: PurchaseOrderStatus;
dateFrom?: string;
dateTo?: string;
search?: string;
page?: number;
limit?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
export interface PurchaseOrdersResponse {
data: PurchaseOrder[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
// Purchase Receipt types
export interface PurchaseReceiptLine {
id: string;
receiptId: string;
orderLineId?: string | null;
productId: string;
productName?: string;
description: string;
quantityOrdered: number;
quantityReceived: number;
uomId: string;
uomName?: string;
warehouseId: string;
locationId?: string | null;
lotNumber?: string | null;
serialNumber?: string | null;
}
export interface PurchaseReceipt {
id: string;
tenantId: string;
companyId: string;
receiptNumber: string;
purchaseOrderId: string;
purchaseOrderName?: string;
partnerId: string;
partnerName?: string;
receiptDate: string;
status: 'draft' | 'done' | 'cancelled';
notes?: string | null;
lines?: PurchaseReceiptLine[];
createdAt: string;
}
export interface CreatePurchaseReceiptDto {
purchaseOrderId: string;
receiptDate?: string;
warehouseId: string;
notes?: string;
lines: {
orderLineId: string;
quantityReceived: number;
locationId?: string;
lotNumber?: string;
serialNumber?: string;
}[];
}
export interface PurchaseReceiptFilters {
purchaseOrderId?: string;
partnerId?: string;
status?: string;
dateFrom?: string;
dateTo?: string;
search?: string;
page?: number;
limit?: number;
}
export interface PurchaseReceiptsResponse {
data: PurchaseReceipt[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}

View File

@ -0,0 +1,194 @@
import { api } from '@services/api/axios-instance';
import type {
SalesOrder,
SalesOrderLine,
CreateSalesOrderDto,
UpdateSalesOrderDto,
CreateSalesOrderLineDto,
UpdateSalesOrderLineDto,
SalesOrderFilters,
SalesOrdersResponse,
Quotation,
CreateQuotationDto,
QuotationFilters,
QuotationsResponse,
} from '../types';
const ORDERS_URL = '/api/v1/sales/orders';
const QUOTATIONS_URL = '/api/v1/sales/quotations';
export const salesApi = {
// ==================== Sales Orders ====================
// Get all sales orders with filters
getOrders: async (filters?: SalesOrderFilters): Promise<SalesOrdersResponse> => {
const params = new URLSearchParams();
if (filters?.companyId) params.append('companyId', filters.companyId);
if (filters?.partnerId) params.append('partnerId', filters.partnerId);
if (filters?.status) params.append('status', filters.status);
if (filters?.invoiceStatus) params.append('invoiceStatus', filters.invoiceStatus);
if (filters?.deliveryStatus) params.append('deliveryStatus', filters.deliveryStatus);
if (filters?.dateFrom) params.append('dateFrom', filters.dateFrom);
if (filters?.dateTo) params.append('dateTo', filters.dateTo);
if (filters?.search) params.append('search', filters.search);
if (filters?.page) params.append('page', String(filters.page));
if (filters?.limit) params.append('limit', String(filters.limit));
if (filters?.sortBy) params.append('sortBy', filters.sortBy);
if (filters?.sortOrder) params.append('sortOrder', filters.sortOrder);
const response = await api.get<SalesOrdersResponse>(`${ORDERS_URL}?${params.toString()}`);
return response.data;
},
// Get sales order by ID
getOrderById: async (id: string): Promise<SalesOrder> => {
const response = await api.get<SalesOrder>(`${ORDERS_URL}/${id}`);
return response.data;
},
// Create sales order
createOrder: async (data: CreateSalesOrderDto): Promise<SalesOrder> => {
const response = await api.post<SalesOrder>(ORDERS_URL, data);
return response.data;
},
// Update sales order
updateOrder: async (id: string, data: UpdateSalesOrderDto): Promise<SalesOrder> => {
const response = await api.patch<SalesOrder>(`${ORDERS_URL}/${id}`, data);
return response.data;
},
// Delete sales order
deleteOrder: async (id: string): Promise<void> => {
await api.delete(`${ORDERS_URL}/${id}`);
},
// ==================== Order Lines ====================
// Add line to order
addLine: async (orderId: string, data: CreateSalesOrderLineDto): Promise<SalesOrderLine> => {
const response = await api.post<SalesOrderLine>(`${ORDERS_URL}/${orderId}/lines`, data);
return response.data;
},
// Update order line
updateLine: async (orderId: string, lineId: string, data: UpdateSalesOrderLineDto): Promise<SalesOrderLine> => {
const response = await api.patch<SalesOrderLine>(`${ORDERS_URL}/${orderId}/lines/${lineId}`, data);
return response.data;
},
// Remove order line
removeLine: async (orderId: string, lineId: string): Promise<void> => {
await api.delete(`${ORDERS_URL}/${orderId}/lines/${lineId}`);
},
// ==================== Order Actions ====================
// Confirm order (send to customer)
confirmOrder: async (id: string): Promise<SalesOrder> => {
const response = await api.post<SalesOrder>(`${ORDERS_URL}/${id}/confirm`);
return response.data;
},
// Cancel order
cancelOrder: async (id: string): Promise<SalesOrder> => {
const response = await api.post<SalesOrder>(`${ORDERS_URL}/${id}/cancel`);
return response.data;
},
// Create invoice from order
createInvoice: async (id: string): Promise<{ orderId: string; invoiceId: string }> => {
const response = await api.post<{ orderId: string; invoiceId: string }>(`${ORDERS_URL}/${id}/invoice`);
return response.data;
},
// Get draft orders
getDrafts: async (filters?: Omit<SalesOrderFilters, 'status'>): Promise<SalesOrdersResponse> => {
return salesApi.getOrders({ ...filters, status: 'draft' });
},
// Get confirmed orders
getConfirmed: async (filters?: Omit<SalesOrderFilters, 'status'>): Promise<SalesOrdersResponse> => {
return salesApi.getOrders({ ...filters, status: 'sale' });
},
// Get orders by customer
getByCustomer: async (partnerId: string, filters?: Omit<SalesOrderFilters, 'partnerId'>): Promise<SalesOrdersResponse> => {
return salesApi.getOrders({ ...filters, partnerId });
},
// ==================== Quotations ====================
// Get all quotations
getQuotations: async (filters?: QuotationFilters): Promise<QuotationsResponse> => {
const params = new URLSearchParams();
if (filters?.companyId) params.append('companyId', filters.companyId);
if (filters?.partnerId) params.append('partnerId', filters.partnerId);
if (filters?.status) params.append('status', filters.status);
if (filters?.dateFrom) params.append('dateFrom', filters.dateFrom);
if (filters?.dateTo) params.append('dateTo', filters.dateTo);
if (filters?.search) params.append('search', filters.search);
if (filters?.page) params.append('page', String(filters.page));
if (filters?.limit) params.append('limit', String(filters.limit));
const response = await api.get<QuotationsResponse>(`${QUOTATIONS_URL}?${params.toString()}`);
return response.data;
},
// Get quotation by ID
getQuotationById: async (id: string): Promise<Quotation> => {
const response = await api.get<Quotation>(`${QUOTATIONS_URL}/${id}`);
return response.data;
},
// Create quotation
createQuotation: async (data: CreateQuotationDto): Promise<Quotation> => {
const response = await api.post<Quotation>(QUOTATIONS_URL, data);
return response.data;
},
// Send quotation
sendQuotation: async (id: string): Promise<Quotation> => {
const response = await api.post<Quotation>(`${QUOTATIONS_URL}/${id}/send`);
return response.data;
},
// Accept quotation
acceptQuotation: async (id: string): Promise<Quotation> => {
const response = await api.post<Quotation>(`${QUOTATIONS_URL}/${id}/accept`);
return response.data;
},
// Reject quotation
rejectQuotation: async (id: string, reason?: string): Promise<Quotation> => {
const response = await api.post<Quotation>(`${QUOTATIONS_URL}/${id}/reject`, { reason });
return response.data;
},
// Convert quotation to sales order
convertToOrder: async (id: string): Promise<SalesOrder> => {
const response = await api.post<SalesOrder>(`${QUOTATIONS_URL}/${id}/convert`);
return response.data;
},
// Generate quotation PDF
generateQuotationPdf: async (id: string): Promise<Blob> => {
const response = await api.get(`${QUOTATIONS_URL}/${id}/pdf`, {
responseType: 'blob',
});
return response.data;
},
// Download quotation PDF
downloadQuotationPdf: async (id: string, fileName?: string): Promise<void> => {
const blob = await salesApi.generateQuotationPdf(id);
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName || `quotation-${id}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
},
};

View File

@ -0,0 +1,2 @@
export * from './api/sales.api';
export * from './types';

View File

@ -0,0 +1 @@
export * from './sales.types';

View File

@ -0,0 +1,207 @@
export type SalesOrderStatus = 'draft' | 'sent' | 'sale' | 'done' | 'cancelled';
export type InvoiceStatus = 'pending' | 'partial' | 'invoiced';
export type DeliveryStatus = 'pending' | 'partial' | 'delivered';
export type InvoicePolicy = 'order' | 'delivery';
export interface SalesOrderLine {
id: string;
orderId: string;
productId: string;
productName?: string;
description: string;
quantity: number;
qtyDelivered: number;
qtyInvoiced: number;
uomId: string;
uomName?: string;
priceUnit: number;
discount: number;
taxIds: string[];
amountUntaxed: number;
amountTax: number;
amountTotal: number;
analyticAccountId?: string | null;
}
export interface SalesOrder {
id: string;
tenantId: string;
companyId: string;
companyName?: string;
name: string;
clientOrderRef?: string | null;
partnerId: string;
partnerName?: string;
orderDate: string;
validityDate?: string | null;
commitmentDate?: string | null;
currencyId: string;
currencyCode?: string;
pricelistId?: string | null;
pricelistName?: string;
paymentTermId?: string | null;
userId?: string | null;
userName?: string;
salesTeamId?: string | null;
salesTeamName?: string;
amountUntaxed: number;
amountTax: number;
amountTotal: number;
status: SalesOrderStatus;
invoiceStatus: InvoiceStatus;
deliveryStatus: DeliveryStatus;
invoicePolicy: InvoicePolicy;
pickingId?: string | null;
notes?: string | null;
termsConditions?: string | null;
lines?: SalesOrderLine[];
createdAt: string;
confirmedAt?: string | null;
}
export interface CreateSalesOrderDto {
companyId: string;
partnerId: string;
clientOrderRef?: string;
orderDate?: string;
validityDate?: string;
commitmentDate?: string;
currencyId: string;
pricelistId?: string;
paymentTermId?: string;
salesTeamId?: string;
invoicePolicy?: InvoicePolicy;
notes?: string;
termsConditions?: string;
}
export interface UpdateSalesOrderDto {
partnerId?: string;
clientOrderRef?: string | null;
orderDate?: string;
validityDate?: string | null;
commitmentDate?: string | null;
currencyId?: string;
pricelistId?: string | null;
paymentTermId?: string | null;
salesTeamId?: string | null;
invoicePolicy?: InvoicePolicy;
notes?: string | null;
termsConditions?: string | null;
}
export interface CreateSalesOrderLineDto {
productId: string;
description: string;
quantity: number;
uomId: string;
priceUnit: number;
discount?: number;
taxIds?: string[];
analyticAccountId?: string;
}
export interface UpdateSalesOrderLineDto {
description?: string;
quantity?: number;
uomId?: string;
priceUnit?: number;
discount?: number;
taxIds?: string[];
analyticAccountId?: string | null;
}
export interface SalesOrderFilters {
companyId?: string;
partnerId?: string;
status?: SalesOrderStatus;
invoiceStatus?: InvoiceStatus;
deliveryStatus?: DeliveryStatus;
dateFrom?: string;
dateTo?: string;
search?: string;
page?: number;
limit?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
export interface SalesOrdersResponse {
data: SalesOrder[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
// Quotation types
export interface Quotation {
id: string;
tenantId: string;
companyId: string;
companyName?: string;
quotationNumber: string;
partnerId: string;
partnerName?: string;
quotationDate: string;
validityDate?: string | null;
currencyId: string;
currencyCode?: string;
amountUntaxed: number;
amountTax: number;
amountTotal: number;
status: 'draft' | 'sent' | 'accepted' | 'rejected' | 'expired' | 'converted';
notes?: string | null;
termsConditions?: string | null;
lines?: QuotationLine[];
createdAt: string;
}
export interface QuotationLine {
id: string;
quotationId: string;
productId: string;
productName?: string;
description: string;
quantity: number;
uomId: string;
uomName?: string;
priceUnit: number;
discount: number;
amountUntaxed: number;
amountTax: number;
amountTotal: number;
}
export interface CreateQuotationDto {
companyId: string;
partnerId: string;
quotationDate?: string;
validityDate?: string;
currencyId: string;
notes?: string;
termsConditions?: string;
}
export interface QuotationFilters {
companyId?: string;
partnerId?: string;
status?: string;
dateFrom?: string;
dateTo?: string;
search?: string;
page?: number;
limit?: number;
}
export interface QuotationsResponse {
data: Quotation[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}