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

20 KiB
Raw Blame History

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å:

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

{
  "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):

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() 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

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

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

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:

  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

# 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.