## 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>
272 lines
13 KiB
Markdown
272 lines
13 KiB
Markdown
# 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)
|
|
```typescript
|
|
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)
|
|
```typescript
|
|
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)
|
|
```typescript
|
|
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
|
|
```
|
|
|