diff --git a/app/__init__.py b/app/__init__.py index 5f637cdd8..a3b5f5619 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -782,6 +782,7 @@ def add_template_filters(application): format_date, format_date_human, format_date_normal, + format_date_numeric, format_date_short, format_datetime_human, format_datetime_relative, diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index e95c3d61f..0b2d3d39b 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -64,7 +64,7 @@ def view_job(service_id, job_id): just_sent_message = 'Your {} been sent. Printing starts {} at 5:30pm.'.format( 'letter has' if job.notification_count == 1 else 'letters have', - printing_today_or_tomorrow() + printing_today_or_tomorrow(job.created_at) ) return render_template( diff --git a/app/main/views/uploads.py b/app/main/views/uploads.py index 510c8fed7..f17a839a4 100644 --- a/app/main/views/uploads.py +++ b/app/main/views/uploads.py @@ -3,6 +3,7 @@ import itertools import json import urllib import uuid +from datetime import datetime from io import BytesIO from zipfile import BadZipFile @@ -87,6 +88,7 @@ def uploads(service_id): jobs=listed_uploads, prev_page=prev_page, next_page=next_page, + now=datetime.utcnow().isoformat(), ) diff --git a/app/models/job.py b/app/models/job.py index 02d40159e..9c987a44c 100644 --- a/app/models/job.py +++ b/app/models/job.py @@ -1,5 +1,6 @@ -from datetime import datetime +from datetime import datetime, timedelta +import pytz from notifications_utils.letter_timings import ( CANCELLABLE_JOB_LETTER_STATUSES, get_letter_timings, @@ -15,7 +16,7 @@ from app.models import JSONModel, ModelList from app.notify_client.job_api_client import job_api_client from app.notify_client.notification_api_client import notification_api_client from app.notify_client.service_api_client import service_api_client -from app.utils import set_status_filters +from app.utils import get_letter_printing_statement, set_status_filters class Job(JSONModel): @@ -159,6 +160,20 @@ class Job(JSONModel): return True + @property + def letter_printing_statement(self): + if self.upload_type != 'letter_day': + raise TypeError() + return get_letter_printing_statement( + 'created', + # We have to make the time just before 5:30pm because a + # letter uploaded at 5:30pm will be printed the next day + ( + utc_string_to_aware_gmt_datetime(self.created_at) - timedelta(minutes=1) + ).astimezone(pytz.utc).isoformat(), + long_form=False, + ) + @cached_property def all_notifications(self): return self.get_notifications(set_status_filters({}))['notifications'] diff --git a/app/templates/views/dashboard/_jobs.html b/app/templates/views/dashboard/_jobs.html index f7d6ab271..cfda4345b 100644 --- a/app/templates/views/dashboard/_jobs.html +++ b/app/templates/views/dashboard/_jobs.html @@ -18,7 +18,9 @@ ) %} {% call row_heading() %}
- {% if item.upload_type == 'letter' %} + {% if item.upload_type == 'letter_day' %} + {{ item.original_file_name }} + {% elif item.upload_type == 'letter' %} {{ item.original_file_name }} {% elif item.upload_type == 'contact_list' %} {{ item.original_file_name }} @@ -37,6 +39,10 @@ item.created_at|format_datetime_relative }} + {% elif item.upload_type == 'letter_day' %} + + {{ item.letter_printing_statement }} + {% else %} Sent {{ diff --git a/app/utils.py b/app/utils.py index b4a92d5a8..6de6baf3b 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, time, timedelta, timezone +from datetime import datetime, timedelta, timezone from functools import wraps from io import BytesIO, StringIO from itertools import chain @@ -14,6 +14,7 @@ import ago import dateutil import pyexcel import pyexcel_xlsx +import pytz from dateutil import parser from flask import abort, current_app, redirect, request, session, url_for from flask_login import current_user, login_required @@ -33,6 +34,7 @@ from notifications_utils.template import ( SMSPreviewTemplate, ) from notifications_utils.timezones import ( + convert_bst_to_utc, convert_utc_to_bst, utc_string_to_aware_gmt_datetime, ) @@ -550,11 +552,13 @@ def get_default_sms_sender(sms_senders): ), "None")) -def printing_today_or_tomorrow(): - now_utc = datetime.utcnow() - now_bst = convert_utc_to_bst(now_utc) +def printing_today_or_tomorrow(created_at): + print_cutoff = convert_bst_to_utc( + convert_utc_to_bst(datetime.utcnow()).replace(hour=17, minute=30) + ).replace(tzinfo=pytz.utc) + created_at = utc_string_to_aware_gmt_datetime(created_at) - if now_bst.time() < time(17, 30): + if created_at < print_cutoff: return 'today' else: return 'tomorrow' @@ -569,10 +573,11 @@ def redact_mobile_number(mobile_number, spacing=""): return "".join(mobile_number_list) -def get_letter_printing_statement(status, created_at): +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): - return 'Printing starts {} at 5:30pm'.format(printing_today_or_tomorrow()) + 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(): @@ -581,8 +586,9 @@ def get_letter_printing_statement(status, created_at): return 'Printed yesterday at 5:30pm' printed_date = printed_datetime.strftime('%d %B').lstrip('0') + description = 'Printed on' if long_form else 'Printed' - return 'Printed on {} at 5:30pm'.format(printed_date) + return f'{description} {printed_date} at 5:30pm' LETTER_VALIDATION_MESSAGES = { diff --git a/tests/app/main/views/test_uploads.py b/tests/app/main/views/test_uploads.py index 528ef0f17..3a6985a2b 100644 --- a/tests/app/main/views/test_uploads.py +++ b/tests/app/main/views/test_uploads.py @@ -91,6 +91,7 @@ def test_get_upload_hub_with_no_uploads( assert not page.select('.file-list-filename') +@freeze_time('2017-10-10 10:10:10') def test_get_upload_hub_page( mocker, client_request, @@ -108,28 +109,41 @@ def test_get_upload_hub_page( uploads = page.select('tbody tr') + assert len(uploads) == 3 + assert normalize_spaces(uploads[0].text.strip()) == ( + 'Uploaded letters ' + 'Printing today at 5:30pm ' + '33 letters' + ) + assert uploads[0].select_one('a.file-list-filename-large')['href'] == url_for( + 'main.uploaded_letters', + service_id=SERVICE_ONE_ID, + letter_print_day='2017-10-10', + ) + + assert normalize_spaces(uploads[1].text.strip()) == ( 'some.csv ' 'Sent 1 January 2016 at 11:09am ' '0 sending 8 delivered 2 failed' ) - assert uploads[0].select_one('a.file-list-filename-large')['href'] == ( + assert uploads[1].select_one('a.file-list-filename-large')['href'] == ( '/services/{}/jobs/job_id_1'.format(SERVICE_ONE_ID) ) - assert normalize_spaces(uploads[1].text.strip()) == ( + assert normalize_spaces(uploads[2].text.strip()) == ( 'some.pdf ' 'Sent 1 January 2016 at 11:09am ' 'Firstname Lastname ' '123 Example Street' ) - assert normalize_spaces(str(uploads[1].select_one('.govuk-body'))) == ( + assert normalize_spaces(str(uploads[2].select_one('.govuk-body'))) == ( '

' 'Firstname Lastname
' '123 Example Street
' '

' ) - assert uploads[1].select_one('a.file-list-filename-large')['href'] == ( + assert uploads[2].select_one('a.file-list-filename-large')['href'] == ( '/services/{}/notification/letter_id_1'.format(SERVICE_ONE_ID) ) diff --git a/tests/app/test_utils.py b/tests/app/test_utils.py index 061282eff..bedb4eb97 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -353,27 +353,27 @@ def test_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', + '2018-08-01T23:00:00+00:00', + '2018-08-01T16:29:00+00:00', + '2018-11-01T00:00:00+00:00', + '2018-11-01T10:00:00+00:00', + '2018-11-01T17:29:00+00:00', ]) def test_printing_today_or_tomorrow_returns_today(utc_datetime): with freeze_time(utc_datetime): - assert printing_today_or_tomorrow() == 'today' + assert printing_today_or_tomorrow(utc_datetime) == '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', +@pytest.mark.parametrize('utc_datetime', [ + '2018-08-01T22:59:00+00:00', + '2018-08-01T16:30:00+00:00', + '2018-11-01T17:30:00+00:00', + '2018-11-01T21:00:00+00:00', + '2018-11-01T23:59:00+00:00', ]) -def test_printing_today_or_tomorrow_returns_tomorrow(datetime): - with freeze_time(datetime): - assert printing_today_or_tomorrow() == 'tomorrow' +def test_printing_today_or_tomorrow_returns_tomorrow(utc_datetime): + with freeze_time(utc_datetime): + assert printing_today_or_tomorrow(utc_datetime) == 'tomorrow' @pytest.mark.parametrize('created_at, current_datetime', [ diff --git a/tests/conftest.py b/tests/conftest.py index 48578599b..bcf826850 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1795,28 +1795,46 @@ def mock_get_jobs(mocker, api_user_active): @pytest.fixture(scope='function') def mock_get_uploads(mocker, api_user_active): def _get_uploads(service_id, limit_days=None, statuses=None, page=1): - uploads = [{'id': 'job_id_1', - 'original_file_name': 'some.csv', - 'notification_count': 10, - 'created_at': '2016-01-01 11:09:00.061258', - 'statistics': [{'count': 8, 'status': 'delivered'}, {'count': 2, 'status': 'temporary-failure'}], - 'upload_type': 'job', - 'template_type': 'sms', - 'recipient': None}, - {'id': 'letter_id_1', - 'original_file_name': 'some.pdf', - 'notification_count': 1, - 'created_at': '2016-01-01 11:09:00.061258', - 'statistics': [{'count': 1, 'status': 'delivered'}], - 'upload_type': 'letter', - 'template_type': None, - 'recipient': ( - 'Firstname Lastname\n' - '123 Example Street\n' - 'City of Town\n' - 'XM4 5QQ' - )} - ] + uploads = [ + { + 'id': None, + 'original_file_name': 'Uploaded letters', + 'recipient': None, + 'notification_count': 33, + 'template_type': 'letter', + 'created_at': '2017-10-10 16:30:00', + 'statistics': [], + 'upload_type': 'letter_day', + }, + { + 'id': 'job_id_1', + 'original_file_name': 'some.csv', + 'notification_count': 10, + 'created_at': '2016-01-01 11:09:00.061258', + 'statistics': [ + {'count': 8, 'status': 'delivered'}, + {'count': 2, 'status': 'temporary-failure'} + ], + 'upload_type': 'job', + 'template_type': 'sms', + 'recipient': None, + }, + { + 'id': 'letter_id_1', + 'original_file_name': 'some.pdf', + 'notification_count': 1, + 'created_at': '2016-01-01 11:09:00.061258', + 'statistics': [{'count': 1, 'status': 'delivered'}], + 'upload_type': 'letter', + 'template_type': None, + 'recipient': ( + 'Firstname Lastname\n' + '123 Example Street\n' + 'City of Town\n' + 'XM4 5QQ' + ), + }, + ] return { 'data': uploads, 'links': {