TeeTime Tee Sheet Management — Implementation Plan
Status: Phases 1-11, 13 Complete; Phase 12, 14-15 Pending (Phase 10 includes scheduled weather alerts + CourseStatusService; Phase 13 adds course conditions, activity log, staff notes; push certificate for Phase 7 still pending) Owners: TeeTime Eng Last Updated: 2025-12-12 Parent Document: tee-sheet-management-spec.md
Progress Summary
| Phase | Priority | Status | Components |
|---|---|---|---|
| 1. Core Daily View | P0 | ✅ Complete | TeeSheetDashboard, TeeTimeTimeline, DateNavigation, QuickStats |
| 2. Block Management | P0 | ✅ Complete | BlocksTab, BlockCalendarPreview, CourseBlocksAdminController |
| 3. Rules Management | P1 | ✅ Complete | RulesTab, 9 form components, 2 controllers, 2 repositories |
| 4. Settings & Actions | P1 | ✅ Complete | SettingsTab, CourseTeeSheetSettingsAdminController |
| 5. Weather Integration | P1 | ✅ Complete | WeatherPanel, playability scoring, alerts, thresholds |
| 6. Carts & Items | P1 | ✅ Complete | CartsItemsPanel, 3 repositories, CourseInventoryController |
| 7. Real-time Operations | P2 | ✅ Complete (push follow-up) | WebSocket gateway + check-in/QR/StarterView live; FCM push wiring pending |
| 8. Player Intelligence | P2 | ✅ Complete | PlayerProfilePopover, flags, no-show history, frequent partners |
| 9. Waitlist & Demand | P2 | ✅ Complete | WaitlistService, SlotOfferService, WaitlistController, cron, DB-backed offers; admin UI wired (settings + analytics) |
| 10. Communication Hub | P2 | ✅ Complete | TeeTimeMessagingService, multi-channel, bulk; reminder scheduler + resolver wired; scheduled weather alerts + in-app channel |
| 11. Tournament Management | P2 | ✅ Complete | Separate module with 13 components |
| 12. Analytics & Insights | P3 | 🔲 Pending | Demand heatmap, revenue analytics |
| 13. Course Conditions | P2 | ✅ Complete | CourseStatusService, CourseConditionsPanel, activity log, staff notes, pin sheet |
| 14. Advanced UX | P3 | 🔲 Pending | Drag & drop, mobile view |
| 15. Enhancements | P3 | 🔲 Pending | Dynamic pricing, multi-course |
Runway Snapshot
- Phasing: 14 phases mapped to P0/P1/P2/P3 (see table above); P0/P1 = complete.
- Quality gates: Unit + integration for services; visual regression on grids/drawers; a11y axe scan on core flows.
- Telemetry: Ship metrics for grid latency, WS connect rate, conflict check duration, and conversion from hold→booked.
- Release posture: Feature flags per surface (Grid, Blocks, Rules, Starter) and per club; staged enablement with guardrails.
UI Parity vs Wireframes (Reality Check)
- Matches: Daily grid, blocks, rules, settings, carts/items, starter view, waitlist settings/analytics, course conditions panel, activity log, staff notes follow the wireframe structure.
- Gaps: Analytics tab (demand/revenue) not present (Phase 12); drag & drop/mobile/responsive polish tracked under Phase 14; multi-course view sits in Phase 15.
Functional Rules
Booking Windows
| Rule Type | Priority Logic | Example |
|---|---|---|
| Member | Lower number = checked first | Priority 10 member rule applies before priority 100 |
| Rate | Lower number = applies first | Priority 50 peak rate beats priority 100 standard |
Block Validation
| Scenario | Validation | Action |
|---|---|---|
| Block overlaps booking | Warning | List affected bookings, require confirmation |
| Block in past | Error | Prevent creation |
| Recurring block conflicts | Warning | Show all conflict dates |
| Block extends past course close | Error | Prevent end time > last tee time |
Slot Status Transitions
AVAILABLE → HELD (5 min timeout)
HELD → BOOKED (payment confirmed)
HELD → AVAILABLE (timeout/cancelled)
BOOKED → AVAILABLE (cancellation)
AVAILABLE → BLOCKED (manual/event)
BLOCKED → AVAILABLE (block removed)
Validation & Error UX
Client-side Validation
- Required field indicators (
*) - Inline validation messages on blur
- Disable submit until form valid
- Time window: end > start
- Date range: end >= start
- Block conflict preview before save
API Error Handling
- Preserve form state on failure
- Show error banner with message
- Highlight affected fields
- Retry button for network errors
- Optimistic UI with rollback
Destructive Actions
- Delete block: "Delete [name]? Any recurring instances will also be removed."
- Delete rule: "Delete [rule name]? This may affect booking availability."
- Set offline: "Take tee sheet offline? New bookings will be blocked."
- Cancel booking: "Cancel booking for [player]? This cannot be undone."
Success Feedback
- Toast notification on save/delete
- Grid row highlight animation on changes
- Auto-close drawer on successful save
- Optimistic status toggle updates
Accessibility
- All form inputs labeled with
aria-labelor visible labels - Focus management: trap focus in drawers
- Keyboard navigation: Tab through grid, Enter to select
- Screen reader announcements for state changes
- Color-blind safe status indicators (icons + color)
- Minimum touch target: 44×44px
Keyboard Shortcuts
| Shortcut | Action | Context |
|---|---|---|
← / → | Navigate dates | Daily view |
T | Jump to today | Daily view |
Ctrl/Cmd+B | Open create block | Any tab |
Escape | Close drawer | Any |
Component Library
Use existing Ant Design components from teetime-admin:
| Component | Usage |
|---|---|
Table | Tee sheet grid, rules lists, blocks list |
Drawer | Slot details, rule forms, block forms |
Form / Form.Item | All form fields with validation |
Select | Dropdowns for type, status, classifications |
DatePicker | Date selection, ranges |
TimePicker | Time windows |
Switch | Enabled/online toggles |
Checkbox.Group | Days of week, tees selection |
InputNumber | Interval, rate, priority |
Tag | Status badges, type indicators |
Alert | Conflict warnings, info messages |
Modal | Confirmation dialogs |
message | Success/error toasts |
Calendar | Block preview calendar |
Progress | Utilization bars |
Statistic | Quick stats display |
Telemetry
Events to Track
| Event | Properties |
|---|---|
teesheet.daily.view | courseId, date |
teesheet.slot.view | courseId, teeTimeUuid |
teesheet.block.create | courseId, type, isRecurring |
teesheet.block.delete | courseId, blockId |
teesheet.rule.create | courseId, ruleType |
teesheet.rule.update | courseId, ruleId, changedFields |
teesheet.settings.update | courseId, changedFields |
teesheet.status.toggle | courseId, newStatus |
teesheet.startersheet.download | courseId, date |
teesheet.weather.view | courseId, date, playingScore |
teesheet.weather.alert.view | courseId, alertType, severity |
teesheet.weather.settings.update | clubId, changedFields |
teesheet.cart.create | courseId, cartType |
teesheet.cart.reserve | courseId, cartId, teeTimeUuid |
teesheet.cart.release | courseId, cartId, reason |
teesheet.cart.status.change | courseId, cartId, oldStatus, newStatus |
teesheet.item.create | courseId, itemType, category |
teesheet.item.book | courseId, itemId, teeTimeUuid, quantity |
teesheet.item.toggle | courseId, itemId, enabled |
Metrics
- Daily view load time (p50, p95)
- Rules/blocks API latency
- Error rate by operation
- Utilization percentage tracking
- Weather data refresh latency
- Playing score distribution per day
- Cart utilization rate (reserved/total)
- Items booking rate by category
- Revenue attribution (green fees vs carts vs items)
Testing
Unit Tests
describe('TeeSheetGrid', () => {
it('renders correct slot status colors');
it('calculates utilization percentage correctly');
it('filters tee times by tee number');
});
describe('BlockForm', () => {
it('validates date range');
it('shows conflict preview for overlapping bookings');
it('generates recurrence preview');
});
describe('RuleForm', () => {
it('validates time window (end > start)');
it('requires at least one classification');
it('generates human-readable preview');
});
E2E Tests (Playwright)
test('navigate to daily view and see tee times', async ({ page }) => {
// Navigate to tee sheet for course
// Verify grid renders with mock data
// Click on a slot and verify drawer opens
});
test('create and delete a block', async ({ page }) => {
// Open blocks tab
// Create new competition block
// Verify it appears in list
// Delete and confirm removal
});
test('toggle tee sheet offline', async ({ page }) => {
// Go to settings
// Toggle offline
// Verify confirmation dialog
// Verify status updates in sidebar
});
Implementation Phases
Phase 1: Core Daily View (P0) ✅ COMPLETE
Frontend: TeeSheetDashboard.tsx, TeeTimeTimeline.tsx, DateNavigation.tsx, QuickStats.tsx, TeeSheetGrid.tsx, SlotTile.tsx
Backend: AdminCourseTeeSheetController at /admin/courses/{courseId}/tee-sheet/{date}
- Club/course selector sidebar
- Daily tee sheet grid with slot visualization
- Date navigation (previous/next/today/calendar)
- Slot detail drawer with player info, weather, and actions
- Quick stats panel (capacity, revenue, utilization)
- Online/offline status indicator
- Print/Export starter sheet functionality
Phase 2: Block Management (P0) ✅ COMPLETE
Frontend: BlocksTab.tsx, BlockCalendarPreview.tsx
Backend: CourseBlocksAdminController at /admin/courses/{courseId}/blocks
Repository: CourseBlockRepository
- Blocks tab with list view
- Create/edit block drawer
- Block conflict detection (
POST /blocks/conflicts) - Single-day blocks
- Recurring blocks (DAILY, WEEKLY, MONTHLY)
- Calendar preview with visual block display
- Day-of-week selection for recurring
- Block notes for pro-shop reference
Block notifications implemented
- Implemented:
BlockNotificationService(libs/tee-time-services/src/lib/blocks/block-notification.service.ts) scans overlapping bookings, honors per-coursenotifyPlayersOnBlocks, and enqueuesBulkNoticevia messaging with course/club/tenant context. - Wired:
BlocksControllerinvokes notifier on create/update; settings flag persisted viatee-sheet.service.ts+ migration20251216_block_notifications. - Remaining UX: surface notification counts + per-request override in Blocks tab (future polish).
Phase 3: Rules Management (P1) ✅ COMPLETE
Frontend: RulesTab.tsx, CourseMemberRuleForm.tsx, CourseRateRuleForm.tsx, + 7 specialized form components
Backend: CourseMemberRulesAdminController, CourseRateRulesAdminController
Repositories: CourseMemberRuleRepository, CourseRateRuleRepository
- Rules tab layout with Member/Rate sub-tabs
- Member rule CRUD with booking window config
- Rate rule CRUD with pricing tiers
- Rule preview/simulation tool
- Priority ordering
- Rule conflict detection with visual warnings
- Day-of-week, classification, association filters
- Cart inclusion and pricing in rate rules
- Phone booking visibility toggle
Phase 4: Settings & Actions (P1) ✅ COMPLETE
Frontend: SettingsTab.tsx
Backend: CourseTeeSheetSettingsAdminController at /admin/courses/{courseId}/tee-sheet/settings
- Settings tab with multiple configuration cards
- Interval/crossover configuration (5-30 min presets)
- Tee configuration (single/shotgun)
- Booking channel toggles (online, phone, walk-in)
- Operating hours and days
- Weather threshold settings (wind, precip, playability)
- Auto-block on severe weather toggle
- Date range application for bulk settings
- Configuration summary with capacity calculations
Settings Enforcement (Public Search):
- Effective config lookup:
TeeSheetService.getCourseTeeSheetConfig()uses overlapping date window (startDate <= end && endDate >= start) withorderBy: descto find most specific config -
SearchEngineService.resolveInternalConfig()maps MCA course ID → teetime DB UUID viaCourseRepository.findById() - Booking channel enforcement: If
bookingChannels.online === false, visitor/member flows return blank (admin bypass viaisAdmin=true) - Operating days enforcement: If
operatingDaysempty or doesn't include sheet weekday, returns blank withofflineReason - Time window filtering:
filterTeeTimesByWindow()filters tee times tostartTime/endTimebefore capacity/pricing - DI wiring:
TeeSheetModuleimported intee-time-services.module.tssoTeeSheetServiceinjectable inSearchEngineService -
numberOfTeesenforcement:MultiClubSearchService.applyInternalConfig()filterstee <= config.numberOfTees - Multi-club surfaces:
MultiClubSearchService.getManyClubs()andstreamManyClubs()now apply config viaresolveInternalConfig()+applyInternalConfig()
Phase 5: Weather Integration (P1) ✅ COMPLETE
Frontend: WeatherPanel.tsx, integrated in TeeSheetDashboard.tsx slot drawer
Backend: /v1/courses/{courseId}/weather?date= endpoint
- Weather API integration
- Weather summary in daily view
- Per-slot weather with hourly forecast
- Playing score calculation (0-100) with color coding
- Weather condition icons and warnings
- Slot detail drawer weather panel
- 4-hour round duration forecast projection
- Weather alerts banner with severity levels
- Weather settings panel in SettingsTab (thresholds)
- Auto-block on severe weather (optional toggle) — server creates day blocks when playability is severe and flag is enabled
Phase 6: Carts & Items Management (P1) ✅ COMPLETE
Frontend: CartsItemsPanel.tsx
Backend: CourseInventoryController at /v1/courses/:courseId/{carts,items,cart-reservations}
Repositories: CourseCartInventoryRepository, CourseItemInventoryRepository, CourseCartReservationRepository
Backend:
- Prisma models:
CourseCartInventory,CourseItemInventory,CourseCartReservation - Repositories with full CRUD operations
- Controller with cart, item, reservation endpoints
- Auto-status computation for items (IN_STOCK, LOW_STOCK, OUT_OF_STOCK)
- Currency conversion (cents ↔ database decimals)
- Enum mapping (DB uppercase ↔ frontend lowercase)
- Facilities hybrid:
useFacilitiesCarts+facilitiesClubIdflags on Course -
FacilitiesCartAdapterplaceholder for future Facilities integration - Migrations:
20251210_course_inventory_models,20251214_course_facilities_carts - Tests: 18 unit tests covering CRUD and error handling
Frontend:
- Carts & Items tab layout
- Cart inventory list with status indicators
- Cart add/edit with type, units, pricing
- Cart reservation tracking by date
- Items list with categories (rental, sale, snack)
- Item add/edit with stock, threshold, pricing
- Status indicators (active/inactive, in_stock/low_stock/out_of_stock)
- Summary statistics (available carts, reserved, low stock items)
Phase 7: Real-time Operations (P2) ✅ COMPLETE (push wiring pending)
Infrastructure Assessment:
- ✅ SSE streaming exists:
multi-club-stream.controller.tswith 15s heartbeat - ✅ Starter sheet PDF generation:
starter-sheet.controller.ts - ✅ WebSocket gateway:
TeeSheetGateway(libs/tee-time-services/src/lib/gateway/tee-sheet.gateway.ts) with Redis fan-out + JWT room scoping - ✅ Check-in endpoints and models implemented
- ✅ Pace tracking models implemented
- ✅ Tablet-optimized StarterView implemented
Completed Implementation:
- Check-in model: Added
checkedInAt,checkedInBy,teeOffAt,lastHoleCompleted,expectedPaceMinutesto Booking - Migration:
20251211_booking_checkin_pace - Check-in endpoints:
BookingCheckInAdminControllerwith GET/POST/DELETE at/admin/bookings/:id/check-in - Pace endpoints: POST
/admin/bookings/:id/pacefor tee-off and hole updates - QR check-in:
BookingQrControllerwith JSON and SVG endpoints at/admin/bookings/:id/qr{,.svg} - Starter view:
StarterView.tsxtablet-optimized React component with touch-friendly tiles - Starter CSS:
StarterView.csswith tablet media queries and large touch targets - Unit tests:
booking-checkin-admin.controller.spec.ts,booking-qr.controller.spec.tswith edge cases - WebSocket gateway:
TeeSheetGatewayinlibs/tee-time-services/src/lib/gateway/- JWT auth on connection with tenant/club scoping
- Redis pub/sub for multi-instance fan-out (in-memory fallback)
- Room subscriptions:
club:{clubId},course:{courseId}:{date},booking:{bookingId} - Events: slot lifecycle, booking lifecycle, check-in/pace, tee-sheet sync
- Pace alert service:
PaceAlertServicewith configurable thresholds- Warning/critical thresholds via env vars
- Emits
pace.alertWebSocket event - FCM push stub (behind
PACE_FCM_ENABLEDflag)
- Gateway tests: 33 tests covering auth, subscriptions, publish, room access control
- Admin role policy: booking modifications gated for
teetime-adminaudience (starter=view/check-in, proshop=cancel, manager/admin=move+cancel)
Quality Gates Passed:
teetime-backend:lint --max-warnings=0✅tee-time-services:lint --max-warnings=0✅tsc -p tsconfig.app.json --noEmit✅- Gateway tests ✅ (33 tests)
Completed Gateway Wiring:
- Gateway registered in
app.module.tswithTeeSheetGatewayandPaceAlertService - WsAdapter configured in
main.tsbootstrap - Check-in controller wired to publish events:
booking.checked-in,booking.check-in-undone,booking.tee-off,booking.pace-updated - Pace alert integration: calls
PaceAlertService.checkPaceAndAlert()on pace updates
Remaining Implementation:
- Full FCM push integration (wire to messaging service when available)
- Hook
BookingControllerevents (hold/confirm/cancel/reschedule) to gateway for real-time slot updates
Phase 8: Player Intelligence (P2) ✅ COMPLETE
Frontend: PlayerProfilePopover.tsx, updated SlotTile.tsx
Backend: PlayerFlagsAdminController at /admin/players/:playerId/profile, PlayerStatsRepository
Database: PlayerFlag enum, flags/flagNotes/flagUpdatedAt/flagUpdatedBy on Player model
- Enhanced slot tiles with player flags (VIP, CORPORATE, WARNING, SLOW_PLAY, STAFF, NEW_VISITOR)
- Player profile popover on hover (lazy-loaded from
/admin/players/:id/profile) - VIP/warning/sponsor badges with color-coded icons
- No-show history display (last 12 months count)
- Frequent partners display (top 3 partners, 3+ rounds in 12 months)
Phase 9: Waitlist & Demand (P2) ✅ COMPLETE
Backend Services (libs/tee-time-services/src/lib/waitlist/):
-
WaitlistService- Core logic: join, leave, match, position, expire notifications -
SlotOfferService- Timed offer management with accept/decline/expire lifecycle -
WaitlistExpiryCron- Expires stale notifications every 10 minutes -
DemandAnalyticsService- Demand forecasting from BookingIntent patterns (P3)
Controller (WaitlistController at /admin/courses/:courseId/waitlist):
-
POST /- Join waitlist for slot -
DELETE /:tenantId/:slotId/:memberId- Leave waitlist -
GET /position/:slotId/:memberId- Get queue position -
GET /count/:slotId- Get waitlist count for slot -
GET /member/:tenantId/:memberId- Get member's active waitlists -
POST /:entryId/booked- Mark entry as booked (internal) -
GET /:date- Get waitlist for date (admin view) -
POST /:entryId/offer- Manual offer to entry -
POST /:entryId/accept- Accept offer endpoint -
GET /settings+PUT /settings- Waitlist settings (per-course)
Admin Controller (WaitlistAdminController at /admin/waitlist):
-
GET /courses/:courseId/settings- Get per-course waitlist settings -
PUT /courses/:courseId/settings- Update per-course waitlist settings -
DELETE /courses/:courseId/settings- Reset to defaults -
GET /courses/:courseId/analytics- Waitlist performance analytics -
POST /courses/:courseId/expire-stale- Bulk expire stale entries -
POST /courses/:courseId/cascade- Cascade offers on cancellation
Repository Enhancements (WaitlistEntryRepository):
-
findActiveBySlotId(slotId)- Get entries for slot -
findActiveByMemberId(tenantId, memberId)- Get member's entries -
findMatchingEntries(params)- FIFO matching with time window -
hasActiveEntry(slotId, memberId)- Check existence -
getPosition(slotId, memberId)- Get queue position -
markNotified(entryId)- Mark as notified -
markBooked(entryId)- Mark as booked -
markCancelled(entryId)- Mark as cancelled -
expireStaleNotifications(minutes)- Cleanup stale offers -
countActiveBySlotId(slotId)- Get waitlist count
Database-Backed Offers (libs/prisma/tee-sheet-data/):
-
WaitlistOfferPrisma model with status enum (PENDING, ACCEPTED, DECLINED, EXPIRED) -
WaitlistOfferRepositorywith full CRUD and stats methods -
findPendingByEntry(),findBySlot(),countByStatus()queries -
getOfferStats()for analytics (total, accepted, declined, expired, avg response time) - Migration:
20251211_waitlist_offers_settings
Per-Course Settings (libs/prisma/tee-sheet-data/):
-
CourseWaitlistSettingsPrisma model with configurable fields -
CourseWaitlistSettingsRepositorywith defaults fallback - Configurable:
enabled,maxQueueSize,offerExpiryMinutes,timeWindowMinutes - Configurable:
maxNotifications,autoExpireStale,staleExpiryMinutes,autoCascade -
getEffectiveSettings()returns defaults when no course-specific config exists
Prometheus Metrics (libs/tee-time-services/src/lib/metrics/waitlist.metrics.ts):
-
waitlist_operations_totalCounter - join/leave/match/expire by courseId -
waitlist_offer_outcomes_totalCounter - accepted/declined/expired by courseId -
waitlist_offer_response_time_secondsHistogram - time to accept/decline -
waitlist_queue_sizeGauge - current queue size by courseId -
waitlist_conversion_rateGauge - conversion rate percentage by courseId
WebSocket Events (TeeSheetGateway):
-
waitlist.joined- Member joined waitlist -
waitlist.left- Member left waitlist -
waitlist.slot-offered- Slot offered to member -
waitlist.offer-created- SlotOfferService created offer -
waitlist.offer-accepted- Member accepted offer -
waitlist.offer-declined- Member declined offer -
waitlist.offer-expired- Offer expired
Messaging Templates:
-
WAITLIST_SLOT_AVAILABLE- Notify member slot available -
WAITLIST_CONFIRMED- Confirm booking from waitlist -
WAITLIST_OFFER_EXPIRING- Reminder before expiry (P3) -
WAITLIST_POSITION_CHANGE- Position changed notification (P3)
BookingController Integration (booking.controller.ts:847):
-
maybeProcessWaitlist()called after booking cancellation - Finds slot info, course ID, calls
waitlistService.processSlotAvailable() - Logs notification count for observability
Module Wiring:
-
WaitlistServiceprovider intee-time-services.module.ts -
SlotOfferServiceprovider intee-time-services.module.ts -
WaitlistControllermounted inapp.module.ts -
WaitlistExpiryCronprovider inapp.module.ts -
WaitlistAdminControllerexported fromtee-time-services -
WaitlistOfferRepository+CourseWaitlistSettingsRepositoryintee-sheet-data
Admin UI (apps/teetime/teetime-admin/src/app/waitlist/):
-
WaitlistSettingsTab.tsx- Full settings form with all configuration options -
WaitlistSettingsTab.css- Styled form layout -
WaitlistSettingsTab.stories.tsx- Storybook with Default, Disabled, HighCapacity, Loading, Error variants -
WaitlistAnalyticsTab.tsx- Analytics dashboard with entry/offer breakdowns, conversion rates -
WaitlistAnalyticsTab.css- Dashboard styling with stat cards and progress bars -
WaitlistAnalyticsTab.stories.tsx- Storybook with Default, HighPerformance, LowActivity, NoData, Loading, Error variants -
index.ts- Barrel export - Wired into
TeeSheetDashboardwaitlist tab (tenant-scoped settings + analytics)
Future Enhancements (P3):
-
DemandAnalyticsService- Demand forecasting from BookingIntent patterns -
WAITLIST_OFFER_EXPIRINGtemplate - Reminder before expiry -
WAITLIST_POSITION_CHANGEtemplate - Position changed notification
Phase 10: Communication Hub (P2) ✅ COMPLETE
Messaging Infrastructure (✅ Complete):
- Multi-channel support: WhatsApp > Push > SMS > Email priority
-
TeeTimeMessagingService(libs/tee-time-services/src/lib/messaging/teetime-messaging.service.ts) -
TeeTimeMessageQueueAdapterwith BullMQ job queues - Channel providers: WhatsApp (Meta API), SMS (Twilio/SMSPortal), Email (SendGrid/SMTP2GO), Push
- Template system with versioning, translations, revision history (
messaging-clientschema)
Notification Templates (🟡 60%):
- Template DB models:
MessageTemplate,MessageTemplateTranslation,TemplatePartial,TemplateRevision - TeeTime template keys defined in
teetime-messaging.types.ts:BOOKING_CONFIRMATION,BOOKING_REMINDER,BOOKING_CANCELLEDCHECKIN_CONFIRMATION,PACE_ALERT,PACE_WARNINGWEATHER_ALERT,PLAYER_MESSAGE,BULK_NOTICE- Waitlist templates (SLOT_AVAILABLE, CONFIRMED)
- WhatsApp template sync service
- Baseline template content (fallback copy for all tee-time keys)
- Provider-branded/SMS-specific template variants
Send Message to Player (✅ 90%):
-
sendBookingConfirmation()- Single recipient -
sendBookingReminder()- Single recipient (scheduler + resolver wired) -
sendBookingCancelled()- Single recipient -
sendCheckInConfirmation()- Single recipient -
sendPaceAlert()- Single recipient -
sendWeatherAlert()- Single recipient -
sendPlayerMessage()- Direct message to player - Generic
send()method - In-app messaging channel (push-backed, user-scoped)
- Player preference integration
Bulk Notifications (✅ Complete):
-
sendBulk()- Generic bulk send -
sendBookingReminderBulk()- Booking reminders to multiple players -
sendPaceAlertBulk()- Pace alerts to groups -
sendWeatherAlertBulk()- Weather alerts to affected players -
sendBulkNotice()- Course-wide announcements -
BlockNotificationService- Notify players affected by tee sheet blocks -
enqueueBulk()in message queue adapter - Bulk send scheduling (delay/sendAt support)
- Progress tracking for large bulk operations
Weather Alert Notifications (✅ Complete):
-
WeatherNotificationService(libs/tee-time-services/src/lib/messaging/weather-notification.service.ts) - Alert types: lightning, rain, wind, heat, cold, general
- Severity levels: warning, watch, advisory
- Time window support:
effectiveFrom,effectiveUntil -
WEATHER_ALERTtemplate key - Weather threshold config in
CourseTeeSheetmodel - Weather API + playability scoring via
CourseWeatherController(/v1/courses/:courseId/weather) with auto-block on severe - Scheduled weather monitoring + alert dispatch
-
WeatherAffectedBookingsResolverport implementation
Scheduled Weather Monitoring (✅ Complete):
-
WeatherAlertScheduler(libs/tee-time-services/src/lib/messaging/weather-alert-scheduler.service.ts)- Cron-based polling (default:
15 * * * *, configurable viaWEATHER_ALERT_CRON) - Lookahead window:
WEATHER_ALERT_LOOKAHEAD_HOURS(default: 6 hours) - Cooldown deduplication:
WEATHER_ALERT_COOLDOWN_MINUTES(default: 180 min)
- Cron-based polling (default:
-
WeatherMonitoringResolverport (weather-monitoring.port.ts)listCoursesForMonitoring()- Returns courses with coordinates + thresholdsgetLastAlert()- Cross-process cooldown viaCourseConditionLogqueriesrecordWeatherAlert()- Audit trail for dispatched alerts
-
WeatherMonitoringResolverImplin events-worker- Queries
CourseTeeSheetfor threshold configs (wind, precip, playability) - Joins today's
TeeSheetto getteeSheetIdfor status reconciliation - Timezone-aware day bounds via
date-fns-tz
- Queries
- CourseStatusService Integration
reconcileCourseStatus()callsCourseStatusService.evaluateAndTransition()after each weather check- Passes playability score + weather snapshot for audit
- Automatic status transitions: OPEN → CONDITIONAL → CLOSED based on thresholds
- Course deduplication (prefers entries with thresholds configured)
- Tests: 6 unit tests covering alerts, cooldowns, deduplication, cross-process cooldown, severity selection
Booking Reminders (✅ Complete — worker-owned):
-
BookingNotificationListener(libs/tee-time-services/src/lib/messaging/booking-notification.listener.ts) -
@OnEvent(BOOKING_CONFIRMED)→ sends confirmation -
@OnEvent(BOOKING_CANCELLED)→ sends cancellation -
BOOKING_REMINDERevent defined inTeeTimeEvents -
sendBookingReminder()method exists -
BookingReminderScheduler(libs/tee-time-services/src/lib/messaging/booking-reminder-scheduler.service.ts)- Cron-based (default: hourly, configurable via
BOOKING_REMINDER_CRON) - 24-hour reminders: tee times in 23-25h window
- 2-hour reminders: tee times in 1.5-2.5h window (optional,
BOOKING_REMINDER_2H_ENABLED) - Port interface:
UpcomingBookingsResolverfor database queries (events-worker) - Manual trigger:
triggerReminderCheck()for testing/API
- Cron-based (default: hourly, configurable via
-
reminder24hSentAt+reminder2hSentAtfields on Booking model - Migration:
20251211_booking_reminder_tracking(applied) -
UpcomingBookingsResolverport implementation (events-worker) -
BookingDetailsResolverport implementation (events-worker) -
WeatherAffectedBookingsResolverport implementation (events-worker)
Remaining Phase 10 polish (non-blocking):
- Provider-branded template variants (SMS/WhatsApp) if required
Phase 11: Tournament Management (P2) ✅ COMPLETE
Note: Implemented as a separate TournamentsModule in libs/tee-time-services/src/lib/tournaments/ with comprehensive functionality far exceeding original scope.
Backend - TournamentsController (apps/teetime/teetime-backend/src/app/tournaments.controller.ts):
- 60+ REST API endpoints with Swagger documentation
- Guard-protected admin operations (
teetime-adminaudience)
Core Services (13 services exported):
-
CompetitionsService- Lifecycle (draft→open→in_progress→completed→cancelled) -
EntriesService- Entry management, eligibility validation, withdrawals -
ScorecardsService- Hole-by-hole scoring, attestation, locking -
ResultsService- Leaderboards, position assignment, finalization -
DrawsService- Pairing generation (random, handicap, seeded, tee-time order) -
MatchplayService- Bracket generation, seeding, hole-by-hole match scoring -
SideCompetitionsService- Nearest pin, longest drive, 2's club -
SeriesService- Order of Merit / league standings -
PrizeService- Prize templates, pool allocation -
AppealsService- Appeal workflow (open→review→resolution) -
HandicapSnapshotService- Capture handicap at entry time -
HandicapPostingService- Post to GolfRSA/DotGolf -
CompetitionTeeSheetSyncService- Sync tee-sheet bookings to entries
Competition Formats (10 formats):
- Individual: STROKE, STABLEFORD, PAR_BOGEY, MATCHPLAY
- Team: FOUR_BALL_BETTER_BALL, FOURSOMES, GREENSOME, SCRAMBLE, TEXAS_SCRAMBLE, AMBROSE
Draw & Flight Features:
- 4 draw types: RANDOM, HANDICAP_ORDER, SEEDED, TEE_TIME_ORDER
- Shotgun start support (multi-hole simultaneous starts)
- Two-tee start support
- Flight management with manual swaps/moves
- Late entry accommodation
- Draw lock mechanism
Scoring System:
- Live hole-by-hole scoring
- Stableford points calculation
- Net score calculation with WHS compliance
- Attestation workflow (player→marker→admin)
- Countback tie resolution (back-9, back-6, back-3, last-hole)
Advanced Features:
- Matchplay brackets (4-64 players), seeding, hole-by-hole scoring
- Division/flight segmentation (handicap, gender, age)
- PCC (Playing Conditions Calculation) support
- CSS (Competition Standard Scratch)
- Handicap posting to associations (GolfRSA, DotGolf)
- Series/Order of Merit standings
- Appeals system
- Prize pool management with templates
- CSV leaderboard exports
Prisma Models (20+ models in @prisma/teetime):
- Competition, CompetitionRound, CompetitionDivision, CompetitionEntry
- CompetitionScorecard, CompetitionScorecardHole, CompetitionResult
- CompetitionPairing, CompetitionPairingPlayer, HandicapSnapshot
- MatchplayBracket, MatchplayMatch, MatchplayHole, CompetitionAppeal
- Season, CourseLayout, CourseLayoutHole, DailyCourseConditions
Documentation (apps/teetime/docs/docs/tournaments/):
- 10 comprehensive guides: api.md, formats.md, draws.md, entries.md, scoring.md, results.md, handicaps.md, matchplay.md, side-competitions.md
Phase 12: Analytics & Insights (P3)
- Analytics tab
- Demand heatmap
- Utilization trends
- Revenue analytics
- Revenue forecast
- Cancellation insights
Phase 13: Course Conditions (P2) ✅ COMPLETE
Backend Services (libs/tee-time-services/src/lib/course-status/):
-
CourseStatusService- Core state machine for course status transitions- State transitions: OPEN → CONDITIONAL → CLOSED → OPEN
- Playability thresholds with hysteresis to prevent rapid toggling
- Default thresholds: conditional < 60, closed < 30, open > 70
-
evaluateAndTransition()- Automatic status changes based on playability score (requires teeSheetId) -
evaluateForCourseDate()- Convenience wrapper that resolves teeSheet by courseId/date -
applyManualOverride()- Staff can override status with expiry time -
clearManualOverride()- Remove override and optionally re-evaluate -
getStatus()- Current status, playability score, override info -
getConditionHistory()- Audit trail queries
Database Models (@prisma/tee-sheet-data):
-
CourseStatusenum:OPEN,CONDITIONAL,CLOSED -
ConditionChangeSourceenum:AUTO_WEATHER,MANUAL_OVERRIDE,SYSTEM -
CourseConditionLogmodel - Full audit trail with:previousStatus,newStatus,playabilityScoresource,reason,weatherSnapshot,alertTypesnotificationsSent,bookingsAffected,createdBy
- TeeSheet fields:
courseStatus,playabilityScore,lastWeatherCheck - TeeSheet override fields:
manualOverride,overrideReason,overrideExpiry,overrideBy - TeeSheet cart suspension:
cartsSuspended - Migration:
20251217_course_conditions
Event System:
-
COURSE_STATUS_CHANGED_EVENT- Emitted on status transitions -
CourseStatusChangedEventinterface with full context - EventEmitter2 integration for notification triggers
Integration Points:
-
WeatherAlertSchedulercallsCourseStatusService.evaluateForCourseDate()after weather checks- Derives playability thresholds from course config (windWarningKmh, playabilityWarningThreshold)
- Passes weather snapshot (maxWindKmh, maxPrecipMm, windowEnd) for audit
- Automatic cart suspension on CLOSED status
- Manual override respected (skips auto-transition while active)
Tests (course-status.service.spec.ts):
- Status getter with manual override details (3 tests)
- evaluateAndTransition full coverage (8 tests)
- applyManualOverride (6 tests)
- clearManualOverride (3 tests)
- getConditionHistory (1 test)
- Hysteresis dead-zone behavior (2 tests)
- Event emission without emitter (1 test)
Controller (CourseStatusController at /admin/courses/:courseId/status):
-
GET /:teeSheetId- Get current status (playability, override, cart suspension) -
GET /history- Get condition change history for date range -
POST /override- Apply manual status override with expiry -
POST /clear-override- Clear manual override, optionally re-evaluate
Frontend Integration (✅ Complete):
- Course conditions panel in TeeSheetDashboard (
CourseConditionsPanel.tsx) - Real-time status updates via WebSocket
- Notification triggers on status change (notify affected bookings)
- Pin sheet management UI (hole status, pin positions)
- Activity log display in drawer (
TeeTimeActivityService,TeeTimeActivityController)
Phase 14: Advanced UX (P3)
Completed:
- Staff notes (
StaffNotesService,StaffNotesControllerat/admin/courses/:courseId/tee-times/:teeTimeId/notes)- Create, list notes per tee time
- Optional slot/booking linking
- User attribution
- Frontend integration in slot drawer
Pending:
- Drag & drop booking moves
- Mobile responsive view
- Bulk block operations
- Rate override per slot
Phase 15: Enhancements (P3)
- Cart check-out/return tracking
- Low inventory alerts for items
- Weather-based booking recommendations
- Send weather notifications to booked players
- Dynamic pricing integration
- Multi-course view (Confirmed) — Combined dashboard showing all courses for clubs with multiple courses
Open Questions
- Booking modification: RESOLVED — Admin audience follows role gates: starter = view + check-in only; proshop = cancel; manager/admin = move + cancel. Enforced on booking cancel/reschedule endpoints for
teetime-adminaudience. Multi-course view: Should there be a combined view showing all courses for a club?RESOLVED: Yes — Add combined multi-course view for clubs with multiple courses.- Rate simulation: RESOLVED — Implement scenario-based preview (POST
/admin/courses/:courseId/rules/rate/preview) returning the winning rule + evaluation list for visitor/member cases (date/time/ball count/gender/age/classification/public-holiday). Notifications: Should creating a block that affects bookings trigger player notifications?RESOLVED: Yes — Notify impacted players; control via admin panel with per-club configuration.- Integration: How should this integrate with POS systems for walk-in bookings?
- Weather data refresh: How frequently should weather data be refreshed? Real-time for current conditions, hourly for forecasts?
- Playing score algorithm: Should the playing score algorithm be configurable per club based on their member preferences?
- Cart tracking: Should carts have GPS tracking integration for real-time location on the course?
- Items inventory: Should additional items support real-time inventory deduction and low-stock alerts?
- Weather-based pricing: Should dynamic pricing integrate with weather conditions (discounts on poor weather days)?
Technical Dependencies
Frontend
- React 18+
- Ant Design 5.x
- React Query for data fetching
- WebSocket client for real-time updates
- Chart.js or similar for analytics
Backend
- NestJS API endpoints
- PostgreSQL database
- Redis for caching (weather data)
- WebSocket server (Socket.io)
- Search & Enrichment Services integration
External Services
- Weather API (via Search & Enrichment Services)
- SMS provider (for notifications)
- Email provider (for notifications)
- PDF generation service (starter sheets)
Revision History
| Date | Author | Changes |
|---|---|---|
| 2025-12-09 | Rudi Haarhoff | Initial implementation plan |
| 2025-12-09 | Rudi Haarhoff | Added phases 5-7 for weather, carts, items |
| 2025-12-09 | Rudi Haarhoff | Expanded to 14 phases covering all features |
| 2025-12-09 | Rudi Haarhoff | Split from main spec into focused document |
| 2025-12-10 | Rudi Haarhoff | Added runway snapshot (quality gates, telemetry, flags) |
| 2025-12-10 | Rudi Haarhoff | Phase 6 backend: Prisma models, repositories, controller, tests |
| 2025-12-10 | Rudi Haarhoff | Full assessment: Phases 1-6 marked COMPLETE with component details |
| 2025-12-10 | Rudi Haarhoff | Phase 7 assessment: SSE exists, WebSocket/check-in/pace not implemented |
| 2025-12-10 | Rudi Haarhoff | Phase 8 COMPLETE: PlayerProfilePopover, PlayerStatsRepository, PlayerFlagsAdminController |
| 2025-12-11 | Rudi Haarhoff | Phase 7 IN PROGRESS: BookingCheckInAdminController, BookingQrController, StarterView.tsx |
| 2025-12-11 | Rudi Haarhoff | Phase 7 Gateway Wiring: TeeSheetGateway + PaceAlertService wired to app.module, WsAdapter in main.ts, check-in events publishing |
| 2025-12-11 | Rudi Haarhoff | Phase 11 COMPLETE: Documented comprehensive TournamentsModule (13 services, 60+ endpoints, 10 formats, matchplay, handicap integration) |
| 2025-12-11 | Rudi Haarhoff | Phase 7 Assessment Update: WebSocket gateway confirmed; check-in + pace flows live; remaining gaps: FCM push wiring, BookingController event fan-out |
| 2025-12-11 | Rudi Haarhoff | Phase 7 Role Policy: Admin booking cancel/reschedule gated by role (starter=view/check-in, proshop=cancel, manager/admin=move+cancel) |
| 2025-12-11 | Rudi Haarhoff | Decision: Multi-course view confirmed for Phase 14 — combined dashboard for clubs with multiple courses |
| 2025-12-11 | Rudi Haarhoff | Rate Preview: Added scenario-based rate preview endpoint (/admin/courses/:courseId/rules/rate/preview) and resolved open question |
| 2025-12-11 | Rudi Haarhoff | Decision: Blocks that impact bookings trigger player notifications; toggles live in admin panel and configurable per club |
| 2025-12-11 | Rudi Haarhoff | Phase 9 IN PROGRESS: WaitlistService, SlotOfferService, WaitlistController, WaitlistExpiryCron implemented; BookingController integration live; WebSocket events added |
| 2025-12-11 | Rudi Haarhoff | Phase 9 COMPLETE: DB-backed offers (WaitlistOffer model), per-course settings (CourseWaitlistSettings), Prometheus metrics, WaitlistAdminController, Admin UI (WaitlistSettingsTab, WaitlistAnalyticsTab with Storybook) |
| 2025-12-11 | Rudi Haarhoff | Phase 10 Assessment: ~70% complete; TeeTimeMessagingService, multi-channel providers, bulk notifications exist; CRITICAL gap: BookingReminderScheduler missing |
| 2025-12-11 | Rudi Haarhoff | Phase 10 Booking Reminders: BookingReminderScheduler implemented with cron, 24h/2h windows, UpcomingBookingsResolver port; reminder tracking fields on Booking model |
| 2025-12-11 | Rudi Haarhoff | Phase 10 Build Verification: tee-time-services + teetime-backend builds green; UpcomingBookingsResolver wired; RulesEngine export added; type fixes in course-rate-rules-admin; migration 20251211_booking_reminder_tracking pending apply |
| 2025-12-12 | Rudi Haarhoff | Phase 10 schedulers/listeners moved to events-worker; migration applied; BookingDetailsResolver + WeatherAffectedBookingsResolver implemented in worker; backend providers pruned to avoid duplicates |
| 2025-12-12 | Rudi Haarhoff | Updated statuses: Phase 7 marked complete (push pending), Waitlist UI wiring marked pending, Weather alert flow reflects live CourseWeatherController, clarified Phase 10 remaining work |
| 2025-12-12 | Rudi Haarhoff | Wired waitlist settings/analytics into TeeSheetDashboard; Phase 9 marked complete; parity notes refreshed |
| 2025-12-11 | Rudi Haarhoff | Phase 13 Course Conditions: Added CourseStatusService with state machine (OPEN→CONDITIONAL→CLOSED), playability thresholds with hysteresis, manual overrides, CourseConditionLog audit trail; WeatherAlertScheduler now integrates with CourseStatusService for automatic status transitions |
| 2025-12-11 | Rudi Haarhoff | Phase 10 Weather Monitoring: Documented WeatherAlertScheduler details (cron, cooldowns, env vars), WeatherMonitoringResolver port with getLastAlert() for cross-process cooldown, course deduplication, 6 unit tests |
| 2025-12-11 | Rudi Haarhoff | Renumbered phases: Phase 13 = Course Conditions (P2, in progress), Phase 14 = Advanced UX (P3), Phase 15 = Enhancements (P3) |
| 2025-12-12 | Rudi Haarhoff | Phase 13 Enhancement: Added evaluateForCourseDate() convenience method to CourseStatusService; WeatherAlertScheduler now uses this method + derives thresholds from course config; 24 unit tests for CourseStatusService (200 suites, 1901 tests total) |
| 2025-12-12 | Rudi Haarhoff | Phase 4 Settings Enforcement: Course settings now enforced in public search — TeeSheetService.getCourseTeeSheetConfig() uses overlapping date window query; SearchEngineService enforces booking channels, operating days, and time windows; MultiClubSearchService applies same config (including numberOfTees) via applyInternalConfig(); DI wiring via TeeSheetModule import |
| 2025-12-12 | Claude Assessment | Phase 13 COMPLETE: Verified CourseConditionsPanel.tsx, TeeTimeActivityService, StaffNotesService implementations; all frontend integration complete; fixed duplicate import in tee-time-services.module.ts; updated status and documentation alignment |