bmc_hub/app/modules/sag/README.md
Christian 29acdf3e01 Add tests for new SAG module endpoints and module deactivation
- Implement test script for new SAG module endpoints BE-003 (Tag State Management) and BE-004 (Bulk Operations).
- Create test cases for creating, updating, and bulk operations on cases and tags.
- Add a test for module deactivation to ensure data integrity is maintained.
- Include setup and teardown for tests to clear database state before and after each test.
2026-01-31 23:16:24 +01:00

197 lines
5.6 KiB
Markdown

# Sag (Case) Module
## Overview
The Sag module is the **process backbone** of BMC Hub. All work flows through cases.
## Core Concept
**One Entity: Case (Sag)**
- Tickets are cases
- Tasks are cases
- Projects are cases
- Differences expressed through:
- Relations (links between cases)
- Tags (workflow state and categorization)
- Attached modules (billing, time tracking, etc.)
## Architecture
### 1. Cases (sag_sager)
- **Binary status**: `åben` or `lukket`
- No workflow embedded in status
- All workflow managed via tags
### 2. Tags (sag_tags)
- Represent work to be done
- Have state: `open` or `closed`
- **Never deleted** - only closed when work completes
- Closing a tag = completion of responsibility
### 3. Relations (sag_relationer)
- First-class data
- Directional: `kilde_sag_id``målsag_id`
- Transitive
- UI can derive parent/child/chain views
- No stored parent/child duality
### 4. Soft Deletes
- All deletes are soft: `deleted_at IS NULL`
- No hard deletes anywhere
## Database Schema
### sag_sager (Cases)
- `id` SERIAL PRIMARY KEY
- `titel` VARCHAR(255) NOT NULL
- `beskrivelse` TEXT
- `template_key` VARCHAR(100) - used only at creation
- `status` VARCHAR(50) CHECK (status IN ('åben', 'lukket'))
- `customer_id` INT - links to customers table
- `ansvarlig_bruger_id` INT
- `created_by_user_id` INT
- `deadline` TIMESTAMP
- `created_at` TIMESTAMP DEFAULT NOW()
- `updated_at` TIMESTAMP DEFAULT NOW()
- `deleted_at` TIMESTAMP
### sag_tags (Tags)
- `id` SERIAL PRIMARY KEY
- `sag_id` INT NOT NULL REFERENCES sag_sager(id)
- `tag_navn` VARCHAR(100) NOT NULL
- `state` VARCHAR(20) CHECK (state IN ('open', 'closed'))
- `closed_at` TIMESTAMP
- `created_at` TIMESTAMP DEFAULT NOW()
- `deleted_at` TIMESTAMP
### sag_relationer (Relations)
- `id` SERIAL PRIMARY KEY
- `kilde_sag_id` INT NOT NULL REFERENCES sag_sager(id)
- `målsag_id` INT NOT NULL REFERENCES sag_sager(id)
- `relationstype` VARCHAR(50) NOT NULL
- `created_at` TIMESTAMP DEFAULT NOW()
- `deleted_at` TIMESTAMP
- `CHECK (kilde_sag_id != målsag_id)`
### sag_kontakter (Contact Links)
- `id` SERIAL PRIMARY KEY
- `sag_id` INT NOT NULL REFERENCES sag_sager(id)
- `contact_id` INT NOT NULL REFERENCES contacts(id)
- `role` VARCHAR(50)
- `created_at` TIMESTAMP DEFAULT NOW()
- `deleted_at` TIMESTAMP
- UNIQUE(sag_id, contact_id)
### sag_kunder (Customer Links)
- `id` SERIAL PRIMARY KEY
- `sag_id` INT NOT NULL REFERENCES sag_sager(id)
- `customer_id` INT NOT NULL REFERENCES customers(id)
- `role` VARCHAR(50)
- `created_at` TIMESTAMP DEFAULT NOW()
- `deleted_at` TIMESTAMP
- UNIQUE(sag_id, customer_id)
## API Endpoints
### Cases CRUD
- `GET /api/v1/cases` - List all cases
- `POST /api/v1/cases` - Create case
- `GET /api/v1/cases/{id}` - Get case
- `PATCH /api/v1/cases/{id}` - Update case
- `DELETE /api/v1/cases/{id}` - Soft-delete case
### Tags
- `GET /api/v1/cases/{id}/tags` - List tags
- `POST /api/v1/cases/{id}/tags` - Add tag
- `PATCH /api/v1/cases/{id}/tags/{tag_id}/state` - Toggle tag state
- `DELETE /api/v1/cases/{id}/tags/{tag_id}` - Soft-delete tag
### Relations
- `GET /api/v1/cases/{id}/relations` - List relations
- `POST /api/v1/cases/{id}/relations` - Create relation
- `DELETE /api/v1/cases/{id}/relations/{rel_id}` - Soft-delete relation
### Contacts & Customers
- `GET /api/v1/cases/{id}/contacts` - List linked contacts
- `POST /api/v1/cases/{id}/contacts` - Link contact
- `DELETE /api/v1/cases/{id}/contacts/{contact_id}` - Unlink contact
- `GET /api/v1/cases/{id}/customers` - List linked customers
- `POST /api/v1/cases/{id}/customers` - Link customer
- `DELETE /api/v1/cases/{id}/customers/{customer_id}` - Unlink customer
### Search
- `GET /api/v1/search/cases?q={query}` - Search cases
- `GET /api/v1/search/contacts?q={query}` - Search contacts
- `GET /api/v1/search/customers?q={query}` - Search customers
### Bulk Operations
- `POST /api/v1/cases/bulk` - Bulk actions (close, add tag)
## Frontend Routes
- `/cases` - List all cases
- `/cases/new` - Create new case
- `/cases/{id}` - View case details
- `/cases/{id}/edit` - Edit case
## Usage Examples
### Create a Case
```python
import requests
response = requests.post('http://localhost:8001/api/v1/cases', json={
'titel': 'New Project',
'beskrivelse': 'Project description',
'status': 'åben',
'customer_id': 123
})
```
### Add Tag to Case
```python
response = requests.post('http://localhost:8001/api/v1/cases/1/tags', json={
'tag_navn': 'urgent'
})
```
### Close a Tag (Mark Work Complete)
```python
response = requests.patch('http://localhost:8001/api/v1/cases/1/tags/5/state', json={
'state': 'closed'
})
```
### Link Related Case
```python
response = requests.post('http://localhost:8001/api/v1/cases/1/relations', json={
'målsag_id': 42,
'relationstype': 'blokkerer'
})
```
## Relation Types
- **relateret** - General relation
- **afhænger af** - This case depends on target
- **blokkerer** - This case blocks target
- **duplikat** - This case duplicates target
## Orders Integration
Orders are **independent entities** but gain meaning through relations to cases.
When creating an Order from a Case:
1. Create the Order independently
2. Create a relation: Case → Order
3. Use relationstype: `ordre_oprettet` or similar
Orders **do not replace cases** - they are transactional satellites.
## Design Philosophy
> "If you think you need a new table or workflow engine, you're probably wrong. Use relations and tags instead."
The Sag module follows these principles:
- **Simplicity** - One entity, not many
- **Flexibility** - Relations express any structure
- **Traceability** - Soft deletes preserve history
- **Clarity** - Tags make workflow visible