commit
57ba7d5979
@ -90,3 +90,13 @@ class TaskOrders(BaseDomainClass):
|
||||
)
|
||||
.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.models import (
|
||||
ApplicationCSPPayload,
|
||||
BillingInstructionCSPPayload,
|
||||
EnvironmentCSPPayload,
|
||||
UserCSPPayload,
|
||||
UserRoleCSPPayload,
|
||||
@ -317,3 +318,32 @@ def send_task_order_files(self):
|
||||
db.session.add(task_order)
|
||||
|
||||
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):
|
||||
return {
|
||||
data = {
|
||||
c.name: getattr(self, c.name)
|
||||
for c in self.__table__.columns
|
||||
if c.name not in ["id"]
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return (
|
||||
|
@ -31,6 +31,10 @@ def update_celery(celery, app):
|
||||
"task": "atst.jobs.send_task_order_files",
|
||||
"schedule": 60,
|
||||
},
|
||||
"beat-create_billing_instruction": {
|
||||
"task": "atst.jobs.create_billing_instruction",
|
||||
"schedule": 60,
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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():
|
||||
portfolio = PortfolioFactory.create()
|
||||
clins = [
|
||||
@ -181,19 +202,18 @@ def test_allows_alphanumeric_number():
|
||||
assert TaskOrders.create(portfolio.id, number, [], None)
|
||||
|
||||
|
||||
def test_get_for_send_task_order_files():
|
||||
new_to = TaskOrderFactory.create(create_clins=[{}])
|
||||
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),
|
||||
)
|
||||
|
||||
def test_get_for_send_task_order_files(
|
||||
new_task_order, updated_task_order, sent_task_order
|
||||
):
|
||||
updated_and_new_task_orders = TaskOrders.get_for_send_task_order_files()
|
||||
assert len(updated_and_new_task_orders) == 2
|
||||
assert sent_to not in updated_and_new_task_orders
|
||||
assert updated_to in updated_and_new_task_orders
|
||||
assert new_to in updated_and_new_task_orders
|
||||
assert sent_task_order not in updated_and_new_task_orders
|
||||
assert updated_task_order 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 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.jobs import (
|
||||
@ -16,6 +17,7 @@ from atst.jobs import (
|
||||
dispatch_create_user,
|
||||
dispatch_create_environment_role,
|
||||
dispatch_provision_portfolio,
|
||||
create_billing_instruction,
|
||||
create_environment,
|
||||
do_create_user,
|
||||
do_provision_portfolio,
|
||||
@ -28,6 +30,7 @@ from atst.jobs import (
|
||||
from tests.factories import (
|
||||
ApplicationFactory,
|
||||
ApplicationRoleFactory,
|
||||
CLINFactory,
|
||||
EnvironmentFactory,
|
||||
EnvironmentRoleFactory,
|
||||
PortfolioFactory,
|
||||
@ -489,3 +492,78 @@ class TestSendTaskOrderFiles:
|
||||
|
||||
# Check that pdf_last_sent_at has not been updated
|
||||
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