@voyant-travel/charters 0.117.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.
- package/LICENSE +201 -0
- package/README.md +16 -0
- package/dist/adapters/index.d.ts +254 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +16 -0
- package/dist/adapters/memoize.d.ts +28 -0
- package/dist/adapters/memoize.d.ts.map +1 -0
- package/dist/adapters/memoize.js +121 -0
- package/dist/adapters/mock.d.ts +50 -0
- package/dist/adapters/mock.d.ts.map +1 -0
- package/dist/adapters/mock.js +194 -0
- package/dist/adapters/registry.d.ts +24 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +40 -0
- package/dist/booking-extension.d.ts +895 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +339 -0
- package/dist/catalog-policy.d.ts +23 -0
- package/dist/catalog-policy.d.ts.map +1 -0
- package/dist/catalog-policy.js +400 -0
- package/dist/content-shape.d.ts +5 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +13 -0
- package/dist/draft-shape.d.ts +29 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +63 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/lib/key.d.ts +22 -0
- package/dist/lib/key.d.ts.map +1 -0
- package/dist/lib/key.js +24 -0
- package/dist/routes-public.d.ts +785 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +234 -0
- package/dist/routes.d.ts +1744 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +543 -0
- package/dist/schema-core.d.ts +815 -0
- package/dist/schema-core.d.ts.map +1 -0
- package/dist/schema-core.js +98 -0
- package/dist/schema-itinerary.d.ts +239 -0
- package/dist/schema-itinerary.d.ts.map +1 -0
- package/dist/schema-itinerary.js +30 -0
- package/dist/schema-pricing.d.ts +385 -0
- package/dist/schema-pricing.d.ts.map +1 -0
- package/dist/schema-pricing.js +62 -0
- package/dist/schema-shared.d.ts +8 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +37 -0
- package/dist/schema-sourced-content.d.ts +253 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +44 -0
- package/dist/schema-yachts.d.ts +367 -0
- package/dist/schema-yachts.d.ts.map +1 -0
- package/dist/schema-yachts.js +30 -0
- package/dist/schema.d.ts +8 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +7 -0
- package/dist/service-bookings-helpers.d.ts +20 -0
- package/dist/service-bookings-helpers.d.ts.map +1 -0
- package/dist/service-bookings-helpers.js +67 -0
- package/dist/service-bookings-local.d.ts +5 -0
- package/dist/service-bookings-local.d.ts.map +1 -0
- package/dist/service-bookings-local.js +177 -0
- package/dist/service-bookings-types.d.ts +88 -0
- package/dist/service-bookings-types.d.ts.map +1 -0
- package/dist/service-bookings-types.js +1 -0
- package/dist/service-bookings.d.ts +36 -0
- package/dist/service-bookings.d.ts.map +1 -0
- package/dist/service-bookings.js +267 -0
- package/dist/service-catalog-plane.d.ts +58 -0
- package/dist/service-catalog-plane.d.ts.map +1 -0
- package/dist/service-catalog-plane.js +145 -0
- package/dist/service-content-synthesizer.d.ts +42 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +122 -0
- package/dist/service-content.d.ts +43 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +248 -0
- package/dist/service-myba.d.ts +85 -0
- package/dist/service-myba.d.ts.map +1 -0
- package/dist/service-myba.js +88 -0
- package/dist/service-pricing.d.ts +64 -0
- package/dist/service-pricing.d.ts.map +1 -0
- package/dist/service-pricing.js +167 -0
- package/dist/service.d.ts +131 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +279 -0
- package/dist/validation-core.d.ts +152 -0
- package/dist/validation-core.d.ts.map +1 -0
- package/dist/validation-core.js +66 -0
- package/dist/validation-itinerary.d.ts +43 -0
- package/dist/validation-itinerary.d.ts.map +1 -0
- package/dist/validation-itinerary.js +19 -0
- package/dist/validation-pricing.d.ts +103 -0
- package/dist/validation-pricing.d.ts.map +1 -0
- package/dist/validation-pricing.js +28 -0
- package/dist/validation-shared.d.ts +61 -0
- package/dist/validation-shared.d.ts.map +1 -0
- package/dist/validation-shared.js +60 -0
- package/dist/validation-yachts.d.ts +76 -0
- package/dist/validation-yachts.d.ts.map +1 -0
- package/dist/validation-yachts.js +36 -0
- package/dist/validation.d.ts +6 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +5 -0
- package/package.json +116 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { typeId } from "@voyant-travel/db/lib/typeid-column";
|
|
2
|
+
import { boolean, index, integer, jsonb, numeric, pgTable, text, timestamp, uniqueIndex, } from "drizzle-orm/pg-core";
|
|
3
|
+
import { yachtClassEnum } from "./schema-shared.js";
|
|
4
|
+
export const charterYachts = pgTable("charter_yachts", {
|
|
5
|
+
id: typeId("charter_yachts"),
|
|
6
|
+
lineSupplierId: text("line_supplier_id"),
|
|
7
|
+
name: text("name").notNull(),
|
|
8
|
+
slug: text("slug").notNull(),
|
|
9
|
+
yachtClass: yachtClassEnum("yacht_class").notNull(),
|
|
10
|
+
capacityGuests: integer("capacity_guests"),
|
|
11
|
+
capacityCrew: integer("capacity_crew"),
|
|
12
|
+
lengthMeters: numeric("length_meters", { precision: 8, scale: 2 }),
|
|
13
|
+
yearBuilt: integer("year_built"),
|
|
14
|
+
yearRefurbished: integer("year_refurbished"),
|
|
15
|
+
imo: text("imo"),
|
|
16
|
+
description: text("description"),
|
|
17
|
+
gallery: jsonb("gallery").$type().default([]),
|
|
18
|
+
amenities: jsonb("amenities").$type().default({}),
|
|
19
|
+
crewBios: jsonb("crew_bios").$type().default([]),
|
|
20
|
+
defaultCharterAreas: jsonb("default_charter_areas").$type().default([]),
|
|
21
|
+
externalRefs: jsonb("external_refs").$type().default({}),
|
|
22
|
+
isActive: boolean("is_active").notNull().default(true),
|
|
23
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
24
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
25
|
+
}, (table) => [
|
|
26
|
+
uniqueIndex("uidx_charter_yachts_slug").on(table.slug),
|
|
27
|
+
uniqueIndex("uidx_charter_yachts_imo").on(table.imo),
|
|
28
|
+
index("idx_charter_yachts_supplier_active").on(table.lineSupplierId, table.isActive),
|
|
29
|
+
index("idx_charter_yachts_class_active").on(table.yachtClass, table.isActive),
|
|
30
|
+
]);
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { bookingCharterDetails } from "./booking-extension.js";
|
|
2
|
+
export * from "./schema-core.js";
|
|
3
|
+
export * from "./schema-itinerary.js";
|
|
4
|
+
export * from "./schema-pricing.js";
|
|
5
|
+
export * from "./schema-shared.js";
|
|
6
|
+
export { CHARTERS_CONTENT_MARKET_ANY, type ChartersSourcedContentFetchStatus, chartersSourcedContentTable, type InsertChartersSourcedContent, type SelectChartersSourcedContent, } from "./schema-sourced-content.js";
|
|
7
|
+
export * from "./schema-yachts.js";
|
|
8
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,cAAc,kBAAkB,CAAA;AAChC,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,OAAO,EACL,2BAA2B,EAC3B,KAAK,iCAAiC,EACtC,2BAA2B,EAC3B,KAAK,4BAA4B,EACjC,KAAK,4BAA4B,GAClC,MAAM,6BAA6B,CAAA;AACpC,cAAc,oBAAoB,CAAA"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { bookingCharterDetails } from "./booking-extension.js";
|
|
2
|
+
export * from "./schema-core.js";
|
|
3
|
+
export * from "./schema-itinerary.js";
|
|
4
|
+
export * from "./schema-pricing.js";
|
|
5
|
+
export * from "./schema-shared.js";
|
|
6
|
+
export { CHARTERS_CONTENT_MARKET_ANY, chartersSourcedContentTable, } from "./schema-sourced-content.js";
|
|
7
|
+
export * from "./schema-yachts.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { SourceRef } from "./adapters/index.js";
|
|
3
|
+
import { type CharterVoyage } from "./schema-core.js";
|
|
4
|
+
import { type CharterSuite } from "./schema-pricing.js";
|
|
5
|
+
import { type CharterYacht } from "./schema-yachts.js";
|
|
6
|
+
import type { CharterGuest } from "./service-bookings-types.js";
|
|
7
|
+
export declare function generateCharterBookingNumber(prefix?: "CHT" | "WYC"): string;
|
|
8
|
+
export declare function priceCentsFromString(s: string): number;
|
|
9
|
+
export declare function loadVoyage(db: PostgresJsDatabase, voyageId: string): Promise<CharterVoyage>;
|
|
10
|
+
export declare function loadSuite(db: PostgresJsDatabase, suiteId: string): Promise<CharterSuite>;
|
|
11
|
+
export declare function loadYacht(db: PostgresJsDatabase, yachtId: string): Promise<CharterYacht | null>;
|
|
12
|
+
export declare function loadProductDefaults(db: PostgresJsDatabase, productId: string): Promise<{
|
|
13
|
+
defaultApaPercent: string | null;
|
|
14
|
+
defaultMybaTemplateId: string | null;
|
|
15
|
+
} | null>;
|
|
16
|
+
export declare function sourceRefEquals(a: SourceRef, b: SourceRef): boolean;
|
|
17
|
+
export declare function createCharterTravelers(db: PostgresJsDatabase, bookingId: string, guests: ReadonlyArray<CharterGuest>, userId: string | undefined, options: {
|
|
18
|
+
includeGuestNotes: boolean;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=service-bookings-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-bookings-helpers.d.ts","sourceRoot":"","sources":["../src/service-bookings-helpers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,KAAK,aAAa,EAAmC,MAAM,kBAAkB,CAAA;AACtF,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,qBAAqB,CAAA;AACtE,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,oBAAoB,CAAA;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE/D,wBAAgB,4BAA4B,CAAC,MAAM,GAAE,KAAK,GAAG,KAAa,GAAG,MAAM,CAIlF;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAQjG;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAI9F;AAED,wBAAsB,SAAS,CAC7B,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAG9B;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAU5F;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAEnE;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,EACnC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE;IAAE,iBAAiB,EAAE,OAAO,CAAA;CAAE,GACtC,OAAO,CAAC,IAAI,CAAC,CAmBf"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { bookingsService } from "@voyant-travel/bookings";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { charterProducts, charterVoyages } from "./schema-core.js";
|
|
4
|
+
import { charterSuites } from "./schema-pricing.js";
|
|
5
|
+
import { charterYachts } from "./schema-yachts.js";
|
|
6
|
+
export function generateCharterBookingNumber(prefix = "CHT") {
|
|
7
|
+
const ts = Date.now().toString(36).toUpperCase();
|
|
8
|
+
const rand = Math.random().toString(36).slice(2, 6).toUpperCase();
|
|
9
|
+
return `${prefix}-${ts}-${rand}`;
|
|
10
|
+
}
|
|
11
|
+
export function priceCentsFromString(s) {
|
|
12
|
+
const negative = s.startsWith("-");
|
|
13
|
+
const abs = negative ? s.slice(1) : s;
|
|
14
|
+
const [whole = "0", frac = ""] = abs.split(".");
|
|
15
|
+
const fracPadded = `${frac}00`.slice(0, 2);
|
|
16
|
+
const cents = Number(whole) * 100 + Number(fracPadded);
|
|
17
|
+
return negative ? -cents : cents;
|
|
18
|
+
}
|
|
19
|
+
export async function loadVoyage(db, voyageId) {
|
|
20
|
+
const [row] = await db
|
|
21
|
+
.select()
|
|
22
|
+
.from(charterVoyages)
|
|
23
|
+
.where(eq(charterVoyages.id, voyageId))
|
|
24
|
+
.limit(1);
|
|
25
|
+
if (!row)
|
|
26
|
+
throw new Error(`Charter voyage ${voyageId} not found`);
|
|
27
|
+
return row;
|
|
28
|
+
}
|
|
29
|
+
export async function loadSuite(db, suiteId) {
|
|
30
|
+
const [row] = await db.select().from(charterSuites).where(eq(charterSuites.id, suiteId)).limit(1);
|
|
31
|
+
if (!row)
|
|
32
|
+
throw new Error(`Charter suite ${suiteId} not found`);
|
|
33
|
+
return row;
|
|
34
|
+
}
|
|
35
|
+
export async function loadYacht(db, yachtId) {
|
|
36
|
+
const [row] = await db.select().from(charterYachts).where(eq(charterYachts.id, yachtId)).limit(1);
|
|
37
|
+
return row ?? null;
|
|
38
|
+
}
|
|
39
|
+
export async function loadProductDefaults(db, productId) {
|
|
40
|
+
const [row] = await db
|
|
41
|
+
.select({
|
|
42
|
+
defaultApaPercent: charterProducts.defaultApaPercent,
|
|
43
|
+
defaultMybaTemplateId: charterProducts.defaultMybaTemplateId,
|
|
44
|
+
})
|
|
45
|
+
.from(charterProducts)
|
|
46
|
+
.where(eq(charterProducts.id, productId))
|
|
47
|
+
.limit(1);
|
|
48
|
+
return row ?? null;
|
|
49
|
+
}
|
|
50
|
+
export function sourceRefEquals(a, b) {
|
|
51
|
+
return (a.connectionId ?? null) === (b.connectionId ?? null) && a.externalId === b.externalId;
|
|
52
|
+
}
|
|
53
|
+
export async function createCharterTravelers(db, bookingId, guests, userId, options) {
|
|
54
|
+
for (const guest of guests) {
|
|
55
|
+
await bookingsService.createTraveler(db, bookingId, {
|
|
56
|
+
firstName: guest.firstName,
|
|
57
|
+
lastName: guest.lastName,
|
|
58
|
+
email: guest.email ?? null,
|
|
59
|
+
phone: guest.phone ?? null,
|
|
60
|
+
travelerCategory: guest.travelerCategory ?? null,
|
|
61
|
+
preferredLanguage: guest.preferredLanguage ?? null,
|
|
62
|
+
specialRequests: guest.specialRequests ?? null,
|
|
63
|
+
isPrimary: guest.isPrimary ?? false,
|
|
64
|
+
notes: options.includeGuestNotes ? (guest.notes ?? null) : null,
|
|
65
|
+
}, userId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { CreatePerSuiteBookingInput, CreatePerSuiteBookingResult, CreateWholeYachtBookingInput, CreateWholeYachtBookingResult } from "./service-bookings-types.js";
|
|
3
|
+
export declare function createPerSuiteBooking(db: PostgresJsDatabase, input: CreatePerSuiteBookingInput, userId?: string): Promise<CreatePerSuiteBookingResult>;
|
|
4
|
+
export declare function createWholeYachtBooking(db: PostgresJsDatabase, input: CreateWholeYachtBookingInput, userId?: string): Promise<CreateWholeYachtBookingResult>;
|
|
5
|
+
//# sourceMappingURL=service-bookings-local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-bookings-local.d.ts","sourceRoot":"","sources":["../src/service-bookings-local.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAWjE,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC9B,MAAM,6BAA6B,CAAA;AAGpC,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,0BAA0B,EACjC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,2BAA2B,CAAC,CAiGtC;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,4BAA4B,EACnC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,6BAA6B,CAAC,CAgGxC"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { bookingsService } from "@voyant-travel/bookings";
|
|
2
|
+
import { bookingCharterDetailsService } from "./booking-extension.js";
|
|
3
|
+
import { createCharterTravelers, generateCharterBookingNumber, loadProductDefaults, loadSuite, loadVoyage, loadYacht, priceCentsFromString, } from "./service-bookings-helpers.js";
|
|
4
|
+
import { composePerSuiteQuote, composeWholeYachtQuote } from "./service-pricing.js";
|
|
5
|
+
export async function createPerSuiteBooking(db, input, userId) {
|
|
6
|
+
if (input.guests.length < 1)
|
|
7
|
+
throw new Error("At least one guest is required");
|
|
8
|
+
return db.transaction(async (tx) => {
|
|
9
|
+
const voyage = await loadVoyage(tx, input.voyageId);
|
|
10
|
+
const suite = await loadSuite(tx, input.suiteId);
|
|
11
|
+
if (suite.voyageId !== voyage.id) {
|
|
12
|
+
throw new Error(`Suite ${suite.id} does not belong to voyage ${voyage.id}`);
|
|
13
|
+
}
|
|
14
|
+
if (!voyage.bookingModes.includes("per_suite")) {
|
|
15
|
+
throw new Error(`Voyage ${voyage.id} does not offer per_suite bookings`);
|
|
16
|
+
}
|
|
17
|
+
if (suite.maxGuests !== null && input.guests.length > suite.maxGuests) {
|
|
18
|
+
throw new Error(`Suite ${suite.id} max guests is ${suite.maxGuests}; got ${input.guests.length}`);
|
|
19
|
+
}
|
|
20
|
+
const yacht = await loadYacht(tx, voyage.yachtId);
|
|
21
|
+
const quote = composePerSuiteQuote({
|
|
22
|
+
voyageId: voyage.id,
|
|
23
|
+
suite,
|
|
24
|
+
currency: input.currency,
|
|
25
|
+
});
|
|
26
|
+
const bookingNumber = generateCharterBookingNumber("CHT");
|
|
27
|
+
const totalCents = priceCentsFromString(quote.total);
|
|
28
|
+
const booking = await bookingsService.createBooking(tx, {
|
|
29
|
+
bookingNumber,
|
|
30
|
+
sellCurrency: quote.currency,
|
|
31
|
+
status: "draft",
|
|
32
|
+
sourceType: "manual",
|
|
33
|
+
personId: input.personId ?? null,
|
|
34
|
+
organizationId: input.organizationId ?? null,
|
|
35
|
+
contactFirstName: input.contact.firstName,
|
|
36
|
+
contactLastName: input.contact.lastName,
|
|
37
|
+
contactEmail: input.contact.email ?? null,
|
|
38
|
+
contactPhone: input.contact.phone ?? null,
|
|
39
|
+
contactPreferredLanguage: input.contact.language ?? null,
|
|
40
|
+
contactCountry: input.contact.country ?? null,
|
|
41
|
+
contactRegion: input.contact.region ?? null,
|
|
42
|
+
contactCity: input.contact.city ?? null,
|
|
43
|
+
contactAddressLine1: input.contact.address ?? null,
|
|
44
|
+
contactPostalCode: input.contact.postalCode ?? null,
|
|
45
|
+
sellAmountCents: totalCents,
|
|
46
|
+
pax: input.guests.length,
|
|
47
|
+
startDate: voyage.departureDate,
|
|
48
|
+
endDate: voyage.returnDate,
|
|
49
|
+
internalNotes: input.notes ?? null,
|
|
50
|
+
}, userId);
|
|
51
|
+
if (!booking)
|
|
52
|
+
throw new Error("bookingsService.createBooking returned null");
|
|
53
|
+
await createCharterTravelers(tx, booking.id, input.guests, userId, {
|
|
54
|
+
includeGuestNotes: true,
|
|
55
|
+
});
|
|
56
|
+
const charterDetails = await bookingCharterDetailsService.upsert(tx, booking.id, {
|
|
57
|
+
bookingMode: "per_suite",
|
|
58
|
+
source: "local",
|
|
59
|
+
sourceProvider: null,
|
|
60
|
+
sourceRef: null,
|
|
61
|
+
voyageId: voyage.id,
|
|
62
|
+
suiteId: suite.id,
|
|
63
|
+
yachtId: voyage.yachtId,
|
|
64
|
+
voyageDisplayName: voyage.name ?? voyage.voyageCode,
|
|
65
|
+
suiteDisplayName: suite.suiteName,
|
|
66
|
+
yachtName: yacht?.name ?? null,
|
|
67
|
+
charterAreaSnapshot: voyage.charterAreaOverride ?? null,
|
|
68
|
+
guestCount: input.guests.length,
|
|
69
|
+
quotedCurrency: quote.currency,
|
|
70
|
+
quotedSuitePrice: quote.suitePrice,
|
|
71
|
+
quotedPortFee: quote.portFee,
|
|
72
|
+
quotedCharterFee: null,
|
|
73
|
+
apaPercent: null,
|
|
74
|
+
apaAmount: null,
|
|
75
|
+
quotedTotal: quote.total,
|
|
76
|
+
mybaTemplateIdSnapshot: null,
|
|
77
|
+
mybaContractId: null,
|
|
78
|
+
apaPaidAmount: null,
|
|
79
|
+
apaSpentAmount: null,
|
|
80
|
+
apaRefundAmount: null,
|
|
81
|
+
connectorBookingRef: null,
|
|
82
|
+
connectorStatus: null,
|
|
83
|
+
notes: input.notes ?? null,
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
bookingId: booking.id,
|
|
87
|
+
bookingNumber: booking.bookingNumber,
|
|
88
|
+
charterDetails,
|
|
89
|
+
quote,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export async function createWholeYachtBooking(db, input, userId) {
|
|
94
|
+
return db.transaction(async (tx) => {
|
|
95
|
+
const voyage = await loadVoyage(tx, input.voyageId);
|
|
96
|
+
if (!voyage.bookingModes.includes("whole_yacht")) {
|
|
97
|
+
throw new Error(`Voyage ${voyage.id} does not offer whole_yacht bookings`);
|
|
98
|
+
}
|
|
99
|
+
const productDefaults = await loadProductDefaults(tx, voyage.productId);
|
|
100
|
+
const yacht = await loadYacht(tx, voyage.yachtId);
|
|
101
|
+
const quote = composeWholeYachtQuote({
|
|
102
|
+
voyage,
|
|
103
|
+
productDefaultApaPercent: productDefaults?.defaultApaPercent ?? null,
|
|
104
|
+
currency: input.currency,
|
|
105
|
+
});
|
|
106
|
+
const mybaTemplateId = voyage.mybaTemplateIdOverride ?? productDefaults?.defaultMybaTemplateId ?? null;
|
|
107
|
+
if (!mybaTemplateId) {
|
|
108
|
+
throw new Error(`Voyage ${voyage.id} cannot be booked whole-yacht: no MYBA template configured (neither voyage override nor product default).`);
|
|
109
|
+
}
|
|
110
|
+
const guestCount = Math.max(1, input.guests?.length ?? 1);
|
|
111
|
+
const bookingNumber = generateCharterBookingNumber("WYC");
|
|
112
|
+
const totalCents = priceCentsFromString(quote.total);
|
|
113
|
+
const booking = await bookingsService.createBooking(tx, {
|
|
114
|
+
bookingNumber,
|
|
115
|
+
sellCurrency: quote.currency,
|
|
116
|
+
status: "draft",
|
|
117
|
+
sourceType: "manual",
|
|
118
|
+
personId: input.personId ?? null,
|
|
119
|
+
organizationId: input.organizationId ?? null,
|
|
120
|
+
contactFirstName: input.contact.firstName,
|
|
121
|
+
contactLastName: input.contact.lastName,
|
|
122
|
+
contactEmail: input.contact.email ?? null,
|
|
123
|
+
contactPhone: input.contact.phone ?? null,
|
|
124
|
+
contactPreferredLanguage: input.contact.language ?? null,
|
|
125
|
+
contactCountry: input.contact.country ?? null,
|
|
126
|
+
contactRegion: input.contact.region ?? null,
|
|
127
|
+
contactCity: input.contact.city ?? null,
|
|
128
|
+
contactAddressLine1: input.contact.address ?? null,
|
|
129
|
+
contactPostalCode: input.contact.postalCode ?? null,
|
|
130
|
+
sellAmountCents: totalCents,
|
|
131
|
+
pax: guestCount,
|
|
132
|
+
startDate: voyage.departureDate,
|
|
133
|
+
endDate: voyage.returnDate,
|
|
134
|
+
internalNotes: input.notes ?? null,
|
|
135
|
+
}, userId);
|
|
136
|
+
if (!booking)
|
|
137
|
+
throw new Error("bookingsService.createBooking returned null");
|
|
138
|
+
await createCharterTravelers(tx, booking.id, input.guests ?? [], userId, {
|
|
139
|
+
includeGuestNotes: true,
|
|
140
|
+
});
|
|
141
|
+
const charterDetails = await bookingCharterDetailsService.upsert(tx, booking.id, {
|
|
142
|
+
bookingMode: "whole_yacht",
|
|
143
|
+
source: "local",
|
|
144
|
+
sourceProvider: null,
|
|
145
|
+
sourceRef: null,
|
|
146
|
+
voyageId: voyage.id,
|
|
147
|
+
suiteId: null,
|
|
148
|
+
yachtId: voyage.yachtId,
|
|
149
|
+
voyageDisplayName: voyage.name ?? voyage.voyageCode,
|
|
150
|
+
suiteDisplayName: null,
|
|
151
|
+
yachtName: yacht?.name ?? null,
|
|
152
|
+
charterAreaSnapshot: voyage.charterAreaOverride ?? null,
|
|
153
|
+
guestCount,
|
|
154
|
+
quotedCurrency: quote.currency,
|
|
155
|
+
quotedSuitePrice: null,
|
|
156
|
+
quotedPortFee: null,
|
|
157
|
+
quotedCharterFee: quote.charterFee,
|
|
158
|
+
apaPercent: quote.apaPercent,
|
|
159
|
+
apaAmount: quote.apaAmount,
|
|
160
|
+
quotedTotal: quote.total,
|
|
161
|
+
mybaTemplateIdSnapshot: mybaTemplateId,
|
|
162
|
+
mybaContractId: null,
|
|
163
|
+
apaPaidAmount: "0.00",
|
|
164
|
+
apaSpentAmount: "0.00",
|
|
165
|
+
apaRefundAmount: "0.00",
|
|
166
|
+
connectorBookingRef: null,
|
|
167
|
+
connectorStatus: null,
|
|
168
|
+
notes: input.notes ?? null,
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
bookingId: booking.id,
|
|
172
|
+
bookingNumber: booking.bookingNumber,
|
|
173
|
+
charterDetails,
|
|
174
|
+
quote,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { CharterAdapter, SourceRef } from "./adapters/index.js";
|
|
2
|
+
import type { BookingCharterDetail } from "./booking-extension.js";
|
|
3
|
+
import type { PerSuiteQuote, WholeYachtQuote } from "./service-pricing.js";
|
|
4
|
+
export type CharterGuest = {
|
|
5
|
+
firstName: string;
|
|
6
|
+
lastName: string;
|
|
7
|
+
email?: string | null;
|
|
8
|
+
phone?: string | null;
|
|
9
|
+
travelerCategory?: "adult" | "child" | "infant" | "senior" | "other" | null;
|
|
10
|
+
preferredLanguage?: string | null;
|
|
11
|
+
specialRequests?: string | null;
|
|
12
|
+
personId?: string | null;
|
|
13
|
+
isPrimary?: boolean;
|
|
14
|
+
notes?: string | null;
|
|
15
|
+
};
|
|
16
|
+
export type CharterContact = {
|
|
17
|
+
firstName: string;
|
|
18
|
+
lastName: string;
|
|
19
|
+
email?: string | null;
|
|
20
|
+
phone?: string | null;
|
|
21
|
+
language?: string | null;
|
|
22
|
+
country?: string | null;
|
|
23
|
+
region?: string | null;
|
|
24
|
+
city?: string | null;
|
|
25
|
+
address?: string | null;
|
|
26
|
+
postalCode?: string | null;
|
|
27
|
+
};
|
|
28
|
+
export type CreatePerSuiteBookingInput = {
|
|
29
|
+
voyageId: string;
|
|
30
|
+
suiteId: string;
|
|
31
|
+
currency: string;
|
|
32
|
+
personId?: string | null;
|
|
33
|
+
organizationId?: string | null;
|
|
34
|
+
contact: CharterContact;
|
|
35
|
+
guests: CharterGuest[];
|
|
36
|
+
notes?: string | null;
|
|
37
|
+
};
|
|
38
|
+
export type CreatePerSuiteBookingResult = {
|
|
39
|
+
bookingId: string;
|
|
40
|
+
bookingNumber: string;
|
|
41
|
+
charterDetails: BookingCharterDetail;
|
|
42
|
+
quote: PerSuiteQuote;
|
|
43
|
+
};
|
|
44
|
+
export type CreateWholeYachtBookingInput = {
|
|
45
|
+
voyageId: string;
|
|
46
|
+
currency: string;
|
|
47
|
+
personId?: string | null;
|
|
48
|
+
organizationId?: string | null;
|
|
49
|
+
contact: CharterContact;
|
|
50
|
+
guests?: CharterGuest[];
|
|
51
|
+
notes?: string | null;
|
|
52
|
+
};
|
|
53
|
+
export type CreateWholeYachtBookingResult = {
|
|
54
|
+
bookingId: string;
|
|
55
|
+
bookingNumber: string;
|
|
56
|
+
charterDetails: BookingCharterDetail;
|
|
57
|
+
quote: WholeYachtQuote;
|
|
58
|
+
};
|
|
59
|
+
export type CreateExternalPerSuiteBookingInput = {
|
|
60
|
+
adapter: CharterAdapter;
|
|
61
|
+
voyageRef: SourceRef;
|
|
62
|
+
suiteRef: SourceRef;
|
|
63
|
+
currency: string;
|
|
64
|
+
personId?: string | null;
|
|
65
|
+
organizationId?: string | null;
|
|
66
|
+
contact: CharterContact;
|
|
67
|
+
guests: CharterGuest[];
|
|
68
|
+
notes?: string | null;
|
|
69
|
+
};
|
|
70
|
+
export type CreateExternalPerSuiteBookingResult = CreatePerSuiteBookingResult & {
|
|
71
|
+
sourceProvider: string;
|
|
72
|
+
sourceRef: SourceRef;
|
|
73
|
+
};
|
|
74
|
+
export type CreateExternalWholeYachtBookingInput = {
|
|
75
|
+
adapter: CharterAdapter;
|
|
76
|
+
voyageRef: SourceRef;
|
|
77
|
+
currency: string;
|
|
78
|
+
personId?: string | null;
|
|
79
|
+
organizationId?: string | null;
|
|
80
|
+
contact: CharterContact;
|
|
81
|
+
guests?: CharterGuest[];
|
|
82
|
+
notes?: string | null;
|
|
83
|
+
};
|
|
84
|
+
export type CreateExternalWholeYachtBookingResult = CreateWholeYachtBookingResult & {
|
|
85
|
+
sourceProvider: string;
|
|
86
|
+
sourceRef: SourceRef;
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=service-bookings-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-bookings-types.d.ts","sourceRoot":"","sources":["../src/service-bookings-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import { createPerSuiteBooking, createWholeYachtBooking } from "./service-bookings-local.js";
|
|
3
|
+
import type { CreateExternalPerSuiteBookingInput, CreateExternalPerSuiteBookingResult, CreateExternalWholeYachtBookingInput, CreateExternalWholeYachtBookingResult } from "./service-bookings-types.js";
|
|
4
|
+
export type { CharterContact, CharterGuest, CreateExternalPerSuiteBookingInput, CreateExternalPerSuiteBookingResult, CreateExternalWholeYachtBookingInput, CreateExternalWholeYachtBookingResult, CreatePerSuiteBookingInput, CreatePerSuiteBookingResult, CreateWholeYachtBookingInput, CreateWholeYachtBookingResult, } from "./service-bookings-types.js";
|
|
5
|
+
export declare const chartersBookingService: {
|
|
6
|
+
createPerSuiteBooking: typeof createPerSuiteBooking;
|
|
7
|
+
createWholeYachtBooking: typeof createWholeYachtBooking;
|
|
8
|
+
/**
|
|
9
|
+
* Create a per-suite booking against an external (adapter-sourced) voyage.
|
|
10
|
+
*
|
|
11
|
+
* 1. Fetch the upstream voyage + its suites; locate the matching suite and
|
|
12
|
+
* compose a `PerSuiteQuote` locally from its multi-currency price columns.
|
|
13
|
+
* 2. Commit upstream BEFORE writing local rows so we can fail loudly if the
|
|
14
|
+
* broker rejects the booking.
|
|
15
|
+
* 3. Inside a single transaction, create the local booking + travelers +
|
|
16
|
+
* snapshot the quote into `booking_charter_details` with `source='external'`
|
|
17
|
+
* and the upstream connectorBookingRef.
|
|
18
|
+
*
|
|
19
|
+
* If the upstream commit succeeds but the local insert fails, the upstream
|
|
20
|
+
* booking exists with no local trace — we surface the upstream ref in the
|
|
21
|
+
* thrown error so the operator can manually reconcile via the broker's UI.
|
|
22
|
+
*/
|
|
23
|
+
createExternalPerSuiteBooking(db: PostgresJsDatabase, input: CreateExternalPerSuiteBookingInput, userId?: string): Promise<CreateExternalPerSuiteBookingResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Create a whole-yacht booking against an external (adapter-sourced) voyage.
|
|
26
|
+
*
|
|
27
|
+
* Same atomicity model as `createExternalPerSuiteBooking`. External
|
|
28
|
+
* whole-yacht bookings still require a Voyant-side MYBA template — the
|
|
29
|
+
* adapter must surface it via `voyage.mybaTemplateRefOverride` or
|
|
30
|
+
* `product.defaultMybaTemplateRef`. The string is stored as
|
|
31
|
+
* `mybaTemplateIdSnapshot` and the operator wires up an actual contract
|
|
32
|
+
* later via `mybaService.generateContract`.
|
|
33
|
+
*/
|
|
34
|
+
createExternalWholeYachtBooking(db: PostgresJsDatabase, input: CreateExternalWholeYachtBookingInput, userId?: string): Promise<CreateExternalWholeYachtBookingResult>;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=service-bookings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-bookings.d.ts","sourceRoot":"","sources":["../src/service-bookings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AASjE,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAA;AAC5F,OAAO,KAAK,EACV,kCAAkC,EAClC,mCAAmC,EACnC,oCAAoC,EACpC,qCAAqC,EACtC,MAAM,6BAA6B,CAAA;AAEpC,YAAY,EACV,cAAc,EACd,YAAY,EACZ,kCAAkC,EAClC,mCAAmC,EACnC,oCAAoC,EACpC,qCAAqC,EACrC,0BAA0B,EAC1B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,6BAA6B,CAAA;AAWpC,eAAO,MAAM,sBAAsB;;;IAIjC;;;;;;;;;;;;;;OAcG;sCAEG,kBAAkB,SACf,kCAAkC,WAChC,MAAM,GACd,OAAO,CAAC,mCAAmC,CAAC;IA0I/C;;;;;;;;;OASG;wCAEG,kBAAkB,SACf,oCAAoC,WAClC,MAAM,GACd,OAAO,CAAC,qCAAqC,CAAC;CA2IlD,CAAA"}
|