diff options
26 files changed, 483 insertions, 57 deletions
diff --git a/bin/update-schema b/bin/update-schema index b85ff87f4..94c42d8ae 100755 --- a/bin/update-schema +++ b/bin/update-schema @@ -194,6 +194,7 @@ else { # By querying the database schema, we can see where we're currently at # (assuming schema change files are never half-applied, which should be the case) sub get_db_version { + return '0049' if column_exists('response_priorities', 'external_id'); return '0048' if column_exists('response_templates', 'state'); return '0047' if column_exists('response_priorities', 'description'); return '0046' if column_exists('users', 'extra'); diff --git a/db/schema.sql b/db/schema.sql index e54c6b7b2..23db82b65 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -135,6 +135,7 @@ CREATE TABLE response_priorities ( deleted boolean not null default 'f', name text not null, description text, + external_id text, unique(body_id, name) ); diff --git a/db/schema_0049-response-priorities-add-external_id.sql b/db/schema_0049-response-priorities-add-external_id.sql new file mode 100644 index 000000000..1d8a1ba1e --- /dev/null +++ b/db/schema_0049-response-priorities-add-external_id.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE response_priorities + ADD COLUMN external_id TEXT; + +COMMIT; diff --git a/perllib/FixMyStreet/App.pm b/perllib/FixMyStreet/App.pm index 7c5ef488b..2fc560bc8 100644 --- a/perllib/FixMyStreet/App.pm +++ b/perllib/FixMyStreet/App.pm @@ -163,7 +163,7 @@ sub setup_request { my $cobrand = $c->cobrand; - $cobrand->add_response_headers if $cobrand->can('add_response_headers'); + $cobrand->call_hook('add_response_headers'); # append the cobrand templates to the include path $c->stash->{additional_template_paths} = $cobrand->path_to_web_templates; diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index 5d496e7e8..11bdca805 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -1217,6 +1217,8 @@ sub user_add : Path('user_edit') : Args(0) { key => 'users_email_key' } ); $c->stash->{user} = $user; + $c->forward('user_cobrand_extra_fields'); + $user->update; $c->forward( 'log_edit', [ $user->id, 'user', 'edit' ] ); @@ -1281,6 +1283,8 @@ sub user_edit : Path('user_edit') : Args(1) { $user->from_body( undef ); } + $c->forward('user_cobrand_extra_fields'); + # Has the user's from_body changed since we fetched areas (if we ever did)? # If so, we need to re-fetch areas so the UI is up to date. if ( $user->from_body && $user->from_body->id ne $c->stash->{fetched_areas_body_id} ) { @@ -1397,6 +1401,15 @@ sub user_edit : Path('user_edit') : Args(1) { return 1; } +sub user_cobrand_extra_fields : Private { + my ( $self, $c ) = @_; + + my @extra_fields = @{ $c->cobrand->call_hook('user_extra_fields') || [] }; + foreach ( @extra_fields ) { + $c->stash->{user}->set_extra_metadata( $_ => $c->get_param("extra[$_]") ); + } +} + sub flagged : Path('flagged') : Args(0) { my ( $self, $c ) = @_; diff --git a/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm new file mode 100644 index 000000000..164a4b42d --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm @@ -0,0 +1,219 @@ +package FixMyStreet::App::Controller::Admin::ExorDefects; +use Moose; +use namespace::autoclean; + +use Text::CSV; +use DateTime; +use mySociety::Random qw(random_bytes); + +BEGIN { extends 'Catalyst::Controller'; } + + +sub begin : Private { + my ( $self, $c ) = @_; + + $c->forward('/admin/begin'); +} + +sub index : Path : Args(0) { + my ( $self, $c ) = @_; + + foreach (qw(error_message start_date end_date user_id)) { + if ( defined $c->flash->{$_} ) { + $c->stash->{$_} = $c->flash->{$_}; + } + } + + my @inspectors = $c->cobrand->users->search({ + 'user_body_permissions.permission_type' => 'report_inspect' + }, { + join => 'user_body_permissions', + distinct => 1, + } + )->all; + $c->stash->{inspectors} = \@inspectors; + + # Default start/end date is today + my $now = DateTime->now( time_zone => + FixMyStreet->time_zone || FixMyStreet->local_time_zone ); + $c->stash->{start_date} ||= $now; + $c->stash->{end_date} ||= $now; + +} + +sub download : Path('download') : Args(0) { + my ( $self, $c ) = @_; + + if ( !$c->cobrand->can('exor_rdi_link_id') ) { + # This only works on the Oxfordshire cobrand currently. + $c->detach( '/page_error_404_not_found', [] ); + } + + my $parser = DateTime::Format::Strptime->new( pattern => '%d/%m/%Y' ); + my $start_date = $parser-> parse_datetime ( $c->get_param('start_date') ); + my $end_date = $parser-> parse_datetime ( $c->get_param('end_date') ) ; + my $one_day = DateTime::Duration->new( days => 1 ); + + my %params = ( + -and => [ + state => [ 'action scheduled' ], + 'admin_log_entries.action' => 'inspected', + 'admin_log_entries.whenedited' => { '>=', $start_date }, + 'admin_log_entries.whenedited' => { '<=', $end_date + $one_day }, + ] + ); + + my $user; + if ( $c->get_param('user_id') ) { + my $uid = $c->get_param('user_id'); + $params{'admin_log_entries.user_id'} = $uid; + $user = $c->model('DB::User')->find( { id => $uid } ); + } + + my $problems = $c->cobrand->problems->search( + \%params, + { + join => 'admin_log_entries', + distinct => 1, + } + ); + + if ( !$problems->count ) { + if ( defined $user ) { + $c->flash->{error_message} = _("No inspections by that inspector in the selected date range."); + } else { + $c->flash->{error_message} = _("No inspections in the selected date range."); + } + $c->flash->{start_date} = $start_date; + $c->flash->{end_date} = $end_date; + $c->flash->{user_id} = $user->id if $user; + $c->res->redirect( $c->uri_for( '' ) ); + } + + # A single RDI file might contain inspections from multiple inspectors, so + # we need to group inspections by inspector within G records. + my $inspectors = {}; + my $inspector_initials = {}; + while ( my $report = $problems->next ) { + my $user = $report->inspection_log_entry->user; + $inspectors->{$user->id} ||= []; + push @{ $inspectors->{$user->id} }, $report; + unless ( $inspector_initials->{$user->id} ) { + $inspector_initials->{$user->id} = $user->get_extra_metadata('initials'); + } + } + + my $csv = Text::CSV->new({ binary => 1, eol => "" }); + + my $p_count = 0; + my $link_id = $c->cobrand->exor_rdi_link_id; + + # RDI first line is always the same + $csv->combine("1", "1.8", "1.0.0.0", "ENHN", ""); + my @body = ($csv->string); + + my $i = 0; + foreach my $inspector_id (keys %$inspectors) { + my $inspections = $inspectors->{$inspector_id}; + my $initials = $inspector_initials->{$inspector_id}; + + $csv->combine( + "G", # start of an area/sequence + $link_id, # area/link id, fixed value for our purposes + "","", # must be empty + $initials || "XX", # inspector initials + $start_date->strftime("%y%m%d"), # date of inspection yymmdd + "0700", # time of inspection hhmm, set to static value for now + "D", # inspection variant, should always be D + "INS", # inspection type, always INS + "N", # Area of the county - north (N) or south (S) + "", "", "", "" # empty fields + ); + push @body, $csv->string; + + $csv->combine( + "H", # initial inspection type + "MC" # minor carriageway (changes depending on activity code) + ); + push @body, $csv->string; + + foreach my $report (@$inspections) { + my ($eastings, $northings) = $report->local_coords; + my $description = sprintf("%s %s", $report->external_id || "", $report->get_extra_metadata('detailed_information') || ""); + $csv->combine( + "I", # beginning of defect record + "MC", # activity code - minor carriageway, also FC (footway) + "", # empty field, can also be A (seen on MC) or B (seen on FC) + sprintf("%03d", ++$i), # randomised sequence number + "${eastings}E ${northings}N", # defect location field, which we don't capture from inspectors + $report->inspection_log_entry->whenedited->strftime("%H%M"), # defect time raised + "","","","","","","", # empty fields + $report->get_extra_metadata('traffic_information') ? 'TM required' : 'TM none', # further description + $description, # defect description + ); + push @body, $csv->string; + + $csv->combine( + "J", # georeferencing record + $report->get_extra_metadata('defect_type') || 'SFP2', # defect type - SFP2: sweep and fill <1m2, POT2 also seen + $report->response_priority ? + $report->response_priority->external_id : + "2", # priority of defect + "","", # empty fields + $eastings, # eastings + $northings, # northings + "","","","","" # empty fields + ); + push @body, $csv->string; + + $csv->combine( + "M", # bill of quantities record + "resolve", # permanent repair + "","", # empty fields + "/CMC", # /C + activity code + "", "" # empty fields + ); + push @body, $csv->string; + } + + # end this group of defects with a P record + $csv->combine( + "P", # end of area/sequence + 0, # always 0 + 999999, # charging code, always 999999 in OCC + ); + push @body, $csv->string; + $p_count++; + } + + # end the RDI file with an X record + my $record_count = $i; + $csv->combine( + "X", # end of inspection record + $p_count, + $p_count, + $record_count, # number of I records + $record_count, # number of J records + 0, 0, 0, # always zero + $record_count, # number of M records + 0, # always zero + $p_count, + 0, 0, 0 # error counts, always zero + ); + push @body, $csv->string; + + my $start = $start_date->strftime("%Y%m%d"); + my $end = $end_date->strftime("%Y%m%d"); + my $filename = sprintf("exor_defects-%s-%s.rdi", $start, $end); + if ( $user ) { + my $initials = $user->get_extra_metadata("initials") || ""; + $filename = sprintf("exor_defects-%s-%s-%s.rdi", $start, $end, $initials); + } + $c->res->content_type('text/csv; charset=utf-8'); + $c->res->header('content-disposition' => "attachment; filename=$filename"); + # The RDI format is very weird CSV - each line must be wrapped in + # double quotes. + $c->res->body( join "", map { "\"$_\"\r\n" } @body ); +} + +1;
\ No newline at end of file diff --git a/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm b/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm index a6c13c117..bae0f71a7 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm @@ -70,6 +70,7 @@ sub edit : Path : Args(2) { $priority->deleted( $c->get_param('deleted') ? 1 : 0 ); $priority->name( $c->get_param('name') ); $priority->description( $c->get_param('description') ); + $priority->external_id( $c->get_param('external_id') ); $priority->update_or_insert; my @live_contact_ids = map { $_->id } @live_contacts; diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm index 175db4a19..35d7afd5b 100644 --- a/perllib/FixMyStreet/App/Controller/Report.pm +++ b/perllib/FixMyStreet/App/Controller/Report.pm @@ -339,16 +339,13 @@ sub inspect : Private { my %update_params = (); if ($permissions->{report_inspect}) { - foreach (qw/detailed_information traffic_information duplicate_of/) { + foreach (qw/detailed_information traffic_information duplicate_of defect_type/) { $problem->set_extra_metadata( $_ => $c->get_param($_) ); } - if ( $c->get_param('save_inspected') ) { + if ( $c->get_param('include_update') ) { $update_text = Utils::cleanup_text( $c->get_param('public_update'), { allow_multiline => 1 } ); - if ($update_text) { - $problem->set_extra_metadata( inspected => 1 ); - $reputation_change = 1; - } else { + if (!$update_text) { $valid = 0; $c->stash->{errors} ||= []; push @{ $c->stash->{errors} }, _('Please provide a public update for this report.'); @@ -374,6 +371,16 @@ sub inspect : Private { } if ( $problem->state ne $old_state ) { $c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'state_change' ] ); + + # If the state has been changed by an inspector, consider the + # report to be inspected. + unless ($problem->get_extra_metadata('inspected')) { + $problem->set_extra_metadata( inspected => 1 ); + $c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'inspected' ] ); + my $state = $problem->state; + $reputation_change = 1 if $c->cobrand->reputation_increment_states->{$state}; + $reputation_change = -1 if $c->cobrand->reputation_decrement_states->{$state}; + } } } diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 6d90b6ee9..2a68b170e 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -659,8 +659,7 @@ sub setup_categories_and_bodies : Private { push @category_options, _('Other') if $seen{_('Other')}; } - $c->cobrand->munge_category_list(\@category_options, \@contacts, \%category_extras) - if $c->cobrand->can('munge_category_list'); + $c->cobrand->call_hook(munge_category_list => \@category_options, \@contacts, \%category_extras); # put results onto stash for display $c->stash->{bodies} = \%bodies; @@ -903,7 +902,7 @@ sub contacts_to_bodies : Private { if ($c->stash->{unresponsive}{$category} || $c->stash->{unresponsive}{ALL}) { []; } else { - if ( $c->cobrand->can('singleton_bodies_str') && $c->cobrand->singleton_bodies_str ) { + if ( $c->cobrand->call_hook('singleton_bodies_str') ) { # Cobrands like Zurich can only ever have a single body: 'x', because some functionality # relies on string comparison against bodies_str. [ $contacts[0]->body ]; @@ -1033,9 +1032,7 @@ sub send_problem_confirm_email : Private { $template = 'problem-confirm-not-sending.txt' unless $report->bodies_str; $c->stash->{token_url} = $c->uri_for_email( '/P', $token->token ); - if ($c->cobrand->can('problem_confirm_email_extras')) { - $c->cobrand->problem_confirm_email_extras($report); - } + $c->cobrand->call_hook(problem_confirm_email_extras => $report); $c->send_email( $template, { to => [ $report->name ? [ $report->user->email, $report->name ] : $report->user->email ], diff --git a/perllib/FixMyStreet/Cobrand/Base.pm b/perllib/FixMyStreet/Cobrand/Base.pm index a9eed0018..ea2b8f410 100644 --- a/perllib/FixMyStreet/Cobrand/Base.pm +++ b/perllib/FixMyStreet/Cobrand/Base.pm @@ -65,6 +65,18 @@ sub is_default { return $self->moniker eq 'default'; } +=head2 call_hook + + $cobrand->call_hook(foo => 1, 2, 3); # calls $cobrand->foo(1, 2, 3) if it exists + +=cut + +sub call_hook { + my ($self, $method_name, @args) = @_; + my $method = $self->can($method_name) or return; + return $self->$method(@args); +} + # NB: this Base class is for 'meta' features. To add base methods for all cobrands, # you may want to look at FMS::Cobrand::Default instead! diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index 1fdbe9de2..63db8b64a 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -665,7 +665,6 @@ sub admin_pages { $pages->{responsepriorities} = [ _('Priorities'), 4 ]; $pages->{responsepriority_edit} = [ undef, undef ]; }; - if ( $user->has_body_permission_to('user_edit') ) { $pages->{users} = [ _('Users'), 6 ]; $pages->{user_edit} = [ undef, undef ]; @@ -1188,6 +1187,16 @@ sub category_extra_hidden { return 0; } +=head2 reputation_increment_states/reputation_decrement_states + +Get a hashref of states that cause the reporting user's reputation to be +incremented/decremented, if a report is changed to this state upon inspection. + +=cut + +sub reputation_increment_states { {} }; +sub reputation_decrement_states { {} }; + sub traffic_management_options { return [ _("Yes"), diff --git a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm index e58c7f36b..7fa548406 100644 --- a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm +++ b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm @@ -129,5 +129,40 @@ sub traffic_management_options { ]; } +sub admin_pages { + my $self = shift; + + my $user = $self->{c}->user; + + my $pages = $self->next::method(); + + # Oxfordshire have a custom admin page for downloading reports in an Exor- + # friendly format which anyone with report_instruct permission can use. + if ( $user->is_superuser || $user->has_body_permission_to('report_instruct') ) { + $pages->{exordefects} = [ _('Download Exor RDI'), 10 ]; + } + + return $pages; +} + +sub defect_types { + { + SFP2 => "SFP2: sweep and fill <1m2", + POT2 => "POT2", + }; +} + +sub exor_rdi_link_id { 1989169 } +sub exor_rdi_link_length { 50 } + +sub reputation_increment_states { + return { + 'action scheduled' => 1, + }; +} + +sub user_extra_fields { + return [ 'initials' ]; +} 1; diff --git a/perllib/FixMyStreet/Cobrand/UKCouncils.pm b/perllib/FixMyStreet/Cobrand/UKCouncils.pm index 4e900e653..e0b6b5298 100644 --- a/perllib/FixMyStreet/Cobrand/UKCouncils.pm +++ b/perllib/FixMyStreet/Cobrand/UKCouncils.pm @@ -76,7 +76,7 @@ sub users_restriction { my $or_query = [ from_body => $self->council_id, - id => [ { -in => $problem_user_ids }, { -in => $update_user_ids } ], + 'me.id' => [ { -in => $problem_user_ids }, { -in => $update_user_ids } ], ]; if ($self->can('admin_user_domain')) { my $domain = $self->admin_user_domain; diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm index 97cb28fe8..d78eda78f 100644 --- a/perllib/FixMyStreet/DB/Result/Problem.pm +++ b/perllib/FixMyStreet/DB/Result/Problem.pm @@ -613,9 +613,7 @@ sub meta_line { my $meta = ''; my $category = $problem->category; - if ($c->cobrand->can('change_category_text')) { - $category = $c->cobrand->change_category_text($category); - } + $category = $c->cobrand->call_hook(change_category_text => $category) || $category; if ( $problem->anonymous ) { if ( $problem->service and $category && $category ne _('Other') ) { @@ -748,17 +746,10 @@ sub can_display_external_id { sub duration_string { my ( $problem, $c ) = @_; - my $body; - if ( $c->cobrand->can('link_to_council_cobrand') ) { - $body = $c->cobrand->link_to_council_cobrand($problem); - } else { - $body = $problem->body( $c ); - } - if ( $c->cobrand->can('get_body_handler_for_problem') ) { - my $handler = $c->cobrand->get_body_handler_for_problem( $problem ); - if ( $handler->can('is_council_with_case_management') && $handler->is_council_with_case_management ) { - return sprintf(_('Received by %s moments later'), $body); - } + my $body = $c->cobrand->call_hook(link_to_council_cobrand => $problem) || $problem->body($c); + my $handler = $c->cobrand->call_hook(get_body_handler_for_problem => $problem); + if ( $handler && $handler->call_hook('is_council_with_case_management') ) { + return sprintf(_('Received by %s moments later'), $body); } return unless $problem->whensent; return sprintf(_('Sent to %s %s later'), $body, @@ -1109,4 +1100,13 @@ has traffic_management_options => ( }, ); +has inspection_log_entry => ( + is => 'ro', + lazy => 1, + default => sub { + my $self = shift; + return $self->admin_log_entries->search({ action => 'inspected' }, { order_by => { -desc => 'whenedited' } })->first; + }, +); + 1; diff --git a/perllib/FixMyStreet/DB/Result/ResponsePriority.pm b/perllib/FixMyStreet/DB/Result/ResponsePriority.pm index 6bc8474fa..44635d174 100644 --- a/perllib/FixMyStreet/DB/Result/ResponsePriority.pm +++ b/perllib/FixMyStreet/DB/Result/ResponsePriority.pm @@ -26,6 +26,8 @@ __PACKAGE__->add_columns( { data_type => "boolean", default_value => \"false", is_nullable => 0 }, "description", { data_type => "text", is_nullable => 1 }, + "external_id", + { data_type => "text", is_nullable => 1 }, ); __PACKAGE__->set_primary_key("id"); __PACKAGE__->add_unique_constraint("response_priorities_body_id_name_key", ["body_id", "name"]); @@ -49,8 +51,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-10-17 16:37:28 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wok3cPA7cPjG4e9lnc1PIg +# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-12-14 17:12:09 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:glsO0fLK6fNvg4TmW1DMPg __PACKAGE__->many_to_many( contacts => 'contact_response_priorities', 'contact' ); diff --git a/perllib/FixMyStreet/Script/Reports.pm b/perllib/FixMyStreet/Script/Reports.pm index 193c5fa41..a7f3cb84b 100644 --- a/perllib/FixMyStreet/Script/Reports.pm +++ b/perllib/FixMyStreet/Script/Reports.pm @@ -112,9 +112,7 @@ sub send(;$) { $h{user_details} .= sprintf(_('Email: %s'), $row->user->email) . "\n\n"; } - if ($cobrand->can('process_additional_metadata_for_email')) { - $cobrand->process_additional_metadata_for_email($row, \%h); - } + $cobrand->call_hook(process_additional_metadata_for_email => $row, \%h); my $bodies = FixMyStreet::DB->resultset('Body')->search( { id => $row->bodies_str_ids }, diff --git a/perllib/FixMyStreet/SendReport/Email.pm b/perllib/FixMyStreet/SendReport/Email.pm index 4cee58d42..5a287a208 100644 --- a/perllib/FixMyStreet/SendReport/Email.pm +++ b/perllib/FixMyStreet/SendReport/Email.pm @@ -84,7 +84,7 @@ sub send { From => $self->send_from( $row ), }; - $cobrand->munge_sendreport_params($row, $h, $params) if $cobrand->can('munge_sendreport_params'); + $cobrand->call_hook(munge_sendreport_params => $row, $h, $params); $params->{Bcc} = $self->bcc if @{$self->bcc}; diff --git a/t/app/controller/report_inspect.t b/t/app/controller/report_inspect.t index 9888fc0f4..cc98f2b64 100644 --- a/t/app/controller/report_inspect.t +++ b/t/app/controller/report_inspect.t @@ -57,7 +57,7 @@ FixMyStreet::override_config { }; subtest "test basic inspect submission" => sub { - $mech->submit_form_ok({ button => 'save', with_fields => { traffic_information => 'Yes', state => 'Action Scheduled', save_inspected => undef } }); + $mech->submit_form_ok({ button => 'save', with_fields => { traffic_information => 'Yes', state => 'Action Scheduled', include_update => undef } }); $report->discard_changes; is $report->state, 'action scheduled', 'report state changed'; is $report->get_extra_metadata('traffic_information'), 'Yes', 'report data changed'; @@ -65,22 +65,25 @@ FixMyStreet::override_config { subtest "test inspect & instruct submission" => sub { $report->unset_extra_metadata('inspected'); + $report->state('confirmed'); $report->update; - my $reputation = $report->user->get_extra_metadata("reputation") || 0; + $report->inspection_log_entry->delete; + my $reputation = $report->user->get_extra_metadata("reputation"); $mech->get_ok("/report/$report_id"); - $mech->submit_form_ok({ button => 'save', with_fields => { public_update => "This is a public update.", save_inspected => "1" } }); + $mech->submit_form_ok({ button => 'save', with_fields => { public_update => "This is a public update.", include_update => "1", state => 'action scheduled' } }); $report->discard_changes; is $report->comments->first->text, "This is a public update.", 'Update was created'; is $report->get_extra_metadata('inspected'), 1, 'report marked as inspected'; - is $report->user->get_extra_metadata('reputation'), $reputation+1, "User reputation was increased"; + is $report->user->get_extra_metadata('reputation'), $reputation, "User reputation wasn't changed"; }; subtest "test update is required when instructing" => sub { $report->unset_extra_metadata('inspected'); $report->update; + $report->inspection_log_entry->delete; $report->comments->delete_all; $mech->get_ok("/report/$report_id"); - $mech->submit_form_ok({ button => 'save', with_fields => { public_update => undef, save_inspected => "1" } }); + $mech->submit_form_ok({ button => 'save', with_fields => { public_update => undef, include_update => "1" } }); is_deeply $mech->page_errors, [ "Please provide a public update for this report." ], 'errors match'; $report->discard_changes; is $report->comments->count, 0, "Update wasn't created"; @@ -117,7 +120,7 @@ FixMyStreet::override_config { $report->comments->delete_all; $mech->get_ok("/report/$report_id"); - $mech->submit_form_ok({ button => 'save', with_fields => { state => 'Duplicate', duplicate_of => $report2->id, public_update => "This is a duplicate.", save_inspected => "1" } }); + $mech->submit_form_ok({ button => 'save', with_fields => { state => 'Duplicate', duplicate_of => $report2->id, public_update => "This is a duplicate.", include_update => "1" } }); $report->discard_changes; is $report->state, 'duplicate', 'report marked as duplicate'; @@ -136,7 +139,7 @@ FixMyStreet::override_config { state => 'Duplicate', duplicate_of => $report2->id, public_update => $update_text, - save_inspected => "1", + include_update => "1", }}); $report->discard_changes; @@ -178,19 +181,32 @@ FixMyStreet::override_config { ALLOWED_COBRANDS => 'oxfordshire', }, sub { subtest "test negative reputation" => sub { - my $reputation = $report->user->get_extra_metadata("reputation"); + my $reputation = $report->user->get_extra_metadata("reputation") || 0; $mech->get_ok("/report/$report_id"); $mech->submit_form( button => 'remove_from_site' ); $report->discard_changes; is $report->user->get_extra_metadata('reputation'), $reputation-1, "User reputation was decreased"; + $report->update({ state => 'confirmed' }); + }; + + subtest "test positive reputation" => sub { + $report->unset_extra_metadata('inspected'); + $report->update; + $report->inspection_log_entry->delete if $report->inspection_log_entry; + my $reputation = $report->user->get_extra_metadata("reputation") || 0; + $mech->get_ok("/report/$report_id"); + $mech->submit_form_ok({ button => 'save', with_fields => { state => 'action scheduled', include_update => undef } }); + $report->discard_changes; + is $report->get_extra_metadata('inspected'), 1, 'report marked as inspected'; + is $report->user->get_extra_metadata('reputation'), $reputation+1, "User reputation was increased"; }; subtest "Oxfordshire-specific traffic management options are shown" => sub { $report->update({ state => 'confirmed' }); $mech->get_ok("/report/$report_id"); - $mech->submit_form_ok({ button => 'save', with_fields => { traffic_information => 'Signs and Cones', state => 'Action Scheduled', save_inspected => undef } }); + $mech->submit_form_ok({ button => 'save', with_fields => { traffic_information => 'Signs and Cones', state => 'Action Scheduled', include_update => undef } }); $report->discard_changes; is $report->state, 'action scheduled', 'report state changed'; is $report->get_extra_metadata('traffic_information'), 'Signs and Cones', 'report data changed'; @@ -211,7 +227,7 @@ FixMyStreet::override_config { # which should cause it to be resent. We clear the host because # otherwise testing stays on host() above. $mech->clear_host; - $mech->submit_form(button => 'save', with_fields => { category => 'Horses', save_inspected => undef, }); + $mech->submit_form(button => 'save', with_fields => { category => 'Horses', include_update => undef, }); $report->discard_changes; is $report->category, "Horses", "Report in correct category"; diff --git a/t/cobrand/oxfordshire.t b/t/cobrand/oxfordshire.t index d9f880d07..b0fad3b56 100644 --- a/t/cobrand/oxfordshire.t +++ b/t/cobrand/oxfordshire.t @@ -45,6 +45,29 @@ subtest 'check /ajax defaults to open reports only' => sub { } }; +my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1); + +subtest 'Exor RDI download appears on Oxfordshire cobrand admin' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'oxfordshire' => '.' } ], + }, sub { + $mech->log_in_ok( $superuser->email ); + $mech->get_ok('/admin'); + $mech->content_contains("Download Exor RDI"); + } +}; + +subtest 'Exor RDI download doesn’t appear outside of Oxfordshire cobrand admin' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + }, sub { + $mech->log_in_ok( $superuser->email ); + $mech->get_ok('/admin'); + $mech->content_lacks("Download Exor RDI"); + } +}; + # Clean up +$mech->delete_user( $superuser ); $mech->delete_problems_for_body( 2237 ); done_testing(); diff --git a/templates/web/base/admin/exordefects/index.html b/templates/web/base/admin/exordefects/index.html new file mode 100644 index 000000000..06d2aa7a5 --- /dev/null +++ b/templates/web/base/admin/exordefects/index.html @@ -0,0 +1,36 @@ +[% INCLUDE 'admin/header.html' title=loc('Download Exor RDI') -%] + +[% IF error_message %] + <h2>Error</h2> + <p>[% error_message %]</p> +[% END %] + +<form method="get" action="[% c.uri_for('download') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8"> + <p> + <label for="start_date">[% loc('Start Date:') %]</label><input type="text" class="form-control" + placeholder="[% loc('Click here or enter as dd/mm/yyyy') %]" name="start_date" id="start_date" + value="[% start_date ? start_date.strftime( '%d/%m/%Y') : '' | html %]" /> + </p> + + <p> + <label for="end_date">[% loc('End Date:') %]</label><input type="text" class="form-control" + placeholder="[% loc('Click here or enter as dd/mm/yyyy') %]" name="end_date" id="end_date" size="5" + value="[% end_date ? end_date.strftime( '%d/%m/%Y') : '' | html %]" /> + </p> + + <p> + [% loc('Inspector:') %] <select class="form-control" id='user_id' name='user_id'> + <option value=''>[% loc('All inspectors') %]</option> + [% FOR inspector IN inspectors %] + <option value="[% inspector.id %]" [% 'selected' IF user_id == inspector.id %]>[% inspector.name %] ([% inspector.get_extra_metadata('initials') %])</option> + [% END %] + </select> + </p> + + <p> + <input type="submit" class="btn" size="30" value="Download RDI file" /> + </p> +</form> + + +[% INCLUDE 'admin/footer.html' %] diff --git a/templates/web/base/admin/responsepriorities/edit.html b/templates/web/base/admin/responsepriorities/edit.html index 4d838eed2..a4dc61213 100644 --- a/templates/web/base/admin/responsepriorities/edit.html +++ b/templates/web/base/admin/responsepriorities/edit.html @@ -20,6 +20,16 @@ <div class="admin-hint"> <p> + [% loc('If this priority is passed to an external service (e.g. Exor/Confirm) enter the priority code to use with that service here.') %] + </p> + </div> + <p> + <strong>[% loc('External ID:') %] </strong> + <input type="text" name="external_id" class="form-control" size="30" value="[% rp.external_id | html %]"> + </p> + + <div class="admin-hint"> + <p> [% loc('If you only want this priority to be an option for specific categories, pick them here. By default they will show for all categories.') %] </p> </div> diff --git a/templates/web/base/admin/user-form.html b/templates/web/base/admin/user-form.html index 0f5452b0a..dbd554b1e 100644 --- a/templates/web/base/admin/user-form.html +++ b/templates/web/base/admin/user-form.html @@ -121,14 +121,6 @@ </label> [% END %] </li> - <li> - <div class="admin-hint"> - <p> - [% loc("Reports from users with high enough reputation will be sent immediately without requiring inspection. Each category's threshold can be managed on its edit page. Users earn reputation when a report they have made is marked as inspected by inspectors.") %] - </p> - </div> - [% loc('Reputation:') %] [% user.get_extra_metadata('reputation') %] - </li> [% END %] [% IF c.user.is_superuser %] @@ -173,6 +165,7 @@ </ul> [% END %] [% END %] + [% TRY %][% INCLUDE 'admin/user-form-extra-fields.html' %][% CATCH file %][% END %] </ul> <input type="submit" class="btn" name="Submit changes" value="[% loc('Submit changes') %]" > </form> diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html index 5a7e99ef9..625887eff 100644 --- a/templates/web/base/report/_inspect.html +++ b/templates/web/base/report/_inspect.html @@ -62,6 +62,18 @@ [% END %] [% IF permissions.report_inspect %] + [% IF c.cobrand.defect_types %] + <p> + <label for="defect_type">[% loc('Defect type') %]</label> + [% defect_type = problem.get_extra_metadata('defect_type') %] + <select id="defect_type" name="defect_type" class="form-control"> + <option value=""[% ' selected' IF NOT defect_type %]>-</option> + [% FOREACH dt IN c.cobrand.defect_types.pairs %] + <option[% ' selected' IF defect_type == dt.key %] value="[% dt.key | html %]">[% dt.value | html %]</option> + [% END %] + </select> + </p> + [% END %] <p> <label for="state">[% loc('State') %]</label> [% INCLUDE 'report/inspect/state_groups_select.html' %] @@ -127,7 +139,7 @@ [% IF permissions.report_inspect %] <p> <label class="label-containing-checkbox"> - <input type="checkbox" name="save_inspected" value="1" class="js-toggle-public-update" checked> + <input type="checkbox" name="include_update" value="1" class="js-toggle-public-update" checked> [% loc('Save with a public update') %] </label> </p> diff --git a/templates/web/oxfordshire/admin/user-form-extra-fields.html b/templates/web/oxfordshire/admin/user-form-extra-fields.html new file mode 100644 index 000000000..4109a3075 --- /dev/null +++ b/templates/web/oxfordshire/admin/user-form-extra-fields.html @@ -0,0 +1,21 @@ +<li> + <div class="admin-hint"> + <p> + [% loc("Reports from users with high enough reputation will be sent immediately without requiring inspection. Each category's threshold can be managed on its edit page. Users earn reputation when a report they have made is marked as inspected by inspectors.") %] + </p> + </div> + [% loc('Reputation:') %] [% user.get_extra_metadata('reputation') %] +</li> + +<li> + <div class="admin-hint"> + <p> + [% loc( + "The user's initials are used when sending inspections to Exor. + Only inspectors need to have this field filled in.") + %] + </p> + </div> + <label for="initials">[% loc('Initials:') %]</label> + <input type='text' class="form-control" name='extra[initials]' id='initials' value='[% user.get_extra_metadata("initials") | html %]'> +</li> diff --git a/web/cobrands/fixmystreet/offline.js b/web/cobrands/fixmystreet/offline.js index 7b9be4c6c..0fdad1279 100644 --- a/web/cobrands/fixmystreet/offline.js +++ b/web/cobrands/fixmystreet/offline.js @@ -322,7 +322,7 @@ fixmystreet.offline = (function() { if (savedForm) { savedForm.replace(/\+/g, '%20').split('&').forEach(function(kv) { kv = kv.split('=', 2); - if (kv[0] != 'save_inspected' && kv[0] != 'public_update' && kv[0] != 'save') { + if (kv[0] != 'include_update' && kv[0] != 'public_update' && kv[0] != 'save') { $('[name=' + kv[0] + ']').val(decodeURIComponent(kv[1])); } }); diff --git a/web/js/fixmystreet-admin.js b/web/js/fixmystreet-admin.js index 884ad1c09..02eb30766 100644 --- a/web/js/fixmystreet-admin.js +++ b/web/js/fixmystreet-admin.js @@ -66,6 +66,18 @@ $(function(){ }); } + // On some cobrands the datepicker ends up beneath items in the header, e.g. + // the logo. + // This function sets an appropriate z-index when the datepicker is shown. + // Sadly there's no way to set the z-index when creating the datepicker, so + // we have to run this little helper using the datepicker beforeShow + // handler. + function fixZIndex() { + setTimeout(function() { + $('.ui-datepicker').css('z-index', 10); + }, 0); + } + $( "#start_date" ).datepicker({ defaultDate: "-1w", changeMonth: true, @@ -73,7 +85,8 @@ $(function(){ // This sets the other fields minDate to our date onClose: function( selectedDate ) { $( "#end_date" ).datepicker( "option", "minDate", selectedDate ); - } + }, + beforeShow: fixZIndex }); $( "#end_date" ).datepicker({ /// defaultDate: "+1w", @@ -81,7 +94,8 @@ $(function(){ dateFormat: 'dd/mm/yy' , onClose: function( selectedDate ) { $( "#start_date" ).datepicker( "option", "maxDate", selectedDate ); - } + }, + beforeShow: fixZIndex }); // On user edit page, hide the area/categories fields if body changes |