import { reservationTaskAudits, reservationTaskDetails, } from '@/fixtures/reservation' import { getReservationHotelId } from '@/config/reservationConfig' import { getJson, sendJson } from '@/services/httpClient' import type { ManualInvoiceGenerationRequest, ManualInvoiceGenerationResult, } from '@/types/manualInvoice' import type { ReservationManualReviewConversionRequest, ReservationManualReviewConversionResult, ReservationManualReviewResolutionRequest, ReservationManualReviewResolutionResult, ReservationOperaOperationResult, ReservationOperaSimulationRequest, ReservationOrderDetailResult, ReservationOrderListFilters, ReservationOrderListResult, ReservationRecord, ReservationTaskAuditListResult, ReservationTaskDetailResult, ReservationTaskListFilters, ReservationTaskListResult, ReservationTaskPayloadMutationRequest, ReservationTaskPayloadMutationResult, ReservationV4CardConfirmRequest, ReservationV4CatalogLookupFilters, ReservationV4CatalogLookupResult, ReservationV4OrderTaskDetailResult, ReservationV4OrderTaskListFilters, ReservationV4OrderTaskListResult, ReservationV4ReviewResolutionRequest, ReservationV4SourceNotificationAckRequest, ReservationV4SourceNotificationDetailResult, ReservationV4WorkbenchFilters, ReservationV4WorkbenchListResult, SourceMessageConversationResult, } from '@/types/reservation' const useReservationFixtures = import.meta.env.VITE_RESERVATION_USE_FIXTURES === 'true' export async function fetchReservationOrders( filters: ReservationOrderListFilters = {}, ): Promise { return getJson(withQuery('/api/reservation/orders', withReservationHotel(filters))) } export async function fetchReservationTaskList( filters: ReservationTaskListFilters = {}, ): Promise { return getJson(withQuery('/api/reservation/tasks', withReservationHotel(filters))) } export async function fetchReservationV4WorkbenchItems( filters: ReservationV4WorkbenchFilters = {}, ): Promise { return getJson( withQuery('/api/reservation/workbench-items', withReservationHotel(filters)), ) } export async function fetchReservationV4OrderTasks( filters: ReservationV4OrderTaskListFilters = {}, ): Promise { return getJson( withQuery('/api/reservation/order-tasks', withReservationHotel(filters)), ) } export async function fetchReservationV4AccountLookups( filters: ReservationV4CatalogLookupFilters = {}, ): Promise { return getJson( withQuery('/api/reservation/lookups/accounts', withReservationHotel(filters)), ) } export async function fetchReservationV4RoomTypeLookups( filters: ReservationV4CatalogLookupFilters = {}, ): Promise { return getJson( withQuery('/api/reservation/lookups/room-types', withReservationHotel(filters)), ) } export async function fetchReservationV4RateCodeLookups( filters: ReservationV4CatalogLookupFilters = {}, ): Promise { return getJson( withQuery('/api/reservation/lookups/rate-codes', withReservationHotel(filters)), ) } export async function fetchReservationOrderDetail(orderId: string): Promise { return getJson( withQuery(`/api/reservation/orders/${encodeURIComponent(orderId)}`, { hotel_id: getReservationHotelId(), include_tasks: true, include_source_summary: true, }), ) } export async function fetchSourceMessageConversation( sourceMessageId: string, ): Promise { return getJson( `/api/source-messages/${encodeURIComponent(sourceMessageId)}/conversation`, ) } export async function fetchReservationTaskDetail(taskId: string): Promise { if (useReservationFixtures) { return fixtureTaskDetail(taskId) } return getJson(`/api/reservation/tasks/${taskId}`) } export async function fetchReservationV4OrderTaskDetail( orderTaskId: string, ): Promise { return getJson( withQuery(`/api/reservation/order-tasks/${encodeURIComponent(orderTaskId)}`, { hotel_id: getReservationHotelId(), }), ) } export async function fetchReservationV4SourceNotificationDetail( notificationId: string, ): Promise { return getJson( withQuery(`/api/reservation/source-notifications/${encodeURIComponent(notificationId)}`, { hotel_id: getReservationHotelId(), }), ) } export async function confirmReservationV4OrderTaskCard( orderTaskId: string, cardId: string, request: ReservationV4CardConfirmRequest, ): Promise { return sendJson( `/api/reservation/order-tasks/${encodeURIComponent(orderTaskId)}/cards/${encodeURIComponent(cardId)}/confirm`, 'POST', request, ) } export async function resolveReservationV4OrderTaskCardReview( orderTaskId: string, cardId: string, request: ReservationV4ReviewResolutionRequest, ): Promise { return sendJson( `/api/reservation/order-tasks/${encodeURIComponent(orderTaskId)}/cards/${encodeURIComponent(cardId)}/review-resolution`, 'POST', request, ) } export async function ackReservationV4SourceNotification( notificationId: string, request: ReservationV4SourceNotificationAckRequest, ): Promise { return sendJson( `/api/reservation/source-notifications/${encodeURIComponent(notificationId)}/ack`, 'POST', request, ) } export async function saveReservationTaskDraft( taskId: string, request: ReservationTaskPayloadMutationRequest, ): Promise { if (useReservationFixtures) { return fixtureTaskMutation(taskId, request, false) } return sendJson(`/api/reservation/tasks/${taskId}/draft`, 'PUT', request) } export async function confirmReservationTask( taskId: string, request: ReservationTaskPayloadMutationRequest, ): Promise { if (useReservationFixtures) { return fixtureTaskMutation(taskId, request, true) } return sendJson(`/api/reservation/tasks/${taskId}/confirm`, 'POST', request) } export async function convertReservationManualReviewTask( taskId: string, request: ReservationManualReviewConversionRequest, ): Promise { return sendJson( `/api/reservation/tasks/${encodeURIComponent(taskId)}/manual-review-conversions`, 'POST', request, ) } export async function resolveReservationManualReviewTask( taskId: string, request: ReservationManualReviewResolutionRequest, ): Promise { if (useReservationFixtures) { return fixtureManualReviewResolution(taskId, request) } return sendJson( `/api/reservation/tasks/${encodeURIComponent(taskId)}/manual-review-resolutions`, 'POST', request, ) } export async function executeReservationOperaOperation( taskId: string, operationId: string, request: ReservationOperaSimulationRequest, ): Promise { if (useReservationFixtures) { return fixtureOperaOperation(taskId, operationId, request, 'execute') } return sendJson( `/api/reservation/tasks/${taskId}/opera-operations/${operationId}/execute`, 'POST', request, ) } export async function retryReservationOperaOperation( taskId: string, operationId: string, request: ReservationOperaSimulationRequest, ): Promise { if (useReservationFixtures) { return fixtureOperaOperation(taskId, operationId, request, 'retry') } return sendJson( `/api/reservation/tasks/${taskId}/opera-operations/${operationId}/retry`, 'POST', request, ) } export async function fetchReservationTaskAudits(taskId: string): Promise { if (useReservationFixtures) { return Promise.resolve( reservationTaskAudits.find((audit) => audit.task_id === taskId) ?? { task_id: taskId, items: [], }, ) } return getJson(`/api/reservation/tasks/${taskId}/audits`) } export async function generateManualReservationInvoice( request: ManualInvoiceGenerationRequest, ): Promise { return sendJson('/api/reservation/invoices/manual-generations', 'POST', request) } function withQuery(path: string, params: object): string { const searchParams = new URLSearchParams() Object.entries(params).forEach(([key, value]) => { if ( value === undefined || value === null || value === '' || !['string', 'number', 'boolean'].includes(typeof value) ) { return } searchParams.set(key, String(value)) }) const query = searchParams.toString() return query ? `${path}?${query}` : path } function withReservationHotel(filters: T): T { return { hotel_id: getReservationHotelId() ?? undefined, ...filters, } } function fixtureTaskDetail(taskId: string): Promise { const detail = reservationTaskDetails.find((item) => item.task_id === taskId) if (!detail) { throw new Error(`Reservation task ${taskId} was not found in frontend fixture data.`) } return Promise.resolve(structuredClone(detail)) } function fixtureTaskMutation( taskId: string, request: ReservationTaskPayloadMutationRequest, confirmed: boolean, ): Promise { return fixtureTaskDetail(taskId).then((detail) => ({ task_id: detail.task_id, order_id: detail.order_id, task_status: confirmed ? 'READY' : detail.task_status, draft_payload: request.field_values, confirmed_payload: confirmed ? request.field_values : detail.confirmed_payload, opera_operations: confirmed ? detail.opera_operations : [], })) } function fixtureManualReviewResolution( taskId: string, request: ReservationManualReviewResolutionRequest, ): Promise { return fixtureTaskDetail(taskId).then((detail) => { const fieldValues = Object.fromEntries( request.field_overrides.map((item) => [fieldOverrideToFixturePath(item), item.value]), ) return { task_id: detail.task_id, order_id: detail.order_id, task_status: 'READY', review_status: 'RESOLVED', review_resolution: { confirmed_order_id: request.confirmed_order_id ?? detail.order_id, reason: request.reason ?? null, field_overrides: request.field_overrides, }, confirmed_payload: { field_values: fieldValues, legacy_field_values: legacyFieldValues(fieldValues), effective_payload: effectivePayload(fieldValues), }, opera_operations: detail.opera_operations, } }) } function fieldOverrideToFixturePath( fieldOverride: ReservationManualReviewResolutionRequest['field_overrides'][number], ): string { if (fieldOverride.field_path) { return canonicalFixtureFieldPath(fieldOverride.field_path) } if (fieldOverride.field_pointer) { return canonicalFixtureFieldPath(fieldOverride.field_pointer.replace(/^\//, '').replace(/\//g, '.')) } throw new Error('Manual review field override requires field_pointer or field_path') } function canonicalFixtureFieldPath(fieldPath: string): string { if (fieldPath === 'extracted_fields.room_quantity') { return 'extracted_fields.room_items.0.room_quantity' } if (fieldPath === 'extracted_fields.room_type') { return 'extracted_fields.room_items.0.room_type_raw' } if (fieldPath === 'extracted_fields.pms_room_type_code') { return 'extracted_fields.room_items.0.pms_room_type_code' } return fieldPath } function legacyFieldValues(fieldValues: Record): Record { return Object.entries(fieldValues).reduce>((legacyValues, [fieldPath, value]) => { if (fieldPath === 'extracted_fields.room_items.0.room_quantity') { legacyValues['extracted_fields.room_quantity'] = value } if (fieldPath === 'extracted_fields.room_items.0.room_type_raw') { legacyValues['extracted_fields.room_type'] = value } if (fieldPath === 'extracted_fields.room_items.0.pms_room_type_code') { legacyValues['extracted_fields.pms_room_type_code'] = value } return legacyValues }, {}) } function effectivePayload(fieldValues: Record): ReservationRecord { return Object.entries(fieldValues).reduce((payload, [fieldPath, value]) => { writeNestedFieldValue(payload, fieldPath, value) return payload }, {}) } function writeNestedFieldValue(payload: ReservationRecord, fieldPath: string, value: unknown): void { const segments = fieldPath.split('.') let current: ReservationRecord | unknown[] = payload segments.forEach((segment, index) => { const last = index === segments.length - 1 const nextSegment = segments[index + 1] if (last) { if (Array.isArray(current)) { current[Number(segment)] = value } else { current[segment] = value } return } const nextIsArray = nextSegment !== undefined && /^\d+$/.test(nextSegment) if (Array.isArray(current)) { const arrayIndex = Number(segment) current[arrayIndex] ??= nextIsArray ? [] : {} current = current[arrayIndex] as ReservationRecord | unknown[] return } current[segment] ??= nextIsArray ? [] : {} current = current[segment] as ReservationRecord | unknown[] }) } async function fixtureOperaOperation( taskId: string, operationId: string, request: ReservationOperaSimulationRequest, action: 'execute' | 'retry', ): Promise { const detail = await fixtureTaskDetail(taskId) const operation = detail.opera_operations.find((item) => item.operation_id === operationId) if (!operation) { throw new Error(`Reservation OPERA operation ${operationId} was not found in frontend fixture data.`) } const success = request.simulate_success !== false const attemptNumber = operation.attempt_count + 1 return { ...operation, operation_status: success ? 'SUCCEEDED' : 'FAILED', attempt_count: attemptNumber, last_error_message: success ? null : request.failure_message ?? 'Frontend simulated OPERA failure', attempts: [ ...operation.attempts, { attempt_id: `${operationId}-${action}-${attemptNumber}`, attempt_number: attemptNumber, attempt_status: success ? 'SUCCEEDED' : 'FAILED', response_payload: success ? { simulated: true } : null, error_message: success ? null : request.failure_message ?? 'Frontend simulated OPERA failure', started_at: new Date().toISOString(), finished_at: new Date().toISOString(), }, ], } }