Merge pull request #243 from dod-ccpo/action-required-flag
Action required flag
This commit is contained in:
commit
0a783c0a1e
@ -153,14 +153,6 @@ class Requests(object):
|
|||||||
request.status_events.append(status_event)
|
request.status_events.append(status_event)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def action_required_by(cls, request):
|
|
||||||
return {
|
|
||||||
RequestStatus.STARTED: "mission_owner",
|
|
||||||
RequestStatus.PENDING_FINANCIAL_VERIFICATION: "mission_owner",
|
|
||||||
RequestStatus.PENDING_CCPO_APPROVAL: "ccpo",
|
|
||||||
}.get(request.status)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def should_auto_approve(cls, request):
|
def should_auto_approve(cls, request):
|
||||||
try:
|
try:
|
||||||
|
@ -60,3 +60,10 @@ class Request(Base):
|
|||||||
if last_submission:
|
if last_submission:
|
||||||
return pendulum.instance(last_submission.time_created)
|
return pendulum.instance(last_submission.time_created)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action_required_by(self):
|
||||||
|
return {
|
||||||
|
RequestStatus.PENDING_FINANCIAL_VERIFICATION: "mission_owner",
|
||||||
|
RequestStatus.PENDING_CCPO_APPROVAL: "ccpo",
|
||||||
|
}.get(self.status)
|
||||||
|
@ -6,10 +6,63 @@ from atst.domain.requests import Requests
|
|||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
|
|
||||||
|
|
||||||
def map_request(request):
|
class RequestsIndex(object):
|
||||||
|
def __init__(self, user):
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
if (
|
||||||
|
Permissions.REVIEW_AND_APPROVE_JEDI_WORKSPACE_REQUEST
|
||||||
|
in self.user.atat_permissions
|
||||||
|
):
|
||||||
|
return self._ccpo_view(self.user)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self._non_ccpo_view(self.user)
|
||||||
|
|
||||||
|
def _ccpo_view(self, user):
|
||||||
|
requests = Requests.get_many()
|
||||||
|
mapped_requests = [self._map_request(r, "ccpo") for r in requests]
|
||||||
|
num_action_required = len(
|
||||||
|
[r for r in mapped_requests if r.get("action_required")]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"requests": mapped_requests,
|
||||||
|
"pending_financial_verification": False,
|
||||||
|
"pending_ccpo_approval": False,
|
||||||
|
"extended_view": True,
|
||||||
|
"kpi_inprogress": Requests.in_progress_count(),
|
||||||
|
"kpi_pending": Requests.pending_ccpo_count(),
|
||||||
|
"kpi_completed": Requests.completed_count(),
|
||||||
|
"num_action_required": num_action_required,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _non_ccpo_view(self, user):
|
||||||
|
requests = Requests.get_many(creator=user)
|
||||||
|
mapped_requests = [self._map_request(r, "mission_owner") for r in requests]
|
||||||
|
num_action_required = len(
|
||||||
|
[r for r in mapped_requests if r.get("action_required")]
|
||||||
|
)
|
||||||
|
pending_fv = any(
|
||||||
|
Requests.is_pending_financial_verification(r) for r in requests
|
||||||
|
)
|
||||||
|
pending_ccpo = any(Requests.is_pending_ccpo_approval(r) for r in requests)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"requests": mapped_requests,
|
||||||
|
"pending_financial_verification": pending_fv,
|
||||||
|
"pending_ccpo_approval": pending_ccpo,
|
||||||
|
"num_action_required": num_action_required,
|
||||||
|
"extended_view": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _map_request(self, request, viewing_role):
|
||||||
time_created = pendulum.instance(request.time_created)
|
time_created = pendulum.instance(request.time_created)
|
||||||
is_new = time_created.add(days=1) > pendulum.now()
|
is_new = time_created.add(days=1) > pendulum.now()
|
||||||
app_count = request.body.get("details_of_use", {}).get("num_software_systems", 0)
|
app_count = request.body.get("details_of_use", {}).get(
|
||||||
|
"num_software_systems", 0
|
||||||
|
)
|
||||||
annual_usage = request.annual_spend
|
annual_usage = request.annual_spend
|
||||||
last_submission_timestamp = (
|
last_submission_timestamp = (
|
||||||
request.last_submission_timestamp.format("M/DD/YYYY")
|
request.last_submission_timestamp.format("M/DD/YYYY")
|
||||||
@ -18,7 +71,9 @@ def map_request(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if Requests.is_pending_financial_verification(request):
|
if Requests.is_pending_financial_verification(request):
|
||||||
edit_link = url_for("requests.financial_verification", request_id=request.id)
|
edit_link = url_for(
|
||||||
|
"requests.financial_verification", request_id=request.id
|
||||||
|
)
|
||||||
elif Requests.is_pending_ccpo_approval(request):
|
elif Requests.is_pending_ccpo_approval(request):
|
||||||
edit_link = url_for("requests.view_pending_request", request_id=request.id)
|
edit_link = url_for("requests.view_pending_request", request_id=request.id)
|
||||||
else:
|
else:
|
||||||
@ -36,48 +91,11 @@ def map_request(request):
|
|||||||
"full_name": request.creator.full_name,
|
"full_name": request.creator.full_name,
|
||||||
"annual_usage": annual_usage,
|
"annual_usage": annual_usage,
|
||||||
"edit_link": edit_link,
|
"edit_link": edit_link,
|
||||||
|
"action_required": request.action_required_by == viewing_role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@requests_bp.route("/requests", methods=["GET"])
|
@requests_bp.route("/requests", methods=["GET"])
|
||||||
def requests_index():
|
def requests_index():
|
||||||
if (
|
context = RequestsIndex(g.current_user).execute()
|
||||||
Permissions.REVIEW_AND_APPROVE_JEDI_WORKSPACE_REQUEST
|
return render_template("requests.html", **context)
|
||||||
in g.current_user.atat_permissions
|
|
||||||
):
|
|
||||||
return _ccpo_view()
|
|
||||||
|
|
||||||
else:
|
|
||||||
return _non_ccpo_view()
|
|
||||||
|
|
||||||
|
|
||||||
def _ccpo_view():
|
|
||||||
requests = Requests.get_many()
|
|
||||||
mapped_requests = [map_request(r) for r in requests]
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
"requests.html",
|
|
||||||
requests=mapped_requests,
|
|
||||||
pending_financial_verification=False,
|
|
||||||
pending_ccpo_approval=False,
|
|
||||||
extended_view=True,
|
|
||||||
kpi_inprogress=Requests.in_progress_count(),
|
|
||||||
kpi_pending=Requests.pending_ccpo_count(),
|
|
||||||
kpi_completed=Requests.completed_count(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _non_ccpo_view():
|
|
||||||
requests = Requests.get_many(creator=g.current_user)
|
|
||||||
mapped_requests = [map_request(r) for r in requests]
|
|
||||||
|
|
||||||
pending_fv = any(Requests.is_pending_financial_verification(r) for r in requests)
|
|
||||||
pending_ccpo = any(Requests.is_pending_ccpo_approval(r) for r in requests)
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
"requests.html",
|
|
||||||
requests=mapped_requests,
|
|
||||||
pending_financial_verification=pending_fv,
|
|
||||||
pending_ccpo_approval=pending_ccpo,
|
|
||||||
extended_view=False,
|
|
||||||
)
|
|
||||||
|
@ -25,6 +25,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|
||||||
|
{% if num_action_required %}
|
||||||
|
{% set title -%}
|
||||||
|
Action required on {{ num_action_required }} requests.
|
||||||
|
{%- endset %}
|
||||||
|
|
||||||
|
{{ Alert (title)}}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if not requests %}
|
{% if not requests %}
|
||||||
|
|
||||||
{{ EmptyState(
|
{{ EmptyState(
|
||||||
@ -109,7 +117,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<a class='icon-link icon-link--large' href="{{ r['edit_link'] }}">{{ r['order_id'] }}</a>
|
<a class='icon-link icon-link--large' href="{{ r['edit_link'] }}">{{ r['order_id'] }}</a>
|
||||||
{% if r['is_new'] %}<span class="usa-label">New</span>{% endif %}
|
{% if r.action_required %}<span class="label label--info">Action Required</span>{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<td>{{ r.last_submission_timestamp }}</td>
|
<td>{{ r.last_submission_timestamp }}</td>
|
||||||
{% if extended_view %}
|
{% if extended_view %}
|
||||||
|
@ -11,7 +11,6 @@ from atst.models.task_order import TaskOrder
|
|||||||
from atst.models.user import User
|
from atst.models.user import User
|
||||||
from atst.models.role import Role
|
from atst.models.role import Role
|
||||||
from atst.models.workspace import Workspace
|
from atst.models.workspace import Workspace
|
||||||
from atst.models.request_status_event import RequestStatusEvent
|
|
||||||
from atst.domain.roles import Roles
|
from atst.domain.roles import Roles
|
||||||
|
|
||||||
|
|
||||||
@ -33,6 +32,11 @@ class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
|
|||||||
atat_role = factory.SubFactory(RoleFactory)
|
atat_role = factory.SubFactory(RoleFactory)
|
||||||
dod_id = factory.LazyFunction(lambda: "".join(random.choices(string.digits, k=10)))
|
dod_id = factory.LazyFunction(lambda: "".join(random.choices(string.digits, k=10)))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_atat_role(cls, atat_role_name, **kwargs):
|
||||||
|
role = Roles.get(atat_role_name)
|
||||||
|
return cls.create(atat_role=role, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class RequestStatusEventFactory(factory.alchemy.SQLAlchemyModelFactory):
|
class RequestStatusEventFactory(factory.alchemy.SQLAlchemyModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -2,23 +2,18 @@ from tests.factories import RequestFactory, UserFactory
|
|||||||
from atst.domain.requests import Requests, RequestStatus
|
from atst.domain.requests import Requests, RequestStatus
|
||||||
|
|
||||||
|
|
||||||
def test_started_request_requires_mo_action():
|
|
||||||
request = RequestFactory.create()
|
|
||||||
assert Requests.action_required_by(request) == "mission_owner"
|
|
||||||
|
|
||||||
|
|
||||||
def test_pending_financial_requires_mo_action():
|
def test_pending_financial_requires_mo_action():
|
||||||
request = RequestFactory.create()
|
request = RequestFactory.create()
|
||||||
request = Requests.set_status(request, RequestStatus.PENDING_FINANCIAL_VERIFICATION)
|
request = Requests.set_status(request, RequestStatus.PENDING_FINANCIAL_VERIFICATION)
|
||||||
|
|
||||||
assert Requests.action_required_by(request) == "mission_owner"
|
assert request.action_required_by == "mission_owner"
|
||||||
|
|
||||||
|
|
||||||
def test_pending_ccpo_approval_requires_ccpo():
|
def test_pending_ccpo_approval_requires_ccpo():
|
||||||
request = RequestFactory.create()
|
request = RequestFactory.create()
|
||||||
request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
|
request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
|
||||||
|
|
||||||
assert Requests.action_required_by(request) == "ccpo"
|
assert request.action_required_by == "ccpo"
|
||||||
|
|
||||||
|
|
||||||
def test_request_has_creator():
|
def test_request_has_creator():
|
||||||
|
23
tests/routes/test_requests_index.py
Normal file
23
tests/routes/test_requests_index.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from atst.routes.requests.index import RequestsIndex
|
||||||
|
from tests.factories import RequestFactory, UserFactory
|
||||||
|
from atst.domain.requests import Requests
|
||||||
|
|
||||||
|
|
||||||
|
def test_action_required_mission_owner():
|
||||||
|
creator = UserFactory.create()
|
||||||
|
requests = RequestFactory.create_batch(5, creator=creator)
|
||||||
|
Requests.submit(requests[0])
|
||||||
|
context = RequestsIndex(creator).execute()
|
||||||
|
|
||||||
|
assert context["requests"][0]["action_required"] == False
|
||||||
|
|
||||||
|
|
||||||
|
def test_action_required_ccpo():
|
||||||
|
creator = UserFactory.create()
|
||||||
|
requests = RequestFactory.create_batch(5, creator=creator)
|
||||||
|
Requests.submit(requests[0])
|
||||||
|
|
||||||
|
ccpo = UserFactory.from_atat_role("ccpo")
|
||||||
|
context = RequestsIndex(ccpo).execute()
|
||||||
|
|
||||||
|
assert context["num_action_required"] == 1
|
Loading…
x
Reference in New Issue
Block a user