def can_letter_job_be_cancelled(job): template = dao_get_template_by_id(job.template_id) if template.template_type != LETTER_TYPE: return ( False, "Only letter jobs can be cancelled through this endpoint. This is not a letter job.", ) notifications = Notification.query.filter(Notification.job_id == job.id).all() count_notifications = len(notifications) if job.job_status != JOB_STATUS_FINISHED or count_notifications != job.notification_count: return ( False, "We are still processing these letters, please try again in a minute.", ) count_cancellable_notifications = len([n for n in notifications if n.status in CANCELLABLE_JOB_LETTER_STATUSES]) if count_cancellable_notifications != job.notification_count or not letter_can_be_cancelled( NOTIFICATION_CREATED, job.created_at ): return ( False, "It’s too late to cancel sending, these letters have already been sent.", ) return True, None
def cancel_notification_for_service(service_id, notification_id): notification = notifications_dao.get_notification_by_id( notification_id, service_id) if not notification: raise InvalidRequest('Notification not found', status_code=404) elif notification.notification_type != LETTER_TYPE: raise InvalidRequest( 'Notification cannot be cancelled - only letters can be cancelled', status_code=400) elif not letter_can_be_cancelled(notification.status, notification.created_at): print_day = letter_print_day(notification.created_at) raise InvalidRequest( "It’s too late to cancel this letter. Printing started {} at 5.30pm" .format(print_day), status_code=400) updated_notification = notifications_dao.update_notification_status_by_id( notification_id, NOTIFICATION_CANCELLED, ) return jsonify( notification_with_template_schema.dump(updated_notification).data), 200
def get_letter_printing_statement(status, created_at): created_at_dt = parser.parse(created_at).replace(tzinfo=None) if letter_can_be_cancelled(status, created_at_dt): return 'Printing starts {} at 5:30pm'.format( printing_today_or_tomorrow()) else: printed_datetime = utc_string_to_aware_gmt_datetime( created_at) + timedelta(hours=6, minutes=30) printed_date = _format_datetime_short(printed_datetime) return 'Printed on {}'.format(printed_date)
def get_letter_printing_statement(status, created_at): created_at_dt = parser.parse(created_at).replace(tzinfo=None) if letter_can_be_cancelled(status, created_at_dt): return 'Printing starts {} at 5:30pm'.format(printing_today_or_tomorrow()) else: printed_datetime = utc_string_to_aware_gmt_datetime(created_at) + timedelta(hours=6, minutes=30) if printed_datetime.date() == datetime.now().date(): return 'Printed today at 5:30pm' elif printed_datetime.date() == datetime.now().date() - timedelta(days=1): return 'Printed yesterday at 5:30pm' printed_date = printed_datetime.strftime('%d %B').lstrip('0') return 'Printed on {} at 5:30pm'.format(printed_date)
def letter_job_can_be_cancelled(self): if self.template['template_type'] != 'letter': return False if any(self.uncancellable_notifications): return False if not letter_can_be_cancelled( 'created', utc_string_to_aware_gmt_datetime(self.created_at).replace(tzinfo=None) ): return False return True
def get_letter_printing_statement(status, created_at, long_form=True): created_at_dt = parser.parse(created_at).replace(tzinfo=None) if letter_can_be_cancelled(status, created_at_dt): decription = 'Printing starts' if long_form else 'Printing' return f'{decription} {printing_today_or_tomorrow(created_at)} at 5:30pm' else: printed_datetime = utc_string_to_aware_gmt_datetime(created_at) + timedelta(hours=6, minutes=30) if printed_datetime.date() == datetime.now().date(): return 'Printed today at 5:30pm' elif printed_datetime.date() == datetime.now().date() - timedelta(days=1): return 'Printed yesterday at 5:30pm' printed_date = printed_datetime.strftime('%d %B').lstrip('0') description = 'Printed on' if long_form else 'Printed' return f'{description} {printed_date} at 5:30pm'
def test_letter_cannot_be_cancelled_if_1730_exactly_and_letter_created_at_or_before_1730( notification_created_at): notification_status = 'pending-virus-check' assert not letter_can_be_cancelled(notification_status, notification_created_at)
def get_job_partials(job, template): filter_args = parse_filter_args(request.args) filter_args['status'] = set_status_filters(filter_args) notifications = notification_api_client.get_notifications_for_service( job['service'], job['id'], status=filter_args['status']) if template['template_type'] == 'letter': # there might be no notifications if the job has only just been created and the tasks haven't run yet if notifications['notifications']: postage = notifications['notifications'][0]['postage'] else: postage = template['postage'] counts = render_template( 'partials/jobs/count-letters.html', total=job.get('notification_count', 0), delivery_estimate=get_letter_timings( job['created_at'], postage=postage).earliest_delivery, ) else: counts = render_template('partials/count.html', counts=_get_job_counts(job), status=filter_args['status']) service_data_retention_days = current_service.get_days_of_retention( template['template_type']) can_letter_job_be_cancelled = False if template["template_type"] == "letter": not_cancellable = [ n for n in notifications["notifications"] if n["status"] not in CANCELLABLE_JOB_LETTER_STATUSES ] job_created = job["created_at"][:-6] if not letter_can_be_cancelled( "created", datetime.strptime( job_created, '%Y-%m-%dT%H:%M:%S.%f')) or len(not_cancellable) != 0: can_letter_job_be_cancelled = False else: can_letter_job_be_cancelled = True return { 'counts': counts, 'notifications': render_template( 'partials/jobs/notifications.html', notifications=list( add_preview_of_content_to_notifications( notifications['notifications'])), more_than_one_page=bool( notifications.get('links', {}).get('next')), percentage_complete=(job['notifications_requested'] / job['notification_count'] * 100), download_link=url_for('.view_job_csv', service_id=current_service.id, job_id=job['id'], status=request.args.get('status')), time_left=get_time_left( job['created_at'], service_data_retention_days=service_data_retention_days), job=job, template=template, template_version=job['template_version'], ), 'status': render_template('partials/jobs/status.html', job=job, template_type=template["template_type"], letter_print_day=get_letter_printing_statement( "created", job["created_at"])), 'can_letter_job_be_cancelled': can_letter_job_be_cancelled, }
def view_notification(service_id, notification_id): notification = notification_api_client.get_notification( service_id, str(notification_id)) notification['template'].update( {'reply_to_text': notification['reply_to_text']}) personalisation = get_all_personalisation_from_notification(notification) if notification['template']['is_precompiled_letter']: try: file_contents = view_letter_notification_as_preview( service_id, notification_id, "pdf") page_count = pdf_page_count(io.BytesIO(file_contents)) except PdfReadError: return render_template( 'views/notifications/invalid_precompiled_letter.html', created_at=notification['created_at']) else: page_count = get_page_count_for_letter(notification['template'], values=personalisation) if notification.get('postage'): notification['template']['postage'] = notification['postage'] template = get_template( notification['template'], current_service, letter_preview_url=url_for( '.view_letter_notification_as_preview', service_id=service_id, notification_id=notification_id, filetype='png', ), page_count=page_count, show_recipient=True, redact_missing_personalisation=True, ) template.values = personalisation if notification['job']: job = job_api_client.get_job(service_id, notification['job']['id'])['data'] else: job = None letter_print_day = get_letter_printing_statement( notification['status'], notification['created_at']) notification_created = parser.parse( notification['created_at']).replace(tzinfo=None) show_cancel_button = notification['notification_type'] == 'letter' and \ letter_can_be_cancelled(notification['status'], notification_created) if get_help_argument() or request.args.get('help') == '0': # help=0 is set when you’ve just sent a notification. We # only want to show the back link when you’ve navigated to a # notification, not when you’ve just sent it. back_link = None elif request.args.get('from_job'): back_link = url_for( 'main.view_job', service_id=current_service.id, job_id=request.args.get('from_job'), ) else: back_link = url_for( 'main.view_notifications', service_id=current_service.id, message_type=template.template_type, status='sending,delivered,failed', ) return render_template( 'views/notifications/notification.html', finished=(notification['status'] in (DELIVERED_STATUSES + FAILURE_STATUSES)), notification_status=notification['status'], uploaded_file_name='Report', template=template, job=job, updates_url=url_for(".view_notification_updates", service_id=service_id, notification_id=notification['id'], status=request.args.get('status'), help=get_help_argument()), partials=get_single_notification_partials(notification), created_by=notification.get('created_by'), created_at=notification['created_at'], updated_at=notification['updated_at'], help=get_help_argument(), estimated_letter_delivery_date=get_letter_timings( notification['created_at'], postage=notification['postage']).earliest_delivery, notification_id=notification['id'], postage=notification['postage'], can_receive_inbound=(current_service.has_permission('inbound_sms')), is_precompiled_letter=notification['template'] ['is_precompiled_letter'], letter_print_day=letter_print_day, show_cancel_button=show_cancel_button, sent_with_test_key=(notification.get('key_type') == KEY_TYPE_TEST), back_link=back_link, )
def get_job_partials(job, template): filter_args = parse_filter_args(request.args) filter_args["status"] = set_status_filters(filter_args) notifications = notification_api_client.get_notifications_for_service( job["service"], job["id"], status=filter_args["status"]) if template["template_type"] == "letter": # there might be no notifications if the job has only just been created and the tasks haven't run yet if notifications["notifications"]: postage = notifications["notifications"][0]["postage"] else: postage = template["postage"] counts = render_template( "partials/jobs/count-letters.html", total=job.get("notification_count", 0), delivery_estimate=get_letter_timings( job["created_at"], postage=postage).earliest_delivery, ) else: counts = render_template( "partials/count.html", counts=_get_job_counts(job), status=filter_args["status"], ) service_data_retention_days = current_service.get_days_of_retention( template["template_type"]) can_letter_job_be_cancelled = False if template["template_type"] == "letter": not_cancellable = [ n for n in notifications["notifications"] if n["status"] not in CANCELLABLE_JOB_LETTER_STATUSES ] job_created = job["created_at"][:-6] if (not letter_can_be_cancelled( "created", datetime.strptime(job_created, "%Y-%m-%dT%H:%M:%S.%f")) or len(not_cancellable) != 0): can_letter_job_be_cancelled = False else: can_letter_job_be_cancelled = True return { "counts": counts, "notifications": render_template( "partials/jobs/notifications.html", notifications=list( add_preview_of_content_to_notifications( notifications["notifications"])), more_than_one_page=bool( notifications.get("links", {}).get("next")), percentage_complete=(job["notifications_requested"] / job["notification_count"] * 100), download_link=url_for( ".view_job_csv", service_id=current_service.id, job_id=job["id"], status=request.args.get("status"), ), available_until_date=get_available_until_date( job["created_at"], service_data_retention_days=service_data_retention_days, ), job=job, template=template, template_version=job["template_version"], ), "status": render_template( "partials/jobs/status.html", job=job, template=template, template_type=template["template_type"], template_version=job["template_version"], letter_print_day=get_letter_printing_statement( "created", job["created_at"]), ), "can_letter_job_be_cancelled": can_letter_job_be_cancelled, }
def test_letter_can_be_cancelled_if_after_1730_and_letter_created_at_1730_today_or_later( notification_created_at): notification_status = 'created' assert letter_can_be_cancelled(notification_status, notification_created_at)
def test_letter_cannot_be_cancelled_if_after_1730_and_letter_created_before_1730( notification_created_at): notification_status = 'created' assert not letter_can_be_cancelled(notification_status, notification_created_at)
def view_notification(service_id, notification_id): notification = notification_api_client.get_notification(service_id, str(notification_id)) notification["template"].update({"reply_to_text": notification["reply_to_text"]}) personalisation = get_all_personalisation_from_notification(notification) if notification["template"]["is_precompiled_letter"]: try: file_contents = view_letter_notification_as_preview(service_id, notification_id, "pdf") page_count = pdf_page_count(io.BytesIO(file_contents)) except PdfReadError: return render_template( "views/notifications/invalid_precompiled_letter.html", created_at=notification["created_at"], ) else: page_count = get_page_count_for_letter(notification["template"], values=personalisation) if notification.get("postage"): notification["template"]["postage"] = notification["postage"] template = get_template( notification["template"], current_service, letter_preview_url=url_for( ".view_letter_notification_as_preview", service_id=service_id, notification_id=notification_id, filetype="png", ), page_count=page_count, show_recipient=True, redact_missing_personalisation=True, ) template.values = personalisation if notification["job"]: job = job_api_client.get_job(service_id, notification["job"]["id"])["data"] else: job = None letter_print_day = get_letter_printing_statement(notification["status"], notification["created_at"]) notification_created = parser.parse(notification["created_at"]).replace(tzinfo=None) show_cancel_button = notification["notification_type"] == "letter" and letter_can_be_cancelled( notification["status"], notification_created ) if get_help_argument() or request.args.get("help") == "0": # help=0 is set when you’ve just sent a notification. We # only want to show the back link when you’ve navigated to a # notification, not when you’ve just sent it. back_link = None elif request.args.get("from_job"): back_link = url_for( "main.view_job", service_id=current_service.id, job_id=request.args.get("from_job"), ) else: back_link = url_for( "main.view_notifications", service_id=current_service.id, message_type=template.template_type, status="sending,delivered,failed", ) return render_template( "views/notifications/notification.html", finished=(notification["status"] in (DELIVERED_STATUSES + FAILURE_STATUSES)), notification_status=notification["status"], uploaded_file_name="Report", template=template, job=job, updates_url=url_for( ".view_notification_updates", service_id=service_id, notification_id=notification["id"], status=request.args.get("status"), help=get_help_argument(), ), partials=get_single_notification_partials(notification), created_by=notification.get("created_by"), created_at=notification["created_at"], updated_at=notification["updated_at"], help=get_help_argument(), estimated_letter_delivery_date=get_letter_timings( notification["created_at"], postage=notification["postage"] ).earliest_delivery, notification_id=notification["id"], postage=notification["postage"], can_receive_inbound=(current_service.has_permission("inbound_sms")), is_precompiled_letter=notification["template"]["is_precompiled_letter"], letter_print_day=letter_print_day, show_cancel_button=show_cancel_button, sent_with_test_key=(notification.get("key_type") == KEY_TYPE_TEST), back_link=back_link, just_sent=request.args.get("just_sent"), attachments=get_attachments(notification, "attach").values(), )
def test_letter_cannot_be_cancelled_if_letter_status_is_not_created_or_pending_virus_check( status): notification_created_at = datetime.utcnow() assert not letter_can_be_cancelled(status, notification_created_at)
def test_letter_can_be_cancelled_always_compares_in_bst( notification_created_at): assert letter_can_be_cancelled('created', notification_created_at)
def test_letter_cannot_be_cancelled_if_before_1730_and_letter_created_after_1730_two_days_ago( ): notification_status = 'created' assert not letter_can_be_cancelled(notification_status, datetime(2018, 7, 5, 19, 0))
def test_letter_cannot_be_cancelled_if_before_1730_and_letter_created_before_1730_yesterday( ): notification_status = 'created' assert not letter_can_be_cancelled(notification_status, datetime(2018, 7, 6, 14, 0))