Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bexley] Missed collection can't be reported until round confirmed #4932

Merged
merged 12 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions perllib/BexleyAddresses.pm
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ SQL
return [ sort _sort_addresses @$addresses ];
}

sub usrn_for_uprn {
my $uprn = shift;

my $db = connect_db() or return '';

return ( $db->selectrow_hashref(
<<"SQL",
SELECT usrn
FROM postcodes
WHERE uprn = ?
SQL
{ Slice => {} },
$uprn,
) // {} )->{usrn};
}

sub address_for_uprn {
my $uprn = shift;

Expand Down
167 changes: 131 additions & 36 deletions perllib/FixMyStreet/Cobrand/Bexley/Waste.pm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ has 'whitespace' => (
default => sub { Integrations::Whitespace->new(%{shift->feature('whitespace')}) },
);

use constant WORKING_DAYS_WINDOW => 3;

sub waste_fetch_events {
my ( $self, $params ) = @_;

Expand Down Expand Up @@ -133,6 +135,9 @@ sub look_up_property {

my $site = $self->whitespace->GetSiteInfo($uprn);

# We assume USRN is the same between parent and child addresses
my $usrn = BexleyAddresses::usrn_for_uprn($uprn);

# We need to call GetAccountSiteID to get parent UPRN
my %parent_property;
if ( my $site_parent_id = $site->{Site}{SiteParentID} ) {
Expand All @@ -141,8 +146,9 @@ sub look_up_property {
parent_property => {
# NOTE 'AccountSiteUPRN' returned from GetSiteInfo, but
# 'AccountSiteUprn' returned from GetAccountSiteID
id => $parent_data->{AccountSiteUprn},
id => $parent_data->{AccountSiteUprn},
uprn => $parent_data->{AccountSiteUprn},
usrn => $usrn,
}
);
}
Expand All @@ -169,6 +175,7 @@ sub look_up_property {
# and 'uprn' in others, we set both here
id => $site->{AccountSiteUPRN},
uprn => $site->{AccountSiteUPRN},
usrn => $usrn,
address => FixMyStreet::Template::title(
BexleyAddresses::address_for_uprn($uprn) ),
latitude => $site->{Site}->{SiteLatitude},
Expand Down Expand Up @@ -204,9 +211,7 @@ sub bin_services_for_address {
my ($property_logs, $street_logs) = $self->_in_cab_logs($property);

$property->{red_tags} = $property_logs;
$property->{service_updates} = $street_logs;
my %round_exceptions = map { $_->{round} => 1 } @$property_logs;
$property->{round_exceptions} = \%round_exceptions;
$property->{service_updates} = grep { $_->{service_update} } @$street_logs;

# Set certain things outside of services loop
my $containers = $self->_containers($property);
Expand Down Expand Up @@ -331,15 +336,21 @@ sub bin_services_for_address {
$filtered_service->{report_open} = 0;
}

$filtered_service->{report_open}
= $property->{missed_collection_reports}{ $filtered_service->{service_id} } ? 1 : 0;
$filtered_service->{report_locked_out} = 0;
$filtered_service->{report_locked_out_reason} = '';
my $log_reason_prefix = $self->get_in_cab_logs_reason_prefix($filtered_service->{service_id});
if ($log_reason_prefix) {
my @relevant_logs = grep { $_->{reason} =~ /^$log_reason_prefix/ && $_->{round} eq $filtered_service->{round} } @$property_logs;
if (@relevant_logs) {
$filtered_service->{report_locked_out} = 1;
$filtered_service->{report_locked_out_reason} = $relevant_logs[0]->{reason};
}

}

$filtered_service->{report_allowed}
= $self->can_report_missed( $property, $filtered_service );

$filtered_service->{report_locked_out}
= $property->{round_exceptions}{ $filtered_service->{round} } ? 1 : 0;

push @site_services_filtered, $filtered_service;
}

Expand Down Expand Up @@ -458,28 +469,49 @@ sub _recent_collections {
# Returns a hashref of recent in-cab logs for the property, split by USRN and
# UPRN
sub _in_cab_logs {
my ( $self, $property ) = @_;
my ( $self, $property, $all_logs ) = @_;

# Logs are recorded against parent properties, not children
$property = $property->{parent_property} if $property->{parent_property};

my $dt_from = $self->_subtract_working_days(3);
my $cab_logs = $self->whitespace->GetInCabLogsByUprn(
$property->{uprn},
$dt_from->stringify,
);
my $dt_from = $self->_subtract_working_days(WORKING_DAYS_WINDOW);
my $cab_logs;
if ( !$self->{c}->stash->{cab_logs} ) {
my $cab_logs_uprn = $self->whitespace->GetInCabLogsByUprn(
$property->{uprn},
$dt_from->stringify,
);

my $cab_logs_usrn
= $property->{usrn}
? $self->whitespace->GetInCabLogsByUsrn(
$property->{usrn},
$dt_from->stringify,
) : [];

$cab_logs = [ @$cab_logs_uprn, @$cab_logs_usrn ];

# Make cab logs unique by LogID and Reason
my %seen;
@$cab_logs = grep { !$seen{ $_->{LogID} . $_->{Reason} }++ } @$cab_logs;

$self->{c}->stash->{cab_logs} = $cab_logs;
} else {
$cab_logs = $self->{c}->stash->{cab_logs};
}

my @property_logs;
my @street_logs;

return ( \@property_logs, \@street_logs ) unless $cab_logs;

for (@$cab_logs) {
next if !$_->{Reason} || $_->{Reason} eq 'N/A'; # Skip non-exceptional logs
# Skip non-exceptional logs
next if (!$_->{Reason} || $_->{Reason} eq 'N/A') && !$all_logs;

my $logdate = DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S' )->parse_datetime( $_->{LogDate} );

if ( $_->{Uprn} ) {
if ( $_->{Uprn} && $_->{Uprn} eq $property->{uprn} ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it is possible for the UPRN to not match because you are also now fetching the USRN logs above?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep exactly. The USRN logs contain both street-level logs (which have a blank UPRN field) and any property logs for that USRN (hence the need to filter out irrelevant ones here).

push @property_logs, {
uprn => $_->{Uprn},
round => $_->{RoundCode},
Expand All @@ -489,6 +521,7 @@ sub _in_cab_logs {
};
} else {
push @street_logs, {
service_update => $_->{Uprn} ? 0 : 1,
round => $_->{RoundCode},
reason => $_->{Reason},
date => $logdate,
Expand All @@ -506,14 +539,63 @@ sub can_report_missed {
# Cannot make a report if there is already an open one for this service
return 0 if $property->{missed_collection_reports}{ $service->{service_id} };

# Need to be within 3 working days of the last collection
my $last_dt = $property->{recent_collections}{ $service->{round_schedule} };
return 0 unless $last_dt && $self->within_working_days($last_dt, 3);

# Can't make a report if an exception has been logged for the service's round
return 0 if $property->{round_exceptions}{ $service->{round} };
# Prevent reporting if there are service updates
return 0 if $property->{service_updates};

# Prevent reporting if there are red tags on the service
# Red tags are matched to services based on prefix
return 0 if $service->{report_locked_out};

# Needs to be within 3 working days of the last completed round, today
# not included.
# NOTE recent_collections shows the 'ideal'/expected collection date,
# not actual. So we need to check cab logs for actual collection date.
my $last_expected_collection_dt
= $property->{recent_collections}{ $service->{round_schedule} };

if ($last_expected_collection_dt) {
# We need to consider both property and street logs here,
# as either type may log a successful collection
# (i.e. 'Reason' = 'N/A' for given round)
my ( $property_logs, $street_logs )
= $self->_in_cab_logs( $property, 1 );

# If there is a log for this collection, that is when
# the round was completed so we can make a report if
# we're within that time
my ($log_for_round)
= grep { $_->{round} eq $service->{round} }
( @$property_logs, @$street_logs );

# log time needs to be greater than or equal to 3 working days ago,
# less than today
my $min_dt = $self->_subtract_working_days(WORKING_DAYS_WINDOW);
my $today_dt
= DateTime->today( time_zone => FixMyStreet->local_time_zone );
my $now_dt
= DateTime->now( time_zone => FixMyStreet->local_time_zone );

# We need to check if the last collection was made over
# WORKING_DAYS_WINDOW ago because some collections are weekly and some
# fortnightly, but they share a round code prefix.
return 0 if $last_expected_collection_dt < $min_dt && !$service->{next}{is_today};

return ( $log_for_round->{date} < $now_dt
&& $log_for_round->{date} >= $min_dt ) ? 1 : 0
if $log_for_round;

$service->{last}{is_delayed} =
($last_expected_collection_dt < $today_dt && $last_expected_collection_dt >= $min_dt)
|| ($service->{next}{is_today} && $now_dt->hour >= 17) ? 1 : 0;
}

return 1;
# At this point, missed report is not allowed because
# a) collection is marked as delayed
# OR
# b) collection was been made over WORKING_DAYS_WINDOW ago
# OR
# c) new service, so no last collection expected
return 0;
}

# Bexley want services to be ordered by next collection date.
Expand Down Expand Up @@ -888,18 +970,6 @@ sub _subtract_working_days {
return $wd->sub_days( $dt, $day_count );
}

sub within_working_days {
my ($self, $dt, $days, $future) = @_;
my $wd = FixMyStreet::WorkingDays->new(public_holidays => FixMyStreet::Cobrand::UK::public_holidays());
$dt = $wd->add_days($dt, $days)->ymd;
my $today = DateTime->now->set_time_zone(FixMyStreet->local_time_zone)->ymd;
if ( $future ) {
return $today ge $dt;
} else {
return $today le $dt;
}
}

sub waste_munge_report_data {
my ($self, $id, $data) = @_;

Expand Down Expand Up @@ -988,4 +1058,29 @@ sub _bin_location_options {
};
}

sub in_cab_logs_reason_prefixes {
{
'Clear Sacks' => ['MDR-SACK', 'CW-SACK'],
'Paper & Card' => ['PA-1100', 'PA-1280', 'PA-140', 'PA-240', 'PA-55', 'PA-660', 'PA-940', 'PC-180', 'PC-55'],
'Food' => ['FO-140', 'FO-23'],
'Garden' => ['GA-140', 'GA-240'],
'Plastics & Glass' => ['GL-1100', 'GL-1280', 'GL-55', 'GL-660', 'PG-1100', 'PG-1280', 'PG-240', 'PG-360', 'PG-55', 'PG-660', 'PG-940', 'PL-1100', 'PL-1280', 'PL-140', 'PL-55', 'PL-660', 'PL-940'],
'Refuse' => ['RES-1100', 'RES-1280', 'RES-140', 'RES-180', 'RES-240', 'RES-660', 'RES-720', 'RES-940', 'RES-CHAM', 'RES-DBIN', 'RES-SACK'],
}
}

sub get_in_cab_logs_reason_prefix {
my ($self, $service_name) = @_;

my $prefixes = in_cab_logs_reason_prefixes();

foreach my $prefix (keys %$prefixes) {
if (grep { $_ eq $service_name } @{$prefixes->{$prefix}}) {
return $prefix;
}
}

return '';
}

1;