Skip to content

Commit 06b7f61

Browse files
authored
Added ability to set initial status when creating a project (#4390)
Closes #4388. Adds a new field to the "Create Project" modal that allows for setting the initial status of the project. The default for this field can be set with the `PROJECTS_DEFAULT_STATUS` environment setting, which would also set the initial status when `PROJECTS_AUTO_CREATE` is enabled. This settings needs to match one of the string literals here: https://github.com/HyphaApp/hypha/blob/cc174b61b6af7dfd9403cdb2fe3a13a9364e37f7/hypha/apply/projects/models/project.py#L75-L80 In the modal the statuses of `INTERNAL_APPROVAL` and `COMPLETE` were removed as choices as I didn't see them being useful statuses to be manually set (internal approval would only be approving the items created in the `DRAFT` step) Also updated the activity message to reflect what status the project was created with
1 parent 99a7bdd commit 06b7f61

File tree

11 files changed

+142
-17
lines changed

11 files changed

+142
-17
lines changed

docs/setup/administrators/configuration.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ Auto create projects for approved applications.
157157

158158
----
159159

160+
Default status for projects, must be a string literal of "draft" (default), "contracting", "invoicing_and_reporting" or "closing".
161+
162+
Will be used for auto-create or be the default selection in the project creation form.
163+
164+
PROJECTS_DEFAULT_STATUS = env.str('PROJECTS_DEFAULT_STATUS', 'draft')
165+
166+
----
167+
160168
Send out e-mail, slack messages etc. from Hypha. Set to true for production.
161169

162170
SEND_MESSAGES = env.bool('SEND_MESSAGES', False)

hypha/apply/activity/adapters/activity_feed.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class ActivityAdapter(AdapterBase):
4848
MESSAGES.DELETE_REVIEW_OPINION: _(
4949
"deleted the opinion for review: {review_opinion.review}"
5050
),
51-
MESSAGES.CREATED_PROJECT: _("Created project"),
51+
MESSAGES.CREATED_PROJECT: _(
52+
'Created project with initial status of "{status}"'
53+
),
5254
MESSAGES.PROJECT_TRANSITION: "handle_project_transition",
5355
MESSAGES.UPDATE_PROJECT_TITLE: _(
5456
"updated the project title from {old_title} to {source.title}"

hypha/apply/activity/tests/test_messaging.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,11 @@ def test_activity_created(self):
223223
submission = ApplicationSubmissionFactory()
224224

225225
self.adapter.send_message(
226-
message, user=user, source=submission, sources=[], related=None
226+
message,
227+
user=user,
228+
source=submission,
229+
sources=[],
230+
related=None,
227231
)
228232

229233
self.assertEqual(Activity.objects.count(), 1)
@@ -786,6 +790,7 @@ def test_activity_created(self):
786790
adapter=self.activity(),
787791
source=project,
788792
related=project.submission,
793+
status="Draft",
789794
)
790795
self.assertEqual(Activity.objects.count(), 1)
791796
activity = Activity.objects.first()

hypha/apply/determinations/tests/test_views.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from hypha.apply.determinations.options import ACCEPTED, NEEDS_MORE_INFO, REJECTED
1010
from hypha.apply.determinations.views import BatchDeterminationCreateView
1111
from hypha.apply.funds.tests.factories import ApplicationSubmissionFactory
12+
from hypha.apply.projects.models.project import CONTRACTING, DRAFT
1213
from hypha.apply.users.tests.factories import StaffFactory, UserFactory
1314
from hypha.apply.utils.testing import BaseViewTestCase
1415

@@ -337,6 +338,50 @@ def test_single_stage_rejected_determination_does_not_create_project(self):
337338
self.assertIsNone(submission_next)
338339
self.assertFalse(hasattr(submission_original, "project"))
339340

341+
@override_settings(PROJECTS_DEFAULT_STATUS="contracting")
342+
def test_auto_creation_uses_status_settings(self):
343+
submission = ApplicationSubmissionFactory(
344+
status="post_review_discussion",
345+
workflow_stages=1,
346+
lead=self.user,
347+
)
348+
349+
self.post_page(
350+
submission,
351+
{
352+
"data": "value",
353+
"outcome": ACCEPTED,
354+
"message": "You are invited to submit a proposal",
355+
},
356+
"form",
357+
)
358+
359+
submission_original = self.refresh(submission)
360+
self.assertTrue(hasattr(submission_original, "project"))
361+
self.assertEqual(submission.project.status, CONTRACTING)
362+
363+
@override_settings(PROJECTS_DEFAULT_STATUS="garbage")
364+
def test_auto_creation_uses_draft_when_invalid_status_settings(self):
365+
submission = ApplicationSubmissionFactory(
366+
status="post_review_discussion",
367+
workflow_stages=1,
368+
lead=self.user,
369+
)
370+
371+
self.post_page(
372+
submission,
373+
{
374+
"data": "value",
375+
"outcome": ACCEPTED,
376+
"message": "You are invited to submit a proposal",
377+
},
378+
"form",
379+
)
380+
381+
submission_original = self.refresh(submission)
382+
self.assertTrue(hasattr(submission_original, "project"))
383+
self.assertEqual(submission.project.status, DRAFT)
384+
340385
@override_settings(PROJECTS_AUTO_CREATE=False)
341386
def test_disabling_project_auto_creation_stops_projects_being_created(self):
342387
submission = ApplicationSubmissionFactory(

hypha/apply/funds/tests/test_views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from hypha.apply.funds.views.submission_detail import SubmissionDetailView
2929
from hypha.apply.funds.workflows import INITIAL_STATE
3030
from hypha.apply.projects.models import Project
31+
from hypha.apply.projects.models.project import CONTRACTING
3132
from hypha.apply.projects.tests.factories import ProjectFactory
3233
from hypha.apply.review.tests.factories import ReviewFactory
3334
from hypha.apply.users.tests.factories import (
@@ -327,6 +328,7 @@ def test_can_create_project(self):
327328
{
328329
"project_create_form": "",
329330
"project_lead": self.user.id,
331+
"project_initial_status": CONTRACTING,
330332
"submission": self.submission.id,
331333
},
332334
view_name="create_project",
@@ -337,6 +339,7 @@ def test_can_create_project(self):
337339

338340
self.assertTrue(hasattr(submission, "project"))
339341
self.assertEqual(submission.project.id, project.id)
342+
self.assertEqual(submission.project.status, CONTRACTING)
340343

341344
def test_can_see_add_determination_primary_action(self):
342345
def assert_add_determination_displayed(submission, button_text):

hypha/apply/funds/views/submission_edit.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
DeterminationCreateOrUpdateView,
3838
)
3939
from hypha.apply.projects.forms import ProjectCreateForm
40+
from hypha.apply.projects.models.project import PROJECT_STATUS_CHOICES
4041
from hypha.apply.stream_forms.blocks import GroupToggleBlock
4142
from hypha.apply.todo.options import PROJECT_WAITING_PF, PROJECT_WAITING_SOW
4243
from hypha.apply.todo.views import add_task_to_user
@@ -434,11 +435,19 @@ def post(self, *args, **kwargs):
434435
form = ProjectCreateForm(self.request.POST, instance=self.submission)
435436
if form.is_valid():
436437
project = form.save()
438+
439+
readable_project_status = next(
440+
status[1]
441+
for status in PROJECT_STATUS_CHOICES
442+
if status[0] == project.status
443+
)
444+
437445
# Record activity
438446
messenger(
439447
MESSAGES.CREATED_PROJECT,
440448
request=self.request,
441449
user=self.request.user,
450+
status=readable_project_status,
442451
source=project,
443452
related=project.submission,
444453
)

hypha/apply/projects/forms/project.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
from django_file_form.forms import FileFormMixin
77

88
from hypha.apply.funds.models import ApplicationSubmission
9+
from hypha.apply.projects.forms.utils import (
10+
get_project_default_status,
11+
get_project_status_options,
12+
)
913
from hypha.apply.stream_forms.fields import SingleFileField
1014
from hypha.apply.stream_forms.forms import StreamBaseForm
1115
from hypha.apply.users.roles import STAFF_GROUP_NAME
@@ -85,6 +89,13 @@ class ProjectCreateForm(forms.Form):
8589
label=_("Select Project Lead"), queryset=User.objects.all()
8690
)
8791

92+
# Set the initial value to the settings default if valid, otherwise fall back to draft
93+
project_initial_status = forms.ChoiceField(
94+
label=_("Initial Project Status"),
95+
choices=get_project_status_options(),
96+
initial=get_project_default_status(),
97+
)
98+
8899
def __init__(self, *args, instance=None, **kwargs):
89100
super().__init__(*args, **kwargs)
90101

@@ -107,7 +118,8 @@ def clean_project_lead(self):
107118
def save(self, *args, **kwargs):
108119
submission = self.cleaned_data["submission"]
109120
lead = self.cleaned_data["project_lead"]
110-
return Project.create_from_submission(submission, lead=lead)
121+
status = self.cleaned_data["project_initial_status"]
122+
return Project.create_from_submission(submission, lead=lead, status=status)
111123

112124

113125
class MixedMetaClass(type(StreamBaseForm), type(forms.ModelForm)):
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from typing import List, Tuple
2+
3+
from django.conf import settings
4+
5+
from hypha.apply.projects.models.project import (
6+
COMPLETE,
7+
INTERNAL_APPROVAL,
8+
PROJECT_STATUS_CHOICES,
9+
)
10+
11+
12+
def get_project_status_options() -> List[Tuple[str, str]]:
13+
"""Gets settable project status options
14+
15+
Filters out complete & internal approval statuses as there isn't value in
16+
being able to set these
17+
"""
18+
return [
19+
status
20+
for status in PROJECT_STATUS_CHOICES
21+
if status[0] not in [COMPLETE, INTERNAL_APPROVAL]
22+
]
23+
24+
25+
def get_project_default_status() -> Tuple[str, str]:
26+
"""Gets the default project status based off the settings
27+
28+
If the `PROJECTS_DEFAULT_STATUS` setting is invalid, status will fall back
29+
to draft
30+
"""
31+
return next(
32+
(
33+
status
34+
for status in PROJECT_STATUS_CHOICES
35+
if status[0] == settings.PROJECTS_DEFAULT_STATUS
36+
),
37+
PROJECT_STATUS_CHOICES[0],
38+
)

hypha/apply/projects/models/project.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def get_address_display(self):
312312
return "" # todo: need to figure out
313313

314314
@classmethod
315-
def create_from_submission(cls, submission, lead=None):
315+
def create_from_submission(cls, submission, lead=None, status=None):
316316
"""
317317
Create a Project from the given submission.
318318
@@ -331,10 +331,22 @@ def create_from_submission(cls, submission, lead=None):
331331
if hasattr(submission, "project"):
332332
return submission.project
333333

334+
# If default status is valid and status arg is None, use it otherwise fallback to draft
335+
if status is None:
336+
status = next(
337+
(
338+
x[0]
339+
for x in PROJECT_STATUS_CHOICES
340+
if x[0] == settings.PROJECTS_DEFAULT_STATUS
341+
),
342+
DRAFT,
343+
)
344+
334345
return Project.objects.create(
335346
submission=submission,
336347
user=submission.user,
337348
title=submission.title,
349+
status=status,
338350
lead=lead if lead else None,
339351
value=submission.form_data.get("value", 0),
340352
)

hypha/apply/projects/utils.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from django.conf import settings
21
from django.utils.translation import gettext_lazy as _
32
from django_file_form.uploaded_file import PlaceholderUploadedFile
43

@@ -60,18 +59,6 @@ def save_project_details(project_id, data):
6059
project.save()
6160

6261

63-
def create_invoice(invoice):
64-
"""
65-
Creates invoice at enabled payment service.
66-
"""
67-
if settings.INTACCT_ENABLED:
68-
from hypha.apply.projects.services.sageintacct.utils import (
69-
create_intacct_invoice,
70-
)
71-
72-
create_intacct_invoice(invoice)
73-
74-
7562
def get_paf_status_display(paf_status):
7663
return dict(PAF_STATUS_CHOICES)[paf_status]
7764

0 commit comments

Comments
 (0)