# 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