- 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.
20 KiB
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)
-
Kunde ringer og skal have ny skærm
- Dette er en Sag
- Tags:
support,urgent - Ansvarlig: Support
- Status: åben
-
Indkøb af skærm hos leverandør
- Dette er også en Sag
- Tags:
indkøb - Relation: afledt fra kundesagen
- Ansvarlig: Indkøb
-
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_keymå 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_atbruges kun til teknisk fjernelse / rollback
API-endpoints
Cases
GET /api/v1/casesPOST /api/v1/casesGET /api/v1/cases/{id}PATCH /api/v1/cases/{id}DELETE /api/v1/cases/{id}(soft-delete)
Relationer
GET /api/v1/cases/{id}/relationsPOST /api/v1/cases/{id}/relationsDELETE /api/v1/cases/{id}/relations/{relation_id}
Tags
GET /api/v1/cases/{id}/tagsPOST /api/v1/cases/{id}/tagsPATCH /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å:
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)
- Der findes kun én entitet: Sag
template_keybruges kun ved oprettelse- Status er binær – proces styres via tags
- Tags lukkes, de slettes ikke
- Relationer er data, ikke implicit logik
- Alle sletninger er soft-deletes
- 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:
-
Kunde ringer og skal have ny skærm
- Dette er en Sag (ticket-type med tag:
support) - Den får tag:
urgentfordi det er ekspres
- Dette er en Sag (ticket-type med tag:
-
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
- Dette er også en Sag (ordre-type med tag:
-
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
- Dette er en Sag (opgave-type med tag:
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 sagGET /api/v1/cases/{id}- Vis detaljer om en sagPATCH /api/v1/cases/{id}- Opdater en sagDELETE /api/v1/cases/{id}- Slet en sag (soft-delete, sætter deleted_at)
Relationer:
GET /api/v1/cases/{id}/relations- Vis alle relaterede sagerPOST /api/v1/cases/{id}/relations- Tilføj relation til anden sagDELETE /api/v1/cases/{id}/relations/{relation_id}- Fjern relation
Tags:
GET /api/v1/cases/{id}/tags- Vis alle tags på sagenPOST /api/v1/cases/{id}/tags- Tilføj tagDELETE /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
{
"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_atkolonne hvor man checkerWHERE deleted_at IS NULL - Foreign keys til
customersfor at linke til kundedata - Indexes for performance
- Triggers til auto-update af
updated_at
Eksempel-query (queries filtrerer soft-deleted):
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):
@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):
@router.post("/cases")
async def create_sag(sag_data: dict):
# Validér input
# INSERT INTO sag_sager
# RETURNING *
# Return ny sag
GET /cases/{id}:
@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):
@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):
@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()fraapp.core.database - Parameteriserede queries (
%splaceholders) RealDictCursorfor dict-like row access- Filtrer
WHERE deleted_at IS NULLpå alle SELECT queries - Eksportér router som
router(module loader leder efter denne)
Fase 4: Frontend-views
Trin 4.1: Opret frontend/views.py
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
docker compose exec db psql -U bmc_admin -d bmc_hub -c "SELECT * FROM sag_sager;"
Trin 6.2: Test API-endpoints
# 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_atskal 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:
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:
- Konvertere tickets til sager - Migrationsscript der tager gamle tickets og laver dem til sager
- Konvertere opgaver til sager - Samme pattern
- Tilføje aktivitetslog - "Hvem ændrede hvad hvornår" på hver sag
- Integrere med e-conomic - Når en sag får tag=faktura, oprettes den som ordre i e-conomic
- Tilføje workflowkonfiguration - "Hvis status=i_gang og tag=urgent, send reminder hver dag"
- Tilføje dependencies - "Sag B kan ikke starte før Sag A er done"
- 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
# 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
- Opret
app/modules/sag/med standard-struktur - Opret
module.jsonmed"enabled": true - Opret
migrations/001_init.sqlmed 3 tabeller (sag_sager,sag_relationer,sag_tags) - Implementer 9 API-endpoints i
backend/router.py(alle queries filtrererdeleted_at IS NULL) - Implementer 2 HTML-views i
frontend/views.py(liste + detaljer) - Opret 2 templates i
templates/(index.html + detail.html) - Test endpoints og UI
- Verifiér soft-delete virker
- Verifiér modulet kan deaktiveres og data bevares
- Skrive README.md
Modulet bliver automatisk loadet af system - ingen manual registration nødvendig.