diff options
Diffstat (limited to 'perllib/FixMyStreet/Cobrand/Zurich.pm')
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Zurich.pm | 574 |
1 files changed, 529 insertions, 45 deletions
diff --git a/perllib/FixMyStreet/Cobrand/Zurich.pm b/perllib/FixMyStreet/Cobrand/Zurich.pm index c64fe1177..6da3e566c 100644 --- a/perllib/FixMyStreet/Cobrand/Zurich.pm +++ b/perllib/FixMyStreet/Cobrand/Zurich.pm @@ -5,9 +5,12 @@ use DateTime; use POSIX qw(strcoll); use RABX; use Scalar::Util 'blessed'; +use DateTime::Format::Pg; use strict; use warnings; +use feature 'say'; +use utf8; =head1 NAME @@ -105,8 +108,11 @@ sub prettify_dt { sub zurich_closed_states { my $states = { 'fixed - council' => 1, - 'closed' => 1, + 'closed' => 1, # extern 'hidden' => 1, + 'investigating' => 1, # wish + 'unable to fix' => 1, # jurisdiction unknown + 'partial' => 1, # not contactable }; return wantarray ? keys %{ $states } : $states; @@ -117,6 +123,37 @@ sub problem_is_closed { return exists $self->zurich_closed_states->{ $problem->state } ? 1 : 0; } +sub zurich_public_response_states { + my $states = { + 'fixed - council' => 1, + 'closed' => 1, # extern + 'unable to fix' => 1, # jurisdiction unknown + }; + + return wantarray ? keys %{ $states } : $states; +} + +sub zurich_user_response_states { + my $states = { + 'hidden' => 1, + 'investigating' => 1, # wish + 'partial' => 1, # not contactable + }; + + return wantarray ? keys %{ $states } : $states; +} + +sub problem_has_public_response { + my ($self, $problem) = @_; + return exists $self->zurich_public_response_states->{ $problem->state } ? 1 : 0; +} + +sub problem_has_user_response { + my ($self, $problem) = @_; + my $state_matches = exists $self->zurich_user_response_states->{ $problem->state } ? 1 : 0; + return $state_matches && $problem->get_extra_metadata('public_response'); +} + sub problem_as_hashref { my $self = shift; my $problem = shift; @@ -132,16 +169,35 @@ sub problem_as_hashref { $hashref->{title} = _('This report is awaiting moderation.'); $hashref->{state} = 'submitted'; $hashref->{state_t} = _('Submitted'); + $hashref->{banner_id} = 'closed'; } else { if ( $problem->state eq 'confirmed' ) { $hashref->{state} = 'open'; $hashref->{state_t} = _('Open'); + $hashref->{banner_id} = 'closed'; + } elsif ( $problem->state eq 'closed' ) { + $hashref->{state} = 'extern'; # is this correct? + $hashref->{banner_id} = 'closed'; + $hashref->{state_t} = _('Extern'); + } elsif ( $problem->state eq 'unable to fix' ) { + $hashref->{state} = 'jurisdiction unknown'; # is this correct? + $hashref->{state_t} = _('Jurisdiction Unknown'); + $hashref->{banner_id} = 'fixed'; # green + } elsif ( $problem->state eq 'partial' ) { + $hashref->{state} = 'not contactable'; # is this correct? + $hashref->{state_t} = _('Not contactable'); + # no banner_id as hidden + } elsif ( $problem->state eq 'investigating' ) { + $hashref->{state} = 'wish'; # is this correct? + $hashref->{state_t} = _('Wish'); } elsif ( $problem->is_fixed ) { $hashref->{state} = 'closed'; + $hashref->{banner_id} = 'fixed'; $hashref->{state_t} = _('Closed'); } elsif ( $problem->state eq 'in progress' || $problem->state eq 'planned' ) { $hashref->{state} = 'in progress'; $hashref->{state_t} = _('In progress'); + $hashref->{banner_id} = 'progress'; } } @@ -275,13 +331,46 @@ sub get_or_check_overdue { return $self->overdue($problem); } +=head1 C<set_problem_state> + +If the state has changed, sets the state and calls C::Admin's C<log_edit> action. +If the state hasn't changed, defers to update_admin_log (to update time_spent if any). + +Returns either undef or the AdminLog entry created. + +=cut + sub set_problem_state { my ($self, $c, $problem, $new_state) = @_; - return if $new_state eq $problem->state; + return $self->update_admin_log($c, $problem) if $new_state eq $problem->state; $problem->state( $new_state ); $c->forward( 'log_edit', [ $problem->id, 'problem', "state change to $new_state" ] ); } +=head1 C<update_admin_log> + +Calls C::Admin's C<log_edit> if either a) text is provided, or b) there has +been time_spent on the task. As set_problem_state will already call log_edit +if required, don't call this as well. + +Returns either undef or the AdminLog entry created. + +=cut + +sub update_admin_log { + my ($self, $c, $problem, $text) = @_; + + my $time_spent = ( ($c->get_param('time_spent') // 0) + 0 ); + $c->set_param('time_spent' => 0); # explicitly zero this to avoid duplicates + + if (!$text) { + return unless $time_spent; + $text = "Logging time_spent"; + } + + $c->forward( 'log_edit', [ $problem->id, 'problem', $text, $time_spent ] ); +} + # Specific administrative displays sub admin_pages { @@ -289,23 +378,25 @@ sub admin_pages { my $c = $self->{c}; my $type = $c->stash->{admin_type}; + my $pages = { 'summary' => [_('Summary'), 0], 'reports' => [_('Reports'), 2], 'report_edit' => [undef, undef], 'update_edit' => [undef, undef], + 'stats' => [_('Stats'), 4], }; return $pages if $type eq 'sdm'; $pages = { %$pages, 'bodies' => [_('Bodies'), 1], 'body' => [undef, undef], + 'templates' => [_('Templates'), 2], }; return $pages if $type eq 'dm'; $pages = { %$pages, 'users' => [_('Users'), 3], - 'stats' => [_('Stats'), 4], 'user_edit' => [undef, undef], }; return $pages if $type eq 'super'; @@ -448,7 +539,7 @@ sub admin_report_edit { } - # If super or sdm check that the token is correct before proceeding + # If super or dm check that the token is correct before proceeding if ( ($type eq 'super' || $type eq 'dm') && $c->get_param('submit') ) { $c->forward('check_token'); } @@ -468,6 +559,7 @@ sub admin_report_edit { } } + # Problem updates upon submission if ( ($type eq 'super' || $type eq 'dm') && $c->get_param('submit') ) { $problem->set_extra_metadata('publish_photo' => $c->get_param('publish_photo') || 0 ); @@ -488,10 +580,43 @@ sub admin_report_edit { my $internal_note_text = ""; # Workflow things + # + # Note that 2 types of email may be sent + # 1) _admin_send_email() sends an email to the *user*, if their email is confirmed + # + # 2) setting $problem->whensent(undef) may make it eligible for generating an email + # to the body (internal or external). See DBRS::Problem->send_reports for Zurich- + # specific categories which are eligible for this. + # + # It looks like both of these will do: + # a) TT processing of [% ... %] directives, in FMS::App->send_email(_cron) + # b) pseudo-PHP substitution of <?=$values['name']?> which is done as + # naive substitution + # commonlib mySociety::Email + # + # So it makes sense to add new parameters as the more powerful TT (a). + my $redirect = 0; - my $new_cat = $c->get_param('category'); - if ( $new_cat && $new_cat ne $problem->category ) { - my $cat = $c->model('DB::Contact')->search( { category => $c->get_param('category') } )->first; + my $new_cat = $c->get_param('category') || ''; + my $state = $c->get_param('state') || ''; + my $oldstate = $problem->state; + + my $closure_states = $self->zurich_closed_states; + delete $closure_states->{'fixed - council'}; # may not be needed? + + my $old_closure_state = $problem->get_extra_metadata('closure_status'); + + # update the public update from DM + if (my $update = $c->get_param('status_update')) { + $problem->set_extra_metadata(public_response => $update); + } + + if ( + ($state eq 'confirmed') + && $new_cat + && $new_cat ne $problem->category + ) { + my $cat = $c->model('DB::Contact')->search({ category => $c->get_param('category') } )->first; my $old_cat = $problem->category; $problem->category( $new_cat ); $problem->external_body( undef ); @@ -499,7 +624,25 @@ sub admin_report_edit { $problem->whensent( undef ); $problem->set_extra_metadata(changed_category => 1); $internal_note_text = "Weitergeleitet von $old_cat an $new_cat"; + $self->update_admin_log($c, $problem, "Changed category from $old_cat to $new_cat"); $redirect = 1 if $cat->body_id ne $body->id; + } elsif ( $closure_states->{$state} and + ( $oldstate ne 'planned' ) + || (($old_closure_state ||'') ne $state)) + { + # for these states + # - closed (Extern) + # - investigating (Wish) + # - hidden + # - partial (Not contactable) + # - unable to fix (Jurisdiction unknown) + # we divert to planned (Rueckmeldung ausstehend) and set closure_status to the requested state + # From here, the DM can reply to the user, triggering the setting of problem to correct state + $problem->set_extra_metadata( closure_status => $state ); + $self->set_problem_state($c, $problem, 'planned'); + $state = 'planned'; + $problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) ); + } elsif ( my $subdiv = $c->get_param('body_subdivision') ) { $problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) ); $self->set_problem_state($c, $problem, 'in progress'); @@ -507,31 +650,82 @@ sub admin_report_edit { $problem->bodies_str( $subdiv ); $problem->whensent( undef ); $redirect = 1; - } elsif ( my $external = $c->get_param('body_external') ) { - $problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) ); - $self->set_problem_state($c, $problem, 'closed'); - $problem->set_extra_metadata_if_undefined( closed_overdue => $self->overdue( $problem ) ); - $problem->external_body( $external ); - $problem->whensent( undef ); - _admin_send_email( $c, 'problem-external.txt', $problem ); - $redirect = 1; } else { - if (my $state = $c->get_param('state')) { + if ($state) { - if ($problem->state eq 'unconfirmed' and $state ne 'unconfirmed') { + if ($oldstate eq 'unconfirmed' and $state ne 'unconfirmed') { # only set this for the first state change $problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) ); } - $self->set_problem_state($c, $problem, $state); + $self->set_problem_state($c, $problem, $state) + unless $closure_states->{$state}; + # we'll defer to 'planned' clause below to change the state + } + } + + if ($problem->state eq 'planned') { + # Rueckmeldung ausstehend + # override $state from the metadata set above + $state = $problem->get_extra_metadata('closure_status') || ''; + my ($moderated, $closed) = (0, 0); - if ($self->problem_is_closed($problem)) { - $problem->set_extra_metadata_if_undefined( closed_overdue => $self->overdue( $problem ) ); + if ($state eq 'hidden' && $c->get_param('publish_response') ) { + _admin_send_email( $c, 'problem-rejected.txt', $problem ); + + $self->set_problem_state($c, $problem, $state); + $moderated++; + $closed++; + } + elsif ($state =~/^(closed|investigating)$/) { # Extern | Wish + $moderated++; + # Nested if instead of `and` because in these cases, we *don't* + # want to close unless we have body_external (so we don't want + # the final elsif clause below to kick in on publish_response) + if (my $external = $c->get_param('body_external')) { + my $external_body = $c->model('DB::Body')->find($external) + or die "Body $external not found"; + $problem->external_body( $external ); + } + if ($problem->external_body && $c->get_param('publish_response')) { + $problem->whensent( undef ); + $self->set_problem_state($c, $problem, $state); + my $template = ($state eq 'investigating') ? 'problem-wish.txt' : 'problem-external.txt'; + _admin_send_email( $c, $template, $problem ); + $redirect = 0; + $closed++; } - if ( $state eq 'hidden' && $c->get_param('send_rejected_email') ) { - _admin_send_email( $c, 'problem-rejected.txt', $problem ); + # set the external_message in extra, so that it can be edited again + if ( my $external_message = $c->get_param('external_message') ) { + $problem->set_extra_metadata( external_message => $external_message ); } + # else should really return a message here + } + elsif ($c->get_param('publish_response')) { + # otherwise we only set the state if publish_response is set + # + + # if $state wasn't set, then we are simply closing the message as fixed + $state ||= 'fixed - council'; + _admin_send_email( $c, 'problem-closed.txt', $problem ); + $redirect = 0; + $moderated++; + $closed++; + } + + if ($moderated) { + $problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) ); } + + if ($closed) { + # set to either the closure_status from metadata or 'fixed - council' as above + $self->set_problem_state($c, $problem, $state); + $problem->set_extra_metadata_if_undefined( closed_overdue => $self->overdue( $problem ) ); + $problem->unset_extra_metadata('closure_status'); + } + } + else { + $problem->unset_extra_metadata('closure_status'); } $problem->title( $c->get_param('title') ) if $c->get_param('title'); @@ -539,21 +733,42 @@ sub admin_report_edit { $problem->latitude( $c->get_param('latitude') ); $problem->longitude( $c->get_param('longitude') ); - # Final, public, Update from DM - if (my $update = $c->get_param('status_update')) { - $problem->set_extra_metadata(public_response => $update); - if ($c->get_param('publish_response')) { - $self->set_problem_state($c, $problem, 'fixed - council'); - $problem->set_extra_metadata( closed_overdue => $self->overdue( $problem ) ); - _admin_send_email( $c, 'problem-closed.txt', $problem ); - } + # send external_message if provided and state is *now* Wish|Extern + # e.g. was already, or was set in the Rueckmeldung ausstehend clause above. + if ( my $external_message = $c->get_param('external_message') + and $problem->state =~ /^(closed|investigating)$/) + { + my $external = $problem->external_body; + my $external_body = $c->model('DB::Body')->find($external) + or die "Body $external not found"; + + $problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) ); + # Create a Comment on this Problem with the content of the external message. + # NB this isn't directly shown anywhere, but is for logging purposes. + $problem->add_to_comments( { + text => ( + sprintf '(%s %s) %s', + $state eq 'closed' ? + _('Forwarded to external body') : + _('Forwarded wish to external body'), + $external_body->name, + $external_message, + ), + user => $c->user->obj, + state => 'hidden', # seems best fit, should not be shown publicly + mark_fixed => 0, + anonymous => 1, + extra => { is_internal_note => 1, is_external_message => 1 }, + } ); + # set the external_message in extra, so that it will be picked up + # later by send-reports + $problem->set_extra_metadata( external_message => $external_message ); } - $problem->lastupdate( \'ms_current_timestamp()' ); + $problem->lastupdate( \'current_timestamp' ); $problem->update; - $c->stash->{status_message} = - '<p><em>' . _('Updated!') . '</em></p>'; + $c->stash->{status_message} = '<p class="message-updated">' . _('Updated!') . '</p>'; # do this here otherwise lastupdate and confirmed times # do not display correctly (reloads problem from database, including @@ -572,14 +787,21 @@ sub admin_report_edit { } ); } - if ( $redirect ) { - $c->detach('index'); + # Just update if time_spent still hasn't been logged + # (this will only happen if no other update_admin_log has already been called) + $self->update_admin_log($c, $problem); + + if ( $redirect and $type eq 'dm' ) { + # only redirect for DM + $c->stash->{status_message} ||= '<p class="message-updated">' . _('Updated!') . '</p>'; + $c->go('index'); } $c->stash->{updates} = [ $c->model('DB::Comment') ->search( { problem_id => $problem->id }, { order_by => 'created' } ) ->all ]; + $self->stash_states($problem); return 1; } @@ -588,15 +810,32 @@ sub admin_report_edit { # Has cut-down edit template for adding update and sending back up only $c->stash->{template} = 'admin/report_edit-sdm.html'; - if ($c->get_param('send_back')) { + if ($c->get_param('send_back') or $c->get_param('not_contactable')) { + # SDM can send back a report either to be assigned to a different + # subdivision, or because the customer was not contactable. + # We handle these in the same way but with different statuses. + $c->forward('check_token'); + my $not_contactable = $c->get_param('not_contactable'); + $problem->bodies_str( $body->parent->id ); - $self->set_problem_state($c, $problem, 'confirmed'); + if ($not_contactable) { + # we can't directly set state, but mark the closure_status for DM to confirm. + $self->set_problem_state($c, $problem, 'planned'); + $problem->set_extra_metadata( closure_status => 'partial'); + } + else { + $self->set_problem_state($c, $problem, 'confirmed'); + } $problem->update; - # log here + $c->forward( 'log_edit', [ $problem->id, 'problem', + $not_contactable ? + _('Customer not contactable') + : _('Sent report back') ] ); + # Make sure the problem's time_spent is updated + $self->update_admin_log($c, $problem); $c->res->redirect( '/admin/summary' ); - } elsif ($c->get_param('submit')) { $c->forward('check_token'); @@ -621,8 +860,10 @@ sub admin_report_edit { anonymous => 1, } ); } + # Make sure the problem's time_spent is updated + $self->update_admin_log($c, $problem); - $c->stash->{status_message} = '<p><em>' . _('Updated!') . '</em></p>'; + $c->stash->{status_message} = '<p class="message-updated">' . _('Updated!') . '</p>'; # If they clicked the no more updates button, we're done. if ($c->get_param('no_more_updates')) { @@ -639,14 +880,113 @@ sub admin_report_edit { ->search( { problem_id => $problem->id }, { order_by => 'created' } ) ->all ]; + $self->stash_states($problem); return 1; } + $self->stash_states($problem); return 0; } +sub stash_states { + my ($self, $problem) = @_; + my $c = $self->{c}; + + # current problem state affects which states are visible in dropdowns + my @states = ( + { + # Erfasst + state => 'unconfirmed', + trans => _('Submitted'), + unconfirmed => 1, + hidden => 1, + }, + { + # Aufgenommen + state => 'confirmed', + trans => _('Open'), + unconfirmed => 1, + }, + { + # Unsichtbar (hidden) + state => 'hidden', + trans => _('Hidden'), + unconfirmed => 1, + hidden => 1, + }, + { + # Extern + state => 'closed', + trans => _('Extern'), + }, + { + # Zustaendigkeit unbekannt + state => 'unable to fix', + trans => _('Jurisdiction unknown'), + }, + { + # Wunsch (hidden) + state => 'investigating', + trans => _('Wish'), + }, + { + # Nicht kontaktierbar (hidden) + state => 'partial', + trans => _('Not contactable'), + }, + ); + my %state_trans = map { $_->{state} => $_->{trans} } @states; + + my $state = $problem->state; + + # Rueckmeldung ausstehend may also indicate the status it's working towards. + push @states, do { + if ($state eq 'planned' and my $closure_status = $problem->get_extra_metadata('closure_status')) { + { + state => $closure_status, + trans => sprintf '%s (%s)', _('Planned'), $state_trans{$closure_status}, + }; + } + else { + { + state => 'planned', + trans => _('Planned'), + }; + } + }; + + if ($state eq 'in progress') { + push @states, { + state => 'in progress', + trans => _('In progress'), + }; + } + elsif ($state eq 'fixed - council') { + push @states, { + state => 'fixed - council', + trans => _('Closed'), + }; + } + elsif ($state =~/^(hidden|unconfirmed)$/) { + @states = grep { $_->{$state} } @states; + } + $c->stash->{states} = \@states; + $c->stash->{states_trans} = { map { $_->{state} => $_->{trans} } @states }; # [% states_trans.${problem.state} %] + + # stash details about the public response + $c->stash->{default_public_response} = "\nFreundliche Grüsse\n\nIhre Stadt Zürich\n"; + $c->stash->{show_publish_response} = + ($problem->state eq 'planned'); +} + +=head2 _admin_send_email + +Send an email to the B<user> who logged the problem, if their email address is confirmed. + +=cut + sub _admin_send_email { my ( $c, $template, $problem ) = @_; @@ -665,9 +1005,36 @@ sub _admin_send_email { to => [ $to ], url => $c->uri_for_email( $problem->url ), from => [ $sender, $sender_name ], + problem => $problem, } ); } +sub munge_sendreport_params { + my ($self, $c, $row, $h, $params) = @_; + if ($row->state =~ /^(closed|investigating)$/ && $row->get_extra_metadata('publish_photo')) { + # we attach images to reports sent to external bodies + my $photoset = $row->get_photoset($c); + my @images = $photoset->all_images + or return; + my $index = 0; + my $id = $row->id; + my @attachments = map { + my $i = $index++; + { + body => $_->[1], + attributes => { + filename => "$id.$i.jpeg", + content_type => 'image/jpeg', + encoding => 'base64', + # quoted-printable ends up with newlines corrupting binary data + name => "$id.$i.jpeg", + }, + } + } @images; + $params->{attachments} = \@attachments; + } +} + sub admin_fetch_all_bodies { my ( $self, @bodies ) = @_; @@ -718,7 +1085,10 @@ sub admin_stats { if ($y && $m) { $c->stash->{start_date} = DateTime->new( year => $y, month => $m, day => 1 ); $c->stash->{end_date} = $c->stash->{start_date} + DateTime::Duration->new( months => 1 ); - $date_params{created} = { '>=', $c->stash->{start_date}, '<', $c->stash->{end_date} }; + $date_params{created} = { + '>=', DateTime::Format::Pg->format_datetime($c->stash->{start_date}), + '<', DateTime::Format::Pg->format_datetime($c->stash->{end_date}), + }; } my %params = ( @@ -730,6 +1100,8 @@ sub admin_stats { my $problems = $c->model('DB::Problem')->search( {%date_params}, { + join => 'admin_log_entries', + distinct => 1, columns => [ 'id', 'created', 'latitude', 'longitude', @@ -741,10 +1113,11 @@ sub admin_stats { 'whensent', 'lastupdate', 'service', 'extra', - ], + { sum_time_spent => { sum => 'admin_log_entries.time_spent' } }, + ] } ); - my $body = "Report ID,Created,Sent to Agency,Last Updated,E,N,Category,Status,UserID,External Body,Title,Detail,Media URL,Interface Used,Council Response\n"; + my $body = "Report ID,Created,Sent to Agency,Last Updated,E,N,Category,Status,UserID,External Body,Time Spent,Title,Detail,Media URL,Interface Used,Council Response\n"; require Text::CSV; my $csv = Text::CSV->new({ binary => 1 }); while ( my $report = $problems->next ) { @@ -759,7 +1132,10 @@ sub admin_stats { # replace newlines with HTML <br/> element $detail =~ s{\r?\n}{ <br/> }g; - $public_response =~ s{\r?\n}{ <br/> }g; + $public_response =~ s{\r?\n}{ <br/> }g if $public_response; + + # Assemble photo URL, if report has a photo + my $media_url = $report->get_photo_params->{url} ? ($c->cobrand->base_url . $report->get_photo_params->{url}) : ''; my @columns = ( $report->id, @@ -769,9 +1145,10 @@ sub admin_stats { $report->local_coords, $report->category, $report->state, $report->user_id, $body_name, + $report->get_column('sum_time_spent') || 0, $report->title, $detail, - $c->cobrand->base_url . $report->get_photo_params->{url}, + $media_url, $report->service || 'Web interface', $public_response, ); @@ -850,4 +1227,111 @@ sub admin_stats { return 1; } +sub problem_confirm_email_extras { + my ($self, $report) = @_; + my $confirmed_reports = $report->user->problems->search({ + extra => { like => '%email_confirmed%' }, + })->count; + + $self->{c}->stash->{email_confirmed} = $confirmed_reports; +} + +sub body_details_data { + return ( + { + name => 'Stadt Zurich' + }, + { + name => 'Elektrizitäwerk Stadt Zürich', + parent => 'Stadt Zurich', + area_id => 423017, + }, + { + name => 'ERZ Entsorgung + Recycling Zürich', + parent => 'Stadt Zurich', + area_id => 423017, + }, + { + name => 'Fachstelle Graffiti', + parent => 'Stadt Zurich', + area_id => 423017, + }, + { + name => 'Grün Stadt Zürich', + parent => 'Stadt Zurich', + area_id => 423017, + }, + { + name => 'Tiefbauamt Stadt Zürich', + parent => 'Stadt Zurich', + area_id => 423017, + }, + { + name => 'Dienstabteilung Verkehr', + parent => 'Stadt Zurich', + area_id => 423017, + }, + ); +} + +sub contact_details_data { + return ( + { + category => 'Beleuchtung/Uhren', + body_name => 'Elektrizitätswerk Stadt Zürich', + fields => [ + { + code => 'strasse', + description => 'Strasse', + datatype => 'string', + required => 'yes', + }, + { + code => 'haus_nr', + description => 'Haus-Nr.', + datatype => 'string', + }, + { + code => 'mast_nr', + description => 'Mast-Nr.', + datatype => 'string', + } + ], + }, + { + category => 'Brunnen/Hydranten', + # body_name ??? + fields => [ + { + code => 'hydranten_nr', + description => 'Hydranten-Nr.', + datatype => 'string', + }, + ], + }, + { + category => "Grünflächen/Spielplätze", + body_name => 'Grün Stadt Zürich', + rename_from => "Tiere/Grünflächen", + }, + { + category => 'Spielplatz/Sitzbank', + body_name => 'Grün Stadt Zürich', + delete => 1, + }, + ); +} + +sub contact_details_data_body_default { + my ($self) = @_; + # temporary measure to assign un-bodied contacts to parent + # (this isn't at all how things will be setup in live, but is + # handy during dev.) + return $self->{c}->model('DB::Body')->find({ name => 'Stadt Zurich' }); +} + +sub reports_per_page { return 20; } + +sub singleton_bodies_str { 1 } + 1; |