@voyant-travel/trips 0.110.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 (63) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +38 -0
  3. package/dist/catalog-component-adapter.d.ts +16 -0
  4. package/dist/catalog-component-adapter.d.ts.map +1 -0
  5. package/dist/catalog-component-adapter.js +34 -0
  6. package/dist/cruise-extension.d.ts +48 -0
  7. package/dist/cruise-extension.d.ts.map +1 -0
  8. package/dist/cruise-extension.js +66 -0
  9. package/dist/index.d.ts +24 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +31 -0
  12. package/dist/mcp-contract.d.ts +79 -0
  13. package/dist/mcp-contract.d.ts.map +1 -0
  14. package/dist/mcp-contract.js +21 -0
  15. package/dist/mcp-registry.d.ts +48 -0
  16. package/dist/mcp-registry.d.ts.map +1 -0
  17. package/dist/mcp-registry.js +88 -0
  18. package/dist/mcp-tools.d.ts +157 -0
  19. package/dist/mcp-tools.d.ts.map +1 -0
  20. package/dist/mcp-tools.js +109 -0
  21. package/dist/routes.d.ts +2068 -0
  22. package/dist/routes.d.ts.map +1 -0
  23. package/dist/routes.js +280 -0
  24. package/dist/schema.d.ts +1897 -0
  25. package/dist/schema.d.ts.map +1 -0
  26. package/dist/schema.js +255 -0
  27. package/dist/service-cancellation.d.ts +6 -0
  28. package/dist/service-cancellation.d.ts.map +1 -0
  29. package/dist/service-cancellation.js +251 -0
  30. package/dist/service-checkout.d.ts +6 -0
  31. package/dist/service-checkout.d.ts.map +1 -0
  32. package/dist/service-checkout.js +328 -0
  33. package/dist/service-helpers.d.ts +17 -0
  34. package/dist/service-helpers.d.ts.map +1 -0
  35. package/dist/service-helpers.js +161 -0
  36. package/dist/service-internals.d.ts +11 -0
  37. package/dist/service-internals.d.ts.map +1 -0
  38. package/dist/service-internals.js +72 -0
  39. package/dist/service-pricing.d.ts +8 -0
  40. package/dist/service-pricing.d.ts.map +1 -0
  41. package/dist/service-pricing.js +142 -0
  42. package/dist/service-reservation.d.ts +5 -0
  43. package/dist/service-reservation.d.ts.map +1 -0
  44. package/dist/service-reservation.js +493 -0
  45. package/dist/service-snapshots.d.ts +8 -0
  46. package/dist/service-snapshots.d.ts.map +1 -0
  47. package/dist/service-snapshots.js +115 -0
  48. package/dist/service-trips.d.ts +14 -0
  49. package/dist/service-trips.d.ts.map +1 -0
  50. package/dist/service-trips.js +378 -0
  51. package/dist/service-types.d.ts +285 -0
  52. package/dist/service-types.d.ts.map +1 -0
  53. package/dist/service-types.js +6 -0
  54. package/dist/service.d.ts +38 -0
  55. package/dist/service.d.ts.map +1 -0
  56. package/dist/service.js +40 -0
  57. package/dist/traveler-party-validation.d.ts +3 -0
  58. package/dist/traveler-party-validation.d.ts.map +1 -0
  59. package/dist/traveler-party-validation.js +68 -0
  60. package/dist/validation.d.ts +449 -0
  61. package/dist/validation.d.ts.map +1 -0
  62. package/dist/validation.js +261 -0
  63. package/package.json +83 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/dist/schema.js ADDED
@@ -0,0 +1,255 @@
1
+ import { typeId, typeIdRef } from "@voyant-travel/db/lib/typeid-column";
2
+ import { relations } from "drizzle-orm";
3
+ import { index, integer, jsonb, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core";
4
+ export const tripEnvelopeStatusEnum = pgEnum("trip_envelope_status", [
5
+ "draft",
6
+ "priced",
7
+ "reserve_in_progress",
8
+ "reserved",
9
+ "checkout_started",
10
+ "booked",
11
+ "failed",
12
+ "cancelled",
13
+ ]);
14
+ export const tripComponentKindEnum = pgEnum("trip_component_kind", [
15
+ "catalog_booking",
16
+ "manual_placeholder",
17
+ "flight_placeholder",
18
+ "flight_order",
19
+ "external_order",
20
+ ]);
21
+ export const tripComponentStatusEnum = pgEnum("trip_component_status", [
22
+ "draft",
23
+ "priced",
24
+ "unavailable",
25
+ "held",
26
+ "booked",
27
+ "checkout_started",
28
+ "failed",
29
+ "cancelled",
30
+ "removed",
31
+ ]);
32
+ export const tripComponentEventTypeEnum = pgEnum("trip_component_event_type", [
33
+ "created",
34
+ "updated",
35
+ "priced",
36
+ "hold_placed",
37
+ "booked",
38
+ "checkout_started",
39
+ "failed",
40
+ "cancelled",
41
+ "removed",
42
+ "staff_remediation_required",
43
+ ]);
44
+ export const tripReservationPlanStatusEnum = pgEnum("trip_reservation_plan_status", [
45
+ "pending",
46
+ "submitted",
47
+ "reserved",
48
+ "failed",
49
+ "cancelled",
50
+ ]);
51
+ export const tripEnvelopes = pgTable("trip_envelopes", {
52
+ id: typeId("trip_envelopes"),
53
+ status: tripEnvelopeStatusEnum("status").notNull().default("draft"),
54
+ title: text("title"),
55
+ description: text("description"),
56
+ travelerParty: jsonb("traveler_party").$type().notNull().default({}),
57
+ constraints: jsonb("constraints").$type().notNull().default({}),
58
+ aggregateCurrency: text("aggregate_currency"),
59
+ aggregateSubtotalAmountCents: integer("aggregate_subtotal_amount_cents"),
60
+ aggregateTaxAmountCents: integer("aggregate_tax_amount_cents"),
61
+ aggregateTotalAmountCents: integer("aggregate_total_amount_cents"),
62
+ aggregatePricingSnapshot: jsonb("aggregate_pricing_snapshot").$type(),
63
+ currentPriceExpiresAt: timestamp("current_price_expires_at", { withTimezone: true }),
64
+ bookingGroupId: text("booking_group_id"),
65
+ orderId: text("order_id"),
66
+ paymentSessionId: text("payment_session_id"),
67
+ reserveIdempotencyKey: text("reserve_idempotency_key"),
68
+ reserveStartedAt: timestamp("reserve_started_at", { withTimezone: true }),
69
+ reservedAt: timestamp("reserved_at", { withTimezone: true }),
70
+ checkoutIdempotencyKey: text("checkout_idempotency_key"),
71
+ checkoutStartedAt: timestamp("checkout_started_at", { withTimezone: true }),
72
+ createdBy: text("created_by"),
73
+ updatedBy: text("updated_by"),
74
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
75
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
76
+ }, (table) => [
77
+ index("idx_trip_envelopes_status_updated").on(table.status, table.updatedAt),
78
+ index("idx_trip_envelopes_created_by_updated").on(table.createdBy, table.updatedAt),
79
+ index("idx_trip_envelopes_booking_group").on(table.bookingGroupId),
80
+ index("idx_trip_envelopes_payment_session").on(table.paymentSessionId),
81
+ index("idx_trip_envelopes_reserve_idempotency").on(table.reserveIdempotencyKey),
82
+ index("idx_trip_envelopes_checkout_idempotency").on(table.checkoutIdempotencyKey),
83
+ ]);
84
+ export const tripComponents = pgTable("trip_components", {
85
+ id: typeId("trip_components"),
86
+ envelopeId: typeIdRef("envelope_id")
87
+ .notNull()
88
+ .references(() => tripEnvelopes.id, { onDelete: "cascade" }),
89
+ sequence: integer("sequence").notNull().default(0),
90
+ kind: tripComponentKindEnum("kind").notNull(),
91
+ status: tripComponentStatusEnum("status").notNull().default("draft"),
92
+ title: text("title"),
93
+ description: text("description"),
94
+ entityModule: text("entity_module"),
95
+ entityId: text("entity_id"),
96
+ sourceKind: text("source_kind"),
97
+ sourceConnectionId: text("source_connection_id"),
98
+ sourceRef: text("source_ref"),
99
+ bookingDraftId: text("booking_draft_id"),
100
+ catalogQuoteId: text("catalog_quote_id"),
101
+ bookingId: text("booking_id"),
102
+ bookingGroupId: text("booking_group_id"),
103
+ orderId: text("order_id"),
104
+ paymentSessionId: text("payment_session_id"),
105
+ providerRef: text("provider_ref"),
106
+ supplierRef: text("supplier_ref"),
107
+ componentCurrency: text("component_currency"),
108
+ componentSubtotalAmountCents: integer("component_subtotal_amount_cents"),
109
+ componentTaxAmountCents: integer("component_tax_amount_cents"),
110
+ componentTotalAmountCents: integer("component_total_amount_cents"),
111
+ pricingSnapshot: jsonb("pricing_snapshot").$type(),
112
+ taxLines: jsonb("tax_lines").$type().default([]),
113
+ cancellationSnapshot: jsonb("cancellation_snapshot").$type(),
114
+ holdToken: text("hold_token"),
115
+ holdExpiresAt: timestamp("hold_expires_at", { withTimezone: true }),
116
+ priceExpiresAt: timestamp("price_expires_at", { withTimezone: true }),
117
+ warningCodes: jsonb("warning_codes").$type().notNull().default([]),
118
+ metadata: jsonb("metadata").$type().notNull().default({}),
119
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
120
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
121
+ }, (table) => [
122
+ index("idx_trip_components_envelope_sequence").on(table.envelopeId, table.sequence),
123
+ index("idx_trip_components_envelope_status").on(table.envelopeId, table.status),
124
+ index("idx_trip_components_catalog_entity").on(table.entityModule, table.entityId),
125
+ index("idx_trip_components_booking_draft").on(table.bookingDraftId),
126
+ index("idx_trip_components_catalog_quote").on(table.catalogQuoteId),
127
+ index("idx_trip_components_booking").on(table.bookingId),
128
+ index("idx_trip_components_order").on(table.orderId),
129
+ index("idx_trip_components_payment_session").on(table.paymentSessionId),
130
+ ]);
131
+ export const tripComponentEvents = pgTable("trip_component_events", {
132
+ id: typeId("trip_component_events"),
133
+ envelopeId: typeIdRef("envelope_id")
134
+ .notNull()
135
+ .references(() => tripEnvelopes.id, { onDelete: "cascade" }),
136
+ componentId: typeIdRef("component_id").references(() => tripComponents.id, {
137
+ onDelete: "set null",
138
+ }),
139
+ eventType: tripComponentEventTypeEnum("event_type").notNull(),
140
+ fromStatus: tripComponentStatusEnum("from_status"),
141
+ toStatus: tripComponentStatusEnum("to_status"),
142
+ payload: jsonb("payload").$type().notNull().default({}),
143
+ actorId: text("actor_id"),
144
+ occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull().defaultNow(),
145
+ }, (table) => [
146
+ index("idx_trip_component_events_envelope_time").on(table.envelopeId, table.occurredAt),
147
+ index("idx_trip_component_events_component_time").on(table.componentId, table.occurredAt),
148
+ index("idx_trip_component_events_type_time").on(table.eventType, table.occurredAt),
149
+ ]);
150
+ export const tripSnapshots = pgTable("trip_snapshots", {
151
+ id: typeId("trip_snapshots"),
152
+ envelopeId: typeIdRef("envelope_id")
153
+ .notNull()
154
+ .references(() => tripEnvelopes.id, { onDelete: "restrict" }),
155
+ sourceEnvelopeUpdatedAt: timestamp("source_envelope_updated_at", {
156
+ withTimezone: true,
157
+ }).notNull(),
158
+ titleSnapshot: text("title_snapshot"),
159
+ descriptionSnapshot: text("description_snapshot"),
160
+ travelerPartySnapshot: jsonb("traveler_party_snapshot")
161
+ .$type()
162
+ .notNull()
163
+ .default({}),
164
+ constraintsSnapshot: jsonb("constraints_snapshot")
165
+ .$type()
166
+ .notNull()
167
+ .default({}),
168
+ currency: text("currency").notNull(),
169
+ subtotalAmountCents: integer("subtotal_amount_cents").notNull().default(0),
170
+ taxAmountCents: integer("tax_amount_cents").notNull().default(0),
171
+ totalAmountCents: integer("total_amount_cents").notNull().default(0),
172
+ componentCount: integer("component_count").notNull().default(0),
173
+ pricedComponentCount: integer("priced_component_count").notNull().default(0),
174
+ frozenEnvelope: jsonb("frozen_envelope").$type().notNull(),
175
+ frozenComponents: jsonb("frozen_components")
176
+ .$type()
177
+ .notNull()
178
+ .default([]),
179
+ proposal: jsonb("proposal").$type().notNull(),
180
+ createdBy: text("created_by"),
181
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
182
+ }, (table) => [
183
+ index("idx_trip_snapshots_envelope_created").on(table.envelopeId, table.createdAt),
184
+ index("idx_trip_snapshots_created").on(table.createdAt),
185
+ ]);
186
+ export const tripReservationPlans = pgTable("trip_reservation_plans", {
187
+ id: typeId("trip_reservation_plans"),
188
+ envelopeId: typeIdRef("envelope_id")
189
+ .notNull()
190
+ .references(() => tripEnvelopes.id, { onDelete: "restrict" }),
191
+ snapshotId: typeIdRef("snapshot_id").references(() => tripSnapshots.id, {
192
+ onDelete: "set null",
193
+ }),
194
+ status: tripReservationPlanStatusEnum("status").notNull().default("pending"),
195
+ idempotencyKey: text("idempotency_key"),
196
+ refreshScope: jsonb("refresh_scope").$type(),
197
+ componentCount: integer("component_count").notNull().default(0),
198
+ components: jsonb("components")
199
+ .$type()
200
+ .notNull()
201
+ .default([]),
202
+ failures: jsonb("failures").$type().default([]),
203
+ compensations: jsonb("compensations")
204
+ .$type()
205
+ .default([]),
206
+ warnings: jsonb("warnings").$type().notNull().default([]),
207
+ submittedAt: timestamp("submitted_at", { withTimezone: true }),
208
+ completedAt: timestamp("completed_at", { withTimezone: true }),
209
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
210
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
211
+ }, (table) => [
212
+ index("idx_trip_reservation_plans_envelope_created").on(table.envelopeId, table.createdAt),
213
+ index("idx_trip_reservation_plans_snapshot").on(table.snapshotId),
214
+ index("idx_trip_reservation_plans_status_updated").on(table.status, table.updatedAt),
215
+ index("idx_trip_reservation_plans_idempotency").on(table.idempotencyKey),
216
+ ]);
217
+ export const tripEnvelopeRelations = relations(tripEnvelopes, ({ many }) => ({
218
+ components: many(tripComponents),
219
+ events: many(tripComponentEvents),
220
+ snapshots: many(tripSnapshots),
221
+ reservationPlans: many(tripReservationPlans),
222
+ }));
223
+ export const tripComponentRelations = relations(tripComponents, ({ one, many }) => ({
224
+ envelope: one(tripEnvelopes, {
225
+ fields: [tripComponents.envelopeId],
226
+ references: [tripEnvelopes.id],
227
+ }),
228
+ events: many(tripComponentEvents),
229
+ }));
230
+ export const tripComponentEventRelations = relations(tripComponentEvents, ({ one }) => ({
231
+ envelope: one(tripEnvelopes, {
232
+ fields: [tripComponentEvents.envelopeId],
233
+ references: [tripEnvelopes.id],
234
+ }),
235
+ component: one(tripComponents, {
236
+ fields: [tripComponentEvents.componentId],
237
+ references: [tripComponents.id],
238
+ }),
239
+ }));
240
+ export const tripSnapshotRelations = relations(tripSnapshots, ({ one }) => ({
241
+ envelope: one(tripEnvelopes, {
242
+ fields: [tripSnapshots.envelopeId],
243
+ references: [tripEnvelopes.id],
244
+ }),
245
+ }));
246
+ export const tripReservationPlanRelations = relations(tripReservationPlans, ({ one }) => ({
247
+ envelope: one(tripEnvelopes, {
248
+ fields: [tripReservationPlans.envelopeId],
249
+ references: [tripEnvelopes.id],
250
+ }),
251
+ snapshot: one(tripSnapshots, {
252
+ fields: [tripReservationPlans.snapshotId],
253
+ references: [tripSnapshots.id],
254
+ }),
255
+ }));
@@ -0,0 +1,6 @@
1
+ import type { AnyDrizzleDb } from "@voyant-travel/db";
2
+ import type { CancelTripComponentsDeps, CancelTripComponentsResult, PreviewTripCancellationDeps, TripCancellationPreviewResult } from "./service-types.js";
3
+ import { type CancelTripComponentsInput, type PreviewTripCancellationInput } from "./validation.js";
4
+ export declare function previewCancellation(db: AnyDrizzleDb, input: PreviewTripCancellationInput, deps?: PreviewTripCancellationDeps): Promise<TripCancellationPreviewResult>;
5
+ export declare function cancelComponents(db: AnyDrizzleDb, input: CancelTripComponentsInput, deps?: CancelTripComponentsDeps): Promise<CancelTripComponentsResult>;
6
+ //# sourceMappingURL=service-cancellation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-cancellation.d.ts","sourceRoot":"","sources":["../src/service-cancellation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAWrD,OAAO,KAAK,EAEV,wBAAwB,EACxB,0BAA0B,EAE1B,2BAA2B,EAE3B,6BAA6B,EAC9B,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACL,KAAK,yBAAyB,EAE9B,KAAK,4BAA4B,EAClC,MAAM,iBAAiB,CAAA;AAExB,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,4BAA4B,EACnC,IAAI,GAAE,2BAAgC,GACrC,OAAO,CAAC,6BAA6B,CAAC,CAqBxC;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,yBAAyB,EAChC,IAAI,GAAE,wBAA6B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CAsFrC"}
@@ -0,0 +1,251 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { tripComponents, tripEnvelopes } from "./schema.js";
3
+ import { assertTripComponentCanBeUpdated, hasCommittedComponentReference, } from "./service-helpers.js";
4
+ import { createComponentEvent, markComponentForStaffRemediation } from "./service-internals.js";
5
+ import { getTrip } from "./service-trips.js";
6
+ import { TripsInvariantError } from "./service-types.js";
7
+ import { isTerminalTripComponentStatus, } from "./validation.js";
8
+ export async function previewCancellation(db, input, deps = {}) {
9
+ const trip = await getTrip(db, input.envelopeId);
10
+ if (!trip) {
11
+ throw new TripsInvariantError(`Trip envelope ${input.envelopeId} was not found`);
12
+ }
13
+ const requestedAt = input.requestedAt ? new Date(input.requestedAt) : new Date();
14
+ const selected = selectComponentsForCancellation(trip, input.componentIds);
15
+ const componentPreviews = [];
16
+ for (const component of selected) {
17
+ componentPreviews.push(await previewComponentCancellation(trip.envelope, component, input, requestedAt, deps));
18
+ }
19
+ return {
20
+ envelope: trip.envelope,
21
+ components: trip.components,
22
+ preview: buildCancellationPreviewAggregate(trip.envelope, selected, componentPreviews),
23
+ };
24
+ }
25
+ export async function cancelComponents(db, input, deps = {}) {
26
+ const preview = await previewCancellation(db, input, deps);
27
+ const requestedAt = input.requestedAt ? new Date(input.requestedAt) : new Date();
28
+ const componentsById = new Map(preview.components.map((component) => [component.id, component]));
29
+ const cancelled = [];
30
+ const remediation = [];
31
+ const skipped = [];
32
+ for (const item of preview.preview.components) {
33
+ const component = componentsById.get(item.componentId);
34
+ if (!component)
35
+ continue;
36
+ if (item.action === "no_op") {
37
+ skipped.push({ componentId: item.componentId, reason: item.reason ?? "no_action" });
38
+ continue;
39
+ }
40
+ if (item.action === "staff_remediation") {
41
+ const reason = item.reason ?? "staff_remediation_required";
42
+ const updated = await markComponentForStaffRemediation(db, component, reason);
43
+ componentsById.set(updated.id, updated);
44
+ remediation.push({ componentId: item.componentId, reason });
45
+ continue;
46
+ }
47
+ if (!deps.cancelComponent && !canCancelComponentLocally(component)) {
48
+ const reason = "cancel_adapter_not_configured";
49
+ const updated = await markComponentForStaffRemediation(db, component, reason);
50
+ componentsById.set(updated.id, updated);
51
+ remediation.push({ componentId: item.componentId, reason });
52
+ continue;
53
+ }
54
+ try {
55
+ const result = canCancelComponentLocally(component)
56
+ ? localCancellationResult(item)
57
+ : await deps.cancelComponent?.({
58
+ envelope: preview.envelope,
59
+ component,
60
+ preview: item,
61
+ reason: input.reason,
62
+ requestedAt,
63
+ request: input.request,
64
+ });
65
+ if (!result || result.status !== "cancelled") {
66
+ const reason = result?.reason ?? `cancel_${result?.status ?? "not_configured"}`;
67
+ const updated = await markComponentForStaffRemediation(db, component, reason);
68
+ componentsById.set(updated.id, updated);
69
+ remediation.push({ componentId: item.componentId, reason });
70
+ continue;
71
+ }
72
+ const updated = await markComponentCancelled(db, component, {
73
+ ...item,
74
+ refundAmountCents: result.refundAmountCents ?? item.refundAmountCents,
75
+ refundCurrency: result.refundCurrency ?? item.refundCurrency,
76
+ snapshot: result.snapshot ?? item.snapshot,
77
+ });
78
+ componentsById.set(updated.id, updated);
79
+ cancelled.push({ componentId: item.componentId, status: "cancelled" });
80
+ }
81
+ catch (error) {
82
+ const reason = error instanceof Error ? error.message : "cancel_failed";
83
+ const updated = await markComponentForStaffRemediation(db, component, reason);
84
+ componentsById.set(updated.id, updated);
85
+ remediation.push({ componentId: item.componentId, reason });
86
+ }
87
+ }
88
+ const refreshed = await getTrip(db, input.envelopeId);
89
+ const components = refreshed?.components ?? [...componentsById.values()];
90
+ const envelope = await maybeCancelEnvelope(db, preview.envelope, components);
91
+ const finalTrip = envelope.status === preview.envelope.status ? refreshed : await getTrip(db, input.envelopeId);
92
+ return {
93
+ envelope,
94
+ components: finalTrip?.components ?? components,
95
+ preview: {
96
+ ...preview.preview,
97
+ staffActionRequired: preview.preview.staffActionRequired || remediation.length > 0,
98
+ },
99
+ cancelled,
100
+ remediation,
101
+ skipped,
102
+ };
103
+ }
104
+ function selectComponentsForCancellation(trip, componentIds) {
105
+ if (!componentIds || componentIds.length === 0) {
106
+ return trip.components.filter((component) => !isTerminalTripComponentStatus(component.status));
107
+ }
108
+ const componentsById = new Map(trip.components.map((component) => [component.id, component]));
109
+ const selected = [];
110
+ for (const componentId of componentIds) {
111
+ const component = componentsById.get(componentId);
112
+ if (!component) {
113
+ throw new TripsInvariantError(`Trip component ${componentId} was not found on envelope ${trip.envelope.id}`);
114
+ }
115
+ selected.push(component);
116
+ }
117
+ return selected;
118
+ }
119
+ async function previewComponentCancellation(envelope, component, input, requestedAt, deps) {
120
+ if (isTerminalTripComponentStatus(component.status)) {
121
+ return componentCancellationPreview(component, {
122
+ action: "no_op",
123
+ staffActionRequired: false,
124
+ reason: `already_${component.status}`,
125
+ });
126
+ }
127
+ if (canCancelComponentLocally(component)) {
128
+ return componentCancellationPreview(component, {
129
+ action: "cancel",
130
+ staffActionRequired: false,
131
+ reason: "local_component_cancel",
132
+ });
133
+ }
134
+ if (deps.previewComponentCancellation) {
135
+ return deps.previewComponentCancellation({
136
+ envelope,
137
+ component,
138
+ reason: input.reason,
139
+ requestedAt,
140
+ request: input.request,
141
+ });
142
+ }
143
+ return componentCancellationPreview(component, {
144
+ action: "staff_remediation",
145
+ staffActionRequired: true,
146
+ reason: "cancel_preview_not_configured",
147
+ });
148
+ }
149
+ function canCancelComponentLocally(component) {
150
+ if (component.kind === "manual_placeholder" || component.kind === "flight_placeholder") {
151
+ return true;
152
+ }
153
+ return (component.kind === "catalog_booking" &&
154
+ !hasCommittedComponentReference(component) &&
155
+ (component.status === "draft" ||
156
+ component.status === "priced" ||
157
+ component.status === "unavailable" ||
158
+ component.status === "failed"));
159
+ }
160
+ function componentCancellationPreview(component, overrides) {
161
+ return {
162
+ componentId: component.id,
163
+ currentStatus: component.status,
164
+ refundAmountCents: 0,
165
+ refundCurrency: component.componentCurrency ?? undefined,
166
+ penaltyAmountCents: 0,
167
+ ...overrides,
168
+ };
169
+ }
170
+ function buildCancellationPreviewAggregate(envelope, selected, componentPreviews) {
171
+ const warnings = new Set();
172
+ let currency = envelope.aggregateCurrency ?? null;
173
+ let estimatedRefundAmountCents = 0;
174
+ let estimatedPenaltyAmountCents = 0;
175
+ for (const item of componentPreviews) {
176
+ if (item.refundCurrency) {
177
+ currency ??= item.refundCurrency;
178
+ if (currency !== item.refundCurrency) {
179
+ warnings.add(`refund_currency_mismatch:${item.refundCurrency}`);
180
+ }
181
+ }
182
+ if (!item.refundCurrency || !currency || item.refundCurrency === currency) {
183
+ estimatedRefundAmountCents += item.refundAmountCents ?? 0;
184
+ }
185
+ estimatedPenaltyAmountCents += item.penaltyAmountCents ?? 0;
186
+ if (item.reason && item.action !== "cancel")
187
+ warnings.add(item.reason);
188
+ }
189
+ return {
190
+ envelopeId: envelope.id,
191
+ selectedComponentIds: selected.map((component) => component.id),
192
+ currency,
193
+ estimatedRefundAmountCents,
194
+ estimatedPenaltyAmountCents,
195
+ staffActionRequired: componentPreviews.some((item) => item.staffActionRequired),
196
+ components: componentPreviews,
197
+ warnings: [...warnings],
198
+ };
199
+ }
200
+ function localCancellationResult(preview) {
201
+ return {
202
+ status: "cancelled",
203
+ refundAmountCents: preview.refundAmountCents,
204
+ refundCurrency: preview.refundCurrency,
205
+ snapshot: preview.snapshot,
206
+ };
207
+ }
208
+ async function markComponentCancelled(db, component, preview) {
209
+ assertTripComponentCanBeUpdated(component, { status: "cancelled" });
210
+ const cancellationSnapshot = {
211
+ action: preview.action,
212
+ refundAmountCents: preview.refundAmountCents ?? 0,
213
+ refundCurrency: preview.refundCurrency ?? component.componentCurrency ?? null,
214
+ penaltyAmountCents: preview.penaltyAmountCents ?? 0,
215
+ supplierCancellationDeadline: preview.supplierCancellationDeadline ?? null,
216
+ policySummary: preview.policySummary ?? null,
217
+ snapshot: preview.snapshot ?? null,
218
+ };
219
+ const [updated] = (await db
220
+ .update(tripComponents)
221
+ .set({
222
+ status: "cancelled",
223
+ cancellationSnapshot,
224
+ updatedAt: new Date(),
225
+ })
226
+ .where(eq(tripComponents.id, component.id))
227
+ .returning());
228
+ if (!updated) {
229
+ throw new Error(`markComponentCancelled: update returned no row for ${component.id}`);
230
+ }
231
+ await createComponentEvent(db, {
232
+ envelopeId: updated.envelopeId,
233
+ componentId: updated.id,
234
+ eventType: "cancelled",
235
+ fromStatus: component.status,
236
+ toStatus: updated.status,
237
+ payload: cancellationSnapshot,
238
+ });
239
+ return updated;
240
+ }
241
+ async function maybeCancelEnvelope(db, envelope, components) {
242
+ const hasActiveComponent = components.some((component) => component.status !== "cancelled" && component.status !== "removed");
243
+ if (hasActiveComponent || envelope.status === "cancelled")
244
+ return envelope;
245
+ const [updated] = (await db
246
+ .update(tripEnvelopes)
247
+ .set({ status: "cancelled", updatedAt: new Date() })
248
+ .where(eq(tripEnvelopes.id, envelope.id))
249
+ .returning());
250
+ return updated ?? envelope;
251
+ }
@@ -0,0 +1,6 @@
1
+ import type { AnyDrizzleDb } from "@voyant-travel/db";
2
+ import type { CompleteTripCheckoutInput, CompleteTripCheckoutResult, StartCheckoutDeps, StartCheckoutResult } from "./service-types.js";
3
+ import type { StartTripCheckoutInput } from "./validation.js";
4
+ export declare function startCheckout(db: AnyDrizzleDb, input: StartTripCheckoutInput, deps: StartCheckoutDeps): Promise<StartCheckoutResult>;
5
+ export declare function completeTripCheckout(db: AnyDrizzleDb, input: CompleteTripCheckoutInput): Promise<CompleteTripCheckoutResult | null>;
6
+ //# sourceMappingURL=service-checkout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-checkout.d.ts","sourceRoot":"","sources":["../src/service-checkout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAqBrD,OAAO,KAAK,EACV,yBAAyB,EACzB,0BAA0B,EAE1B,iBAAiB,EACjB,mBAAmB,EAKpB,MAAM,oBAAoB,CAAA;AAG3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAK7D,wBAAsB,aAAa,CACjC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,sBAAsB,EAC7B,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,mBAAmB,CAAC,CAyI9B;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAkG5C"}