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:
parent
d3fdc0b9c0
commit
6cb8e99f50
204
src/features/inventory/api/inventory.api.ts
Normal file
204
src/features/inventory/api/inventory.api.ts
Normal 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}`);
|
||||
},
|
||||
};
|
||||
2
src/features/inventory/index.ts
Normal file
2
src/features/inventory/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './api/inventory.api';
|
||||
export * from './types';
|
||||
1
src/features/inventory/types/index.ts
Normal file
1
src/features/inventory/types/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './inventory.types';
|
||||
223
src/features/inventory/types/inventory.types.ts
Normal file
223
src/features/inventory/types/inventory.types.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
138
src/features/invoices/api/invoices.api.ts
Normal file
138
src/features/invoices/api/invoices.api.ts
Normal 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' });
|
||||
},
|
||||
};
|
||||
2
src/features/invoices/index.ts
Normal file
2
src/features/invoices/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './api/invoices.api';
|
||||
export * from './types';
|
||||
1
src/features/invoices/types/index.ts
Normal file
1
src/features/invoices/types/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './invoice.types';
|
||||
189
src/features/invoices/types/invoice.types.ts
Normal file
189
src/features/invoices/types/invoice.types.ts
Normal 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;
|
||||
}
|
||||
157
src/features/purchases/api/purchases.api.ts
Normal file
157
src/features/purchases/api/purchases.api.ts
Normal 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);
|
||||
},
|
||||
};
|
||||
2
src/features/purchases/index.ts
Normal file
2
src/features/purchases/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './api/purchases.api';
|
||||
export * from './types';
|
||||
1
src/features/purchases/types/index.ts
Normal file
1
src/features/purchases/types/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './purchase.types';
|
||||
174
src/features/purchases/types/purchase.types.ts
Normal file
174
src/features/purchases/types/purchase.types.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
194
src/features/sales/api/sales.api.ts
Normal file
194
src/features/sales/api/sales.api.ts
Normal 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);
|
||||
},
|
||||
};
|
||||
2
src/features/sales/index.ts
Normal file
2
src/features/sales/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './api/sales.api';
|
||||
export * from './types';
|
||||
1
src/features/sales/types/index.ts
Normal file
1
src/features/sales/types/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './sales.types';
|
||||
207
src/features/sales/types/sales.types.ts
Normal file
207
src/features/sales/types/sales.types.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user