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:
Urtzi Alfaro
2025-11-18 11:59:23 +01:00
parent 5c45164c8e
commit 3c3d3ce042
32 changed files with 654 additions and 874 deletions

View File

@@ -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[];

View File

@@ -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
*/

View File

@@ -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>

View File

@@ -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,

View File

@@ -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
*/

View File

@@ -24,11 +24,6 @@ import type {
SupplierStatistics,
SupplierDeletionSummary,
SupplierResponse as SupplierResponse_,
PurchaseOrderCreate,
PurchaseOrderUpdate,
PurchaseOrderResponse,
PurchaseOrderApproval,
PurchaseOrderSearchParams,
DeliveryCreate,
DeliveryUpdate,
DeliveryResponse,

View File

@@ -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;