Bump go-cart-actor: scheduling cluster service + booking-hold cart/checkout seam
Deploys cmd/scheduling as a 3-replica actor cluster (shared RWX commerce-data event logs, gRPC mesh via POD_IP + actor-pool label, edge /api/scheduling pass-through, fail-closed mcpauth on admin/MCP). cart/checkout get SCHEDULING_SERVICE_URL so line removal releases holds; backoffice gets SCHEDULING_URL for the admin proxy. Platform seam: order.Line.Properties carries booking_* fields through order.created; scheduling.booking_confirmed/ cancelled event types. Compose + prep-dev-data wire the schedules dir. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
1 parent
7f73d6885b
commit
b16820bf62
5 files changed
+542
-10
No files matched your search
@@ -0,0 +1,157 @@
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user