diff --git a/perllib/FixMyStreet/App/Controller/Admin/Templates.pm b/perllib/FixMyStreet/App/Controller/Admin/Templates.pm index 43a657ad546..151febf2594 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/Templates.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/Templates.pm @@ -126,6 +126,15 @@ sub edit : Path : Args(2) { } $template->auto_response( $c->get_param('auto_response') && ( $template->state || $template->external_status_code ) ? 1 : 0 ); + + if ($body_cobrand) { + my $errors = $body_cobrand->call_hook(validate_response_template => $template); + if ($errors && ref $errors eq 'HASH') { + $c->stash->{errors} ||= {}; + %{ $c->stash->{errors} } = (%{ $c->stash->{errors} }, %$errors); + } + } + if ($template->auto_response) { my @check_contact_ids = @new_contact_ids; # If the new template has not specific categories (i.e. it diff --git a/perllib/FixMyStreet/Cobrand/Dumfries.pm b/perllib/FixMyStreet/Cobrand/Dumfries.pm index 928454ff887..42db37a79c6 100644 --- a/perllib/FixMyStreet/Cobrand/Dumfries.pm +++ b/perllib/FixMyStreet/Cobrand/Dumfries.pm @@ -323,6 +323,57 @@ sub state_groups_inspect { ] } +=head2 response_template_override + +When an update moves a report from planned to confirmed, use the first +matching response template whose title starts with "Cancelled Job". + +=cut + +sub response_template_override { + my ($self, $problem, $state, $old_state) = @_; + + return unless $old_state eq 'planned' && $state eq 'confirmed'; + + return $problem->response_templates->search({ + 'me.title' => { -like => 'Cancelled Job%' }, + }, { + order_by => 'me.title', + })->first; +} + + +=head2 validate_response_template + + +Performs some custom validation for templates whose title start with 'Cancelled Job': +can't be auto-response; can't be for an external status code; must be for 'confirmed' state. +=cut + +sub validate_response_template { + my ($self, $template) = @_; + + return unless ($template->title || '') =~ /^Cancelled Job/; + + my %errors; + + if (($template->state || '') ne 'confirmed') { + $errors{state} = + 'Templates whose title starts with "Cancelled Job" are reserved for cancelled job handling and must use the Open state.'; + } + + if ($template->external_status_code) { + $errors{external_status_code} = + 'Templates whose title starts with "Cancelled Job" are reserved for cancelled job handling and cannot use an external status code.'; + } + + if ($template->auto_response) { + $errors{auto_response} = + 'Templates whose title starts with "Cancelled Job" are reserved for the cancelled job feature and cannot be set as auto-response.'; + } + + return %errors ? \%errors : undef; +} =head2 validate_response_template_external_status_code diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm index 0f742d2e13b..d5c2e8cda45 100644 --- a/perllib/FixMyStreet/DB/Result/Problem.pm +++ b/perllib/FixMyStreet/DB/Result/Problem.pm @@ -928,6 +928,7 @@ sub response_templates { sub response_template_for { my ($self, $body, $state, $old_state, $ext_code, $old_ext_code) = @_; + my $cobrand = $body->get_cobrand_handler; # Response templates are only triggered if the state/external status has changed. # And treat any fixed state as fixed. @@ -943,8 +944,6 @@ sub response_template_for { push @$state_params, { 'me.state' => $state, 'me.external_status_code' => ["", undef] }; } if ($ext_code_changed) { - my $cobrand = $body->get_cobrand_handler; - # Allow cobrands to use regex matching for wildcard patterns in templates if (my $regex_match = $cobrand && $cobrand->call_hook( response_template_external_status_code_regex_match => $ext_code @@ -972,6 +971,14 @@ sub response_template_for { auto_response => 1, -or => $state_params, }, $order )->first; + + if ($cobrand) { + $template = $cobrand->call_hook( + response_template_override => $self, + $state, + $old_state, + ) || $template; + } } return $template; } diff --git a/t/cobrand/dumfries.t b/t/cobrand/dumfries.t index 77906281aa5..fc44d373693 100644 --- a/t/cobrand/dumfries.t +++ b/t/cobrand/dumfries.t @@ -8,6 +8,8 @@ use Test::More; use Test::MockModule; use Test::MockTime qw(:all); use LWP::Protocol::PSGI; +use Open311; +use Open311::GetUpdates; use t::Mock::MyGovScotOIDC; my $mech = FixMyStreet::TestMech->new; @@ -759,6 +761,172 @@ subtest 'admin template external_status_code validation' => sub { }; }; +subtest 'planned->confirmed uses Cancelled Job template override' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => ['dumfries'], + }, sub { + my $test_problem = FixMyStreet::DB->resultset('Problem')->create({ + postcode => 'DG1 1AA', + bodies_str => $body->id, + areas => ',2656,', + category => 'Potholes', + title => 'Cancelled Job override test problem', + detail => 'Test detail', + used_map => 1, + name => 'Reporter', + anonymous => 0, + state => 'planned', + confirmed => DateTime->now->subtract(days => 1), + lastupdate => DateTime->now->subtract(days => 1), + latitude => 55.0706, + longitude => -3.9568, + user_id => $reporter->id, + cobrand => 'dumfries', + external_id => 'dumfries-open311-cancelled-job-1', + }); + + my $normal_template = FixMyStreet::DB->resultset('ResponseTemplate')->create({ + body_id => $body->id, + title => 'Normal Confirmed Auto Template', + text => 'Normal confirmed template text', + auto_response => 1, + state => 'confirmed', + external_status_code => '', + }); + + my $cancelled_job_template = FixMyStreet::DB->resultset('ResponseTemplate')->create({ + body_id => $body->id, + title => 'Cancelled Job - Planned to Confirmed', + text => 'Cancelled job template text', + auto_response => 0, + state => '', + external_status_code => '', + }); + + my $open311 = Open311->new(endpoint => 'http://example.com', jurisdiction => 'dumfries'); + my $updates = Open311::GetUpdates->new( + current_open311 => $open311, + current_body => $body, + system_user => $staff_user, + ); + + subtest 'override applies for planned->confirmed transition via update processing' => sub { + my $request = { + comment_time => DateTime->now, + status => 'OPEN', + description => '', + update_id => 'cancelled-job-override-1', + external_status_code => '', + }; + + my $comment = $updates->process_update($request, $test_problem); + ok $comment, 'Update comment created'; + is $comment->text, 'Cancelled job template text', + 'Cancelled Job template text used for planned->confirmed'; + is $comment->problem_state, 'confirmed', + 'Comment state is confirmed'; + + $test_problem->discard_changes; + is $test_problem->state, 'confirmed', + 'Problem moved to confirmed'; + }; + + subtest 'override does not apply outside planned->confirmed transition' => sub { + $test_problem->update({ + state => 'investigating', + lastupdate => DateTime->now->subtract(hours => 1), + }); + $test_problem->discard_changes; + + my $request = { + comment_time => DateTime->now, + status => 'OPEN', + description => '', + update_id => 'cancelled-job-override-2', + external_status_code => '', + }; + + my $comment = $updates->process_update($request, $test_problem); + ok $comment, 'Second update comment created'; + is $comment->text, 'Normal confirmed template text', + 'Normal template used when transition is not planned->confirmed'; + }; + + $test_problem->comments->delete; + $normal_template->delete; + $cancelled_job_template->delete; + $test_problem->delete; + }; +}; + +subtest 'admin validates reserved Cancelled Job templates' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => ['dumfries'], + }, sub { + $mech->log_in_ok($superuser->email); + + subtest 'auto-response set on Cancelled Job title is rejected' => sub { + $mech->get_ok('/admin/templates/' . $body->id . '/new'); + $mech->submit_form_ok({ with_fields => { + title => 'Cancelled Job - Reserved Template', + text => 'Template text', + state => 'confirmed', + auto_response => 'on', + } }); + + is $mech->uri->path, '/admin/templates/' . $body->id . '/new', + 'Not redirected when reserved template is set as auto-response'; + $mech->content_contains('reserved for the cancelled job feature', + 'Reserved-template validation message shown'); + }; + + subtest 'Cancelled Job title must use Open state' => sub { + $mech->get_ok('/admin/templates/' . $body->id . '/new'); + $mech->submit_form_ok({ with_fields => { + title => 'Cancelled Job - Wrong State', + text => 'Template text', + state => 'investigating', + } }); + + is $mech->uri->path, '/admin/templates/' . $body->id . '/new', + 'Not redirected when reserved template uses non-open state'; + $mech->content_contains('must use the Open state', + 'Open-state validation message shown'); + }; + + subtest 'Cancelled Job title cannot use external status code' => sub { + $mech->get_ok('/admin/templates/' . $body->id . '/new'); + $mech->submit_form_ok({ with_fields => { + title => 'Cancelled Job - External Code', + text => 'Template text', + state => 'confirmed', + external_status_code => 'status1:outcome1:priority1', + } }); + + is $mech->uri->path, '/admin/templates/' . $body->id . '/new', + 'Not redirected when reserved template uses external status code'; + $mech->content_contains('cannot use an external status code', + 'External status code validation message shown'); + }; + + subtest 'Cancelled Job title without auto-response is allowed' => sub { + $mech->get_ok('/admin/templates/' . $body->id . '/new'); + $mech->submit_form_ok({ with_fields => { + title => 'Cancelled Job - Manual Template', + text => 'Template text', + state => 'confirmed', + } }); + + is $mech->uri->path, '/admin/templates/' . $body->id, + 'Redirected when Cancelled Job template is not auto-response'; + $mech->delete_response_template($_) + for $body->response_templates->search({ title => 'Cancelled Job - Manual Template' }); + }; + + $mech->log_out_ok; + }; +}; + subtest "check not responsible as correct text" => sub { my $c = FixMyStreet::DB->resultset('Comment')->create({ problem => $problem, user => $problem->user, anonymous => 't', text => 'Update text',