package services import ( "context" "database/sql" "fmt" "time" "github.com/google/uuid" "github.com/jmoiron/sqlx" "mgit.msbls.de/m/KanzlAI-mGMT/internal/models" ) type NoteService struct { db *sqlx.DB audit *AuditService } func NewNoteService(db *sqlx.DB, audit *AuditService) *NoteService { return &NoteService{db: db, audit: audit} } // ListByParent returns all notes for a given parent entity, scoped to tenant. func (s *NoteService) ListByParent(ctx context.Context, tenantID uuid.UUID, parentType string, parentID uuid.UUID) ([]models.Note, error) { col, err := parentColumn(parentType) if err != nil { return nil, err } query := fmt.Sprintf( `SELECT id, tenant_id, case_id, deadline_id, appointment_id, case_event_id, content, created_by, created_at, updated_at FROM notes WHERE tenant_id = $1 AND %s = $2 ORDER BY created_at DESC`, col) var notes []models.Note if err := s.db.SelectContext(ctx, ¬es, query, tenantID, parentID); err != nil { return nil, fmt.Errorf("listing notes by %s: %w", parentType, err) } if notes == nil { notes = []models.Note{} } return notes, nil } type CreateNoteInput struct { CaseID *uuid.UUID `json:"case_id,omitempty"` DeadlineID *uuid.UUID `json:"deadline_id,omitempty"` AppointmentID *uuid.UUID `json:"appointment_id,omitempty"` CaseEventID *uuid.UUID `json:"case_event_id,omitempty"` Content string `json:"content"` } // Create inserts a new note. func (s *NoteService) Create(ctx context.Context, tenantID uuid.UUID, createdBy *uuid.UUID, input CreateNoteInput) (*models.Note, error) { id := uuid.New() now := time.Now().UTC() query := `INSERT INTO notes (id, tenant_id, case_id, deadline_id, appointment_id, case_event_id, content, created_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $9) RETURNING id, tenant_id, case_id, deadline_id, appointment_id, case_event_id, content, created_by, created_at, updated_at` var n models.Note err := s.db.GetContext(ctx, &n, query, id, tenantID, input.CaseID, input.DeadlineID, input.AppointmentID, input.CaseEventID, input.Content, createdBy, now) if err != nil { return nil, fmt.Errorf("creating note: %w", err) } s.audit.Log(ctx, "create", "note", &id, nil, n) return &n, nil } // Update modifies a note's content. func (s *NoteService) Update(ctx context.Context, tenantID, noteID uuid.UUID, content string) (*models.Note, error) { query := `UPDATE notes SET content = $1, updated_at = $2 WHERE id = $3 AND tenant_id = $4 RETURNING id, tenant_id, case_id, deadline_id, appointment_id, case_event_id, content, created_by, created_at, updated_at` var n models.Note err := s.db.GetContext(ctx, &n, query, content, time.Now().UTC(), noteID, tenantID) if err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, fmt.Errorf("updating note: %w", err) } s.audit.Log(ctx, "update", "note", ¬eID, nil, n) return &n, nil } // Delete removes a note. func (s *NoteService) Delete(ctx context.Context, tenantID, noteID uuid.UUID) error { result, err := s.db.ExecContext(ctx, "DELETE FROM notes WHERE id = $1 AND tenant_id = $2", noteID, tenantID) if err != nil { return fmt.Errorf("deleting note: %w", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("checking delete result: %w", err) } if rows == 0 { return fmt.Errorf("note not found") } s.audit.Log(ctx, "delete", "note", ¬eID, nil, nil) return nil } func parentColumn(parentType string) (string, error) { switch parentType { case "case": return "case_id", nil case "deadline": return "deadline_id", nil case "appointment": return "appointment_id", nil case "case_event": return "case_event_id", nil default: return "", fmt.Errorf("invalid parent type: %s", parentType) } }