PHASE 9: Trainer, Shop & Franchise

Trigger: "Execute Phase 9 as per masterplan."

Goal: Trainer-specific views and dashboard, simple shop with cart and checkout, external integrations (iCal, Slack, WhatsApp, associations, fitness devices), and franchise multi-studio management with template propagation and cross-entity reporting.

Prerequisite: Phase 8 is completed.

Step 9.0 — Technical Conception

Create doc/developer/conception/phase-9-conception.md.

(reference: Chapter 05 — Feature Catalog, Chapter 08 — User Journeys, Chapter 12 — Integration)

Content: - Scope: Trainer views, shop module (new entities), external integrations, franchise features - Trainer Data Model: TrainerProfile extension (schedule, certifications, notes) - Shop Architecture: ShopOrder, OrderLine, CartItem entities, checkout flow, inventory - Integration Design: Per-integration adapter pattern (iCal, Slack webhook, WhatsApp Business API, federation data export, fitness device API) - Franchise Model: FranchiseConfig entity, parent-child permission propagation, template inheritance - Cross-Entity Queries: Aggregation across child entities (SQL UNION or application-level merge) - Design Chapter References: Ch05, Ch07, Ch08 (Journeys 3, 5, 7), Ch12

Result: Technical conception for Phase 9.


Step 9.1 — Trainer Entity and Service

(reference: Chapter 08 — Journey 3: Trainer Course Management)

Approach:

  1. Create TrainerProfile entity (extends User with additional fields):

TrainerProfile fields:

Field Type Constraints Description
id Long PK, generated
userId Long FK UNIQUE NOT NULL Link to User entity
entityId Long FK NOT NULL Organization
bio String(2000) Trainer biography (public)
photoUrl String(500) Profile photo URL
specializations JSONB default '[]' e.g., ["Yoga", "CrossFit", "Rehabilitation"]
certifications JSONB default '[]' e.g., [{"name": "DVGS Fitnesstrainer A", "issuedBy": "DVGS", "validUntil": "2027-06-30"}]
availabilitySchedule JSONB default '{}' Weekly recurring slots: {"monday": [{"from": "08:00", "to": "12:00"}, {"from": "14:00", "to": "18:00"}], ...}
hourlyRate BigDecimal(19,4) nullable Rate for personal training sessions
maxClientsPerSession Integer default 1 1 = personal training, >1 = group training
notesTemplate String(2000) nullable Default template for session notes
status String(20) default 'ACTIVE' ACTIVE / ON_LEAVE / INACTIVE
createdAt Instant auto
updatedAt Instant auto
version Long @Version
2. Create TrainerService:
- Profile CRUD (trainer manages own profile)
- Schedule management (availability, assigned courses)
- Session notes: per-member, per-course notes (stored in AuditLog with type TRAINER_NOTE)
- Course participant list with attendance history
- Member interaction: view limited profile (name, photo, emergency contact, medical notes)
- Certification expiry alerts: scheduled check, notify trainer 30 days before expiry
3. Create controller:
- GET /api/trainers — List trainers for entity
- GET /api/trainers/{id} — Get trainer profile
- PUT /api/trainers/{id} — Update own profile (trainer only)
- GET /api/trainers/{id}/schedule — Get weekly schedule with courses
- GET /api/trainers/{id}/courses — List assigned courses with attendance stats
4. Create Flyway: V900__create_trainer_profile.sql
- Indexes: (entityId, userId) unique, (entityId, status)
5. Tests: TrainerServiceTest
- Tests: CRUD, schedule overlap detection, certification JSONB parsing, participant list, notes CRUD

Result: Trainer profile with schedule, certifications, and notes.


Step 9.2 — Trainer UI

(reference: Chapter 08 — Journey 3)

Approach:

  1. Trainer Dashboard: - Today's courses (time, name, room, registered/capacity) - Upcoming schedule (week view) - Assigned members (personal training clients) - Quick stats: total sessions this month, average attendance
  2. Course Management (trainer scope): - View participant list per course session - Mark attendance (checkbox per member, bulk "all present") - Add session notes (free text, linked to course + date) - View course history (past sessions with attendance %)
  3. Member Interaction: - View member profile (limited: name, photo, emergency contact, medical notes) - Record training notes (linked to member) - View member's attendance history for own courses
  4. Access: Same Flutter app, role-based routing (TRAINER role sees trainer screens)

Result: Trainer-specific views with course management, attendance, and notes.


Step 9.3 — Shop Entities and Service

(reference: Chapter 05 — Feature Catalog (Simple Shop))

Approach:

  1. Create JPA entities:

ShopOrder fields:

Field Type Constraints Description
id Long PK, generated
entityId Long FK NOT NULL Organization
orderNumber String(20) UNIQUE per entity Auto-generated: SO-{YYYY}-{5-digit-seq}
memberId Long FK NOT NULL Ordering member
status String(20) default 'CART' CART / PENDING / PAID / SHIPPED / COMPLETED / CANCELLED / REFUNDED
totalAmount BigDecimal(19,4) NOT NULL Order total (gross, incl. VAT)
totalVat BigDecimal(19,4) NOT NULL Total VAT amount
currency String(3) default 'EUR' ISO 4217
paymentMethod String(30) nullable STORED_BANK_ACCOUNT / SEPA_DIRECT / CASH / INVOICE
transactionId Long FK nullable Linked payment transaction
shippingAddress String(500) nullable Shipping address (for physical goods)
notes String(2000) nullable Customer notes
paidAt Instant nullable Payment timestamp
shippedAt Instant nullable Shipping timestamp
completedAt Instant nullable Completion timestamp
cancelledAt Instant nullable Cancellation timestamp
createdAt Instant auto
updatedAt Instant auto
version Long @Version

OrderLine fields:

Field Type Constraints Description
id Long PK, generated
orderId Long FK NOT NULL Parent order
productId Long FK NOT NULL Product reference
productName String(255) NOT NULL Snapshot of product name at order time
quantity Integer NOT NULL, min 1 Item quantity
unitPrice BigDecimal(19,4) NOT NULL Price per unit (gross)
vatRate BigDecimal(5,2) NOT NULL VAT rate at order time
totalPrice BigDecimal(19,4) NOT NULL quantity * unitPrice

CartItem: id, memberId, productId, quantity, addedAt (session-scoped, Redis-backed with 24h TTL) 2. Create ShopService: - Cart management: add item, remove item, update quantity, clear cart (Redis-backed, 24h TTL) - Checkout flow (4 steps): 1. Review cart: List items, quantities, prices, VAT breakdown, total 2. Select payment: Stored bank account (SEPA via Cash360), cash at reception, or invoice 3. Confirm order: Create ShopOrder with status PENDING, create OrderLine entries, decrement stock 4. Process payment: - SEPA: create Transaction via Cash360 API → order status PAID when webhook confirms - Cash: order status PAID immediately (receptionist marks as paid) - Invoice: order status PENDING until manual payment confirmation - Inventory tracking: decrement on order creation, increment on cancel/refund - Low-stock alert: when stockQuantity < 5 → notification to admin (via RabbitMQ) - Order history per member (sorted by date, status filter) - Admin: order management (view, update status, refund via Cash360 storno) 3. Create Flyway: V901__create_shop.sql 4. Tests: ShopServiceTest

Result: Shop entities with cart, checkout, and inventory.


Step 9.4 — Shop UI

Approach:

  1. Member App — Shop: - Product catalog (grid view with images, prices) - Product detail (description, price, availability) - Cart (slide-out panel, quantity adjustment, total) - Checkout: confirm items -> select payment -> confirm order - Order history (list with status)
  2. Admin App — Shop Management: - Product management: CRUD with shop-specific fields (stock, category, images) - Order management: DataTable with status, member, total, date - Order detail: line items, status update, refund button - Inventory report: low-stock alerts
  3. Create controller: - POST /api/shop/cart/add — Add to cart - DELETE /api/shop/cart/{itemId} — Remove from cart - GET /api/shop/cart — Get cart contents - POST /api/shop/checkout — Place order - GET /api/shop/orders — Order history (member or admin) - GET /api/shop/orders/{id} — Order detail - PUT /api/shop/orders/{id}/status — Update order status (admin) - POST /api/shop/orders/{id}/refund — Refund order (admin)

Result: Shop with catalog, cart, checkout, and order management.


Step 9.5 — External Integrations

(reference: Chapter 12 — Integration)

Approach (each integration as a separate, toggleable adapter):

  1. iCal Export (RFC 5545): - GET /api/calendar/ical/{entityId} — Full course schedule as .ics file - GET /api/calendar/ical/{entityId}/courses/{courseId} — Single course series - GET /api/calendar/ical/member/{memberId} — Personal calendar (enrolled courses only) - Members can subscribe in Google Calendar / Apple Calendar / Outlook - iCal format: BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Membership One//Course Schedule//EN X-WR-CALNAME:{organizationName} - Kurse BEGIN:VEVENT UID:{courseId}-{sessionDate}@membership-one.com DTSTART:{sessionDate}T{startTime} DTEND:{sessionDate}T{endTime} RRULE:FREQ=WEEKLY;BYDAY={dayOfWeek} SUMMARY:{courseName} DESCRIPTION:{courseDescription}\nTrainer: {trainerName}\nPlätze: {registered}/{capacity} LOCATION:{resourceName} CATEGORIES:{courseCategory} END:VEVENT END:VCALENDAR - Auto-update: calendar URL returns current state (subscribers see changes within polling interval) - Cache: Redis 5 min TTL per entity calendar, evict on course/event change

  2. Slack Notifications: - Webhook integration: admin configures Slack webhook URL in entity settings - Events: new member registration, cancellation, payment failure, support ticket - SlackNotificationAdapter implements NotificationAdapter

  3. WhatsApp Business (v3.0+): - WhatsApp Business API integration (via provider: Twilio or official Meta Cloud API) - Pre-approved message templates:

    Template Name Trigger Content
    appointment_reminder 24h before course "Erinnerung: {courseName} morgen um {time} in {location}"
    payment_reminder 3 days before due date "Hinweis: Dein Mitgliedsbeitrag von {amount} wird am {date} eingezogen"
    payment_failed SEPA rejection "Achtung: Die Abbuchung von {amount} konnte nicht durchgeführt werden"
    checkin_welcome First check-in of the day "Willkommen im {organizationName}! Viel Spaß beim Training"
    contract_expiry 30 days before expiry "Dein Vertrag endet am {date}. Möchtest du verlängern?"
  • Template registration via WhatsApp Business Platform (admin provides phone number + API key)
  • WhatsAppNotificationAdapter implements NotificationAdapter
  • Rate limiting: max 1 message per member per day (avoid spam classification)
  1. Association Interfaces (Verbände): - Configurable data export templates for federation reporting - Pre-defined export templates:

    Template Association Required Fields
    DOSB Deutscher Olympischer Sportbund Name, Geburtsdatum, Geschlecht, Verein, Abteilung, Beitrittsdatum
    DFB Deutscher Fußball-Bund Spielerpass-Nr, Name, Geburtsdatum, Mannschaft, Lizenzklasse
    DTB Deutscher Turner-Bund Name, Geburtsdatum, Geschlecht, Verein, Mitgliedsnummer
    LSB Landessportbund (generisch) Name, Geburtsdatum, Geschlecht, Beitrittsdatum, Abteilung
    GENERIC Custom template Admin maps fields from member attributes
  • Export: member demographics, activity statistics, financial summary
  • Format: CSV (delimiter configurable) / XML per association requirements
  • Scheduling: auto-export on configurable date (e.g., Jan 1 for annual reporting)
  • GET /api/integration/association/export?format=csv&template=DOSB&year=2026
  • GET /api/integration/association/templates — List available templates
  1. Fitness Device Data (v3.0+): - API endpoints for receiving body composition data (InBody, Tanita, seca) - Measurement data model (JSONB stored on Member.customAttributes under measurements key): json { "measurements": [ { "date": "2026-03-15", "device": "InBody 770", "data": { "weight": 75.2, "bmi": 24.1, "bodyFatPercent": 18.5, "muscleMass": 32.8, "visceralFatLevel": 7, "basalMetabolicRate": 1650 } } ] } - POST /api/integration/fitness-device/measurement — Receive measurement (device pushes data) - GET /api/members/{id}/measurements — Get measurement history (member or trainer) - Member profile: measurement trend charts (weight, body fat % over time) - Device authentication via API key per device (configured in entity settings)

Result: 5 external integrations, each independently toggleable.


Step 9.6 — Franchise Configuration

(reference: Chapter 07 — User Roles, Chapter 12 — Integration)

Approach:

  1. Create JPA entity:

FranchiseConfig fields:

Field Type Constraints Description
id Long PK, generated
parentEntityId Long FK NOT NULL Parent organization (franchise owner)
childEntityId Long FK NOT NULL UNIQUE Child organization (franchise location)
templatePropagation boolean default false Auto-push parent template changes to child
brandLocking JSONB default '{}' Locked settings: {"logo": true, "primaryColor": true, "emailFooter": true}
permissionOverrides JSONB default '{}' Override child admin permissions: {"maxRoles": ["CLUB_ADMIN", "RECEPTIONIST", "TRAINER"]}
reportingEnabled boolean default true Include child in parent's reporting dashboard
crossLocationCheckin boolean default false Allow members to check in at sibling locations
dataSharing String(20) default 'AGGREGATED' NONE / AGGREGATED / FULL (what parent sees)
createdAt Instant auto
version Long @Version
2. Create FranchiseService:
- Parent-child entity linking (Organization.parentId already exists)
- Template propagation: when parent updates a MembershipTemplate, push changes to all children (opt-in per template)
- Brand consistency: lock specific settings in child entities (logo, colors, email footer)
- Permission inheritance: parent admin can define which permissions child admins have
- Multi-location member access: member at Location A can check in at Location B (cross-entity credential validation)
3. Create Flyway: V902__create_franchise_config.sql
4. Tests: FranchiseServiceTest

Result: Franchise configuration with template propagation and brand locking.


Step 9.7 — Franchise Dashboard

(reference: Chapter 08 — Journey 5: Executive KPI Review)

Approach:

  1. Group Admin Dashboard (Franchise Owner / Franchise Admin view): - Consolidated view across all child entities - Cards: total members (all locations), total revenue, total check-ins, active contracts - Per-location breakdown table: location name, members, revenue, check-ins, churn - Cross-entity member search (search across all child entities)
  2. Benchmarking: - Compare locations on key metrics:

    Metric Calculation Displayed As
    Revenue per member Total revenue / active members EUR/member/month
    Check-in frequency Check-ins / active members / 30 days Avg. visits/month
    Churn rate Cancelled contracts / total contracts % per month
    Conversion rate New members / leads % per month
    Average revenue MRR per location EUR/month
    NPS/CSAT Average health score 0-100
  • Ranking: best/worst performing locations with sparkline trend (last 6 months)
  • Trend charts: per-location metrics over time (line chart, selectable metric)
  • Export: CSV with all locations and all metrics for selected period 3. Aggregated Financial Reports:
  • Combined P&L across all child entities (with per-location breakdown)
  • Revenue by location (stacked bar chart)
  • Revenue by product category across all locations (pie chart)
  • Cost allocation per location 4. Create controller:
  • GET /api/franchise/dashboard — Consolidated metrics
  • GET /api/franchise/locations — List child entities with KPIs
  • GET /api/franchise/benchmark — Location benchmarking
  • GET /api/franchise/reports/financial — Aggregated financial report
  • GET /api/franchise/members/search — Cross-entity member search

Result: Enterprise-grade franchise dashboard with benchmarking.


Step 9.8 — Phase 9 Documentation

  1. Update doc/enduser/admin-manual.md (shop, franchise sections)
  2. Create doc/enduser/trainer-manual.md (full trainer feature guide)
  3. Update doc/enduser/consumer-manual.md (shop, support tickets, documents)
  4. Update doc/developer/backend-guide.md (all new modules)
  5. Update doc/developer/api-reference.md (all new endpoints)

Step 9.9 — Update Intranet

Run python doc/intranet/build.py.


Phase 9 — Quality Gate

# Check Target
1 Conception exists
2 Compilation + tests 0 errors, 0 failures
3 Coverage >= 70%
4 Trainer login -> view courses -> mark attendance -> add notes
5 Shop browse -> add to cart -> checkout -> order history
6 Inventory stock decremented on order
7 iCal valid .ics file downloadable
8 Franchise template parent update propagates to children
9 Franchise dashboard shows aggregated metrics across locations
10 Cross-entity search finds members across child entities
11 Trainer manual exists
12 Documentation + intranet updated
13 CLAUDE.md updated

Report: "Phase 9 completed."

---