Files
Bookra/apps/backend/internal/domain/models.go
T
Tomas DvorakandDevin cdad7875e1 fix(reminders): lifecycle-aware jobs and dispatch skip guard
The reminder pipeline fired regardless of what happened to the booking
after creation: cancelled customers got reminded about bookings that
weren't happening, reschedules kept the old reminder time, and
waitlisted bookings armed jobs too. Pending jobs are now cancelled on
both cancel paths, deleted and re-armed on reschedule, and dispatch
joins bookings.status to skip anything not confirmed. Reschedule also
gained the same availability-window and blocked-day validation as
booking creation, closing a 3am-reschedule hole via the manage URL.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 12:47:07 +02:00

647 lines
22 KiB
Go

package domain
import "time"
type Principal struct {
Subject string `json:"subject"`
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
Role string `json:"role"`
}
type DashboardKPI struct {
Code string `json:"code"`
Label string `json:"label"`
Value string `json:"value"`
Change string `json:"change,omitempty"`
Trend string `json:"trend,omitempty"`
}
type UpcomingBooking struct {
ID string `json:"id"`
Reference string `json:"reference"`
CustomerName string `json:"customerName"`
CustomerEmail string `json:"customerEmail"`
Service string `json:"service,omitempty"`
Location string `json:"location,omitempty"`
StaffName string `json:"staffName,omitempty"`
Notes string `json:"notes,omitempty"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
Status string `json:"status"`
Label string `json:"label,omitempty"`
}
type WidgetSnippet struct {
Kind string `json:"kind"`
Code string `json:"code"`
}
type TrackingStatus struct {
Provider string `json:"provider"`
Connected bool `json:"connected"`
SiteID string `json:"siteId,omitempty"`
Message string `json:"message,omitempty"`
}
type DashboardSummary struct {
TenantName string `json:"tenantName"`
TenantSlug string `json:"tenantSlug"`
Locale string `json:"locale"`
Timezone string `json:"timezone"`
PlanCode string `json:"planCode"`
PublicBookingURL string `json:"publicBookingUrl"`
SetupCompletion int `json:"setupCompletion"`
KPIs []DashboardKPI `json:"kpis"`
UpcomingBookings []UpcomingBooking `json:"upcomingBookings"`
AllBookings []UpcomingBooking `json:"allBookings"`
WidgetSnippets []WidgetSnippet `json:"widgetSnippets"`
Tracking TrackingStatus `json:"tracking"`
}
type BrandProfile struct {
Name string `json:"name"`
SiteURL string `json:"siteUrl,omitempty"`
LogoURL string `json:"logoUrl,omitempty"`
PrimaryColor string `json:"primaryColor,omitempty"`
}
type TenantBootstrap struct {
TenantID string `json:"tenantId"`
TenantName string `json:"tenantName"`
TenantSlug string `json:"tenantSlug,omitempty"`
Preset string `json:"preset"`
Locale string `json:"locale"`
Timezone string `json:"timezone"`
PlanCode string `json:"planCode,omitempty"`
OnboardingCompleted bool `json:"onboardingCompleted"`
Brand BrandProfile `json:"brand"`
CurrentUser Principal `json:"currentUser"`
}
type TeamInviteRequest struct {
Email string `json:"email"`
Role string `json:"role,omitempty"`
}
type AvailabilityBlockRequest struct {
DayOfWeek int `json:"dayOfWeek"`
StartsLocal string `json:"startsLocal"`
EndsLocal string `json:"endsLocal"`
Busy bool `json:"busy,omitempty"`
}
type BookingDefaultsRequest struct {
ServiceName string `json:"serviceName"`
DurationMinutes int `json:"durationMinutes"`
BufferBeforeMinutes int `json:"bufferBeforeMinutes"`
BufferAfterMinutes int `json:"bufferAfterMinutes"`
CancelWindowHours int `json:"cancelWindowHours"`
}
type OnboardTenantRequest struct {
Name string `json:"name"`
Slug string `json:"slug"`
Preset string `json:"preset"`
Locale string `json:"locale"`
Timezone string `json:"timezone"`
Brand BrandProfile `json:"brand"`
LocationName string `json:"locationName"`
BookingDefaults BookingDefaultsRequest `json:"bookingDefaults"`
AvailabilityBlocks []AvailabilityBlockRequest `json:"availabilityBlocks"`
TeamInvites []TeamInviteRequest `json:"teamInvites"`
}
type TimeSlot struct {
ServiceID *string `json:"serviceId,omitempty"`
ClassSessionID *string `json:"classSessionId,omitempty"`
StaffID *string `json:"staffId,omitempty"`
StaffName *string `json:"staffName,omitempty"`
LocationID *string `json:"locationId,omitempty"`
StartsAt string `json:"startsAt"`
EndsAt string `json:"endsAt"`
Mode string `json:"mode"`
Label string `json:"label"`
RemainingCapacity *int32 `json:"remainingCapacity,omitempty"`
}
type PublicAvailability struct {
TenantSlug string `json:"tenantSlug"`
Timezone string `json:"timezone"`
Locale string `json:"locale"`
Slots []TimeSlot `json:"slots"`
}
type PublicService struct {
ID string `json:"id"`
Name string `json:"name"`
DurationMinutes int `json:"durationMinutes"`
PriceCents int `json:"priceCents"`
}
type PublicStaff struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
}
type PublicCatalog struct {
TenantSlug string `json:"tenantSlug"`
Services []PublicService `json:"services"`
Staff []PublicStaff `json:"staff"`
}
type CreateBookingRequest struct {
TenantSlug string `json:"tenantSlug"`
BookingMode string `json:"bookingMode"`
ServiceID *string `json:"serviceId,omitempty"`
ClassSessionID *string `json:"classSessionId,omitempty"`
StaffID *string `json:"staffId,omitempty"`
LocationID *string `json:"locationId,omitempty"`
CustomerName string `json:"customerName"`
CustomerEmail string `json:"customerEmail"`
CustomerPhone string `json:"customerPhone,omitempty"`
Notes string `json:"notes"`
StartsAt string `json:"startsAt"`
EndsAt string `json:"endsAt"`
}
type CreateBookingResponse struct {
BookingID string `json:"bookingId"`
Reference string `json:"reference"`
Status string `json:"status"`
ManageURL string `json:"manageUrl"`
}
type PlanEntitlements struct {
MaxLocations int `json:"maxLocations"`
MaxStaff int `json:"maxStaff"`
MaxBookingsMonth int `json:"maxBookingsMonth"` // -1 = unlimited
EmailReminders bool `json:"emailReminders"`
AdvancedReporting bool `json:"advancedReporting"`
WidgetEmbedding bool `json:"widgetEmbedding"`
UmamiTracking bool `json:"umamiTracking"`
APIAccess bool `json:"apiAccess"`
DedicatedManager bool `json:"dedicatedManager"`
SMSAvailable bool `json:"smsAvailable"`
}
type PlanPricing struct {
MonthlyAmountCents int `json:"monthlyAmountCents"`
YearlyAmountCents int `json:"yearlyAmountCents"`
MonthlyFormatted string `json:"monthlyFormatted"`
YearlyFormatted string `json:"yearlyFormatted"`
YearlySavings string `json:"yearlySavings"`
YearlySavingsPercent int `json:"yearlySavingsPercent"`
}
type PlanDisplayPrice struct {
Currency string `json:"currency"`
AmountCents int `json:"amountCents"`
Formatted string `json:"formatted"`
YearlyAmountCents int `json:"yearlyAmountCents,omitempty"`
YearlyFormatted string `json:"yearlyFormatted,omitempty"`
YearlySavings string `json:"yearlySavings,omitempty"`
YearlySavingsPercent int `json:"yearlySavingsPercent,omitempty"`
}
type SubscriptionSnapshot struct {
TenantID string `json:"tenantId"`
Provider string `json:"provider"`
CustomerID string `json:"customerId"`
SubscriptionID string `json:"subscriptionId"`
Status string `json:"status"`
PlanCode string `json:"planCode"`
Currency string `json:"currency"`
PriceID string `json:"priceId"`
CancelAtPeriodEnd bool `json:"cancelAtPeriodEnd"`
CurrentPeriodStart *time.Time `json:"currentPeriodStart,omitempty"`
CurrentPeriodEnd *time.Time `json:"currentPeriodEnd,omitempty"`
PaymentMethodBrand string `json:"paymentMethodBrand,omitempty"`
PaymentMethodLast4 string `json:"paymentMethodLast4,omitempty"`
Entitlements PlanEntitlements `json:"entitlements"`
DisplayPrices []PlanDisplayPrice `json:"displayPrices"`
TrialDays int `json:"trialDays"`
LastSyncedAt *time.Time `json:"lastSyncedAt,omitempty"`
CheckoutURLAvailable bool `json:"checkoutUrlAvailable"`
SyncAvailable bool `json:"syncAvailable"`
PortalAvailable bool `json:"portalAvailable"`
}
type CheckoutSessionRequest struct {
PlanCode string `json:"planCode"`
Currency string `json:"currency,omitempty"`
BillingInterval string `json:"billingInterval,omitempty"` // "monthly" or "yearly", defaults to "monthly"
}
type CheckoutLaunchResponse struct {
CheckoutURL string `json:"checkoutUrl,omitempty"`
SuccessRedirectURL string `json:"successRedirectUrl,omitempty"`
CancelRedirectURL string `json:"cancelRedirectUrl,omitempty"`
}
type PortalSessionResponse struct {
URL string `json:"url"`
}
type DispatchReminderJobsRequest struct {
Limit int `json:"limit,omitempty"`
}
type DispatchReminderJobsResponse struct {
ProcessedCount int `json:"processedCount"`
SentCount int `json:"sentCount"`
FailedCount int `json:"failedCount"`
SkippedCount int `json:"skippedCount"`
}
// ============================================
// LOCATION / ZONE MODELS
// ============================================
type Location struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Name string `json:"name"`
Type string `json:"type"` // room, private, hall, etc.
Capacity int `json:"capacity"`
Timezone string `json:"timezone"`
CreatedAt time.Time `json:"createdAt"`
}
type DashboardBookingRequest struct {
CustomerName string `json:"customerName" binding:"required"`
CustomerEmail string `json:"customerEmail" binding:"required,email"`
CustomerPhone string `json:"customerPhone,omitempty"`
Service string `json:"service,omitempty"`
StartsAt string `json:"startsAt" binding:"required"`
Notes string `json:"notes,omitempty"`
}
type CreateLocationRequest struct {
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required"`
Capacity int `json:"capacity"`
}
type UpdateLocationRequest struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Capacity int `json:"capacity,omitempty"`
}
// ============================================
// SERVICE MODELS
// ============================================
type Service struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Name string `json:"name"`
DurationMinutes int `json:"durationMinutes"`
BufferBeforeMinutes int `json:"bufferBeforeMinutes"`
BufferAfterMinutes int `json:"bufferAfterMinutes"`
PriceCents int `json:"priceCents"`
CreatedAt time.Time `json:"createdAt"`
}
type CreateServiceRequest struct {
Name string `json:"name" binding:"required"`
DurationMinutes int `json:"durationMinutes" binding:"required"`
BufferBeforeMinutes int `json:"bufferBeforeMinutes,omitempty"`
BufferAfterMinutes int `json:"bufferAfterMinutes,omitempty"`
PriceCents int `json:"priceCents,omitempty"`
}
type UpdateServiceRequest struct {
Name *string `json:"name,omitempty"`
DurationMinutes *int `json:"durationMinutes,omitempty"`
BufferBeforeMinutes *int `json:"bufferBeforeMinutes,omitempty"`
BufferAfterMinutes *int `json:"bufferAfterMinutes,omitempty"`
PriceCents *int `json:"priceCents,omitempty"`
}
// ============================================
// STAFF MODELS
// ============================================
type StaffMember struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
LocationID *string `json:"locationId,omitempty"`
DisplayName string `json:"displayName"`
Active bool `json:"active"`
CreatedAt time.Time `json:"createdAt"`
}
type CreateStaffRequest struct {
DisplayName string `json:"displayName" binding:"required"`
LocationID *string `json:"locationId,omitempty"`
Active *bool `json:"active,omitempty"` // defaults to true
}
type UpdateStaffRequest struct {
DisplayName *string `json:"displayName,omitempty"`
LocationID *string `json:"locationId,omitempty"` // empty string clears the assignment
Active *bool `json:"active,omitempty"`
}
// ============================================
// BLOCKED DAYS / AVAILABILITY EXCEPTION MODELS
// ============================================
type BlockedDay struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Date time.Time `json:"date"`
Reason string `json:"reason"`
Type string `json:"type"` // full, partial
StaffID *string `json:"staffId,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
type CreateBlockedDayRequest struct {
Date string `json:"date" binding:"required"` // RFC3339
Reason string `json:"reason" binding:"required"`
Type string `json:"type" binding:"required"` // full, partial
StaffID *string `json:"staffId,omitempty"`
}
type UpdateBlockedDayRequest struct {
Reason string `json:"reason,omitempty"`
Type string `json:"type,omitempty"`
}
// ============================================
// CLASS MODELS
// ============================================
type ClassTemplate struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Title string `json:"title"`
DurationMinutes int `json:"durationMinutes"`
Capacity int `json:"capacity"`
CreatedAt time.Time `json:"createdAt"`
}
type UpsertClassTemplateRequest struct {
Title string `json:"title" binding:"required"`
DurationMinutes int `json:"durationMinutes" binding:"required"`
Capacity int `json:"capacity" binding:"required"`
}
type ClassSession struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
TemplateID string `json:"templateId"`
TemplateTitle string `json:"templateTitle"`
LocationID *string `json:"locationId,omitempty"`
LocationName string `json:"locationName,omitempty"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
Capacity int `json:"capacity"`
BookedCount int `json:"bookedCount"`
}
type CreateClassSessionRequest struct {
TemplateID string `json:"templateId" binding:"required"`
LocationID *string `json:"locationId,omitempty"`
StartsAt string `json:"startsAt" binding:"required"` // RFC3339
Capacity *int `json:"capacity,omitempty"`
}
// ============================================
// CUSTOMER MODELS
// ============================================
type Customer struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Name string `json:"name"`
Email string `json:"email"`
Phone *string `json:"phone,omitempty"`
Status string `json:"status"` // active, inactive, vip
BookingsCount int `json:"bookingsCount"`
LastBookingAt *time.Time `json:"lastBookingAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
Notes string `json:"notes,omitempty"`
}
type CreateCustomerRequest struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Phone *string `json:"phone,omitempty"`
Status string `json:"status,omitempty"` // defaults to active
Notes string `json:"notes,omitempty"`
}
type UpdateCustomerRequest struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Phone *string `json:"phone,omitempty"`
Status string `json:"status,omitempty"`
Notes string `json:"notes,omitempty"`
}
// ============================================
// CUSTOMER BOOKING MANAGEMENT MODELS
// ============================================
type CustomerBookingView struct {
Reference string `json:"reference"`
CustomerName string `json:"customerName"`
CustomerEmail string `json:"customerEmail"`
Service string `json:"service"`
BusinessName string `json:"businessName"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
Location string `json:"location"`
Status string `json:"status"`
Notes string `json:"notes,omitempty"`
}
type RescheduleBookingRequest struct {
NewStartsAt string `json:"newStartsAt" binding:"required"` // RFC3339
NewEndsAt string `json:"newEndsAt" binding:"required"` // RFC3339
Reason string `json:"reason,omitempty"`
}
type CancelBookingRequest struct {
Reason string `json:"reason,omitempty"`
}
// ============================================
// ADMIN MODELS
// ============================================
type AdminDashboardStats struct {
TotalTenants int64 `json:"totalTenants"`
TotalUsers int64 `json:"totalUsers"`
ActiveSubscriptions int64 `json:"activeSubscriptions"`
TrialSubscriptions int64 `json:"trialSubscriptions"`
BookingsThisMonth int64 `json:"bookingsThisMonth"`
RevenueThisMonthCents int64 `json:"revenueThisMonthCents"`
}
type AdminTenantList struct {
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
Tenants []AdminTenant `json:"tenants"`
}
type AdminTenant struct {
ID string `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
PlanCode string `json:"planCode"`
SubscriptionStatus string `json:"subscriptionStatus"`
BillingProvider string `json:"billingProvider"`
}
type AdminUserList struct {
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
Users []AdminUser `json:"users"`
}
type AdminUser struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name,omitempty"`
EmailVerified bool `json:"emailVerified"`
Provider string `json:"provider"`
Role string `json:"role"`
CreatedAt time.Time `json:"createdAt"`
}
type AdminLoginRequest struct {
Email string `json:"email" binding:"required,email"`
Key string `json:"key" binding:"required"`
}
type UpdateUserRoleRequest struct {
Role string `json:"role" binding:"required,oneof=user admin superadmin"`
}
// ============================================
// WORKING HOURS MODELS
// ============================================
type WorkingHours struct {
DayOfWeek int `json:"dayOfWeek"` // 0=Sunday, 1=Monday, etc.
Open string `json:"open"` // HH:MM format
Close string `json:"close"` // HH:MM format
IsOpen bool `json:"isOpen"`
}
type UpdateWorkingHoursRequest struct {
Open string `json:"open,omitempty"`
Close string `json:"close,omitempty"`
IsOpen *bool `json:"isOpen,omitempty"`
}
// ============================================
// EMAIL TEMPLATE MODELS
// ============================================
type EmailTemplate struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Type string `json:"type"` // booking_confirmation, reminder, cancellation, etc.
Subject string `json:"subject"`
BodyHTML string `json:"bodyHtml"`
BodyText string `json:"bodyText"`
IsEnabled bool `json:"isEnabled"`
}
type UpdateBrandRequest struct {
Name string `json:"name,omitempty"`
SiteURL string `json:"siteUrl,omitempty"`
LogoURL string `json:"logoUrl,omitempty"`
PrimaryColor string `json:"primaryColor" binding:"required"`
}
type UpdateEmailTemplateRequest struct {
Type string `json:"type" binding:"required"`
Subject string `json:"subject"`
Body string `json:"body"`
IsEnabled *bool `json:"isEnabled,omitempty"`
}
type SendEmailRequest struct {
To string `json:"to" binding:"required,email"`
Subject string `json:"subject" binding:"required"`
Body string `json:"body" binding:"required"`
Data map[string]string `json:"data,omitempty"` // Template variables
}
type EmailNotification struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
BookingID string `json:"bookingId,omitempty"`
Channel string `json:"channel"` // email, sms
Type string `json:"type"` // confirmation, reminder, cancellation
Recipient string `json:"recipient"`
Status string `json:"status"` // pending, sent, failed
SentAt *time.Time `json:"sentAt,omitempty"`
Error string `json:"error,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
// ============================================
// SMS MODELS
// ============================================
type SMSSettings struct {
Enabled bool `json:"enabled"`
SenderName string `json:"senderName,omitempty"`
MonthlyLimit int `json:"monthlyLimit,omitempty"`
MessagesSent int `json:"messagesSent"`
TotalCostCents int `json:"totalCostCents"`
Available bool `json:"available"`
}
type UpdateSMSSettingsRequest struct {
Enabled bool `json:"enabled"`
SenderName string `json:"senderName,omitempty"`
MonthlyLimit int `json:"monthlyLimit,omitempty"`
}
type SendSMSRequest struct {
To string `json:"to" binding:"required"`
Body string `json:"body" binding:"required,max=1000"`
}
type SendSMSResponse struct {
LogID string `json:"logId"`
MessageID string `json:"messageId,omitempty"`
RequestID string `json:"requestId,omitempty"`
Status string `json:"status"`
CostCents int `json:"costCents"`
}
type SMSUsageLog struct {
ID string `json:"id"`
RecipientPhone string `json:"recipientPhone"`
MessageBody string `json:"messageBody,omitempty"`
Status string `json:"status"`
CostCents int `json:"costCents"`
CreatedAt time.Time `json:"createdAt"`
}
type SMSUsageReport struct {
YearMonth string `json:"yearMonth"`
MessageCount int `json:"messageCount"`
TotalCostCents int `json:"totalCostCents"`
StripeInvoiceID string `json:"stripeInvoiceId,omitempty"`
InvoiceSentAt *time.Time `json:"invoiceSentAt,omitempty"`
}
type SMSInvoiceBatchResponse struct {
YearMonth string `json:"yearMonth"`
ProcessedCount int `json:"processedCount"`
FailedCount int `json:"failedCount"`
}