mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
feat(availability): per-staff working hours and class/session management
- Working-hours repo accepts nullable staff scope (pgx + memory); staff rules live in availability_rules and drive public slot generation - Protected routes for staff working hours, class templates, and class sessions with tenant-ownership checks and input validation - Sessions derive endsAt/capacity from templates; list resolves location names and live booked counts; template delete cascades sessions, session delete preserves bookings via SET NULL (mirrored in memory) - Zones page: staff-scope selector on working hours, new Classes tab with template/session CRUD, CS+EN strings, mobile tab wrapping - Normalize availability_rules times to HH:MM in the service layer 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
d9aa29dbcb
commit
0405106587
12 files changed
+2069
-58
No files matched your search
@@ -0,0 +1,115 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
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"
|
||||
|
||||
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) 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,
|
||||
)
|
||||
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)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id
|
||||
`, params.TenantID, params.TemplateID, params.LocationID, params.StartsAt, params.EndsAt, params.Capacity).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
|
||||
}
|
||||
@@ -122,13 +122,24 @@ func (r *PGRepository) DeleteBlockedDay(ctx context.Context, blockedDayID string
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *PGRepository) ListWorkingHoursByTenant(ctx context.Context, tenantID string) ([]WorkingHoursRecord, error) {
|
||||
rows, err := r.pool.Query(ctx, `
|
||||
func (r *PGRepository) ListWorkingHoursByTenant(ctx context.Context, tenantID string, staffID *string) ([]WorkingHoursRecord, error) {
|
||||
query := `
|
||||
SELECT tenant_id, staff_id, day_of_week, starts_local::text, ends_local::text
|
||||
FROM availability_rules
|
||||
WHERE tenant_id = $1 AND staff_id IS NULL
|
||||
ORDER BY day_of_week
|
||||
`, tenantID)
|
||||
`
|
||||
args := []any{tenantID}
|
||||
if staffID != nil {
|
||||
query = `
|
||||
SELECT tenant_id, staff_id, day_of_week, starts_local::text, ends_local::text
|
||||
FROM availability_rules
|
||||
WHERE tenant_id = $1 AND staff_id = $2
|
||||
ORDER BY day_of_week
|
||||
`
|
||||
args = append(args, *staffID)
|
||||
}
|
||||
rows, err := r.pool.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -145,12 +156,22 @@ func (r *PGRepository) ListWorkingHoursByTenant(ctx context.Context, tenantID st
|
||||
return records, rows.Err()
|
||||
}
|
||||
|
||||
func (r *PGRepository) UpdateWorkingHours(ctx context.Context, tenantID string, dayOfWeek int, params UpdateWorkingHoursParams) error {
|
||||
if params.IsOpen != nil && !*params.IsOpen {
|
||||
_, err := r.pool.Exec(ctx, `
|
||||
func (r *PGRepository) UpdateWorkingHours(ctx context.Context, tenantID string, staffID *string, dayOfWeek int, params UpdateWorkingHoursParams) error {
|
||||
deleteQuery := `
|
||||
DELETE FROM availability_rules
|
||||
WHERE tenant_id = $1 AND day_of_week = $2 AND staff_id IS NULL
|
||||
`
|
||||
args := []any{tenantID, dayOfWeek}
|
||||
if staffID != nil {
|
||||
deleteQuery = `
|
||||
DELETE FROM availability_rules
|
||||
WHERE tenant_id = $1 AND day_of_week = $2 AND staff_id IS NULL
|
||||
`, tenantID, dayOfWeek)
|
||||
WHERE tenant_id = $1 AND day_of_week = $2 AND staff_id = $3
|
||||
`
|
||||
args = append(args, *staffID)
|
||||
}
|
||||
|
||||
if params.IsOpen != nil && !*params.IsOpen {
|
||||
_, err := r.pool.Exec(ctx, deleteQuery, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -159,17 +180,14 @@ func (r *PGRepository) UpdateWorkingHours(ctx context.Context, tenantID string,
|
||||
}
|
||||
|
||||
// availability_rules has no unique constraint on (tenant, day): replace the
|
||||
// tenant-level row so repeated updates stay idempotent.
|
||||
if _, err := r.pool.Exec(ctx, `
|
||||
DELETE FROM availability_rules
|
||||
WHERE tenant_id = $1 AND day_of_week = $2 AND staff_id IS NULL
|
||||
`, tenantID, dayOfWeek); err != nil {
|
||||
// scoped row so repeated updates stay idempotent.
|
||||
if _, err := r.pool.Exec(ctx, deleteQuery, args...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := r.pool.Exec(ctx, `
|
||||
INSERT INTO availability_rules (id, tenant_id, staff_id, day_of_week, starts_local, ends_local)
|
||||
VALUES (gen_random_uuid(), $1, NULL, $2, $3::time, $4::time)
|
||||
`, tenantID, dayOfWeek, *params.StartsLocal, *params.EndsLocal)
|
||||
VALUES (gen_random_uuid(), $1, $2, $3, $4::time, $5::time)
|
||||
`, tenantID, staffID, dayOfWeek, *params.StartsLocal, *params.EndsLocal)
|
||||
return err
|
||||
}
|
||||
@@ -26,6 +26,13 @@ type Repository interface {
|
||||
ListServicesByTenant(ctx context.Context, tenantID string) ([]ServiceRecord, error)
|
||||
ListAvailabilityRulesByTenant(ctx context.Context, tenantID string) ([]AvailabilityRuleRecord, error)
|
||||
ListClassSessionsByTenant(ctx context.Context, tenantID string, from time.Time, limit int) ([]ClassSessionRecord, error)
|
||||
GetClassSessionByID(ctx context.Context, sessionID string) (ClassSessionRecord, error)
|
||||
CreateClassSession(ctx context.Context, params CreateClassSessionParams) (ClassSessionRecord, error)
|
||||
DeleteClassSession(ctx context.Context, sessionID string) error
|
||||
ListClassTemplatesByTenant(ctx context.Context, tenantID string) ([]ClassTemplateRecord, error)
|
||||
GetClassTemplateByID(ctx context.Context, templateID string) (ClassTemplateRecord, error)
|
||||
CreateClassTemplate(ctx context.Context, params CreateClassTemplateParams) (ClassTemplateRecord, error)
|
||||
DeleteClassTemplate(ctx context.Context, templateID string) error
|
||||
ListBookingsByTenantBetween(ctx context.Context, tenantID string, from time.Time, to time.Time) ([]BookingRecord, error)
|
||||
CreateBooking(ctx context.Context, params CreateBookingParams) (CreatedBooking, error)
|
||||
AppendWaitlistEntry(ctx context.Context, params WaitlistEntryParams) error
|
||||
@@ -100,8 +107,8 @@ type Repository interface {
|
||||
RescheduleBooking(ctx context.Context, bookingID string, startsAt time.Time, endsAt time.Time) error
|
||||
|
||||
// Working Hours
|
||||
ListWorkingHoursByTenant(ctx context.Context, tenantID string) ([]WorkingHoursRecord, error)
|
||||
UpdateWorkingHours(ctx context.Context, tenantID string, dayOfWeek int, params UpdateWorkingHoursParams) error
|
||||
ListWorkingHoursByTenant(ctx context.Context, tenantID string, staffID *string) ([]WorkingHoursRecord, error)
|
||||
UpdateWorkingHours(ctx context.Context, tenantID string, staffID *string, dayOfWeek int, params UpdateWorkingHoursParams) error
|
||||
|
||||
// Email templates
|
||||
ListEmailTemplates(ctx context.Context, tenantID string) ([]EmailTemplateRecord, error)
|
||||
@@ -293,6 +300,22 @@ type AvailabilityRuleRecord struct {
|
||||
EndsLocal string
|
||||
}
|
||||
|
||||
type ClassTemplateRecord struct {
|
||||
ID string
|
||||
TenantID string
|
||||
Title string
|
||||
DurationMinutes int
|
||||
Capacity int
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type CreateClassTemplateParams struct {
|
||||
TenantID string
|
||||
Title string
|
||||
DurationMinutes int
|
||||
Capacity int
|
||||
}
|
||||
|
||||
type ClassSessionRecord struct {
|
||||
ID string
|
||||
TenantID string
|
||||
@@ -304,6 +327,15 @@ type ClassSessionRecord struct {
|
||||
Capacity int32
|
||||
}
|
||||
|
||||
type CreateClassSessionParams struct {
|
||||
TenantID string
|
||||
TemplateID string
|
||||
LocationID *string
|
||||
StartsAt time.Time
|
||||
EndsAt time.Time
|
||||
Capacity int32
|
||||
}
|
||||
|
||||
type BookingRecord struct {
|
||||
ID string
|
||||
TenantID string
|
||||
@@ -719,6 +751,7 @@ type MemoryRepository struct {
|
||||
brand BrandProfileRecord
|
||||
services []ServiceRecord
|
||||
rules []AvailabilityRuleRecord
|
||||
classTemplates []ClassTemplateRecord
|
||||
classSessions []ClassSessionRecord
|
||||
bookings []BookingRecord
|
||||
waitlistSize int
|
||||
@@ -810,6 +843,9 @@ func NewMemoryRepository() *MemoryRepository {
|
||||
{ID: uuid.NewString(), TenantID: tenantID, StaffID: &staffID, DayOfWeek: 4, StartsLocal: "09:00:00", EndsLocal: "17:00:00"},
|
||||
{ID: uuid.NewString(), TenantID: tenantID, StaffID: &staffID, DayOfWeek: 5, StartsLocal: "09:00:00", EndsLocal: "17:00:00"},
|
||||
},
|
||||
classTemplates: []ClassTemplateRecord{
|
||||
{ID: templateID, TenantID: tenantID, Title: "Small group mobility class", DurationMinutes: 60, Capacity: 4, CreatedAt: now},
|
||||
},
|
||||
classSessions: []ClassSessionRecord{
|
||||
{
|
||||
ID: sessionID,
|
||||
@@ -1179,6 +1215,97 @@ func (r *MemoryRepository) ListClassSessionsByTenant(_ context.Context, tenantID
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) GetClassSessionByID(_ context.Context, sessionID string) (ClassSessionRecord, error) {
|
||||
for _, session := range r.classSessions {
|
||||
if session.ID == sessionID {
|
||||
return session, nil
|
||||
}
|
||||
}
|
||||
return ClassSessionRecord{}, pgx.ErrNoRows
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) CreateClassSession(_ context.Context, params CreateClassSessionParams) (ClassSessionRecord, error) {
|
||||
record := ClassSessionRecord{
|
||||
ID: uuid.NewString(),
|
||||
TenantID: params.TenantID,
|
||||
TemplateID: params.TemplateID,
|
||||
LocationID: params.LocationID,
|
||||
StartsAt: params.StartsAt,
|
||||
EndsAt: params.EndsAt,
|
||||
Capacity: params.Capacity,
|
||||
}
|
||||
for _, template := range r.classTemplates {
|
||||
if template.ID == params.TemplateID {
|
||||
record.Title = template.Title
|
||||
break
|
||||
}
|
||||
}
|
||||
r.classSessions = append(r.classSessions, record)
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) DeleteClassSession(_ context.Context, sessionID string) error {
|
||||
for i, session := range r.classSessions {
|
||||
if session.ID == sessionID {
|
||||
r.classSessions = append(r.classSessions[:i], r.classSessions[i+1:]...)
|
||||
for j := range r.bookings {
|
||||
if r.bookings[j].ClassSessionID != nil && *r.bookings[j].ClassSessionID == sessionID {
|
||||
r.bookings[j].ClassSessionID = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) ListClassTemplatesByTenant(_ context.Context, tenantID string) ([]ClassTemplateRecord, error) {
|
||||
if tenantID != r.tenant.ID {
|
||||
return nil, nil
|
||||
}
|
||||
return append([]ClassTemplateRecord(nil), r.classTemplates...), nil
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) GetClassTemplateByID(_ context.Context, templateID string) (ClassTemplateRecord, error) {
|
||||
for _, template := range r.classTemplates {
|
||||
if template.ID == templateID {
|
||||
return template, nil
|
||||
}
|
||||
}
|
||||
return ClassTemplateRecord{}, pgx.ErrNoRows
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) CreateClassTemplate(_ context.Context, params CreateClassTemplateParams) (ClassTemplateRecord, error) {
|
||||
record := ClassTemplateRecord{
|
||||
ID: uuid.NewString(),
|
||||
TenantID: params.TenantID,
|
||||
Title: params.Title,
|
||||
DurationMinutes: params.DurationMinutes,
|
||||
Capacity: params.Capacity,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
r.classTemplates = append(r.classTemplates, record)
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) DeleteClassTemplate(_ context.Context, templateID string) error {
|
||||
for i, template := range r.classTemplates {
|
||||
if template.ID == templateID {
|
||||
r.classTemplates = append(r.classTemplates[:i], r.classTemplates[i+1:]...)
|
||||
// class_sessions.template_id has ON DELETE CASCADE
|
||||
var remaining []ClassSessionRecord
|
||||
for _, session := range r.classSessions {
|
||||
if session.TemplateID != templateID {
|
||||
remaining = append(remaining, session)
|
||||
}
|
||||
}
|
||||
r.classSessions = remaining
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) ListBookingsByTenantBetween(_ context.Context, tenantID string, from time.Time, to time.Time) ([]BookingRecord, error) {
|
||||
var out []BookingRecord
|
||||
for _, booking := range r.bookings {
|
||||
@@ -1699,29 +1826,114 @@ func (r *MemoryRepository) UpsertEmailTemplate(_ context.Context, params EmailTe
|
||||
// WORKING HOURS METHODS - MEMORY REPOSITORY
|
||||
// ============================================
|
||||
|
||||
func (r *MemoryRepository) ListWorkingHoursByTenant(_ context.Context, tenantID string) ([]WorkingHoursRecord, error) {
|
||||
// ListWorkingHoursByTenant returns tenant-wide hours from the dedicated
|
||||
// working-hours store, and staff-scoped hours from the availability rules that
|
||||
// slot generation consumes — mirroring PostgreSQL where both live in
|
||||
// availability_rules.
|
||||
func (r *MemoryRepository) ListWorkingHoursByTenant(_ context.Context, tenantID string, staffID *string) ([]WorkingHoursRecord, error) {
|
||||
if tenantID != r.tenant.ID {
|
||||
return nil, pgx.ErrNoRows
|
||||
}
|
||||
return r.workingHours, nil
|
||||
var out []WorkingHoursRecord
|
||||
if staffID == nil {
|
||||
for _, wh := range r.workingHours {
|
||||
if wh.TenantID == tenantID && wh.StaffID == nil {
|
||||
out = append(out, wh)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
for _, rule := range r.rules {
|
||||
if rule.TenantID == tenantID && rule.StaffID != nil && *rule.StaffID == *staffID {
|
||||
out = append(out, WorkingHoursRecord{
|
||||
TenantID: rule.TenantID,
|
||||
StaffID: rule.StaffID,
|
||||
DayOfWeek: rule.DayOfWeek,
|
||||
StartsLocal: rule.StartsLocal,
|
||||
EndsLocal: rule.EndsLocal,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) UpdateWorkingHours(_ context.Context, tenantID string, dayOfWeek int, params UpdateWorkingHoursParams) error {
|
||||
func staffIDPtrEqual(a, b *string) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == nil && b == nil
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) UpdateWorkingHours(_ context.Context, tenantID string, staffID *string, dayOfWeek int, params UpdateWorkingHoursParams) error {
|
||||
if tenantID != r.tenant.ID {
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
if staffID != nil {
|
||||
return r.updateStaffWorkingHours(tenantID, staffID, dayOfWeek, params)
|
||||
}
|
||||
if params.IsOpen != nil && !*params.IsOpen {
|
||||
filtered := r.workingHours[:0]
|
||||
for _, wh := range r.workingHours {
|
||||
if wh.TenantID == tenantID && wh.DayOfWeek == dayOfWeek && wh.StaffID == nil {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, wh)
|
||||
}
|
||||
r.workingHours = filtered
|
||||
return nil
|
||||
}
|
||||
if params.StartsLocal == nil || params.EndsLocal == nil {
|
||||
return nil
|
||||
}
|
||||
for i, wh := range r.workingHours {
|
||||
if wh.TenantID == tenantID && wh.DayOfWeek == dayOfWeek {
|
||||
if params.StartsLocal != nil {
|
||||
r.workingHours[i].StartsLocal = *params.StartsLocal
|
||||
}
|
||||
if params.EndsLocal != nil {
|
||||
r.workingHours[i].EndsLocal = *params.EndsLocal
|
||||
}
|
||||
if wh.TenantID == tenantID && wh.DayOfWeek == dayOfWeek && wh.StaffID == nil {
|
||||
r.workingHours[i].StartsLocal = *params.StartsLocal
|
||||
r.workingHours[i].EndsLocal = *params.EndsLocal
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return pgx.ErrNoRows
|
||||
r.workingHours = append(r.workingHours, WorkingHoursRecord{
|
||||
TenantID: tenantID,
|
||||
DayOfWeek: dayOfWeek,
|
||||
StartsLocal: *params.StartsLocal,
|
||||
EndsLocal: *params.EndsLocal,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateStaffWorkingHours replaces the staff-scoped availability rule for a
|
||||
// day so edits actually change generated slots, matching PostgreSQL semantics.
|
||||
func (r *MemoryRepository) updateStaffWorkingHours(tenantID string, staffID *string, dayOfWeek int, params UpdateWorkingHoursParams) error {
|
||||
filtered := r.rules[:0]
|
||||
for _, rule := range r.rules {
|
||||
if rule.TenantID == tenantID && rule.DayOfWeek == dayOfWeek && rule.StaffID != nil && *rule.StaffID == *staffID {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, rule)
|
||||
}
|
||||
r.rules = filtered
|
||||
if params.IsOpen != nil && !*params.IsOpen {
|
||||
return nil
|
||||
}
|
||||
if params.StartsLocal == nil || params.EndsLocal == nil {
|
||||
return nil
|
||||
}
|
||||
r.rules = append(r.rules, AvailabilityRuleRecord{
|
||||
ID: uuid.NewString(),
|
||||
TenantID: tenantID,
|
||||
StaffID: staffID,
|
||||
DayOfWeek: dayOfWeek,
|
||||
StartsLocal: normalizeLocalTime(*params.StartsLocal),
|
||||
EndsLocal: normalizeLocalTime(*params.EndsLocal),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeLocalTime(value string) string {
|
||||
if len(value) == 5 {
|
||||
return value + ":00"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Auth methods for MemoryRepository
|
||||
|
||||
Reference in new issue
Block a user