commit
57ba7d5979
@ -90,3 +90,13 @@ class TaskOrders(BaseDomainClass):
|
|||||||
)
|
)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_clins_for_create_billing_instructions(cls):
|
||||||
|
return (
|
||||||
|
db.session.query(CLIN)
|
||||||
|
.filter(
|
||||||
|
CLIN.last_sent_at.is_(None), CLIN.start_date < pendulum.now(tz="UTC")
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
30
atst/jobs.py
30
atst/jobs.py
@ -10,6 +10,7 @@ from atst.domain.csp.cloud import CloudProviderInterface
|
|||||||
from atst.domain.csp.cloud.exceptions import GeneralCSPException
|
from atst.domain.csp.cloud.exceptions import GeneralCSPException
|
||||||
from atst.domain.csp.cloud.models import (
|
from atst.domain.csp.cloud.models import (
|
||||||
ApplicationCSPPayload,
|
ApplicationCSPPayload,
|
||||||
|
BillingInstructionCSPPayload,
|
||||||
EnvironmentCSPPayload,
|
EnvironmentCSPPayload,
|
||||||
UserCSPPayload,
|
UserCSPPayload,
|
||||||
UserRoleCSPPayload,
|
UserRoleCSPPayload,
|
||||||
@ -317,3 +318,32 @@ def send_task_order_files(self):
|
|||||||
db.session.add(task_order)
|
db.session.add(task_order)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task(bind=True)
|
||||||
|
def create_billing_instruction(self):
|
||||||
|
clins = TaskOrders.get_clins_for_create_billing_instructions()
|
||||||
|
for clin in clins:
|
||||||
|
portfolio = clin.task_order.portfolio
|
||||||
|
|
||||||
|
payload = BillingInstructionCSPPayload(
|
||||||
|
tenant_id=portfolio.csp_data.get("tenant_id"),
|
||||||
|
billing_account_name=portfolio.csp_data.get("billing_account_name"),
|
||||||
|
billing_profile_name=portfolio.csp_data.get("billing_profile_name"),
|
||||||
|
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_task_order_id=str(clin.task_order_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
app.csp.cloud.create_billing_instruction(payload)
|
||||||
|
except (AzureError) as err:
|
||||||
|
app.logger.exception(err)
|
||||||
|
continue
|
||||||
|
|
||||||
|
clin.last_sent_at = pendulum.now(tz="UTC")
|
||||||
|
db.session.add(clin)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
@ -66,12 +66,14 @@ class CLIN(Base, mixins.TimestampsMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def to_dictionary(self):
|
def to_dictionary(self):
|
||||||
return {
|
data = {
|
||||||
c.name: getattr(self, c.name)
|
c.name: getattr(self, c.name)
|
||||||
for c in self.__table__.columns
|
for c in self.__table__.columns
|
||||||
if c.name not in ["id"]
|
if c.name not in ["id"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return (
|
return (
|
||||||
|
@ -31,6 +31,10 @@ def update_celery(celery, app):
|
|||||||
"task": "atst.jobs.send_task_order_files",
|
"task": "atst.jobs.send_task_order_files",
|
||||||
"schedule": 60,
|
"schedule": 60,
|
||||||
},
|
},
|
||||||
|
"beat-create_billing_instruction": {
|
||||||
|
"task": "atst.jobs.create_billing_instruction",
|
||||||
|
"schedule": 60,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContextTask(celery.Task):
|
class ContextTask(celery.Task):
|
||||||
|
@ -9,6 +9,27 @@ from atst.models.task_order import TaskOrder, SORT_ORDERING, Status
|
|||||||
from tests.factories import TaskOrderFactory, CLINFactory, PortfolioFactory
|
from tests.factories import TaskOrderFactory, CLINFactory, PortfolioFactory
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def new_task_order():
|
||||||
|
return TaskOrderFactory.create(create_clins=[{}])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def updated_task_order():
|
||||||
|
return TaskOrderFactory.create(
|
||||||
|
create_clins=[{"last_sent_at": pendulum.date(2020, 2, 1)}],
|
||||||
|
pdf_last_sent_at=pendulum.date(2020, 1, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sent_task_order():
|
||||||
|
return TaskOrderFactory.create(
|
||||||
|
create_clins=[{"last_sent_at": pendulum.date(2020, 1, 1)}],
|
||||||
|
pdf_last_sent_at=pendulum.date(2020, 1, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_create_adds_clins():
|
def test_create_adds_clins():
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
clins = [
|
clins = [
|
||||||
@ -181,19 +202,18 @@ def test_allows_alphanumeric_number():
|
|||||||
assert TaskOrders.create(portfolio.id, number, [], None)
|
assert TaskOrders.create(portfolio.id, number, [], None)
|
||||||
|
|
||||||
|
|
||||||
def test_get_for_send_task_order_files():
|
def test_get_for_send_task_order_files(
|
||||||
new_to = TaskOrderFactory.create(create_clins=[{}])
|
new_task_order, updated_task_order, sent_task_order
|
||||||
updated_to = TaskOrderFactory.create(
|
):
|
||||||
create_clins=[{"last_sent_at": pendulum.datetime(2020, 2, 1)}],
|
|
||||||
pdf_last_sent_at=pendulum.datetime(2020, 1, 1),
|
|
||||||
)
|
|
||||||
sent_to = TaskOrderFactory.create(
|
|
||||||
create_clins=[{"last_sent_at": pendulum.datetime(2020, 1, 1)}],
|
|
||||||
pdf_last_sent_at=pendulum.datetime(2020, 1, 1),
|
|
||||||
)
|
|
||||||
|
|
||||||
updated_and_new_task_orders = TaskOrders.get_for_send_task_order_files()
|
updated_and_new_task_orders = TaskOrders.get_for_send_task_order_files()
|
||||||
assert len(updated_and_new_task_orders) == 2
|
assert len(updated_and_new_task_orders) == 2
|
||||||
assert sent_to not in updated_and_new_task_orders
|
assert sent_task_order not in updated_and_new_task_orders
|
||||||
assert updated_to in updated_and_new_task_orders
|
assert updated_task_order in updated_and_new_task_orders
|
||||||
assert new_to in updated_and_new_task_orders
|
assert new_task_order in updated_and_new_task_orders
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_clins_for_create_billing_instructions(new_task_order, sent_task_order):
|
||||||
|
new_clins = TaskOrders.get_clins_for_create_billing_instructions()
|
||||||
|
assert len(new_clins) == 1
|
||||||
|
assert new_task_order.clins[0] in new_clins
|
||||||
|
assert sent_task_order.clins[0] not in new_clins
|
||||||
|
@ -6,7 +6,8 @@ from smtplib import SMTPException
|
|||||||
from azure.core.exceptions import AzureError
|
from azure.core.exceptions import AzureError
|
||||||
|
|
||||||
from atst.domain.csp.cloud import MockCloudProvider
|
from atst.domain.csp.cloud import MockCloudProvider
|
||||||
from atst.domain.csp.cloud.models import UserRoleCSPResult
|
from atst.domain.csp.cloud.models import BillingInstructionCSPPayload, UserRoleCSPResult
|
||||||
|
from atst.domain.portfolios import Portfolios
|
||||||
from atst.models import ApplicationRoleStatus, Portfolio, FSMStates
|
from atst.models import ApplicationRoleStatus, Portfolio, FSMStates
|
||||||
|
|
||||||
from atst.jobs import (
|
from atst.jobs import (
|
||||||
@ -16,6 +17,7 @@ from atst.jobs import (
|
|||||||
dispatch_create_user,
|
dispatch_create_user,
|
||||||
dispatch_create_environment_role,
|
dispatch_create_environment_role,
|
||||||
dispatch_provision_portfolio,
|
dispatch_provision_portfolio,
|
||||||
|
create_billing_instruction,
|
||||||
create_environment,
|
create_environment,
|
||||||
do_create_user,
|
do_create_user,
|
||||||
do_provision_portfolio,
|
do_provision_portfolio,
|
||||||
@ -28,6 +30,7 @@ from atst.jobs import (
|
|||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
ApplicationFactory,
|
ApplicationFactory,
|
||||||
ApplicationRoleFactory,
|
ApplicationRoleFactory,
|
||||||
|
CLINFactory,
|
||||||
EnvironmentFactory,
|
EnvironmentFactory,
|
||||||
EnvironmentRoleFactory,
|
EnvironmentRoleFactory,
|
||||||
PortfolioFactory,
|
PortfolioFactory,
|
||||||
@ -489,3 +492,78 @@ class TestSendTaskOrderFiles:
|
|||||||
|
|
||||||
# Check that pdf_last_sent_at has not been updated
|
# Check that pdf_last_sent_at has not been updated
|
||||||
assert not task_order.pdf_last_sent_at
|
assert not task_order.pdf_last_sent_at
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateBillingInstructions:
|
||||||
|
@pytest.fixture
|
||||||
|
def unsent_clin(self):
|
||||||
|
start_date = pendulum.now().subtract(days=1)
|
||||||
|
portfolio = PortfolioFactory.create(
|
||||||
|
csp_data={
|
||||||
|
"tenant_id": str(uuid4()),
|
||||||
|
"billing_account_name": "fake",
|
||||||
|
"billing_profile_name": "fake",
|
||||||
|
},
|
||||||
|
task_orders=[{"create_clins": [{"start_date": start_date}]}],
|
||||||
|
)
|
||||||
|
return portfolio.task_orders[0].clins[0]
|
||||||
|
|
||||||
|
def test_update_clin_last_sent_at(self, session, unsent_clin):
|
||||||
|
assert not unsent_clin.last_sent_at
|
||||||
|
|
||||||
|
# The session needs to be nested to prevent detached SQLAlchemy instance
|
||||||
|
session.begin_nested()
|
||||||
|
create_billing_instruction()
|
||||||
|
|
||||||
|
# check that last_sent_at has been updated
|
||||||
|
assert unsent_clin.last_sent_at
|
||||||
|
session.rollback()
|
||||||
|
|
||||||
|
def test_failure(self, monkeypatch, session, unsent_clin):
|
||||||
|
def _create_billing_instruction(MockCloudProvider, object_name):
|
||||||
|
raise AzureError("something went wrong")
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"atst.domain.csp.cloud.MockCloudProvider.create_billing_instruction",
|
||||||
|
_create_billing_instruction,
|
||||||
|
)
|
||||||
|
|
||||||
|
# The session needs to be nested to prevent detached SQLAlchemy instance
|
||||||
|
session.begin_nested()
|
||||||
|
create_billing_instruction()
|
||||||
|
|
||||||
|
# check that last_sent_at has not been updated
|
||||||
|
assert not unsent_clin.last_sent_at
|
||||||
|
session.rollback()
|
||||||
|
|
||||||
|
def test_task_order_with_multiple_clins(self, session):
|
||||||
|
start_date = pendulum.now(tz="UTC").subtract(days=1)
|
||||||
|
portfolio = PortfolioFactory.create(
|
||||||
|
csp_data={
|
||||||
|
"tenant_id": str(uuid4()),
|
||||||
|
"billing_account_name": "fake",
|
||||||
|
"billing_profile_name": "fake",
|
||||||
|
},
|
||||||
|
task_orders=[
|
||||||
|
{
|
||||||
|
"create_clins": [
|
||||||
|
{"start_date": start_date, "last_sent_at": start_date}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
task_order = portfolio.task_orders[0]
|
||||||
|
sent_clin = task_order.clins[0]
|
||||||
|
|
||||||
|
# Add new CLIN to the Task Order
|
||||||
|
new_clin = CLINFactory.create(task_order=task_order)
|
||||||
|
assert not new_clin.last_sent_at
|
||||||
|
|
||||||
|
session.begin_nested()
|
||||||
|
create_billing_instruction()
|
||||||
|
session.add(sent_clin)
|
||||||
|
|
||||||
|
# check that last_sent_at has been update for the new clin only
|
||||||
|
assert new_clin.last_sent_at
|
||||||
|
assert sent_clin.last_sent_at != new_clin.last_sent_at
|
||||||
|
session.rollback()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user