diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 38527d17..3f997d4e 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -1,4 +1,4 @@ -import datetime +from datetime import datetime from sqlalchemy import or_ from atst.database import db @@ -41,7 +41,7 @@ class TaskOrders(BaseDomainClass): @classmethod def sign(cls, task_order, signer_dod_id): task_order.signer_dod_id = signer_dod_id - task_order.signed_at = datetime.datetime.now() + task_order.signed_at = datetime.now() db.session.add(task_order) db.session.commit() @@ -91,3 +91,9 @@ class TaskOrders(BaseDomainClass): ) .all() ) + + @classmethod + def update_pdf_last_sent_at(cls, task_order): + task_order.pdf_last_sent_at = datetime.now() + db.session.add(task_order) + db.session.commit() diff --git a/atst/jobs.py b/atst/jobs.py index f7ac2df9..69bc84be 100644 --- a/atst/jobs.py +++ b/atst/jobs.py @@ -14,8 +14,10 @@ from atst.domain.csp.cloud.models import ( from atst.domain.environments import Environments from atst.domain.portfolios import Portfolios from atst.models import JobFailure +from atst.domain.task_orders import TaskOrders from atst.models.utils import claim_for_update, claim_many_for_update from atst.queue import celery +from atst.utils.localization import translate class RecordFailure(celery.Task): @@ -144,6 +146,14 @@ def do_provision_portfolio(csp: CloudProviderInterface, portfolio_id=None): fsm.trigger_next_transition() +def do_send_with_attachment( + csp: CloudProviderInterface, recipients, subject, body, attachments +): + app.mailer.send( + recipients=recipients, subject=subject, body=body, attachments=attachments + ) + + @celery.task(bind=True, base=RecordFailure) def provision_portfolio(self, portfolio_id=None): do_work(do_provision_portfolio, self, app.csp.cloud, portfolio_id=portfolio_id) @@ -166,6 +176,32 @@ def create_environment(self, environment_id=None): do_work(do_create_environment, self, app.csp.cloud, environment_id=environment_id) +@celery.task(bind=True) +def send_with_attachment(self, recipients, subject, body, attachments): + do_work( + do_send_with_attachment, + self, + app.csp.cloud, + recipients=recipients, + subject=subject, + body=body, + attachments=attachments, + ) + + +@celery.task(bind=True) +def send_with_attachment(self, recipients, subject, body, attachments): + do_work( + do_send_with_attachment, + self, + app.csp.cloud, + recipients=recipients, + subject=subject, + body=body, + attachments=attachments, + ) + + @celery.task(bind=True) def dispatch_provision_portfolio(self): """ @@ -193,3 +229,32 @@ def dispatch_create_environment(self): pendulum.now() ): create_environment.delay(environment_id=environment_id) + + +@celery.task(bind=True) +def dispatch_create_atat_admin_user(self): + for environment_id in Environments.get_environments_pending_atat_user_creation( + pendulum.now() + ): + create_atat_admin_user.delay(environment_id=environment_id) + + +@celery.task(bind=True) +def dispatch_send_task_order_files(self): + task_orders = TaskOrders.get_for_send_task_order_files() + recipients = app.config.get("MICROSOFT_TASK_ORDER_EMAIL_ADDRESS") + + for task_order in task_orders: + subject = translate( + "email.task_order_sent.subject", {"to_number": task_order.number} + ) + body = translate("email.task_order_sent.body", {"to_number": task_order.number}) + + file = app.csp.files.download_task_order(task_order.pdf.object_name) + file["maintype"] = "application" + file["subtype"] = "pdf" + + send_with_attachment.delay( + recipients=recipients, subject=subject, body=body, attachments=[file] + ) + TaskOrders.update_pdf_last_sent_at(task_order) diff --git a/tests/test_jobs.py b/tests/test_jobs.py index a5549407..1e878ca5 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -13,6 +13,7 @@ from atst.jobs import ( dispatch_create_application, dispatch_create_user, dispatch_provision_portfolio, + dispatch_send_task_order_files, create_environment, do_create_user, do_provision_portfolio, @@ -20,15 +21,17 @@ from atst.jobs import ( do_create_application, ) from tests.factories import ( + ApplicationFactory, + ApplicationRoleFactory, EnvironmentFactory, EnvironmentRoleFactory, PortfolioFactory, PortfolioStateMachineFactory, - ApplicationFactory, - ApplicationRoleFactory, + TaskOrderFactory, UserFactory, ) from atst.models import CSPRole, EnvironmentRole, ApplicationRoleStatus, JobFailure +from atst.utils.localization import translate @pytest.fixture(autouse=True, scope="function") @@ -287,3 +290,52 @@ def test_provision_portfolio_create_tenant( # monkeypatch.setattr("atst.jobs.provision_portfolio", mock) # dispatch_provision_portfolio.run() # mock.delay.assert_called_once_with(portfolio_id=portfolio.id) + + +def test_dispatch_send_task_order_files( + csp, session, celery_app, celery_worker, monkeypatch, app +): + mock = Mock() + monkeypatch.setattr("atst.jobs.send_with_attachment", mock) + + def _download_task_order(MockFileService, object_name): + return {"name": object_name} + + monkeypatch.setattr( + "atst.domain.csp.files.MockFileService.download_task_order", + _download_task_order, + ) + + # Create 3 new Task Orders + for i in range(3): + TaskOrderFactory.create(create_clins=[{"number": "0001"}]) + + dispatch_send_task_order_files.run() + + # Check that send_with_attachment was called once for each task order + assert mock.delay.call_count == 3 + mock.reset_mock() + + # Create new TO + task_order = TaskOrderFactory.create(create_clins=[{"number": "0001"}]) + assert not task_order.pdf_last_sent_at + + dispatch_send_task_order_files.run() + + # Check that send_with_attachment was called with correct kwargs + mock.delay.assert_called_once_with( + recipients=app.config.get("MICROSOFT_TASK_ORDER_EMAIL_ADDRESS"), + subject=translate( + "email.task_order_sent.subject", {"to_number": task_order.number} + ), + body=translate("email.task_order_sent.body", {"to_number": task_order.number}), + attachments=[ + { + "name": task_order.pdf.object_name, + "maintype": "application", + "subtype": "pdf", + } + ], + ) + + assert task_order.pdf_last_sent_at