Skip to content

Commit b728462

Browse files
committed
v0.19.2: Separate sub-workflow tasks from parent approval history
- Group approval tasks by workflow_stage_id instead of stage_number - Separate parent workflow tasks from sub-workflow tasks into distinct sections in submission_detail view - Add 'Sub-Workflow Approvals' section in submission_detail template with its own stage cards
1 parent 26609b5 commit b728462

3 files changed

Lines changed: 101 additions & 13 deletions

File tree

django_forms_workflows/templates/django_forms_workflows/submission_detail.html

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,75 @@ <h5 class="mb-3"><i class="bi bi-diagram-3"></i> Approval History</h5>
241241
</div>
242242
{% endfor %}
243243

244+
{% if sub_workflow_groups %}
245+
{# ---- Sub-workflow tasks (detached child workflow stages) ---- #}
246+
<h5 class="mb-3 mt-4"><i class="bi bi-diagram-2"></i> Sub-Workflow Approvals</h5>
247+
{% for stage in sub_workflow_groups %}
248+
<div class="card mb-3
249+
{% if stage.rejected_count > 0 %}border-danger
250+
{% elif stage.approved_count == stage.total_count and stage.total_count > 0 %}border-success
251+
{% elif stage.approved_count > 0 %}border-primary
252+
{% else %}border-secondary{% endif %}">
253+
<div class="card-header d-flex justify-content-between align-items-center
254+
{% if stage.rejected_count > 0 %}bg-danger text-white
255+
{% elif stage.approved_count == stage.total_count and stage.total_count > 0 %}bg-success text-white
256+
{% elif stage.approved_count > 0 %}bg-primary text-white
257+
{% else %}bg-light text-muted{% endif %}">
258+
<span>
259+
<strong>{{ stage.name }}</strong>
260+
{% if stage.approval_logic == "all" %}
261+
<span class="badge bg-light text-dark ms-2" style="font-size:11px;">All must approve</span>
262+
{% elif stage.approval_logic == "any" %}
263+
<span class="badge bg-light text-dark ms-2" style="font-size:11px;">Any can approve</span>
264+
{% elif stage.approval_logic == "sequence" %}
265+
<span class="badge bg-light text-dark ms-2" style="font-size:11px;">Sequential</span>
266+
{% endif %}
267+
</span>
268+
<span class="badge bg-light text-dark">{{ stage.approved_count }}/{{ stage.total_count }} approved</span>
269+
</div>
270+
<div class="card-body p-0">
271+
<div class="table-responsive">
272+
<table class="table table-sm mb-0">
273+
<thead>
274+
<tr>
275+
<th>Assigned To</th>
276+
<th>Status</th>
277+
<th>Completed By</th>
278+
<th>Completed At</th>
279+
<th>Comments</th>
280+
</tr>
281+
</thead>
282+
<tbody>
283+
{% for task in stage.tasks %}
284+
<tr>
285+
<td>
286+
{% if task.assigned_to %}
287+
{{ task.assigned_to.get_full_name|default:task.assigned_to.username }}
288+
{% elif task.assigned_group %}
289+
{{ task.assigned_group.name }} <span class="text-muted">(Group)</span>
290+
{% else %}-{% endif %}
291+
</td>
292+
<td>
293+
{% if task.status == 'pending' %}<span class="badge bg-warning text-dark">Pending</span>
294+
{% elif task.status == 'approved' %}<span class="badge bg-success">Approved</span>
295+
{% elif task.status == 'rejected' %}<span class="badge bg-danger">Rejected</span>
296+
{% elif task.status == 'expired' %}<span class="badge bg-secondary">Expired</span>
297+
{% elif task.status == 'skipped' %}<span class="badge bg-secondary">Skipped</span>
298+
{% endif %}
299+
</td>
300+
<td>{% if task.completed_by %}{{ task.completed_by.get_full_name|default:task.completed_by.username }}{% else %}-{% endif %}</td>
301+
<td>{% if task.completed_at %}{{ task.completed_at|date:"Y-m-d H:i" }}{% else %}-{% endif %}</td>
302+
<td>{{ task.comments|default:"-" }}</td>
303+
</tr>
304+
{% endfor %}
305+
</tbody>
306+
</table>
307+
</div>
308+
</div>
309+
</div>
310+
{% endfor %}
311+
{% endif %}
312+
244313
{% else %}
245314
{# ---- Legacy flat workflow ---- #}
246315
<div class="card mb-4">

django_forms_workflows/views.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -544,37 +544,55 @@ def submission_detail(request, submission_id):
544544
"workflow_stage", "assigned_to", "assigned_group", "completed_by"
545545
).order_by("stage_number", "-created_at")
546546

547-
# Build stage groups if any tasks are linked to a workflow stage
547+
# Build stage groups if any tasks are linked to a workflow stage.
548+
# Separate parent-workflow tasks from sub-workflow tasks so they
549+
# render in distinct sections.
548550
stage_groups = None
551+
sub_workflow_groups = None
549552
if approval_tasks.filter(workflow_stage__isnull=False).exists():
550-
groups: dict = {}
553+
# Determine which stage IDs belong to the parent workflow
554+
workflow = getattr(submission.form_definition, "workflow", None)
555+
parent_stage_ids = set()
556+
if workflow:
557+
parent_stage_ids = set(workflow.stages.values_list("id", flat=True))
558+
559+
parent_groups: dict = {}
560+
sub_groups: dict = {}
551561
for task in approval_tasks:
552-
stage_key = task.stage_number or 0
553-
if stage_key not in groups:
562+
if not task.workflow_stage_id:
563+
continue
564+
is_parent = task.workflow_stage_id in parent_stage_ids
565+
target = parent_groups if is_parent else sub_groups
566+
stage_key = task.workflow_stage_id
567+
if stage_key not in target:
554568
stage_name = (
555569
task.workflow_stage.name
556570
if task.workflow_stage
557-
else f"Stage {stage_key}"
571+
else f"Stage {task.stage_number or 0}"
558572
)
559573
approval_logic = (
560574
task.workflow_stage.approval_logic if task.workflow_stage else "all"
561575
)
562-
groups[stage_key] = {
563-
"number": stage_key,
576+
target[stage_key] = {
577+
"number": task.stage_number or 0,
564578
"name": stage_name,
565579
"approval_logic": approval_logic,
566580
"tasks": [],
567581
"approved_count": 0,
568582
"rejected_count": 0,
569583
"total_count": 0,
570584
}
571-
groups[stage_key]["tasks"].append(task)
572-
groups[stage_key]["total_count"] += 1
585+
target[stage_key]["tasks"].append(task)
586+
target[stage_key]["total_count"] += 1
573587
if task.status == "approved":
574-
groups[stage_key]["approved_count"] += 1
588+
target[stage_key]["approved_count"] += 1
575589
elif task.status == "rejected":
576-
groups[stage_key]["rejected_count"] += 1
577-
stage_groups = sorted(groups.values(), key=lambda x: x["number"])
590+
target[stage_key]["rejected_count"] += 1
591+
592+
if parent_groups:
593+
stage_groups = sorted(parent_groups.values(), key=lambda x: x["number"])
594+
if sub_groups:
595+
sub_workflow_groups = sorted(sub_groups.values(), key=lambda x: x["number"])
578596

579597
# Resolve fresh presigned URLs for any file-upload fields
580598
form_data = _resolve_form_data_urls(submission.form_data)
@@ -605,6 +623,7 @@ def submission_detail(request, submission_id):
605623
"submission": submission,
606624
"approval_tasks": approval_tasks,
607625
"stage_groups": stage_groups,
626+
"sub_workflow_groups": sub_workflow_groups,
608627
"form_data": form_data,
609628
"form_data_ordered": form_data_ordered,
610629
"approval_field_names": approval_field_names,

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-forms-workflows"
3-
version = "0.19.1"
3+
version = "0.19.2"
44
description = "Enterprise-grade, database-driven form builder with approval workflows and external data integration"
55
license = "LGPL-3.0-only"
66
readme = "README.md"

0 commit comments

Comments
 (0)