diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index dca9c53c9..1560e4e56 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -32,6 +32,7 @@ from app.utils import ( get_page_from_request, get_time_left, parse_filter_args, + printing_today_or_tomorrow, set_status_filters, user_has_permissions, ) @@ -108,7 +109,8 @@ def view_job(service_id, job_id): just_sent=bool( request.args.get('just_sent') == 'yes' and template['template_type'] == 'letter' - ) + ), + printing_day=printing_today_or_tomorrow() ) diff --git a/app/main/views/notifications.py b/app/main/views/notifications.py index d9ff30463..dee53af1c 100644 --- a/app/main/views/notifications.py +++ b/app/main/views/notifications.py @@ -2,8 +2,9 @@ import base64 import io import os -from datetime import datetime +from datetime import datetime, timedelta +from dateutil import parser from flask import ( Response, abort, @@ -16,10 +17,15 @@ from flask import ( ) from flask_login import login_required from notifications_python_client.errors import APIError -from notifications_utils.letter_timings import get_letter_timings +from notifications_utils.letter_timings import ( + get_letter_timings, + letter_can_be_cancelled, +) from notifications_utils.pdf import pdf_page_count +from notifications_utils.timezones import utc_string_to_aware_gmt_datetime from app import ( + _format_datetime_short, current_service, format_date_numeric, job_api_client, @@ -36,6 +42,7 @@ from app.utils import ( get_help_argument, get_template, parse_filter_args, + printing_today_or_tomorrow, set_status_filters, user_has_permissions, ) @@ -76,6 +83,8 @@ def view_notification(service_id, notification_id): else: job = None + letter_print_day = get_letter_printing_statement(notification['status'], notification['created_at']) + return render_template( 'views/notifications/notification.html', finished=(notification['status'] in (DELIVERED_STATUSES + FAILURE_STATUSES)), @@ -102,10 +111,23 @@ def view_notification(service_id, notification_id): notification_id=notification['id'], postage=notification['postage'], can_receive_inbound=(current_service.has_permission('inbound_sms')), - is_precompiled_letter=notification['template']['is_precompiled_letter'] + is_precompiled_letter=notification['template']['is_precompiled_letter'], + letter_print_day=letter_print_day ) +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_preview_error_image(): path = os.path.join(os.path.dirname(__file__), "..", "..", "static", "images", "preview_error.png") with open(path, "rb") as file: diff --git a/app/templates/views/jobs/job.html b/app/templates/views/jobs/job.html index 9b8eccf32..e65d4bf97 100644 --- a/app/templates/views/jobs/job.html +++ b/app/templates/views/jobs/job.html @@ -14,7 +14,9 @@ {% if just_sent %} - {{ banner('We’ve started printing your letters', type='default', with_tick=True) }} + {{ banner('Your letter has been sent. Printing starts {} at 5.30pm.'.format(printing_day), + type='default', + with_tick=True) }} {% else %} {{ ajax_block(partials, updates_url, 'status', finished=finished) }} {% endif %} diff --git a/app/templates/views/notifications/notification.html b/app/templates/views/notifications/notification.html index d0e8c9613..78a618908 100644 --- a/app/templates/views/notifications/notification.html +++ b/app/templates/views/notifications/notification.html @@ -19,14 +19,16 @@ Provided as PDF, sent {% else %} {% if help %} - {{ template.name }} + ‘{{ template.name }}’ {% else %} - {{ template.name }} + ‘{{ template.name }}’ {% endif %} - sent + was sent {% endif %} {% if job and job.original_file_name != 'Report' %} - from + {% set destination = + {'letter': 'an address', 'email': 'an email address', 'sms': 'a phone number'} %} + to {{ destination[template.template_type] }} from {{ job.original_file_name }} {% elif created_by %} by {{ created_by.name }} @@ -45,6 +47,9 @@ (letter has content outside the printable area)

{% else %} +

+ {{ letter_print_day }} +

Postage: {{ postage }} class

diff --git a/app/utils.py b/app/utils.py index 7d8d886eb..d15ed9cc6 100644 --- a/app/utils.py +++ b/app/utils.py @@ -2,7 +2,7 @@ import csv import os import re import unicodedata -from datetime import datetime, timedelta, timezone +from datetime import datetime, time, timedelta, timezone from functools import wraps from io import StringIO from itertools import chain @@ -33,6 +33,7 @@ from notifications_utils.template import ( LetterPreviewTemplate, SMSPreviewTemplate, ) +from notifications_utils.timezones import convert_utc_to_bst from orderedset._orderedset import OrderedSet from werkzeug.datastructures import MultiDict @@ -643,3 +644,13 @@ def get_default_sms_sender(sms_senders): Field(x['sms_sender'], html='escape') for x in sms_senders if x['is_default'] ), "None")) + + +def printing_today_or_tomorrow(): + now_utc = datetime.utcnow() + now_bst = convert_utc_to_bst(now_utc) + + if now_bst.time() < time(17, 30): + return 'today' + else: + return 'tomorrow' diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index 0ed8ea3bd..01223abc8 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -343,8 +343,8 @@ def test_should_show_letter_job( ) -@freeze_time("2016-01-01 11:09:00.061258") -def test_should_show_letter_job_with_banner_after_sending( +@freeze_time("2016-01-01 11:09:00") +def test_should_show_letter_job_with_banner_after_sending_before_1730( client_request, mock_get_service_letter_template, mock_get_job, @@ -362,7 +362,30 @@ def test_should_show_letter_job_with_banner_after_sending( assert page.select('p.bottom-gutter') == [] assert normalize_spaces(page.select('.banner-default-with-tick')[0].text) == ( - 'We’ve started printing your letters' + 'Your letter has been sent. Printing starts today at 5.30pm.' + ) + + +@freeze_time("2016-01-01 18:09:00") +def test_should_show_letter_job_with_banner_after_sending_after_1730( + client_request, + mock_get_service_letter_template, + mock_get_job, + mock_get_notifications, + mock_get_service_data_retention_by_notification_type, + fake_uuid, +): + + page = client_request.get( + 'main.view_job', + service_id=SERVICE_ONE_ID, + job_id=fake_uuid, + just_sent='yes', + ) + + assert page.select('p.bottom-gutter') == [] + assert normalize_spaces(page.select('.banner-default-with-tick')[0].text) == ( + 'Your letter has been sent. Printing starts tomorrow at 5.30pm.' ) diff --git a/tests/app/main/views/test_notifications.py b/tests/app/main/views/test_notifications.py index 9efa3cf4e..48c234b27 100644 --- a/tests/app/main/views/test_notifications.py +++ b/tests/app/main/views/test_notifications.py @@ -1,4 +1,5 @@ import base64 +from datetime import datetime from functools import partial from unittest.mock import mock_open @@ -7,6 +8,7 @@ from flask import url_for from freezegun import freeze_time from notifications_python_client.errors import APIError +from app.main.views.notifications import get_letter_printing_statement from tests.conftest import ( SERVICE_ONE_ID, active_caseworking_user, @@ -127,7 +129,7 @@ def test_notification_page_doesnt_link_to_template_in_tour( ) assert normalize_spaces(page.select('main p:nth-of-type(1)')[0].text) == ( - 'sample template sent by Test User on 1 January at 1:01am' + "‘sample template’ was sent by Test User on 1 January at 1:01am" ) assert len(page.select('main p:nth-of-type(1) a')) == 0 @@ -141,7 +143,14 @@ def test_notification_page_shows_page_for_letter_notification( count_of_pages = 3 - mock_get_notification(mocker, fake_uuid, template_type='letter', postage='second') + notification = mock_get_notification( + mocker, + fake_uuid, + notification_status='created', + template_type='letter', + postage='second') + notification.created_at = datetime.utcnow() + mock_page_count = mocker.patch( 'app.main.views.notifications.get_page_count_for_letter', return_value=count_of_pages @@ -154,12 +163,15 @@ def test_notification_page_shows_page_for_letter_notification( ) assert normalize_spaces(page.select('main p:nth-of-type(1)')[0].text) == ( - 'sample template sent by Test User on 1 January at 1:01am' + "‘sample template’ was sent by Test User on 1 January at 1:01am" ) assert normalize_spaces(page.select('main p:nth-of-type(2)')[0].text) == ( - 'Postage: second class' + 'Printing starts today at 5.30pm' ) assert normalize_spaces(page.select('main p:nth-of-type(3)')[0].text) == ( + 'Postage: second class' + ) + assert normalize_spaces(page.select('main p:nth-of-type(4)')[0].text) == ( 'Estimated delivery date: 6 January' ) assert page.select('p.notification-status') == [] @@ -215,7 +227,7 @@ def test_notification_page_shows_cancelled_letter( ) assert normalize_spaces(page.select('main p')[0].text) == ( - 'sample template sent by Test User on 1 January at 1:01am' + "‘sample template’ was sent by Test User on 1 January at 1:01am" ) assert normalize_spaces(page.select('main p')[1].text) == ( expected_message @@ -225,13 +237,18 @@ def test_notification_page_shows_cancelled_letter( assert page.select_one('main img')['src'].endswith('.png?page=1') -@freeze_time("2016-01-01 01:01") +@freeze_time("2016-01-01 18:00") def test_notification_page_shows_page_for_first_class_letter_notification( client_request, mocker, fake_uuid, ): - mock_get_notification(mocker, fake_uuid, template_type='letter', postage='first') + mock_get_notification( + mocker, + fake_uuid, + notification_status='pending-virus-check', + template_type='letter', + postage='first') mocker.patch('app.main.views.notifications.get_page_count_for_letter', return_value=3) page = client_request.get( @@ -240,8 +257,9 @@ def test_notification_page_shows_page_for_first_class_letter_notification( notification_id=fake_uuid, ) - assert normalize_spaces(page.select('main p:nth-of-type(2)')[0].text) == 'Postage: first class' - assert normalize_spaces(page.select('main p:nth-of-type(3)')[0].text) == 'Estimated delivery date: 5 January' + assert normalize_spaces(page.select('main p:nth-of-type(2)')[0].text) == 'Printing starts tomorrow at 5.30pm' + assert normalize_spaces(page.select('main p:nth-of-type(3)')[0].text) == 'Postage: first class' + assert normalize_spaces(page.select('main p:nth-of-type(4)')[0].text) == 'Estimated delivery date: 5 January' @pytest.mark.parametrize('filetype', [ @@ -473,3 +491,39 @@ def test_should_show_image_of_precompiled_letter_notification( assert response.status_code == 200 assert response.get_data(as_text=True) == 'foo' assert mock_pdf_page_count.called_once() + + +@pytest.mark.parametrize('created_at, current_datetime', [ + ('2017-07-07T12:00:00+00:00', '2017-07-07 16:29:00'), # created today, summer + ('2017-12-12T12:00:00+00:00', '2017-12-12 17:29:00'), # created today, winter + ('2017-12-12T21:30:00+00:00', '2017-12-13 17:29:00'), # created after 5.30 yesterday + ('2017-03-25T17:30:00+00:00', '2017-03-26 16:29:00'), # over clock change period on 2017-03-26 +]) +def test_get_letter_printing_statement_when_letter_prints_today(created_at, current_datetime): + with freeze_time(current_datetime): + statement = get_letter_printing_statement('created', created_at) + + assert statement == 'Printing starts today at 5.30pm' + + +@pytest.mark.parametrize('created_at, current_datetime', [ + ('2017-07-07T16:31:00+00:00', '2017-07-07 22:59:00'), # created today, summer + ('2017-12-12T17:31:00+00:00', '2017-12-12 23:59:00'), # created today, winter +]) +def test_get_letter_printing_statement_when_letter_prints_tomorrow(created_at, current_datetime): + with freeze_time(current_datetime): + statement = get_letter_printing_statement('created', created_at) + + assert statement == 'Printing starts tomorrow at 5.30pm' + + +@pytest.mark.parametrize('created_at, print_day', [ + ('2017-07-06T16:30:00+00:00', '7 July'), + ('2017-12-01T00:00:00+00:00', '1 December'), + ('2017-03-26T12:00:00+00:00', '26 March'), +]) +@freeze_time('2017-07-07 12:00:00') +def test_get_letter_printing_statement_for_letter_that_has_been_sent(created_at, print_day): + statement = get_letter_printing_statement('delivered', created_at) + + assert statement == 'Printed on {}'.format(print_day) diff --git a/tests/app/test_utils.py b/tests/app/test_utils.py index 75fb651c9..3a59a864f 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -17,6 +17,7 @@ from app.utils import ( generate_notifications_csv, generate_previous_dict, get_logo_cdn_domain, + printing_today_or_tomorrow, ) from tests.conftest import fake_uuid @@ -452,3 +453,27 @@ def test_validate_email_domain_data(): def test_format_datetime_relative(time, human_readable_datetime): with freeze_time('2018-03-21 12:00'): assert format_datetime_relative(time) == human_readable_datetime + + +@pytest.mark.parametrize('utc_datetime', [ + '2018-08-01 23:00', + '2018-08-01 16:29', + '2018-11-01 00:00', + '2018-11-01 10:00', + '2018-11-01 17:29', +]) +def test_printing_today_or_tomorrow_returns_today(utc_datetime): + with freeze_time(utc_datetime): + assert printing_today_or_tomorrow() == 'today' + + +@pytest.mark.parametrize('datetime', [ + '2018-08-01 22:59', + '2018-08-01 16:30', + '2018-11-01 17:30', + '2018-11-01 21:00', + '2018-11-01 23:59', +]) +def test_printing_today_or_tomorrow_returns_tomorrow(datetime): + with freeze_time(datetime): + assert printing_today_or_tomorrow() == 'tomorrow'