@voyantjs/extras 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/catalog-policy.d.ts.map +1 -1
- package/dist/catalog-policy.js +15 -1
- package/dist/content-shape.d.ts +118 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +98 -0
- package/dist/draft-shape.d.ts +34 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +69 -0
- package/dist/routes.d.ts +18 -14
- package/dist/routes.d.ts.map +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 +45 -0
- package/dist/schema.d.ts +21 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +6 -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 +41 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +138 -0
- package/dist/service-content.d.ts +48 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +253 -0
- package/dist/service.d.ts +18 -14
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +2 -0
- package/dist/validation.d.ts +28 -24
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +2 -0
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAErF,QAAA,MAAM,mBAAmB,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAErF,QAAA,MAAM,mBAAmB,EAAE,gBAAgB,EAwR1C,CAAA;AAED,eAAO,MAAM,mBAAmB,2CAAyC,CAAA;AAEzE,OAAO,EAAE,mBAAmB,EAAE,CAAA"}
|
package/dist/catalog-policy.js
CHANGED
|
@@ -31,7 +31,7 @@ const EXTRAS_FIELD_POLICY = [
|
|
|
31
31
|
class: "managed",
|
|
32
32
|
merge: "source-only",
|
|
33
33
|
drift: "critical",
|
|
34
|
-
reindex: "
|
|
34
|
+
reindex: "facet-affecting",
|
|
35
35
|
snapshot: "on-book",
|
|
36
36
|
query: "indexed-column",
|
|
37
37
|
localized: false,
|
|
@@ -140,6 +140,20 @@ const EXTRAS_FIELD_POLICY = [
|
|
|
140
140
|
overrideFriction: "none",
|
|
141
141
|
sourceFreshness: "sync",
|
|
142
142
|
},
|
|
143
|
+
{
|
|
144
|
+
path: "supplierId",
|
|
145
|
+
class: "structural",
|
|
146
|
+
merge: "source-only",
|
|
147
|
+
drift: "high",
|
|
148
|
+
reindex: "none",
|
|
149
|
+
snapshot: "on-book",
|
|
150
|
+
query: "indexed-column",
|
|
151
|
+
localized: false,
|
|
152
|
+
visibility: ["staff"],
|
|
153
|
+
editRole: "none",
|
|
154
|
+
overrideFriction: "none",
|
|
155
|
+
sourceFreshness: "sync",
|
|
156
|
+
},
|
|
143
157
|
// ── Snapshot-relevant managed fields ────────────────────────────────────
|
|
144
158
|
// Extras' name/description aren't merchandised standalone — they're
|
|
145
159
|
// shown inside the parent product's add-on UI. Marked as managed for
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extras content shape — the rich detail-page content shape returned
|
|
3
|
+
* by `getContent` for sourced extras (excursions, transfers, add-on
|
|
4
|
+
* services).
|
|
5
|
+
*
|
|
6
|
+
* The extras content aggregate is `{ extra, options[], media[],
|
|
7
|
+
* policies[] }` — one payload returned by a single `getContent`.
|
|
8
|
+
* Pricing stays out (volatile-live, flows through `liveResolve`).
|
|
9
|
+
*
|
|
10
|
+
* Extras are simpler than the other verticals because they're add-ons,
|
|
11
|
+
* not standalone products. There's no day-by-day itinerary, no
|
|
12
|
+
* room-type / cabin-category map, no ship spec — just an extra
|
|
13
|
+
* description, optional sub-options (e.g. "half-day vs full-day"),
|
|
14
|
+
* media, and the operational/cancellation policies.
|
|
15
|
+
*
|
|
16
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.2, §3.5.4, §3.6.
|
|
17
|
+
*/
|
|
18
|
+
import { type ContentOverlay, type MergeOverlaysOptions } from "@voyantjs/catalog";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
export declare const EXTRAS_CONTENT_SCHEMA_VERSION = "extras/v1";
|
|
21
|
+
export declare const extraSummarySchema: z.ZodObject<{
|
|
22
|
+
id: z.ZodString;
|
|
23
|
+
name: z.ZodString;
|
|
24
|
+
status: z.ZodOptional<z.ZodString>;
|
|
25
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
26
|
+
selection_type: z.ZodOptional<z.ZodString>;
|
|
27
|
+
pricing_mode: z.ZodOptional<z.ZodString>;
|
|
28
|
+
priced_per_person: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
30
|
+
hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
32
|
+
supplier: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
33
|
+
duration_minutes: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
34
|
+
requirements_summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
export declare const extraOptionSchema: z.ZodObject<{
|
|
37
|
+
id: z.ZodString;
|
|
38
|
+
name: z.ZodString;
|
|
39
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
default_selected: z.ZodOptional<z.ZodBoolean>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
export declare const extraMediaItemSchema: z.ZodObject<{
|
|
43
|
+
url: z.ZodString;
|
|
44
|
+
type: z.ZodDefault<z.ZodEnum<{
|
|
45
|
+
image: "image";
|
|
46
|
+
video: "video";
|
|
47
|
+
document: "document";
|
|
48
|
+
}>>;
|
|
49
|
+
caption: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
50
|
+
alt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
export declare const extraPolicySchema: z.ZodObject<{
|
|
53
|
+
kind: z.ZodEnum<{
|
|
54
|
+
supplier_notes: "supplier_notes";
|
|
55
|
+
cancellation: "cancellation";
|
|
56
|
+
payment: "payment";
|
|
57
|
+
requirements: "requirements";
|
|
58
|
+
}>;
|
|
59
|
+
body: z.ZodString;
|
|
60
|
+
rules: z.ZodOptional<z.ZodUnknown>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
export declare const extraContentSchema: z.ZodObject<{
|
|
63
|
+
extra: z.ZodObject<{
|
|
64
|
+
id: z.ZodString;
|
|
65
|
+
name: z.ZodString;
|
|
66
|
+
status: z.ZodOptional<z.ZodString>;
|
|
67
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
68
|
+
selection_type: z.ZodOptional<z.ZodString>;
|
|
69
|
+
pricing_mode: z.ZodOptional<z.ZodString>;
|
|
70
|
+
priced_per_person: z.ZodOptional<z.ZodBoolean>;
|
|
71
|
+
category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
72
|
+
hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
73
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
74
|
+
supplier: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
75
|
+
duration_minutes: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
76
|
+
requirements_summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
options: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
79
|
+
id: z.ZodString;
|
|
80
|
+
name: z.ZodString;
|
|
81
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
82
|
+
default_selected: z.ZodOptional<z.ZodBoolean>;
|
|
83
|
+
}, z.core.$strip>>>;
|
|
84
|
+
media: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
85
|
+
url: z.ZodString;
|
|
86
|
+
type: z.ZodDefault<z.ZodEnum<{
|
|
87
|
+
image: "image";
|
|
88
|
+
video: "video";
|
|
89
|
+
document: "document";
|
|
90
|
+
}>>;
|
|
91
|
+
caption: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
92
|
+
alt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
93
|
+
}, z.core.$strip>>>;
|
|
94
|
+
policies: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
95
|
+
kind: z.ZodEnum<{
|
|
96
|
+
supplier_notes: "supplier_notes";
|
|
97
|
+
cancellation: "cancellation";
|
|
98
|
+
payment: "payment";
|
|
99
|
+
requirements: "requirements";
|
|
100
|
+
}>;
|
|
101
|
+
body: z.ZodString;
|
|
102
|
+
rules: z.ZodOptional<z.ZodUnknown>;
|
|
103
|
+
}, z.core.$strip>>>;
|
|
104
|
+
}, z.core.$strip>;
|
|
105
|
+
export type ExtraContent = z.infer<typeof extraContentSchema>;
|
|
106
|
+
export type ExtraSummary = z.infer<typeof extraSummarySchema>;
|
|
107
|
+
export type ExtraOption = z.infer<typeof extraOptionSchema>;
|
|
108
|
+
export type ExtraMediaItem = z.infer<typeof extraMediaItemSchema>;
|
|
109
|
+
export type ExtraPolicy = z.infer<typeof extraPolicySchema>;
|
|
110
|
+
export declare function validateExtraContent(payload: unknown): {
|
|
111
|
+
valid: true;
|
|
112
|
+
content: ExtraContent;
|
|
113
|
+
} | {
|
|
114
|
+
valid: false;
|
|
115
|
+
reason: string;
|
|
116
|
+
};
|
|
117
|
+
export declare function mergeOverlaysIntoExtraContent(payload: ExtraContent, overlays: ReadonlyArray<ContentOverlay>, options?: Pick<MergeOverlaysOptions, "onOverlayError">): ExtraContent;
|
|
118
|
+
//# 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;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAE1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,6BAA6B,cAAc,CAAA;AAExD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;iBAgC7B,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;iBAM5B,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;iBAK/B,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAK7B,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAC3D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACjE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAE3D,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,GACf;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAU3E;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,EACvC,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,CAAM,GACzD,YAAY,CASd"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extras content shape — the rich detail-page content shape returned
|
|
3
|
+
* by `getContent` for sourced extras (excursions, transfers, add-on
|
|
4
|
+
* services).
|
|
5
|
+
*
|
|
6
|
+
* The extras content aggregate is `{ extra, options[], media[],
|
|
7
|
+
* policies[] }` — one payload returned by a single `getContent`.
|
|
8
|
+
* Pricing stays out (volatile-live, flows through `liveResolve`).
|
|
9
|
+
*
|
|
10
|
+
* Extras are simpler than the other verticals because they're add-ons,
|
|
11
|
+
* not standalone products. There's no day-by-day itinerary, no
|
|
12
|
+
* room-type / cabin-category map, no ship spec — just an extra
|
|
13
|
+
* description, optional sub-options (e.g. "half-day vs full-day"),
|
|
14
|
+
* media, and the operational/cancellation policies.
|
|
15
|
+
*
|
|
16
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.2, §3.5.4, §3.6.
|
|
17
|
+
*/
|
|
18
|
+
import { mergeOverlaysIntoContent, } from "@voyantjs/catalog";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
export const EXTRAS_CONTENT_SCHEMA_VERSION = "extras/v1";
|
|
21
|
+
export const extraSummarySchema = z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
name: z.string(),
|
|
24
|
+
status: z.string().optional(),
|
|
25
|
+
description: z.string().nullable().optional(),
|
|
26
|
+
/**
|
|
27
|
+
* Selection type — mirrors the owned `extra_selection_type` enum:
|
|
28
|
+
* "optional" | "required" | "default_selected" | "unavailable".
|
|
29
|
+
* Sourced adapters set what they support; thin synthesis defaults to
|
|
30
|
+
* "optional".
|
|
31
|
+
*/
|
|
32
|
+
selection_type: z.string().optional(),
|
|
33
|
+
/**
|
|
34
|
+
* Pricing mode — mirrors the owned `extra_pricing_mode` enum:
|
|
35
|
+
* "included" | "per_person" | "per_booking" | "quantity_based" |
|
|
36
|
+
* "on_request" | "free". Captures the structural pricing model the
|
|
37
|
+
* upstream advertises; actual prices come through `liveResolve`.
|
|
38
|
+
*/
|
|
39
|
+
pricing_mode: z.string().optional(),
|
|
40
|
+
/** Hint — true when the extra is priced per traveler, not per booking. */
|
|
41
|
+
priced_per_person: z.boolean().optional(),
|
|
42
|
+
/** Service category (e.g. "transfer", "excursion", "insurance", "spa"). */
|
|
43
|
+
category: z.string().nullable().optional(),
|
|
44
|
+
/** Hero media URL. */
|
|
45
|
+
hero_image_url: z.string().nullable().optional(),
|
|
46
|
+
highlights: z.array(z.string()).optional(),
|
|
47
|
+
/** Free-form supplier hint surfaced to ops (not customer-facing). */
|
|
48
|
+
supplier: z.string().nullable().optional(),
|
|
49
|
+
/** Estimated duration in minutes for time-bound extras (excursions). */
|
|
50
|
+
duration_minutes: z.number().int().nonnegative().nullable().optional(),
|
|
51
|
+
/** Constraints / requirements summary surfaced on the booking flow. */
|
|
52
|
+
requirements_summary: z.string().nullable().optional(),
|
|
53
|
+
});
|
|
54
|
+
export const extraOptionSchema = z.object({
|
|
55
|
+
id: z.string(),
|
|
56
|
+
name: z.string(),
|
|
57
|
+
description: z.string().nullable().optional(),
|
|
58
|
+
/** Whether this option auto-selects when the extra is selected. */
|
|
59
|
+
default_selected: z.boolean().optional(),
|
|
60
|
+
});
|
|
61
|
+
export const extraMediaItemSchema = z.object({
|
|
62
|
+
url: z.string(),
|
|
63
|
+
type: z.enum(["image", "video", "document"]).default("image"),
|
|
64
|
+
caption: z.string().nullable().optional(),
|
|
65
|
+
alt: z.string().nullable().optional(),
|
|
66
|
+
});
|
|
67
|
+
export const extraPolicySchema = z.object({
|
|
68
|
+
kind: z.enum(["cancellation", "payment", "supplier_notes", "requirements"]),
|
|
69
|
+
body: z.string(),
|
|
70
|
+
rules: z.unknown().optional(),
|
|
71
|
+
});
|
|
72
|
+
export const extraContentSchema = z.object({
|
|
73
|
+
extra: extraSummarySchema,
|
|
74
|
+
options: z.array(extraOptionSchema).default([]),
|
|
75
|
+
media: z.array(extraMediaItemSchema).default([]),
|
|
76
|
+
policies: z.array(extraPolicySchema).default([]),
|
|
77
|
+
});
|
|
78
|
+
export function validateExtraContent(payload) {
|
|
79
|
+
const result = extraContentSchema.safeParse(payload);
|
|
80
|
+
if (result.success) {
|
|
81
|
+
return { valid: true, content: result.data };
|
|
82
|
+
}
|
|
83
|
+
const issue = result.error.issues[0];
|
|
84
|
+
return {
|
|
85
|
+
valid: false,
|
|
86
|
+
reason: issue ? `${issue.path.join(".")}: ${issue.message}` : "validation failed",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function mergeOverlaysIntoExtraContent(payload, overlays, options = {}) {
|
|
90
|
+
const merged = mergeOverlaysIntoContent(payload, overlays, {
|
|
91
|
+
validate(p) {
|
|
92
|
+
const r = validateExtraContent(p);
|
|
93
|
+
return r.valid ? { valid: true } : { valid: false, reason: r.reason };
|
|
94
|
+
},
|
|
95
|
+
onOverlayError: options.onOverlayError,
|
|
96
|
+
});
|
|
97
|
+
return extraContentSchema.parse(merged);
|
|
98
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project an `ExtraContent` payload into a `BookingDraftShape`.
|
|
3
|
+
*
|
|
4
|
+
* Extras are booking add-ons (excursions, transfers, insurance) —
|
|
5
|
+
* they're never standalone bookable on their own; they always layer
|
|
6
|
+
* onto a parent product. So the descriptor here is **degenerate**:
|
|
7
|
+
*
|
|
8
|
+
* - `showsConfigure: false` (no configure step — extras are picked
|
|
9
|
+
* during the parent product's add-on step).
|
|
10
|
+
* - `showsTravelers: false` (extras don't have their own pax flow).
|
|
11
|
+
* - `showsPayment: false` (extras roll up into the parent's pricing).
|
|
12
|
+
* - `addons.catalog`: the extra itself + its sub-options projected
|
|
13
|
+
* as a small catalog. Useful for journey contexts that want to
|
|
14
|
+
* render an add-on detail view (e.g. ops admin reviewing a
|
|
15
|
+
* supplier's extras).
|
|
16
|
+
*
|
|
17
|
+
* In practice templates rarely call this directly — the parent
|
|
18
|
+
* product's `BookingDraftShape` aggregates extras via the journey's
|
|
19
|
+
* cross-product composer. This function is the building block.
|
|
20
|
+
*/
|
|
21
|
+
import { type BookingDraftShape } from "@voyantjs/catalog/booking-engine";
|
|
22
|
+
import type { ExtraContent } from "./content-shape.js";
|
|
23
|
+
export interface BuildExtraDraftShapeOptions {
|
|
24
|
+
locale?: string;
|
|
25
|
+
/**
|
|
26
|
+
* When true, returns the full descriptor surface (configure +
|
|
27
|
+
* travelers + payment all visible). Useful for tests or admin
|
|
28
|
+
* surfaces that render extras standalone. Defaults to false (the
|
|
29
|
+
* normal "extras-as-addon" mode).
|
|
30
|
+
*/
|
|
31
|
+
standalone?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function buildExtraDraftShape(content: ExtraContent, options?: BuildExtraDraftShapeOptions): BookingDraftShape;
|
|
34
|
+
//# sourceMappingURL=draft-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft-shape.d.ts","sourceRoot":"","sources":["../src/draft-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAEL,KAAK,iBAAiB,EAMvB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAMtD,MAAM,WAAW,2BAA2B;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,2BAAgC,GACxC,iBAAiB,CAsCnB"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project an `ExtraContent` payload into a `BookingDraftShape`.
|
|
3
|
+
*
|
|
4
|
+
* Extras are booking add-ons (excursions, transfers, insurance) —
|
|
5
|
+
* they're never standalone bookable on their own; they always layer
|
|
6
|
+
* onto a parent product. So the descriptor here is **degenerate**:
|
|
7
|
+
*
|
|
8
|
+
* - `showsConfigure: false` (no configure step — extras are picked
|
|
9
|
+
* during the parent product's add-on step).
|
|
10
|
+
* - `showsTravelers: false` (extras don't have their own pax flow).
|
|
11
|
+
* - `showsPayment: false` (extras roll up into the parent's pricing).
|
|
12
|
+
* - `addons.catalog`: the extra itself + its sub-options projected
|
|
13
|
+
* as a small catalog. Useful for journey contexts that want to
|
|
14
|
+
* render an add-on detail view (e.g. ops admin reviewing a
|
|
15
|
+
* supplier's extras).
|
|
16
|
+
*
|
|
17
|
+
* In practice templates rarely call this directly — the parent
|
|
18
|
+
* product's `BookingDraftShape` aggregates extras via the journey's
|
|
19
|
+
* cross-product composer. This function is the building block.
|
|
20
|
+
*/
|
|
21
|
+
import { defaultBookingFields, defaultDraftShapeFlags, defaultTravelerFields, paxBandsAllowedTotalFrom, } from "@voyantjs/catalog/booking-engine";
|
|
22
|
+
const DEGENERATE_PAX_BANDS = [
|
|
23
|
+
{ code: "adult", label: "Adult", minCount: 0, maxCount: 8 },
|
|
24
|
+
];
|
|
25
|
+
export function buildExtraDraftShape(content, options = {}) {
|
|
26
|
+
const flags = defaultDraftShapeFlags();
|
|
27
|
+
const standalone = options.standalone ?? false;
|
|
28
|
+
// Project the extra + its options as add-on offers. The extra
|
|
29
|
+
// itself becomes the lead item; sub-options follow.
|
|
30
|
+
const items = [
|
|
31
|
+
{
|
|
32
|
+
id: content.extra.id,
|
|
33
|
+
name: content.extra.name,
|
|
34
|
+
description: content.extra.description ?? null,
|
|
35
|
+
kind: kindForExtraCategory(content.extra.category ?? null),
|
|
36
|
+
pricingMode: content.extra.pricing_mode ?? null,
|
|
37
|
+
},
|
|
38
|
+
...content.options.map((opt) => ({
|
|
39
|
+
id: opt.id,
|
|
40
|
+
name: opt.name,
|
|
41
|
+
description: opt.description ?? null,
|
|
42
|
+
kind: "extras",
|
|
43
|
+
pricingMode: null,
|
|
44
|
+
})),
|
|
45
|
+
];
|
|
46
|
+
return {
|
|
47
|
+
...flags,
|
|
48
|
+
showsConfigure: standalone,
|
|
49
|
+
showsTravelers: standalone,
|
|
50
|
+
showsPayment: standalone,
|
|
51
|
+
showsAddons: true,
|
|
52
|
+
paxBands: DEGENERATE_PAX_BANDS,
|
|
53
|
+
paxBandsAllowedTotal: paxBandsAllowedTotalFrom(DEGENERATE_PAX_BANDS),
|
|
54
|
+
travelerFields: standalone ? defaultTravelerFields() : [],
|
|
55
|
+
bookingFields: standalone ? defaultBookingFields() : [],
|
|
56
|
+
addons: { catalog: items },
|
|
57
|
+
paymentIntents: standalone ? ["hold", "card"] : [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function kindForExtraCategory(category) {
|
|
61
|
+
if (!category)
|
|
62
|
+
return "extras";
|
|
63
|
+
const lower = category.toLowerCase();
|
|
64
|
+
if (lower.includes("excursion") || lower.includes("tour"))
|
|
65
|
+
return "excursions";
|
|
66
|
+
if (lower.includes("insurance"))
|
|
67
|
+
return "insurance";
|
|
68
|
+
return "extras";
|
|
69
|
+
}
|
package/dist/routes.d.ts
CHANGED
|
@@ -13,10 +13,11 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
13
13
|
data: {
|
|
14
14
|
id: string;
|
|
15
15
|
productId: string;
|
|
16
|
+
supplierId: string | null;
|
|
16
17
|
code: string | null;
|
|
17
18
|
name: string;
|
|
18
19
|
description: string | null;
|
|
19
|
-
selectionType: "
|
|
20
|
+
selectionType: "unavailable" | "optional" | "default_selected" | "required";
|
|
20
21
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
|
|
21
22
|
pricedPerPerson: boolean;
|
|
22
23
|
minQuantity: number | null;
|
|
@@ -49,9 +50,10 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
49
50
|
createdAt: string;
|
|
50
51
|
updatedAt: string;
|
|
51
52
|
productId: string;
|
|
53
|
+
supplierId: string | null;
|
|
52
54
|
name: string;
|
|
53
55
|
description: string | null;
|
|
54
|
-
selectionType: "
|
|
56
|
+
selectionType: "unavailable" | "optional" | "default_selected" | "required";
|
|
55
57
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
|
|
56
58
|
pricedPerPerson: boolean;
|
|
57
59
|
minQuantity: number | null;
|
|
@@ -91,10 +93,11 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
91
93
|
data: {
|
|
92
94
|
id: string;
|
|
93
95
|
productId: string;
|
|
96
|
+
supplierId: string | null;
|
|
94
97
|
code: string | null;
|
|
95
98
|
name: string;
|
|
96
99
|
description: string | null;
|
|
97
|
-
selectionType: "
|
|
100
|
+
selectionType: "unavailable" | "optional" | "default_selected" | "required";
|
|
98
101
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
|
|
99
102
|
pricedPerPerson: boolean;
|
|
100
103
|
minQuantity: number | null;
|
|
@@ -136,10 +139,11 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
136
139
|
data: {
|
|
137
140
|
id: string;
|
|
138
141
|
productId: string;
|
|
142
|
+
supplierId: string | null;
|
|
139
143
|
code: string | null;
|
|
140
144
|
name: string;
|
|
141
145
|
description: string | null;
|
|
142
|
-
selectionType: "
|
|
146
|
+
selectionType: "unavailable" | "optional" | "default_selected" | "required";
|
|
143
147
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
|
|
144
148
|
pricedPerPerson: boolean;
|
|
145
149
|
minQuantity: number | null;
|
|
@@ -193,7 +197,7 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
193
197
|
id: string;
|
|
194
198
|
optionId: string;
|
|
195
199
|
productExtraId: string;
|
|
196
|
-
selectionType: "
|
|
200
|
+
selectionType: "unavailable" | "optional" | "default_selected" | "required" | null;
|
|
197
201
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | null;
|
|
198
202
|
pricedPerPerson: boolean | null;
|
|
199
203
|
minQuantity: number | null;
|
|
@@ -226,7 +230,7 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
226
230
|
id: string;
|
|
227
231
|
createdAt: string;
|
|
228
232
|
updatedAt: string;
|
|
229
|
-
selectionType: "
|
|
233
|
+
selectionType: "unavailable" | "optional" | "default_selected" | "required" | null;
|
|
230
234
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | null;
|
|
231
235
|
pricedPerPerson: boolean | null;
|
|
232
236
|
minQuantity: number | null;
|
|
@@ -271,7 +275,7 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
271
275
|
id: string;
|
|
272
276
|
optionId: string;
|
|
273
277
|
productExtraId: string;
|
|
274
|
-
selectionType: "
|
|
278
|
+
selectionType: "unavailable" | "optional" | "default_selected" | "required" | null;
|
|
275
279
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | null;
|
|
276
280
|
pricedPerPerson: boolean | null;
|
|
277
281
|
minQuantity: number | null;
|
|
@@ -316,7 +320,7 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
316
320
|
id: string;
|
|
317
321
|
optionId: string;
|
|
318
322
|
productExtraId: string;
|
|
319
|
-
selectionType: "
|
|
323
|
+
selectionType: "unavailable" | "optional" | "default_selected" | "required" | null;
|
|
320
324
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free" | null;
|
|
321
325
|
pricedPerPerson: boolean | null;
|
|
322
326
|
minQuantity: number | null;
|
|
@@ -375,7 +379,7 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
375
379
|
optionExtraConfigId: string | null;
|
|
376
380
|
name: string;
|
|
377
381
|
description: string | null;
|
|
378
|
-
status: "
|
|
382
|
+
status: "confirmed" | "cancelled" | "draft" | "selected" | "fulfilled";
|
|
379
383
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
|
|
380
384
|
pricedPerPerson: boolean;
|
|
381
385
|
quantity: number;
|
|
@@ -413,15 +417,15 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
413
417
|
description: string | null;
|
|
414
418
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
|
|
415
419
|
pricedPerPerson: boolean;
|
|
420
|
+
status: "confirmed" | "cancelled" | "draft" | "selected" | "fulfilled";
|
|
416
421
|
metadata: {
|
|
417
422
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
418
423
|
} | null;
|
|
419
|
-
status: "draft" | "selected" | "confirmed" | "cancelled" | "fulfilled";
|
|
420
|
-
notes: string | null;
|
|
421
424
|
bookingId: string;
|
|
425
|
+
quantity: number;
|
|
426
|
+
notes: string | null;
|
|
422
427
|
productExtraId: string | null;
|
|
423
428
|
optionExtraConfigId: string | null;
|
|
424
|
-
quantity: number;
|
|
425
429
|
sellCurrency: string;
|
|
426
430
|
unitSellAmountCents: number | null;
|
|
427
431
|
totalSellAmountCents: number | null;
|
|
@@ -461,7 +465,7 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
461
465
|
optionExtraConfigId: string | null;
|
|
462
466
|
name: string;
|
|
463
467
|
description: string | null;
|
|
464
|
-
status: "
|
|
468
|
+
status: "confirmed" | "cancelled" | "draft" | "selected" | "fulfilled";
|
|
465
469
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
|
|
466
470
|
pricedPerPerson: boolean;
|
|
467
471
|
quantity: number;
|
|
@@ -510,7 +514,7 @@ export declare const extrasRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
|
510
514
|
optionExtraConfigId: string | null;
|
|
511
515
|
name: string;
|
|
512
516
|
description: string | null;
|
|
513
|
-
status: "
|
|
517
|
+
status: "confirmed" | "cancelled" | "draft" | "selected" | "fulfilled";
|
|
514
518
|
pricingMode: "included" | "per_person" | "per_booking" | "quantity_based" | "on_request" | "free";
|
|
515
519
|
pricedPerPerson: boolean;
|
|
516
520
|
quantity: number;
|
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAgBjE,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAgBjE,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAsGrB,CAAA;AAEJ,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAA"}
|