/** * Admin routes for booking extras — a separately, independently mounted module * (`bookingsExtrasApiModule`). Covers two resource sub-chains: booking-extras * CRUD and the slot extra manifest (read + selection/collection mutations). * * Migrated to `@hono/zod-openapi` for the OpenAPI admin backfill (voyant#2114). * Request schemas reuse the existing `validation.ts` schemas the handlers * already parse; the `booking-extras` list uses the framework's canonical * `listResponseSchema(...)` envelope; response row schemas are authored from the * Drizzle `$inferSelect` shapes (§17: timestamp columns serialize to ISO strings * over the wire). The slot manifest GET returns a bespoke composite (slot + * extras + travelers-with-fullName + per-traveler/per-extra selections) that has * no single base row, so its response schema is authored from the * service-manifest shape. The manifest mutations carry typed * `slot_not_found`/`extra_not_found`/`traveler_not_found` → 404 unions, declared * inline per leg. The business logic + service wiring are unchanged. * * The routes are split into per-resource child `OpenAPIHono` sub-chains * (`.route("/", child)`) rather than one long flat `.openapi()` chain to keep * the tsc inference cost linear. */ import { OpenAPIHono, z } from "@hono/zod-openapi"; import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"; type Env = { Variables: { db: PostgresJsDatabase; userId?: string; }; }; export declare const bookingsExtrasRoutes: OpenAPIHono | null | undefined; }; }; output: { error: string; }; outputFormat: "json"; status: 400; } | { input: { param: { slotId: string; }; } & { json: { bookingId: string; travelerId: string; productExtraId: string; optionExtraConfigId?: string | null | undefined; status?: "selected" | "cancelled" | "fulfilled" | "no_show" | undefined; collectionStatus?: "not_required" | "pending" | "collected" | "waived" | "refunded" | undefined; collectionCurrency?: string | null | undefined; collectionAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }; }; output: { error: string; }; outputFormat: "json"; status: 404; } | { input: { param: { slotId: string; }; } & { json: { bookingId: string; travelerId: string; productExtraId: string; optionExtraConfigId?: string | null | undefined; status?: "selected" | "cancelled" | "fulfilled" | "no_show" | undefined; collectionStatus?: "not_required" | "pending" | "collected" | "waived" | "refunded" | undefined; collectionCurrency?: string | null | undefined; collectionAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }; }; output: { data: { id: string; bookingId: string; bookingItemId: string | null; travelerId: string; productExtraId: string; optionExtraConfigId: string | null; status: "selected" | "cancelled" | "fulfilled" | "no_show"; collectionMode: "external" | "none" | "included" | "booking_total" | "cash_on_trip"; collectionStatus: "not_required" | "pending" | "collected" | "waived" | "refunded"; collectionCurrency: string | null; collectionAmountCents: number | null; collectedAt: string | null; collectedBy: string | null; notes: string | null; metadata: { [x: string]: import("hono/utils/types").JSONValue; } | null; createdAt: string; updatedAt: string; } | null; }; outputFormat: "json"; status: 200; }; }; } & { "/slot-manifests/:slotId/selections/bulk": { $post: { input: { param: { slotId: string; }; } & { json: { selections: { bookingId: string; travelerId: string; productExtraId: string; optionExtraConfigId?: string | null | undefined; status?: "selected" | "cancelled" | "fulfilled" | "no_show" | undefined; collectionStatus?: "not_required" | "pending" | "collected" | "waived" | "refunded" | undefined; collectionCurrency?: string | null | undefined; collectionAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }[]; }; }; output: { error: string; }; outputFormat: "json"; status: 400; } | { input: { param: { slotId: string; }; } & { json: { selections: { bookingId: string; travelerId: string; productExtraId: string; optionExtraConfigId?: string | null | undefined; status?: "selected" | "cancelled" | "fulfilled" | "no_show" | undefined; collectionStatus?: "not_required" | "pending" | "collected" | "waived" | "refunded" | undefined; collectionCurrency?: string | null | undefined; collectionAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }[]; }; }; output: { error: string; }; outputFormat: "json"; status: 404; } | { input: { param: { slotId: string; }; } & { json: { selections: { bookingId: string; travelerId: string; productExtraId: string; optionExtraConfigId?: string | null | undefined; status?: "selected" | "cancelled" | "fulfilled" | "no_show" | undefined; collectionStatus?: "not_required" | "pending" | "collected" | "waived" | "refunded" | undefined; collectionCurrency?: string | null | undefined; collectionAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }[]; }; }; output: { data: ({ id: string; bookingId: string; bookingItemId: string | null; travelerId: string; productExtraId: string; optionExtraConfigId: string | null; status: "selected" | "cancelled" | "fulfilled" | "no_show"; collectionMode: "external" | "none" | "included" | "booking_total" | "cash_on_trip"; collectionStatus: "not_required" | "pending" | "collected" | "waived" | "refunded"; collectionCurrency: string | null; collectionAmountCents: number | null; collectedAt: string | null; collectedBy: string | null; notes: string | null; metadata: { [x: string]: import("hono/utils/types").JSONValue; } | null; createdAt: string; updatedAt: string; } | null)[]; }; outputFormat: "json"; status: 200; }; }; } & { "/slot-manifests/:slotId/collections/bulk": { $post: { input: { param: { slotId: string; }; } & { json: { productExtraId: string; travelerIds: string[]; collectionStatus: "not_required" | "pending" | "collected" | "waived" | "refunded"; collectionCurrency?: string | null | undefined; collectionAmountCents?: number | null | undefined; notes?: string | null | undefined; }; }; output: { error: string; }; outputFormat: "json"; status: 400; } | { input: { param: { slotId: string; }; } & { json: { productExtraId: string; travelerIds: string[]; collectionStatus: "not_required" | "pending" | "collected" | "waived" | "refunded"; collectionCurrency?: string | null | undefined; collectionAmountCents?: number | null | undefined; notes?: string | null | undefined; }; }; output: { error: string; }; outputFormat: "json"; status: 404; } | { input: { param: { slotId: string; }; } & { json: { productExtraId: string; travelerIds: string[]; collectionStatus: "not_required" | "pending" | "collected" | "waived" | "refunded"; collectionCurrency?: string | null | undefined; collectionAmountCents?: number | null | undefined; notes?: string | null | undefined; }; }; output: { data: ({ id: string; bookingId: string; bookingItemId: string | null; travelerId: string; productExtraId: string; optionExtraConfigId: string | null; status: "selected" | "cancelled" | "fulfilled" | "no_show"; collectionMode: "external" | "none" | "included" | "booking_total" | "cash_on_trip"; collectionStatus: "not_required" | "pending" | "collected" | "waived" | "refunded"; collectionCurrency: string | null; collectionAmountCents: number | null; collectedAt: string | null; collectedBy: string | null; notes: string | null; metadata: { [x: string]: import("hono/utils/types").JSONValue; } | null; createdAt: string; updatedAt: string; } | null)[]; }; outputFormat: "json"; status: 200; }; }; }, "/"> & import("hono/types").MergeSchemaPath<{ "/booking-extras": { $get: { input: { query: { limit?: unknown; offset?: unknown; bookingId?: string | undefined; productExtraId?: string | undefined; optionExtraConfigId?: string | undefined; status?: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled" | undefined; }; }; output: { data: { id: string; bookingId: string; productExtraId: string | null; optionExtraConfigId: string | null; name: string; description: string | null; status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled"; pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free"; pricedPerPerson: boolean; quantity: number; sellCurrency: string; unitSellAmountCents: number | null; totalSellAmountCents: number | null; costCurrency: string | null; unitCostAmountCents: number | null; totalCostAmountCents: number | null; notes: string | null; metadata: { [x: string]: import("hono/utils/types").JSONValue; } | null; createdAt: string; updatedAt: string; }[]; total: number; limit: number; offset: number; }; outputFormat: "json"; status: 200; } | { input: { query: { limit?: unknown; offset?: unknown; bookingId?: string | undefined; productExtraId?: string | undefined; optionExtraConfigId?: string | undefined; status?: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled" | undefined; }; }; output: { error: string; }; outputFormat: "json"; status: 400; }; }; } & { "/booking-extras": { $post: { input: { json: { bookingId: string; name: string; sellCurrency: string; productExtraId?: string | null | undefined; optionExtraConfigId?: string | null | undefined; description?: string | null | undefined; status?: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled" | undefined; pricingMode?: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | undefined; pricedPerPerson?: boolean | undefined; quantity?: number | undefined; unitSellAmountCents?: number | null | undefined; totalSellAmountCents?: number | null | undefined; costCurrency?: string | null | undefined; unitCostAmountCents?: number | null | undefined; totalCostAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }; }; output: { error: string; }; outputFormat: "json"; status: 400; } | { input: { json: { bookingId: string; name: string; sellCurrency: string; productExtraId?: string | null | undefined; optionExtraConfigId?: string | null | undefined; description?: string | null | undefined; status?: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled" | undefined; pricingMode?: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | undefined; pricedPerPerson?: boolean | undefined; quantity?: number | undefined; unitSellAmountCents?: number | null | undefined; totalSellAmountCents?: number | null | undefined; costCurrency?: string | null | undefined; unitCostAmountCents?: number | null | undefined; totalCostAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }; }; output: { data: { id: string; bookingId: string; productExtraId: string | null; optionExtraConfigId: string | null; name: string; description: string | null; status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled"; pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free"; pricedPerPerson: boolean; quantity: number; sellCurrency: string; unitSellAmountCents: number | null; totalSellAmountCents: number | null; costCurrency: string | null; unitCostAmountCents: number | null; totalCostAmountCents: number | null; notes: string | null; metadata: { [x: string]: import("hono/utils/types").JSONValue; } | null; createdAt: string; updatedAt: string; }; }; outputFormat: "json"; status: 201; }; }; } & { "/booking-extras/:id": { $get: { input: { param: { id: string; }; }; output: { data: { id: string; bookingId: string; productExtraId: string | null; optionExtraConfigId: string | null; name: string; description: string | null; status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled"; pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free"; pricedPerPerson: boolean; quantity: number; sellCurrency: string; unitSellAmountCents: number | null; totalSellAmountCents: number | null; costCurrency: string | null; unitCostAmountCents: number | null; totalCostAmountCents: number | null; notes: string | null; metadata: { [x: string]: import("hono/utils/types").JSONValue; } | null; createdAt: string; updatedAt: string; }; }; outputFormat: "json"; status: 200; } | { input: { param: { id: string; }; }; output: { error: string; }; outputFormat: "json"; status: 404; }; }; } & { "/booking-extras/:id": { $patch: { input: { param: { id: string; }; } & { json: { bookingId?: string | undefined; productExtraId?: string | null | undefined; optionExtraConfigId?: string | null | undefined; name?: string | undefined; description?: string | null | undefined; status?: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled" | undefined; pricingMode?: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | undefined; pricedPerPerson?: boolean | undefined; quantity?: number | undefined; sellCurrency?: string | undefined; unitSellAmountCents?: number | null | undefined; totalSellAmountCents?: number | null | undefined; costCurrency?: string | null | undefined; unitCostAmountCents?: number | null | undefined; totalCostAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }; }; output: { error: string; }; outputFormat: "json"; status: 400; } | { input: { param: { id: string; }; } & { json: { bookingId?: string | undefined; productExtraId?: string | null | undefined; optionExtraConfigId?: string | null | undefined; name?: string | undefined; description?: string | null | undefined; status?: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled" | undefined; pricingMode?: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | undefined; pricedPerPerson?: boolean | undefined; quantity?: number | undefined; sellCurrency?: string | undefined; unitSellAmountCents?: number | null | undefined; totalSellAmountCents?: number | null | undefined; costCurrency?: string | null | undefined; unitCostAmountCents?: number | null | undefined; totalCostAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }; }; output: { data: { id: string; bookingId: string; productExtraId: string | null; optionExtraConfigId: string | null; name: string; description: string | null; status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled"; pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free"; pricedPerPerson: boolean; quantity: number; sellCurrency: string; unitSellAmountCents: number | null; totalSellAmountCents: number | null; costCurrency: string | null; unitCostAmountCents: number | null; totalCostAmountCents: number | null; notes: string | null; metadata: { [x: string]: import("hono/utils/types").JSONValue; } | null; createdAt: string; updatedAt: string; }; }; outputFormat: "json"; status: 200; } | { input: { param: { id: string; }; } & { json: { bookingId?: string | undefined; productExtraId?: string | null | undefined; optionExtraConfigId?: string | null | undefined; name?: string | undefined; description?: string | null | undefined; status?: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled" | undefined; pricingMode?: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | undefined; pricedPerPerson?: boolean | undefined; quantity?: number | undefined; sellCurrency?: string | undefined; unitSellAmountCents?: number | null | undefined; totalSellAmountCents?: number | null | undefined; costCurrency?: string | null | undefined; unitCostAmountCents?: number | null | undefined; totalCostAmountCents?: number | null | undefined; notes?: string | null | undefined; metadata?: Record | null | undefined; }; }; output: { error: string; }; outputFormat: "json"; status: 404; }; }; } & { "/booking-extras/:id": { $delete: { input: { param: { id: string; }; }; output: { error: string; }; outputFormat: "json"; status: 404; } | { input: { param: { id: string; }; }; output: { success: boolean; }; outputFormat: "json"; status: 200; }; }; }, "/">, "/">; export type BookingsExtrasRoutes = typeof bookingsExtrasRoutes; export declare const __test__: { bookingExtraSchema: z.ZodObject<{ id: z.ZodString; bookingId: z.ZodString; productExtraId: z.ZodNullable; optionExtraConfigId: z.ZodNullable; name: z.ZodString; description: z.ZodNullable; status: z.ZodEnum<{ draft: "draft"; selected: "selected"; confirmed: "confirmed"; cancelled: "cancelled"; fulfilled: "fulfilled"; }>; pricingMode: z.ZodEnum<{ included: "included"; per_person: "per_person"; per_booking: "per_booking"; quantity_based: "quantity_based"; on_request: "on_request"; free: "free"; }>; pricedPerPerson: z.ZodBoolean; quantity: z.ZodNumber; sellCurrency: z.ZodString; unitSellAmountCents: z.ZodNullable; totalSellAmountCents: z.ZodNullable; costCurrency: z.ZodNullable; unitCostAmountCents: z.ZodNullable; totalCostAmountCents: z.ZodNullable; notes: z.ZodNullable; metadata: z.ZodNullable>; createdAt: z.ZodString; updatedAt: z.ZodString; }, z.core.$strip>; extraParticipantSelectionSchema: z.ZodObject<{ id: z.ZodString; bookingId: z.ZodString; bookingItemId: z.ZodNullable; travelerId: z.ZodString; productExtraId: z.ZodString; optionExtraConfigId: z.ZodNullable; status: z.ZodEnum<{ selected: "selected"; cancelled: "cancelled"; fulfilled: "fulfilled"; no_show: "no_show"; }>; collectionMode: z.ZodEnum<{ external: "external"; none: "none"; included: "included"; booking_total: "booking_total"; cash_on_trip: "cash_on_trip"; }>; collectionStatus: z.ZodEnum<{ not_required: "not_required"; pending: "pending"; collected: "collected"; waived: "waived"; refunded: "refunded"; }>; collectionCurrency: z.ZodNullable; collectionAmountCents: z.ZodNullable; collectedAt: z.ZodNullable; collectedBy: z.ZodNullable; notes: z.ZodNullable; metadata: z.ZodNullable>; createdAt: z.ZodString; updatedAt: z.ZodString; }, z.core.$strip>; slotExtraManifestSchema: z.ZodObject<{ slot: z.ZodObject<{ id: z.ZodString; productId: z.ZodString; optionId: z.ZodNullable; facilityId: z.ZodNullable; availabilityRuleId: z.ZodNullable; startTimeId: z.ZodNullable; dateLocal: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodNullable; timezone: z.ZodString; status: z.ZodEnum<{ cancelled: "cancelled"; open: "open"; closed: "closed"; sold_out: "sold_out"; }>; unlimited: z.ZodBoolean; initialPax: z.ZodNullable; remainingPax: z.ZodNullable; initialPickups: z.ZodNullable; remainingPickups: z.ZodNullable; remainingResources: z.ZodNullable; pastCutoff: z.ZodBoolean; tooEarly: z.ZodBoolean; nights: z.ZodNullable; days: z.ZodNullable; notes: z.ZodNullable; createdAt: z.ZodString; updatedAt: z.ZodString; }, z.core.$strip>; extras: z.ZodArray; code: z.ZodNullable; name: z.ZodString; description: z.ZodNullable; selectionType: z.ZodEnum<{ required: "required"; optional: "optional"; unavailable: "unavailable"; default_selected: "default_selected"; }>; pricingMode: z.ZodEnum<{ included: "included"; per_person: "per_person"; per_booking: "per_booking"; quantity_based: "quantity_based"; on_request: "on_request"; free: "free"; }>; pricedPerPerson: z.ZodBoolean; collectionMode: z.ZodEnum<{ external: "external"; none: "none"; included: "included"; booking_total: "booking_total"; cash_on_trip: "cash_on_trip"; }>; showOnSlotManifest: z.ZodBoolean; minQuantity: z.ZodNullable; maxQuantity: z.ZodNullable; defaultQuantity: z.ZodNullable; active: z.ZodBoolean; sortOrder: z.ZodNumber; metadata: z.ZodNullable>; createdAt: z.ZodString; updatedAt: z.ZodString; }, z.core.$strip>>; travelers: z.ZodArray; firstName: z.ZodString; lastName: z.ZodString; email: z.ZodNullable; phone: z.ZodNullable; isPrimary: z.ZodBoolean; createdAt: z.ZodString; fullName: z.ZodString; }, z.core.$strip>>; selections: z.ZodArray; bookingItemId: z.ZodNullable; status: z.ZodString; selected: z.ZodBoolean; collectionMode: z.ZodEnum<{ external: "external"; none: "none"; included: "included"; booking_total: "booking_total"; cash_on_trip: "cash_on_trip"; }>; collectionStatus: z.ZodString; collectionCurrency: z.ZodNullable; collectionAmountCents: z.ZodNullable; collectedAt: z.ZodNullable; collectedBy: z.ZodNullable; notes: z.ZodNullable; metadata: z.ZodNullable>; source: z.ZodString; }, z.core.$strip>>; }, z.core.$strip>; manifestSlotSchema: z.ZodObject<{ id: z.ZodString; productId: z.ZodString; optionId: z.ZodNullable; facilityId: z.ZodNullable; availabilityRuleId: z.ZodNullable; startTimeId: z.ZodNullable; dateLocal: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodNullable; timezone: z.ZodString; status: z.ZodEnum<{ cancelled: "cancelled"; open: "open"; closed: "closed"; sold_out: "sold_out"; }>; unlimited: z.ZodBoolean; initialPax: z.ZodNullable; remainingPax: z.ZodNullable; initialPickups: z.ZodNullable; remainingPickups: z.ZodNullable; remainingResources: z.ZodNullable; pastCutoff: z.ZodBoolean; tooEarly: z.ZodBoolean; nights: z.ZodNullable; days: z.ZodNullable; notes: z.ZodNullable; createdAt: z.ZodString; updatedAt: z.ZodString; }, z.core.$strip>; manifestExtraSchema: z.ZodObject<{ id: z.ZodString; productId: z.ZodString; supplierId: z.ZodNullable; code: z.ZodNullable; name: z.ZodString; description: z.ZodNullable; selectionType: z.ZodEnum<{ required: "required"; optional: "optional"; unavailable: "unavailable"; default_selected: "default_selected"; }>; pricingMode: z.ZodEnum<{ included: "included"; per_person: "per_person"; per_booking: "per_booking"; quantity_based: "quantity_based"; on_request: "on_request"; free: "free"; }>; pricedPerPerson: z.ZodBoolean; collectionMode: z.ZodEnum<{ external: "external"; none: "none"; included: "included"; booking_total: "booking_total"; cash_on_trip: "cash_on_trip"; }>; showOnSlotManifest: z.ZodBoolean; minQuantity: z.ZodNullable; maxQuantity: z.ZodNullable; defaultQuantity: z.ZodNullable; active: z.ZodBoolean; sortOrder: z.ZodNumber; metadata: z.ZodNullable>; createdAt: z.ZodString; updatedAt: z.ZodString; }, z.core.$strip>; manifestTravelerSchema: z.ZodObject<{ id: z.ZodString; bookingId: z.ZodString; bookingNumber: z.ZodString; bookingStatus: z.ZodString; participantType: z.ZodString; travelerCategory: z.ZodNullable; firstName: z.ZodString; lastName: z.ZodString; email: z.ZodNullable; phone: z.ZodNullable; isPrimary: z.ZodBoolean; createdAt: z.ZodString; fullName: z.ZodString; }, z.core.$strip>; manifestSelectionSchema: z.ZodObject<{ bookingId: z.ZodString; travelerId: z.ZodString; productExtraId: z.ZodString; optionExtraConfigId: z.ZodNullable; bookingItemId: z.ZodNullable; status: z.ZodString; selected: z.ZodBoolean; collectionMode: z.ZodEnum<{ external: "external"; none: "none"; included: "included"; booking_total: "booking_total"; cash_on_trip: "cash_on_trip"; }>; collectionStatus: z.ZodString; collectionCurrency: z.ZodNullable; collectionAmountCents: z.ZodNullable; collectedAt: z.ZodNullable; collectedBy: z.ZodNullable; notes: z.ZodNullable; metadata: z.ZodNullable>; source: z.ZodString; }, z.core.$strip>; }; export {};