# Implementeringsplan: Sag-modulet (Case Module) ## Oversigt – Hvad er “Sag”? **Sag-modulet** er hjertet i BMC Hub’s 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: 1–2 timer - Frontend (liste + detalje): 1–2 timer - Test + dokumentation: 1 time **Total: 4–6 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.**