408 lines
12 KiB
TypeScript
408 lines
12 KiB
TypeScript
import { apiClient, ApiResponse } from './client';
|
|
|
|
// Enums
|
|
export enum PurchaseOrderStatus {
|
|
DRAFT = 'draft',
|
|
PENDING = 'pending',
|
|
APPROVED = 'approved',
|
|
SENT = 'sent',
|
|
PARTIALLY_RECEIVED = 'partially_received',
|
|
RECEIVED = 'received',
|
|
CANCELLED = 'cancelled',
|
|
}
|
|
|
|
export enum DeliveryStatus {
|
|
SCHEDULED = 'scheduled',
|
|
IN_TRANSIT = 'in_transit',
|
|
DELIVERED = 'delivered',
|
|
FAILED = 'failed',
|
|
RETURNED = 'returned',
|
|
}
|
|
|
|
// Request/Response Types
|
|
export interface PurchaseOrderItem {
|
|
ingredient_id: string;
|
|
ingredient_name: string;
|
|
quantity: number;
|
|
unit_price: number;
|
|
total_price: number;
|
|
notes?: string;
|
|
}
|
|
|
|
export interface PurchaseOrderCreate {
|
|
supplier_id: string;
|
|
items: PurchaseOrderItem[];
|
|
delivery_date?: string;
|
|
notes?: string;
|
|
priority?: 'low' | 'normal' | 'high' | 'urgent';
|
|
}
|
|
|
|
export interface PurchaseOrderUpdate {
|
|
supplier_id?: string;
|
|
delivery_date?: string;
|
|
notes?: string;
|
|
priority?: 'low' | 'normal' | 'high' | 'urgent';
|
|
status?: PurchaseOrderStatus;
|
|
}
|
|
|
|
export interface PurchaseOrderResponse {
|
|
id: string;
|
|
tenant_id: string;
|
|
order_number: string;
|
|
supplier_id: string;
|
|
supplier_name: string;
|
|
status: PurchaseOrderStatus;
|
|
items: PurchaseOrderItem[];
|
|
subtotal: number;
|
|
tax_amount: number;
|
|
total_amount: number;
|
|
delivery_date?: string;
|
|
expected_delivery_date?: string;
|
|
actual_delivery_date?: string;
|
|
notes?: string;
|
|
priority: 'low' | 'normal' | 'high' | 'urgent';
|
|
created_at: string;
|
|
updated_at: string;
|
|
created_by: string;
|
|
approved_by?: string;
|
|
approved_at?: string;
|
|
}
|
|
|
|
export interface Supplier {
|
|
id: string;
|
|
tenant_id: string;
|
|
name: string;
|
|
contact_name?: string;
|
|
email?: string;
|
|
phone?: string;
|
|
address: string;
|
|
tax_id?: string;
|
|
payment_terms?: string;
|
|
delivery_terms?: string;
|
|
rating?: number;
|
|
is_active: boolean;
|
|
performance_metrics: {
|
|
on_time_delivery_rate: number;
|
|
quality_score: number;
|
|
total_orders: number;
|
|
average_delivery_time: number;
|
|
};
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface DeliveryResponse {
|
|
id: string;
|
|
tenant_id: string;
|
|
purchase_order_id: string;
|
|
delivery_number: string;
|
|
supplier_id: string;
|
|
status: DeliveryStatus;
|
|
scheduled_date: string;
|
|
actual_delivery_date?: string;
|
|
delivery_items: Array<{
|
|
ingredient_id: string;
|
|
ingredient_name: string;
|
|
ordered_quantity: number;
|
|
delivered_quantity: number;
|
|
unit_price: number;
|
|
batch_number?: string;
|
|
expiration_date?: string;
|
|
quality_notes?: string;
|
|
}>;
|
|
total_items: number;
|
|
delivery_notes?: string;
|
|
quality_check_notes?: string;
|
|
received_by?: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
class ProcurementService {
|
|
private readonly baseUrl = '/procurement';
|
|
|
|
// Purchase Order management
|
|
async getPurchaseOrders(params?: {
|
|
page?: number;
|
|
size?: number;
|
|
status?: PurchaseOrderStatus;
|
|
supplier_id?: string;
|
|
start_date?: string;
|
|
end_date?: string;
|
|
}): Promise<ApiResponse<{ items: PurchaseOrderResponse[]; total: number; page: number; size: number; pages: number }>> {
|
|
const queryParams = new URLSearchParams();
|
|
|
|
if (params) {
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
queryParams.append(key, value.toString());
|
|
}
|
|
});
|
|
}
|
|
|
|
const url = queryParams.toString()
|
|
? `${this.baseUrl}/purchase-orders?${queryParams.toString()}`
|
|
: `${this.baseUrl}/purchase-orders`;
|
|
|
|
return apiClient.get(url);
|
|
}
|
|
|
|
async getPurchaseOrder(orderId: string): Promise<ApiResponse<PurchaseOrderResponse>> {
|
|
return apiClient.get(`${this.baseUrl}/purchase-orders/${orderId}`);
|
|
}
|
|
|
|
async createPurchaseOrder(orderData: PurchaseOrderCreate): Promise<ApiResponse<PurchaseOrderResponse>> {
|
|
return apiClient.post(`${this.baseUrl}/purchase-orders`, orderData);
|
|
}
|
|
|
|
async updatePurchaseOrder(orderId: string, orderData: PurchaseOrderUpdate): Promise<ApiResponse<PurchaseOrderResponse>> {
|
|
return apiClient.put(`${this.baseUrl}/purchase-orders/${orderId}`, orderData);
|
|
}
|
|
|
|
async approvePurchaseOrder(orderId: string): Promise<ApiResponse<PurchaseOrderResponse>> {
|
|
return apiClient.post(`${this.baseUrl}/purchase-orders/${orderId}/approve`);
|
|
}
|
|
|
|
async sendPurchaseOrder(orderId: string, sendEmail: boolean = true): Promise<ApiResponse<{ message: string; sent_at: string }>> {
|
|
return apiClient.post(`${this.baseUrl}/purchase-orders/${orderId}/send`, { send_email: sendEmail });
|
|
}
|
|
|
|
async cancelPurchaseOrder(orderId: string, reason?: string): Promise<ApiResponse<PurchaseOrderResponse>> {
|
|
return apiClient.post(`${this.baseUrl}/purchase-orders/${orderId}/cancel`, { reason });
|
|
}
|
|
|
|
// Supplier management
|
|
async getSuppliers(params?: {
|
|
page?: number;
|
|
size?: number;
|
|
is_active?: boolean;
|
|
search?: string;
|
|
}): Promise<ApiResponse<{ items: Supplier[]; total: number; page: number; size: number; pages: number }>> {
|
|
const queryParams = new URLSearchParams();
|
|
|
|
if (params) {
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
queryParams.append(key, value.toString());
|
|
}
|
|
});
|
|
}
|
|
|
|
const url = queryParams.toString()
|
|
? `${this.baseUrl}/suppliers?${queryParams.toString()}`
|
|
: `${this.baseUrl}/suppliers`;
|
|
|
|
return apiClient.get(url);
|
|
}
|
|
|
|
async getSupplier(supplierId: string): Promise<ApiResponse<Supplier>> {
|
|
return apiClient.get(`${this.baseUrl}/suppliers/${supplierId}`);
|
|
}
|
|
|
|
async createSupplier(supplierData: Omit<Supplier, 'id' | 'tenant_id' | 'performance_metrics' | 'created_at' | 'updated_at'>): Promise<ApiResponse<Supplier>> {
|
|
return apiClient.post(`${this.baseUrl}/suppliers`, supplierData);
|
|
}
|
|
|
|
async updateSupplier(supplierId: string, supplierData: Partial<Supplier>): Promise<ApiResponse<Supplier>> {
|
|
return apiClient.put(`${this.baseUrl}/suppliers/${supplierId}`, supplierData);
|
|
}
|
|
|
|
async deleteSupplier(supplierId: string): Promise<ApiResponse<{ message: string }>> {
|
|
return apiClient.delete(`${this.baseUrl}/suppliers/${supplierId}`);
|
|
}
|
|
|
|
async getSupplierPerformance(supplierId: string): Promise<ApiResponse<{
|
|
supplier: Supplier;
|
|
performance_history: Array<{
|
|
month: string;
|
|
on_time_delivery_rate: number;
|
|
quality_score: number;
|
|
order_count: number;
|
|
total_value: number;
|
|
}>;
|
|
recent_deliveries: DeliveryResponse[];
|
|
}>> {
|
|
return apiClient.get(`${this.baseUrl}/suppliers/${supplierId}/performance`);
|
|
}
|
|
|
|
// Delivery management
|
|
async getDeliveries(params?: {
|
|
page?: number;
|
|
size?: number;
|
|
status?: DeliveryStatus;
|
|
supplier_id?: string;
|
|
purchase_order_id?: string;
|
|
start_date?: string;
|
|
end_date?: string;
|
|
}): Promise<ApiResponse<{ items: DeliveryResponse[]; total: number; page: number; size: number; pages: number }>> {
|
|
const queryParams = new URLSearchParams();
|
|
|
|
if (params) {
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
queryParams.append(key, value.toString());
|
|
}
|
|
});
|
|
}
|
|
|
|
const url = queryParams.toString()
|
|
? `${this.baseUrl}/deliveries?${queryParams.toString()}`
|
|
: `${this.baseUrl}/deliveries`;
|
|
|
|
return apiClient.get(url);
|
|
}
|
|
|
|
async getDelivery(deliveryId: string): Promise<ApiResponse<DeliveryResponse>> {
|
|
return apiClient.get(`${this.baseUrl}/deliveries/${deliveryId}`);
|
|
}
|
|
|
|
async receiveDelivery(deliveryId: string, deliveryData: {
|
|
delivered_items: Array<{
|
|
ingredient_id: string;
|
|
delivered_quantity: number;
|
|
batch_number?: string;
|
|
expiration_date?: string;
|
|
quality_notes?: string;
|
|
}>;
|
|
delivery_notes?: string;
|
|
quality_check_notes?: string;
|
|
}): Promise<ApiResponse<DeliveryResponse>> {
|
|
return apiClient.post(`${this.baseUrl}/deliveries/${deliveryId}/receive`, deliveryData);
|
|
}
|
|
|
|
async reportDeliveryIssue(deliveryId: string, issue: {
|
|
issue_type: 'late_delivery' | 'quality_issue' | 'quantity_mismatch' | 'damaged_goods' | 'other';
|
|
description: string;
|
|
affected_items?: string[];
|
|
severity: 'low' | 'medium' | 'high';
|
|
}): Promise<ApiResponse<{ message: string; issue_id: string }>> {
|
|
return apiClient.post(`${this.baseUrl}/deliveries/${deliveryId}/report-issue`, issue);
|
|
}
|
|
|
|
// Analytics and reporting
|
|
async getProcurementAnalytics(params?: {
|
|
start_date?: string;
|
|
end_date?: string;
|
|
supplier_id?: string;
|
|
}): Promise<ApiResponse<{
|
|
total_purchase_value: number;
|
|
total_orders: number;
|
|
average_order_value: number;
|
|
on_time_delivery_rate: number;
|
|
quality_score: number;
|
|
cost_savings: number;
|
|
top_suppliers: Array<{
|
|
supplier_name: string;
|
|
total_value: number;
|
|
order_count: number;
|
|
performance_score: number;
|
|
}>;
|
|
spending_trends: Array<{
|
|
month: string;
|
|
total_spending: number;
|
|
order_count: number;
|
|
}>;
|
|
}>> {
|
|
const queryParams = new URLSearchParams();
|
|
|
|
if (params) {
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
queryParams.append(key, value.toString());
|
|
}
|
|
});
|
|
}
|
|
|
|
const url = queryParams.toString()
|
|
? `${this.baseUrl}/analytics?${queryParams.toString()}`
|
|
: `${this.baseUrl}/analytics`;
|
|
|
|
return apiClient.get(url);
|
|
}
|
|
|
|
async getSpendingByCategory(params?: {
|
|
start_date?: string;
|
|
end_date?: string;
|
|
}): Promise<ApiResponse<Array<{
|
|
category: string;
|
|
total_spending: number;
|
|
percentage: number;
|
|
trend: 'up' | 'down' | 'stable';
|
|
}>>> {
|
|
const queryParams = new URLSearchParams();
|
|
|
|
if (params) {
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
queryParams.append(key, value.toString());
|
|
}
|
|
});
|
|
}
|
|
|
|
const url = queryParams.toString()
|
|
? `${this.baseUrl}/spending/by-category?${queryParams.toString()}`
|
|
: `${this.baseUrl}/spending/by-category`;
|
|
|
|
return apiClient.get(url);
|
|
}
|
|
|
|
// Automated procurement
|
|
async getReorderSuggestions(): Promise<ApiResponse<Array<{
|
|
ingredient_id: string;
|
|
ingredient_name: string;
|
|
current_stock: number;
|
|
reorder_point: number;
|
|
suggested_quantity: number;
|
|
preferred_supplier: {
|
|
id: string;
|
|
name: string;
|
|
last_price: number;
|
|
lead_time_days: number;
|
|
};
|
|
urgency: 'low' | 'medium' | 'high' | 'critical';
|
|
}>>> {
|
|
return apiClient.get(`${this.baseUrl}/reorder-suggestions`);
|
|
}
|
|
|
|
async createReorderFromSuggestions(suggestions: Array<{
|
|
ingredient_id: string;
|
|
supplier_id: string;
|
|
quantity: number;
|
|
}>): Promise<ApiResponse<PurchaseOrderResponse[]>> {
|
|
return apiClient.post(`${this.baseUrl}/auto-reorder`, { suggestions });
|
|
}
|
|
|
|
// Utility methods
|
|
getPurchaseOrderStatusOptions(): { value: PurchaseOrderStatus; label: string; color: string }[] {
|
|
return [
|
|
{ value: PurchaseOrderStatus.DRAFT, label: 'Draft', color: 'gray' },
|
|
{ value: PurchaseOrderStatus.PENDING, label: 'Pending', color: 'yellow' },
|
|
{ value: PurchaseOrderStatus.APPROVED, label: 'Approved', color: 'blue' },
|
|
{ value: PurchaseOrderStatus.SENT, label: 'Sent', color: 'purple' },
|
|
{ value: PurchaseOrderStatus.PARTIALLY_RECEIVED, label: 'Partially Received', color: 'orange' },
|
|
{ value: PurchaseOrderStatus.RECEIVED, label: 'Received', color: 'green' },
|
|
{ value: PurchaseOrderStatus.CANCELLED, label: 'Cancelled', color: 'red' },
|
|
];
|
|
}
|
|
|
|
getDeliveryStatusOptions(): { value: DeliveryStatus; label: string; color: string }[] {
|
|
return [
|
|
{ value: DeliveryStatus.SCHEDULED, label: 'Scheduled', color: 'blue' },
|
|
{ value: DeliveryStatus.IN_TRANSIT, label: 'In Transit', color: 'yellow' },
|
|
{ value: DeliveryStatus.DELIVERED, label: 'Delivered', color: 'green' },
|
|
{ value: DeliveryStatus.FAILED, label: 'Failed', color: 'red' },
|
|
{ value: DeliveryStatus.RETURNED, label: 'Returned', color: 'orange' },
|
|
];
|
|
}
|
|
|
|
getPriorityOptions(): { value: string; label: string; color: string }[] {
|
|
return [
|
|
{ value: 'low', label: 'Low', color: 'gray' },
|
|
{ value: 'normal', label: 'Normal', color: 'blue' },
|
|
{ value: 'high', label: 'High', color: 'orange' },
|
|
{ value: 'urgent', label: 'Urgent', color: 'red' },
|
|
];
|
|
}
|
|
}
|
|
|
|
export const procurementService = new ProcurementService(); |