- 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.
5.6 KiB
5.6 KiB
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:
åbenorlukket - No workflow embedded in status
- All workflow managed via tags
2. Tags (sag_tags)
- Represent work to be done
- Have state:
openorclosed - Never deleted - only closed when work completes
- Closing a tag = completion of responsibility
3. Relations (sag_relationer)
- First-class data
- Directional:
kilde_sag_id→må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)
idSERIAL PRIMARY KEYtitelVARCHAR(255) NOT NULLbeskrivelseTEXTtemplate_keyVARCHAR(100) - used only at creationstatusVARCHAR(50) CHECK (status IN ('åben', 'lukket'))customer_idINT - links to customers tableansvarlig_bruger_idINTcreated_by_user_idINTdeadlineTIMESTAMPcreated_atTIMESTAMP DEFAULT NOW()updated_atTIMESTAMP DEFAULT NOW()deleted_atTIMESTAMP
sag_tags (Tags)
idSERIAL PRIMARY KEYsag_idINT NOT NULL REFERENCES sag_sager(id)tag_navnVARCHAR(100) NOT NULLstateVARCHAR(20) CHECK (state IN ('open', 'closed'))closed_atTIMESTAMPcreated_atTIMESTAMP DEFAULT NOW()deleted_atTIMESTAMP
sag_relationer (Relations)
idSERIAL PRIMARY KEYkilde_sag_idINT NOT NULL REFERENCES sag_sager(id)målsag_idINT NOT NULL REFERENCES sag_sager(id)relationstypeVARCHAR(50) NOT NULLcreated_atTIMESTAMP DEFAULT NOW()deleted_atTIMESTAMPCHECK (kilde_sag_id != målsag_id)
sag_kontakter (Contact Links)
idSERIAL PRIMARY KEYsag_idINT NOT NULL REFERENCES sag_sager(id)contact_idINT NOT NULL REFERENCES contacts(id)roleVARCHAR(50)created_atTIMESTAMP DEFAULT NOW()deleted_atTIMESTAMP- UNIQUE(sag_id, contact_id)
sag_kunder (Customer Links)
idSERIAL PRIMARY KEYsag_idINT NOT NULL REFERENCES sag_sager(id)customer_idINT NOT NULL REFERENCES customers(id)roleVARCHAR(50)created_atTIMESTAMP DEFAULT NOW()deleted_atTIMESTAMP- UNIQUE(sag_id, customer_id)
API Endpoints
Cases CRUD
GET /api/v1/cases- List all casesPOST /api/v1/cases- Create caseGET /api/v1/cases/{id}- Get casePATCH /api/v1/cases/{id}- Update caseDELETE /api/v1/cases/{id}- Soft-delete case
Tags
GET /api/v1/cases/{id}/tags- List tagsPOST /api/v1/cases/{id}/tags- Add tagPATCH /api/v1/cases/{id}/tags/{tag_id}/state- Toggle tag stateDELETE /api/v1/cases/{id}/tags/{tag_id}- Soft-delete tag
Relations
GET /api/v1/cases/{id}/relations- List relationsPOST /api/v1/cases/{id}/relations- Create relationDELETE /api/v1/cases/{id}/relations/{rel_id}- Soft-delete relation
Contacts & Customers
GET /api/v1/cases/{id}/contacts- List linked contactsPOST /api/v1/cases/{id}/contacts- Link contactDELETE /api/v1/cases/{id}/contacts/{contact_id}- Unlink contactGET /api/v1/cases/{id}/customers- List linked customersPOST /api/v1/cases/{id}/customers- Link customerDELETE /api/v1/cases/{id}/customers/{customer_id}- Unlink customer
Search
GET /api/v1/search/cases?q={query}- Search casesGET /api/v1/search/contacts?q={query}- Search contactsGET /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'
})
Link Related Case
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:
- Create the Order independently
- Create a relation: Case → Order
- Use relationstype:
ordre_oprettetor 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