Fix Purchase Order modal and reorganize documentation
Frontend Changes: - Fix runtime error: Remove undefined handleModify reference from ActionQueueCard in DashboardPage - Migrate PurchaseOrderDetailsModal to use correct PurchaseOrderItem type from purchase_orders service - Fix item display: Parse unit_price as string (Decimal) instead of number - Use correct field names: item_notes instead of notes - Remove deprecated PurchaseOrder types from suppliers.ts to prevent type conflicts - Update CreatePurchaseOrderModal to use unified types - Clean up API exports: Remove old PO hooks re-exported from suppliers - Add comprehensive translations for PO modal (en, es, eu) Documentation Reorganization: - Move WhatsApp implementation docs to docs/03-features/notifications/whatsapp/ - Move forecast validation docs to docs/03-features/forecasting/ - Move specification docs to docs/03-features/specifications/ - Move deployment docs (Colima, K8s, VPS sizing) to docs/05-deployment/ - Archive completed implementation summaries to docs/archive/implementation-summaries/ - Delete obsolete FRONTEND_CHANGES_NEEDED.md - Standardize filenames to lowercase with hyphens 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,8 @@ export interface ReasoningInputs {
|
||||
aiInsights: boolean;
|
||||
}
|
||||
|
||||
// Note: This is a different interface from PurchaseOrderSummary in purchase_orders.ts
|
||||
// This is specifically for OrchestrationSummary dashboard display
|
||||
export interface PurchaseOrderSummary {
|
||||
supplierName: string;
|
||||
itemCategories: string[];
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
PurchaseOrderDetail,
|
||||
PurchaseOrderSearchParams,
|
||||
PurchaseOrderUpdateData,
|
||||
PurchaseOrderCreateData,
|
||||
PurchaseOrderStatus,
|
||||
CreateDeliveryInput,
|
||||
DeliveryResponse
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
getPurchaseOrder,
|
||||
getPendingApprovalPurchaseOrders,
|
||||
getPurchaseOrdersByStatus,
|
||||
createPurchaseOrder,
|
||||
updatePurchaseOrder,
|
||||
approvePurchaseOrder,
|
||||
rejectPurchaseOrder,
|
||||
@@ -112,6 +114,37 @@ export const usePurchaseOrder = (
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to create a new purchase order
|
||||
*/
|
||||
export const useCreatePurchaseOrder = (
|
||||
options?: UseMutationOptions<
|
||||
PurchaseOrderDetail,
|
||||
ApiError,
|
||||
{ tenantId: string; data: PurchaseOrderCreateData }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
PurchaseOrderDetail,
|
||||
ApiError,
|
||||
{ tenantId: string; data: PurchaseOrderCreateData }
|
||||
>({
|
||||
mutationFn: ({ tenantId, data }) => createPurchaseOrder(tenantId, data),
|
||||
onSuccess: (data, variables) => {
|
||||
// Invalidate all lists to refresh with new PO
|
||||
queryClient.invalidateQueries({ queryKey: purchaseOrderKeys.lists() });
|
||||
// Add to cache
|
||||
queryClient.setQueryData(
|
||||
purchaseOrderKeys.detail(variables.tenantId, data.id),
|
||||
data
|
||||
);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to update a purchase order
|
||||
*/
|
||||
|
||||
@@ -15,11 +15,6 @@ import type {
|
||||
SupplierSearchParams,
|
||||
SupplierStatistics,
|
||||
SupplierDeletionSummary,
|
||||
PurchaseOrderCreate,
|
||||
PurchaseOrderUpdate,
|
||||
PurchaseOrderResponse,
|
||||
PurchaseOrderApproval,
|
||||
PurchaseOrderSearchParams,
|
||||
DeliveryCreate,
|
||||
DeliveryUpdate,
|
||||
DeliveryResponse,
|
||||
@@ -49,15 +44,6 @@ export const suppliersKeys = {
|
||||
byType: (tenantId: string, supplierType: string) =>
|
||||
[...suppliersKeys.suppliers.all(), 'by-type', tenantId, supplierType] as const,
|
||||
},
|
||||
purchaseOrders: {
|
||||
all: () => [...suppliersKeys.all, 'purchase-orders'] as const,
|
||||
lists: () => [...suppliersKeys.purchaseOrders.all(), 'list'] as const,
|
||||
list: (params?: PurchaseOrderSearchParams) =>
|
||||
[...suppliersKeys.purchaseOrders.lists(), params] as const,
|
||||
details: () => [...suppliersKeys.purchaseOrders.all(), 'detail'] as const,
|
||||
detail: (orderId: string) =>
|
||||
[...suppliersKeys.purchaseOrders.details(), orderId] as const,
|
||||
},
|
||||
deliveries: {
|
||||
all: () => [...suppliersKeys.all, 'deliveries'] as const,
|
||||
lists: () => [...suppliersKeys.deliveries.all(), 'list'] as const,
|
||||
@@ -173,35 +159,6 @@ export const useSuppliersByType = (
|
||||
});
|
||||
};
|
||||
|
||||
// Purchase Order Queries
|
||||
export const usePurchaseOrders = (
|
||||
tenantId: string,
|
||||
queryParams?: PurchaseOrderSearchParams,
|
||||
options?: Omit<UseQueryOptions<PurchaseOrderResponse[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PurchaseOrderResponse[], ApiError>({
|
||||
queryKey: suppliersKeys.purchaseOrders.list(queryParams),
|
||||
queryFn: () => suppliersService.getPurchaseOrders(tenantId, queryParams as any),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePurchaseOrder = (
|
||||
tenantId: string,
|
||||
orderId: string,
|
||||
options?: Omit<UseQueryOptions<PurchaseOrderResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<PurchaseOrderResponse, ApiError>({
|
||||
queryKey: suppliersKeys.purchaseOrders.detail(orderId),
|
||||
queryFn: () => suppliersService.getPurchaseOrder(tenantId, orderId),
|
||||
enabled: !!tenantId && !!orderId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Delivery Queries
|
||||
export const useDeliveries = (
|
||||
tenantId: string,
|
||||
@@ -462,94 +419,6 @@ export const useHardDeleteSupplier = (
|
||||
});
|
||||
};
|
||||
|
||||
// Purchase Order Mutations
|
||||
export const useCreatePurchaseOrder = (
|
||||
options?: UseMutationOptions<PurchaseOrderResponse, ApiError, PurchaseOrderCreate>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<PurchaseOrderResponse, ApiError, PurchaseOrderCreate>({
|
||||
mutationFn: (orderData) => suppliersService.createPurchaseOrder(orderData),
|
||||
onSuccess: (data) => {
|
||||
// Add to cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.purchaseOrders.detail(data.id),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.purchaseOrders.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdatePurchaseOrder = (
|
||||
options?: UseMutationOptions<
|
||||
PurchaseOrderResponse,
|
||||
ApiError,
|
||||
{ orderId: string; updateData: PurchaseOrderUpdate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
PurchaseOrderResponse,
|
||||
ApiError,
|
||||
{ orderId: string; updateData: PurchaseOrderUpdate }
|
||||
>({
|
||||
mutationFn: ({ orderId, updateData }) =>
|
||||
suppliersService.updatePurchaseOrder(orderId, updateData),
|
||||
onSuccess: (data, { orderId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.purchaseOrders.detail(orderId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.purchaseOrders.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useApprovePurchaseOrder = (
|
||||
options?: UseMutationOptions<
|
||||
PurchaseOrderResponse,
|
||||
ApiError,
|
||||
{ orderId: string; approval: PurchaseOrderApproval }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
PurchaseOrderResponse,
|
||||
ApiError,
|
||||
{ orderId: string; approval: PurchaseOrderApproval }
|
||||
>({
|
||||
mutationFn: ({ orderId, approval }) =>
|
||||
suppliersService.approvePurchaseOrder(orderId, approval),
|
||||
onSuccess: (data, { orderId }) => {
|
||||
// Update cache
|
||||
queryClient.setQueryData(
|
||||
suppliersKeys.purchaseOrders.detail(orderId),
|
||||
data
|
||||
);
|
||||
|
||||
// Invalidate lists
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: suppliersKeys.purchaseOrders.lists()
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Delivery Mutations
|
||||
export const useCreateDelivery = (
|
||||
options?: UseMutationOptions<DeliveryResponse, ApiError, DeliveryCreate>
|
||||
|
||||
@@ -593,8 +593,6 @@ export {
|
||||
useTopSuppliers,
|
||||
usePendingApprovalSuppliers,
|
||||
useSuppliersByType,
|
||||
usePurchaseOrders,
|
||||
usePurchaseOrder,
|
||||
useDeliveries,
|
||||
useDelivery,
|
||||
useSupplierPerformanceMetrics,
|
||||
@@ -603,9 +601,6 @@ export {
|
||||
useUpdateSupplier,
|
||||
useDeleteSupplier,
|
||||
useApproveSupplier,
|
||||
useCreatePurchaseOrder,
|
||||
useUpdatePurchaseOrder,
|
||||
useApprovePurchaseOrder,
|
||||
useCreateDelivery,
|
||||
useUpdateDelivery,
|
||||
useConfirmDeliveryReceipt,
|
||||
|
||||
@@ -123,6 +123,40 @@ export interface PurchaseOrderUpdateData {
|
||||
internal_notes?: string;
|
||||
}
|
||||
|
||||
export interface PurchaseOrderItemCreate {
|
||||
inventory_product_id: string;
|
||||
ordered_quantity: number;
|
||||
unit_price: string; // Decimal as string
|
||||
unit_of_measure: string;
|
||||
quality_requirements?: string;
|
||||
item_notes?: string;
|
||||
}
|
||||
|
||||
export interface PurchaseOrderCreateData {
|
||||
supplier_id: string;
|
||||
required_delivery_date?: string;
|
||||
priority?: PurchaseOrderPriority;
|
||||
tax_amount?: number;
|
||||
shipping_cost?: number;
|
||||
discount_amount?: number;
|
||||
notes?: string;
|
||||
procurement_plan_id?: string;
|
||||
items: PurchaseOrderItemCreate[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new purchase order
|
||||
*/
|
||||
export async function createPurchaseOrder(
|
||||
tenantId: string,
|
||||
data: PurchaseOrderCreateData
|
||||
): Promise<PurchaseOrderDetail> {
|
||||
return apiClient.post<PurchaseOrderDetail>(
|
||||
`/tenants/${tenantId}/procurement/purchase-orders`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of purchase orders with optional filters
|
||||
*/
|
||||
|
||||
@@ -24,11 +24,6 @@ import type {
|
||||
SupplierStatistics,
|
||||
SupplierDeletionSummary,
|
||||
SupplierResponse as SupplierResponse_,
|
||||
PurchaseOrderCreate,
|
||||
PurchaseOrderUpdate,
|
||||
PurchaseOrderResponse,
|
||||
PurchaseOrderApproval,
|
||||
PurchaseOrderSearchParams,
|
||||
DeliveryCreate,
|
||||
DeliveryUpdate,
|
||||
DeliveryResponse,
|
||||
|
||||
@@ -367,173 +367,6 @@ export interface SupplierSummary {
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// ===== PURCHASE ORDER SCHEMAS =====
|
||||
// Mirror: PurchaseOrderItemCreate from suppliers.py:187
|
||||
|
||||
export interface PurchaseOrderItemCreate {
|
||||
inventory_product_id: string;
|
||||
product_code?: string | null; // max_length=100
|
||||
ordered_quantity: number; // gt=0
|
||||
unit_of_measure: string; // max_length=20
|
||||
unit_price: number; // gt=0
|
||||
quality_requirements?: string | null;
|
||||
item_notes?: string | null;
|
||||
}
|
||||
|
||||
// Mirror: PurchaseOrderItemUpdate from suppliers.py:198
|
||||
export interface PurchaseOrderItemUpdate {
|
||||
ordered_quantity?: number | null; // gt=0
|
||||
unit_price?: number | null; // gt=0
|
||||
quality_requirements?: string | null;
|
||||
item_notes?: string | null;
|
||||
}
|
||||
|
||||
// Mirror: PurchaseOrderItemResponse from suppliers.py (inferred)
|
||||
export interface PurchaseOrderItemResponse {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
purchase_order_id: string;
|
||||
inventory_product_id: string;
|
||||
product_code: string | null;
|
||||
price_list_item_id: string | null;
|
||||
ordered_quantity: number;
|
||||
unit_of_measure: string;
|
||||
unit_price: number;
|
||||
line_total: number;
|
||||
received_quantity: number;
|
||||
remaining_quantity: number;
|
||||
quality_requirements: string | null;
|
||||
item_notes: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// Mirror: PurchaseOrderCreate from suppliers.py (inferred)
|
||||
export interface PurchaseOrderCreate {
|
||||
supplier_id: string;
|
||||
items: PurchaseOrderItemCreate[]; // min_items=1
|
||||
|
||||
// Order details
|
||||
reference_number?: string | null; // max_length=100
|
||||
priority?: string; // Default: "normal", max_length=20
|
||||
required_delivery_date?: string | null;
|
||||
|
||||
// Delivery info
|
||||
delivery_address?: string | null;
|
||||
delivery_instructions?: string | null;
|
||||
delivery_contact?: string | null; // max_length=200
|
||||
delivery_phone?: string | null; // max_length=30
|
||||
|
||||
// Financial (all default=0, ge=0)
|
||||
tax_amount?: number;
|
||||
shipping_cost?: number;
|
||||
discount_amount?: number;
|
||||
|
||||
// Additional
|
||||
notes?: string | null;
|
||||
internal_notes?: string | null;
|
||||
terms_and_conditions?: string | null;
|
||||
}
|
||||
|
||||
// Mirror: PurchaseOrderUpdate from suppliers.py (inferred)
|
||||
export interface PurchaseOrderUpdate {
|
||||
reference_number?: string | null;
|
||||
priority?: string | null;
|
||||
required_delivery_date?: string | null;
|
||||
estimated_delivery_date?: string | null;
|
||||
supplier_reference?: string | null; // max_length=100
|
||||
delivery_address?: string | null;
|
||||
delivery_instructions?: string | null;
|
||||
delivery_contact?: string | null;
|
||||
delivery_phone?: string | null;
|
||||
tax_amount?: number | null;
|
||||
shipping_cost?: number | null;
|
||||
discount_amount?: number | null;
|
||||
notes?: string | null;
|
||||
internal_notes?: string | null;
|
||||
terms_and_conditions?: string | null;
|
||||
}
|
||||
|
||||
// Mirror: PurchaseOrderStatusUpdate from suppliers.py (inferred)
|
||||
export interface PurchaseOrderStatusUpdate {
|
||||
status: PurchaseOrderStatus;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
// Mirror: PurchaseOrderApproval from suppliers.py (inferred)
|
||||
export interface PurchaseOrderApproval {
|
||||
action: 'approve' | 'reject';
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
// Mirror: PurchaseOrderResponse from suppliers.py (inferred)
|
||||
export interface PurchaseOrderResponse {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
supplier_id: string;
|
||||
po_number: string;
|
||||
status: PurchaseOrderStatus;
|
||||
order_date: string;
|
||||
reference_number: string | null;
|
||||
priority: string;
|
||||
required_delivery_date: string | null;
|
||||
estimated_delivery_date: string | null;
|
||||
|
||||
// Financial
|
||||
subtotal: number;
|
||||
tax_amount: number;
|
||||
shipping_cost: number;
|
||||
discount_amount: number;
|
||||
total_amount: number;
|
||||
currency: string;
|
||||
|
||||
// Delivery
|
||||
delivery_address: string | null;
|
||||
delivery_instructions: string | null;
|
||||
delivery_contact: string | null;
|
||||
delivery_phone: string | null;
|
||||
|
||||
// Approval
|
||||
requires_approval: boolean;
|
||||
approved_by: string | null;
|
||||
approved_at: string | null;
|
||||
rejection_reason: string | null;
|
||||
|
||||
// Communication
|
||||
sent_to_supplier_at: string | null;
|
||||
supplier_confirmation_date: string | null;
|
||||
supplier_reference: string | null;
|
||||
|
||||
// Additional
|
||||
notes: string | null;
|
||||
internal_notes: string | null;
|
||||
terms_and_conditions: string | null;
|
||||
|
||||
// Audit
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by: string;
|
||||
updated_by: string;
|
||||
|
||||
// Related data
|
||||
supplier?: SupplierSummary | null;
|
||||
items?: PurchaseOrderItemResponse[] | null;
|
||||
}
|
||||
|
||||
// Mirror: PurchaseOrderSummary from suppliers.py (inferred)
|
||||
export interface PurchaseOrderSummary {
|
||||
id: string;
|
||||
po_number: string;
|
||||
supplier_id: string;
|
||||
supplier_name: string | null;
|
||||
status: PurchaseOrderStatus;
|
||||
priority: string;
|
||||
order_date: string;
|
||||
required_delivery_date: string | null;
|
||||
total_amount: number;
|
||||
currency: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// ===== DELIVERY SCHEMAS =====
|
||||
// Mirror: DeliveryItemCreate from suppliers.py (inferred)
|
||||
@@ -828,15 +661,6 @@ export interface SupplierStatistics {
|
||||
total_spend: number;
|
||||
}
|
||||
|
||||
export interface PurchaseOrderStatistics {
|
||||
total_orders: number;
|
||||
status_counts: Record<string, number>;
|
||||
this_month_orders: number;
|
||||
this_month_spend: number;
|
||||
avg_order_value: number;
|
||||
overdue_count: number;
|
||||
pending_approval: number;
|
||||
}
|
||||
|
||||
export interface DeliveryPerformanceStats {
|
||||
total_deliveries: number;
|
||||
@@ -934,16 +758,8 @@ export interface SupplierSearchParams {
|
||||
offset?: number; // Default: 0, ge=0
|
||||
}
|
||||
|
||||
export interface PurchaseOrderSearchParams {
|
||||
supplier_id?: string | null;
|
||||
status?: PurchaseOrderStatus | null;
|
||||
priority?: string | null;
|
||||
date_from?: string | null;
|
||||
date_to?: string | null;
|
||||
search_term?: string | null;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
// ⚠️ DEPRECATED: Use PurchaseOrderSearchParams from '@/api/services/purchase_orders' instead
|
||||
// Duplicate definition - use the one from purchase_orders service
|
||||
|
||||
export interface DeliverySearchParams {
|
||||
supplier_id?: string | null;
|
||||
|
||||
Reference in New Issue
Block a user