mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
class_schedules rules generate concrete session rows for a rolling 28-day horizon — no cron; materialize runs idempotently on dashboard and public reads via a partial unique index on (schedule_id, starts_at). Rule delete stops future sessions; existing sessions keep bookings and overrides. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
173 lines
4.7 KiB
Go
173 lines
4.7 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
const serviceColumns = "id, tenant_id, name, duration_minutes, buffer_before_minutes, buffer_after_minutes, price_cents, created_at"
|
|
|
|
func (r *PGRepository) ListServicesByTenant(ctx context.Context, tenantID string) ([]ServiceRecord, error) {
|
|
rows, err := r.pool.Query(ctx, `
|
|
SELECT `+serviceColumns+`
|
|
FROM services
|
|
WHERE tenant_id = $1
|
|
ORDER BY created_at ASC
|
|
`, tenantID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []ServiceRecord
|
|
for rows.Next() {
|
|
var record ServiceRecord
|
|
if err := rows.Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.Name,
|
|
&record.DurationMinutes,
|
|
&record.BufferBeforeMinutes,
|
|
&record.BufferAfterMinutes,
|
|
&record.PriceCents,
|
|
&record.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
records = append(records, record)
|
|
}
|
|
return records, rows.Err()
|
|
}
|
|
|
|
func (r *PGRepository) GetServiceByID(ctx context.Context, serviceID string) (ServiceRecord, error) {
|
|
var record ServiceRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT `+serviceColumns+`
|
|
FROM services
|
|
WHERE id = $1
|
|
`, serviceID).Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.Name,
|
|
&record.DurationMinutes,
|
|
&record.BufferBeforeMinutes,
|
|
&record.BufferAfterMinutes,
|
|
&record.PriceCents,
|
|
&record.CreatedAt,
|
|
)
|
|
return record, err
|
|
}
|
|
|
|
func (r *PGRepository) CreateService(ctx context.Context, params CreateServiceParams) (ServiceRecord, error) {
|
|
var record ServiceRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
INSERT INTO services (tenant_id, name, duration_minutes, buffer_before_minutes, buffer_after_minutes, price_cents)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING `+serviceColumns+`
|
|
`, params.TenantID, params.Name, params.DurationMinutes, params.BufferBeforeMinutes, params.BufferAfterMinutes, params.PriceCents).Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.Name,
|
|
&record.DurationMinutes,
|
|
&record.BufferBeforeMinutes,
|
|
&record.BufferAfterMinutes,
|
|
&record.PriceCents,
|
|
&record.CreatedAt,
|
|
)
|
|
return record, err
|
|
}
|
|
|
|
func (r *PGRepository) UpdateService(ctx context.Context, serviceID string, params UpdateServiceParams) (ServiceRecord, error) {
|
|
var record ServiceRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
UPDATE services
|
|
SET name = COALESCE($2, name),
|
|
duration_minutes = COALESCE($3, duration_minutes),
|
|
buffer_before_minutes = COALESCE($4, buffer_before_minutes),
|
|
buffer_after_minutes = COALESCE($5, buffer_after_minutes),
|
|
price_cents = COALESCE($6, price_cents)
|
|
WHERE id = $1
|
|
RETURNING `+serviceColumns+`
|
|
`, serviceID, params.Name, params.DurationMinutes, params.BufferBeforeMinutes, params.BufferAfterMinutes, params.PriceCents).Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.Name,
|
|
&record.DurationMinutes,
|
|
&record.BufferBeforeMinutes,
|
|
&record.BufferAfterMinutes,
|
|
&record.PriceCents,
|
|
&record.CreatedAt,
|
|
)
|
|
return record, err
|
|
}
|
|
|
|
func (r *PGRepository) DeleteService(ctx context.Context, serviceID string) error {
|
|
_, err := r.pool.Exec(ctx, `DELETE FROM services WHERE id = $1`, serviceID)
|
|
return err
|
|
}
|
|
|
|
func (r *PGRepository) ListAvailabilityRulesByTenant(ctx context.Context, tenantID string) ([]AvailabilityRuleRecord, error) {
|
|
rows, err := r.pool.Query(ctx, `
|
|
SELECT id, tenant_id, staff_id, day_of_week, starts_local, ends_local
|
|
FROM availability_rules
|
|
WHERE tenant_id = $1
|
|
ORDER BY day_of_week ASC, starts_local ASC
|
|
`, tenantID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []AvailabilityRuleRecord
|
|
for rows.Next() {
|
|
var record AvailabilityRuleRecord
|
|
if err := rows.Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.StaffID,
|
|
&record.DayOfWeek,
|
|
&record.StartsLocal,
|
|
&record.EndsLocal,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
records = append(records, record)
|
|
}
|
|
return records, rows.Err()
|
|
}
|
|
|
|
func (r *PGRepository) ListClassSessionsByTenant(ctx context.Context, tenantID string, from time.Time, limit int) ([]ClassSessionRecord, error) {
|
|
rows, err := r.pool.Query(ctx, `
|
|
SELECT cs.id, cs.tenant_id, cs.template_id, cs.location_id, ct.title, cs.starts_at, cs.ends_at, cs.capacity, cs.schedule_id
|
|
FROM class_sessions cs
|
|
INNER JOIN class_templates ct ON ct.id = cs.template_id
|
|
WHERE cs.tenant_id = $1 AND cs.starts_at >= $2
|
|
ORDER BY cs.starts_at ASC
|
|
LIMIT $3
|
|
`, tenantID, from, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []ClassSessionRecord
|
|
for rows.Next() {
|
|
var record ClassSessionRecord
|
|
if err := rows.Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.TemplateID,
|
|
&record.LocationID,
|
|
&record.Title,
|
|
&record.StartsAt,
|
|
&record.EndsAt,
|
|
&record.Capacity,
|
|
&record.ScheduleID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
records = append(records, record)
|
|
}
|
|
return records, rows.Err()
|
|
}
|