From dba63cdd15903add73f01620a69e743834103a90 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Thu, 20 Feb 2020 15:15:50 -0500 Subject: [PATCH] Refactor Portfolio to_dictionary method In refactoring this method, refactor some other areas: - add a property to generate a dict needed for the "initial clin" - add new relationship field to get all CLINs associated with a portfolio - add a property to either get or generate a portfolio's domain name - least importantly, sort imports - On the CLIN model, add a property to get a JEDI clin type's number, i.e. JEDI_CLIN_3 -> 3. This should be the "initial_clin_type". --- atst/jobs.py | 2 +- atst/models/clin.py | 4 ++ atst/models/portfolio.py | 105 ++++++++++++++++++++------------------- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/atst/jobs.py b/atst/jobs.py index 855af5b2..e5facabe 100644 --- a/atst/jobs.py +++ b/atst/jobs.py @@ -333,7 +333,7 @@ def create_billing_instruction(self): initial_clin_amount=clin.obligated_amount, initial_clin_start_date=str(clin.start_date), initial_clin_end_date=str(clin.end_date), - initial_clin_type=clin.number, + initial_clin_type=clin.jedi_clin_number, initial_task_order_id=str(clin.task_order_id), ) diff --git a/atst/models/clin.py b/atst/models/clin.py index a96e907f..b74f9b0d 100644 --- a/atst/models/clin.py +++ b/atst/models/clin.py @@ -65,6 +65,10 @@ class CLIN(Base, mixins.TimestampsMixin): ] ) + @property + def jedi_clin_number(self): + return self.jedi_clin_type.value[-1] + def to_dictionary(self): data = { c.name: getattr(self, c.name) diff --git a/atst/models/portfolio.py b/atst/models/portfolio.py index f683ac30..bd430ed3 100644 --- a/atst/models/portfolio.py +++ b/atst/models/portfolio.py @@ -1,23 +1,23 @@ import re -from string import ascii_lowercase, digits -from random import choices from itertools import chain +from random import choices +from string import ascii_lowercase, digits +from typing import Dict from sqlalchemy import Column, String from sqlalchemy.orm import relationship from sqlalchemy.types import ARRAY - -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 -from atst.database import db - from sqlalchemy_json import NestedMutableJson +from atst.database import db +import atst.models.mixins as mixins +import atst.models.types as types +from atst.domain.permission_sets import PermissionSets +from atst.models.base import Base +from atst.models.portfolio_role import PortfolioRole +from atst.models.portfolio_role import Status as PortfolioRoleStatus +from atst.utils import first_or_none + class Portfolio( Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin @@ -54,6 +54,7 @@ class Portfolio( roles = relationship("PortfolioRole") task_orders = relationship("TaskOrder") + clins = relationship("CLIN", secondary="task_orders") @property def owner_role(self): @@ -82,13 +83,27 @@ class Portfolio( return len(self.task_orders) @property - def active_clins(self): - return [ - clin - for task_order in self.task_orders - for clin in task_order.clins - if clin.is_active - ] + def initial_clin_dict(self) -> Dict: + initial_clin = min( + ( + clin + for clin in self.clins + if (clin.is_active and clin.task_order.is_signed) + ), + key=lambda clin: clin.start_date, + default=None, + ) + if initial_clin: + return { + "initial_task_order_id": initial_clin.task_order.number, + "initial_clin_number": initial_clin.number, + "initial_clin_type": initial_clin.jedi_clin_number, + "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"), + } + else: + return {} @property def active_task_orders(self): @@ -170,25 +185,33 @@ class Portfolio( def portfolio_id(self): return self.id + @property + def domain_name(self): + """ + CSP domain name associated with portfolio. + If a domain name is not set, generate one. + """ + domain_name = re.sub("[^0-9a-zA-Z]+", "", self.name).lower() + "".join( + choices(ascii_lowercase + digits, k=4) + ) + if self.csp_data: + return self.csp_data.get("domain_name", domain_name) + else: + return domain_name + @property 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, + return { + "user_id": f"{self.owner.first_name[0]}{self.owner.last_name}".lower(), "password": "", - "domain_name": domain_name, - "first_name": ppoc.first_name, - "last_name": ppoc.last_name, + "domain_name": self.domain_name, + "first_name": self.owner.first_name, + "last_name": self.owner.last_name, "country_code": "US", - "password_recovery_email_address": ppoc.email, + "password_recovery_email_address": self.owner.email, "address": { # TODO: TBD if we're sourcing this from data or config "company_name": "", "address_line_1": "", @@ -198,27 +221,9 @@ class Portfolio( "postal_code": "", }, "billing_profile_display_name": "ATAT Billing Profile", + **self.initial_clin_dict, } - try: - initial_task_order: TaskOrder = self.task_orders[0] - initial_clin = initial_task_order.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 "".format( self.name, self.user_count, self.id