mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
POST /v1/internal/jobs/reports/dispatch emails each tenant owner a trailing-window digest (bookings, revenue, per-service and per-staff breakdown) localized to the tenant locale; zero-activity tenants are skipped. Metrics cores extracted to tenant-scoped variants so the job needs no principal. PATCH /v1/catalog/class-sessions/:id sets a session's seat count with a confirmed-bookings floor — shrinking below it needs allowOverCapacity, matching the waitlist-promotion convention. Session rows gain an inline capacity editor with an overbook confirm state. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
148 lines
4.3 KiB
Go
148 lines
4.3 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
const classTemplateColumns = "id, tenant_id, title, duration_minutes, capacity, created_at"
|
|
const classSessionColumns = "cs.id, cs.tenant_id, cs.template_id, cs.location_id, ct.title, cs.starts_at, cs.ends_at, cs.capacity, cs.schedule_id"
|
|
|
|
func (r *PGRepository) ListClassTemplatesByTenant(ctx context.Context, tenantID string) ([]ClassTemplateRecord, error) {
|
|
rows, err := r.pool.Query(ctx, `
|
|
SELECT `+classTemplateColumns+`
|
|
FROM class_templates
|
|
WHERE tenant_id = $1
|
|
ORDER BY created_at ASC
|
|
`, tenantID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []ClassTemplateRecord
|
|
for rows.Next() {
|
|
var record ClassTemplateRecord
|
|
if err := rows.Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.Title,
|
|
&record.DurationMinutes,
|
|
&record.Capacity,
|
|
&record.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
records = append(records, record)
|
|
}
|
|
return records, rows.Err()
|
|
}
|
|
|
|
func (r *PGRepository) GetClassTemplateByID(ctx context.Context, templateID string) (ClassTemplateRecord, error) {
|
|
var record ClassTemplateRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT `+classTemplateColumns+`
|
|
FROM class_templates
|
|
WHERE id = $1
|
|
`, templateID).Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.Title,
|
|
&record.DurationMinutes,
|
|
&record.Capacity,
|
|
&record.CreatedAt,
|
|
)
|
|
return record, err
|
|
}
|
|
|
|
func (r *PGRepository) CreateClassTemplate(ctx context.Context, params CreateClassTemplateParams) (ClassTemplateRecord, error) {
|
|
var record ClassTemplateRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
INSERT INTO class_templates (tenant_id, title, duration_minutes, capacity)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING `+classTemplateColumns+`
|
|
`, params.TenantID, params.Title, params.DurationMinutes, params.Capacity).Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.Title,
|
|
&record.DurationMinutes,
|
|
&record.Capacity,
|
|
&record.CreatedAt,
|
|
)
|
|
return record, err
|
|
}
|
|
|
|
func (r *PGRepository) UpdateClassTemplate(ctx context.Context, templateID string, params CreateClassTemplateParams) (ClassTemplateRecord, error) {
|
|
var record ClassTemplateRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
UPDATE class_templates
|
|
SET title = $2, duration_minutes = $3, capacity = $4
|
|
WHERE id = $1
|
|
RETURNING `+classTemplateColumns+`
|
|
`, templateID, params.Title, params.DurationMinutes, params.Capacity).Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.Title,
|
|
&record.DurationMinutes,
|
|
&record.Capacity,
|
|
&record.CreatedAt,
|
|
)
|
|
return record, err
|
|
}
|
|
|
|
func (r *PGRepository) DeleteClassTemplate(ctx context.Context, templateID string) error {
|
|
_, err := r.pool.Exec(ctx, `DELETE FROM class_templates WHERE id = $1`, templateID)
|
|
return err
|
|
}
|
|
|
|
func (r *PGRepository) GetClassSessionByID(ctx context.Context, sessionID string) (ClassSessionRecord, error) {
|
|
var record ClassSessionRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT `+classSessionColumns+`
|
|
FROM class_sessions cs
|
|
INNER JOIN class_templates ct ON ct.id = cs.template_id
|
|
WHERE cs.id = $1
|
|
`, sessionID).Scan(
|
|
&record.ID,
|
|
&record.TenantID,
|
|
&record.TemplateID,
|
|
&record.LocationID,
|
|
&record.Title,
|
|
&record.StartsAt,
|
|
&record.EndsAt,
|
|
&record.Capacity,
|
|
&record.ScheduleID,
|
|
)
|
|
return record, err
|
|
}
|
|
|
|
func (r *PGRepository) CreateClassSession(ctx context.Context, params CreateClassSessionParams) (ClassSessionRecord, error) {
|
|
var record ClassSessionRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
INSERT INTO class_sessions (tenant_id, template_id, location_id, starts_at, ends_at, capacity, schedule_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id
|
|
`, params.TenantID, params.TemplateID, params.LocationID, params.StartsAt, params.EndsAt, params.Capacity, params.ScheduleID).Scan(&record.ID)
|
|
if err != nil {
|
|
return record, err
|
|
}
|
|
return r.GetClassSessionByID(ctx, record.ID)
|
|
}
|
|
|
|
func (r *PGRepository) DeleteClassSession(ctx context.Context, sessionID string) error {
|
|
_, err := r.pool.Exec(ctx, `DELETE FROM class_sessions WHERE id = $1`, sessionID)
|
|
return err
|
|
}
|
|
|
|
func (r *PGRepository) UpdateClassSessionCapacity(ctx context.Context, sessionID string, capacity int) (ClassSessionRecord, error) {
|
|
tag, err := r.pool.Exec(ctx, `UPDATE class_sessions SET capacity = $2 WHERE id = $1`, sessionID, capacity)
|
|
if err != nil {
|
|
return ClassSessionRecord{}, err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return ClassSessionRecord{}, pgx.ErrNoRows
|
|
}
|
|
return r.GetClassSessionByID(ctx, sessionID)
|
|
}
|