aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet
diff options
context:
space:
mode:
authorDave Arter <davea@mysociety.org>2017-02-13 15:13:12 +0000
committerMatthew Somerville <matthew@mysociety.org>2017-03-23 12:54:24 +0000
commit3f21a9742d89c3e4fda47a0be6ec2a17f802c99a (patch)
tree3f199f8f6b43aa8818dc3c5349623bb2d4a5e8a3 /perllib/FixMyStreet
parent6fb4eca34fd47612216f642985cac74359727b15 (diff)
Add customisable defect types.
Problems can have an associated defect type, that can be assigned during an inspection. Include an admin interface for managing these types, that can also be assigned on a per-category basis, currently available to the Oxfordshire cobrand. (Also include 'TM' in traffic management Exor RDI output.)
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm113
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm16
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm8
-rw-r--r--perllib/FixMyStreet/Cobrand/Oxfordshire.pm22
-rw-r--r--perllib/FixMyStreet/DB/Result/Body.pm10
-rw-r--r--perllib/FixMyStreet/DB/Result/Contact.pm11
-rw-r--r--perllib/FixMyStreet/DB/Result/ContactDefectType.pm46
-rw-r--r--perllib/FixMyStreet/DB/Result/DefectType.pm66
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm29
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/DefectType.pm22
-rw-r--r--perllib/FixMyStreet/TestMech.pm9
11 files changed, 341 insertions, 11 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm b/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm
new file mode 100644
index 000000000..bcfeb3dd8
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm
@@ -0,0 +1,113 @@
+package FixMyStreet::App::Controller::Admin::DefectTypes;
+use Moose;
+use namespace::autoclean;
+use mySociety::ArrayUtils;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+
+sub begin : Private {
+ my ( $self, $c ) = @_;
+
+ $c->forward('/admin/begin');
+}
+
+sub index : Path : Args(0) {
+ my ( $self, $c ) = @_;
+
+ my $user = $c->user;
+
+ if ($user->is_superuser) {
+ $c->forward('/admin/fetch_all_bodies');
+ } elsif ( $user->from_body ) {
+ $c->forward('load_user_body', [ $user->from_body->id ]);
+ $c->res->redirect( $c->uri_for( '', $c->stash->{body}->id ) );
+ } else {
+ $c->detach( '/page_error_404_not_found' );
+ }
+}
+
+sub list : Path : Args(1) {
+ my ($self, $c, $body_id) = @_;
+
+ $c->forward('load_user_body', [ $body_id ]);
+
+ my @defect_types = $c->stash->{body}->defect_types->search(
+ undef,
+ {
+ order_by => 'name'
+ }
+ );
+
+ $c->stash->{defect_types} = \@defect_types;
+}
+
+sub edit : Path : Args(2) {
+ my ( $self, $c, $body_id, $defect_type_id ) = @_;
+
+ $c->forward('load_user_body', [ $body_id ]);
+
+ my $defect_type;
+ if ($defect_type_id eq 'new') {
+ $defect_type = $c->stash->{body}->defect_types->new({});
+ }
+ else {
+ $defect_type = $c->stash->{body}->defect_types->find( $defect_type_id )
+ or $c->detach( '/page_error_404_not_found' );
+ }
+
+ $c->forward('/admin/fetch_contacts');
+ my @contacts = $defect_type->contacts->all;
+ my @live_contacts = $c->stash->{live_contacts}->all;
+ my %active_contacts = map { $_->id => 1 } @contacts;
+ my @all_contacts = map { {
+ id => $_->id,
+ category => $_->category,
+ active => $active_contacts{$_->id},
+ } } @live_contacts;
+ $c->stash->{contacts} = \@all_contacts;
+
+ if ($c->req->method eq 'POST') {
+ $defect_type->name( $c->get_param('name') );
+ $defect_type->description( $c->get_param('description') );
+
+ my @extra_fields = @{ $c->cobrand->call_hook('defect_type_extra_fields') || [] };
+ foreach ( @extra_fields ) {
+ $defect_type->set_extra_metadata( $_ => $c->get_param("extra[$_]") );
+ }
+
+ $defect_type->update_or_insert;
+ my @live_contact_ids = map { $_->id } @live_contacts;
+ my @new_contact_ids = $c->get_param_list('categories');
+ @new_contact_ids = @{ mySociety::ArrayUtils::intersection(\@live_contact_ids, \@new_contact_ids) };
+ $defect_type->contact_defect_types->search({
+ contact_id => { '!=' => \@new_contact_ids },
+ })->delete;
+ foreach my $contact_id (@new_contact_ids) {
+ $defect_type->contact_defect_types->find_or_create({
+ contact_id => $contact_id,
+ });
+ }
+
+ $c->res->redirect( $c->uri_for( '', $c->stash->{body}->id ) );
+ }
+
+ $c->stash->{defect_type} = $defect_type;
+}
+
+sub load_user_body : Private {
+ my ($self, $c, $body_id) = @_;
+
+ my $has_permission = $c->user->has_body_permission_to('defect_type_edit', $body_id);
+
+ unless ( $has_permission ) {
+ $c->detach( '/page_error_404_not_found' );
+ }
+
+ $c->stash->{body} = $c->model('DB::Body')->find($body_id)
+ or $c->detach( '/page_error_404_not_found' );
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm
index 013d710af..201742c81 100644
--- a/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm
@@ -141,22 +141,32 @@ sub download : Path('download') : Args(0) {
foreach my $report (@$inspections) {
my ($eastings, $northings) = $report->local_coords;
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
- "MC", # activity code - minor carriageway, also FC (footway)
+ $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
"${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
+ $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
- $report->get_extra_metadata('defect_type') || 'SFP2', # defect type - SFP2: sweep and fill <1m2, POT2 also seen
+ $defect_type, # defect type - SFP2: sweep and fill <1m2, POT2 also seen
$report->response_priority ?
$report->response_priority->external_id :
"2", # priority of defect
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index 27f4393bb..12280db47 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -340,10 +340,16 @@ sub inspect : Private {
my %update_params = ();
if ($permissions->{report_inspect}) {
- foreach (qw/detailed_information traffic_information duplicate_of defect_type/) {
+ foreach (qw/detailed_information traffic_information duplicate_of/) {
$problem->set_extra_metadata( $_ => $c->get_param($_) );
}
+ if ( $c->get_param('defect_type') ) {
+ $problem->defect_type($problem->defect_types->find($c->get_param('defect_type')));
+ } else {
+ $problem->defect_type(undef);
+ }
+
if ( $c->get_param('include_update') ) {
$update_text = Utils::cleanup_text( $c->get_param('public_update'), { allow_multiline => 1 } );
if (!$update_text) {
diff --git a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
index d585a5328..78247e39d 100644
--- a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
+++ b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
@@ -161,9 +161,13 @@ sub admin_pages {
# 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') ) {
+ if ( $user->has_body_permission_to('report_instruct') ) {
$pages->{exordefects} = [ _('Download Exor RDI'), 10 ];
}
+ if ( $user->has_body_permission_to('defect_type_edit') ) {
+ $pages->{defecttypes} = [ _('Defect Types'), 11 ];
+ $pages->{defecttype_edit} = [ undef, undef ];
+ };
return $pages;
}
@@ -190,4 +194,20 @@ sub user_extra_fields {
sub display_days_ago_threshold { 28 }
+sub defect_type_extra_fields {
+ return [
+ 'activity_code',
+ 'defect_code',
+ ];
+};
+
+sub available_permissions {
+ my $self = shift;
+
+ my $perms = $self->next::method();
+ $perms->{Bodies}->{defect_type_edit} = "Add/edit defect types";
+
+ return $perms;
+}
+
1;
diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm
index 6dac8821c..82015ad2d 100644
--- a/perllib/FixMyStreet/DB/Result/Body.pm
+++ b/perllib/FixMyStreet/DB/Result/Body.pm
@@ -75,6 +75,12 @@ __PACKAGE__->has_many(
{ "foreign.body_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
+__PACKAGE__->has_many(
+ "defect_types",
+ "FixMyStreet::DB::Result::DefectType",
+ { "foreign.body_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
__PACKAGE__->belongs_to(
"parent",
"FixMyStreet::DB::Result::Body",
@@ -112,8 +118,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-09-06 15:33:04
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZuzscnLqcx0k512cTZ/kdg
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BOJANVwg3kR/1VjDq0LykA
sub url {
my ( $self, $c, $args ) = @_;
diff --git a/perllib/FixMyStreet/DB/Result/Contact.pm b/perllib/FixMyStreet/DB/Result/Contact.pm
index f7e8ac5b5..a620b7358 100644
--- a/perllib/FixMyStreet/DB/Result/Contact.pm
+++ b/perllib/FixMyStreet/DB/Result/Contact.pm
@@ -56,6 +56,12 @@ __PACKAGE__->belongs_to(
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
);
__PACKAGE__->has_many(
+ "contact_defect_types",
+ "FixMyStreet::DB::Result::ContactDefectType",
+ { "foreign.contact_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+__PACKAGE__->has_many(
"contact_response_priorities",
"FixMyStreet::DB::Result::ContactResponsePriority",
{ "foreign.contact_id" => "self.id" },
@@ -69,8 +75,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-09-06 15:33:04
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ocmQGeFJtO3wmvyx6W+EKQ
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:f9VepR/oPyr3z6PUpJ4w2A
__PACKAGE__->load_components("+FixMyStreet::DB::RABXColumn");
__PACKAGE__->rabx_column('extra');
@@ -82,6 +88,7 @@ with 'FixMyStreet::Roles::Extra';
__PACKAGE__->many_to_many( response_templates => 'contact_response_templates', 'response_template' );
__PACKAGE__->many_to_many( response_priorities => 'contact_response_priorities', 'response_priority' );
+__PACKAGE__->many_to_many( defect_types => 'contact_defect_types', 'defect_type' );
sub get_metadata_for_input {
my $self = shift;
diff --git a/perllib/FixMyStreet/DB/Result/ContactDefectType.pm b/perllib/FixMyStreet/DB/Result/ContactDefectType.pm
new file mode 100644
index 000000000..2199f0b42
--- /dev/null
+++ b/perllib/FixMyStreet/DB/Result/ContactDefectType.pm
@@ -0,0 +1,46 @@
+use utf8;
+package FixMyStreet::DB::Result::ContactDefectType;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
+__PACKAGE__->table("contact_defect_types");
+__PACKAGE__->add_columns(
+ "id",
+ {
+ data_type => "integer",
+ is_auto_increment => 1,
+ is_nullable => 0,
+ sequence => "contact_defect_types_id_seq",
+ },
+ "contact_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+ "defect_type_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+);
+__PACKAGE__->set_primary_key("id");
+__PACKAGE__->belongs_to(
+ "contact",
+ "FixMyStreet::DB::Result::Contact",
+ { id => "contact_id" },
+ { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
+);
+__PACKAGE__->belongs_to(
+ "defect_type",
+ "FixMyStreet::DB::Result::DefectType",
+ { id => "defect_type_id" },
+ { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:VIczmM0OXXpWgQVpop3SMw
+
+
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
+1;
diff --git a/perllib/FixMyStreet/DB/Result/DefectType.pm b/perllib/FixMyStreet/DB/Result/DefectType.pm
new file mode 100644
index 000000000..a2969f59e
--- /dev/null
+++ b/perllib/FixMyStreet/DB/Result/DefectType.pm
@@ -0,0 +1,66 @@
+use utf8;
+package FixMyStreet::DB::Result::DefectType;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
+__PACKAGE__->table("defect_types");
+__PACKAGE__->add_columns(
+ "id",
+ {
+ data_type => "integer",
+ is_auto_increment => 1,
+ is_nullable => 0,
+ sequence => "defect_types_id_seq",
+ },
+ "body_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+ "name",
+ { data_type => "text", is_nullable => 0 },
+ "description",
+ { data_type => "text", is_nullable => 0 },
+ "extra",
+ { data_type => "text", is_nullable => 1 },
+);
+__PACKAGE__->set_primary_key("id");
+__PACKAGE__->add_unique_constraint("defect_types_body_id_name_key", ["body_id", "name"]);
+__PACKAGE__->belongs_to(
+ "body",
+ "FixMyStreet::DB::Result::Body",
+ { id => "body_id" },
+ { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
+);
+__PACKAGE__->has_many(
+ "contact_defect_types",
+ "FixMyStreet::DB::Result::ContactDefectType",
+ { "foreign.defect_type_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+__PACKAGE__->has_many(
+ "problems",
+ "FixMyStreet::DB::Result::Problem",
+ { "foreign.defect_type_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BBLjb/aAoTKJZerdYCeBMQ
+
+__PACKAGE__->many_to_many( contacts => 'contact_defect_types', 'contact' );
+
+__PACKAGE__->load_components("+FixMyStreet::DB::RABXColumn");
+__PACKAGE__->rabx_column('extra');
+
+use Moo;
+use namespace::clean -except => [ 'meta' ];
+
+with 'FixMyStreet::Roles::Extra';
+
+
+1;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index 0ab52628e..84db41490 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -108,6 +108,8 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"response_priority_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
+ "defect_type_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
@@ -116,6 +118,17 @@ __PACKAGE__->has_many(
{ "foreign.problem_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
+__PACKAGE__->belongs_to(
+ "defect_type",
+ "FixMyStreet::DB::Result::DefectType",
+ { id => "defect_type_id" },
+ {
+ is_deferrable => 0,
+ join_type => "LEFT",
+ on_delete => "NO ACTION",
+ on_update => "NO ACTION",
+ },
+);
__PACKAGE__->has_many(
"moderation_original_datas",
"FixMyStreet::DB::Result::ModerationOriginalData",
@@ -153,8 +166,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-09-07 11:01:40
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:iH9c4VZZN/ONnhN6g89DFw
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:8zzWlJX7OQOdvrGxKuZUmg
# Add fake relationship to stored procedure table
__PACKAGE__->has_one(
@@ -754,6 +767,18 @@ sub response_priorities {
return $self->result_source->schema->resultset('ResponsePriority')->for_bodies($self->bodies_str_ids, $self->category);
}
+=head2 defect_types
+
+Returns all DefectTypes attached to this problem's category/contact, in
+alphabetical order of name.
+
+=cut
+
+sub defect_types {
+ my $self = shift;
+ return $self->result_source->schema->resultset('DefectType')->for_bodies($self->bodies_str_ids, $self->category);
+}
+
# returns true if the external id is the council's ref, i.e., useful to publish it
# (by way of an example, the Oxfordshire send method returns a useful reference when
# it succeeds, so that is the ref we should show on the problem report page).
diff --git a/perllib/FixMyStreet/DB/ResultSet/DefectType.pm b/perllib/FixMyStreet/DB/ResultSet/DefectType.pm
new file mode 100644
index 000000000..a873ef252
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/DefectType.pm
@@ -0,0 +1,22 @@
+package FixMyStreet::DB::ResultSet::DefectType;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+
+sub for_bodies {
+ my ($rs, $bodies, $category) = @_;
+ my $attrs = {
+ 'me.body_id' => $bodies,
+ };
+ if ($category) {
+ $attrs->{'contact.category'} = [ $category, undef ];
+ }
+ $rs->search($attrs, {
+ order_by => 'name',
+ join => { 'contact_defect_types' => 'contact' },
+ distinct => 1,
+ });
+}
+
+1;
diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm
index 122a5d0c9..70d5c05ed 100644
--- a/perllib/FixMyStreet/TestMech.pm
+++ b/perllib/FixMyStreet/TestMech.pm
@@ -610,6 +610,7 @@ sub delete_body {
my $body = shift;
$mech->delete_problems_for_body($body->id);
+ $mech->delete_defect_type($_) for $body->defect_types;
$mech->delete_contact($_) for $body->contacts;
$mech->delete_user($_) for $body->users;
$_->delete for $body->response_templates;
@@ -641,6 +642,14 @@ sub delete_problems_for_body {
}
}
+sub delete_defect_type {
+ my $mech = shift;
+ my $defect_type = shift;
+
+ $defect_type->contact_defect_types->delete_all;
+ $defect_type->delete;
+}
+
sub create_contact_ok {
my $self = shift;
my %contact_params = (