mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-11 17:38:02 -04:00
remove unneeded govuk dependencies
This commit is contained in:
@@ -1,177 +0,0 @@
|
||||
from collections import namedtuple
|
||||
from datetime import time, timedelta
|
||||
|
||||
import pytz
|
||||
from govuk_bank_holidays.bank_holidays import BankHolidays
|
||||
|
||||
from app.utils import utc_now
|
||||
from notifications_utils.countries.data import Postage
|
||||
from notifications_utils.timezones import utc_string_to_aware_gmt_datetime
|
||||
|
||||
LETTER_PROCESSING_DEADLINE = time(17, 30)
|
||||
CANCELLABLE_JOB_LETTER_STATUSES = [
|
||||
"created",
|
||||
"cancelled",
|
||||
"virus-scan-failed",
|
||||
"validation-failed",
|
||||
"technical-failure",
|
||||
"pending-virus-check",
|
||||
]
|
||||
|
||||
|
||||
non_working_days_dvla = BankHolidays(
|
||||
use_cached_holidays=True,
|
||||
weekend=(5, 6),
|
||||
)
|
||||
non_working_days_royal_mail = BankHolidays(
|
||||
use_cached_holidays=True,
|
||||
weekend=(6,), # Only Sunday (day 6 of the week) is a non-working day
|
||||
)
|
||||
|
||||
|
||||
def set_gmt_hour(day, hour):
|
||||
return (
|
||||
day.astimezone(pytz.timezone("Europe/London"))
|
||||
.replace(hour=hour, minute=0)
|
||||
.astimezone(pytz.utc)
|
||||
)
|
||||
|
||||
|
||||
def get_next_work_day(date, non_working_days):
|
||||
next_day = date + timedelta(days=1)
|
||||
if non_working_days.is_work_day(
|
||||
date=next_day.date(),
|
||||
division=BankHolidays.ENGLAND_AND_WALES,
|
||||
):
|
||||
return next_day
|
||||
return get_next_work_day(next_day, non_working_days)
|
||||
|
||||
|
||||
def get_next_dvla_working_day(date):
|
||||
"""
|
||||
Printing takes place monday to friday, excluding bank holidays
|
||||
"""
|
||||
return get_next_work_day(date, non_working_days=non_working_days_dvla)
|
||||
|
||||
|
||||
def get_next_royal_mail_working_day(date):
|
||||
"""
|
||||
Royal mail deliver letters on monday to saturday
|
||||
"""
|
||||
return get_next_work_day(date, non_working_days=non_working_days_royal_mail)
|
||||
|
||||
|
||||
def get_delivery_day(date, *, days_to_deliver):
|
||||
next_day = get_next_royal_mail_working_day(date)
|
||||
if days_to_deliver == 1:
|
||||
return next_day
|
||||
return get_delivery_day(next_day, days_to_deliver=(days_to_deliver - 1))
|
||||
|
||||
|
||||
def get_min_and_max_days_in_transit(postage):
|
||||
return {
|
||||
# first class post is printed earlier in the day, so will
|
||||
# actually transit on the printing day, and be delivered the next
|
||||
# day, so effectively spends no full days in transit
|
||||
"first": (0, 0),
|
||||
"second": (1, 2),
|
||||
Postage.EUROPE: (3, 5),
|
||||
Postage.REST_OF_WORLD: (5, 7),
|
||||
}[postage]
|
||||
|
||||
|
||||
def get_earliest_and_latest_delivery(print_day, postage):
|
||||
for days_to_transit in get_min_and_max_days_in_transit(postage):
|
||||
yield get_delivery_day(print_day, days_to_deliver=1 + days_to_transit)
|
||||
|
||||
|
||||
def get_letter_timings(upload_time, postage):
|
||||
LetterTimings = namedtuple(
|
||||
"LetterTimings", "printed_by, is_printed, earliest_delivery, latest_delivery"
|
||||
)
|
||||
|
||||
# shift anything after 5:30pm to the next day
|
||||
processing_day = utc_string_to_aware_gmt_datetime(upload_time) + timedelta(
|
||||
hours=6, minutes=30
|
||||
)
|
||||
print_day = get_next_dvla_working_day(processing_day)
|
||||
|
||||
earliest_delivery, latest_delivery = get_earliest_and_latest_delivery(
|
||||
print_day, postage
|
||||
)
|
||||
|
||||
# print deadline is 3pm BST
|
||||
printed_by = set_gmt_hour(print_day, hour=15)
|
||||
now = utc_now().replace(tzinfo=pytz.utc).astimezone(pytz.timezone("Europe/London"))
|
||||
|
||||
return LetterTimings(
|
||||
printed_by=printed_by,
|
||||
is_printed=(now > printed_by),
|
||||
earliest_delivery=set_gmt_hour(earliest_delivery, hour=16),
|
||||
latest_delivery=set_gmt_hour(latest_delivery, hour=16),
|
||||
)
|
||||
|
||||
|
||||
def letter_can_be_cancelled(notification_status, notification_created_at):
|
||||
"""
|
||||
If letter does not have status of created or pending-virus-check
|
||||
=> can't be cancelled (it has already been processed)
|
||||
|
||||
If it's after 5.30pm local time and the notification was created today before 5.30pm local time
|
||||
=> can't be cancelled (it will already be zipped up to be sent)
|
||||
"""
|
||||
if notification_status not in ("created", "pending-virus-check"):
|
||||
return False
|
||||
|
||||
if too_late_to_cancel_letter(notification_created_at):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def too_late_to_cancel_letter(notification_created_at):
|
||||
time_created_at = notification_created_at
|
||||
day_created_on = time_created_at.date()
|
||||
|
||||
current_time = utc_now()
|
||||
current_day = current_time.date()
|
||||
if (
|
||||
_after_letter_processing_deadline()
|
||||
and _notification_created_before_today_deadline(notification_created_at)
|
||||
):
|
||||
return True
|
||||
if (
|
||||
_notification_created_before_that_day_deadline(notification_created_at)
|
||||
and day_created_on < current_day
|
||||
):
|
||||
return True
|
||||
if (current_day - day_created_on).days > 1:
|
||||
return True
|
||||
|
||||
|
||||
def _after_letter_processing_deadline():
|
||||
current_utc_datetime = utc_now()
|
||||
bst_time = current_utc_datetime.time()
|
||||
|
||||
return bst_time >= LETTER_PROCESSING_DEADLINE
|
||||
|
||||
|
||||
def _notification_created_before_today_deadline(notification_created_at):
|
||||
current_bst_datetime = utc_now()
|
||||
todays_deadline = current_bst_datetime.replace(
|
||||
hour=LETTER_PROCESSING_DEADLINE.hour,
|
||||
minute=LETTER_PROCESSING_DEADLINE.minute,
|
||||
)
|
||||
|
||||
notification_created_at_in_bst = notification_created_at
|
||||
|
||||
return notification_created_at_in_bst <= todays_deadline
|
||||
|
||||
|
||||
def _notification_created_before_that_day_deadline(notification_created_at):
|
||||
notification_created_at_bst_datetime = notification_created_at
|
||||
created_at_day_deadline = notification_created_at_bst_datetime.replace(
|
||||
hour=LETTER_PROCESSING_DEADLINE.hour,
|
||||
minute=LETTER_PROCESSING_DEADLINE.minute,
|
||||
)
|
||||
|
||||
return notification_created_at_bst_datetime <= created_at_day_deadline
|
||||
16
poetry.lock
generated
16
poetry.lock
generated
@@ -1562,20 +1562,6 @@ files = [
|
||||
{file = "geojson-3.1.0.tar.gz", hash = "sha256:58a7fa40727ea058efc28b0e9ff0099eadf6d0965e04690830208d3ef571adac"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "govuk-bank-holidays"
|
||||
version = "0.14"
|
||||
description = "Tool to load UK bank holidays from GOV.UK"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "govuk-bank-holidays-0.14.tar.gz", hash = "sha256:ce85102423b72908957d25981f616494729686515d5d66c09a1d35a354ce20a6"},
|
||||
{file = "govuk_bank_holidays-0.14-py3-none-any.whl", hash = "sha256:da485c4a40c6c874c925916e492e3f20b807cffba7eed5f07fb69327aef6b10b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
requests = "*"
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.0.3"
|
||||
@@ -4739,4 +4725,4 @@ multidict = ">=4.0"
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12.2"
|
||||
content-hash = "89b99841ebd4bd735104048c8f8f0a35bfedb700a7a5648ecabe713816e25579"
|
||||
content-hash = "8fa243a938720113dd0999656594d7d3660a1e6202a4f26b9e9abe25d19b0cc0"
|
||||
|
||||
@@ -52,7 +52,6 @@ faker = "^26.0.0"
|
||||
async-timeout = "^4.0.3"
|
||||
bleach = "^6.1.0"
|
||||
geojson = "^3.1.0"
|
||||
govuk-bank-holidays = "^0.14"
|
||||
numpy = "^1.26.4"
|
||||
ordered-set = "^4.1.0"
|
||||
phonenumbers = "^8.13.42"
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
from freezegun import freeze_time
|
||||
|
||||
from app.utils import utc_now
|
||||
from notifications_utils.letter_timings import (
|
||||
get_letter_timings,
|
||||
letter_can_be_cancelled,
|
||||
)
|
||||
|
||||
|
||||
@freeze_time("2017-07-14 13:59:59") # Friday, before print deadline (3PM EST)
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"upload_time",
|
||||
"expected_print_time",
|
||||
"is_printed",
|
||||
"first_class",
|
||||
"expected_earliest",
|
||||
"expected_latest",
|
||||
),
|
||||
[
|
||||
# EST
|
||||
# ==================================================================
|
||||
# First thing Monday
|
||||
(
|
||||
"Monday 2017-07-10 00:00:01",
|
||||
"Tuesday 2017-07-11 15:00",
|
||||
True,
|
||||
"Wednesday 2017-07-12 16:00",
|
||||
"Thursday 2017-07-13 16:00",
|
||||
"Friday 2017-07-14 16:00",
|
||||
),
|
||||
# Monday at 17:29 EST (sent on monday)
|
||||
(
|
||||
"Monday 2017-07-10 16:29:59",
|
||||
"Tuesday 2017-07-11 15:00",
|
||||
True,
|
||||
"Wednesday 2017-07-12 16:00",
|
||||
"Thursday 2017-07-13 16:00",
|
||||
"Friday 2017-07-14 16:00",
|
||||
),
|
||||
# Monday at 17:30 EST (sent on tuesday)
|
||||
(
|
||||
"Monday 2017-07-10 16:30:01",
|
||||
"Wednesday 2017-07-12 15:00",
|
||||
True,
|
||||
"Thursday 2017-07-13 16:00",
|
||||
"Friday 2017-07-14 16:00",
|
||||
"Saturday 2017-07-15 16:00",
|
||||
),
|
||||
# Tuesday before 17:30 EST
|
||||
(
|
||||
"Tuesday 2017-07-11 12:00:00",
|
||||
"Wednesday 2017-07-12 15:00",
|
||||
True,
|
||||
"Thursday 2017-07-13 16:00",
|
||||
"Friday 2017-07-14 16:00",
|
||||
"Saturday 2017-07-15 16:00",
|
||||
),
|
||||
# Wednesday before 17:30 EST
|
||||
(
|
||||
"Wednesday 2017-07-12 12:00:00",
|
||||
"Thursday 2017-07-13 15:00",
|
||||
True,
|
||||
"Friday 2017-07-14 16:00",
|
||||
"Saturday 2017-07-15 16:00",
|
||||
"Monday 2017-07-17 16:00",
|
||||
),
|
||||
# Thursday before 17:30 EST
|
||||
(
|
||||
"Thursday 2017-07-13 12:00:00",
|
||||
"Friday 2017-07-14 15:00",
|
||||
False,
|
||||
"Saturday 2017-07-15 16:00",
|
||||
"Monday 2017-07-17 16:00",
|
||||
"Tuesday 2017-07-18 16:00",
|
||||
),
|
||||
# Friday anytime
|
||||
(
|
||||
"Friday 2017-07-14 00:00:00",
|
||||
"Monday 2017-07-17 15:00",
|
||||
False,
|
||||
"Tuesday 2017-07-18 16:00",
|
||||
"Wednesday 2017-07-19 16:00",
|
||||
"Thursday 2017-07-20 16:00",
|
||||
),
|
||||
(
|
||||
"Friday 2017-07-14 12:00:00",
|
||||
"Monday 2017-07-17 15:00",
|
||||
False,
|
||||
"Tuesday 2017-07-18 16:00",
|
||||
"Wednesday 2017-07-19 16:00",
|
||||
"Thursday 2017-07-20 16:00",
|
||||
),
|
||||
(
|
||||
"Friday 2017-07-14 22:00:00",
|
||||
"Monday 2017-07-17 15:00",
|
||||
False,
|
||||
"Tuesday 2017-07-18 16:00",
|
||||
"Wednesday 2017-07-19 16:00",
|
||||
"Thursday 2017-07-20 16:00",
|
||||
),
|
||||
# Saturday anytime
|
||||
(
|
||||
"Saturday 2017-07-14 12:00:00",
|
||||
"Monday 2017-07-17 15:00",
|
||||
False,
|
||||
"Tuesday 2017-07-18 16:00",
|
||||
"Wednesday 2017-07-19 16:00",
|
||||
"Thursday 2017-07-20 16:00",
|
||||
),
|
||||
# Sunday before 1730 EST
|
||||
(
|
||||
"Sunday 2017-07-15 15:59:59",
|
||||
"Monday 2017-07-17 15:00",
|
||||
False,
|
||||
"Tuesday 2017-07-18 16:00",
|
||||
"Wednesday 2017-07-19 16:00",
|
||||
"Thursday 2017-07-20 16:00",
|
||||
),
|
||||
# Sunday after 17:30 EST
|
||||
(
|
||||
"Sunday 2017-07-16 16:30:01",
|
||||
"Tuesday 2017-07-18 15:00",
|
||||
False,
|
||||
"Wednesday 2017-07-19 16:00",
|
||||
"Thursday 2017-07-20 16:00",
|
||||
"Friday 2017-07-21 16:00",
|
||||
),
|
||||
# GMT
|
||||
# ==================================================================
|
||||
# Monday at 17:29 GMT
|
||||
(
|
||||
"Monday 2017-01-02 17:29:59",
|
||||
"Tuesday 2017-01-03 15:00",
|
||||
True,
|
||||
"Wednesday 2017-01-04 16:00",
|
||||
"Thursday 2017-01-05 16:00",
|
||||
"Friday 2017-01-06 16:00",
|
||||
),
|
||||
# Monday at 17:00 GMT
|
||||
(
|
||||
"Monday 2017-01-02 17:30:01",
|
||||
"Wednesday 2017-01-04 15:00",
|
||||
True,
|
||||
"Thursday 2017-01-05 16:00",
|
||||
"Friday 2017-01-06 16:00",
|
||||
"Saturday 2017-01-07 16:00",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.skip(reason="Letters being developed later")
|
||||
def test_get_estimated_delivery_date_for_letter(
|
||||
upload_time,
|
||||
expected_print_time,
|
||||
is_printed,
|
||||
first_class,
|
||||
expected_earliest,
|
||||
expected_latest,
|
||||
):
|
||||
# remove the day string from the upload_time, which is purely informational
|
||||
|
||||
def format_dt(x):
|
||||
return x.astimezone(pytz.timezone("America/New_York")).strftime(
|
||||
"%A %Y-%m-%d %H:%M"
|
||||
)
|
||||
|
||||
upload_time = upload_time.split(" ", 1)[1]
|
||||
|
||||
timings = get_letter_timings(upload_time, postage="second")
|
||||
|
||||
assert format_dt(timings.printed_by) == expected_print_time
|
||||
assert timings.is_printed == is_printed
|
||||
assert format_dt(timings.earliest_delivery) == expected_earliest
|
||||
assert format_dt(timings.latest_delivery) == expected_latest
|
||||
|
||||
first_class_timings = get_letter_timings(upload_time, postage="first")
|
||||
|
||||
assert format_dt(first_class_timings.printed_by) == expected_print_time
|
||||
assert first_class_timings.is_printed == is_printed
|
||||
assert format_dt(first_class_timings.earliest_delivery) == first_class
|
||||
assert format_dt(first_class_timings.latest_delivery) == first_class
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", ["sending", "pending"])
|
||||
def test_letter_cannot_be_cancelled_if_letter_status_is_not_created_or_pending_virus_check(
|
||||
status,
|
||||
):
|
||||
notification_created_at = utc_now()
|
||||
|
||||
assert not letter_can_be_cancelled(status, notification_created_at)
|
||||
|
||||
|
||||
@freeze_time("2018-7-7 16:00:00")
|
||||
@pytest.mark.parametrize(
|
||||
"notification_created_at",
|
||||
[
|
||||
datetime(2018, 7, 6, 18, 0), # created yesterday after 1730
|
||||
datetime(2018, 7, 7, 12, 0), # created today
|
||||
],
|
||||
)
|
||||
@pytest.mark.skip(reason="Letters not part of release")
|
||||
def test_letter_can_be_cancelled_if_before_1730_and_letter_created_before_1730(
|
||||
notification_created_at,
|
||||
):
|
||||
notification_status = "pending-virus-check"
|
||||
|
||||
assert letter_can_be_cancelled(notification_status, notification_created_at)
|
||||
|
||||
|
||||
@freeze_time("2017-12-12 17:30:00")
|
||||
@pytest.mark.parametrize(
|
||||
"notification_created_at",
|
||||
[
|
||||
datetime(2017, 12, 12, 17, 0),
|
||||
datetime(2017, 12, 12, 17, 30),
|
||||
],
|
||||
)
|
||||
@pytest.mark.skip(reason="Letters not part of release")
|
||||
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)
|
||||
|
||||
|
||||
@freeze_time("2018-7-7 19:00:00")
|
||||
@pytest.mark.parametrize(
|
||||
"notification_created_at",
|
||||
[
|
||||
datetime(2018, 7, 6, 18, 0), # created yesterday after 1730
|
||||
datetime(2018, 7, 7, 12, 0), # created today before 1730
|
||||
],
|
||||
)
|
||||
@pytest.mark.skip(reason="Letters not part of release")
|
||||
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)
|
||||
|
||||
|
||||
@freeze_time("2018-7-7 15:00:00")
|
||||
@pytest.mark.skip(reason="Letters not part of release")
|
||||
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))
|
||||
|
||||
|
||||
@freeze_time("2018-7-7 15:00:00")
|
||||
@pytest.mark.skip(reason="Letters not part of release")
|
||||
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))
|
||||
|
||||
|
||||
@freeze_time("2018-7-7 19:00:00")
|
||||
@pytest.mark.parametrize(
|
||||
"notification_created_at",
|
||||
[
|
||||
datetime(2018, 7, 7, 18, 30),
|
||||
datetime(2018, 7, 7, 19, 0),
|
||||
],
|
||||
)
|
||||
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)
|
||||
Reference in New Issue
Block a user