includio-cms 0.36.9 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +178 -3
- package/CHANGELOG.md +77 -0
- package/DOCS.md +28 -3
- package/ROADMAP.md +32 -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 +1 -1
- 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/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 +247 -0
- package/dist/admin/remote/booking.remote.js +286 -0
- 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 +42 -0
- package/dist/booking/client/index.js +48 -0
- package/dist/booking/client/use-booking.svelte.d.ts +24 -0
- package/dist/booking/client/use-booking.svelte.js +56 -0
- package/dist/booking/config.d.ts +176 -0
- package/dist/booking/config.js +37 -0
- package/dist/booking/draft.d.ts +96 -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 +139 -0
- package/dist/booking/server/bookings.d.ts +51 -0
- package/dist/booking/server/bookings.js +336 -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 +70 -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 +103 -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 +206 -0
- package/dist/booking/server/portal.js +236 -0
- package/dist/booking/server/price-source.d.ts +73 -0
- package/dist/booking/server/price-source.js +94 -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/slugPath.d.ts +9 -1
- package/dist/core/fields/slugPath.js +16 -2
- 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 +324 -0
- package/dist/db-postgres/schema/booking/bookings.js +52 -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.37.0/index.d.ts +2 -0
- package/dist/updates/0.37.0/index.js +52 -0
- package/dist/updates/0.37.1/index.d.ts +2 -0
- package/dist/updates/0.37.1/index.js +24 -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,24 @@
|
|
|
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
|
+
assignments: string[];
|
|
23
|
+
}>) => Promise<void>;
|
|
24
|
+
};
|
|
@@ -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,176 @@
|
|
|
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
|
+
/**
|
|
54
|
+
* **Szew serwerowy: cena bazowa (dorosłego) pozycji katalogu, w groszach.**
|
|
55
|
+
*
|
|
56
|
+
* Rdzeń nie zna kształtu katalogu (`tripRef` jest dla niego nieprzezroczysty), więc
|
|
57
|
+
* cenę musi podać projekt. Wołany **wyłącznie na serwerze** przy każdej rezerwacji
|
|
58
|
+
* składanej przez klienta końcowego — dzięki temu przeglądarka nie ma na cenę wpływu.
|
|
59
|
+
*
|
|
60
|
+
* **Wymagany, gdy booking wystawia publiczne endpointy** (`createBookingHandler`,
|
|
61
|
+
* portal). Bez niego rdzeń odmawia wyceny (fail-closed) zamiast zaufać cenie z żądania.
|
|
62
|
+
* Obsługa w adminie (`actor: 'staff'`) nadal może podać cenę ręcznie — korekta jest
|
|
63
|
+
* zamierzoną funkcją.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* resolvePrice: async (tripRef) => {
|
|
67
|
+
* const entry = await resolveEntry({ id: tripRef, status: 'published' });
|
|
68
|
+
* return Math.round(Number(entry?.price ?? 0) * 100); // PLN → grosze
|
|
69
|
+
* }
|
|
70
|
+
*/
|
|
71
|
+
resolvePrice?: (tripRef: string) => number | Promise<number>;
|
|
72
|
+
/** Discount rules (legacy per-line engine; booking now prices per-participant). */
|
|
73
|
+
pricing?: PricingConfig;
|
|
74
|
+
/**
|
|
75
|
+
* Per-participant pricing config (age bands + volume ladder + early-bird) as a
|
|
76
|
+
* resolver so values can come from an editable CMS singleton (globalSettings).
|
|
77
|
+
* Called per create/edit. Omit ⇒ no age/volume discounts (everyone full price).
|
|
78
|
+
*/
|
|
79
|
+
participantPricing?: () => ParticipantPricingConfig | Promise<ParticipantPricingConfig>;
|
|
80
|
+
/** Customer field holding the arrival date — anchor for age + early-bird + reminders. Default `arrivalDate`. */
|
|
81
|
+
arrivalDateField?: string;
|
|
82
|
+
/** Payment adapters (reused from the shop module's adapter family). */
|
|
83
|
+
payment?: PaymentAdapter[];
|
|
84
|
+
/**
|
|
85
|
+
* Portal URL template handed to payment adapters as the post-payment return
|
|
86
|
+
* URL. Placeholder `{token}` → `booking.accessToken`. Must be absolute, e.g.
|
|
87
|
+
* `https://shop.example/rezerwacja/{token}`. Omit to disable redirect return.
|
|
88
|
+
*/
|
|
89
|
+
portalUrlTemplate?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Absolute return URL for the CHECKOUT deposit payment (post-payment landing,
|
|
92
|
+
* e.g. a thank-you page), when it should differ from the portal. Handed to the
|
|
93
|
+
* adapter as `ctx.continueUrl` for the deposit only; installments/balance keep
|
|
94
|
+
* `portalUrlTemplate`. Omit ⇒ deposit returns to the portal like other payments.
|
|
95
|
+
*/
|
|
96
|
+
checkoutReturnUrl?: string;
|
|
97
|
+
/** Operator address for staff notifications. */
|
|
98
|
+
adminEmail?: string;
|
|
99
|
+
/** Business display name shown in transactional email footers; omit for none. */
|
|
100
|
+
brand?: string;
|
|
101
|
+
/** Minutes an unpaid draft is held before auto-cancel (default 60). */
|
|
102
|
+
depositTtlMinutes?: number;
|
|
103
|
+
/** Days before departure the balance is due; omit to disable forfeiture. */
|
|
104
|
+
balanceDueDaysBefore?: number;
|
|
105
|
+
/** Field on the catalog entry holding seat capacity; omit for unlimited. */
|
|
106
|
+
capacityField?: string;
|
|
107
|
+
/** Reminder schedule (offsets before departure). */
|
|
108
|
+
reminders?: ReminderRule[];
|
|
109
|
+
/** Client-specific validation seam. */
|
|
110
|
+
validateBooking?: BookingValidator;
|
|
111
|
+
/** In-process maintenance loop tuning. */
|
|
112
|
+
maintenance?: {
|
|
113
|
+
autoRun?: boolean;
|
|
114
|
+
intervalMinutes?: number;
|
|
115
|
+
};
|
|
116
|
+
/** Bearer token guarding the secured maintenance endpoint; omit to disable. */
|
|
117
|
+
maintenanceToken?: string;
|
|
118
|
+
/** Sekret HMAC dla sesji portalu (2FA telefonem); omit ⇒ portal fail-closed. */
|
|
119
|
+
portalSecret?: string;
|
|
120
|
+
/** Schemat pól uczestnika: statyczny lub resolver per wpis katalogu. */
|
|
121
|
+
participantFields?: FieldDef[] | ((catalogEntry: Record<string, unknown>) => FieldDef[]);
|
|
122
|
+
/** Flaga wieku (np. „Dziecko") z pola roli `birthDate` + próg. */
|
|
123
|
+
ageFlag?: AgeFlagConfig;
|
|
124
|
+
/**
|
|
125
|
+
* Opis pól worka `customer` — WYŁĄCZNIE dla prezentacji w adminie. Rdzeń nie zna kształtu
|
|
126
|
+
* `customer` (definiuje go projekt), więc bez tego panel „Klient" pokazuje uczłowieczone
|
|
127
|
+
* klucze (`referralSource` → „Referral source"). Podaj `label`, żeby nazwać pola po ludzku,
|
|
128
|
+
* i `role: 'birthDate'`, żeby rdzeń dopisał wiek obok daty.
|
|
129
|
+
*
|
|
130
|
+
* Klucze zagnieżdżone (kolumny tabel, np. manifest osób) adresuje się kropką:
|
|
131
|
+
* `{ key: 'people.birthDate', label: 'Data urodzenia', role: 'birthDate' }`.
|
|
132
|
+
*
|
|
133
|
+
* Nie waliduje ani nie zmienia danych — to czysta warstwa prezentacji.
|
|
134
|
+
*
|
|
135
|
+
* @experimental Świadomie NIEPEŁNE: pola uczestnika mają w rdzeniu prawdziwy schemat
|
|
136
|
+
* (`participantFields` — napędza walidację, render i formularz), a `customer` ma dziś tylko
|
|
137
|
+
* etykiety, bo formularz kontaktowy jest pisany ręcznie w projekcie. Skutek: worek `customer`
|
|
138
|
+
* ma DWIE deklaracje (etykiety tutaj, reguły w repo projektu) i może się rozjechać. Domknięcie
|
|
139
|
+
* w 0.38 — patrz ROADMAP.
|
|
140
|
+
*/
|
|
141
|
+
customerFields?: FieldDef[];
|
|
142
|
+
}
|
|
143
|
+
/** Fully-defaulted booking config, carried on `CMSModule.config`. */
|
|
144
|
+
export interface ResolvedBookingConfig {
|
|
145
|
+
currency: string;
|
|
146
|
+
catalogCollection: string | null;
|
|
147
|
+
surfacedCollections: string[];
|
|
148
|
+
depositAmount: number;
|
|
149
|
+
resolvePrice: ((tripRef: string) => number | Promise<number>) | null;
|
|
150
|
+
pricing: PricingConfig;
|
|
151
|
+
participantPricing: (() => ParticipantPricingConfig | Promise<ParticipantPricingConfig>) | null;
|
|
152
|
+
arrivalDateField: string;
|
|
153
|
+
payment: PaymentAdapter[];
|
|
154
|
+
portalUrlTemplate: string | null;
|
|
155
|
+
checkoutReturnUrl: string | null;
|
|
156
|
+
adminEmail: string | null;
|
|
157
|
+
brand: string | null;
|
|
158
|
+
depositTtlMinutes: number;
|
|
159
|
+
balanceDueDaysBefore: number | null;
|
|
160
|
+
capacityField: string | null;
|
|
161
|
+
reminders: ReminderRule[];
|
|
162
|
+
validateBooking: BookingValidator | null;
|
|
163
|
+
maintenance: {
|
|
164
|
+
autoRun: boolean;
|
|
165
|
+
intervalMinutes: number;
|
|
166
|
+
};
|
|
167
|
+
maintenanceToken: string | null;
|
|
168
|
+
portalSecret: string | null;
|
|
169
|
+
participantFields: FieldDef[] | ((catalogEntry: Record<string, unknown>) => FieldDef[]);
|
|
170
|
+
ageFlag: AgeFlagConfig | null;
|
|
171
|
+
customerFields: FieldDef[];
|
|
172
|
+
}
|
|
173
|
+
/** Resolves a raw {@link BookingConfig} into a {@link ResolvedBookingConfig}. */
|
|
174
|
+
export declare function resolveBookingConfig(config: BookingConfig): ResolvedBookingConfig;
|
|
175
|
+
/** Rozwiązuje schemat pól dla danego wpisu katalogu (array lub resolver). */
|
|
176
|
+
export declare function resolveParticipantFields(config: ResolvedBookingConfig, catalogEntry: Record<string, unknown>): FieldDef[];
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
resolvePrice: config.resolvePrice ?? null,
|
|
9
|
+
pricing: config.pricing ?? { rules: [] },
|
|
10
|
+
participantPricing: config.participantPricing ?? null,
|
|
11
|
+
arrivalDateField: config.arrivalDateField ?? 'arrivalDate',
|
|
12
|
+
payment: config.payment ?? [],
|
|
13
|
+
portalUrlTemplate: config.portalUrlTemplate ?? null,
|
|
14
|
+
checkoutReturnUrl: config.checkoutReturnUrl ?? null,
|
|
15
|
+
adminEmail: config.adminEmail ?? null,
|
|
16
|
+
brand: config.brand ?? null,
|
|
17
|
+
depositTtlMinutes: config.depositTtlMinutes ?? 60,
|
|
18
|
+
balanceDueDaysBefore: config.balanceDueDaysBefore ?? null,
|
|
19
|
+
capacityField: config.capacityField ?? null,
|
|
20
|
+
reminders: config.reminders ?? [],
|
|
21
|
+
validateBooking: config.validateBooking ?? null,
|
|
22
|
+
maintenance: {
|
|
23
|
+
autoRun: config.maintenance?.autoRun ?? true,
|
|
24
|
+
intervalMinutes: config.maintenance?.intervalMinutes ?? 15
|
|
25
|
+
},
|
|
26
|
+
maintenanceToken: config.maintenanceToken ?? null,
|
|
27
|
+
portalSecret: config.portalSecret ?? null,
|
|
28
|
+
participantFields: config.participantFields ?? [],
|
|
29
|
+
ageFlag: config.ageFlag ?? null,
|
|
30
|
+
customerFields: config.customerFields ?? []
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Rozwiązuje schemat pól dla danego wpisu katalogu (array lub resolver). */
|
|
34
|
+
export function resolveParticipantFields(config, catalogEntry) {
|
|
35
|
+
const pf = config.participantFields;
|
|
36
|
+
return typeof pf === 'function' ? pf(catalogEntry) : pf;
|
|
37
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
/**
|
|
28
|
+
* Cena bazowa (dorosłego) w groszach, zamrażana na pozycji.
|
|
29
|
+
*
|
|
30
|
+
* **Honorowana wyłącznie dla `actor: 'staff'`** (ręczna korekta w adminie). Dla klienta
|
|
31
|
+
* końcowego (`public`/`portal`) jest ignorowana — cenę ustala rdzeń z katalogu przez szew
|
|
32
|
+
* `resolvePrice`, bo inaczej każdy mógłby zamówić wyprawę za grosz. Pomiń, gdy cena ma
|
|
33
|
+
* pochodzić z katalogu.
|
|
34
|
+
*/
|
|
35
|
+
unitPrice?: number;
|
|
36
|
+
/** Who takes this trip (+ their per-trip data). Count derives from this. */
|
|
37
|
+
assignments: ItemAssignmentInput[];
|
|
38
|
+
}
|
|
39
|
+
export interface CreateBookingInput {
|
|
40
|
+
customer: Record<string, unknown>;
|
|
41
|
+
/** People on the booking (identity, shared across trips). */
|
|
42
|
+
persons: PersonInput[];
|
|
43
|
+
items: BookingItemInput[];
|
|
44
|
+
language?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Per-call pricing context (dynamic: from globalSettings + this booking's customer). */
|
|
47
|
+
export interface DraftPricingContext {
|
|
48
|
+
participantPricing: ParticipantPricingConfig;
|
|
49
|
+
/** Arrival date — anchor for age + early-bird. Null ⇒ everyone full price. */
|
|
50
|
+
anchor: Date | null;
|
|
51
|
+
/** Identity field schema, used to locate the role:birthDate key for age. */
|
|
52
|
+
identityFields: FieldDef[];
|
|
53
|
+
}
|
|
54
|
+
export interface BookingDraft {
|
|
55
|
+
totals: BookingTotals;
|
|
56
|
+
persons: PersonInput[];
|
|
57
|
+
items: Array<{
|
|
58
|
+
tripRef: string;
|
|
59
|
+
departureDate: Date | null;
|
|
60
|
+
participantsCount: number;
|
|
61
|
+
unitPriceSnapshot: number;
|
|
62
|
+
subtotalSnapshot: number | null;
|
|
63
|
+
appliedDiscounts: AppliedRule[];
|
|
64
|
+
assignments: ItemAssignmentInput[];
|
|
65
|
+
}>;
|
|
66
|
+
reminders: Array<{
|
|
67
|
+
type: string;
|
|
68
|
+
templateKey: string;
|
|
69
|
+
scheduledFor: Date;
|
|
70
|
+
}>;
|
|
71
|
+
balanceDueAt: Date | null;
|
|
72
|
+
holdExpiresAt: Date;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Pozycja po przejściu przez bramkę cenową — cena jest już ustalona (z katalogu, ze
|
|
76
|
+
* snapshotu albo od obsługi). Typ istnieje po to, by **nie dało się** zbudować rezerwacji
|
|
77
|
+
* z pozycji, której nikt nie wycenił: kompilator pilnuje, że bramka poszła pierwsza.
|
|
78
|
+
*/
|
|
79
|
+
export type PricedBookingItem = BookingItemInput & {
|
|
80
|
+
unitPrice: number;
|
|
81
|
+
};
|
|
82
|
+
/** Ładunek create z pozycjami już wycenionymi. */
|
|
83
|
+
export type PricedCreateInput = Omit<CreateBookingInput, 'items'> & {
|
|
84
|
+
items: PricedBookingItem[];
|
|
85
|
+
};
|
|
86
|
+
/** Build the per-participant pricing inputs from a create payload. */
|
|
87
|
+
export declare function pricingInputsFromCreate(input: PricedCreateInput, identityFields: FieldDef[]): {
|
|
88
|
+
participants: PricingParticipant[];
|
|
89
|
+
trips: PricedTrip[];
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Pure: turn a create payload + config + pricing context + clock into the
|
|
93
|
+
* rows/totals to persist. Prices per-participant (age bands + volume ladder +
|
|
94
|
+
* early-bird), anchored to the arrival date in `ctx.anchor`.
|
|
95
|
+
*/
|
|
96
|
+
export declare function buildBookingDraft(input: PricedCreateInput, config: ResolvedBookingConfig, ctx: DraftPricingContext, now: Date): BookingDraft;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { computeBalanceDueAt, computeReminderSchedule } from './reminders-schedule.js';
|
|
2
|
+
import { priceByParticipant } from './participant-pricing.js';
|
|
3
|
+
import { resultToTotals, grossByTripRef } from './totals.js';
|
|
4
|
+
import { NO_ADJUSTMENTS } from './adjustments.js';
|
|
5
|
+
/** Build the per-participant pricing inputs from a create payload. */
|
|
6
|
+
export function pricingInputsFromCreate(input, identityFields) {
|
|
7
|
+
const dobKey = identityFields.find((f) => f.role === 'birthDate')?.key ?? null;
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
const trips = [];
|
|
10
|
+
for (const it of input.items) {
|
|
11
|
+
if (seen.has(it.tripRef))
|
|
12
|
+
continue;
|
|
13
|
+
seen.add(it.tripRef);
|
|
14
|
+
trips.push({ ref: it.tripRef, adultPrice: it.unitPrice });
|
|
15
|
+
}
|
|
16
|
+
const tripsByPersonRef = new Map();
|
|
17
|
+
for (const it of input.items) {
|
|
18
|
+
for (const a of it.assignments) {
|
|
19
|
+
const list = tripsByPersonRef.get(a.personRef);
|
|
20
|
+
if (list) {
|
|
21
|
+
if (!list.includes(it.tripRef))
|
|
22
|
+
list.push(it.tripRef);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
tripsByPersonRef.set(a.personRef, [it.tripRef]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const participants = input.persons.map((p) => {
|
|
30
|
+
const dobRaw = dobKey ? p.identity[dobKey] : null;
|
|
31
|
+
return {
|
|
32
|
+
id: p.ref,
|
|
33
|
+
dob: typeof dobRaw === 'string' ? dobRaw : null,
|
|
34
|
+
tripRefs: tripsByPersonRef.get(p.ref) ?? []
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
return { participants, trips };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Pure: turn a create payload + config + pricing context + clock into the
|
|
41
|
+
* rows/totals to persist. Prices per-participant (age bands + volume ladder +
|
|
42
|
+
* early-bird), anchored to the arrival date in `ctx.anchor`.
|
|
43
|
+
*/
|
|
44
|
+
export function buildBookingDraft(input, config, ctx, now) {
|
|
45
|
+
const dates = input.items.map((it) => it.departureDate);
|
|
46
|
+
const { participants, trips } = pricingInputsFromCreate(input, ctx.identityFields);
|
|
47
|
+
const result = priceByParticipant(participants, trips, ctx.anchor, now, ctx.participantPricing);
|
|
48
|
+
const totals = resultToTotals(result, NO_ADJUSTMENTS, 0);
|
|
49
|
+
const subtotalByRef = grossByTripRef(result);
|
|
50
|
+
const items = input.items.map((it) => ({
|
|
51
|
+
tripRef: it.tripRef,
|
|
52
|
+
departureDate: it.departureDate,
|
|
53
|
+
participantsCount: it.assignments.length,
|
|
54
|
+
unitPriceSnapshot: it.unitPrice,
|
|
55
|
+
subtotalSnapshot: subtotalByRef.get(it.tripRef) ?? null,
|
|
56
|
+
appliedDiscounts: totals.appliedRules.filter((r) => r.targetRef === it.tripRef),
|
|
57
|
+
assignments: it.assignments
|
|
58
|
+
}));
|
|
59
|
+
return {
|
|
60
|
+
totals,
|
|
61
|
+
persons: input.persons,
|
|
62
|
+
items,
|
|
63
|
+
reminders: computeReminderSchedule(dates, config.reminders),
|
|
64
|
+
balanceDueAt: computeBalanceDueAt(dates, config.balanceDueDaysBefore),
|
|
65
|
+
holdExpiresAt: new Date(now.getTime() + config.depositTtlMinutes * 60_000)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision helpers for the booking maintenance cron. No clock/db access —
|
|
3
|
+
* the caller passes `now` and the rows. Tested in isolation; the DB wrappers in
|
|
4
|
+
* `server/maintenance.ts` just fetch rows and apply the results.
|
|
5
|
+
*/
|
|
6
|
+
import type { BookingStatus } from './state-machine.js';
|
|
7
|
+
/** Ids of reminders that are due to send (unsent and `scheduledFor <= now`). */
|
|
8
|
+
export declare function selectDueReminders(rows: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
scheduledFor: Date;
|
|
11
|
+
sentAt: Date | null;
|
|
12
|
+
}>, now: Date): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Partition active bookings into those to auto-cancel:
|
|
15
|
+
* - `abandon`: a `draft` whose deposit hold (`holdExpiresAt`) has lapsed.
|
|
16
|
+
* - `forfeit`: any non-terminal booking past `balanceDueAt` with balance left
|
|
17
|
+
* (deposit forfeited). `paid`/`completed`/`cancelled` are skipped.
|
|
18
|
+
*/
|
|
19
|
+
export declare function decideExpirations(bookings: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
status: BookingStatus;
|
|
22
|
+
holdExpiresAt: Date | null;
|
|
23
|
+
balanceDueAt: Date | null;
|
|
24
|
+
totals: {
|
|
25
|
+
balance: number;
|
|
26
|
+
};
|
|
27
|
+
}>, now: Date): {
|
|
28
|
+
abandon: string[];
|
|
29
|
+
forfeit: string[];
|
|
30
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision helpers for the booking maintenance cron. No clock/db access —
|
|
3
|
+
* the caller passes `now` and the rows. Tested in isolation; the DB wrappers in
|
|
4
|
+
* `server/maintenance.ts` just fetch rows and apply the results.
|
|
5
|
+
*/
|
|
6
|
+
/** Ids of reminders that are due to send (unsent and `scheduledFor <= now`). */
|
|
7
|
+
export function selectDueReminders(rows, now) {
|
|
8
|
+
return rows
|
|
9
|
+
.filter((r) => r.sentAt == null && r.scheduledFor.getTime() <= now.getTime())
|
|
10
|
+
.map((r) => r.id);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Partition active bookings into those to auto-cancel:
|
|
14
|
+
* - `abandon`: a `draft` whose deposit hold (`holdExpiresAt`) has lapsed.
|
|
15
|
+
* - `forfeit`: any non-terminal booking past `balanceDueAt` with balance left
|
|
16
|
+
* (deposit forfeited). `paid`/`completed`/`cancelled` are skipped.
|
|
17
|
+
*/
|
|
18
|
+
export function decideExpirations(bookings, now) {
|
|
19
|
+
const abandon = [];
|
|
20
|
+
const forfeit = [];
|
|
21
|
+
for (const b of bookings) {
|
|
22
|
+
if (b.status === 'cancelled' || b.status === 'completed' || b.status === 'paid')
|
|
23
|
+
continue;
|
|
24
|
+
if (b.status === 'draft' && b.holdExpiresAt && b.holdExpiresAt.getTime() <= now.getTime()) {
|
|
25
|
+
abandon.push(b.id);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (b.balanceDueAt && b.balanceDueAt.getTime() <= now.getTime() && b.totals.balance > 0) {
|
|
29
|
+
forfeit.push(b.id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { abandon, forfeit };
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CMSModule } from '../types/modules.js';
|
|
2
|
+
import { type BookingConfig } from './config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Defines the booking module — a long-lived, mutable reservation system with a
|
|
5
|
+
* payment ledger, layered on the CMS core as a {@link CMSModule}. The resolved
|
|
6
|
+
* config is reachable via `getCMS().getModuleConfig('booking')`.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export declare function defineBooking(config: BookingConfig): CMSModule;
|
|
11
|
+
export { type BookingConfig, type ResolvedBookingConfig, type ReminderRule, type BookingValidator, type BookingValidationContext } from './config.js';
|
|
12
|
+
export { price, daysUntil, type Condition, type Effect, type DiscountRule, type PricingConfig, type PricingLine, type AppliedRule, type Perk, type PriceResult } from './pricing.js';
|
|
13
|
+
export { priceByParticipant, bandPctFor, ladderTierFor, type AgeBand, type LadderTier, type EarlyBird, type ParticipantPricingConfig, type PricedTrip, type PricingParticipant, type ParticipantLine, type ParticipantBreakdown, type ParticipantPriceResult } from './participant-pricing.js';
|
|
14
|
+
export { buildPricingInputs, type PersonRow, type ItemRow, type ItemPersonRow, type PricingInputs } from './pricing-inputs.js';
|
|
15
|
+
export { deriveStatus, canTransition, assertTransition, BookingStateError, BOOKING_STATUSES, type BookingStatus } from './state-machine.js';
|
|
16
|
+
export { signedAmount, sumAdjustments, assertAdjustmentMagnitude, NO_ADJUSTMENTS, type AdjustmentKind, type BookingAdjustment, type AdjustmentSums } from './adjustments.js';
|
|
17
|
+
export { totalPaid, totalRefunded, totalPending, balance, isFullyPaid, canReduceGrossTo, assertGrossNotBelowPaid, ledgerSummary, LedgerError, type PaymentEntry, type PaymentKind, type PaymentStatus, type LedgerSummary } from './ledger.js';
|
|
18
|
+
export * from './participant-fields/index.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { resolveBookingConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Defines the booking module — a long-lived, mutable reservation system with a
|
|
4
|
+
* payment ledger, layered on the CMS core as a {@link CMSModule}. The resolved
|
|
5
|
+
* config is reachable via `getCMS().getModuleConfig('booking')`.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export function defineBooking(config) {
|
|
10
|
+
const resolved = resolveBookingConfig(config);
|
|
11
|
+
// Katalog + kolekcje wspierające booking (np. typy wypraw definiujące pola uczestnika).
|
|
12
|
+
// Pusta lista ⇒ `undefined`, żeby nie zmieniać semantyki „brak wciągniętych kolekcji".
|
|
13
|
+
const surfaced = [
|
|
14
|
+
...(resolved.catalogCollection ? [resolved.catalogCollection] : []),
|
|
15
|
+
...resolved.surfacedCollections
|
|
16
|
+
];
|
|
17
|
+
return {
|
|
18
|
+
id: 'booking',
|
|
19
|
+
label: { pl: 'Rezerwacje', en: 'Bookings' },
|
|
20
|
+
icon: 'calendar',
|
|
21
|
+
context: {
|
|
22
|
+
contextLabel: { pl: 'Rezerwacje', en: 'Bookings' },
|
|
23
|
+
landingUrl: '/admin/booking',
|
|
24
|
+
sections: [
|
|
25
|
+
{
|
|
26
|
+
key: 'reservations',
|
|
27
|
+
title: { pl: 'Rezerwacje', en: 'Reservations' },
|
|
28
|
+
url: '/admin/booking/reservations',
|
|
29
|
+
icon: 'clipboard-list'
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
surfacedCollections: surfaced.length > 0 ? surfaced : undefined
|
|
33
|
+
},
|
|
34
|
+
config: resolved,
|
|
35
|
+
serverHooks: {
|
|
36
|
+
// A plain string, NOT an inline import: this barrel is client-safe (re-exported
|
|
37
|
+
// via `includio-cms/booking`), and inlining `import('./server/...')` here makes
|
|
38
|
+
// client bundlers pull server-only code (node:fs/url via the email template
|
|
39
|
+
// registry) and break browser builds. `initCMS` (server-only) imports this
|
|
40
|
+
// specifier at runtime and runs its `serverInit` — see ModuleServerHooks.initModule.
|
|
41
|
+
initModule: 'includio-cms/booking/server'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export {} from './config.js';
|
|
46
|
+
export { price, daysUntil } from './pricing.js';
|
|
47
|
+
export { priceByParticipant, bandPctFor, ladderTierFor } from './participant-pricing.js';
|
|
48
|
+
export { buildPricingInputs } from './pricing-inputs.js';
|
|
49
|
+
export { deriveStatus, canTransition, assertTransition, BookingStateError, BOOKING_STATUSES } from './state-machine.js';
|
|
50
|
+
export { signedAmount, sumAdjustments, assertAdjustmentMagnitude, NO_ADJUSTMENTS } from './adjustments.js';
|
|
51
|
+
export { totalPaid, totalRefunded, totalPending, balance, isFullyPaid, canReduceGrossTo, assertGrossNotBelowPaid, ledgerSummary, LedgerError } from './ledger.js';
|
|
52
|
+
export * from './participant-fields/index.js';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Booking payment ledger — pure helpers over a list of payment entries. The
|
|
3
|
+
* ledger is the source of truth for "how much is paid"; the booking status is
|
|
4
|
+
* {@link import('./state-machine.js').deriveStatus | derived} from the balance.
|
|
5
|
+
*
|
|
6
|
+
* All amounts are in **minor units** (grosze).
|
|
7
|
+
*/
|
|
8
|
+
export type PaymentKind = 'deposit' | 'installment' | 'cash' | 'online';
|
|
9
|
+
export type PaymentStatus = 'pending' | 'paid' | 'failed' | 'refunded';
|
|
10
|
+
export interface PaymentEntry {
|
|
11
|
+
amount: number;
|
|
12
|
+
status: PaymentStatus;
|
|
13
|
+
kind?: PaymentKind;
|
|
14
|
+
}
|
|
15
|
+
/** Thrown when an edit would push the gross below what has already been paid. */
|
|
16
|
+
export declare class LedgerError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** Sum of entries currently in `paid` state. */
|
|
20
|
+
export declare function totalPaid(payments: PaymentEntry[]): number;
|
|
21
|
+
/** Sum of entries that were refunded. */
|
|
22
|
+
export declare function totalRefunded(payments: PaymentEntry[]): number;
|
|
23
|
+
/** Sum of entries still pending (links generated, not yet settled). */
|
|
24
|
+
export declare function totalPending(payments: PaymentEntry[]): number;
|
|
25
|
+
/** Outstanding balance: `gross − paid` (never below 0). */
|
|
26
|
+
export declare function balance(gross: number, payments: PaymentEntry[]): number;
|
|
27
|
+
/** Whether the booking is fully settled (`gross > 0` and balance reached 0). */
|
|
28
|
+
export declare function isFullyPaid(gross: number, payments: PaymentEntry[]): boolean;
|
|
29
|
+
/** Whether the gross may be reduced to `newGross` without going below paid. */
|
|
30
|
+
export declare function canReduceGrossTo(newGross: number, payments: PaymentEntry[]): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Asserts an edit's new gross does not drop below the already-paid total.
|
|
33
|
+
* @throws {LedgerError} when `newGross < totalPaid`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertGrossNotBelowPaid(newGross: number, payments: PaymentEntry[]): void;
|
|
36
|
+
export interface LedgerSummary {
|
|
37
|
+
gross: number;
|
|
38
|
+
paid: number;
|
|
39
|
+
pending: number;
|
|
40
|
+
refunded: number;
|
|
41
|
+
balance: number;
|
|
42
|
+
/** Amount paid beyond gross (e.g. after a downward edit), 0 when none. */
|
|
43
|
+
overpaid: number;
|
|
44
|
+
}
|
|
45
|
+
/** Full ledger snapshot for a given gross. */
|
|
46
|
+
export declare function ledgerSummary(gross: number, payments: PaymentEntry[]): LedgerSummary;
|