Merge pull request #541 from dod-ccpo/task-order-status
Rework Task Order status
This commit is contained in:
commit
a010487f34
@ -0,0 +1,28 @@
|
||||
"""Remove status column from task order
|
||||
|
||||
Revision ID: da9d1c911a52
|
||||
Revises: a6837632686c
|
||||
Create Date: 2019-01-14 11:21:51.729134
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'da9d1c911a52'
|
||||
down_revision = 'a6837632686c'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('task_orders', 'status')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('task_orders', sa.Column('status', sa.VARCHAR(length=7), autoincrement=False, nullable=True))
|
||||
# ### end Alembic commands ###
|
@ -1,14 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
Enum as SQLAEnum,
|
||||
Numeric,
|
||||
String,
|
||||
ForeignKey,
|
||||
Date,
|
||||
Integer,
|
||||
)
|
||||
import pendulum
|
||||
from sqlalchemy import Column, Numeric, String, ForeignKey, Date, Integer
|
||||
from sqlalchemy.types import ARRAY
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
@ -17,6 +10,8 @@ from atst.models import Base, types, mixins
|
||||
|
||||
class Status(Enum):
|
||||
PENDING = "Pending"
|
||||
ACTIVE = "Active"
|
||||
EXPIRED = "Expired"
|
||||
|
||||
|
||||
class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
@ -41,8 +36,6 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
so_id = Column(ForeignKey("users.id"))
|
||||
security_officer = relationship("User", foreign_keys="TaskOrder.so_id")
|
||||
|
||||
status = Column(SQLAEnum(Status, native_enum=False))
|
||||
|
||||
scope = Column(String) # Cloud Project Scope
|
||||
defense_component = Column(String) # Department of Defense Component
|
||||
app_migration = Column(String) # App Migration
|
||||
@ -79,10 +72,21 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
number = Column(String, unique=True) # Task Order Number
|
||||
loa = Column(ARRAY(String)) # Line of Accounting (LOA)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if "status" not in kwargs:
|
||||
self.status = Status.PENDING
|
||||
@property
|
||||
def is_submitted(self):
|
||||
return self.number is not None
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
if self.is_submitted:
|
||||
now = pendulum.now().date()
|
||||
if self.start_date > now:
|
||||
return Status.PENDING
|
||||
elif self.end_date < now:
|
||||
return Status.EXPIRED
|
||||
return Status.ACTIVE
|
||||
else:
|
||||
return Status.PENDING
|
||||
|
||||
@property
|
||||
def budget(self):
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
FILES_TO_FORMAT="atst/ tests/ app.py"
|
||||
FILES_TO_FORMAT="atst/ tests/ app.py script/"
|
||||
|
||||
if [ "$1" == "check" ]; then
|
||||
pipenv run black --check ${FILES_TO_FORMAT}
|
||||
|
@ -4,7 +4,8 @@ import csv
|
||||
# Add root project dir to the python path
|
||||
import os
|
||||
import sys
|
||||
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
sys.path.append(parent_dir)
|
||||
|
||||
from atst.app import make_app, make_config
|
||||
@ -16,9 +17,10 @@ def get_pe_numbers(url):
|
||||
t = response.read().decode("utf-8")
|
||||
return list(csv.reader(t.split("\r\n")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = make_config()
|
||||
url = config['PE_NUMBER_CSV_URL']
|
||||
url = config["PE_NUMBER_CSV_URL"]
|
||||
print("Fetching PE numbers from {}".format(url))
|
||||
pe_numbers = get_pe_numbers(url)
|
||||
|
||||
|
@ -41,19 +41,21 @@ dod_ids = [
|
||||
"4567890123",
|
||||
"5678901234",
|
||||
"6789012345",
|
||||
"2342342342", # Andy
|
||||
"3453453453", # Sally
|
||||
"4564564564", # Betty
|
||||
"2342342342", # Andy
|
||||
"3453453453", # Sally
|
||||
"4564564564", # Betty
|
||||
"6786786786",
|
||||
]
|
||||
|
||||
|
||||
def create_demo_portfolio(name, data):
|
||||
try:
|
||||
portfolio_owner = Users.get_by_dod_id("678678678") # Other
|
||||
auditor = Users.get_by_dod_id("3453453453") # Sally
|
||||
portfolio_owner = Users.get_by_dod_id("678678678") # Other
|
||||
auditor = Users.get_by_dod_id("3453453453") # Sally
|
||||
except NotFoundError:
|
||||
print("Could not find demo users; will not create demo portfolio {}".format(name))
|
||||
print(
|
||||
"Could not find demo users; will not create demo portfolio {}".format(name)
|
||||
)
|
||||
return
|
||||
|
||||
request = RequestFactory.build(creator=portfolio_owner)
|
||||
@ -64,10 +66,12 @@ def create_demo_portfolio(name, data):
|
||||
approved_request = Requests.set_status(request, RequestStatus.APPROVED)
|
||||
|
||||
portfolio = Requests.approve_and_create_portfolio(request)
|
||||
portfolios.update(portfolio, { "name": name })
|
||||
portfolios.update(portfolio, {"name": name})
|
||||
|
||||
for mock_application in data["applications"]:
|
||||
application = application(portfolio=portfolio, name=mock_application.name, description='')
|
||||
application = application(
|
||||
portfolio=portfolio, name=mock_application.name, description=""
|
||||
)
|
||||
env_names = [env.name for env in mock_application.environments]
|
||||
envs = Environments.create_many(application, env_names)
|
||||
db.session.add(application)
|
||||
@ -153,5 +157,9 @@ if __name__ == "__main__":
|
||||
app = make_app(config)
|
||||
with app.app_context():
|
||||
remove_sample_data()
|
||||
create_demo_portfolio('Aardvark', MockReportingProvider.REPORT_FIXTURE_MAP["Aardvark"])
|
||||
create_demo_portfolio('Beluga', MockReportingProvider.REPORT_FIXTURE_MAP["Beluga"])
|
||||
create_demo_portfolio(
|
||||
"Aardvark", MockReportingProvider.REPORT_FIXTURE_MAP["Aardvark"]
|
||||
)
|
||||
create_demo_portfolio(
|
||||
"Beluga", MockReportingProvider.REPORT_FIXTURE_MAP["Beluga"]
|
||||
)
|
||||
|
@ -14,10 +14,17 @@ from atst.domain.applications import Applications
|
||||
from atst.domain.portfolio_roles import PortfolioRoles
|
||||
from atst.models.invitation import Status as InvitationStatus
|
||||
from atst.domain.exceptions import AlreadyExistsError
|
||||
from tests.factories import RequestFactory, LegacyTaskOrderFactory, InvitationFactory
|
||||
from tests.factories import (
|
||||
InvitationFactory,
|
||||
RequestFactory,
|
||||
TaskOrderFactory,
|
||||
random_future_date,
|
||||
random_past_date,
|
||||
random_task_order_number,
|
||||
)
|
||||
from atst.routes.dev import _DEV_USERS as DEV_USERS
|
||||
|
||||
portfolio_USERS = [
|
||||
PORTFOLIO_USERS = [
|
||||
{
|
||||
"first_name": "Danny",
|
||||
"last_name": "Knight",
|
||||
@ -48,7 +55,7 @@ PORTFOLIO_INVITED_USERS = [
|
||||
"email": "frederick@mil.gov",
|
||||
"portfolio_role": "developer",
|
||||
"dod_id": "0000000004",
|
||||
"status": InvitationStatus.REJECTED_WRONG_USER
|
||||
"status": InvitationStatus.REJECTED_WRONG_USER,
|
||||
},
|
||||
{
|
||||
"first_name": "Gina",
|
||||
@ -56,7 +63,7 @@ PORTFOLIO_INVITED_USERS = [
|
||||
"email": "gina@mil.gov",
|
||||
"portfolio_role": "developer",
|
||||
"dod_id": "0000000005",
|
||||
"status": InvitationStatus.REJECTED_EXPIRED
|
||||
"status": InvitationStatus.REJECTED_EXPIRED,
|
||||
},
|
||||
{
|
||||
"first_name": "Hector",
|
||||
@ -64,7 +71,7 @@ PORTFOLIO_INVITED_USERS = [
|
||||
"email": "hector@mil.gov",
|
||||
"portfolio_role": "developer",
|
||||
"dod_id": "0000000006",
|
||||
"status": InvitationStatus.REVOKED
|
||||
"status": InvitationStatus.REVOKED,
|
||||
},
|
||||
{
|
||||
"first_name": "Isabella",
|
||||
@ -72,7 +79,7 @@ PORTFOLIO_INVITED_USERS = [
|
||||
"email": "isabella@mil.gov",
|
||||
"portfolio_role": "developer",
|
||||
"dod_id": "0000000007",
|
||||
"status": InvitationStatus.PENDING
|
||||
"status": InvitationStatus.PENDING,
|
||||
},
|
||||
]
|
||||
|
||||
@ -88,38 +95,45 @@ def seed_db():
|
||||
users.append(user)
|
||||
|
||||
for user in users:
|
||||
if Requests.get_many(creator=user):
|
||||
continue
|
||||
|
||||
requests = []
|
||||
for dollar_value in [1, 200, 3000, 40000, 500000, 1000000]:
|
||||
request = RequestFactory.build(creator=user)
|
||||
request.latest_revision.dollar_value = dollar_value
|
||||
db.session.add(request)
|
||||
db.session.commit()
|
||||
|
||||
Requests.submit(request)
|
||||
requests.append(request)
|
||||
|
||||
request = requests[0]
|
||||
request.legacy_task_order = LegacyTaskOrderFactory.build()
|
||||
request = Requests.update(
|
||||
request.id, {"financial_verification": RequestFactory.mock_financial_data()}
|
||||
)
|
||||
|
||||
portfolio = Portfolios.create(
|
||||
user, name="{}'s portfolio".format(user.first_name)
|
||||
)
|
||||
for portfolio_role in portfolio_USERS:
|
||||
for portfolio_role in PORTFOLIO_USERS:
|
||||
ws_role = Portfolios.create_member(user, portfolio, portfolio_role)
|
||||
db.session.refresh(ws_role)
|
||||
PortfolioRoles.enable(ws_role)
|
||||
|
||||
for portfolio_role in PORTFOLIO_INVITED_USERS:
|
||||
ws_role = Portfolios.create_member(user, portfolio, portfolio_role)
|
||||
invitation = InvitationFactory.build(portfolio_role=ws_role, status=portfolio_role["status"])
|
||||
invitation = InvitationFactory.build(
|
||||
portfolio_role=ws_role, status=portfolio_role["status"]
|
||||
)
|
||||
db.session.add(invitation)
|
||||
|
||||
[expired_start, expired_end] = sorted(
|
||||
[
|
||||
random_past_date(year_max=2, year_min=1),
|
||||
random_past_date(year_max=1, year_min=1),
|
||||
]
|
||||
)
|
||||
active_start = expired_end
|
||||
active_end = random_future_date(year_min=1, year_max=1)
|
||||
|
||||
date_ranges = [(expired_start, expired_end), (active_start, active_end)]
|
||||
for (start_date, end_date) in date_ranges:
|
||||
task_order = TaskOrderFactory.build(
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
number=random_task_order_number(),
|
||||
portfolio=portfolio,
|
||||
)
|
||||
db.session.add(task_order)
|
||||
|
||||
pending_task_order = TaskOrderFactory.build(
|
||||
start_date=None, end_date=None, number=None, portfolio=portfolio
|
||||
)
|
||||
db.session.add(pending_task_order)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
Applications.create(
|
||||
|
@ -56,6 +56,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.label--pending {
|
||||
background-color: $color-gold;
|
||||
}
|
||||
|
||||
.label--active {
|
||||
background-color: $color-green;
|
||||
}
|
||||
|
||||
.label--expired {
|
||||
background-color: $color-red;
|
||||
}
|
||||
|
||||
.task-order-heading {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
@ -63,7 +63,7 @@
|
||||
<div class="panel task-order-heading row">
|
||||
<div class="panel__content task-order-heading__name row">
|
||||
<h2>New Task Order</h2>
|
||||
<span class="label label--{{ 'warning' if task_order.is_pending }}">{{ task_order.status.value }}</span>
|
||||
<span class="label label--{{ task_order.status.value.lower() }}">{{ task_order.status.value }}</span>
|
||||
</div>
|
||||
<div class="task_order-heading__details row">
|
||||
<div class="task-order-heading__value col">
|
||||
|
@ -1,3 +1,4 @@
|
||||
import operator
|
||||
import random
|
||||
import string
|
||||
import factory
|
||||
@ -41,14 +42,26 @@ def random_phone_number():
|
||||
return "".join(random.choices(string.digits, k=10))
|
||||
|
||||
|
||||
def random_task_order_number():
|
||||
return "-".join([str(random.randint(100, 999)) for _ in range(4)])
|
||||
|
||||
|
||||
def random_past_date(year_min=1, year_max=5):
|
||||
return _random_date(year_min, year_max, operator.sub)
|
||||
|
||||
|
||||
def random_future_date(year_min=1, year_max=5):
|
||||
return _random_date(year_min, year_max, operator.add)
|
||||
|
||||
|
||||
def _random_date(year_min, year_max, operation):
|
||||
if year_min == year_max:
|
||||
inc = year_min
|
||||
else:
|
||||
inc = random.randrange(year_min, year_max)
|
||||
|
||||
return datetime.date(
|
||||
datetime.date.today().year + inc,
|
||||
operation(datetime.date.today().year, inc),
|
||||
random.randrange(1, 12),
|
||||
random.randrange(1, 28),
|
||||
)
|
||||
|
@ -1,9 +1,32 @@
|
||||
from atst.models.task_order import TaskOrder, Status
|
||||
|
||||
from tests.factories import random_future_date, random_past_date
|
||||
|
||||
def test_default_status():
|
||||
|
||||
class TestTaskOrderStatus:
|
||||
def test_pending_status(self):
|
||||
to = TaskOrder()
|
||||
assert to.status == Status.PENDING
|
||||
|
||||
to = TaskOrder(number="42", start_date=random_future_date())
|
||||
assert to.status == Status.PENDING
|
||||
|
||||
def test_active_status(self):
|
||||
to = TaskOrder(
|
||||
number="42", start_date=random_past_date(), end_date=random_future_date()
|
||||
)
|
||||
assert to.status == Status.ACTIVE
|
||||
|
||||
def test_expired_status(self):
|
||||
to = TaskOrder(
|
||||
number="42", start_date=random_past_date(), end_date=random_past_date()
|
||||
)
|
||||
assert to.status == Status.EXPIRED
|
||||
|
||||
|
||||
def test_is_submitted():
|
||||
to = TaskOrder()
|
||||
assert to.status == Status.PENDING
|
||||
assert not to.is_submitted
|
||||
|
||||
with_args = TaskOrder(number="42")
|
||||
assert to.status == Status.PENDING
|
||||
to = TaskOrder(number="42")
|
||||
assert to.is_submitted
|
||||
|
Loading…
x
Reference in New Issue
Block a user