chore: seminarhof customer project
This commit is contained in:
129
apps/app/src/specs.md
Normal file
129
apps/app/src/specs.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Seminarhof — product backlog
|
||||
|
||||
Status legend: ✅ done · 🟡 partial · ⬜ todo · 🔮 phase 2
|
||||
|
||||
---
|
||||
|
||||
## ✅ Shipped
|
||||
|
||||
- **Enquiry table.** Public enquiries land in `enquiry` (not `booking`). Admin
|
||||
view at `/admin/enquiries` with approve / waitlist / decline.
|
||||
- **Approve flow.** Requires start + end date and a client — either pick an
|
||||
existing one (searchable autosuggest) or create a new one (email required,
|
||||
defaults to the enquiry contact email; rejects duplicate emails). Creates a
|
||||
`reserved` booking linked via `booking.enquiry_id`.
|
||||
- **`organization` → `client` rename** (+ `client_member` already models
|
||||
multiple login users per client; one is seeded from the enquiry email).
|
||||
- **`course` → `event` rename** across the codebase.
|
||||
- **Enquiry required fields trimmed:** event name + contact email only;
|
||||
privacy-policy consent only (no T&C checkbox).
|
||||
- **Booking-detail "Enquiry" milestone** sources its date from the enquiry's
|
||||
real submission time, not the approval timestamp.
|
||||
|
||||
---
|
||||
|
||||
## Backlog (epics)
|
||||
|
||||
### E1 · Calendar / availability
|
||||
- ✅ **1.1** Hide `enquiry` status on public availability — show it to
|
||||
admins/owners only. `getAvailability` reads the optional session and filters
|
||||
out enquiry-state bookings for non-staff; the legend drops the enquiry swatch
|
||||
when no such day is present.
|
||||
- ✅ **1.2** Half-house: render the day cell **split by a diagonal line** (two
|
||||
triangles, one per half-house booking) instead of the current dot. On hover,
|
||||
show **both** events being held that day. `getAvailability` returns a
|
||||
per-day `bookings[]` (deterministic order); `availability.tsx` renders the
|
||||
diagonal `linear-gradient` split when two half-house bookings share a day and
|
||||
lists both in the tooltip. Single half-house days keep the corner dot.
|
||||
- ⬜ **1.3** Hover → rich detail card for bookings that are **reserved AND
|
||||
public**.
|
||||
- ⬜ **1.4** Waitlist dot in the top-right corner of the day cell. *(Pairs with
|
||||
E6 — the wishlist/waitlist data.)*
|
||||
- 🔮 **1.5** Google Calendar auto-sync. **Phase 2.**
|
||||
|
||||
### E2 · Actions hub + transition safety ✅
|
||||
- ✅ **2.1** All booking actions consolidated into one header hub
|
||||
(`BookingActions.tsx`), beside Cancel booking. `BookingStatusPanel` is now
|
||||
display-only (timeline + next-action alert).
|
||||
- ✅ **2.2** Every transition goes through a two-click `ConfirmButton`
|
||||
(`ConfirmButton.tsx`): first click arms ("Confirm?", red); second commits;
|
||||
auto-reverts after 3s. Replaced the raw `window.confirm` for cancel too.
|
||||
|
||||
### E3 · Contract form + invoicing *(big rock; status-model redesign approved)*
|
||||
- 🟡 **3.1** Binding booking form scoped to a booking — **core fields shipped**.
|
||||
Client opens a **passwordless magic link** (`/booking-form/$token`), lands
|
||||
logged-in as a provisioned `client` user, and fills name/company, billing
|
||||
address, phone, website, payment method (**Cash / bank transfer**), event
|
||||
basics + AGB/privacy consents. Submit is legally binding (stamps
|
||||
`contractResponse='approved'` + `agbAcceptedAt`/`privacyAcceptedAt`). Token =
|
||||
a short-lived signed JWT via **`@pikku/jose`** (no token table). Admin mints
|
||||
the link from the booking actions hub. *Deferred to E7/E5: the priced add-on
|
||||
groups (extras / room equipment / materials) that feed the final invoice.*
|
||||
- ✅ **3.2** Sending the contract → recorded response / binding. *(Lifecycle
|
||||
work — `recordContractResponse` + the form submit both mark it binding.)*
|
||||
- ⬜ **3.3** Generate a **preview invoice PDF** from the form → email to client
|
||||
+ admin → upload into files. *(Depends on E5 — blocked.)*
|
||||
- ✅ **3.4** 14-day deadline for signing + sending the deposit. *(Lifecycle
|
||||
cron: deposit `dueOn=+14d`, day-7 reminder, day-14 overdue flag.)*
|
||||
- ⬜ **3.5** On bank receipt, admin uploads the **official invoice** and marks
|
||||
it paid → *deposit paid*. *(Mark-paid done via `recordDepositReceived`; the
|
||||
upload depends on E5 — blocked.)*
|
||||
- ✅ **3.6** **Status-model redesign** — done by the lifecycle work: enum is
|
||||
`enquiry → reserved → confirmed → ended` (+ `cancelled`); contract-signed +
|
||||
deposit-paid are sub-milestones inside `reserved`, shown as a timeline in
|
||||
`BookingStatusPanel`.
|
||||
|
||||
*Auth note:* the magic link issues the same DB-session as password login
|
||||
(reuses `lib/session.ts`); `app_user` is reused by email, `client_member`
|
||||
links the user to the client org. `consumeBookingFormLink` rejects
|
||||
cancelled/ended bookings and re-checks org membership.
|
||||
|
||||
### E4 · Admin dashboard ✅
|
||||
- ✅ Required-actions hub at `/admin` (the admin landing): four "needs action"
|
||||
cards — pending enquiries (with a waitlist badge), contracts to send,
|
||||
contracts awaiting response, deposits outstanding — plus an "upcoming events"
|
||||
column (confirmed, next 30 days). One read-only `getAdminDashboard` function
|
||||
(`pikkuFunc`, `isAdminOrOwner`) aggregates the buckets from existing booking /
|
||||
enquiry / invoice / contract data; each card row links to its workspace
|
||||
(enquiries list or booking detail). "Deposits outstanding" ≠ overdue — the
|
||||
14-day deadline lands with E7.
|
||||
|
||||
### E5 · File uploads *(infra prerequisite for E3.3 + event images)*
|
||||
- ⬜ Upload invoices.
|
||||
- ⬜ Upload public event images.
|
||||
|
||||
### E6 · Multi-date / wishlist enquiries
|
||||
- ⬜ **6.1** Enquiry form allows multiple date options, sorted by priority.
|
||||
- ⬜ **6.2** "Wishlist" path when dates conflict: instead of approve→booking,
|
||||
keep it as a wishlist with its own workspace table. *(Builds on the existing
|
||||
`waitlisted_at`.)*
|
||||
|
||||
### E7 · Post-confirmation editing rules + `finalizing` state
|
||||
- ⬜ **New lifecycle state `finalizing`** (badge "Finalizing"), entered
|
||||
automatically **14 days before** start: `confirmed → finalizing → ended`.
|
||||
Add the enum value, a `finalizing_at` column, and the auto-transition (cron).
|
||||
- ⬜ After *confirmed*, participants (count) / rooms (allocation) / diet are
|
||||
editable up to **14 days** before the event starts.
|
||||
- ⬜ Entering `finalizing` (the 14-day mark) → auto-create a project/base
|
||||
invoice.
|
||||
- ⬜ Edits after the 14-day mark are still allowed but produce a **differing**
|
||||
(delta) invoice.
|
||||
- ⬜ Allergies remain editable until the event ends, with no invoice change.
|
||||
|
||||
### E8 · Rename `completed` → `ended` ✅
|
||||
- ✅ Status value `completed` → `ended` and column `completed_at` → `ended_at`,
|
||||
end-to-end (DB-first). Enum now:
|
||||
`enquiry → reserved → confirmed → ended` (+ `cancelled`); `finalizing`
|
||||
slots in before `ended` later via E7.
|
||||
|
||||
### E9 · Unified public site chrome
|
||||
- ⬜ Make the public enquiry / events / availability pages match the
|
||||
seminarhof-drawehn.de header + footer for one cohesive website/app feel.
|
||||
|
||||
---
|
||||
|
||||
## Next up
|
||||
|
||||
Quick wins **E8** + **E2** are shipped. Suggested next sequence (per epic
|
||||
notes above): **E5** (file uploads — unblocks E3.3) → **E3** (contract +
|
||||
invoicing, incl. status-model redesign) → **E4** (dashboard) → **E6 / E7 / E9**.
|
||||
Reference in New Issue
Block a user