1 package SL::Controller::EmailJournal;
5 use parent qw(SL::Controller::Base);
8 use SL::Controller::ZUGFeRD;
9 use SL::Controller::Helper::GetModels;
11 use SL::DB::EmailJournal;
12 use SL::DB::EmailJournalAttachment;
13 use SL::Presenter::EmailJournal;
14 use SL::Presenter::Record qw(grouped_record_list);
15 use SL::Presenter::Tag qw(html_tag div_tag button_tag);
16 use SL::Helper::Flash;
17 use SL::Locale::String qw(t8);
20 use SL::DB::Order::TypeData;
21 use SL::DB::DeliveryOrder;
22 use SL::DB::DeliveryOrder::TypeData;
23 use SL::DB::Reclamation;
24 use SL::DB::Reclamation::TypeData;
26 use SL::DB::Invoice::TypeData;
27 use SL::DB::PurchaseInvoice;
28 use SL::DB::PurchaseInvoice::TypeData;
30 use SL::DB::Manager::Customer;
31 use SL::DB::Manager::Vendor;
33 use List::Util qw(first);
34 use List::MoreUtils qw(any);
36 use Rose::Object::MakeMethods::Generic
38 scalar => [ qw(entry) ],
39 'scalar --get_set_init' => [ qw(models can_view_all filter_summary) ],
42 __PACKAGE__->run_before('add_stylesheet');
43 __PACKAGE__->run_before('add_js');
45 my %RECORD_TYPES_INFO = (
47 controller => 'Order',
49 types => SL::DB::Order::TypeData->valid_types(),
52 controller => 'DeliveryOrder',
53 class => 'DeliveryOrder',
54 types => SL::DB::DeliveryOrder::TypeData->valid_types(),
57 controller => 'Reclamation',
58 class => 'Reclamation',
59 types => SL::DB::Reclamation::TypeData->valid_types(),
62 controller => 'gl.pl',
63 class => 'GLTransaction',
69 controller => 'ar.pl',
76 controller => 'is.pl',
78 types => SL::DB::Invoice::TypeData->valid_types(),
81 controller => 'ap.pl',
82 class => 'PurchaseInvoice',
88 controller => 'ir.pl',
89 class => 'PurchaseInvoice',
90 types => SL::DB::PurchaseInvoice::TypeData->valid_types(),
93 controller => 'gl.pl',
94 class => 'RecordTemplate',
96 'gl_transaction_template',
100 controller => 'ar.pl',
101 class => 'RecordTemplate',
103 'ar_transaction_template',
106 ApRecordTemplate => {
107 controller => 'ap.pl',
108 class => 'RecordTemplate',
110 'ap_transaction_template',
114 my %RECORD_TYPE_TO_CONTROLLER =
116 my $controller = $RECORD_TYPES_INFO{$_}->{controller};
117 map { $_ => $controller } @{ $RECORD_TYPES_INFO{$_}->{types} }
118 } keys %RECORD_TYPES_INFO;
119 my %RECORD_TYPE_TO_MODEL =
121 my $class = $RECORD_TYPES_INFO{$_}->{class};
122 map { $_ => "SL::DB::$class" } @{ $RECORD_TYPES_INFO{$_}->{types} }
123 } keys %RECORD_TYPES_INFO;
124 my %RECORD_TYPE_TO_MANAGER =
126 my $class = $RECORD_TYPES_INFO{$_}->{class};
127 map { $_ => "SL::DB::Manager::$class" } @{ $RECORD_TYPES_INFO{$_}->{types} }
128 } keys %RECORD_TYPES_INFO;
129 my @ALL_RECORD_TYPES =
130 map { @{ $RECORD_TYPES_INFO{$_}->{types} } } keys %RECORD_TYPES_INFO;
131 my %RECORD_TYPE_TO_NR_KEY =
133 my $model = $RECORD_TYPE_TO_MODEL{$_};
134 if (any {$model eq $_} qw(SL::DB::Invoice SL::DB::PurchaseInvoice)) {
136 } elsif (any {$model eq $_} qw(SL::DB::RecordTemplate)) {
137 $_ => 'template_name';
138 } elsif (any {$model eq $_} qw(SL::DB::GLTransaction)) {
141 my $type_data = SL::DB::Helper::TypeDataProxy->new($model, $_);
142 $_ => $type_data->properties('nr_key');
146 # has do be done at runtime for translation to work
147 sub get_record_types_with_info {
148 my @record_types_with_info = ();
149 for my $record_class (
150 'SL::DB::Order', 'SL::DB::DeliveryOrder', 'SL::DB::Reclamation',
151 'SL::DB::Invoice', 'SL::DB::PurchaseInvoice',
153 my $type_data = "${record_class}::TypeData";
154 my $valid_types = $type_data->valid_types();
155 for my $type (@$valid_types) {
156 push @record_types_with_info, {
157 record_type => $type,
158 text => $type_data->can('get3')->($type, 'text', 'type'),
159 customervendor => $type_data->can('get3')->($type, 'properties', 'customervendor'),
160 workflow_needed => $type_data->can('get3')->($type, 'properties', 'worflow_needed'),
163 $_ ne 'delete' && $type_data->can('get3')->($type, 'show_menu', $_)
164 } keys %{$type_data->can('get')->($type, 'show_menu')}
169 push @record_types_with_info, (
171 # gl_transaction can be for vendor and customer
172 { record_type => 'gl_transaction', customervendor => 'customer', workflow_needed => 0, can_workflow => 1, text => t8('GL Transaction')},
173 { record_type => 'gl_transaction', customervendor => 'vendor', workflow_needed => 0, can_workflow => 1, text => t8('GL Transaction')},
174 { record_type => 'ar_transaction', customervendor => 'customer', workflow_needed => 0, can_workflow => 1, text => t8('AR Transaction')},
175 { record_type => 'ap_transaction', customervendor => 'vendor', workflow_needed => 0, can_workflow => 1, text => t8('AP Transaction')},
177 { record_type => 'gl_transaction_template', is_template => 1, customervendor => 'customer', workflow_needed => 0, can_workflow => 0, text => t8('GL Transaction')},
178 { record_type => 'gl_transaction_template', is_template => 1, customervendor => 'vendor', workflow_needed => 0, can_workflow => 0, text => t8('GL Transaction')},
179 { record_type => 'ar_transaction_template', is_template => 1, customervendor => 'customer', workflow_needed => 0, can_workflow => 0, text => t8('AR Transaction')},
180 { record_type => 'ap_transaction_template', is_template => 1, customervendor => 'vendor', workflow_needed => 0, can_workflow => 0, text => t8('AP Transaction')},
182 return @record_types_with_info;
185 # has do be done at runtime for translation to work
186 sub get_record_types_to_text {
187 my @record_types_with_info = get_record_types_with_info();
189 my %record_types_to_text = ();
190 $record_types_to_text{$_->{record_type}} = $_->{text} for @record_types_with_info;
191 $record_types_to_text{'catch_all'} = t8("Catch-all");
193 return %record_types_to_text;
196 sub record_types_for_customer_vendor_type_and_action {
197 my ($self, $customer_vendor_type, $action) = @_;
199 map { $_->{record_type} }
201 # No gl_transaction in standard workflows
202 # They can't be filtered by customer/vendor or open/closed and polute the list
203 ($_->{record_type} ne 'gl_transaction')
206 ($_->{customervendor} eq $customer_vendor_type)
207 && ($action eq 'workflow_record' ? $_->{can_workflow} : 1)
208 && ($action eq 'create_new' ? $_->{workflow_needed} : 1)
209 && ($action eq 'linking_record' ? !$_->{is_template} : 1)
210 && ($action eq 'template_record' ? $_->{is_template} : 1)
212 $self->get_record_types_with_info()
223 $::auth->assert('email_journal');
225 $::form->{filter} ||= {"obsolete:eq_ignore_empty" => 0};
227 if ( $::instance_conf->get_email_journal == 0 ) {
228 flash('info', $::locale->text('Storing the emails in the journal is currently disabled in the client configuration.'));
230 $self->setup_list_action_bar;
231 my @record_types_with_info = $self->get_record_types_with_info();
232 my %record_types_to_text = $self->get_record_types_to_text();
233 $self->render('email_journal/list',
234 title => $::locale->text('Email journal'),
235 ENTRIES => $self->models->get,
236 MODELS => $self->models,
237 RECORD_TYPES_WITH_INFO => \@record_types_with_info,
238 RECORD_TYPES_TO_TEXT => \%record_types_to_text,
245 $::auth->assert('email_journal');
247 my $back_to = $::form->{back_to} || $self->url_for(action => 'list');
249 $self->entry(SL::DB::EmailJournal->new(id => $::form->{id})->load);
251 if (!$self->can_view_all && ($self->entry->sender_id != SL::DB::Manager::Employee->current->id)) {
252 $::form->error(t8('You do not have permission to access this entry.'));
255 my @record_types_with_info = $self->get_record_types_with_info();
256 my %record_types_to_text = $self->get_record_types_to_text();
258 my $customer = $self->find_customer_vendor_from_email('customer', $self->entry);
259 my $vendor = $self->find_customer_vendor_from_email('vendor' , $self->entry);
261 my $record_type_info =
262 first {$_->{record_type} eq $self->entry->record_type}
263 @record_types_with_info;
264 my $cv_type_found = $record_type_info ? $record_type_info->{customervendor}
265 : defined $vendor ? 'vendor'
268 my $record_types = $self->record_types_for_customer_vendor_type_and_action(
269 $cv_type_found, 'workflow_record'
272 $self->setup_show_action_bar;
274 'email_journal/show',
275 title => $::locale->text('View email'),
276 CUSTOMER => $customer,
278 CV_TYPE_FOUND => $cv_type_found,
279 RECORD_TYPES_WITH_INFO => \@record_types_with_info,
280 RECORD_TYPES_TO_TEXT => \%record_types_to_text,
285 sub action_attachment_preview {
289 $::auth->assert('email_journal');
291 my $attachment_id = $::form->{attachment_id};
292 die "no 'attachment_id' was given" unless $attachment_id;
295 $attachment = SL::DB::EmailJournalAttachment->new(
296 id => $attachment_id,
300 if (!$self->can_view_all
301 && $attachment->email_journal->sender_id
302 && ($attachment->email_journal->sender_id != SL::DB::Manager::Employee->current->id)) {
303 $::form->error(t8('You do not have permission to access this entry.'));
306 my $output = SL::Presenter::EmailJournal::attachment_preview(
308 style => "height: 1800px"
311 $self->render( \$output, { layout => 0, process => 0,});
313 $self->render('generic/error', { layout => 0 }, label_error => $@);
317 sub action_show_attachment {
320 $::auth->assert('email_journal');
322 my $attachment_id = $::form->{attachment_id};
323 my $attachment = SL::DB::EmailJournalAttachment->new(id => $attachment_id)->load;
325 if (!$self->can_view_all && ($attachment->email_journal->sender_id != SL::DB::Manager::Employee->current->id)) {
326 $::form->error(t8('You do not have permission to access this entry.'));
329 return $self->send_file(
330 \$attachment->content,
331 name => $attachment->name,
332 type => $attachment->mime_type,
333 content_disposition => 'inline',
337 sub action_download_attachment {
340 $::auth->assert('email_journal');
342 my $attachment = SL::DB::EmailJournalAttachment->new(id => $::form->{id})->load;
344 if (!$self->can_view_all && ($attachment->email_journal->sender_id != SL::DB::Manager::Employee->current->id)) {
345 $::form->error(t8('You do not have permission to access this entry.'));
347 my $ref = \$attachment->content;
348 # hot hot fix don't offer some random version of this file if we have a real saved state in the email journal
349 if (!$ref && $attachment->file_id > 0 ) {
350 my $file = SL::File->get(id => $attachment->file_id );
351 $ref = $file->get_content if $file;
353 $self->send_file($ref, name => $attachment->name, type => $attachment->mime_type);
356 sub action_apply_record_action {
358 my $email_journal_id = $::form->{email_journal_id};
359 my $attachment_id = $::form->{attachment_id};
360 my $customer_vendor = $::form->{customer_vendor_selection};
361 my $customer_vendor_id = $::form->{"${customer_vendor}_id"};
362 my $action = $::form->{action_selection};
363 my $record_id = $::form->{"record_id"};
364 my $record_type = $::form->{"record_type"};
365 $record_type ||= $::form->{"${customer_vendor}_${action}_type_selection"};
367 die t8("No record is selected.") unless $record_id || $action eq 'new_record';
368 die t8("No record type is selected.") unless $record_type;
369 die "no 'email_journal_id' was given" unless $email_journal_id;
370 die "no 'customer_vendor_selection' was given" unless $customer_vendor;
371 die "no 'action_selection' was given" unless $action;
373 if ($action eq 'linking_record') {
374 return $self->link_and_add_attachment_to_record({
375 email_journal_id => $email_journal_id,
376 attachment_id => $attachment_id,
377 record_type => $record_type,
378 record_id => $record_id,
382 my %additional_params = ();
383 if ($action eq 'new_record') {
384 $additional_params{action} = 'add_from_email_journal';
385 $additional_params{"${customer_vendor}_id"} = $customer_vendor_id;
386 } elsif ($action eq 'template_record') {
387 $additional_params{action} = 'load_record_template_from_email_journal';
388 $additional_params{id} = $record_id;
389 $additional_params{form_defaults} = {
390 email_journal_id => $email_journal_id,
391 email_attachment_id => $attachment_id,
392 callback => $::form->{back_to},
394 } else { # workflow_record
395 $additional_params{action} = 'edit_with_email_journal_workflow';
396 $additional_params{id} = $record_id;
400 controller => $RECORD_TYPE_TO_CONTROLLER{$record_type},
401 type => $record_type,
402 email_journal_id => $email_journal_id,
403 email_attachment_id => $attachment_id,
404 callback => $::form->{back_to},
409 sub action_ap_transaction_template_with_zugferd_import {
411 my $email_journal_id = $::form->{email_journal_id};
412 die "no 'email_journal_id' was given" unless $email_journal_id;
414 my $record_id = $::form->{"record_id"};
415 my $record_type = $::form->{"record_type"};
416 die "ZUGFeRD-Import only implemented for ap transaction templates" unless $record_type == 'ap_transaction';
418 my $attachment_id = $::form->{attachment_id};
421 if ($attachment_id) {
422 my $attachment = SL::DB::EmailJournalAttachment->new(id => $attachment_id)->load();
423 my $content = $attachment->content; # scalar ref
425 if ($content =~ m/^%PDF|<\?xml/) {
428 if ( $content =~ m/^%PDF/ ) {
429 %res = %{SL::ZUGFeRD->extract_from_pdf($content)};
431 %res = %{SL::ZUGFeRD->extract_from_xml($content)};
434 if ($res{'result'} == SL::ZUGFeRD::RES_OK()) {
435 my $ap_template = SL::DB::RecordTemplate->new(id => $record_id)->load();
436 my $vendor = $ap_template->vendor;
438 $form_defaults = SL::Controller::ZUGFeRD->build_ap_transaction_form_defaults(\%res, vendor => $vendor);
440 t8("The ZUGFeRD/Factur-X invoice '#1' has been loaded.", $attachment->name));
445 $form_defaults->{email_journal_id} = $email_journal_id;
446 $form_defaults->{email_attachment_id} = $attachment_id;
447 $form_defaults->{callback} = $::form->{back_to};
450 controller => 'ap.pl',
451 action => 'load_zugferd',
452 record_template_id => $record_id,
453 form_defaults => $form_defaults,
457 sub action_update_attachment_preview {
459 $::auth->assert('email_journal');
460 my $attachment_id = $::form->{attachment_id};
463 $attachment = SL::DB::EmailJournalAttachment->new(
464 id => $attachment_id,
465 )->load if $attachment_id;
468 ->replaceWith('#attachment_preview',
469 SL::Presenter::EmailJournal::attachment_preview(
471 style => "height:1800px"
477 sub action_update_record_list {
479 $::auth->assert('email_journal');
480 my $customer_vendor_type = $::form->{customer_vendor_selection};
481 my $customer_vendor_id = $::form->{"${customer_vendor_type}_id"};
482 my $action = $::form->{action_selection};
483 my $record_type = $::form->{"${customer_vendor_type}_${action}_type_selection"};
484 my $record_number = $::form->{record_number};
485 my $with_closed = $::form->{with_closed};
487 $record_type ||= $self->record_types_for_customer_vendor_type_and_action($customer_vendor_type, $action);
489 my @records = $self->get_records_for_types(
491 customer_vendor_type => $customer_vendor_type,
492 customer_vendor_id => $customer_vendor_id,
493 record_number => $record_number,
494 with_closed => $with_closed,
497 my $new_div = $self->get_records_div(\@records);
499 $self->js->replaceWith('#record_list', $new_div);
500 $self->js->hide('#record_toggle_closed') if scalar @records < 20;
501 $self->js->show('#record_toggle_open') if scalar @records < 20;
505 sub action_toggle_obsolete {
508 $::auth->assert('email_journal');
510 $self->entry(SL::DB::EmailJournal->new(id => $::form->{id})->load);
512 if (!$self->can_view_all && ($self->entry->sender_id != SL::DB::Manager::Employee->current->id)) {
513 $::form->error(t8('You do not have permission to access this entry.'));
516 $self->entry->obsolete(!$self->entry->obsolete);
520 ->val('#obsolete', $self->entry->obsolete_as_bool_yn)
522 $self->entry->obsolete ?
523 $::locale->text('Email marked as obsolete.')
524 : $::locale->text('Email marked as not obsolete.')
535 $::request->{layout}->use_stylesheet('email_journal.css');
542 sub get_records_for_types {
543 my ($self, $record_types, %params) = @_;
544 $record_types = [ $record_types ] unless ref $record_types eq 'ARRAY';
546 my $cv_type = $params{customer_vendor_type};
547 my $cv_id = $params{customer_vendor_id};
548 my $record_number = $params{record_number};
549 my $with_closed = $params{with_closed};
552 foreach my $record_type (@$record_types) {
553 my $manager = $RECORD_TYPE_TO_MANAGER{$record_type};
554 my $model = $RECORD_TYPE_TO_MODEL{$record_type};
555 my %additional_where = ();
556 if ($cv_type && $cv_id && $record_type !~ /^gl_transaction/) {
557 $additional_where{"${cv_type}_id"} = $cv_id;
559 if ($record_number) {
560 my $nr_key = $RECORD_TYPE_TO_NR_KEY{$record_type};
561 $additional_where{$nr_key} = { ilike => "%$record_number%" };
563 unless ($with_closed) {
564 if (any {$_ eq 'closed'} $model->meta->columns) {
565 $additional_where{closed} = 0;
566 } elsif (any {$_ eq 'paid'} $model->meta->columns) {
567 $additional_where{amount} = { gt => \'paid' };
570 my $records_of_type = $manager->get_all(
572 $manager->type_filter($record_type),
576 push @records, @$records_of_type;
582 sub get_records_div {
583 my ($self, $records) = @_;
587 with_columns => [ qw(email_journal_action) ],
594 sub link_and_add_attachment_to_record {
595 my ($self, $params) = @_;
597 my $email_journal_id = $params->{email_journal_id};
598 my $attachment_id = $params->{attachment_id};
599 my $record_type = $params->{record_type};
600 my $record_id = $params->{record_id};
602 my $record_type_model = $RECORD_TYPE_TO_MODEL{$record_type};
603 my $record = $record_type_model->new(id => $record_id)->load;
604 my $email_journal = SL::DB::EmailJournal->new(id => $email_journal_id)->load;
606 if ($attachment_id) {
607 my $attachment = SL::DB::EmailJournalAttachment->new(id => $attachment_id)->load;
608 $attachment->add_file_to_record($record);
611 $email_journal->link_to_record($record);
613 $self->js->flash('info',
614 $::locale->text('Linked email and attachment to ') . $record->displayable_name
618 sub find_customer_vendor_from_email {
619 my ($self, $cv_type, $email_journal) = @_;
621 my $manager = $cv_type eq 'customer' ? 'SL::DB::Manager::Customer'
622 : $cv_type eq 'vendor' ? 'SL::DB::Manager::Vendor'
623 : die "No valid customer vendor option: $cv_type";
625 my $email_address = $email_journal->from;
626 $email_address =~ s/.*<(.*)>/$1/; # address can look like "name surname <email_address>"
628 # Separate query otherwise cv without contacts and shipto is not found
630 $customer_vendor ||= $manager->get_first(
633 email => $email_address,
634 cc => $email_address,
635 bcc => $email_address,
639 $customer_vendor ||= $manager->get_first(
642 'contacts.cp_email' => $email_address,
643 'contacts.cp_privatemail' => $email_address,
646 with_objects => [ 'contacts'],
648 $customer_vendor ||= $manager->get_first(
651 'shipto.shiptoemail' => $email_address,
654 with_objects => [ 'shipto' ],
656 if ($manager eq 'SL::DB::Manager::Customer') {
657 $customer_vendor ||= $manager->get_first(
660 'additional_billing_addresses.email' => $email_address,
663 with_objects => [ 'additional_billing_addresses' ],
667 # if no exact match is found search for domain and match only on one hit
668 unless ($customer_vendor) {
669 my $email_domain = $email_address;
670 $email_domain =~ s/.*@(.*)/$1/;
671 my @domain_hits_cusotmer_vendor = ();
672 my @domain_hits = ();
673 push @domain_hits, @{$manager->get_all(
676 email => {ilike => "%$email_domain"},
677 cc => {ilike => "%$email_domain"},
678 bcc => {ilike => "%$email_domain"},
682 push @domain_hits, @{$manager->get_all(
685 'contacts.cp_email' => {ilike => "%$email_domain"},
686 'contacts.cp_privatemail' => {ilike => "%$email_domain"},
689 with_objects => [ 'contacts'],
691 push @domain_hits, @{$manager->get_all(
694 'shipto.shiptoemail' => {ilike => "%$email_domain"},
697 with_objects => [ 'shipto' ],
699 push @domain_hits, @{$manager->get_all(
702 'shipto.shiptoemail' => {ilike => "%$email_domain"},
705 with_objects => [ 'shipto' ],
707 if ($manager eq 'SL::DB::Manager::Customer') {
708 push @domain_hits, @{$manager->get_all(
711 'additional_billing_addresses.email' => {ilike => "%$email_domain"},
714 with_objects => [ 'additional_billing_addresses' ],
717 # update on only one unique customer_vendor
718 if (scalar @domain_hits) {
719 my $first_customer_vendor = $domain_hits[0];
720 unless (any {$_->id != $first_customer_vendor->id} @domain_hits) {
721 $customer_vendor = $first_customer_vendor;
726 return $customer_vendor;
730 $::request->{layout}->use_javascript("${_}.js") for qw(
735 sub init_can_view_all { $::auth->assert('email_employee_readall', 1) }
741 push @where, (sender_id => SL::DB::Manager::Employee->current->id) if !$self->can_view_all;
743 SL::Controller::Helper::GetModels->new(
746 with_objects => [ 'sender' ],
748 sender => t8('Sender'),
750 recipients => t8('Recipients'),
751 subject => t8('Subject'),
752 sent_on => t8('Sent on'),
753 status => t8('Status'),
754 extended_status => t8('Extended status'),
755 record_type => t8('Record Type'),
756 obsolete => t8('Obsolete'),
757 linked_to => t8('Linked to'),
762 sub init_filter_summary {
765 my $filter = $::form->{filter} || {};
767 [ "from:substr::ilike", $::locale->text('From') ],
768 [ "recipients:substr::ilike", $::locale->text('Recipients') ],
769 [ "sent_on:date::ge", $::locale->text('Sent on') . " " . $::locale->text('From Date') ],
770 [ "sent_on:date::le", $::locale->text('Sent on') . " " . $::locale->text('To Date') ],
773 my @filter_strings = grep { $_ }
774 map { $filter->{ $_->[0] } ? $_->[1] . ' ' . $filter->{ $_->[0] } : undef }
778 send_failed => $::locale->text('send failed'),
779 sent => $::locale->text('sent'),
780 imported => $::locale->text('imported'),
782 push @filter_strings, $status{ $filter->{'status:eq_ignore_empty'} } if $filter->{'status:eq_ignore_empty'};
785 my %record_type_to_text = $self->get_record_types_to_text();
786 push @filter_strings, $record_type_to_text{ $filter->{'record_type:eq_ignore_empty'} } if $filter->{'record_type:eq_ignore_empty'};
788 push @filter_strings, $::locale->text('Obsolete') if $filter->{'obsolete:eq_ignore_empty'} eq '1';
789 push @filter_strings, $::locale->text('Not obsolete') if $filter->{'obsolete:eq_ignore_empty'} eq '0';
791 push @filter_strings, $::locale->text('Linked') if $filter->{'linked_to:eq_ignore_empty'} eq '1';
792 push @filter_strings, $::locale->text('Not linked') if $filter->{'linked_to:eq_ignore_empty'} eq '0';
794 return join ', ', @filter_strings;
797 sub setup_list_action_bar {
800 for my $bar ($::request->layout->get('actionbar')) {
804 submit => [ '#filter_form', { action => 'EmailJournal/list' } ],
805 accesskey => 'enter',
811 sub setup_show_action_bar {
814 for my $bar ($::request->layout->get('actionbar')) {
818 call => [ 'kivi.history_back' ],