@voyantjs/accommodations 0.55.0
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/README.md +11 -0
- package/dist/booking-engine/handler.d.ts +103 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +254 -0
- package/dist/booking-engine/index.d.ts +8 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +7 -0
- package/dist/catalog-policy.d.ts +23 -0
- package/dist/catalog-policy.d.ts.map +1 -0
- package/dist/catalog-policy.js +422 -0
- package/dist/content-shape.d.ts +185 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +122 -0
- package/dist/draft-shape.d.ts +35 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +84 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/routes-content.d.ts +31 -0
- package/dist/routes-content.d.ts.map +1 -0
- package/dist/routes-content.js +87 -0
- package/dist/schema-bookings.d.ts +582 -0
- package/dist/schema-bookings.d.ts.map +1 -0
- package/dist/schema-bookings.js +65 -0
- package/dist/schema-inventory.d.ts +1361 -0
- package/dist/schema-inventory.d.ts.map +1 -0
- package/dist/schema-inventory.js +132 -0
- package/dist/schema-shared.d.ts +5 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +24 -0
- package/dist/schema-sourced-content.d.ts +254 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +48 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +4 -0
- package/dist/service-catalog-plane.d.ts +55 -0
- package/dist/service-catalog-plane.d.ts.map +1 -0
- package/dist/service-catalog-plane.js +202 -0
- package/dist/service-content-synthesizer.d.ts +43 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +149 -0
- package/dist/service-content.d.ts +54 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +480 -0
- package/package.json +113 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @voyantjs/accommodations
|
|
2
|
+
|
|
3
|
+
Accommodation resale contracts for Voyant.
|
|
4
|
+
|
|
5
|
+
This package is for OTAs, tour operators, and DMCs selling lodging as catalog
|
|
6
|
+
inventory or trip components. It is not a hotel-operations or PMS surface.
|
|
7
|
+
|
|
8
|
+
Retained scope includes sourced lodging content, room options, board basis,
|
|
9
|
+
rate plans, payment-policy lookup, booking draft shape, and catalog projection.
|
|
10
|
+
Do not add hotel staff workflows such as room-unit management, housekeeping,
|
|
11
|
+
maintenance, folios, or in-stay operations.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-arm booking handler for the `accommodation` vertical.
|
|
3
|
+
*
|
|
4
|
+
* Per `docs/architecture/booking-journey-architecture.md` §6.
|
|
5
|
+
*
|
|
6
|
+
* Phase B scope (deliberately narrow):
|
|
7
|
+
* - `computeQuote` projects the property's accommodation content
|
|
8
|
+
* into a `BookingDraftShape` with date-range + occupancy
|
|
9
|
+
* sub-steps and a Rooms accommodation step. Pricing is best-
|
|
10
|
+
* effort: `nights × room_count × baseRateHint` when the content
|
|
11
|
+
* surfaces a rate hint; otherwise no pricing returned (the
|
|
12
|
+
* wizard hides the total until a real quote lands).
|
|
13
|
+
* - `commit` uses a caller-supplied bridge when the host template has a
|
|
14
|
+
* resale booking write path. Without one, it fails explicitly.
|
|
15
|
+
*
|
|
16
|
+
* Templates wire this handler at boot the same way they wire the
|
|
17
|
+
* products handler. Once `commit` ships, the registry is genuinely
|
|
18
|
+
* multi-vertical with no further dispatch changes.
|
|
19
|
+
*/
|
|
20
|
+
import type { OwnedBookingHandler, OwnedHandlerContext } from "@voyantjs/catalog/booking-engine";
|
|
21
|
+
import type { AccommodationContent } from "../content-shape.js";
|
|
22
|
+
/**
|
|
23
|
+
* Caller-supplied loader — keeps the handler free of a hard
|
|
24
|
+
* dependency on the template's content service wiring (which spans
|
|
25
|
+
* sourced + owned + overlays). Templates wire this to
|
|
26
|
+
* `getAccommodationContent` from `@voyantjs/accommodations/service-content`.
|
|
27
|
+
*/
|
|
28
|
+
export type AccommodationContentLoader = (ctx: OwnedHandlerContext, entityId: string) => Promise<AccommodationContent | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Resale booking-line commit input. Structural so the handler stays free of a
|
|
31
|
+
* dependency on any host template's persistence path.
|
|
32
|
+
*/
|
|
33
|
+
export interface AccommodationCommitBridgeInput {
|
|
34
|
+
propertyId: string;
|
|
35
|
+
roomTypeId: string;
|
|
36
|
+
ratePlanId: string;
|
|
37
|
+
mealPlanId?: string | null;
|
|
38
|
+
checkInDate: string;
|
|
39
|
+
checkOutDate: string;
|
|
40
|
+
roomCount?: number;
|
|
41
|
+
adults?: number;
|
|
42
|
+
children?: number;
|
|
43
|
+
infants?: number;
|
|
44
|
+
dailyRates: Array<{
|
|
45
|
+
sellCurrency: string;
|
|
46
|
+
sellAmountCents?: number | null;
|
|
47
|
+
costCurrency?: string | null;
|
|
48
|
+
costAmountCents?: number | null;
|
|
49
|
+
}>;
|
|
50
|
+
personId?: string | null;
|
|
51
|
+
organizationId?: string | null;
|
|
52
|
+
contact: {
|
|
53
|
+
firstName: string;
|
|
54
|
+
lastName: string;
|
|
55
|
+
email?: string | null;
|
|
56
|
+
phone?: string | null;
|
|
57
|
+
country?: string | null;
|
|
58
|
+
};
|
|
59
|
+
passengers: Array<{
|
|
60
|
+
firstName: string;
|
|
61
|
+
lastName: string;
|
|
62
|
+
email?: string | null;
|
|
63
|
+
phone?: string | null;
|
|
64
|
+
travelerCategory?: "adult" | "child" | "infant" | "senior" | "other" | null;
|
|
65
|
+
isPrimary?: boolean | null;
|
|
66
|
+
}>;
|
|
67
|
+
notes?: string | null;
|
|
68
|
+
}
|
|
69
|
+
export interface AccommodationCommitBridgeResult {
|
|
70
|
+
status: "ok" | "failed";
|
|
71
|
+
bookingId?: string;
|
|
72
|
+
bookingNumber?: string;
|
|
73
|
+
reason?: string;
|
|
74
|
+
}
|
|
75
|
+
export type AccommodationCommitBridge = (input: AccommodationCommitBridgeInput, options?: {
|
|
76
|
+
userId?: string;
|
|
77
|
+
}) => Promise<AccommodationCommitBridgeResult>;
|
|
78
|
+
export interface CreateAccommodationBookingHandlerOptions {
|
|
79
|
+
/** Loader for the property's content payload. */
|
|
80
|
+
loadContent: AccommodationContentLoader;
|
|
81
|
+
/**
|
|
82
|
+
* Default min/max nights when the supplier hasn't declared bounds.
|
|
83
|
+
* The journey's date-range sub-step uses these as guard rails.
|
|
84
|
+
*/
|
|
85
|
+
defaultMinNights?: number;
|
|
86
|
+
defaultMaxNights?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Caller-supplied bridge to a host template's accommodation booking write
|
|
89
|
+
* path. When omitted, commit returns
|
|
90
|
+
* `failed:accommodation_commit_bridge_not_wired`.
|
|
91
|
+
*
|
|
92
|
+
* The journey doesn't currently surface room-type / rate-plan
|
|
93
|
+
* picks at the granularity reserveStay needs (specifically
|
|
94
|
+
* `ratePlanId` and per-night `dailyRates`). Until the journey's
|
|
95
|
+
* Accommodation step + content shape extend to expose those, the
|
|
96
|
+
* caller may map drafts to a "best guess" room/rate from the
|
|
97
|
+
* descriptor's room-options projection — we leave that decision
|
|
98
|
+
* to the template.
|
|
99
|
+
*/
|
|
100
|
+
commitBridge?: AccommodationCommitBridge;
|
|
101
|
+
}
|
|
102
|
+
export declare function createAccommodationBookingHandler(options: CreateAccommodationBookingHandlerOptions): OwnedBookingHandler;
|
|
103
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/booking-engine/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAMV,mBAAmB,EACnB,mBAAmB,EAEpB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAuB/D;;;;;GAKG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACvC,GAAG,EAAE,mBAAmB,EACxB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAA;AAEzC;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,KAAK,CAAC;QAChB,YAAY,EAAE,MAAM,CAAA;QACpB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAChC,CAAC,CAAA;IACF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KACxB,CAAA;IACD,UAAU,EAAE,KAAK,CAAC;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,gBAAgB,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAA;QAC3E,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAC3B,CAAC,CAAA;IACF,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,yBAAyB,GAAG,CACtC,KAAK,EAAE,8BAA8B,EACrC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KAC1B,OAAO,CAAC,+BAA+B,CAAC,CAAA;AAE7C,MAAM,WAAW,wCAAwC;IACvD,iDAAiD;IACjD,WAAW,EAAE,0BAA0B,CAAA;IACvC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,yBAAyB,CAAA;CACzC;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,wCAAwC,GAChD,mBAAmB,CAkLrB"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-arm booking handler for the `accommodation` vertical.
|
|
3
|
+
*
|
|
4
|
+
* Per `docs/architecture/booking-journey-architecture.md` §6.
|
|
5
|
+
*
|
|
6
|
+
* Phase B scope (deliberately narrow):
|
|
7
|
+
* - `computeQuote` projects the property's accommodation content
|
|
8
|
+
* into a `BookingDraftShape` with date-range + occupancy
|
|
9
|
+
* sub-steps and a Rooms accommodation step. Pricing is best-
|
|
10
|
+
* effort: `nights × room_count × baseRateHint` when the content
|
|
11
|
+
* surfaces a rate hint; otherwise no pricing returned (the
|
|
12
|
+
* wizard hides the total until a real quote lands).
|
|
13
|
+
* - `commit` uses a caller-supplied bridge when the host template has a
|
|
14
|
+
* resale booking write path. Without one, it fails explicitly.
|
|
15
|
+
*
|
|
16
|
+
* Templates wire this handler at boot the same way they wire the
|
|
17
|
+
* products handler. Once `commit` ships, the registry is genuinely
|
|
18
|
+
* multi-vertical with no further dispatch changes.
|
|
19
|
+
*/
|
|
20
|
+
import { buildAccommodationDraftShape } from "../draft-shape.js";
|
|
21
|
+
export function createAccommodationBookingHandler(options) {
|
|
22
|
+
return {
|
|
23
|
+
entityModule: "accommodations",
|
|
24
|
+
async computeQuote(ctx, request) {
|
|
25
|
+
const content = await options.loadContent(ctx, request.entityId);
|
|
26
|
+
if (!content) {
|
|
27
|
+
return { available: false, invalidReason: "property_not_found" };
|
|
28
|
+
}
|
|
29
|
+
const shape = buildAccommodationDraftShape(content, {
|
|
30
|
+
minNights: options.defaultMinNights,
|
|
31
|
+
maxNights: options.defaultMaxNights,
|
|
32
|
+
});
|
|
33
|
+
const draft = (request.draft ?? {});
|
|
34
|
+
const pricing = computeBestEffortPricing(shape, draft);
|
|
35
|
+
return {
|
|
36
|
+
available: true,
|
|
37
|
+
pricing,
|
|
38
|
+
shape,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
async commit(_ctx, request) {
|
|
42
|
+
if (!options.commitBridge) {
|
|
43
|
+
return {
|
|
44
|
+
status: "failed",
|
|
45
|
+
orderRef: "",
|
|
46
|
+
upstreamPayload: { reason: "accommodation_commit_bridge_not_wired" },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const draft = (request.draft ?? {});
|
|
50
|
+
const range = draft.configure?.dateRange;
|
|
51
|
+
if (!range?.checkIn || !range?.checkOut) {
|
|
52
|
+
return {
|
|
53
|
+
status: "failed",
|
|
54
|
+
orderRef: "",
|
|
55
|
+
upstreamPayload: {
|
|
56
|
+
reason: "accommodation_commit_missing_inputs",
|
|
57
|
+
need: ["dateRange.checkIn", "dateRange.checkOut"],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Map the draft's room selection into a single (roomType, ratePlan) pair.
|
|
62
|
+
// The journey's Accommodation step picks an option_unit_id (which is
|
|
63
|
+
// accommodation's `roomTypeId`); rate plan defaults to the room's first
|
|
64
|
+
// available — templates can override the default by augmenting the
|
|
65
|
+
// bridge output. When no room is picked yet, the commit fails fast.
|
|
66
|
+
const firstRoom = draft.accommodation?.rooms?.[0];
|
|
67
|
+
if (!firstRoom) {
|
|
68
|
+
return {
|
|
69
|
+
status: "failed",
|
|
70
|
+
orderRef: "",
|
|
71
|
+
upstreamPayload: {
|
|
72
|
+
reason: "accommodation_commit_missing_inputs",
|
|
73
|
+
need: ["accommodation.rooms[0]"],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const adults = draft.configure?.pax?.adult ?? draft.travelers?.length ?? 1;
|
|
78
|
+
const children = draft.configure?.pax?.child ?? 0;
|
|
79
|
+
const infants = draft.configure?.pax?.infant ?? 0;
|
|
80
|
+
const billing = draft.billing ?? {};
|
|
81
|
+
const contact = {
|
|
82
|
+
firstName: billing.contact?.firstName ?? "",
|
|
83
|
+
lastName: billing.contact?.lastName ?? "",
|
|
84
|
+
email: billing.contact?.email ?? null,
|
|
85
|
+
phone: billing.contact?.phone ?? null,
|
|
86
|
+
country: billing.address?.country ?? null,
|
|
87
|
+
};
|
|
88
|
+
const passengers = (draft.travelers ?? []).map((t, idx) => ({
|
|
89
|
+
firstName: t.firstName,
|
|
90
|
+
lastName: t.lastName,
|
|
91
|
+
travelerCategory: t.band === "child" || t.band === "infant"
|
|
92
|
+
? t.band
|
|
93
|
+
: "adult",
|
|
94
|
+
isPrimary: idx === 0,
|
|
95
|
+
}));
|
|
96
|
+
if (passengers.length === 0) {
|
|
97
|
+
passengers.push({
|
|
98
|
+
firstName: contact.firstName,
|
|
99
|
+
lastName: contact.lastName,
|
|
100
|
+
travelerCategory: "adult",
|
|
101
|
+
isPrimary: true,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// Per-night rate hint from the draft's pricing breakdown
|
|
105
|
+
// (computed by the handler earlier). The bridge expects
|
|
106
|
+
// `dailyRates[]` with one entry per night — we replicate the
|
|
107
|
+
// averaged hint across nights as a placeholder; production
|
|
108
|
+
// needs supplier-provided rate-plan rows.
|
|
109
|
+
const nights = nightsBetween(range.checkIn, range.checkOut);
|
|
110
|
+
const totalCents = readPricingTotalCents(request.pricing);
|
|
111
|
+
const perNightCents = nights > 0 && totalCents > 0 ? Math.round(totalCents / nights / firstRoom.quantity) : 0;
|
|
112
|
+
const currency = readPricingCurrency(request.pricing) ?? "EUR";
|
|
113
|
+
const dailyRates = Array.from({ length: nights }, () => ({
|
|
114
|
+
sellCurrency: currency,
|
|
115
|
+
sellAmountCents: perNightCents,
|
|
116
|
+
}));
|
|
117
|
+
// The journey now surfaces rate-plan choice via the descriptor's
|
|
118
|
+
// `RoomOption.ratePlans`. When the user hasn't picked one — e.g.
|
|
119
|
+
// the property has no rate plans configured — the commit fails
|
|
120
|
+
// with a helpful reason; the bridge no longer guesses.
|
|
121
|
+
if (!firstRoom.ratePlanId) {
|
|
122
|
+
return {
|
|
123
|
+
status: "failed",
|
|
124
|
+
orderRef: "",
|
|
125
|
+
upstreamPayload: {
|
|
126
|
+
reason: "accommodation_commit_missing_inputs",
|
|
127
|
+
need: ["accommodation.rooms[0].ratePlanId"],
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const bridge = await options.commitBridge({
|
|
132
|
+
propertyId: request.entityId,
|
|
133
|
+
roomTypeId: firstRoom.optionUnitId,
|
|
134
|
+
ratePlanId: firstRoom.ratePlanId,
|
|
135
|
+
checkInDate: range.checkIn,
|
|
136
|
+
checkOutDate: range.checkOut,
|
|
137
|
+
roomCount: firstRoom.quantity,
|
|
138
|
+
adults,
|
|
139
|
+
children,
|
|
140
|
+
infants,
|
|
141
|
+
dailyRates,
|
|
142
|
+
personId: extractPersonId(request.party),
|
|
143
|
+
organizationId: extractOrganizationId(request.party),
|
|
144
|
+
contact,
|
|
145
|
+
passengers,
|
|
146
|
+
notes: typeof draft.internalNotes === "string" ? draft.internalNotes : null,
|
|
147
|
+
});
|
|
148
|
+
if (bridge.status !== "ok" || !bridge.bookingId) {
|
|
149
|
+
return {
|
|
150
|
+
status: "failed",
|
|
151
|
+
orderRef: "",
|
|
152
|
+
upstreamPayload: { reason: bridge.reason ?? "accommodation_commit_failed" },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
status: "held",
|
|
157
|
+
orderRef: bridge.bookingNumber ?? bridge.bookingId,
|
|
158
|
+
pricing: request.pricing,
|
|
159
|
+
upstreamPayload: { bridgeBookingId: bridge.bookingId },
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
async placeHold(_ctx, request) {
|
|
163
|
+
const token = request.draftId ?? `acc_${Date.now().toString(36)}`;
|
|
164
|
+
return {
|
|
165
|
+
holdToken: token,
|
|
166
|
+
expiresAt: new Date(Date.now() + request.ttlMs),
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
async releaseHold(_ctx, _holdToken) {
|
|
170
|
+
// No-op until inventory reservation against rate plans + room
|
|
171
|
+
// inventory lands.
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// ─────────────────────────────────────────────────────────────────
|
|
176
|
+
// Pricing heuristic
|
|
177
|
+
// ─────────────────────────────────────────────────────────────────
|
|
178
|
+
function computeBestEffortPricing(shape, draft) {
|
|
179
|
+
const range = draft.configure?.dateRange;
|
|
180
|
+
if (!range?.checkIn || !range?.checkOut)
|
|
181
|
+
return undefined;
|
|
182
|
+
const nights = nightsBetween(range.checkIn, range.checkOut);
|
|
183
|
+
if (nights <= 0)
|
|
184
|
+
return undefined;
|
|
185
|
+
const rooms = draft.accommodation?.rooms ?? [];
|
|
186
|
+
const totalRooms = rooms.length === 0 ? 1 : rooms.reduce((sum, r) => sum + r.quantity, 0);
|
|
187
|
+
if (totalRooms <= 0)
|
|
188
|
+
return undefined;
|
|
189
|
+
// Pick the cheapest available room option's hint (when surfaced).
|
|
190
|
+
// The journey's Configure step doesn't pin a rate until the user
|
|
191
|
+
// picks a room — this is just a starter total.
|
|
192
|
+
const roomOptions = (shape.accommodation?.roomOptions ?? []);
|
|
193
|
+
const hint = roomOptions
|
|
194
|
+
.map((r) => r.baseRateHint ?? 0)
|
|
195
|
+
.filter((n) => n > 0)
|
|
196
|
+
.sort((a, b) => a - b)[0];
|
|
197
|
+
if (!hint)
|
|
198
|
+
return undefined;
|
|
199
|
+
const totalCents = hint * nights * totalRooms;
|
|
200
|
+
return {
|
|
201
|
+
base_amount: totalCents,
|
|
202
|
+
taxes: 0,
|
|
203
|
+
fees: 0,
|
|
204
|
+
surcharges: 0,
|
|
205
|
+
// Currency is unknown until the rate plan is picked — fall back
|
|
206
|
+
// to EUR as the most common storefront default. Real commits
|
|
207
|
+
// override at quote time.
|
|
208
|
+
currency: "EUR",
|
|
209
|
+
breakdown: {
|
|
210
|
+
lines: [
|
|
211
|
+
{
|
|
212
|
+
kind: "accommodations",
|
|
213
|
+
label: `${totalRooms} room × ${nights} night${nights === 1 ? "" : "s"}`,
|
|
214
|
+
quantity: nights * totalRooms,
|
|
215
|
+
unitAmount: hint,
|
|
216
|
+
totalAmount: totalCents,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
subtotal: totalCents,
|
|
220
|
+
taxTotal: 0,
|
|
221
|
+
total: totalCents,
|
|
222
|
+
nights,
|
|
223
|
+
rooms: totalRooms,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function nightsBetween(checkIn, checkOut) {
|
|
228
|
+
const inDate = new Date(checkIn);
|
|
229
|
+
const outDate = new Date(checkOut);
|
|
230
|
+
if (Number.isNaN(inDate.getTime()) || Number.isNaN(outDate.getTime()))
|
|
231
|
+
return 0;
|
|
232
|
+
const ms = outDate.getTime() - inDate.getTime();
|
|
233
|
+
return Math.round(ms / (1000 * 60 * 60 * 24));
|
|
234
|
+
}
|
|
235
|
+
function extractPersonId(party) {
|
|
236
|
+
if (!party)
|
|
237
|
+
return undefined;
|
|
238
|
+
const v = party.personId;
|
|
239
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
240
|
+
}
|
|
241
|
+
function extractOrganizationId(party) {
|
|
242
|
+
if (!party)
|
|
243
|
+
return undefined;
|
|
244
|
+
const v = party.organizationId;
|
|
245
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
246
|
+
}
|
|
247
|
+
function readPricingTotalCents(pricing) {
|
|
248
|
+
if (!pricing)
|
|
249
|
+
return 0;
|
|
250
|
+
return (pricing.base_amount ?? 0) + (pricing.taxes ?? 0);
|
|
251
|
+
}
|
|
252
|
+
function readPricingCurrency(pricing) {
|
|
253
|
+
return pricing?.currency;
|
|
254
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@voyantjs/accommodations/booking-engine` — owned-arm booking
|
|
3
|
+
* handler for the accommodation vertical.
|
|
4
|
+
*
|
|
5
|
+
* Per `docs/architecture/booking-journey-architecture.md` §6.
|
|
6
|
+
*/
|
|
7
|
+
export { type AccommodationCommitBridge, type AccommodationCommitBridgeInput, type AccommodationCommitBridgeResult, type AccommodationContentLoader, type CreateAccommodationBookingHandlerOptions, createAccommodationBookingHandler, } from "./handler.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/booking-engine/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,EACpC,KAAK,0BAA0B,EAC/B,KAAK,wCAAwC,EAC7C,iCAAiC,GAClC,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog plane field policy for `packages/accommodations`.
|
|
3
|
+
*
|
|
4
|
+
* The accommodation vertical's catalog entry is the **room type** — the
|
|
5
|
+
* sellable variant within a property. Properties themselves live in
|
|
6
|
+
* `packages/facilities` and are referenced via `propertyId`.
|
|
7
|
+
*
|
|
8
|
+
* Scope of this file:
|
|
9
|
+
* - The root `room_types` table (from `schema-inventory.ts`).
|
|
10
|
+
* - Provenance + identity fields the catalog plane needs to track.
|
|
11
|
+
*
|
|
12
|
+
* Out of scope for this resale package:
|
|
13
|
+
* - hotel-managed physical room units and their lifecycle.
|
|
14
|
+
* - `rate_plans` — variant pricing axis with its own lifecycle.
|
|
15
|
+
* - `meal_plans` — board-basis variants, structural choice axis.
|
|
16
|
+
* - `room_type_bed_configs` — composite list with managed structural
|
|
17
|
+
* fields under each row.
|
|
18
|
+
*/
|
|
19
|
+
import { type FieldPolicyInput } from "@voyantjs/catalog/contract";
|
|
20
|
+
declare const ACCOMMODATION_FIELD_POLICY: FieldPolicyInput[];
|
|
21
|
+
export declare const accommodationCatalogPolicy: import("@voyantjs/catalog").FieldPolicy[];
|
|
22
|
+
export { ACCOMMODATION_FIELD_POLICY };
|
|
23
|
+
//# sourceMappingURL=catalog-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAErF,QAAA,MAAM,0BAA0B,EAAE,gBAAgB,EAsZjD,CAAA;AAED,eAAO,MAAM,0BAA0B,2CAAgD,CAAA;AAEvF,OAAO,EAAE,0BAA0B,EAAE,CAAA"}
|