diff options
author | Dave Arter <davea@mysociety.org> | 2017-02-13 15:13:12 +0000 |
---|---|---|
committer | Matthew Somerville <matthew@mysociety.org> | 2017-03-23 12:54:24 +0000 |
commit | 3f21a9742d89c3e4fda47a0be6ec2a17f802c99a (patch) | |
tree | 3f199f8f6b43aa8818dc3c5349623bb2d4a5e8a3 | |
parent | 6fb4eca34fd47612216f642985cac74359727b15 (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.)
25 files changed, 593 insertions, 40 deletions
diff --git a/bin/update-schema b/bin/update-schema index 94c42d8ae..171b87576 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 '0050' if table_exists('defect_types'); 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'); diff --git a/db/schema.sql b/db/schema.sql index 23db82b65..18c1533d9 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -502,3 +502,21 @@ CREATE TABLE contact_response_priorities ( contact_id int REFERENCES contacts(id) NOT NULL, response_priority_id int REFERENCES response_priorities(id) NOT NULL ); + +CREATE TABLE defect_types ( + id serial not null primary key, + body_id int references body(id) not null, + name text not null, + description text not null, + extra text, + unique(body_id, name) +); + +CREATE TABLE contact_defect_types ( + id serial NOT NULL PRIMARY KEY, + contact_id int REFERENCES contacts(id) NOT NULL, + defect_type_id int REFERENCES defect_types(id) NOT NULL +); + +ALTER TABLE problem + ADD COLUMN defect_type_id int REFERENCES defect_types(id); diff --git a/db/schema_0050-add-defect-type-table.sql b/db/schema_0050-add-defect-type-table.sql new file mode 100644 index 000000000..d65e17940 --- /dev/null +++ b/db/schema_0050-add-defect-type-table.sql @@ -0,0 +1,21 @@ +BEGIN; + +CREATE TABLE defect_types ( + id serial not null primary key, + body_id int references body(id) not null, + name text not null, + description text not null, + extra text, + unique(body_id, name) +); + +CREATE TABLE contact_defect_types ( + id serial NOT NULL PRIMARY KEY, + contact_id int REFERENCES contacts(id) NOT NULL, + defect_type_id int REFERENCES defect_types(id) NOT NULL +); + +ALTER TABLE problem + ADD COLUMN defect_type_id int REFERENCES defect_types(id); + +COMMIT; 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 = ( diff --git a/t/app/model/defecttype.t b/t/app/model/defecttype.t new file mode 100644 index 000000000..0f66ac684 --- /dev/null +++ b/t/app/model/defecttype.t @@ -0,0 +1,67 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet::App; +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +my $potholes_contact = $mech->create_contact_ok( body_id => $oxfordshire->id, category => 'Potholes', email => 'potholes@example.com' ); +my $traffic_lights_contact =$mech->create_contact_ok( body_id => $oxfordshire->id, category => 'Traffic lights', email => 'lights@example.com' ); + +my $potholes_defect_type = FixMyStreet::App->model('DB::DefectType')->find_or_create( + { + body_id => 2237, + name => 'Potholes', + description => 'This defect type is to do with potholes' + } +); +$potholes_defect_type->contact_defect_types->find_or_create({ + contact_id => $potholes_contact->id, +}); + +my $general_defect_type = FixMyStreet::App->model('DB::DefectType')->find_or_create( + { + body_id => 2237, + name => 'All categories', + description => 'This defect type is for all categories' + } +); + + +subtest 'for_bodies returns correct results' => sub { + my $defect_types = FixMyStreet::App->model('DB::DefectType')->for_bodies( + [ $oxfordshire->id ], + 'Potholes' + ); + + is $defect_types->count, 2, 'Both defect types are included for Potholes category'; + + $defect_types = FixMyStreet::App->model('DB::DefectType')->for_bodies( + [ $oxfordshire->id ], + 'Traffic lights' + ); + + is $defect_types->count, 1, 'Only 1 defect type is included for Traffic lights category'; + is $defect_types->first->name, $general_defect_type->name, 'Correct defect type is returned for Traffic lights category'; +}; + +subtest 'Problem->defect_types behaves correctly' => sub { + my ($problem) = $mech->create_problems_for_body(1, $oxfordshire->id, 'Test', { + category => 'Potholes', + }); + + is $problem->defect_types->count, 2, 'Both defect types are available for the problem'; + + $problem->update({ category => 'Traffic lights' }); + is $problem->defect_types->count, 1, 'Only 1 defect type is included for Traffic lights category'; + is $problem->defect_types->first->name, $general_defect_type->name, 'Correct defect type is returned for Traffic lights category'; +}; + + +END { + $mech->delete_body( $oxfordshire ); + + done_testing(); +} diff --git a/templates/web/base/admin/category-multiselect.html b/templates/web/base/admin/category-multiselect.html new file mode 100644 index 000000000..98416204f --- /dev/null +++ b/templates/web/base/admin/category-multiselect.html @@ -0,0 +1,10 @@ +<p> + <strong>[% loc('Categories:') %]</strong> +</p> +<p> + <select class="form-control js-multiple" name="categories" id="categories" multiple data-all="[% loc('All categories') %]"> + [% FOR contact IN contacts %] + <option value="[% contact.id %]" [% 'selected' IF contact.active %]>[% contact.category | html %]</option> + [% END %] + </select> +</p> diff --git a/templates/web/base/admin/defecttypes/edit.html b/templates/web/base/admin/defecttypes/edit.html new file mode 100644 index 000000000..65c8a5ab7 --- /dev/null +++ b/templates/web/base/admin/defecttypes/edit.html @@ -0,0 +1,37 @@ +[% INCLUDE 'admin/header.html' title=tprintf(loc('Defect Type for %s'), body.name) -%] +[% dt = defect_type %] + +[% UNLESS dt.id %]<h3>[% loc('New defect type') %]</h3>[% END %] + +<form method="post" + action="[% c.uri_for('', body.id, dt.id || 'new' ) %]" + enctype="application/x-www-form-urlencoded" + accept-charset="utf-8" + class="validate"> + + <p> + <strong>[% loc('Name:') %] </strong> + <input type="text" name="name" class="required form-control" size="30" value="[% dt.name | html %]"> + </p> + <p> + <strong>[% loc('Description:') %] </strong> + <input type="text" name="description" class="form-control" size="30" value="[% dt.description | html %]"> + </p> + + <div class="admin-hint"> + <p> + [% loc('If you only want this defect type to be an option for specific categories, pick them here. By default they will show for all categories.') %] + </p> + </div> + + [% INCLUDE 'admin/category-multiselect.html' %] + + [% TRY %][% INCLUDE 'admin/defecttypes/extra_fields.html' %][% CATCH file %][% END %] + + <p> + <input type="hidden" name="token" value="[% csrf_token %]" > + <input type="submit" class="btn" name="save" value="[% dt.id ? loc('Save changes') : loc('Create defect type') %]" > + </p> +</form> + +[% INCLUDE 'admin/footer.html' %] diff --git a/templates/web/base/admin/defecttypes/index.html b/templates/web/base/admin/defecttypes/index.html new file mode 100644 index 000000000..2e6ce7e1b --- /dev/null +++ b/templates/web/base/admin/defecttypes/index.html @@ -0,0 +1,13 @@ +[% INCLUDE 'admin/header.html' title=loc('Defect Types') -%] + +<ul> + [% FOR body IN bodies %] + <li> + <a href="[% c.uri_for('', body.id) %]">[% body.name %]</a> + [% defect_types_count = body.defect_types.count %] + [% IF defect_types_count %]([% defect_types_count %])[% END %] + </li> + [% END %] +</ul> + +[% INCLUDE 'admin/footer.html' %] diff --git a/templates/web/base/admin/defecttypes/list.html b/templates/web/base/admin/defecttypes/list.html new file mode 100644 index 000000000..1a9cb4fa7 --- /dev/null +++ b/templates/web/base/admin/defecttypes/list.html @@ -0,0 +1,35 @@ +[% INCLUDE 'admin/header.html' title=tprintf(loc('Defect Types for %s'), body.name) -%] + +<table> + <thead> + <tr> + <th> [% loc('Name') %] </th> + <th> [% loc('Description') %] </th> + <th> [% loc('Categories') %] </th> + <th> </th> + </tr> + </thead> + <tbody> + [% PROCESS 'defect_type/format.html' %] + [% FOR d IN defect_types %] + <tr> + <td> [% defect_type_format(defect_type=d) %] </td> + <td> [% d.description | html %] </td> + <td> + [% UNLESS d.contacts.size %] + <em>[% loc('All categories') %]</em> + [% ELSE %] + [% FOR contact IN d.contacts %] + [% contact.category %][% ',' UNLESS loop.last %] + [% END %] + [% END %] + </td> + <td> <a href="[% c.uri_for('', body.id, d.id) %]" class="btn">[% loc('Edit') %]</a> </td> + </tr> + [% END %] + </tbody> +</table> + +<a href="[% c.uri_for('', body.id, 'new') %]" class="btn">[% loc('New defect type') %]</a> + +[% INCLUDE 'admin/footer.html' %] diff --git a/templates/web/base/defect_type/format.html b/templates/web/base/defect_type/format.html new file mode 100644 index 000000000..3c0781501 --- /dev/null +++ b/templates/web/base/defect_type/format.html @@ -0,0 +1,9 @@ +[% +# This template can be overridden by cobrands if they've added extra fields +# to the DefectType model (e.g Cobrand::Oxfordshire->defect_type_extra_fields) +# which should be used to represent this DefectType +# to the user in the inspect form. +~%] +[% MACRO defect_type_format BLOCK ~%] +[%~ defect_type.name | html ~%] +[%~ END %]
\ No newline at end of file diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html index 625887eff..5e97de3f4 100644 --- a/templates/web/base/report/_inspect.html +++ b/templates/web/base/report/_inspect.html @@ -62,18 +62,17 @@ [% 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 %] + [% PROCESS 'defect_type/format.html' %] + <p> + <label for="defect_type">[% loc('Defect type') %]</label> + <select id="defect_type" name="defect_type" class="form-control"> + <option value=""[% ' selected' IF NOT problem.defect_type %]>-</option> + [% FOREACH defect_type IN problem.defect_types %] + <option[% ' selected' IF problem.defect_type_id == defect_type.id %] value="[% defect_type.id %]">[% defect_type_format() %]</option> + [% END %] + </select> + </p> + <p> <label for="state">[% loc('State') %]</label> [% INCLUDE 'report/inspect/state_groups_select.html' %] diff --git a/templates/web/oxfordshire/admin/defecttypes/extra_fields.html b/templates/web/oxfordshire/admin/defecttypes/extra_fields.html new file mode 100644 index 000000000..73cc54f0c --- /dev/null +++ b/templates/web/oxfordshire/admin/defecttypes/extra_fields.html @@ -0,0 +1,8 @@ +<p> + <strong>[% loc('Activity Code:') %] </strong> + <input type="text" name="extra[activity_code]" class="form-control" size="30" value="[% dt.get_extra_metadata('activity_code') | html %]"> +</p> +<p> + <strong>[% loc('Defect Code:') %] </strong> + <input type="text" name="extra[defect_code]" class="form-control" size="30" value="[% dt.get_extra_metadata('defect_code') | html %]"> +</p> diff --git a/templates/web/oxfordshire/defect_type/format.html b/templates/web/oxfordshire/defect_type/format.html new file mode 100644 index 000000000..9cbf2d873 --- /dev/null +++ b/templates/web/oxfordshire/defect_type/format.html @@ -0,0 +1,4 @@ +[% MACRO defect_type_format BLOCK ~%] +[%~ defect_type.get_extra_metadata('defect_code') | html %] - [% defect_type.get_extra_metadata('activity_code') | html %] +([% defect_type.name | html %]) +[%~ END %]
\ No newline at end of file diff --git a/web/cobrands/fixmystreet/admin.js b/web/cobrands/fixmystreet/admin.js index 02eb30766..f7fcaf276 100644 --- a/web/cobrands/fixmystreet/admin.js +++ b/web/cobrands/fixmystreet/admin.js @@ -48,6 +48,8 @@ $(function(){ } }); + $("select.js-multiple[multiple]").make_multi(); + // on a body's page, hide/show deleted contact categories var $table_with_deleted_contacts = $('table tr.is-deleted td.contact-category').closest('table'); if ($table_with_deleted_contacts.length == 1) { diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index c09eeb803..e92395661 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -109,6 +109,20 @@ function isR2L() { $drawer.hide(); }); }); + }, + + make_multi: function() { + var $this = $(this), + all = $this.data('all'); + $this.multiSelect({ + allText: all, + noneText: all, + positionMenuWithin: $('#side'), + presets: [{ + name: all, + options: [] + }] + }); } }); @@ -535,23 +549,8 @@ $.extend(fixmystreet.set_up, { // to refresh the map when the filter inputs are changed. $(".report-list-filters [type=submit]").hide(); - function make_multi(id) { - var $id = $('#' + id), - all = $id.data('all'), - none = $id.data('none') || all, - allOpts = $id.data('allOptions') || []; - $id.multiSelect({ - allText: all, - noneText: none, - positionMenuWithin: $('#side'), - presets: [{ - name: all, - options: allOpts - }] - }); - } - make_multi('statuses'); - make_multi('filter_categories'); + $('#statuses').make_multi(); + $('#filter_categories').make_multi(); }, mobile_ui_tweaks: function() { |