diff options
author | Matthew Somerville <matthew@mysociety.org> | 2020-06-29 16:07:52 +0100 |
---|---|---|
committer | M Somerville <matthew-github@dracos.co.uk> | 2020-11-11 10:29:05 +0000 |
commit | dbde76a5090e772c806bd189426ee078b9b7e4ba (patch) | |
tree | f15015485554b852865e2388063a6942940f8340 /perllib/FixMyStreet | |
parent | ce1b3ec61fdaa954c26e55b8ce8cd1ad619b3538 (diff) |
[Bromley] Add waste reporting service.
This creates a non-map property-based reporting flow for reporting
missed collections and requesting new containers. Such reports are
always private, in categories that are hidden from the map filters
and never shown on .com.
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report/New.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Waste.pm | 263 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Page/Waste.pm | 11 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste.pm | 52 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/AboutYou.pm | 38 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/Report.pm | 65 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/Request.pm | 64 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Bromley.pm | 50 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/FixMyStreet.pm | 20 |
9 files changed, 561 insertions, 4 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 3b42085ff..98ebd4972 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -1140,7 +1140,7 @@ sub process_report : Private { # save the cobrand and language related information $report->cobrand( $c->cobrand->moniker ); - $report->cobrand_data( '' ); + $report->cobrand_data( $c->stash->{cobrand_data} || '' ); $report->lang( $c->stash->{lang_code} ); return 1; diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 6319b5f22..667edad10 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -5,7 +5,11 @@ use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } use utf8; +use Lingua::EN::Inflect qw( NUMWORDS ); use FixMyStreet::App::Form::Waste::UPRN; +use FixMyStreet::App::Form::Waste::AboutYou; +use FixMyStreet::App::Form::Waste::Request; +use FixMyStreet::App::Form::Waste::Report; sub auto : Private { my ( $self, $c ) = @_; @@ -55,6 +59,9 @@ sub address_list_form { sub redirect_to_uprn : Private { my ($self, $c, $uprn) = @_; my $uri = '/waste/uprn/' . $uprn; + my $type = $c->get_param('type') || ''; + $uri .= '/request' if $type eq 'request'; + $uri .= '/report' if $type eq 'report'; $c->res->redirect($uri); $c->detach; } @@ -84,6 +91,262 @@ sub bin_days : Chained('uprn') : PathPart('') : Args(0) { my ($self, $c) = @_; } +sub construct_bin_request_form { + my $c = shift; + + my $field_list = []; + + foreach (@{$c->stash->{service_data}}) { + next unless $_->{next}; + my $name = $_->{service_name}; + my $containers = $_->{request_containers}; + my $max = $_->{request_max}; + foreach my $id (@$containers) { + push @$field_list, "container-$id" => { + type => 'Checkbox', + apply => [ + { + when => { "quantity-$id" => sub { $_[0] > 0 } }, + check => qr/^1$/, + message => 'Please tick the box', + }, + ], + label => $name, + option_label => $c->stash->{containers}->{$id}, + tags => { toggle => "form-quantity-$id-row" }, + }; + $name = ''; # Only on first container + push @$field_list, "quantity-$id" => { + type => 'Select', + label => 'Quantity', + tags => { + hint => "You can request a maximum of " . NUMWORDS($max) . " containers", + initial_hidden => 1, + }, + options => [ + { value => "", label => '-' }, + map { { value => $_, label => $_ } } (1..$max), + ], + required_when => { "container-$id" => 1 }, + }; + } + } + + return $field_list; +} + +sub request : Chained('uprn') : Args(0) { + my ($self, $c) = @_; + + my $field_list = construct_bin_request_form($c); + + $c->stash->{first_page} = 'request'; + $c->stash->{form_class} = 'FixMyStreet::App::Form::Waste::Request'; + $c->stash->{page_list} = [ + request => { + fields => [ grep { ! ref $_ } @$field_list, 'submit' ], + title => 'Which containers do you need?', + next => 'about_you', + }, + ]; + $c->stash->{field_list} = $field_list; + $c->forward('form'); +} + +sub process_request_data : Private { + my ($self, $c, $form) = @_; + my $data = $form->saved_data; + my $address = $c->stash->{property}->{address}; + my @services = grep { /^container-/ && $data->{$_} } keys %$data; + foreach (@services) { + my ($id) = /container-(.*)/; + my $container = $c->stash->{containers}{$id}; + my $quantity = $data->{"quantity-$id"}; + $data->{title} = "Request new $container"; + $data->{detail} = "Quantity: $quantity\n\n$address"; + $c->set_param('Container_Type', $id); + $c->set_param('Quantity', $quantity); + $c->forward('add_report', [ $data ]) or return; + push @{$c->stash->{report_ids}}, $c->stash->{report}->id; + } + return 1; +} + +sub construct_bin_report_form { + my $c = shift; + + my $field_list = []; + + foreach (@{$c->stash->{service_data}}) { + next unless $_->{last}; + my $id = $_->{service_id}; + my $name = $_->{service_name}; + push @$field_list, "service-$id" => { + type => 'Checkbox', + label => $name, + option_label => $name, + }; + } + + return $field_list; +} + +sub report : Chained('uprn') : Args(0) { + my ($self, $c) = @_; + + my $field_list = construct_bin_report_form($c); + + $c->stash->{first_page} = 'report'; + $c->stash->{form_class} = 'FixMyStreet::App::Form::Waste::Report'; + $c->stash->{page_list} = [ + report => { + fields => [ grep { ! ref $_ } @$field_list, 'submit' ], + title => 'Select your missed collection', + next => 'about_you', + }, + ]; + $c->stash->{field_list} = $field_list; + $c->forward('form'); +} + +sub process_report_data : Private { + my ($self, $c, $form) = @_; + my $data = $form->saved_data; + my $address = $c->stash->{property}->{address}; + my @services = grep { /^service-/ && $data->{$_} } keys %$data; + foreach (@services) { + my ($id) = /service-(.*)/; + my $service = $c->stash->{services}{$id}{service_name}; + $data->{title} = "Report missed $service"; + $data->{detail} = "$data->{title}\n\n$address"; + $c->set_param('service_id', $id); + $c->forward('add_report', [ $data ]) or return; + push @{$c->stash->{report_ids}}, $c->stash->{report}->id; + } + return 1; +} + +sub load_form { + my ($c, $previous_form) = @_; + + my $page; + if ($previous_form) { + $page = $previous_form->next; + } else { + $page = $c->forward('get_page'); + } + + my $form = $c->stash->{form_class}->new( + page_list => $c->stash->{page_list}, + $c->stash->{field_list} ? (field_list => $c->stash->{field_list}) : (), + page_name => $page, + csrf_token => $c->stash->{csrf_token}, + c => $c, + previous_form => $previous_form, + saved_data_encoded => $c->get_param('saved_data'), + no_preload => 1, + ); + + if (!$form->has_current_page) { + $c->detach('/page_error_400_bad_request', [ 'Bad request' ]); + } + + return $form; +} + +sub form : Private { + my ($self, $c) = @_; + + my $form = load_form($c); + if ($c->get_param('process')) { + $c->forward('/auth/check_csrf_token'); + $form->process(params => $c->req->body_params); + if ($form->validated) { + $form = load_form($c, $form); + } + } + + $form->process unless $form->processed; + + $c->stash->{template} = $form->template || 'waste/index.html'; + $c->stash->{form} = $form; +} + +sub get_page : Private { + my ($self, $c) = @_; + + my $goto = $c->get_param('goto') || ''; + my $process = $c->get_param('process') || ''; + $goto = $c->stash->{first_page} unless $goto || $process; + if ($goto && $process) { + $c->detach('/page_error_400_bad_request', [ 'Bad request' ]); + } + + return $goto || $process; +} + +sub add_report : Private { + my ( $self, $c, $data ) = @_; + + $c->stash->{cobrand_data} = 'waste'; + + # XXX Is this best way to do this? + if ($c->user_exists && $c->user->from_body && $c->user->email ne $data->{email}) { + $c->set_param('form_as', 'another_user'); + $c->set_param('username', $data->{email} || $data->{phone}); + } else { + $c->set_param('username_register', $data->{email} || $data->{phone}); + } + + # Set the data as if a new report form has been submitted + + $c->set_param('submit_problem', 1); + $c->set_param('pc', ''); + $c->set_param('non_public', 1); + + $c->set_param('name', $data->{name}); + $c->set_param('phone', $data->{phone}); + + $c->set_param('category', $data->{category}); + $c->set_param('title', $data->{title}); + $c->set_param('detail', $data->{detail}); + $c->set_param('uprn', $c->stash->{uprn}); + + $c->forward('setup_categories_and_bodies') unless $c->stash->{contacts}; + $c->forward('/report/new/non_map_creation', [['/waste/remove_name_errors']]) or return; + my $report = $c->stash->{report}; + $report->confirm; + $report->update; + + $c->model('DB::Alert')->find_or_create({ + user => $report->user, + alert_type => 'new_updates', + parameter => $report->id, + cobrand => $report->cobrand, + lang => $report->lang, + })->confirm; + + return 1; +} + +sub remove_name_errors : Private { + my ($self, $c) = @_; + # We do not mind about missing title/split name here + my $field_errors = $c->stash->{field_errors}; + delete $field_errors->{fms_extra_title}; + delete $field_errors->{first_name}; + delete $field_errors->{last_name}; +} + +sub setup_categories_and_bodies : Private { + my ($self, $c) = @_; + + $c->stash->{all_areas} = $c->stash->{all_areas_mapit} = { $c->cobrand->council_area_id => { id => $c->cobrand->council_area_id } }; + $c->forward('/report/new/setup_categories_and_bodies'); + my $contacts = $c->stash->{contacts}; + @$contacts = grep { grep { $_ eq 'Waste' } @{$_->groups} } @$contacts; +} + __PACKAGE__->meta->make_immutable; 1; diff --git a/perllib/FixMyStreet/App/Form/Page/Waste.pm b/perllib/FixMyStreet/App/Form/Page/Waste.pm new file mode 100644 index 000000000..5275cae7f --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Page/Waste.pm @@ -0,0 +1,11 @@ +package FixMyStreet::App::Form::Page::Waste; +use Moose; +extends 'FixMyStreet::App::Form::Page::Simple'; + +# Title to use for this page +has title => ( is => 'ro', isa => 'Str' ); + +# Special template to use in preference to the default +has template => ( is => 'ro', isa => 'Str' ); + +1; diff --git a/perllib/FixMyStreet/App/Form/Waste.pm b/perllib/FixMyStreet/App/Form/Waste.pm new file mode 100644 index 000000000..c430506c7 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste.pm @@ -0,0 +1,52 @@ +package FixMyStreet::App::Form::Waste; + +use HTML::FormHandler::Moose; +extends 'FixMyStreet::App::Form::Wizard'; + +has c => ( is => 'ro' ); + +has default_page_type => ( is => 'ro', isa => 'Str', default => 'Waste' ); + +has finished_action => ( is => 'ro' ); + +before _process_page_array => sub { + my ($self, $pages) = @_; + foreach my $page (@$pages) { + $page->{type} = $self->default_page_type + unless $page->{type}; + } +}; + +# Add some functions to the form to pass through to the current page +has '+current_page' => ( + handles => { + title => 'title', + template => 'template', + } +); + +sub wizard_finished { + my ($form, $action) = @_; + my $c = $form->c; + my $success = $c->forward($action, [ $form ]); + if (!$success) { + $form->add_form_error('Something went wrong, please try again'); + foreach (keys %{$c->stash->{field_errors}}) { + $form->add_form_error("$_: " . $c->stash->{field_errors}{$_}); + } + } + return $success; +} + +# Make sure we can have pre-ticked things on the first page +before after_build => sub { + my $self = shift; + + my $saved_data = $self->previous_form ? $self->previous_form->saved_data : $self->saved_data; + + my $c = $self->c; + + map { $saved_data->{$_} = 1 } grep { /^(service|container)-/ && $c->req->params->{$_} } keys %{$c->req->params}; +}; + +1; diff --git a/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm b/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm new file mode 100644 index 000000000..d5bb3df2b --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm @@ -0,0 +1,38 @@ +package FixMyStreet::App::Form::Waste::AboutYou; + +use utf8; +use HTML::FormHandler::Moose::Role; +use FixMyStreet::SMS; + +has_field name => ( + type => 'Text', + label => 'Your name', + required => 1, + validate_method => sub { + my $self = shift; + $self->add_error('Please enter your full name.') + if length($self->value) < 5 + || $self->value !~ m/\s/ + || $self->value =~ m/\ba\s*n+on+((y|o)mo?u?s)?(ly)?\b/i; + }, +); + +has_field phone => ( + type => 'Text', + label => 'Telephone number', + validate_method => sub { + my $self = shift; + my $parsed = FixMyStreet::SMS->parse_username($self->value); + $self->add_error('Please provide a valid phone number') + unless $parsed->{phone}; + } +); + +has_field email => ( + type => 'Email', + tags => { + hint => 'If you provide an email address, we can send you order status updates' + }, +); + +1; diff --git a/perllib/FixMyStreet/App/Form/Waste/Report.pm b/perllib/FixMyStreet/App/Form/Waste/Report.pm new file mode 100644 index 000000000..589e75d48 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/Report.pm @@ -0,0 +1,65 @@ +package FixMyStreet::App::Form::Waste::Report; + +use utf8; +use HTML::FormHandler::Moose; +extends 'FixMyStreet::App::Form::Waste'; + +# First page has dynamic fields, so is set in code + +has_page about_you => ( + fields => ['name', 'email', 'phone', 'continue'], + title => 'About you', + next => 'summary', +); + +with 'FixMyStreet::App::Form::Waste::AboutYou'; + +has_page summary => ( + fields => ['submit'], + title => 'Submit missed collection', + template => 'waste/summary_report.html', + finished => sub { + return $_[0]->wizard_finished('process_report_data'); + }, + next => 'done', +); + +has_page done => ( + title => 'Missed collection sent', + template => 'waste/confirmation.html', +); + +has_field category => ( + type => 'Hidden', + default => 'Report missed collection' +); + +has_field continue => ( + type => 'Submit', + value => 'Continue', + element_attr => { class => 'govuk-button' }, +); + +has_field submit => ( + type => 'Submit', + value => 'Report collection as missed', + element_attr => { class => 'govuk-button' }, + order => 999, +); + +sub validate { + my $self = shift; + my $any = 0; + + foreach ($self->all_fields) { + $any = 1 if $_->name =~ /^service-/ && ($_->value || $self->saved_data->{$_->name}); + } + $self->add_form_error('Please specify what was missed') + unless $any; + + $self->add_form_error('Please specify at least one of phone or email') + unless $self->field('phone')->is_inactive || $self->field('phone')->value || $self->field('email')->value; +} + +1; + diff --git a/perllib/FixMyStreet/App/Form/Waste/Request.pm b/perllib/FixMyStreet/App/Form/Waste/Request.pm new file mode 100644 index 000000000..e7caaa206 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/Request.pm @@ -0,0 +1,64 @@ +package FixMyStreet::App::Form::Waste::Request; + +use utf8; +use HTML::FormHandler::Moose; +extends 'FixMyStreet::App::Form::Waste'; + +# First page has dynamic fields, so is set in code + +has_page about_you => ( + fields => ['name', 'email', 'phone', 'continue'], + title => 'About you', + next => 'summary', +); + +with 'FixMyStreet::App::Form::Waste::AboutYou'; + +has_page summary => ( + fields => ['submit'], + title => 'Submit container request', + template => 'waste/summary_request.html', + finished => sub { + return $_[0]->wizard_finished('process_request_data'); + }, + next => 'done', +); + +has_page done => ( + title => 'Container request sent', + template => 'waste/confirmation.html', +); + +has_field category => ( + type => 'Hidden', + default => 'Request new container', +); + +has_field continue => ( + type => 'Submit', + value => 'Continue', + element_attr => { class => 'govuk-button' }, +); + +has_field submit => ( + type => 'Submit', + value => 'Request new containers', + element_attr => { class => 'govuk-button' }, + order => 999, +); + +sub validate { + my $self = shift; + my $any = 0; + + foreach ($self->all_fields) { + $any = 1 if $_->name =~ /^container-/ && ($_->value || $self->saved_data->{$_->name}); + } + $self->add_form_error('Please specify what you need') + unless $any; + + $self->add_form_error('Please specify at least one of phone or email') + unless $self->field('phone')->is_inactive || $self->field('phone')->value || $self->field('email')->value; +} + +1; diff --git a/perllib/FixMyStreet/Cobrand/Bromley.pm b/perllib/FixMyStreet/Cobrand/Bromley.pm index e4e509f77..6441180e3 100644 --- a/perllib/FixMyStreet/Cobrand/Bromley.pm +++ b/perllib/FixMyStreet/Cobrand/Bromley.pm @@ -378,6 +378,25 @@ sub munge_load_and_group_problems { } } +sub munge_around_category_where { + my ($self, $where) = @_; + $where->{extra} = [ undef, { -not_like => '%Waste%' } ]; +} + +sub munge_reports_category_list { + my ($self, $categories) = @_; + @$categories = grep { grep { $_ ne 'Waste' } @{$_->groups} } @$categories; +} + +sub munge_report_new_contacts { + my ($self, $categories) = @_; + + return if $self->{c}->action =~ /^waste/; + + @$categories = grep { grep { $_ ne 'Waste' } @{$_->groups} } @$categories; + $self->SUPER::munge_report_new_contacts($categories); +} + sub bin_addresses_for_postcode { my $self = shift; my $pc = shift; @@ -442,6 +461,33 @@ sub bin_services_for_address { 545 => 'Garden Waste', ); + $self->{c}->stash->{containers} = { + 1 => 'Green Box (Plastic)', + 2 => 'Wheeled Bin (Plastic)', + 12 => 'Black Box (Paper)', + 13 => 'Wheeled Bin (Paper)', + 9 => 'Kitchen Caddy', + 10 => 'Outside Food Waste Container', + 45 => 'Wheeled Bin (Food)', + }; + my %service_to_containers = ( + 535 => [ 1 ], + 536 => [ 2 ], + 537 => [ 12 ], + 541 => [ 13 ], + 542 => [ 9, 10 ], + 544 => [ 45 ], + ); + my %request_allowed = map { $_ => 1 } keys %service_to_containers; + my %quantity_max = ( + 535 => 6, + 536 => 4, + 537 => 6, + 541 => 4, + 542 => 6, + 544 => 4, + ); + my $echo = $self->feature('echo'); $echo = Integrations::Echo->new(%$echo); my $result = $echo->GetServiceUnitsForObject($property->{uprn}); @@ -456,10 +502,14 @@ sub bin_services_for_address { next unless $schedules->{next} or $schedules->{last}; + my $containers = $service_to_containers{$_->{ServiceId}}; my $row = { id => $_->{Id}, service_id => $_->{ServiceId}, service_name => $service_name_override{$_->{ServiceId}} || $_->{ServiceName}, + request_allowed => $request_allowed{$_->{ServiceId}}, + request_containers => $containers, + request_max => $quantity_max{$_->{ServiceId}}, service_task_id => $servicetask->{Id}, service_task_name => $servicetask->{TaskTypeName}, service_task_type_id => $servicetask->{TaskTypeId}, diff --git a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm index 7344a0f5e..ae96924d8 100644 --- a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm +++ b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm @@ -35,15 +35,19 @@ sub restriction { return {}; } -# FixMyStreet needs to not show TfL reports... +# FixMyStreet needs to not show TfL reports or Bromley waste reports sub problems_restriction { my ($self, $rs) = @_; my $table = ref $rs eq 'FixMyStreet::DB::ResultSet::Nearby' ? 'problem' : 'me'; - return $rs->search({ "$table.cobrand" => { '!=' => 'tfl' } }); + return $rs->search({ + "$table.cobrand" => { '!=' => 'tfl' }, + "$table.cobrand_data" => { '!=' => 'waste' }, + }); } sub problems_sql_restriction { my $self = shift; return "AND cobrand != 'tfl'"; + # Doesn't need Bromley one as all waste reports non-public } sub relative_url_for_report { @@ -61,6 +65,10 @@ sub munge_around_category_where { # can't determine the body $where->{send_method} = [ { '!=' => 'Triage' }, undef ]; } + my $bromley = grep { $_->name eq 'Bromley Council' } @{ $self->{c}->stash->{around_bodies} }; + if ($bromley) { + $where->{extra} = [ undef, { -not_like => '%Waste%' } ]; + } } sub _iow_category_munge { @@ -75,13 +83,16 @@ sub _iow_category_munge { @$categories = grep { $_->send_method && $_->send_method eq 'Triage' } @$categories; } -sub munge_reports_categories_list { +sub munge_reports_category_list { my ($self, $categories) = @_; my %bodies = map { $_->body->name => $_->body } @$categories; if ( my $body = $bodies{'Isle of Wight Council'} ) { return $self->_iow_category_munge($body, $categories); } + if ( $bodies{'Bromley Council'} ) { + @$categories = grep { grep { $_ ne 'Waste' } @{$_->groups} } @$categories; + } } sub munge_reports_area_list { @@ -122,6 +133,9 @@ sub munge_report_new_contacts { if ( my $body = $bodies{'Isle of Wight Council'} ) { return $self->_iow_category_munge($body, $contacts); } + if ( $bodies{'Bromley Council'} ) { + @$contacts = grep { grep { $_ ne 'Waste' } @{$_->groups} } @$contacts; + } if ( $bodies{'TfL'} ) { # Presented categories vary if we're on/off a red route my $tfl = FixMyStreet::Cobrand->get_class_for_moniker( 'tfl' )->new({ c => $self->{c} }); |