@voyantjs/hospitality 0.20.0 → 0.21.1
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/dist/booking-engine/handler.d.ts +105 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +255 -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.map +1 -1
- package/dist/catalog-policy.js +15 -1
- 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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/routes-accommodation.d.ts +12 -4
- package/dist/routes-accommodation.d.ts.map +1 -1
- package/dist/routes-inventory.d.ts +16 -16
- package/dist/routes-operations.d.ts +29 -29
- package/dist/routes-stays.d.ts +13 -13
- package/dist/routes.d.ts +41 -33
- package/dist/routes.d.ts.map +1 -1
- package/dist/schema-bookings.d.ts +3 -3
- package/dist/schema-inventory.d.ts +34 -0
- package/dist/schema-inventory.d.ts.map +1 -1
- package/dist/schema-inventory.js +10 -0
- package/dist/schema-operations.d.ts +1 -1
- 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 +49 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/service-bookings.d.ts +111 -0
- package/dist/service-bookings.d.ts.map +1 -0
- package/dist/service-bookings.js +147 -0
- package/dist/service-catalog-plane.d.ts.map +1 -1
- package/dist/service-catalog-plane.js +1 -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 +45 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +254 -0
- package/dist/service.d.ts +41 -33
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +2 -0
- package/dist/validation-accommodation.d.ts +4 -0
- package/dist/validation-accommodation.d.ts.map +1 -1
- package/dist/validation-accommodation.js +2 -0
- package/dist/validation-operations.d.ts +20 -20
- package/dist/validation-shared.d.ts +5 -5
- package/package.json +18 -8
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-arm booking handler for the `hospitality` vertical.
|
|
3
|
+
*
|
|
4
|
+
* Per `docs/architecture/booking-journey-architecture.md` §6.
|
|
5
|
+
*
|
|
6
|
+
* Phase B scope (deliberately narrow):
|
|
7
|
+
* - `computeQuote` projects the property's hospitality 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` returns `failed:not_yet_implemented` — the real
|
|
14
|
+
* stay-booking-items + daily-rates write is a follow-up that
|
|
15
|
+
* belongs in the hospitality module's own commit primitive.
|
|
16
|
+
*
|
|
17
|
+
* Templates wire this handler at boot the same way they wire the
|
|
18
|
+
* products handler. Once `commit` ships, the registry is genuinely
|
|
19
|
+
* multi-vertical with no further dispatch changes.
|
|
20
|
+
*/
|
|
21
|
+
import type { OwnedBookingHandler, OwnedHandlerContext } from "@voyantjs/catalog/booking-engine";
|
|
22
|
+
import type { HospitalityContent } from "../content-shape.js";
|
|
23
|
+
/**
|
|
24
|
+
* Caller-supplied loader — keeps the handler free of a hard
|
|
25
|
+
* dependency on the template's content service wiring (which spans
|
|
26
|
+
* sourced + owned + overlays). Templates wire this to
|
|
27
|
+
* `getHospitalityContent` from `@voyantjs/hospitality/service-content`.
|
|
28
|
+
*/
|
|
29
|
+
export type HospitalityContentLoader = (ctx: OwnedHandlerContext, entityId: string) => Promise<HospitalityContent | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Subset of `hospitalityBookingsService.createStayBooking`'s
|
|
32
|
+
* input. Structural so the handler stays free of a self-import
|
|
33
|
+
* cycle.
|
|
34
|
+
*/
|
|
35
|
+
export interface HospitalityCommitBridgeInput {
|
|
36
|
+
propertyId: string;
|
|
37
|
+
roomTypeId: string;
|
|
38
|
+
ratePlanId: string;
|
|
39
|
+
mealPlanId?: string | null;
|
|
40
|
+
checkInDate: string;
|
|
41
|
+
checkOutDate: string;
|
|
42
|
+
roomCount?: number;
|
|
43
|
+
adults?: number;
|
|
44
|
+
children?: number;
|
|
45
|
+
infants?: number;
|
|
46
|
+
dailyRates: Array<{
|
|
47
|
+
sellCurrency: string;
|
|
48
|
+
sellAmountCents?: number | null;
|
|
49
|
+
costCurrency?: string | null;
|
|
50
|
+
costAmountCents?: number | null;
|
|
51
|
+
}>;
|
|
52
|
+
personId?: string | null;
|
|
53
|
+
organizationId?: string | null;
|
|
54
|
+
contact: {
|
|
55
|
+
firstName: string;
|
|
56
|
+
lastName: string;
|
|
57
|
+
email?: string | null;
|
|
58
|
+
phone?: string | null;
|
|
59
|
+
country?: string | null;
|
|
60
|
+
};
|
|
61
|
+
passengers: Array<{
|
|
62
|
+
firstName: string;
|
|
63
|
+
lastName: string;
|
|
64
|
+
email?: string | null;
|
|
65
|
+
phone?: string | null;
|
|
66
|
+
travelerCategory?: "adult" | "child" | "infant" | "senior" | "other" | null;
|
|
67
|
+
isPrimary?: boolean | null;
|
|
68
|
+
}>;
|
|
69
|
+
notes?: string | null;
|
|
70
|
+
}
|
|
71
|
+
export interface HospitalityCommitBridgeResult {
|
|
72
|
+
status: "ok" | "failed";
|
|
73
|
+
bookingId?: string;
|
|
74
|
+
bookingNumber?: string;
|
|
75
|
+
reason?: string;
|
|
76
|
+
}
|
|
77
|
+
export type HospitalityCommitBridge = (input: HospitalityCommitBridgeInput, options?: {
|
|
78
|
+
userId?: string;
|
|
79
|
+
}) => Promise<HospitalityCommitBridgeResult>;
|
|
80
|
+
export interface CreateHospitalityBookingHandlerOptions {
|
|
81
|
+
/** Loader for the property's content payload. */
|
|
82
|
+
loadContent: HospitalityContentLoader;
|
|
83
|
+
/**
|
|
84
|
+
* Default min/max nights when the supplier hasn't declared bounds.
|
|
85
|
+
* The journey's date-range sub-step uses these as guard rails.
|
|
86
|
+
*/
|
|
87
|
+
defaultMinNights?: number;
|
|
88
|
+
defaultMaxNights?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Caller-supplied bridge to
|
|
91
|
+
* `hospitalityBookingsService.createStayBooking`. When omitted,
|
|
92
|
+
* commit returns `failed:hospitality_commit_bridge_not_wired`.
|
|
93
|
+
*
|
|
94
|
+
* The journey doesn't currently surface room-type / rate-plan
|
|
95
|
+
* picks at the granularity reserveStay needs (specifically
|
|
96
|
+
* `ratePlanId` and per-night `dailyRates`). Until the journey's
|
|
97
|
+
* Accommodation step + content shape extend to expose those, the
|
|
98
|
+
* caller may map drafts to a "best guess" room/rate from the
|
|
99
|
+
* descriptor's room-options projection — we leave that decision
|
|
100
|
+
* to the template.
|
|
101
|
+
*/
|
|
102
|
+
commitBridge?: HospitalityCommitBridge;
|
|
103
|
+
}
|
|
104
|
+
export declare function createHospitalityBookingHandler(options: CreateHospitalityBookingHandlerOptions): OwnedBookingHandler;
|
|
105
|
+
//# 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;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAMV,mBAAmB,EACnB,mBAAmB,EAEpB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAuB7D;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,GAAG,EAAE,mBAAmB,EACxB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAA;AAEvC;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C,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,6BAA6B;IAC5C,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,uBAAuB,GAAG,CACpC,KAAK,EAAE,4BAA4B,EACnC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KAC1B,OAAO,CAAC,6BAA6B,CAAC,CAAA;AAE3C,MAAM,WAAW,sCAAsC;IACrD,iDAAiD;IACjD,WAAW,EAAE,wBAAwB,CAAA;IACrC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,uBAAuB,CAAA;CACvC;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,sCAAsC,GAC9C,mBAAmB,CAkLrB"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-arm booking handler for the `hospitality` vertical.
|
|
3
|
+
*
|
|
4
|
+
* Per `docs/architecture/booking-journey-architecture.md` §6.
|
|
5
|
+
*
|
|
6
|
+
* Phase B scope (deliberately narrow):
|
|
7
|
+
* - `computeQuote` projects the property's hospitality 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` returns `failed:not_yet_implemented` — the real
|
|
14
|
+
* stay-booking-items + daily-rates write is a follow-up that
|
|
15
|
+
* belongs in the hospitality module's own commit primitive.
|
|
16
|
+
*
|
|
17
|
+
* Templates wire this handler at boot the same way they wire the
|
|
18
|
+
* products handler. Once `commit` ships, the registry is genuinely
|
|
19
|
+
* multi-vertical with no further dispatch changes.
|
|
20
|
+
*/
|
|
21
|
+
import { buildHospitalityDraftShape } from "../draft-shape.js";
|
|
22
|
+
export function createHospitalityBookingHandler(options) {
|
|
23
|
+
return {
|
|
24
|
+
entityModule: "hospitality",
|
|
25
|
+
async computeQuote(ctx, request) {
|
|
26
|
+
const content = await options.loadContent(ctx, request.entityId);
|
|
27
|
+
if (!content) {
|
|
28
|
+
return { available: false, invalidReason: "property_not_found" };
|
|
29
|
+
}
|
|
30
|
+
const shape = buildHospitalityDraftShape(content, {
|
|
31
|
+
minNights: options.defaultMinNights,
|
|
32
|
+
maxNights: options.defaultMaxNights,
|
|
33
|
+
});
|
|
34
|
+
const draft = (request.draft ?? {});
|
|
35
|
+
const pricing = computeBestEffortPricing(shape, draft);
|
|
36
|
+
return {
|
|
37
|
+
available: true,
|
|
38
|
+
pricing,
|
|
39
|
+
shape,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
async commit(_ctx, request) {
|
|
43
|
+
if (!options.commitBridge) {
|
|
44
|
+
return {
|
|
45
|
+
status: "failed",
|
|
46
|
+
orderRef: "",
|
|
47
|
+
upstreamPayload: { reason: "hospitality_commit_bridge_not_wired" },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const draft = (request.draft ?? {});
|
|
51
|
+
const range = draft.configure?.dateRange;
|
|
52
|
+
if (!range?.checkIn || !range?.checkOut) {
|
|
53
|
+
return {
|
|
54
|
+
status: "failed",
|
|
55
|
+
orderRef: "",
|
|
56
|
+
upstreamPayload: {
|
|
57
|
+
reason: "hospitality_commit_missing_inputs",
|
|
58
|
+
need: ["dateRange.checkIn", "dateRange.checkOut"],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Map the draft's room selection into a single (roomType, ratePlan) pair.
|
|
63
|
+
// The journey's Accommodation step picks an option_unit_id (which is
|
|
64
|
+
// hospitality's `roomTypeId`); rate plan defaults to the room's first
|
|
65
|
+
// available — templates can override the default by augmenting the
|
|
66
|
+
// bridge output. When no room is picked yet, the commit fails fast.
|
|
67
|
+
const firstRoom = draft.accommodation?.rooms?.[0];
|
|
68
|
+
if (!firstRoom) {
|
|
69
|
+
return {
|
|
70
|
+
status: "failed",
|
|
71
|
+
orderRef: "",
|
|
72
|
+
upstreamPayload: {
|
|
73
|
+
reason: "hospitality_commit_missing_inputs",
|
|
74
|
+
need: ["accommodation.rooms[0]"],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const adults = draft.configure?.pax?.adult ?? draft.travelers?.length ?? 1;
|
|
79
|
+
const children = draft.configure?.pax?.child ?? 0;
|
|
80
|
+
const infants = draft.configure?.pax?.infant ?? 0;
|
|
81
|
+
const billing = draft.billing ?? {};
|
|
82
|
+
const contact = {
|
|
83
|
+
firstName: billing.contact?.firstName ?? "",
|
|
84
|
+
lastName: billing.contact?.lastName ?? "",
|
|
85
|
+
email: billing.contact?.email ?? null,
|
|
86
|
+
phone: billing.contact?.phone ?? null,
|
|
87
|
+
country: billing.address?.country ?? null,
|
|
88
|
+
};
|
|
89
|
+
const passengers = (draft.travelers ?? []).map((t, idx) => ({
|
|
90
|
+
firstName: t.firstName,
|
|
91
|
+
lastName: t.lastName,
|
|
92
|
+
travelerCategory: t.band === "child" || t.band === "infant"
|
|
93
|
+
? t.band
|
|
94
|
+
: "adult",
|
|
95
|
+
isPrimary: idx === 0,
|
|
96
|
+
}));
|
|
97
|
+
if (passengers.length === 0) {
|
|
98
|
+
passengers.push({
|
|
99
|
+
firstName: contact.firstName,
|
|
100
|
+
lastName: contact.lastName,
|
|
101
|
+
travelerCategory: "adult",
|
|
102
|
+
isPrimary: true,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// Per-night rate hint from the draft's pricing breakdown
|
|
106
|
+
// (computed by the handler earlier). The bridge expects
|
|
107
|
+
// `dailyRates[]` with one entry per night — we replicate the
|
|
108
|
+
// averaged hint across nights as a placeholder; production
|
|
109
|
+
// needs supplier-provided rate-plan rows.
|
|
110
|
+
const nights = nightsBetween(range.checkIn, range.checkOut);
|
|
111
|
+
const totalCents = readPricingTotalCents(request.pricing);
|
|
112
|
+
const perNightCents = nights > 0 && totalCents > 0 ? Math.round(totalCents / nights / firstRoom.quantity) : 0;
|
|
113
|
+
const currency = readPricingCurrency(request.pricing) ?? "EUR";
|
|
114
|
+
const dailyRates = Array.from({ length: nights }, () => ({
|
|
115
|
+
sellCurrency: currency,
|
|
116
|
+
sellAmountCents: perNightCents,
|
|
117
|
+
}));
|
|
118
|
+
// The journey now surfaces rate-plan choice via the descriptor's
|
|
119
|
+
// `RoomOption.ratePlans`. When the user hasn't picked one — e.g.
|
|
120
|
+
// the property has no rate plans configured — the commit fails
|
|
121
|
+
// with a helpful reason; the bridge no longer guesses.
|
|
122
|
+
if (!firstRoom.ratePlanId) {
|
|
123
|
+
return {
|
|
124
|
+
status: "failed",
|
|
125
|
+
orderRef: "",
|
|
126
|
+
upstreamPayload: {
|
|
127
|
+
reason: "hospitality_commit_missing_inputs",
|
|
128
|
+
need: ["accommodation.rooms[0].ratePlanId"],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const bridge = await options.commitBridge({
|
|
133
|
+
propertyId: request.entityId,
|
|
134
|
+
roomTypeId: firstRoom.optionUnitId,
|
|
135
|
+
ratePlanId: firstRoom.ratePlanId,
|
|
136
|
+
checkInDate: range.checkIn,
|
|
137
|
+
checkOutDate: range.checkOut,
|
|
138
|
+
roomCount: firstRoom.quantity,
|
|
139
|
+
adults,
|
|
140
|
+
children,
|
|
141
|
+
infants,
|
|
142
|
+
dailyRates,
|
|
143
|
+
personId: extractPersonId(request.party),
|
|
144
|
+
organizationId: extractOrganizationId(request.party),
|
|
145
|
+
contact,
|
|
146
|
+
passengers,
|
|
147
|
+
notes: typeof draft.internalNotes === "string" ? draft.internalNotes : null,
|
|
148
|
+
});
|
|
149
|
+
if (bridge.status !== "ok" || !bridge.bookingId) {
|
|
150
|
+
return {
|
|
151
|
+
status: "failed",
|
|
152
|
+
orderRef: "",
|
|
153
|
+
upstreamPayload: { reason: bridge.reason ?? "hospitality_commit_failed" },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
status: "held",
|
|
158
|
+
orderRef: bridge.bookingNumber ?? bridge.bookingId,
|
|
159
|
+
pricing: request.pricing,
|
|
160
|
+
upstreamPayload: { bridgeBookingId: bridge.bookingId },
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
async placeHold(_ctx, request) {
|
|
164
|
+
const token = request.draftId ?? `hosp_${Date.now().toString(36)}`;
|
|
165
|
+
return {
|
|
166
|
+
holdToken: token,
|
|
167
|
+
expiresAt: new Date(Date.now() + request.ttlMs),
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
async releaseHold(_ctx, _holdToken) {
|
|
171
|
+
// No-op until inventory reservation against rate plans + room
|
|
172
|
+
// inventory lands.
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// ─────────────────────────────────────────────────────────────────
|
|
177
|
+
// Pricing heuristic
|
|
178
|
+
// ─────────────────────────────────────────────────────────────────
|
|
179
|
+
function computeBestEffortPricing(shape, draft) {
|
|
180
|
+
const range = draft.configure?.dateRange;
|
|
181
|
+
if (!range?.checkIn || !range?.checkOut)
|
|
182
|
+
return undefined;
|
|
183
|
+
const nights = nightsBetween(range.checkIn, range.checkOut);
|
|
184
|
+
if (nights <= 0)
|
|
185
|
+
return undefined;
|
|
186
|
+
const rooms = draft.accommodation?.rooms ?? [];
|
|
187
|
+
const totalRooms = rooms.length === 0 ? 1 : rooms.reduce((sum, r) => sum + r.quantity, 0);
|
|
188
|
+
if (totalRooms <= 0)
|
|
189
|
+
return undefined;
|
|
190
|
+
// Pick the cheapest available room option's hint (when surfaced).
|
|
191
|
+
// The journey's Configure step doesn't pin a rate until the user
|
|
192
|
+
// picks a room — this is just a starter total.
|
|
193
|
+
const roomOptions = (shape.accommodation?.roomOptions ?? []);
|
|
194
|
+
const hint = roomOptions
|
|
195
|
+
.map((r) => r.baseRateHint ?? 0)
|
|
196
|
+
.filter((n) => n > 0)
|
|
197
|
+
.sort((a, b) => a - b)[0];
|
|
198
|
+
if (!hint)
|
|
199
|
+
return undefined;
|
|
200
|
+
const totalCents = hint * nights * totalRooms;
|
|
201
|
+
return {
|
|
202
|
+
base_amount: totalCents,
|
|
203
|
+
taxes: 0,
|
|
204
|
+
fees: 0,
|
|
205
|
+
surcharges: 0,
|
|
206
|
+
// Currency is unknown until the rate plan is picked — fall back
|
|
207
|
+
// to EUR as the most common storefront default. Real commits
|
|
208
|
+
// override at quote time.
|
|
209
|
+
currency: "EUR",
|
|
210
|
+
breakdown: {
|
|
211
|
+
lines: [
|
|
212
|
+
{
|
|
213
|
+
kind: "accommodation",
|
|
214
|
+
label: `${totalRooms} room × ${nights} night${nights === 1 ? "" : "s"}`,
|
|
215
|
+
quantity: nights * totalRooms,
|
|
216
|
+
unitAmount: hint,
|
|
217
|
+
totalAmount: totalCents,
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
subtotal: totalCents,
|
|
221
|
+
taxTotal: 0,
|
|
222
|
+
total: totalCents,
|
|
223
|
+
nights,
|
|
224
|
+
rooms: totalRooms,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function nightsBetween(checkIn, checkOut) {
|
|
229
|
+
const inDate = new Date(checkIn);
|
|
230
|
+
const outDate = new Date(checkOut);
|
|
231
|
+
if (Number.isNaN(inDate.getTime()) || Number.isNaN(outDate.getTime()))
|
|
232
|
+
return 0;
|
|
233
|
+
const ms = outDate.getTime() - inDate.getTime();
|
|
234
|
+
return Math.round(ms / (1000 * 60 * 60 * 24));
|
|
235
|
+
}
|
|
236
|
+
function extractPersonId(party) {
|
|
237
|
+
if (!party)
|
|
238
|
+
return undefined;
|
|
239
|
+
const v = party.personId;
|
|
240
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
241
|
+
}
|
|
242
|
+
function extractOrganizationId(party) {
|
|
243
|
+
if (!party)
|
|
244
|
+
return undefined;
|
|
245
|
+
const v = party.organizationId;
|
|
246
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
247
|
+
}
|
|
248
|
+
function readPricingTotalCents(pricing) {
|
|
249
|
+
if (!pricing)
|
|
250
|
+
return 0;
|
|
251
|
+
return (pricing.base_amount ?? 0) + (pricing.taxes ?? 0);
|
|
252
|
+
}
|
|
253
|
+
function readPricingCurrency(pricing) {
|
|
254
|
+
return pricing?.currency;
|
|
255
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@voyantjs/hospitality/booking-engine` — owned-arm booking
|
|
3
|
+
* handler for the hospitality vertical.
|
|
4
|
+
*
|
|
5
|
+
* Per `docs/architecture/booking-journey-architecture.md` §6.
|
|
6
|
+
*/
|
|
7
|
+
export { type CreateHospitalityBookingHandlerOptions, createHospitalityBookingHandler, type HospitalityCommitBridge, type HospitalityCommitBridgeInput, type HospitalityCommitBridgeResult, type HospitalityContentLoader, } 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,sCAAsC,EAC3C,+BAA+B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAErF,QAAA,MAAM,wBAAwB,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAErF,QAAA,MAAM,wBAAwB,EAAE,gBAAgB,EAwY/C,CAAA;AAED,eAAO,MAAM,wBAAwB,2CAA8C,CAAA;AAEnF,OAAO,EAAE,wBAAwB,EAAE,CAAA"}
|
package/dist/catalog-policy.js
CHANGED
|
@@ -25,7 +25,7 @@ const HOSPITALITY_FIELD_POLICY = [
|
|
|
25
25
|
class: "managed",
|
|
26
26
|
merge: "source-only",
|
|
27
27
|
drift: "critical",
|
|
28
|
-
reindex: "
|
|
28
|
+
reindex: "facet-affecting",
|
|
29
29
|
snapshot: "on-book",
|
|
30
30
|
query: "indexed-column",
|
|
31
31
|
localized: false,
|
|
@@ -134,6 +134,20 @@ const HOSPITALITY_FIELD_POLICY = [
|
|
|
134
134
|
overrideFriction: "none",
|
|
135
135
|
sourceFreshness: "sync",
|
|
136
136
|
},
|
|
137
|
+
{
|
|
138
|
+
path: "supplierId",
|
|
139
|
+
class: "structural",
|
|
140
|
+
merge: "source-only",
|
|
141
|
+
drift: "high",
|
|
142
|
+
reindex: "facet-affecting",
|
|
143
|
+
snapshot: "on-book",
|
|
144
|
+
query: "indexed-column",
|
|
145
|
+
localized: false,
|
|
146
|
+
visibility: ["staff"],
|
|
147
|
+
editRole: "none",
|
|
148
|
+
overrideFriction: "none",
|
|
149
|
+
sourceFreshness: "sync",
|
|
150
|
+
},
|
|
137
151
|
// ── Merchandisable / marketing ──────────────────────────────────────────
|
|
138
152
|
{
|
|
139
153
|
path: "name",
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hospitality content shape — the rich detail-page content shape
|
|
3
|
+
* returned by `getContent` for sourced room types (bedbanks like
|
|
4
|
+
* Hotelbeds / Expedia, direct-property feeds, hotel groups via Voyant
|
|
5
|
+
* Connect).
|
|
6
|
+
*
|
|
7
|
+
* Per sourced-content §3.6, the hospitality content aggregate is
|
|
8
|
+
* `{ hotel, room_types[], rate_plans[], meal_plans[], amenities[],
|
|
9
|
+
* policies[] }` — one payload returned by a single `getContent`.
|
|
10
|
+
* Pricing stays out (volatile-live, flows through `liveResolve`).
|
|
11
|
+
*
|
|
12
|
+
* The aggregate is **per property** — one row per property × locale ×
|
|
13
|
+
* market — even though the sellable catalog entry is a room type.
|
|
14
|
+
* That's because bedbanks return whole-property payloads and splitting
|
|
15
|
+
* by room type would multiply cache writes and refresh work without
|
|
16
|
+
* benefit. The vertical's read service projects the cached property
|
|
17
|
+
* payload to the requested room-type detail page.
|
|
18
|
+
*
|
|
19
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.2, §3.5.4, §3.6.
|
|
20
|
+
*/
|
|
21
|
+
import { type ContentOverlay, type MergeOverlaysOptions } from "@voyantjs/catalog";
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
export declare const HOSPITALITY_CONTENT_SCHEMA_VERSION = "hospitality/v1";
|
|
24
|
+
export declare const hotelSummarySchema: z.ZodObject<{
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
28
|
+
star_rating: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
29
|
+
hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
30
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
31
|
+
brand: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
|
+
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
33
|
+
city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
34
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
35
|
+
postal_code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
36
|
+
latitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
37
|
+
longitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
38
|
+
check_in_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
39
|
+
check_out_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
export declare const hospitalityRoomTypeSchema: z.ZodObject<{
|
|
42
|
+
id: z.ZodString;
|
|
43
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
44
|
+
name: z.ZodString;
|
|
45
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
46
|
+
room_class: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
47
|
+
view: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
48
|
+
bedrooms: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
49
|
+
beds: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
50
|
+
size_sqm: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
51
|
+
max_adults: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
52
|
+
max_children: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
53
|
+
max_occupancy: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
54
|
+
amenities: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
55
|
+
images: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
export declare const hospitalityRatePlanSchema: z.ZodObject<{
|
|
58
|
+
id: z.ZodString;
|
|
59
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
60
|
+
name: z.ZodString;
|
|
61
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
62
|
+
charge_frequency: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
63
|
+
per_night: "per_night";
|
|
64
|
+
per_stay: "per_stay";
|
|
65
|
+
}>>>;
|
|
66
|
+
applies_to_room_type_ids: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
67
|
+
cancellation_policy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
68
|
+
inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
export declare const hospitalityMealPlanSchema: z.ZodObject<{
|
|
71
|
+
id: z.ZodString;
|
|
72
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
73
|
+
name: z.ZodString;
|
|
74
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
75
|
+
basis: z.ZodString;
|
|
76
|
+
inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
export declare const hospitalityAmenitySchema: z.ZodObject<{
|
|
79
|
+
id: z.ZodString;
|
|
80
|
+
category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
81
|
+
name: z.ZodString;
|
|
82
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
83
|
+
is_free: z.ZodOptional<z.ZodBoolean>;
|
|
84
|
+
}, z.core.$strip>;
|
|
85
|
+
export declare const hospitalityPolicySchema: z.ZodObject<{
|
|
86
|
+
kind: z.ZodEnum<{
|
|
87
|
+
supplier_notes: "supplier_notes";
|
|
88
|
+
cancellation: "cancellation";
|
|
89
|
+
payment: "payment";
|
|
90
|
+
requirements: "requirements";
|
|
91
|
+
check_in: "check_in";
|
|
92
|
+
}>;
|
|
93
|
+
body: z.ZodString;
|
|
94
|
+
rules: z.ZodOptional<z.ZodUnknown>;
|
|
95
|
+
}, z.core.$strip>;
|
|
96
|
+
export declare const hospitalityContentSchema: z.ZodObject<{
|
|
97
|
+
hotel: z.ZodObject<{
|
|
98
|
+
id: z.ZodString;
|
|
99
|
+
name: z.ZodString;
|
|
100
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
101
|
+
star_rating: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
102
|
+
hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
103
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
104
|
+
brand: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
105
|
+
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
106
|
+
city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
107
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
108
|
+
postal_code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
109
|
+
latitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
110
|
+
longitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
111
|
+
check_in_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
112
|
+
check_out_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
room_types: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
115
|
+
id: z.ZodString;
|
|
116
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
117
|
+
name: z.ZodString;
|
|
118
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
119
|
+
room_class: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
120
|
+
view: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
121
|
+
bedrooms: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
122
|
+
beds: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
123
|
+
size_sqm: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
124
|
+
max_adults: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
125
|
+
max_children: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
126
|
+
max_occupancy: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
127
|
+
amenities: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
128
|
+
images: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
129
|
+
}, z.core.$strip>>>;
|
|
130
|
+
rate_plans: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
131
|
+
id: z.ZodString;
|
|
132
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
133
|
+
name: z.ZodString;
|
|
134
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
135
|
+
charge_frequency: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
136
|
+
per_night: "per_night";
|
|
137
|
+
per_stay: "per_stay";
|
|
138
|
+
}>>>;
|
|
139
|
+
applies_to_room_type_ids: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
140
|
+
cancellation_policy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
141
|
+
inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
142
|
+
}, z.core.$strip>>>;
|
|
143
|
+
meal_plans: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
144
|
+
id: z.ZodString;
|
|
145
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
146
|
+
name: z.ZodString;
|
|
147
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
148
|
+
basis: z.ZodString;
|
|
149
|
+
inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
150
|
+
}, z.core.$strip>>>;
|
|
151
|
+
amenities: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
152
|
+
id: z.ZodString;
|
|
153
|
+
category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
154
|
+
name: z.ZodString;
|
|
155
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
156
|
+
is_free: z.ZodOptional<z.ZodBoolean>;
|
|
157
|
+
}, z.core.$strip>>>;
|
|
158
|
+
policies: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
159
|
+
kind: z.ZodEnum<{
|
|
160
|
+
supplier_notes: "supplier_notes";
|
|
161
|
+
cancellation: "cancellation";
|
|
162
|
+
payment: "payment";
|
|
163
|
+
requirements: "requirements";
|
|
164
|
+
check_in: "check_in";
|
|
165
|
+
}>;
|
|
166
|
+
body: z.ZodString;
|
|
167
|
+
rules: z.ZodOptional<z.ZodUnknown>;
|
|
168
|
+
}, z.core.$strip>>>;
|
|
169
|
+
}, z.core.$strip>;
|
|
170
|
+
export type HospitalityContent = z.infer<typeof hospitalityContentSchema>;
|
|
171
|
+
export type HotelSummary = z.infer<typeof hotelSummarySchema>;
|
|
172
|
+
export type HospitalityRoomType = z.infer<typeof hospitalityRoomTypeSchema>;
|
|
173
|
+
export type HospitalityRatePlan = z.infer<typeof hospitalityRatePlanSchema>;
|
|
174
|
+
export type HospitalityMealPlan = z.infer<typeof hospitalityMealPlanSchema>;
|
|
175
|
+
export type HospitalityAmenity = z.infer<typeof hospitalityAmenitySchema>;
|
|
176
|
+
export type HospitalityPolicy = z.infer<typeof hospitalityPolicySchema>;
|
|
177
|
+
export declare function validateHospitalityContent(payload: unknown): {
|
|
178
|
+
valid: true;
|
|
179
|
+
content: HospitalityContent;
|
|
180
|
+
} | {
|
|
181
|
+
valid: false;
|
|
182
|
+
reason: string;
|
|
183
|
+
};
|
|
184
|
+
export declare function mergeOverlaysIntoHospitalityContent(payload: HospitalityContent, overlays: ReadonlyArray<ContentOverlay>, options?: Pick<MergeOverlaysOptions, "onOverlayError">): HospitalityContent;
|
|
185
|
+
//# sourceMappingURL=content-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-shape.d.ts","sourceRoot":"","sources":["../src/content-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAE1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,kCAAkC,mBAAmB,CAAA;AAElE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;iBAkB7B,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;iBAgBpC,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;iBAWpC,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;;;;;iBAQpC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;iBAOnC,CAAA;AAEF,eAAO,MAAM,uBAAuB;;;;;;;;;;iBAIlC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOnC,CAAA;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACzE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACzE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAEvE,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,GACf;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAUjF;AAED,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,EACvC,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,CAAM,GACzD,kBAAkB,CASpB"}
|