Files
member-console/internal/workflows/billing/schedule.go
T
cgalo5758 af8603b3cf Add billing sweep schedule and proration preview
Create Temporal billing sweep schedule, workflow and activities and
register them in the worker/start initialization. Add an HTMX preview
route and banner for plan switches (switch button now GETs a preview;
Confirm posts the switch). Extend the Stripe test mock to support
invoice previews and add integration tests for PreviewSwitch behavior.
2026-05-30 01:16:58 -05:00

88 lines
3.1 KiB
Go

package billing
import (
"context"
"fmt"
"log/slog"
"time"
"git.coopcloud.tech/wiki-cafe/member-console/internal/workflows/queues"
"go.temporal.io/sdk/client"
)
const (
// SweepScheduleID is the unique identifier for the scheduled-change sweep schedule.
SweepScheduleID = "billing-scheduled-change-sweep"
// SweepWorkflowID is the workflow ID used for sweep executions.
SweepWorkflowID = "billing-scheduled-change-sweep-workflow"
// DefaultSweepInterval is the default interval between sweeps.
DefaultSweepInterval = 1 * time.Hour
)
// ScheduleManager manages Temporal schedules for billing maintenance.
type ScheduleManager struct {
client client.Client
logger *slog.Logger
}
// NewScheduleManager creates a new ScheduleManager.
func NewScheduleManager(c client.Client, logger *slog.Logger) *ScheduleManager {
return &ScheduleManager{client: c, logger: logger}
}
// SweepScheduleConfig holds configuration for the sweep schedule.
type SweepScheduleConfig struct {
// Interval is how often to run the sweep.
Interval time.Duration
// TriggerImmediately runs the sweep immediately when creating the schedule.
TriggerImmediately bool
}
// EnsureSweepSchedule creates or updates the scheduled-change sweep schedule.
// If the schedule already exists, it updates the spec; otherwise it creates it.
func (m *ScheduleManager) EnsureSweepSchedule(ctx context.Context, cfg SweepScheduleConfig) error {
if cfg.Interval == 0 {
cfg.Interval = DefaultSweepInterval
}
scheduleClient := m.client.ScheduleClient()
handle := scheduleClient.GetHandle(ctx, SweepScheduleID)
if _, err := handle.Describe(ctx); err == nil {
m.logger.Info("updating existing billing sweep schedule", slog.Duration("interval", cfg.Interval))
if err := handle.Update(ctx, client.ScheduleUpdateOptions{
DoUpdate: func(schedule client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) {
schedule.Description.Schedule.Spec = &client.ScheduleSpec{
Intervals: []client.ScheduleIntervalSpec{{Every: cfg.Interval}},
}
schedule.Description.Schedule.Action = &client.ScheduleWorkflowAction{
ID: SweepWorkflowID,
Workflow: SweepScheduledChangesWorkflow,
TaskQueue: queues.Main,
}
return &client.ScheduleUpdate{Schedule: &schedule.Description.Schedule}, nil
},
}); err != nil {
return fmt.Errorf("failed to update sweep schedule: %w", err)
}
return nil
}
m.logger.Info("creating billing sweep schedule",
slog.Duration("interval", cfg.Interval), slog.Bool("triggerImmediately", cfg.TriggerImmediately))
if _, err := scheduleClient.Create(ctx, client.ScheduleOptions{
ID: SweepScheduleID,
Spec: client.ScheduleSpec{Intervals: []client.ScheduleIntervalSpec{{Every: cfg.Interval}}},
Action: &client.ScheduleWorkflowAction{
ID: SweepWorkflowID,
Workflow: SweepScheduledChangesWorkflow,
TaskQueue: queues.Main,
},
TriggerImmediately: cfg.TriggerImmediately,
}); err != nil {
return fmt.Errorf("failed to create sweep schedule: %w", err)
}
m.logger.Info("billing sweep schedule created", slog.String("scheduleID", SweepScheduleID))
return nil
}