bmc_hub/SAG_MODULE_PLAN.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

741 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Implementeringsplan: Sag-modulet (Case Module)
## Oversigt Hvad er “Sag”?
**Sag-modulet** er hjertet i BMC Hubs relation- og proces-styringssystem.
I stedet for separate systemer for tickets, opgaver og ordrer findes der én universel entitet: **en Sag**.
### Kerneidéen (meget vigtig!)
> **Der er kun én ting: en Sag.**
> Tickets, opgaver og ordrer er blot sager med forskellige relationer, tags og moduler.
---
## Eksempler (samme datatype forskellige relationer)
1. **Kunde ringer og skal have ny skærm**
- Dette er en Sag
- Tags: `support`, `urgent`
- Ansvarlig: Support
- Status: åben
2. **Indkøb af skærm hos leverandør**
- Dette er også en Sag
- Tags: `indkøb`
- Relation: afledt fra kundesagen
- Ansvarlig: Indkøb
3. **Ompakning og afsendelse**
- Dette er også en Sag
- Tags: `ompakning`
- Relation: afledt fra indkøbssagen
- Ansvarlig: Lager
- Deadline: i dag
Alle tre er samme datatype i databasen.
Forskellen er udelukkende:
- hvilke tags sagen har
- hvilke relationer den indgår i
- hvem der er ansvarlig
- hvilke moduler der er koblet på
---
## Hvad betyder det for systemet?
**Uden Sag-modulet**
- Separate tickets, tasks og ordrer
- Kompleks synkronisering
- Dubleret data
- Svær historik
**Med Sag-modulet**
- Ét API: `/api/v1/cases`
- Ét UI-område: Sager
- Relationer er førsteklasses data
- Tags styrer processer
- Sager kan vokse og forgrene sig
- Alt er søgbart på tværs
---
## Teknisk arkitektur
### Databasestruktur
Sag-modulet består af tre kerne-tabeller (prefix `sag_`).
---
### **sag_sager Hovedtabel**
```
id (primary key)
titel (VARCHAR)
beskrivelse (TEXT)
template_key (VARCHAR, NULL)
- Bruges kun ved oprettelse
- Har ingen forretningslogik efterfølgende
status (VARCHAR)
- Tilladte værdier: 'åben', 'lukket'
customer_id (foreign key, NULLABLE)
ansvarlig_bruger_id (foreign key, NULLABLE)
created_by_user_id (foreign key, NOT NULL)
deadline (TIMESTAMP, NULLABLE)
created_at (TIMESTAMP)
updated_at (TIMESTAMP)
deleted_at (TIMESTAMP) -- soft-delete
```
**Vigtige regler**
- status er binær (åben/lukket)
- Al proceslogik udtrykkes via tags
- `template_key` må aldrig bruges til business logic
---
### **sag_relationer Relationer mellem sager**
```
id (primary key)
kilde_sag_id (foreign key)
målsag_id (foreign key)
relationstype (VARCHAR)
- f.eks. 'derived', 'blocks', 'executes'
created_at (TIMESTAMP)
deleted_at (TIMESTAMP)
```
**Principper**
- Relationer er retningsbestemte
- Relationer er transitive
- Der oprettes kun én relation pr. sammenhæng
- Begreber som “forælder” og “barn” er UI-views, ikke data
**Eksempel (kæde med flere led)**
Sag A → Sag B → Sag C → Sag D
Databasen indeholder tre relationer intet mere.
---
### **sag_tags Proces og kategorisering**
```
id (primary key)
sag_id (foreign key)
tag_navn (VARCHAR)
state (VARCHAR DEFAULT 'open')
- 'open' = ikke færdigbehandlet
- 'closed' = færdigbehandlet
closed_at (TIMESTAMP, NULLABLE)
created_at (TIMESTAMP)
deleted_at (TIMESTAMP)
```
**Betydning**
- Tags repræsenterer arbejde der skal udføres
- Et tag slettes ikke, når det er færdigt det lukkes
- `deleted_at` bruges kun til teknisk fjernelse / rollback
---
## API-endpoints
**Cases**
- `GET /api/v1/cases`
- `POST /api/v1/cases`
- `GET /api/v1/cases/{id}`
- `PATCH /api/v1/cases/{id}`
- `DELETE /api/v1/cases/{id}` (soft-delete)
**Relationer**
- `GET /api/v1/cases/{id}/relations`
- `POST /api/v1/cases/{id}/relations`
- `DELETE /api/v1/cases/{id}/relations/{relation_id}`
**Tags**
- `GET /api/v1/cases/{id}/tags`
- `POST /api/v1/cases/{id}/tags`
- `PATCH /api/v1/cases/{id}/tags/{tag_id}` (luk tag)
- `DELETE /api/v1/cases/{id}/tags/{tag_id}` (soft-delete)
Alle SELECT-queries skal filtrere på:
```sql
WHERE deleted_at IS NULL
```
---
## UI-koncept
**Sag-liste** (`/cases`)
- Liste over alle relevante sager
- Filtre:
- mine sager
- åbne sager
- sager med tag
- Sortering:
- deadline
- oprettet dato
**Sag-detalje** (`/cases/{id}`)
- Titel, status, deadline
- Tags (åbne vises tydeligt)
- Relaterede sager (afledte, blokerende, udførende)
- Ansvarlig
- Klar navigation mellem sager
---
## Implementeringsprincipper (MÅ IKKE BRYDES)
1. Der findes kun én entitet: Sag
2. `template_key` bruges kun ved oprettelse
3. Status er binær proces styres via tags
4. Tags lukkes, de slettes ikke
5. Relationer er data, ikke implicit logik
6. Alle sletninger er soft-deletes
7. Hvis du tror, du mangler en ny tabel → brug en relation
---
## Tidsestimat
- Database + migration: 30 min
- Backend API: 12 timer
- Frontend (liste + detalje): 12 timer
- Test + dokumentation: 1 time
**Total: 46 timer**
---
## TL;DR for udvikler
- Alt er en sag
- Forskelle = tags + relationer
- Ingen tickets, ingen tasks, ingen orders
- Relationer danner kæder
- Tags styrer arbejdet
- Status er kun åben/lukket
---
Hvis du vil næste skridt, kan vi:
- lave SQL CTE-eksempler til traversal
- definere første reference-workflow
- skrive README “Architectural Laws”
- eller lave et diagram, der matcher præcis dette
Men modellen?
Den er nu færdig og sund.# Implementeringsplan: Sag-modulet (Case Module)
## Oversigt - Hvad er "Sag"?
**Sag-modulet** er hjertet i BMC Hub's nye relation- og proces-styringssystem. I stedet for at have separate systemer for "tickets", "opgaver" og "ordrer", har vi én universel entitet: **en Sag**.
### Kerneidéen (meget vigtig!)
> **Der er kun én ting: en Sag. Tickets, opgaver og ordrer er blot sager med forskellige relationer, tags og moduler.**
**Eksempler:**
1. **Kunde ringer og skal have ny skærm**
- Dette er en *Sag* (ticket-type med tag: `support`)
- Den får tag: `urgent` fordi det er ekspres
2. **Indkøb af skærm hos leverandør**
- Dette er også en *Sag* (ordre-type med tag: `indkøb`)
- Den er *relateret til* den første sag som "afledt_af"
- Ansvarlig: Indkøbschef
3. **Ompakning og afsendelse af skærm**
- Dette er en *Sag* (opgave-type med tag: `ompakning`)
- Den er relateret til indkøbssagen som "udførelse_for"
- Ansvarlig: Lagermedarbejder
- Deadline: I dag
**Alle tre er samme datatype i databasen.** Forskellen er:
- Hvilke *tags* de har
- Hvilken *kunde/kontakt* de er knyttet til
- Hvilke *relationer* de har til andre sager
- Hvem der er *ansvarlig*
### Hvad betyder det for systemet?
**Uden Sag-modulet:**
- Du skal have en "Ticket-sektion" for support
- Du skal have en "Task-sektion" for opgaver
- Du skal have en "Order-sektion" for ordrer
- De snakker ikke sammen naturligt
- Data-duplikering
- Kompleks logik
**Med Sag-modulet:**
- Ét API endpoint: `/api/v1/sag`
- Ét UI-område: "Sager" med intelligente filtre
- Relationer er førsteklasses borgere (se hvad der hænger sammen)
- Tags styr flowet (f.eks. "support" + "urgent" = prioriteret)
- Sager kan "vokse": Start som ticket → bliv til ordre → bliv til installation
- Alt er søgbart og filterabelt på tværs af domæner
---
## Teknisk arkitektur
### Databasestruktur
Sag-modulet bruger tre hovedtabeller (med `sag_` prefix):
#### **sag_sager** (Hovedtabel for sager)
```
id (primary key)
titel (VARCHAR) - kort navn på sagen
beskrivelse (TEXT) - detaljeret beskrivelse
template_key (VARCHAR) - struktur-template (f.eks. "ticket", "opgave", "ordre") - default NULL
status (VARCHAR) - "åben" eller "lukket"
customer_id (foreign key) - hvilken kunde sagen handler om - NULLABLE
ansvarlig_bruger_id (foreign key) - hvem skal håndtere den
created_by_user_id (foreign key) - hvem oprettede sagen
deadline (TIMESTAMP) - hvornår skal det være færdigt
created_at (TIMESTAMP)
updated_at (TIMESTAMP)
deleted_at (TIMESTAMP) - soft-delete: sættes når sagen "slettes"
```
**Soft-delete:** Når du sletter en sag, bliver `deleted_at` sat til nu. Sagen bliver ikke fjernet fra DB. Det betyder:
- Du kan gendanne data hvis modulet deaktiveres
- Historien bevares (audit trail)
- Relations er intakte hvis du genopretter
#### **sag_relationer** (Hvordan sager hænger sammen)
```
id (primary key)
kilde_sag_id (foreign key) - hvilken sag relationen STARTER fra (retning: fra denne)
målsag_id (foreign key) - hvilken sag relationen PEGER PÅ (retning: til denne)
relationstype (VARCHAR) - f.eks. "parent_of", "child_of", "derived_from", "blocks", "executes_for"
created_at (TIMESTAMP)
deleted_at (TIMESTAMP) - soft-delete
```
**Eksempel (retningsbestemt):**
- Sag 1 (kundesamtale) → Sag 5 (indkøb af skærm)
- kilde_sag_id: 1, målsag_id: 5
- relationstype: "derives" eller "parent_of"
- Betyder: "Sag 1 er forælder/genererer Sag 5"
**Note:** Relationer er enrettet. For bidirektionale links oprettes to relations (1→5 og 5→1).
#### **sag_tags** (Hvordan vi kategoriserer sager)
```
id (primary key)
sag_id (foreign key) - hvilken sag tagget tilhører
tag_navn (VARCHAR) - f.eks. "support", "urgent", "vip", "ompakning"
state (VARCHAR) - "aktiv" eller "inaktiv" - default "aktiv"
closed_at (TIMESTAMP) - hvornår tagget blev lukket/inaktiveret - NULLABLE
created_at (TIMESTAMP)
deleted_at (TIMESTAMP) - soft-delete
```
**Tags bruges til:**
- Filtrering: "Vis alle sager med tag = support"
- Workflow: "Sager med tag = urgent skal løses i dag"
- Kategorisering: "Alle sager med tag = ompakning"
### API-endpoints
**Sager CRUD:**
- `GET /api/v1/cases` - Liste alle sager (filter efter tags, status, ansvarlig)
- `POST /api/v1/cases` - Opret ny sag
- `GET /api/v1/cases/{id}` - Vis detaljer om en sag
- `PATCH /api/v1/cases/{id}` - Opdater en sag
- `DELETE /api/v1/cases/{id}` - Slet en sag (soft-delete, sætter deleted_at)
**Relationer:**
- `GET /api/v1/cases/{id}/relations` - Vis alle relaterede sager
- `POST /api/v1/cases/{id}/relations` - Tilføj relation til anden sag
- `DELETE /api/v1/cases/{id}/relations/{relation_id}` - Fjern relation
**Tags:**
- `GET /api/v1/cases/{id}/tags` - Vis alle tags på sagen
- `POST /api/v1/cases/{id}/tags` - Tilføj tag
- `DELETE /api/v1/cases/{id}/tags/{tag_id}` - Fjern tag
### UI-koncept
**Sag-listen** (`/sag`):
- Alle dine sager på ét sted
- Filter: "Mine sager", "Åbne sager", "Sager med tag=support", "Sager med tag=urgent"
- Søgebar
- Sortering efter deadline, oprettelsestid, status
**Sag-listen** (`/cases`):
**Sag-detaljer** (`/cases/{id}`):
- Hovedinfo: titel, beskrivelse, status, deadline
- **Relaterede sager**: Sektioner som:
- "Forælder-sag" (hvis denne sag er en del af noget større)
- "Barn-sager" (sager der er afledt af denne)
- "Blokeret af" (sager der holder denne op)
- "Udfører for" (hvis denne er udførelsessag for noget)
- **Tags**: Viste tags, mulighed for at tilføje flere
- **Ansvarlig**: Hvem skal håndtere det
- **Historie**: Hvis modulet får aktivitetslog senere
**Designet:**
- Nordic Top minimalistisk design
- Dark mode support
- Responsive (mobil-venligt)
- Intuitivt navigation mellem relaterede sager
---
## Implementeringsplan - Trin for trin
### Fase 1: Modul-struktur (forberedelse)
#### Trin 1.1: Opret modul-mappen
```
app/modules/sag/
├── module.json # Modulets metadata
├── README.md # Dokumentation
├── backend/
│ ├── __init__.py
│ └── router.py # FastAPI endpoints
├── frontend/
│ ├── __init__.py
│ └── views.py # HTML views
├── templates/
│ ├── index.html # Sag-liste
│ └── detail.html # Sag-detaljer
└── migrations/
└── 001_init.sql # Database schema
```
#### Trin 1.2: Opret module.json
```json
{
"name": "sag",
"version": "1.0.0",
"description": "Universel sag-håndtering - tickets, opgaver og ordrer som sager med relationer",
"author": "BMC Networks",
"enabled": true,
"dependencies": [],
"table_prefix": "sag_",
"api_prefix": "/api/v1/cases",
"tags": ["Sag", "Case Management"],
"config": {
"safety_switches": {
"read_only": false,
"dry_run": false
}
}
}
```
### Fase 2: Database-setup
#### Trin 2.1: Opret migrations/001_init.sql
SQL-migrations definerer tabeller for sager, relationer og tags. Se `migrations/001_init.sql` for detaljer.
**Vigtige points:**
- Alle tabelnavne starter med `sag_`
- Soft-delete: `deleted_at` kolonne hvor man checker `WHERE deleted_at IS NULL`
- Foreign keys til `customers` for at linke til kundedata
- Indexes for performance
- Triggers til auto-update af `updated_at`
**Eksempel-query (queries filtrerer soft-deleted):**
```sql
SELECT * FROM sag_sager
WHERE customer_id = %s
AND deleted_at IS NULL
ORDER BY created_at DESC;
```
### Fase 3: Backend-API
#### Trin 3.1: Opret backend/router.py
Implementer alle 9 API-endpoints med disse mønstre:
**GET /cases (list):**
```python
@router.get("/cases")
async def list_sager(
status: str = None,
tag: str = None,
customer_id: int = None,
ansvarlig_bruger_id: int = None
):
# Build query med WHERE deleted_at IS NULL
# Filter efter parameters
# Return liste
```
**POST /cases (create):**
```python
@router.post("/cases")
async def create_sag(sag_data: dict):
# Validér input
# INSERT INTO sag_sager
# RETURNING *
# Return ny sag
```
**GET /cases/{id}:**
```python
@router.get("/cases/{id}")
async def get_sag(id: int):
# SELECT * FROM sag_sager WHERE id = %s AND deleted_at IS NULL
# Hvis ikke found: HTTPException(404)
# Return sag detaljer
```
**PATCH /cases/{id} (update):**
```python
@router.patch("/cases/{id}")
async def update_sag(id: int, updates: dict):
# UPDATE sag_sager SET ... WHERE id = %s
# Automatisk updated_at via trigger
# Return opdateret sag
```
**DELETE /cases/{id} (soft-delete):**
```python
@router.delete("/cases/{id}")
async def delete_sag(id: int):
# UPDATE sag_sager SET deleted_at = NOW() WHERE id = %s
# Return success
```
**Relationer endpoints:** Lignende pattern for `/cases/{id}/relations`
**Tags endpoints:** Lignende pattern for `/cases/{id}/tags`
**Vigtige mønstre:**
- Altid bruge `execute_query()` fra `app.core.database`
- Parameteriserede queries (`%s` placeholders)
- `RealDictCursor` for dict-like row access
- Filtrer `WHERE deleted_at IS NULL` på alle SELECT queries
- Eksportér router som `router` (module loader leder efter denne)
### Fase 4: Frontend-views
#### Trin 4.1: Opret frontend/views.py
```python
from fastapi import APIRouter
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
router = APIRouter()
templates = Jinja2Templates(directory="app/modules/sag/templates")
@router.get("/cases", response_class=HTMLResponse)
async def cases_liste(request):
# Hent sager fra API
return templates.TemplateResponse("index.html", {"request": request, "cases": ...})
@router.get("/cases/{id}", response_class=HTMLResponse)
async def sag_detaljer(request, id: int):
# Hent sag + relationer + tags
return templates.TemplateResponse("detail.html", {"request": request, "sag": ..., "relationer": ...})
```
### Fase 5: Frontend-templates
#### Trin 5.1: Opret templates/index.html
Sag-listen med:
- Search-bar
- Filter-knapper (status, tags, ansvarlig)
- Tabel/kort-view med alle sager
- Klikkable sager der går til `/sag/{id}`
- Nordic Top design med dark mode
#### Trin 5.2: Opret templates/detail.html
Sag-detaljer med:
- Hovedinfo: titel, beskrivelse, status, deadline, ansvarlig
- Sektioner: "Relaterede sager", "Tags", "Aktivitet" (hvis implementeret senere)
- Knap til at redigere sagen
- Knap til at tilføje relation
- Knap til at tilføje tag
- Mulighed for at se og slette relationer/tags
### Fase 6: Test og aktivering
#### Trin 6.1: Test databasen
```bash
docker compose exec db psql -U bmc_admin -d bmc_hub -c "SELECT * FROM sag_sager;"
```
#### Trin 6.2: Test API-endpoints
```bash
# Opret sag
curl -X POST http://localhost:8001/api/v1/cases \
-H "Content-Type: application/json" \
-d '{"titel": "Test sag", "customer_id": 1}'
# Hent sag
curl http://localhost:8001/api/v1/cases/1
# Hent sag-liste
curl http://localhost:8001/api/v1/cases
```
#### Trin 6.3: Test frontend
- Besøg http://localhost:8001/cases
- Se sag-liste
- Klik på sag → se detaljer
- Tilføj tag, relation
#### Trin 6.4: Test soft-delete
- Slet sag via `DELETE /cases/{id}`
- Check databasen: `deleted_at` skal være sat
- Verify den ikke vises i list-endpoints mere
#### Trin 6.5: Test modul-deaktivering
- Rediger `module.json`: sæt `"enabled": false`
- Restart Docker: `docker compose restart api`
- Besøg http://localhost:8001/cases → 404
- Besøg http://localhost:8001/api/v1/cases → 404
- Revert: `"enabled": true`, restart, verifiér det virker igen
### Fase 7: Dokumentation
#### Trin 7.1: Opret README.md i modulet
Dokumenter:
- Hvad modulet gør
- API-endpoints med eksempler
- Database-schema
- Hvordan man bruger relationer og tags
- Eksempel-workflows
---
## Vigtige principper under implementeringen
### 1. **Soft-delete først**
Alle `DELETE` operationer sætter `deleted_at` til `NOW()` i stedet for at slette fysisk. Det betyder:
- Data bevares hvis modulet deaktiveres
- Audit trail bevares
- Relationer forbliver intakte
### 2. **Always filter deleted_at**
Alle SELECT queries skal have:
```sql
WHERE deleted_at IS NULL
```
Undtagelse: Admin-sider der skal se "deleted history" (implementeres senere).
### 3. **Foreign keys til customers**
Alle sager skal være knyttet til en `customer_id`. Det gør det muligt at:
- Lave customer-specifikke views senere
- Sikre data-isolation
- Tracke hvem sagerne handler om
### 4. **Relationer er data**
Relationer er ikke blot links - de er egne database-records med type og soft-delete. Det betyder:
- Du kan se hele historien af relationer
- Du kan "gendanne" relationer hvis de slettes
- Relationstyper er konfigurerbare
### 5. **Tags driver visibility**
Tags bruges til:
- UI-filtre: "Vis kun sager med tag=urgent"
- Workflow: "Sager med tag=support skal have SLA"
- Kategorisering: "Alt med tag=ompakning"
---
## Hvad efter?
Når Sag-modulet er live, kan du:
1. **Konvertere tickets til sager** - Migrationsscript der tager gamle tickets og laver dem til sager
2. **Konvertere opgaver til sager** - Samme pattern
3. **Tilføje aktivitetslog** - "Hvem ændrede hvad hvornår" på hver sag
4. **Integrere med e-conomic** - Når en sag får tag=faktura, oprettes den som ordre i e-conomic
5. **Tilføje workflowkonfiguration** - "Hvis status=i_gang og tag=urgent, send reminder hver dag"
6. **Tilføje dependencies** - "Sag B kan ikke starte før Sag A er done"
7. **Tilføje SLA-tracking** - "Support-sager skal løses inden 24 timer"
Men først: **Få grundlaget på plads med denne modul-implementering.**
---
## Kommandoer til at komme i gang
```bash
# Gå til workspace
cd /Users/christianthomas/DEV/bmc_hub_dev
# Se hvor vi er
docker compose ps -a
# Start dev-miljø hvis det ikke kører
docker compose up -d
# Se logs
docker compose logs -f api
# Efter at have lavet koden: restart API
docker compose restart api
# Test at modulet loadet
docker compose logs api | grep -i "sag"
# Manuelt test af database-migration
docker compose exec db psql -U bmc_admin -d bmc_hub -c "\dt sag_*"
```
---
## Tidsestimation
- **Fase 1-2 (modul + database)**: 30 min
- **Fase 3 (backend API)**: 1-2 timer
- **Fase 4-5 (frontend)**: 1-2 timer
- **Fase 6 (test)**: 30 min
- **Fase 7 (dokumentation)**: 30 min
**Total: 4-6 timer**
---
## TL;DR - for implementer
1. Opret `app/modules/sag/` med standard-struktur
2. Opret `module.json` med `"enabled": true`
3. Opret `migrations/001_init.sql` med 3 tabeller (`sag_sager`, `sag_relationer`, `sag_tags`)
4. Implementer 9 API-endpoints i `backend/router.py` (alle queries filtrerer `deleted_at IS NULL`)
5. Implementer 2 HTML-views i `frontend/views.py` (liste + detaljer)
6. Opret 2 templates i `templates/` (index.html + detail.html)
7. Test endpoints og UI
8. Verifiér soft-delete virker
9. Verifiér modulet kan deaktiveres og data bevares
10. Skrive README.md
**Modulet bliver automatisk loadet af system - ingen manual registration nødvendig.**