bmc_hub/docs/SAG_API.md

975 lines
19 KiB
Markdown
Raw Permalink Normal View History

# 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."