<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package FixMyStreet::Script::Reports;

use Moo;
use FixMyStreet;
use FixMyStreet::DB;
use FixMyStreet::Queue::Item::Report;

has verbose =&gt; ( is =&gt; 'ro' );

has unconfirmed_data =&gt; ( is =&gt; 'ro', default =&gt; sub { {} } );

# Static method, used by send-reports cron script and tests.
# Creates a manager object from provided data and processes it.
sub send {
    my ($verbose, $nomail, $debug, $id) = @_;
    $debug = 1 if $id;

    my $manager = __PACKAGE__-&gt;new(
        verbose =&gt; $verbose,
    );

    my $params = construct_query($debug);
    my $db = FixMyStreet::DB-&gt;schema-&gt;storage;

    my $unsent = FixMyStreet::DB-&gt;resultset('Problem')-&gt;search($params);

    $manager-&gt;log("starting to loop through unsent problem reports...");
    my $unsent_count = 0;
    while (my $row = $unsent-&gt;next) {
        next if $id &amp;&amp; $row-&gt;id != $id;
        $row = $db-&gt;txn_do(sub {
            my $p = FixMyStreet::DB-&gt;resultset('Problem')-&gt;search({ id =&gt; $row-&gt;id }, { for =&gt; \'UPDATE SKIP LOCKED' })-&gt;single;
            $p-&gt;update({ send_state =&gt; 'processing' }) if $p &amp;&amp; $p-&gt;send_state eq 'unprocessed';
        }) or next;
        $unsent_count++;
        my $item = FixMyStreet::Queue::Item::Report-&gt;new(
            report =&gt; $row,
            manager =&gt; $manager,
            verbose =&gt; $verbose,
            nomail =&gt; $nomail,
        );
        $item-&gt;process;
        $db-&gt;txn_do(sub {
            my $p = FixMyStreet::DB-&gt;resultset('Problem')-&gt;search({ id =&gt; $row-&gt;id }, { for =&gt; \'UPDATE' } )-&gt;single;
            $p-&gt;update({ send_state =&gt; 'unprocessed' }) if $p &amp;&amp; $p-&gt;send_state eq 'processing';
        });
    }

    $manager-&gt;end_line($unsent_count);
    $manager-&gt;end_summary_unconfirmed;
}

sub construct_query {
    my ($debug) = @_;

    # Devolved Noop categories (unlikely to be any, but still)
    my @noop_params;
    my $noop_cats = FixMyStreet::DB-&gt;resultset('Contact')-&gt;search({
        'body.can_be_devolved' =&gt; 1,
        'me.send_method' =&gt; 'Noop'
    }, { join =&gt; 'body' });
    while (my $cat = $noop_cats-&gt;next) {
        push @noop_params, [
            \[ "NOT regexp_split_to_array(bodies_str, ',') &amp;&amp; ?", [ {} =&gt; [ $cat-&gt;body_id ] ] ],
            category =&gt; { '!=' =&gt; $cat-&gt;category } ];
    }

    # Noop bodies
    my @noop_bodies = FixMyStreet::DB-&gt;resultset('Body')-&gt;search({ send_method =&gt; 'Noop' })-&gt;all;
    @noop_bodies = map { $_-&gt;id } @noop_bodies;
    push @noop_params, \[ "NOT regexp_split_to_array(bodies_str, ',') &amp;&amp; ?", [ {} =&gt; \@noop_bodies ] ] if @noop_bodies;

    my $params = {
        state =&gt; { -not_in =&gt; [ FixMyStreet::DB::Result::Problem::hidden_states ] },
        send_state =&gt; 'unprocessed',
        @noop_params ? (-and =&gt; \@noop_params) : (),
    };
    if (!$debug) {
        $params-&gt;{'-or'} = [
            send_fail_count =&gt; 0,
            { send_fail_count =&gt; 1, send_fail_timestamp =&gt; { '&lt;', \"current_timestamp - '5 minutes'::interval" } },
            { send_fail_timestamp =&gt; { '&lt;', \"current_timestamp - '30 minutes'::interval" } },
        ];
    }

    return $params;
}

sub end_line {
    my ($self, $unsent_count) = @_;
    return unless $self-&gt;verbose;

    if ($unsent_count) {
        $self-&gt;log("processed all unsent reports (total: $unsent_count)");
    } else {
        $self-&gt;log("no unsent reports were found (must have send_state=unprocessed) -- nothing to send");
    }
}

sub end_summary_unconfirmed {
    my $self = shift;
    return unless $self-&gt;verbose;

    my %unconfirmed_data = %{$self-&gt;unconfirmed_data};
    print "Council email addresses that need checking:\n" if keys %unconfirmed_data;
    foreach my $e (keys %unconfirmed_data) {
        foreach my $c (keys %{$unconfirmed_data{$e}}) {
            my $data = $unconfirmed_data{$e}{$c};
            print "    " . $data-&gt;{count} . " problem, to $e category $c (" . $data-&gt;{note} . ")\n";
        }
    }
}

sub end_summary_failures {
    my $self = shift;

    my $sending_errors = '';
    my $unsent = FixMyStreet::DB-&gt;resultset('Problem')-&gt;search( {
        send_state =&gt; 'unprocessed',
        state =&gt; [ FixMyStreet::DB::Result::Problem::open_states() ],
        bodies_str =&gt; { '!=', undef },
        send_fail_count =&gt; { '&gt;', 0 }
    })-&gt;order_by('-confirmed');
    my %bodies;
    while (my $row = $unsent-&gt;next) {
        my $base_url = FixMyStreet-&gt;config('BASE_URL');
        my $key =  join ', ', @{ $row-&gt;body_names };
        $bodies{$key} ||= [];
        push @{ $bodies{$key} }, $row-&gt;id;
        $sending_errors .= "\n" . '=' x 80 . "\n\n" . "* " . $base_url . "/report/" . $row-&gt;id . ", failed "
            . $row-&gt;send_fail_count . " times, last at " . $row-&gt;send_fail_timestamp
            . ", reason " . $row-&gt;send_fail_reason . "\n";
    }
    if ($sending_errors) {
        my $count = $unsent-&gt;count;
        my $bodies = join "\n", map {
            my $n = scalar @{ $bodies{$_} };
            "$_ ($n): " . join ', ', @{ $bodies{$_} }
        } keys %bodies;

        print "The following $count reports had problems sending:\n$bodies\n$sending_errors";
    }
}

sub log {
    my ($self, $msg) = @_;
    return unless $self-&gt;verbose;
    STDERR-&gt;print("[fmsd] $msg\n");
}

1;
</pre></body></html>