# Sag Module API Documentation

## Overview
This document provides comprehensive API documentation for the Sag (Case) module endpoints. All endpoints follow RESTful conventions and return JSON responses.

**Base URL**: `http://localhost:8001/api/v1`

---

## Cases CRUD

### List All Cases
Retrieve a list of all cases with optional filtering.

**Endpoint**: `GET /cases`

**Query Parameters**:
- `status` (optional): Filter by status (`åben`, `lukket`)
- `customer_id` (optional): Filter by customer ID

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/cases?status=åben&customer_id=123"
```

**Response**: `200 OK`
```json
[
  {
    "id": 1,
    "titel": "Server maintenance",
    "beskrivelse": "Regular server maintenance",
    "template_key": null,
    "status": "åben",
    "customer_id": 123,
    "ansvarlig_bruger_id": 5,
    "created_by_user_id": 5,
    "deadline": "2026-02-15T10:00:00",
    "created_at": "2026-01-15T09:00:00",
    "updated_at": "2026-01-15T09:00:00",
    "deleted_at": null
  }
]
```

**Error Responses**:
- `500 Internal Server Error` - Database connection error

---

### Create New Case
Create a new case in the system.

**Endpoint**: `POST /cases`

**Request Body**:
```json
{
  "titel": "Network issue troubleshooting",
  "beskrivelse": "Customer reports intermittent connection drops",
  "template_key": "support_ticket",
  "status": "åben",
  "customer_id": 456,
  "ansvarlig_bruger_id": 3,
  "created_by_user_id": 3,
  "deadline": "2026-02-01T17:00:00"
}
```

**Request Example**:
```bash
curl -X POST "http://localhost:8001/api/v1/cases" \
  -H "Content-Type: application/json" \
  -d '{
    "titel": "Network issue troubleshooting",
    "beskrivelse": "Customer reports intermittent connection drops",
    "status": "åben",
    "customer_id": 456
  }'
```

**Response**: `201 Created`
```json
{
  "id": 42,
  "titel": "Network issue troubleshooting",
  "beskrivelse": "Customer reports intermittent connection drops",
  "template_key": "support_ticket",
  "status": "åben",
  "customer_id": 456,
  "ansvarlig_bruger_id": 3,
  "created_by_user_id": 3,
  "deadline": "2026-02-01T17:00:00",
  "created_at": "2026-01-30T14:23:00",
  "updated_at": "2026-01-30T14:23:00",
  "deleted_at": null
}
```

**Error Responses**:
- `400 Bad Request` - Missing required fields
- `500 Internal Server Error` - Failed to create case

---

### Get Case by ID
Retrieve a specific case by its ID.

**Endpoint**: `GET /cases/{id}`

**Path Parameters**:
- `id` (required): Case ID

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/cases/42"
```

**Response**: `200 OK`
```json
{
  "id": 42,
  "titel": "Network issue troubleshooting",
  "beskrivelse": "Customer reports intermittent connection drops",
  "template_key": "support_ticket",
  "status": "åben",
  "customer_id": 456,
  "ansvarlig_bruger_id": 3,
  "created_by_user_id": 3,
  "deadline": "2026-02-01T17:00:00",
  "created_at": "2026-01-30T14:23:00",
  "updated_at": "2026-01-30T14:23:00",
  "deleted_at": null
}
```

**Error Responses**:
- `404 Not Found` - Case not found or deleted

---

### Update Case
Update an existing case. Only provided fields will be updated.

**Endpoint**: `PATCH /cases/{id}`

**Path Parameters**:
- `id` (required): Case ID

**Request Body** (all fields optional):
```json
{
  "titel": "Updated title",
  "beskrivelse": "Updated description",
  "status": "lukket",
  "ansvarlig_bruger_id": 7,
  "deadline": "2026-02-10T12:00:00"
}
```

**Request Example**:
```bash
curl -X PATCH "http://localhost:8001/api/v1/cases/42" \
  -H "Content-Type: application/json" \
  -d '{"status": "lukket"}'
```

**Response**: `200 OK`
```json
{
  "id": 42,
  "titel": "Network issue troubleshooting",
  "beskrivelse": "Customer reports intermittent connection drops",
  "template_key": "support_ticket",
  "status": "lukket",
  "customer_id": 456,
  "ansvarlig_bruger_id": 3,
  "created_by_user_id": 3,
  "deadline": "2026-02-01T17:00:00",
  "created_at": "2026-01-30T14:23:00",
  "updated_at": "2026-01-30T15:45:00",
  "deleted_at": null
}
```

**Error Responses**:
- `404 Not Found` - Case not found or deleted
- `400 Bad Request` - Invalid field values

---

### Delete Case (Soft Delete)
Soft-delete a case. The case is not removed from the database but marked as deleted.

**Endpoint**: `DELETE /cases/{id}`

**Path Parameters**:
- `id` (required): Case ID

**Request Example**:
```bash
curl -X DELETE "http://localhost:8001/api/v1/cases/42"
```

**Response**: `200 OK`
```json
{
  "id": 42,
  "titel": "Network issue troubleshooting",
  "beskrivelse": "Customer reports intermittent connection drops",
  "template_key": "support_ticket",
  "status": "lukket",
  "customer_id": 456,
  "ansvarlig_bruger_id": 3,
  "created_by_user_id": 3,
  "deadline": "2026-02-01T17:00:00",
  "created_at": "2026-01-30T14:23:00",
  "updated_at": "2026-01-30T15:45:00",
  "deleted_at": "2026-01-30T16:00:00"
}
```

**Error Responses**:
- `404 Not Found` - Case not found or already deleted

---

## Tags

### List Tags for Case
Retrieve all tags associated with a case.

**Endpoint**: `GET /cases/{id}/tags`

**Path Parameters**:
- `id` (required): Case ID

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/cases/42/tags"
```

**Response**: `200 OK`
```json
[
  {
    "id": 10,
    "sag_id": 42,
    "tag_navn": "urgent",
    "state": "open",
    "closed_at": null,
    "created_at": "2026-01-30T14:25:00",
    "deleted_at": null
  },
  {
    "id": 11,
    "sag_id": 42,
    "tag_navn": "network",
    "state": "closed",
    "closed_at": "2026-01-30T15:30:00",
    "created_at": "2026-01-30T14:25:00",
    "deleted_at": null
  }
]
```

**Error Responses**:
- `500 Internal Server Error` - Database error

---

### Add Tag to Case
Add a new tag to a case.

**Endpoint**: `POST /cases/{id}/tags`

**Path Parameters**:
- `id` (required): Case ID

**Request Body**:
```json
{
  "tag_navn": "high_priority",
  "state": "open"
}
```

**Note**: `state` defaults to `"open"` if not provided.

**Request Example**:
```bash
curl -X POST "http://localhost:8001/api/v1/cases/42/tags" \
  -H "Content-Type: application/json" \
  -d '{"tag_navn": "high_priority"}'
```

**Response**: `201 Created`
```json
{
  "id": 12,
  "sag_id": 42,
  "tag_navn": "high_priority",
  "state": "open",
  "closed_at": null,
  "created_at": "2026-01-30T16:10:00",
  "deleted_at": null
}
```

**Error Responses**:
- `400 Bad Request` - Missing tag_navn
- `500 Internal Server Error` - Failed to create tag

---

### Update Tag State
Change tag state between `open` and `closed`. Tags are never deleted, only closed when work completes.

**Endpoint**: `PATCH /cases/{id}/tags/{tag_id}/state`

**Path Parameters**:
- `id` (required): Case ID
- `tag_id` (required): Tag ID

**Request Body**:
```json
{
  "state": "closed"
}
```

**Valid states**: `"open"` or `"closed"`

**Request Example**:
```bash
curl -X PATCH "http://localhost:8001/api/v1/cases/42/tags/12/state" \
  -H "Content-Type: application/json" \
  -d '{"state": "closed"}'
```

**Response**: `200 OK`
```json
{
  "id": 12,
  "sag_id": 42,
  "tag_navn": "high_priority",
  "state": "closed",
  "closed_at": "2026-01-30T16:45:00",
  "created_at": "2026-01-30T16:10:00",
  "deleted_at": null
}
```

**Error Responses**:
- `400 Bad Request` - Invalid state value
- `404 Not Found` - Tag not found or doesn't belong to case

---

### Delete Tag (Soft Delete)
Soft-delete a tag from a case.

**Endpoint**: `DELETE /cases/{id}/tags/{tag_id}`

**Path Parameters**:
- `id` (required): Case ID
- `tag_id` (required): Tag ID

**Request Example**:
```bash
curl -X DELETE "http://localhost:8001/api/v1/cases/42/tags/12"
```

**Response**: `200 OK`
```json
{
  "id": 12,
  "sag_id": 42,
  "tag_navn": "high_priority",
  "state": "closed",
  "closed_at": "2026-01-30T16:45:00",
  "created_at": "2026-01-30T16:10:00",
  "deleted_at": "2026-01-30T17:00:00"
}
```

**Error Responses**:
- `404 Not Found` - Tag not found or already deleted

---

## Relations

### List Case Relations
Retrieve all relations for a specific case (both incoming and outgoing).

**Endpoint**: `GET /cases/{id}/relations`

**Path Parameters**:
- `id` (required): Case ID

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/cases/42/relations"
```

**Response**: `200 OK`
```json
[
  {
    "id": 5,
    "kilde_sag_id": 42,
    "målsag_id": 55,
    "relationstype": "blokkerer",
    "kilde_titel": "Network issue troubleshooting",
    "mål_titel": "Deploy new firewall",
    "created_at": "2026-01-30T14:30:00",
    "deleted_at": null
  },
  {
    "id": 6,
    "kilde_sag_id": 30,
    "målsag_id": 42,
    "relationstype": "afhænger af",
    "kilde_titel": "Server upgrade",
    "mål_titel": "Network issue troubleshooting",
    "created_at": "2026-01-30T15:00:00",
    "deleted_at": null
  }
]
```

**Error Responses**:
- `500 Internal Server Error` - Database error

---

### Create Relation
Create a new relation between two cases.

**Endpoint**: `POST /cases/{id}/relations`

**Path Parameters**:
- `id` (required): Source case ID (kilde_sag_id)

**Request Body**:
```json
{
  "målsag_id": 55,
  "relationstype": "blokkerer"
}
```

**Valid relation types**:
- `relateret` - General relation
- `afhænger af` - Source depends on target
- `blokkerer` - Source blocks target
- `duplikat` - Source duplicates target

**Request Example**:
```bash
curl -X POST "http://localhost:8001/api/v1/cases/42/relations" \
  -H "Content-Type: application/json" \
  -d '{"målsag_id": 55, "relationstype": "blokkerer"}'
```

**Response**: `201 Created`
```json
{
  "id": 7,
  "kilde_sag_id": 42,
  "målsag_id": 55,
  "relationstype": "blokkerer",
  "created_at": "2026-01-30T17:15:00",
  "deleted_at": null
}
```

**Error Responses**:
- `400 Bad Request` - Missing fields or self-reference
- `404 Not Found` - Target case not found
- `500 Internal Server Error` - Failed to create relation

---

### Delete Relation (Soft Delete)
Soft-delete a relation between cases.

**Endpoint**: `DELETE /cases/{id}/relations/{relation_id}`

**Path Parameters**:
- `id` (required): Case ID
- `relation_id` (required): Relation ID

**Request Example**:
```bash
curl -X DELETE "http://localhost:8001/api/v1/cases/42/relations/7"
```

**Response**: `200 OK`
```json
{
  "id": 7,
  "kilde_sag_id": 42,
  "målsag_id": 55,
  "relationstype": "blokkerer",
  "created_at": "2026-01-30T17:15:00",
  "deleted_at": "2026-01-30T17:45:00"
}
```

**Error Responses**:
- `404 Not Found` - Relation not found or already deleted

---

## Contacts

### List Case Contacts
Retrieve all contacts linked to a case.

**Endpoint**: `GET /cases/{id}/contacts`

**Path Parameters**:
- `id` (required): Case ID

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/cases/42/contacts"
```

**Response**: `200 OK`
```json
[
  {
    "id": 3,
    "sag_id": 42,
    "contact_id": 15,
    "role": "Kontakt",
    "contact_name": "John Doe",
    "contact_email": "john.doe@example.com",
    "created_at": "2026-01-30T14:30:00",
    "deleted_at": null
  }
]
```

**Error Responses**:
- `500 Internal Server Error` - Database error

---

### Add Contact to Case
Link a contact to a case.

**Endpoint**: `POST /cases/{id}/contacts`

**Path Parameters**:
- `id` (required): Case ID

**Request Body**:
```json
{
  "contact_id": 15,
  "role": "Primary Contact"
}
```

**Note**: `role` defaults to `"Kontakt"` if not provided.

**Request Example**:
```bash
curl -X POST "http://localhost:8001/api/v1/cases/42/contacts" \
  -H "Content-Type: application/json" \
  -d '{"contact_id": 15, "role": "Primary Contact"}'
```

**Response**: `201 Created`
```json
{
  "id": 4,
  "sag_id": 42,
  "contact_id": 15,
  "role": "Primary Contact",
  "created_at": "2026-01-30T18:00:00",
  "deleted_at": null
}
```

**Error Responses**:
- `400 Bad Request` - Missing contact_id or contact already linked
- `500 Internal Server Error` - Failed to add contact

---

### Remove Contact from Case
Unlink a contact from a case.

**Endpoint**: `DELETE /cases/{id}/contacts/{contact_id}`

**Path Parameters**:
- `id` (required): Case ID
- `contact_id` (required): Contact ID

**Request Example**:
```bash
curl -X DELETE "http://localhost:8001/api/v1/cases/42/contacts/15"
```

**Response**: `200 OK`
```json
{
  "id": 4,
  "sag_id": 42,
  "contact_id": 15,
  "role": "Primary Contact",
  "created_at": "2026-01-30T18:00:00",
  "deleted_at": "2026-01-30T18:30:00"
}
```

**Error Responses**:
- `404 Not Found` - Contact not linked to case
- `500 Internal Server Error` - Failed to remove contact

---

## Customers

### List Case Customers
Retrieve all customers linked to a case.

**Endpoint**: `GET /cases/{id}/customers`

**Path Parameters**:
- `id` (required): Case ID

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/cases/42/customers"
```

**Response**: `200 OK`
```json
[
  {
    "id": 2,
    "sag_id": 42,
    "customer_id": 456,
    "role": "Kunde",
    "customer_name": "Acme Corporation",
    "customer_email": "contact@acme.com",
    "created_at": "2026-01-30T14:30:00",
    "deleted_at": null
  }
]
```

**Error Responses**:
- `500 Internal Server Error` - Database error

---

### Add Customer to Case
Link a customer to a case.

**Endpoint**: `POST /cases/{id}/customers`

**Path Parameters**:
- `id` (required): Case ID

**Request Body**:
```json
{
  "customer_id": 456,
  "role": "Main Customer"
}
```

**Note**: `role` defaults to `"Kunde"` if not provided.

**Request Example**:
```bash
curl -X POST "http://localhost:8001/api/v1/cases/42/customers" \
  -H "Content-Type: application/json" \
  -d '{"customer_id": 456, "role": "Main Customer"}'
```

**Response**: `201 Created`
```json
{
  "id": 3,
  "sag_id": 42,
  "customer_id": 456,
  "role": "Main Customer",
  "created_at": "2026-01-30T18:45:00",
  "deleted_at": null
}
```

**Error Responses**:
- `400 Bad Request` - Missing customer_id or customer already linked
- `500 Internal Server Error` - Failed to add customer

---

### Remove Customer from Case
Unlink a customer from a case.

**Endpoint**: `DELETE /cases/{id}/customers/{customer_id}`

**Path Parameters**:
- `id` (required): Case ID
- `customer_id` (required): Customer ID

**Request Example**:
```bash
curl -X DELETE "http://localhost:8001/api/v1/cases/42/customers/456"
```

**Response**: `200 OK`
```json
{
  "id": 3,
  "sag_id": 42,
  "customer_id": 456,
  "role": "Main Customer",
  "created_at": "2026-01-30T18:45:00",
  "deleted_at": "2026-01-30T19:00:00"
}
```

**Error Responses**:
- `404 Not Found` - Customer not linked to case
- `500 Internal Server Error` - Failed to remove customer

---

## Search

### Search Cases
Search for cases by title or description.

**Endpoint**: `GET /search/cases`

**Query Parameters**:
- `q` (required): Search query (minimum 2 characters)

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/search/cases?q=network"
```

**Response**: `200 OK`
```json
[
  {
    "id": 42,
    "titel": "Network issue troubleshooting",
    "status": "åben",
    "created_at": "2026-01-30T14:23:00"
  },
  {
    "id": 55,
    "titel": "Deploy new firewall",
    "status": "åben",
    "created_at": "2026-01-28T10:00:00"
  }
]
```

**Error Responses**:
- `500 Internal Server Error` - Search failed

---

### Search Contacts
Search for contacts by name, email, or company.

**Endpoint**: `GET /search/contacts`

**Query Parameters**:
- `q` (required): Search query (minimum 1 character)

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/search/contacts?q=john"
```

**Response**: `200 OK`
```json
[
  {
    "id": 15,
    "first_name": "John",
    "last_name": "Doe",
    "email": "john.doe@example.com",
    "user_company": "Acme Corp",
    "phone": "+45 12 34 56 78"
  }
]
```

**Note**: Limited to 20 results. Only active contacts are returned.

**Error Responses**:
- `400 Bad Request` - Query too short
- `500 Internal Server Error` - Search failed

---

### Search Customers
Search for customers by name, email, or CVR number.

**Endpoint**: `GET /search/customers`

**Query Parameters**:
- `q` (required): Search query (minimum 1 character)

**Request Example**:
```bash
curl -X GET "http://localhost:8001/api/v1/search/customers?q=acme"
```

**Response**: `200 OK`
```json
[
  {
    "id": 456,
    "name": "Acme Corporation",
    "email": "contact@acme.com",
    "cvr_number": "12345678",
    "city": "Copenhagen"
  }
]
```

**Note**: Limited to 20 results.

**Error Responses**:
- `400 Bad Request` - Query too short
- `500 Internal Server Error` - Search failed

---

## Bulk Operations

### Bulk Actions on Cases
Perform bulk actions on multiple cases simultaneously.

**Endpoint**: `POST /cases/bulk`

**Request Body**:
```json
{
  "case_ids": [42, 55, 67],
  "action": "update_status",
  "params": {
    "status": "lukket"
  }
}
```

**Available Actions**:

#### 1. Update Status
```json
{
  "case_ids": [42, 55],
  "action": "update_status",
  "params": {
    "status": "lukket"
  }
}
```

#### 2. Add Tag
```json
{
  "case_ids": [42, 55, 67],
  "action": "add_tag",
  "params": {
    "tag_navn": "reviewed"
  }
}
```

#### 3. Close All
```json
{
  "case_ids": [42, 55, 67],
  "action": "close_all",
  "params": {}
}
```

**Request Example**:
```bash
curl -X POST "http://localhost:8001/api/v1/cases/bulk" \
  -H "Content-Type: application/json" \
  -d '{
    "case_ids": [42, 55, 67],
    "action": "update_status",
    "params": {"status": "lukket"}
  }'
```

**Response**: `200 OK`
```json
{
  "success": true,
  "affected_cases": 3,
  "action": "update_status"
}
```

**Error Responses**:
- `400 Bad Request` - Missing fields or invalid action
- `500 Internal Server Error` - Bulk operation failed

---

## Common Error Responses

All endpoints may return the following error codes:

### 400 Bad Request
```json
{
  "detail": "Missing required field: titel"
}
```

### 404 Not Found
```json
{
  "detail": "Case not found."
}
```

### 500 Internal Server Error
```json
{
  "detail": "Database connection failed"
}
```

---

## Notes

### Soft Deletes
All delete operations are **soft deletes**. Records are never removed from the database but marked with a `deleted_at` timestamp. This preserves data integrity and audit trails.

### Tag Philosophy
Tags are **never hard-deleted**. Instead, they should be closed when work completes. This maintains a complete history of work done on a case.

### Relations
Relations are **directional** (source → target) and **transitive**. The UI can derive parent/child views, but the database stores only directional links.

### Search Limits
All search endpoints return a maximum of 20 results to prevent performance issues. Implement pagination if more results are needed.

---

## Architecture Principles

The Sag module follows these principles:
- **Simplicity** - One entity (Case), not many specialized types
- **Flexibility** - Relations and tags express any structure
- **Traceability** - Soft deletes preserve complete history
- **Clarity** - Tags make workflow state explicit

> "If you think you need a new table or workflow engine, you're probably wrong. Use relations and tags instead."
