mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-24 08:16:51 -04:00
Group uploaded letters by day of printing
Some teams have started uploading quite a lot of letters (in the hundreds per week). They’re also uploading CSVs of emails. This means the uploads page ends up quite jumbled. This is because: - there’s just a lot of items to scan through - conceptually it’s a bit odd to have batches of things displayed alongside individual things on the same page So instead we’re going to start grouping together uploaded letters. This will be by the date on which we ‘start’ printing them, or in other words the time at which they can no longer be cancelled. This feels like a natural grouping, and it matches what we know about people’s mental models of ‘batches’ and ‘runs’ when talking about printing. This grouping will be done in the API, so all this commit need to do is: - be ready to display this new type of pseudo-job - link to the page that displays all the uploaded letters for a given print day
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
) %}
|
||||
{% call row_heading() %}
|
||||
<div class="file-list">
|
||||
{% if item.upload_type == 'letter' %}
|
||||
{% if item.upload_type == 'letter_day' %}
|
||||
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.uploaded_letters', service_id=current_service.id, letter_print_day=item.created_at|format_date_numeric) }}">{{ item.original_file_name }}</a>
|
||||
{% elif item.upload_type == 'letter' %}
|
||||
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.view_notification', service_id=current_service.id, notification_id=item.id) }}">{{ item.original_file_name }}</a>
|
||||
{% elif item.upload_type == 'contact_list' %}
|
||||
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.contact_list', service_id=current_service.id, contact_list_id=item.id) }}">{{ item.original_file_name }}</a>
|
||||
@@ -37,6 +39,10 @@
|
||||
item.created_at|format_datetime_relative
|
||||
}}
|
||||
</span>
|
||||
{% elif item.upload_type == 'letter_day' %}
|
||||
<span class="file-list-hint-large">
|
||||
{{ item.letter_printing_statement }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="file-list-hint-large">
|
||||
Sent {{
|
||||
|
||||
22
app/utils.py
22
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 = {
|
||||
|
||||
Reference in New Issue
Block a user