import { api as client } from "@/auth/http"; import { URL_CONSTANTS } from "@/constants/URLS"; import type { RuleEngineListMeta, RuleEngineListResult, RuleEngineRecord, RuleEngineResourceSlug, } from "@/types/rule-engine"; import { unwrap } from "@/utils/endpoint"; export interface RuleEngineListParams { search?: string; page?: number; pageSize?: number; isActive?: boolean; status?: string; sortBy?: string; sortOrder?: "ASC" | "DESC"; requiresDirectorApproval?: boolean; } export interface RuleEngineReorderPayload { ids: string[]; requiresDirectorApproval?: boolean; } const RESOURCE_BASE: Record = { "cargo-types": URL_CONSTANTS.RULE_ENGINE.CARGO_TYPES, "container-types": URL_CONSTANTS.RULE_ENGINE.CONTAINER_TYPES, "wagon-types": URL_CONSTANTS.RULE_ENGINE.WAGON_TYPES, "priority-configs": URL_CONSTANTS.RULE_ENGINE.PRIORITY_CONFIGS, "service-types": URL_CONSTANTS.RULE_ENGINE.SERVICE_TYPES, "weight-limit-rules": URL_CONSTANTS.RULE_ENGINE.WEIGHT_LIMIT_RULES, yards: URL_CONSTANTS.RULE_ENGINE.YARDS, "shipping-lines": URL_CONSTANTS.RULE_ENGINE.SHIPPING_LINES, rates: URL_CONSTANTS.RULE_ENGINE.RATES, "approval-rules": URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULES, }; const byIdPath = (resource: RuleEngineResourceSlug, id: string): string => { switch (resource) { case "cargo-types": return URL_CONSTANTS.RULE_ENGINE.CARGO_TYPE_BY_ID(id); case "container-types": return URL_CONSTANTS.RULE_ENGINE.CONTAINER_TYPE_BY_ID(id); case "wagon-types": return URL_CONSTANTS.RULE_ENGINE.WAGON_TYPE_BY_ID(id); case "priority-configs": return URL_CONSTANTS.RULE_ENGINE.PRIORITY_CONFIG_BY_ID(id); case "service-types": return URL_CONSTANTS.RULE_ENGINE.SERVICE_TYPE_BY_ID(id); case "weight-limit-rules": return URL_CONSTANTS.RULE_ENGINE.WEIGHT_LIMIT_RULE_BY_ID(id); case "yards": return URL_CONSTANTS.RULE_ENGINE.YARD_BY_ID(id); case "shipping-lines": return URL_CONSTANTS.RULE_ENGINE.SHIPPING_LINE_BY_ID(id); case "rates": return URL_CONSTANTS.RULE_ENGINE.RATE_BY_ID(id); case "approval-rules": return URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULE_BY_ID(id); default: return `${RESOURCE_BASE[resource]}/${id}`; } }; const defaultMeta = ( dataLength: number, page = 1, pageSize = 10, ): RuleEngineListMeta => { const totalPages = Math.max(1, Math.ceil(dataLength / pageSize)); return { total: dataLength, page, pageSize, totalPages, hasNextPage: page < totalPages, hasPreviousPage: page > 1, }; }; /** Standard envelope from the shared pagination toolkit: `{ items, meta }`. */ const isItemsEnvelope = ( value: unknown, ): value is RuleEngineListResult => Boolean(value) && typeof value === "object" && Array.isArray((value as { items?: unknown }).items); /** Legacy envelope (`{ data, meta }`) — still returned by wagon-types. */ const isLegacyEnvelope = ( value: unknown, ): value is { data: T[]; meta?: RuleEngineListMeta } => Boolean(value) && typeof value === "object" && Array.isArray((value as { data?: unknown }).data); const normalizeList = ( payload: unknown, page = 1, pageSize = 10, ): RuleEngineListResult => { const candidates: unknown[] = [payload, unwrap(payload as { data: unknown })]; for (const body of candidates) { if (isItemsEnvelope(body)) { return { items: body.items, meta: body.meta ?? defaultMeta(body.items.length, page, pageSize), }; } if (isLegacyEnvelope(body)) { return { items: body.data, meta: body.meta ?? defaultMeta(body.data.length, page, pageSize), }; } if (Array.isArray(body)) { return { items: body as T[], meta: defaultMeta(body.length, page, pageSize), }; } } return { items: [], meta: defaultMeta(0, page, pageSize) }; }; const normalizeEntity = (payload: unknown): T => { return unwrap(payload as { data: T }) as T; }; export const ruleEngineService = { list: async ( resource: RuleEngineResourceSlug, params?: RuleEngineListParams, ): Promise> => { const page = params?.page ?? 1; const pageSize = params?.pageSize ?? 10; const response = await client.get(RESOURCE_BASE[resource], { params: { page, pageSize, search: params?.search, isActive: params?.isActive, status: params?.status, sortBy: params?.sortBy, sortOrder: params?.sortOrder, requiresDirectorApproval: params?.requiresDirectorApproval, }, }); return normalizeList(response.data, page, pageSize); }, /** * Fetch every row of a resource by walking the pages. The API caps pageSize * at 100, so option/dropdown consumers that used to ask for 200-500 rows in * one shot go through here instead of getting silently capped (or a 400). */ listAll: async ( resource: RuleEngineResourceSlug, params?: Omit, ): Promise => { const pageSize = 100; const first = await ruleEngineService.list(resource, { ...params, page: 1, pageSize, }); const items = [...first.items]; const totalPages = first.meta.totalPages ?? 1; for (let page = 2; page <= totalPages; page += 1) { const next = await ruleEngineService.list(resource, { ...params, page, pageSize, }); items.push(...next.items); } return items; }, getById: async ( resource: RuleEngineResourceSlug, id: string, ): Promise => { const response = await client.get(byIdPath(resource, id)); return normalizeEntity(response.data); }, create: async ( resource: RuleEngineResourceSlug, payload: Record, ): Promise => { const response = await client.post(RESOURCE_BASE[resource], payload); return normalizeEntity(response.data); }, update: async ( resource: RuleEngineResourceSlug, id: string, payload: Record, ): Promise => { const response = await client.patch(byIdPath(resource, id), payload); return normalizeEntity(response.data); }, remove: async ( resource: RuleEngineResourceSlug, id: string, ): Promise => { await client.delete(byIdPath(resource, id)); }, reorder: async ( resource: RuleEngineResourceSlug, payload: RuleEngineReorderPayload, ): Promise => { await client.post(`${RESOURCE_BASE[resource]}/reorder`, payload); }, moveOrder: async ( resource: RuleEngineResourceSlug, id: string, direction: "up" | "down", ): Promise => { await client.post(`${byIdPath(resource, id)}/move-order`, { direction }); }, submitRate: async (id: string): Promise => { const response = await client.post( URL_CONSTANTS.RULE_ENGINE.RATE_SUBMIT(id), ); return normalizeEntity(response.data); }, approveRate: async (id: string): Promise => { const response = await client.post( URL_CONSTANTS.RULE_ENGINE.RATE_APPROVE(id), ); return normalizeEntity(response.data); }, getApprovalChain: async ( requiresDirectorApproval = true, ): Promise => { const response = await client.get( URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULES_CHAIN, { params: { requiresDirectorApproval }, }, ); const body = unwrap(response.data) as unknown; if (Array.isArray(body)) return body as RuleEngineRecord[]; if ( body && typeof body === "object" && "data" in body && Array.isArray((body as { data: unknown }).data) ) { return (body as { data: RuleEngineRecord[] }).data; } return []; }, };