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,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;
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
/** Thrown when an edit would push the gross below what has already been paid. */
|
|
9
|
+
export class LedgerError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'LedgerError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Sum of entries currently in `paid` state. */
|
|
16
|
+
export function totalPaid(payments) {
|
|
17
|
+
return payments.reduce((sum, p) => (p.status === 'paid' ? sum + p.amount : sum), 0);
|
|
18
|
+
}
|
|
19
|
+
/** Sum of entries that were refunded. */
|
|
20
|
+
export function totalRefunded(payments) {
|
|
21
|
+
return payments.reduce((sum, p) => (p.status === 'refunded' ? sum + p.amount : sum), 0);
|
|
22
|
+
}
|
|
23
|
+
/** Sum of entries still pending (links generated, not yet settled). */
|
|
24
|
+
export function totalPending(payments) {
|
|
25
|
+
return payments.reduce((sum, p) => (p.status === 'pending' ? sum + p.amount : sum), 0);
|
|
26
|
+
}
|
|
27
|
+
/** Outstanding balance: `gross − paid` (never below 0). */
|
|
28
|
+
export function balance(gross, payments) {
|
|
29
|
+
return Math.max(0, gross - totalPaid(payments));
|
|
30
|
+
}
|
|
31
|
+
/** Whether the booking is fully settled (`gross > 0` and balance reached 0). */
|
|
32
|
+
export function isFullyPaid(gross, payments) {
|
|
33
|
+
return gross > 0 && totalPaid(payments) >= gross;
|
|
34
|
+
}
|
|
35
|
+
/** Whether the gross may be reduced to `newGross` without going below paid. */
|
|
36
|
+
export function canReduceGrossTo(newGross, payments) {
|
|
37
|
+
return newGross >= totalPaid(payments);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Asserts an edit's new gross does not drop below the already-paid total.
|
|
41
|
+
* @throws {LedgerError} when `newGross < totalPaid`.
|
|
42
|
+
*/
|
|
43
|
+
export function assertGrossNotBelowPaid(newGross, payments) {
|
|
44
|
+
const paid = totalPaid(payments);
|
|
45
|
+
if (newGross < paid) {
|
|
46
|
+
throw new LedgerError(`Cannot set gross to ${newGross}: already paid ${paid} (would owe a refund of ${paid - newGross}).`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Full ledger snapshot for a given gross. */
|
|
50
|
+
export function ledgerSummary(gross, payments) {
|
|
51
|
+
const paid = totalPaid(payments);
|
|
52
|
+
return {
|
|
53
|
+
gross,
|
|
54
|
+
paid,
|
|
55
|
+
pending: totalPending(payments),
|
|
56
|
+
refunded: totalRefunded(payments),
|
|
57
|
+
balance: Math.max(0, gross - paid),
|
|
58
|
+
overpaid: Math.max(0, paid - gross)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Crockford base32 alphabet — unambiguous (no I, L, O, U).
|
|
2
|
+
const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
3
|
+
/** Random `BK-XXXXX` booking number (~25 bits). Caller retries on UNIQUE collision. */
|
|
4
|
+
export function generateBookingNumber() {
|
|
5
|
+
const bytes = new Uint8Array(5);
|
|
6
|
+
crypto.getRandomValues(bytes);
|
|
7
|
+
let out = '';
|
|
8
|
+
for (let i = 0; i < 5; i++)
|
|
9
|
+
out += ALPHABET[bytes[i] % 32];
|
|
10
|
+
return `BK-${out}`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FieldDef } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a given audience may fill/edit a field. Default `whoFills` is `'both'`.
|
|
4
|
+
*
|
|
5
|
+
* Staff (admin) is **privileged** — it may edit every field regardless of
|
|
6
|
+
* `whoFills` (operators must always be able to correct/complete any datum).
|
|
7
|
+
* `whoFills` therefore only gates the **customer/portal** side: a `'staff'`
|
|
8
|
+
* field is simply hidden from the portal, not locked away from the operator.
|
|
9
|
+
*/
|
|
10
|
+
export declare function canFill(field: FieldDef, audience: 'customer' | 'staff'): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a given audience may fill/edit a field. Default `whoFills` is `'both'`.
|
|
3
|
+
*
|
|
4
|
+
* Staff (admin) is **privileged** — it may edit every field regardless of
|
|
5
|
+
* `whoFills` (operators must always be able to correct/complete any datum).
|
|
6
|
+
* `whoFills` therefore only gates the **customer/portal** side: a `'staff'`
|
|
7
|
+
* field is simply hidden from the portal, not locked away from the operator.
|
|
8
|
+
*/
|
|
9
|
+
export function canFill(field, audience) {
|
|
10
|
+
if (audience === 'staff')
|
|
11
|
+
return true;
|
|
12
|
+
const who = field.whoFills ?? 'both';
|
|
13
|
+
return who === 'both' || who === 'customer';
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FieldDef, AgeFlagConfig } from './types.js';
|
|
2
|
+
/** Pełne lata między `dob` a `anchor`. `null` gdy data niepoprawna. */
|
|
3
|
+
export declare function ageAt(dob: string, anchor: Date): number | null;
|
|
4
|
+
/**
|
|
5
|
+
* Generyczna flaga wieku — bez magicznego klucza. Bierze pole z rolą `birthDate`,
|
|
6
|
+
* liczy wiek na `anchorDate` (data wylotu) i flaguje gdy < `config.below`.
|
|
7
|
+
* Brak configu / kotwicy / pola-roli / wartości ⇒ `{ flagged: false, age: null }`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function deriveAgeFlag(data: Record<string, unknown>, fields: FieldDef[], config: AgeFlagConfig | null, anchorDate: Date | null): {
|
|
10
|
+
flagged: boolean;
|
|
11
|
+
age: number | null;
|
|
12
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Pełne lata między `dob` a `anchor`. `null` gdy data niepoprawna. */
|
|
2
|
+
export function ageAt(dob, anchor) {
|
|
3
|
+
if (typeof dob !== 'string' || Number.isNaN(Date.parse(dob)))
|
|
4
|
+
return null;
|
|
5
|
+
const d = new Date(dob);
|
|
6
|
+
let age = anchor.getUTCFullYear() - d.getUTCFullYear();
|
|
7
|
+
const m = anchor.getUTCMonth() - d.getUTCMonth();
|
|
8
|
+
if (m < 0 || (m === 0 && anchor.getUTCDate() < d.getUTCDate()))
|
|
9
|
+
age--;
|
|
10
|
+
return age;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generyczna flaga wieku — bez magicznego klucza. Bierze pole z rolą `birthDate`,
|
|
14
|
+
* liczy wiek na `anchorDate` (data wylotu) i flaguje gdy < `config.below`.
|
|
15
|
+
* Brak configu / kotwicy / pola-roli / wartości ⇒ `{ flagged: false, age: null }`.
|
|
16
|
+
*/
|
|
17
|
+
export function deriveAgeFlag(data, fields, config, anchorDate) {
|
|
18
|
+
if (!config || !anchorDate)
|
|
19
|
+
return { flagged: false, age: null };
|
|
20
|
+
const dobField = fields.find((f) => f.role === 'birthDate');
|
|
21
|
+
if (!dobField)
|
|
22
|
+
return { flagged: false, age: null };
|
|
23
|
+
const raw = data[dobField.key];
|
|
24
|
+
if (typeof raw !== 'string')
|
|
25
|
+
return { flagged: false, age: null };
|
|
26
|
+
const age = ageAt(raw, anchorDate);
|
|
27
|
+
return { flagged: age !== null && age < config.below, age };
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function isEmpty(v) {
|
|
2
|
+
return v === undefined || v === null || v === '';
|
|
3
|
+
}
|
|
4
|
+
export function completeness(data, fields) {
|
|
5
|
+
const required = fields.filter((f) => f.required);
|
|
6
|
+
const missing = required.filter((f) => isEmpty(data[f.key])).map((f) => f.label);
|
|
7
|
+
return {
|
|
8
|
+
filled: required.length - missing.length,
|
|
9
|
+
total: required.length,
|
|
10
|
+
missing,
|
|
11
|
+
complete: missing.length === 0
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function groupBySection(fields) {
|
|
2
|
+
const groups = [];
|
|
3
|
+
const index = new Map();
|
|
4
|
+
for (const f of fields) {
|
|
5
|
+
const key = f.section ?? null;
|
|
6
|
+
let g = index.get(key);
|
|
7
|
+
if (!g) {
|
|
8
|
+
g = { section: key, fields: [] };
|
|
9
|
+
index.set(key, g);
|
|
10
|
+
groups.push(g);
|
|
11
|
+
}
|
|
12
|
+
g.fields.push(f);
|
|
13
|
+
}
|
|
14
|
+
return groups;
|
|
15
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Paleta typów pól v1. Rozszerzalna przez dopisanie typu + renderera/walidatora. */
|
|
2
|
+
export declare const FIELD_TYPES: readonly ["text", "number", "boolean", "select", "date"];
|
|
3
|
+
export type FieldType = (typeof FIELD_TYPES)[number];
|
|
4
|
+
export declare function isFieldType(v: unknown): v is FieldType;
|
|
5
|
+
/** Kto wypełnia pole. Default 'both'. */
|
|
6
|
+
export type WhoFills = 'customer' | 'staff' | 'both';
|
|
7
|
+
/**
|
|
8
|
+
* Generyczna „rola" pola — opcjonalna wskazówka semantyczna, którą UI/logika
|
|
9
|
+
* mogą wykorzystać bez magicznych kluczy:
|
|
10
|
+
* - `name` — wartość użyta jako tytuł karty uczestnika,
|
|
11
|
+
* - `birthDate` — pole daty będące podstawą flagi wieku (patrz {@link AgeFlagConfig}).
|
|
12
|
+
*/
|
|
13
|
+
export type FieldRole = 'name' | 'birthDate';
|
|
14
|
+
/** Serializowalna definicja pojedynczego pola uczestnika. */
|
|
15
|
+
export interface FieldDef {
|
|
16
|
+
/** Stabilny klucz w `participant.data`. */
|
|
17
|
+
key: string;
|
|
18
|
+
label: string;
|
|
19
|
+
type: FieldType;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
/** Dla `select`. */
|
|
22
|
+
options?: string[];
|
|
23
|
+
/** Grupowanie w UI (np. "Podstawowe", "Ankieta"). */
|
|
24
|
+
section?: string;
|
|
25
|
+
/** Kto wypełnia (default 'both'). */
|
|
26
|
+
whoFills?: WhoFills;
|
|
27
|
+
/** Opcjonalna rola semantyczna (tytuł karty / data do flagi wieku). */
|
|
28
|
+
role?: FieldRole;
|
|
29
|
+
helpText?: string;
|
|
30
|
+
/** v1 stałe 'participant'; szew pod 'reservation' później. */
|
|
31
|
+
scope?: 'participant';
|
|
32
|
+
/** Dla `number`. */
|
|
33
|
+
min?: number;
|
|
34
|
+
max?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generyczna flaga wieku: oznacza uczestnika, którego wiek (liczony z pola roli
|
|
38
|
+
* `birthDate` na datę-kotwicę) jest **poniżej** progu `below`. Domeno-agnostyczna —
|
|
39
|
+
* klient nadaje znaczenie przez `label` ("Dziecko", "Junior", …). Brak configu =
|
|
40
|
+
* brak flagi. Próg wieku liczony na datę wylotu pozycji.
|
|
41
|
+
*/
|
|
42
|
+
export interface AgeFlagConfig {
|
|
43
|
+
/** Flaguj, gdy wiek < `below`. */
|
|
44
|
+
below: number;
|
|
45
|
+
/** Etykieta badge'a (domyślnie „Dziecko"/"Child"). */
|
|
46
|
+
label?: {
|
|
47
|
+
pl: string;
|
|
48
|
+
en: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FieldDef } from './types.js';
|
|
2
|
+
export interface FieldError {
|
|
3
|
+
key: string;
|
|
4
|
+
code: 'required' | 'type' | 'min' | 'max' | 'option';
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function validateAgainstSchema(data: Record<string, unknown>, fields: FieldDef[]): FieldError[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function isEmpty(v) {
|
|
2
|
+
return v === undefined || v === null || v === '';
|
|
3
|
+
}
|
|
4
|
+
export function validateAgainstSchema(data, fields) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
for (const f of fields) {
|
|
7
|
+
const v = data[f.key];
|
|
8
|
+
if (isEmpty(v)) {
|
|
9
|
+
if (f.required)
|
|
10
|
+
errors.push({ key: f.key, code: 'required', message: `${f.label} jest wymagane` });
|
|
11
|
+
continue; // puste opcjonalne = OK, nie typujemy
|
|
12
|
+
}
|
|
13
|
+
switch (f.type) {
|
|
14
|
+
case 'text':
|
|
15
|
+
if (typeof v !== 'string')
|
|
16
|
+
errors.push({ key: f.key, code: 'type', message: `${f.label}: oczekiwano tekstu` });
|
|
17
|
+
break;
|
|
18
|
+
case 'boolean':
|
|
19
|
+
if (typeof v !== 'boolean')
|
|
20
|
+
errors.push({ key: f.key, code: 'type', message: `${f.label}: oczekiwano tak/nie` });
|
|
21
|
+
break;
|
|
22
|
+
case 'date':
|
|
23
|
+
if (typeof v !== 'string' || Number.isNaN(Date.parse(v)))
|
|
24
|
+
errors.push({ key: f.key, code: 'type', message: `${f.label}: niepoprawna data` });
|
|
25
|
+
break;
|
|
26
|
+
case 'select':
|
|
27
|
+
if (!(f.options ?? []).includes(String(v)))
|
|
28
|
+
errors.push({ key: f.key, code: 'option', message: `${f.label}: niedozwolona wartość` });
|
|
29
|
+
break;
|
|
30
|
+
case 'number': {
|
|
31
|
+
if (typeof v !== 'number' || Number.isNaN(v)) {
|
|
32
|
+
errors.push({ key: f.key, code: 'type', message: `${f.label}: oczekiwano liczby` });
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (f.min !== undefined && v < f.min)
|
|
36
|
+
errors.push({ key: f.key, code: 'min', message: `${f.label}: min ${f.min}` });
|
|
37
|
+
if (f.max !== undefined && v > f.max)
|
|
38
|
+
errors.push({ key: f.key, code: 'max', message: `${f.label}: max ${f.max}` });
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return errors;
|
|
44
|
+
}
|