@voyant-travel/cruises 0.118.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +50 -0
  3. package/dist/adapters/connect-compat.d.ts +20 -0
  4. package/dist/adapters/connect-compat.d.ts.map +1 -0
  5. package/dist/adapters/connect-compat.js +71 -0
  6. package/dist/adapters/contract-fixture.d.ts +32 -0
  7. package/dist/adapters/contract-fixture.d.ts.map +1 -0
  8. package/dist/adapters/contract-fixture.js +152 -0
  9. package/dist/adapters/index.d.ts +331 -0
  10. package/dist/adapters/index.d.ts.map +1 -0
  11. package/dist/adapters/index.js +16 -0
  12. package/dist/adapters/memoize.d.ts +28 -0
  13. package/dist/adapters/memoize.d.ts.map +1 -0
  14. package/dist/adapters/memoize.js +131 -0
  15. package/dist/adapters/mock.d.ts +44 -0
  16. package/dist/adapters/mock.d.ts.map +1 -0
  17. package/dist/adapters/mock.js +192 -0
  18. package/dist/adapters/registry.d.ts +26 -0
  19. package/dist/adapters/registry.d.ts.map +1 -0
  20. package/dist/adapters/registry.js +42 -0
  21. package/dist/adapters/source-adapter-shim.d.ts +80 -0
  22. package/dist/adapters/source-adapter-shim.d.ts.map +1 -0
  23. package/dist/adapters/source-adapter-shim.js +390 -0
  24. package/dist/booking-engine/handler.d.ts +108 -0
  25. package/dist/booking-engine/handler.d.ts.map +1 -0
  26. package/dist/booking-engine/handler.js +225 -0
  27. package/dist/booking-engine/index.d.ts +9 -0
  28. package/dist/booking-engine/index.d.ts.map +1 -0
  29. package/dist/booking-engine/index.js +8 -0
  30. package/dist/booking-extension.d.ts +1179 -0
  31. package/dist/booking-extension.d.ts.map +1 -0
  32. package/dist/booking-extension.js +342 -0
  33. package/dist/cabin-features.d.ts +8 -0
  34. package/dist/cabin-features.d.ts.map +1 -0
  35. package/dist/cabin-features.js +7 -0
  36. package/dist/catalog-policy-cabins.d.ts +18 -0
  37. package/dist/catalog-policy-cabins.d.ts.map +1 -0
  38. package/dist/catalog-policy-cabins.js +96 -0
  39. package/dist/catalog-policy-core.d.ts +3 -0
  40. package/dist/catalog-policy-core.d.ts.map +1 -0
  41. package/dist/catalog-policy-core.js +247 -0
  42. package/dist/catalog-policy-structure.d.ts +3 -0
  43. package/dist/catalog-policy-structure.d.ts.map +1 -0
  44. package/dist/catalog-policy-structure.js +387 -0
  45. package/dist/catalog-policy.d.ts +15 -0
  46. package/dist/catalog-policy.d.ts.map +1 -0
  47. package/dist/catalog-policy.js +19 -0
  48. package/dist/content-shape.d.ts +5 -0
  49. package/dist/content-shape.d.ts.map +1 -0
  50. package/dist/content-shape.js +13 -0
  51. package/dist/draft-shape.d.ts +59 -0
  52. package/dist/draft-shape.d.ts.map +1 -0
  53. package/dist/draft-shape.js +98 -0
  54. package/dist/events.d.ts +21 -0
  55. package/dist/events.d.ts.map +1 -0
  56. package/dist/events.js +21 -0
  57. package/dist/index.d.ts +43 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +66 -0
  60. package/dist/lib/key.d.ts +41 -0
  61. package/dist/lib/key.d.ts.map +1 -0
  62. package/dist/lib/key.js +100 -0
  63. package/dist/routes-booking-payloads.d.ts +133 -0
  64. package/dist/routes-booking-payloads.d.ts.map +1 -0
  65. package/dist/routes-booking-payloads.js +142 -0
  66. package/dist/routes-content.d.ts +53 -0
  67. package/dist/routes-content.d.ts.map +1 -0
  68. package/dist/routes-content.js +158 -0
  69. package/dist/routes-core.d.ts +4 -0
  70. package/dist/routes-core.d.ts.map +1 -0
  71. package/dist/routes-core.js +68 -0
  72. package/dist/routes-detail.d.ts +4 -0
  73. package/dist/routes-detail.d.ts.map +1 -0
  74. package/dist/routes-detail.js +261 -0
  75. package/dist/routes-env.d.ts +13 -0
  76. package/dist/routes-env.d.ts.map +1 -0
  77. package/dist/routes-env.js +1 -0
  78. package/dist/routes-keying.d.ts +28 -0
  79. package/dist/routes-keying.d.ts.map +1 -0
  80. package/dist/routes-keying.js +70 -0
  81. package/dist/routes-public.d.ts +911 -0
  82. package/dist/routes-public.d.ts.map +1 -0
  83. package/dist/routes-public.js +252 -0
  84. package/dist/routes-sailings-prices.d.ts +4 -0
  85. package/dist/routes-sailings-prices.d.ts.map +1 -0
  86. package/dist/routes-sailings-prices.js +278 -0
  87. package/dist/routes-search-index.d.ts +4 -0
  88. package/dist/routes-search-index.d.ts.map +1 -0
  89. package/dist/routes-search-index.js +25 -0
  90. package/dist/routes-ships.d.ts +4 -0
  91. package/dist/routes-ships.d.ts.map +1 -0
  92. package/dist/routes-ships.js +147 -0
  93. package/dist/routes-voyage-groups.d.ts +4 -0
  94. package/dist/routes-voyage-groups.d.ts.map +1 -0
  95. package/dist/routes-voyage-groups.js +85 -0
  96. package/dist/routes.d.ts +5 -0
  97. package/dist/routes.d.ts.map +1 -0
  98. package/dist/routes.js +14 -0
  99. package/dist/schema-cabins.d.ts +1098 -0
  100. package/dist/schema-cabins.d.ts.map +1 -0
  101. package/dist/schema-cabins.js +105 -0
  102. package/dist/schema-content.d.ts +577 -0
  103. package/dist/schema-content.d.ts.map +1 -0
  104. package/dist/schema-content.js +63 -0
  105. package/dist/schema-core.d.ts +1790 -0
  106. package/dist/schema-core.d.ts.map +1 -0
  107. package/dist/schema-core.js +171 -0
  108. package/dist/schema-itinerary.d.ts +556 -0
  109. package/dist/schema-itinerary.d.ts.map +1 -0
  110. package/dist/schema-itinerary.js +50 -0
  111. package/dist/schema-pricing.d.ts +633 -0
  112. package/dist/schema-pricing.d.ts.map +1 -0
  113. package/dist/schema-pricing.js +73 -0
  114. package/dist/schema-search.d.ts +611 -0
  115. package/dist/schema-search.d.ts.map +1 -0
  116. package/dist/schema-search.js +64 -0
  117. package/dist/schema-shared.d.ts +23 -0
  118. package/dist/schema-shared.d.ts.map +1 -0
  119. package/dist/schema-shared.js +107 -0
  120. package/dist/schema-sourced-content.d.ts +247 -0
  121. package/dist/schema-sourced-content.d.ts.map +1 -0
  122. package/dist/schema-sourced-content.js +38 -0
  123. package/dist/schema.d.ts +10 -0
  124. package/dist/schema.d.ts.map +1 -0
  125. package/dist/schema.js +9 -0
  126. package/dist/service-booking-helpers.d.ts +12 -0
  127. package/dist/service-booking-helpers.d.ts.map +1 -0
  128. package/dist/service-booking-helpers.js +94 -0
  129. package/dist/service-booking-types.d.ts +101 -0
  130. package/dist/service-booking-types.d.ts.map +1 -0
  131. package/dist/service-booking-types.js +1 -0
  132. package/dist/service-bookings.d.ts +46 -0
  133. package/dist/service-bookings.d.ts.map +1 -0
  134. package/dist/service-bookings.js +420 -0
  135. package/dist/service-catalog-plane-cabins.d.ts +24 -0
  136. package/dist/service-catalog-plane-cabins.d.ts.map +1 -0
  137. package/dist/service-catalog-plane-cabins.js +90 -0
  138. package/dist/service-catalog-plane.d.ts +74 -0
  139. package/dist/service-catalog-plane.d.ts.map +1 -0
  140. package/dist/service-catalog-plane.js +194 -0
  141. package/dist/service-content-synthesizer.d.ts +42 -0
  142. package/dist/service-content-synthesizer.d.ts.map +1 -0
  143. package/dist/service-content-synthesizer.js +144 -0
  144. package/dist/service-content.d.ts +74 -0
  145. package/dist/service-content.d.ts.map +1 -0
  146. package/dist/service-content.js +315 -0
  147. package/dist/service-core.d.ts +134 -0
  148. package/dist/service-core.d.ts.map +1 -0
  149. package/dist/service-core.js +257 -0
  150. package/dist/service-detach.d.ts +18 -0
  151. package/dist/service-detach.d.ts.map +1 -0
  152. package/dist/service-detach.js +199 -0
  153. package/dist/service-enrichment.d.ts +11 -0
  154. package/dist/service-enrichment.d.ts.map +1 -0
  155. package/dist/service-enrichment.js +47 -0
  156. package/dist/service-external-refresh.d.ts +39 -0
  157. package/dist/service-external-refresh.d.ts.map +1 -0
  158. package/dist/service-external-refresh.js +47 -0
  159. package/dist/service-itinerary.d.ts +22 -0
  160. package/dist/service-itinerary.d.ts.map +1 -0
  161. package/dist/service-itinerary.js +34 -0
  162. package/dist/service-prices.d.ts +46 -0
  163. package/dist/service-prices.d.ts.map +1 -0
  164. package/dist/service-prices.js +89 -0
  165. package/dist/service-pricing.d.ts +97 -0
  166. package/dist/service-pricing.d.ts.map +1 -0
  167. package/dist/service-pricing.js +198 -0
  168. package/dist/service-sailings.d.ts +48 -0
  169. package/dist/service-sailings.d.ts.map +1 -0
  170. package/dist/service-sailings.js +145 -0
  171. package/dist/service-search-types.d.ts +54 -0
  172. package/dist/service-search-types.d.ts.map +1 -0
  173. package/dist/service-search-types.js +1 -0
  174. package/dist/service-search.d.ts +65 -0
  175. package/dist/service-search.d.ts.map +1 -0
  176. package/dist/service-search.js +467 -0
  177. package/dist/service-shared.d.ts +22 -0
  178. package/dist/service-shared.d.ts.map +1 -0
  179. package/dist/service-shared.js +22 -0
  180. package/dist/service-ships.d.ts +47 -0
  181. package/dist/service-ships.d.ts.map +1 -0
  182. package/dist/service-ships.js +156 -0
  183. package/dist/service.d.ts +255 -0
  184. package/dist/service.d.ts.map +1 -0
  185. package/dist/service.js +12 -0
  186. package/dist/validation-cabins.d.ts +267 -0
  187. package/dist/validation-cabins.d.ts.map +1 -0
  188. package/dist/validation-cabins.js +77 -0
  189. package/dist/validation-content.d.ts +123 -0
  190. package/dist/validation-content.d.ts.map +1 -0
  191. package/dist/validation-content.js +40 -0
  192. package/dist/validation-core.d.ts +393 -0
  193. package/dist/validation-core.d.ts.map +1 -0
  194. package/dist/validation-core.js +162 -0
  195. package/dist/validation-itinerary.d.ts +123 -0
  196. package/dist/validation-itinerary.d.ts.map +1 -0
  197. package/dist/validation-itinerary.js +47 -0
  198. package/dist/validation-pricing.d.ts +137 -0
  199. package/dist/validation-pricing.d.ts.map +1 -0
  200. package/dist/validation-pricing.js +49 -0
  201. package/dist/validation-search.d.ts +118 -0
  202. package/dist/validation-search.d.ts.map +1 -0
  203. package/dist/validation-search.js +60 -0
  204. package/dist/validation-shared.d.ts +123 -0
  205. package/dist/validation-shared.d.ts.map +1 -0
  206. package/dist/validation-shared.js +103 -0
  207. package/dist/validation.d.ts +8 -0
  208. package/dist/validation.d.ts.map +1 -0
  209. package/dist/validation.js +7 -0
  210. package/package.json +146 -0
@@ -0,0 +1,34 @@
1
+ export function mergeDay(base, override) {
2
+ if (!override) {
3
+ return {
4
+ dayNumber: base.dayNumber,
5
+ title: base.title,
6
+ description: base.description,
7
+ portFacilityId: base.portFacilityId,
8
+ portCanonicalPlaceId: base.portCanonicalPlaceId,
9
+ arrivalTime: base.arrivalTime,
10
+ departureTime: base.departureTime,
11
+ isOvernight: base.isOvernight,
12
+ isSeaDay: base.isSeaDay,
13
+ isExpeditionLanding: base.isExpeditionLanding,
14
+ isSkipped: false,
15
+ meals: base.meals ?? {},
16
+ hasOverride: false,
17
+ };
18
+ }
19
+ return {
20
+ dayNumber: base.dayNumber,
21
+ title: override.title ?? base.title,
22
+ description: override.description ?? base.description,
23
+ portFacilityId: override.portFacilityId ?? base.portFacilityId,
24
+ portCanonicalPlaceId: override.portCanonicalPlaceId ?? base.portCanonicalPlaceId,
25
+ arrivalTime: override.arrivalTime ?? base.arrivalTime,
26
+ departureTime: override.departureTime ?? base.departureTime,
27
+ isOvernight: override.isOvernight ?? base.isOvernight,
28
+ isSeaDay: override.isSeaDay ?? base.isSeaDay,
29
+ isExpeditionLanding: override.isExpeditionLanding ?? base.isExpeditionLanding,
30
+ isSkipped: override.isSkipped,
31
+ meals: override.meals ?? base.meals ?? {},
32
+ hasOverride: true,
33
+ };
34
+ }
@@ -0,0 +1,46 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { CruisePrice } from "./schema-pricing.js";
3
+ import type { InsertPrice, InsertPriceComponent, PriceListQuery, UpdatePrice } from "./validation-pricing.js";
4
+ export declare const cruisePriceRowsService: {
5
+ listPrices(db: PostgresJsDatabase, query: PriceListQuery): Promise<{
6
+ data: {
7
+ id: string;
8
+ sailingId: string;
9
+ cabinCategoryId: string;
10
+ occupancy: number;
11
+ fareCode: string | null;
12
+ fareCodeName: string | null;
13
+ fareVariant: "cruise_only" | "air_inclusive";
14
+ currency: string;
15
+ pricePerPerson: string;
16
+ originalPricePerPerson: string | null;
17
+ secondGuestPricePerPerson: string | null;
18
+ singlePricePerPerson: string | null;
19
+ singleSupplementPercent: string | null;
20
+ availability: "on_request" | "wait_list" | "sold_out" | "available" | "limited";
21
+ availabilityCount: number | null;
22
+ priceCatalogId: string | null;
23
+ priceScheduleId: string | null;
24
+ bookingDeadline: string | null;
25
+ earlyBookingDeadline: string | null;
26
+ earlyBookingBonusDescription: string | null;
27
+ requiresRequest: boolean;
28
+ notes: string | null;
29
+ externalRefs: Record<string, string> | null;
30
+ lastSyncedAt: Date | null;
31
+ createdAt: Date;
32
+ updatedAt: Date;
33
+ }[];
34
+ total: number;
35
+ limit: number;
36
+ offset: number;
37
+ }>;
38
+ createPrice(db: PostgresJsDatabase, data: InsertPrice): Promise<CruisePrice>;
39
+ updatePrice(db: PostgresJsDatabase, id: string, data: UpdatePrice): Promise<CruisePrice | null>;
40
+ replaceSailingPricing(db: PostgresJsDatabase, sailingId: string, payload: {
41
+ prices: Array<InsertPrice & {
42
+ components?: Array<Omit<InsertPriceComponent, "priceId">>;
43
+ }>;
44
+ }): Promise<CruisePrice[]>;
45
+ };
46
+ //# sourceMappingURL=service-prices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-prices.d.ts","sourceRoot":"","sources":["../src/service-prices.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,KAAK,EAAE,WAAW,EAA2C,MAAM,qBAAqB,CAAA;AAG/F,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,WAAW,EACZ,MAAM,yBAAyB,CAAA;AAEhC,eAAO,MAAM,sBAAsB;mBACZ,kBAAkB,SAAS,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA0BxC,kBAAkB,QAAQ,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;oBAU5E,kBAAkB,MAClB,MAAM,QACJ,WAAW,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;8BAUxB,kBAAkB,aACX,MAAM,WACR;QACP,MAAM,EAAE,KAAK,CAAC,WAAW,GAAG;YAAE,UAAU,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC,CAAA;SAAE,CAAC,CAAA;KAC3F,GACA,OAAO,CAAC,WAAW,EAAE,CAAC;CAyC1B,CAAA"}
@@ -0,0 +1,89 @@
1
+ import { and, asc, count, eq } from "drizzle-orm";
2
+ import { cruiseSailings } from "./schema-core.js";
3
+ import { cruisePriceComponents, cruisePrices } from "./schema-pricing.js";
4
+ import { paginate, reprojectIfPossible, setUpdated } from "./service-shared.js";
5
+ export const cruisePriceRowsService = {
6
+ async listPrices(db, query) {
7
+ const conditions = [];
8
+ if (query.sailingId)
9
+ conditions.push(eq(cruisePrices.sailingId, query.sailingId));
10
+ if (query.cabinCategoryId)
11
+ conditions.push(eq(cruisePrices.cabinCategoryId, query.cabinCategoryId));
12
+ if (query.occupancy)
13
+ conditions.push(eq(cruisePrices.occupancy, query.occupancy));
14
+ if (query.fareCode)
15
+ conditions.push(eq(cruisePrices.fareCode, query.fareCode));
16
+ if (query.fareVariant)
17
+ conditions.push(eq(cruisePrices.fareVariant, query.fareVariant));
18
+ if (query.availability)
19
+ conditions.push(eq(cruisePrices.availability, query.availability));
20
+ if (query.priceCatalogId)
21
+ conditions.push(eq(cruisePrices.priceCatalogId, query.priceCatalogId));
22
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
23
+ const { limit, offset } = paginate(query);
24
+ const [rows, totalRows] = await Promise.all([
25
+ db
26
+ .select()
27
+ .from(cruisePrices)
28
+ .where(where)
29
+ .orderBy(asc(cruisePrices.cabinCategoryId), asc(cruisePrices.occupancy))
30
+ .limit(limit)
31
+ .offset(offset),
32
+ db.select({ value: count() }).from(cruisePrices).where(where),
33
+ ]);
34
+ return { data: rows, total: totalRows[0]?.value ?? 0, limit, offset };
35
+ },
36
+ async createPrice(db, data) {
37
+ const [row] = await db
38
+ .insert(cruisePrices)
39
+ .values({ ...data, lastSyncedAt: new Date() })
40
+ .returning();
41
+ if (!row)
42
+ throw new Error("Failed to create price");
43
+ return row;
44
+ },
45
+ async updatePrice(db, id, data) {
46
+ const [row] = await db
47
+ .update(cruisePrices)
48
+ .set({ ...data, ...setUpdated })
49
+ .where(eq(cruisePrices.id, id))
50
+ .returning();
51
+ return row ?? null;
52
+ },
53
+ async replaceSailingPricing(db, sailingId, payload) {
54
+ const result = await db.transaction(async (tx) => {
55
+ // Cascade-delete existing prices for this sailing — components go with them via FK.
56
+ await tx.delete(cruisePrices).where(eq(cruisePrices.sailingId, sailingId));
57
+ if (payload.prices.length === 0)
58
+ return [];
59
+ const insertedPrices = [];
60
+ for (const p of payload.prices) {
61
+ const { components, ...priceFields } = p;
62
+ const [priceRow] = await tx
63
+ .insert(cruisePrices)
64
+ .values({ ...priceFields, sailingId, lastSyncedAt: new Date() })
65
+ .returning();
66
+ if (!priceRow)
67
+ throw new Error("Failed to insert price");
68
+ insertedPrices.push(priceRow);
69
+ if (components && components.length > 0) {
70
+ await tx
71
+ .insert(cruisePriceComponents)
72
+ .values(components.map((c) => ({ ...c, priceId: priceRow.id })));
73
+ }
74
+ }
75
+ return insertedPrices;
76
+ });
77
+ // Bulk pricing changes likely move the lowest-price aggregate. Re-project
78
+ // the parent cruise so the storefront index stays current.
79
+ const [sailing] = await db
80
+ .select({ cruiseId: cruiseSailings.cruiseId })
81
+ .from(cruiseSailings)
82
+ .where(eq(cruiseSailings.id, sailingId))
83
+ .limit(1);
84
+ if (sailing)
85
+ await reprojectIfPossible(db, sailing.cruiseId);
86
+ return result;
87
+ },
88
+ // ---------- enrichment programs (expedition-focused) ----------
89
+ };
@@ -0,0 +1,97 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import { type CruisePrice, type CruisePriceComponent } from "./schema-pricing.js";
3
+ export type QuotePriceComponentKind = CruisePriceComponent["kind"] | "single_supplement" | "other";
4
+ export type QuoteBookingTerms = {
5
+ cancellationPolicy?: {
6
+ summary?: string | null;
7
+ rules?: Array<{
8
+ from?: string | null;
9
+ until?: string | null;
10
+ penaltyAmount?: string | null;
11
+ penaltyCurrency?: string | null;
12
+ penaltyPercent?: string | null;
13
+ description?: string | null;
14
+ }>;
15
+ [key: string]: unknown;
16
+ } | null;
17
+ paymentTerms?: {
18
+ summary?: string | null;
19
+ depositAmount?: string | null;
20
+ depositCurrency?: string | null;
21
+ depositPercent?: string | null;
22
+ dueDate?: string | null;
23
+ schedule?: Array<Record<string, unknown>>;
24
+ [key: string]: unknown;
25
+ } | null;
26
+ supplierTermsUrl?: string | null;
27
+ notes?: string | null;
28
+ [key: string]: unknown;
29
+ };
30
+ export type QuoteComponent = {
31
+ kind: QuotePriceComponentKind;
32
+ label: string | null;
33
+ amount: string;
34
+ currency: string;
35
+ direction: CruisePriceComponent["direction"];
36
+ perPerson: boolean;
37
+ };
38
+ export type Quote = {
39
+ fareCode: string | null;
40
+ fareCodeName: string | null;
41
+ fareVariant: CruisePrice["fareVariant"];
42
+ currency: string;
43
+ occupancy: number;
44
+ guestCount: number;
45
+ basePerPerson: string;
46
+ originalPricePerPerson: string | null;
47
+ singlePricePerPerson: string | null;
48
+ earlyBookingDeadline: string | null;
49
+ earlyBookingBonusDescription: string | null;
50
+ components: QuoteComponent[];
51
+ totalPerPerson: string;
52
+ totalForCabin: string;
53
+ bookingTerms?: QuoteBookingTerms | null;
54
+ };
55
+ export type ComposeQuoteInput = {
56
+ price: Pick<CruisePrice, "pricePerPerson" | "originalPricePerPerson" | "secondGuestPricePerPerson" | "singlePricePerPerson" | "singleSupplementPercent" | "currency" | "fareCode" | "fareCodeName" | "fareVariant" | "earlyBookingDeadline" | "earlyBookingBonusDescription">;
57
+ components: Array<Pick<CruisePriceComponent, "label" | "amount" | "currency" | "direction" | "perPerson"> & {
58
+ kind: QuotePriceComponentKind;
59
+ }>;
60
+ occupancy: number;
61
+ guestCount: number;
62
+ bookingTerms?: QuoteBookingTerms | null;
63
+ };
64
+ export declare function composeQuote(input: ComposeQuoteInput): Quote;
65
+ export type LowestPriceResult = {
66
+ pricePerPerson: string;
67
+ currency: string;
68
+ cabinCategoryId: string;
69
+ fareCode: string | null;
70
+ fareVariant: CruisePrice["fareVariant"];
71
+ } | null;
72
+ export type GridCell = {
73
+ cabinCategoryId: string;
74
+ occupancy: number;
75
+ fareCode: string | null;
76
+ fareVariant: CruisePrice["fareVariant"];
77
+ pricePerPerson: string;
78
+ originalPricePerPerson: string | null;
79
+ currency: string;
80
+ availability: CruisePrice["availability"];
81
+ };
82
+ export declare const pricingService: {
83
+ lowestAvailablePrice(db: PostgresJsDatabase, args: {
84
+ sailingId: string;
85
+ occupancy: number;
86
+ }): Promise<LowestPriceResult>;
87
+ gridForSailing(db: PostgresJsDatabase, sailingId: string): Promise<GridCell[]>;
88
+ assembleQuote(db: PostgresJsDatabase, args: {
89
+ sailingId: string;
90
+ cabinCategoryId: string;
91
+ occupancy: number;
92
+ guestCount: number;
93
+ fareCode?: string | null;
94
+ fareVariant?: CruisePrice["fareVariant"] | null;
95
+ }): Promise<Quote>;
96
+ };
97
+ //# sourceMappingURL=service-pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-pricing.d.ts","sourceRoot":"","sources":["../src/service-pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,198 @@
1
+ import { and, asc, eq, sql } from "drizzle-orm";
2
+ import { cruiseCabinCategories } from "./schema-cabins.js";
3
+ import { cruisePriceComponents, cruisePrices, } from "./schema-pricing.js";
4
+ // ---------- money helpers ----------
5
+ // All math is performed in integer cents to avoid float drift.
6
+ const CENTS_PER_UNIT = 100n;
7
+ function decimalStringToCents(s) {
8
+ const trimmed = s.trim();
9
+ if (!/^-?\d+(\.\d{1,2})?$/.test(trimmed)) {
10
+ throw new Error(`Invalid money string: ${s}`);
11
+ }
12
+ const negative = trimmed.startsWith("-");
13
+ const abs = negative ? trimmed.slice(1) : trimmed;
14
+ const parts = abs.split(".");
15
+ const whole = parts[0] ?? "0";
16
+ const frac = parts[1] ?? "";
17
+ const fracPadded = `${frac}00`.slice(0, 2);
18
+ const cents = BigInt(whole) * CENTS_PER_UNIT + BigInt(fracPadded);
19
+ return negative ? -cents : cents;
20
+ }
21
+ function centsToDecimalString(c) {
22
+ const negative = c < 0n;
23
+ const abs = negative ? -c : c;
24
+ const whole = abs / CENTS_PER_UNIT;
25
+ const frac = abs % CENTS_PER_UNIT;
26
+ const fracStr = frac.toString().padStart(2, "0");
27
+ return `${negative ? "-" : ""}${whole.toString()}.${fracStr}`;
28
+ }
29
+ function percentOf(cents, percentString) {
30
+ // percent stored with up to 2 decimal places (e.g. "75.50")
31
+ const trimmed = percentString.trim();
32
+ if (!/^-?\d+(\.\d{1,2})?$/.test(trimmed)) {
33
+ throw new Error(`Invalid percent string: ${percentString}`);
34
+ }
35
+ const parts = trimmed.split(".");
36
+ const whole = parts[0] ?? "0";
37
+ const frac = parts[1] ?? "";
38
+ const fracPadded = `${frac}00`.slice(0, 2);
39
+ // percent * 100 → integer basis points; multiply cents, divide by 10000
40
+ const basisPoints = BigInt(whole) * 100n + BigInt(fracPadded);
41
+ return (cents * basisPoints) / 10000n;
42
+ }
43
+ export function composeQuote(input) {
44
+ const { price, components, occupancy, guestCount } = input;
45
+ if (occupancy < 1)
46
+ throw new Error("occupancy must be >= 1");
47
+ if (guestCount < 1)
48
+ throw new Error("guestCount must be >= 1");
49
+ if (guestCount > occupancy)
50
+ throw new Error(`guestCount (${guestCount}) cannot exceed occupancy (${occupancy})`);
51
+ const basePerPersonCents = decimalStringToCents(price.pricePerPerson);
52
+ // Resolve effective base per cabin, accounting for second-guest reduction and single supplement.
53
+ let baseCabinCents;
54
+ if (occupancy === 1 && price.singlePricePerPerson && guestCount === 1) {
55
+ baseCabinCents = decimalStringToCents(price.singlePricePerPerson);
56
+ }
57
+ else if (occupancy === 1 && price.singleSupplementPercent && guestCount === 1) {
58
+ const supplementCents = percentOf(basePerPersonCents, price.singleSupplementPercent);
59
+ baseCabinCents = basePerPersonCents + supplementCents;
60
+ }
61
+ else if (occupancy === 2 && price.secondGuestPricePerPerson && guestCount === 2) {
62
+ const secondCents = decimalStringToCents(price.secondGuestPricePerPerson);
63
+ baseCabinCents = basePerPersonCents + secondCents;
64
+ }
65
+ else {
66
+ baseCabinCents = basePerPersonCents * BigInt(guestCount);
67
+ }
68
+ // Apply price components.
69
+ let cabinAdjustmentCents = 0n;
70
+ const renderedComponents = [];
71
+ for (const c of components) {
72
+ if (c.currency !== price.currency) {
73
+ throw new Error(`Component currency ${c.currency} does not match price currency ${price.currency}`);
74
+ }
75
+ const amountCents = decimalStringToCents(c.amount);
76
+ const componentTotalCents = c.perPerson ? amountCents * BigInt(guestCount) : amountCents;
77
+ if (c.direction === "addition") {
78
+ cabinAdjustmentCents += componentTotalCents;
79
+ }
80
+ else if (c.direction === "credit") {
81
+ cabinAdjustmentCents -= componentTotalCents;
82
+ }
83
+ // 'inclusion' is display-only — does not affect totals.
84
+ renderedComponents.push({
85
+ kind: c.kind,
86
+ label: c.label ?? null,
87
+ amount: c.amount,
88
+ currency: c.currency,
89
+ direction: c.direction,
90
+ perPerson: c.perPerson,
91
+ });
92
+ }
93
+ const totalForCabinCents = baseCabinCents + cabinAdjustmentCents;
94
+ const totalPerPersonCents = totalForCabinCents / BigInt(guestCount);
95
+ return {
96
+ fareCode: price.fareCode ?? null,
97
+ fareCodeName: price.fareCodeName ?? null,
98
+ fareVariant: price.fareVariant,
99
+ currency: price.currency,
100
+ occupancy,
101
+ guestCount,
102
+ basePerPerson: centsToDecimalString(basePerPersonCents),
103
+ originalPricePerPerson: price.originalPricePerPerson ?? null,
104
+ singlePricePerPerson: price.singlePricePerPerson ?? null,
105
+ earlyBookingDeadline: price.earlyBookingDeadline ?? null,
106
+ earlyBookingBonusDescription: price.earlyBookingBonusDescription ?? null,
107
+ components: renderedComponents,
108
+ totalPerPerson: centsToDecimalString(totalPerPersonCents),
109
+ totalForCabin: centsToDecimalString(totalForCabinCents),
110
+ bookingTerms: input.bookingTerms ?? null,
111
+ };
112
+ }
113
+ export const pricingService = {
114
+ async lowestAvailablePrice(db, args) {
115
+ const [row] = await db
116
+ .select({
117
+ pricePerPerson: cruisePrices.pricePerPerson,
118
+ currency: cruisePrices.currency,
119
+ cabinCategoryId: cruisePrices.cabinCategoryId,
120
+ fareCode: cruisePrices.fareCode,
121
+ fareVariant: cruisePrices.fareVariant,
122
+ })
123
+ .from(cruisePrices)
124
+ .where(and(eq(cruisePrices.sailingId, args.sailingId), eq(cruisePrices.occupancy, args.occupancy),
125
+ // agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
126
+ sql `${cruisePrices.availability} <> 'sold_out'`))
127
+ // agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
128
+ .orderBy(asc(sql `${cruisePrices.pricePerPerson}::numeric`))
129
+ .limit(1);
130
+ return row ?? null;
131
+ },
132
+ async gridForSailing(db, sailingId) {
133
+ const rows = await db
134
+ .select({
135
+ cabinCategoryId: cruisePrices.cabinCategoryId,
136
+ occupancy: cruisePrices.occupancy,
137
+ fareCode: cruisePrices.fareCode,
138
+ fareVariant: cruisePrices.fareVariant,
139
+ pricePerPerson: cruisePrices.pricePerPerson,
140
+ originalPricePerPerson: cruisePrices.originalPricePerPerson,
141
+ currency: cruisePrices.currency,
142
+ availability: cruisePrices.availability,
143
+ })
144
+ .from(cruisePrices)
145
+ .where(eq(cruisePrices.sailingId, sailingId))
146
+ .orderBy(asc(cruisePrices.cabinCategoryId), asc(cruisePrices.occupancy),
147
+ // agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
148
+ asc(sql `${cruisePrices.pricePerPerson}::numeric`));
149
+ return rows;
150
+ },
151
+ async assembleQuote(db, args) {
152
+ // Validate cabin category exists and respects occupancy bounds.
153
+ const [category] = await db
154
+ .select({
155
+ id: cruiseCabinCategories.id,
156
+ minOccupancy: cruiseCabinCategories.minOccupancy,
157
+ maxOccupancy: cruiseCabinCategories.maxOccupancy,
158
+ })
159
+ .from(cruiseCabinCategories)
160
+ .where(eq(cruiseCabinCategories.id, args.cabinCategoryId))
161
+ .limit(1);
162
+ if (!category)
163
+ throw new Error(`Cabin category ${args.cabinCategoryId} not found`);
164
+ if (args.guestCount < category.minOccupancy || args.guestCount > category.maxOccupancy) {
165
+ throw new Error(`guestCount ${args.guestCount} outside category bounds [${category.minOccupancy}, ${category.maxOccupancy}]`);
166
+ }
167
+ // Pick the cheapest matching price row.
168
+ const conditions = [
169
+ eq(cruisePrices.sailingId, args.sailingId),
170
+ eq(cruisePrices.cabinCategoryId, args.cabinCategoryId),
171
+ eq(cruisePrices.occupancy, args.occupancy),
172
+ ];
173
+ if (args.fareCode)
174
+ conditions.push(eq(cruisePrices.fareCode, args.fareCode));
175
+ if (args.fareVariant)
176
+ conditions.push(eq(cruisePrices.fareVariant, args.fareVariant));
177
+ const [price] = await db
178
+ .select()
179
+ .from(cruisePrices)
180
+ .where(and(...conditions))
181
+ // agent-quality: raw-sql reviewed -- owner: cruises; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
182
+ .orderBy(asc(sql `${cruisePrices.pricePerPerson}::numeric`))
183
+ .limit(1);
184
+ if (!price) {
185
+ throw new Error(`No price found for sailing=${args.sailingId} category=${args.cabinCategoryId} occupancy=${args.occupancy}${args.fareCode ? ` fareCode=${args.fareCode}` : ""}${args.fareVariant ? ` fareVariant=${args.fareVariant}` : ""}`);
186
+ }
187
+ const components = await db
188
+ .select()
189
+ .from(cruisePriceComponents)
190
+ .where(eq(cruisePriceComponents.priceId, price.id));
191
+ return composeQuote({
192
+ price,
193
+ components,
194
+ occupancy: args.occupancy,
195
+ guestCount: args.guestCount,
196
+ });
197
+ },
198
+ };
@@ -0,0 +1,48 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { CruiseSailing } from "./schema-core.js";
3
+ import type { CruiseDay, CruiseSailingDay } from "./schema-itinerary.js";
4
+ import type { CruisePrice, CruisePriceComponent } from "./schema-pricing.js";
5
+ import { type EffectiveItineraryDay } from "./service-itinerary.js";
6
+ import type { InsertSailing, SailingListQuery, UpdateSailing } from "./validation-core.js";
7
+ import type { ReplaceCruiseDays, ReplaceSailingDays } from "./validation-itinerary.js";
8
+ export declare const cruiseSailingsService: {
9
+ listSailings(db: PostgresJsDatabase, query: SailingListQuery): Promise<{
10
+ data: {
11
+ id: string;
12
+ cruiseId: string;
13
+ shipId: string;
14
+ departureDate: string;
15
+ returnDate: string;
16
+ embarkPortFacilityId: string | null;
17
+ embarkPortCanonicalPlaceId: string | null;
18
+ disembarkPortFacilityId: string | null;
19
+ disembarkPortCanonicalPlaceId: string | null;
20
+ direction: "upstream" | "downstream" | "round_trip" | "one_way" | null;
21
+ availabilityNote: string | null;
22
+ isCharter: boolean;
23
+ salesStatus: "open" | "on_request" | "wait_list" | "sold_out" | "closed";
24
+ externalRefs: Record<string, string> | null;
25
+ customerPaymentPolicy: unknown;
26
+ lastSyncedAt: Date | null;
27
+ createdAt: Date;
28
+ updatedAt: Date;
29
+ }[];
30
+ total: number;
31
+ limit: number;
32
+ offset: number;
33
+ }>;
34
+ getSailingById(db: PostgresJsDatabase, id: string, options?: {
35
+ withPricing?: boolean;
36
+ withItinerary?: boolean;
37
+ }): Promise<(CruiseSailing & {
38
+ prices?: CruisePrice[];
39
+ priceComponents?: CruisePriceComponent[];
40
+ effectiveDays?: EffectiveItineraryDay[];
41
+ }) | null>;
42
+ upsertSailing(db: PostgresJsDatabase, data: InsertSailing): Promise<CruiseSailing>;
43
+ updateSailing(db: PostgresJsDatabase, id: string, data: UpdateSailing): Promise<CruiseSailing | null>;
44
+ getEffectiveItinerary(db: PostgresJsDatabase, sailingId: string): Promise<EffectiveItineraryDay[]>;
45
+ replaceCruiseDays(db: PostgresJsDatabase, payload: ReplaceCruiseDays): Promise<CruiseDay[]>;
46
+ replaceSailingDays(db: PostgresJsDatabase, payload: ReplaceSailingDays): Promise<CruiseSailingDay[]>;
47
+ };
48
+ //# sourceMappingURL=service-sailings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-sailings.d.ts","sourceRoot":"","sources":["../src/service-sailings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,kBAAkB,CAAA;AAEvE,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAE5E,OAAO,EAAE,KAAK,qBAAqB,EAAY,MAAM,wBAAwB,CAAA;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAC1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAEtF,eAAO,MAAM,qBAAqB;qBACT,kBAAkB,SAAS,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;uBAwB5D,kBAAkB,MAClB,MAAM,YACD;QAAE,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAC1D,OAAO,CACN,CAAC,aAAa,GAAG;QACf,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;QACtB,eAAe,CAAC,EAAE,oBAAoB,EAAE,CAAA;QACxC,aAAa,CAAC,EAAE,qBAAqB,EAAE,CAAA;KACxC,CAAC,GACF,IAAI,CACP;sBAkCuB,kBAAkB,QAAQ,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;sBAkClF,kBAAkB,MAClB,MAAM,QACJ,aAAa,GAClB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;8BAa1B,kBAAkB,aACX,MAAM,GAChB,OAAO,CAAC,qBAAqB,EAAE,CAAC;0BA0B7B,kBAAkB,WACb,iBAAiB,GACzB,OAAO,CAAC,SAAS,EAAE,CAAC;2BAajB,kBAAkB,WACb,kBAAkB,GAC1B,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAa/B,CAAA"}