feat: add appointment CRUD backend (Phase 1D)
- AppointmentService with tenant-scoped List, GetByID, Create, Update, Delete
- List supports filtering by case_id, appointment_type, and date range (start_from/start_to)
- AppointmentHandler with JSON request/response handling and input validation
- Router wired up: GET/POST /api/appointments, PUT/DELETE /api/appointments/{id}
This commit is contained in:
135
backend/internal/services/appointment_service.go
Normal file
135
backend/internal/services/appointment_service.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"mgit.msbls.de/m/KanzlAI-mGMT/internal/models"
|
||||
)
|
||||
|
||||
type AppointmentService struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewAppointmentService(db *sqlx.DB) *AppointmentService {
|
||||
return &AppointmentService{db: db}
|
||||
}
|
||||
|
||||
type AppointmentFilter struct {
|
||||
CaseID *uuid.UUID
|
||||
Type *string
|
||||
StartFrom *time.Time
|
||||
StartTo *time.Time
|
||||
}
|
||||
|
||||
func (s *AppointmentService) List(ctx context.Context, tenantID uuid.UUID, filter AppointmentFilter) ([]models.Appointment, error) {
|
||||
query := "SELECT * FROM appointments WHERE tenant_id = $1"
|
||||
args := []any{tenantID}
|
||||
argN := 2
|
||||
|
||||
if filter.CaseID != nil {
|
||||
query += fmt.Sprintf(" AND case_id = $%d", argN)
|
||||
args = append(args, *filter.CaseID)
|
||||
argN++
|
||||
}
|
||||
if filter.Type != nil {
|
||||
query += fmt.Sprintf(" AND appointment_type = $%d", argN)
|
||||
args = append(args, *filter.Type)
|
||||
argN++
|
||||
}
|
||||
if filter.StartFrom != nil {
|
||||
query += fmt.Sprintf(" AND start_at >= $%d", argN)
|
||||
args = append(args, *filter.StartFrom)
|
||||
argN++
|
||||
}
|
||||
if filter.StartTo != nil {
|
||||
query += fmt.Sprintf(" AND start_at <= $%d", argN)
|
||||
args = append(args, *filter.StartTo)
|
||||
argN++
|
||||
}
|
||||
|
||||
query += " ORDER BY start_at ASC"
|
||||
|
||||
var appointments []models.Appointment
|
||||
if err := s.db.SelectContext(ctx, &appointments, query, args...); err != nil {
|
||||
return nil, fmt.Errorf("listing appointments: %w", err)
|
||||
}
|
||||
if appointments == nil {
|
||||
appointments = []models.Appointment{}
|
||||
}
|
||||
return appointments, nil
|
||||
}
|
||||
|
||||
func (s *AppointmentService) GetByID(ctx context.Context, tenantID, id uuid.UUID) (*models.Appointment, error) {
|
||||
var a models.Appointment
|
||||
err := s.db.GetContext(ctx, &a, "SELECT * FROM appointments WHERE id = $1 AND tenant_id = $2", id, tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting appointment: %w", err)
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (s *AppointmentService) Create(ctx context.Context, a *models.Appointment) error {
|
||||
a.ID = uuid.New()
|
||||
now := time.Now().UTC()
|
||||
a.CreatedAt = now
|
||||
a.UpdatedAt = now
|
||||
|
||||
_, err := s.db.NamedExecContext(ctx, `
|
||||
INSERT INTO appointments (id, tenant_id, case_id, title, description, start_at, end_at, location, appointment_type, caldav_uid, caldav_etag, created_at, updated_at)
|
||||
VALUES (:id, :tenant_id, :case_id, :title, :description, :start_at, :end_at, :location, :appointment_type, :caldav_uid, :caldav_etag, :created_at, :updated_at)
|
||||
`, a)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating appointment: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AppointmentService) Update(ctx context.Context, a *models.Appointment) error {
|
||||
a.UpdatedAt = time.Now().UTC()
|
||||
|
||||
result, err := s.db.NamedExecContext(ctx, `
|
||||
UPDATE appointments SET
|
||||
case_id = :case_id,
|
||||
title = :title,
|
||||
description = :description,
|
||||
start_at = :start_at,
|
||||
end_at = :end_at,
|
||||
location = :location,
|
||||
appointment_type = :appointment_type,
|
||||
caldav_uid = :caldav_uid,
|
||||
caldav_etag = :caldav_etag,
|
||||
updated_at = :updated_at
|
||||
WHERE id = :id AND tenant_id = :tenant_id
|
||||
`, a)
|
||||
if err != nil {
|
||||
return fmt.Errorf("updating appointment: %w", err)
|
||||
}
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking rows affected: %w", err)
|
||||
}
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("appointment not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AppointmentService) Delete(ctx context.Context, tenantID, id uuid.UUID) error {
|
||||
result, err := s.db.ExecContext(ctx, "DELETE FROM appointments WHERE id = $1 AND tenant_id = $2", id, tenantID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting appointment: %w", err)
|
||||
}
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking rows affected: %w", err)
|
||||
}
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("appointment not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user