@voyantjs/products 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.
Files changed (70) hide show
  1. package/dist/booking-engine/handler.d.ts +203 -0
  2. package/dist/booking-engine/handler.d.ts.map +1 -0
  3. package/dist/booking-engine/handler.js +330 -0
  4. package/dist/booking-engine/index.d.ts +8 -0
  5. package/dist/booking-engine/index.d.ts.map +1 -0
  6. package/dist/booking-engine/index.js +7 -0
  7. package/dist/catalog-policy.d.ts.map +1 -1
  8. package/dist/catalog-policy.js +15 -1
  9. package/dist/content-shape.d.ts +217 -0
  10. package/dist/content-shape.d.ts.map +1 -0
  11. package/dist/content-shape.js +159 -0
  12. package/dist/draft-shape.d.ts +43 -0
  13. package/dist/draft-shape.d.ts.map +1 -0
  14. package/dist/draft-shape.js +46 -0
  15. package/dist/events.d.ts +37 -0
  16. package/dist/events.d.ts.map +1 -0
  17. package/dist/events.js +32 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/routes-content.d.ts +74 -0
  22. package/dist/routes-content.d.ts.map +1 -0
  23. package/dist/routes-content.js +117 -0
  24. package/dist/routes.d.ts +40 -20
  25. package/dist/routes.d.ts.map +1 -1
  26. package/dist/routes.js +83 -13
  27. package/dist/schema-core.d.ts +240 -1
  28. package/dist/schema-core.d.ts.map +1 -1
  29. package/dist/schema-core.js +49 -0
  30. package/dist/schema-itinerary.d.ts +18 -1
  31. package/dist/schema-itinerary.d.ts.map +1 -1
  32. package/dist/schema-itinerary.js +1 -0
  33. package/dist/schema-settings.d.ts +1 -1
  34. package/dist/schema-sourced-content.d.ts +262 -0
  35. package/dist/schema-sourced-content.d.ts.map +1 -0
  36. package/dist/schema-sourced-content.js +69 -0
  37. package/dist/schema-taxonomy.d.ts +17 -0
  38. package/dist/schema-taxonomy.d.ts.map +1 -1
  39. package/dist/schema-taxonomy.js +13 -0
  40. package/dist/schema.d.ts +1 -0
  41. package/dist/schema.d.ts.map +1 -1
  42. package/dist/schema.js +1 -0
  43. package/dist/service-catalog-plane.d.ts.map +1 -1
  44. package/dist/service-catalog-plane.js +1 -0
  45. package/dist/service-content-owned.d.ts +68 -0
  46. package/dist/service-content-owned.d.ts.map +1 -0
  47. package/dist/service-content-owned.js +224 -0
  48. package/dist/service-content-synthesizer.d.ts +90 -0
  49. package/dist/service-content-synthesizer.d.ts.map +1 -0
  50. package/dist/service-content-synthesizer.js +171 -0
  51. package/dist/service-content.d.ts +106 -0
  52. package/dist/service-content.d.ts.map +1 -0
  53. package/dist/service-content.js +365 -0
  54. package/dist/service.d.ts +76 -22
  55. package/dist/service.d.ts.map +1 -1
  56. package/dist/service.js +4 -0
  57. package/dist/tasks/brochures.d.ts +1 -0
  58. package/dist/tasks/brochures.d.ts.map +1 -1
  59. package/dist/tasks/brochures.js +3 -0
  60. package/dist/validation-catalog.d.ts +4 -4
  61. package/dist/validation-config.d.ts +3 -3
  62. package/dist/validation-content.d.ts +34 -4
  63. package/dist/validation-content.d.ts.map +1 -1
  64. package/dist/validation-content.js +13 -0
  65. package/dist/validation-core.d.ts +53 -3
  66. package/dist/validation-core.d.ts.map +1 -1
  67. package/dist/validation-core.js +16 -0
  68. package/dist/validation-public.d.ts +9 -9
  69. package/dist/validation-shared.d.ts +4 -4
  70. package/package.json +12 -7
package/dist/routes.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { parseJsonBody, parseQuery, RequestValidationError, requireUserId } from "@voyantjs/hono";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
+ import { emitProductContentChanged } from "./events.js";
4
5
  import { productsService } from "./service.js";
5
6
  import { destinationListQuerySchema, destinationTranslationListQuerySchema, duplicateItinerarySchema, insertDaySchema, insertDayServiceSchema, insertDestinationSchema, insertDestinationTranslationSchema, insertItinerarySchema, insertOptionUnitSchema, insertOptionUnitTranslationSchema, insertProductActivationSettingSchema, insertProductCapabilitySchema, insertProductCategorySchema, insertProductDeliveryFormatSchema, insertProductDestinationSchema, insertProductFaqSchema, insertProductFeatureSchema, insertProductLocationSchema, insertProductMediaSchema, insertProductNoteSchema, insertProductOptionSchema, insertProductOptionTranslationSchema, insertProductSchema, insertProductTagSchema, insertProductTicketSettingSchema, insertProductTranslationSchema, insertProductTypeSchema, insertProductVisibilitySettingSchema, insertVersionSchema, optionUnitListQuerySchema, optionUnitTranslationListQuerySchema, productActivationSettingListQuerySchema, productAggregatesQuerySchema, productCapabilityListQuerySchema, productCategoryListQuerySchema, productDeliveryFormatListQuerySchema, productDestinationListQuerySchema, productFaqListQuerySchema, productFeatureListQuerySchema, productListQuerySchema, productLocationListQuerySchema, productMediaListQuerySchema, productOptionListQuerySchema, productOptionTranslationListQuerySchema, productTagListQuerySchema, productTicketSettingListQuerySchema, productTranslationListQuerySchema, productTypeListQuerySchema, productVisibilitySettingListQuerySchema, reorderProductMediaSchema, updateDaySchema, updateDayServiceSchema, updateDestinationSchema, updateDestinationTranslationSchema, updateItinerarySchema, updateOptionUnitSchema, updateOptionUnitTranslationSchema, updateProductActivationSettingSchema, updateProductCapabilitySchema, updateProductCategorySchema, updateProductDeliveryFormatSchema, updateProductFaqSchema, updateProductFeatureSchema, updateProductLocationSchema, updateProductMediaSchema, updateProductOptionSchema, updateProductOptionTranslationSchema, updateProductSchema, updateProductTagSchema, updateProductTicketSettingSchema, updateProductTranslationSchema, updateProductTypeSchema, updateProductVisibilitySettingSchema, upsertProductBrochureSchema, } from "./validation.js";
6
7
  // ==========================================================================
@@ -198,10 +199,12 @@ export const productRoutes = new Hono()
198
199
  return c.json({ data: row });
199
200
  })
200
201
  .post("/:id/features", async (c) => {
201
- const row = await productsService.createFeature(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertProductFeatureSchema));
202
+ const productId = c.req.param("id");
203
+ const row = await productsService.createFeature(c.get("db"), productId, await parseJsonBody(c, insertProductFeatureSchema));
202
204
  if (!row) {
203
205
  return c.json({ error: "Product not found" }, 404);
204
206
  }
207
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "feature" });
205
208
  return c.json({ data: row }, 201);
206
209
  })
207
210
  .patch("/features/:id", async (c) => {
@@ -209,6 +212,9 @@ export const productRoutes = new Hono()
209
212
  if (!row) {
210
213
  return c.json({ error: "Product feature not found" }, 404);
211
214
  }
215
+ if (row.productId) {
216
+ await emitProductContentChanged(c.get("eventBus"), { id: row.productId, axis: "feature" });
217
+ }
212
218
  return c.json({ data: row });
213
219
  })
214
220
  .delete("/features/:id", async (c) => {
@@ -216,6 +222,9 @@ export const productRoutes = new Hono()
216
222
  if (!row) {
217
223
  return c.json({ error: "Product feature not found" }, 404);
218
224
  }
225
+ if ("productId" in row && typeof row.productId === "string") {
226
+ await emitProductContentChanged(c.get("eventBus"), { id: row.productId, axis: "feature" });
227
+ }
219
228
  return c.json({ success: true }, 200);
220
229
  })
221
230
  .get("/faqs", async (c) => {
@@ -230,10 +239,12 @@ export const productRoutes = new Hono()
230
239
  return c.json({ data: row });
231
240
  })
232
241
  .post("/:id/faqs", async (c) => {
233
- const row = await productsService.createFaq(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertProductFaqSchema));
242
+ const productId = c.req.param("id");
243
+ const row = await productsService.createFaq(c.get("db"), productId, await parseJsonBody(c, insertProductFaqSchema));
234
244
  if (!row) {
235
245
  return c.json({ error: "Product not found" }, 404);
236
246
  }
247
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "faq" });
237
248
  return c.json({ data: row }, 201);
238
249
  })
239
250
  .patch("/faqs/:id", async (c) => {
@@ -241,6 +252,9 @@ export const productRoutes = new Hono()
241
252
  if (!row) {
242
253
  return c.json({ error: "Product FAQ not found" }, 404);
243
254
  }
255
+ if (row.productId) {
256
+ await emitProductContentChanged(c.get("eventBus"), { id: row.productId, axis: "faq" });
257
+ }
244
258
  return c.json({ data: row });
245
259
  })
246
260
  .delete("/faqs/:id", async (c) => {
@@ -248,6 +262,9 @@ export const productRoutes = new Hono()
248
262
  if (!row) {
249
263
  return c.json({ error: "Product FAQ not found" }, 404);
250
264
  }
265
+ if ("productId" in row && typeof row.productId === "string") {
266
+ await emitProductContentChanged(c.get("eventBus"), { id: row.productId, axis: "faq" });
267
+ }
251
268
  return c.json({ success: true }, 200);
252
269
  })
253
270
  .get("/locations", async (c) => {
@@ -262,10 +279,12 @@ export const productRoutes = new Hono()
262
279
  return c.json({ data: row });
263
280
  })
264
281
  .post("/:id/locations", async (c) => {
265
- const row = await productsService.createLocation(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertProductLocationSchema));
282
+ const productId = c.req.param("id");
283
+ const row = await productsService.createLocation(c.get("db"), productId, await parseJsonBody(c, insertProductLocationSchema));
266
284
  if (!row) {
267
285
  return c.json({ error: "Product not found" }, 404);
268
286
  }
287
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "location" });
269
288
  return c.json({ data: row }, 201);
270
289
  })
271
290
  .patch("/locations/:id", async (c) => {
@@ -273,6 +292,9 @@ export const productRoutes = new Hono()
273
292
  if (!row) {
274
293
  return c.json({ error: "Product location not found" }, 404);
275
294
  }
295
+ if (row.productId) {
296
+ await emitProductContentChanged(c.get("eventBus"), { id: row.productId, axis: "location" });
297
+ }
276
298
  return c.json({ data: row });
277
299
  })
278
300
  .delete("/locations/:id", async (c) => {
@@ -280,6 +302,9 @@ export const productRoutes = new Hono()
280
302
  if (!row) {
281
303
  return c.json({ error: "Product location not found" }, 404);
282
304
  }
305
+ if ("productId" in row && typeof row.productId === "string") {
306
+ await emitProductContentChanged(c.get("eventBus"), { id: row.productId, axis: "location" });
307
+ }
283
308
  return c.json({ success: true }, 200);
284
309
  })
285
310
  .get("/destinations", async (c) => {
@@ -341,17 +366,21 @@ export const productRoutes = new Hono()
341
366
  return c.json(await productsService.listProductDestinations(c.get("db"), query));
342
367
  })
343
368
  .post("/:id/destinations", async (c) => {
344
- const row = await productsService.assignProductDestination(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertProductDestinationSchema));
369
+ const productId = c.req.param("id");
370
+ const row = await productsService.assignProductDestination(c.get("db"), productId, await parseJsonBody(c, insertProductDestinationSchema));
345
371
  if (!row) {
346
372
  return c.json({ error: "Product or destination not found" }, 404);
347
373
  }
374
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "destination" });
348
375
  return c.json({ data: row }, 201);
349
376
  })
350
377
  .delete("/:id/destinations/:destinationId", async (c) => {
351
- const row = await productsService.removeProductDestination(c.get("db"), c.req.param("id"), c.req.param("destinationId"));
378
+ const productId = c.req.param("id");
379
+ const row = await productsService.removeProductDestination(c.get("db"), productId, c.req.param("destinationId"));
352
380
  if (!row) {
353
381
  return c.json({ error: "Product destination link not found" }, 404);
354
382
  }
383
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "destination" });
355
384
  return c.json({ success: true }, 200);
356
385
  })
357
386
  // ==========================================================================
@@ -372,10 +401,12 @@ export const productRoutes = new Hono()
372
401
  })
373
402
  // POST /:id/options — Create option for product
374
403
  .post("/:id/options", async (c) => {
375
- const row = await productsService.createOption(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertProductOptionSchema));
404
+ const productId = c.req.param("id");
405
+ const row = await productsService.createOption(c.get("db"), productId, await parseJsonBody(c, insertProductOptionSchema));
376
406
  if (!row) {
377
407
  return c.json({ error: "Product not found" }, 404);
378
408
  }
409
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "option" });
379
410
  return c.json({ data: row }, 201);
380
411
  })
381
412
  // PATCH /options/:optionId — Update option
@@ -384,6 +415,9 @@ export const productRoutes = new Hono()
384
415
  if (!row) {
385
416
  return c.json({ error: "Product option not found" }, 404);
386
417
  }
418
+ if (row.productId) {
419
+ await emitProductContentChanged(c.get("eventBus"), { id: row.productId, axis: "option" });
420
+ }
387
421
  return c.json({ data: row });
388
422
  })
389
423
  // DELETE /options/:optionId — Delete option
@@ -392,6 +426,9 @@ export const productRoutes = new Hono()
392
426
  if (!row) {
393
427
  return c.json({ error: "Product option not found" }, 404);
394
428
  }
429
+ if ("productId" in row && typeof row.productId === "string") {
430
+ await emitProductContentChanged(c.get("eventBus"), { id: row.productId, axis: "option" });
431
+ }
395
432
  return c.json({ success: true }, 200);
396
433
  })
397
434
  // ==========================================================================
@@ -449,10 +486,12 @@ export const productRoutes = new Hono()
449
486
  return c.json({ data: row });
450
487
  })
451
488
  .post("/:id/translations", async (c) => {
452
- const row = await productsService.createProductTranslation(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertProductTranslationSchema));
489
+ const productId = c.req.param("id");
490
+ const row = await productsService.createProductTranslation(c.get("db"), productId, await parseJsonBody(c, insertProductTranslationSchema));
453
491
  if (!row) {
454
492
  return c.json({ error: "Product not found" }, 404);
455
493
  }
494
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "translation" });
456
495
  return c.json({ data: row }, 201);
457
496
  })
458
497
  .patch("/translations/:translationId", async (c) => {
@@ -460,6 +499,12 @@ export const productRoutes = new Hono()
460
499
  if (!row) {
461
500
  return c.json({ error: "Product translation not found" }, 404);
462
501
  }
502
+ if (row.productId) {
503
+ await emitProductContentChanged(c.get("eventBus"), {
504
+ id: row.productId,
505
+ axis: "translation",
506
+ });
507
+ }
463
508
  return c.json({ data: row });
464
509
  })
465
510
  .delete("/translations/:translationId", async (c) => {
@@ -467,6 +512,12 @@ export const productRoutes = new Hono()
467
512
  if (!row) {
468
513
  return c.json({ error: "Product translation not found" }, 404);
469
514
  }
515
+ if ("productId" in row && typeof row.productId === "string") {
516
+ await emitProductContentChanged(c.get("eventBus"), {
517
+ id: row.productId,
518
+ axis: "translation",
519
+ });
520
+ }
470
521
  return c.json({ success: true }, 200);
471
522
  })
472
523
  .get("/option-translations", async (c) => {
@@ -730,6 +781,7 @@ export const productRoutes = new Hono()
730
781
  return c.json({ error: "Product not found" }, 404);
731
782
  }
732
783
  await c.get("eventBus")?.emit("product.updated", { id: row.id });
784
+ await emitProductContentChanged(c.get("eventBus"), { id: row.id, axis: "product" });
733
785
  return c.json({ data: row });
734
786
  })
735
787
  // DELETE /:id — Delete product
@@ -797,26 +849,32 @@ export const productRoutes = new Hono()
797
849
  })
798
850
  // POST /:id/days — Add day to product
799
851
  .post("/:id/days", async (c) => {
800
- const row = await productsService.createDay(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertDaySchema));
852
+ const productId = c.req.param("id");
853
+ const row = await productsService.createDay(c.get("db"), productId, await parseJsonBody(c, insertDaySchema));
801
854
  if (!row) {
802
855
  return c.json({ error: "Product not found" }, 404);
803
856
  }
857
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "day" });
804
858
  return c.json({ data: row }, 201);
805
859
  })
806
860
  // PATCH /:id/days/:dayId — Update day
807
861
  .patch("/:id/days/:dayId", async (c) => {
862
+ const productId = c.req.param("id");
808
863
  const row = await productsService.updateDay(c.get("db"), c.req.param("dayId"), await parseJsonBody(c, updateDaySchema));
809
864
  if (!row) {
810
865
  return c.json({ error: "Day not found" }, 404);
811
866
  }
867
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "day" });
812
868
  return c.json({ data: row });
813
869
  })
814
870
  // DELETE /:id/days/:dayId — Delete day
815
871
  .delete("/:id/days/:dayId", async (c) => {
872
+ const productId = c.req.param("id");
816
873
  const row = await productsService.deleteDay(c.get("db"), c.req.param("dayId"));
817
874
  if (!row) {
818
875
  return c.json({ error: "Day not found" }, 404);
819
876
  }
877
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "day" });
820
878
  return c.json({ success: true }, 200);
821
879
  })
822
880
  // ==========================================================================
@@ -830,26 +888,32 @@ export const productRoutes = new Hono()
830
888
  })
831
889
  // POST /:id/days/:dayId/services — Add service to day
832
890
  .post("/:id/days/:dayId/services", async (c) => {
833
- const row = await productsService.createDayService(c.get("db"), c.req.param("id"), c.req.param("dayId"), await parseJsonBody(c, insertDayServiceSchema));
891
+ const productId = c.req.param("id");
892
+ const row = await productsService.createDayService(c.get("db"), productId, c.req.param("dayId"), await parseJsonBody(c, insertDayServiceSchema));
834
893
  if (!row) {
835
894
  return c.json({ error: "Day not found" }, 404);
836
895
  }
896
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "day" });
837
897
  return c.json({ data: row }, 201);
838
898
  })
839
899
  // PATCH /:id/days/:dayId/services/:serviceId — Update service
840
900
  .patch("/:id/days/:dayId/services/:serviceId", async (c) => {
841
- const row = await productsService.updateDayService(c.get("db"), c.req.param("id"), c.req.param("serviceId"), await parseJsonBody(c, updateDayServiceSchema));
901
+ const productId = c.req.param("id");
902
+ const row = await productsService.updateDayService(c.get("db"), productId, c.req.param("serviceId"), await parseJsonBody(c, updateDayServiceSchema));
842
903
  if (!row) {
843
904
  return c.json({ error: "Service not found" }, 404);
844
905
  }
906
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "day" });
845
907
  return c.json({ data: row });
846
908
  })
847
909
  // DELETE /:id/days/:dayId/services/:serviceId — Delete service
848
910
  .delete("/:id/days/:dayId/services/:serviceId", async (c) => {
849
- const row = await productsService.deleteDayService(c.get("db"), c.req.param("id"), c.req.param("serviceId"));
911
+ const productId = c.req.param("id");
912
+ const row = await productsService.deleteDayService(c.get("db"), productId, c.req.param("serviceId"));
850
913
  if (!row) {
851
914
  return c.json({ error: "Service not found" }, 404);
852
915
  }
916
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "day" });
853
917
  return c.json({ success: true }, 200);
854
918
  })
855
919
  // ==========================================================================
@@ -952,16 +1016,20 @@ export const productRoutes = new Hono()
952
1016
  })
953
1017
  // POST /:id/media — Create media for product
954
1018
  .post("/:id/media", async (c) => {
955
- const row = await productsService.createMedia(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertProductMediaSchema));
1019
+ const productId = c.req.param("id");
1020
+ const row = await productsService.createMedia(c.get("db"), productId, await parseJsonBody(c, insertProductMediaSchema));
956
1021
  if (!row) {
957
1022
  return c.json({ error: "Product not found or invalid dayId" }, 404);
958
1023
  }
1024
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "media" });
959
1025
  return c.json({ data: row }, 201);
960
1026
  })
961
1027
  // POST /:id/media/reorder — Batch reorder media
962
1028
  .post("/:id/media/reorder", async (c) => {
1029
+ const productId = c.req.param("id");
963
1030
  const data = await parseJsonBody(c, reorderProductMediaSchema);
964
1031
  const results = await productsService.reorderMedia(c.get("db"), data);
1032
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "media" });
965
1033
  return c.json({ data: results });
966
1034
  })
967
1035
  // GET /:id/days/:dayId/media — List day media
@@ -974,14 +1042,16 @@ export const productRoutes = new Hono()
974
1042
  })
975
1043
  // POST /:id/days/:dayId/media — Create day media
976
1044
  .post("/:id/days/:dayId/media", async (c) => {
1045
+ const productId = c.req.param("id");
977
1046
  const body = await parseJsonBody(c, insertProductMediaSchema);
978
- const row = await productsService.createMedia(c.get("db"), c.req.param("id"), {
1047
+ const row = await productsService.createMedia(c.get("db"), productId, {
979
1048
  ...body,
980
1049
  dayId: c.req.param("dayId"),
981
1050
  });
982
1051
  if (!row) {
983
1052
  return c.json({ error: "Product or day not found" }, 404);
984
1053
  }
1054
+ await emitProductContentChanged(c.get("eventBus"), { id: productId, axis: "media" });
985
1055
  return c.json({ data: row }, 201);
986
1056
  })
987
1057
  // ==========================================================================
@@ -257,6 +257,23 @@ export declare const products: import("drizzle-orm/pg-core").PgTableWithColumns<
257
257
  identity: undefined;
258
258
  generated: undefined;
259
259
  }, {}, {}>;
260
+ supplierId: import("drizzle-orm/pg-core").PgColumn<{
261
+ name: "supplier_id";
262
+ tableName: "products";
263
+ dataType: "string";
264
+ columnType: "PgText";
265
+ data: string;
266
+ driverParam: string;
267
+ notNull: false;
268
+ hasDefault: false;
269
+ isPrimaryKey: false;
270
+ isAutoincrement: false;
271
+ hasRuntimeDefault: false;
272
+ enumValues: [string, ...string[]];
273
+ baseColumn: never;
274
+ identity: undefined;
275
+ generated: undefined;
276
+ }, {}, {}>;
260
277
  startDate: import("drizzle-orm/pg-core").PgColumn<{
261
278
  name: "start_date";
262
279
  tableName: "products";
@@ -325,6 +342,40 @@ export declare const products: import("drizzle-orm/pg-core").PgTableWithColumns<
325
342
  identity: undefined;
326
343
  generated: undefined;
327
344
  }, {}, {}>;
345
+ taxClassId: import("drizzle-orm/pg-core").PgColumn<{
346
+ name: "tax_class_id";
347
+ tableName: "products";
348
+ dataType: "string";
349
+ columnType: "PgText";
350
+ data: string;
351
+ driverParam: string;
352
+ notNull: false;
353
+ hasDefault: false;
354
+ isPrimaryKey: false;
355
+ isAutoincrement: false;
356
+ hasRuntimeDefault: false;
357
+ enumValues: [string, ...string[]];
358
+ baseColumn: never;
359
+ identity: undefined;
360
+ generated: undefined;
361
+ }, {}, {}>;
362
+ customerPaymentPolicy: import("drizzle-orm/pg-core").PgColumn<{
363
+ name: "customer_payment_policy";
364
+ tableName: "products";
365
+ dataType: "json";
366
+ columnType: "PgJsonb";
367
+ data: unknown;
368
+ driverParam: unknown;
369
+ notNull: false;
370
+ hasDefault: false;
371
+ isPrimaryKey: false;
372
+ isAutoincrement: false;
373
+ hasRuntimeDefault: false;
374
+ enumValues: undefined;
375
+ baseColumn: never;
376
+ identity: undefined;
377
+ generated: undefined;
378
+ }, {}, {}>;
328
379
  tags: import("drizzle-orm/pg-core").PgColumn<{
329
380
  name: "tags";
330
381
  tableName: "products";
@@ -690,7 +741,7 @@ export declare const optionUnits: import("drizzle-orm/pg-core").PgTableWithColum
690
741
  tableName: "option_units";
691
742
  dataType: "string";
692
743
  columnType: "PgEnumColumn";
693
- data: "service" | "other" | "person" | "group" | "room" | "vehicle";
744
+ data: "service" | "other" | "group" | "person" | "room" | "vehicle";
694
745
  driverParam: string;
695
746
  notNull: true;
696
747
  hasDefault: true;
@@ -894,4 +945,192 @@ export declare const optionUnits: import("drizzle-orm/pg-core").PgTableWithColum
894
945
  }>;
895
946
  export type OptionUnit = typeof optionUnits.$inferSelect;
896
947
  export type NewOptionUnit = typeof optionUnits.$inferInsert;
948
+ /**
949
+ * Per-product per-occupancy rate tiers for non-cruise verticals.
950
+ * Cruises keep the specialized `cruise_prices` table; everyone else
951
+ * uses this. Per booking-journey-architecture §9.
952
+ *
953
+ * `tier_pax` is the occupancy count the rate applies to (1-supp, 2-default,
954
+ * 3-share, 4). Falls back to `option_unit_tiers` (quantity-based, not
955
+ * occupancy-based) when no occupancy tier exists.
956
+ */
957
+ export declare const productPaxPricingTiers: import("drizzle-orm/pg-core").PgTableWithColumns<{
958
+ name: "product_pax_pricing_tiers";
959
+ schema: undefined;
960
+ columns: {
961
+ id: import("drizzle-orm/pg-core").PgColumn<{
962
+ name: string;
963
+ tableName: "product_pax_pricing_tiers";
964
+ dataType: "string";
965
+ columnType: "PgText";
966
+ data: string;
967
+ driverParam: string;
968
+ notNull: true;
969
+ hasDefault: true;
970
+ isPrimaryKey: true;
971
+ isAutoincrement: false;
972
+ hasRuntimeDefault: true;
973
+ enumValues: [string, ...string[]];
974
+ baseColumn: never;
975
+ identity: undefined;
976
+ generated: undefined;
977
+ }, {}, {}>;
978
+ productId: import("drizzle-orm/pg-core").PgColumn<{
979
+ name: string;
980
+ tableName: "product_pax_pricing_tiers";
981
+ dataType: "string";
982
+ columnType: "PgText";
983
+ data: string;
984
+ driverParam: string;
985
+ notNull: true;
986
+ hasDefault: false;
987
+ isPrimaryKey: false;
988
+ isAutoincrement: false;
989
+ hasRuntimeDefault: false;
990
+ enumValues: [string, ...string[]];
991
+ baseColumn: never;
992
+ identity: undefined;
993
+ generated: undefined;
994
+ }, {}, {}>;
995
+ optionUnitId: import("drizzle-orm/pg-core").PgColumn<{
996
+ name: string;
997
+ tableName: "product_pax_pricing_tiers";
998
+ dataType: "string";
999
+ columnType: "PgText";
1000
+ data: string;
1001
+ driverParam: string;
1002
+ notNull: false;
1003
+ hasDefault: false;
1004
+ isPrimaryKey: false;
1005
+ isAutoincrement: false;
1006
+ hasRuntimeDefault: false;
1007
+ enumValues: [string, ...string[]];
1008
+ baseColumn: never;
1009
+ identity: undefined;
1010
+ generated: undefined;
1011
+ }, {}, {}>;
1012
+ tierPax: import("drizzle-orm/pg-core").PgColumn<{
1013
+ name: "tier_pax";
1014
+ tableName: "product_pax_pricing_tiers";
1015
+ dataType: "number";
1016
+ columnType: "PgInteger";
1017
+ data: number;
1018
+ driverParam: string | number;
1019
+ notNull: true;
1020
+ hasDefault: false;
1021
+ isPrimaryKey: false;
1022
+ isAutoincrement: false;
1023
+ hasRuntimeDefault: false;
1024
+ enumValues: undefined;
1025
+ baseColumn: never;
1026
+ identity: undefined;
1027
+ generated: undefined;
1028
+ }, {}, {}>;
1029
+ pricePerPaxCents: import("drizzle-orm/pg-core").PgColumn<{
1030
+ name: "price_per_pax_cents";
1031
+ tableName: "product_pax_pricing_tiers";
1032
+ dataType: "number";
1033
+ columnType: "PgInteger";
1034
+ data: number;
1035
+ driverParam: string | number;
1036
+ notNull: true;
1037
+ hasDefault: false;
1038
+ isPrimaryKey: false;
1039
+ isAutoincrement: false;
1040
+ hasRuntimeDefault: false;
1041
+ enumValues: undefined;
1042
+ baseColumn: never;
1043
+ identity: undefined;
1044
+ generated: undefined;
1045
+ }, {}, {}>;
1046
+ promoPricePerPaxCents: import("drizzle-orm/pg-core").PgColumn<{
1047
+ name: "promo_price_per_pax_cents";
1048
+ tableName: "product_pax_pricing_tiers";
1049
+ dataType: "number";
1050
+ columnType: "PgInteger";
1051
+ data: number;
1052
+ driverParam: string | number;
1053
+ notNull: false;
1054
+ hasDefault: false;
1055
+ isPrimaryKey: false;
1056
+ isAutoincrement: false;
1057
+ hasRuntimeDefault: false;
1058
+ enumValues: undefined;
1059
+ baseColumn: never;
1060
+ identity: undefined;
1061
+ generated: undefined;
1062
+ }, {}, {}>;
1063
+ effectiveFrom: import("drizzle-orm/pg-core").PgColumn<{
1064
+ name: "effective_from";
1065
+ tableName: "product_pax_pricing_tiers";
1066
+ dataType: "string";
1067
+ columnType: "PgDateString";
1068
+ data: string;
1069
+ driverParam: string;
1070
+ notNull: false;
1071
+ hasDefault: false;
1072
+ isPrimaryKey: false;
1073
+ isAutoincrement: false;
1074
+ hasRuntimeDefault: false;
1075
+ enumValues: undefined;
1076
+ baseColumn: never;
1077
+ identity: undefined;
1078
+ generated: undefined;
1079
+ }, {}, {}>;
1080
+ effectiveTo: import("drizzle-orm/pg-core").PgColumn<{
1081
+ name: "effective_to";
1082
+ tableName: "product_pax_pricing_tiers";
1083
+ dataType: "string";
1084
+ columnType: "PgDateString";
1085
+ data: string;
1086
+ driverParam: string;
1087
+ notNull: false;
1088
+ hasDefault: false;
1089
+ isPrimaryKey: false;
1090
+ isAutoincrement: false;
1091
+ hasRuntimeDefault: false;
1092
+ enumValues: undefined;
1093
+ baseColumn: never;
1094
+ identity: undefined;
1095
+ generated: undefined;
1096
+ }, {}, {}>;
1097
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
1098
+ name: "created_at";
1099
+ tableName: "product_pax_pricing_tiers";
1100
+ dataType: "date";
1101
+ columnType: "PgTimestamp";
1102
+ data: Date;
1103
+ driverParam: string;
1104
+ notNull: true;
1105
+ hasDefault: true;
1106
+ isPrimaryKey: false;
1107
+ isAutoincrement: false;
1108
+ hasRuntimeDefault: false;
1109
+ enumValues: undefined;
1110
+ baseColumn: never;
1111
+ identity: undefined;
1112
+ generated: undefined;
1113
+ }, {}, {}>;
1114
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
1115
+ name: "updated_at";
1116
+ tableName: "product_pax_pricing_tiers";
1117
+ dataType: "date";
1118
+ columnType: "PgTimestamp";
1119
+ data: Date;
1120
+ driverParam: string;
1121
+ notNull: true;
1122
+ hasDefault: true;
1123
+ isPrimaryKey: false;
1124
+ isAutoincrement: false;
1125
+ hasRuntimeDefault: false;
1126
+ enumValues: undefined;
1127
+ baseColumn: never;
1128
+ identity: undefined;
1129
+ generated: undefined;
1130
+ }, {}, {}>;
1131
+ };
1132
+ dialect: "pg";
1133
+ }>;
1134
+ export type ProductPaxPricingTier = typeof productPaxPricingTiers.$inferSelect;
1135
+ export type NewProductPaxPricingTier = typeof productPaxPricingTiers.$inferInsert;
897
1136
  //# sourceMappingURL=schema-core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-core.d.ts","sourceRoot":"","sources":["../src/schema-core.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CpB,CAAA;AAED,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAA;AAClD,MAAM,MAAM,UAAU,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAA;AAErD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyB1B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AAC9D,MAAM,MAAM,gBAAgB,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AAEjE,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BvB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,OAAO,WAAW,CAAC,YAAY,CAAA;AACxD,MAAM,MAAM,aAAa,GAAG,OAAO,WAAW,CAAC,YAAY,CAAA"}
1
+ {"version":3,"file":"schema-core.d.ts","sourceRoot":"","sources":["../src/schema-core.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgEpB,CAAA;AAED,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAA;AAClD,MAAM,MAAM,UAAU,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAA;AAErD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyB1B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AAC9D,MAAM,MAAM,gBAAgB,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AAEjE,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BvB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,OAAO,WAAW,CAAC,YAAY,CAAA;AACxD,MAAM,MAAM,aAAa,GAAG,OAAO,WAAW,CAAC,YAAY,CAAA;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBlC,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,OAAO,sBAAsB,CAAC,YAAY,CAAA;AAC9E,MAAM,MAAM,wBAAwB,GAAG,OAAO,sBAAsB,CAAC,YAAY,CAAA"}
@@ -17,16 +17,35 @@ export const products = pgTable("products", {
17
17
  costAmountCents: integer("cost_amount_cents"),
18
18
  marginPercent: integer("margin_percent"),
19
19
  facilityId: text("facility_id"),
20
+ supplierId: text("supplier_id"),
20
21
  startDate: date("start_date"),
21
22
  endDate: date("end_date"),
22
23
  pax: integer("pax"),
23
24
  productTypeId: text("product_type_id"),
25
+ /**
26
+ * Per-product tax class — drives the engine's tax computation at
27
+ * quote time. Plain text (no FK) since `tax_classes` lives in
28
+ * @voyantjs/finance and cross-domain refs go through the link
29
+ * service per schema-discipline. Default null → falls through to a
30
+ * market-level default. Per booking-journey-architecture §9.
31
+ */
32
+ taxClassId: text("tax_class_id"),
33
+ /**
34
+ * Per-listing customer payment policy override. Wins over the
35
+ * product's category and supplier policies in the cascade. Shape
36
+ * mirrors `PaymentPolicy` from `@voyantjs/finance`.
37
+ *
38
+ * `null` means "inherit from category / supplier / operator
39
+ * default" — most products leave this empty.
40
+ */
41
+ customerPaymentPolicy: jsonb("customer_payment_policy"),
24
42
  tags: jsonb("tags").$type().default([]),
25
43
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
26
44
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
27
45
  }, (table) => [
28
46
  index("idx_products_status").on(table.status),
29
47
  index("idx_products_facility").on(table.facilityId),
48
+ index("idx_products_supplier").on(table.supplierId),
30
49
  index("idx_products_product_type").on(table.productTypeId),
31
50
  index("idx_products_status_created").on(table.status, table.createdAt),
32
51
  index("idx_products_booking_mode_created").on(table.bookingMode, table.createdAt),
@@ -34,6 +53,7 @@ export const products = pgTable("products", {
34
53
  index("idx_products_visibility_created").on(table.visibility, table.createdAt),
35
54
  index("idx_products_activated_created").on(table.activated, table.createdAt),
36
55
  index("idx_products_facility_created").on(table.facilityId, table.createdAt),
56
+ index("idx_products_supplier_created").on(table.supplierId, table.createdAt),
37
57
  index("idx_products_product_type_created").on(table.productTypeId, table.createdAt),
38
58
  index("idx_products_public_created").on(table.status, table.activated, table.visibility, table.createdAt),
39
59
  ]);
@@ -85,3 +105,32 @@ export const optionUnits = pgTable("option_units", {
85
105
  index("idx_option_units_type").on(table.unitType),
86
106
  uniqueIndex("uidx_option_units_option_code").on(table.optionId, table.code),
87
107
  ]);
108
+ /**
109
+ * Per-product per-occupancy rate tiers for non-cruise verticals.
110
+ * Cruises keep the specialized `cruise_prices` table; everyone else
111
+ * uses this. Per booking-journey-architecture §9.
112
+ *
113
+ * `tier_pax` is the occupancy count the rate applies to (1-supp, 2-default,
114
+ * 3-share, 4). Falls back to `option_unit_tiers` (quantity-based, not
115
+ * occupancy-based) when no occupancy tier exists.
116
+ */
117
+ export const productPaxPricingTiers = pgTable("product_pax_pricing_tiers", {
118
+ id: typeId("product_pax_pricing_tiers"),
119
+ productId: typeIdRef("product_id")
120
+ .notNull()
121
+ .references(() => products.id, { onDelete: "cascade" }),
122
+ optionUnitId: typeIdRef("option_unit_id").references(() => optionUnits.id, {
123
+ onDelete: "cascade",
124
+ }),
125
+ tierPax: integer("tier_pax").notNull(),
126
+ pricePerPaxCents: integer("price_per_pax_cents").notNull(),
127
+ promoPricePerPaxCents: integer("promo_price_per_pax_cents"),
128
+ effectiveFrom: date("effective_from"),
129
+ effectiveTo: date("effective_to"),
130
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
131
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
132
+ }, (table) => [
133
+ index("idx_pax_tiers_product").on(table.productId),
134
+ index("idx_pax_tiers_unit").on(table.optionUnitId),
135
+ uniqueIndex("uidx_pax_tiers_unit_pax").on(table.optionUnitId, table.tierPax),
136
+ ]);