docs: reorganise Obsidian docs from monolith into multi-page knowledge base
Split Crussell Nails.md (822 lines) into 5 focused documents: - README.md: slimmed to 84 lines (elevator pitch + run instructions) - Overview.md: expanded landing page with architecture diagram and quick links - User Manual.md: 358 lines, customer-facing journeys in plain English - Admin Manual.md: 493 lines, staff operations guide with zero technical jargon - Technical Manual.md: 736 lines, full API reference, schema, key systems All user/admin manuals rewritten for non-technical audiences — no API endpoints, component names, or implementation details.
This commit is contained in:
@@ -1,446 +1,84 @@
|
||||
# Crussell
|
||||
#KM|> **Last Updated:** May 2026 — Booking edit modal for /today page, 306/308 tests passing
|
||||
Crussell is a **full‑stack application** that powers a nail‑bar / salon booking service. The repository is split into a **Go** backend and a **SvelteKit** front‑end, both of which are containerised with Docker. A lightweight **SabreDAV** instance is also exposed so that the salon can offer WebDAV access to clients.
|
||||
|
||||
## 📦 Project Structure
|
||||
Nail salon booking platform — Go 1.25 backend + SvelteKit 5 frontend + Docker. Built for a UK sole-trader nail artist.
|
||||
|
||||
## Features
|
||||
|
||||
- **Three booking flows**: self-service (customer), walk-in (admin), call-in (admin)
|
||||
- **Slot reservations**: temporary holds prevent double-booking (4 TTL types)
|
||||
- **Guest accounts**: disposable accounts for one-off bookings, GDPR-compliant anonymization
|
||||
- **Service eligibility**: age requirements + patch test validation
|
||||
- **Deposit system**: 3-strike rule for late cancellations, 24-hour threshold
|
||||
- **Scheduling**: default hours, holiday overrides, time blockers
|
||||
- **Loyalty & discounts**: stamp-based loyalty, time-based and milestone campaigns
|
||||
- **Portfolio gallery**: S3/R2 storage with tag/category filtering
|
||||
- **CardDAV sync**: profile photos synced to SabreDAV contacts
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
Crussell/
|
||||
├─ backend/ # Go 1.25 + chi router API
|
||||
├─ frontend/ # SvelteKit 5 SPA (static build)
|
||||
├─ sabredav/ # PHP + Composer for DAV
|
||||
├─ nginx/ # Nginx reverse‑proxy for HTTP & HTTPS
|
||||
├─ nginx/ # Nginx reverse-proxy for HTTP & HTTPS
|
||||
├─ init-scripts/ # PostgreSQL init SQL
|
||||
├─ compose.yml # Docker‑Compose definition
|
||||
├─ local-dev-2.sh # Development helper using tmux (seeded with guest accounts, time blockers, reservations)
|
||||
├─ compose.yml # Docker-Compose definition
|
||||
├─ local-dev-2.sh # Dev helper using tmux (seeds DB with test data)
|
||||
└─ README.md
|
||||
```
|
||||
|
||||
## ⚙️ Prerequisites
|
||||
## Prerequisites
|
||||
|
||||
| Tool | Version |
|
||||
|------|---------|
|
||||
| Docker & Docker‑Compose | ≥ 20.10 |
|
||||
| Go | ≥ 1.22 |
|
||||
| Node | ≥ 18 (npm) |
|
||||
| tmux | ≥ 3.0 |
|
||||
| Docker & Docker Compose | >= 20.10 |
|
||||
| Go | >= 1.22 |
|
||||
| Node | >= 18 (npm) |
|
||||
| tmux | >= 3.0 |
|
||||
|
||||
> **Tip**: If you already have Docker Desktop or Docker Engine installed, you are good to go.
|
||||
## Getting Started
|
||||
|
||||
## 📥 Getting Started
|
||||
### Docker Compose
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone http://git.popertots.com/popertots/Crussell.git
|
||||
cd Crussell
|
||||
|
||||
# Copy the example environment file and edit it
|
||||
cp .env.example .env
|
||||
# Open .env and provide values for POSTGRES_*, JWT_SECRET_KEY, etc.
|
||||
```
|
||||
|
||||
### Docker‑Compose
|
||||
|
||||
The simplest way to bring the whole stack up is with Docker‑Compose.
|
||||
|
||||
```bash
|
||||
# Edit .env — set POSTGRES_*, JWT_SECRET_KEY
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
> `postgres` – PostgreSQL 17
|
||||
> `backend` – Go API (exposed on `:8080`)
|
||||
> `sabredav` – PHP-based WebDAV (served by Nginx)
|
||||
> `nginx` – Reverse-proxy (HTTP on `:80` and HTTPS on `:443`)
|
||||
| Service | URL |
|
||||
|---------|-----|
|
||||
| Frontend | http://localhost |
|
||||
| API | http://localhost/api |
|
||||
| SabreDAV | http://localhost/dav |
|
||||
|
||||
After the containers are running, the front‑end is reachable at `http://localhost`. The API is available at `http://localhost/api`. SabreDAV can be accessed via `http://localhost/dav`.
|
||||
|
||||
### Development with `local-dev-2.sh`
|
||||
|
||||
For a more interactive dev experience the repository ships a small helper script that launches Docker, starts a tmux session with four panes (PostgreSQL console, Go dev server, Svelte dev server, Rustfs logs) and seeds the database with an admin, regular users, guest accounts, services, bookings, time blockers, and exceptional hours.
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
chmod +x local-dev-2.sh
|
||||
./local-dev-2.sh
|
||||
```
|
||||
|
||||
The script performs the following steps:
|
||||
Launches a tmux session (`crussell-dev`) with 4 panes: psql console, Go dev server, Svelte dev server, and Rustfs logs. Seeds the database with 20 users, 12 services, 43 bookings, guest accounts, time blockers, and exceptional hours.
|
||||
|
||||
1. **Docker checks** – starts Docker if it isn't already running.
|
||||
2. **PostgreSQL reset** – removes the old volume and starts a fresh container.
|
||||
3. **tmux session** – creates `crussell-dev` with panes:
|
||||
* `psql` console
|
||||
* Go server (`go run -tags dev ./main.go`)
|
||||
* Svelte dev server (`npm run dev -- --host`)
|
||||
* Rustfs logs
|
||||
4. **Seeding** – creates admin, regular users, guest accounts, 12 services, bookings (past/today/tomorrow/upcoming/guest), time blockers, exceptional hours, and runs the full test suite (222/224 passing).
|
||||
Default login (all password: `password`):
|
||||
- Admin: `admin@example.com`
|
||||
- User: `user@example.com`
|
||||
|
||||
> **Note**: The script uses a temporary shell script to perform the HTTP calls, so no external tooling like `jq` is required.
|
||||
|
||||
## 🔧 Building & Testing
|
||||
|
||||
### Backend
|
||||
## Building & Testing
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
# Build the binary
|
||||
go build -o bin/backend ./main.go
|
||||
# Backend
|
||||
cd backend && go build -o bin/backend ./main.go
|
||||
|
||||
# Frontend
|
||||
cd frontend && npm ci && npm run build
|
||||
|
||||
# Tests (306/308 passing)
|
||||
cd backend && go test -tags test ./...
|
||||
```
|
||||
|
||||
The binary is then copied into the Docker image via the `Dockerfile`.
|
||||
## Full Documentation
|
||||
|
||||
### Frontend
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm ci
|
||||
npm run build # Production build (static)
|
||||
npm run dev # Development server
|
||||
```
|
||||
|
||||
### SabreDAV
|
||||
|
||||
SabreDAV is bundled with PHP‑FPM and Composer. The Docker image installs dependencies automatically during the container start‑up.
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Test Infrastructure
|
||||
|
||||
Crussell has a comprehensive Go testing infrastructure located in `backend/testutils/`:
|
||||
|
||||
| Component | File | Description |
|
||||
|-----------|------|-------------|
|
||||
| **Database** | `testutils/testdb/testdb.go` | PostgreSQL test pool, migrations, table truncation |
|
||||
| **HTTP Helpers** | `testutils/helpers.go` | Request builders, auth helpers, assertions |
|
||||
| **JWT** | `testutils/jwt/jwt.go` | Test token generation for users/admins |
|
||||
| **HTTP Client** | `testutils/httptest/client.go` | REST client wrapper with auth support |
|
||||
| **Fixtures** | `testutils/fixtures/fixtures.go` | Factory functions for test data |
|
||||
| **Validators** | `internal/validators/validators.go` | ID validation utilities |
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# Run all tests
|
||||
go test -tags test ./...
|
||||
|
||||
# Run with verbose output
|
||||
go test -tags test -v ./...
|
||||
|
||||
# Run specific test package
|
||||
go test -tags test -v ./handlers/bookings
|
||||
|
||||
# Run tests matching pattern
|
||||
go test -tags test -v -run "TestBooking" ./...
|
||||
|
||||
# Run tests twice to catch state leakage
|
||||
go test -tags test -v -p 1 -count=2 ./...
|
||||
```
|
||||
|
||||
### Test Database Setup
|
||||
|
||||
Tests use a dedicated PostgreSQL database (`crussell_test`). Set the connection via environment variables:
|
||||
|
||||
```bash
|
||||
export POSTGRES_USER=myuser POSTGRES_PASSWORD=mypassword POSTGRES_HOST=localhost POSTGRES_DB=crussell_test GO_TESTING=1
|
||||
go test -tags test ./...
|
||||
```
|
||||
|
||||
### Test Architecture
|
||||
|
||||
- All test files use `//go:build test` build tag
|
||||
- Each test package has a `TestMain` that runs schema migration **once** (not per test)
|
||||
- Between tests, `TruncateTables()` clears data via `TRUNCATE TABLE ... CASCADE` — fast, no schema rebuild
|
||||
- Tests run sequentially (`-p 1`) because all packages share the same test database
|
||||
- Test tokens use a fixed secret: `test-secret-key-for-testing-only`
|
||||
- Fixtures auto-generate unique emails to avoid conflicts
|
||||
- `-count=N` is safe to increase for flaky test detection (each count re-runs TestMain + all tests)
|
||||
|
||||
### Test Coverage
|
||||
|
||||
**306/308 tests passing** across all handler packages. Coverage includes guest user creation, guest bookings, slot reservation lifecycle, time blockers, anonymization, booking CRUD, admin management, authentication, scheduling, notifications, portfolio, user profiles, deposit system, edit requests, discount campaigns, and **booking service editing**.
|
||||
|
||||
## 📂 Environment Variables
|
||||
|
||||
| Variable | Purpose | Example |
|
||||
|----------|---------|---------|
|
||||
| `POSTGRES_USER` | DB username | `myuser` |
|
||||
| `POSTGRES_PASSWORD` | DB password | `mysecret` |
|
||||
| `POSTGRES_DB` | DB name | `mydb` |
|
||||
| `JWT_SECRET_KEY` | HMAC key for JWT (HS256) | `supersecret` |
|
||||
| `SABRE_DAV_*` | Optional SabreDAV overrides | – |
|
||||
|
||||
Create a `.env` file in the project root based on the provided `.env.example`.
|
||||
|
||||
> **Note**: JWT tokens expire after 30 days. Login is rate-limited to 1 attempt per 5 seconds.
|
||||
|
||||
## 📊 Seeding Data
|
||||
|
||||
The `local-dev-2.sh` script automatically seeds:
|
||||
|
||||
| Resource | Count | Details |
|
||||
|----------|-------|---------|
|
||||
| Users | 20 | 1 admin, 19 regular users (incl. deposit-triggered, patch-test-verified) |
|
||||
| Services | 12 | 10 standard + 2 requiring patch tests |
|
||||
| Past Bookings | ~17 | Historical data |
|
||||
| Today's Bookings | 4 | |
|
||||
| Tomorrow's Bookings | 5 | |
|
||||
| Upcoming Bookings | ~15 | Spread over 14 days |
|
||||
| Guest Bookings | 3 | Created via disposable guest accounts (Nina, Bob, Carol) |
|
||||
| Total Bookings | 42 | |
|
||||
| Time Blockers | 3 | Staff meeting, holiday, late start |
|
||||
| Schedule Groups | 3 | Exceptional working hour configurations |
|
||||
| Cancellations | 1 | Simulated client cancellation |
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Implementation Status
|
||||
|
||||
### ✅ Complete
|
||||
|
||||
| Feature | Backend | Frontend | Notes |
|
||||
|---------|---------|----------|-------|
|
||||
| JWT Authentication | ✅ | ✅ | Login, register, refresh, middleware; HS256, 30-day expiry, auto-refresh |
|
||||
| Booking CRUD | ✅ | ✅ | Full guest + authenticated booking flow with 5-step wizard |
|
||||
| Guest Booking System | ✅ | ✅ | `POST /api/users/guest` creates disposable accounts; email uniqueness enforced for registered users only |
|
||||
| Slot Reservation | ✅ | ✅ | `POST /api/bookings/reserve` (public) + admin endpoints; 4 TTL types (user:1h, anon:10min, walkin:5min, callin:1h); 50-cap for anon |
|
||||
| Admin Endpoints | ✅ | ✅ | Services, users, today view, notifications, bookings, **booking service editing** |
|
||||
| Scheduling System | ✅ | ✅ | Default hours, exceptional hours, working/available hours, time blockers |
|
||||
| Holiday Hours | ✅ | ✅ | Integrated in all 3 booking flows |
|
||||
| Loyalty Backend | ✅ | ✅ | DB schema + displayed in account page |
|
||||
| VAT System | ✅ | ❌ | `get_vat_return_data()`, `calculate_vat()` functions exist |
|
||||
| User Referrals | ✅ | ❌ | `user_referrals` table, backend logic exists |
|
||||
| Token Refresh | ✅ | ✅ | POST /api/refresh-token, auto-refresh in auth store |
|
||||
| Portfolio System | ✅ | ✅ | S3/R2 storage abstraction, tag-based filtering, category filters, admin upload, ?img= featured image param |
|
||||
| Service Eligibility | ✅ | ✅ | Age + patch test filtering (dedicated `patch_tests` table); `/api/services/eligible-for/{user_id}` for admin booking flows |
|
||||
| Patch Test System | ✅ | ✅ | Dedicated `patch_tests` table, notice periods, expiry (6 months), admin can record in UserModal |
|
||||
| Image Metadata Stripping | ✅ | ❌ | EXIF/GPS stripped on upload via `imaging` library |
|
||||
| Profile Pictures | ✅ | ✅ | Upload to separate bucket, cropper, circular display, CalDAV sync |
|
||||
| Auto-Booking Status | ✅ | ✅ | Auto-transition: confirmed → in_progress → completed based on time |
|
||||
| Simplified Deposits | ✅ | ⚠️ | **24h late cancellation rule**: < 24h = no-show (+3 deposits, optional forgiveness), ≥ 24h = normal cancellation. Admin can bypass checks. Reduces by 1 per payment on completed booking. |
|
||||
| Contact Page | ✅ | ✅ | Dynamic data from first admin user via `/api/contact` endpoint |
|
||||
| Email Verification | ✅ | ❌ | Verification codes, generate/check endpoints |
|
||||
| Calendar Export | ✅ | ✅ | ICS download endpoint, Add to Calendar button |
|
||||
| Loyalty Stamps | ✅ | ✅ | Backend complete, displayed in account page |
|
||||
| GDPR Anonymization | ✅ | ❌ | `AnonymizeStaleGuestAccounts()` scrubs PII 6 months after last booking's start_time; financial records preserved |
|
||||
| Chi Router Fix | ✅ | N/A | Flattened `/bookings` sub-Route to explicit paths to prevent RequireAuth bleeding into OptionalAuth POST endpoints |
|
||||
|
||||
### ⚠️ Partially Complete
|
||||
|
||||
| Feature | Status | Details |
|
||||
|---------|--------|---------|
|
||||
| Notifications | Backend only | Admin notifications wired; acknowledgment on confirm/cancel; no frontend panel, no push (email/SMS), no regular user notifications |
|
||||
| User Notification Preferences | DB ready | Table `user_notification_preferences` exists; waiting on user notification system |
|
||||
| Analytics | Handler exists | `handlers/admin/analytics.go` exists but NOT wired in router |
|
||||
| Payment Integration | Stub | Square placeholder only |
|
||||
|
||||
### ❌ Not Wired (Handlers Exist)
|
||||
|
||||
| Handler | File | Notes |
|
||||
|---------|------|-------|
|
||||
| Social Auth | `handlers/auth/social.go` | OAuth integration placeholder |
|
||||
| Analytics | `handlers/admin/analytics.go` | Statistics/dashboard endpoint |
|
||||
|
||||
### 🚧 Critical TODOs
|
||||
|
||||
| Location | Issue | Priority |
|
||||
|----------|-------|----------|
|
||||
| GDPR Export | Need endpoint for `export_all_user_data()` | Medium |
|
||||
| Tax Export | Endpoint for VAT return data export | Medium |
|
||||
| Frontend Deposits UI | Display `deposits_required` status to users | Medium |
|
||||
| Payment Integration | Full Square SDK integration | Medium |
|
||||
|
||||
### 📋 Feature Requests
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| One-off Custom Services | Allow creating single-use services not in regular catalog |
|
||||
| One-off Exceptional Hours | Single-day overrides without creating a group |
|
||||
| Auto Lunch Protection | Block bookings that remove lunch break |
|
||||
| Begin Button (Today) | Manual start for early arrivals, gray out if >3hrs away |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Booking Flows
|
||||
|
||||
Crussell supports **three distinct booking flows**:
|
||||
|
||||
| Flow | User | Entry Point | Status |
|
||||
|------|------|-------------|--------|
|
||||
| Self-Service | Customer | `BookingFlow.svelte` → reserve slot → guest account (if needed) → `POST /api/bookings` | ✅ |
|
||||
| Walk-In | Admin | Admin panel → walk-in modal → reserve (5min TTL) → create booking | ✅ |
|
||||
| Call/Message-In | Admin | Admin panel → booking modal → reserve (60min TTL) → create booking | ✅ |
|
||||
| **Booking Edit** | Admin | `/today` → Next Appointment → Edit → `PUT /api/admin/bookings/{id}` | ✅ |
|
||||
|
||||
All three flows integrate with the **holiday/exceptional hours** system and **time blockers** to prevent bookings during closed or blocked periods.
|
||||
|
||||
> **Timezone Note**: Crussell is a UK-only service. All appointment times are displayed in the browser's local timezone (which for UK customers is Europe/London). We intentionally do **not** auto-adjust for international timezones — the time shown is the actual UK salon time. Cloudflare geo-blocking prevents non-UK access. When BST/GMT transitions occur, staff manually update working hours in the admin panel; the app does not perform automatic timezone conversions.
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Slot Reservation System
|
||||
|
||||
### Overview
|
||||
|
||||
The reservation system protects against double-booking by temporarily holding a slot while the user completes the booking form. Reservations are stored as `time_blocker` entries with `RESERVATION:*` descriptions.
|
||||
|
||||
### TTL Types
|
||||
|
||||
| Type | TTL | Trigger |
|
||||
|------|-----|---------|
|
||||
| Logged-in user | 1 hour | `POST /api/bookings/reserve` |
|
||||
| Anonymous user | 10 minutes | `POST /api/bookings/reserve` |
|
||||
| Admin walk-in | 5 minutes | Modal opens |
|
||||
| Admin call-in | 1 hour | Reserve step before final submission |
|
||||
|
||||
### Anonymous Cap
|
||||
|
||||
50 total anonymous reservations within any 10-minute rolling window. Returns 429 if exceeded.
|
||||
|
||||
### Cleanup
|
||||
|
||||
`CleanupOldReservations()` runs on availability fetch and deletes expired reservations by type.
|
||||
|
||||
### Endpoint Reference
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| POST | `/api/bookings/reserve` | Optional | Reserve slot for customer journey |
|
||||
| POST | `/api/admin/bookings/reserve` | Admin | Reserve slot for admin booking |
|
||||
|
||||
---
|
||||
|
||||
## 👤 Guest User System
|
||||
|
||||
### Overview
|
||||
|
||||
Guest accounts are disposable, created on-the-fly via `POST /api/users/guest`. Each booking gets a fresh guest user; no identity tracking across bookings.
|
||||
|
||||
### Email Handling
|
||||
|
||||
- **Email uniqueness**: Enforced for registered users only (partial unique index: `WHERE account_role != 'guest'`).
|
||||
- **Guest collision**: If a guest tries to use a registered email, the API returns 409 with "Please log in to book."
|
||||
- **Guest-to-guest**: Multiple guest accounts can share the same email.
|
||||
|
||||
### GDPR Anonymization
|
||||
|
||||
Guest account PII is scrubbed 6 months after their booking's `start_time`. Preserved fields: `account_role`, `account_type`, `deposits_required`, `id`, `created_at`. Scrubbed fields: `n_first_name` → "Guest", `n_last_name` → "Anonymized", email, phone, date_of_birth. Active/pending bookings excluded.
|
||||
|
||||
### Deposit Rules
|
||||
|
||||
- Guest bookings bypass deposit and patch-test checks entirely.
|
||||
- Users with `deposits_required > 0` must book at least 24h in advance (guests exempt).
|
||||
- All bookings must be at least 1 hour in advance.
|
||||
|
||||
---
|
||||
|
||||
## 💰 Deposit System
|
||||
|
||||
### Overview
|
||||
|
||||
The deposit system is a **simplified user-level tracking mechanism** that enforces a 3-deposit requirement before new bookings are allowed. It's designed to reduce booking abandonment and protect against chronic no-shows.
|
||||
|
||||
**Key Concepts**:
|
||||
- `deposits_required`: Integer (0-3) on users table tracking outstanding deposit obligations
|
||||
- `deposit_required`: Boolean on bookings table, snapshotted at creation time
|
||||
- `deposit_amount`: Calculated as 20% of booking total for display
|
||||
- `deposit_paid`: True when pre-start payments cover the deposit amount
|
||||
|
||||
### Cancellation Rules (Late = < 24 Hours)
|
||||
|
||||
| Scenario | Time Until Start | Forgiveness | Result Status | Penalty | User Blocked? |
|
||||
|----------|------------------|-------------|---------------|---------|---------------|
|
||||
| **Late cancellation (normal)** | < 24h | No/Omitted | `no_show` | +3 deposits (resets to 3, not +=) | Yes* |
|
||||
| **Late cancellation (forgiven)** | < 24h | Yes | `client_cancelled` | None | No |
|
||||
| **Normal cancellation** | ≥ 24h | Any | `client_cancelled` | None | No |
|
||||
| **Pending cancellation** | Any | Any | Deleted | None | No |
|
||||
|
||||
*Assuming `deposits_required` > 0 after penalty
|
||||
|
||||
### API: User Cancellation
|
||||
|
||||
**Endpoint**: `DELETE /api/bookings/{id}`
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"reason": "no_show",
|
||||
"forgive_no_show": true // Optional: true = forgive penalty, false/omitted = enforce penalty
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
- `< 24h` without forgiveness → `status = no_show`, `deposits_required = 3`
|
||||
- `< 24h` with forgiveness → `status = client_cancelled`, no penalty
|
||||
- `≥ 24h` → `status = client_cancelled`, no penalty (always)
|
||||
|
||||
### Admin Booking Creation with Deposit Control
|
||||
|
||||
**Endpoint**: `POST /api/admin/bookings`
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"user_id": "USR123",
|
||||
"start_time": "2026-03-10T10:00:00Z",
|
||||
"service_ids": ["SVC1"],
|
||||
"enforce_deposits": false // Optional: true (default) = enforce checks, false = bypass checks
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
- `enforce_deposits = true` (default): Apply one-active-booking limit if `deposits_required > 0`
|
||||
- `enforce_deposits = false`: Bypass all deposit checks, allow multiple active bookings
|
||||
|
||||
### Deposit Reduction
|
||||
|
||||
**Rule**: When booking transitions to `completed` with ≥ 1 payment → `deposits_required -= 1`
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Database Schema Overview
|
||||
|
||||
### Enums
|
||||
|
||||
| Enum | Values |
|
||||
|------|--------|
|
||||
| `account_role` | `unverified_email`, `verified_email`, `admin`, `guest`, `affiliate` |
|
||||
| `account_type` | `email`, `google`, `microsoft`, `facebook`, `guest` |
|
||||
| `booking_status` | `pending`, `confirmed`, `in_progress`, `completed`, `client_cancelled`, `we_cancelled`, `re-schedule`, `no_show`, `no_deposit` |
|
||||
| `payment_type` | `deposit`, `full`, `tip`, `balance`, `partial` |
|
||||
| `payment_method` | `online_square`, `in_person_card`, `cash`, `giftcard`, `discount` |
|
||||
| `payment_status` | `pending`, `completed`, `failed`, `refunded` |
|
||||
| `admin_notification_reason` | `pending_booking`, `cancelled_booking`, `rescheduled_booking`, `1_week_no_pay`, `1_month_no_pay`, `affiliate_claim`, `late_cancellation`, `no_deposit`, `deposit_paid` |
|
||||
|
||||
### Key Tables
|
||||
|
||||
| Table | Purpose |
|
||||
|-------|--------|
|
||||
| `users` | Customer and admin accounts (email uniqueness: partial index WHERE account_role != 'guest') |
|
||||
| `verification_codes` | Email verification and password reset codes |
|
||||
| `services` | Salon service catalog |
|
||||
| `patch_tests` | Patch test definitions with notice periods and expiry |
|
||||
| `user_patch_tests` | User patch test completion records |
|
||||
| `bookings` | Appointment records |
|
||||
| `booking_services` | Services per booking (many-to-many) |
|
||||
| `booking_edit_requests` | Pending customer edit requests |
|
||||
| `time_blockers` | Admin time blocks + slot reservations (description LIKE 'RESERVATION:%') |
|
||||
| `payments` | Payment transactions |
|
||||
| `working_hours` | Default weekly schedule |
|
||||
| `exceptional_working_hours_groups` | Holiday/special schedules |
|
||||
| `admin_notifications` | Admin alerts |
|
||||
| `user_notification_preferences` | User notification preferences (email/sms/push) |
|
||||
| `user_referrals` | Referral tracking |
|
||||
|
||||
### Validation Rules
|
||||
|
||||
| Field | Rules |
|
||||
|-------|-------|
|
||||
| Names | 1-50 chars, unicode letters/spaces/hyphen/apostrophe/dot |
|
||||
| Phone | UK format → E.164 (+44...) |
|
||||
| Age | Must be 16+ years |
|
||||
| Login Rate Limit | 1 attempt per 5 seconds |
|
||||
|
||||
---
|
||||
|
||||
**Happy coding!**
|
||||
For detailed user journeys, admin workflows, architecture, API reference, and database schema, see [obsidian/Crussell/](obsidian/Crussell/).
|
||||
|
||||
Reference in New Issue
Block a user