]> wagnertech.de Git - mfinanz.git/blob - SL/Controller/EmailJournal.pm
restart apache2 in postinst
[mfinanz.git] / SL / Controller / EmailJournal.pm
1 package SL::Controller::EmailJournal;
2
3 use strict;
4
5 use parent qw(SL::Controller::Base);
6
7 use SL::ZUGFeRD;
8 use SL::Controller::ZUGFeRD;
9 use SL::Controller::Helper::GetModels;
10 use SL::DB::Employee;
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);
18
19 use SL::DB::Order;
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;
25 use SL::DB::Invoice;
26 use SL::DB::Invoice::TypeData;
27 use SL::DB::PurchaseInvoice;
28 use SL::DB::PurchaseInvoice::TypeData;
29
30 use SL::DB::Manager::Customer;
31 use SL::DB::Manager::Vendor;
32
33 use List::Util qw(first);
34 use List::MoreUtils qw(any);
35
36 use Rose::Object::MakeMethods::Generic
37 (
38   scalar                  => [ qw(entry) ],
39   'scalar --get_set_init' => [ qw(models can_view_all filter_summary) ],
40 );
41
42 __PACKAGE__->run_before('add_stylesheet');
43 __PACKAGE__->run_before('add_js');
44
45 my %RECORD_TYPES_INFO = (
46   Order => {
47     controller => 'Order',
48     class      => 'Order',
49     types => SL::DB::Order::TypeData->valid_types(),
50   },
51   DeliveryOrder => {
52     controller => 'DeliveryOrder',
53     class      => 'DeliveryOrder',
54     types => SL::DB::DeliveryOrder::TypeData->valid_types(),
55   },
56   Reclamation => {
57     controller => 'Reclamation',
58     class      => 'Reclamation',
59     types => SL::DB::Reclamation::TypeData->valid_types(),
60   },
61   GlTransaction => {
62     controller => 'gl.pl',
63     class      => 'GLTransaction',
64     types => [
65       'gl_transaction',
66     ],
67   },
68   ArTransaction => {
69     controller => 'ar.pl',
70     class      => 'Invoice',
71     types => [
72       'ar_transaction',
73     ],
74   },
75   Invoice => {
76     controller => 'is.pl',
77     class      => 'Invoice',
78     types => SL::DB::Invoice::TypeData->valid_types(),
79   },
80   ApTransaction => {
81     controller => 'ap.pl',
82     class      => 'PurchaseInvoice',
83     types => [
84       'ap_transaction',
85     ],
86   },
87   PurchaseInvoice => {
88     controller => 'ir.pl',
89     class      => 'PurchaseInvoice',
90     types => SL::DB::PurchaseInvoice::TypeData->valid_types(),
91   },
92   GlRecordTemplate => {
93     controller => 'gl.pl',
94     class      => 'RecordTemplate',
95     types => [
96       'gl_transaction_template',
97     ],
98   },
99   ArRecordTemplate => {
100     controller => 'ar.pl',
101     class      => 'RecordTemplate',
102     types => [
103       'ar_transaction_template',
104     ],
105   },
106   ApRecordTemplate => {
107     controller => 'ap.pl',
108     class      => 'RecordTemplate',
109     types => [
110       'ap_transaction_template',
111     ],
112   },
113 );
114 my %RECORD_TYPE_TO_CONTROLLER =
115   map {
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 =
120   map {
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 =
125   map {
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 =
132   map {
133     my $model = $RECORD_TYPE_TO_MODEL{$_};
134     if (any {$model eq $_} qw(SL::DB::Invoice SL::DB::PurchaseInvoice)) {
135       $_ => 'invnumber';
136     } elsif (any {$model eq $_} qw(SL::DB::RecordTemplate)) {
137       $_ => 'template_name';
138     } elsif (any {$model eq $_} qw(SL::DB::GLTransaction)) {
139       $_ => 'reference';
140     } else {
141       my $type_data = SL::DB::Helper::TypeDataProxy->new($model, $_);
142       $_ => $type_data->properties('nr_key');
143     }
144   } @ALL_RECORD_TYPES;
145
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',
152     ) {
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'),
161         can_workflow    => (
162           any {
163             $_ ne 'delete' && $type_data->can('get3')->($type, 'show_menu', $_)
164           } keys %{$type_data->can('get')->($type, 'show_menu')}
165         ),
166       };
167     }
168   }
169   push @record_types_with_info, (
170     # transactions
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')},
176     # templates
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')},
181   );
182   return @record_types_with_info;
183 }
184
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();
188
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");
192
193   return %record_types_to_text;
194 }
195
196 sub record_types_for_customer_vendor_type_and_action {
197   my ($self, $customer_vendor_type, $action) = @_;
198   return [
199     map { $_->{record_type} }
200     grep {
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')
204     }
205     grep {
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)
211     }
212     $self->get_record_types_with_info()
213   ];
214 }
215
216 #
217 # actions
218 #
219
220 sub action_list {
221   my ($self) = @_;
222
223   $::auth->assert('email_journal');
224   # default filter
225   $::form->{filter} ||= {"obsolete:eq_ignore_empty" => 0};
226
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.'));
229   }
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,
239               );
240 }
241
242 sub action_show {
243   my ($self) = @_;
244
245   $::auth->assert('email_journal');
246
247   my $back_to = $::form->{back_to} || $self->url_for(action => 'list');
248
249   $self->entry(SL::DB::EmailJournal->new(id => $::form->{id})->load);
250
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.'));
253   }
254
255   my @record_types_with_info = $self->get_record_types_with_info();
256   my %record_types_to_text   = $self->get_record_types_to_text();
257
258   my $customer = $self->find_customer_vendor_from_email('customer', $self->entry);
259   my $vendor   = $self->find_customer_vendor_from_email('vendor'  , $self->entry);
260
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'
266                     : 'customer';
267
268   my $record_types = $self->record_types_for_customer_vendor_type_and_action(
269     $cv_type_found, 'workflow_record'
270   );
271
272   $self->setup_show_action_bar;
273   $self->render(
274     'email_journal/show',
275     title                  => $::locale->text('View email'),
276     CUSTOMER               => $customer,
277     VENDOR                 => $vendor,
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,
281     back_to  => $back_to,
282   );
283 }
284
285 sub action_attachment_preview {
286   my ($self) = @_;
287
288   eval {
289     $::auth->assert('email_journal');
290
291     my $attachment_id = $::form->{attachment_id};
292     die "no 'attachment_id' was given" unless $attachment_id;
293
294     my $attachment;
295     $attachment = SL::DB::EmailJournalAttachment->new(
296       id => $attachment_id,
297     )->load;
298
299
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.'));
304     }
305
306     my $output = SL::Presenter::EmailJournal::attachment_preview(
307       $attachment,
308       style => "height: 1800px"
309     );
310
311     $self->render( \$output, { layout => 0, process => 0,});
312   } or do {
313     $self->render('generic/error', { layout => 0 }, label_error => $@);
314   };
315 }
316
317 sub action_show_attachment {
318   my ($self) = @_;
319
320   $::auth->assert('email_journal');
321
322   my $attachment_id      = $::form->{attachment_id};
323   my $attachment = SL::DB::EmailJournalAttachment->new(id => $attachment_id)->load;
324
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.'));
327   }
328
329   return $self->send_file(
330     \$attachment->content,
331     name => $attachment->name,
332     type => $attachment->mime_type,
333     content_disposition => 'inline',
334   );
335 }
336
337 sub action_download_attachment {
338   my ($self) = @_;
339
340   $::auth->assert('email_journal');
341
342   my $attachment = SL::DB::EmailJournalAttachment->new(id => $::form->{id})->load;
343
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.'));
346   }
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;
352   }
353   $self->send_file($ref, name => $attachment->name, type => $attachment->mime_type);
354 }
355
356 sub action_apply_record_action {
357   my ($self) = @_;
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"};
366
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;
372
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,
379       });
380   }
381
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},
393     };
394   } else { # workflow_record
395     $additional_params{action} = 'edit_with_email_journal_workflow';
396     $additional_params{id} = $record_id;
397   }
398
399   $self->redirect_to(
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},
405     %additional_params,
406   );
407 }
408
409 sub action_ap_transaction_template_with_zugferd_import {
410   my ($self) = @_;
411   my $email_journal_id = $::form->{email_journal_id};
412   die "no 'email_journal_id' was given" unless $email_journal_id;
413
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';
417
418   my $attachment_id = $::form->{attachment_id};
419
420   my $form_defaults;
421   if ($attachment_id) {
422     my $attachment = SL::DB::EmailJournalAttachment->new(id => $attachment_id)->load();
423     my $content = $attachment->content; # scalar ref
424
425     if ($content =~ m/^%PDF|<\?xml/) {
426
427       my %res;
428       if ( $content =~ m/^%PDF/ ) {
429         %res = %{SL::ZUGFeRD->extract_from_pdf($content)};
430       } else {
431         %res = %{SL::ZUGFeRD->extract_from_xml($content)};
432       }
433
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;
437
438         $form_defaults = SL::Controller::ZUGFeRD->build_ap_transaction_form_defaults(\%res, vendor => $vendor);
439         flash_later('info',
440           t8("The ZUGFeRD/Factur-X invoice '#1' has been loaded.", $attachment->name));
441       }
442     }
443   }
444
445   $form_defaults->{email_journal_id}    = $email_journal_id;
446   $form_defaults->{email_attachment_id} = $attachment_id;
447   $form_defaults->{callback}            = $::form->{back_to};
448
449   $self->redirect_to(
450     controller         => 'ap.pl',
451     action             => 'load_zugferd',
452     record_template_id => $record_id,
453     form_defaults      => $form_defaults,
454   );
455 }
456
457 sub action_update_attachment_preview {
458   my ($self) = @_;
459   $::auth->assert('email_journal');
460   my $attachment_id = $::form->{attachment_id};
461
462   my $attachment;
463   $attachment = SL::DB::EmailJournalAttachment->new(
464     id => $attachment_id,
465   )->load if $attachment_id;
466
467   $self->js
468     ->replaceWith('#attachment_preview',
469       SL::Presenter::EmailJournal::attachment_preview(
470         $attachment,
471         style => "height:1800px"
472       )
473     )
474     ->render();
475 }
476
477 sub action_update_record_list {
478   my ($self) = @_;
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};
486
487   $record_type ||= $self->record_types_for_customer_vendor_type_and_action($customer_vendor_type, $action);
488
489   my @records = $self->get_records_for_types(
490     $record_type,
491     customer_vendor_type => $customer_vendor_type,
492     customer_vendor_id   => $customer_vendor_id,
493     record_number        => $record_number,
494     with_closed          => $with_closed,
495   );
496
497   my $new_div = $self->get_records_div(\@records);
498
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;
502   $self->js->render();
503 }
504
505 sub action_toggle_obsolete {
506   my ($self) = @_;
507
508   $::auth->assert('email_journal');
509
510   $self->entry(SL::DB::EmailJournal->new(id => $::form->{id})->load);
511
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.'));
514   }
515
516   $self->entry->obsolete(!$self->entry->obsolete);
517   $self->entry->save;
518
519   $self->js
520   ->val('#obsolete', $self->entry->obsolete_as_bool_yn)
521   ->flash('info',
522     $self->entry->obsolete ?
523       $::locale->text('Email marked as obsolete.')
524     : $::locale->text('Email marked as not obsolete.')
525   )->render();
526
527   return;
528 }
529
530 #
531 # filters
532 #
533
534 sub add_stylesheet {
535   $::request->{layout}->use_stylesheet('email_journal.css');
536 }
537
538 #
539 # helpers
540 #
541
542 sub get_records_for_types {
543   my ($self, $record_types, %params) = @_;
544   $record_types = [ $record_types ] unless ref $record_types eq 'ARRAY';
545
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};
550
551   my @records = ();
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;
558     }
559     if ($record_number) {
560       my $nr_key = $RECORD_TYPE_TO_NR_KEY{$record_type};
561       $additional_where{$nr_key} = { ilike => "%$record_number%" };
562     }
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' };
568       }
569     }
570     my $records_of_type = $manager->get_all(
571       where => [
572         $manager->type_filter($record_type),
573         %additional_where,
574       ],
575     );
576     push @records, @$records_of_type;
577   }
578
579   return @records;
580 }
581
582 sub get_records_div {
583   my ($self, $records) = @_;
584   my $div = div_tag(
585     grouped_record_list(
586       $records,
587       with_columns => [ qw(email_journal_action) ],
588     ),
589     id => 'record_list',
590   );
591   return $div;
592 }
593
594 sub link_and_add_attachment_to_record {
595  my ($self, $params) = @_;
596
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};
601
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;
605
606   if ($attachment_id) {
607     my $attachment = SL::DB::EmailJournalAttachment->new(id => $attachment_id)->load;
608     $attachment->add_file_to_record($record);
609   }
610
611   $email_journal->link_to_record($record);
612
613   $self->js->flash('info',
614     $::locale->text('Linked email and attachment to ') . $record->displayable_name
615   )->render();
616 }
617
618 sub find_customer_vendor_from_email {
619   my ($self, $cv_type, $email_journal) = @_;
620
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";
624
625   my $email_address = $email_journal->from;
626   $email_address =~ s/.*<(.*)>/$1/; # address can look like "name surname <email_address>"
627
628   # Separate query otherwise cv without contacts and shipto is not found
629   my $customer_vendor;
630   $customer_vendor ||= $manager->get_first(
631     where => [
632       or => [
633         email => $email_address,
634         cc    => $email_address,
635         bcc   => $email_address,
636       ],
637     ],
638   );
639   $customer_vendor ||= $manager->get_first(
640     where => [
641       or => [
642         'contacts.cp_email' => $email_address,
643         'contacts.cp_privatemail' => $email_address,
644       ],
645     ],
646     with_objects => [ 'contacts'],
647   );
648   $customer_vendor ||= $manager->get_first(
649     where => [
650       or => [
651         'shipto.shiptoemail' => $email_address,
652       ],
653     ],
654     with_objects => [ 'shipto' ],
655   );
656   if ($manager eq 'SL::DB::Manager::Customer') {
657     $customer_vendor ||= $manager->get_first(
658       where => [
659         or => [
660           'additional_billing_addresses.email' => $email_address,
661         ],
662       ],
663       with_objects => [ 'additional_billing_addresses' ],
664     );
665   }
666
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(
674       where => [
675         or => [
676           email => {ilike => "%$email_domain"},
677           cc    => {ilike => "%$email_domain"},
678           bcc   => {ilike => "%$email_domain"},
679         ],
680       ],
681     )};
682     push @domain_hits, @{$manager->get_all(
683       where => [
684         or => [
685           'contacts.cp_email'       => {ilike => "%$email_domain"},
686           'contacts.cp_privatemail' => {ilike => "%$email_domain"},
687         ],
688       ],
689       with_objects => [ 'contacts'],
690     )};
691     push @domain_hits, @{$manager->get_all(
692       where => [
693         or => [
694           'shipto.shiptoemail' => {ilike => "%$email_domain"},
695         ],
696       ],
697       with_objects => [ 'shipto' ],
698     )};
699     push @domain_hits, @{$manager->get_all(
700       where => [
701         or => [
702           'shipto.shiptoemail' => {ilike => "%$email_domain"},
703         ],
704       ],
705       with_objects => [ 'shipto' ],
706     )};
707     if ($manager eq 'SL::DB::Manager::Customer') {
708       push @domain_hits, @{$manager->get_all(
709         where => [
710           or => [
711             'additional_billing_addresses.email' => {ilike => "%$email_domain"},
712           ],
713         ],
714         with_objects => [ 'additional_billing_addresses' ],
715       )};
716     }
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;
722       }
723     }
724   }
725
726   return $customer_vendor;
727 }
728
729 sub add_js {
730   $::request->{layout}->use_javascript("${_}.js") for qw(
731     kivi.EmailJournal
732     );
733 }
734
735 sub init_can_view_all { $::auth->assert('email_employee_readall', 1) }
736
737 sub init_models {
738   my ($self) = @_;
739
740   my @where;
741   push @where, (sender_id => SL::DB::Manager::Employee->current->id) if !$self->can_view_all;
742
743   SL::Controller::Helper::GetModels->new(
744     controller        => $self,
745     query             => \@where,
746     with_objects      => [ 'sender' ],
747     sorted            => {
748       sender          => t8('Sender'),
749       from            => t8('From'),
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'),
758     },
759   );
760 }
761
762 sub init_filter_summary {
763   my ($self)  = @_;
764
765   my $filter  = $::form->{filter} || {};
766   my @filters = (
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')   ],
771   );
772
773   my @filter_strings = grep { $_ }
774                        map  { $filter->{ $_->[0] } ? $_->[1] . ' ' . $filter->{ $_->[0] } : undef }
775                        @filters;
776
777   my %status = (
778     send_failed     => $::locale->text('send failed'),
779     sent            => $::locale->text('sent'),
780     imported        => $::locale->text('imported'),
781   );
782   push @filter_strings, $status{ $filter->{'status:eq_ignore_empty'} } if $filter->{'status:eq_ignore_empty'};
783
784
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'};
787
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';
790
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';
793
794   return join ', ', @filter_strings;
795 }
796
797 sub setup_list_action_bar {
798   my ($self) = @_;
799
800   for my $bar ($::request->layout->get('actionbar')) {
801     $bar->add(
802       action => [
803         t8('Filter'),
804         submit    => [ '#filter_form', { action => 'EmailJournal/list' } ],
805         accesskey => 'enter',
806       ],
807     );
808   }
809 }
810
811 sub setup_show_action_bar {
812   my ($self) = @_;
813
814   for my $bar ($::request->layout->get('actionbar')) {
815     $bar->add(
816       action => [
817         t8('Back'),
818         call => [ 'kivi.history_back' ],
819       ],
820     );
821   }
822 }
823
824 1;