Files
mp-pilates/SCHEDULING_FLOW_DIAGRAM.md
richarjiang b6986ba30c feat(admin): implement full day-by-day schedule editor with live preview
## Features

### Admin Schedule Page (`packages/app/src/pages/admin/schedule.vue`)
- Interactive date-based slot editor for managing daily schedules
- Real-time slot editing: start/end times, capacity adjustments
- Slot deletion with conflict warnings when bookings exist
- Add new slots with modal dialog
- Live booking status display (booked count, people names)
- Publish/Save changes with sync feedback
- Revert unsaved changes with confirmation
- Skeleton loading states and empty state handling
- Responsive design with optimized mobile UX

### Backend Enhancements
- **New DTO** (`PublishDaySlotsDto`): Structured slot publishing with validation
  - Date string validation
  - Slot array with existing slot IDs for updates
  - Time and capacity validation per slot

- **Schedule Preview API** (`getSchedulePreview`):
  - Check for existing published slots
  - Fallback to active WeekTemplates for unpublished dates
  - Unified response format with isPublished flag

- **Publish Slots API** (`publishDaySlots`):
  - Atomic transaction for consistency
  - Update existing slots with new times/capacity
  - Create new slots from template data
  - Delete unpublished slots or set to CLOSED if bookings exist
  - Prevent capacity reduction below existing bookings
  - Returns all published slots for feedback

### State Management
- Enhanced admin store with schedule state
- Support for pending/unsaved slot changes
- Optimistic UI updates with server sync

### Documentation
- Comprehensive scheduling system architecture docs
- Quick reference for admin workflows
- Flow diagrams and state transitions
- Implementation guide for future maintenance

## Breaking Changes
None

## Testing Recommendations
- Create slots for future dates via schedule editor
- Verify booking prevention for locked/full slots
- Test capacity adjustments with existing bookings
- Confirm template-based schedule generation
- Verify transaction rollback on publish failures

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 12:18:49 +08:00

13 KiB

Admin Scheduling Flow Diagram

Component Architecture

┌─────────────────────────────────────────────────────────┐
│                   Admin Dashboard                        │
│              (pages/admin/index.vue)                    │
│                                                          │
│  📅 排课设置  🔧 临时调整  👥 会员  📋 订单  💳 卡  🏢 工作室
└─────────────────────────────────────────────────────────┘
        │
        └─► 📅 排课设置 (Week Template)
                 └─────────────────────────────────────────┐
                 │  pages/admin/week-template.vue          │
                 │  ================================        │
                 │                                          │
                 │  1. Fetch Templates (onMounted)          │
                 │     └─ GET /admin/week-template          │
                 │                                          │
                 │  2. Display grouped by day (Mon-Sun)     │
                 │                                          │
                 │  3. Add/Edit/Delete/Toggle locally       │
                 │     └─ isDirty flag = true               │
                 │                                          │
                 │  4. Save All Changes (bottom bar)        │
                 │     └─ PUT /admin/week-template          │
                 │        (Full template array)             │
                 │                                          │
                 │  5. Backend transaction:                 │
                 │     - DELETE all templates               │
                 │     - CREATE new templates               │
                 └────────────────────────────────────────┘

        └─► 🔧 临时调整 (Slot Adjustment - 3 Tabs)
                 └─────────────────────────────────────────┐
                 │  pages/admin/slot-adjust.vue            │
                 │  ================================        │
                 │                                          │
                 │  TAB 0: 新增时段 (Add Manual Slot)      │
                 │  ├─ Date picker                          │
                 │  ├─ Time pickers                         │
                 │  ├─ Capacity input                       │
                 │  └─ POST /admin/time-slot/manual         │
                 │     └─ Creates slot with source=MANUAL   │
                 │                                          │
                 │  TAB 1: 关闭时段 (Close Slots)          │
                 │  ├─ Date picker                          │
                 │  ├─ Fetch slots for date                 │
                 │  │  └─ GET /admin/time-slots?date=XXX   │
                 │  ├─ Display with status badges           │
                 │  │  (OPEN/FULL/CLOSED)                  │
                 │  └─ PUT /admin/time-slot/:id/close       │
                 │                                          │
                 │  TAB 2: 批量生成 (Batch Generate)       │
                 │  ├─ Start/end date pickers              │
                 │  ├─ POST /admin/generate-slots           │
                 │  └─ Backend:                             │
                 │     1. Fetch active WeekTemplates        │
                 │     2. For each day in range:            │
                 │        - Get ISO weekday (1-7)           │
                 │        - Find matching templates         │
                 │        - Create TimeSlot records         │
                 │     3. Returns { count: N }              │
                 └────────────────────────────────────────┘

Data Flow: Template → Slots

┌──────────────────────────────────────────────────────────────────┐
│                   ADMIN TEMPLATE SETUP                           │
│  (weeks/admin/week-template.vue)                                │
└──────────────────────────────────────────────────────────────────┘
                            │
                            ▼
            ┌───────────────────────────────────┐
            │ Admin configures templates:       │
            │                                   │
            │ 周一: 09:00-10:00 (10 ppl)        │
            │ 周一: 18:00-19:00 (8 ppl)         │
            │ 周三: 10:00-11:00 (12 ppl)        │
            │ 周五: 18:00-20:00 (15 ppl)        │
            └───────────────────────────────────┘
                            │
                            ▼
            ┌───────────────────────────────────┐
            │ PUT /admin/week-template          │
            │ (All templates replaced)          │
            └───────────────────────────────────┘
                            │
                            ▼
        ┌────────────────────────────────────────────┐
        │ Backend: Delete all, Create new (atomic)  │
        └────────────────────────────────────────────┘
                            │
                            ▼
        ┌────────────────────────────────────────────┐
        │ Scheduler (nightly cron or manual trigger)│
        │ POST /admin/generate-slots (14 days)      │
        └────────────────────────────────────────────┘
                            │
                            ▼
        ┌────────────────────────────────────────────┐
        │ SlotGeneratorService.generateSlots()      │
        │                                            │
        │ For each active template:                 │
        │   For date in next 14 days:               │
        │     If template.dayOfWeek == date.dayOfWeek:
        │       CREATE TimeSlot {                   │
        │         date, startTime, endTime,         │
        │         capacity, source=TEMPLATE,        │
        │         templateId                        │
        │       }                                    │
        └────────────────────────────────────────────┘
                            │
                            ▼
        ┌────────────────────────────────────────────┐
        │         GENERATED TIME SLOTS               │
        │                                            │
        │ 2026-04-06 (Mon):                         │
        │   09:00-10:00 (10 ppl, OPEN)              │
        │   18:00-19:00 (8 ppl, OPEN)               │
        │                                            │
        │ 2026-04-08 (Wed):                         │
        │   10:00-11:00 (12 ppl, OPEN)              │
        │                                            │
        │ 2026-04-11 (Fri):                         │
        │   18:00-20:00 (15 ppl, OPEN)              │
        │   ... (more dates)                        │
        └────────────────────────────────────────────┘
                            │
                            ▼
        ┌────────────────────────────────────────────┐
        │    Members can book available slots        │
        │    GET /time-slot/available?date=YYYY-MM-DD
        └────────────────────────────────────────────┘

State Management

Component State (week-template.vue)

templates: LocalTemplate[]        ◄─ Main data array
loading: boolean                  ◄─ Fetch state
saving: boolean                   ◄─ Save state
isDirty: boolean                  ◄─ "Save bar" trigger
showModal: boolean                ◄─ Modal visibility
editTarget: LocalTemplate | null  ◄─ Which template is being edited
form: {                           ◄─ Modal form data
  dayIdx: number
  startTime: string
  endTime: string
  capacityStr: string
}
grouped: Computed<Record<number, LocalTemplate[]>>  ◄─ Grouped by dayOfWeek

Store State (stores/admin.ts)

weekTemplates: WeekTemplate[]     ◄─ Cached from server
cardTypes: CardType[]
studioConfig: StudioConfig | null
// ...other admin state

API Endpoints Summary

Week Templates

GET    /admin/week-template              Fetch all templates
PUT    /admin/week-template              Replace all templates

Time Slots

GET    /admin/time-slots?date=YYYY-MM-DD Fetch slots for date
POST   /admin/time-slot/manual            Create manual slot
PUT    /admin/time-slot/:id/close         Close a slot
POST   /admin/generate-slots              Generate slots from templates

Public Endpoints

GET    /time-slot/available?date=YYYY-MM-DD  For members
GET    /time-slot/:id                        For members

Entity Relationships

┌─────────────────────┐
│  WeekTemplate       │
├─────────────────────┤
│ id                  │
│ dayOfWeek (1-7)     │
│ startTime           │
│ endTime             │
│ capacity            │
│ isActive            │
└─────────────────────┘
         │ (1:N)
         │
         ▼
┌─────────────────────┐      ┌──────────────────┐
│   TimeSlot          │      │  Booking (M:1)   │
├─────────────────────┤      ├──────────────────┤
│ id                  │◄─────│ timeSlotId       │
│ date                │      │ userId           │
│ startTime           │      │ status           │
│ endTime             │      └──────────────────┘
│ capacity            │
│ bookedCount         │
│ status              │
│ source (TEMPLATE/   │
│          MANUAL)    │
│ templateId (FK)     │
└─────────────────────┘

Weekday Mapping

Frontend Picker (dayOptions)

Index 0: 周一 (Monday)    ──► dayOfWeek = 1
Index 1: 周二 (Tuesday)   ──► dayOfWeek = 2
Index 2: 周三 (Wednesday) ──► dayOfWeek = 3
Index 3: 周四 (Thursday)  ──► dayOfWeek = 4
Index 4: 周五 (Friday)    ──► dayOfWeek = 5
Index 5: 周六 (Saturday)  ──► dayOfWeek = 6
Index 6: 周日 (Sunday)    ──► dayOfWeek = 7

Backend Conversion (slot-generator.service.ts)

JS getDay():  0=Sun, 1=Mon, 2=Tue, ..., 6=Sat
              
               toIsoWeekday()
              
ISO weekday:  1=Mon, 2=Tue, ..., 7=Sun

Timeline Example

TODAY: 2026-04-05 (Sunday)

Admin actions:
  1. Sets up weekly templates for Mon-Fri
  2. Taps "保存全部更改"
  3. PUT /admin/week-template sent

Backend scheduler (daily at midnight):
  4. Runs generateSlots(14)
  5. Tomorrow is 2026-04-06 (Monday)
  6. Generates slots for Apr 6-19 (next 14 days)
  7. Creates TimeSlots based on active templates:

Generated slots:
  2026-04-06 (Mon):    09:00-10:00, 18:00-19:00
  2026-04-07 (Tue):    (none if no templates)
  2026-04-08 (Wed):    10:00-11:00
  2026-04-09 (Thu):    (none if no templates)
  2026-04-10 (Fri):    18:00-20:00
  2026-04-11 (Sat):    (none - weekend)
  2026-04-12 (Sun):    (none - weekend)
  ...repeats until 2026-04-19

Members can book from Apr 6 onwards