diff --git a/app/dao/jobs_dao.py b/app/dao/jobs_dao.py index e22c1c709..c40311d16 100644 --- a/app/dao/jobs_dao.py +++ b/app/dao/jobs_dao.py @@ -2,14 +2,17 @@ import uuid from datetime import datetime, timedelta from flask import current_app +from notifications_utils.letter_timings import letter_can_be_cancelled from notifications_utils.statsd_decorators import statsd from sqlalchemy import ( asc, desc, func, ) +from sqlalchemy.testing import not_in_ from app import db +from app.dao.dao_utils import transactional from app.utils import midnight_n_days_ago from app.models import ( Job, @@ -18,7 +21,11 @@ from app.models import ( LETTER_TYPE, Notification, Template, - ServiceDataRetention + ServiceDataRetention, + NOTIFICATION_SENDING, + NOTIFICATION_CREATED, + NOTIFICATION_CANCELLED, + JOB_STATUS_CANCELLED ) from app.variables import LETTER_TEST_API_FILENAME @@ -148,16 +155,37 @@ def dao_get_jobs_older_than_data_retention(notification_types): return jobs -def dao_get_all_letter_jobs(): - return db.session.query( - Job - ).join( - Job.template - ).filter( - Template.template_type == LETTER_TYPE, - # test letter jobs (or from research mode services) are created with a different filename, - # exclude them so we don't see them on the send to CSV - Job.original_file_name != LETTER_TEST_API_FILENAME - ).order_by( - desc(Job.created_at) - ).all() +@transactional +def dao_cancel_letter_job(service_id, job_id): + if can_cancel_letter_job(job_id, service_id): + number_of_notifications_cancelled = Notification.query.filter( + Notification.job_id == job_id + ).update({'status': NOTIFICATION_CANCELLED, + 'updated_at': datetime.utcnow(), + 'billable_units': 0}) + # calling this 2x - pass job into can_cancel_letter_job + job = dao_get_job_by_service_id_and_job_id(service_id, job_id) + job.job_status = JOB_STATUS_CANCELLED + dao_update_job(job) + return number_of_notifications_cancelled + + +def can_cancel_letter_job(job_id, service_id): + job = dao_get_job_by_service_id_and_job_id(service_id, job_id) + # assert is a letter job + # assert job status == finished??? + # Notifications are not in pending-virus-check + count_notifications_in_sending = Notification.query.filter( + Notification.job_id == job_id, + Notification.status.not_in_('created', 'pending-virus-check', 'cancelled') + ).count() + if count_notifications_in_sending > 0: + return False + if not letter_can_be_cancelled(NOTIFICATION_CREATED, job.created_at): + return False + else: + return True + + + + diff --git a/app/job/rest.py b/app/job/rest.py index 20f37efed..1d125c007 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -5,6 +5,7 @@ from flask import ( request, current_app ) +from notifications_utils.letter_timings import letter_can_be_cancelled from app.aws.s3 import get_job_metadata_from_s3 from app.dao.jobs_dao import ( @@ -13,7 +14,8 @@ from app.dao.jobs_dao import ( dao_get_job_by_service_id_and_job_id, dao_get_jobs_by_service_id, dao_get_future_scheduled_job_by_id_and_service_id, - dao_get_notification_outcomes_for_job) + dao_get_notification_outcomes_for_job, dao_cancel_letter_job +) from app.dao.fact_notification_status_dao import fetch_notification_statuses_for_job from app.dao.services_dao import dao_fetch_service_by_id from app.dao.templates_dao import dao_get_template_by_id diff --git a/tests/app/dao/test_jobs_dao.py b/tests/app/dao/test_jobs_dao.py index 524d17f8e..500bd90f0 100644 --- a/tests/app/dao/test_jobs_dao.py +++ b/tests/app/dao/test_jobs_dao.py @@ -14,6 +14,7 @@ from app.dao.jobs_dao import ( dao_get_future_scheduled_job_by_id_and_service_id, dao_get_notification_outcomes_for_job, dao_get_jobs_older_than_data_retention, + dao_cancel_letter_job ) from app.models import ( Job, @@ -308,3 +309,17 @@ def assert_job_stat(job, result, sent, delivered, failed): assert result.sent == sent assert result.delivered == delivered assert result.failed == failed + + +def test_dao_cancel_letter_job_does_not_allow_cancel_if_notification_in_sending(sample_job): + create_notification(template=sample_job.template, job=sample_job, status='sending') + create_notification(template=sample_job.template, job=sample_job, status='created') + assert not dao_cancel_letter_job(service_id=sample_job.service_id, job_id=sample_job.id) + + +def test_dao_cancel_letter_job_updates_notifications_and_job_to_cancelled(sample_job): + notification = create_notification(template=sample_job.template, job=sample_job, status='created') + assert dao_cancel_letter_job(service_id=sample_job.service_id, job_id=sample_job.id) == 1 + assert notification.status == 'cancelled' + assert sample_job.job_status == 'cancelled' +