// Package scheduling defines the platform-wide scheduling and timeslot seam: // wire shapes, DTOs, domain events, and client interfaces for booking services, // appointments, and capacity-based timeslots. // // Like platform/inventory and platform/order, this package depends only on the // stdlib and shared platform types (money, uid, event). package scheduling import ( "encoding/json" "errors" "time" ) var ( // ErrSlotUnavailable is returned when a requested slot has no remaining capacity. ErrSlotUnavailable = errors.New("scheduling: requested timeslot is not available") // ErrHoldExpired is returned when attempting to confirm or extend an expired hold. ErrHoldExpired = errors.New("scheduling: hold has expired") // ErrBookingNotFound is returned when the specified booking is not found. ErrBookingNotFound = errors.New("scheduling: booking not found") // ErrResourceNotFound is returned when the specified resource or schedule is not found. ErrResourceNotFound = errors.New("scheduling: resource not found") // ErrOutsideWorkingHours is returned when a hold/booking window does not // lie inside the resource's configured working hours (weekly rules or a // date override). Guards the hold/direct-booking/reschedule appliers so // API clients cannot reserve capacity outside bookable windows. ErrOutsideWorkingHours = errors.New("scheduling: window is outside working hours") ) // Slot represents a discrete bookable window of time. type Slot struct { StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Capacity int `json:"capacity"` Remaining int `json:"remaining"` } // ResourceConfig defines a bookable resource (staff member, room, bay, equipment). type ResourceConfig struct { ID string `json:"id"` Name string `json:"name"` LocationID string `json:"location_id,omitempty"` Timezone string `json:"timezone"` Capacity int `json:"capacity"` // max concurrent bookings per slot (default 1) SlotDuration time.Duration `json:"slot_duration"` // standard duration if not overridden BufferBefore time.Duration `json:"buffer_before"` // buffer time before appointments BufferAfter time.Duration `json:"buffer_after"` // buffer time after appointments MinLeadTime time.Duration `json:"min_lead_time"` // minimum advance notice required MaxAdvanceTime time.Duration `json:"max_advance_time"` // maximum booking window into the future } // WorkingHours defines recurring weekly schedule rules (e.g. Mon-Fri 09:00-17:00). type WorkingHours struct { DayOfWeek int `json:"day_of_week"` // 0 = Sunday, 1 = Monday, ..., 6 = Saturday StartTime string `json:"start_time"` // "09:00" (HH:MM) EndTime string `json:"end_time"` // "17:00" (HH:MM) } // ScheduleOverride defines a specific blackout (holiday, sick leave) or custom opening. type ScheduleOverride struct { Date string `json:"date"` // "2026-09-01" (YYYY-MM-DD) IsClosed bool `json:"is_closed"` // true for complete closure / blackout CustomStart string `json:"custom_start,omitempty"` // custom opening if not closed CustomEnd string `json:"custom_end,omitempty"` Reason string `json:"reason,omitempty"` } // HoldRequest is the payload to request a temporary TTL reservation on a slot. type HoldRequest struct { ResourceID string `json:"resource_id"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Quantity int `json:"quantity"` // number of capacity units (default 1) CartID string `json:"cart_id,omitempty"` TTLSeconds int `json:"ttl_seconds,omitempty"` // hold duration in seconds (default 900 = 15m) } // HoldResult is returned when a slot hold is successfully placed. type HoldResult struct { HoldID string `json:"hold_id"` ResourceID string `json:"resource_id"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Quantity int `json:"quantity"` ExpiresAt time.Time `json:"expires_at"` } // BookingStatus represents the lifecycle state of a booking. type BookingStatus string const ( BookingStatusConfirmed BookingStatus = "confirmed" BookingStatusCancelled BookingStatus = "cancelled" BookingStatusCompleted BookingStatus = "completed" BookingStatusNoShow BookingStatus = "no_show" ) // BookingDetails holds a confirmed booking record. type BookingDetails struct { BookingID string `json:"booking_id"` HoldID string `json:"hold_id,omitempty"` OrderID string `json:"order_id,omitempty"` ResourceID string `json:"resource_id"` ResourceName string `json:"resource_name,omitempty"` SKU string `json:"sku,omitempty"` ServiceName string `json:"service_name,omitempty"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Quantity int `json:"quantity"` CustomerEmail string `json:"customer_email,omitempty"` CustomerName string `json:"customer_name,omitempty"` CustomerPhone string `json:"customer_phone,omitempty"` Notes string `json:"notes,omitempty"` Status BookingStatus `json:"status"` CreatedAt time.Time `json:"created_at"` } // BookingConfirmedEvent is the payload for event.SchedulingBookingConfirmed. type BookingConfirmedEvent struct { BookingID string `json:"booking_id"` OrderID string `json:"order_id"` ResourceID string `json:"resource_id"` SKU string `json:"sku,omitempty"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Quantity int `json:"quantity"` CustomerEmail string `json:"customer_email,omitempty"` CustomerName string `json:"customer_name,omitempty"` } // Encode serializes the event payload. func (e BookingConfirmedEvent) Encode() ([]byte, error) { return json.Marshal(e) } // DecodeBookingConfirmed parses the event payload. func DecodeBookingConfirmed(raw []byte) (BookingConfirmedEvent, error) { var e BookingConfirmedEvent err := json.Unmarshal(raw, &e) return e, err } // BookingCancelledEvent is the payload for event.SchedulingBookingCancelled. type BookingCancelledEvent struct { BookingID string `json:"booking_id"` OrderID string `json:"order_id,omitempty"` Reason string `json:"reason,omitempty"` } // Encode serializes the event payload. func (e BookingCancelledEvent) Encode() ([]byte, error) { return json.Marshal(e) } // DecodeBookingCancelled parses the event payload. func DecodeBookingCancelled(raw []byte) (BookingCancelledEvent, error) { var e BookingCancelledEvent err := json.Unmarshal(raw, &e) return e, err }