includio-cms 0.36.9 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/API.md +178 -3
  2. package/CHANGELOG.md +54 -0
  3. package/DOCS.md +28 -3
  4. package/ROADMAP.md +18 -0
  5. package/dist/admin/client/booking/booking-dashboard.svelte +40 -0
  6. package/dist/admin/client/booking/booking-dashboard.svelte.d.ts +3 -0
  7. package/dist/admin/client/booking/customer-panel.svelte +102 -0
  8. package/dist/admin/client/booking/customer-panel.svelte.d.ts +11 -0
  9. package/dist/admin/client/booking/customer-view.d.ts +52 -0
  10. package/dist/admin/client/booking/customer-view.js +134 -0
  11. package/dist/admin/client/booking/participant-data-panel.svelte +330 -0
  12. package/dist/admin/client/booking/participant-data-panel.svelte.d.ts +22 -0
  13. package/dist/admin/client/booking/reservation-detail-page.svelte +637 -0
  14. package/dist/admin/client/booking/reservation-detail-page.svelte.d.ts +3 -0
  15. package/dist/admin/client/booking/reservations-page.svelte +221 -0
  16. package/dist/admin/client/booking/reservations-page.svelte.d.ts +3 -0
  17. package/dist/admin/client/collection/collection-entries.svelte +1 -1
  18. package/dist/admin/client/collection/entry-link.svelte +6 -4
  19. package/dist/admin/client/collection/entry-link.svelte.d.ts +3 -1
  20. package/dist/admin/client/collection/status-badge.svelte +39 -3
  21. package/dist/admin/client/collection/status-badge.svelte.d.ts +5 -3
  22. package/dist/admin/client/entry/entry.svelte +26 -9
  23. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +73 -10
  24. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte.d.ts +6 -5
  25. package/dist/admin/client/entry/preview-views-context.svelte.d.ts +22 -0
  26. package/dist/admin/client/entry/preview-views-context.svelte.js +58 -0
  27. package/dist/admin/client/entry/preview-views.d.ts +27 -0
  28. package/dist/admin/client/entry/preview-views.js +33 -0
  29. package/dist/admin/client/index.d.ts +3 -0
  30. package/dist/admin/client/index.js +3 -0
  31. package/dist/admin/client/modules/dashboard-registry.d.ts +7 -0
  32. package/dist/admin/client/modules/dashboard-registry.js +11 -0
  33. package/dist/admin/client/modules/module-dashboard-page.svelte +26 -0
  34. package/dist/admin/client/modules/module-dashboard-page.svelte.d.ts +6 -0
  35. package/dist/admin/client/modules/stat-card.svelte +22 -0
  36. package/dist/admin/client/modules/stat-card.svelte.d.ts +10 -0
  37. package/dist/admin/client/shop/shop-dashboard.svelte +29 -0
  38. package/dist/admin/client/shop/shop-dashboard.svelte.d.ts +3 -0
  39. package/dist/admin/components/fields/relation-field.svelte +11 -2
  40. package/dist/admin/components/icons/icon-map.js +8 -0
  41. package/dist/admin/components/layout/app-sidebar.svelte +45 -14
  42. package/dist/admin/components/layout/context-switcher.svelte +90 -0
  43. package/dist/admin/components/layout/context-switcher.svelte.d.ts +3 -0
  44. package/dist/admin/components/layout/lang.d.ts +4 -0
  45. package/dist/admin/components/layout/lang.js +8 -0
  46. package/dist/admin/components/layout/layout-renderer.svelte +2 -1
  47. package/dist/admin/components/layout/layout-tabs.svelte +14 -0
  48. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +2 -0
  49. package/dist/admin/components/layout/nav-collections.svelte +16 -9
  50. package/dist/admin/components/layout/nav-collections.svelte.d.ts +5 -1
  51. package/dist/admin/components/layout/nav-global.svelte +45 -0
  52. package/dist/admin/components/layout/nav-global.svelte.d.ts +3 -0
  53. package/dist/admin/components/layout/nav-main.svelte +8 -32
  54. package/dist/admin/components/layout/nav-main.svelte.d.ts +6 -1
  55. package/dist/admin/components/layout/nav-module.svelte +57 -0
  56. package/dist/admin/components/layout/nav-module.svelte.d.ts +7 -0
  57. package/dist/admin/components/layout/nav-section.svelte +1 -1
  58. package/dist/admin/components/layout/nav-section.svelte.d.ts +1 -1
  59. package/dist/admin/components/layout/nav-singletons.svelte +14 -7
  60. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +5 -1
  61. package/dist/admin/remote/booking.remote.d.ts +244 -0
  62. package/dist/admin/remote/booking.remote.js +283 -0
  63. package/dist/admin/remote/index.d.ts +2 -0
  64. package/dist/admin/remote/index.js +2 -0
  65. package/dist/admin/remote/modules.remote.d.ts +10 -0
  66. package/dist/admin/remote/modules.remote.js +14 -0
  67. package/dist/admin/state/active-context-resolve.d.ts +37 -0
  68. package/dist/admin/state/active-context-resolve.js +66 -0
  69. package/dist/admin/state/active-context.svelte.d.ts +8 -0
  70. package/dist/admin/state/active-context.svelte.js +27 -0
  71. package/dist/booking/adjustments.d.ts +33 -0
  72. package/dist/booking/adjustments.js +26 -0
  73. package/dist/booking/capacity.d.ts +12 -0
  74. package/dist/booking/capacity.js +10 -0
  75. package/dist/booking/client/index.d.ts +39 -0
  76. package/dist/booking/client/index.js +44 -0
  77. package/dist/booking/client/use-booking.svelte.d.ts +25 -0
  78. package/dist/booking/client/use-booking.svelte.js +56 -0
  79. package/dist/booking/config.d.ts +156 -0
  80. package/dist/booking/config.js +36 -0
  81. package/dist/booking/draft.d.ts +77 -0
  82. package/dist/booking/draft.js +67 -0
  83. package/dist/booking/expiry-logic.d.ts +30 -0
  84. package/dist/booking/expiry-logic.js +33 -0
  85. package/dist/booking/index.d.ts +18 -0
  86. package/dist/booking/index.js +52 -0
  87. package/dist/booking/ledger.d.ts +46 -0
  88. package/dist/booking/ledger.js +60 -0
  89. package/dist/booking/number.d.ts +2 -0
  90. package/dist/booking/number.js +11 -0
  91. package/dist/booking/participant-fields/access.d.ts +10 -0
  92. package/dist/booking/participant-fields/access.js +14 -0
  93. package/dist/booking/participant-fields/age.d.ts +12 -0
  94. package/dist/booking/participant-fields/age.js +28 -0
  95. package/dist/booking/participant-fields/completeness.d.ts +8 -0
  96. package/dist/booking/participant-fields/completeness.js +13 -0
  97. package/dist/booking/participant-fields/group.d.ts +6 -0
  98. package/dist/booking/participant-fields/group.js +15 -0
  99. package/dist/booking/participant-fields/index.d.ts +6 -0
  100. package/dist/booking/participant-fields/index.js +6 -0
  101. package/dist/booking/participant-fields/types.d.ts +50 -0
  102. package/dist/booking/participant-fields/types.js +5 -0
  103. package/dist/booking/participant-fields/validate.d.ts +7 -0
  104. package/dist/booking/participant-fields/validate.js +44 -0
  105. package/dist/booking/participant-pricing.d.ts +93 -0
  106. package/dist/booking/participant-pricing.js +91 -0
  107. package/dist/booking/participants-view.d.ts +32 -0
  108. package/dist/booking/participants-view.js +22 -0
  109. package/dist/booking/payment-context.d.ts +19 -0
  110. package/dist/booking/payment-context.js +29 -0
  111. package/dist/booking/pricing-inputs.d.ts +28 -0
  112. package/dist/booking/pricing-inputs.js +35 -0
  113. package/dist/booking/pricing.d.ts +86 -0
  114. package/dist/booking/pricing.js +125 -0
  115. package/dist/booking/recompute.d.ts +22 -0
  116. package/dist/booking/recompute.js +31 -0
  117. package/dist/booking/reminders-schedule.d.ts +11 -0
  118. package/dist/booking/reminders-schedule.js +28 -0
  119. package/dist/booking/server/adjustments.d.ts +27 -0
  120. package/dist/booking/server/adjustments.js +136 -0
  121. package/dist/booking/server/bookings.d.ts +45 -0
  122. package/dist/booking/server/bookings.js +288 -0
  123. package/dist/booking/server/catalog.d.ts +9 -0
  124. package/dist/booking/server/catalog.js +20 -0
  125. package/dist/booking/server/db.d.ts +7 -0
  126. package/dist/booking/server/db.js +18 -0
  127. package/dist/booking/server/email.d.ts +23 -0
  128. package/dist/booking/server/email.js +59 -0
  129. package/dist/booking/server/emailTemplateRegistry.d.ts +29 -0
  130. package/dist/booking/server/emailTemplateRegistry.js +125 -0
  131. package/dist/booking/server/http/create-handler.d.ts +5 -0
  132. package/dist/booking/server/http/create-handler.js +68 -0
  133. package/dist/booking/server/http/index.d.ts +4 -0
  134. package/dist/booking/server/http/index.js +4 -0
  135. package/dist/booking/server/http/maintenance-handler.d.ts +9 -0
  136. package/dist/booking/server/http/maintenance-handler.js +36 -0
  137. package/dist/booking/server/http/portal-handler.d.ts +13 -0
  138. package/dist/booking/server/http/portal-handler.js +99 -0
  139. package/dist/booking/server/http/webhook-handler.d.ts +9 -0
  140. package/dist/booking/server/http/webhook-handler.js +39 -0
  141. package/dist/booking/server/index.d.ts +12 -0
  142. package/dist/booking/server/index.js +14 -0
  143. package/dist/booking/server/maintenance.d.ts +28 -0
  144. package/dist/booking/server/maintenance.js +134 -0
  145. package/dist/booking/server/participants.d.ts +26 -0
  146. package/dist/booking/server/participants.js +92 -0
  147. package/dist/booking/server/payments.d.ts +34 -0
  148. package/dist/booking/server/payments.js +207 -0
  149. package/dist/booking/server/portal-session.d.ts +16 -0
  150. package/dist/booking/server/portal-session.js +42 -0
  151. package/dist/booking/server/portal.d.ts +207 -0
  152. package/dist/booking/server/portal.js +234 -0
  153. package/dist/booking/server/refunds.d.ts +7 -0
  154. package/dist/booking/server/refunds.js +58 -0
  155. package/dist/booking/server/reminders.d.ts +9 -0
  156. package/dist/booking/server/reminders.js +24 -0
  157. package/dist/booking/server/validation.d.ts +17 -0
  158. package/dist/booking/server/validation.js +55 -0
  159. package/dist/booking/state-machine.d.ts +41 -0
  160. package/dist/booking/state-machine.js +73 -0
  161. package/dist/booking/templates/_partials/footer.en.html +4 -0
  162. package/dist/booking/templates/_partials/footer.pl.html +4 -0
  163. package/dist/booking/templates/_partials/header.en.html +4 -0
  164. package/dist/booking/templates/_partials/header.pl.html +4 -0
  165. package/dist/booking/templates/balance-due.en.html +10 -0
  166. package/dist/booking/templates/balance-due.pl.html +10 -0
  167. package/dist/booking/templates/balance-overdue-cancelled.en.html +5 -0
  168. package/dist/booking/templates/balance-overdue-cancelled.pl.html +5 -0
  169. package/dist/booking/templates/booking-received.en.html +9 -0
  170. package/dist/booking/templates/booking-received.pl.html +9 -0
  171. package/dist/booking/templates/deposit-paid.en.html +6 -0
  172. package/dist/booking/templates/deposit-paid.pl.html +6 -0
  173. package/dist/booking/templates/payment-link.en.html +5 -0
  174. package/dist/booking/templates/payment-link.pl.html +5 -0
  175. package/dist/booking/templates/payment-received.en.html +10 -0
  176. package/dist/booking/templates/payment-received.pl.html +10 -0
  177. package/dist/booking/totals.d.ts +38 -0
  178. package/dist/booking/totals.js +61 -0
  179. package/dist/booking/webhook-logic.d.ts +28 -0
  180. package/dist/booking/webhook-logic.js +50 -0
  181. package/dist/cli/create-user.d.ts +7 -1
  182. package/dist/cli/create-user.js +23 -15
  183. package/dist/cli/index.js +55 -7
  184. package/dist/cli/scaffold/admin.d.ts +6 -0
  185. package/dist/cli/scaffold/admin.js +5 -3
  186. package/dist/cli/scaffold/booking.d.ts +12 -0
  187. package/dist/cli/scaffold/booking.js +103 -0
  188. package/dist/core/cms.d.ts +24 -9
  189. package/dist/core/cms.js +72 -34
  190. package/dist/core/config/validatePreviews.d.ts +12 -0
  191. package/dist/core/config/validatePreviews.js +90 -0
  192. package/dist/core/fields/slugPath.d.ts +9 -1
  193. package/dist/core/fields/slugPath.js +16 -2
  194. package/dist/core/server/fields/resolveRelationFields.d.ts +11 -2
  195. package/dist/core/server/fields/resolveRelationFields.js +12 -3
  196. package/dist/core/server/generator/generator.js +7 -2
  197. package/dist/db-postgres/schema/booking/bookingAdjustments.d.ts +186 -0
  198. package/dist/db-postgres/schema/booking/bookingAdjustments.js +22 -0
  199. package/dist/db-postgres/schema/booking/bookingItemPersons.d.ts +117 -0
  200. package/dist/db-postgres/schema/booking/bookingItemPersons.js +22 -0
  201. package/dist/db-postgres/schema/booking/bookingItems.d.ts +181 -0
  202. package/dist/db-postgres/schema/booking/bookingItems.js +22 -0
  203. package/dist/db-postgres/schema/booking/bookingPayments.d.ts +204 -0
  204. package/dist/db-postgres/schema/booking/bookingPayments.js +25 -0
  205. package/dist/db-postgres/schema/booking/bookingPersons.d.ts +99 -0
  206. package/dist/db-postgres/schema/booking/bookingPersons.js +17 -0
  207. package/dist/db-postgres/schema/booking/bookingReminders.d.ts +131 -0
  208. package/dist/db-postgres/schema/booking/bookingReminders.js +18 -0
  209. package/dist/db-postgres/schema/booking/bookings.d.ts +307 -0
  210. package/dist/db-postgres/schema/booking/bookings.js +40 -0
  211. package/dist/db-postgres/schema/booking/index.d.ts +7 -0
  212. package/dist/db-postgres/schema/booking/index.js +7 -0
  213. package/dist/db-postgres/schema/index.d.ts +1 -0
  214. package/dist/db-postgres/schema/index.js +1 -0
  215. package/dist/db-postgres/schema-booking.d.ts +1 -0
  216. package/dist/db-postgres/schema-booking.js +1 -0
  217. package/dist/server/security/rate-limit-handler.d.ts +19 -0
  218. package/dist/server/security/rate-limit-handler.js +34 -0
  219. package/dist/shop/adapters/payu/client.js +3 -1
  220. package/dist/shop/adapters/payu/index.js +37 -17
  221. package/dist/shop/adapters/payu/payload.d.ts +6 -0
  222. package/dist/shop/adapters/payu/payload.js +1 -1
  223. package/dist/shop/adapters/stripe/index.js +36 -21
  224. package/dist/shop/index.d.ts +11 -2
  225. package/dist/shop/index.js +43 -1
  226. package/dist/shop/rate-limit.d.ts +1 -9
  227. package/dist/shop/rate-limit.js +3 -28
  228. package/dist/shop/types.d.ts +19 -0
  229. package/dist/sveltekit/config.d.ts +4 -0
  230. package/dist/sveltekit/config.js +6 -0
  231. package/dist/types/cms.d.ts +15 -1
  232. package/dist/types/cms.schema.d.ts +96 -2
  233. package/dist/types/cms.schema.js +84 -1
  234. package/dist/types/config.d.ts +33 -0
  235. package/dist/types/layout.d.ts +6 -0
  236. package/dist/types/modules.d.ts +133 -0
  237. package/dist/types/modules.js +27 -0
  238. package/dist/updates/0.37.0/index.d.ts +2 -0
  239. package/dist/updates/0.37.0/index.js +52 -0
  240. package/dist/updates/index.js +3 -1
  241. package/package.json +19 -1
  242. package/dist/admin/components/layout/nav-shop.svelte +0 -33
  243. package/dist/admin/components/layout/nav-shop.svelte.d.ts +0 -3
@@ -0,0 +1,17 @@
1
+ import { jsonb, pgTable, timestamp, uuid } from 'drizzle-orm/pg-core';
2
+ import { bookingsTable } from './bookings.js';
3
+ /**
4
+ * A real person on a booking. Holds identity fields (name, date of birth — the
5
+ * role:name / role:birthDate participant fields) ONCE, shared across every trip
6
+ * they take. Per-trip answers live on `booking_item_persons`.
7
+ */
8
+ export const bookingPersonsTable = pgTable('booking_persons', {
9
+ id: uuid('id').primaryKey().defaultRandom(),
10
+ bookingId: uuid('booking_id')
11
+ .notNull()
12
+ .references(() => bookingsTable.id, { onDelete: 'cascade' }),
13
+ /** Identity custom-field answers (name, date of birth, …). */
14
+ data: jsonb('data').$type().notNull().default({}),
15
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
16
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
17
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Scheduled email reminders (balance-due, pre-departure, …). A scheduler queries
3
+ * rows where `scheduledFor <= now AND sentAt IS NULL`, sends, then stamps
4
+ * `sentAt` — idempotent.
5
+ */
6
+ export declare const bookingRemindersTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
7
+ name: "booking_reminders";
8
+ schema: undefined;
9
+ columns: {
10
+ id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
11
+ name: "id";
12
+ tableName: "booking_reminders";
13
+ dataType: "string";
14
+ columnType: "PgUUID";
15
+ data: string;
16
+ driverParam: string;
17
+ notNull: true;
18
+ hasDefault: true;
19
+ isPrimaryKey: true;
20
+ isAutoincrement: false;
21
+ hasRuntimeDefault: false;
22
+ enumValues: undefined;
23
+ baseColumn: never;
24
+ identity: undefined;
25
+ generated: undefined;
26
+ }, {}, {}>;
27
+ bookingId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
28
+ name: "booking_id";
29
+ tableName: "booking_reminders";
30
+ dataType: "string";
31
+ columnType: "PgUUID";
32
+ data: string;
33
+ driverParam: string;
34
+ notNull: true;
35
+ hasDefault: false;
36
+ isPrimaryKey: false;
37
+ isAutoincrement: false;
38
+ hasRuntimeDefault: false;
39
+ enumValues: undefined;
40
+ baseColumn: never;
41
+ identity: undefined;
42
+ generated: undefined;
43
+ }, {}, {}>;
44
+ type: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
45
+ name: "type";
46
+ tableName: "booking_reminders";
47
+ dataType: "string";
48
+ columnType: "PgText";
49
+ data: string;
50
+ driverParam: string;
51
+ notNull: true;
52
+ hasDefault: false;
53
+ isPrimaryKey: false;
54
+ isAutoincrement: false;
55
+ hasRuntimeDefault: false;
56
+ enumValues: [string, ...string[]];
57
+ baseColumn: never;
58
+ identity: undefined;
59
+ generated: undefined;
60
+ }, {}, {}>;
61
+ templateKey: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
62
+ name: "template_key";
63
+ tableName: "booking_reminders";
64
+ dataType: "string";
65
+ columnType: "PgText";
66
+ data: string;
67
+ driverParam: string;
68
+ notNull: true;
69
+ hasDefault: false;
70
+ isPrimaryKey: false;
71
+ isAutoincrement: false;
72
+ hasRuntimeDefault: false;
73
+ enumValues: [string, ...string[]];
74
+ baseColumn: never;
75
+ identity: undefined;
76
+ generated: undefined;
77
+ }, {}, {}>;
78
+ scheduledFor: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
79
+ name: "scheduled_for";
80
+ tableName: "booking_reminders";
81
+ dataType: "date";
82
+ columnType: "PgTimestamp";
83
+ data: Date;
84
+ driverParam: string;
85
+ notNull: true;
86
+ hasDefault: false;
87
+ isPrimaryKey: false;
88
+ isAutoincrement: false;
89
+ hasRuntimeDefault: false;
90
+ enumValues: undefined;
91
+ baseColumn: never;
92
+ identity: undefined;
93
+ generated: undefined;
94
+ }, {}, {}>;
95
+ sentAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
96
+ name: "sent_at";
97
+ tableName: "booking_reminders";
98
+ dataType: "date";
99
+ columnType: "PgTimestamp";
100
+ data: Date;
101
+ driverParam: string;
102
+ notNull: false;
103
+ hasDefault: false;
104
+ isPrimaryKey: false;
105
+ isAutoincrement: false;
106
+ hasRuntimeDefault: false;
107
+ enumValues: undefined;
108
+ baseColumn: never;
109
+ identity: undefined;
110
+ generated: undefined;
111
+ }, {}, {}>;
112
+ createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
113
+ name: "created_at";
114
+ tableName: "booking_reminders";
115
+ dataType: "date";
116
+ columnType: "PgTimestamp";
117
+ data: Date;
118
+ driverParam: string;
119
+ notNull: true;
120
+ hasDefault: true;
121
+ isPrimaryKey: false;
122
+ isAutoincrement: false;
123
+ hasRuntimeDefault: false;
124
+ enumValues: undefined;
125
+ baseColumn: never;
126
+ identity: undefined;
127
+ generated: undefined;
128
+ }, {}, {}>;
129
+ };
130
+ dialect: "pg";
131
+ }>;
@@ -0,0 +1,18 @@
1
+ import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
2
+ import { bookingsTable } from './bookings.js';
3
+ /**
4
+ * Scheduled email reminders (balance-due, pre-departure, …). A scheduler queries
5
+ * rows where `scheduledFor <= now AND sentAt IS NULL`, sends, then stamps
6
+ * `sentAt` — idempotent.
7
+ */
8
+ export const bookingRemindersTable = pgTable('booking_reminders', {
9
+ id: uuid('id').primaryKey().defaultRandom(),
10
+ bookingId: uuid('booking_id')
11
+ .notNull()
12
+ .references(() => bookingsTable.id, { onDelete: 'cascade' }),
13
+ type: text('type').notNull(),
14
+ templateKey: text('template_key').notNull(),
15
+ scheduledFor: timestamp('scheduled_for', { withTimezone: true }).notNull(),
16
+ sentAt: timestamp('sent_at', { withTimezone: true }),
17
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
18
+ });
@@ -0,0 +1,307 @@
1
+ import type { BookingStatus } from '../../../booking/state-machine.js';
2
+ import type { AppliedRule, Perk } from '../../../booking/pricing.js';
3
+ /** Computed money snapshot kept on the booking for fast list/detail reads. */
4
+ export interface BookingTotals {
5
+ gross: number;
6
+ discount: number;
7
+ /** Σ active manual surcharges (minor units). */
8
+ surcharge: number;
9
+ /** Σ active manual discounts, magnitude (minor units). */
10
+ manualDiscount: number;
11
+ paid: number;
12
+ balance: number;
13
+ /** Granted non-monetary perks (staff notes; manual fulfilment). */
14
+ perks: Perk[];
15
+ /** Per-rule discount breakdown for display. */
16
+ appliedRules: AppliedRule[];
17
+ }
18
+ /**
19
+ * A long-lived, mutable reservation with a payment ledger. Unlike a shop order
20
+ * ("buy → ship → done"), a booking is edited after payment and its price is
21
+ * recomputed. Amounts are in minor units.
22
+ */
23
+ export declare const bookingsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
24
+ name: "bookings";
25
+ schema: undefined;
26
+ columns: {
27
+ id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
28
+ name: "id";
29
+ tableName: "bookings";
30
+ dataType: "string";
31
+ columnType: "PgUUID";
32
+ data: string;
33
+ driverParam: string;
34
+ notNull: true;
35
+ hasDefault: true;
36
+ isPrimaryKey: true;
37
+ isAutoincrement: false;
38
+ hasRuntimeDefault: false;
39
+ enumValues: undefined;
40
+ baseColumn: never;
41
+ identity: undefined;
42
+ generated: undefined;
43
+ }, {}, {}>;
44
+ number: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
45
+ name: "number";
46
+ tableName: "bookings";
47
+ dataType: "string";
48
+ columnType: "PgText";
49
+ data: string;
50
+ driverParam: string;
51
+ notNull: true;
52
+ hasDefault: false;
53
+ isPrimaryKey: false;
54
+ isAutoincrement: false;
55
+ hasRuntimeDefault: false;
56
+ enumValues: [string, ...string[]];
57
+ baseColumn: never;
58
+ identity: undefined;
59
+ generated: undefined;
60
+ }, {}, {}>;
61
+ status: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
62
+ name: "status";
63
+ tableName: "bookings";
64
+ dataType: "string";
65
+ columnType: "PgText";
66
+ data: BookingStatus;
67
+ driverParam: string;
68
+ notNull: true;
69
+ hasDefault: true;
70
+ isPrimaryKey: false;
71
+ isAutoincrement: false;
72
+ hasRuntimeDefault: false;
73
+ enumValues: [string, ...string[]];
74
+ baseColumn: never;
75
+ identity: undefined;
76
+ generated: undefined;
77
+ }, {}, {
78
+ $type: BookingStatus;
79
+ }>;
80
+ currency: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
81
+ name: "currency";
82
+ tableName: "bookings";
83
+ dataType: "string";
84
+ columnType: "PgText";
85
+ data: string;
86
+ driverParam: string;
87
+ notNull: true;
88
+ hasDefault: false;
89
+ isPrimaryKey: false;
90
+ isAutoincrement: false;
91
+ hasRuntimeDefault: false;
92
+ enumValues: [string, ...string[]];
93
+ baseColumn: never;
94
+ identity: undefined;
95
+ generated: undefined;
96
+ }, {}, {}>;
97
+ customer: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
98
+ name: "customer";
99
+ tableName: "bookings";
100
+ dataType: "json";
101
+ columnType: "PgJsonb";
102
+ data: Record<string, unknown>;
103
+ driverParam: unknown;
104
+ notNull: true;
105
+ hasDefault: true;
106
+ isPrimaryKey: false;
107
+ isAutoincrement: false;
108
+ hasRuntimeDefault: false;
109
+ enumValues: undefined;
110
+ baseColumn: never;
111
+ identity: undefined;
112
+ generated: undefined;
113
+ }, {}, {
114
+ $type: Record<string, unknown>;
115
+ }>;
116
+ totals: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
117
+ name: "totals";
118
+ tableName: "bookings";
119
+ dataType: "json";
120
+ columnType: "PgJsonb";
121
+ data: BookingTotals;
122
+ driverParam: unknown;
123
+ notNull: true;
124
+ hasDefault: true;
125
+ isPrimaryKey: false;
126
+ isAutoincrement: false;
127
+ hasRuntimeDefault: false;
128
+ enumValues: undefined;
129
+ baseColumn: never;
130
+ identity: undefined;
131
+ generated: undefined;
132
+ }, {}, {
133
+ $type: BookingTotals;
134
+ }>;
135
+ accessToken: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
136
+ name: "access_token";
137
+ tableName: "bookings";
138
+ dataType: "string";
139
+ columnType: "PgUUID";
140
+ data: string;
141
+ driverParam: string;
142
+ notNull: true;
143
+ hasDefault: true;
144
+ isPrimaryKey: false;
145
+ isAutoincrement: false;
146
+ hasRuntimeDefault: false;
147
+ enumValues: undefined;
148
+ baseColumn: never;
149
+ identity: undefined;
150
+ generated: undefined;
151
+ }, {}, {}>;
152
+ staffNotes: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
153
+ name: "staff_notes";
154
+ tableName: "bookings";
155
+ dataType: "string";
156
+ columnType: "PgText";
157
+ data: string;
158
+ driverParam: string;
159
+ notNull: false;
160
+ hasDefault: false;
161
+ isPrimaryKey: false;
162
+ isAutoincrement: false;
163
+ hasRuntimeDefault: false;
164
+ enumValues: [string, ...string[]];
165
+ baseColumn: never;
166
+ identity: undefined;
167
+ generated: undefined;
168
+ }, {}, {}>;
169
+ language: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
170
+ name: "language";
171
+ tableName: "bookings";
172
+ dataType: "string";
173
+ columnType: "PgText";
174
+ data: string;
175
+ driverParam: string;
176
+ notNull: false;
177
+ hasDefault: false;
178
+ isPrimaryKey: false;
179
+ isAutoincrement: false;
180
+ hasRuntimeDefault: false;
181
+ enumValues: [string, ...string[]];
182
+ baseColumn: never;
183
+ identity: undefined;
184
+ generated: undefined;
185
+ }, {}, {}>;
186
+ balanceDueAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
187
+ name: "balance_due_at";
188
+ tableName: "bookings";
189
+ dataType: "date";
190
+ columnType: "PgTimestamp";
191
+ data: Date;
192
+ driverParam: string;
193
+ notNull: false;
194
+ hasDefault: false;
195
+ isPrimaryKey: false;
196
+ isAutoincrement: false;
197
+ hasRuntimeDefault: false;
198
+ enumValues: undefined;
199
+ baseColumn: never;
200
+ identity: undefined;
201
+ generated: undefined;
202
+ }, {}, {}>;
203
+ holdExpiresAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
204
+ name: "hold_expires_at";
205
+ tableName: "bookings";
206
+ dataType: "date";
207
+ columnType: "PgTimestamp";
208
+ data: Date;
209
+ driverParam: string;
210
+ notNull: false;
211
+ hasDefault: false;
212
+ isPrimaryKey: false;
213
+ isAutoincrement: false;
214
+ hasRuntimeDefault: false;
215
+ enumValues: undefined;
216
+ baseColumn: never;
217
+ identity: undefined;
218
+ generated: undefined;
219
+ }, {}, {}>;
220
+ cancelReason: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
221
+ name: "cancel_reason";
222
+ tableName: "bookings";
223
+ dataType: "string";
224
+ columnType: "PgText";
225
+ data: string;
226
+ driverParam: string;
227
+ notNull: false;
228
+ hasDefault: false;
229
+ isPrimaryKey: false;
230
+ isAutoincrement: false;
231
+ hasRuntimeDefault: false;
232
+ enumValues: [string, ...string[]];
233
+ baseColumn: never;
234
+ identity: undefined;
235
+ generated: undefined;
236
+ }, {}, {}>;
237
+ deletedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
238
+ name: "deleted_at";
239
+ tableName: "bookings";
240
+ dataType: "date";
241
+ columnType: "PgTimestamp";
242
+ data: Date;
243
+ driverParam: string;
244
+ notNull: false;
245
+ hasDefault: false;
246
+ isPrimaryKey: false;
247
+ isAutoincrement: false;
248
+ hasRuntimeDefault: false;
249
+ enumValues: undefined;
250
+ baseColumn: never;
251
+ identity: undefined;
252
+ generated: undefined;
253
+ }, {}, {}>;
254
+ deletedBy: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
255
+ name: "deleted_by";
256
+ tableName: "bookings";
257
+ dataType: "string";
258
+ columnType: "PgText";
259
+ data: string;
260
+ driverParam: string;
261
+ notNull: false;
262
+ hasDefault: false;
263
+ isPrimaryKey: false;
264
+ isAutoincrement: false;
265
+ hasRuntimeDefault: false;
266
+ enumValues: [string, ...string[]];
267
+ baseColumn: never;
268
+ identity: undefined;
269
+ generated: undefined;
270
+ }, {}, {}>;
271
+ createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
272
+ name: "created_at";
273
+ tableName: "bookings";
274
+ dataType: "date";
275
+ columnType: "PgTimestamp";
276
+ data: Date;
277
+ driverParam: string;
278
+ notNull: true;
279
+ hasDefault: true;
280
+ isPrimaryKey: false;
281
+ isAutoincrement: false;
282
+ hasRuntimeDefault: false;
283
+ enumValues: undefined;
284
+ baseColumn: never;
285
+ identity: undefined;
286
+ generated: undefined;
287
+ }, {}, {}>;
288
+ updatedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
289
+ name: "updated_at";
290
+ tableName: "bookings";
291
+ dataType: "date";
292
+ columnType: "PgTimestamp";
293
+ data: Date;
294
+ driverParam: string;
295
+ notNull: true;
296
+ hasDefault: true;
297
+ isPrimaryKey: false;
298
+ isAutoincrement: false;
299
+ hasRuntimeDefault: false;
300
+ enumValues: undefined;
301
+ baseColumn: never;
302
+ identity: undefined;
303
+ generated: undefined;
304
+ }, {}, {}>;
305
+ };
306
+ dialect: "pg";
307
+ }>;
@@ -0,0 +1,40 @@
1
+ import { jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
2
+ /**
3
+ * A long-lived, mutable reservation with a payment ledger. Unlike a shop order
4
+ * ("buy → ship → done"), a booking is edited after payment and its price is
5
+ * recomputed. Amounts are in minor units.
6
+ */
7
+ export const bookingsTable = pgTable('bookings', {
8
+ id: uuid('id').primaryKey().defaultRandom(),
9
+ number: text('number').notNull().unique(),
10
+ status: text('status').$type().notNull().default('draft'),
11
+ currency: text('currency').notNull(),
12
+ /** Booker details (name, email, phone, "how did you hear about us", …). */
13
+ customer: jsonb('customer').$type().notNull().default({}),
14
+ /** Computed totals (gross/discount/paid/balance), refreshed on every edit. */
15
+ totals: jsonb('totals').$type().notNull().default({
16
+ gross: 0,
17
+ discount: 0,
18
+ surcharge: 0,
19
+ manualDiscount: 0,
20
+ paid: 0,
21
+ balance: 0,
22
+ perks: [],
23
+ appliedRules: []
24
+ }),
25
+ /** Unguessable token for the self-service portal (`/rezerwacja/[token]`). */
26
+ accessToken: uuid('access_token').defaultRandom().notNull(),
27
+ staffNotes: text('staff_notes'),
28
+ language: text('language'),
29
+ /** Deadline to pay the balance; past it with balance>0 ⇒ auto-cancel + deposit forfeited. */
30
+ balanceDueAt: timestamp('balance_due_at', { withTimezone: true }),
31
+ /** TTL for an abandoned draft (deposit never paid) ⇒ auto-cancel. Cleared on deposit payment. */
32
+ holdExpiresAt: timestamp('hold_expires_at', { withTimezone: true }),
33
+ /** Why a booking was cancelled: 'abandoned' | 'balance_overdue' | 'manual'. */
34
+ cancelReason: text('cancel_reason'),
35
+ // Soft-delete (accounting/audit safety) — NULL = visible.
36
+ deletedAt: timestamp('deleted_at', { withTimezone: true }),
37
+ deletedBy: text('deleted_by'),
38
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
39
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
40
+ });
@@ -0,0 +1,7 @@
1
+ export * from './bookings.js';
2
+ export * from './bookingItems.js';
3
+ export * from './bookingPersons.js';
4
+ export * from './bookingItemPersons.js';
5
+ export * from './bookingPayments.js';
6
+ export * from './bookingReminders.js';
7
+ export * from './bookingAdjustments.js';
@@ -0,0 +1,7 @@
1
+ export * from './bookings.js';
2
+ export * from './bookingItems.js';
3
+ export * from './bookingPersons.js';
4
+ export * from './bookingItemPersons.js';
5
+ export * from './bookingPayments.js';
6
+ export * from './bookingReminders.js';
7
+ export * from './bookingAdjustments.js';
@@ -9,3 +9,4 @@ export * from './imageStyle.js';
9
9
  export * from './videoStyle.js';
10
10
  export * from './mediaJob.js';
11
11
  export * from './shop/index.js';
12
+ export * from './booking/index.js';
@@ -9,3 +9,4 @@ export * from './imageStyle.js';
9
9
  export * from './videoStyle.js';
10
10
  export * from './mediaJob.js';
11
11
  export * from './shop/index.js';
12
+ export * from './booking/index.js';
@@ -0,0 +1 @@
1
+ export * from './schema/booking/index.js';
@@ -0,0 +1 @@
1
+ export * from './schema/booking/index.js';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Per-handler in-memory rate limiter (fixed window). Generic infra shared by the
3
+ * shop and booking HTTP handlers — not shop-specific. For a single instance this
4
+ * needs no external store; multi-instance deployments would swap the bucket map
5
+ * for a shared store.
6
+ */
7
+ /** A fixed-window rule: at most `limit` hits per `windowSec` seconds. */
8
+ export interface RateLimitRule {
9
+ limit: number;
10
+ windowSec: number;
11
+ }
12
+ export interface RateLimitResult {
13
+ allowed: boolean;
14
+ remaining: number;
15
+ resetAt: number;
16
+ }
17
+ export declare function checkRateLimit(key: string, rule: RateLimitRule): RateLimitResult;
18
+ export declare function resetRateLimit(key?: string): void;
19
+ export declare function clientKey(req: Request, prefix: string): string;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Per-handler in-memory rate limiter (fixed window). Generic infra shared by the
3
+ * shop and booking HTTP handlers — not shop-specific. For a single instance this
4
+ * needs no external store; multi-instance deployments would swap the bucket map
5
+ * for a shared store.
6
+ */
7
+ const buckets = new Map();
8
+ export function checkRateLimit(key, rule) {
9
+ const now = Date.now();
10
+ const windowMs = rule.windowSec * 1000;
11
+ const bucket = buckets.get(key);
12
+ if (!bucket || bucket.resetAt <= now) {
13
+ const fresh = { count: 1, resetAt: now + windowMs };
14
+ buckets.set(key, fresh);
15
+ return { allowed: true, remaining: rule.limit - 1, resetAt: fresh.resetAt };
16
+ }
17
+ if (bucket.count >= rule.limit) {
18
+ return { allowed: false, remaining: 0, resetAt: bucket.resetAt };
19
+ }
20
+ bucket.count += 1;
21
+ return { allowed: true, remaining: rule.limit - bucket.count, resetAt: bucket.resetAt };
22
+ }
23
+ export function resetRateLimit(key) {
24
+ if (key)
25
+ buckets.delete(key);
26
+ else
27
+ buckets.clear();
28
+ }
29
+ export function clientKey(req, prefix) {
30
+ const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
31
+ req.headers.get('x-real-ip') ||
32
+ 'unknown';
33
+ return `${prefix}:${ip}`;
34
+ }
@@ -52,7 +52,9 @@ export class PayuClient {
52
52
  });
53
53
  // PayU returns 302 by default with a JSON body (manual redirect is used to read it).
54
54
  if (res.status !== 302 && !res.ok) {
55
- throw new Error(`PayU createOrder failed: ${res.status}`);
55
+ // Surface PayU's error body (e.g. ERROR_ORDER_NOT_UNIQUE) — don't swallow it.
56
+ const detail = await res.text().catch(() => '');
57
+ throw new Error(`PayU createOrder failed: ${res.status}${detail ? ` — ${detail}` : ''}`);
56
58
  }
57
59
  const data = (await res.json());
58
60
  if (data.status && data.status.statusCode && data.status.statusCode !== 'SUCCESS') {