aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/fetch-comments58
-rwxr-xr-xbin/send-comments92
-rwxr-xr-xbin/send-reports151
-rw-r--r--db/schema.sql20
-rw-r--r--db/schema_0013-add_external_id_to_comment.sql6
-rw-r--r--db/schema_0013-add_send_method_column_to_open311conf.sql7
-rw-r--r--db/schema_0014-add_send_fail_columns_to_problem.sql10
-rw-r--r--db/schema_0015-add_extra_to_comment.sql6
-rw-r--r--db/schema_0016-add_whensent_and_send_fail_to_comment.sql11
-rw-r--r--db/schema_0017-add_send_comments_to_open311conf.sql6
-rw-r--r--db/schema_0018-add_comment_user_to_open311conf.sql6
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm19
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/Update.pm7
-rw-r--r--perllib/FixMyStreet/DB/Result/Comment.pm51
-rw-r--r--perllib/FixMyStreet/DB/Result/Open311conf.pm23
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm10
-rw-r--r--perllib/Open311.pm82
-rw-r--r--perllib/Open311/GetServiceRequestUpdates.pm68
-rw-r--r--perllib/Open311/PopulateServiceList.pm26
-rw-r--r--t/open311.t144
-rw-r--r--t/open311/getservicerequestupdates.t266
-rw-r--r--t/open311/populate-service-list.t298
-rw-r--r--templates/web/default/admin/report_edit.html1
-rw-r--r--templates/web/fixmystreet/report/display.html4
-rw-r--r--templates/web/fixmystreet/report/new/extra_name.html15
-rw-r--r--templates/web/fixmystreet/report/new/fill_in_details_form.html3
-rw-r--r--web/js/map-OpenLayers.js4
27 files changed, 1353 insertions, 41 deletions
diff --git a/bin/fetch-comments b/bin/fetch-comments
new file mode 100755
index 000000000..e0e53826e
--- /dev/null
+++ b/bin/fetch-comments
@@ -0,0 +1,58 @@
+#!/usr/bin/env perl
+
+# send-reports:
+# Send new problem reports to councils
+#
+# Copyright (c) 2011 UK Citizens Online Democracy. All rights reserved.
+# Email: matthew@mysociety.org. WWW: http://www.mysociety.org
+
+use strict;
+use warnings;
+require 5.8.0;
+
+use Digest::MD5;
+use Encode;
+use Error qw(:try);
+use CronFns;
+
+use FixMyStreet::App;
+
+use Utils;
+use mySociety::Config;
+use mySociety::EmailUtil;
+
+use Open311;
+use Open311::GetServiceRequestUpdates;
+
+# send_method config values found in by-area config data, for selecting to appropriate method
+use constant SEND_METHOD_EMAIL => 'email';
+use constant SEND_METHOD_OPEN311 => 'open311';
+
+# Set up site, language etc.
+my ( $verbose, $nomail ) = CronFns::options();
+my $base_url = mySociety::Config::get('BASE_URL');
+my $site = CronFns::site($base_url);
+
+my $councils = FixMyStreet::App->model('DB::Open311Conf')->search(
+ {
+ send_method => SEND_METHOD_OPEN311,
+ send_comments => 1,
+ comment_user_id => { '!=', undef },
+ endpoint => { '!=', '' },
+ }
+);
+
+while ( my $council = $councils->next ) {
+
+ my $o = Open311->new(
+ endpoint => $council->endpoint,
+ api_key => $council->api_key,
+ jurisdiction => $council->jurisdiction,
+ );
+
+ my $updates =
+ Open311::GetServiceRequestUpdates->new(
+ system_user => $council->comment_user );
+
+ $updates->update_comments( $o, { areaid => $council->area_id }, );
+}
diff --git a/bin/send-comments b/bin/send-comments
new file mode 100755
index 000000000..df646a682
--- /dev/null
+++ b/bin/send-comments
@@ -0,0 +1,92 @@
+#!/usr/bin/env perl
+
+# send-reports:
+# Send new problem reports to councils
+#
+# Copyright (c) 2011 UK Citizens Online Democracy. All rights reserved.
+# Email: matthew@mysociety.org. WWW: http://www.mysociety.org
+
+use strict;
+use warnings;
+require 5.8.0;
+
+use Digest::MD5;
+use Encode;
+use Error qw(:try);
+use CronFns;
+
+use FixMyStreet::App;
+
+use Utils;
+use mySociety::Config;
+use mySociety::EmailUtil;
+
+use Open311;
+
+# maximum number of webservice attempts to send before not trying any more (XXX may be better in config?)
+use constant SEND_FAIL_RETRIES_CUTOFF => 3;
+
+# send_method config values found in by-area config data, for selecting to appropriate method
+use constant SEND_METHOD_EMAIL => 'email';
+use constant SEND_METHOD_OPEN311 => 'open311';
+
+# Set up site, language etc.
+my ($verbose, $nomail) = CronFns::options();
+my $base_url = mySociety::Config::get('BASE_URL');
+my $site = CronFns::site($base_url);
+
+my $councils = FixMyStreet::App->model('DB::Open311Conf')->search( {
+ send_method => SEND_METHOD_OPEN311,
+ send_comments => 1,
+} );
+
+while ( my $council = $councils->next ) {
+ my $comments = FixMyStreet::App->model('DB::Comment')->search( {
+ 'me.whensent' => undef,
+ 'problem.whensent' => { '!=' => undef },
+ 'problem.external_id' => { '!=' => undef },
+ 'problem.council' => { -like => '%' . $council->area_id .'%' },
+ },
+ {
+ join => 'problem',
+ }
+ );
+
+ my $o = Open311->new(
+ endpoint => $council->endpoint
+ );
+
+ while ( my $comment = $comments->next ) {
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($comment->cobrand)->new();
+
+ if ( $comment->send_fail_count ) {
+ next if bromley_retry_timeout( $comment );
+ }
+
+ my $id = $o->post_service_request_update( $comment );
+
+ if ( $id ) {
+ $comment->external_id( $id );
+ $comment->update();
+ } else {
+ $comment->update( {
+ send_fail_count => $comment->send_fail_count + 1,
+ send_fail_timestamp => \'ms_current_timestamp()',
+ send_fail_reason => 'Failed to post over Open311',
+ } );
+ }
+ }
+}
+
+sub bromley_retry_timeout {
+ my $row = shift;
+
+ my $tz = DateTime::TimeZone->new( name => 'local' );
+ my $now = DateTime->now( time_zone => $tz );
+ my $diff = $now - $row->send_fail_timestamp;
+ if ( $diff->minutes < 30 ) {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/bin/send-reports b/bin/send-reports
index 22bd12732..1b93f8c5a 100755
--- a/bin/send-reports
+++ b/bin/send-reports
@@ -30,6 +30,20 @@ use mySociety::Web qw(ent);
use Open311;
+# maximum number of webservice attempts to send before not trying any more (XXX may be better in config?)
+use constant SEND_FAIL_RETRIES_CUTOFF => 3;
+
+# specific council numbers
+use constant COUNCIL_ID_EAST_HANTS => 2330;
+
+use constant MAX_LINE_LENGTH => 132;
+
+# send_method config values found in by-area config data, for selecting to appropriate method
+use constant SEND_METHOD_EMAIL => 'email';
+use constant SEND_METHOD_OPEN311 => 'open311';
+use constant SEND_METHOD_EAST_HANTS => 'easthants';
+use constant SEND_METHOD_LONDON => 'london';
+
# Set up site, language etc.
my ($verbose, $nomail) = CronFns::options();
my $base_url = mySociety::Config::get('BASE_URL');
@@ -40,6 +54,9 @@ my $unsent = FixMyStreet::App->model("DB::Problem")->search( {
whensent => undef,
council => { '!=', undef },
} );
+
+my %sending_skipped_by_method = ();
+
my (%notgot, %note);
while (my $row = $unsent->next) {
@@ -56,8 +73,10 @@ while (my $row = $unsent->next) {
next;
}
- my $send_email = 0;
- my $send_web = 0;
+ # Due to multiple councils, it's possible to want to send both by email *and* another method
+ # NB: might need to revist this if multiple councils have custom send methods
+ my $send_email = 0;
+ my $send_method = 0;
# Template variables for the email
my $email_base_url = $cobrand->base_url_for_emails($row->cobrand_data);
@@ -118,6 +137,7 @@ while (my $row = $unsent->next) {
push @to, [ $council_email, $name ];
@recips = ($council_email);
+ $send_method = 0;
$send_email = 1;
$template = Utils::read_file("$FindBin::Bin/../templates/email/emptyhomes/" . $row->lang . "/submit.txt");
@@ -133,15 +153,37 @@ while (my $row = $unsent->next) {
foreach my $council (@councils) {
my $name = $areas_info->{$council}->{name};
push @dear, $name;
- if ($council == 2330) { # E. Hants have a web service
- $send_web = 'easthants';
+
+ # look in the DB to determine if there is a special handler for this council (e.g., open311, or custom)
+ my $council_config = FixMyStreet::App->model("DB::Open311conf")->search( { area_id => $council} )->first;
+ $send_method = $council_config->send_method if $council_config;
+ if ($council == COUNCIL_ID_EAST_HANTS) { # E. Hants have a web service
+ $send_method = SEND_METHOD_EAST_HANTS; # TODO: delete? should be in the db
$h{category} = 'Customer Services' if $h{category} eq 'Other';
- } elsif ($areas_info->{$council}->{type} eq 'LBO') { # London
- $send_web = 'london';
- } elsif ( my $endpoint = FixMyStreet::App->model("DB::Open311conf")->search( { area_id => $council, endpoint => { '!=', '' } } )->first ) {
- push @open311_councils, $endpoint;
- $send_web = 'open311';
- } else {
+ }
+
+ # if council lookup provided no explicit send_method, maybe there's some other criterion for setting it:
+ if (! $send_method) {
+ if ($areas_info->{$council}->{type} eq 'LBO') { # London
+ $send_method = SEND_METHOD_LONDON;
+ }
+ }
+ $send_email = 1 unless $send_method; # default to email if nothing explicit was provided
+
+ # currently: open311 without an endpoint is useless, so check the endpoint is set
+ if ($send_method eq SEND_METHOD_OPEN311) {
+ if ($council_config->endpoint) {
+ if ($send_method eq SEND_METHOD_OPEN311) {
+ push @open311_councils, $council_config;
+ }
+ } else {
+ print "Warning: no endpoint specified in config data for council=$council (will try email instead)\n";
+ $send_method = 0;
+ $send_email = 1;
+ }
+ }
+
+ if ($send_email) {
my $contact = FixMyStreet::App->model("DB::Contact")->find( {
deleted => 0,
area_id => $council,
@@ -160,7 +202,6 @@ while (my $row = $unsent->next) {
}
push @to, [ $council_email, $name ];
$recips{$council_email} = 1;
- $send_email = 1;
}
}
@recips = keys %recips;
@@ -199,15 +240,19 @@ while (my $row = $unsent->next) {
}
- unless ($send_email || $send_web) {
+ unless ($send_method) {
die 'Report not going anywhere for ID ' . $row->id . '!';
}
if (mySociety::Config::get('STAGING_SITE')) {
# on a staging server send emails to ourselves rather than the councils
- @recips = ( mySociety::Config::get('CONTACT_EMAIL') );
- $send_web = 0;
- $send_email = 1;
+ # ...webservice calls will only go through if explictly allowed here:
+ my @testing_councils = ();
+ unless (grep {$row->council eq $_} @testing_councils) {
+ @recips = ( mySociety::Config::get('CONTACT_EMAIL') );
+ $send_method = 0;
+ $send_email = 1;
+ }
} elsif ($site eq 'emptyhomes') {
my $council = $row->council;
my $country = $areas_info->{$council}->{country};
@@ -243,20 +288,36 @@ while (my $row = $unsent->next) {
);
}
- if ($send_web eq 'easthants') {
+ if ($send_method eq SEND_METHOD_EAST_HANTS) {
$h{message} = construct_easthants_message(%h);
if (!$nomail) {
$result *= post_easthants_message(%h);
}
- } elsif ($send_web eq 'london') {
+ } elsif ($send_method eq SEND_METHOD_LONDON) {
$h{message} = construct_london_message(%h);
if (!$nomail) {
$result *= post_london_report( $row, %h );
}
- } elsif ($send_web eq 'open311') {
+ } elsif ($send_method eq SEND_METHOD_OPEN311) {
foreach my $conf ( @open311_councils ) {
print 'posting to end point for ' . $conf->area_id . "\n" if $verbose;
+ # Extra bromley fields
+ if ( $row->council =~ /2482/ ) {
+ if ( $row->send_fail_count > 0 ) {
+ next if bromley_retry_timeout( $row );
+ }
+
+ my $extra = $row->extra;
+ push @$extra, { name => 'northing', value => $h{northing} };
+ push @$extra, { name => 'easting', value => $h{easting} };
+ push @$extra, { name => 'report_url', value => $h{url} };
+ push @$extra, { name => 'service_request_id', value => $row->id };
+ push @$extra, { name => 'report_title', value => $row->title };
+ push @$extra, { name => 'public_anonymity_required', value => $row->anonymous ? 'TRUE' : 'FALSE' };
+ $row->extra( $extra );
+ }
+
my $contact = FixMyStreet::App->model("DB::Contact")->find( {
deleted => 0,
area_id => $conf->area_id,
@@ -282,7 +343,7 @@ while (my $row = $unsent->next) {
my $resp = $open311->send_service_request( $row, \%h, $contact->email );
# make sure we don't save user changes from above
- if ( $row->council =~ /2218/ ) {
+ if ( $row->council =~ /2218/ or $row->council =~ /2482/ ) {
$row->discard_changes();
}
@@ -291,6 +352,7 @@ while (my $row = $unsent->next) {
$result *= 0;
} else {
$result *= 1;
+ update_send_fail_data( $row, 'Open311 failed' );
# temporary fix to resolve some issues with west berks
if ( $row->council =~ /2619/ ) {
$result *= 0;
@@ -316,6 +378,17 @@ if ($verbose) {
}
}
+# not conditional on verbose because these can be considered failures (more relevant than one-off error messages?)
+if (keys %sending_skipped_by_method) {
+ my $c = 0;
+ print "\nProblem reports that send-reports did not attempt to send because retries >= " . SEND_FAIL_RETRIES_CUTOFF . ":\n";
+ foreach my $send_method (sort keys %sending_skipped_by_method) {
+ printf " %-24s %4d\n", "$send_method:", $sending_skipped_by_method{$send_method};
+ $c+=$sending_skipped_by_method{$send_method};
+ }
+ printf " %-24s %4d\n", "Total:", $c;
+}
+
sub _get_district_for_contact {
my ( $lat, $lon ) = @_;
my $district =
@@ -489,3 +562,43 @@ sub london_lookup {
return $str;
}
+
+# tests send_fail_count agains cutoff limit
+# args: problem (row from problem db)
+# returns false if there is no cutoff, otherwise error message
+sub does_exceed_cutoff_limit {
+ my ($problem, $council_name) = @_;
+ my $err_msg = "";
+ if ($problem->send_fail_count >= SEND_FAIL_RETRIES_CUTOFF) {
+ $sending_skipped_by_method{$council_name || '?'}++;
+ $council_name &&= " to $council_name";
+ $err_msg = "skipped: problem id=" . $problem->id . " send$council_name has failed "
+ . $problem->send_fail_count . " times, cutoff is " . SEND_FAIL_RETRIES_CUTOFF;
+ }
+ return $err_msg;
+}
+
+# update_send_fail_data records the failure (of a webservice send)
+# args: problem (row from problem db)
+# returns: no return value (updates record)
+sub update_send_fail_data {
+ my ($problem, $err_msg) = @_;
+ $problem->update( {
+ send_fail_count => $problem->send_fail_count + 1,
+ send_fail_timestamp => \'ms_current_timestamp()',
+ send_fail_reason => $err_msg
+ } );
+}
+
+sub bromley_retry_timeout {
+ my $row = shift;
+
+ my $tz = DateTime::TimeZone->new( name => 'local' );
+ my $now = DateTime->now( time_zone => $tz );
+ my $diff = $now - $row->send_fail_timestamp;
+ if ( $diff->minutes < 30 ) {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/db/schema.sql b/db/schema.sql
index 395d1c07b..169939315 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -189,7 +189,12 @@ create table problem (
send_questionnaire boolean not null default 't',
extra text, -- extra fields required for open311
flagged boolean not null default 'f',
- geocode bytea
+ geocode bytea,
+
+ -- logging sending failures (used by webservices)
+ send_fail_count integer not null default 0,
+ send_fail_reason text,
+ send_fail_timestamp timestamp
);
create index problem_state_latitude_longitude_idx on problem(state, latitude, longitude);
create index problem_user_id_idx on problem ( user_id );
@@ -303,9 +308,15 @@ create table comment (
or problem_state = 'fixed'
or problem_state = 'fixed - council'
or problem_state = 'fixed - user'
- )
+ ),
-- other fields? one to indicate whether this was written by the council
-- and should be highlighted in the display?
+ external_id text,
+ extra text,
+ send_fail_count integer not null default 0,
+ send_fail_reason text,
+ send_fail_timestamp timestamp,
+ whensent timestamp
);
create index comment_user_id_idx on comment(user_id);
@@ -419,5 +430,8 @@ create table open311conf (
area_id integer not null unique,
endpoint text not null,
jurisdiction text,
- api_key text
+ api_key text,
+ send_method text,
+ send_comments boolean not null default 'f',
+ comment_user_id int references users(id)
);
diff --git a/db/schema_0013-add_external_id_to_comment.sql b/db/schema_0013-add_external_id_to_comment.sql
new file mode 100644
index 000000000..a1cd11c3f
--- /dev/null
+++ b/db/schema_0013-add_external_id_to_comment.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER TABLE comment
+ ADD COLUMN external_id TEXT;
+
+commit;
diff --git a/db/schema_0013-add_send_method_column_to_open311conf.sql b/db/schema_0013-add_send_method_column_to_open311conf.sql
new file mode 100644
index 000000000..516fdd698
--- /dev/null
+++ b/db/schema_0013-add_send_method_column_to_open311conf.sql
@@ -0,0 +1,7 @@
+
+begin;
+
+ALTER table open311conf
+ ADD column send_method TEXT;
+
+commit;
diff --git a/db/schema_0014-add_send_fail_columns_to_problem.sql b/db/schema_0014-add_send_fail_columns_to_problem.sql
new file mode 100644
index 000000000..369c4118d
--- /dev/null
+++ b/db/schema_0014-add_send_fail_columns_to_problem.sql
@@ -0,0 +1,10 @@
+begin;
+
+ALTER table problem
+ ADD column send_fail_count integer not null default 0;
+ALTER table problem
+ ADD column send_fail_reason text;
+ALTER table problem
+ ADD column send_fail_timestamp timestamp;
+
+commit;
diff --git a/db/schema_0015-add_extra_to_comment.sql b/db/schema_0015-add_extra_to_comment.sql
new file mode 100644
index 000000000..5c1789ea2
--- /dev/null
+++ b/db/schema_0015-add_extra_to_comment.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER TABLE comment
+ ADD COLUMN extra TEXT;
+
+commit;
diff --git a/db/schema_0016-add_whensent_and_send_fail_to_comment.sql b/db/schema_0016-add_whensent_and_send_fail_to_comment.sql
new file mode 100644
index 000000000..792ff2e38
--- /dev/null
+++ b/db/schema_0016-add_whensent_and_send_fail_to_comment.sql
@@ -0,0 +1,11 @@
+begin;
+
+ALTER table comment
+ ADD column send_fail_count integer not null default 0;
+ALTER table comment
+ ADD column send_fail_reason text;
+ALTER table comment
+ ADD column send_fail_timestamp timestamp;
+ALTER table comment
+ ADD column whensent timestamp;
+commit;
diff --git a/db/schema_0017-add_send_comments_to_open311conf.sql b/db/schema_0017-add_send_comments_to_open311conf.sql
new file mode 100644
index 000000000..0ee015575
--- /dev/null
+++ b/db/schema_0017-add_send_comments_to_open311conf.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER table open311conf
+ ADD column send_comments BOOL NOT NULL DEFAULT 'f';
+
+commit;
diff --git a/db/schema_0018-add_comment_user_to_open311conf.sql b/db/schema_0018-add_comment_user_to_open311conf.sql
new file mode 100644
index 000000000..93851e2a3
--- /dev/null
+++ b/db/schema_0018-add_comment_user_to_open311conf.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER TABLE open311conf
+ ADD COLUMN comment_user_id INT REFERENCES users(id);
+
+commit;
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index af4cdd5aa..68a30ca2c 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -124,12 +124,14 @@ sub report_form_ajax : Path('ajax') : Args(0) {
my $category = $c->render_fragment( 'report/new/category.html');
my $councils_text = $c->render_fragment( 'report/new/councils_text.html');
my $has_open311 = keys %{ $c->stash->{category_extras} };
+ my $extra_name_info = $c->render_fragment('report/new/extra_name.html');
my $body = JSON->new->utf8(1)->encode(
{
councils_text => $councils_text,
category => $category,
has_open311 => $has_open311,
+ extra_name_info => $extra_name_info,
}
);
@@ -542,7 +544,7 @@ sub setup_categories_and_councils : Private {
);
$category_label = _('Property type:');
- } elsif ($first_council->{type} eq 'LBO') {
+ } elsif ($first_council->{id} != 2482 && $first_council->{type} eq 'LBO') {
$area_ids_to_list{ $first_council->{id} } = 1;
@category_options = (
@@ -589,6 +591,7 @@ sub setup_categories_and_councils : Private {
$c->stash->{category_options} = \@category_options;
$c->stash->{category_extras} = \%category_extras;
$c->stash->{category_extras_json} = encode_json \%category_extras;
+ $c->stash->{extra_name_info} = $first_council->{id} == 2482 ? 1 : 0;
my @missing_details_councils =
grep { !$area_ids_to_list{$_} } #
@@ -733,7 +736,7 @@ sub process_report : Private {
$councils = join( ',', @{ $c->stash->{area_ids_to_list} } ) || -1;
$report->council( $councils );
- } elsif ( $first_council->{type} eq 'LBO') {
+ } elsif ( $first_council->{id} != 2482 && $first_council->{type} eq 'LBO') {
unless ( Utils::london_categories()->{ $report->category } ) {
$c->stash->{field_errors}->{category} = _('Please choose a category');
@@ -785,6 +788,16 @@ sub process_report : Private {
};
}
+ if ( $contacts[0]->area_id == 2482 ) {
+ for my $field ( qw/ fms_extra_title / ) {
+ push @extra, {
+ name => $field,
+ description => uc( $field),
+ value => $c->request->param( $field ) || '',
+ };
+ }
+ }
+
if ( @extra ) {
$c->stash->{report_meta} = \@extra;
$report->extra( \@extra );
@@ -916,6 +929,8 @@ sub check_for_errors : Private {
%{ $c->stash->{report}->check_for_errors },
);
+ # FIXME: need to check for required bromley fields here
+
# if they're got the login details wrong when signing in then
# we don't care about the name field even though it's validated
# by the user object
diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm
index 15444f556..772a0b8ee 100644
--- a/perllib/FixMyStreet/App/Controller/Report/Update.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm
@@ -185,6 +185,13 @@ sub process_update : Private {
$update->problem_state( $params{state} );
}
+ if ( $c->req->param('fms_extra_title') ) {
+ my %extras = ();
+ $extras{title} = $c->req->param('fms_extra_title');
+ $extras{email_alerts_required} = $c->req->param('add_alert');
+ $update->extra( \%extras );
+ }
+
$c->stash->{update} = $update;
$c->stash->{add_alert} = $c->req->param('add_alert');
diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm
index 195fe4019..d31bf5510 100644
--- a/perllib/FixMyStreet/DB/Result/Comment.pm
+++ b/perllib/FixMyStreet/DB/Result/Comment.pm
@@ -1,3 +1,4 @@
+use utf8;
package FixMyStreet::DB::Result::Comment;
# Created by DBIx::Class::Schema::Loader
@@ -7,7 +8,6 @@ use strict;
use warnings;
use base 'DBIx::Class::Core';
-
__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
__PACKAGE__->table("comment");
__PACKAGE__->add_columns(
@@ -54,29 +54,62 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", is_nullable => 0 },
"problem_state",
{ data_type => "text", is_nullable => 1 },
+ "external_id",
+ { data_type => "text", is_nullable => 1 },
+ "extra",
+ { data_type => "text", is_nullable => 1 },
+ "send_fail_count",
+ { data_type => "integer", default_value => 0, is_nullable => 0 },
+ "send_fail_reason",
+ { data_type => "text", is_nullable => 1 },
+ "send_fail_timestamp",
+ { data_type => "timestamp", is_nullable => 1 },
+ "whensent",
+ { data_type => "timestamp", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to(
- "user",
- "FixMyStreet::DB::Result::User",
- { id => "user_id" },
- { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
-);
-__PACKAGE__->belongs_to(
"problem",
"FixMyStreet::DB::Result::Problem",
{ id => "problem_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
+__PACKAGE__->belongs_to(
+ "user",
+ "FixMyStreet::DB::Result::User",
+ { id => "user_id" },
+ { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
-# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-06-27 10:07:32
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ilLn3dlagg5COdpZDmzrVQ
+# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-03-26 15:44:18
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nvkElEgSU6XcLd9znSqhmQ
+
+__PACKAGE__->filter_column(
+ extra => {
+ filter_from_storage => sub {
+ my $self = shift;
+ my $ser = shift;
+ return undef unless defined $ser;
+ my $h = new IO::String($ser);
+ return RABX::wire_rd($h);
+ },
+ filter_to_storage => sub {
+ my $self = shift;
+ my $data = shift;
+ my $ser = '';
+ my $h = new IO::String($ser);
+ RABX::wire_wr( $data, $h );
+ return $ser;
+ },
+ }
+);
use DateTime::TimeZone;
use Image::Size;
use Moose;
use namespace::clean -except => [ 'meta' ];
+use RABX;
with 'FixMyStreet::Roles::Abuser';
diff --git a/perllib/FixMyStreet/DB/Result/Open311conf.pm b/perllib/FixMyStreet/DB/Result/Open311conf.pm
index 0a5784560..b9cd432ee 100644
--- a/perllib/FixMyStreet/DB/Result/Open311conf.pm
+++ b/perllib/FixMyStreet/DB/Result/Open311conf.pm
@@ -1,3 +1,4 @@
+use utf8;
package FixMyStreet::DB::Result::Open311conf;
# Created by DBIx::Class::Schema::Loader
@@ -7,7 +8,6 @@ use strict;
use warnings;
use base 'DBIx::Class::Core';
-
__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
__PACKAGE__->table("open311conf");
__PACKAGE__->add_columns(
@@ -26,13 +26,30 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"api_key",
{ data_type => "text", is_nullable => 1 },
+ "send_method",
+ { data_type => "text", is_nullable => 1 },
+ "send_comments",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "comment_user_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("open311conf_area_id_key", ["area_id"]);
+__PACKAGE__->belongs_to(
+ "comment_user",
+ "FixMyStreet::DB::Result::User",
+ { id => "comment_user_id" },
+ {
+ is_deferrable => 1,
+ join_type => "LEFT",
+ on_delete => "CASCADE",
+ on_update => "CASCADE",
+ },
+);
-# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-07-29 18:09:25
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ryqCpvwjNtQrZm4I3s0hxg
+# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-03-26 17:03:34
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:fC6Ws8p/pXyjgqfm2LRKsw
# You can replace this text with custom code or comments, and it will be preserved on regeneration
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index ce7488703..8c479953b 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -84,6 +84,12 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"geocode",
{ data_type => "bytea", is_nullable => 1 },
+ "send_fail_count",
+ { data_type => "integer", is_nullable => 1 },
+ "send_fail_reason",
+ { data_type => "text", is_nullable => 1 },
+ "send_fail_timestamp",
+ { data_type => "timestamp", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
@@ -106,8 +112,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-09-19 14:38:43
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nq8Ufn/SEoDGSrrGlHIxag
+# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-03-16 10:08:56
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:VODeZlWk8l/+IzBBlRNV0A
# Add fake relationship to stored procedure table
__PACKAGE__->has_one(
diff --git a/perllib/Open311.pm b/perllib/Open311.pm
index e26e3e4c6..50c7abb54 100644
--- a/perllib/Open311.pm
+++ b/perllib/Open311.pm
@@ -12,8 +12,9 @@ has api_key => ( is => 'ro', isa => 'Str' );
has endpoint => ( is => 'ro', isa => 'Str' );
has test_mode => ( is => 'ro', isa => 'Bool' );
has test_uri_used => ( is => 'rw', 'isa' => 'Str' );
+has test_req_used => ( is => 'rw' );
has test_get_returns => ( is => 'rw' );
-has endpoints => ( is => 'rw', default => sub { { services => 'services.xml', requests => 'requests.xml' } } );
+has endpoints => ( is => 'rw', default => sub { { services => 'services.xml', requests => 'requests.xml', service_request_updates => 'update.xml', update => 'update.xml' } } );
has debug => ( is => 'ro', isa => 'Bool', default => 0 );
has debug_details => ( is => 'rw', 'isa' => 'Str', default => '' );
@@ -74,7 +75,9 @@ EOT
my $extras = $problem->extra;
for my $attr ( @$extras ) {
- my $name = sprintf( 'attribute[%s]', $attr->{name} );
+ my $attr_name = $attr->{name};
+ $attr_name =~ s/fms_extra_//;
+ my $name = sprintf( 'attribute[%s]', $attr_name );
$params->{ $name } = $attr->{value};
}
}
@@ -129,6 +132,72 @@ sub get_service_request_id_from_token {
}
}
+sub get_service_request_updates {
+ my $self = shift;
+
+ my $params = {};
+
+ my $xml = $self->_get( $self->endpoints->{service_request_updates}, $params || undef );
+ my $service_requests = $self->_get_xml_object( $xml );
+ my $requests;
+ if ( ref $service_requests->{request_update } eq 'ARRAY' ) {
+ $requests = $service_requests->{request_update};
+ }
+ else {
+ $requests = [ $service_requests->{request_update} ];
+ }
+
+ return $requests;
+}
+
+sub post_service_request_update {
+ my $self = shift;
+ my $comment = shift;
+
+ my $params = {
+ update_id_ext => $comment->id,
+ updated_datetime => $comment->confirmed,
+ service_request_id => $comment->problem->external_id,
+ service_request_id_ext => $comment->problem->id,
+ status => $comment->problem->is_open ? 'OPEN' : 'CLOSED',
+ email => $comment->user->email,
+ description => $comment->text,
+ public_anonymity_required => $comment->anonymous ? 'TRUE' : 'FALSE',
+ # also need last_name, title
+ };
+
+ if ( $comment->extra ) {
+ $params->{'email_alerts_request'}
+ = $comment->extra->{email_alerts_requested} ? 'TRUE' : 'FALSE';
+ $params->{'title'} = $comment->extra->{title};
+ }
+
+ my $response = $self->_post( $self->endpoints->{update}, $params );
+
+ if ( $response ) {
+ my $obj = $self->_get_xml_object( $response );
+
+ if ( $obj ) {
+ if ( $obj->{ request_update }->{ update_id } ) {
+ my $update_id = $obj->{request_update}->{update_id};
+
+ # if there's nothing in the update_id element we get a HASHREF back
+ unless ( ref $update_id ) {
+ return $obj->{ request_update }->{ update_id };
+ }
+ } else {
+ my $token = $obj->{ request_update }->{ token };
+ if ( $token ) {
+ return $self->get_service_request_id_from_token( $token );
+ }
+ }
+ }
+
+ warn sprintf( "Failed to submit comment %s over Open311, response - %s\n%s", $comment->id, $response, $self->debug_details );
+ return 0;
+ }
+}
+
sub _get {
my $self = shift;
my $path = shift;
@@ -171,7 +240,14 @@ sub _post {
$self->debug_details( $self->debug_details . "\nrequest:" . $req->as_string );
my $ua = LWP::UserAgent->new();
- my $res = $ua->request( $req );
+ my $res;
+
+ if ( $self->test_mode ) {
+ $res = $self->test_get_returns->{ $path };
+ $self->test_req_used( $req );
+ } else {
+ $res = $ua->request( $req );
+ }
if ( $res->is_success ) {
return $res->decoded_content;
diff --git a/perllib/Open311/GetServiceRequestUpdates.pm b/perllib/Open311/GetServiceRequestUpdates.pm
new file mode 100644
index 000000000..dc8fb7672
--- /dev/null
+++ b/perllib/Open311/GetServiceRequestUpdates.pm
@@ -0,0 +1,68 @@
+package Open311::GetServiceRequestUpdates;
+
+use Moose;
+use Open311;
+use FixMyStreet::App;
+
+has council_list => ( is => 'ro' );
+has system_user => ( is => 'ro' );
+
+sub update_comments {
+ my ( $self, $open311, $council_details ) = @_;
+
+ my $requests = $open311->get_service_request_updates( );
+
+ for my $request (@$requests) {
+ my $request_id = $request->{service_request_id};
+
+ # If there's no request id then we can't work out
+ # what problem it belongs to so just skip
+ next unless $request_id;
+
+ my $problem =
+ FixMyStreet::App->model('DB::Problem')
+ ->search( {
+ external_id => $request_id,
+ council => { like => '%' . $council_details->{areaid} . '%' },
+ } );
+
+ if (my $p = $problem->first) {
+ my $c = $p->comments->search( { external_id => $request->{update_id} } );
+
+ if ( !$c->first ) {
+ my $comment = FixMyStreet::App->model('DB::Comment')->new(
+ {
+ problem => $p,
+ user => $self->system_user,
+ external_id => $request->{update_id},
+ text => $request->{description},
+ mark_fixed => 0,
+ mark_open => 0,
+ anonymous => 0,
+ name => $self->system_user->name
+ }
+ );
+ $comment->confirm;
+
+
+ if ( $p->is_open and $request->{status} eq 'closed' ) {
+ $p->state( 'fixed - council' );
+ $p->update;
+
+ $comment->mark_fixed( 1 );
+ } elsif ( ( $p->is_closed || $p->is_fixed ) and $request->{status} eq 'open' ) {
+ $p->state( 'confirmed' );
+ $p->update;
+
+ $comment->mark_open( 1 );
+ }
+
+ $comment->insert();
+ }
+ }
+ }
+
+ return 1;
+}
+
+1;
diff --git a/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm
index cfec9005d..ac355a33c 100644
--- a/perllib/Open311/PopulateServiceList.pm
+++ b/perllib/Open311/PopulateServiceList.pm
@@ -189,6 +189,12 @@ sub _add_contact_to_meta {
print "Fetching meta data for $self->_current_service->{service_code}\n";
my $meta_data = $self->_current_open311->get_service_meta_info( $self->_current_service->{service_code} );
+ if ( ref $meta_data->{ attributes }->{ attribute } eq 'HASH' ) {
+ $meta_data->{ attributes }->{ attribute } = [
+ $meta_data->{ attributes }->{ attribute }
+ ];
+ }
+
# turn the data into something a bit more friendly to use
my @meta =
# remove trailing colon as we add this when we display so we don't want 2
@@ -197,6 +203,26 @@ sub _add_contact_to_meta {
sort { $a->{order} <=> $b->{order} }
@{ $meta_data->{attributes}->{attribute} };
+ # we add these later on from bromley so don't list them here
+ # as we don't want to display them
+ if ( $self->_current_council->area_id == 2482 ) {
+ my %ignore = map { $_ => 1 } qw/
+ service_request_id_ext
+ requested_datetime
+ report_url
+ title
+ last_name
+ email
+ easting
+ northing
+ report_title
+ public_anonymity_required
+ email_alerts_requested
+ /;
+
+ @meta = grep { ! $ignore{ $_->{ code } } } @meta;
+ }
+
$contact->extra( \@meta );
$contact->update;
}
diff --git a/t/open311.t b/t/open311.t
index 30de330b6..2a9d513eb 100644
--- a/t/open311.t
+++ b/t/open311.t
@@ -5,6 +5,9 @@ use warnings;
use Test::More;
use Test::Warn;
use FixMyStreet::App;
+use CGI::Simple;
+use HTTP::Response;
+use DateTime;
use FindBin;
use lib "$FindBin::Bin/../perllib";
@@ -39,4 +42,145 @@ my $expected_error = qr{.*request failed: 500 Can.t connect to 192.168.50.1:80 \
warning_like {$o2->send_service_request( $p, { url => 'http://example.com/' }, 1 )} $expected_error, 'warning generated on failed call';
+my $dt = DateTime->now();
+
+my $user = FixMyStreet::App->model('DB::User')->new( {
+ email => 'test@example.com',
+} );
+
+my $problem = FixMyStreet::App->model('DB::Problem')->new( {
+ id => 80,
+ external_id => 81,
+ state => 'confirmed',
+} );
+
+my $comment = FixMyStreet::App->model('DB::Comment')->new( {
+ id => 38362,
+ user => $user,
+ problem => $problem,
+ anonymous => 0,
+ text => 'this is a comment',
+ confirmed => $dt,
+} );
+
+subtest 'testing posting updates' => sub {
+ my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' );
+
+ is $results->{ res }, 248, 'got update id';
+
+ my $req = $o->test_req_used;
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+
+ is $c->param('description'), 'this is a comment', 'email correct';
+ is $c->param('email'), 'test@example.com', 'email correct';
+ is $c->param('status'), 'OPEN', 'status correct';
+ is $c->param('service_request_id_ext'), 80, 'external request id correct';
+ is $c->param('service_request_id'), 81, 'request id correct';
+ is $c->param('public_anonymity_required'), 'FALSE', 'anon status correct';
+ is $c->param('updated_datetime'), $dt, 'correct date';
+};
+
+foreach my $test (
+ {
+ desc => 'fixed is CLOSED',
+ state => 'fixed',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'fixed - user is CLOSED',
+ state => 'fixed - user',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'fixed - council is CLOSED',
+ state => 'fixed - council',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'closed is CLOSED',
+ state => 'closed',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'investigating is OPEN',
+ state => 'investigating',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'planned is OPEN',
+ state => 'planned',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'in progress is OPEN',
+ state => 'in progress',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'anonymous set to true',
+ state => 'confirmed',
+ anon => 1,
+ status => 'OPEN',
+ },
+) {
+ subtest $test->{desc} => sub {
+ $comment->problem->state( $test->{state} );
+ $comment->anonymous( $test->{anon} );
+
+ my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' );
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+ is $c->param('status'), $test->{status}, 'correct status';
+ is $c->param('public_anonymity_required'), $test->{anon} ? 'TRUE' : 'FALSE', 'correct anonymity';
+ };
+}
+
+subtest 'No update id in reponse' => sub {
+ my $results;
+ warning_like {
+ $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id></update_id></request_update></service_request_updates>' )
+ } qr/Failed to submit comment \d+ over Open311/, 'correct error message';
+
+ is $results->{ res }, 0, 'No update_id is a failure';
+};
+
+subtest 'error reponse' => sub {
+ my $results;
+ warning_like {
+ $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><errors><error><code>400</code><description>There was an error</description</error></errors>' )
+ } qr/Failed to submit comment \d+ over Open311.*There was an error/, 'correct error messages';
+
+ is $results->{ res }, 0, 'error in response is a failure';
+};
+
done_testing();
+
+sub make_update_req {
+ my $comment = shift;
+ my $xml = shift;
+
+ my $o = Open311->new( test_mode => 1, end_point => 'http://localhost/o311' );
+
+ my $test_res = HTTP::Response->new();
+ $test_res->code( 200 );
+ $test_res->message( 'OK' );
+ $test_res->content( $xml );
+
+ $o->test_get_returns( {
+ 'update.xml' => $test_res
+ } );
+
+ my $res = $o->post_service_request_update( $comment );
+
+ my $req = $o->test_req_used;
+
+ return { res => $res, req => $req };
+}
diff --git a/t/open311/getservicerequestupdates.t b/t/open311/getservicerequestupdates.t
new file mode 100644
index 000000000..c9f97cd5a
--- /dev/null
+++ b/t/open311/getservicerequestupdates.t
@@ -0,0 +1,266 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use Test::More;
+
+use FindBin;
+use lib "$FindBin::Bin/../perllib";
+use lib "$FindBin::Bin/../commonlib/perllib";
+
+use_ok( 'Open311' );
+
+use_ok( 'Open311::GetServiceRequestUpdates' );
+use DateTime;
+use FixMyStreet::App;
+
+my $user = FixMyStreet::App->model('DB::User')->find_or_create(
+ {
+ email => 'system_user@example.com'
+ }
+);
+
+my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+<service_requests_updates>
+<request_update>
+<update_id>638344</update_id>
+<service_request_id>1</service_request_id>
+<service_request_id_ext>1</service_request_id_ext>
+<status>open</status>
+<description>This is a note</description>
+UPDATED_DATETIME
+</request_update>
+</service_requests_updates>
+};
+
+
+my $dt = DateTime->now;
+
+# basic xml -> perl object tests
+for my $test (
+ {
+ desc => 'basic parsing - element missing',
+ updated_datetime => '',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note' },
+ },
+ {
+ desc => 'basic parsing - empty element',
+ updated_datetime => '<updated_datetime />',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => {} } ,
+ },
+ {
+ desc => 'basic parsing - element with no content',
+ updated_datetime => '<updated_datetime></updated_datetime>',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => {} } ,
+ },
+ {
+ desc => 'basic parsing - element with content',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => $dt } ,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ my $res = $o->get_service_request_updates;
+ is_deeply $res->[0], $test->{ res }, 'result looks correct';
+
+ };
+}
+
+my $problem_rs = FixMyStreet::App->model('DB::Problem');
+my $problem = $problem_rs->new(
+ {
+ postcode => 'EH99 1SP',
+ latitude => 1,
+ longitude => 1,
+ areas => 1,
+ title => '',
+ detail => '',
+ used_map => 1,
+ user_id => 1,
+ name => '',
+ state => 'confirmed',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ user => $user,
+ created => DateTime->now()->subtract( days => 1 ),
+ lastupdate => DateTime->now()->subtract( days => 1 ),
+ anonymous => 1,
+ external_id => time(),
+ council => 2482,
+ }
+);
+
+$problem->insert;
+
+for my $test (
+ {
+ desc => 'element with content',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'confirmed',
+ close_comment => 0,
+ mark_fixed=> 0,
+ mark_open => 0,
+ end_state => 'confirmed',
+ },
+ {
+ desc => 'comment closes report',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'confirmed',
+ close_comment => 1,
+ mark_fixed=> 1,
+ mark_open => 0,
+ end_state => 'fixed - council',
+ },
+ {
+ desc => 'comment re-opens fixed report',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'fixed - user',
+ close_comment => 0,
+ mark_fixed => 0,
+ mark_open => 1,
+ end_state => 'confirmed',
+ },
+ {
+ desc => 'comment re-opens closed report',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'closed',
+ close_comment => 0,
+ mark_fixed => 0,
+ mark_open => 1,
+ end_state => 'confirmed',
+ },
+ {
+ desc => 'comment leaves report closed',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'closed',
+ close_comment => 1,
+ mark_fixed => 0,
+ mark_open => 0,
+ end_state => 'closed',
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/;
+ $local_requests_xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>@{[$problem->external_id]}</service_request_id>#;
+ $local_requests_xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>@{[$problem->id]}</service_request_id_ext>#;
+ $local_requests_xml =~ s#<status>\w+</status>#<status>closed</status># if $test->{close_comment};
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ $problem->comments->delete;
+ $problem->state( $test->{start_state} );
+ $problem->update;
+
+ my $council_details = { areaid => 2482 };
+ my $update = Open311::GetServiceRequestUpdates->new( system_user => $user );
+ $update->update_comments( $o, $council_details );
+
+ is $problem->comments->count, 1, 'comment count';
+ $problem->discard_changes;
+
+ my $c = FixMyStreet::App->model('DB::Comment')->search( { external_id => $test->{external_id} } )->first;
+ ok $c, 'comment exists';
+ is $c->text, $test->{description}, 'text correct';
+ is $c->mark_fixed, $test->{mark_fixed}, 'mark_closed correct';
+ is $c->mark_open, $test->{mark_open}, 'mark_open correct';
+ is $problem->state, $test->{end_state}, 'correct problem state';
+ };
+}
+
+my $problem2 = $problem_rs->new(
+ {
+ postcode => 'EH99 1SP',
+ latitude => 1,
+ longitude => 1,
+ areas => 1,
+ title => '',
+ detail => '',
+ used_map => 1,
+ user_id => 1,
+ name => '',
+ state => 'confirmed',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ user => $user,
+ created => DateTime->now(),
+ lastupdate => DateTime->now(),
+ anonymous => 1,
+ external_id => $problem->external_id,
+ council => 2651,
+ }
+);
+
+$problem2->insert();
+$problem->comments->delete;
+$problem2->comments->delete;
+
+for my $test (
+ {
+ desc => 'identical external_ids on problem resolved using council',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ external_id => 638344,
+ area_id => 2651,
+ request_id => $problem2->external_id,
+ request_id_ext => $problem2->id,
+ p1_comments => 0,
+ p2_comments => 1,
+ },
+ {
+ desc => 'identical external_ids on comments resolved',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ external_id => 638344,
+ area_id => 2482,
+ request_id => $problem->external_id,
+ request_id_ext => $problem->id,
+ p1_comments => 1,
+ p2_comments => 1,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/;
+ $local_requests_xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>$test->{request_id}</service_request_id>#;
+ $local_requests_xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>$test->{request_id_ext}</service_request_id_ext>#;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+
+ my $council_details = { areaid => $test->{area_id} };
+ my $update = Open311::GetServiceRequestUpdates->new( system_user => $user );
+ $update->update_comments( $o, $council_details );
+
+ is $problem->comments->count, $test->{p1_comments}, 'comment count for first problem';
+ is $problem2->comments->count, $test->{p2_comments}, 'comment count for second problem';
+ };
+}
+$problem2->comments->delete();
+$problem->comments->delete();
+$problem2->delete;
+$problem->delete;
+$user->comments->delete;
+$user->problems->delete;
+$user->delete;
+
+done_testing();
diff --git a/t/open311/populate-service-list.t b/t/open311/populate-service-list.t
index bdb7404f9..63500d6cb 100644
--- a/t/open311/populate-service-list.t
+++ b/t/open311/populate-service-list.t
@@ -203,6 +203,304 @@ subtest 'check conflicting contacts not changed' => sub {
is $contact_count, 4, 'correct number of contacts';
};
+subtest 'check meta data population' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 2482
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_contact_to_meta( $contact );
+
+ my $extra = [ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ } ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'meta data saved';
+};
+
+subtest 'check attribute ordering' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>colour</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Colour of bin</datatype_description>
+ <order>3</order>
+ <description>Colour of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>size</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Size of bin</datatype_description>
+ <order>2</order>
+ <description>Size of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 1
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_contact_to_meta( $contact );
+
+ my $extra = [
+ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'size',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Size of bin',
+ order => 2,
+ description => 'Size of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'colour',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Colour of bin',
+ order => 3,
+ description => 'Colour of bin'
+
+ },
+ ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'meta data re-ordered correctly';
+};
+
+subtest 'check bromely skip code' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>title</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>report_url</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 2482
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_contact_to_meta( $contact );
+
+ my $extra = [ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ } ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'only non std bromley meta data saved';
+
+ $council->area_id(1);
+
+ $processor->_current_council( $council );
+ $processor->_add_contact_to_meta( $contact );
+
+ $extra = [
+ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'title',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'report_url',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'all meta data saved for non bromley';
+};
+
sub get_standard_xml {
return qq{<?xml version="1.0" encoding="utf-8"?>
<services>
diff --git a/templates/web/default/admin/report_edit.html b/templates/web/default/admin/report_edit.html
index cbba1b3b0..9ef7e8248 100644
--- a/templates/web/default/admin/report_edit.html
+++ b/templates/web/default/admin/report_edit.html
@@ -37,6 +37,7 @@
<li>[% loc('Service:') %] [% problem.service %]</li>
<li>[% loc('Cobrand:') %] [% problem.cobrand %]</li>
<li>[% loc('Cobrand data:') %] [% problem.cobrand_data %]</li>
+<li>[% loc('Extra data:') %] [% problem.extra ? 'Yes' : 'No' %]</li>
<li>[% loc('Going to send questionnaire?') %] [% IF problem.send_questionnaire %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]</li>
<li><label for="flagged">[% loc('Flagged:') %]</label> <input type="checkbox" name="flagged"[% ' checked' IF problem.flagged %]></li>
diff --git a/templates/web/fixmystreet/report/display.html b/templates/web/fixmystreet/report/display.html
index 0f650b7c2..72c637a5b 100644
--- a/templates/web/fixmystreet/report/display.html
+++ b/templates/web/fixmystreet/report/display.html
@@ -178,6 +178,10 @@
[% INCLUDE 'footer.html' %]
[% BLOCK name %]
+ [% IF problem.council == '2482' %]
+ [% extra_name_info = 1 %]
+ [% INCLUDE 'report/new/extra_name.html' %]
+ [% END %]
<label for="form_name">[% loc('Name') %]</label>
[% IF field_errors.name %]
<p class='form-error'>[% field_errors.name %]</p>
diff --git a/templates/web/fixmystreet/report/new/extra_name.html b/templates/web/fixmystreet/report/new/extra_name.html
new file mode 100644
index 000000000..7b057a831
--- /dev/null
+++ b/templates/web/fixmystreet/report/new/extra_name.html
@@ -0,0 +1,15 @@
+[% IF extra_name_info %]
+<label for="form_fms_extra_title">Title</label>
+[% IF field_errors.fms_extra_title %]
+ <p class='form-error'>[% field_errors.fms_extra_title %]</p>
+[% END %]
+<select class="form-focus-trigger" id="form_fms_extra_title"
+ name="fms_extra_title" required>
+ <option></option>
+ <option value="Mr">Mr</option>
+ <option value="Miss">Miss</option>
+ <option value="Mrs">Mrs</option>
+ <option value="Ms">Ms</option>
+ <option value="Dr">Dr</option>
+</select>
+[% END %]
diff --git a/templates/web/fixmystreet/report/new/fill_in_details_form.html b/templates/web/fixmystreet/report/new/fill_in_details_form.html
index 283f748ba..5ee11fb57 100644
--- a/templates/web/fixmystreet/report/new/fill_in_details_form.html
+++ b/templates/web/fixmystreet/report/new/fill_in_details_form.html
@@ -168,10 +168,13 @@
<div id="form_sign_in_no" class="form-box">
<h5>[% loc('<strong>No</strong> Let me confirm my report by email') %]</h5>
+ [% INCLUDE 'report/new/extra_name.html' %]
+
<label for="form_may_show_nameme">[% loc('Name') %]</label>
[% IF field_errors.name %]
<p class='form-error'>[% field_errors.name %]</p>
[% END %]
+
<input type="text" class="form-focus-trigger validName" value="[% report.name | html %]" name="name" id="form_name" placeholder="[% loc('Your name') %]">
[%# if there is nothing in the name field then set check box as default on form %]
diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js
index 272fd6c12..79740de78 100644
--- a/web/js/map-OpenLayers.js
+++ b/web/js/map-OpenLayers.js
@@ -440,6 +440,10 @@ OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
}
$('#councils_text').html(data.councils_text);
$('#form_category_row').html(data.category);
+ if ( data.extra_name_info ) {
+ var lb = $('#form_name').prev();
+ lb.before(data.extra_name_info);
+ }
});
$('#side-form, #site-logo').show();