@voyant-travel/inventory 0.1.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.
Files changed (311) hide show
  1. package/LICENSE +201 -0
  2. package/dist/action-ledger-drift.d.ts +29 -0
  3. package/dist/action-ledger-drift.d.ts.map +1 -0
  4. package/dist/action-ledger-drift.js +338 -0
  5. package/dist/action-ledger.d.ts +104 -0
  6. package/dist/action-ledger.d.ts.map +1 -0
  7. package/dist/action-ledger.js +100 -0
  8. package/dist/authoring/builder.d.ts +37 -0
  9. package/dist/authoring/builder.d.ts.map +1 -0
  10. package/dist/authoring/builder.js +248 -0
  11. package/dist/authoring/clone-content.d.ts +38 -0
  12. package/dist/authoring/clone-content.d.ts.map +1 -0
  13. package/dist/authoring/clone-content.js +367 -0
  14. package/dist/authoring/clone-pricing.d.ts +9 -0
  15. package/dist/authoring/clone-pricing.d.ts.map +1 -0
  16. package/dist/authoring/clone-pricing.js +242 -0
  17. package/dist/authoring/clone.d.ts +45 -0
  18. package/dist/authoring/clone.d.ts.map +1 -0
  19. package/dist/authoring/clone.js +142 -0
  20. package/dist/authoring/errors.d.ts +21 -0
  21. package/dist/authoring/errors.d.ts.map +1 -0
  22. package/dist/authoring/errors.js +13 -0
  23. package/dist/authoring/extension.d.ts +248 -0
  24. package/dist/authoring/extension.d.ts.map +1 -0
  25. package/dist/authoring/extension.js +116 -0
  26. package/dist/authoring/index.d.ts +12 -0
  27. package/dist/authoring/index.d.ts.map +1 -0
  28. package/dist/authoring/index.js +11 -0
  29. package/dist/authoring/schema.d.ts +85 -0
  30. package/dist/authoring/schema.d.ts.map +1 -0
  31. package/dist/authoring/schema.js +16 -0
  32. package/dist/authoring/service.d.ts +28 -0
  33. package/dist/authoring/service.d.ts.map +1 -0
  34. package/dist/authoring/service.js +66 -0
  35. package/dist/authoring/spec.d.ts +524 -0
  36. package/dist/authoring/spec.d.ts.map +1 -0
  37. package/dist/authoring/spec.js +167 -0
  38. package/dist/authoring/validate.d.ts +17 -0
  39. package/dist/authoring/validate.d.ts.map +1 -0
  40. package/dist/authoring/validate.js +83 -0
  41. package/dist/authoring.d.ts +2 -0
  42. package/dist/authoring.d.ts.map +1 -0
  43. package/dist/authoring.js +1 -0
  44. package/dist/booking-engine/handler-support.d.ts +91 -0
  45. package/dist/booking-engine/handler-support.d.ts.map +1 -0
  46. package/dist/booking-engine/handler-support.js +355 -0
  47. package/dist/booking-engine/handler.d.ts +404 -0
  48. package/dist/booking-engine/handler.d.ts.map +1 -0
  49. package/dist/booking-engine/handler.js +398 -0
  50. package/dist/booking-engine/index.d.ts +8 -0
  51. package/dist/booking-engine/index.d.ts.map +1 -0
  52. package/dist/booking-engine/index.js +7 -0
  53. package/dist/booking-engine.d.ts +2 -0
  54. package/dist/booking-engine.d.ts.map +1 -0
  55. package/dist/booking-engine.js +1 -0
  56. package/dist/booking-extension.d.ts +278 -0
  57. package/dist/booking-extension.d.ts.map +1 -0
  58. package/dist/booking-extension.js +161 -0
  59. package/dist/catalog-policy-departures.d.ts +52 -0
  60. package/dist/catalog-policy-departures.d.ts.map +1 -0
  61. package/dist/catalog-policy-departures.js +169 -0
  62. package/dist/catalog-policy-destinations.d.ts +43 -0
  63. package/dist/catalog-policy-destinations.d.ts.map +1 -0
  64. package/dist/catalog-policy-destinations.js +165 -0
  65. package/dist/catalog-policy-pricing.d.ts +55 -0
  66. package/dist/catalog-policy-pricing.d.ts.map +1 -0
  67. package/dist/catalog-policy-pricing.js +109 -0
  68. package/dist/catalog-policy-promotions.d.ts +52 -0
  69. package/dist/catalog-policy-promotions.d.ts.map +1 -0
  70. package/dist/catalog-policy-promotions.js +270 -0
  71. package/dist/catalog-policy-taxonomy.d.ts +51 -0
  72. package/dist/catalog-policy-taxonomy.d.ts.map +1 -0
  73. package/dist/catalog-policy-taxonomy.js +191 -0
  74. package/dist/catalog-policy.d.ts +33 -0
  75. package/dist/catalog-policy.d.ts.map +1 -0
  76. package/dist/catalog-policy.js +733 -0
  77. package/dist/content-shape.d.ts +15 -0
  78. package/dist/content-shape.d.ts.map +1 -0
  79. package/dist/content-shape.js +28 -0
  80. package/dist/draft-shape.d.ts +43 -0
  81. package/dist/draft-shape.d.ts.map +1 -0
  82. package/dist/draft-shape.js +48 -0
  83. package/dist/events.d.ts +37 -0
  84. package/dist/events.d.ts.map +1 -0
  85. package/dist/events.js +32 -0
  86. package/dist/extras/catalog-policy.d.ts +30 -0
  87. package/dist/extras/catalog-policy.d.ts.map +1 -0
  88. package/dist/extras/catalog-policy.js +319 -0
  89. package/dist/extras/content-shape.d.ts +5 -0
  90. package/dist/extras/content-shape.d.ts.map +1 -0
  91. package/dist/extras/content-shape.js +13 -0
  92. package/dist/extras/draft-shape.d.ts +34 -0
  93. package/dist/extras/draft-shape.d.ts.map +1 -0
  94. package/dist/extras/draft-shape.js +69 -0
  95. package/dist/extras/routes.d.ts +380 -0
  96. package/dist/extras/routes.d.ts.map +1 -0
  97. package/dist/extras/routes.js +59 -0
  98. package/dist/extras/schema-sourced-content.d.ts +254 -0
  99. package/dist/extras/schema-sourced-content.d.ts.map +1 -0
  100. package/dist/extras/schema-sourced-content.js +45 -0
  101. package/dist/extras/schema.d.ts +628 -0
  102. package/dist/extras/schema.d.ts.map +1 -0
  103. package/dist/extras/schema.js +87 -0
  104. package/dist/extras/service-catalog-plane.d.ts +77 -0
  105. package/dist/extras/service-catalog-plane.d.ts.map +1 -0
  106. package/dist/extras/service-catalog-plane.js +219 -0
  107. package/dist/extras/service-content-synthesizer.d.ts +41 -0
  108. package/dist/extras/service-content-synthesizer.d.ts.map +1 -0
  109. package/dist/extras/service-content-synthesizer.js +138 -0
  110. package/dist/extras/service-content.d.ts +48 -0
  111. package/dist/extras/service-content.d.ts.map +1 -0
  112. package/dist/extras/service-content.js +253 -0
  113. package/dist/extras/service.d.ts +185 -0
  114. package/dist/extras/service.d.ts.map +1 -0
  115. package/dist/extras/service.js +96 -0
  116. package/dist/extras/validation.d.ts +437 -0
  117. package/dist/extras/validation.d.ts.map +1 -0
  118. package/dist/extras/validation.js +149 -0
  119. package/dist/extras.d.ts +267 -0
  120. package/dist/extras.d.ts.map +1 -0
  121. package/dist/extras.js +19 -0
  122. package/dist/index.d.ts +22 -0
  123. package/dist/index.d.ts.map +1 -0
  124. package/dist/index.js +32 -0
  125. package/dist/interface.d.ts +5869 -0
  126. package/dist/interface.d.ts.map +1 -0
  127. package/dist/interface.js +54 -0
  128. package/dist/public-routes.d.ts +2 -0
  129. package/dist/public-routes.d.ts.map +1 -0
  130. package/dist/public-routes.js +1 -0
  131. package/dist/public-validation.d.ts +2 -0
  132. package/dist/public-validation.d.ts.map +1 -0
  133. package/dist/public-validation.js +1 -0
  134. package/dist/read-model.d.ts +25 -0
  135. package/dist/read-model.d.ts.map +1 -0
  136. package/dist/read-model.js +99 -0
  137. package/dist/route-env.d.ts +22 -0
  138. package/dist/route-env.d.ts.map +1 -0
  139. package/dist/route-env.js +1 -0
  140. package/dist/routes-associations.d.ts +164 -0
  141. package/dist/routes-associations.d.ts.map +1 -0
  142. package/dist/routes-associations.js +100 -0
  143. package/dist/routes-catalog.d.ts +436 -0
  144. package/dist/routes-catalog.d.ts.map +1 -0
  145. package/dist/routes-catalog.js +104 -0
  146. package/dist/routes-configuration.d.ts +773 -0
  147. package/dist/routes-configuration.d.ts.map +1 -0
  148. package/dist/routes-configuration.js +364 -0
  149. package/dist/routes-content.d.ts +74 -0
  150. package/dist/routes-content.d.ts.map +1 -0
  151. package/dist/routes-content.js +117 -0
  152. package/dist/routes-core.d.ts +331 -0
  153. package/dist/routes-core.d.ts.map +1 -0
  154. package/dist/routes-core.js +95 -0
  155. package/dist/routes-itinerary.d.ts +759 -0
  156. package/dist/routes-itinerary.d.ts.map +1 -0
  157. package/dist/routes-itinerary.js +387 -0
  158. package/dist/routes-maintenance.d.ts +32 -0
  159. package/dist/routes-maintenance.d.ts.map +1 -0
  160. package/dist/routes-maintenance.js +14 -0
  161. package/dist/routes-media.d.ts +634 -0
  162. package/dist/routes-media.d.ts.map +1 -0
  163. package/dist/routes-media.js +245 -0
  164. package/dist/routes-merchandising.d.ts +1120 -0
  165. package/dist/routes-merchandising.d.ts.map +1 -0
  166. package/dist/routes-merchandising.js +377 -0
  167. package/dist/routes-options.d.ts +363 -0
  168. package/dist/routes-options.d.ts.map +1 -0
  169. package/dist/routes-options.js +173 -0
  170. package/dist/routes-public.d.ts +776 -0
  171. package/dist/routes-public.d.ts.map +1 -0
  172. package/dist/routes-public.js +119 -0
  173. package/dist/routes-translations.d.ts +489 -0
  174. package/dist/routes-translations.d.ts.map +1 -0
  175. package/dist/routes-translations.js +258 -0
  176. package/dist/routes.d.ts +5097 -0
  177. package/dist/routes.d.ts.map +1 -0
  178. package/dist/routes.js +64 -0
  179. package/dist/schema-core.d.ts +1238 -0
  180. package/dist/schema-core.d.ts.map +1 -0
  181. package/dist/schema-core.js +157 -0
  182. package/dist/schema-itinerary.d.ts +1169 -0
  183. package/dist/schema-itinerary.d.ts.map +1 -0
  184. package/dist/schema-itinerary.js +130 -0
  185. package/dist/schema-relations.d.ts +117 -0
  186. package/dist/schema-relations.d.ts.map +1 -0
  187. package/dist/schema-relations.js +192 -0
  188. package/dist/schema-settings.d.ts +1800 -0
  189. package/dist/schema-settings.d.ts.map +1 -0
  190. package/dist/schema-settings.js +220 -0
  191. package/dist/schema-shared.d.ts +15 -0
  192. package/dist/schema-shared.d.ts.map +1 -0
  193. package/dist/schema-shared.js +91 -0
  194. package/dist/schema-sourced-content.d.ts +262 -0
  195. package/dist/schema-sourced-content.d.ts.map +1 -0
  196. package/dist/schema-sourced-content.js +69 -0
  197. package/dist/schema-taxonomy.d.ts +1363 -0
  198. package/dist/schema-taxonomy.d.ts.map +1 -0
  199. package/dist/schema-taxonomy.js +203 -0
  200. package/dist/schema.d.ts +10 -0
  201. package/dist/schema.d.ts.map +1 -0
  202. package/dist/schema.js +9 -0
  203. package/dist/service-aggregates.d.ts +29 -0
  204. package/dist/service-aggregates.d.ts.map +1 -0
  205. package/dist/service-aggregates.js +56 -0
  206. package/dist/service-catalog-plane-destinations.d.ts +30 -0
  207. package/dist/service-catalog-plane-destinations.d.ts.map +1 -0
  208. package/dist/service-catalog-plane-destinations.js +143 -0
  209. package/dist/service-catalog-plane-taxonomy.d.ts +73 -0
  210. package/dist/service-catalog-plane-taxonomy.d.ts.map +1 -0
  211. package/dist/service-catalog-plane-taxonomy.js +242 -0
  212. package/dist/service-catalog-plane.d.ts +179 -0
  213. package/dist/service-catalog-plane.d.ts.map +1 -0
  214. package/dist/service-catalog-plane.js +431 -0
  215. package/dist/service-catalog.d.ts +251 -0
  216. package/dist/service-catalog.d.ts.map +1 -0
  217. package/dist/service-catalog.js +517 -0
  218. package/dist/service-configuration.d.ts +261 -0
  219. package/dist/service-configuration.d.ts.map +1 -0
  220. package/dist/service-configuration.js +343 -0
  221. package/dist/service-content-owned.d.ts +68 -0
  222. package/dist/service-content-owned.d.ts.map +1 -0
  223. package/dist/service-content-owned.js +329 -0
  224. package/dist/service-content-synthesizer.d.ts +90 -0
  225. package/dist/service-content-synthesizer.d.ts.map +1 -0
  226. package/dist/service-content-synthesizer.js +178 -0
  227. package/dist/service-content.d.ts +106 -0
  228. package/dist/service-content.d.ts.map +1 -0
  229. package/dist/service-content.js +388 -0
  230. package/dist/service-core.d.ts +194 -0
  231. package/dist/service-core.d.ts.map +1 -0
  232. package/dist/service-core.js +213 -0
  233. package/dist/service-delivery-formats.d.ts +58 -0
  234. package/dist/service-delivery-formats.d.ts.map +1 -0
  235. package/dist/service-delivery-formats.js +107 -0
  236. package/dist/service-destinations.d.ts +223 -0
  237. package/dist/service-destinations.d.ts.map +1 -0
  238. package/dist/service-destinations.js +310 -0
  239. package/dist/service-itinerary-history.d.ts +457 -0
  240. package/dist/service-itinerary-history.d.ts.map +1 -0
  241. package/dist/service-itinerary-history.js +135 -0
  242. package/dist/service-itinerary.d.ts +1149 -0
  243. package/dist/service-itinerary.d.ts.map +1 -0
  244. package/dist/service-itinerary.js +419 -0
  245. package/dist/service-media.d.ts +272 -0
  246. package/dist/service-media.d.ts.map +1 -0
  247. package/dist/service-media.js +320 -0
  248. package/dist/service-merchandising.d.ts +184 -0
  249. package/dist/service-merchandising.d.ts.map +1 -0
  250. package/dist/service-merchandising.js +181 -0
  251. package/dist/service-option-translations.d.ts +268 -0
  252. package/dist/service-option-translations.d.ts.map +1 -0
  253. package/dist/service-option-translations.js +300 -0
  254. package/dist/service-options.d.ts +181 -0
  255. package/dist/service-options.d.ts.map +1 -0
  256. package/dist/service-options.js +179 -0
  257. package/dist/service-product-destinations.d.ts +37 -0
  258. package/dist/service-product-destinations.d.ts.map +1 -0
  259. package/dist/service-product-destinations.js +94 -0
  260. package/dist/service-public.d.ts +664 -0
  261. package/dist/service-public.d.ts.map +1 -0
  262. package/dist/service-public.js +374 -0
  263. package/dist/service-taxonomy.d.ts +197 -0
  264. package/dist/service-taxonomy.d.ts.map +1 -0
  265. package/dist/service-taxonomy.js +221 -0
  266. package/dist/service.d.ts +3929 -0
  267. package/dist/service.d.ts.map +1 -0
  268. package/dist/service.js +28 -0
  269. package/dist/tasks/brochure-printers.d.ts +31 -0
  270. package/dist/tasks/brochure-printers.d.ts.map +1 -0
  271. package/dist/tasks/brochure-printers.js +149 -0
  272. package/dist/tasks/brochure-templates.d.ts +36 -0
  273. package/dist/tasks/brochure-templates.d.ts.map +1 -0
  274. package/dist/tasks/brochure-templates.js +110 -0
  275. package/dist/tasks/brochures.d.ts +43 -0
  276. package/dist/tasks/brochures.d.ts.map +1 -0
  277. package/dist/tasks/brochures.js +72 -0
  278. package/dist/tasks/generate-pdf.d.ts +8 -0
  279. package/dist/tasks/generate-pdf.d.ts.map +1 -0
  280. package/dist/tasks/generate-pdf.js +106 -0
  281. package/dist/tasks/index.d.ts +5 -0
  282. package/dist/tasks/index.d.ts.map +1 -0
  283. package/dist/tasks/index.js +4 -0
  284. package/dist/tasks/pdf-text.d.ts +2 -0
  285. package/dist/tasks/pdf-text.d.ts.map +1 -0
  286. package/dist/tasks/pdf-text.js +40 -0
  287. package/dist/tasks.d.ts +2 -0
  288. package/dist/tasks.d.ts.map +1 -0
  289. package/dist/tasks.js +1 -0
  290. package/dist/validation-catalog.d.ts +2 -0
  291. package/dist/validation-catalog.d.ts.map +1 -0
  292. package/dist/validation-catalog.js +3 -0
  293. package/dist/validation-config.d.ts +2 -0
  294. package/dist/validation-config.d.ts.map +1 -0
  295. package/dist/validation-config.js +3 -0
  296. package/dist/validation-content.d.ts +2 -0
  297. package/dist/validation-content.d.ts.map +1 -0
  298. package/dist/validation-content.js +3 -0
  299. package/dist/validation-core.d.ts +2 -0
  300. package/dist/validation-core.d.ts.map +1 -0
  301. package/dist/validation-core.js +3 -0
  302. package/dist/validation-public.d.ts +2 -0
  303. package/dist/validation-public.d.ts.map +1 -0
  304. package/dist/validation-public.js +3 -0
  305. package/dist/validation-shared.d.ts +2 -0
  306. package/dist/validation-shared.d.ts.map +1 -0
  307. package/dist/validation-shared.js +3 -0
  308. package/dist/validation.d.ts +2 -0
  309. package/dist/validation.d.ts.map +1 -0
  310. package/dist/validation.js +3 -0
  311. package/package.json +204 -0
@@ -0,0 +1,355 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { products } from "../schema-core.js";
3
+ export async function loadProduct(db, productId) {
4
+ const drizzle = db;
5
+ const rows = (await drizzle
6
+ .select()
7
+ .from(products)
8
+ .where(eq(products.id, productId))
9
+ .limit(1));
10
+ return rows[0];
11
+ }
12
+ export function sumPax(pax) {
13
+ if (!pax)
14
+ return 0;
15
+ let total = 0;
16
+ for (const v of Object.values(pax)) {
17
+ if (typeof v === "number" && Number.isFinite(v) && v > 0)
18
+ total += v;
19
+ }
20
+ return total;
21
+ }
22
+ export function normalizeOptionSelections(selections) {
23
+ if (!Array.isArray(selections))
24
+ return [];
25
+ return selections.flatMap((selection) => {
26
+ if (!selection ||
27
+ typeof selection !== "object" ||
28
+ typeof selection.optionId !== "string" ||
29
+ selection.optionId.length === 0) {
30
+ return [];
31
+ }
32
+ const quantity = typeof selection.quantity === "number" && Number.isFinite(selection.quantity)
33
+ ? Math.floor(selection.quantity)
34
+ : 0;
35
+ if (quantity <= 0)
36
+ return [];
37
+ return [
38
+ {
39
+ optionId: selection.optionId,
40
+ ...(typeof selection.optionUnitId === "string" && selection.optionUnitId.length > 0
41
+ ? { optionUnitId: selection.optionUnitId }
42
+ : {}),
43
+ ...(typeof selection.optionName === "string" ? { optionName: selection.optionName } : {}),
44
+ ...(typeof selection.optionUnitName === "string"
45
+ ? { optionUnitName: selection.optionUnitName }
46
+ : {}),
47
+ quantity,
48
+ },
49
+ ];
50
+ });
51
+ }
52
+ export async function priceOptionSelections(input) {
53
+ const lines = [];
54
+ let totalCents = 0;
55
+ const optionsById = new Map(input.productOptions.map((option) => [option.id, option]));
56
+ for (const selection of input.selections) {
57
+ const resolvedPrice = input.slotDate && input.options.loadResolvedOptionPrice
58
+ ? await input.options.loadResolvedOptionPrice(input.ctx, {
59
+ productId: input.product.id,
60
+ optionId: selection.optionId,
61
+ date: input.slotDate,
62
+ })
63
+ : null;
64
+ const unitPrice = selection.optionUnitId && resolvedPrice?.unitPrices
65
+ ? resolvedPrice.unitPrices.find((unit) => unit.unitId === selection.optionUnitId)
66
+ ?.sellAmountCents
67
+ : null;
68
+ const unitAmount = unitPrice ?? resolvedPrice?.baseSellAmountCents ?? input.product.sellAmountCents ?? 0;
69
+ if (unitAmount <= 0)
70
+ continue;
71
+ const totalAmount = unitAmount * selection.quantity;
72
+ totalCents += totalAmount;
73
+ lines.push({
74
+ kind: "base",
75
+ // Prefer the specific room/unit name ("Standard - Single"); fall back to
76
+ // the option name, then the product name.
77
+ label: selection.optionUnitName ?? optionsById.get(selection.optionId)?.name ?? input.product.name,
78
+ quantity: selection.quantity,
79
+ unitAmount,
80
+ totalAmount,
81
+ });
82
+ }
83
+ return { totalCents, lines };
84
+ }
85
+ export function bookingItemLinesFromOptionSelections(selections) {
86
+ const lines = selections.flatMap((selection) => selection.optionUnitId
87
+ ? [
88
+ {
89
+ optionId: selection.optionId,
90
+ optionUnitId: selection.optionUnitId,
91
+ quantity: selection.quantity,
92
+ },
93
+ ]
94
+ : []);
95
+ return lines.length > 0 ? lines : undefined;
96
+ }
97
+ export function applyAddonSelections(input) {
98
+ const extraLines = bookingExtraLinesFromAddonSelections({
99
+ addons: input.addons,
100
+ addonCatalog: input.addonCatalog,
101
+ currency: "EUR",
102
+ });
103
+ if (!extraLines?.length)
104
+ return input.priced;
105
+ const lines = [...input.priced.lines];
106
+ let totalCents = input.priced.totalCents;
107
+ for (const extra of extraLines) {
108
+ const unitAmount = extra.unitSellAmountCents ?? 0;
109
+ const quantity = extra.pricingMode === "per_person" || extra.pricedPerPerson
110
+ ? Math.max(1, input.effectivePax) * extra.quantity
111
+ : extra.quantity;
112
+ const totalAmount = unitAmount * quantity;
113
+ if (totalAmount <= 0)
114
+ continue;
115
+ totalCents += totalAmount;
116
+ lines.push({
117
+ kind: "addon",
118
+ label: extra.name,
119
+ quantity,
120
+ unitAmount,
121
+ totalAmount,
122
+ });
123
+ }
124
+ return { totalCents, lines };
125
+ }
126
+ export function bookingExtraLinesFromAddonSelections(input) {
127
+ if (!Array.isArray(input.addons) || input.addons.length === 0)
128
+ return undefined;
129
+ const catalogById = new Map((input.addonCatalog ?? []).map((offer) => [offer.id, offer]));
130
+ const lines = input.addons.flatMap((selection) => {
131
+ const offer = catalogById.get(selection.extraId);
132
+ const quantity = typeof selection.quantity === "number" && Number.isFinite(selection.quantity)
133
+ ? Math.floor(selection.quantity)
134
+ : 0;
135
+ if (!offer || quantity <= 0)
136
+ return [];
137
+ const unitSellAmountCents = offer.unitAmountCents ?? null;
138
+ const chargedQuantity = offer.pricingMode === "per_person" || offer.pricedPerPerson
139
+ ? quantity * Math.max(1, input.quantityMultiplier ?? 1)
140
+ : quantity;
141
+ return [
142
+ {
143
+ productExtraId: offer.id,
144
+ name: offer.name,
145
+ description: offer.description ?? null,
146
+ pricingMode: offer.pricingMode ?? null,
147
+ pricedPerPerson: offer.pricedPerPerson ?? null,
148
+ quantity,
149
+ sellCurrency: offer.currency ?? input.currency,
150
+ unitSellAmountCents,
151
+ totalSellAmountCents: unitSellAmountCents == null ? null : unitSellAmountCents * chargedQuantity,
152
+ },
153
+ ];
154
+ });
155
+ return lines.length > 0 ? lines : undefined;
156
+ }
157
+ /**
158
+ * Three-way price computation:
159
+ *
160
+ * 1. **Per-band** (preferred): when `resolvedPrice.unitPrices` matches
161
+ * at least one band with positive count, sum `pax[band] ×
162
+ * unit.sellAmountCents` for each matching band. One breakdown line
163
+ * per band.
164
+ *
165
+ * 2. **Per-booking**: when no per-band match but `baseSellAmountCents`
166
+ * is set, charge a single `base × paxCount` line.
167
+ *
168
+ * 3. **Fallback**: `product.sellAmountCents × paxCount`. Same shape as
169
+ * Phase A behavior, kept for bookings without an option/slot
170
+ * configured yet.
171
+ */
172
+ export function priceQuote(input) {
173
+ const { product, resolvedPrice, pax, effectivePax } = input;
174
+ if (resolvedPrice && resolvedPrice.unitPrices.length > 0) {
175
+ const bandLines = [];
176
+ let total = 0;
177
+ for (const unit of resolvedPrice.unitPrices) {
178
+ if (!unit.travelerCategory)
179
+ continue;
180
+ const count = pax?.[unit.travelerCategory] ?? 0;
181
+ if (count <= 0)
182
+ continue;
183
+ const sell = unit.sellAmountCents ?? 0;
184
+ if (sell <= 0)
185
+ continue;
186
+ const lineTotal = sell * count;
187
+ total += lineTotal;
188
+ bandLines.push({
189
+ kind: "base",
190
+ label: `${product.name} — ${unit.travelerCategory}`,
191
+ quantity: count,
192
+ unitAmount: sell,
193
+ totalAmount: lineTotal,
194
+ });
195
+ }
196
+ if (bandLines.length > 0) {
197
+ return { totalCents: total, lines: bandLines };
198
+ }
199
+ }
200
+ if (resolvedPrice && resolvedPrice.baseSellAmountCents !== null) {
201
+ const unitCents = resolvedPrice.baseSellAmountCents;
202
+ const totalCents = unitCents * effectivePax;
203
+ return {
204
+ totalCents,
205
+ lines: [
206
+ {
207
+ kind: "base",
208
+ label: product.name,
209
+ quantity: effectivePax,
210
+ unitAmount: unitCents,
211
+ totalAmount: totalCents,
212
+ },
213
+ ],
214
+ };
215
+ }
216
+ const unitCents = product.sellAmountCents ?? 0;
217
+ const totalCents = unitCents * effectivePax;
218
+ return {
219
+ totalCents,
220
+ lines: [
221
+ {
222
+ kind: "base",
223
+ label: product.name,
224
+ quantity: effectivePax,
225
+ unitAmount: unitCents,
226
+ totalAmount: totalCents,
227
+ },
228
+ ],
229
+ };
230
+ }
231
+ export function readInitialStatus(parameters) {
232
+ const allowed = [
233
+ "draft",
234
+ "on_hold",
235
+ "awaiting_payment",
236
+ "confirmed",
237
+ "in_progress",
238
+ "completed",
239
+ "cancelled",
240
+ "expired",
241
+ ];
242
+ const raw = parameters?.initialStatus;
243
+ return typeof raw === "string" && allowed.includes(raw)
244
+ ? raw
245
+ : undefined;
246
+ }
247
+ export function extractInternalNotes(party) {
248
+ if (!party)
249
+ return undefined;
250
+ const v = party.internalNotes;
251
+ return typeof v === "string" && v.length > 0 ? v : undefined;
252
+ }
253
+ export function extractBillingParty(party) {
254
+ const directBilling = asRecord(party?.billing);
255
+ const travelerParty = asRecord(party?.travelerParty);
256
+ const envelopeBilling = asRecord(travelerParty?.billing);
257
+ const billing = envelopeBilling ?? directBilling;
258
+ const contact = asRecord(billing?.contact);
259
+ return {
260
+ personId: stringValue(party?.personId) ?? stringValue(billing?.personId),
261
+ organizationId: stringValue(party?.organizationId) ?? stringValue(billing?.organizationId),
262
+ contactFirstName: stringValue(contact?.firstName),
263
+ contactLastName: stringValue(contact?.lastName),
264
+ contactEmail: stringValue(contact?.email),
265
+ contactPhone: stringValue(contact?.phone),
266
+ };
267
+ }
268
+ export function extractPartyTravelers(party) {
269
+ const travelerParty = asRecord(party?.travelerParty);
270
+ const travelers = Array.isArray(travelerParty?.travelers) ? travelerParty.travelers : [];
271
+ return travelers.map((traveler) => ({
272
+ personId: stringValue(asRecord(traveler)?.personId),
273
+ }));
274
+ }
275
+ export function asRecord(value) {
276
+ return value && typeof value === "object" && !Array.isArray(value)
277
+ ? value
278
+ : null;
279
+ }
280
+ export function stringValue(value) {
281
+ return typeof value === "string" && value.trim() ? value.trim() : null;
282
+ }
283
+ export function extractTaxLines(pricing) {
284
+ const breakdown = pricing?.breakdown;
285
+ if (!breakdown || typeof breakdown !== "object" || Array.isArray(breakdown))
286
+ return undefined;
287
+ const taxes = breakdown.taxes;
288
+ if (!Array.isArray(taxes))
289
+ return undefined;
290
+ const lines = [];
291
+ for (const [index, tax] of taxes.entries()) {
292
+ if (!tax || typeof tax !== "object" || Array.isArray(tax))
293
+ continue;
294
+ const row = tax;
295
+ const amountCents = asFiniteInteger(row.amount);
296
+ const rate = typeof row.rate === "number" && Number.isFinite(row.rate) ? row.rate : null;
297
+ const currency = typeof pricing?.currency === "string" && pricing.currency.length === 3
298
+ ? pricing.currency
299
+ : "EUR";
300
+ const name = typeof row.label === "string" && row.label.length > 0 ? row.label : "Tax";
301
+ if (!amountCents || amountCents <= 0)
302
+ continue;
303
+ const includedInPrice = row.includedInPrice === true || row.scope === "included";
304
+ lines.push({
305
+ code: typeof row.code === "string" ? row.code : null,
306
+ name,
307
+ scope: includedInPrice ? "included" : "excluded",
308
+ currency,
309
+ amountCents,
310
+ rateBasisPoints: rate == null ? null : Math.round(rate * 10_000),
311
+ includedInPrice,
312
+ sortOrder: index,
313
+ });
314
+ }
315
+ return lines.length ? lines : undefined;
316
+ }
317
+ export function resolveSellAmountCentsOverride(pricing) {
318
+ if (!pricing)
319
+ return null;
320
+ const breakdown = pricing.breakdown;
321
+ if (hasInclusiveTaxLine(breakdown)) {
322
+ const total = readBreakdownTotal(breakdown);
323
+ if (total != null)
324
+ return total;
325
+ }
326
+ return pricing.base_amount != null ? Math.round(pricing.base_amount) : null;
327
+ }
328
+ export function hasInclusiveTaxLine(breakdown) {
329
+ if (!breakdown || typeof breakdown !== "object" || Array.isArray(breakdown))
330
+ return false;
331
+ const taxes = breakdown.taxes;
332
+ if (!Array.isArray(taxes))
333
+ return false;
334
+ return taxes.some((tax) => {
335
+ if (!tax || typeof tax !== "object" || Array.isArray(tax))
336
+ return false;
337
+ const row = tax;
338
+ return row.includedInPrice === true || row.scope === "included";
339
+ });
340
+ }
341
+ export function readBreakdownTotal(breakdown) {
342
+ if (!breakdown || typeof breakdown !== "object" || Array.isArray(breakdown))
343
+ return null;
344
+ const total = breakdown.total;
345
+ return typeof total === "number" && Number.isFinite(total) ? Math.round(total) : null;
346
+ }
347
+ export function asFiniteInteger(value) {
348
+ if (typeof value !== "number" || !Number.isFinite(value))
349
+ return null;
350
+ return Math.round(value);
351
+ }
352
+ export function defaultBookingNumber() {
353
+ const ts = Date.now().toString(36).toUpperCase();
354
+ return `BK-${ts}`;
355
+ }