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:
- Create
TrainerProfileentity (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:
- 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
- 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 %)
- 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
- 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:
- 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:
- 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)
- 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
- 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):
-
iCal Export (RFC 5545): -
GET /api/calendar/ical/{entityId}— Full course schedule as.icsfile -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 -
Slack Notifications: - Webhook integration: admin configures Slack webhook URL in entity settings - Events: new member registration, cancellation, payment failure, support ticket -
SlackNotificationAdapter implements NotificationAdapter -
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_reminder24h before course "Erinnerung: {courseName} morgen um {time} in {location}" payment_reminder3 days before due date "Hinweis: Dein Mitgliedsbeitrag von {amount} wird am {date} eingezogen" payment_failedSEPA rejection "Achtung: Die Abbuchung von {amount} konnte nicht durchgeführt werden" checkin_welcomeFirst check-in of the day "Willkommen im {organizationName}! Viel Spaß beim Training" contract_expiry30 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)
-
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=2026GET /api/integration/association/templates— List available templates
- Fitness Device Data (v3.0+):
- API endpoints for receiving body composition data (InBody, Tanita, seca)
- Measurement data model (JSONB stored on Member.customAttributes under
measurementskey):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:
- 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:
- 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)
-
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 metricsGET /api/franchise/locations— List child entities with KPIsGET /api/franchise/benchmark— Location benchmarkingGET /api/franchise/reports/financial— Aggregated financial reportGET /api/franchise/members/search— Cross-entity member search
Result: Enterprise-grade franchise dashboard with benchmarking.
Step 9.8 — Phase 9 Documentation
- Update
doc/enduser/admin-manual.md(shop, franchise sections) - Create
doc/enduser/trainer-manual.md(full trainer feature guide) - Update
doc/enduser/consumer-manual.md(shop, support tickets, documents) - Update
doc/developer/backend-guide.md(all new modules) - 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."