Add fixes to procurement logic and fix rel-time connections

This commit is contained in:
Urtzi Alfaro
2025-10-02 13:20:30 +02:00
parent c9d8d1d071
commit 1243c2ca6d
24 changed files with 4984 additions and 348 deletions

View File

@@ -29,6 +29,9 @@ import {
GeneratePlanResponse,
PaginatedProcurementPlans,
GetProcurementPlansParams,
CreatePOsResult,
LinkRequirementToPORequest,
UpdateDeliveryStatusRequest,
GetPlanRequirementsParams,
UpdatePlanStatusParams,
} from '../types/orders';
@@ -546,3 +549,186 @@ export const useTriggerDailyScheduler = (
});
};
// ===== NEW PROCUREMENT FEATURE HOOKS =====
/**
* Hook to recalculate a procurement plan
*/
export const useRecalculateProcurementPlan = (
options?: UseMutationOptions<GeneratePlanResponse, ApiError, { tenantId: string; planId: string }>
) => {
const queryClient = useQueryClient();
return useMutation<GeneratePlanResponse, ApiError, { tenantId: string; planId: string }>({
mutationFn: ({ tenantId, planId }) => OrdersService.recalculateProcurementPlan(tenantId, planId),
onSuccess: (data, variables) => {
if (data.plan) {
// Update the specific plan in cache
queryClient.setQueryData(
ordersKeys.procurementPlan(variables.tenantId, variables.planId),
data.plan
);
}
// Invalidate plans list and dashboard
queryClient.invalidateQueries({
queryKey: ordersKeys.procurement(),
predicate: (query) => {
return JSON.stringify(query.queryKey).includes(variables.tenantId);
},
});
},
...options,
});
};
/**
* Hook to approve a procurement plan
*/
export const useApproveProcurementPlan = (
options?: UseMutationOptions<ProcurementPlanResponse, ApiError, { tenantId: string; planId: string; approval_notes?: string }>
) => {
const queryClient = useQueryClient();
return useMutation<ProcurementPlanResponse, ApiError, { tenantId: string; planId: string; approval_notes?: string }>({
mutationFn: ({ tenantId, planId, approval_notes }) =>
OrdersService.approveProcurementPlan(tenantId, planId, { approval_notes }),
onSuccess: (data, variables) => {
// Update the specific plan in cache
queryClient.setQueryData(
ordersKeys.procurementPlan(variables.tenantId, variables.planId),
data
);
// Invalidate plans list and dashboard
queryClient.invalidateQueries({
queryKey: ordersKeys.procurement(),
predicate: (query) => {
return JSON.stringify(query.queryKey).includes(variables.tenantId);
},
});
},
...options,
});
};
/**
* Hook to reject a procurement plan
*/
export const useRejectProcurementPlan = (
options?: UseMutationOptions<ProcurementPlanResponse, ApiError, { tenantId: string; planId: string; rejection_notes?: string }>
) => {
const queryClient = useQueryClient();
return useMutation<ProcurementPlanResponse, ApiError, { tenantId: string; planId: string; rejection_notes?: string }>({
mutationFn: ({ tenantId, planId, rejection_notes }) =>
OrdersService.rejectProcurementPlan(tenantId, planId, { rejection_notes }),
onSuccess: (data, variables) => {
// Update the specific plan in cache
queryClient.setQueryData(
ordersKeys.procurementPlan(variables.tenantId, variables.planId),
data
);
// Invalidate plans list and dashboard
queryClient.invalidateQueries({
queryKey: ordersKeys.procurement(),
predicate: (query) => {
return JSON.stringify(query.queryKey).includes(variables.tenantId);
},
});
},
...options,
});
};
/**
* Hook to create purchase orders from procurement plan
*/
export const useCreatePurchaseOrdersFromPlan = (
options?: UseMutationOptions<CreatePOsResult, ApiError, { tenantId: string; planId: string; autoApprove?: boolean }>
) => {
const queryClient = useQueryClient();
return useMutation<CreatePOsResult, ApiError, { tenantId: string; planId: string; autoApprove?: boolean }>({
mutationFn: ({ tenantId, planId, autoApprove = false }) =>
OrdersService.createPurchaseOrdersFromPlan(tenantId, planId, autoApprove),
onSuccess: (data, variables) => {
// Invalidate procurement plan to refresh requirements status
queryClient.invalidateQueries({
queryKey: ordersKeys.procurementPlan(variables.tenantId, variables.planId),
});
// Invalidate dashboard
queryClient.invalidateQueries({
queryKey: ordersKeys.procurementDashboard(variables.tenantId),
});
},
...options,
});
};
/**
* Hook to link a requirement to a purchase order
*/
export const useLinkRequirementToPurchaseOrder = (
options?: UseMutationOptions<
{ success: boolean; message: string; requirement_id: string; purchase_order_id: string },
ApiError,
{ tenantId: string; requirementId: string; request: LinkRequirementToPORequest }
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ success: boolean; message: string; requirement_id: string; purchase_order_id: string },
ApiError,
{ tenantId: string; requirementId: string; request: LinkRequirementToPORequest }
>({
mutationFn: ({ tenantId, requirementId, request }) =>
OrdersService.linkRequirementToPurchaseOrder(tenantId, requirementId, request),
onSuccess: (data, variables) => {
// Invalidate procurement data to refresh requirements
queryClient.invalidateQueries({
queryKey: ordersKeys.procurement(),
predicate: (query) => {
return JSON.stringify(query.queryKey).includes(variables.tenantId);
},
});
},
...options,
});
};
/**
* Hook to update delivery status for a requirement
*/
export const useUpdateRequirementDeliveryStatus = (
options?: UseMutationOptions<
{ success: boolean; message: string; requirement_id: string; delivery_status: string },
ApiError,
{ tenantId: string; requirementId: string; request: UpdateDeliveryStatusRequest }
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ success: boolean; message: string; requirement_id: string; delivery_status: string },
ApiError,
{ tenantId: string; requirementId: string; request: UpdateDeliveryStatusRequest }
>({
mutationFn: ({ tenantId, requirementId, request }) =>
OrdersService.updateRequirementDeliveryStatus(tenantId, requirementId, request),
onSuccess: (data, variables) => {
// Invalidate procurement data to refresh requirements
queryClient.invalidateQueries({
queryKey: ordersKeys.procurement(),
predicate: (query) => {
return JSON.stringify(query.queryKey).includes(variables.tenantId);
},
});
},
...options,
});
};

View File

@@ -661,6 +661,12 @@ export {
useGenerateProcurementPlan,
useUpdateProcurementPlanStatus,
useTriggerDailyScheduler,
useRecalculateProcurementPlan,
useApproveProcurementPlan,
useRejectProcurementPlan,
useCreatePurchaseOrdersFromPlan,
useLinkRequirementToPurchaseOrder,
useUpdateRequirementDeliveryStatus,
ordersKeys,
} from './hooks/orders';

View File

@@ -34,6 +34,11 @@ import {
GetProcurementPlansParams,
GetPlanRequirementsParams,
UpdatePlanStatusParams,
CreatePOsResult,
LinkRequirementToPORequest,
UpdateDeliveryStatusRequest,
ApprovalRequest,
RejectionRequest,
} from '../types/orders';
export class OrdersService {
@@ -303,6 +308,82 @@ export class OrdersService {
return apiClient.get<{ status: string; service: string; procurement_enabled: boolean; timestamp: string }>(`/tenants/${tenantId}/procurement/health`);
}
// ===== NEW PROCUREMENT FEATURES =====
/**
* Recalculate an existing procurement plan
* POST /tenants/{tenant_id}/procurement/plans/{plan_id}/recalculate
*/
static async recalculateProcurementPlan(tenantId: string, planId: string): Promise<GeneratePlanResponse> {
return apiClient.post<GeneratePlanResponse>(
`/tenants/${tenantId}/procurement/plans/${planId}/recalculate`,
{}
);
}
/**
* Approve a procurement plan with notes
* POST /tenants/{tenant_id}/procurement/plans/{plan_id}/approve
*/
static async approveProcurementPlan(tenantId: string, planId: string, request?: ApprovalRequest): Promise<ProcurementPlanResponse> {
return apiClient.post<ProcurementPlanResponse>(
`/tenants/${tenantId}/procurement/plans/${planId}/approve`,
request || {}
);
}
/**
* Reject a procurement plan with notes
* POST /tenants/{tenant_id}/procurement/plans/{plan_id}/reject
*/
static async rejectProcurementPlan(tenantId: string, planId: string, request?: RejectionRequest): Promise<ProcurementPlanResponse> {
return apiClient.post<ProcurementPlanResponse>(
`/tenants/${tenantId}/procurement/plans/${planId}/reject`,
request || {}
);
}
/**
* Create purchase orders automatically from procurement plan
* POST /tenants/{tenant_id}/procurement/plans/{plan_id}/create-purchase-orders
*/
static async createPurchaseOrdersFromPlan(tenantId: string, planId: string, autoApprove: boolean = false): Promise<CreatePOsResult> {
return apiClient.post<CreatePOsResult>(
`/tenants/${tenantId}/procurement/plans/${planId}/create-purchase-orders`,
{ auto_approve: autoApprove }
);
}
/**
* Link a procurement requirement to a purchase order
* POST /tenants/{tenant_id}/procurement/requirements/{requirement_id}/link-purchase-order
*/
static async linkRequirementToPurchaseOrder(
tenantId: string,
requirementId: string,
request: LinkRequirementToPORequest
): Promise<{ success: boolean; message: string; requirement_id: string; purchase_order_id: string }> {
return apiClient.post<{ success: boolean; message: string; requirement_id: string; purchase_order_id: string }>(
`/tenants/${tenantId}/procurement/requirements/${requirementId}/link-purchase-order`,
request
);
}
/**
* Update delivery status for a requirement
* PUT /tenants/{tenant_id}/procurement/requirements/{requirement_id}/delivery-status
*/
static async updateRequirementDeliveryStatus(
tenantId: string,
requirementId: string,
request: UpdateDeliveryStatusRequest
): Promise<{ success: boolean; message: string; requirement_id: string; delivery_status: string }> {
return apiClient.put<{ success: boolean; message: string; requirement_id: string; delivery_status: string }>(
`/tenants/${tenantId}/procurement/requirements/${requirementId}/delivery-status`,
request
);
}
}
export default OrdersService;

View File

@@ -479,6 +479,14 @@ export interface ProcurementPlanUpdate {
seasonal_adjustments?: Record<string, any>;
}
export interface ApprovalWorkflowEntry {
timestamp: string;
from_status: string;
to_status: string;
user_id?: string;
notes?: string;
}
export interface ProcurementPlanResponse extends ProcurementPlanBase {
id: string;
tenant_id: string;
@@ -502,6 +510,7 @@ export interface ProcurementPlanResponse extends ProcurementPlanBase {
on_time_delivery_rate?: number;
cost_accuracy?: number;
quality_score?: number;
approval_workflow?: ApprovalWorkflowEntry[];
created_at: string;
updated_at: string;
created_by?: string;
@@ -551,6 +560,46 @@ export interface GeneratePlanResponse {
errors: string[];
}
// New Feature Types
export interface CreatePOsResult {
success: boolean;
created_pos: {
po_id: string;
po_number: string;
supplier_id: string;
items_count: number;
total_amount: number;
}[];
failed_pos: {
supplier_id: string;
error: string;
}[];
total_created: number;
total_failed: number;
}
export interface LinkRequirementToPORequest {
purchase_order_id: string;
purchase_order_number: string;
ordered_quantity: number;
expected_delivery_date?: string;
}
export interface UpdateDeliveryStatusRequest {
delivery_status: string;
received_quantity?: number;
actual_delivery_date?: string;
quality_rating?: number;
}
export interface ApprovalRequest {
approval_notes?: string;
}
export interface RejectionRequest {
rejection_notes?: string;
}
export interface PaginatedProcurementPlans {
plans: ProcurementPlanResponse[];
total: number;