@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.
- package/dist/booking-engine/handler.d.ts +203 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +330 -0
- package/dist/booking-engine/index.d.ts +8 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +7 -0
- package/dist/catalog-policy.d.ts.map +1 -1
- package/dist/catalog-policy.js +15 -1
- package/dist/content-shape.d.ts +217 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +159 -0
- package/dist/draft-shape.d.ts +43 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +46 -0
- package/dist/events.d.ts +37 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +32 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/routes-content.d.ts +74 -0
- package/dist/routes-content.d.ts.map +1 -0
- package/dist/routes-content.js +117 -0
- package/dist/routes.d.ts +40 -20
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +83 -13
- package/dist/schema-core.d.ts +240 -1
- package/dist/schema-core.d.ts.map +1 -1
- package/dist/schema-core.js +49 -0
- package/dist/schema-itinerary.d.ts +18 -1
- package/dist/schema-itinerary.d.ts.map +1 -1
- package/dist/schema-itinerary.js +1 -0
- package/dist/schema-settings.d.ts +1 -1
- package/dist/schema-sourced-content.d.ts +262 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +69 -0
- package/dist/schema-taxonomy.d.ts +17 -0
- package/dist/schema-taxonomy.d.ts.map +1 -1
- package/dist/schema-taxonomy.js +13 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/service-catalog-plane.d.ts.map +1 -1
- package/dist/service-catalog-plane.js +1 -0
- package/dist/service-content-owned.d.ts +68 -0
- package/dist/service-content-owned.d.ts.map +1 -0
- package/dist/service-content-owned.js +224 -0
- package/dist/service-content-synthesizer.d.ts +90 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +171 -0
- package/dist/service-content.d.ts +106 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +365 -0
- package/dist/service.d.ts +76 -22
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +4 -0
- package/dist/tasks/brochures.d.ts +1 -0
- package/dist/tasks/brochures.d.ts.map +1 -1
- package/dist/tasks/brochures.js +3 -0
- package/dist/validation-catalog.d.ts +4 -4
- package/dist/validation-config.d.ts +3 -3
- package/dist/validation-content.d.ts +34 -4
- package/dist/validation-content.d.ts.map +1 -1
- package/dist/validation-content.js +13 -0
- package/dist/validation-core.d.ts +53 -3
- package/dist/validation-core.d.ts.map +1 -1
- package/dist/validation-core.js +16 -0
- package/dist/validation-public.d.ts +9 -9
- package/dist/validation-shared.d.ts +4 -4
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"),
|
|
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
|
// ==========================================================================
|
package/dist/schema-core.d.ts
CHANGED
|
@@ -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" | "
|
|
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
|
|
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"}
|
package/dist/schema-core.js
CHANGED
|
@@ -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
|
+
]);
|