aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm20
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm7
-rw-r--r--perllib/FixMyStreet/App/Controller/Open311.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm8
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm5
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm49
-rw-r--r--perllib/FixMyStreet/Cobrand/Rutland.pm46
-rw-r--r--perllib/FixMyStreet/Cobrand/UK.pm3
-rw-r--r--perllib/FixMyStreet/Cobrand/Zurich.pm573
-rw-r--r--perllib/FixMyStreet/DB/Factories.pm133
-rw-r--r--perllib/FixMyStreet/DB/Result/Body.pm8
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm14
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Problem.pm4
-rw-r--r--perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm35
-rw-r--r--perllib/FixMyStreet/Script/Reports.pm4
-rw-r--r--perllib/FixMyStreet/SendReport/Open311.pm1
-rw-r--r--perllib/FixMyStreet/SendReport/Zurich.pm8
-rw-r--r--perllib/FixMyStreet/TestAppProve.pm90
18 files changed, 562 insertions, 448 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 253840082..c12fdf9b9 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -340,6 +340,7 @@ sub update_contacts : Private {
}
$c->forward('update_extra_fields', [ $contact ]);
+ $c->forward('contact_cobrand_extra_fields', [ $contact ]);
if ( %errors ) {
$c->stash->{updated} = _('Please correct the errors below');
@@ -444,6 +445,8 @@ sub body_params : Private {
my %defaults = map { $_ => '' } @fields;
%defaults = ( %defaults,
send_comments => 0,
+ fetch_problems => 0,
+ blank_updates_permitted => 0,
suppress_alerts => 0,
comment_user_id => undef,
send_extended_statuses => 0,
@@ -1008,11 +1011,18 @@ sub report_edit_location : Private {
my ($lat, $lon) = map { Utils::truncate_coordinate($_) } $problem->latitude, $problem->longitude;
if ( $c->stash->{latitude} != $lat || $c->stash->{longitude} != $lon ) {
+ # The two actions below change the stash, setting things up for e.g. a
+ # new report. But here we're only doing it in order to check the found
+ # bodies match; we don't want to overwrite the existing report data if
+ # this lookup is bad. So let's save the stash and restore it after the
+ # comparison.
+ my $safe_stash = { %{$c->stash} };
$c->forward('/council/load_and_check_areas', []);
$c->forward('/report/new/setup_categories_and_bodies');
my %allowed_bodies = map { $_ => 1 } @{$problem->bodies_str_ids};
my @new_bodies = @{$c->stash->{bodies_to_list}};
my $bodies_match = grep { exists( $allowed_bodies{$_} ) } @new_bodies;
+ $c->stash($safe_stash);
return unless $bodies_match;
$problem->latitude($c->stash->{latitude});
$problem->longitude($c->stash->{longitude});
@@ -1035,7 +1045,6 @@ sub categories_for_point : Private {
# Remove the "Pick a category" option
shift @{$c->stash->{category_options}} if @{$c->stash->{category_options}};
- $c->stash->{category_options_copy} = $c->stash->{category_options};
$c->stash->{categories_hash} = { map { $_->{name} => 1 } @{$c->stash->{category_options}} };
}
@@ -1610,6 +1619,15 @@ sub user_edit : Path('user_edit') : Args(1) {
return 1;
}
+sub contact_cobrand_extra_fields : Private {
+ my ( $self, $c, $contact ) = @_;
+
+ my $extra_fields = $c->cobrand->call_hook('contact_extra_fields');
+ foreach ( @$extra_fields ) {
+ $contact->set_extra_metadata( $_ => $c->get_param("extra[$_]") );
+ }
+}
+
sub user_cobrand_extra_fields : Private {
my ( $self, $c ) = @_;
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
index 661579f95..7cdf150aa 100644
--- a/perllib/FixMyStreet/App/Controller/Dashboard.pm
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -345,6 +345,8 @@ Generates a CSV output, given a 'csv' stash hashref containing:
* columns: an arrayref of the columns (looked up in the row's as_hashref, plus
the following: user_name_display, acknowledged, fixed, closed, wards,
local_coords_x, local_coords_y, url).
+* extra_data: If present, a function that is passed the report and returns a
+hashref of extra data to include that can be used by 'columns'.
=cut
@@ -398,6 +400,11 @@ sub generate_csv : Private {
$report->local_coords;
$hashref->{url} = join '', $c->cobrand->base_url_for_report($report), $report->url;
+ if (my $fn = $c->stash->{csv}->{extra_data}) {
+ my $extra = $fn->($report);
+ $hashref = { %$hashref, %$extra };
+ }
+
$csv->combine(
@{$hashref}{
@{$c->stash->{csv}->{columns}}
diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm
index ddc976f2d..b50728b77 100644
--- a/perllib/FixMyStreet/App/Controller/Open311.pm
+++ b/perllib/FixMyStreet/App/Controller/Open311.pm
@@ -308,6 +308,7 @@ sub get_requests : Private {
# Only provide access to the published reports
my $states = FixMyStreet::DB::Result::Problem->visible_states();
delete $states->{unconfirmed};
+ delete $states->{submitted};
my $criteria = {
state => [ keys %$states ]
};
@@ -410,6 +411,7 @@ sub get_request : Private {
my $states = FixMyStreet::DB::Result::Problem->visible_states();
delete $states->{unconfirmed};
+ delete $states->{submitted};
my $criteria = {
state => [ keys %$states ],
id => $id,
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index 7062f481f..df4dc5f77 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -221,9 +221,15 @@ sub format_problem_for_display : Private {
if ( $c->stash->{ajax} ) {
$c->res->content_type('application/json; charset=utf-8');
+
+ # encode_json doesn't like DateTime objects, so strip them out
+ my $report_hashref = $c->cobrand->problem_as_hashref( $problem, $c );
+ delete $report_hashref->{created};
+ delete $report_hashref->{confirmed};
+
my $content = encode_json(
{
- report => $c->cobrand->problem_as_hashref( $problem, $c ),
+ report => $report_hashref,
updates => $c->cobrand->updates_as_hashref( $problem, $c ),
}
);
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 888110429..e4e82f091 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -967,6 +967,7 @@ sub process_report : Private {
# set defaults that make sense
$report->state('unconfirmed');
+ $report->state('submitted') if $c->cobrand->moniker eq 'zurich';
# save the cobrand and language related information
$report->cobrand( $c->cobrand->moniker );
@@ -1002,7 +1003,7 @@ sub set_report_extras : Private {
foreach my $contact (@$contacts) {
my $metas = $contact->get_metadata_for_input;
foreach my $field ( @$metas ) {
- if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field->{code})) {
+ if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field)) {
unless ( $c->get_param($param_prefix . $field->{code}) ) {
$c->stash->{field_errors}->{ $field->{code} } = _('This information is required');
}
@@ -1019,7 +1020,7 @@ sub set_report_extras : Private {
my $metas = $extra_fields->get_extra_fields;
$param_prefix = "extra[" . $extra_fields->id . "]";
foreach my $field ( @$metas ) {
- if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field->{code})) {
+ if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field)) {
unless ( $c->get_param($param_prefix . $field->{code}) ) {
$c->stash->{field_errors}->{ $field->{code} } = _('This information is required');
}
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index c6ca5c56b..8c4d8be53 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -667,6 +667,7 @@ sub admin_pages {
$pages->{responsepriority_edit} = [ undef, undef ];
};
if ( $user->has_body_permission_to('user_edit') ) {
+ $pages->{reports} = [ _('Reports'), 2 ];
$pages->{users} = [ _('Users'), 6 ];
$pages->{user_edit} = [ undef, undef ];
}
@@ -727,7 +728,7 @@ sub available_permissions {
# trusted => _("Trusted to make reports that don't need to be inspected"),
},
_("Users") => {
- user_edit => _("Edit other users' details"),
+ user_edit => _("Edit users' details/search for their reports"),
user_manage_permissions => _("Edit other users' permissions"),
user_assign_body => _("Grant access to the admin"),
user_assign_areas => _("Assign users to areas"), # future use
@@ -1159,50 +1160,6 @@ sub jurisdiction_id_example {
return $self->moniker;
}
-=item body_details_data
-
-Returns a list of bodies to create with ensure_body. These
-are mostly just passed to ->find_or_create, but there is some
-pre-processing so that you can enter:
-
- area_id => 123,
- parent => 'Big Town',
-
-instead of
-
- body_areas => [ { area_id => 123 } ],
- parent => { name => 'Big Town' },
-
-For example:
-
- return (
- {
- name => 'Big Town',
- },
- {
- name => 'Small town',
- parent => 'Big Town',
- area_id => 1234,
- },
-
-
-=cut
-
-sub body_details_data {
- return ();
-}
-
-=item contact_details_data
-
-Returns a list of contact_data to create with setup_contacts.
-See Zurich for an example.
-
-=cut
-
-sub contact_details_data {
- return ()
-}
-
=item lookup_by_ref_regex
Returns a regex to match postcode form input against to determine if a lookup
@@ -1221,7 +1178,7 @@ Return true if an Open311 service attribute should be a hidden field.
sub category_extra_hidden {
my ($self, $meta) = @_;
- return 0;
+ return 0;
}
=item reputation_increment_states/reputation_decrement_states
diff --git a/perllib/FixMyStreet/Cobrand/Rutland.pm b/perllib/FixMyStreet/Cobrand/Rutland.pm
new file mode 100644
index 000000000..c0f9dd197
--- /dev/null
+++ b/perllib/FixMyStreet/Cobrand/Rutland.pm
@@ -0,0 +1,46 @@
+package FixMyStreet::Cobrand::Rutland;
+use base 'FixMyStreet::Cobrand::UKCouncils';
+
+use strict;
+use warnings;
+
+sub council_area_id { return 2600; }
+sub council_area { return 'Rutland'; }
+sub council_name { return 'Rutland County Council'; }
+sub council_url { return 'rutland'; }
+
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+
+ my $extra = $row->get_extra_fields;
+ push @$extra, { name => 'external_id', value => $row->id };
+ push @$extra, { name => 'title', value => $row->title };
+ push @$extra, { name => 'description', value => $row->detail };
+
+ if ($h->{closest_address}) {
+ push @$extra, { name => 'closest_address', value => $h->{closest_address} }
+ }
+ $row->set_extra_fields( @$extra );
+
+ $params->{multi_photos} = 1;
+}
+
+sub example_places {
+ return ( 'LE15 6HP', 'High Street', 'Oakham' );
+}
+
+sub disambiguate_location {
+ my $self = shift;
+ my $string = shift;
+
+ return {
+ bounds => [52.524755166940075, -0.8217480325342802, 52.7597945702699, -0.4283542728893742]
+ };
+}
+
+sub pin_colour {
+ my ( $self, $p, $context ) = @_;
+ return 'green' if $p->is_fixed || $p->is_closed;
+ return 'yellow';
+}
+1;
diff --git a/perllib/FixMyStreet/Cobrand/UK.pm b/perllib/FixMyStreet/Cobrand/UK.pm
index e1f5e565f..0ecb6a7c6 100644
--- a/perllib/FixMyStreet/Cobrand/UK.pm
+++ b/perllib/FixMyStreet/Cobrand/UK.pm
@@ -396,7 +396,8 @@ sub lookup_by_ref_regex {
sub category_extra_hidden {
my ($self, $meta) = @_;
- return 1 if $meta eq 'usrn' || $meta eq 'asset_id';
+ return 1 if $meta->{code} eq 'usrn' || $meta->{code} eq 'asset_id';
+ return 1 if $meta->{automated} eq 'hidden_field';
return 0;
}
diff --git a/perllib/FixMyStreet/Cobrand/Zurich.pm b/perllib/FixMyStreet/Cobrand/Zurich.pm
index 06244ccd4..c8af63987 100644
--- a/perllib/FixMyStreet/Cobrand/Zurich.pm
+++ b/perllib/FixMyStreet/Cobrand/Zurich.pm
@@ -10,7 +10,6 @@ use DateTime::Format::Pg;
use strict;
use warnings;
-use feature 'say';
use utf8;
=head1 NAME
@@ -56,8 +55,7 @@ you already have, and the countres set so that they shouldn't in future.
=cut
sub setup_states {
- FixMyStreet::DB::Result::Problem->visible_states_add('unconfirmed');
- FixMyStreet::DB::Result::Problem->visible_states_remove('investigating');
+ FixMyStreet::DB::Result::Problem->visible_states_remove('not contactable');
}
sub shorten_recency_if_new_greater_than_fixed {
@@ -67,7 +65,7 @@ sub shorten_recency_if_new_greater_than_fixed {
sub pin_colour {
my ( $self, $p, $context ) = @_;
return 'green' if $p->is_fixed || $p->is_closed;
- return 'red' if $p->state eq 'unconfirmed' || $p->state eq 'confirmed';
+ return 'red' if $p->state eq 'submitted' || $p->state eq 'confirmed';
return 'yellow';
}
@@ -98,35 +96,11 @@ sub prettify_dt {
return Utils::prettify_dt( $dt, 'zurich' );
}
-# problem already has a concept of is_fixed/is_closed, but Zurich has different
-# workflow for this here.
-#
-# TODO: look at more elegant way of doing this, for example having ::DB::Problem
-# consider cobrand specific state config?
-
-sub zurich_closed_states {
- my $states = {
- 'fixed - council' => 1,
- 'closed' => 1, # extern
- 'hidden' => 1,
- 'investigating' => 1, # wish
- 'unable to fix' => 1, # jurisdiction unknown
- 'partial' => 1, # not contactable
- };
-
- return wantarray ? keys %{ $states } : $states;
-}
-
-sub problem_is_closed {
- my ($self, $problem) = @_;
- return exists $self->zurich_closed_states->{ $problem->state } ? 1 : 0;
-}
-
sub zurich_public_response_states {
my $states = {
'fixed - council' => 1,
- 'closed' => 1, # extern
- 'unable to fix' => 1, # jurisdiction unknown
+ 'external' => 1,
+ 'wish' => 1,
};
return wantarray ? keys %{ $states } : $states;
@@ -134,9 +108,9 @@ sub zurich_public_response_states {
sub zurich_user_response_states {
my $states = {
+ 'jurisdiction unknown' => 1,
'hidden' => 1,
- 'investigating' => 1, # wish
- 'partial' => 1, # not contactable
+ 'not contactable' => 1,
};
return wantarray ? keys %{ $states } : $states;
@@ -160,43 +134,33 @@ sub problem_as_hashref {
my $hashref = $problem->as_hashref( $ctx );
- if ( $problem->state eq 'unconfirmed' ) {
- for my $var ( qw( photo detail state state_t is_fixed meta ) ) {
+ if ( $problem->state eq 'submitted' ) {
+ for my $var ( qw( photo is_fixed meta ) ) {
delete $hashref->{ $var };
}
$hashref->{detail} = _('This report is awaiting moderation.');
$hashref->{title} = _('This report is awaiting moderation.');
- $hashref->{state} = 'submitted';
- $hashref->{state_t} = _('Submitted');
$hashref->{banner_id} = 'closed';
} else {
+ if ( $problem->state eq 'confirmed' || $problem->state eq 'external' ) {
+ $hashref->{banner_id} = 'closed';
+ } elsif ( $problem->is_fixed || $problem->is_closed ) {
+ $hashref->{banner_id} = 'fixed';
+ } else {
+ $hashref->{banner_id} = 'progress';
+ }
+
if ( $problem->state eq 'confirmed' ) {
$hashref->{state} = 'open';
$hashref->{state_t} = _('Open');
- $hashref->{banner_id} = 'closed';
- } elsif ( $problem->state eq 'closed' ) {
- $hashref->{state} = 'extern'; # is this correct?
- $hashref->{banner_id} = 'closed';
- $hashref->{state_t} = _('Extern');
- } elsif ( $problem->state eq 'unable to fix' ) {
- $hashref->{state} = 'jurisdiction unknown'; # is this correct?
- $hashref->{state_t} = _('Jurisdiction Unknown');
- $hashref->{banner_id} = 'fixed'; # green
- } elsif ( $problem->state eq 'partial' ) {
- $hashref->{state} = 'not contactable'; # is this correct?
- $hashref->{state_t} = _('Not contactable');
- # no banner_id as hidden
- } elsif ( $problem->state eq 'investigating' ) {
- $hashref->{state} = 'wish'; # is this correct?
- $hashref->{state_t} = _('Wish');
+ } elsif ( $problem->state eq 'wish' ) {
+ $hashref->{state_t} = _('Closed');
} elsif ( $problem->is_fixed ) {
$hashref->{state} = 'closed';
- $hashref->{banner_id} = 'fixed';
$hashref->{state_t} = _('Closed');
- } elsif ( $problem->state eq 'in progress' || $problem->state eq 'planned' ) {
+ } elsif ( $problem->state eq 'feedback pending' ) {
$hashref->{state} = 'in progress';
- $hashref->{state_t} = _('In progress');
- $hashref->{banner_id} = 'progress';
+ $hashref->{state_t} = FixMyStreet::DB->resultset("State")->display('in progress');
}
}
@@ -210,13 +174,13 @@ sub updates_as_hashref {
my $hashref = {};
- if ( $problem->state eq 'fixed - council' || $problem->state eq 'closed' ) {
+ if ($self->problem_has_public_response($problem)) {
$hashref->{update_pp} = $self->prettify_dt( $problem->lastupdate );
- if ( $problem->state eq 'fixed - council' ) {
+ if ( $problem->state ne 'external' ) {
$hashref->{details} = FixMyStreet::App::View::Web::add_links(
$problem->get_extra_metadata('public_response') || '' );
- } elsif ( $problem->state eq 'closed' ) {
+ } else {
$hashref->{details} = sprintf( _('Assigned to %s'), $problem->body($ctx)->name );
}
}
@@ -229,6 +193,7 @@ sub updates_as_hashref {
# boolean whether that indexed photo can be shown.
sub allow_photo_display {
my ( $self, $r, $num ) = @_;
+ return unless $r;
my $publish_photo;
if (blessed $r) {
$publish_photo = $r->get_extra_metadata('publish_photo');
@@ -250,10 +215,6 @@ sub allow_photo_display {
return $i + 1;
}
-sub show_unconfirmed_reports {
- 1;
-}
-
sub get_body_sender {
my ( $self, $body, $category ) = @_;
return { method => 'Zurich' };
@@ -324,25 +285,23 @@ sub overdue {
return 0 unless $w;
# call with previous state
- if ( $problem->state eq 'unconfirmed' ) {
+ if ( $problem->state eq 'submitted' ) {
# One working day
$w = add_days( $w, 1 );
return $w < DateTime->now() ? 1 : 0;
- } elsif ( $problem->state eq 'confirmed' || $problem->state eq 'in progress' || $problem->state eq 'planned' ) {
+ } elsif ( $problem->state eq 'confirmed' || $problem->state eq 'in progress' || $problem->state eq 'feedback pending' ) {
# States which affect the subdiv_overdue statistic. TODO: this may no longer be required
# Six working days from creation
$w = add_days( $w, 6 );
return $w < DateTime->now() ? 1 : 0;
# call with new state
- } elsif ( $self->problem_is_closed($problem) ) {
+ } else {
# States which affect the closed_overdue statistic
# Five working days from moderation (so 6 from creation)
$w = add_days( $w, 6 );
return $w < DateTime->now() ? 1 : 0;
- } else {
- return 0;
}
}
@@ -430,6 +389,13 @@ sub admin_pages {
'users' => [_('Users'), 3],
'user_edit' => [undef, undef],
};
+
+ # There are some pages that only super users can see
+ if ($self->{c}->user->is_superuser) {
+ $pages->{states} = [ _('States'), 8 ];
+ $pages->{config} = [ _('Configuration'), 9];
+ };
+
return $pages if $type eq 'super';
}
@@ -471,14 +437,14 @@ sub admin {
$order .= ' desc' if $dir;
# XXX No multiples or missing bodies
- $c->stash->{unconfirmed} = $c->cobrand->problems->search({
- state => [ 'unconfirmed', 'confirmed' ],
+ $c->stash->{submitted} = $c->cobrand->problems->search({
+ state => [ 'submitted', 'confirmed' ],
bodies_str => $c->stash->{body}->id,
}, {
order_by => $order,
});
$c->stash->{approval} = $c->cobrand->problems->search({
- state => 'planned',
+ state => 'feedback pending',
bodies_str => $c->stash->{body}->id,
}, {
order_by => $order,
@@ -486,7 +452,7 @@ sub admin {
my $page = $c->get_param('p') || 1;
$c->stash->{other} = $c->cobrand->problems->search({
- state => { -not_in => [ 'unconfirmed', 'confirmed', 'planned' ] },
+ state => { -not_in => [ 'submitted', 'confirmed', 'feedback pending' ] },
bodies_str => \@all,
}, {
order_by => $order,
@@ -512,7 +478,7 @@ sub admin {
order_by => $order
} );
$c->stash->{reports_unpublished} = $c->cobrand->problems->search( {
- state => 'planned',
+ state => 'feedback pending',
bodies_str => $body->parent->id,
}, {
order_by => $order
@@ -529,6 +495,15 @@ sub admin {
}
}
+sub category_options {
+ my ($self, $c) = @_;
+ my @categories = $c->model('DB::Contact')->not_deleted->all;
+ $c->stash->{category_options} = [ map { {
+ name => $_->category, value => $_->category,
+ abbreviation => $_->get_extra_metadata('abbreviation'),
+ } } @categories ];
+}
+
sub admin_report_edit {
my $self = shift;
my $c = $self->{c};
@@ -539,6 +514,8 @@ sub admin_report_edit {
if ($type ne 'super') {
my %allowed_bodies = map { $_->id => 1 } ( $body->bodies->all, $body );
+ # SDMs can see parent reports but not edit them
+ $allowed_bodies{$body->parent->id} = 1 if $type eq 'sdm';
$c->detach( '/page_error_404_not_found' )
unless $allowed_bodies{$problem->bodies_str};
}
@@ -550,8 +527,7 @@ sub admin_report_edit {
$c->stash->{bodies} = \@bodies;
# Can change category to any other
- my @categories = $c->model('DB::Contact')->not_deleted->all;
- $c->stash->{category_options} = [ map { { name => $_->category, value => $_->category } } @categories ];
+ $self->category_options($c);
} elsif ($type eq 'dm') {
@@ -565,8 +541,7 @@ sub admin_report_edit {
$c->stash->{bodies} = \@bodies;
# Can change category to any other
- my @categories = $c->model('DB::Contact')->not_deleted->all;
- $c->stash->{category_options} = [ map { { name => $_->category, value => $_->category } } @categories ];
+ $self->category_options($c);
}
@@ -636,8 +611,7 @@ sub admin_report_edit {
my $state = $c->get_param('state') || '';
my $oldstate = $problem->state;
- my $closure_states = $self->zurich_closed_states;
- delete $closure_states->{'fixed - council'}; # may not be needed?
+ my $closure_states = { map { $_ => 1 } FixMyStreet::DB::Result::Problem->closed_states(), FixMyStreet::DB::Result::Problem->hidden_states() };
my $old_closure_state = $problem->get_extra_metadata('closure_status') || '';
@@ -662,19 +636,19 @@ sub admin_report_edit {
$self->update_admin_log($c, $problem, "Changed category from $old_cat to $new_cat");
$redirect = 1 if $cat->body_id ne $body->id;
} elsif ( $oldstate ne $state and $closure_states->{$state} and
- $oldstate ne 'planned' || $old_closure_state ne $state)
+ $oldstate ne 'feedback pending' || $old_closure_state ne $state)
{
# for these states
- # - closed (Extern)
- # - investigating (Wish)
+ # - external
+ # - wish
# - hidden
- # - partial (Not contactable)
- # - unable to fix (Jurisdiction unknown)
- # we divert to planned (Rueckmeldung ausstehend) and set closure_status to the requested state
+ # - not contactable
+ # - jurisdiction unknown
+ # we divert to feedback pending (Rueckmeldung ausstehend) and set closure_status to the requested state
# From here, the DM can reply to the user, triggering the setting of problem to correct state
$problem->set_extra_metadata( closure_status => $state );
- $self->set_problem_state($c, $problem, 'planned');
- $state = 'planned';
+ $self->set_problem_state($c, $problem, 'feedback pending');
+ $state = 'feedback pending';
$problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) );
} elsif ( my $subdiv = $c->get_param('body_subdivision') ) {
@@ -687,18 +661,18 @@ sub admin_report_edit {
} else {
if ($state) {
- if ($oldstate eq 'unconfirmed' and $state ne 'unconfirmed') {
+ if ($oldstate eq 'submitted' and $state ne 'submitted') {
# only set this for the first state change
$problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) );
}
$self->set_problem_state($c, $problem, $state)
unless $closure_states->{$state};
- # we'll defer to 'planned' clause below to change the state
+ # we'll defer to 'feedback pending' clause below to change the state
}
}
- if ($problem->state eq 'planned') {
+ if ($problem->state eq 'feedback pending') {
# Rueckmeldung ausstehend
# override $state from the metadata set above
$state = $problem->get_extra_metadata('closure_status') || '';
@@ -711,7 +685,7 @@ sub admin_report_edit {
$moderated++;
$closed++;
}
- elsif ($state =~/^(closed|investigating)$/) { # Extern | Wish
+ elsif ($state =~/^(external|wish)$/) {
$moderated++;
# Nested if instead of `and` because in these cases, we *don't*
# want to close unless we have body_external (so we don't want
@@ -724,7 +698,7 @@ sub admin_report_edit {
if ($problem->external_body && $c->get_param('publish_response')) {
$problem->whensent( undef );
$self->set_problem_state($c, $problem, $state);
- my $template = ($state eq 'investigating') ? 'problem-wish.txt' : 'problem-external.txt';
+ my $template = ($state eq 'wish') ? 'problem-wish.txt' : 'problem-external.txt';
_admin_send_email( $c, $template, $problem );
$redirect = 0;
$closed++;
@@ -770,7 +744,7 @@ sub admin_report_edit {
# send external_message if provided and state is *now* Wish|Extern
# e.g. was already, or was set in the Rueckmeldung ausstehend clause above.
if ( my $external_message = $c->get_param('external_message')
- and $problem->state =~ /^(closed|investigating)$/)
+ and $problem->state =~ /^(external|wish)$/)
{
my $external = $problem->external_body;
my $external_body = $c->model('DB::Body')->find($external)
@@ -782,7 +756,7 @@ sub admin_report_edit {
$problem->add_to_comments( {
text => (
sprintf '(%s %s) %s',
- $state eq 'closed' ?
+ $state eq 'external' ?
_('Forwarded to external body') :
_('Forwarded wish to external body'),
$external_body->name,
@@ -841,10 +815,13 @@ sub admin_report_edit {
if ($type eq 'sdm') {
+ my $editable = $type eq 'sdm' && $body->id eq $problem->bodies_str;
+ $c->stash->{sdm_disabled} = $editable ? '' : 'disabled';
+
# Has cut-down edit template for adding update and sending back up only
$c->stash->{template} = 'admin/report_edit-sdm.html';
- if ($c->get_param('send_back') or $c->get_param('not_contactable')) {
+ if ($editable && $c->get_param('send_back') or $c->get_param('not_contactable')) {
# SDM can send back a report either to be assigned to a different
# subdivision, or because the customer was not contactable.
# We handle these in the same way but with different statuses.
@@ -856,8 +833,8 @@ sub admin_report_edit {
$problem->bodies_str( $body->parent->id );
if ($not_contactable) {
# we can't directly set state, but mark the closure_status for DM to confirm.
- $self->set_problem_state($c, $problem, 'planned');
- $problem->set_extra_metadata( closure_status => 'partial');
+ $self->set_problem_state($c, $problem, 'feedback pending');
+ $problem->set_extra_metadata( closure_status => 'not contactable');
}
else {
$self->set_problem_state($c, $problem, 'confirmed');
@@ -870,7 +847,7 @@ sub admin_report_edit {
# Make sure the problem's time_spent is updated
$self->update_admin_log($c, $problem);
$c->res->redirect( '/admin/summary' );
- } elsif ($c->get_param('submit')) {
+ } elsif ($editable && $c->get_param('submit')) {
$c->forward('/auth/check_csrf_token');
my $db_update = 0;
@@ -904,7 +881,7 @@ sub admin_report_edit {
$problem->set_extra_metadata( subdiv_overdue => $self->overdue( $problem ) );
$problem->bodies_str( $body->parent->id );
$problem->whensent( undef );
- $self->set_problem_state($c, $problem, 'planned');
+ $self->set_problem_state($c, $problem, 'feedback pending');
$problem->update;
$c->res->redirect( '/admin/summary' );
}
@@ -932,61 +909,52 @@ sub stash_states {
my @states = (
{
# Erfasst
- state => 'unconfirmed',
- trans => _('Submitted'),
- unconfirmed => 1,
+ state => 'submitted',
+ submitted => 1,
hidden => 1,
},
{
# Aufgenommen
state => 'confirmed',
- trans => _('Open'),
- unconfirmed => 1,
+ submitted => 1,
},
{
# Unsichtbar (hidden)
state => 'hidden',
- trans => _('Hidden'),
- unconfirmed => 1,
+ submitted => 1,
hidden => 1,
},
{
# Extern
- state => 'closed',
- trans => _('Extern'),
+ state => 'external',
},
{
# Zustaendigkeit unbekannt
- state => 'unable to fix',
- trans => _('Jurisdiction unknown'),
+ state => 'jurisdiction unknown',
},
{
- # Wunsch (hidden)
- state => 'investigating',
- trans => _('Wish'),
+ # Wunsch
+ state => 'wish',
},
{
# Nicht kontaktierbar (hidden)
- state => 'partial',
- trans => _('Not contactable'),
+ state => 'not contactable',
},
);
- my %state_trans = map { $_->{state} => $_->{trans} } @states;
my $state = $problem->state;
# Rueckmeldung ausstehend may also indicate the status it's working towards.
push @states, do {
- if ($state eq 'planned' and my $closure_status = $problem->get_extra_metadata('closure_status')) {
+ if ($state eq 'feedback pending' and my $closure_status = $problem->get_extra_metadata('closure_status')) {
{
state => $closure_status,
- trans => sprintf '%s (%s)', _('Planned'), $state_trans{$closure_status},
+ trans => sprintf 'Rückmeldung ausstehend (%s)', FixMyStreet::DB->resultset("State")->display($closure_status),
};
}
else {
{
- state => 'planned',
- trans => _('Planned'),
+ state => 'feedback pending',
};
}
};
@@ -994,25 +962,22 @@ sub stash_states {
if ($state eq 'in progress') {
push @states, {
state => 'in progress',
- trans => _('In progress'),
};
}
elsif ($state eq 'fixed - council') {
push @states, {
state => 'fixed - council',
- trans => _('Closed'),
};
}
- elsif ($state =~/^(hidden|unconfirmed)$/) {
+ elsif ($state =~/^(hidden|submitted)$/) {
@states = grep { $_->{$state} } @states;
}
$c->stash->{states} = \@states;
- $c->stash->{states_trans} = { map { $_->{state} => $_->{trans} } @states }; # [% states_trans.${problem.state} %]
# stash details about the public response
$c->stash->{default_public_response} = "\nFreundliche Grüsse\n\nIhre Stadt Zürich\n";
$c->stash->{show_publish_response} =
- ($problem->state eq 'planned');
+ ($problem->state eq 'feedback pending');
}
=head2 _admin_send_email
@@ -1044,7 +1009,7 @@ sub _admin_send_email {
sub munge_sendreport_params {
my ($self, $row, $h, $params) = @_;
- if ($row->state =~ /^(closed|investigating)$/) {
+ if ($row->state =~ /^(external|wish)$/) {
# we attach images to reports sent to external bodies
my $photoset = $row->get_photoset();
my $num = $photoset->num_images
@@ -1114,138 +1079,34 @@ sub admin_stats {
my $self = shift;
my $c = $self->{c};
- my %date_params;
+ my %optional_params;
my $ym = $c->get_param('ym');
my ($m, $y) = $ym ? ($ym =~ /^(\d+)\.(\d+)$/) : ();
$c->stash->{ym} = $ym;
if ($y && $m) {
$c->stash->{start_date} = DateTime->new( year => $y, month => $m, day => 1 );
$c->stash->{end_date} = $c->stash->{start_date} + DateTime::Duration->new( months => 1 );
- $date_params{created} = {
+ $optional_params{created} = {
'>=', DateTime::Format::Pg->format_datetime($c->stash->{start_date}),
'<', DateTime::Format::Pg->format_datetime($c->stash->{end_date}),
};
}
+ my $cat = $c->stash->{category} = $c->get_param('category');
+ $optional_params{category} = $cat if $cat;
+
my %params = (
- %date_params,
+ %optional_params,
state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
);
if ( $c->get_param('export') ) {
- my $problems = $c->model('DB::Problem')->search(
- {%date_params},
- {
- join => 'admin_log_entries',
- distinct => 1,
- columns => [
- 'id', 'created',
- 'latitude', 'longitude',
- 'cobrand', 'category',
- 'state', 'user_id',
- 'external_body',
- 'title', 'detail',
- 'photo',
- 'whensent', 'lastupdate',
- 'service',
- 'extra',
- { sum_time_spent => { sum => 'admin_log_entries.time_spent' } },
- ]
- }
- );
- my @fields = (
- 'Report ID',
- 'Created',
- 'Sent to Agency',
- 'Last Updated',
- 'E',
- 'N',
- 'Category',
- 'Status',
- 'Closure Status',
- 'UserID',
- 'External Body',
- 'Time Spent',
- 'Title',
- 'Detail',
- 'Media URL',
- 'Interface Used',
- 'Council Response',
- 'Strasse',
- 'Mast-Nr.',
- 'Haus-Nr.',
- 'Hydranten-Nr.',
- );
-
- my $body = "";
- require Text::CSV;
- my $csv = Text::CSV->new({ binary => 1 });
-
- if ($csv->combine(@fields)) {
- $body .= $csv->string . "\n";
- }
- else {
- $body .= sprintf "{{error emitting CSV line: %s}}\n", $csv->error_diag;
- }
-
- while ( my $report = $problems->next ) {
- my $external_body;
- my $body_name = "";
- if ( $external_body = $report->body($c) ) {
- $body_name = $external_body->name || '[Unknown body]';
- }
-
- my $detail = $report->detail;
- my $public_response = $report->get_extra_metadata('public_response') || '';
- my $metas = $report->get_extra_fields();
- my %extras;
- foreach my $field (@$metas) {
- $extras{$field->{name}} = $field->{value};
- }
-
- # replace newlines with HTML <br/> element
- $detail =~ s{\r?\n}{ <br/> }g;
- $public_response =~ s{\r?\n}{ <br/> }g if $public_response;
-
- # Assemble photo URL, if report has a photo
- my $photo_to_display = $c->cobrand->allow_photo_display($report);
- my $media_url = (@{$report->photos} && $photo_to_display)
- ? $c->cobrand->base_url . $report->photos->[$photo_to_display-1]->{url}
- : '';
-
- my @columns = (
- $report->id,
- $report->created,
- $report->whensent,
- $report->lastupdate,
- $report->local_coords, $report->category,
- $report->state,
- $report->get_extra_metadata('closure_status') || '',
- $report->user_id,
- $body_name,
- $report->get_column('sum_time_spent') || 0,
- $report->title,
- $detail,
- $media_url,
- $report->service || 'Web interface',
- $public_response,
- $extras{'strasse'} || '',
- $extras{'mast_nr'} || '',
- $extras{'haus_nr'} || '',
- $extras{'hydranten_nr'} || ''
- );
- if ($csv->combine(@columns)) {
- $body .= $csv->string . "\n";
- }
- else {
- $body .= sprintf "{{error emitting CSV line: %s}}\n", $csv->error_diag;
- }
- }
- $c->res->content_type('text/csv; charset=utf-8');
- $c->res->header('Content-Disposition' => 'attachment; filename=stats.csv');
- $c->res->body($body);
+ return $self->export_as_csv($c, \%optional_params);
}
+ # Can change category to any other
+ $self->category_options($c);
+
# Total reports (non-hidden)
my $total = $c->model('DB::Problem')->search( \%params )->count;
# Device for apps (iOS/Android)
@@ -1255,17 +1116,17 @@ sub admin_stats {
group_by => [ 'service' ],
});
# Reports solved
- my $solved = $c->model('DB::Problem')->search( { state => 'fixed - council', %date_params } )->count;
+ my $solved = $c->model('DB::Problem')->search( { state => 'fixed - council', %optional_params } )->count;
# Reports marked as spam
- my $hidden = $c->model('DB::Problem')->search( { state => 'hidden', %date_params } )->count;
+ my $hidden = $c->model('DB::Problem')->search( { state => 'hidden', %optional_params } )->count;
# Reports assigned to third party
- my $closed = $c->model('DB::Problem')->search( { state => 'closed', %date_params } )->count;
+ my $external = $c->model('DB::Problem')->search( { state => 'external', %optional_params } )->count;
# Reports moderated within 1 day
- my $moderated = $c->model('DB::Problem')->search( { extra => { like => '%moderated_overdue,I1:0%' }, %date_params } )->count;
+ my $moderated = $c->model('DB::Problem')->search( { extra => { like => '%moderated_overdue,I1:0%' }, %optional_params } )->count;
# Reports solved within 5 days (sent back from subdiv)
my $subdiv_dealtwith = $c->model('DB::Problem')->search( { extra => { like => '%subdiv_overdue,I1:0%' }, %params } )->count;
- # Reports solved within 5 days (marked as 'fixed - council', 'closed', or 'hidden'
- my $fixed_in_time = $c->model('DB::Problem')->search( { extra => { like => '%closed_overdue,I1:0%' }, %date_params } )->count;
+ # Reports solved within 5 days (marked as 'fixed - council', 'external', or 'hidden'
+ my $fixed_in_time = $c->model('DB::Problem')->search( { extra => { like => '%closed_overdue,I1:0%' }, %optional_params } )->count;
# Reports per category
my $per_category = $c->model('DB::Problem')->search( \%params, {
select => [ 'category', { count => 'id' } ],
@@ -1294,7 +1155,7 @@ sub admin_stats {
reports_total => $total,
reports_solved => $solved,
reports_spam => $hidden,
- reports_assigned => $closed,
+ reports_assigned => $external,
reports_moderated => $moderated,
reports_dealtwith => $fixed_in_time,
reports_category_changed => $changed,
@@ -1309,6 +1170,96 @@ sub admin_stats {
return 1;
}
+sub export_as_csv {
+ my ($self, $c, $params) = @_;
+ my $csv = $c->stash->{csv} = {
+ problems => $c->model('DB::Problem')->search_rs(
+ $params,
+ {
+ join => 'admin_log_entries',
+ distinct => 1,
+ columns => [
+ 'id', 'created',
+ 'latitude', 'longitude',
+ 'cobrand', 'category',
+ 'state', 'user_id',
+ 'external_body',
+ 'title', 'detail',
+ 'photo',
+ 'whensent', 'lastupdate',
+ 'service',
+ 'extra',
+ { sum_time_spent => { sum => 'admin_log_entries.time_spent' } },
+ ]
+ }
+ ),
+ headers => [
+ 'Report ID', 'Created', 'Sent to Agency', 'Last Updated',
+ 'E', 'N', 'Category', 'Status', 'Closure Status',
+ 'UserID', 'User email', 'User phone', 'User name',
+ 'External Body', 'Time Spent', 'Title', 'Detail',
+ 'Media URL', 'Interface Used', 'Council Response',
+ 'Strasse', 'Mast-Nr.', 'Haus-Nr.', 'Hydranten-Nr.',
+ ],
+ columns => [
+ 'id', 'created', 'whensent',' lastupdate', 'local_coords_x',
+ 'local_coords_y', 'category', 'state', 'closure_status',
+ 'user_id', 'user_email', 'user_phone', 'user_name',
+ 'body_name', 'sum_time_spent', 'title', 'detail',
+ 'media_url', 'service', 'public_response',
+ 'strasse', 'mast_nr',' haus_nr', 'hydranten_nr',
+ ],
+ extra_data => sub {
+ my $report = shift;
+
+ my $body_name = "";
+ if ( my $external_body = $report->body($c) ) {
+ $body_name = $external_body->name || '[Unknown body]';
+ }
+
+ my $detail = $report->detail;
+ my $public_response = $report->get_extra_metadata('public_response') || '';
+ my $metas = $report->get_extra_fields();
+ my %extras;
+ foreach my $field (@$metas) {
+ $extras{$field->{name}} = $field->{value};
+ }
+
+ # replace newlines with HTML <br/> element
+ $detail =~ s{\r?\n}{ <br/> }g;
+ $public_response =~ s{\r?\n}{ <br/> }g if $public_response;
+
+ # Assemble photo URL, if report has a photo
+ my $photo_to_display = $c->cobrand->allow_photo_display($report);
+ my $media_url = (@{$report->photos} && $photo_to_display)
+ ? $c->cobrand->base_url . $report->photos->[$photo_to_display-1]->{url}
+ : '';
+
+ return {
+ whensent => $report->whensent,
+ lastupdate => $report->lastupdate,
+ user_id => $report->user_id,
+ user_email => $report->user->email || '',
+ user_phone => $report->user->phone || '',
+ user_name => $report->name,
+ closure_status => $report->get_extra_metadata('closure_status') || '',
+ body_name => $body_name,
+ sum_time_spent => $report->get_column('sum_time_spent') || 0,
+ detail => $detail,
+ media_url => $media_url,
+ service => $report->service || 'Web interface',
+ public_response => $public_response,
+ strasse => $extras{'strasse'} || '',
+ mast_nr => $extras{'mast_nr'} || '',
+ haus_nr => $extras{'haus_nr'} || '',
+ hydranten_nr => $extras{'hydranten_nr'} || ''
+ };
+ },
+ filename => 'stats',
+ };
+ $c->forward('/dashboard/generate_csv');
+}
+
sub problem_confirm_email_extras {
my ($self, $report) = @_;
my $confirmed_reports = $report->user->problems->search({
@@ -1318,102 +1269,44 @@ sub problem_confirm_email_extras {
$self->{c}->stash->{email_confirmed} = $confirmed_reports;
}
-sub body_details_data {
- return (
- {
- name => 'Stadt Zurich'
- },
- {
- name => 'Elektrizitäwerk Stadt Zürich',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'ERZ Entsorgung + Recycling Zürich',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'Fachstelle Graffiti',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'Grün Stadt Zürich',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'Tiefbauamt Stadt Zürich',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'Dienstabteilung Verkehr',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- );
-}
+sub reports_per_page { return 20; }
-sub contact_details_data {
- return (
- {
- category => 'Beleuchtung/Uhren',
- body_name => 'Elektrizitätswerk Stadt Zürich',
- fields => [
- {
- code => 'strasse',
- description => 'Strasse',
- datatype => 'string',
- required => 'yes',
- },
- {
- code => 'haus_nr',
- description => 'Haus-Nr.',
- datatype => 'string',
- },
- {
- code => 'mast_nr',
- description => 'Mast-Nr.',
- datatype => 'string',
- }
- ],
- },
- {
- category => 'Brunnen/Hydranten',
- # body_name ???
- fields => [
- {
- code => 'hydranten_nr',
- description => 'Hydranten-Nr.',
- datatype => 'string',
- },
- ],
- },
- {
- category => "Grünflächen/Spielplätze",
- body_name => 'Grün Stadt Zürich',
- rename_from => "Tiere/Grünflächen",
- },
- {
- category => 'Spielplatz/Sitzbank',
- body_name => 'Grün Stadt Zürich',
- delete => 1,
- },
+sub singleton_bodies_str { 1 }
+
+sub contact_extra_fields { [ 'abbreviation' ] };
+
+sub db_state_migration {
+ my $rs = FixMyStreet::DB->resultset('State');
+
+ # Create new states needed
+ $rs->create({ label => 'submitted', type => 'open', name => 'Erfasst' });
+ $rs->create({ label => 'feedback pending', type => 'open', name => 'Rückmeldung ausstehend' });
+ $rs->create({ label => 'wish', type => 'closed', name => 'Wunsch' });
+ $rs->create({ label => 'external', type => 'closed', name => 'Extern' });
+ $rs->create({ label => 'jurisdiction unknown', type => 'closed', name => 'Zuständigkeit unbekannt' });
+ $rs->create({ label => 'not contactable', type => 'closed', name => 'Nicht kontaktierbar' });
+
+ # And update used current ones to have correct name
+ $rs->find({ label => 'in progress' })->update({ name => 'In Bearbeitung' });
+ $rs->find({ label => 'fixed' })->update({ name => 'Beantwortet' });
+
+ # Move reports to correct new state
+ my %state_move = (
+ unconfirmed => 'submitted',
+ closed => 'external',
+ investigating => 'wish',
+ 'unable to fix' => 'jurisdiction unknown',
+ planned => 'feedback pending',
+ partial => 'not contactable',
);
-}
+ foreach (keys %state_move) {
+ FixMyStreet::DB->resultset('Problem')->search({ state => $_ })->update({ state => $state_move{$_} });
+ }
-sub contact_details_data_body_default {
- my ($self) = @_;
- # temporary measure to assign un-bodied contacts to parent
- # (this isn't at all how things will be setup in live, but is
- # handy during dev.)
- return $self->{c}->model('DB::Body')->find({ name => 'Stadt Zurich' });
+ # Delete unused standard states from the database
+ for ('action scheduled', 'duplicate', 'not responsible', 'internal referral', 'planned', 'investigating', 'unable to fix') {
+ $rs->find({ label => $_ })->delete;
+ }
}
-sub reports_per_page { return 20; }
-
-sub singleton_bodies_str { 1 }
-
1;
diff --git a/perllib/FixMyStreet/DB/Factories.pm b/perllib/FixMyStreet/DB/Factories.pm
index 0e99608e1..66148ad61 100644
--- a/perllib/FixMyStreet/DB/Factories.pm
+++ b/perllib/FixMyStreet/DB/Factories.pm
@@ -1,5 +1,46 @@
+use strict;
+use warnings;
+use v5.14;
+
use FixMyStreet::DB;
+package FixMyStreet::DB::Factories;
+
+my $db;
+my $opt;
+
+END {
+ if ($db) {
+ $opt->commit ? $db->txn_commit : $db->txn_rollback;
+ }
+}
+sub setup {
+ my $cls = shift;
+
+ $opt = shift;
+ $db = FixMyStreet::DB->schema->storage;
+ $db->txn_begin;
+ if (!$opt->commit) {
+ say "NOT COMMITTING TO DATABASE";
+ }
+
+ if ($opt->empty) {
+ $db->dbh->do(q{
+DO
+$func$
+BEGIN
+ EXECUTE
+ (SELECT 'TRUNCATE TABLE ' || string_agg(quote_ident(tablename), ', ') || ' RESTART IDENTITY CASCADE '
+ FROM pg_tables WHERE schemaname='public');
+END
+$func$;
+}) or die $!;
+ $db->dbh->do( scalar FixMyStreet->path_to('db/fixture.sql')->slurp ) or die $!;
+ $db->dbh->do( scalar FixMyStreet->path_to('db/generate_secret.sql')->slurp ) or die $!;
+ say "Emptied database";
+ }
+}
+
package FixMyStreet::DB::Factory::Base;
use parent "DBIx::Class::Factory";
@@ -19,6 +60,10 @@ sub find_or_create {
package FixMyStreet::DB::Factory::Problem;
use parent "DBIx::Class::Factory";
+use Path::Tiny;
+use DateTime::Format::Pg;
+use FixMyStreet;
+use FixMyStreet::App::Model::PhotoSet;
__PACKAGE__->resultset(FixMyStreet::DB->resultset("Problem"));
@@ -43,6 +88,94 @@ __PACKAGE__->fields({
category => 'Other',
});
+sub data {
+ my $self = shift;
+
+ my %titles = (
+ 'Potholes' => ['Deep pothole', 'Small pothole', 'Pothole in cycle lane', 'Pothole on busy pavement', 'Large pothole', 'Sinking manhole'],
+ 'Street lighting' => ['Faulty light', 'Street light not working', 'Lights out in tunnel', 'Light not coming on', 'Light not going off'],
+ 'Graffiti' => ['Graffiti', 'Graffiti', 'Offensive graffiti', 'Graffiti on the bridge', 'Remove graffiti'],
+ 'Other' => ['Loose drain cover', 'Flytipping on country lane', 'Vehicle blocking footpath', 'Hedge encroaching pavement', 'Full litter bins'],
+ );
+ my %photos = (
+ 'Potholes' => [ '33717571655_46dfc6f65f_z.jpg', '37855543925_9dbbbecf41_z.jpg', '19119222668_a3c866d7c8_z.jpg', '12049724866_404b066875_z.jpg', '3705226606_eac71cf195_z.jpg', '6304445383_bd216ca892_z.jpg' ],
+ 'Street lighting' => ['38110448864_fd71227247_z.jpg', '27050321819_ac123400eb_z.jpg', '35732107202_b790c61f63_z.jpg', '31889115854_01cdf38b0d_z.jpg', undef ],
+ 'Graffiti' => ['12205918375_f37f0b27a9_z.jpg', '8895442578_376a9b0be0_z.jpg', '22998854352_17555b7536_z.jpg', '22593395257_3d48f23bfa_z.jpg', '20515339175_f4ed9fc1d9_z.jpg' ],
+ 'Other' => ['14347396807_20737504f7_z.jpg', '14792525771_167bc20e3d_z.jpg', undef, '36296226976_a83a118ff8_z.jpg', '23222004240_273977b2b2_z.jpg'],
+ );
+ my %descriptions = (
+ 'Potholes' => [
+ '6” deep pothole in the very centre of the Bristol road; cars are swerving to avoid it. Please treat this as a matter of urgency.',
+ 'It’s small but it’s a trip hazard. Right where people cross over to get into the school or church. About 3” across but will become larger if not attended to.',
+ 'Just went over my handlebars as I didn’t see this pothole on Banbury road, just before the traffic lights. Dread to think what might have happened if the traffic had been busier.',
+ 'I work in the cafe at 34 Clarington Avenue and we’ve had four people come in having tripped over in the last seven days. The pothole’s right outside the key-cutting shop, just near the alleyway.',
+ 'This has been here, next to the side of the road, for a month',
+ 'A manhole on the junction of Etherington Road is sinking into the road surface. Not only is it an accident waiting to happen but it’s making a terrible noise every time a car passes over it.',
+ ],
+ 'Street lighting' => [
+ 'I saw a workman attempting to fix this streetlight over a week ago, and ever since then it’s come on in the daytime and gone off as soon as it gets dark. Come and sort it out please!',
+ 'Every Tuesday night I have to walk across the carpark outside the station at around 9pm. Not a problem in summer but now the nights are drawing in I feel very unsafe. Please get the streetlight by the exit fixed as I’m sure I can’t be the only woman feeling vulnerable.',
+ 'My toddler is too scared to go in now, as soon as you’re more than a few paces in it’s absolutely pitch black with no hope of seeing any puddles or worse on the floor. I think this needs seeing to as a priority. Thank you.',
+ 'I think the lights in the multi storey carpark are motion sensitive but I’ve actually never seen them come on. Maybe the bulb needs replacing??',
+ 'This streetlight is right outside my bedroom window. It is on 24 hours a day, even in blazing sunlight. Apart from the fact that it’s a waste of electricity, it makes my bedroom feel like an interrogation chamber. Please come and fix it.',
+ ],
+ 'Graffiti' => [
+ 'Someone has scrawled a really offensive piece of graffiti (are they called ‘tags’??) on the side of the town hall. You might want to see about getting it cleaned off. Wouldn’t want my own children to see that, I’m sure others feel the same.',
+ 'Can’t see the timetable at the bus shelter cos some idiot’s covered it all in red spray paint. Honestly. Kids of today.',
+ 'Not gonna write down what it depicts cos I suspect that’d get caught in your profanity filter lol. But please do come and paint over this monstrosity before it causes an accident.',
+ 'That same guy that’s graffitied all over town has gone and done the same on the passenger bridge over the tracks, you can see it as you come into the station. Ugly bit of garbage graffiti. Bit of a poor first impression for the town eh.',
+ 'What’s the procedure for requesting a bit of graffiti be removed? There’s been a huge scrawl on the wall outside the club for months. Nice sentiment maybe but really brings the tone of the area down.',
+ ],
+ 'Other' => [
+ 'Surprised me so much when I crossed the road I nearly took a tumble! Glad I didn’t fall in, this really needs securing now.',
+ 'Some unmentionable has driven down Larker’s Lane and left a huge heap of old rubbish on the verge. Talk about ruining the view! Such a beautiful spot and these lowlifes come and dump their junk. Probably trying to avoid paying the tip.',
+ 'Well someone on foot can just about squeeze through but good luck if you’ve got a pushchair or god forbid a wheelchair. Think someone’s abandoned this car; it hasn’t moved in weeks.',
+ 'Awful trying to walk past after a rain shower, well any time really.',
+ 'I think these need seeing to more frequently, they’re always full to overflowing by midday.',
+ ],
+ );
+
+ return {
+ titles => \%titles,
+ descriptions => \%descriptions,
+ photos => \%photos,
+ };
+}
+
+sub create_problem {
+ my $self = shift;
+ my $params = shift;
+
+ my $data = $self->data;
+ my $category = $params->{category};
+ my $inaccurate_km = 0.01;
+
+ my $titles = $data->{titles}{$category};
+ my $descs = $data->{descriptions}{$category};
+ my $rand = int(rand(@$titles));
+
+ my $photo;
+ if (my $file = $data->{photos}{$category}->[$rand]) {
+ my $files = [ $file ];
+ if ($category eq 'Graffiti') {
+ push @$files, $data->{photos}{$category}->[int(rand(@$titles))];
+ }
+ $files = [ map { path(FixMyStreet->path_to("t/images/$_"))->slurp_raw } @$files ];
+ my $photoset = FixMyStreet::App::Model::PhotoSet->new({
+ data_items => $files,
+ });
+ $photo = $photoset->data;
+ }
+
+ $params->{latitude} += rand(2 * $inaccurate_km) - $inaccurate_km;
+ $params->{longitude} += rand(3 * $inaccurate_km) - 1.5 * $inaccurate_km,
+ $params->{title} = $titles->[$rand];
+ $params->{detail} = $descs->[$rand];
+ $params->{photo_id} = $photo;
+ $params->{confirmed} = DateTime::Format::Pg->format_datetime($params->{confirmed});
+ return $self->create($params);
+}
+
#######################
package FixMyStreet::DB::Factory::Body;
diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm
index 07bea276c..a9df1aeb7 100644
--- a/perllib/FixMyStreet/DB/Result/Body.pm
+++ b/perllib/FixMyStreet/DB/Result/Body.pm
@@ -44,6 +44,10 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"external_url",
{ data_type => "text", is_nullable => 1 },
+ "fetch_problems",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "blank_updates_permitted",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
@@ -118,8 +122,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BOJANVwg3kR/1VjDq0LykA
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2018-03-01 12:27:28
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dzqgZI1wkGDPS2PfJgDEIg
use Moo;
use namespace::clean;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index c73f7efca..2deeb3084 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -341,7 +341,7 @@ around service => sub {
sub title_safe {
my $self = shift;
- return _('Awaiting moderation') if $self->cobrand eq 'zurich' && $self->state eq 'unconfirmed';
+ return _('Awaiting moderation') if $self->cobrand eq 'zurich' && $self->state eq 'submitted';
return $self->title;
}
@@ -509,6 +509,18 @@ sub tokenised_url {
return "/M/". $token->token;
}
+=head2 is_hidden
+
+Returns 1 if the problem is in an hidden state otherwise 0.
+
+=cut
+
+sub is_hidden {
+ my $self = shift;
+
+ return exists $self->hidden_states->{ $self->state } ? 1 : 0;
+}
+
=head2 is_open
Returns 1 if the problem is in a open state otherwise 0.
diff --git a/perllib/FixMyStreet/DB/ResultSet/Problem.pm b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
index 458efa179..ebc6e6c1b 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Problem.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
@@ -118,8 +118,8 @@ sub _recent {
my $key = $photos ? 'recent_photos' : 'recent';
$key .= ":$site_key:$num";
- # unconfirmed might be returned for e.g. Zurich, but would mean in moderation, so no photo
- my @states = grep { $_ ne 'unconfirmed' } FixMyStreet::DB::Result::Problem->visible_states();
+ # submitted might be returned for e.g. Zurich, but would mean in moderation, so no photo
+ my @states = grep { $_ ne 'submitted' } FixMyStreet::DB::Result::Problem->visible_states();
my $query = {
non_public => 0,
state => \@states,
diff --git a/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm b/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm
index 5d1d45379..31876d83d 100644
--- a/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm
+++ b/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm
@@ -14,10 +14,6 @@ use FixMyStreet::Email;
my $opts = {
commit => 0,
- body => '2237',
- cobrand => 'oxfordshire',
- closure_cutoff => "2015-01-01 00:00:00",
- email_cutoff => "2016-01-01 00:00:00",
};
sub query {
@@ -84,11 +80,7 @@ sub archive {
});
printf("Closing %d old reports, without sending emails: ", $problems_to_close->count);
-
- if ( $opts->{commit} ) {
- $problems_to_close->update({ state => 'closed', send_questionnaire => 0 });
- }
-
+ close_problems($problems_to_close);
printf("done.\n")
}
@@ -132,10 +124,31 @@ sub send_email_and_close {
unless ( $email_error ) {
printf("done.\n Closing reports: ");
-
- $problems->update({ state => 'closed', send_questionnaire => 0 });
+ close_problems($problems);
printf("done.\n");
} else {
printf("error! Not closing reports for this user.\n")
}
}
+
+sub close_problems {
+ return unless $opts->{commit};
+
+ my $problems = shift;
+ while (my $problem = $problems->next) {
+ my $timestamp = \'current_timestamp';
+ $problem->add_to_comments( {
+ text => '',
+ created => $timestamp,
+ confirmed => $timestamp,
+ user_id => $opts->{user},
+ name => _('an administrator'),
+ mark_fixed => 0,
+ anonymous => 0,
+ state => 'confirmed',
+ problem_state => 'closed',
+ extra => { is_superuser => 1 },
+ } );
+ $problem->update({ state => 'closed', send_questionnaire => 0 });
+ }
+}
diff --git a/perllib/FixMyStreet/Script/Reports.pm b/perllib/FixMyStreet/Script/Reports.pm
index be3c4c69a..b8c3d6d0d 100644
--- a/perllib/FixMyStreet/Script/Reports.pm
+++ b/perllib/FixMyStreet/Script/Reports.pm
@@ -29,7 +29,7 @@ sub send(;$) {
my $site = $site_override || CronFns::site($base_url);
my $states = [ FixMyStreet::DB::Result::Problem::open_states() ];
- $states = [ 'unconfirmed', 'confirmed', 'in progress', 'planned', 'closed', 'investigating' ] if $site eq 'zurich';
+ $states = [ 'submitted', 'confirmed', 'in progress', 'feedback pending', 'external', 'wish' ] if $site eq 'zurich';
my $unsent = $rs->search( {
state => $states,
whensent => undef,
@@ -88,6 +88,8 @@ sub send(;$) {
if ($row->photo) {
$h{has_photo} = _("This web page also contains a photo of the problem, provided by the user.") . "\n\n";
$h{image_url} = $email_base_url . $row->photos->[0]->{url_full};
+ my @all_images = map { $email_base_url . $_->{url_full} } @{ $row->photos };
+ $h{all_image_urls} = \@all_images;
} else {
$h{has_photo} = '';
$h{image_url} = '';
diff --git a/perllib/FixMyStreet/SendReport/Open311.pm b/perllib/FixMyStreet/SendReport/Open311.pm
index ecda0bca1..98637dd1f 100644
--- a/perllib/FixMyStreet/SendReport/Open311.pm
+++ b/perllib/FixMyStreet/SendReport/Open311.pm
@@ -28,6 +28,7 @@ sub send {
send_notpinpointed => 0,
use_service_as_deviceid => 0,
extended_description => 1,
+ multi_photos => 0,
);
my $cobrand = $body->get_cobrand_handler || $row->get_cobrand_logged;
diff --git a/perllib/FixMyStreet/SendReport/Zurich.pm b/perllib/FixMyStreet/SendReport/Zurich.pm
index b38981d94..59adfd688 100644
--- a/perllib/FixMyStreet/SendReport/Zurich.pm
+++ b/perllib/FixMyStreet/SendReport/Zurich.pm
@@ -44,15 +44,15 @@ sub get_template {
my ( $self, $row ) = @_;
my $template;
- if ( $row->state eq 'unconfirmed' || $row->state eq 'confirmed' ) {
+ if ( $row->state eq 'submitted' || $row->state eq 'confirmed' ) {
$template = 'submit.txt';
} elsif ( $row->state eq 'in progress' ) {
$template = 'submit-in-progress.txt';
- } elsif ( $row->state eq 'planned' ) {
+ } elsif ( $row->state eq 'feedback pending' ) {
$template = 'submit-feedback-pending.txt';
- } elsif ( $row->state eq 'investigating' ) {
+ } elsif ( $row->state eq 'wish' ) {
$template = 'submit-external-wish.txt';
- } elsif ( $row->state eq 'closed' ) {
+ } elsif ( $row->state eq 'external' ) {
$template = 'submit-external.txt';
if ( $row->extra->{third_personal} ) {
$template = 'submit-external-personal.txt';
diff --git a/perllib/FixMyStreet/TestAppProve.pm b/perllib/FixMyStreet/TestAppProve.pm
index d549b0148..049f7da6c 100644
--- a/perllib/FixMyStreet/TestAppProve.pm
+++ b/perllib/FixMyStreet/TestAppProve.pm
@@ -33,6 +33,59 @@ END {
cleanup();
}
+my $pg;
+
+sub spin_up_database {
+ warn "Spinning up a Pg cluster/database...\n";
+ $pg = Test::PostgreSQL->new();
+
+ warn sprintf "# Connected to %s\n", $pg->dsn;
+
+ my $dbh = DBI->connect($pg->dsn);
+
+ my $tmpwarn = $SIG{__WARN__};
+ $SIG{__WARN__} =
+ sub { print STDERR @_ if $_[0] !~ m/NOTICE: CREATE TABLE/; };
+ $dbh->do( path('db/schema.sql')->slurp ) or die $!;
+ $dbh->do( path('db/fixture.sql')->slurp ) or die $!;
+ $dbh->do( path('db/generate_secret.sql')->slurp ) or die $!;
+ $SIG{__WARN__} = $tmpwarn;
+
+ return {
+ FMS_DB_PORT => $pg->port,
+ FMS_DB_NAME => 'test',
+ FMS_DB_USER => 'postgres',
+ FMS_DB_HOST => 'localhost',
+ FMS_DB_PASS => '',
+ };
+}
+
+sub get_config {
+ my $cls = shift;
+ my $extra_config = shift;
+ my $config_file = delete $extra_config->{config_file};
+ my $db_config_file = delete $extra_config->{db_config_file};
+
+ my $config = YAML::Load( path($config_file)->slurp );
+ if ($db_config_file) {
+ my $db_config = YAML::Load( path($db_config_file)->slurp );
+ $config->{FMS_DB_PORT} = $db_config->{FMS_DB_PORT};
+ $config->{FMS_DB_NAME} = $db_config->{FMS_DB_NAME};
+ $config->{FMS_DB_USER} = $db_config->{FMS_DB_USER};
+ $config->{FMS_DB_HOST} = $db_config->{FMS_DB_HOST};
+ $config->{FMS_DB_PASS} = $db_config->{FMS_DB_PASS};
+ } else {
+ my $new_db_config = $cls->spin_up_database();
+ $config = { %$config, %$new_db_config };
+ }
+
+ $config = { %$config, %$extra_config };
+
+ my $config_out = "general-test-autogenerated.$$.yml";
+ path("conf/$config_out")->spew( YAML::Dump($config) );
+ return $config_out;
+}
+
sub run {
my ($class, @args) = @_;
local @ARGV = @args;
@@ -53,42 +106,7 @@ sub run {
'state=s@' => \@state,
);
- my $config = YAML::Load( path($config_file)->slurp );
- my $pg;
- if ($db_config_file) {
- my $db_config = YAML::Load( path($db_config_file)->slurp );
- $config->{FMS_DB_PORT} = $db_config->{FMS_DB_PORT};
- $config->{FMS_DB_NAME} = $db_config->{FMS_DB_NAME};
- $config->{FMS_DB_USER} = $db_config->{FMS_DB_USER};
- $config->{FMS_DB_HOST} = $db_config->{FMS_DB_HOST};
- $config->{FMS_DB_PASS} = $db_config->{FMS_DB_PASS};
- }
- else {
- warn "Spinning up a Pg cluster/database...\n";
- $pg = Test::PostgreSQL->new();
-
- warn sprintf "# Connected to %s\n", $pg->dsn;
-
- my $dbh = DBI->connect($pg->dsn);
-
- my $tmpwarn = $SIG{__WARN__};
- $SIG{__WARN__} =
- sub { print STDERR @_ if $_[0] !~ m/NOTICE: CREATE TABLE/; };
- $dbh->do( path('db/schema.sql')->slurp ) or die $!;
- $dbh->do( path('db/fixture.sql')->slurp ) or die $!;
- $dbh->do( path('db/generate_secret.sql')->slurp ) or die $!;
- $SIG{__WARN__} = $tmpwarn;
-
- $config->{FMS_DB_PORT} = $pg->port;
- $config->{FMS_DB_NAME} = 'test';
- $config->{FMS_DB_USER} = 'postgres';
- $config->{FMS_DB_HOST} = 'localhost';
- $config->{FMS_DB_PASS} = '';
- }
-
- my $config_out = "general-test-autogenerated.$$.yml";
- path("conf/$config_out")->spew( YAML::Dump($config) );
-
+ my $config_out = $class->get_config({ config_file => $config_file, db_config_file => $db_config_file });
local $ENV{FMS_OVERRIDE_CONFIG} = $config_out;
my $prove = App::Prove->new;