includio-cms 0.36.8 → 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.
- package/API.md +179 -3
- package/CHANGELOG.md +64 -0
- package/DOCS.md +28 -3
- package/ROADMAP.md +18 -0
- package/dist/admin/client/booking/booking-dashboard.svelte +40 -0
- package/dist/admin/client/booking/booking-dashboard.svelte.d.ts +3 -0
- package/dist/admin/client/booking/customer-panel.svelte +102 -0
- package/dist/admin/client/booking/customer-panel.svelte.d.ts +11 -0
- package/dist/admin/client/booking/customer-view.d.ts +52 -0
- package/dist/admin/client/booking/customer-view.js +134 -0
- package/dist/admin/client/booking/participant-data-panel.svelte +330 -0
- package/dist/admin/client/booking/participant-data-panel.svelte.d.ts +22 -0
- package/dist/admin/client/booking/reservation-detail-page.svelte +637 -0
- package/dist/admin/client/booking/reservation-detail-page.svelte.d.ts +3 -0
- package/dist/admin/client/booking/reservations-page.svelte +221 -0
- package/dist/admin/client/booking/reservations-page.svelte.d.ts +3 -0
- package/dist/admin/client/collection/collection-entries.svelte +21 -2
- package/dist/admin/client/collection/entry-link.svelte +6 -4
- package/dist/admin/client/collection/entry-link.svelte.d.ts +3 -1
- package/dist/admin/client/collection/row-actions.svelte +12 -4
- package/dist/admin/client/collection/row-actions.svelte.d.ts +1 -0
- package/dist/admin/client/collection/status-badge.svelte +39 -3
- package/dist/admin/client/collection/status-badge.svelte.d.ts +5 -3
- package/dist/admin/client/entry/entry.svelte +26 -9
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +73 -10
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte.d.ts +6 -5
- package/dist/admin/client/entry/preview-views-context.svelte.d.ts +22 -0
- package/dist/admin/client/entry/preview-views-context.svelte.js +58 -0
- package/dist/admin/client/entry/preview-views.d.ts +27 -0
- package/dist/admin/client/entry/preview-views.js +33 -0
- package/dist/admin/client/index.d.ts +3 -0
- package/dist/admin/client/index.js +3 -0
- package/dist/admin/client/modules/dashboard-registry.d.ts +7 -0
- package/dist/admin/client/modules/dashboard-registry.js +11 -0
- package/dist/admin/client/modules/module-dashboard-page.svelte +26 -0
- package/dist/admin/client/modules/module-dashboard-page.svelte.d.ts +6 -0
- package/dist/admin/client/modules/stat-card.svelte +22 -0
- package/dist/admin/client/modules/stat-card.svelte.d.ts +10 -0
- package/dist/admin/client/shop/shop-dashboard.svelte +29 -0
- package/dist/admin/client/shop/shop-dashboard.svelte.d.ts +3 -0
- package/dist/admin/components/fields/relation-field.svelte +11 -2
- package/dist/admin/components/icons/icon-map.js +8 -0
- package/dist/admin/components/layout/app-sidebar.svelte +45 -14
- package/dist/admin/components/layout/context-switcher.svelte +90 -0
- package/dist/admin/components/layout/context-switcher.svelte.d.ts +3 -0
- package/dist/admin/components/layout/lang.d.ts +4 -0
- package/dist/admin/components/layout/lang.js +8 -0
- package/dist/admin/components/layout/layout-renderer.svelte +2 -1
- package/dist/admin/components/layout/layout-tabs.svelte +14 -0
- package/dist/admin/components/layout/layout-tabs.svelte.d.ts +2 -0
- package/dist/admin/components/layout/nav-collections.svelte +16 -9
- package/dist/admin/components/layout/nav-collections.svelte.d.ts +5 -1
- package/dist/admin/components/layout/nav-global.svelte +45 -0
- package/dist/admin/components/layout/nav-global.svelte.d.ts +3 -0
- package/dist/admin/components/layout/nav-main.svelte +8 -32
- package/dist/admin/components/layout/nav-main.svelte.d.ts +6 -1
- package/dist/admin/components/layout/nav-module.svelte +57 -0
- package/dist/admin/components/layout/nav-module.svelte.d.ts +7 -0
- package/dist/admin/components/layout/nav-section.svelte +1 -1
- package/dist/admin/components/layout/nav-section.svelte.d.ts +1 -1
- package/dist/admin/components/layout/nav-singletons.svelte +14 -7
- package/dist/admin/components/layout/nav-singletons.svelte.d.ts +5 -1
- package/dist/admin/remote/booking.remote.d.ts +244 -0
- package/dist/admin/remote/booking.remote.js +283 -0
- package/dist/admin/remote/entry.remote.d.ts +3 -0
- package/dist/admin/remote/entry.remote.js +14 -3
- package/dist/admin/remote/index.d.ts +2 -0
- package/dist/admin/remote/index.js +2 -0
- package/dist/admin/remote/modules.remote.d.ts +10 -0
- package/dist/admin/remote/modules.remote.js +14 -0
- package/dist/admin/state/active-context-resolve.d.ts +37 -0
- package/dist/admin/state/active-context-resolve.js +66 -0
- package/dist/admin/state/active-context.svelte.d.ts +8 -0
- package/dist/admin/state/active-context.svelte.js +27 -0
- package/dist/booking/adjustments.d.ts +33 -0
- package/dist/booking/adjustments.js +26 -0
- package/dist/booking/capacity.d.ts +12 -0
- package/dist/booking/capacity.js +10 -0
- package/dist/booking/client/index.d.ts +39 -0
- package/dist/booking/client/index.js +44 -0
- package/dist/booking/client/use-booking.svelte.d.ts +25 -0
- package/dist/booking/client/use-booking.svelte.js +56 -0
- package/dist/booking/config.d.ts +156 -0
- package/dist/booking/config.js +36 -0
- package/dist/booking/draft.d.ts +77 -0
- package/dist/booking/draft.js +67 -0
- package/dist/booking/expiry-logic.d.ts +30 -0
- package/dist/booking/expiry-logic.js +33 -0
- package/dist/booking/index.d.ts +18 -0
- package/dist/booking/index.js +52 -0
- package/dist/booking/ledger.d.ts +46 -0
- package/dist/booking/ledger.js +60 -0
- package/dist/booking/number.d.ts +2 -0
- package/dist/booking/number.js +11 -0
- package/dist/booking/participant-fields/access.d.ts +10 -0
- package/dist/booking/participant-fields/access.js +14 -0
- package/dist/booking/participant-fields/age.d.ts +12 -0
- package/dist/booking/participant-fields/age.js +28 -0
- package/dist/booking/participant-fields/completeness.d.ts +8 -0
- package/dist/booking/participant-fields/completeness.js +13 -0
- package/dist/booking/participant-fields/group.d.ts +6 -0
- package/dist/booking/participant-fields/group.js +15 -0
- package/dist/booking/participant-fields/index.d.ts +6 -0
- package/dist/booking/participant-fields/index.js +6 -0
- package/dist/booking/participant-fields/types.d.ts +50 -0
- package/dist/booking/participant-fields/types.js +5 -0
- package/dist/booking/participant-fields/validate.d.ts +7 -0
- package/dist/booking/participant-fields/validate.js +44 -0
- package/dist/booking/participant-pricing.d.ts +93 -0
- package/dist/booking/participant-pricing.js +91 -0
- package/dist/booking/participants-view.d.ts +32 -0
- package/dist/booking/participants-view.js +22 -0
- package/dist/booking/payment-context.d.ts +19 -0
- package/dist/booking/payment-context.js +29 -0
- package/dist/booking/pricing-inputs.d.ts +28 -0
- package/dist/booking/pricing-inputs.js +35 -0
- package/dist/booking/pricing.d.ts +86 -0
- package/dist/booking/pricing.js +125 -0
- package/dist/booking/recompute.d.ts +22 -0
- package/dist/booking/recompute.js +31 -0
- package/dist/booking/reminders-schedule.d.ts +11 -0
- package/dist/booking/reminders-schedule.js +28 -0
- package/dist/booking/server/adjustments.d.ts +27 -0
- package/dist/booking/server/adjustments.js +136 -0
- package/dist/booking/server/bookings.d.ts +45 -0
- package/dist/booking/server/bookings.js +288 -0
- package/dist/booking/server/catalog.d.ts +9 -0
- package/dist/booking/server/catalog.js +20 -0
- package/dist/booking/server/db.d.ts +7 -0
- package/dist/booking/server/db.js +18 -0
- package/dist/booking/server/email.d.ts +23 -0
- package/dist/booking/server/email.js +59 -0
- package/dist/booking/server/emailTemplateRegistry.d.ts +29 -0
- package/dist/booking/server/emailTemplateRegistry.js +125 -0
- package/dist/booking/server/http/create-handler.d.ts +5 -0
- package/dist/booking/server/http/create-handler.js +68 -0
- package/dist/booking/server/http/index.d.ts +4 -0
- package/dist/booking/server/http/index.js +4 -0
- package/dist/booking/server/http/maintenance-handler.d.ts +9 -0
- package/dist/booking/server/http/maintenance-handler.js +36 -0
- package/dist/booking/server/http/portal-handler.d.ts +13 -0
- package/dist/booking/server/http/portal-handler.js +99 -0
- package/dist/booking/server/http/webhook-handler.d.ts +9 -0
- package/dist/booking/server/http/webhook-handler.js +39 -0
- package/dist/booking/server/index.d.ts +12 -0
- package/dist/booking/server/index.js +14 -0
- package/dist/booking/server/maintenance.d.ts +28 -0
- package/dist/booking/server/maintenance.js +134 -0
- package/dist/booking/server/participants.d.ts +26 -0
- package/dist/booking/server/participants.js +92 -0
- package/dist/booking/server/payments.d.ts +34 -0
- package/dist/booking/server/payments.js +207 -0
- package/dist/booking/server/portal-session.d.ts +16 -0
- package/dist/booking/server/portal-session.js +42 -0
- package/dist/booking/server/portal.d.ts +207 -0
- package/dist/booking/server/portal.js +234 -0
- package/dist/booking/server/refunds.d.ts +7 -0
- package/dist/booking/server/refunds.js +58 -0
- package/dist/booking/server/reminders.d.ts +9 -0
- package/dist/booking/server/reminders.js +24 -0
- package/dist/booking/server/validation.d.ts +17 -0
- package/dist/booking/server/validation.js +55 -0
- package/dist/booking/state-machine.d.ts +41 -0
- package/dist/booking/state-machine.js +73 -0
- package/dist/booking/templates/_partials/footer.en.html +4 -0
- package/dist/booking/templates/_partials/footer.pl.html +4 -0
- package/dist/booking/templates/_partials/header.en.html +4 -0
- package/dist/booking/templates/_partials/header.pl.html +4 -0
- package/dist/booking/templates/balance-due.en.html +10 -0
- package/dist/booking/templates/balance-due.pl.html +10 -0
- package/dist/booking/templates/balance-overdue-cancelled.en.html +5 -0
- package/dist/booking/templates/balance-overdue-cancelled.pl.html +5 -0
- package/dist/booking/templates/booking-received.en.html +9 -0
- package/dist/booking/templates/booking-received.pl.html +9 -0
- package/dist/booking/templates/deposit-paid.en.html +6 -0
- package/dist/booking/templates/deposit-paid.pl.html +6 -0
- package/dist/booking/templates/payment-link.en.html +5 -0
- package/dist/booking/templates/payment-link.pl.html +5 -0
- package/dist/booking/templates/payment-received.en.html +10 -0
- package/dist/booking/templates/payment-received.pl.html +10 -0
- package/dist/booking/totals.d.ts +38 -0
- package/dist/booking/totals.js +61 -0
- package/dist/booking/webhook-logic.d.ts +28 -0
- package/dist/booking/webhook-logic.js +50 -0
- package/dist/cli/create-user.d.ts +7 -1
- package/dist/cli/create-user.js +23 -15
- package/dist/cli/index.js +55 -7
- package/dist/cli/scaffold/admin.d.ts +6 -0
- package/dist/cli/scaffold/admin.js +5 -3
- package/dist/cli/scaffold/booking.d.ts +12 -0
- package/dist/cli/scaffold/booking.js +103 -0
- package/dist/core/cms.d.ts +24 -9
- package/dist/core/cms.js +72 -34
- package/dist/core/config/validatePreviews.d.ts +12 -0
- package/dist/core/config/validatePreviews.js +90 -0
- package/dist/core/fields/dotPath.d.ts +4 -0
- package/dist/core/fields/dotPath.js +18 -0
- package/dist/core/fields/slugPath.d.ts +9 -1
- package/dist/core/fields/slugPath.js +16 -2
- package/dist/core/fields/slugify.d.ts +3 -0
- package/dist/core/fields/slugify.js +4 -0
- package/dist/core/server/entries/operations/duplicate.d.ts +10 -0
- package/dist/core/server/entries/operations/duplicate.js +73 -0
- package/dist/core/server/entries/operations/slugNormalize.d.ts +8 -0
- package/dist/core/server/entries/operations/slugNormalize.js +20 -0
- package/dist/core/server/entries/operations/slugUniqueness.d.ts +17 -0
- package/dist/core/server/entries/operations/slugUniqueness.js +39 -0
- package/dist/core/server/fields/resolveRelationFields.d.ts +11 -2
- package/dist/core/server/fields/resolveRelationFields.js +12 -3
- package/dist/core/server/generator/generator.js +7 -2
- package/dist/db-postgres/schema/booking/bookingAdjustments.d.ts +186 -0
- package/dist/db-postgres/schema/booking/bookingAdjustments.js +22 -0
- package/dist/db-postgres/schema/booking/bookingItemPersons.d.ts +117 -0
- package/dist/db-postgres/schema/booking/bookingItemPersons.js +22 -0
- package/dist/db-postgres/schema/booking/bookingItems.d.ts +181 -0
- package/dist/db-postgres/schema/booking/bookingItems.js +22 -0
- package/dist/db-postgres/schema/booking/bookingPayments.d.ts +204 -0
- package/dist/db-postgres/schema/booking/bookingPayments.js +25 -0
- package/dist/db-postgres/schema/booking/bookingPersons.d.ts +99 -0
- package/dist/db-postgres/schema/booking/bookingPersons.js +17 -0
- package/dist/db-postgres/schema/booking/bookingReminders.d.ts +131 -0
- package/dist/db-postgres/schema/booking/bookingReminders.js +18 -0
- package/dist/db-postgres/schema/booking/bookings.d.ts +307 -0
- package/dist/db-postgres/schema/booking/bookings.js +40 -0
- package/dist/db-postgres/schema/booking/index.d.ts +7 -0
- package/dist/db-postgres/schema/booking/index.js +7 -0
- package/dist/db-postgres/schema/index.d.ts +1 -0
- package/dist/db-postgres/schema/index.js +1 -0
- package/dist/db-postgres/schema-booking.d.ts +1 -0
- package/dist/db-postgres/schema-booking.js +1 -0
- package/dist/server/security/rate-limit-handler.d.ts +19 -0
- package/dist/server/security/rate-limit-handler.js +34 -0
- package/dist/shop/adapters/payu/client.js +3 -1
- package/dist/shop/adapters/payu/index.js +37 -17
- package/dist/shop/adapters/payu/payload.d.ts +6 -0
- package/dist/shop/adapters/payu/payload.js +1 -1
- package/dist/shop/adapters/stripe/index.js +36 -21
- package/dist/shop/index.d.ts +11 -2
- package/dist/shop/index.js +43 -1
- package/dist/shop/rate-limit.d.ts +1 -9
- package/dist/shop/rate-limit.js +3 -28
- package/dist/shop/types.d.ts +19 -0
- package/dist/sveltekit/config.d.ts +4 -0
- package/dist/sveltekit/config.js +6 -0
- package/dist/types/cms.d.ts +15 -1
- package/dist/types/cms.schema.d.ts +96 -2
- package/dist/types/cms.schema.js +84 -1
- package/dist/types/config.d.ts +33 -0
- package/dist/types/layout.d.ts +6 -0
- package/dist/types/modules.d.ts +133 -0
- package/dist/types/modules.js +27 -0
- package/dist/updates/0.36.9/index.d.ts +2 -0
- package/dist/updates/0.36.9/index.js +12 -0
- package/dist/updates/0.37.0/index.d.ts +2 -0
- package/dist/updates/0.37.0/index.js +52 -0
- package/dist/updates/index.js +5 -1
- package/package.json +19 -1
- package/dist/admin/components/layout/nav-shop.svelte +0 -33
- package/dist/admin/components/layout/nav-shop.svelte.d.ts +0 -3
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active admin context resolution. Pure, deterministic, framework-free so it can
|
|
3
|
+
* be unit-tested without the browser/runed state.
|
|
4
|
+
*
|
|
5
|
+
* Precedence (highest first):
|
|
6
|
+
* 1. **Unambiguous module path** — `/admin/{id}` or `/admin/{id}/...` always
|
|
7
|
+
* wins, so a deep-link to `/admin/shop/orders` shows the shop context
|
|
8
|
+
* regardless of stored state.
|
|
9
|
+
* 2. **`?ctx=` query** — explicit intent, used when linking a surfaced core
|
|
10
|
+
* collection (`/admin/collections/{slug}?ctx=shop`) whose path doesn't
|
|
11
|
+
* encode a context.
|
|
12
|
+
* 3. **Content-core path** — the dashboard (`/admin`) and core CMS sections
|
|
13
|
+
* (collections, singles, entries, forms) resolve to content. Neutral/global
|
|
14
|
+
* routes (media, users, account, maintenance) are excluded: they fall through
|
|
15
|
+
* to the stored sticky context so they stay inside the active module workspace.
|
|
16
|
+
* 4. **Stored sticky** — the last context the user switched to (covers
|
|
17
|
+
* ambiguous routes like the entry editor `/admin/entries/{id}`).
|
|
18
|
+
* 5. **Content** — the default CMS core workspace.
|
|
19
|
+
*/
|
|
20
|
+
/** The base CMS-core context (content editing). */
|
|
21
|
+
export const CONTENT_CONTEXT = 'content';
|
|
22
|
+
/**
|
|
23
|
+
* Route prefixes owned by the content (CMS core) context. Navigating to one of
|
|
24
|
+
* these from a module context exits back to content. **Neutral/global** routes
|
|
25
|
+
* (`/admin/media|users|account|maintenance`) are deliberately NOT here: they are
|
|
26
|
+
* reachable from every context, so they keep the `stored` sticky context instead
|
|
27
|
+
* of forcing content.
|
|
28
|
+
*/
|
|
29
|
+
const CONTENT_CORE_PREFIXES = [
|
|
30
|
+
'/admin/collections',
|
|
31
|
+
'/admin/singles',
|
|
32
|
+
'/admin/entries',
|
|
33
|
+
'/admin/forms'
|
|
34
|
+
];
|
|
35
|
+
/** Whether `pathname` is the dashboard or a core CMS section (not a module). */
|
|
36
|
+
export function isContentCorePath(pathname) {
|
|
37
|
+
if (pathname === '/admin' || pathname === '/admin/')
|
|
38
|
+
return true;
|
|
39
|
+
return CONTENT_CORE_PREFIXES.some((p) => pathname === p || pathname.startsWith(p + '/'));
|
|
40
|
+
}
|
|
41
|
+
/** Whether `pathname` is the landing or a sub-route of module `id`. */
|
|
42
|
+
export function isModulePath(pathname, id) {
|
|
43
|
+
const base = `/admin/${id}`;
|
|
44
|
+
return pathname === base || pathname.startsWith(base + '/');
|
|
45
|
+
}
|
|
46
|
+
/** Resolves the active context id from URL, stored state, and module registry. */
|
|
47
|
+
export function resolveActiveContext(input) {
|
|
48
|
+
const { ctxQuery, stored, pathname, moduleIds } = input;
|
|
49
|
+
// 1. Unambiguous module path wins (deep-link correctness).
|
|
50
|
+
for (const id of moduleIds) {
|
|
51
|
+
if (isModulePath(pathname, id))
|
|
52
|
+
return id;
|
|
53
|
+
}
|
|
54
|
+
const isValid = (id) => !!id && (id === CONTENT_CONTEXT || moduleIds.includes(id));
|
|
55
|
+
// 2. Explicit ?ctx intent.
|
|
56
|
+
if (isValid(ctxQuery))
|
|
57
|
+
return ctxQuery;
|
|
58
|
+
// 3. Content-core path → content (dashboard / collections / media / …).
|
|
59
|
+
if (isContentCorePath(pathname))
|
|
60
|
+
return CONTENT_CONTEXT;
|
|
61
|
+
// 4. Sticky stored context.
|
|
62
|
+
if (isValid(stored))
|
|
63
|
+
return stored;
|
|
64
|
+
// 5. Default to content.
|
|
65
|
+
return CONTENT_CONTEXT;
|
|
66
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive active-context id. Reads the URL (pathname + `?ctx`) and the sticky
|
|
3
|
+
* store. Call inside a `$derived`/`$effect` so it tracks `page` + store changes.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getActiveContext(moduleIds: string[]): string;
|
|
6
|
+
/** Persists the user's context choice (called by the context switcher). */
|
|
7
|
+
export declare function setActiveContext(id: string): void;
|
|
8
|
+
export { CONTENT_CONTEXT } from './active-context-resolve.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PersistedState } from 'runed';
|
|
2
|
+
import { page } from '$app/state';
|
|
3
|
+
import { resolveActiveContext, CONTENT_CONTEXT } from './active-context-resolve.js';
|
|
4
|
+
/**
|
|
5
|
+
* Persisted "sticky" admin context. Survives navigation to ambiguous routes
|
|
6
|
+
* (e.g. the entry editor of a surfaced collection) so the workspace doesn't
|
|
7
|
+
* flicker back to content. Module deep-links and `?ctx=` still take precedence
|
|
8
|
+
* — see {@link resolveActiveContext}.
|
|
9
|
+
*/
|
|
10
|
+
const stored = new PersistedState('admin-active-context', CONTENT_CONTEXT);
|
|
11
|
+
/**
|
|
12
|
+
* Reactive active-context id. Reads the URL (pathname + `?ctx`) and the sticky
|
|
13
|
+
* store. Call inside a `$derived`/`$effect` so it tracks `page` + store changes.
|
|
14
|
+
*/
|
|
15
|
+
export function getActiveContext(moduleIds) {
|
|
16
|
+
return resolveActiveContext({
|
|
17
|
+
ctxQuery: page.url.searchParams.get('ctx'),
|
|
18
|
+
stored: stored.current,
|
|
19
|
+
pathname: page.url.pathname,
|
|
20
|
+
moduleIds
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** Persists the user's context choice (called by the context switcher). */
|
|
24
|
+
export function setActiveContext(id) {
|
|
25
|
+
stored.current = id;
|
|
26
|
+
}
|
|
27
|
+
export { CONTENT_CONTEXT } from './active-context-resolve.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual booking adjustments — staff-entered surcharges (e.g. e-bike +200) and
|
|
3
|
+
* negotiated discounts, kept OUTSIDE the rule engine so a pricing recompute
|
|
4
|
+
* never wipes them. Signed minor units: surcharge > 0, discount < 0. Pure.
|
|
5
|
+
*/
|
|
6
|
+
export type AdjustmentKind = 'manual_surcharge' | 'manual_discount';
|
|
7
|
+
export interface BookingAdjustment {
|
|
8
|
+
id: string;
|
|
9
|
+
bookingId: string;
|
|
10
|
+
kind: AdjustmentKind;
|
|
11
|
+
/** Signed minor units: surcharge > 0, discount < 0. */
|
|
12
|
+
amount: number;
|
|
13
|
+
label: string;
|
|
14
|
+
reason: string | null;
|
|
15
|
+
createdBy: string;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
/** Soft-void: set → excluded from recompute, kept for audit. */
|
|
18
|
+
voidedAt: Date | null;
|
|
19
|
+
voidedBy: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface AdjustmentSums {
|
|
22
|
+
/** Σ positive amounts of active surcharges. */
|
|
23
|
+
surcharge: number;
|
|
24
|
+
/** Σ magnitudes of active discounts (positive). */
|
|
25
|
+
manualDiscount: number;
|
|
26
|
+
}
|
|
27
|
+
export declare const NO_ADJUSTMENTS: AdjustmentSums;
|
|
28
|
+
/** Convert a UI-entered positive magnitude + kind into a signed stored amount. */
|
|
29
|
+
export declare function signedAmount(kind: AdjustmentKind, magnitude: number): number;
|
|
30
|
+
/** Fold active (non-voided) adjustments into totals contributions. By sign. */
|
|
31
|
+
export declare function sumAdjustments(rows: Pick<BookingAdjustment, 'amount' | 'voidedAt'>[]): AdjustmentSums;
|
|
32
|
+
/** Reject a zero/negative magnitude at the write boundary. */
|
|
33
|
+
export declare function assertAdjustmentMagnitude(magnitude: number): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const NO_ADJUSTMENTS = { surcharge: 0, manualDiscount: 0 };
|
|
2
|
+
/** Convert a UI-entered positive magnitude + kind into a signed stored amount. */
|
|
3
|
+
export function signedAmount(kind, magnitude) {
|
|
4
|
+
const m = Math.abs(magnitude);
|
|
5
|
+
return kind === 'manual_discount' ? -m : m;
|
|
6
|
+
}
|
|
7
|
+
/** Fold active (non-voided) adjustments into totals contributions. By sign. */
|
|
8
|
+
export function sumAdjustments(rows) {
|
|
9
|
+
let surcharge = 0;
|
|
10
|
+
let manualDiscount = 0;
|
|
11
|
+
for (const r of rows) {
|
|
12
|
+
if (r.voidedAt)
|
|
13
|
+
continue;
|
|
14
|
+
if (r.amount >= 0)
|
|
15
|
+
surcharge += r.amount;
|
|
16
|
+
else
|
|
17
|
+
manualDiscount += -r.amount;
|
|
18
|
+
}
|
|
19
|
+
return { surcharge, manualDiscount };
|
|
20
|
+
}
|
|
21
|
+
/** Reject a zero/negative magnitude at the write boundary. */
|
|
22
|
+
export function assertAdjustmentMagnitude(magnitude) {
|
|
23
|
+
if (!Number.isFinite(magnitude) || magnitude <= 0) {
|
|
24
|
+
throw new Error(`Adjustment magnitude must be a positive amount, got ${magnitude}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether `requested` seats fit a trip. `capacity == null` means
|
|
3
|
+
* unlimited (no catalog capacity configured) → always ok. Pure.
|
|
4
|
+
*/
|
|
5
|
+
export declare function decideCapacity(input: {
|
|
6
|
+
capacity: number | null;
|
|
7
|
+
alreadyBooked: number;
|
|
8
|
+
requested: number;
|
|
9
|
+
}): {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
available: number | null;
|
|
12
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether `requested` seats fit a trip. `capacity == null` means
|
|
3
|
+
* unlimited (no catalog capacity configured) → always ok. Pure.
|
|
4
|
+
*/
|
|
5
|
+
export function decideCapacity(input) {
|
|
6
|
+
if (input.capacity == null)
|
|
7
|
+
return { ok: true, available: null };
|
|
8
|
+
const available = input.capacity - input.alreadyBooked;
|
|
9
|
+
return { ok: input.requested <= available, available };
|
|
10
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser SDK for the booking storefront + self-service portal. Thin fetch
|
|
3
|
+
* wrappers over the booking HTTP handlers. Isomorphic — no server imports.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export interface BookingClientOptions {
|
|
8
|
+
/** Base URL prefix (default ''), e.g. when the API is on another origin. */
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
/** Custom fetch (e.g. SvelteKit's `fetch` during SSR). Defaults to global. */
|
|
11
|
+
fetch?: typeof globalThis.fetch;
|
|
12
|
+
}
|
|
13
|
+
/** Error thrown on a non-2xx response; `code` carries the server's error code. */
|
|
14
|
+
export declare class BookingClientError extends Error {
|
|
15
|
+
code?: string;
|
|
16
|
+
constructor(message: string, code?: string);
|
|
17
|
+
}
|
|
18
|
+
/** Create a booking API client bound to a base URL + fetch. @public */
|
|
19
|
+
export declare function createBookingClient(opts?: BookingClientOptions): {
|
|
20
|
+
/** Create a booking (public self-serve). Returns deposit redirect info. */
|
|
21
|
+
create: (input: unknown) => Promise<unknown>;
|
|
22
|
+
/** Fetch the portal view for an access token. */
|
|
23
|
+
getByToken: (token: string) => Promise<unknown>;
|
|
24
|
+
/** Self-service edit; rejects with `code: 'CONTACT_STAFF'` on below-paid edits. */
|
|
25
|
+
edit: (token: string, changes: unknown) => Promise<unknown>;
|
|
26
|
+
/** Create an installment/balance payment link. */
|
|
27
|
+
pay: (token: string, amount: number) => Promise<unknown>;
|
|
28
|
+
/** Self-service: save a participant's custom-field data (staff fields ignored). */
|
|
29
|
+
updateParticipant: (token: string, participantId: string, data: Record<string, unknown>) => Promise<unknown>;
|
|
30
|
+
/** Drugi czynnik: weryfikuj numer telefonu; sukces ustawia sesję portalu (cookie). */
|
|
31
|
+
verifyPhone: (token: string, phone: string) => Promise<unknown>;
|
|
32
|
+
/** Self-service: dołóż wycieczki istniejącym osobom (recompute salda). */
|
|
33
|
+
addTrips: (token: string, additions: Array<{
|
|
34
|
+
tripRef: string;
|
|
35
|
+
unitPrice: number;
|
|
36
|
+
assignments: string[];
|
|
37
|
+
}>) => Promise<unknown>;
|
|
38
|
+
};
|
|
39
|
+
export { useBooking } from './use-booking.svelte.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Error thrown on a non-2xx response; `code` carries the server's error code. */
|
|
2
|
+
export class BookingClientError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(message, code) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'BookingClientError';
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Create a booking API client bound to a base URL + fetch. @public */
|
|
11
|
+
export function createBookingClient(opts = {}) {
|
|
12
|
+
const base = opts.baseUrl ?? '';
|
|
13
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
14
|
+
async function req(path, init) {
|
|
15
|
+
const res = await f(`${base}${path}`, init);
|
|
16
|
+
const data = (await res.json().catch(() => ({})));
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
throw new BookingClientError(typeof data.error === 'string' ? data.error : `HTTP ${res.status}`, typeof data.code === 'string' ? data.code : undefined);
|
|
19
|
+
}
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
const jsonInit = (method, body) => ({
|
|
23
|
+
method,
|
|
24
|
+
headers: { 'content-type': 'application/json' },
|
|
25
|
+
body: body === undefined ? undefined : JSON.stringify(body)
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
/** Create a booking (public self-serve). Returns deposit redirect info. */
|
|
29
|
+
create: (input) => req('/api/booking', jsonInit('POST', input)),
|
|
30
|
+
/** Fetch the portal view for an access token. */
|
|
31
|
+
getByToken: (token) => req(`/api/booking/portal/${token}`, jsonInit('GET')),
|
|
32
|
+
/** Self-service edit; rejects with `code: 'CONTACT_STAFF'` on below-paid edits. */
|
|
33
|
+
edit: (token, changes) => req(`/api/booking/portal/${token}?action=edit`, jsonInit('POST', changes)),
|
|
34
|
+
/** Create an installment/balance payment link. */
|
|
35
|
+
pay: (token, amount) => req(`/api/booking/portal/${token}?action=pay`, jsonInit('POST', { amount })),
|
|
36
|
+
/** Self-service: save a participant's custom-field data (staff fields ignored). */
|
|
37
|
+
updateParticipant: (token, participantId, data) => req(`/api/booking/portal/${token}?action=participant`, jsonInit('POST', { participantId, data })),
|
|
38
|
+
/** Drugi czynnik: weryfikuj numer telefonu; sukces ustawia sesję portalu (cookie). */
|
|
39
|
+
verifyPhone: (token, phone) => req(`/api/booking/portal/${token}?action=verify-phone`, jsonInit('POST', { phone })),
|
|
40
|
+
/** Self-service: dołóż wycieczki istniejącym osobom (recompute salda). */
|
|
41
|
+
addTrips: (token, additions) => req(`/api/booking/portal/${token}?action=add-trips`, jsonInit('POST', { additions }))
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export { useBooking } from './use-booking.svelte.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type BookingClientOptions } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reactive self-service portal state for a booking access token. Loads the view
|
|
4
|
+
* on creation; `edit`/`reload` refresh it, `pay` returns a payment link. Errors
|
|
5
|
+
* (including `CONTACT_STAFF` 409s) surface via `error`. A `PHONE_REQUIRED` 2FA
|
|
6
|
+
* gate surfaces via `needsPhone`; call `verifyPhone` to clear it and re-load the
|
|
7
|
+
* view.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export declare function useBooking(token: string, opts?: BookingClientOptions): {
|
|
12
|
+
readonly view: unknown;
|
|
13
|
+
readonly loading: boolean;
|
|
14
|
+
readonly error: string | null;
|
|
15
|
+
readonly needsPhone: boolean;
|
|
16
|
+
reload: () => Promise<void>;
|
|
17
|
+
edit: (changes: unknown) => Promise<void>;
|
|
18
|
+
pay: (amount: number) => Promise<unknown>;
|
|
19
|
+
verifyPhone: (phone: string) => Promise<void>;
|
|
20
|
+
addTrips: (additions: Array<{
|
|
21
|
+
tripRef: string;
|
|
22
|
+
unitPrice: number;
|
|
23
|
+
assignments: string[];
|
|
24
|
+
}>) => Promise<void>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createBookingClient, BookingClientError } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reactive self-service portal state for a booking access token. Loads the view
|
|
4
|
+
* on creation; `edit`/`reload` refresh it, `pay` returns a payment link. Errors
|
|
5
|
+
* (including `CONTACT_STAFF` 409s) surface via `error`. A `PHONE_REQUIRED` 2FA
|
|
6
|
+
* gate surfaces via `needsPhone`; call `verifyPhone` to clear it and re-load the
|
|
7
|
+
* view.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export function useBooking(token, opts = {}) {
|
|
12
|
+
const client = createBookingClient(opts);
|
|
13
|
+
let view = $state(null);
|
|
14
|
+
let loading = $state(false);
|
|
15
|
+
let error = $state(null);
|
|
16
|
+
let needsPhone = $state(false);
|
|
17
|
+
async function run(fn) {
|
|
18
|
+
loading = true;
|
|
19
|
+
error = null;
|
|
20
|
+
try {
|
|
21
|
+
view = await fn();
|
|
22
|
+
needsPhone = false;
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
if (e instanceof BookingClientError && e.code === 'PHONE_REQUIRED') {
|
|
26
|
+
needsPhone = true;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
error = e instanceof Error ? e.message : 'Error';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
loading = false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
run(() => client.getByToken(token));
|
|
37
|
+
return {
|
|
38
|
+
get view() {
|
|
39
|
+
return view;
|
|
40
|
+
},
|
|
41
|
+
get loading() {
|
|
42
|
+
return loading;
|
|
43
|
+
},
|
|
44
|
+
get error() {
|
|
45
|
+
return error;
|
|
46
|
+
},
|
|
47
|
+
get needsPhone() {
|
|
48
|
+
return needsPhone;
|
|
49
|
+
},
|
|
50
|
+
reload: () => run(() => client.getByToken(token)),
|
|
51
|
+
edit: (changes) => run(() => client.edit(token, changes)),
|
|
52
|
+
pay: (amount) => client.pay(token, amount),
|
|
53
|
+
verifyPhone: (phone) => run(() => client.verifyPhone(token, phone)),
|
|
54
|
+
addTrips: (additions) => run(() => client.addTrips(token, additions))
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { PaymentAdapter } from '../shop/types.js';
|
|
2
|
+
import type { PricingConfig } from './pricing.js';
|
|
3
|
+
import type { ParticipantPricingConfig } from './participant-pricing.js';
|
|
4
|
+
import type { FieldDef, AgeFlagConfig } from './participant-fields/types.js';
|
|
5
|
+
/** One scheduled reminder rule, relative to the earliest departure date. */
|
|
6
|
+
export interface ReminderRule {
|
|
7
|
+
/** Stable type tag for the reminder (e.g. `pre-departure`). */
|
|
8
|
+
type: string;
|
|
9
|
+
/** Whole days before departure to send. */
|
|
10
|
+
daysBefore: number;
|
|
11
|
+
/** Email template key to render (see booking email registry). */
|
|
12
|
+
templateKey: string;
|
|
13
|
+
}
|
|
14
|
+
/** Context handed to the client-supplied {@link BookingValidator} seam. */
|
|
15
|
+
export interface BookingValidationContext {
|
|
16
|
+
operation: 'create' | 'edit';
|
|
17
|
+
bookingId: string | null;
|
|
18
|
+
items: Array<{
|
|
19
|
+
tripRef: string;
|
|
20
|
+
departureDate: Date | null;
|
|
21
|
+
participantsCount: number;
|
|
22
|
+
}>;
|
|
23
|
+
customer: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Client-specific validation seam (e.g. weekly-model term conflicts). Throws to
|
|
27
|
+
* reject. The framework only enforces generic capacity + not-below-paid.
|
|
28
|
+
*/
|
|
29
|
+
export type BookingValidator = (ctx: BookingValidationContext) => void | Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* User-facing booking configuration. Passed to `defineBooking` and registered
|
|
32
|
+
* via `defineConfig({ modules: [defineBooking({...})] })`.
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export interface BookingConfig {
|
|
37
|
+
/** ISO currency code, e.g. `PLN`. */
|
|
38
|
+
currency: string;
|
|
39
|
+
/** Slug of the (client-specific) trip catalog collection. Omit when none. */
|
|
40
|
+
catalogCollection?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Dodatkowe kolekcje rdzenia wciągane do kontekstu „Rezerwacje" w adminie (obok katalogu) —
|
|
43
|
+
* np. kolekcja definiująca typy wypraw i pola uczestnika. Bez tego mieszkają w kontekście
|
|
44
|
+
* „Treści" i wyglądają jak doszyta kolekcja CMS-a, choć obsługują rezerwacje.
|
|
45
|
+
*
|
|
46
|
+
* Wyłącznie prezentacja: nie zmienia danych ani uprawnień.
|
|
47
|
+
*
|
|
48
|
+
* @experimental Docelowo moduł będzie wnosił typy wypraw sam (0.38) — patrz ROADMAP.
|
|
49
|
+
*/
|
|
50
|
+
surfacedCollections?: string[];
|
|
51
|
+
/** Deposit that secures a reservation, in minor units (default 0). */
|
|
52
|
+
depositAmount?: number;
|
|
53
|
+
/** Discount rules (legacy per-line engine; booking now prices per-participant). */
|
|
54
|
+
pricing?: PricingConfig;
|
|
55
|
+
/**
|
|
56
|
+
* Per-participant pricing config (age bands + volume ladder + early-bird) as a
|
|
57
|
+
* resolver so values can come from an editable CMS singleton (globalSettings).
|
|
58
|
+
* Called per create/edit. Omit ⇒ no age/volume discounts (everyone full price).
|
|
59
|
+
*/
|
|
60
|
+
participantPricing?: () => ParticipantPricingConfig | Promise<ParticipantPricingConfig>;
|
|
61
|
+
/** Customer field holding the arrival date — anchor for age + early-bird + reminders. Default `arrivalDate`. */
|
|
62
|
+
arrivalDateField?: string;
|
|
63
|
+
/** Payment adapters (reused from the shop module's adapter family). */
|
|
64
|
+
payment?: PaymentAdapter[];
|
|
65
|
+
/**
|
|
66
|
+
* Portal URL template handed to payment adapters as the post-payment return
|
|
67
|
+
* URL. Placeholder `{token}` → `booking.accessToken`. Must be absolute, e.g.
|
|
68
|
+
* `https://shop.example/rezerwacja/{token}`. Omit to disable redirect return.
|
|
69
|
+
*/
|
|
70
|
+
portalUrlTemplate?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Absolute return URL for the CHECKOUT deposit payment (post-payment landing,
|
|
73
|
+
* e.g. a thank-you page), when it should differ from the portal. Handed to the
|
|
74
|
+
* adapter as `ctx.continueUrl` for the deposit only; installments/balance keep
|
|
75
|
+
* `portalUrlTemplate`. Omit ⇒ deposit returns to the portal like other payments.
|
|
76
|
+
*/
|
|
77
|
+
checkoutReturnUrl?: string;
|
|
78
|
+
/** Operator address for staff notifications. */
|
|
79
|
+
adminEmail?: string;
|
|
80
|
+
/** Business display name shown in transactional email footers; omit for none. */
|
|
81
|
+
brand?: string;
|
|
82
|
+
/** Minutes an unpaid draft is held before auto-cancel (default 60). */
|
|
83
|
+
depositTtlMinutes?: number;
|
|
84
|
+
/** Days before departure the balance is due; omit to disable forfeiture. */
|
|
85
|
+
balanceDueDaysBefore?: number;
|
|
86
|
+
/** Field on the catalog entry holding seat capacity; omit for unlimited. */
|
|
87
|
+
capacityField?: string;
|
|
88
|
+
/** Reminder schedule (offsets before departure). */
|
|
89
|
+
reminders?: ReminderRule[];
|
|
90
|
+
/** Client-specific validation seam. */
|
|
91
|
+
validateBooking?: BookingValidator;
|
|
92
|
+
/** In-process maintenance loop tuning. */
|
|
93
|
+
maintenance?: {
|
|
94
|
+
autoRun?: boolean;
|
|
95
|
+
intervalMinutes?: number;
|
|
96
|
+
};
|
|
97
|
+
/** Bearer token guarding the secured maintenance endpoint; omit to disable. */
|
|
98
|
+
maintenanceToken?: string;
|
|
99
|
+
/** Sekret HMAC dla sesji portalu (2FA telefonem); omit ⇒ portal fail-closed. */
|
|
100
|
+
portalSecret?: string;
|
|
101
|
+
/** Schemat pól uczestnika: statyczny lub resolver per wpis katalogu. */
|
|
102
|
+
participantFields?: FieldDef[] | ((catalogEntry: Record<string, unknown>) => FieldDef[]);
|
|
103
|
+
/** Flaga wieku (np. „Dziecko") z pola roli `birthDate` + próg. */
|
|
104
|
+
ageFlag?: AgeFlagConfig;
|
|
105
|
+
/**
|
|
106
|
+
* Opis pól worka `customer` — WYŁĄCZNIE dla prezentacji w adminie. Rdzeń nie zna kształtu
|
|
107
|
+
* `customer` (definiuje go projekt), więc bez tego panel „Klient" pokazuje uczłowieczone
|
|
108
|
+
* klucze (`referralSource` → „Referral source"). Podaj `label`, żeby nazwać pola po ludzku,
|
|
109
|
+
* i `role: 'birthDate'`, żeby rdzeń dopisał wiek obok daty.
|
|
110
|
+
*
|
|
111
|
+
* Klucze zagnieżdżone (kolumny tabel, np. manifest osób) adresuje się kropką:
|
|
112
|
+
* `{ key: 'people.birthDate', label: 'Data urodzenia', role: 'birthDate' }`.
|
|
113
|
+
*
|
|
114
|
+
* Nie waliduje ani nie zmienia danych — to czysta warstwa prezentacji.
|
|
115
|
+
*
|
|
116
|
+
* @experimental Świadomie NIEPEŁNE: pola uczestnika mają w rdzeniu prawdziwy schemat
|
|
117
|
+
* (`participantFields` — napędza walidację, render i formularz), a `customer` ma dziś tylko
|
|
118
|
+
* etykiety, bo formularz kontaktowy jest pisany ręcznie w projekcie. Skutek: worek `customer`
|
|
119
|
+
* ma DWIE deklaracje (etykiety tutaj, reguły w repo projektu) i może się rozjechać. Domknięcie
|
|
120
|
+
* w 0.38 — patrz ROADMAP.
|
|
121
|
+
*/
|
|
122
|
+
customerFields?: FieldDef[];
|
|
123
|
+
}
|
|
124
|
+
/** Fully-defaulted booking config, carried on `CMSModule.config`. */
|
|
125
|
+
export interface ResolvedBookingConfig {
|
|
126
|
+
currency: string;
|
|
127
|
+
catalogCollection: string | null;
|
|
128
|
+
surfacedCollections: string[];
|
|
129
|
+
depositAmount: number;
|
|
130
|
+
pricing: PricingConfig;
|
|
131
|
+
participantPricing: (() => ParticipantPricingConfig | Promise<ParticipantPricingConfig>) | null;
|
|
132
|
+
arrivalDateField: string;
|
|
133
|
+
payment: PaymentAdapter[];
|
|
134
|
+
portalUrlTemplate: string | null;
|
|
135
|
+
checkoutReturnUrl: string | null;
|
|
136
|
+
adminEmail: string | null;
|
|
137
|
+
brand: string | null;
|
|
138
|
+
depositTtlMinutes: number;
|
|
139
|
+
balanceDueDaysBefore: number | null;
|
|
140
|
+
capacityField: string | null;
|
|
141
|
+
reminders: ReminderRule[];
|
|
142
|
+
validateBooking: BookingValidator | null;
|
|
143
|
+
maintenance: {
|
|
144
|
+
autoRun: boolean;
|
|
145
|
+
intervalMinutes: number;
|
|
146
|
+
};
|
|
147
|
+
maintenanceToken: string | null;
|
|
148
|
+
portalSecret: string | null;
|
|
149
|
+
participantFields: FieldDef[] | ((catalogEntry: Record<string, unknown>) => FieldDef[]);
|
|
150
|
+
ageFlag: AgeFlagConfig | null;
|
|
151
|
+
customerFields: FieldDef[];
|
|
152
|
+
}
|
|
153
|
+
/** Resolves a raw {@link BookingConfig} into a {@link ResolvedBookingConfig}. */
|
|
154
|
+
export declare function resolveBookingConfig(config: BookingConfig): ResolvedBookingConfig;
|
|
155
|
+
/** Rozwiązuje schemat pól dla danego wpisu katalogu (array lub resolver). */
|
|
156
|
+
export declare function resolveParticipantFields(config: ResolvedBookingConfig, catalogEntry: Record<string, unknown>): FieldDef[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Resolves a raw {@link BookingConfig} into a {@link ResolvedBookingConfig}. */
|
|
2
|
+
export function resolveBookingConfig(config) {
|
|
3
|
+
return {
|
|
4
|
+
currency: config.currency,
|
|
5
|
+
catalogCollection: config.catalogCollection ?? null,
|
|
6
|
+
surfacedCollections: config.surfacedCollections ?? [],
|
|
7
|
+
depositAmount: config.depositAmount ?? 0,
|
|
8
|
+
pricing: config.pricing ?? { rules: [] },
|
|
9
|
+
participantPricing: config.participantPricing ?? null,
|
|
10
|
+
arrivalDateField: config.arrivalDateField ?? 'arrivalDate',
|
|
11
|
+
payment: config.payment ?? [],
|
|
12
|
+
portalUrlTemplate: config.portalUrlTemplate ?? null,
|
|
13
|
+
checkoutReturnUrl: config.checkoutReturnUrl ?? null,
|
|
14
|
+
adminEmail: config.adminEmail ?? null,
|
|
15
|
+
brand: config.brand ?? null,
|
|
16
|
+
depositTtlMinutes: config.depositTtlMinutes ?? 60,
|
|
17
|
+
balanceDueDaysBefore: config.balanceDueDaysBefore ?? null,
|
|
18
|
+
capacityField: config.capacityField ?? null,
|
|
19
|
+
reminders: config.reminders ?? [],
|
|
20
|
+
validateBooking: config.validateBooking ?? null,
|
|
21
|
+
maintenance: {
|
|
22
|
+
autoRun: config.maintenance?.autoRun ?? true,
|
|
23
|
+
intervalMinutes: config.maintenance?.intervalMinutes ?? 15
|
|
24
|
+
},
|
|
25
|
+
maintenanceToken: config.maintenanceToken ?? null,
|
|
26
|
+
portalSecret: config.portalSecret ?? null,
|
|
27
|
+
participantFields: config.participantFields ?? [],
|
|
28
|
+
ageFlag: config.ageFlag ?? null,
|
|
29
|
+
customerFields: config.customerFields ?? []
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Rozwiązuje schemat pól dla danego wpisu katalogu (array lub resolver). */
|
|
33
|
+
export function resolveParticipantFields(config, catalogEntry) {
|
|
34
|
+
const pf = config.participantFields;
|
|
35
|
+
return typeof pf === 'function' ? pf(catalogEntry) : pf;
|
|
36
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ResolvedBookingConfig } from './config.js';
|
|
2
|
+
import { type PricingParticipant, type PricedTrip, type ParticipantPricingConfig } from './participant-pricing.js';
|
|
3
|
+
import { type BookingTotals } from './totals.js';
|
|
4
|
+
import type { FieldDef } from './participant-fields/types.js';
|
|
5
|
+
import type { AppliedRule } from './pricing.js';
|
|
6
|
+
/**
|
|
7
|
+
* A person on the booking. Identity fields (name, date of birth — the
|
|
8
|
+
* role:name / role:birthDate participant fields) are entered ONCE and shared
|
|
9
|
+
* across every trip the person takes. `ref` is a client-stable key used to
|
|
10
|
+
* correlate the person with their item assignments within a single payload.
|
|
11
|
+
*/
|
|
12
|
+
export interface PersonInput {
|
|
13
|
+
ref: string;
|
|
14
|
+
identity: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/** Assignment of a person to a trip line, with that person's per-trip answers. */
|
|
17
|
+
export interface ItemAssignmentInput {
|
|
18
|
+
/** References a {@link PersonInput.ref} in the same payload. */
|
|
19
|
+
personRef: string;
|
|
20
|
+
/** Per-trip custom-field answers (trip-type-specific). */
|
|
21
|
+
data?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/** One requested trip line in a create/edit payload. */
|
|
24
|
+
export interface BookingItemInput {
|
|
25
|
+
tripRef: string;
|
|
26
|
+
departureDate: Date | null;
|
|
27
|
+
/** Unit (adult) price (minor units) snapshotted at booking time. */
|
|
28
|
+
unitPrice: number;
|
|
29
|
+
/** Who takes this trip (+ their per-trip data). Count derives from this. */
|
|
30
|
+
assignments: ItemAssignmentInput[];
|
|
31
|
+
}
|
|
32
|
+
export interface CreateBookingInput {
|
|
33
|
+
customer: Record<string, unknown>;
|
|
34
|
+
/** People on the booking (identity, shared across trips). */
|
|
35
|
+
persons: PersonInput[];
|
|
36
|
+
items: BookingItemInput[];
|
|
37
|
+
language?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Per-call pricing context (dynamic: from globalSettings + this booking's customer). */
|
|
40
|
+
export interface DraftPricingContext {
|
|
41
|
+
participantPricing: ParticipantPricingConfig;
|
|
42
|
+
/** Arrival date — anchor for age + early-bird. Null ⇒ everyone full price. */
|
|
43
|
+
anchor: Date | null;
|
|
44
|
+
/** Identity field schema, used to locate the role:birthDate key for age. */
|
|
45
|
+
identityFields: FieldDef[];
|
|
46
|
+
}
|
|
47
|
+
export interface BookingDraft {
|
|
48
|
+
totals: BookingTotals;
|
|
49
|
+
persons: PersonInput[];
|
|
50
|
+
items: Array<{
|
|
51
|
+
tripRef: string;
|
|
52
|
+
departureDate: Date | null;
|
|
53
|
+
participantsCount: number;
|
|
54
|
+
unitPriceSnapshot: number;
|
|
55
|
+
subtotalSnapshot: number | null;
|
|
56
|
+
appliedDiscounts: AppliedRule[];
|
|
57
|
+
assignments: ItemAssignmentInput[];
|
|
58
|
+
}>;
|
|
59
|
+
reminders: Array<{
|
|
60
|
+
type: string;
|
|
61
|
+
templateKey: string;
|
|
62
|
+
scheduledFor: Date;
|
|
63
|
+
}>;
|
|
64
|
+
balanceDueAt: Date | null;
|
|
65
|
+
holdExpiresAt: Date;
|
|
66
|
+
}
|
|
67
|
+
/** Build the per-participant pricing inputs from a create payload. */
|
|
68
|
+
export declare function pricingInputsFromCreate(input: CreateBookingInput, identityFields: FieldDef[]): {
|
|
69
|
+
participants: PricingParticipant[];
|
|
70
|
+
trips: PricedTrip[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Pure: turn a create payload + config + pricing context + clock into the
|
|
74
|
+
* rows/totals to persist. Prices per-participant (age bands + volume ladder +
|
|
75
|
+
* early-bird), anchored to the arrival date in `ctx.anchor`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function buildBookingDraft(input: CreateBookingInput, config: ResolvedBookingConfig, ctx: DraftPricingContext, now: Date): BookingDraft;
|