Merge pull request #884 from dod-ccpo/to-funding-statuses
Funding page Task Order statuses
This commit is contained in:
@@ -2,7 +2,7 @@ from flask import current_app as app
|
||||
|
||||
from atst.database import db
|
||||
from atst.models.clin import CLIN
|
||||
from atst.models.task_order import TaskOrder
|
||||
from atst.models.task_order import TaskOrder, SORT_ORDERING
|
||||
from . import BaseDomainClass
|
||||
|
||||
|
||||
@@ -98,3 +98,10 @@ class TaskOrders(BaseDomainClass):
|
||||
if not app.config.get("CLASSIFIED"):
|
||||
section_list["funding"] = TaskOrders.UNCLASSIFIED_FUNDING
|
||||
return section_list
|
||||
|
||||
@classmethod
|
||||
def sort(cls, task_orders: [TaskOrder]) -> [TaskOrder]:
|
||||
# Sorts a list of task orders on two keys: status (primary) and time_created (secondary)
|
||||
by_time_created = sorted(task_orders, key=lambda to: to.time_created)
|
||||
by_status = sorted(by_time_created, key=lambda to: SORT_ORDERING.get(to.status))
|
||||
return by_status
|
||||
|
@@ -1,5 +1,4 @@
|
||||
from enum import Enum
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, String
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
@@ -8,13 +7,23 @@ from werkzeug.datastructures import FileStorage
|
||||
|
||||
from atst.models import Attachment, Base, mixins, types
|
||||
from atst.models.clin import JEDICLINType
|
||||
from atst.utils.clock import Clock
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
STARTED = "Started"
|
||||
PENDING = "Pending"
|
||||
DRAFT = "Draft"
|
||||
ACTIVE = "Active"
|
||||
UPCOMING = "Upcoming"
|
||||
EXPIRED = "Expired"
|
||||
UNSIGNED = "Not signed"
|
||||
|
||||
|
||||
SORT_ORDERING = {
|
||||
status: order
|
||||
for (order, status) in enumerate(
|
||||
[Status.DRAFT, Status.ACTIVE, Status.UPCOMING, Status.EXPIRED, Status.UNSIGNED]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
@@ -62,28 +71,41 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
def is_expired(self):
|
||||
return self.status == Status.EXPIRED
|
||||
|
||||
@property
|
||||
def is_completed(self):
|
||||
return all([self.pdf, self.number, len(self.clins)])
|
||||
|
||||
@property
|
||||
def is_signed(self):
|
||||
return self.signed_at is not None
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
# TODO: fix task order -- implement correctly using CLINs
|
||||
# Faked for display purposes
|
||||
return Status.ACTIVE
|
||||
today = Clock.today()
|
||||
|
||||
if not self.is_completed and not self.is_signed:
|
||||
return Status.DRAFT
|
||||
elif self.is_completed and not self.is_signed:
|
||||
return Status.UNSIGNED
|
||||
elif today < self.start_date:
|
||||
return Status.UPCOMING
|
||||
elif today >= self.end_date:
|
||||
return Status.EXPIRED
|
||||
elif self.start_date <= today < self.end_date:
|
||||
return Status.ACTIVE
|
||||
|
||||
@property
|
||||
def start_date(self):
|
||||
# TODO: fix task order -- reimplement using CLINs
|
||||
# Faked for display purposes
|
||||
return date.today()
|
||||
return min((c.start_date for c in self.clins), default=self.time_created.date())
|
||||
|
||||
@property
|
||||
def end_date(self):
|
||||
# TODO: fix task order -- reimplement using CLINs
|
||||
# Faked for display purposes
|
||||
return date.today()
|
||||
return max((c.end_date for c in self.clins), default=None)
|
||||
|
||||
@property
|
||||
def days_to_expiration(self):
|
||||
if self.end_date:
|
||||
return (self.end_date - date.today()).days
|
||||
return (self.end_date - Clock.today()).days
|
||||
|
||||
@property
|
||||
def total_obligated_funds(self):
|
||||
|
@@ -1,13 +1,11 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from flask import g, render_template
|
||||
|
||||
from . import task_orders_bp
|
||||
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
||||
from atst.domain.portfolios import Portfolios
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.models.task_order import Status
|
||||
from atst.models import Permissions
|
||||
from atst.models.task_order import Status as TaskOrderStatus
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>")
|
||||
@@ -34,19 +32,16 @@ def review_task_order(task_order_id):
|
||||
@user_can(Permissions.VIEW_PORTFOLIO_FUNDING, message="view portfolio funding")
|
||||
def portfolio_funding(portfolio_id):
|
||||
portfolio = Portfolios.get(g.current_user, portfolio_id)
|
||||
task_orders_by_status = defaultdict(list)
|
||||
|
||||
for task_order in portfolio.task_orders:
|
||||
task_orders_by_status[task_order.status].append(task_order)
|
||||
|
||||
active_task_orders = task_orders_by_status.get(TaskOrderStatus.ACTIVE, [])
|
||||
|
||||
task_orders = TaskOrders.sort(portfolio.task_orders)
|
||||
label_colors = {
|
||||
Status.DRAFT: "warning",
|
||||
Status.ACTIVE: "success",
|
||||
Status.UPCOMING: "info",
|
||||
Status.EXPIRED: "error",
|
||||
Status.UNSIGNED: "purple",
|
||||
}
|
||||
return render_template(
|
||||
"portfolios/task_orders/index.html",
|
||||
pending_task_orders=(
|
||||
task_orders_by_status.get(TaskOrderStatus.STARTED, [])
|
||||
+ task_orders_by_status.get(TaskOrderStatus.PENDING, [])
|
||||
),
|
||||
active_task_orders=active_task_orders,
|
||||
expired_task_orders=task_orders_by_status.get(TaskOrderStatus.EXPIRED, []),
|
||||
task_orders=task_orders,
|
||||
label_colors=label_colors,
|
||||
)
|
||||
|
11
atst/utils/clock.py
Normal file
11
atst/utils/clock.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import pendulum
|
||||
|
||||
|
||||
class Clock(object):
|
||||
@classmethod
|
||||
def today(cls, tz="UTC"):
|
||||
return pendulum.today(tz=tz).date()
|
||||
|
||||
@classmethod
|
||||
def now(cls, tz="UTC"):
|
||||
return pendulum.now(tz=tz)
|
Reference in New Issue
Block a user