From cdc210ca61e7975b8f258c179f54a0b704e15b14 Mon Sep 17 00:00:00 2001 From: Moray Jones Date: Mon, 14 Oct 2024 17:09:52 +0100 Subject: [PATCH] [Merton][WW] Allow transfer of ggw subscription When a Green Garden Waste subscriber in Merton moves to another address in Merton, they can transfer the remainder of their ggw subscription to the new address 1) As long as the new address does not already have a sub 2) As long as there is a ggw on their previous property, and it is not in the renewal period 3) They have brought their bin(s) with them This is a staff only feature. https://github.com/mysociety/societyworks/issues/4551 --- perllib/FixMyStreet/App/Controller/Waste.pm | 61 ++++++- .../App/Form/Waste/Garden/Transfer.pm | 162 ++++++++++++++++++ perllib/FixMyStreet/Cobrand/Merton/Waste.pm | 37 ++++ perllib/FixMyStreet/Roles/Cobrand/Echo.pm | 1 - t/app/controller/waste_merton_garden.t | 152 +++++++++++++--- .../garden/transfer_confirm_addresses.html | 3 + .../web/base/waste/garden/transferred.html | 19 ++ .../merton/waste/_more_services_sidebar.html | 8 + 8 files changed, 412 insertions(+), 31 deletions(-) create mode 100644 perllib/FixMyStreet/App/Form/Waste/Garden/Transfer.pm create mode 100644 templates/web/base/waste/garden/transfer_confirm_addresses.html create mode 100644 templates/web/base/waste/garden/transferred.html diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index c25f669001..68326d2643 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,52 @@ 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->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->{current_bins} = $data->{transfer_old_ggw_sub}->{transfer_bin_number}; + $new->{current_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->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 +1316,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 +1398,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 +1406,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 +1656,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..2d7f0da428 100644 --- a/perllib/FixMyStreet/Roles/Cobrand/Echo.pm +++ b/perllib/FixMyStreet/Roles/Cobrand/Echo.pm @@ -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/t/app/controller/waste_merton_garden.t b/t/app/controller/waste_merton_garden.t index a5316be5ad..f5b1b96373 100644 --- a/t/app/controller/waste_merton_garden.t +++ b/t/app/controller/waste_merton_garden.t @@ -41,6 +41,7 @@ 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' }, ); create_contact({ category => 'Cancel Garden Subscription', email => 'garden_cancel@example.com'}, { code => 'Bin_Detail_Quantity', required => 1, automated => 'hidden_field' }, @@ -1164,37 +1165,132 @@ 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('current_containers'), 1, 'Correct bin containers amount set on new report'; + is $new_report->get_extra_field_value('End_Date'), '2021-03-30', 'Subscription time transferred'; + 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('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 %]