Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
035c4a7
Add central filing workflow registry
nonprofittechy Jun 29, 2026
dbfed91
Add workflow context to options page
nonprofittechy Jun 29, 2026
87d01cb
Add workflow context to upload-first page
nonprofittechy Jun 29, 2026
7aca569
Add workflow context to case information page
nonprofittechy Jun 29, 2026
eb1f209
Add workflow context to payment page
nonprofittechy Jun 29, 2026
c5bd6c6
Fix workflow lint imports
nonprofittechy Jun 29, 2026
7a4b93d
Add workflow registry tests
nonprofittechy Jun 29, 2026
99b9ad1
Address workflow typing review comments
nonprofittechy Jun 29, 2026
4fc3182
Add workflow context to document upload page
nonprofittechy Jun 29, 2026
826e12a
Add workflow context to confirmation page
nonprofittechy Jun 29, 2026
1ab8d77
Add workflow context to review page
nonprofittechy Jun 29, 2026
3030492
Document workflow step maintenance
nonprofittechy Jun 29, 2026
54b7aca
Merge branch 'main' into workflow-registry
nonprofittechy Jun 29, 2026
8bed869
Use enum keys for workflow steps
nonprofittechy Jun 29, 2026
1b7fd8a
Use workflow enum in options view
nonprofittechy Jun 29, 2026
03e77f4
Use workflow enum in lead upload view
nonprofittechy Jun 29, 2026
dc8dc7f
Use workflow enum in case information view
nonprofittechy Jun 29, 2026
98af822
Use workflow enum in document upload view
nonprofittechy Jun 29, 2026
deeaa61
Use workflow enum in payment view
nonprofittechy Jun 29, 2026
a8bdbb1
Use workflow enum in review view
nonprofittechy Jun 29, 2026
1ad9a0d
Use workflow enum in confirmation view
nonprofittechy Jun 29, 2026
f031f59
Use workflow enum in workflow tests
nonprofittechy Jun 29, 2026
9bf8a3c
Fix workflow test lint issues
nonprofittechy Jun 29, 2026
ecd148b
Sort workflow test imports
nonprofittechy Jun 29, 2026
e40b17e
fix ruff
nonprofittechy Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions efile_app/efile/tests/test_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import pytest
from django.urls import reverse

from efile.workflow import (
FILING_WORKFLOW,
WorkflowStepKey,
get_next_step,
get_previous_step,
get_step,
get_step_url,
get_workflow_context,
get_workflow_steps,
)

EXPECTED_WORKFLOW_KEYS = [
WorkflowStepKey.OPTIONS,
WorkflowStepKey.UPLOAD_FIRST,
WorkflowStepKey.CASE_INFORMATION,
WorkflowStepKey.DOCUMENTS,
WorkflowStepKey.PAYMENT,
WorkflowStepKey.REVIEW,
WorkflowStepKey.CONFIRMATION,
]


@pytest.mark.parametrize(
("step_key", "label"),
[
(WorkflowStepKey.OPTIONS, "Options"),
(WorkflowStepKey.UPLOAD_FIRST, "Upload lead document"),
(WorkflowStepKey.CASE_INFORMATION, "Case information"),
(WorkflowStepKey.DOCUMENTS, "Documents"),
(WorkflowStepKey.PAYMENT, "Payment"),
(WorkflowStepKey.REVIEW, "Review"),
(WorkflowStepKey.CONFIRMATION, "Confirmation"),
],
)
def test_get_step_returns_registered_step(step_key, label):
step = get_step(step_key)

assert step.key == step_key
assert step.label == label


def test_get_workflow_steps_returns_ordered_workflow():
assert get_workflow_steps() == FILING_WORKFLOW
assert [step.key for step in get_workflow_steps()] == EXPECTED_WORKFLOW_KEYS


def test_get_step_raises_key_error_for_invalid_step():
with pytest.raises(KeyError):
get_step("invalid_step")


def test_get_previous_step_returns_none_for_first_step():
assert get_previous_step(WorkflowStepKey.OPTIONS) is None


def test_get_previous_step_returns_prior_step():
previous_step = get_previous_step(WorkflowStepKey.CASE_INFORMATION)

assert previous_step.key == WorkflowStepKey.UPLOAD_FIRST


def test_get_next_step_returns_following_step():
next_step = get_next_step(WorkflowStepKey.CASE_INFORMATION)

assert next_step.key == WorkflowStepKey.DOCUMENTS


def test_get_next_step_returns_none_for_last_step():
assert get_next_step(WorkflowStepKey.CONFIRMATION) is None


def test_get_step_url_reverses_workflow_route():
expected_url = reverse("payment", kwargs={"jurisdiction": "illinois"})

assert get_step_url(WorkflowStepKey.PAYMENT, "illinois") == expected_url


def test_get_workflow_context_includes_current_previous_and_next_urls():
context = get_workflow_context(WorkflowStepKey.PAYMENT, "illinois")
previous_url = reverse("upload", kwargs={"jurisdiction": "illinois"})
next_url = reverse("case_review", kwargs={"jurisdiction": "illinois"})

assert context["workflow_current_step"].key == WorkflowStepKey.PAYMENT
assert context["workflow_previous_step"].key == WorkflowStepKey.DOCUMENTS
assert context["workflow_next_step"].key == WorkflowStepKey.REVIEW
assert context["workflow_previous_url"] == previous_url
assert context["workflow_next_url"] == next_url
3 changes: 3 additions & 0 deletions efile_app/efile/views/confirmation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from efile.api.suffolk_api_views import get_tyler_token

from ..workflow import WorkflowStepKey, get_workflow_context


def filing_confirmation(request, jurisdiction):
"""Confirmation page after successful filing submission."""
Expand All @@ -17,5 +19,6 @@ def filing_confirmation(request, jurisdiction):
"page_title": "Filing Confirmation",
"success_message": "Your filing has been successfully submitted!",
}
context.update(get_workflow_context(WorkflowStepKey.CONFIRMATION, jurisdiction))

return render(request, "efile/confirmation.html", context)
2 changes: 2 additions & 0 deletions efile_app/efile/views/expert_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from efile.api.suffolk_api_views import get_tyler_token

from ..utils.case_data_utils import get_upload_data
from ..workflow import WorkflowStepKey, get_workflow_context

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -59,5 +60,6 @@ def efile_expert_form(request, jurisdiction):
"missing_required_fields": not has_all_required,
"missing_party_info": has_all_required and not has_party_info,
}
context.update(get_workflow_context(WorkflowStepKey.CASE_INFORMATION, jurisdiction))

return render(request, "efile/expert_form.html", context)
5 changes: 2 additions & 3 deletions efile_app/efile/views/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from efile.api.suffolk_api_views import get_tyler_token

from ..utils.case_data_utils import get_case_data
from ..workflow import WorkflowStepKey, get_workflow_context


def efile_options(request, jurisdiction):
Expand All @@ -17,14 +18,12 @@ def efile_options(request, jurisdiction):
if not get_tyler_token(request, jurisdiction):
is_logged_in = False

is_logged_in = request.user.is_authenticated
if not get_tyler_token(request, jurisdiction):
is_logged_in = False
# Pass case data to template for display
context = {
"is_logged_in": is_logged_in,
"case_data": case_data,
"has_case_data": bool(case_data),
}
context.update(get_workflow_context(WorkflowStepKey.OPTIONS, jurisdiction))

return render(request, "efile/options.html", context)
2 changes: 2 additions & 0 deletions efile_app/efile/views/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from efile.api.suffolk_api_views import get_tyler_token

from ..utils.case_data_utils import get_case_data
from ..workflow import WorkflowStepKey, get_workflow_context

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -37,5 +38,6 @@ def efile_payment(request, jurisdiction):
"new_toga_url": new_toga_url,
"case_data": case_data,
}
context.update(get_workflow_context(WorkflowStepKey.PAYMENT, jurisdiction))

return render(request, "efile/payment.html", context)
2 changes: 2 additions & 0 deletions efile_app/efile/views/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from efile.api.suffolk_api_views import get_tyler_token

from ..utils.case_data_utils import get_case_classification, get_case_data, get_name_sought_info, get_petitioner_info
from ..workflow import WorkflowStepKey, get_workflow_context

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -112,5 +113,6 @@ def case_review(request, jurisdiction):
"document_type": friendly_document_type,
},
}
context.update(get_workflow_context(WorkflowStepKey.REVIEW, jurisdiction))

return render(request, "efile/review.html", context)
7 changes: 2 additions & 5 deletions efile_app/efile/views/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,27 @@
get_petitioner_info,
get_upload_data,
)
from ..workflow import WorkflowStepKey, get_workflow_context

logger = logging.getLogger(__name__)


def efile_upload(request, jurisdiction):
"""Upload view for document submission and filing creation."""

# Check if user is authenticated first
if not request.user.is_authenticated:
return redirect("efile_login", jurisdiction=jurisdiction)

# Get case data from session
case_data = get_case_data(request)

# If no case data exists, redirect back to options page
if not case_data:
messages.error(request, gettext("Please complete the case details first."))
return redirect("efile_options", jurisdiction=jurisdiction)

# Get organized case information
petitioner_info = get_petitioner_info(request)
name_sought_info = get_name_sought_info(request)
case_classification = get_case_classification(request)

# Use friendly names if available, otherwise fallback to raw values
friendly_case_type = case_data.get("case_type_name", case_classification["case_type"])
friendly_filing_type = case_data.get("filing_type_name", case_classification["filing_type"])
friendly_court = case_data.get("court_name", case_classification["court"])
Expand All @@ -62,5 +58,6 @@ def efile_upload(request, jurisdiction):
"filing_type_raw": case_classification["filing_type"],
"court_raw": case_classification["court"],
}
context.update(get_workflow_context(WorkflowStepKey.DOCUMENTS, jurisdiction))

return render(request, "efile/upload.html", context)
2 changes: 2 additions & 0 deletions efile_app/efile/views/upload_first.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
get_upload_data,
)
from ..utils.django_helpers import flush_cache_stay_logged_in
from ..workflow import WorkflowStepKey, get_workflow_context

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -55,5 +56,6 @@ def efile_upload_first(request, jurisdiction):
"name_sought_info": name_sought_info,
"case_classification": case_classification,
}
context.update(get_workflow_context(WorkflowStepKey.UPLOAD_FIRST, jurisdiction))

return render(request, "efile/upload_first.html", context)
115 changes: 115 additions & 0 deletions efile_app/efile/workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Central filing workflow registry.

Use FILING_WORKFLOW as the single high-level map of the filing flow.

To add a step:
1. Add a WorkflowStepKey member for the new step.
2. Add the URL route and view.
3. Add a WorkflowStep entry in the desired position below.
4. Add get_workflow_context(WorkflowStepKey.YOUR_STEP, jurisdiction) to that view's context.
5. Update any navigation copy that mentions the surrounding steps.
6. Update efile/tests/test_workflow.py.

To rearrange steps:
1. Reorder FILING_WORKFLOW.
2. Update affected labels, navigation copy, and workflow tests.

This registry is intentionally linear for now. Future branching should be added
here after the durable filing draft model exists as the workflow state source.
"""

from dataclasses import dataclass
from enum import StrEnum

from django.urls import reverse


class WorkflowStepKey(StrEnum):
"""Stable identifiers for filing workflow steps."""

OPTIONS = "options"
UPLOAD_FIRST = "upload_first"
CASE_INFORMATION = "case_information"
DOCUMENTS = "documents"
PAYMENT = "payment"
REVIEW = "review"
CONFIRMATION = "confirmation"


@dataclass(frozen=True)
class WorkflowStep:
"""A single screen in the filing workflow."""

key: WorkflowStepKey
label: str
url_name: str


FILING_WORKFLOW: tuple[WorkflowStep, ...] = (
WorkflowStep(WorkflowStepKey.OPTIONS, "Options", "efile_options"),
WorkflowStep(WorkflowStepKey.UPLOAD_FIRST, "Upload lead document", "upload_first"),
WorkflowStep(WorkflowStepKey.CASE_INFORMATION, "Case information", "expert_form"),
WorkflowStep(WorkflowStepKey.DOCUMENTS, "Documents", "upload"),
WorkflowStep(WorkflowStepKey.PAYMENT, "Payment", "payment"),
WorkflowStep(WorkflowStepKey.REVIEW, "Review", "case_review"),
WorkflowStep(WorkflowStepKey.CONFIRMATION, "Confirmation", "filing_confirmation"),
)
Comment thread
nonprofittechy marked this conversation as resolved.


def get_workflow_steps() -> tuple[WorkflowStep, ...]:
return FILING_WORKFLOW


def get_step(step_key: WorkflowStepKey | str) -> WorkflowStep:
try:
return next(step for step in FILING_WORKFLOW if step.key == step_key)
except StopIteration as exc:
raise KeyError(f"Unknown workflow step: {step_key}") from exc


def get_step_index(step_key: WorkflowStepKey | str) -> int:
for index, step in enumerate(FILING_WORKFLOW):
if step.key == step_key:
return index
raise KeyError(f"Unknown workflow step: {step_key}")


def get_previous_step(step_key: WorkflowStepKey | str) -> WorkflowStep | None:
index = get_step_index(step_key)
if index == 0:
return None
return FILING_WORKFLOW[index - 1]


def get_next_step(step_key: WorkflowStepKey | str) -> WorkflowStep | None:
index = get_step_index(step_key)
try:
return FILING_WORKFLOW[index + 1]
except IndexError:
return None


def get_step_url(step_key: WorkflowStepKey | str, jurisdiction: str) -> str:
step = get_step(step_key)
return reverse(step.url_name, kwargs={"jurisdiction": jurisdiction})


def get_workflow_context(current_step: WorkflowStepKey | str, jurisdiction: str) -> dict:
previous_step = get_previous_step(current_step)
next_step = get_next_step(current_step)
previous_url = None
next_url = None

if previous_step:
previous_url = get_step_url(previous_step.key, jurisdiction)
if next_step:
next_url = get_step_url(next_step.key, jurisdiction)

return {
"workflow_steps": get_workflow_steps(),
"workflow_current_step": get_step(current_step),
"workflow_previous_step": previous_step,
"workflow_next_step": next_step,
"workflow_previous_url": previous_url,
"workflow_next_url": next_url,
}