@voyantjs/finance 0.81.1 → 0.81.5
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.
|
@@ -6,6 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
import type { BookingPaymentSchedule, Invoice, Payment, Voucher, VoucherRedemption } from "./schema.js";
|
|
7
7
|
import { type InvoiceDocumentGenerator } from "./service-documents.js";
|
|
8
8
|
declare const travelerInputSchema: z.ZodObject<{
|
|
9
|
+
clientTravelerKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
9
10
|
firstName: z.ZodString;
|
|
10
11
|
lastName: z.ZodString;
|
|
11
12
|
email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
@@ -64,6 +65,7 @@ export declare const bookingCreateSchema: z.ZodObject<{
|
|
|
64
65
|
contactAddressLine1: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
65
66
|
contactPostalCode: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
66
67
|
travelers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
68
|
+
clientTravelerKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
67
69
|
firstName: z.ZodString;
|
|
68
70
|
lastName: z.ZodString;
|
|
69
71
|
email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
@@ -95,6 +97,7 @@ export declare const bookingCreateSchema: z.ZodObject<{
|
|
|
95
97
|
description: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
96
98
|
unitSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
97
99
|
totalSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
100
|
+
travelerKeys: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
98
101
|
travelerIndexes: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodNumber>>>;
|
|
99
102
|
}, z.core.$strip>>>;
|
|
100
103
|
extraLines: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -109,6 +112,7 @@ export declare const bookingCreateSchema: z.ZodObject<{
|
|
|
109
112
|
sellCurrency: z.ZodString;
|
|
110
113
|
unitSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
111
114
|
totalSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
115
|
+
travelerKeys: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
112
116
|
travelerIndexes: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodNumber>>>;
|
|
113
117
|
}, z.core.$strip>>>;
|
|
114
118
|
paymentSchedules: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -177,6 +181,7 @@ export declare const bookingCreateSubSchema: z.ZodObject<{
|
|
|
177
181
|
contactPostalCode: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
178
182
|
internalNotes: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
179
183
|
travelers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
184
|
+
clientTravelerKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
180
185
|
firstName: z.ZodString;
|
|
181
186
|
lastName: z.ZodString;
|
|
182
187
|
email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
@@ -223,6 +228,7 @@ export declare const bookingCreateSubSchema: z.ZodObject<{
|
|
|
223
228
|
description: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
224
229
|
unitSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
225
230
|
totalSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
231
|
+
travelerKeys: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
226
232
|
travelerIndexes: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodNumber>>>;
|
|
227
233
|
}, z.core.$strip>>>;
|
|
228
234
|
suppressNotifications: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -238,6 +244,7 @@ export declare const bookingCreateSubSchema: z.ZodObject<{
|
|
|
238
244
|
sellCurrency: z.ZodString;
|
|
239
245
|
unitSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
240
246
|
totalSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
247
|
+
travelerKeys: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
241
248
|
travelerIndexes: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodNumber>>>;
|
|
242
249
|
}, z.core.$strip>>>;
|
|
243
250
|
paymentSchedules: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-booking-create.d.ts","sourceRoot":"","sources":["../src/service-booking-create.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,oBAAoB,EAG1B,MAAM,uCAAuC,CAAA;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAG7F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EACV,sBAAsB,EACtB,OAAO,EACP,OAAO,EACP,OAAO,EACP,iBAAiB,EAClB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAA2B,KAAK,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AAU/F,QAAA,MAAM,mBAAmB
|
|
1
|
+
{"version":3,"file":"service-booking-create.d.ts","sourceRoot":"","sources":["../src/service-booking-create.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ import { VoucherServiceError, vouchersService } from "./service-vouchers.js";
|
|
|
11
11
|
import { paymentMethodSchema, paymentScheduleStatusSchema, paymentScheduleTypeSchema, } from "./validation-shared.js";
|
|
12
12
|
// ---------- validation ----------
|
|
13
13
|
const travelerInputSchema = z.object({
|
|
14
|
+
clientTravelerKey: z.string().min(1).max(255).optional().nullable(),
|
|
14
15
|
firstName: z.string().min(1).max(255),
|
|
15
16
|
lastName: z.string().min(1).max(255),
|
|
16
17
|
email: z.string().email().optional().nullable(),
|
|
@@ -21,11 +22,10 @@ const travelerInputSchema = z.object({
|
|
|
21
22
|
preferredLanguage: z.string().max(35).optional().nullable(),
|
|
22
23
|
specialRequests: z.string().optional().nullable(),
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* persisted
|
|
27
|
-
*
|
|
28
|
-
* metadata JSONB or wire into booking_allocations.
|
|
25
|
+
* Deprecated compatibility alias for the traveler's pricing-tier option
|
|
26
|
+
* unit. Accepted by the input schema for wire compatibility but not
|
|
27
|
+
* persisted; item-line travelerKeys are the supported traveler-to-item
|
|
28
|
+
* linkage.
|
|
29
29
|
*/
|
|
30
30
|
roomUnitId: z.string().optional().nullable(),
|
|
31
31
|
isPrimary: z.boolean().optional().nullable(),
|
|
@@ -60,9 +60,13 @@ const itemLineInputSchema = z.object({
|
|
|
60
60
|
unitSellAmountCents: z.number().int().min(0).optional().nullable(),
|
|
61
61
|
totalSellAmountCents: z.number().int().min(0).optional().nullable(),
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
|
|
63
|
+
* Stable traveler keys this item applies to. Server inserts one
|
|
64
|
+
* `booking_item_travelers` row per traveler.
|
|
65
|
+
*/
|
|
66
|
+
travelerKeys: z.array(z.string().min(1).max(255)).optional().nullable(),
|
|
67
|
+
/**
|
|
68
|
+
* Deprecated position-based traveler links. Removal target: next
|
|
69
|
+
* booking-create wire-format major.
|
|
66
70
|
*/
|
|
67
71
|
travelerIndexes: z.array(z.number().int().min(0)).optional().nullable(),
|
|
68
72
|
});
|
|
@@ -78,6 +82,7 @@ const extraLineInputSchema = z.object({
|
|
|
78
82
|
sellCurrency: z.string().length(3),
|
|
79
83
|
unitSellAmountCents: z.number().int().min(0).optional().nullable(),
|
|
80
84
|
totalSellAmountCents: z.number().int().min(0).optional().nullable(),
|
|
85
|
+
travelerKeys: z.array(z.string().min(1).max(255)).optional().nullable(),
|
|
81
86
|
travelerIndexes: z.array(z.number().int().min(0)).optional().nullable(),
|
|
82
87
|
});
|
|
83
88
|
const voucherRedemptionInputSchema = z.object({
|
|
@@ -188,6 +193,50 @@ function requireCompleteBookingParty(value, ctx) {
|
|
|
188
193
|
}
|
|
189
194
|
});
|
|
190
195
|
}
|
|
196
|
+
function findDuplicateClientTravelerKeys(travelers) {
|
|
197
|
+
const seen = new Set();
|
|
198
|
+
const duplicates = new Set();
|
|
199
|
+
for (const traveler of travelers ?? []) {
|
|
200
|
+
const key = traveler.clientTravelerKey?.trim();
|
|
201
|
+
if (!key)
|
|
202
|
+
continue;
|
|
203
|
+
if (seen.has(key))
|
|
204
|
+
duplicates.add(key);
|
|
205
|
+
else
|
|
206
|
+
seen.add(key);
|
|
207
|
+
}
|
|
208
|
+
return [...duplicates];
|
|
209
|
+
}
|
|
210
|
+
function requireUniqueClientTravelerKeys(value, ctx) {
|
|
211
|
+
for (const duplicateKey of findDuplicateClientTravelerKeys(value.travelers)) {
|
|
212
|
+
ctx.addIssue({
|
|
213
|
+
code: z.ZodIssueCode.custom,
|
|
214
|
+
path: ["travelers"],
|
|
215
|
+
message: `Duplicate clientTravelerKey: ${duplicateKey}`,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function requireKnownTravelerKeys(value, ctx) {
|
|
220
|
+
const knownKeys = new Set((value.travelers ?? [])
|
|
221
|
+
.map((traveler) => traveler.clientTravelerKey?.trim())
|
|
222
|
+
.filter((key) => Boolean(key)));
|
|
223
|
+
const checkLines = (field, lines) => {
|
|
224
|
+
lines?.forEach((line, lineIndex) => {
|
|
225
|
+
line.travelerKeys?.forEach((travelerKey, keyIndex) => {
|
|
226
|
+
const key = travelerKey.trim();
|
|
227
|
+
if (!key || knownKeys.has(key))
|
|
228
|
+
return;
|
|
229
|
+
ctx.addIssue({
|
|
230
|
+
code: z.ZodIssueCode.custom,
|
|
231
|
+
path: [field, lineIndex, "travelerKeys", keyIndex],
|
|
232
|
+
message: `Unknown travelerKey: ${key}`,
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
checkLines("itemLines", value.itemLines);
|
|
238
|
+
checkLines("extraLines", value.extraLines);
|
|
239
|
+
}
|
|
191
240
|
function isRealEmail(value) {
|
|
192
241
|
const normalized = value?.trim().toLowerCase() ?? "";
|
|
193
242
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized) && !placeholderEmails.has(normalized);
|
|
@@ -255,11 +304,15 @@ const bookingCreateBaseSchema = z.object({
|
|
|
255
304
|
});
|
|
256
305
|
export const bookingCreateSchema = bookingCreateBaseSchema
|
|
257
306
|
.superRefine(requirePriceOverrideReason)
|
|
258
|
-
.superRefine(requireCompleteBookingParty)
|
|
307
|
+
.superRefine(requireCompleteBookingParty)
|
|
308
|
+
.superRefine(requireUniqueClientTravelerKeys)
|
|
309
|
+
.superRefine(requireKnownTravelerKeys);
|
|
259
310
|
export const bookingCreateSubSchema = bookingCreateBaseSchema
|
|
260
311
|
.omit({ groupMembership: true })
|
|
261
312
|
.superRefine(requirePriceOverrideReason)
|
|
262
|
-
.superRefine(requireCompleteBookingParty)
|
|
313
|
+
.superRefine(requireCompleteBookingParty)
|
|
314
|
+
.superRefine(requireUniqueClientTravelerKeys)
|
|
315
|
+
.superRefine(requireKnownTravelerKeys);
|
|
263
316
|
// ---------- service ----------
|
|
264
317
|
/**
|
|
265
318
|
* Atomic booking-create orchestrator. Runs product conversion + travelers +
|
|
@@ -347,11 +400,54 @@ async function loadProductOptionUnits(tx, productId) {
|
|
|
347
400
|
unitType: row.unitType ?? null,
|
|
348
401
|
}));
|
|
349
402
|
}
|
|
403
|
+
function isInventoryOptionUnit(unit) {
|
|
404
|
+
return unit.unitType === "room" || unit.unitType === "vehicle";
|
|
405
|
+
}
|
|
406
|
+
function isPersonOptionUnit(unit) {
|
|
407
|
+
return unit.unitType == null || unit.unitType === "person";
|
|
408
|
+
}
|
|
409
|
+
function normalizeAccommodationItemLinesToInventoryUnits(options) {
|
|
410
|
+
if (!options.itemLines?.length || options.units.length === 0)
|
|
411
|
+
return options.itemLines;
|
|
412
|
+
const unitsByOption = new Map();
|
|
413
|
+
const unitById = new Map();
|
|
414
|
+
const unitToPrimaryInventory = new Map();
|
|
415
|
+
for (const unit of options.units) {
|
|
416
|
+
const optionKey = unit.optionId ?? unit.optionUnitId;
|
|
417
|
+
unitById.set(unit.optionUnitId, unit);
|
|
418
|
+
const optionUnits = unitsByOption.get(optionKey);
|
|
419
|
+
if (optionUnits)
|
|
420
|
+
optionUnits.push(unit);
|
|
421
|
+
else
|
|
422
|
+
unitsByOption.set(optionKey, [unit]);
|
|
423
|
+
}
|
|
424
|
+
for (const optionUnits of unitsByOption.values()) {
|
|
425
|
+
const primaryInventory = optionUnits.find(isInventoryOptionUnit);
|
|
426
|
+
if (!primaryInventory)
|
|
427
|
+
continue;
|
|
428
|
+
for (const unit of optionUnits) {
|
|
429
|
+
unitToPrimaryInventory.set(unit.optionUnitId, primaryInventory);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return options.itemLines.map((line) => {
|
|
433
|
+
const submittedUnit = unitById.get(line.optionUnitId);
|
|
434
|
+
const targetInventory = unitToPrimaryInventory.get(line.optionUnitId);
|
|
435
|
+
if (!submittedUnit || !targetInventory)
|
|
436
|
+
return line;
|
|
437
|
+
if (isInventoryOptionUnit(submittedUnit) || !isPersonOptionUnit(submittedUnit))
|
|
438
|
+
return line;
|
|
439
|
+
return {
|
|
440
|
+
...line,
|
|
441
|
+
optionUnitId: targetInventory.optionUnitId,
|
|
442
|
+
};
|
|
443
|
+
});
|
|
444
|
+
}
|
|
350
445
|
function hasResolverRejectionSignals(input) {
|
|
446
|
+
const hasTravelerLinks = (line) => (Array.isArray(line.travelerKeys) && line.travelerKeys.length > 0) ||
|
|
447
|
+
(Array.isArray(line.travelerIndexes) && line.travelerIndexes.length > 0);
|
|
351
448
|
return (input.travelers.every((traveler) => traveler.travelerCategory === "adult" ||
|
|
352
449
|
traveler.travelerCategory === "child" ||
|
|
353
|
-
traveler.travelerCategory === "infant") &&
|
|
354
|
-
input.itemLines.every((line) => Array.isArray(line.travelerIndexes) && line.travelerIndexes.length > 0));
|
|
450
|
+
traveler.travelerCategory === "infant") && input.itemLines.every(hasTravelerLinks));
|
|
355
451
|
}
|
|
356
452
|
/**
|
|
357
453
|
* Re-runs `resolveBookingDraft` against the submitted payload and
|
|
@@ -379,7 +475,7 @@ async function verifyBookingCreatePayload(tx, input) {
|
|
|
379
475
|
}
|
|
380
476
|
}
|
|
381
477
|
/**
|
|
382
|
-
* Filter + dedupe `travelerIndexes` against the inserted traveler
|
|
478
|
+
* Filter + dedupe deprecated `travelerIndexes` against the inserted traveler
|
|
383
479
|
* array, dropping any indexes outside `[0, travelersLength)`.
|
|
384
480
|
*/
|
|
385
481
|
function uniqueValidTravelerIndexes(indexes, travelersLength) {
|
|
@@ -397,6 +493,20 @@ function uniqueValidTravelerIndexes(indexes, travelersLength) {
|
|
|
397
493
|
}
|
|
398
494
|
return result;
|
|
399
495
|
}
|
|
496
|
+
function uniqueTravelerKeys(keys) {
|
|
497
|
+
if (!keys?.length)
|
|
498
|
+
return [];
|
|
499
|
+
const seen = new Set();
|
|
500
|
+
const result = [];
|
|
501
|
+
for (const key of keys) {
|
|
502
|
+
const normalized = key.trim();
|
|
503
|
+
if (!normalized || seen.has(normalized))
|
|
504
|
+
continue;
|
|
505
|
+
seen.add(normalized);
|
|
506
|
+
result.push(normalized);
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
400
510
|
/**
|
|
401
511
|
* Look up each `booking_item` the converter inserted by its stamped
|
|
402
512
|
* `metadata.bookingCreateLineKey`, then write one
|
|
@@ -408,13 +518,34 @@ function uniqueValidTravelerIndexes(indexes, travelersLength) {
|
|
|
408
518
|
* through the create flow without forcing the converter to return a
|
|
409
519
|
* map back to the orchestrator. See voyantjs/voyant#1267.
|
|
410
520
|
*/
|
|
411
|
-
async function linkBookingCreateItemsToTravelers(tx, bookingId, travelers, lines) {
|
|
521
|
+
async function linkBookingCreateItemsToTravelers(tx, bookingId, travelers, travelerInputs, lines) {
|
|
412
522
|
if (travelers.length === 0 || lines.length === 0)
|
|
413
523
|
return;
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
524
|
+
const duplicateTravelerKeys = findDuplicateClientTravelerKeys(travelerInputs);
|
|
525
|
+
if (duplicateTravelerKeys.length > 0) {
|
|
526
|
+
throw new Error(`Duplicate clientTravelerKey: ${duplicateTravelerKeys.join(", ")}`);
|
|
527
|
+
}
|
|
528
|
+
const travelerByClientKey = new Map();
|
|
529
|
+
for (const [index, travelerInput] of travelerInputs.entries()) {
|
|
530
|
+
const key = travelerInput.clientTravelerKey?.trim();
|
|
531
|
+
const traveler = travelers[index];
|
|
532
|
+
if (key && traveler && !travelerByClientKey.has(key))
|
|
533
|
+
travelerByClientKey.set(key, traveler);
|
|
534
|
+
}
|
|
535
|
+
const requestedLinks = lines.flatMap((line) => {
|
|
536
|
+
const travelerKeys = uniqueTravelerKeys(line.travelerKeys);
|
|
537
|
+
if (travelerKeys.length > 0) {
|
|
538
|
+
return travelerKeys.map((travelerKey) => ({
|
|
539
|
+
clientLineKey: line.clientLineKey ?? null,
|
|
540
|
+
travelerKey,
|
|
541
|
+
traveler: travelerByClientKey.get(travelerKey) ?? null,
|
|
542
|
+
}));
|
|
543
|
+
}
|
|
544
|
+
return uniqueValidTravelerIndexes(line.travelerIndexes, travelers.length).map((travelerIndex) => ({
|
|
545
|
+
clientLineKey: line.clientLineKey ?? null,
|
|
546
|
+
traveler: travelers[travelerIndex] ?? null,
|
|
547
|
+
}));
|
|
548
|
+
});
|
|
418
549
|
if (requestedLinks.length === 0)
|
|
419
550
|
return;
|
|
420
551
|
const itemRows = await tx.select().from(bookingItems).where(eq(bookingItems.bookingId, bookingId));
|
|
@@ -426,11 +557,17 @@ async function linkBookingCreateItemsToTravelers(tx, bookingId, travelers, lines
|
|
|
426
557
|
itemByClientLineKey.set(key, item);
|
|
427
558
|
}
|
|
428
559
|
const seen = new Set();
|
|
429
|
-
const
|
|
560
|
+
const unknownTravelerKeys = requestedLinks
|
|
561
|
+
.filter((link) => "travelerKey" in link && !link.traveler)
|
|
562
|
+
.map((link) => ("travelerKey" in link ? link.travelerKey : null))
|
|
563
|
+
.filter((key) => Boolean(key));
|
|
564
|
+
if (unknownTravelerKeys.length > 0) {
|
|
565
|
+
throw new Error(`Unknown travelerKey: ${unknownTravelerKeys.join(", ")}`);
|
|
566
|
+
}
|
|
567
|
+
const linkRows = requestedLinks.flatMap(({ clientLineKey, traveler }) => {
|
|
430
568
|
if (!clientLineKey)
|
|
431
569
|
return [];
|
|
432
570
|
const item = itemByClientLineKey.get(clientLineKey);
|
|
433
|
-
const traveler = travelers[travelerIndex];
|
|
434
571
|
if (!item || !traveler)
|
|
435
572
|
return [];
|
|
436
573
|
const dedupeKey = `${item.id}:${traveler.id}`;
|
|
@@ -540,6 +677,13 @@ export async function createBooking(db, rawInput, options = {}) {
|
|
|
540
677
|
let result;
|
|
541
678
|
try {
|
|
542
679
|
result = await db.transaction(async (tx) => {
|
|
680
|
+
const productOptionUnits = input.itemLines?.length
|
|
681
|
+
? await loadProductOptionUnits(tx, input.productId)
|
|
682
|
+
: [];
|
|
683
|
+
const normalizedItemLines = normalizeAccommodationItemLinesToInventoryUnits({
|
|
684
|
+
itemLines: input.itemLines,
|
|
685
|
+
units: productOptionUnits,
|
|
686
|
+
});
|
|
543
687
|
// 1. Booking from product
|
|
544
688
|
const booking = await bookingsService.createBookingFromProduct(tx, {
|
|
545
689
|
productId: input.productId,
|
|
@@ -565,7 +709,7 @@ export async function createBooking(db, rawInput, options = {}) {
|
|
|
565
709
|
contactCity: input.contactCity ?? null,
|
|
566
710
|
contactAddressLine1: input.contactAddressLine1 ?? null,
|
|
567
711
|
contactPostalCode: input.contactPostalCode ?? null,
|
|
568
|
-
itemLines:
|
|
712
|
+
itemLines: normalizedItemLines,
|
|
569
713
|
});
|
|
570
714
|
if (!booking) {
|
|
571
715
|
// Caller gave us a product that doesn't resolve. Throw so drizzle
|
|
@@ -615,10 +759,10 @@ export async function createBooking(db, rawInput, options = {}) {
|
|
|
615
759
|
};
|
|
616
760
|
}));
|
|
617
761
|
}
|
|
618
|
-
// 2. Travelers. The wire-format `roomUnitId` on a traveler is
|
|
619
|
-
// accepted for
|
|
620
|
-
// traveler row itself
|
|
621
|
-
// expressed through `booking_item_travelers` rows linked from
|
|
762
|
+
// 2. Travelers. The wire-format `roomUnitId` on a traveler is a
|
|
763
|
+
// deprecated pricing-tier alias accepted for compatibility but
|
|
764
|
+
// not stored on the traveler row itself. Per-traveler item linkage
|
|
765
|
+
// is expressed through `booking_item_travelers` rows linked from
|
|
622
766
|
// each `booking_item`. See voyantjs/voyant#1267.
|
|
623
767
|
const travelers = [];
|
|
624
768
|
for (const traveler of input.travelers ?? []) {
|
|
@@ -643,19 +787,20 @@ export async function createBooking(db, rawInput, options = {}) {
|
|
|
643
787
|
travelers.push(row);
|
|
644
788
|
}
|
|
645
789
|
// 2b. Link booking_items + extras to specific travelers when
|
|
646
|
-
// the caller supplied `clientLineKey` + `
|
|
647
|
-
//
|
|
790
|
+
// the caller supplied `clientLineKey` + `travelerKeys` on any
|
|
791
|
+
// line. Deprecated `travelerIndexes` remain a fallback. Item
|
|
792
|
+
// rows were inserted earlier by
|
|
648
793
|
// `convertProductToBooking` (this slice's product converter
|
|
649
794
|
// doesn't run them in the orchestrator); we look them up by
|
|
650
795
|
// the `metadata.bookingCreateLineKey` the converter stamped.
|
|
651
|
-
await linkBookingCreateItemsToTravelers(tx, booking.id, travelers, [
|
|
652
|
-
...(
|
|
796
|
+
await linkBookingCreateItemsToTravelers(tx, booking.id, travelers, input.travelers ?? [], [
|
|
797
|
+
...(normalizedItemLines ?? []),
|
|
653
798
|
...(input.extraLines ?? []),
|
|
654
799
|
]);
|
|
655
800
|
// 2c. Re-run the resolver server-side against the submitted
|
|
656
801
|
// itemLines + travelers and reject any client/server drift on
|
|
657
802
|
// per-band quantities. See voyantjs/voyant#1272.
|
|
658
|
-
await verifyBookingCreatePayload(tx, input);
|
|
803
|
+
await verifyBookingCreatePayload(tx, { ...input, itemLines: normalizedItemLines });
|
|
659
804
|
// 3. Payment schedules
|
|
660
805
|
const paymentSchedules = [];
|
|
661
806
|
for (const schedule of input.paymentSchedules ?? []) {
|
|
@@ -23,6 +23,7 @@ export declare const dualCreateBookingSchema: z.ZodObject<{
|
|
|
23
23
|
contactPostalCode: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
24
24
|
internalNotes: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
25
25
|
travelers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
26
|
+
clientTravelerKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
26
27
|
firstName: z.ZodString;
|
|
27
28
|
lastName: z.ZodString;
|
|
28
29
|
email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
@@ -69,6 +70,7 @@ export declare const dualCreateBookingSchema: z.ZodObject<{
|
|
|
69
70
|
description: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
70
71
|
unitSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
71
72
|
totalSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
73
|
+
travelerKeys: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
72
74
|
travelerIndexes: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodNumber>>>;
|
|
73
75
|
}, z.core.$strip>>>;
|
|
74
76
|
suppressNotifications: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -84,6 +86,7 @@ export declare const dualCreateBookingSchema: z.ZodObject<{
|
|
|
84
86
|
sellCurrency: z.ZodString;
|
|
85
87
|
unitSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
86
88
|
totalSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
89
|
+
travelerKeys: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
87
90
|
travelerIndexes: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodNumber>>>;
|
|
88
91
|
}, z.core.$strip>>>;
|
|
89
92
|
paymentSchedules: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -135,6 +138,7 @@ export declare const dualCreateBookingSchema: z.ZodObject<{
|
|
|
135
138
|
contactPostalCode: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
136
139
|
internalNotes: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
137
140
|
travelers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
141
|
+
clientTravelerKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
138
142
|
firstName: z.ZodString;
|
|
139
143
|
lastName: z.ZodString;
|
|
140
144
|
email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
@@ -181,6 +185,7 @@ export declare const dualCreateBookingSchema: z.ZodObject<{
|
|
|
181
185
|
description: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
182
186
|
unitSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
183
187
|
totalSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
188
|
+
travelerKeys: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
184
189
|
travelerIndexes: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodNumber>>>;
|
|
185
190
|
}, z.core.$strip>>>;
|
|
186
191
|
suppressNotifications: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -196,6 +201,7 @@ export declare const dualCreateBookingSchema: z.ZodObject<{
|
|
|
196
201
|
sellCurrency: z.ZodString;
|
|
197
202
|
unitSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
198
203
|
totalSellAmountCents: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
|
|
204
|
+
travelerKeys: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
199
205
|
travelerIndexes: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodNumber>>>;
|
|
200
206
|
}, z.core.$strip>>>;
|
|
201
207
|
paymentSchedules: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-bookings-dual-create.d.ts","sourceRoot":"","sources":["../src/service-bookings-dual-create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AACjF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAGzB,MAAM,6BAA6B,CAAA;AAkBpC,eAAO,MAAM,uBAAuB
|
|
1
|
+
{"version":3,"file":"service-bookings-dual-create.d.ts","sourceRoot":"","sources":["../src/service-bookings-dual-create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AACjF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAGzB,MAAM,6BAA6B,CAAA;AAkBpC,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIlC,CAAA;AAEF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAI5E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,MAAM,CAAA;IACxB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,UAAU,EAAE,IAAI,CAAA;CACjB;AAID,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,mBAAmB,CAAA;IAC5B,SAAS,EAAE,mBAAmB,CAAA;IAC9B,KAAK,EAAE,YAAY,CAAA;IACnB,aAAa,EAAE,kBAAkB,CAAA;IACjC,eAAe,EAAE,kBAAkB,CAAA;CACpC;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GACjD;IACE,MAAM,EAAE,gBAAgB,GAAG,kBAAkB,CAAA;IAC7C,MAAM,EAAE,OAAO,CAAC,oBAAoB,EAAE;QAAE,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;CACxD,CAAA;AAqBL;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,kBAAkB,EACtB,QAAQ,EAAE,sBAAsB,EAChC,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,wBAAwB,CAAA;CAC9B,GACL,OAAO,CAAC,wBAAwB,CAAC,CAkFnC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/finance",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -60,14 +60,14 @@
|
|
|
60
60
|
"drizzle-orm": "^0.45.2",
|
|
61
61
|
"hono": "^4.12.10",
|
|
62
62
|
"zod": "^4.3.6",
|
|
63
|
-
"@voyantjs/action-ledger": "0.81.
|
|
64
|
-
"@voyantjs/bookings": "0.81.
|
|
65
|
-
"@voyantjs/core": "0.81.
|
|
66
|
-
"@voyantjs/db": "0.81.
|
|
67
|
-
"@voyantjs/hono": "0.81.
|
|
68
|
-
"@voyantjs/products": "0.81.
|
|
69
|
-
"@voyantjs/storage": "0.81.
|
|
70
|
-
"@voyantjs/utils": "0.81.
|
|
63
|
+
"@voyantjs/action-ledger": "0.81.5",
|
|
64
|
+
"@voyantjs/bookings": "0.81.5",
|
|
65
|
+
"@voyantjs/core": "0.81.5",
|
|
66
|
+
"@voyantjs/db": "0.81.5",
|
|
67
|
+
"@voyantjs/hono": "0.81.5",
|
|
68
|
+
"@voyantjs/products": "0.81.5",
|
|
69
|
+
"@voyantjs/storage": "0.81.5",
|
|
70
|
+
"@voyantjs/utils": "0.81.5"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"typescript": "^6.0.2",
|