Skip to content

Commit

Permalink
Allow canceling of case (projectcaluma#142)
Browse files Browse the repository at this point in the history
Allows to withdraw from a case at any point. When withdrawal is allowed is specified by permission.
All not completed work items are canceled when case is canceled.
  • Loading branch information
sliverc authored Dec 5, 2018
1 parent 52ea541 commit 40c0ea2
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 16 deletions.
17 changes: 15 additions & 2 deletions caluma/tests/snapshots/snap_test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@
clientMutationId: String
}
input CancelCaseInput {
id: ID!
clientMutationId: String
}
type CancelCasePayload {
case: Case
clientMutationId: String
}
type Case implements Node {
createdAt: DateTime!
modifiedAt: DateTime!
Expand Down Expand Up @@ -146,7 +156,8 @@
enum CaseStatus {
RUNNING
COMPLETE
COMPLETED
CANCELED
}
type CheckboxQuestion implements Question, Node {
Expand Down Expand Up @@ -343,6 +354,7 @@
saveTask(input: SaveTaskInput!): SaveTaskPayload
archiveTask(input: ArchiveTaskInput!): ArchiveTaskPayload
startCase(input: StartCaseInput!): StartCasePayload
cancelCase(input: CancelCaseInput!): CancelCasePayload
completeWorkItem(input: CompleteWorkItemInput!): CompleteWorkItemPayload
saveForm(input: SaveFormInput!): SaveFormPayload
archiveForm(input: ArchiveFormInput!): ArchiveFormPayload
Expand Down Expand Up @@ -884,7 +896,8 @@
enum WorkItemStatus {
READY
COMPLETE
COMPLETED
CANCELED
}
type Workflow implements Node {
Expand Down
39 changes: 39 additions & 0 deletions caluma/workflow/migrations/0004_auto_20181205_1222.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2018-12-05 12:22
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [("workflow", "0003_auto_20181017_1238")]

operations = [
migrations.AlterField(
model_name="case",
name="status",
field=models.CharField(
choices=[
("running", "Case is running and work items need to be completed."),
("completed", "Case is done."),
("canceled", "Case is cancelled."),
],
db_index=True,
max_length=50,
),
),
migrations.AlterField(
model_name="workitem",
name="status",
field=models.CharField(
choices=[
("ready", "Task is ready to be processed."),
("completed", "Task is done."),
("canceled", "Task is cancelled."),
],
db_index=True,
max_length=50,
),
),
]
16 changes: 10 additions & 6 deletions caluma/workflow/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ class Meta:

class Case(UUIDModel):
STATUS_RUNNING = "running"
STATUS_COMPLETE = "complete"
STATUS_COMPLETED = "completed"
STATUS_CANCELED = "canceled"

STATUS_CHOICES = (STATUS_RUNNING, STATUS_COMPLETE)
STATUS_CHOICES = (STATUS_RUNNING, STATUS_COMPLETED, STATUS_CANCELED)
STATUS_CHOICE_TUPLE = (
(STATUS_RUNNING, "Case is running and work items need to be completed."),
(STATUS_COMPLETE, "Case is done."),
(STATUS_COMPLETED, "Case is done."),
(STATUS_CANCELED, "Case is cancelled."),
)

workflow = models.ForeignKey(
Expand All @@ -69,12 +71,14 @@ class Case(UUIDModel):

class WorkItem(UUIDModel):
STATUS_READY = "ready"
STATUS_COMPLETE = "complete"
STATUS_COMPLETED = "completed"
STATUS_CANCELED = "canceled"

STATUS_CHOICES = (STATUS_READY, STATUS_COMPLETE)
STATUS_CHOICES = (STATUS_READY, STATUS_COMPLETED, STATUS_CANCELED)
STATUS_CHOICE_TUPLE = (
(STATUS_READY, "Task is ready to be processed."),
(STATUS_COMPLETE, "Task is done."),
(STATUS_COMPLETED, "Task is done."),
(STATUS_CANCELED, "Task is cancelled."),
)

task = models.ForeignKey(
Expand Down
7 changes: 7 additions & 0 deletions caluma/workflow/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ class Meta:
model_operations = ["create"]


class CancelCase(Mutation):
class Meta:
serializer_class = serializers.CancelCaseSerializer
model_operations = ["update"]


class CompleteWorkItem(Mutation):
class Meta:
serializer_class = serializers.CompleteWorkItemSerializer
Expand All @@ -121,6 +127,7 @@ class Mutation(object):
archive_task = ArchiveTask().Field()

start_case = StartCase().Field()
cancel_case = CancelCase().Field()
complete_work_item = CompleteWorkItem().Field()


Expand Down
32 changes: 28 additions & 4 deletions caluma/workflow/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,42 @@ class Meta:
fields = ("workflow", "meta")


class CancelCaseSerializer(serializers.ModelSerializer):
id = serializers.GlobalIDField()

class Meta:
model = models.Case
fields = ("id",)

def validate(self, data):
if self.instance.status != models.Case.STATUS_RUNNING:
raise exceptions.ValidationError("Only running cases can be canceled.")

data["status"] = models.Case.STATUS_CANCELED
return data

@transaction.atomic
def update(self, instance, validated_data):
instance = super().update(instance, validated_data)
instance.work_items.exclude(status=models.WorkItem.STATUS_COMPLETED).update(
status=models.WorkItem.STATUS_CANCELED
)
return instance


class CompleteWorkItemSerializer(serializers.ModelSerializer):
id = serializers.GlobalIDField()

def validate(self, data):
if self.instance.status == models.WorkItem.STATUS_COMPLETE:
raise exceptions.ValidationError("Task has already been completed.")
if self.instance.status != models.WorkItem.STATUS_READY:
raise exceptions.ValidationError("Only ready tasks can be completed.")

# TODO: add validation according to task type

data["status"] = models.WorkItem.STATUS_COMPLETE
data["status"] = models.WorkItem.STATUS_COMPLETED
return data

@transaction.atomic
def update(self, instance, validated_data):
instance = super().update(instance, validated_data)
case = instance.case
Expand All @@ -171,7 +195,7 @@ def update(self, instance, validated_data):
)
else:
# no more tasks, mark case as complete
case.status = models.Case.STATUS_COMPLETE
case.status = models.Case.STATUS_COMPLETED
case.save(update_fields=["status"])

return instance
Expand Down
22 changes: 22 additions & 0 deletions caluma/workflow/tests/snapshots/snap_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,25 @@
"clientMutationId": None,
}
}

snapshots["test_cancel_case[running-True-completed] 1"] = {
"cancelCase": {
"case": {
"document": {"form": {"slug": "sound-air-mission"}},
"status": "CANCELED",
"workItems": {"edges": [{"node": {"status": "COMPLETED"}}]},
},
"clientMutationId": None,
}
}

snapshots["test_cancel_case[running-True-ready] 1"] = {
"cancelCase": {
"case": {
"document": {"form": {"slug": "sound-air-mission"}},
"status": "CANCELED",
"workItems": {"edges": [{"node": {"status": "CANCELED"}}]},
},
"clientMutationId": None,
}
}
6 changes: 3 additions & 3 deletions caluma/workflow/tests/snapshots/snap_test_work_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
snapshots["test_complete_work_item_last[ready-True] 1"] = {
"completeWorkItem": {
"clientMutationId": None,
"workItem": {"case": {"status": "COMPLETE"}, "status": "COMPLETE"},
"workItem": {"case": {"status": "COMPLETED"}, "status": "COMPLETED"},
}
}

Expand All @@ -27,11 +27,11 @@
"workItems": {
"edges": [
{"node": {"status": "READY"}},
{"node": {"status": "COMPLETE"}},
{"node": {"status": "COMPLETED"}},
]
},
},
"status": "COMPLETE",
"status": "COMPLETED",
},
}
}
45 changes: 45 additions & 0 deletions caluma/workflow/tests/test_case.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import pytest

from .. import models


def test_query_all_cases(db, snapshot, case, flow, schema_executor):
query = """
query AllCases {
Expand Down Expand Up @@ -46,3 +51,43 @@ def test_start_case(db, snapshot, workflow, schema_executor):

assert not result.errors
snapshot.assert_match(result.data)


@pytest.mark.parametrize(
"work_item__status",
[models.WorkItem.STATUS_COMPLETED, models.WorkItem.STATUS_READY],
)
@pytest.mark.parametrize(
"case__status,success",
[(models.Case.STATUS_RUNNING, True), (models.Case.STATUS_COMPLETED, False)],
)
def test_cancel_case(db, snapshot, case, work_item, schema_executor, success):
query = """
mutation CancelCase($input: CancelCaseInput!) {
cancelCase(input: $input) {
case {
document {
form {
slug
}
}
status
workItems {
edges {
node {
status
}
}
}
}
clientMutationId
}
}
"""

inp = {"input": {"id": case.pk}}
result = schema_executor(query, variables=inp)

assert not bool(result.errors) == success
if success:
snapshot.assert_match(result.data)
2 changes: 1 addition & 1 deletion caluma/workflow/tests/test_work_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_query_all_work_items(db, snapshot, work_item, schema_executor):

@pytest.mark.parametrize(
"work_item__status,success",
[(models.WorkItem.STATUS_READY, True), (models.WorkItem.STATUS_COMPLETE, False)],
[(models.WorkItem.STATUS_READY, True), (models.WorkItem.STATUS_COMPLETED, False)],
)
def test_complete_work_item_last(db, snapshot, work_item, success, schema_executor):
query = """
Expand Down

0 comments on commit 40c0ea2

Please sign in to comment.