Skip to content

Commit e56e79b

Browse files
committed
v0.20.2: Unified sub-workflow table in submission detail
- Merge sub-workflow instance list and approval task info into a single table showing #, Label, Assigned To, Status, Completed By, Completed At - Remove duplicate sub-workflow sections (old separate instance table) - Use customizable section_label for the heading
1 parent 9c4bfa7 commit e56e79b

3 files changed

Lines changed: 77 additions & 95 deletions

File tree

django_forms_workflows/templates/django_forms_workflows/submission_detail.html

Lines changed: 30 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -241,75 +241,59 @@ <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 ---- #}
246-
{% for stage in sub_workflow_groups %}
247-
{% if forloop.first %}
248-
<h5 class="mb-3 mt-4"><i class="bi bi-list-check"></i> {{ stage.section_label|default:"Additional Approvals" }}</h5>
249-
{% endif %}
250-
<div class="card mb-3
251-
{% if stage.rejected_count > 0 %}border-danger
252-
{% elif stage.approved_count == stage.total_count and stage.total_count > 0 %}border-success
253-
{% elif stage.approved_count > 0 %}border-primary
254-
{% else %}border-secondary{% endif %}">
255-
<div class="card-header d-flex justify-content-between align-items-center
256-
{% if stage.rejected_count > 0 %}bg-danger text-white
257-
{% elif stage.approved_count == stage.total_count and stage.total_count > 0 %}bg-success text-white
258-
{% elif stage.approved_count > 0 %}bg-primary text-white
259-
{% else %}bg-light text-muted{% endif %}">
260-
<span>
261-
<strong>{{ stage.name }}</strong>
262-
{% if stage.approval_logic == "all" %}
263-
<span class="badge bg-light text-dark ms-2" style="font-size:11px;">All must approve</span>
264-
{% elif stage.approval_logic == "any" %}
265-
<span class="badge bg-light text-dark ms-2" style="font-size:11px;">Any can approve</span>
266-
{% elif stage.approval_logic == "sequence" %}
267-
<span class="badge bg-light text-dark ms-2" style="font-size:11px;">Sequential</span>
268-
{% endif %}
269-
</span>
270-
<span class="badge bg-light text-dark">{{ stage.approved_count }}/{{ stage.total_count }} approved</span>
271-
</div>
244+
{% if sub_workflow_instances_merged %}
245+
{# ---- Merged sub-workflow table ---- #}
246+
<h5 class="mb-3 mt-4"><i class="bi bi-list-check"></i> {{ sub_wf_section_label|default:"Additional Approvals" }}</h5>
247+
<div class="card mb-3 border-secondary">
272248
<div class="card-body p-0">
273249
<div class="table-responsive">
274-
<table class="table table-sm mb-0">
275-
<thead>
250+
<table class="table table-sm table-hover mb-0">
251+
<thead class="table-light">
276252
<tr>
253+
<th>#</th>
254+
<th>Label</th>
277255
<th>Assigned To</th>
278256
<th>Status</th>
279257
<th>Completed By</th>
280258
<th>Completed At</th>
281-
<th>Comments</th>
259+
<th></th>
282260
</tr>
283261
</thead>
284262
<tbody>
285-
{% for task in stage.tasks %}
263+
{% for row in sub_workflow_instances_merged %}
286264
<tr>
265+
<td>{{ row.instance.index }}</td>
266+
<td>{{ row.instance.label }}</td>
287267
<td>
288-
{% if task.assigned_to %}
289-
{{ task.assigned_to.get_full_name|default:task.assigned_to.username }}
290-
{% elif task.assigned_group %}
291-
{{ task.assigned_group.name }} <span class="text-muted">(Group)</span>
268+
{% if row.task %}
269+
{% if row.task.assigned_to %}
270+
{{ row.task.assigned_to.get_full_name|default:row.task.assigned_to.username }}
271+
{% elif row.task.assigned_group %}
272+
{{ row.task.assigned_group.name }}
273+
{% else %}-{% endif %}
292274
{% else %}-{% endif %}
293275
</td>
294276
<td>
295-
{% if task.status == 'pending' %}<span class="badge bg-warning text-dark">Pending</span>
296-
{% elif task.status == 'approved' %}<span class="badge bg-success">Approved</span>
297-
{% elif task.status == 'rejected' %}<span class="badge bg-danger">Rejected</span>
298-
{% elif task.status == 'expired' %}<span class="badge bg-secondary">Expired</span>
299-
{% elif task.status == 'skipped' %}<span class="badge bg-secondary">Skipped</span>
277+
{% if row.instance.status == 'approved' %}<span class="badge bg-success">Approved</span>
278+
{% elif row.instance.status == 'rejected' %}<span class="badge bg-danger">Rejected</span>
279+
{% elif row.instance.status == 'in_progress' %}<span class="badge bg-primary">In Progress</span>
280+
{% else %}<span class="badge bg-secondary">Pending</span>
300281
{% endif %}
301282
</td>
302-
<td>{% if task.completed_by %}{{ task.completed_by.get_full_name|default:task.completed_by.username }}{% else %}-{% endif %}</td>
303-
<td>{% if task.completed_at %}{{ task.completed_at|date:"Y-m-d H:i" }}{% else %}-{% endif %}</td>
304-
<td>{{ task.comments|default:"-" }}</td>
283+
<td>{% if row.task and row.task.completed_by %}{{ row.task.completed_by.get_full_name|default:row.task.completed_by.username }}{% else %}-{% endif %}</td>
284+
<td>{% if row.task and row.task.completed_at %}{{ row.task.completed_at|date:"Y-m-d H:i" }}{% else %}-{% endif %}</td>
285+
<td>
286+
<a href="{% url 'forms_workflows:sub_workflow_detail' row.instance.id %}" class="btn btn-sm btn-outline-secondary">
287+
<i class="bi bi-eye"></i> View
288+
</a>
289+
</td>
305290
</tr>
306291
{% endfor %}
307292
</tbody>
308293
</table>
309294
</div>
310295
</div>
311296
</div>
312-
{% endfor %}
313297
{% endif %}
314298

315299
{% else %}
@@ -364,54 +348,7 @@ <h5 class="mb-0">Approval History</h5>
364348

365349
{% endif %}
366350

367-
<!-- Sub-workflows -->
368-
{% with sub_wfs=submission.sub_workflows.all %}
369-
{% if sub_wfs %}
370-
<div class="card mb-4">
371-
<div class="card-header bg-light">
372-
<h5 class="mb-0"><i class="bi bi-diagram-2"></i> Payment Approvals</h5>
373-
</div>
374-
<div class="card-body p-0">
375-
<table class="table table-hover mb-0">
376-
<thead class="table-light">
377-
<tr>
378-
<th>#</th>
379-
<th>Label</th>
380-
<th>Status</th>
381-
<th>Created</th>
382-
<th></th>
383-
</tr>
384-
</thead>
385-
<tbody>
386-
{% for swf in sub_wfs %}
387-
<tr>
388-
<td>{{ swf.index }}</td>
389-
<td>{{ swf.label }}</td>
390-
<td>
391-
{% if swf.status == 'approved' %}
392-
<span class="badge bg-success">Approved</span>
393-
{% elif swf.status == 'rejected' %}
394-
<span class="badge bg-danger">Rejected</span>
395-
{% elif swf.status == 'in_progress' %}
396-
<span class="badge bg-primary">In Progress</span>
397-
{% else %}
398-
<span class="badge bg-secondary">Pending</span>
399-
{% endif %}
400-
</td>
401-
<td>{{ swf.created_at|date:"M d, Y" }}</td>
402-
<td>
403-
<a href="{% url 'forms_workflows:sub_workflow_detail' swf.id %}" class="btn btn-sm btn-outline-secondary">
404-
<i class="bi bi-eye"></i> View
405-
</a>
406-
</td>
407-
</tr>
408-
{% endfor %}
409-
</tbody>
410-
</table>
411-
</div>
412-
</div>
413-
{% endif %}
414-
{% endwith %}
351+
415352

416353
<!-- Actions -->
417354
<div class="mt-3">

django_forms_workflows/views.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,11 +600,54 @@ def submission_detail(request, submission_id):
600600
for sg in sub_workflow_groups:
601601
sg["section_label"] = sub_wf_config.section_label
602602
elif sub_wf_config:
603-
# Fall back to the sub-workflow's form definition name
604603
sg_name = sub_wf_config.sub_workflow.form_definition.name
605604
for sg in sub_workflow_groups:
606605
sg["section_label"] = sg_name
607606

607+
# Build merged sub-workflow instance rows that combine instance info
608+
# (index, label, status, created) with per-instance approval task info
609+
# (assigned_to, completed_by, completed_at) into a single table.
610+
sub_workflow_instances_merged = None
611+
sub_wf_section_label = None
612+
sub_wfs = list(
613+
submission.sub_workflows.select_related("definition")
614+
.prefetch_related(
615+
"approval_tasks__assigned_to",
616+
"approval_tasks__assigned_group",
617+
"approval_tasks__completed_by",
618+
)
619+
.order_by("index")
620+
)
621+
if sub_wfs:
622+
swf_config = sub_wfs[0].definition if sub_wfs else None
623+
sub_wf_section_label = (
624+
(
625+
swf_config.section_label
626+
if swf_config and swf_config.section_label
627+
else None
628+
)
629+
or (swf_config.sub_workflow.form_definition.name if swf_config else None)
630+
or "Additional Approvals"
631+
)
632+
merged_rows = []
633+
for swf in sub_wfs:
634+
tasks = list(swf.approval_tasks.order_by("-completed_at", "id"))
635+
# Pick the most relevant task for display (completed or first pending)
636+
display_task = None
637+
for t in tasks:
638+
if t.status in ("approved", "rejected"):
639+
display_task = t
640+
break
641+
if not display_task and tasks:
642+
display_task = tasks[0]
643+
merged_rows.append(
644+
{
645+
"instance": swf,
646+
"task": display_task,
647+
}
648+
)
649+
sub_workflow_instances_merged = merged_rows
650+
608651
# Resolve fresh presigned URLs for any file-upload fields
609652
form_data = _resolve_form_data_urls(submission.form_data)
610653
form_data_ordered = _build_ordered_form_data(submission, form_data)
@@ -635,6 +678,8 @@ def submission_detail(request, submission_id):
635678
"approval_tasks": approval_tasks,
636679
"stage_groups": stage_groups,
637680
"sub_workflow_groups": sub_workflow_groups,
681+
"sub_workflow_instances_merged": sub_workflow_instances_merged,
682+
"sub_wf_section_label": sub_wf_section_label,
638683
"form_data": form_data,
639684
"form_data_ordered": form_data_ordered,
640685
"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.20.1"
3+
version = "0.20.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)