Skip to content

Commit fc0fb19

Browse files
authored
Merge pull request #95 from itk-dev-rpa/feature/nova-group-caseworker
Feature/nova group caseworker
2 parents d7675f0 + 47a17c1 commit fc0fb19

File tree

10 files changed

+184
-80
lines changed

10 files changed

+184
-80
lines changed

changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- kmd_nova: Added support for case worker groups/units.
13+
1014
### Fixed
1115

1216
- Child folders in Graph are now found, even with more than 10 child folders to go through.

itk_dev_shared_components/kmd_nova/nova_cases.py

Lines changed: 21 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
to the KMD Nova api."""
33

44
import uuid
5-
import base64
65
import urllib.parse
76

87
import requests
98

109
from itk_dev_shared_components.kmd_nova.authentication import NovaAccess
11-
from itk_dev_shared_components.kmd_nova.nova_objects import NovaCase, CaseParty, JournalNote, Caseworker, Department
12-
from itk_dev_shared_components.kmd_nova.util import datetime_from_iso_string
10+
from itk_dev_shared_components.kmd_nova.nova_objects import NovaCase, CaseParty, Department
11+
from itk_dev_shared_components.kmd_nova.util import datetime_from_iso_string, extract_caseworker
1312

1413

1514
def get_case(case_uuid: str, nova_access: NovaAccess) -> NovaCase:
@@ -125,7 +124,7 @@ def _get_nova_cases(nova_access: NovaAccess, payload: dict) -> list[NovaCase]:
125124
kle_number = case_dict['caseClassification']['kleNumber']['code'],
126125
proceeding_facet = case_dict['caseClassification']['proceedingFacet']['code'],
127126
sensitivity = case_dict["sensitivity"]["sensitivity"],
128-
caseworker = _extract_case_worker(case_dict),
127+
caseworker = extract_caseworker(case_dict),
129128
security_unit=security_unit,
130129
responsible_department=responsible_department
131130
)
@@ -189,6 +188,11 @@ def _create_payload(*, case_uuid: str = None, identification: str = None, identi
189188
"novaUserId": True,
190189
"fullName": True,
191190
"racfId": True
191+
},
192+
"losIdentity": {
193+
"novaUnitId": True,
194+
"fullName": True,
195+
"administrativeUnitId": True
192196
}
193197
},
194198
"responsibleDepartment": {
@@ -235,29 +239,6 @@ def _extract_departments(case_dict: dict) -> tuple[Department, Department]:
235239
return security_unit, responsible_department
236240

237241

238-
def _extract_case_worker(case_dict: dict) -> Caseworker | None:
239-
"""Extract the case worker from a HTTP request response.
240-
If the case worker is in a unexpected format, None is returned.
241-
242-
Args:
243-
case_dict: The dictionary describing the case.
244-
245-
Returns:
246-
A case worker object describing the case worker if any.
247-
"""
248-
if 'caseworker' in case_dict:
249-
try:
250-
return Caseworker(
251-
uuid = case_dict['caseworker']['kspIdentity']['novaUserId'],
252-
name = case_dict['caseworker']['kspIdentity']['fullName'],
253-
ident = case_dict['caseworker']['kspIdentity']['racfId']
254-
)
255-
except KeyError:
256-
return None
257-
258-
return None
259-
260-
261242
def _extract_case_parties(case_dict: dict) -> list[CaseParty]:
262243
"""Extract the case parties from a HTTP request response.
263244
@@ -281,29 +262,6 @@ def _extract_case_parties(case_dict: dict) -> list[CaseParty]:
281262
return parties
282263

283264

284-
def _extract_journal_notes(case_dict: dict) -> list:
285-
"""Extract the journal notes from a HTTP request response.
286-
287-
Args:
288-
case_dict: The dictionary describing the journal note.
289-
290-
Returns:
291-
A journal note object describing the journal note.
292-
"""
293-
notes = []
294-
for note_dict in case_dict['journalNotes']['journalNotes']:
295-
note = JournalNote(
296-
uuid = note_dict['uuid'],
297-
title = note_dict['journalNoteAttributes']['title'],
298-
journal_date = note_dict['journalNoteAttributes']['journalNoteDate'],
299-
note_format = note_dict['journalNoteAttributes']['format'],
300-
note = base64.b64decode(note_dict['journalNoteAttributes']['note']),
301-
approved = note_dict['journalNoteAttributes'].get('approved', False)
302-
)
303-
notes.append(note)
304-
return notes
305-
306-
307265
def add_case(case: NovaCase, nova_access: NovaAccess):
308266
"""Add a case to KMD Nova. The case will be created as 'Active'.
309267
@@ -371,12 +329,20 @@ def add_case(case: NovaCase, nova_access: NovaAccess):
371329
}
372330

373331
if case.caseworker:
374-
payload['caseworker'] = {
375-
"kspIdentity": {
376-
"racfId": case.caseworker.ident,
377-
"fullName": case.caseworker.name
332+
if case.caseworker.type == 'user':
333+
payload['caseworker'] = {
334+
"kspIdentity": {
335+
"racfId": case.caseworker.ident,
336+
"fullName": case.caseworker.name
337+
}
338+
}
339+
elif case.caseworker.type == 'group':
340+
payload['caseworker'] = {
341+
"losIdentity": {
342+
"administrativeUnitId": case.caseworker.ident,
343+
"fullName": case.caseworker.name
344+
}
378345
}
379-
}
380346

381347
headers = {'Content-Type': 'application/json', 'Authorization': f"Bearer {nova_access.get_bearer_token()}"}
382348

itk_dev_shared_components/kmd_nova/nova_documents.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
import requests
1111

1212
from itk_dev_shared_components.kmd_nova.authentication import NovaAccess
13-
from itk_dev_shared_components.kmd_nova.nova_objects import Document, Caseworker
14-
from itk_dev_shared_components.kmd_nova. util import datetime_from_iso_string
13+
from itk_dev_shared_components.kmd_nova.nova_objects import Document
14+
from itk_dev_shared_components.kmd_nova.util import datetime_from_iso_string, extract_caseworker
1515

1616

1717
def get_documents(case_uuid: str, nova_access: NovaAccess) -> list[Document]:
@@ -56,16 +56,6 @@ def get_documents(case_uuid: str, nova_access: NovaAccess) -> list[Document]:
5656

5757
documents = []
5858
for document_dict in response.json()['documents']:
59-
60-
if 'caseworker' in document_dict:
61-
caseworker = Caseworker(
62-
uuid = document_dict['caseworker']['kspIdentity']['novaUserId'],
63-
name = document_dict['caseworker']['kspIdentity']['fullName'],
64-
ident = document_dict['caseworker']['kspIdentity']['racfId']
65-
)
66-
else:
67-
caseworker = None
68-
6959
doc = Document(
7060
uuid = document_dict['documentUuid'],
7161
document_number = document_dict['documentNumber'],
@@ -78,7 +68,7 @@ def get_documents(case_uuid: str, nova_access: NovaAccess) -> list[Document]:
7868
file_extension = document_dict['fileExtension'],
7969
category_name = document_dict.get('documentCategoryName'),
8070
category_uuid = document_dict.get('documentCategoryUuid'),
81-
caseworker=caseworker
71+
caseworker=extract_caseworker(document_dict)
8272
)
8373
documents.append(doc)
8474

@@ -197,12 +187,20 @@ def attach_document_to_case(case_uuid: str, document: Document, nova_access: Nov
197187
}
198188

199189
if document.caseworker:
200-
payload['caseworker'] = {
201-
"kspIdentity": {
202-
"racfId": document.caseworker.ident,
203-
"fullName": document.caseworker.name
190+
if document.caseworker.type == 'user':
191+
payload['caseworker'] = {
192+
"kspIdentity": {
193+
"racfId": document.caseworker.ident,
194+
"fullName": document.caseworker.name
195+
}
196+
}
197+
elif document.caseworker.type == 'group':
198+
payload['caseworker'] = {
199+
"losIdentity": {
200+
"administrativeUnitId": document.caseworker.ident,
201+
"fullName": document.caseworker.name
202+
}
204203
}
205-
}
206204

207205
headers = {'Content-Type': 'application/json', 'Authorization': f"Bearer {nova_access.get_bearer_token()}"}
208206
response = requests.post(url, params=params, headers=headers, json=payload, timeout=60)

itk_dev_shared_components/kmd_nova/nova_notes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def add_text_note(case_uuid: str, note_title: str, note_text: str, caseworker: C
1818
case_uuid: The uuid of the case to add the journal note to.
1919
note_title: The title of the note.
2020
note_text: The text content of the note.
21+
caseworker: The author of the note.
2122
approved: Whether the journal note should be marked as approved in Nova.
2223
nova_access: The NovaAccess object used to authenticate.
2324

itk_dev_shared_components/kmd_nova/nova_objects.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Caseworker:
2222
uuid: str
2323
name: str
2424
ident: str
25+
type: Literal['user', 'group'] = 'user'
2526

2627

2728
@dataclass(slots=True, kw_only=True)

itk_dev_shared_components/kmd_nova/nova_tasks.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@ def attach_task_to_case(case_uuid: str, task: Task, nova_access: NovaAccess) ->
3535
"caseUuid": case_uuid,
3636
"title": task.title,
3737
"description": task.description, # Optional
38-
"caseworkerPersonId": task.caseworker.uuid, # Optional
3938
"statusCode": task.status_code,
4039
"deadline": datetime_to_iso_string(task.deadline),
4140
"startDate": datetime_to_iso_string(task.started_date), # Optional
4241
"closeDate": datetime_to_iso_string(task.closed_date), # Optional
4342
"taskTypeName": "Aktivitet"
4443
}
4544

45+
if task.caseworker.type == 'user':
46+
payload["caseworkerPersonId"] = task.caseworker.uuid
47+
elif task.caseworker.type == 'group':
48+
payload["caseworkerGroupId"] = task.caseworker.uuid
49+
4650
headers = {'Content-Type': 'application/json', 'Authorization': f"Bearer {nova_access.get_bearer_token()}"}
4751
response = requests.post(url, params=params, headers=headers, json=payload, timeout=60)
4852
response.raise_for_status()
@@ -114,7 +118,16 @@ def _extract_caseworker(task_dict: dict) -> Caseworker | None:
114118
return Caseworker(
115119
uuid = task_dict['caseWorker']['id'],
116120
ident = task_dict['caseWorker']['ident'],
117-
name = task_dict['caseWorker']['name']
121+
name = task_dict['caseWorker']['name'],
122+
type='user'
123+
)
124+
125+
if 'caseWorkerGroup' in task_dict:
126+
return Caseworker(
127+
uuid = task_dict['caseWorkerGroup']['id'],
128+
ident = None,
129+
name = task_dict['caseWorkerGroup']['name'],
130+
type='group'
118131
)
119132

120133
return None
@@ -143,14 +156,18 @@ def update_task(task: Task, case_uuid: str, nova_access: NovaAccess):
143156
"caseUuid": case_uuid,
144157
"title": task.title,
145158
"description": task.description,
146-
"caseworkerPersonId": task.caseworker.uuid,
147159
"statusCode": task.status_code,
148160
"deadline": datetime_to_iso_string(task.deadline),
149161
"startDate": datetime_to_iso_string(task.started_date),
150162
"closeDate": datetime_to_iso_string(task.closed_date),
151163
"taskType": "Aktivitet"
152164
}
153165

166+
if task.caseworker.type == 'user':
167+
payload["caseworkerPersonId"] = task.caseworker.uuid
168+
elif task.caseworker.type == 'group':
169+
payload["caseworkerGroupId"] = task.caseworker.uuid
170+
154171
headers = {'Content-Type': 'application/json', 'Authorization': f"Bearer {nova_access.get_bearer_token()}"}
155172
response = requests.put(url, params=params, headers=headers, json=payload, timeout=60)
156173
response.raise_for_status()

itk_dev_shared_components/kmd_nova/util.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from datetime import datetime
44
from typing import Optional
55

6+
from itk_dev_shared_components.kmd_nova.nova_objects import Caseworker
7+
68

79
def datetime_from_iso_string(date_string: Optional[str]) -> Optional[datetime]:
810
"""A helper function to convert an ISO date string to a datetime.
@@ -34,3 +36,37 @@ def datetime_to_iso_string(datetime_: Optional[datetime]) -> Optional[str]:
3436
return datetime_.isoformat()
3537

3638
return None
39+
40+
41+
def extract_caseworker(response_dict: dict) -> Caseworker | None:
42+
"""Extract the case worker from a HTTP request response.
43+
If the case worker is in a unexpected format, None is returned.
44+
45+
Args:
46+
response_dict: The dictionary describing the response object.
47+
48+
Returns:
49+
A case worker object describing the case worker if any.
50+
"""
51+
try:
52+
if 'caseworker' in response_dict:
53+
if 'kspIdentity' in response_dict['caseworker']:
54+
return Caseworker(
55+
uuid = response_dict['caseworker']['kspIdentity']['novaUserId'],
56+
name = response_dict['caseworker']['kspIdentity']['fullName'],
57+
ident = response_dict['caseworker']['kspIdentity']['racfId'],
58+
type='user'
59+
)
60+
61+
if 'losIdentity' in response_dict['caseworker']:
62+
return Caseworker(
63+
uuid = response_dict['caseworker']['losIdentity']['novaUnitId'],
64+
name = response_dict['caseworker']['losIdentity']['fullName'],
65+
ident = str(response_dict['caseworker']['losIdentity']['administrativeUnitId']),
66+
type='group'
67+
)
68+
69+
except KeyError:
70+
pass
71+
72+
return None

tests/readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,11 @@ NOVA_PARTY = '6101009805,Test Test'
6262
NOVA_DEPARTMENT = '{"id": 818485,"name": "Borgerservice","user_key": "4BBORGER"}'
6363
NOVA_CREDENTIALS = 'nova_login,nova_password'
6464
NOVA_USER = '{"name": "svcitkopeno svcitkopeno", "ident": "AZX0080", "uuid": "0bacdddd-5c61-4676-9a61-b01a18cec1d5"}'
65+
NOVA_USER_GROUP = '{"name": "ÅÅÅ_Frontoffice", "ident": "819697", "uuid": "144d7ab7-302f-4c62-83d2-dcdefcd92dea"}'
6566

6667
NOVA_CVR_CASE = '{"cvr": "55133018", "case_title": "rpa_testcase", "case_number": "S2024-25614"}'
6768
NOVA_CPR_CASE = '{"cpr": "6101009805", "case_title": "Meget_Unik_Case_Overskrift", "case_number": "S2023-61078"}'
69+
NOVA_GROUP_CASE = '{"cpr": "6101009805", "case_title": "Testsag brugergruppe", "case_number": "S2025-5318"}'
6870

6971
Note: The NOVA_CVR_CASE and NOVA_CPR_CASE variables require cases to be created in Nova, and the parameters set from those cases.
7072

tests/test_nova_api/test_cases.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,6 @@ def test_get_cvr_cases(self):
6161
case_title = cvr_case['case_title']
6262
case_number = cvr_case['case_number']
6363

64-
cases = nova_cases.get_cvr_cases(cvr=cvr, nova_access=self.nova_access)
65-
self.assertIsInstance(cases[0], NovaCase)
66-
self.assertEqual(cases[0].case_parties[0].identification, cvr)
67-
6864
cases = nova_cases.get_cvr_cases(case_number=case_number, nova_access=self.nova_access)
6965
self.assertEqual(cases[0].case_number, case_number)
7066

@@ -147,6 +143,56 @@ def test_add_case(self):
147143
self.assertEqual(nova_case.responsible_department.id, case.responsible_department.id)
148144
self.assertEqual(nova_case.security_unit.id, case.security_unit.id)
149145

146+
def test_user_groups(self):
147+
"""Test getting and adding a case with a user group as caseworker."""
148+
# Get case
149+
group_case = json.loads(os.environ['NOVA_GROUP_CASE'])
150+
cpr = group_case['cpr']
151+
case_number = group_case['case_number']
152+
caseworker_dict = json.loads(os.environ['NOVA_USER_GROUP'])
153+
caseworker = Caseworker(
154+
type='group',
155+
**caseworker_dict
156+
)
157+
158+
cases = nova_cases.get_cases(cpr=cpr, case_number=case_number, nova_access=self.nova_access)
159+
160+
self.assertEqual(len(cases), 1)
161+
nova_case = cases[0]
162+
self.assertIsInstance(nova_case, NovaCase)
163+
164+
self.assertEqual(nova_case.caseworker, caseworker)
165+
166+
# Add case
167+
nova_party = os.getenv('NOVA_PARTY').split(',')
168+
party = CaseParty(
169+
role="Primær",
170+
identification_type="CprNummer",
171+
identification=nova_party[0],
172+
name=nova_party[1]
173+
)
174+
175+
department_dict = json.loads(os.environ['NOVA_DEPARTMENT'])
176+
department = Department(
177+
**department_dict
178+
)
179+
180+
case = NovaCase(
181+
uuid=str(uuid.uuid4()),
182+
title=f"Test {datetime.now()}",
183+
case_date=datetime.now(),
184+
progress_state="Opstaaet",
185+
case_parties=[party],
186+
kle_number="23.05.01",
187+
proceeding_facet="G01",
188+
sensitivity="Fortrolige",
189+
caseworker=caseworker,
190+
responsible_department=department,
191+
security_unit=department
192+
)
193+
194+
nova_cases.add_case(case, self.nova_access)
195+
150196

151197
if __name__ == '__main__':
152198
unittest.main()

0 commit comments

Comments
 (0)