bmc_hub/app/modules/sag
2026-02-01 00:25:02 +01:00
..
backend Add tests for new SAG module endpoints and module deactivation 2026-01-31 23:16:24 +01:00
frontend Add tests for new SAG module endpoints and module deactivation 2026-01-31 23:16:24 +01:00
migrations feat(sag): Initialize case management module with CRUD operations, relations, and tags 2026-01-29 23:07:33 +01:00
templates feat(tag-picker): Enhance keyboard shortcut context handling and logging 2026-02-01 00:25:02 +01:00
module.json feat(sag): Initialize case management module with CRUD operations, relations, and tags 2026-01-29 23:07:33 +01:00
README.md Add tests for new SAG module endpoints and module deactivation 2026-01-31 23:16:24 +01:00

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_idmå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)
  • 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)
  • 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
  • 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

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

response = requests.post('http://localhost:8001/api/v1/cases/1/tags', json={
    'tag_navn': 'urgent'
})

Close a Tag (Mark Work Complete)

response = requests.patch('http://localhost:8001/api/v1/cases/1/tags/5/state', json={
    'state': 'closed'
})
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