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,10 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Payment received — booking {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Thank you! We've recorded your payment.</p>
4
+ {{#if booking.totals.balance}}
5
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Amount still due: <strong>{{currency booking.totals.balance currency}}</strong>.</p>
6
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Pay the balance</a></div>{{/if}}
7
+ {{else}}
8
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Your booking is paid in full — see you soon!</p>
9
+ {{/if}}
10
+ {{> footer}}
@@ -0,0 +1,10 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Wpłata zaksięgowana — rezerwacja {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Dziękujemy! Zaksięgowaliśmy Twoją wpłatę.</p>
4
+ {{#if booking.totals.balance}}
5
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Pozostała kwota do dopłaty: <strong>{{currency booking.totals.balance currency}}</strong>.</p>
6
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Dopłać w panelu</a></div>{{/if}}
7
+ {{else}}
8
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Rezerwacja jest opłacona w całości — do zobaczenia!</p>
9
+ {{/if}}
10
+ {{> footer}}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Map a per-participant {@link ParticipantPriceResult} into the persisted
3
+ * booking totals + a synthesized per-rule audit list. Pure.
4
+ */
5
+ import type { AppliedRule, Perk } from './pricing.js';
6
+ import type { ParticipantPriceResult } from './participant-pricing.js';
7
+ import type { AdjustmentSums } from './adjustments.js';
8
+ export interface BookingTotals {
9
+ gross: number;
10
+ discount: number;
11
+ /** Σ active manual surcharges (minor units). */
12
+ surcharge: number;
13
+ /** Σ active manual discounts, magnitude (minor units). */
14
+ manualDiscount: number;
15
+ paid: number;
16
+ balance: number;
17
+ perks: Perk[];
18
+ appliedRules: AppliedRule[];
19
+ }
20
+ /** Amount owed = gross − rule discounts − manual discounts + surcharges (≥ 0). Legacy-safe. */
21
+ export declare function netOf(t: Pick<BookingTotals, 'gross' | 'discount' | 'surcharge' | 'manualDiscount'>): number;
22
+ /**
23
+ * Flatten per-participant discounts into audit rules. Package discounts carry a
24
+ * `targetRef` (the person's cheapest trip) so they attach to that item line;
25
+ * early-bird is booking-level (no target).
26
+ */
27
+ export declare function synthesizeAppliedRules(result: ParticipantPriceResult): AppliedRule[];
28
+ /**
29
+ * Build the persisted totals snapshot from a pricing result, manual-adjustment
30
+ * sums, and amount paid. Balance = net − paid, where net folds in surcharges and
31
+ * manual discounts on top of rule discounts.
32
+ */
33
+ export declare function resultToTotals(result: ParticipantPriceResult, sums: AdjustmentSums, paid: number): BookingTotals;
34
+ /**
35
+ * Age-adjusted gross per trip ref = Σ participant line bases on that trip.
36
+ * Drives `booking_items.subtotalSnapshot` so the line gross reflects age pricing.
37
+ */
38
+ export declare function grossByTripRef(result: ParticipantPriceResult): Map<string, number>;
@@ -0,0 +1,61 @@
1
+ /** Amount owed = gross − rule discounts − manual discounts + surcharges (≥ 0). Legacy-safe. */
2
+ export function netOf(t) {
3
+ return Math.max(0, t.gross - t.discount - (t.manualDiscount ?? 0) + (t.surcharge ?? 0));
4
+ }
5
+ /**
6
+ * Flatten per-participant discounts into audit rules. Package discounts carry a
7
+ * `targetRef` (the person's cheapest trip) so they attach to that item line;
8
+ * early-bird is booking-level (no target).
9
+ */
10
+ export function synthesizeAppliedRules(result) {
11
+ const rules = [];
12
+ for (const p of result.participants) {
13
+ if (p.packageDiscount > 0) {
14
+ const rule = {
15
+ ruleId: 'package',
16
+ label: 'Rabat pakietowy',
17
+ amount: p.packageDiscount
18
+ };
19
+ if (p.packageTargetRef)
20
+ rule.targetRef = p.packageTargetRef;
21
+ rules.push(rule);
22
+ }
23
+ if (p.earlyBirdDiscount > 0) {
24
+ rules.push({
25
+ ruleId: 'early-bird',
26
+ label: 'Wcześniejsza rezerwacja',
27
+ amount: p.earlyBirdDiscount
28
+ });
29
+ }
30
+ }
31
+ return rules;
32
+ }
33
+ /**
34
+ * Build the persisted totals snapshot from a pricing result, manual-adjustment
35
+ * sums, and amount paid. Balance = net − paid, where net folds in surcharges and
36
+ * manual discounts on top of rule discounts.
37
+ */
38
+ export function resultToTotals(result, sums, paid) {
39
+ const base = {
40
+ gross: result.gross,
41
+ discount: result.discountTotal,
42
+ surcharge: sums.surcharge,
43
+ manualDiscount: sums.manualDiscount,
44
+ perks: result.perks,
45
+ appliedRules: synthesizeAppliedRules(result)
46
+ };
47
+ return { ...base, paid, balance: Math.max(0, netOf(base) - paid) };
48
+ }
49
+ /**
50
+ * Age-adjusted gross per trip ref = Σ participant line bases on that trip.
51
+ * Drives `booking_items.subtotalSnapshot` so the line gross reflects age pricing.
52
+ */
53
+ export function grossByTripRef(result) {
54
+ const byRef = new Map();
55
+ for (const p of result.participants) {
56
+ for (const line of p.lines) {
57
+ byRef.set(line.tripRef, (byRef.get(line.tripRef) ?? 0) + line.base);
58
+ }
59
+ }
60
+ return byRef;
61
+ }
@@ -0,0 +1,28 @@
1
+ import { type BookingStatus } from './state-machine.js';
2
+ import { type PaymentEntry, type PaymentStatus } from './ledger.js';
3
+ import type { PaymentEvent } from '../shop/types.js';
4
+ /**
5
+ * Pure decision for a payment webhook. Given the event, the targeted payment
6
+ * row, and the booking's other payments, decides the new payment status, the
7
+ * new total paid, the derived booking status, and whether to clear the draft
8
+ * hold. `alreadyApplied` short-circuits idempotently (duplicate webhook for a
9
+ * settled/refunded payment) — the caller returns early without mutating, so the
10
+ * other fields are intentionally a benign snapshot of the current state.
11
+ */
12
+ export declare function applyPaymentEvent(input: {
13
+ event: PaymentEvent;
14
+ payment: {
15
+ status: PaymentStatus;
16
+ amount: number;
17
+ } | null;
18
+ otherPayments: PaymentEntry[];
19
+ gross: number;
20
+ depositRequired: number;
21
+ currentStatus: BookingStatus;
22
+ }): {
23
+ alreadyApplied: boolean;
24
+ newPaymentStatus: PaymentStatus;
25
+ newTotalsPaid: number;
26
+ newStatus: BookingStatus;
27
+ clearHold: boolean;
28
+ };
@@ -0,0 +1,50 @@
1
+ import { deriveStatus } from './state-machine.js';
2
+ import { totalPaid } from './ledger.js';
3
+ /**
4
+ * Pure decision for a payment webhook. Given the event, the targeted payment
5
+ * row, and the booking's other payments, decides the new payment status, the
6
+ * new total paid, the derived booking status, and whether to clear the draft
7
+ * hold. `alreadyApplied` short-circuits idempotently (duplicate webhook for a
8
+ * settled/refunded payment) — the caller returns early without mutating, so the
9
+ * other fields are intentionally a benign snapshot of the current state.
10
+ */
11
+ export function applyPaymentEvent(input) {
12
+ const { event, payment, otherPayments, gross, depositRequired, currentStatus } = input;
13
+ const paidBefore = totalPaid(otherPayments);
14
+ if (!payment || payment.status === 'paid' || payment.status === 'refunded') {
15
+ return {
16
+ alreadyApplied: true,
17
+ newPaymentStatus: payment?.status ?? 'pending',
18
+ newTotalsPaid: paidBefore,
19
+ newStatus: currentStatus,
20
+ clearHold: false
21
+ };
22
+ }
23
+ if (event.status === 'paid') {
24
+ const paid = paidBefore + payment.amount;
25
+ return {
26
+ alreadyApplied: false,
27
+ newPaymentStatus: 'paid',
28
+ newTotalsPaid: paid,
29
+ newStatus: deriveStatus({ gross, paid, depositRequired, current: currentStatus }),
30
+ clearHold: true
31
+ };
32
+ }
33
+ if (event.status === 'paymentRejected') {
34
+ return {
35
+ alreadyApplied: false,
36
+ newPaymentStatus: 'failed',
37
+ newTotalsPaid: paidBefore,
38
+ newStatus: deriveStatus({ gross, paid: paidBefore, depositRequired, current: currentStatus }),
39
+ clearHold: false
40
+ };
41
+ }
42
+ // pending / unknown: no-op
43
+ return {
44
+ alreadyApplied: false,
45
+ newPaymentStatus: 'pending',
46
+ newTotalsPaid: paidBefore,
47
+ newStatus: currentStatus,
48
+ clearHold: false
49
+ };
50
+ }
@@ -1,2 +1,8 @@
1
1
  import 'dotenv/config';
2
- export declare function createUser(): Promise<void>;
2
+ export interface CreateUserOptions {
3
+ name?: string;
4
+ email?: string;
5
+ password?: string;
6
+ role?: string;
7
+ }
8
+ export declare function createUser(opts?: CreateUserOptions): Promise<void>;
@@ -15,37 +15,38 @@ const roleSchema = z.enum(['admin', 'user']);
15
15
  function generatePassword(length = 24) {
16
16
  return crypto.randomBytes(length).toString('base64url').slice(0, length);
17
17
  }
18
- export async function createUser() {
18
+ export async function createUser(opts = {}) {
19
19
  const DATABASE_URL = process.env.DATABASE_URL;
20
20
  if (!DATABASE_URL) {
21
21
  console.error('DATABASE_URL is not set. Check your .env file.');
22
22
  process.exit(1);
23
23
  }
24
- const client = postgres(DATABASE_URL);
25
- const db = drizzle(client);
26
- const auth = betterAuth({
27
- database: drizzleAdapter(db, { provider: 'pg', schema: authSchema }),
28
- emailAndPassword: { enabled: true },
29
- plugins: [admin()]
30
- });
31
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
24
+ // Non-interactive when both name and email are supplied via flags; otherwise
25
+ // prompt only for the fields that weren't provided.
26
+ const nonInteractive = Boolean(opts.name && opts.email);
27
+ let rl;
32
28
  function ask(q) {
29
+ if (!rl)
30
+ rl = readline.createInterface({ input: process.stdin, output: process.stdout });
33
31
  return new Promise((resolve) => rl.question(q, resolve));
34
32
  }
35
- console.log('Please provide the following information to create a new user:');
36
- const name = await ask('Name: ');
33
+ if (!nonInteractive) {
34
+ console.log('Please provide the following information to create a new user:');
35
+ }
36
+ const name = opts.name ?? (await ask('Name: '));
37
37
  const parsedName = nameSchema.safeParse(name);
38
38
  if (!parsedName.success) {
39
39
  console.error(parsedName.error.flatten());
40
40
  process.exit(1);
41
41
  }
42
- const email = await ask('E-mail: ');
42
+ const email = opts.email ?? (await ask('E-mail: '));
43
43
  const parsedEmail = emailSchema.safeParse(email);
44
44
  if (!parsedEmail.success) {
45
45
  console.error(parsedEmail.error.flatten());
46
46
  process.exit(1);
47
47
  }
48
- const passwordInput = await ask('Password (leave empty to generate): ');
48
+ // Flag wins; interactive prompts (empty generate); non-interactive w/o flag → generate.
49
+ const passwordInput = opts.password ?? (nonInteractive ? '' : await ask('Password (leave empty to generate): '));
49
50
  const password = passwordInput.trim() || generatePassword();
50
51
  const wasGenerated = !passwordInput.trim();
51
52
  const parsedPassword = passwordSchema.safeParse(password);
@@ -53,14 +54,21 @@ export async function createUser() {
53
54
  console.error(parsedPassword.error.flatten());
54
55
  process.exit(1);
55
56
  }
56
- const roleInput = await ask('Role (admin/user) [admin]: ');
57
+ const roleInput = opts.role ?? (nonInteractive ? 'admin' : await ask('Role (admin/user) [admin]: '));
57
58
  const role = roleInput.trim() || 'admin';
58
59
  const parsedRole = roleSchema.safeParse(role);
59
60
  if (!parsedRole.success) {
60
61
  console.error('Invalid role. Must be "admin" or "user".');
61
62
  process.exit(1);
62
63
  }
63
- rl.close();
64
+ rl?.close();
65
+ const client = postgres(DATABASE_URL);
66
+ const db = drizzle(client);
67
+ const auth = betterAuth({
68
+ database: drizzleAdapter(db, { provider: 'pg', schema: authSchema }),
69
+ emailAndPassword: { enabled: true },
70
+ plugins: [admin()]
71
+ });
64
72
  const response = await auth.api.createUser({
65
73
  body: {
66
74
  email: parsedEmail.data,
package/dist/cli/index.js CHANGED
@@ -61,6 +61,8 @@ Options:
61
61
  --cms-config <FILE> Path to CMS config (default: src/lib/cms/cms.config.ts)
62
62
  --shop Force shop routes ON (overrides auto-detect)
63
63
  --no-shop Force shop routes OFF
64
+ --booking Force booking routes ON (overrides auto-detect)
65
+ --no-booking Force booking routes OFF
64
66
  --help, -h Show this help
65
67
 
66
68
  Examples:
@@ -90,16 +92,24 @@ Examples:
90
92
  function printCreateUserHelp() {
91
93
  console.log(`includio create-user — create an admin/user account
92
94
 
93
- Interactive prompt — asks for email, password, name, and role. Requires
94
- \`DATABASE_URL\` in the environment (or .env loaded by the project).
95
+ Interactive by default — asks for name, email, password, and role. Pass
96
+ \`--name\` and \`--email\` to run non-interactively (e.g. from another tool);
97
+ any field you omit gets a sensible default. Requires \`DATABASE_URL\` in the
98
+ environment (or .env loaded by the project).
95
99
 
96
100
  Usage: includio create-user [options]
97
101
 
98
102
  Options:
99
- --help, -h Show this help
103
+ --name <NAME> User's display name
104
+ --email <EMAIL> User's e-mail
105
+ --password <PASS> Password (omit → securely generated and printed)
106
+ --role <ROLE> Role: admin | user (default: admin)
107
+ --help, -h Show this help
100
108
 
101
- Example:
109
+ Examples:
102
110
  includio create-user
111
+ includio create-user --name "Jan Kowalski" --email jan@example.com
112
+ includio create-user --name Admin --email admin@example.com --role admin
103
113
  `);
104
114
  }
105
115
  function flagValue(name) {
@@ -124,6 +134,18 @@ function detectShopUsage(cmsConfigPath) {
124
134
  .replace(/\/\/[^\n]*/g, '');
125
135
  return /\bshop\s*:\s*\S/.test(stripped);
126
136
  }
137
+ /**
138
+ * Best-effort detection: scan cms config text for an active `defineBooking(`
139
+ * call (the booking module is registered via `modules: [defineBooking({...})]`).
140
+ * Returns null when the config can't be located.
141
+ */
142
+ function detectBookingUsage(cmsConfigPath) {
143
+ if (!fs.existsSync(cmsConfigPath))
144
+ return null;
145
+ const raw = fs.readFileSync(cmsConfigPath, 'utf-8');
146
+ const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
147
+ return /\bdefineBooking\s*\(/.test(stripped);
148
+ }
127
149
  // Top-level: no command, --help, or --version
128
150
  if (!command || HELP_FLAGS.has(command)) {
129
151
  printTopLevelHelp();
@@ -163,8 +185,29 @@ if (command === 'scaffold' && subcommand === 'admin') {
163
185
  shopSource = `${cmsConfigPath} → ${detected ? 'shop: detected' : 'no shop config'}`;
164
186
  }
165
187
  }
166
- console.log(`Scaffolding admin routes (shop ${shop ? 'enabled' : 'disabled'} — ${shopSource})...\n`);
167
- scaffoldAdmin({ routesDir, force, shop });
188
+ let booking;
189
+ let bookingSource;
190
+ if (args.includes('--booking')) {
191
+ booking = true;
192
+ bookingSource = 'flag --booking';
193
+ }
194
+ else if (args.includes('--no-booking')) {
195
+ booking = false;
196
+ bookingSource = 'flag --no-booking';
197
+ }
198
+ else {
199
+ const detected = detectBookingUsage(cmsConfigPath);
200
+ if (detected === null) {
201
+ booking = false;
202
+ bookingSource = `${cmsConfigPath} not found → off`;
203
+ }
204
+ else {
205
+ booking = detected;
206
+ bookingSource = `${cmsConfigPath} → ${detected ? 'defineBooking detected' : 'no booking'}`;
207
+ }
208
+ }
209
+ console.log(`Scaffolding admin routes (shop ${shop ? 'enabled' : 'disabled'} — ${shopSource}; booking ${booking ? 'enabled' : 'disabled'} — ${bookingSource})...\n`);
210
+ scaffoldAdmin({ routesDir, force, shop, booking });
168
211
  }
169
212
  else if (command === 'install-peers') {
170
213
  if (hasHelpFlag()) {
@@ -179,7 +222,12 @@ else if (command === 'create-user') {
179
222
  printCreateUserHelp();
180
223
  process.exit(0);
181
224
  }
182
- createUser();
225
+ createUser({
226
+ name: flagValue('--name'),
227
+ email: flagValue('--email'),
228
+ password: flagValue('--password'),
229
+ role: flagValue('--role')
230
+ });
183
231
  }
184
232
  else {
185
233
  printTopLevelHelp();
@@ -7,5 +7,11 @@ export interface ScaffoldAdminOptions {
7
7
  * module — keeps the route tree clean and avoids 404 stubs in your app.
8
8
  */
9
9
  shop?: boolean;
10
+ /**
11
+ * Emit booking module routes (api/booking/*, admin booking pages,
12
+ * rezerwacja/[token] portal). Default `true`. Set to `false` for projects
13
+ * without the booking module.
14
+ */
15
+ booking?: boolean;
10
16
  }
11
17
  export declare function scaffoldAdmin(options: ScaffoldAdminOptions): void;
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { getBookingFiles } from './booking.js';
3
4
  const GENERATED_COMMENT = '<!-- Generated by includio-cms -->';
4
5
  const GENERATED_COMMENT_TS = '// Generated by includio-cms';
5
6
  function isShopFile(p) {
@@ -404,9 +405,10 @@ export const POST: RequestHandler = async (event) => {
404
405
  ];
405
406
  }
406
407
  export function scaffoldAdmin(options) {
407
- const { routesDir, force = false, shop = true } = options;
408
- const allFiles = getAdminFiles();
409
- const files = shop ? allFiles : allFiles.filter((f) => !isShopFile(f.path));
408
+ const { routesDir, force = false, shop = true, booking = true } = options;
409
+ const adminFiles = getAdminFiles();
410
+ const withShop = shop ? adminFiles : adminFiles.filter((f) => !isShopFile(f.path));
411
+ const files = booking ? [...withShop, ...getBookingFiles()] : withShop;
410
412
  let created = 0;
411
413
  let skipped = 0;
412
414
  for (const file of files) {
@@ -0,0 +1,12 @@
1
+ export type FileEntry = {
2
+ path: string;
3
+ content: string;
4
+ };
5
+ /** Route prefixes owned by the booking module (for gating in scaffoldAdmin). */
6
+ export declare function isBookingFile(p: string): boolean;
7
+ /**
8
+ * Route files the booking module needs in a client project: public + portal +
9
+ * maintenance API endpoints (server handlers), the admin staff zone pages, and
10
+ * a portal page skeleton on the client SDK (brand it in the client repo).
11
+ */
12
+ export declare function getBookingFiles(): FileEntry[];
@@ -0,0 +1,103 @@
1
+ const GENERATED_COMMENT = '<!-- Generated by includio-cms -->';
2
+ const GENERATED_COMMENT_TS = '// Generated by includio-cms';
3
+ /** Route prefixes owned by the booking module (for gating in scaffoldAdmin). */
4
+ export function isBookingFile(p) {
5
+ return (p.startsWith('api/booking/') ||
6
+ p.startsWith('admin/(afterLogin)/booking/') ||
7
+ p.startsWith('rezerwacja/'));
8
+ }
9
+ /**
10
+ * Route files the booking module needs in a client project: public + portal +
11
+ * maintenance API endpoints (server handlers), the admin staff zone pages, and
12
+ * a portal page skeleton on the client SDK (brand it in the client repo).
13
+ */
14
+ export function getBookingFiles() {
15
+ return [
16
+ // --- HTTP endpoints (server handlers) ---
17
+ {
18
+ path: 'api/booking/+server.ts',
19
+ content: `${GENERATED_COMMENT_TS}
20
+ import { createBookingHandler } from 'includio-cms/booking/server';
21
+
22
+ export const { POST } = createBookingHandler();
23
+ `
24
+ },
25
+ {
26
+ path: 'api/booking/webhook/[provider]/+server.ts',
27
+ content: `${GENERATED_COMMENT_TS}
28
+ import { createWebhookHandler } from 'includio-cms/booking/server';
29
+
30
+ export const { POST } = createWebhookHandler();
31
+ `
32
+ },
33
+ {
34
+ path: 'api/booking/portal/[token]/+server.ts',
35
+ content: `${GENERATED_COMMENT_TS}
36
+ import { createPortalHandler } from 'includio-cms/booking/server';
37
+
38
+ export const { GET, POST } = createPortalHandler();
39
+ `
40
+ },
41
+ {
42
+ path: 'api/booking/maintenance/+server.ts',
43
+ content: `${GENERATED_COMMENT_TS}
44
+ import { createMaintenanceHandler } from 'includio-cms/booking/server';
45
+
46
+ export const { POST } = createMaintenanceHandler();
47
+ `
48
+ },
49
+ // --- Admin staff zone ---
50
+ {
51
+ path: 'admin/(afterLogin)/booking/+page.svelte',
52
+ content: `${GENERATED_COMMENT}
53
+ <script lang="ts">
54
+ import { ModuleDashboardPage } from 'includio-cms/admin/client';
55
+ </script>
56
+
57
+ <ModuleDashboardPage moduleId="booking" />
58
+ `
59
+ },
60
+ {
61
+ path: 'admin/(afterLogin)/booking/reservations/+page.svelte',
62
+ content: `${GENERATED_COMMENT}
63
+ <script lang="ts">
64
+ import { BookingReservationsPage } from 'includio-cms/admin/client';
65
+ </script>
66
+
67
+ <BookingReservationsPage />
68
+ `
69
+ },
70
+ {
71
+ path: 'admin/(afterLogin)/booking/reservations/[id]/+page.svelte',
72
+ content: `${GENERATED_COMMENT}
73
+ <script lang="ts">
74
+ import { BookingReservationDetailPage } from 'includio-cms/admin/client';
75
+ </script>
76
+
77
+ <BookingReservationDetailPage />
78
+ `
79
+ },
80
+ // --- Customer self-service portal (brand this page) ---
81
+ {
82
+ path: 'rezerwacja/[token]/+page.svelte',
83
+ content: `${GENERATED_COMMENT}
84
+ <script lang="ts">
85
+ import { page } from '$app/state';
86
+ import { useBooking } from 'includio-cms/booking/client';
87
+
88
+ const booking = useBooking(page.params.token ?? '');
89
+ </script>
90
+
91
+ {#if booking.loading}
92
+ <p>Ładowanie…</p>
93
+ {:else if booking.error}
94
+ <p>{booking.error}</p>
95
+ {:else if booking.view}
96
+ {@const view = booking.view as { number: string }}
97
+ <h1>Rezerwacja {view.number}</h1>
98
+ <!-- TODO: brand the self-service portal (ledger, pay, edit) using \`booking\` -->
99
+ {/if}
100
+ `
101
+ }
102
+ ];
103
+ }
@@ -11,6 +11,7 @@ import type { EmailAdapter, SendMailOptions } from '../types/adapters/email.js';
11
11
  import { betterAuth } from 'better-auth';
12
12
  import type { ResolvedShopConfig } from '../shop/types.js';
13
13
  import type { ResolvedCmpConfig } from '../cmp/types.js';
14
+ import type { CMSModule } from '../types/modules.js';
14
15
  export declare class CMS implements ICMS {
15
16
  private config;
16
17
  databaseAdapter: DatabaseAdapter;
@@ -26,26 +27,40 @@ export declare class CMS implements ICMS {
26
27
  mediaConfig: MediaConfig;
27
28
  typographyConfig: TypographyConfig;
28
29
  sidebarHelp: boolean;
29
- shopConfig: ResolvedShopConfig | null;
30
30
  cmpConfig: ResolvedCmpConfig | null;
31
31
  adminConfig: AdminConfig | null;
32
- /**
33
- * Resolves once the shop's variant-attribute GIN indexes have been applied
34
- * by `initCMS()`. `null` when the CMS is configured without a shop. Tests
35
- * can `await` this to gate on init completion; production callers don't
36
- * need to — index application is idempotent and non-blocking.
37
- * @internal
38
- */
39
- shopInitPromise: Promise<void> | null;
32
+ /** Per-module init promises (`serverHooks.init`), keyed by module id. @internal */
33
+ private _moduleInitPromises;
40
34
  plugins: Plugin[];
41
35
  customFields: Map<string, CustomFieldDefinition>;
42
36
  iconSets: Map<string, IconSetPlugin>;
43
37
  apiKeys: ApiKeyConfig[];
38
+ modules: Map<string, CMSModule>;
44
39
  constructor(config: CMSConfig);
45
40
  private validateFieldSlugs;
46
41
  get auth(): ReturnType<typeof betterAuth>;
47
42
  getBySlug(slug: string): CollectionConfigWithType | SingleConfigWithType;
48
43
  getFormBySlug(slug: string): FormConfig;
44
+ getModule(id: string): CMSModule | undefined;
45
+ getModuleConfig<T = unknown>(id: string): T | undefined;
46
+ hasModule(id: string): boolean;
47
+ /**
48
+ * Back-compat shim — the shop config now lives on the `shop` module
49
+ * (`defineConfig({ modules: [defineShop(...)] })`). Reads through the module
50
+ * registry so the entire shop server/http/remote layer keeps working unchanged.
51
+ */
52
+ get shopConfig(): ResolvedShopConfig | null;
53
+ /**
54
+ * Resolves once the shop module's init (variant-attribute GIN indexes) has run.
55
+ * `null` when no shop module is configured. Idempotent; tests can `await` it.
56
+ * @internal
57
+ */
58
+ get shopInitPromise(): Promise<void> | null;
59
+ /**
60
+ * Runs each module's `serverHooks.init` (idempotent, non-blocking). Called by
61
+ * `initCMS`. Errors are logged, never thrown. @internal
62
+ */
63
+ runModuleInit(): void;
49
64
  }
50
65
  export declare function initCMS(config: CMSConfig): CMS;
51
66
  /**