diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index c25f669001..1b367cb57c 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -18,6 +18,7 @@ use FixMyStreet::App::Form::Waste::Garden::Modify; use FixMyStreet::App::Form::Waste::Garden::Cancel; use FixMyStreet::App::Form::Waste::Garden::Renew; use FixMyStreet::App::Form::Waste::Garden::Sacks::Purchase; +use FixMyStreet::App::Form::Waste::Garden::Transfer; use Memcached; use JSON::MaybeXS; @@ -835,6 +836,54 @@ sub process_request_data : Private { return 1; } +sub process_garden_transfer : Private { + my ($self, $c, $form) = @_; + my $data = $form->saved_data; + + # Get the current subscription for the old address + my $old_property_id = $data->{previous_ggw_address}->{value}; + #$c->forward('get_original_sub', ['', $old_property_id]); + + my $base = {}; + $base->{name} = $c->get_param('name'); + $base->{email} = $c->get_param('email'); + $base->{phone} = $c->get_param('phone'); + + # Cancel the old subscription + my $cancel = { %$base }; + $cancel->{category} = 'Cancel Garden Subscription'; + $cancel->{title} = 'Garden Subscription - Cancel'; + $cancel->{address} = $data->{previous_ggw_address}->{label}; + my $now = DateTime->now->set_time_zone(FixMyStreet->local_time_zone); + my $end_date_field = $c->cobrand->call_hook(alternative_backend_field_names => 'Subscription_End_Date') || 'Subscription_End_Date'; + $c->set_param($end_date_field, $now->ymd); + $c->set_param('property_id', $old_property_id); + $c->set_param('uprn', $data->{transfer_old_ggw_sub}{transfer_uprn}); + $c->set_param('transferred_to', $c->stash->{property}->{uprn}); + $c->forward('setup_garden_sub_params', [ $cancel, undef ]); + $c->forward('add_report', [ $cancel ]) or return; + $c->stash->{report}->confirm; + $c->stash->{report}->update; + + # Create a report for it for the new address + my $new = { %$base }; + $new->{category} = 'Garden Subscription'; + $new->{title} = 'Garden Subscription - New'; + $new->{bins_wanted} = $data->{transfer_old_ggw_sub}->{transfer_bin_number}; + $new->{transfer_bin_type} = $data->{transfer_old_ggw_sub}->{transfer_bin_type}; + + my $expiry = $data->{transfer_old_ggw_sub}->{subscription_enddate}; + $expiry = DateTime::Format::W3CDTF->parse_datetime($expiry); + $c->set_param($end_date_field, $expiry->ymd); + $c->set_param('property_id', ''); + $c->set_param('uprn', ''); + $c->set_param('transferred_from', $data->{transfer_old_ggw_sub}{transfer_uprn}); + $c->forward('setup_garden_sub_params', [ $new, $c->stash->{garden_subs}->{New} ]); + $c->forward('add_report', [ $new ]) or return; + $c->stash->{report}->confirm; + $c->stash->{report}->update; +} + sub group_reports { my ($c, @reports) = @_; my $report = shift @reports; @@ -1269,6 +1318,15 @@ sub garden_renew : Chained('garden_setup') : Args(0) { $c->forward('form'); } +sub garden_transfer : Chained('garden_setup') : Args(0) { + my ($self, $c) = @_; + + $c->detach( '/page_error_403_access_denied', [] ) unless $c->stash->{is_staff}; + + $c->stash->{form_class} = 'FixMyStreet::App::Form::Waste::Garden::Transfer'; + $c->forward('form'); +} + sub process_garden_cancellation : Private { my ($self, $c, $form) = @_; @@ -1342,6 +1400,7 @@ sub get_original_sub : Private { } my $r = $c->stash->{orig_sub} = $p->first; + $c->cobrand->call_hook(waste_check_existing_dd => $r) if $r && ($r->get_extra_field_value('payment_method') || '') eq 'direct_debit'; } @@ -1349,7 +1408,7 @@ sub get_original_sub : Private { sub setup_garden_sub_params : Private { my ($self, $c, $data, $type) = @_; - my $address = $c->stash->{property}->{address}; + my $address = $data->{address} || $c->stash->{property}->{address}; $data->{detail} = "$data->{category}\n\n$address"; @@ -1599,7 +1658,7 @@ sub add_report : Private { $c->set_param('title', $data->{title}); $c->set_param('detail', $data->{detail}); $c->set_param('uprn', $c->stash->{property}{uprn}) unless $c->get_param('uprn'); - $c->set_param('property_id', $c->stash->{property}{id}); + $c->set_param('property_id', $c->stash->{property}{id}) unless $c->get_param('property_id'); # Data may contain duplicate photo data under different keys e.g. # 'item_photo_1' => 'c8a965ad74acad4104341a8ea893b1a1275efa4d.jpeg', diff --git a/perllib/FixMyStreet/App/Form/Waste/Garden/Transfer.pm b/perllib/FixMyStreet/App/Form/Waste/Garden/Transfer.pm new file mode 100644 index 0000000000..7e5f6ff09c --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/Garden/Transfer.pm @@ -0,0 +1,162 @@ +package FixMyStreet::App::Form::Waste::Garden::Transfer; + +use utf8; +use HTML::FormHandler::Moose; +use JSON; +extends 'FixMyStreet::App::Form::Waste'; + +has original_subscriber => ( + is => 'ro', + lazy => 1, + default => sub { + my $self = shift; + my $c = $self->{c}; + + my $p = $c->cobrand->problems->search({ + category => 'Garden Subscription', + title => ['Garden Subscription - New', 'Garden Subscription - Renew'], + extra => { '@>' => encode_json({ "_fields" => [ { name => "property_id", value => ($self->saved_data->{previous_ggw_address}->{value}) } ] }) }, + state => [ FixMyStreet::DB::Result::Problem->open_states ] + })->order_by('-id')->to_body($c->cobrand->body)->first; + + my $user; + ($user) = $c->model('DB::User')->find({ id => $p->user_id }) if $p; + return $user; + }, +); + +has_page intro => ( + title => 'Transfer garden waste subscription - check', + fields => ['resident_moved', 'continue_address'], + next => 'old_address', +); + +has_page old_address => ( + title => 'Transfer garden waste subscription - old address', + fields => ['postcode', 'continue_select'], + next => 'select_old_address', +); + +has_page select_old_address => ( + title => 'Transfer garden waste subscription - old address', + fields => ['addresses', 'continue_confirm'], + next => 'confirm', +); + +has_page confirm => ( + intro => 'garden/transfer_confirm_addresses.html', + title => 'Confirm transfer', + fields => ['email', 'phone', 'name', 'continue_done'], + finished => sub { + return $_[0]->wizard_finished('process_garden_transfer'); + }, + next => 'done', +); + +with 'FixMyStreet::App::Form::Waste::AboutYou'; + +sub default_name { + my $self = shift; + + return $self->original_subscriber ? $self->original_subscriber->name : ''; +} + +sub default_phone { + my $self = shift; + + return $self->original_subscriber ? $self->original_subscriber->phone : ''; +} + +sub default_email { + my $self = shift; + + return $self->original_subscriber ? $self->original_subscriber->email : ''; +} + +has_page done => ( + title => 'Transferred', + template => 'waste/garden/transferred.html', +); + + +has_field resident_moved => ( + type => 'Checkbox', + required => 1, + option_label => 'Confirm that resident has moved to the address above and has brought their garden bins or bags with them', +); + +has_field continue_address => ( + type => 'Submit', + value => 'Find old address', + element_attr => { class => 'govuk-button' }, +); + +has_field continue_select => ( + type => 'Submit', + element_attr => { class => 'govuk-button' }, + order => 999, +); + +has_field continue_confirm => ( + type => 'Submit', + value => 'Select', + element_attr => { class => 'govuk-button' }, + order => 999, +); + +has_field continue_done => ( + type => 'Submit', + value => 'Transfer', + element_attr => { class => 'govuk-button' }, + order => 999 +); + +has_field postcode => ( + type => 'Postcode', + value => 'Enter postcode', + validate_method => sub { + my $self = shift; + my $c = $self->form->c; + return if $self->has_errors; # Called even if already failed + my $data = $c->cobrand->bin_addresses_for_postcode($self->value); + if (!@$data) { + my $error = 'Sorry, we did not find any results for that postcode'; + $self->add_error($error); + } + $self->form->saved_data->{addresses} = $data; + } +); + +has_field addresses => ( + label => 'Select previous address', + type => 'Select', + options_method => sub { + my $field = shift; + return $field->form->saved_data->{addresses}; + }, + validate_method => sub { + my $self = shift; + my $c = $self->form->c; + + my %messages = ( + 'current' => 'There is currently a garden subscription at the new address', + 'no_previous' => 'There is no garden subscription at this address', + 'due_soon' => 'Subscription can not be transferred as is in the renewal period or expired', + 'duplicate' => "This should be the old address, not the new one", + ); + + if ($self->value == $c->stash->{property}{id}) { + $self->add_error($messages{'duplicate'}); + return; + } + my $data = $c->cobrand->call_hook('check_ggw_transfer_applicable' => $self->value); + if ($data->{error}) { + $self->add_error($messages{ $data->{error} }); + return; + }; + $self->form->saved_data->{transfer_old_ggw_sub} = $data; + ($self->form->saved_data->{previous_ggw_address}) = (grep { $_->{value} == $self->value } @{$self->form->saved_data->{addresses}})[0]; + } +); + +1; \ No newline at end of file diff --git a/perllib/FixMyStreet/Cobrand/Merton/Waste.pm b/perllib/FixMyStreet/Cobrand/Merton/Waste.pm index c64f2c6f7c..4adb699ff3 100644 --- a/perllib/FixMyStreet/Cobrand/Merton/Waste.pm +++ b/perllib/FixMyStreet/Cobrand/Merton/Waste.pm @@ -376,6 +376,43 @@ sub waste_post_report_creation { } } +sub check_ggw_transfer_applicable { + my ($self, $old_address) = @_; + + # Check new address doesn't have a ggw subscription + return { error => 'current' } if $self->garden_current_subscription; + + # Check that the old address has a ggw subscription and it's not + # in its expiry period + my $details = $self->look_up_property($old_address); + my $old_services = $self->{api_serviceunits}; + + my ($old_garden) = grep { $_->{ServiceId} eq '409' } @$old_services; + $old_garden->{transfer_uprn} = $details->{uprn}; + + my $servicetask = $self->garden_current_service_from_service_units($old_services); + + return { error => 'no_previous' } unless $servicetask; + + my $subscription_enddate = _parse_schedules($servicetask)->{end_date}; + return { error => 'due_soon' } if ($subscription_enddate && $self->waste_sub_due($subscription_enddate)); + + my $old_subscription_bin_data = Integrations::Echo::force_arrayref($servicetask->{Data}, 'ExtensibleDatum'); + + foreach (@$old_subscription_bin_data) { + my $moredata = Integrations::Echo::force_arrayref($_->{ChildData}, 'ExtensibleDatum'); + foreach (@$moredata) { + if ($_->{DatatypeName} eq 'Quantity') { + $old_garden->{transfer_bin_number} = $_->{Value}; + } elsif ($_->{DatatypeName} eq 'Container Type') { + $old_garden->{transfer_bin_type} = $_->{Value}; + } + } + }; + $old_garden->{subscription_enddate} = $subscription_enddate; + return $old_garden; +} + =head2 Bulky waste collection Merton has a 6am collection and cut-off for cancellation time. diff --git a/perllib/FixMyStreet/Roles/Cobrand/Echo.pm b/perllib/FixMyStreet/Roles/Cobrand/Echo.pm index 3fccd7151c..6a0d887b65 100644 --- a/perllib/FixMyStreet/Roles/Cobrand/Echo.pm +++ b/perllib/FixMyStreet/Roles/Cobrand/Echo.pm @@ -105,7 +105,7 @@ sub bin_addresses_for_postcode { sub _allow_async_echo_lookup { my $self = shift; my $action = $self->{c}->action; - return 0 if $action eq 'waste/pay_retry' || $action eq 'waste/direct_debit_error' || $action eq 'waste/calendar_ics'; + return 0 if $action eq 'waste/pay_retry' || $action eq 'waste/direct_debit_error' || $action eq 'waste/calendar_ics' || $action eq 'waste/garden_transfer'; return 1; } @@ -498,7 +498,6 @@ sub _parse_schedules { my $start_date = construct_bin_date($schedule->{StartDate})->strftime("%F"); my $end_date = construct_bin_date($schedule->{EndDate})->strftime("%F"); $max_end_date = $end_date if !defined($max_end_date) || $max_end_date lt $end_date; - next if $end_date lt $today; my $next = $schedule->{NextInstance}; diff --git a/perllib/FixMyStreet/Roles/Cobrand/SLWP.pm b/perllib/FixMyStreet/Roles/Cobrand/SLWP.pm index 4dd4f7a46f..ae5223d67c 100644 --- a/perllib/FixMyStreet/Roles/Cobrand/SLWP.pm +++ b/perllib/FixMyStreet/Roles/Cobrand/SLWP.pm @@ -369,6 +369,7 @@ sub waste_garden_sub_params { my $service = $self->garden_current_subscription; my $choice = $data->{container_choice} || ''; my $existing = $service ? $service->{garden_container} : undef; + $existing = $data->{transfer_bin_type} if $data->{transfer_bin_type}; my $container; if ($choice eq 'sack') { $container = CONTAINER_GARDEN_SACK; diff --git a/t/app/controller/waste_merton_garden.t b/t/app/controller/waste_merton_garden.t index a5316be5ad..dbb1d54fc2 100644 --- a/t/app/controller/waste_merton_garden.t +++ b/t/app/controller/waste_merton_garden.t @@ -41,6 +41,8 @@ create_contact({ category => 'Garden Subscription', email => 'garden@example.com { code => 'payment_method', required => 1, automated => 'hidden_field' }, { code => 'pro_rata', required => 0, automated => 'hidden_field' }, { code => 'admin_fee', required => 0, automated => 'hidden_field' }, + { code => 'End_Date', required => 0, automated => 'hidden_field' }, + { code => 'transferred_from', required => 0, automated => 'hidden_field' }, ); create_contact({ category => 'Cancel Garden Subscription', email => 'garden_cancel@example.com'}, { code => 'Bin_Detail_Quantity', required => 1, automated => 'hidden_field' }, @@ -48,6 +50,7 @@ create_contact({ category => 'Cancel Garden Subscription', email => 'garden_canc { code => 'Bin_Detail_Type', required => 1, automated => 'hidden_field' }, { code => 'End_Date', required => 1, automated => 'hidden_field' }, { code => 'payment_method', required => 1, automated => 'hidden_field' }, + { code => 'transferred_to', required => 0, automated => 'hidden_field' }, ); my $change_address_contact = create_contact({ category => 'Garden Subscription Address Change', email => 'garden_address_change@example.com'}, { code => 'staff_form', automated => 'hidden_field' }, @@ -1164,37 +1167,135 @@ FixMyStreet::override_config { is $new_report->get_extra_field_value('pro_rata'), '', 'no pro rata payment if removing bins'; }; $echo->mock('GetServiceUnitsForObject', \&garden_waste_one_bin); + $echo->mock('GetPointAddress', sub { + my ($self, $id) = @_; + return { + Id => $id, + SharedRef => { Value => { anyType => $id eq '11345' ? '1000000001' : '1000000002'} }, + PointType => 'PointAddress', + PointAddressType => { Name => 'House' }, + Coordinates => { GeoPoint => { Latitude => 51.400975, Longitude => -0.19655 } }, + Description => '2 Example Street, Merton, ', + }; + }); + subtest 'staff transfer ggw subscription' => sub { + my ($p) = $mech->create_problems_for_body(1, $body->id, 'Garden Subscription - New', { + user_id => $user->id, + category => 'Garden Subscription', + whensent => \'current_timestamp', + send_state => 'sent', + }); + $p->title('Garden Subscription - New'); + $p->update_extra_field({ name => 'property_id', value => '11345' }); + $p->update; - # subtest 'staff change address for sub' => sub { - # set_fixed_time('2021-03-09T17:00:00Z'); # After sample data collection - # $mech->get_ok('/waste/12345/'); - # $mech->content_contains('Report an address change', "contains link to address change form"); - # $mech->follow_link_ok({ text => 'Report an address change' }); - # $mech->content_contains('Only fill this in if they confirm they have moved to the new property.', 'contains notice'); - # $mech->submit_form_ok({ with_fields => { - # extra_new_address => 'New Address', - # extra_old_address => 'Old Address', - # } }); - # $mech->submit_form_ok({ with_fields => { - # email => 'resident@example.org', - # name => 'Arthur Address-Change', - # } }); - # $mech->submit_form_ok({ with_fields => { process => 'summary' } }); - # $mech->content_contains('Your enquiry has been submitted'); - - # my $new_report = FixMyStreet::DB->resultset('Problem')->search( - # { }, - # { order_by => { -desc => 'id' } }, - # )->first; - - # is $new_report->category, 'Garden Subscription Address Change', 'correct category on report'; - # is $new_report->get_extra_field_value('new_address'), 'New Address', 'correct new address on report'; - # is $new_report->get_extra_field_value('old_address'), 'Old Address', 'correct old address on report'; - # is $new_report->get_extra_metadata('no_echo'), 1, 'metadata set to indicate not sent to echo'; - # is $new_report->send_state, 'sent', 'report marked as sent'; - # }; + set_fixed_time('2021-03-09T17:00:00Z'); # After sample data collection + + $mech->log_out_ok; + $mech->get_ok('/waste/12345/'); + $mech->content_lacks('Transfer GGW to property', "No change address form for non-staff users"); + + $mech->get('/waste/12345/garden_transfer'); + $mech->content_contains('Access denied', 'Direct access to form denied for non-staff users'); + + $mech->log_in_ok($staff_user->email); + + $mech->get_ok('/waste/12345/'); + $mech->content_contains('Transfer GGW to property', "change address details form present"); + $mech->content_contains('There is already a garden waste subscription', 'Can not transfer to this property as has subscription'); + + $echo->mock('GetServiceUnitsForObject', \&garden_waste_only_refuse_sacks); + $mech->get_ok('/waste/12345/'); + $mech->follow_link_ok({text => 'Transfer GGW subscription'}); + $mech->content_contains('2 Example Street, Merton', 'Shows current address'); + $mech->content_contains('Confirm that resident has moved to the address above', 'Contains conditions check'); + + $mech->submit_form_ok(); + $mech->content_contains('Resident moved field is required', 'Must confirm resident is at new address with bin(s)'); + $mech->submit_form_ok({ with_fields => { + resident_moved => 1, + } }); + + $mech->submit_form_ok({ with_fields => { + postcode => 'wrong postcode' + }}); + $mech->content_contains('Sorry, we did not recognise that postcode', 'Warning if incorrect postcode'); + $mech->submit_form_ok({ with_fields => { + postcode => 'SM2 5HF' + }}); + + $mech->submit_form_ok({ with_fields => { + addresses => '12345' + }}); + $mech->content_contains('This should be the old address, not the new one', + 'Can not transfer from current address'); + + $echo->mock('GetServiceUnitsForObject', sub { + return &garden_waste_no_bins; + }); + $mech->submit_form_ok({ with_fields => { + addresses => '11345' + }}); + $mech->content_contains('There is no garden subscription at this address', + 'Can not transfer from property that does not have a ggw subscription'); + + $echo->mock('GetServiceUnitsForObject', sub { + return &garden_waste_one_bin; + }); + $mech->submit_form_ok({ with_fields => { + addresses => '11345' + }}); + $mech->content_contains('There is currently a garden subscription at the new address', + 'Can not transfer to a property with a ggw subscription'); + + $echo->mock('GetServiceUnitsForObject', sub { + if ($_[1] == '12345') { + return &garden_waste_only_refuse_sacks; + } else { + return &garden_waste_one_bin; + } + }); + $mech->submit_form_ok({ with_fields => { + addresses => '11345' + }}); + $mech->content_contains('Subscription can not be transferred as is in the renewal period or expired', + 'Subscription can not be transferred as it is due for renewal'); + + set_fixed_time('2021-02-09T17:00:00Z'); + $mech->submit_form_ok({ with_fields => { + addresses => '11345' + }}); + $mech->content_contains('Confirm transfer', 'Moved onto confirm page'); + $mech->content_contains('From: 1 Example Street, Merton', 'Correct from address'); + $mech->content_contains('To: 2 Example Street, Merton', 'Correct to address'); + $mech->content_contains($p->user->name, 'Prefilled with user name of previous sub'); + $mech->content_contains($p->user->email, 'Prefilled with user email of previous sub'); + $mech->submit_form_ok(); + my $recent_reports = FixMyStreet::DB->resultset('Problem')->search( + { }, + { order_by => { -desc => 'id' }, rows => 2 }, + ); + my ($new_report, $cancel_report) = $recent_reports->all; + is $new_report->user->name, $p->user->name, 'User name on report'; + is $new_report->user->email, $p->user->email, 'User email on report'; + is $new_report->title, 'Garden Subscription - New', 'New report title correct'; + is $new_report->detail, "Garden Subscription\n\n2 Example Street, Merton,", 'New report detail correct'; + is $new_report->get_extra_field_value('uprn'), '1000000002', 'Correct uprn on new report'; + is $new_report->get_extra_field_value('property_id'), '12345', 'Correct property id on new report'; + is $new_report->get_extra_field_value('Subscription_Details_Quantity'), 1, 'Correct bin containers amount set on new report'; + is $new_report->get_extra_field_value('Subscription_Details_Containers'), 26, 'Correct bin type set on new report'; + is $new_report->get_extra_field_value('End_Date'), '2021-03-30', 'Subscription time transferred'; + is $new_report->get_extra_field_value('transferred_from'), '1000000001'; + is $cancel_report->title, 'Garden Subscription - Cancel', 'Cancelled report title correct'; + is $cancel_report->detail, "Cancel Garden Subscription\n\n1 Example Street, Merton, SM2 5HF", 'Cancelled report detail correct'; + is $cancel_report->get_extra_field_value('uprn'), '1000000001', 'Correct uprn on cancelled report'; + is $cancel_report->get_extra_field_value('property_id'), '11345', 'Correct property id on cancelled report'; + is $cancel_report->get_extra_field_value('transferred_to'), '1000000002'; + is $cancel_report->get_extra_field_value('End_Date'), '2021-02-09', 'Subscription ends today on cancelled report'; + }; subtest 'cancel staff sub' => sub { + $echo->mock('GetServiceUnitsForObject', \&garden_waste_one_bin); set_fixed_time('2021-03-09T17:00:00Z'); # After sample data collection $mech->get_ok('/waste/12345/garden_cancel'); $mech->submit_form_ok({ with_fields => { name => 'Test McTest', email => 'test@example.org', confirm => 1 } }); diff --git a/templates/web/base/waste/garden/transfer_confirm_addresses.html b/templates/web/base/waste/garden/transfer_confirm_addresses.html new file mode 100644 index 0000000000..a7741b2904 --- /dev/null +++ b/templates/web/base/waste/garden/transfer_confirm_addresses.html @@ -0,0 +1,3 @@ +

Please confirm that you want to transfer the green garden waste subscription:

+

From: [% form.saved_data.previous_ggw_address.label %]

+

To: [% property.address %] diff --git a/templates/web/base/waste/garden/transferred.html b/templates/web/base/waste/garden/transferred.html new file mode 100644 index 0000000000..ae1b0a278e --- /dev/null +++ b/templates/web/base/waste/garden/transferred.html @@ -0,0 +1,19 @@ +[% title = 'The subscription has been transferred' %] +[% PROCESS 'waste/header.html' %] + +

+

+ [% title %] +

+
+

+ Subscription has been successfully transferred +

+
+
+ +[% TRY %][% PROCESS 'waste/_confirmation_after.html' %][% CATCH file %][% END %] + +[% INCLUDE 'waste/_button_show_upcoming.html' %] + +[% INCLUDE footer.html %] diff --git a/templates/web/merton/waste/_more_services_sidebar.html b/templates/web/merton/waste/_more_services_sidebar.html index bb49614948..b20b35c2aa 100644 --- a/templates/web/merton/waste/_more_services_sidebar.html +++ b/templates/web/merton/waste/_more_services_sidebar.html @@ -42,5 +42,13 @@

Assisted collection

  • Set up for assisted collection
  • [% END %] +

    Transfer GGW to property

    + [% IF !show_garden_subscribe %] +

    There is already a garden waste subscription at this property or garden waste is not permitted

    + [% ELSE %] + + [% END %] [% END %]