diff options
-rwxr-xr-x | bin/oxfordshire/send-rdi-emails | 69 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm | 198 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Default.pm | 9 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/UK.pm | 3 | ||||
-rw-r--r-- | perllib/FixMyStreet/Integrations/ExorRDI.pm | 208 | ||||
-rw-r--r-- | t/Mock/MapIt.pm | 14 | ||||
-rw-r--r-- | t/cobrand/oxfordshire.t | 62 | ||||
-rw-r--r-- | templates/email/oxfordshire/rdi.txt | 8 |
8 files changed, 383 insertions, 188 deletions
diff --git a/bin/oxfordshire/send-rdi-emails b/bin/oxfordshire/send-rdi-emails new file mode 100755 index 000000000..ef0931d6c --- /dev/null +++ b/bin/oxfordshire/send-rdi-emails @@ -0,0 +1,69 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use v5.14; + +BEGIN { + use File::Basename qw(dirname); + use File::Spec; + my $d = dirname(File::Spec->rel2abs($0)); + require "$d/../../setenv.pl"; +} + +use DateTime; +use Try::Tiny; +use FixMyStreet; +use FixMyStreet::Cobrand; +use FixMyStreet::Email; +use FixMyStreet::Integrations::ExorRDI; + +my $end_date = DateTime->now( time_zone => FixMyStreet->time_zone || FixMyStreet->local_time_zone ) + ->truncate(to => 'hour')->set_hour(16); +my $start_date = $end_date->clone->subtract(days => 1); + +my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('oxfordshire')->new; +$cobrand->set_lang_and_domain('en-gb', 1); +my @inspectors = $cobrand->users->search({ + 'user_body_permissions.permission_type' => 'report_inspect' +}, { + join => 'user_body_permissions', + distinct => 1, +})->all; + +foreach my $inspector (@inspectors) { + my $params = { + start_date => $start_date, + end_date => $end_date, + user => $inspector, + }; + my $rdi = FixMyStreet::Integrations::ExorRDI->new($params); + try { + my $hdrs = { + To => join('', 'fms', '_', 'admin', '@', $cobrand->admin_user_domain), + _attachments_ => [ { + body => $rdi->construct, + attributes => { + filename => $rdi->filename, + charset => 'utf-8', + content_type => 'text/csv', + name => $rdi->filename, + } + } ], + }; + + my $result = FixMyStreet::Email::send_cron( + FixMyStreet::DB->storage->schema, + "rdi.txt", $params, $hdrs, + undef, 0, $cobrand, + ); + if ($result) { + say "Failed to send inspection for " . $inspector->id; + } + } catch { + die $_ unless $_ =~ /FixMyStreet::Integrations::ExorRDI::Error/; + # Nothing to report, continue + } +} + +1; diff --git a/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm index 31638662f..1711ecb10 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm @@ -2,9 +2,9 @@ package FixMyStreet::App::Controller::Admin::ExorDefects; use Moose; use namespace::autoclean; -use Text::CSV; use DateTime; -use mySociety::Random qw(random_bytes); +use Try::Tiny; +use FixMyStreet::Integrations::ExorRDI; BEGIN { extends 'Catalyst::Controller'; } @@ -54,186 +54,30 @@ sub download : Path('download') : Args(0) { 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' ], - external_id => { '!=' => undef }, - '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 ) { + my $params = { + start_date => $start_date, + end_date => $end_date + $one_day, + user => $c->get_param('user_id'), + }; + my $rdi = FixMyStreet::Integrations::ExorRDI->new($params); + + try { + my $out = $rdi->construct; + $c->res->content_type('text/csv; charset=utf-8'); + $c->res->header('content-disposition' => "attachment; filename=" . $rdi->filename); + $c->res->body( $out ); + } catch { + die $_ unless $_ =~ /FixMyStreet::Integrations::ExorRDI::Error/; + if ($params->{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->flash->{start_date} = $params->{start_date}; + $c->flash->{end_date} = $params->{end_date}; + $c->flash->{user_id} = $params->{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 $location = "${eastings}E ${northings}N."; - $location = "[DID NOT USE MAP] $location" unless $report->used_map; - my $closest_address = $c->cobrand->find_closest($report, 1); - if (%$closest_address) { - $location .= " Nearest road: $closest_address->{road}." if $closest_address->{road}; - $location .= " Nearest postcode: $closest_address->{postcode}{postcode}." if $closest_address->{postcode}; - } - - my $description = sprintf("%s %s", $report->external_id || "", $report->get_extra_metadata('detailed_information') || ""); - my $activity_code = $report->defect_type ? - $report->defect_type->get_extra_metadata('activity_code') - : 'MC'; - my $traffic_information = $report->get_extra_metadata('traffic_information') ? - 'TM ' . $report->get_extra_metadata('traffic_information') - : 'TM none'; - - $csv->combine( - "I", # beginning of defect record - $activity_code, # 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 - $location, # defect location field, which we don't capture from inspectors - $report->inspection_log_entry->whenedited->strftime("%H%M"), # defect time raised - "","","","","","","", # empty fields - $traffic_information, - $description, # defect description - ); - push @body, $csv->string; - - my $defect_type = $report->defect_type ? - $report->defect_type->get_extra_metadata('defect_code') - : 'SFP2'; - $csv->combine( - "J", # georeferencing record - $defect_type, # 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; diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index ad84bd496..195978845 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -4,6 +4,7 @@ use base 'FixMyStreet::Cobrand::Base'; use strict; use warnings; use FixMyStreet; +use FixMyStreet::DB; use FixMyStreet::Geocode::Bing; use DateTime; use Encode; @@ -71,7 +72,7 @@ a cobrand that only wants some of the data. sub problems { my $self = shift; - return $self->problems_restriction($self->{c}->model('DB::Problem')); + return $self->problems_restriction(FixMyStreet::DB->resultset('Problem')); } =head1 problems_on_map @@ -83,7 +84,7 @@ restricted to a subset if we're on a cobrand that only wants some of the data. sub problems_on_map { my $self = shift; - return $self->problems_on_map_restriction($self->{c}->model('DB::Problem')); + return $self->problems_on_map_restriction(FixMyStreet::DB->resultset('Problem')); } =head1 updates @@ -95,7 +96,7 @@ a cobrand that only wants some of the data. sub updates { my $self = shift; - return $self->updates_restriction($self->{c}->model('DB::Comment')); + return $self->updates_restriction(FixMyStreet::DB->resultset('Comment')); } =head1 problems_restriction/updates_restriction @@ -149,7 +150,7 @@ a cobrand that only wants some of the data. sub users { my $self = shift; - return $self->users_restriction($self->{c}->model('DB::User')); + return $self->users_restriction(FixMyStreet::DB->resultset('User')); } =head1 users_restriction diff --git a/perllib/FixMyStreet/Cobrand/UK.pm b/perllib/FixMyStreet/Cobrand/UK.pm index 2d4a4d891..46891f906 100644 --- a/perllib/FixMyStreet/Cobrand/UK.pm +++ b/perllib/FixMyStreet/Cobrand/UK.pm @@ -121,7 +121,8 @@ sub find_closest { my $data = $self->SUPER::find_closest($problem, $as_data); - my $url = "https://mapit.mysociety.org/nearest/4326/" . $problem->longitude . ',' . $problem->latitude; + my $mapit_url = FixMyStreet->config('MAPIT_URL'); + my $url = $mapit_url . "nearest/4326/" . $problem->longitude . ',' . $problem->latitude; my $j = LWP::Simple::get($url); if ($j) { $j = JSON->new->utf8->allow_nonref->decode($j); diff --git a/perllib/FixMyStreet/Integrations/ExorRDI.pm b/perllib/FixMyStreet/Integrations/ExorRDI.pm new file mode 100644 index 000000000..b0e581f84 --- /dev/null +++ b/perllib/FixMyStreet/Integrations/ExorRDI.pm @@ -0,0 +1,208 @@ +package FixMyStreet::Integrations::ExorRDI::Error; + +use Moo; +with 'Throwable'; + +has message => (is => 'ro'); + +package FixMyStreet::Integrations::ExorRDI; + +use DateTime; +use Moo; +use Scalar::Util 'blessed'; +use Text::CSV; +use FixMyStreet::DB; +use namespace::clean; + +has [qw(start_date end_date)] => ( + is => 'ro', + required => 1, +); + +has user => ( + is => 'ro', + coerce => sub { + return $_[0] if blessed($_[0]) && $_[0]->isa('FixMyStreet::DB::Result::User'); + FixMyStreet::DB->resultset('User')->find( { id => $_[0] } ); + }, +); + +sub construct { + my $self = shift; + + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('oxfordshire')->new; + my $dtf = $cobrand->problems->result_source->storage->datetime_parser; + + my %params = ( + -and => [ + state => [ 'action scheduled' ], + external_id => { '!=' => undef }, + 'admin_log_entries.action' => 'inspected', + 'admin_log_entries.whenedited' => { '>=', $dtf->format_datetime($self->start_date) }, + 'admin_log_entries.whenedited' => { '<=', $dtf->format_datetime($self->end_date) }, + ] + ); + + $params{'admin_log_entries.user_id'} = $self->user->id if $self->user; + + my $problems = $cobrand->problems->search( + \%params, + { + join => 'admin_log_entries', + distinct => 1, + } + ); + FixMyStreet::Integrations::ExorRDI::Error->throw unless $problems->count; + + # 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 = $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 + $self->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 $location = "${eastings}E ${northings}N"; + $location = "[DID NOT USE MAP] $location" unless $report->used_map; + my $closest_address = $cobrand->find_closest($report, 1); + if (%$closest_address) { + $location .= " Nearest road: $closest_address->{road}." if $closest_address->{road}; + $location .= " Nearest postcode: $closest_address->{postcode}{postcode}." if $closest_address->{postcode}; + } + + my $description = sprintf("%s %s", $report->external_id || "", $report->get_extra_metadata('detailed_information') || ""); + my $activity_code = $report->defect_type ? + $report->defect_type->get_extra_metadata('activity_code') + : 'MC'; + my $traffic_information = $report->get_extra_metadata('traffic_information') ? + 'TM ' . $report->get_extra_metadata('traffic_information') + : 'TM none'; + + $csv->combine( + "I", # beginning of defect record + $activity_code, # 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 + $location, # defect location field, which we don't capture from inspectors + $report->inspection_log_entry->whenedited->strftime("%H%M"), # defect time raised + "","","","","","","", # empty fields + $traffic_information, + $description, # defect description + ); + push @body, $csv->string; + + my $defect_type = $report->defect_type ? + $report->defect_type->get_extra_metadata('defect_code') + : 'SFP2'; + $csv->combine( + "J", # georeferencing record + $defect_type, # 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; + + # The RDI format is very weird CSV - each line must be wrapped in + # double quotes. + return join "", map { "\"$_\"\r\n" } @body; +} + +has filename => ( + is => 'lazy', + default => sub { + my $self = shift; + my $start = $self->start_date->strftime("%Y%m%d"); + my $end = $self->end_date->strftime("%Y%m%d"); + my $filename = sprintf("exor_defects-%s-%s.rdi", $start, $end); + if ( $self->user ) { + my $initials = $self->user->get_extra_metadata("initials") || ""; + $filename = sprintf("exor_defects-%s-%s-%s.rdi", $start, $end, $initials); + } + return $filename; + }, +); + +1; diff --git a/t/Mock/MapIt.pm b/t/Mock/MapIt.pm index 43d44d519..0b355983b 100644 --- a/t/Mock/MapIt.pm +++ b/t/Mock/MapIt.pm @@ -31,6 +31,7 @@ my @PLACES = ( [ '?', 50.78301, -0.646929 ], [ 'GU51 4AE', 51.279456, -0.846216, 2333, 'Hart District Council', 'DIS', 2227, 'Hampshire County Council', 'CTY' ], [ 'WS1 4NH', 52.563074, -1.991032, 2535, 'Sandwell Borough Council', 'MTD' ], + [ 'OX28 4DS', 51.784721, -1.494453 ], ); sub dispatch_request { @@ -105,6 +106,19 @@ sub dispatch_request { my ($self, $area) = @_; return [ 200, [ 'Content-Type' => 'application/json' ], [ '"AB12 1AA"' ] ]; }, + + sub (GET + /nearest/**.*) { + my ($self, $point) = @_; + foreach (@PLACES) { + if ($point eq "4326/$_->[2],$_->[1]") { + return $self->output({ + postcode => { wgs84_lat => $_->[1], wgs84_lon => $_->[2], postcode => $_->[0] }, + }); + } + } + return $self->output({}); + }, + } LWP::Protocol::PSGI->register(t::Mock::MapIt->to_psgi_app, host => 'mapit.uk'); diff --git a/t/cobrand/oxfordshire.t b/t/cobrand/oxfordshire.t index b0fad3b56..4849554ea 100644 --- a/t/cobrand/oxfordshire.t +++ b/t/cobrand/oxfordshire.t @@ -5,12 +5,15 @@ use Test::More; use FixMyStreet::TestMech; my $mech = FixMyStreet::TestMech->new; +my $oxon = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); + subtest 'check /ajax defaults to open reports only' => sub { my $categories = [ 'Bridges', 'Fences', 'Manhole' ]; my $params = { postcode => 'OX28 4DS', - latitude => 51.7847208192, - longitude => -1.49445264029, + cobrand => 'oxfordshire', + latitude => 51.784721, + longitude => -1.494453, }; my $bbox = ($params->{longitude} - 0.01) . ',' . ($params->{latitude} - 0.01) . ',' . ($params->{longitude} + 0.01) . ',' . ($params->{latitude} + 0.01); @@ -23,7 +26,7 @@ subtest 'check /ajax defaults to open reports only' => sub { category => $category, state => $state, ); - $mech->create_problems_for_body( 1, 2237, 'Around page', \%report_params ); + $mech->create_problems_for_body( 1, $oxon->id, 'Around page', \%report_params ); } } @@ -46,6 +49,8 @@ subtest 'check /ajax defaults to open reports only' => sub { }; my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1); +my $inspector = $mech->create_user_ok('inspector@example.com', name => 'Inspector'); +$inspector->user_body_permissions->create({ body => $oxon, permission_type => 'report_inspect' }); subtest 'Exor RDI download appears on Oxfordshire cobrand admin' => sub { FixMyStreet::override_config { @@ -67,7 +72,52 @@ subtest 'Exor RDI download doesn’t appear outside of Oxfordshire cobrand admin } }; +subtest 'Exor file looks okay' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->log_in_ok( $superuser->email ); + $mech->get_ok('/admin/exordefects'); + $mech->submit_form_ok( { with_fields => { + start_date => '05/05/2017', + end_date => '05/05/2017', + user_id => $inspector->id, + } }, 'submit download'); + $mech->content_contains("No inspections by that inspector in the selected date range"); + my $problem = FixMyStreet::DB->resultset('Problem')->first; + $problem->update({ state => 'action scheduled', external_id => 123 }); + FixMyStreet::DB->resultset('AdminLog')->create({ + admin_user => $inspector->name, + user => $inspector, + object_type => 'problem', + action => 'inspected', + object_id => $problem->id, + whenedited => DateTime->new(year => 2017, month => 5, day => 5, hour => 12), + }); + $mech->submit_form_ok( { with_fields => { + start_date => '05/05/2017', + end_date => '05/05/2017', + user_id => $inspector->id, + } }, 'submit download'); + (my $rdi = $mech->content) =~ s/\r\n/\n/g; + is $rdi, <<EOF, "RDI file matches expected"; +"1,1.8,1.0.0.0,ENHN," +"G,1989169,,,XX,170505,0700,D,INS,N,,,," +"H,MC" +"I,MC,,001,"434970E 209683N Nearest postcode: OX28 4DS.",1200,,,,,,,,"TM none","123 "" +"J,SFP2,2,,,434970,209683,,,,," +"M,resolve,,,/CMC,," +"P,0,999999" +"X,1,1,1,1,0,0,0,1,0,1,0,0,0" +EOF + } +}; + # Clean up -$mech->delete_user( $superuser ); -$mech->delete_problems_for_body( 2237 ); -done_testing(); +END { + $mech->delete_user( $superuser ); + $mech->delete_user( $inspector ); + $mech->delete_problems_for_body( $oxon->id ); + done_testing(); +} diff --git a/templates/email/oxfordshire/rdi.txt b/templates/email/oxfordshire/rdi.txt new file mode 100644 index 000000000..f12467d68 --- /dev/null +++ b/templates/email/oxfordshire/rdi.txt @@ -0,0 +1,8 @@ +Subject: RDI report for [% user.name %] + +Please find attached RDI file for [% start_date %] +to [% end_date %]. + +---- + +The mySociety team and Oxfordshire County Council |