resolve merge conflict

This commit is contained in:
2020-02-11 15:40:15 -05:00
201 changed files with 1667 additions and 3959 deletions

View File

@@ -1,5 +1,13 @@
from enum import Enum
from sqlalchemy import Column, Date, Enum as SQLAEnum, ForeignKey, Numeric, String
from sqlalchemy import (
Column,
Date,
DateTime,
Enum as SQLAEnum,
ForeignKey,
Numeric,
String,
)
from sqlalchemy.orm import relationship
from datetime import date
@@ -29,6 +37,7 @@ class CLIN(Base, mixins.TimestampsMixin):
total_amount = Column(Numeric(scale=2), nullable=False)
obligated_amount = Column(Numeric(scale=2), nullable=False)
jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=False)
last_sent_at = Column(DateTime)
#
# NOTE: For now obligated CLINS are CLIN 1 + CLIN 3

View File

@@ -61,6 +61,10 @@ class Environment(
def portfolio_id(self):
return self.application.portfolio_id
@property
def is_pending(self):
return self.cloud_id is None
def __repr__(self):
return "<Environment(name='{}', num_users='{}', application='{}', portfolio='{}', id='{}')>".format(
self.name,

View File

@@ -36,7 +36,7 @@ class EnvironmentRole(
)
application_role = relationship("ApplicationRole")
csp_user_id = Column(String())
cloud_id = Column(String())
class Status(Enum):
PENDING = "pending"

View File

@@ -24,6 +24,8 @@ class AzureStages(Enum):
TENANT_PRINCIPAL_CREDENTIAL = "tenant principal credential"
ADMIN_ROLE_DEFINITION = "admin role definition"
PRINCIPAL_ADMIN_ROLE = "tenant principal admin"
INITIAL_MGMT_GROUP = "initial management group"
INITIAL_MGMT_GROUP_VERIFICATION = "initial management group verification"
TENANT_ADMIN_OWNERSHIP = "tenant admin ownership"
TENANT_PRINCIPAL_OWNERSHIP = "tenant principial ownership"

View File

@@ -1,11 +1,16 @@
import re
from string import ascii_lowercase, digits
from random import choices
from itertools import chain
from sqlalchemy import Column, String
from sqlalchemy.orm import relationship
from sqlalchemy.types import ARRAY
from itertools import chain
from atst.models.base import Base
import atst.models.types as types
import atst.models.mixins as mixins
from atst.models.task_order import TaskOrder
from atst.models.portfolio_role import PortfolioRole, Status as PortfolioRoleStatus
from atst.domain.permission_sets import PermissionSets
from atst.utils import first_or_none
@@ -89,6 +94,22 @@ class Portfolio(
def active_task_orders(self):
return [task_order for task_order in self.task_orders if task_order.is_active]
@property
def total_obligated_funds(self):
return sum(
(task_order.total_obligated_funds for task_order in self.active_task_orders)
)
@property
def upcoming_obligated_funds(self):
return sum(
(
task_order.total_obligated_funds
for task_order in self.task_orders
if task_order.is_upcoming
)
)
@property
def funding_duration(self):
"""
@@ -153,6 +174,51 @@ class Portfolio(
def application_id(self):
return None
def to_dictionary(self):
ppoc = self.owner
user_id = f"{ppoc.first_name[0]}{ppoc.last_name}".lower()
domain_name = re.sub("[^0-9a-zA-Z]+", "", self.name).lower() + "".join(
choices(ascii_lowercase + digits, k=4)
)
portfolio_data = {
"user_id": user_id,
"password": "",
"domain_name": domain_name,
"first_name": ppoc.first_name,
"last_name": ppoc.last_name,
"country_code": "US",
"password_recovery_email_address": ppoc.email,
"address": { # TODO: TBD if we're sourcing this from data or config
"company_name": "",
"address_line_1": "",
"city": "",
"region": "",
"country": "",
"postal_code": "",
},
"billing_profile_display_name": "ATAT Billing Profile",
}
try:
initial_task_order: TaskOrder = self.task_orders[0]
initial_clin = initial_task_order.sorted_clins[0]
portfolio_data.update(
{
"initial_clin_amount": initial_clin.obligated_amount,
"initial_clin_start_date": initial_clin.start_date.strftime(
"%Y/%m/%d"
),
"initial_clin_end_date": initial_clin.end_date.strftime("%Y/%m/%d"),
"initial_clin_type": initial_clin.number,
"initial_task_order_id": initial_task_order.number,
}
)
except IndexError:
pass
return portfolio_data
def __repr__(self):
return "<Portfolio(name='{}', user_count='{}', id='{}')>".format(
self.name, self.user_count, self.id

View File

@@ -119,6 +119,8 @@ class PortfolioStateMachine(
def trigger_next_transition(self, **kwargs):
state_obj = self.machine.get_state(self.state)
kwargs["csp_data"] = kwargs.get("csp_data", {})
if state_obj.is_system:
if self.current_state in (FSMStates.UNSTARTED, FSMStates.STARTING):
# call the first trigger availabe for these two system states

View File

@@ -25,7 +25,6 @@ SORT_ORDERING = [
Status.DRAFT,
Status.UPCOMING,
Status.EXPIRED,
Status.UNSIGNED,
]
@@ -39,6 +38,7 @@ class TaskOrder(Base, mixins.TimestampsMixin):
pdf_attachment_id = Column(ForeignKey("attachments.id"))
_pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id])
pdf_last_sent_at = Column(DateTime)
number = Column(String, unique=True,) # Task Order Number
signer_dod_id = Column(String)
signed_at = Column(DateTime)
@@ -87,6 +87,10 @@ class TaskOrder(Base, mixins.TimestampsMixin):
def is_expired(self):
return self.status == Status.EXPIRED
@property
def is_upcoming(self):
return self.status == Status.UPCOMING
@property
def clins_are_completed(self):
return all([len(self.clins), (clin.is_completed for clin in self.clins)])
@@ -147,7 +151,10 @@ class TaskOrder(Base, mixins.TimestampsMixin):
@property
def display_status(self):
return self.status.value
if self.status == Status.UNSIGNED:
return Status.DRAFT.value
else:
return self.status.value
@property
def portfolio_name(self):