chore: seminarhof customer project
This commit is contained in:
50
db/annotations.ts
Normal file
50
db/annotations.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { DbClassificationMap } from '../packages/functions/.pikku/db/classification-map.gen.d.ts'
|
||||
|
||||
export const classifications = {
|
||||
"account": {},
|
||||
"audit": {},
|
||||
"bathroom": {},
|
||||
"booking": {
|
||||
"booking_id": { security: 'public' },
|
||||
"event_name": { security: 'public' },
|
||||
"start_date": { security: 'public' },
|
||||
"end_date": { security: 'public' },
|
||||
"status": { security: 'public' },
|
||||
"half_house": { security: 'public' },
|
||||
"online_ad": { security: 'public' },
|
||||
"cover_image_url": { security: 'public' },
|
||||
"description": { security: 'public' },
|
||||
"organiser_website": { security: 'public' },
|
||||
"expected_persons": { security: 'public' },
|
||||
"event_outline": { security: 'public' },
|
||||
},
|
||||
"booking_extra": {
|
||||
"booking_id": { security: 'public' },
|
||||
},
|
||||
"booking_session": {},
|
||||
"client": {
|
||||
"name": { security: 'public' },
|
||||
"website": { security: 'public' },
|
||||
},
|
||||
"client_member": {},
|
||||
"enquiry": {
|
||||
"half_house": { security: 'public' },
|
||||
},
|
||||
"enquiry_date_option": {},
|
||||
"invoice": {
|
||||
"booking_id": { security: 'public' },
|
||||
},
|
||||
"participant": {
|
||||
"booking_id": { security: 'public' },
|
||||
},
|
||||
"participant_allergy": {},
|
||||
"room": {},
|
||||
"room_assignment": {
|
||||
"booking_id": { security: 'public' },
|
||||
},
|
||||
"seminar_room": {},
|
||||
"session": {},
|
||||
"user": {},
|
||||
"venue": {},
|
||||
"verification": {},
|
||||
} satisfies DbClassificationMap
|
||||
635
db/sqlite-seed.sql
Normal file
635
db/sqlite-seed.sql
Normal file
@@ -0,0 +1,635 @@
|
||||
-- Seed data for Seminarhof Drawehn dev environment.
|
||||
-- Idempotent: existing rows with matching primary keys are left untouched.
|
||||
|
||||
-- ─── Venue ───────────────────────────────────────────────────────────
|
||||
INSERT OR IGNORE INTO venue (venue_id, name, slug) VALUES
|
||||
('v_drawehn', 'Seminarhof Drawehn', 'drawehn');
|
||||
|
||||
-- ─── Bathrooms ───────────────────────────────────────────────────────
|
||||
INSERT OR IGNORE INTO bathroom (bathroom_id, venue_id, label) VALUES
|
||||
('bath_1', 'v_drawehn', 'Bad Zi 1'),
|
||||
('bath_2', 'v_drawehn', 'Bad Zi 2'),
|
||||
('bath_3', 'v_drawehn', 'Bad Zi 3'),
|
||||
('bath_4', 'v_drawehn', 'Bad Zi 4'),
|
||||
('bath_5', 'v_drawehn', 'Bad Zi 5'),
|
||||
('bath_6', 'v_drawehn', 'Bad Zi 6'),
|
||||
('bath_7', 'v_drawehn', 'Bad Zi 7'),
|
||||
('bath_8_9','v_drawehn', 'Bad Zi 8+9 (shared)'),
|
||||
('bath_10', 'v_drawehn', 'Bad Zi 10'),
|
||||
('bath_11', 'v_drawehn', 'Bad Zi 11'),
|
||||
('bath_12', 'v_drawehn', 'Bad Zi 12'),
|
||||
('bath_13', 'v_drawehn', 'Bad Zi 13'),
|
||||
('bath_14', 'v_drawehn', 'Bad Zi 14'),
|
||||
('bath_15', 'v_drawehn', 'Bad Zi 15'),
|
||||
('bath_16', 'v_drawehn', 'Bad Zi 16'),
|
||||
('bath_17', 'v_drawehn', 'Bad Zi 17'),
|
||||
('bath_18', 'v_drawehn', 'Bad Zi 18'),
|
||||
('bath_19_20','v_drawehn','Bad Zi 19+20 (shared)'),
|
||||
('bath_21', 'v_drawehn', 'Bad Zi 21'),
|
||||
('bath_22', 'v_drawehn', 'Bad Zi 22'),
|
||||
('bath_23', 'v_drawehn', 'Bad Zi 23');
|
||||
|
||||
-- ─── Rooms ───────────────────────────────────────────────────────────
|
||||
INSERT OR IGNORE INTO room
|
||||
(room_id, venue_id, number, beds, room_type, building, floor, bathroom_id,
|
||||
wheelchair, ground_floor, quiet, shared_bath, dogs_allowed, double_bed_140,
|
||||
surcharge_eur_cents)
|
||||
VALUES
|
||||
('r_1', 'v_drawehn', 1, 1, 'EZ_only', 'gaestehaus', 'eg', 'bath_1', 1, 1, 0, 0, 0, 0, 0),
|
||||
('r_2', 'v_drawehn', 2, 2, 'DZ', 'gaestehaus', 'eg', 'bath_2', 1, 1, 0, 0, 0, 0, 0),
|
||||
('r_3', 'v_drawehn', 3, 2, 'DZ', 'gaestehaus', 'eg', 'bath_3', 0, 1, 0, 0, 0, 0, 0),
|
||||
('r_4', 'v_drawehn', 4, 2, 'DZ', 'gaestehaus', 'eg', 'bath_4', 0, 1, 0, 0, 0, 0, 0),
|
||||
('r_5', 'v_drawehn', 5, 3, 'MBZ', 'gaestehaus', 'eg', 'bath_5', 0, 1, 0, 0, 0, 0, 0),
|
||||
('r_6', 'v_drawehn', 6, 1, 'EZ_only', 'gaestehaus', 'eg', 'bath_6', 0, 1, 0, 0, 0, 0, 0),
|
||||
('r_7', 'v_drawehn', 7, 3, 'MBZ', 'gaestehaus', 'eg', 'bath_7', 0, 1, 1, 0, 0, 0, 0),
|
||||
('r_8', 'v_drawehn', 8, 2, 'DZ', 'gaestehaus', 'eg', 'bath_8_9',0, 1, 1, 1, 0, 0, 0),
|
||||
('r_9', 'v_drawehn', 9, 2, 'DZ', 'gaestehaus', 'eg', 'bath_8_9',0, 1, 1, 1, 0, 0, 0),
|
||||
('r_10', 'v_drawehn', 10, 2, 'DZ', 'gaestehaus', 'eg', 'bath_10', 0, 1, 1, 0, 1, 0, 2000),
|
||||
('r_11', 'v_drawehn', 11, 2, 'DZ', 'gaestehaus', 'eg', 'bath_11', 0, 1, 1, 0, 0, 0, 0),
|
||||
('r_12', 'v_drawehn', 12, 2, 'DZ', 'gaestehaus', 'eg', 'bath_12', 0, 1, 1, 0, 0, 0, 0),
|
||||
('r_13', 'v_drawehn', 13, 1, 'EZ_only', 'gaestehaus', 'og', 'bath_13', 0, 0, 0, 0, 0, 0, 0),
|
||||
('r_14', 'v_drawehn', 14, 1, 'EZ_only', 'gaestehaus', 'og', 'bath_14', 0, 0, 0, 0, 0, 1, 0),
|
||||
('r_15', 'v_drawehn', 15, 2, 'DZ', 'gaestehaus', 'og', 'bath_15', 0, 0, 0, 0, 0, 0, 0),
|
||||
('r_16', 'v_drawehn', 16, 2, 'DZ', 'gaestehaus', 'og', 'bath_16', 0, 0, 0, 0, 0, 0, 0),
|
||||
('r_17', 'v_drawehn', 17, 2, 'DZ', 'gaestehaus', 'og', 'bath_17', 0, 0, 0, 0, 0, 0, 0),
|
||||
('r_18', 'v_drawehn', 18, 2, 'DZ', 'gaestehaus', 'og', 'bath_18', 0, 0, 0, 0, 0, 0, 0),
|
||||
('r_19', 'v_drawehn', 19, 2, 'DZ', 'haupthaus', 'eg', 'bath_19_20',0, 1, 0, 1, 0, 0, 0),
|
||||
('r_20', 'v_drawehn', 20, 3, 'DZ', 'haupthaus', 'eg', 'bath_19_20',0, 1, 0, 1, 0, 1, 0),
|
||||
('r_21', 'v_drawehn', 21, 2, 'DZ', 'haupthaus', 'og', 'bath_21', 0, 0, 0, 0, 0, 0, 0),
|
||||
('r_22', 'v_drawehn', 22, 1, 'EZ_only', 'haupthaus', 'og', 'bath_22', 0, 0, 0, 0, 0, 1, 0),
|
||||
('r_23', 'v_drawehn', 23, 2, 'DZ', 'haupthaus', 'og', 'bath_23', 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
-- ─── Seminar rooms ───────────────────────────────────────────────────
|
||||
INSERT OR IGNORE INTO seminar_room (seminar_room_id, venue_id, name) VALUES
|
||||
('sr_mandala', 'v_drawehn', 'Mandala'),
|
||||
('sr_yogaraum', 'v_drawehn', 'Yogaraum');
|
||||
|
||||
-- ─── Demo client ─────────────────────────────────────────────────────
|
||||
-- Users are created via better-auth sign-up (not seeded here).
|
||||
-- Demo credentials (sign up via /api/auth/sign-up/email):
|
||||
-- sarah@seminarhof.example → owner1234
|
||||
-- christina@seminarhof.example → admin1234
|
||||
-- demo@yoga-retreat.example → demo1234
|
||||
INSERT OR IGNORE INTO client
|
||||
(client_id, venue_id, name, is_stammgruppe, contact_email, website)
|
||||
VALUES
|
||||
('client_yoga', 'v_drawehn', 'Yoga Retreat e.V.', 1, 'demo@yoga-retreat.example', 'https://yoga-retreat.example');
|
||||
|
||||
-- ─── Demo bookings ──────────────────────────────────────────────────
|
||||
INSERT OR IGNORE INTO booking
|
||||
(booking_id, venue_id, client_id, event_name, start_date, end_date,
|
||||
status, half_house, online_ad, expected_persons, description,
|
||||
meal_time_breakfast, meal_time_lunch, meal_time_dinner, payment_method,
|
||||
agb_year, agb_accepted_at, privacy_accepted_at)
|
||||
VALUES
|
||||
('b_yoga_2026_summer',
|
||||
'v_drawehn', 'client_yoga',
|
||||
'Summer Yoga Retreat 2026',
|
||||
'2026-07-12', '2026-07-19',
|
||||
'confirmed', 0, 1, 28,
|
||||
'A week of yoga, breath work, and quiet meals in Lower Saxony.',
|
||||
'08:00', '13:00', '19:00', 'transfer',
|
||||
2026, '2026-02-14T10:30:00Z', '2026-02-14T10:30:00Z'),
|
||||
('b_yoga_2026_winter',
|
||||
'v_drawehn', 'client_yoga',
|
||||
'Winter Stille 2026',
|
||||
'2026-12-27', '2027-01-02',
|
||||
'confirmed', 0, 0, 18,
|
||||
'Year-end silent retreat.',
|
||||
'08:30', '13:00', '18:30', 'transfer',
|
||||
2026, '2026-04-01T09:00:00Z', '2026-04-01T09:00:00Z'),
|
||||
('b_lc_enquiry',
|
||||
'v_drawehn', 'client_yoga',
|
||||
'Lifecycle Test Retreat',
|
||||
'2026-12-20', '2026-12-27',
|
||||
'enquiry', 0, 0, 10,
|
||||
'Lifecycle e2e test booking.',
|
||||
'08:00', '13:00', '19:00', 'transfer',
|
||||
NULL, NULL, NULL),
|
||||
('b_lc_reserved',
|
||||
'v_drawehn', 'client_yoga',
|
||||
'Lifecycle Test Retreat (Reserved)',
|
||||
'2026-12-20', '2026-12-27',
|
||||
'reserved', 0, 0, 10,
|
||||
'Reserved - contract not yet sent.',
|
||||
'08:00', '13:00', '19:00', 'transfer',
|
||||
NULL, NULL, NULL),
|
||||
('b_lc_contract_sent',
|
||||
'v_drawehn', 'client_yoga',
|
||||
'Lifecycle Test Retreat (Contract Sent)',
|
||||
'2026-12-20', '2026-12-27',
|
||||
'reserved', 0, 0, 10,
|
||||
'Reserved - contract sent 8 days ago.',
|
||||
'08:00', '13:00', '19:00', 'transfer',
|
||||
NULL, NULL, NULL),
|
||||
('b_lc_confirmed',
|
||||
'v_drawehn', 'client_yoga',
|
||||
'Lifecycle Test Retreat (Confirmed)',
|
||||
'2026-05-26', '2026-06-02',
|
||||
'confirmed', 0, 0, 10,
|
||||
'Confirmed - ended yesterday, ready for auto-complete.',
|
||||
'08:00', '13:00', '19:00', 'transfer',
|
||||
NULL, NULL, NULL);
|
||||
|
||||
-- ─── Sample participants for the summer retreat ─────────────────────
|
||||
INSERT OR IGNORE INTO participant (participant_id, booking_id, name, kind, dietary_tag) VALUES
|
||||
('p_s_01', 'b_yoga_2026_summer', 'Anja Weber', 'overnight', 'vegan'),
|
||||
('p_s_02', 'b_yoga_2026_summer', 'Bernd Krause', 'overnight', 'vegetarian'),
|
||||
('p_s_03', 'b_yoga_2026_summer', 'Clara Hoffmann', 'overnight', 'vegan'),
|
||||
('p_s_04', 'b_yoga_2026_summer', 'Dieter Lange', 'overnight', 'omnivore'),
|
||||
('p_s_05', 'b_yoga_2026_summer', 'Eva Richter', 'overnight', 'gluten_free'),
|
||||
('p_s_06', 'b_yoga_2026_summer', 'Frank Müller', 'overnight', NULL),
|
||||
('p_s_07', 'b_yoga_2026_summer', 'Greta Schulz', 'overnight', 'vegan'),
|
||||
('p_s_08', 'b_yoga_2026_summer', 'Hans Becker', 'day_guest', 'vegetarian');
|
||||
|
||||
INSERT OR IGNORE INTO participant_allergy (participant_id, allergen, severity) VALUES
|
||||
('p_s_01', 'nuts', 'standard'),
|
||||
('p_s_05', 'gluten_strict', 'separate_prep');
|
||||
|
||||
-- ─── Sample invoices ────────────────────────────────────────────────
|
||||
INSERT OR IGNORE INTO invoice
|
||||
(invoice_id, booking_id, kind, invoice_number, issued_on, due_on, paid_on, amount_cents, status)
|
||||
VALUES
|
||||
('inv_s_dep', 'b_yoga_2026_summer', 'deposit', '2026-001', '2026-02-15', '2026-03-01', '2026-02-22', 30000, 'paid'),
|
||||
('inv_w_dep', 'b_yoga_2026_winter', 'deposit', '2026-002', '2026-04-02', '2026-04-16', '2026-04-10', 30000, 'paid'),
|
||||
('inv_lc_dep', 'b_lc_contract_sent', 'deposit', 'LC-DEP-001', '2026-05-26', '2026-06-09', NULL, 30000, 'outstanding');
|
||||
|
||||
-- ─── Additional clients ────────────────────────────────────────────
|
||||
INSERT OR IGNORE INTO client
|
||||
(client_id, venue_id, name, is_stammgruppe, contact_email, contact_phone, billing_address, website)
|
||||
VALUES
|
||||
('client_breath', 'v_drawehn', 'Breath & Body Collective', 1, 'hi@breath-body.example', '+49 30 22221111', 'Hauptstr. 22, 10115 Berlin', 'https://breath-body.example'),
|
||||
('client_silent', 'v_drawehn', 'Silent Path Sangha', 0, 'team@silent-path.example','+49 40 33334444', 'Mönckebergstr. 7, 20095 Hamburg', 'https://silent-path.example'),
|
||||
('client_dance', 'v_drawehn', '5Rhythms Nord', 0, 'kontakt@5r-nord.example', '+49 511 5556677', 'Lister Meile 14, 30161 Hannover','https://5r-nord.example'),
|
||||
('client_writers', 'v_drawehn', 'Schreibwerkstatt Lüneburger Heide', 0, 'mail@schreiben-heide.example', '+49 5841 991122', 'Lange Str. 4, 29410 Salzwedel', NULL),
|
||||
('client_qigong', 'v_drawehn', 'Qigong Verein Hamburg e.V.', 1, 'verein@qigong-hh.example','+49 40 88991122', 'Eppendorfer Weg 12, 20259 Hamburg', NULL),
|
||||
('client_workation','v_drawehn', 'Remote First GmbH', 0, 'ops@remotefirst.example', '+49 30 99887766', 'Linienstr. 3, 10119 Berlin', 'https://remotefirst.example'),
|
||||
('client_choir', 'v_drawehn', 'Heide Vocal Ensemble', 0, 'info@heide-vocal.example',NULL, NULL, NULL);
|
||||
|
||||
-- ─── Additional users (clients linked to orgs) ─────────────────────
|
||||
-- Inserted into user table (better-auth schema). Passwords must be set via /api/auth/sign-up/email.
|
||||
INSERT OR IGNORE INTO "user" (id, email, name, locale, role, email_verified, created_at, updated_at) VALUES
|
||||
('u_breath', 'lea@breath-body.example', 'Lea Breath', 'de', 'client', 0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('u_silent', 'tom@silent-path.example', 'Tom Silent', 'en', 'client', 0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('u_dance', 'jana@5r-nord.example', 'Jana Dance', 'de', 'client', 0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('u_writers', 'paula@schreiben-heide.example', 'Paula Schreiber','de', 'client', 0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('u_qigong', 'kai@qigong-hh.example', 'Kai Wu', 'de', 'client', 0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('u_workation','noah@remotefirst.example', 'Noah Remote', 'en', 'client', 0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('u_choir', 'sofia@heide-vocal.example', 'Sofia Vocal', 'de', 'client', 0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z');
|
||||
|
||||
INSERT OR IGNORE INTO client_member (client_id, user_id, role) VALUES
|
||||
('client_breath', 'u_breath', 'organiser'),
|
||||
('client_silent', 'u_silent', 'organiser'),
|
||||
('client_dance', 'u_dance', 'organiser'),
|
||||
('client_writers', 'u_writers', 'organiser'),
|
||||
('client_qigong', 'u_qigong', 'organiser'),
|
||||
('client_workation','u_workation','organiser'),
|
||||
('client_choir', 'u_choir', 'organiser');
|
||||
|
||||
-- ─── Additional bookings spanning the year ─────────────────────────
|
||||
INSERT OR IGNORE INTO booking
|
||||
(booking_id, venue_id, client_id, event_name, start_date, end_date,
|
||||
status, half_house, online_ad, expected_persons, description,
|
||||
meal_time_breakfast, meal_time_lunch, meal_time_dinner, payment_method,
|
||||
agb_year, agb_accepted_at, privacy_accepted_at, organiser_website, cover_image_url, notes)
|
||||
VALUES
|
||||
-- past, ended
|
||||
('b_breath_spring',
|
||||
'v_drawehn', 'client_breath',
|
||||
'Spring Breath Intensive',
|
||||
'2026-03-15', '2026-03-22',
|
||||
'ended', 0, 1, 24,
|
||||
'Pranayama and somatic breath work in the early bloom of spring.',
|
||||
'08:00','13:00','19:00','transfer',
|
||||
2026, '2025-11-12T09:00:00Z','2025-11-12T09:00:00Z',
|
||||
'https://breath-body.example', NULL, NULL),
|
||||
|
||||
('b_qigong_easter',
|
||||
'v_drawehn', 'client_qigong',
|
||||
'Qigong Osterklausur',
|
||||
'2026-04-03','2026-04-09',
|
||||
'ended', 1, 0, 14,
|
||||
'Half-house qigong retreat over the Easter break.',
|
||||
'07:30','12:30','18:30','transfer',
|
||||
2026, '2025-12-01T10:00:00Z','2025-12-01T10:00:00Z',
|
||||
NULL, NULL, 'Stammgruppe — repeat client'),
|
||||
|
||||
-- in-flight, current
|
||||
('b_writers_may',
|
||||
'v_drawehn', 'client_writers',
|
||||
'Schreibwoche Mai',
|
||||
'2026-05-08','2026-05-14',
|
||||
'confirmed', 0, 1, 16,
|
||||
'A writing week with morning prompts and afternoon silence.',
|
||||
'08:30','13:00','19:00','transfer',
|
||||
2026, '2026-01-20T11:00:00Z','2026-01-20T11:00:00Z',
|
||||
NULL, NULL, NULL),
|
||||
|
||||
-- upcoming, deposit paid
|
||||
('b_dance_june',
|
||||
'v_drawehn', 'client_dance',
|
||||
'5Rhythms Solstice',
|
||||
'2026-06-19','2026-06-22',
|
||||
'confirmed', 1, 1, 22,
|
||||
'Long-weekend movement intensive at the start of summer.',
|
||||
'08:30','13:30','19:30','transfer',
|
||||
2026, '2026-02-02T14:00:00Z','2026-02-02T14:00:00Z',
|
||||
'https://5r-nord.example', NULL, NULL),
|
||||
|
||||
-- shares the solstice weekend with 5Rhythms (both half-house) → the
|
||||
-- availability calendar renders Jun 20–22 as a diagonal split.
|
||||
('b_writers_solstice',
|
||||
'v_drawehn', 'client_writers',
|
||||
'Solstice Schreibzirkel',
|
||||
'2026-06-20','2026-06-22',
|
||||
'confirmed', 1, 1, 10,
|
||||
'A small writing circle taking the other half of the house over the solstice.',
|
||||
'08:30','13:00','19:00','transfer',
|
||||
2026, '2026-02-10T10:00:00Z','2026-02-10T10:00:00Z',
|
||||
NULL, NULL, 'Half-house — shares the weekend with 5Rhythms Solstice'),
|
||||
|
||||
-- upcoming, reserved
|
||||
('b_workation_aug',
|
||||
'v_drawehn', 'client_workation',
|
||||
'Remote First Workation',
|
||||
'2026-08-10','2026-08-17',
|
||||
'reserved', 0, 0, 12,
|
||||
'Engineering team offsite — co-working with daily morning movement.',
|
||||
'08:00','13:00','19:00','transfer',
|
||||
2026, '2026-03-12T16:00:00Z','2026-03-12T16:00:00Z',
|
||||
'https://remotefirst.example', NULL, 'Needs strong wifi and 2nd seminar room'),
|
||||
|
||||
('b_silent_sept',
|
||||
'v_drawehn', 'client_silent',
|
||||
'Silent Autumn Sangha',
|
||||
'2026-09-21','2026-09-28',
|
||||
'confirmed', 0, 1, 26,
|
||||
'Seven days of noble silence, sitting and walking meditation.',
|
||||
'07:30','12:30','18:30','transfer',
|
||||
2026, '2026-04-04T09:30:00Z','2026-04-04T09:30:00Z',
|
||||
'https://silent-path.example', NULL, NULL),
|
||||
|
||||
('b_choir_october',
|
||||
'v_drawehn', 'client_choir',
|
||||
'Vocal Ensemble Probenwoche',
|
||||
'2026-10-12','2026-10-18',
|
||||
'confirmed', 0, 1, 20,
|
||||
'Choir rehearsal week preparing the winter programme.',
|
||||
'08:30','13:00','19:30','transfer',
|
||||
2026, '2026-04-15T10:00:00Z','2026-04-15T10:00:00Z',
|
||||
NULL, NULL, NULL),
|
||||
|
||||
-- new enquiry, awaiting admin approve/decline
|
||||
('b_qigong_nov',
|
||||
'v_drawehn', 'client_qigong',
|
||||
'Qigong Spätherbst',
|
||||
'2026-11-08','2026-11-13',
|
||||
'enquiry', 1, 0, 12,
|
||||
NULL,
|
||||
'08:00','13:00','18:30','transfer',
|
||||
2026, NULL, NULL,
|
||||
NULL, NULL, NULL),
|
||||
|
||||
-- reserved, contract sent + signed, deposit pending (day 8 of 14)
|
||||
('b_breath_dec',
|
||||
'v_drawehn', 'client_breath',
|
||||
'Atemarbeit zum Jahreswechsel',
|
||||
'2026-12-12','2026-12-19',
|
||||
'reserved', 0, 1, 22,
|
||||
'Year-end breath work for the dark half of the year.',
|
||||
'08:30','13:00','19:00','transfer',
|
||||
2026, '2026-05-01T09:00:00Z','2026-05-01T09:00:00Z',
|
||||
'https://breath-body.example', NULL, NULL),
|
||||
|
||||
-- cancelled (was reserved, fell through)
|
||||
('b_dance_cancelled',
|
||||
'v_drawehn', 'client_dance',
|
||||
'5Rhythms Sommerfest (cancelled)',
|
||||
'2026-07-25','2026-07-28',
|
||||
'cancelled', 1, 0, 18,
|
||||
'Cancelled due to overlap with Solstice retreat.',
|
||||
'08:00','13:00','19:00','transfer',
|
||||
2026, '2026-02-10T09:00:00Z','2026-02-10T09:00:00Z',
|
||||
NULL, NULL, NULL);
|
||||
|
||||
-- ─── Additional participants for the new bookings ──────────────────
|
||||
INSERT OR IGNORE INTO participant (participant_id, booking_id, name, kind, dietary_tag) VALUES
|
||||
-- breath spring (ended) — 12 named
|
||||
('p_b_01','b_breath_spring','Lea Breath', 'overnight','vegan'),
|
||||
('p_b_02','b_breath_spring','Mara Lehmann', 'overnight','vegetarian'),
|
||||
('p_b_03','b_breath_spring','Niko Petrov', 'overnight','omnivore'),
|
||||
('p_b_04','b_breath_spring','Olga Schmidt', 'overnight','vegan'),
|
||||
('p_b_05','b_breath_spring','Pia Brandt', 'overnight','gluten_free'),
|
||||
('p_b_06','b_breath_spring','Quentin Ross', 'overnight','vegetarian'),
|
||||
('p_b_07','b_breath_spring','Rosa Jung', 'overnight','vegan'),
|
||||
('p_b_08','b_breath_spring','Stefan Kühn', 'overnight','omnivore'),
|
||||
('p_b_09','b_breath_spring','Tara Singh', 'overnight','lactose_free'),
|
||||
('p_b_10','b_breath_spring','Ulla Berger', 'day_guest','vegetarian'),
|
||||
('p_b_11','b_breath_spring','Vincent Aydin', 'day_guest','vegan'),
|
||||
('p_b_12','b_breath_spring','Wenke Otto', 'overnight','vegetarian'),
|
||||
|
||||
-- writers may (confirmed, in-flight) — 9 named
|
||||
('p_w_01','b_writers_may','Paula Schreiber','overnight','vegetarian'),
|
||||
('p_w_02','b_writers_may','Annika Born', 'overnight','vegan'),
|
||||
('p_w_03','b_writers_may','Bjarne Holm', 'overnight','omnivore'),
|
||||
('p_w_04','b_writers_may','Conny Dittmer', 'overnight','vegan'),
|
||||
('p_w_05','b_writers_may','Detlef Vogel', 'overnight','gluten_free'),
|
||||
('p_w_06','b_writers_may','Erika Pohl', 'overnight','vegetarian'),
|
||||
('p_w_07','b_writers_may','Falk Reuter', 'overnight','omnivore'),
|
||||
('p_w_08','b_writers_may','Gesa Mahler', 'day_guest','pescatarian'),
|
||||
('p_w_09','b_writers_may','Henrik Lass', 'overnight','vegan'),
|
||||
|
||||
-- dance june — 8 named
|
||||
('p_d_01','b_dance_june','Jana Dance', 'overnight','vegan'),
|
||||
('p_d_02','b_dance_june','Karim Yilmaz', 'overnight','omnivore'),
|
||||
('p_d_03','b_dance_june','Lena Frey', 'overnight','vegetarian'),
|
||||
('p_d_04','b_dance_june','Mira Hoch', 'overnight','vegan'),
|
||||
('p_d_05','b_dance_june','Nils Roth', 'overnight','vegetarian'),
|
||||
('p_d_06','b_dance_june','Olaf Heim', 'overnight','omnivore'),
|
||||
('p_d_07','b_dance_june','Petra Wenz', 'overnight','vegan'),
|
||||
('p_d_08','b_dance_june','Quirin Albers', 'overnight','gluten_free'),
|
||||
|
||||
-- silent sept — 10 named
|
||||
('p_si_01','b_silent_sept','Tom Silent', 'overnight','vegan'),
|
||||
('p_si_02','b_silent_sept','Ute Mendel', 'overnight','vegetarian'),
|
||||
('p_si_03','b_silent_sept','Vera Klein', 'overnight','vegan'),
|
||||
('p_si_04','b_silent_sept','Walter Frey', 'overnight','omnivore'),
|
||||
('p_si_05','b_silent_sept','Xenia Adler', 'overnight','vegan'),
|
||||
('p_si_06','b_silent_sept','Yannick Bauer', 'overnight','vegetarian'),
|
||||
('p_si_07','b_silent_sept','Zoe Maier', 'overnight','gluten_free'),
|
||||
('p_si_08','b_silent_sept','Ada Friese', 'overnight','vegan'),
|
||||
('p_si_09','b_silent_sept','Bo Tillmann', 'overnight','vegetarian'),
|
||||
('p_si_10','b_silent_sept','Carla Hess', 'overnight','vegan'),
|
||||
|
||||
-- workation aug — 10 named
|
||||
('p_wa_01','b_workation_aug','Noah Remote', 'overnight','vegetarian'),
|
||||
('p_wa_02','b_workation_aug','Aisha Khan', 'overnight','vegan'),
|
||||
('p_wa_03','b_workation_aug','Bruno Sá', 'overnight','omnivore'),
|
||||
('p_wa_04','b_workation_aug','Chen Wei', 'overnight','pescatarian'),
|
||||
('p_wa_05','b_workation_aug','Dani Lopez', 'overnight','vegan'),
|
||||
('p_wa_06','b_workation_aug','Elif Demir', 'overnight','vegetarian'),
|
||||
('p_wa_07','b_workation_aug','Felix Andersen','overnight','omnivore'),
|
||||
('p_wa_08','b_workation_aug','Gita Rao', 'overnight','vegan'),
|
||||
('p_wa_09','b_workation_aug','Henri Dubois', 'overnight','gluten_free'),
|
||||
('p_wa_10','b_workation_aug','Ivy Park', 'overnight','vegan'),
|
||||
|
||||
-- choir october — 8 named
|
||||
('p_c_01','b_choir_october','Sofia Vocal', 'overnight','vegetarian'),
|
||||
('p_c_02','b_choir_october','Toni Krug', 'overnight','vegan'),
|
||||
('p_c_03','b_choir_october','Una Berg', 'overnight','omnivore'),
|
||||
('p_c_04','b_choir_october','Vlad Olar', 'overnight','vegan'),
|
||||
('p_c_05','b_choir_october','Wim de Jong', 'overnight','vegetarian'),
|
||||
('p_c_06','b_choir_october','Yuna Park', 'overnight','vegan'),
|
||||
('p_c_07','b_choir_october','Zara Mahmood', 'overnight','gluten_free'),
|
||||
('p_c_08','b_choir_october','Ali Khalil', 'overnight','omnivore'),
|
||||
|
||||
-- qigong easter (ended, half-house) — 6 named
|
||||
('p_qe_01','b_qigong_easter','Kai Wu', 'overnight','vegan'),
|
||||
('p_qe_02','b_qigong_easter','Sun Park', 'overnight','vegetarian'),
|
||||
('p_qe_03','b_qigong_easter','Marek Liu', 'overnight','vegan'),
|
||||
('p_qe_04','b_qigong_easter','Hannah Lee', 'overnight','pescatarian'),
|
||||
('p_qe_05','b_qigong_easter','Iris Wang', 'overnight','vegetarian'),
|
||||
('p_qe_06','b_qigong_easter','Jonas Meier','overnight','omnivore');
|
||||
|
||||
-- ─── Allergies sprinkled across new participants ──────────────────
|
||||
INSERT OR IGNORE INTO participant_allergy (participant_id, allergen, severity) VALUES
|
||||
('p_b_05','gluten_strict','separate_prep'),
|
||||
('p_b_09','lactose','standard'),
|
||||
('p_w_05','gluten_strict','separate_prep'),
|
||||
('p_d_08','gluten_strict','separate_prep'),
|
||||
('p_si_07','gluten_strict','separate_prep'),
|
||||
('p_wa_04','shellfish','standard'),
|
||||
('p_wa_09','gluten_strict','separate_prep'),
|
||||
('p_c_07','gluten_strict','separate_prep'),
|
||||
('p_c_03','peanuts','severe');
|
||||
|
||||
-- ─── Invoices for new bookings ─────────────────────────────────────
|
||||
INSERT OR IGNORE INTO invoice
|
||||
(invoice_id, booking_id, kind, invoice_number, issued_on, due_on, paid_on, amount_cents, status)
|
||||
VALUES
|
||||
('inv_b_dep','b_breath_spring','deposit','2026-010','2025-12-01','2025-12-15','2025-12-08', 36000,'paid'),
|
||||
('inv_b_fin','b_breath_spring','final', '2026-011','2026-03-23','2026-04-06','2026-04-02',144000,'paid'),
|
||||
('inv_qe_dep','b_qigong_easter','deposit','2026-012','2025-12-10','2025-12-24','2025-12-20', 21000,'paid'),
|
||||
('inv_qe_fin','b_qigong_easter','final', '2026-013','2026-04-10','2026-04-24','2026-04-22', 84000,'paid'),
|
||||
('inv_w_dep_writers','b_writers_may','deposit','2026-014','2026-02-01','2026-02-15','2026-02-09', 24000,'paid'),
|
||||
('inv_d_dep','b_dance_june','deposit','2026-015','2026-02-15','2026-03-01','2026-02-22', 33000,'paid'),
|
||||
('inv_si_dep','b_silent_sept','deposit','2026-016','2026-04-15','2026-04-29',NULL, 39000,'outstanding'),
|
||||
('inv_c_dep','b_choir_october','deposit','2026-017','2026-04-20','2026-05-04','2026-04-30', 30000,'paid'),
|
||||
('inv_wa_dep','b_workation_aug','deposit','2026-018','2026-03-20','2026-04-03',NULL, 18000,'outstanding'),
|
||||
('inv_bd_dep','b_breath_dec','deposit','2026-019','2026-05-13','2026-05-27',NULL, 33000,'outstanding');
|
||||
|
||||
-- ─── Lifecycle milestone state ────────────────────────────────────
|
||||
-- Status-transition timestamps (migration 0007) and contract milestones.
|
||||
|
||||
-- confirmed bookings: reserved → contract → deposit → confirmed
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-02-15T10:00:00Z',
|
||||
contract_sent_at = '2026-02-15T10:00:00Z',
|
||||
contract_response = 'approved', contract_response_at = '2026-02-18T14:00:00Z',
|
||||
confirmed_at = '2026-02-22T09:00:00Z'
|
||||
WHERE booking_id = 'b_yoga_2026_summer';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-04-02T10:00:00Z',
|
||||
contract_sent_at = '2026-04-02T10:00:00Z',
|
||||
contract_response = 'approved', contract_response_at = '2026-04-05T14:00:00Z',
|
||||
confirmed_at = '2026-04-10T09:00:00Z'
|
||||
WHERE booking_id = 'b_yoga_2026_winter';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2025-11-13T10:00:00Z',
|
||||
contract_sent_at = '2025-11-13T10:00:00Z',
|
||||
contract_response = 'approved', contract_response_at = '2025-11-16T14:00:00Z',
|
||||
confirmed_at = '2025-12-08T09:00:00Z',
|
||||
ended_at = '2026-03-23T12:00:00Z'
|
||||
WHERE booking_id = 'b_breath_spring';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2025-12-02T10:00:00Z',
|
||||
contract_sent_at = '2025-12-02T10:00:00Z',
|
||||
contract_response = 'approved', contract_response_at = '2025-12-05T14:00:00Z',
|
||||
confirmed_at = '2025-12-20T09:00:00Z',
|
||||
ended_at = '2026-04-10T12:00:00Z'
|
||||
WHERE booking_id = 'b_qigong_easter';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-01-21T10:00:00Z',
|
||||
contract_sent_at = '2026-01-21T10:00:00Z',
|
||||
contract_response = 'approved', contract_response_at = '2026-01-24T14:00:00Z',
|
||||
confirmed_at = '2026-02-09T09:00:00Z'
|
||||
WHERE booking_id = 'b_writers_may';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-02-03T10:00:00Z',
|
||||
contract_sent_at = '2026-02-03T10:00:00Z',
|
||||
contract_response = 'approved', contract_response_at = '2026-02-06T14:00:00Z',
|
||||
confirmed_at = '2026-02-22T09:00:00Z'
|
||||
WHERE booking_id = 'b_dance_june';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-04-05T10:00:00Z',
|
||||
contract_sent_at = '2026-04-05T10:00:00Z',
|
||||
contract_response = 'approved', contract_response_at = '2026-04-08T14:00:00Z',
|
||||
confirmed_at = '2026-04-15T09:00:00Z'
|
||||
WHERE booking_id = 'b_silent_sept';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-04-16T10:00:00Z',
|
||||
contract_sent_at = '2026-04-16T10:00:00Z',
|
||||
contract_response = 'approved', contract_response_at = '2026-04-19T14:00:00Z',
|
||||
confirmed_at = '2026-04-30T09:00:00Z'
|
||||
WHERE booking_id = 'b_choir_october';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-06-03T10:00:00Z'
|
||||
WHERE booking_id = 'b_lc_reserved';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-06-03T10:00:00Z',
|
||||
contract_sent_at = '2026-05-26'
|
||||
WHERE booking_id = 'b_lc_contract_sent';
|
||||
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-05-19T10:00:00Z',
|
||||
contract_sent_at = '2026-05-19T10:00:00Z',
|
||||
contract_response = 'approved',
|
||||
contract_response_at = '2026-05-22T14:00:00Z',
|
||||
confirmed_at = '2026-05-26T09:00:00Z',
|
||||
ended_at = '2026-06-02T12:00:00Z'
|
||||
WHERE booking_id = 'b_lc_confirmed';
|
||||
|
||||
-- reserved booking: contract sent + signed, deposit pending, day 8 of 14 (healthy)
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-05-02T10:00:00Z',
|
||||
contract_sent_at = '2026-05-13T09:00:00Z',
|
||||
contract_response = 'approved',
|
||||
contract_response_at = '2026-05-15T14:00:00Z',
|
||||
deposit_reminder_sent_at = '2026-05-20T06:00:00Z'
|
||||
WHERE booking_id = 'b_breath_dec';
|
||||
|
||||
-- reserved booking: contract sent + signed, deposit overdue → flagged
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-03-13T10:00:00Z',
|
||||
contract_sent_at = '2026-03-20T09:00:00Z',
|
||||
contract_response = 'approved',
|
||||
contract_response_at = '2026-03-24T11:00:00Z',
|
||||
deposit_reminder_sent_at = '2026-03-27T06:00:00Z',
|
||||
flagged_at = '2026-04-03T06:00:00Z',
|
||||
flag_reason = 'deposit_overdue'
|
||||
WHERE booking_id = 'b_workation_aug';
|
||||
|
||||
-- cancelled booking
|
||||
UPDATE booking SET
|
||||
reserved_at = '2026-02-11T10:00:00Z',
|
||||
cancelled_at = '2026-02-25T09:00:00Z'
|
||||
WHERE booking_id = 'b_dance_cancelled';
|
||||
|
||||
-- ─── Sample room assignments (so the room-map view is populated) ────
|
||||
INSERT OR IGNORE INTO room_assignment (booking_id, participant_id, room_id) VALUES
|
||||
('b_writers_may','p_w_01','r_1'),
|
||||
('b_writers_may','p_w_02','r_2'),
|
||||
('b_writers_may','p_w_03','r_3'),
|
||||
('b_writers_may','p_w_04','r_4'),
|
||||
('b_writers_may','p_w_05','r_5'),
|
||||
('b_writers_may','p_w_06','r_6'),
|
||||
('b_writers_may','p_w_07','r_7'),
|
||||
('b_writers_may','p_w_09','r_15');
|
||||
|
||||
-- ─── Audit log — status transition history for seeded bookings ───────
|
||||
-- Audit log — status transition history for seeded bookings. Stored as
|
||||
-- pikku AuditEvents: the domain payload lives in the `data` JSON column,
|
||||
-- which get-booking-audit-log reads back via json_extract.
|
||||
INSERT OR IGNORE INTO audit (audit_id, occurred_at, type, source, actor_user_id, data) VALUES
|
||||
-- b_yoga_2026_summer: enquiry → reserved → confirmed
|
||||
('al_ys_1', '2026-02-15T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_yoga_2026_summer","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_ys_2', '2026-02-22T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_yoga_2026_summer","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
-- b_yoga_2026_winter: enquiry → reserved → confirmed
|
||||
('al_yw_1', '2026-04-02T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_yoga_2026_winter","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_yw_2', '2026-04-10T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_yoga_2026_winter","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
-- b_breath_spring: enquiry → reserved → confirmed → ended
|
||||
('al_bs_1', '2025-11-13T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_breath_spring","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_bs_2', '2025-12-08T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_breath_spring","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
('al_bs_3', '2026-03-23T12:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_breath_spring","action":"update","field":"status","before":"confirmed","after":"ended","venueId":"v_drawehn"}'),
|
||||
-- b_qigong_easter: enquiry → reserved → confirmed → ended
|
||||
('al_qe_1', '2025-12-02T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_qigong_easter","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_qe_2', '2025-12-20T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_qigong_easter","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
('al_qe_3', '2026-04-10T12:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_qigong_easter","action":"update","field":"status","before":"confirmed","after":"ended","venueId":"v_drawehn"}'),
|
||||
-- b_writers_may: enquiry → reserved → confirmed
|
||||
('al_wm_1', '2026-01-21T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_writers_may","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_wm_2', '2026-02-09T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_writers_may","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
-- b_dance_june: enquiry → reserved → confirmed
|
||||
('al_dj_1', '2026-02-03T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_dance_june","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_dj_2', '2026-02-22T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_dance_june","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
-- b_workation_aug: enquiry → reserved
|
||||
('al_wa_1', '2026-03-13T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_workation_aug","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
-- b_silent_sept: enquiry → reserved → confirmed
|
||||
('al_ss_1', '2026-04-05T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_silent_sept","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_ss_2', '2026-04-15T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_silent_sept","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
-- lifecycle fixtures
|
||||
('al_lcr_1', '2026-06-03T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_lc_reserved","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_lcc_1', '2026-06-03T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_lc_contract_sent","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_lcf_1', '2026-05-19T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_lc_confirmed","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_lcf_2', '2026-05-26T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_lc_confirmed","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
('al_lcf_3', '2026-06-02T12:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_lc_confirmed","action":"update","field":"status","before":"confirmed","after":"ended","venueId":"v_drawehn"}'),
|
||||
-- b_choir_october: enquiry → reserved → confirmed
|
||||
('al_co_1', '2026-04-16T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_choir_october","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_co_2', '2026-04-30T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_choir_october","action":"update","field":"status","before":"reserved","after":"confirmed","venueId":"v_drawehn"}'),
|
||||
-- b_breath_dec: enquiry → reserved
|
||||
('al_bd_1', '2026-05-02T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_breath_dec","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
-- b_dance_cancelled: enquiry → reserved → cancelled
|
||||
('al_dc_1', '2026-02-11T10:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_dance_cancelled","action":"update","field":"status","before":"enquiry","after":"reserved","venueId":"v_drawehn"}'),
|
||||
('al_dc_2', '2026-02-25T09:00:00Z', 'booking.update', 'explicit', NULL, '{"entity":"booking","entityId":"b_dance_cancelled","action":"update","field":"status","before":"reserved","after":"cancelled","venueId":"v_drawehn"}');
|
||||
|
||||
-- ─── Public enquiries — awaiting admin triage ──────────────────────
|
||||
-- Dates live in enquiry_date_option (priority-ordered), not on the enquiry.
|
||||
INSERT OR IGNORE INTO enquiry
|
||||
(enquiry_id, venue_id, contact_name, contact_email, contact_phone, website,
|
||||
event_name, event_outline, description,
|
||||
expected_persons, half_house, notes, privacy_accepted_at, status,
|
||||
waitlisted_at, approved_at, declined_at, created_at) VALUES
|
||||
-- Fresh pending enquiry with a single date option
|
||||
('enq_pottery', 'v_drawehn', 'Lena Hoffmann', 'lena.hoffmann@example.com', '+49 170 1112233', 'https://tonundton.example.com',
|
||||
'Pottery & Clay Retreat', 'A hands-on weekend of wheel-throwing and glazing.', 'Small group ceramics retreat with daily guided sessions.',
|
||||
12, 0, 'Would need kiln access if possible.', '2026-05-18T14:30:00Z', 'pending',
|
||||
NULL, NULL, NULL, '2026-05-18T14:30:00Z'),
|
||||
-- Pending enquiry, no dates yet (flexible) — zero date options
|
||||
('enq_songwriting', 'v_drawehn', 'Marco Velten', 'marco@vamp-music.example.com', NULL, NULL,
|
||||
'Songwriting Intensive', 'Collaborative songwriting and recording.', 'Looking for a quiet space, flexible on timing in autumn.',
|
||||
8, 1, NULL, '2026-05-20T09:10:00Z', 'pending',
|
||||
NULL, NULL, NULL, '2026-05-20T09:10:00Z'),
|
||||
-- Waitlisted (dates clash with an existing booking) — two date options
|
||||
('enq_breathwork', 'v_drawehn', 'Sophie Klar', 'sophie.klar@example.com', '+49 151 9988776', NULL,
|
||||
'Breathwork Weekend', 'Guided breathwork and cold exposure.', 'Preferred dates may overlap another group.',
|
||||
20, 0, 'Flexible by a week either side.', '2026-05-12T16:45:00Z', 'pending',
|
||||
'2026-05-15T11:00:00Z', NULL, NULL, '2026-05-12T16:45:00Z');
|
||||
|
||||
INSERT OR IGNORE INTO enquiry_date_option
|
||||
(option_id, enquiry_id, start_date, end_date, priority) VALUES
|
||||
('edo_pottery_1', 'enq_pottery', '2026-09-18', '2026-09-20', 0),
|
||||
('edo_breath_1', 'enq_breathwork', '2026-10-09', '2026-10-11', 0),
|
||||
('edo_breath_2', 'enq_breathwork', '2026-10-16', '2026-10-18', 1);
|
||||
|
||||
-- ─── Demo auth accounts (better-auth credentials) ──────────────────────
|
||||
-- Generated by .tmp-gen-seed-accounts.mjs. These let demo users log in
|
||||
-- directly from the seed; passwords are documented below. The account.password
|
||||
-- is a better-auth scrypt hash (salt:hash, self-contained → portable).
|
||||
-- sarah@seminarhof.example → owner1234
|
||||
-- christina@seminarhof.example → admin1234
|
||||
-- demo@yoga-retreat.example → demo1234
|
||||
-- <all client users> → client1234
|
||||
INSERT OR IGNORE INTO "user" (id, email, name, locale, role, email_verified, created_at, updated_at) VALUES
|
||||
('u_sarah', 'sarah@seminarhof.example', 'Sarah Drawehn', 'de', 'owner', 1, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('u_christina', 'christina@seminarhof.example', 'Christina Admin', 'de', 'admin', 1, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('u_demo', 'demo@yoga-retreat.example', 'Demo Organiser', 'en', 'client', 1, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z');
|
||||
|
||||
INSERT OR IGNORE INTO account (id, account_id, provider_id, user_id, password, created_at, updated_at) VALUES
|
||||
('acct_u_sarah', 'u_sarah', 'credential', 'u_sarah', '3d22bc6eefcbad9f528ec3b3138a9520:c084a3eec4a456ed966e78e77b0f0e3b8cfc1531eb8d06379e8831fc9b97426feacde413767f4a765c7eb907bfe15c84b6ccba26bb3048adc711811f0e45af31', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_christina', 'u_christina', 'credential', 'u_christina', '7d8cccdd9a221d22c77163cf5f386c0b:f0e8f680914ffab822d2ceaf172469c15352c5e5470397d149079c1c077915812beabc8a167f85b59e506f33e769bb1b1018a55820e7b66c13c35965a6ccde71', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_demo', 'u_demo', 'credential', 'u_demo', '8048a18c1e5f5174f7d6741401d0e5da:6cceb3546aec90ad4e27c3fcea0da435eab4d85ecc3deb68f288acc01774b9fe85c1a2d514804d047e01df56435a5bff1affde91196b18883a43e0dea79dbceb', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_breath', 'u_breath', 'credential', 'u_breath', '3042ba9c3b893b91f28cb45fccdd6766:7ddbe83af827f84e83dea9302388d007b96ab484025339eb70260849531d5897f4128bbf3a789bf6b49ddc1997659ccdcd9239e1e5e2dfe1a9f482913f3050c6', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_silent', 'u_silent', 'credential', 'u_silent', 'ebbf8f286058ddf0023424706daed7cd:04286f7173acdc6c6e146eba2b47a21631a964cb7987f354c1a12958379abb74fc89f2c34f6ccb15aa557de969c0b88f88f6169eb380c66c6d75bedd37ee3d95', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_dance', 'u_dance', 'credential', 'u_dance', 'aea4f8e5cd2abb13d4bd311762279a17:67f8a7435161108bd4561c60e8eac1c5ddccac31bdac3b66fb465dee0dd6787feec5a84ca3d2a8fb94370ccdfeac44d3d7417d7c2e1d5e0bc6582c7670b97fdd', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_writers', 'u_writers', 'credential', 'u_writers', 'fdb4f899224c45faaad0eebcc119047a:99107e9232e7114a653bcf29702312ef1ceeb4277135fcc6ff0a69034076a8cb066d3a247478b7844446a5466d32b6402995dfff9efbfa6194e1a8369ed90bf0', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_qigong', 'u_qigong', 'credential', 'u_qigong', '61b26a60ec6ffa7a8c06e21c160a05d3:0dbe469c60e36268152bb32629a14b3502ac88cf812191f92d886d9080e8807e8820dcb8c34999ff6dc9d5fbf109731dfcfc1e483dd9f4d5e2ed3cb0f23edc0a', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_workation', 'u_workation', 'credential', 'u_workation', '6fca2b7798eb61b72f70935fdd8fcd78:8160b10d86917d3e0a36910cc930a47989ca998a5ed3af688873b2ee8dbd82ada7fde4f71578c0a141ae2525d5b1f766ca6616a38b411b841b7d31bd28369580', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z'),
|
||||
('acct_u_choir', 'u_choir', 'credential', 'u_choir', '129e67fd24ef2bb88126febc0bf9a951:3e0700ae7f90a1a623ccac1748fb2d928dd56b0a0b3bb8a84647eda17ac726690b89bdabb9e215d85fb5db2c496e93c51d3e784a6c1096b197080ac4a8507fa9', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z');
|
||||
10
db/sqlite/0001-init.sql
Normal file
10
db/sqlite/0001-init.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE IF NOT EXISTS kanban_card (
|
||||
card_id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'todo',
|
||||
position INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_kanban_card_status_position
|
||||
ON kanban_card (status, position);
|
||||
218
db/sqlite/0002-seminarhof.sql
Normal file
218
db/sqlite/0002-seminarhof.sql
Normal file
@@ -0,0 +1,218 @@
|
||||
-- Drop kanban template scaffold.
|
||||
DROP TABLE IF EXISTS kanban_card;
|
||||
|
||||
-- Seminarhof Drawehn core schema.
|
||||
-- Multi-tenant from day one (venue_id on every domain row).
|
||||
-- All money stored as integer cents (euro_cents) to avoid float drift.
|
||||
--
|
||||
-- Enum-valued columns carry a CHECK (col IN (...)) so the DB is the single
|
||||
-- source of truth for each enum. The pikku CLI introspects these into
|
||||
-- string-literal unions (db.types.ts + enums.gen.ts).
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────
|
||||
-- Tenancy
|
||||
|
||||
CREATE TABLE IF NOT EXISTS venue (
|
||||
venue_id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
locale_default TEXT NOT NULL DEFAULT 'de',
|
||||
meal_window_breakfast TEXT NOT NULL DEFAULT '07:00-10:00',
|
||||
meal_window_lunch TEXT NOT NULL DEFAULT '12:00-14:00',
|
||||
meal_window_dinner TEXT NOT NULL DEFAULT '18:00-20:00',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────
|
||||
-- Identity
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_user (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
locale TEXT NOT NULL DEFAULT 'de',
|
||||
role TEXT NOT NULL CHECK (role IN ('admin', 'client', 'owner')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS client (
|
||||
client_id TEXT PRIMARY KEY,
|
||||
venue_id TEXT NOT NULL REFERENCES venue(venue_id),
|
||||
name TEXT,
|
||||
is_stammgruppe INTEGER NOT NULL DEFAULT 0,
|
||||
billing_address TEXT,
|
||||
contact_email TEXT,
|
||||
contact_phone TEXT,
|
||||
website TEXT,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS client_member (
|
||||
client_id TEXT NOT NULL REFERENCES client(client_id),
|
||||
user_id TEXT NOT NULL REFERENCES app_user(user_id),
|
||||
role TEXT NOT NULL DEFAULT 'organiser' CHECK (role IN ('organiser', 'colleague')),
|
||||
PRIMARY KEY (client_id, user_id)
|
||||
);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────
|
||||
-- Physical inventory
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bathroom (
|
||||
bathroom_id TEXT PRIMARY KEY,
|
||||
venue_id TEXT NOT NULL REFERENCES venue(venue_id),
|
||||
label TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS room (
|
||||
room_id TEXT PRIMARY KEY,
|
||||
venue_id TEXT NOT NULL REFERENCES venue(venue_id),
|
||||
number INTEGER NOT NULL,
|
||||
beds INTEGER NOT NULL,
|
||||
room_type TEXT NOT NULL CHECK (room_type IN ('MBZ', 'DZ', 'EZ_only')),
|
||||
building TEXT NOT NULL CHECK (building IN ('gaestehaus', 'haupthaus')),
|
||||
floor TEXT NOT NULL CHECK (floor IN ('eg', 'og')),
|
||||
bathroom_id TEXT REFERENCES bathroom(bathroom_id),
|
||||
wheelchair INTEGER NOT NULL DEFAULT 0,
|
||||
ground_floor INTEGER NOT NULL DEFAULT 0,
|
||||
quiet INTEGER NOT NULL DEFAULT 0,
|
||||
shared_bath INTEGER NOT NULL DEFAULT 0,
|
||||
dogs_allowed INTEGER NOT NULL DEFAULT 0,
|
||||
double_bed_140 INTEGER NOT NULL DEFAULT 0,
|
||||
allergy_friendly INTEGER NOT NULL DEFAULT 0,
|
||||
surcharge_eur_cents INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (venue_id, number)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS seminar_room (
|
||||
seminar_room_id TEXT PRIMARY KEY,
|
||||
venue_id TEXT NOT NULL REFERENCES venue(venue_id),
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────
|
||||
-- Bookings
|
||||
|
||||
CREATE TABLE IF NOT EXISTS booking (
|
||||
booking_id TEXT PRIMARY KEY,
|
||||
venue_id TEXT NOT NULL REFERENCES venue(venue_id),
|
||||
client_id TEXT NOT NULL REFERENCES client(client_id),
|
||||
event_name TEXT NOT NULL,
|
||||
start_date TEXT,
|
||||
end_date TEXT,
|
||||
status TEXT NOT NULL CHECK (status IN ('enquiry', 'reserved', 'confirmed', 'ended', 'cancelled')),
|
||||
half_house INTEGER NOT NULL DEFAULT 0,
|
||||
online_ad INTEGER NOT NULL DEFAULT 0,
|
||||
cover_image_url TEXT,
|
||||
short_description TEXT,
|
||||
organiser_website TEXT,
|
||||
expected_persons INTEGER,
|
||||
meal_time_breakfast TEXT,
|
||||
meal_time_lunch TEXT,
|
||||
meal_time_dinner TEXT,
|
||||
daily_plan TEXT,
|
||||
arrival_time TEXT,
|
||||
departure_time TEXT,
|
||||
payment_method TEXT NOT NULL DEFAULT 'transfer' CHECK (payment_method IN ('cash', 'transfer')),
|
||||
agb_year INTEGER,
|
||||
agb_accepted_at TEXT,
|
||||
privacy_accepted_at TEXT,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_booking_venue_dates
|
||||
ON booking (venue_id, start_date, end_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_booking_client
|
||||
ON booking (client_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_booking_status
|
||||
ON booking (venue_id, status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS booking_extra (
|
||||
booking_extra_id TEXT PRIMARY KEY,
|
||||
booking_id TEXT NOT NULL REFERENCES booking(booking_id),
|
||||
kind TEXT NOT NULL CHECK (kind IN ('cake', 'second_seminar_room', 'early_arrival', 'bedlinen', 'massage_bench', 'sauna')),
|
||||
qty INTEGER NOT NULL DEFAULT 1,
|
||||
on_dates TEXT, -- JSON array of dates for cake-day picker
|
||||
unit_price_cents INTEGER NOT NULL DEFAULT 0,
|
||||
note TEXT
|
||||
);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────
|
||||
-- Participants
|
||||
|
||||
CREATE TABLE IF NOT EXISTS participant (
|
||||
participant_id TEXT PRIMARY KEY,
|
||||
booking_id TEXT NOT NULL REFERENCES booking(booking_id),
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
age INTEGER,
|
||||
kind TEXT NOT NULL DEFAULT 'overnight' CHECK (kind IN ('overnight', 'day_guest')),
|
||||
dietary_tag TEXT CHECK (dietary_tag IN ('omnivore', 'vegetarian', 'vegan', 'gluten_free', 'lactose_free', 'pescatarian', 'unknown')),
|
||||
free_text TEXT,
|
||||
self_form_token TEXT UNIQUE,
|
||||
self_form_submitted_at TEXT,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_participant_booking
|
||||
ON participant (booking_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS participant_allergy (
|
||||
participant_id TEXT NOT NULL REFERENCES participant(participant_id),
|
||||
allergen TEXT NOT NULL CHECK (allergen IN ('nuts', 'soy', 'gluten_strict', 'dairy', 'egg', 'fish', 'celery', 'mustard', 'sesame', 'shellfish', 'other')),
|
||||
severity TEXT NOT NULL DEFAULT 'standard' CHECK (severity IN ('standard', 'mild', 'moderate', 'severe', 'separate_prep')),
|
||||
note TEXT,
|
||||
PRIMARY KEY (participant_id, allergen)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS room_assignment (
|
||||
booking_id TEXT NOT NULL REFERENCES booking(booking_id),
|
||||
participant_id TEXT NOT NULL REFERENCES participant(participant_id),
|
||||
room_id TEXT NOT NULL REFERENCES room(room_id),
|
||||
PRIMARY KEY (booking_id, participant_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_room_assignment_room
|
||||
ON room_assignment (room_id);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────
|
||||
-- Invoicing
|
||||
|
||||
CREATE TABLE IF NOT EXISTS invoice (
|
||||
invoice_id TEXT PRIMARY KEY,
|
||||
booking_id TEXT NOT NULL REFERENCES booking(booking_id),
|
||||
kind TEXT NOT NULL CHECK (kind IN ('deposit', 'final')),
|
||||
invoice_number TEXT NOT NULL UNIQUE,
|
||||
issued_on TEXT NOT NULL,
|
||||
due_on TEXT,
|
||||
paid_on TEXT,
|
||||
amount_cents INTEGER NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'outstanding' CHECK (status IN ('outstanding', 'paid', 'cancelled')),
|
||||
pdf_url TEXT,
|
||||
datev_ref TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_invoice_booking
|
||||
ON invoice (booking_id);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────
|
||||
-- Audit
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
audit_id TEXT PRIMARY KEY,
|
||||
venue_id TEXT NOT NULL REFERENCES venue(venue_id),
|
||||
user_id TEXT REFERENCES app_user(user_id),
|
||||
entity TEXT NOT NULL,
|
||||
entity_id TEXT NOT NULL,
|
||||
field TEXT,
|
||||
before_value TEXT,
|
||||
after_value TEXT,
|
||||
action TEXT NOT NULL CHECK (action IN ('create', 'update', 'delete')),
|
||||
at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_entity
|
||||
ON audit_log (entity, entity_id);
|
||||
12
db/sqlite/0003-sessions.sql
Normal file
12
db/sqlite/0003-sessions.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- Auth sessions: opaque cookie token → user_id mapping.
|
||||
-- We avoid JWT to keep the dependency footprint small; revocation is just DELETE.
|
||||
|
||||
CREATE TABLE session (
|
||||
session_id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES app_user(user_id) ON DELETE CASCADE,
|
||||
expires_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX session_user_idx ON session(user_id);
|
||||
CREATE INDEX session_expires_idx ON session(expires_at);
|
||||
4
db/sqlite/0004-passwords.sql
Normal file
4
db/sqlite/0004-passwords.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- Password support for app_user. Format stored: pbkdf2:<iterations>:<salt_b64>:<hash_b64>
|
||||
-- Existing rows have NULL password_hash and cannot log in until one is set.
|
||||
|
||||
ALTER TABLE app_user ADD COLUMN password_hash TEXT;
|
||||
4
db/sqlite/0005-course-fields.sql
Normal file
4
db/sqlite/0005-course-fields.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- Rename short_description -> description (the field is multi-sentence prose, not a
|
||||
-- "short" blurb) and add a separate one-line event_outline captured on the enquiry form.
|
||||
ALTER TABLE booking RENAME COLUMN short_description TO description;
|
||||
ALTER TABLE booking ADD COLUMN event_outline TEXT;
|
||||
17
db/sqlite/0006-booking-lifecycle.sql
Normal file
17
db/sqlite/0006-booking-lifecycle.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Booking lifecycle milestones.
|
||||
--
|
||||
-- The status enum is reshaped to a lean "bucket": enquiry | reserved |
|
||||
-- confirmed | ended | cancelled. The contract + deposit progression that
|
||||
-- runs inside `reserved` (over ~1 year) is tracked as the columns below plus the
|
||||
-- deposit row on the invoice table — NOT as extra statuses. A daily cron reads
|
||||
-- these columns; nothing here runs a long-lived workflow.
|
||||
ALTER TABLE booking ADD COLUMN contract_sent_at TEXT; -- ISO; day-0 anchor + R1 idempotency guard
|
||||
ALTER TABLE booking ADD COLUMN contract_response TEXT CHECK (contract_response IN ('approved', 'declined')); -- null = pending
|
||||
ALTER TABLE booking ADD COLUMN contract_response_at TEXT; -- ISO
|
||||
ALTER TABLE booking ADD COLUMN deposit_reminder_sent_at TEXT; -- ISO; R2 idempotency guard
|
||||
ALTER TABLE booking ADD COLUMN flagged_at TEXT; -- ISO; set when admin review is needed
|
||||
ALTER TABLE booking ADD COLUMN flag_reason TEXT CHECK (flag_reason IN ('deposit_overdue', 'short_window', 'contract_declined'));
|
||||
|
||||
-- Collapse the two retired statuses onto the new bucket (for any non-reset DB).
|
||||
UPDATE booking SET status = 'reserved' WHERE status = 'form_received';
|
||||
UPDATE booking SET status = 'confirmed' WHERE status = 'deposit_paid';
|
||||
6
db/sqlite/0007-booking-status-dates.sql
Normal file
6
db/sqlite/0007-booking-status-dates.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- Status transition timestamps. Set by applyBookingTransition when each
|
||||
-- bucket is entered for the first time. No backfill — existing rows get NULL.
|
||||
ALTER TABLE booking ADD COLUMN reserved_at TEXT;
|
||||
ALTER TABLE booking ADD COLUMN confirmed_at TEXT;
|
||||
ALTER TABLE booking ADD COLUMN ended_at TEXT;
|
||||
ALTER TABLE booking ADD COLUMN cancelled_at TEXT;
|
||||
64
db/sqlite/0008-enquiry.sql
Normal file
64
db/sqlite/0008-enquiry.sql
Normal file
@@ -0,0 +1,64 @@
|
||||
-- Public enquiries get their own table and lifecycle, separate from bookings.
|
||||
-- A public submission lands here as `pending`. An admin then either:
|
||||
-- approve → status=approved, a booking is created (copy) referencing this
|
||||
-- enquiry via booking.enquiry_id (admin picks start/end + a client)
|
||||
-- waitlist → waitlisted_at set (requires ≥1 date option); stays pending so it
|
||||
-- can still be approved or declined later
|
||||
-- declined → status=declined
|
||||
--
|
||||
-- `waitlisted_at` is a parallel timestamp on a pending row, NOT a status — a
|
||||
-- waitlisted enquiry is still pending and can be approved or declined.
|
||||
--
|
||||
-- Dates live in `enquiry_date_option` (1-to-N, priority-ordered), NOT on the
|
||||
-- enquiry row — a visitor can propose several date ranges in priority order
|
||||
-- (E6.1). The enquiry has no single start/end of its own; reads that need a
|
||||
-- "preferred" date pick the lowest-priority option.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS enquiry (
|
||||
enquiry_id TEXT PRIMARY KEY,
|
||||
venue_id TEXT NOT NULL REFERENCES venue(venue_id),
|
||||
-- Contact details from the public form. No client row exists yet — one is
|
||||
-- selected or created on approval.
|
||||
contact_name TEXT,
|
||||
contact_email TEXT NOT NULL,
|
||||
contact_phone TEXT,
|
||||
website TEXT,
|
||||
-- Event details.
|
||||
event_name TEXT NOT NULL,
|
||||
event_outline TEXT,
|
||||
description TEXT,
|
||||
expected_persons INTEGER,
|
||||
half_house INTEGER NOT NULL DEFAULT 0,
|
||||
notes TEXT,
|
||||
privacy_accepted_at TEXT,
|
||||
-- Lifecycle.
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'declined')), -- 'waitlisted' is a derived UI state (pending + waitlisted_at), not a status
|
||||
waitlisted_at TEXT,
|
||||
approved_at TEXT,
|
||||
declined_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_enquiry_venue_status
|
||||
ON enquiry (venue_id, status);
|
||||
|
||||
-- Prioritized date options for an enquiry (E6.1). `priority` is 0-based; 0 is
|
||||
-- the visitor's first choice. Optional: an enquiry may have zero options.
|
||||
CREATE TABLE IF NOT EXISTS enquiry_date_option (
|
||||
option_id TEXT PRIMARY KEY,
|
||||
enquiry_id TEXT NOT NULL REFERENCES enquiry(enquiry_id) ON DELETE CASCADE,
|
||||
start_date TEXT NOT NULL,
|
||||
end_date TEXT NOT NULL,
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_enquiry_date_option_enquiry
|
||||
ON enquiry_date_option (enquiry_id, priority);
|
||||
|
||||
-- A booking may originate from an approved enquiry. NULL for admin-created
|
||||
-- bookings. UNIQUE so an enquiry maps to at most one booking (SQLite allows
|
||||
-- many NULLs in a unique index).
|
||||
ALTER TABLE booking ADD COLUMN enquiry_id TEXT REFERENCES enquiry(enquiry_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_booking_enquiry
|
||||
ON booking (enquiry_id);
|
||||
94
db/sqlite/0009-better-auth.sql
Normal file
94
db/sqlite/0009-better-auth.sql
Normal file
@@ -0,0 +1,94 @@
|
||||
-- Migrate from custom app_user/session to Better Auth.
|
||||
-- Drop tables with FKs to app_user, drop app_user, recreate with better-auth schema.
|
||||
|
||||
-- Drop tables that reference app_user
|
||||
DROP TABLE IF EXISTS client_member;
|
||||
DROP TABLE IF EXISTS session;
|
||||
DROP TABLE IF EXISTS audit_log;
|
||||
DROP TABLE IF EXISTS app_user;
|
||||
|
||||
-- Better Auth core tables
|
||||
CREATE TABLE IF NOT EXISTS "user" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL UNIQUE,
|
||||
"email_verified" INTEGER NOT NULL,
|
||||
"image" TEXT,
|
||||
"created_at" TEXT NOT NULL,
|
||||
"updated_at" TEXT NOT NULL,
|
||||
"role" TEXT NOT NULL DEFAULT 'client' CHECK ("role" IN ('admin', 'client', 'owner')),
|
||||
"locale" TEXT NOT NULL DEFAULT 'de'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "session" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"expires_at" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL UNIQUE,
|
||||
"created_at" TEXT NOT NULL,
|
||||
"updated_at" TEXT NOT NULL,
|
||||
"ip_address" TEXT,
|
||||
"user_agent" TEXT,
|
||||
"user_id" TEXT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "account" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"account_id" TEXT NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE,
|
||||
"access_token" TEXT,
|
||||
"refresh_token" TEXT,
|
||||
"id_token" TEXT,
|
||||
"access_token_expires_at" TEXT,
|
||||
"refresh_token_expires_at" TEXT,
|
||||
"scope" TEXT,
|
||||
"password" TEXT,
|
||||
"created_at" TEXT NOT NULL,
|
||||
"updated_at" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "verification" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"identifier" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"expires_at" TEXT NOT NULL,
|
||||
"created_at" TEXT NOT NULL,
|
||||
"updated_at" TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Booking-client cookie sessions (kept separate from better-auth sessions)
|
||||
CREATE TABLE IF NOT EXISTS booking_session (
|
||||
session_id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE,
|
||||
expires_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- Recreate client_member referencing user(id)
|
||||
CREATE TABLE IF NOT EXISTS client_member (
|
||||
client_id TEXT NOT NULL REFERENCES client(client_id),
|
||||
user_id TEXT NOT NULL REFERENCES "user" ("id"),
|
||||
role TEXT NOT NULL DEFAULT 'organiser' CHECK (role IN ('organiser', 'colleague')),
|
||||
PRIMARY KEY (client_id, user_id)
|
||||
);
|
||||
|
||||
-- Recreate audit_log referencing user(id)
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
audit_id TEXT PRIMARY KEY,
|
||||
venue_id TEXT NOT NULL REFERENCES venue(venue_id),
|
||||
user_id TEXT REFERENCES "user" ("id"),
|
||||
entity TEXT NOT NULL,
|
||||
entity_id TEXT NOT NULL,
|
||||
field TEXT,
|
||||
before_value TEXT,
|
||||
after_value TEXT,
|
||||
action TEXT NOT NULL CHECK (action IN ('create', 'update', 'delete')),
|
||||
at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS session_user_id_idx ON "session" ("user_id");
|
||||
CREATE INDEX IF NOT EXISTS account_user_id_idx ON "account" ("user_id");
|
||||
CREATE INDEX IF NOT EXISTS verification_identifier_idx ON "verification" ("identifier");
|
||||
CREATE INDEX IF NOT EXISTS booking_session_user_idx ON booking_session (user_id);
|
||||
CREATE INDEX IF NOT EXISTS booking_session_expires_idx ON booking_session (expires_at);
|
||||
CREATE INDEX IF NOT EXISTS audit_log_entity_idx ON audit_log (entity, entity_id);
|
||||
28
db/sqlite/0010-fabric-audit.sql
Normal file
28
db/sqlite/0010-fabric-audit.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
-- ============================================================
|
||||
-- Fabric audit event sink — the table Pikku Fabric writes AuditEvent
|
||||
-- records to. This is the framework-level event stream (distinct from any
|
||||
-- domain-level row-change history). Columns mirror the AuditEvent shape
|
||||
-- (camelCase → snake_case for the CamelCasePlugin kysely).
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE audit (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL,
|
||||
source TEXT,
|
||||
outcome TEXT,
|
||||
occurred_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
function_id TEXT,
|
||||
wire_type TEXT,
|
||||
wire_id TEXT,
|
||||
trace_id TEXT,
|
||||
transaction_id TEXT,
|
||||
query_id TEXT,
|
||||
actor TEXT,
|
||||
input TEXT,
|
||||
metadata TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_type ON audit(type);
|
||||
CREATE INDEX idx_audit_occurred ON audit(occurred_at);
|
||||
CREATE INDEX idx_audit_function ON audit(function_id);
|
||||
CREATE INDEX idx_audit_trace ON audit(trace_id);
|
||||
32
db/sqlite/0011-audit-canonical.sql
Normal file
32
db/sqlite/0011-audit-canonical.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- Tear out the hand-rolled `audit_log` domain-history table and realign the
|
||||
-- framework `audit` sink to the canonical pikku AuditEvent shape (audit_id PK,
|
||||
-- actor_user_id, data JSON). Domain row-change history now flows through the
|
||||
-- pikku audit service — KyselyAuditService locally, a platform queue sink in
|
||||
-- prod — persisted here and read back by get-booking-audit-log via metadata →
|
||||
-- data. The old `audit` table had a drifted shape and was never written to.
|
||||
DROP TABLE IF EXISTS audit_log;
|
||||
DROP TABLE IF EXISTS audit;
|
||||
|
||||
CREATE TABLE audit (
|
||||
audit_id TEXT NOT NULL PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
||||
occurred_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
type TEXT NOT NULL,
|
||||
source TEXT NOT NULL DEFAULT 'auto',
|
||||
outcome TEXT,
|
||||
function_id TEXT,
|
||||
wire_type TEXT,
|
||||
trace_id TEXT,
|
||||
transaction_id TEXT,
|
||||
query_id TEXT,
|
||||
actor_user_id TEXT,
|
||||
actor_org_id TEXT,
|
||||
tables TEXT, -- JSON array of table names touched
|
||||
changed_cols TEXT, -- JSON array of changed column names
|
||||
event TEXT, -- custom event label
|
||||
old TEXT, -- JSON: previous values
|
||||
data TEXT -- JSON: new values / event payload
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_occurred_at ON audit (occurred_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit (actor_user_id) WHERE actor_user_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_function ON audit (function_id) WHERE function_id IS NOT NULL;
|
||||
11
db/sqlite/0012-user-actor.sql
Normal file
11
db/sqlite/0012-user-actor.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- =============================================================================
|
||||
-- BETTER AUTH ACTOR PLUGIN — user-flow actors
|
||||
-- Adds the `actor` boolean the Better Auth `actor()` plugin declares so its
|
||||
-- schema is covered by an explicit migration and the db drift check passes.
|
||||
-- Actor rows are synthetic users signed in by pikkuUserFlow via
|
||||
-- POST /api/auth/sign-in/actor with the server-held USER_FLOW_ACTOR_SECRET;
|
||||
-- the flag rides into the pikku core session so audits/analytics can address
|
||||
-- synthetic traffic. A non-actor user can never be signed in this way.
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "user" ADD COLUMN "actor" integer NOT NULL DEFAULT 0;
|
||||
17
db/sqlite/0013-admin.sql
Normal file
17
db/sqlite/0013-admin.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- =============================================================================
|
||||
-- BETTER AUTH ADMIN PLUGIN — user administration
|
||||
-- Adds the columns the Better Auth `admin()` plugin declares so its schema is
|
||||
-- covered by an explicit migration and the db drift check passes. The plugin
|
||||
-- exposes /api/auth/admin/* (listUsers, setRole, ban, impersonateUser, …);
|
||||
-- `impersonated_by` marks a session created via "view as user" so it can be
|
||||
-- reverted with admin.stopImpersonating. All fields are optional — a user with
|
||||
-- `role` = 'admin' is an administrator; everyone else stays a normal user.
|
||||
-- `role` already exists on the user table (0009-better-auth.sql), so only the
|
||||
-- admin-specific columns are added here.
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "user" ADD COLUMN "banned" integer DEFAULT 0;
|
||||
ALTER TABLE "user" ADD COLUMN "ban_reason" text;
|
||||
ALTER TABLE "user" ADD COLUMN "ban_expires" date;
|
||||
|
||||
ALTER TABLE "session" ADD COLUMN "impersonated_by" text;
|
||||
13
db/sqlite/0014-fabric.sql
Normal file
13
db/sqlite/0014-fabric.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- =============================================================================
|
||||
-- BETTER AUTH FABRIC PLUGIN — Fabric operator admin sessions
|
||||
-- Adds the `fabric` boolean the Better Auth `fabric()` plugin declares so its
|
||||
-- schema is covered by an explicit migration and the db drift check passes.
|
||||
-- Fabric rows are synthetic operator users minted by POST /api/auth/sign-in/fabric
|
||||
-- after verifying a short-lived RS256 token the Fabric control plane signed
|
||||
-- (checked against FABRIC_AUTH_PUBLIC_KEY). They are created with role 'admin' so
|
||||
-- the console Users tab can list/impersonate real end-users WITHOUT the operator
|
||||
-- being one of them; these rows are filtered out of any end-user listing. A real
|
||||
-- user can never be signed in this way.
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "user" ADD COLUMN "fabric" integer NOT NULL DEFAULT 0;
|
||||
Reference in New Issue
Block a user