mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
feat(catalog): add service and staff management end-to-end
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>
This commit is contained in:
1 parent
7a6223c07b
commit
09f780e7a6
21 files changed
+2422
-15
No files matched your search
@@ -5,9 +5,11 @@ import (
|
||||
"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 id, tenant_id, name, duration_minutes, buffer_before_minutes, buffer_after_minutes, price_cents
|
||||
SELECT `+serviceColumns+`
|
||||
FROM services
|
||||
WHERE tenant_id = $1
|
||||
ORDER BY created_at ASC
|
||||
@@ -28,6 +30,7 @@ func (r *PGRepository) ListServicesByTenant(ctx context.Context, tenantID string
|
||||
&record.BufferBeforeMinutes,
|
||||
&record.BufferAfterMinutes,
|
||||
&record.PriceCents,
|
||||
&record.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -36,6 +39,73 @@ func (r *PGRepository) ListServicesByTenant(ctx context.Context, tenantID string
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user