Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DST-946 - Save & Return - Tasklist #208

Open
wants to merge 5 commits into
base: save-and-return
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions django_app/apply_for_a_licence/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ class StatusChoices(models.TextChoices):
submitted = "submitted", "Submitted"


class EntityStatusChoices(models.TextChoices):
draft = "draft", "Draft"
complete = "complete", "Complete"


class WhereIsTheAddressChoices(models.TextChoices):
outside_uk = "outside-uk", "Outside the UK"
in_uk = "in-uk", "In the UK"
2 changes: 2 additions & 0 deletions django_app/apply_for_a_licence/forms/forms_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Meta:
form_h1_header = "Give your application a name"
bold_labels = False
save_and_return = True
show_skip_button = False

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand All @@ -28,6 +29,7 @@ def __init__(self, *args, **kwargs):

class StartForm(BaseModelForm):
save_and_return = True
show_skip_button = False

class Meta:
model = Licence
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.19 on 2025-02-26 16:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("apply_for_a_licence", "0024_historicalindividual_is_applicant_and_more"),
]

operations = [
migrations.AddField(
model_name="historicalindividual",
name="status",
field=models.CharField(choices=[("draft", "Draft"), ("complete", "Complete")], default="draft", max_length=10),
),
migrations.AddField(
model_name="historicalorganisation",
name="status",
field=models.CharField(choices=[("draft", "Draft"), ("complete", "Complete")], default="draft", max_length=10),
),
migrations.AddField(
model_name="individual",
name="status",
field=models.CharField(choices=[("draft", "Draft"), ("complete", "Complete")], default="draft", max_length=10),
),
migrations.AddField(
model_name="organisation",
name="status",
field=models.CharField(choices=[("draft", "Draft"), ("complete", "Complete")], default="draft", max_length=10),
),
]
6 changes: 6 additions & 0 deletions django_app/apply_for_a_licence/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ class Meta:
relationship_provider = models.TextField(
blank=False, null=True, db_comment="what is the relationship between the provider and the recipient?"
)
status = models.CharField(
max_length=10, choices=choices.EntityStatusChoices.choices, default=choices.EntityStatusChoices.draft
)

def readable_address(self) -> str:
"""If we have registered_office_address, use that instead of the address fields"""
Expand Down Expand Up @@ -219,6 +222,9 @@ class Individual(BaseModelID, AddressMixin):
blank=True, null=True, db_comment="what is the relationship between the provider and the recipient?"
)
is_applicant = models.BooleanField(default=False)
status = models.CharField(
max_length=10, choices=choices.EntityStatusChoices.choices, default=choices.EntityStatusChoices.draft
)

@property
def full_name(self) -> str:
Expand Down
37 changes: 37 additions & 0 deletions django_app/apply_for_a_licence/tasklist/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from .sub_tasks import UploadDocumentsSubTask
from .tasks import (
AboutTheServicesTask,
AboutYouTask,
RecipientsTask,
ReviewAndSubmitTask,
UploadDocumentsTask,
WhoTheLicenceCoversTask,
)


class Tasklist:
def __init__(self, licence):
self.licence = licence

def get_tasks(self):
tasks = [AboutYouTask(licence=self.licence)]
if self.licence.who_do_you_want_the_licence_to_cover != "myself":
tasks.append(WhoTheLicenceCoversTask(licence=self.licence))

tasks.append(AboutTheServicesTask(licence=self.licence))

tasks.append(RecipientsTask(licence=self.licence))
tasks.append(UploadDocumentsTask(licence=self.licence))

# now we need to get the status of each of the sub-tasks so we can figure out if the user is allowed to start
# the CYA. Basically they should all be complete apart from UploadDocuments because that is optional.

# note - this code is messy, hard to understand, and resource-wasteful.
# Please consider optimising I'm sure it can be done better
all_sub_tasks = [sub_task for task in tasks for sub_task in task.get_sub_tasks()]
if all([sub_task.is_completed for sub_task in all_sub_tasks if not isinstance(sub_task, UploadDocumentsSubTask)]):
can_go_to_cya = True
else:
can_go_to_cya = False
tasks.append(ReviewAndSubmitTask(licence=self.licence, can_go_to_cya=can_go_to_cya))
return tasks
83 changes: 83 additions & 0 deletions django_app/apply_for_a_licence/tasklist/base_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from typing import Literal, Type

from apply_for_a_licence.models import Licence


class BaseSubTask:
name: str = ""
help_text: str = ""
status: Literal["cannot_start", "not_started", "in_progress", "complete"] = "cannot_start"
url: str = ""

def __init__(self, licence, *args, **kwargs):
self.licence: Licence = licence
super().__init__(*args, **kwargs)

@property
def is_completed(self) -> bool:
return False

@property
def tag_colour(self) -> str:
if self.status == "in_progress":
return "light-blue"
elif self.status == "cannot_start":
return "blue"
return "blue"

@property
def id(self) -> str:
return self.name.lower().replace(" ", "-")

@property
def should_show_tag(self) -> bool:
return self.status in ["not_started", "in_progress"]

@property
def can_start(self) -> bool:
return self.status in ["not_started", "in_progress", "complete"]

def get_human_readable_status(self) -> str:
status_mapping = {
"cannot_start": "Cannot start yet",
"not_started": "Not yet started",
"in_progress": "In progress",
"complete": "Complete",
}
return status_mapping[self.status]


class BaseTask:
name: str = ""
sub_tasks: list[Type[BaseSubTask]] = []

def __init__(self, licence, *args, **kwargs):
self.licence: Licence = licence
super().__init__(*args, **kwargs)

@property
def id(self) -> str:
return self.name.lower().replace(" ", "-")

def get_sub_tasks(self) -> list[BaseSubTask]:
"""Gets an instantiated list of subtasks whilst also setting their status correctly.

If the previous sub-task has been completed, we can assume the next one is ready to start.
"""
sub_tasks = [each(self.licence) for each in self.sub_tasks]
for index, each in enumerate(sub_tasks):
if each.is_completed:
each.status = "complete"
continue

if index == 0:
previous_task_completed = True
else:
previous_task_completed = sub_tasks[index - 1].is_completed

if previous_task_completed:
each.status = "not_started"
else:
each.status = "cannot_start"

return sub_tasks
150 changes: 150 additions & 0 deletions django_app/apply_for_a_licence/tasklist/sub_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import uuid

from apply_for_a_licence import choices
from apply_for_a_licence.models import Individual
from apply_for_a_licence.tasklist.base_classes import BaseSubTask
from django.urls import reverse, reverse_lazy


class YourDetailsSubTask(BaseSubTask):
name = "Your details"

@property
def help_text(self):
if self.licence.who_do_you_want_the_licence_to_cover == "myself":
return "Your name and address, details of anyone else you want to add"
else:
return ""

@property
def url(self):
if self.licence.who_do_you_want_the_licence_to_cover == "myself":
try:
applicant_individual = self.licence.individuals.filter(is_applicant=True).get()
return reverse("add_yourself", kwargs={"yourself_uuid": applicant_individual.pk})
except Individual.DoesNotExist:
return reverse("add_yourself", kwargs={"yourself_uuid": uuid.uuid4()})
else:
return reverse("are_you_third_party")

@property
def is_completed(self):
return bool(self.licence.applicant_full_name)


class DetailsOfTheEntityYouWantToCoverSubTask(BaseSubTask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.is_business = (
self.licence.who_do_you_want_the_licence_to_cover == choices.WhoDoYouWantTheLicenceToCoverChoices.business
)

@property
def name(self) -> str:
if self.is_business:
return "Details of the business you want to cover"
else:
return "Details of the individual you want to cover"

@property
def help_text(self):
if self.is_business:
return "Name and address of business"
else:
return "Name, address, business they work for"

@property
def url(self):
if self.is_business:
if self.licence.organisations.filter(type_of_relationship=choices.TypeOfRelationshipChoices.business).exists():
return reverse("business_added")
else:
return reverse("is_the_business_registered_with_companies_house", kwargs={"business_uuid": str(uuid.uuid4())})
else:
if self.licence.individuals.filter(is_applicant=False).exists():
return reverse("individual_added")
else:
return reverse("add_an_individual", kwargs={"individual_uuid": str(uuid.uuid4())})

@property
def is_completed(self) -> bool:
if self.licence.who_do_you_want_the_licence_to_cover == choices.WhoDoYouWantTheLicenceToCoverChoices.business:
return self.licence.organisations.filter(
type_of_relationship=choices.TypeOfRelationshipChoices.business, status="complete"
).exists()
elif self.licence.who_do_you_want_the_licence_to_cover == choices.WhoDoYouWantTheLicenceToCoverChoices.individual:
return self.licence.individuals.filter(is_applicant=False, status="complete").exists()
elif self.licence.who_do_you_want_the_licence_to_cover == choices.WhoDoYouWantTheLicenceToCoverChoices.myself:
return self.licence.individuals.filter(is_applicant=True, status="complete").exists()
else:
return False


class PreviousLicensesHeldSubTask(BaseSubTask):
name = "Previous licences"
help_text = "Any previous licence numbers"
url = reverse_lazy("previous_licence")

@property
def is_completed(self):
return bool(self.licence.held_existing_licence)


class ServicesYouWantToProvideSubTask(BaseSubTask):
name = "The services you want to provide"
help_text = "Description of your services"
url = reverse_lazy("type_of_service")

@property
def is_completed(self) -> bool:
return bool(self.licence.type_of_service)


class PurposeForProvidingServicesSubTask(BaseSubTask):
name = "Your purpose for providing the services"
help_text = "Licensing grounds or alignment with sanctions regulations"

@property
def is_completed(self):
return bool(self.licence.purpose_of_provision)

@property
def url(self):
if self.licence.type_of_service == choices.TypeOfServicesChoices.professional_and_business:
return reverse("licensing_grounds")
else:
return reverse("purpose_of_provision")


class UploadDocumentsSubTask(BaseSubTask):
name = "Upload documents"
help_text = "Attach files to support your application"
url = reverse_lazy("upload_documents")

@property
def is_completed(self) -> bool:
return self.licence.documents.exists()


class RecipientContactDetailsSubTask(BaseSubTask):
name = "Recipient contact details"

@property
def is_completed(self) -> bool:
return self.licence.recipients.filter(status=choices.EntityStatusChoices.complete).exists()

@property
def url(self) -> str:
if self.licence.recipients.exists():
return reverse("recipient_added")
else:
return reverse("where_is_the_recipient_located", kwargs={"recipient_uuid": uuid.uuid4()})


class CheckYourAnswersSubTask(BaseSubTask):
name = "Check your answers before you submit your application"
url = reverse_lazy("check_your_answers")

@property
def is_completed(self) -> bool:
return self.licence.status == choices.StatusChoices.submitted
Loading