mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
Services and staff existed as schema + booking FK targets but had no management surface. Adds full CRUD: repository (pgx + in-memory with matching FK semantics), catalog service validation with tenant isolation, MaxStaff entitlement enforcement, protected API routes, OpenAPI schemas, and dashboard screens for both entities. Dashboard summary now resolves staff names on bookings and computes setup completion from real configured state. Booking modal selects from the tenant's actual services; blocked days can be scoped per staff member. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
586 lines
20 KiB
Go
586 lines
20 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"`
|
|
}
|
|
|
|
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"`
|
|
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 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"`
|
|
}
|
|
|
|
// ============================================
|
|
// 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"`
|
|
}
|
|
|
|
// ============================================
|
|
// 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"`
|
|
}
|