diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 2283ef90994..f829e0eee2a 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -58,6 +58,11 @@ sub auto : Private { $c->stash->{staff_payments_allowed} = ''; $c->cobrand->call_hook( 'waste_check_staff_payment_permissions' ); + $c->cobrand->call_hook( 'waste_check_downtime' ) + if $c->action ne 'waste/echo/receive_echo_event_notification' + && $c->action ne 'waste/pay' + && $c->action ne 'waste/pay_complete'; + return 1; } diff --git a/perllib/FixMyStreet/Roles/Cobrand/Echo.pm b/perllib/FixMyStreet/Roles/Cobrand/Echo.pm index 76dfa556529..c47f70e8b8a 100644 --- a/perllib/FixMyStreet/Roles/Cobrand/Echo.pm +++ b/perllib/FixMyStreet/Roles/Cobrand/Echo.pm @@ -6,6 +6,7 @@ use DateTime; use DateTime::Format::Strptime; use List::Util qw(min); use Moo::Role; +use Path::Tiny; use POSIX qw(floor); # use Sort::Key::Natural qw(natkeysort_inplace); use FixMyStreet::DateRange; @@ -32,8 +33,43 @@ requires 'waste_bulky_missed_blocked_codes'; FixMyStreet::Roles::Cobrand::Echo - shared code between cobrands using an Echo backend +=head2 waste_check_downtime + +Echo has regular periods of scheduled downtime; we record these +in a file and check it here to show appropriate messaging. + =cut +sub waste_check_downtime { + my $self = shift; + my $c = $self->{c}; + + my $parser = DateTime::Format::W3CDTF->new; + my $now = DateTime->now->set_time_zone(FixMyStreet->local_time_zone); + + my $cfg = $self->feature('echo'); + my $downtime_csv = $cfg->{downtime_csv}; + my @lines = eval { path(FixMyStreet->path_to($downtime_csv))->lines({ chomp => 1 }) }; + foreach (@lines) { + my ($start, $end) = map { eval { $parser->parse_datetime($_) } } split /,/; + next unless $start && $end && $start < $end; + my $start_hour = $start->strftime('%l%P'); + my $end_hour = $end->strftime('%l%P'); + my $message = "Due to planned maintenance, this waste service will be unavailable from $start_hour until $end_hour."; + + $end->add( minutes => 15 ); # Add a buffer, sometimes they go past their end time + if ($now >= $start && $now < $end) { + $message .= " Please accept our apologies for the disruption and try again later."; + $c->detach('/page_error', [ $message, 503 ]); + } + + my $start_warning = $start->clone->subtract( hours => 2 ); + if ($now >= $start_warning && $now < $start) { + $c->stash->{site_message_upcoming_downtime} = $message; + } + } +} + sub bin_day_format { '%A, %-d~~~ %B' } sub bin_addresses_for_postcode { diff --git a/t/app/controller/waste_echo.t b/t/app/controller/waste_echo.t index e9acf7e6d2e..39462921f52 100644 --- a/t/app/controller/waste_echo.t +++ b/t/app/controller/waste_echo.t @@ -1,3 +1,4 @@ +use Test::MockTime qw(set_fixed_time); use FixMyStreet::TestMech; my $mech = FixMyStreet::TestMech->new; @@ -35,4 +36,56 @@ EOF } }; +subtest 'Echo downtime' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => 'merton', + COBRAND_FEATURES => { + waste => { merton => 1 }, + echo => { merton => { downtime_csv => 't/fixtures/echo-downtime.csv' } }, + }, + }, sub { + my $now = DateTime->new( year => 2024, month => 7, day => 23, time_zone => FixMyStreet->local_time_zone ); + subtest 'before a period' => sub { + $now->set_hour(16); + set_fixed_time($now->clone->set_time_zone('UTC')); + $mech->get_ok('/waste'); + $mech->content_lacks('Due to planned maintenance'); + $mech->content_lacks('Please accept our apologies'); + }; + subtest 'in warning period' => sub { + $now->set_hour(18); + set_fixed_time($now->clone->set_time_zone('UTC')); + $mech->get_ok('/waste'); + $mech->content_contains('Due to planned maintenance'); + $mech->content_contains('from 8pm until 11pm'); + $mech->content_lacks('Please accept our apologies'); + }; + subtest 'in closure period' => sub { + $now->set_hour(20); + set_fixed_time($now->clone->set_time_zone('UTC')); + $mech->get('/waste'); + is $mech->res->code, 503; + $mech->content_contains('Due to planned maintenance'); + $mech->content_contains('from 8pm until 11pm'); + $mech->content_contains('Please accept our apologies'); + }; + subtest 'end of closure period, in buffer' => sub { + $now->set_hour(23); + set_fixed_time($now->clone->set_time_zone('UTC')); + $mech->get('/waste'); + is $mech->res->code, 503; + $mech->content_contains('Due to planned maintenance'); + $mech->content_contains('from 8pm until 11pm'); + $mech->content_contains('Please accept our apologies'); + }; + subtest 'after closure period buffer' => sub { + $now->set_minute(15); + set_fixed_time($now->clone->set_time_zone('UTC')); + $mech->get_ok('/waste'); + $mech->content_lacks('Due to planned maintenance'); + $mech->content_lacks('Please accept our apologies'); + }; + }; +}; + done_testing(); diff --git a/t/fixtures/echo-downtime.csv b/t/fixtures/echo-downtime.csv new file mode 100644 index 00000000000..d99468baa36 --- /dev/null +++ b/t/fixtures/echo-downtime.csv @@ -0,0 +1,2 @@ +2024-07-16T20:00:00,2024-07-16T22:00:00 +2024-07-23T20:00:00,2024-07-23T23:00:00 diff --git a/templates/web/base/waste/header.html b/templates/web/base/waste/header.html index 0e90e0ba7ef..d38cafef7f5 100644 --- a/templates/web/base/waste/header.html +++ b/templates/web/base/waste/header.html @@ -23,3 +23,8 @@ [% site_message | html_para %] [% END %] +[% IF site_message_upcoming_downtime %] +
+ [% site_message_upcoming_downtime | html_para %] +
+[% END %]