Brieffunktion: Umstellung Druck auf Template Toolkit
[kivitendo-erp.git] / SL / Controller / Letter.pm
1 package SL::Controller::Letter;
2
3 use strict;
4 use parent qw(SL::Controller::Base);
5
6 use Carp;
7 use File::Basename;
8 use POSIX qw(strftime);
9 use SL::Controller::Helper::GetModels;
10 use SL::Controller::Helper::ReportGenerator;
11 use SL::CT;
12 use SL::DB::Letter;
13 use SL::DB::LetterDraft;
14 use SL::DB::Employee;
15 use SL::Helper::Flash qw(flash flash_later);
16 use SL::Helper::CreatePDF;
17 use SL::Helper::PrintOptions;
18 use SL::Locale::String qw(t8);
19 use SL::Mailer;
20 use SL::IS;
21 use SL::ReportGenerator;
22 use SL::Webdav;
23 use SL::Webdav::File;
24
25 use Rose::Object::MakeMethods::Generic (
26   'scalar --get_set_init' => [ qw(letter all_employees models webdav_objects) ],
27 );
28
29 __PACKAGE__->run_before('check_auth_edit');
30 __PACKAGE__->run_before('check_auth_report', only => [ qw(list) ]);
31
32 use constant TEXT_CREATED_FOR_VALUES => (qw(presskit fax letter));
33 use constant PAGE_CREATED_FOR_VALUES => (qw(sketch 1 2));
34
35 my %sort_columns = (
36   date                  => t8('Date'),
37   subject               => t8('Subject'),
38   letternumber          => t8('Letternumber'),
39   customer_id           => t8('Customer'),
40   contact               => t8('Contact'),
41 );
42
43 sub action_add {
44   my ($self, %params) = @_;
45
46   return if $self->load_letter_draft(%params);
47
48   $self->letter->employee_id(SL::DB::Manager::Employee->current->id);
49   $self->letter->salesman_id(SL::DB::Manager::Employee->current->id);
50
51   $self->_display(
52     title       => t8('Add Letter'),
53     language_id => $params{language_id},
54   );
55 }
56
57 sub action_edit {
58   my ($self, %params) = @_;
59
60   return $self->action_add
61     unless $::form->{letter} || $::form->{draft};
62
63   $self->letter(SL::DB::Letter->new_from_draft($::form->{draft}{id}))
64     if $::form->{draft};
65
66   $self->_display(
67     title  => t8('Edit Letter'),
68   );
69 }
70
71 sub action_save {
72   my ($self, %params) = @_;
73
74   my $letter = $self->_update;
75
76   if (!$self->check_letter($letter)) {
77     return $self->_display;
78   }
79
80   $self->check_number;
81
82   if (!$letter->save) {
83     flash('error', t8('There was an error saving the letter'));
84     return $self->_display;
85   }
86
87   flash('info', t8('Letter saved!'));
88
89   $self->_display;
90 }
91
92 sub action_update_contacts {
93   my ($self) = @_;
94
95   my $letter = $self->letter;
96
97   if (!$self->letter->customer_id || !$self->letter->customer) {
98     return $self->js
99       ->replaceWith(
100         '#letter_cp_id',
101         SL::Presenter->get->select_tag('letter.cp_id', [], value_key => 'cp_id', title_key => 'full_name')
102       )
103       ->render;
104   }
105
106   my $contacts = $letter->customer->contacts;
107
108   my $default;
109   if (   $letter->contact
110       && $letter->contact->cp_cv_id
111       && $letter->contact->cp_cv_id == $letter->customer_id) {
112     $default = $letter->contact->cp_id;
113   } else {
114     $default = '';
115   }
116
117   $self->js
118     ->replaceWith(
119       '#letter_cp_id',
120       SL::Presenter->get->select_tag('letter.cp_id', $contacts, default => $default, value_key => 'cp_id', title_key => 'full_name')
121     )
122     ->render;
123 }
124
125 sub action_save_letter_draft {
126   my ($self, %params) = @_;
127
128   $self->check_letter;
129
130   my $letter_draft = SL::DB::LetterDraft->new_from_letter($self->_update);
131
132   if (!$letter_draft->save) {
133     flash('error', t8('There was an error saving the letter draft'));
134     return $self->_display;
135   }
136
137   flash('info', t8('Draft for this Letter saved!'));
138
139   $self->_display;
140 }
141
142 sub action_delete {
143   my ($self, %params) = @_;
144
145   if (!$self->letter->delete) {
146     flash('error', t8('An error occured. Letter could not be deleted.'));
147     return $self->action_update;
148   }
149
150   flash_later('info', t8('Letter deleted'));
151   $self->redirect_to(action => 'list');
152 }
153
154 sub action_delete_letter_drafts {
155   my ($self, %params) = @_;
156
157   my @ids =  grep { /^checked_(.*)/ && $::form->{$_} } keys %$::form;
158
159   SL::DB::Manager::LetterDraft->delete_all(query => [ ids => \@ids ]) if @ids;
160
161   $self->redirect_to(action => 'add');
162 }
163
164 sub action_list {
165   my ($self, %params) = @_;
166
167   $self->make_filter_summary;
168   $self->prepare_report;
169
170   my $letters = $self->models->get;
171   $self->report_generator_list_objects(report => $self->{report}, objects => $letters);
172
173 }
174
175 sub action_print_letter {
176   my ($self, %params) = @_;
177
178   my $display_form = $::form->{display_form} || "display_form";
179   my $letter       = $self->_update;
180
181   my ($template_file, @template_files) = SL::Helper::CreatePDF->find_template(
182     name        => 'letter',
183     printer_id  => $::form->{printer_id},
184     language_id => $::form->{language_id},
185     formname    => 'letter',
186     format      => 'pdf',
187   );
188
189   if (!defined $template_file) {
190     $::form->error($::locale->text('Cannot find matching template for this print request. Please contact your template maintainer. I tried these: #1.', join ', ', map { "'$_'"} @template_files));
191   }
192
193   my %result;
194   eval {
195     %result = SL::Template::LaTeX->parse_and_create_pdf(
196       $template_file,
197       SELF          => $self,
198       FORM          => $::form,
199       letter        => $letter,
200       template_meta => {
201         formname  => 'letter',
202         language  => SL::DB::Language->new,
203         extension => 'pdf',
204         format    => $::form->{format},
205         media     => $::form->{media},
206         printer   => SL::DB::Manager::Printer->find_by_or_create(id => $::form->{printer_id} || undef),
207         today     => DateTime->today,
208       },
209     );
210
211     die $result{error} if $result{error};
212
213     $::form->{type}         = 'letter';
214     $::form->{formname}     = 'letter';
215     $::form->{letternumber} = $letter->letternumber;
216     my $attachment_name     = $::form->generate_attachment_filename;
217
218     if ($::instance_conf->get_webdav_documents) {
219       my $webdav_file = SL::Webdav::File->new(
220         filename => $attachment_name,
221         webdav   => SL::Webdav->new(
222           type   => 'letter',
223           number => $letter->letternumber,
224         ),
225       );
226
227       $webdav_file->store(file => $result{file_name});
228     }
229
230     # set some form defaults for printing webdav copy variables
231     if ( $::form->{media} eq 'email') {
232       my $mail             = Mailer->new;
233       my $signature        = $::myconfig{signature};
234       $mail->{$_}          = $params{email}->{$_} for qw(to cc subject message bcc);
235       $mail->{from}        = qq|"$::myconfig{name}" <$::myconfig{email}>|;
236       $mail->{attachments} = [{ filename => $result{file_name},
237                                 name     => $params{email}->{attachment_filename} }];
238       $mail->{message}    .=  "\n-- \n$signature";
239       $mail->{message}     =~ s/\r//g;
240
241       $mail->send;
242       unlink $result{file_name};
243
244       flash_later('info', t8('The email has been sent.'));
245       $self->redirect_to(action => 'edit', 'letter.id' => $letter->id);
246
247       return 1;
248     }
249
250     if (!$::form->{printer_id} || $::form->{media} eq 'screen') {
251       $self->send_file($result{file_name}, name => $attachment_name);
252       unlink $result{file_name};
253
254       return 1;
255     }
256
257     my $printer = SL::DB::Printer->new(id => $::form->{printer_id})->load;
258     $printer->print_document(
259       copies    => $::form->{copies},
260       file_name => $result{file_name},
261     );
262
263     unlink $result{file_name};
264
265     flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
266     $self->redirect_to(action => 'edit', 'letter.id' => $letter->id, media => 'printer', printer_id => $::form->{printer_id});
267     1;
268   } or do {
269     unlink $result{file_name} if $result{file_name};
270     $::form->error(t8("Creating the PDF failed:") . " " . $@);
271   };
272 }
273
274 sub action_update {
275   my ($self, $name_selected) = @_;
276
277   $self->_display(
278     letter => $self->_update,
279   );
280 }
281
282 sub action_skip_draft {
283   my ($self) = @_;
284   $self->action_add(skip_drafts => 1);
285 }
286
287 sub action_delete_drafts {
288   my ($self) = @_;
289
290   my @ids = @{ $::form->{ids} || [] };
291   SL::DB::Manager::LetterDraft->delete_all(where => [ id => \@ids ]) if @ids;
292
293   $self->action_add(skip_drafts => 1);
294 }
295
296 sub action_edit_email {
297   my ($self) = @_;
298
299   my $letter = $self->_update;
300   $self->export_letter_to_form($letter);
301
302   $::form->{formname}     = "letter";
303   $::form->{type}         = "letter";
304   $::form->{letternumber} = $self->letter->letternumber;
305
306   my @hiddens = map {
307     my $value = $letter->$_;
308     $value    = $value->to_kivitendo if ref($_) =~ m{Date};
309
310     { name => "letter.$_", value => $value }
311   } ($letter->meta->columns);
312
313   my %vars = (
314     script     => 'controller.pl',
315     title      => t8('Send letter via e-mail'),
316     email      => $letter->contact ? $letter->contact->cp_email : '',
317     subject    => $::form->generate_email_subject,
318     a_filename => $::form->generate_attachment_filename,
319     action     => 'Letter/send_email',
320     HIDDEN     => \@hiddens,
321     SHOW_BCC   => $::auth->assert('email_bcc', 'may fail'),
322   );
323
324   $self->render('generic/edit_email', %vars);
325 }
326
327 sub action_send_email {
328   my ($self) = @_;
329
330   $::form->{media} = 'email';
331   $self->action_print_letter(
332     email => {
333       to => $::form->{email},
334       map { ($_ => $::form->{$_}) } qw(cc bcc subject attachment_filename message)
335     }
336   );
337 }
338
339 sub _display {
340   my ($self, %params) = @_;
341
342   $::request->{layout}->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery);
343
344   my $letter = $self->letter;
345
346  $params{title} ||= t8('Edit Letter');
347
348   $::form->{type}             = 'letter';   # needed for print_options
349   $::form->{vc}               = 'customer'; # needs to be for _get_contacts...
350
351   $::request->layout->add_javascripts('customer_or_vendor_selection.js');
352   $::request->layout->add_javascripts('edit_part_window.js');
353
354   $::form->{language_id} ||= $params{language_id};
355   $::form->{printers}      = SL::DB::Manager::Printer->get_all_sorted;
356
357   $self->render('letter/edit',
358     %params,
359     TCF           => [ map { key => $_, value => t8(ucfirst $_) }, TEXT_CREATED_FOR_VALUES() ],
360     PCF           => [ map { key => $_, value => t8(ucfirst $_) }, PAGE_CREATED_FOR_VALUES() ],
361     letter        => $letter,
362     employees     => $self->all_employees,
363     print_options => SL::Helper::PrintOptions->get_print_options (
364       options => { no_postscript   => 1,
365                    no_opendocument => 1,
366                    no_html         => 1,
367                    no_queue        => 1 }),
368
369   );
370 }
371
372 sub _update {
373   my ($self, %params) = @_;
374
375   my $letter = $self->letter;
376
377   $self->check_date;
378   $self->set_greetings;
379
380   return $letter;
381 }
382
383 sub prepare_report {
384   my ($self) = @_;
385
386   my $report      = SL::ReportGenerator->new(\%::myconfig, $::form);
387   $self->{report} = $report;
388
389   my @columns  = qw(date subject letternumber customer_id contact date);
390   my @sortable = qw(date subject letternumber customer_id contact date);
391
392   my %column_defs = (
393     date                  => { text => t8('Date'),         sub => sub { $_[0]->date_as_date } },
394     subject               => { text => t8('Subject'),      sub => sub { $_[0]->subject },
395                                obj_link => sub { $self->url_for(action => 'edit', 'letter.id' => $_[0]->id, callback => $self->models->get_callback) }  },
396     letternumber          => { text => t8('Letternumber'), sub => sub { $_[0]->letternumber },
397                                obj_link => sub { $self->url_for(action => 'edit', 'letter.id' => $_[0]->id, callback => $self->models->get_callback) }  },
398     customer_id           => { text => t8('Customer'),      sub => sub { SL::DB::Manager::Customer->find_by_or_create(id => $_[0]->customer_id)->displayable_name } },
399     contact               => { text => t8('Contact'),       sub => sub { $_[0]->contact ? $_[0]->contact->full_name : '' } },
400   );
401
402   $column_defs{$_}{text} = $sort_columns{$_} for keys %column_defs;
403
404   $report->set_options(
405     std_column_visibility => 1,
406     controller_class      => 'Letter',
407     output_format         => 'HTML',
408     top_info_text         => t8('Letters'),
409     title                 => t8('Letters'),
410     allow_pdf_export      => 1,
411     allow_csv_export      => 1,
412   );
413
414   $report->set_columns(%column_defs);
415   $report->set_column_order(@columns);
416   $report->set_export_options(qw(list filter));
417   $report->set_options_from_form;
418
419   $self->models->disable_plugin('paginated') if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
420   $self->models->finalize;
421   $self->models->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable);
422
423   $report->set_options(
424     raw_top_info_text    => $self->render('letter/report_top',    { output => 0 }),
425     raw_bottom_info_text => $self->render('letter/report_bottom', { output => 0 }, models => $self->models),
426     attachment_basename  => t8('letters_list') . strftime('_%Y%m%d', localtime time),
427   );
428 }
429
430 sub make_filter_summary {
431   my ($self) = @_;
432
433   my $filter = $::form->{filter} || {};
434   my @filter_strings;
435
436   my $employee = $filter->{employee_id} ? SL::DB::Employee->new(id => $filter->{employee_id})->load->name : '';
437   my $salesman = $filter->{salesman_id} ? SL::DB::Employee->new(id => $filter->{salesman_id})->load->name : '';
438
439   my @filters = (
440     [ $filter->{"letternumber:substr::ilike"},  t8('Number')     ],
441     [ $filter->{"subject:substr::ilike"},       t8('Subject')    ],
442     [ $filter->{"body:substr::ilike"},          t8('Body')       ],
443     [ $filter->{"date:date::ge"},               t8('From Date')  ],
444     [ $filter->{"date:date::le"},               t8('To Date')    ],
445     [ $employee,                                t8('Employee')   ],
446     [ $salesman,                                t8('Salesman')   ],
447   );
448
449   my %flags = (
450   );
451   my @flags = map { $flags{$_} } @{ $filter->{part}{type} || [] };
452
453   for (@flags) {
454     push @filter_strings, $_ if $_;
455   }
456   for (@filters) {
457     push @filter_strings, "$_->[1]: $_->[0]" if $_->[0];
458   }
459
460   $self->{filter_summary} = join ', ', @filter_strings;
461 }
462
463 sub load_letter_draft {
464   my ($self, %params) = @_;
465
466   return 0 if $params{skip_drafts};
467
468   my $letter_drafts = SL::DB::Manager::LetterDraft->get_all;
469
470   return unless @$letter_drafts;
471
472   $self->render('letter/load_drafts',
473     title         => t8('Letter Draft'),
474     LETTER_DRAFTS => $letter_drafts,
475   );
476
477   return 1;
478 }
479
480 sub check_date {
481   my ($self) = @_;
482   my $letter = $self->letter;
483
484   return unless $letter;
485   return if $letter->date;
486
487   $letter->date(DateTime->today)
488 }
489
490 sub check_letter {
491   my ($self, $letter) = @_;
492
493   $letter ||= $self->letter;
494
495   my $error;
496
497   if (!$letter->subject) {
498     flash('error', t8('The subject is missing.'));
499     $error = 1;
500   }
501   if (!$letter->body) {
502     flash('error', t8('The body is missing.'));
503     $error = 1;
504   }
505   if (!$letter->employee_id) {
506     flash('error', t8('The employee is missing.'));
507     $error = 1;
508   }
509
510   return !$error;
511 }
512
513 sub check_number {
514   my ($self, $letter) = @_;
515
516   $letter ||= $self->letter;
517
518   return if $letter->letternumber;
519
520   $letter->letternumber(SL::TransNumber->new(type => 'letter', id => $self->{id}, number => $self->{letternumber})->create_unique);
521 }
522
523 sub set_greetings {
524   my ($self) = @_;
525   my $letter = $self->letter;
526
527   return unless $letter;
528   return if $letter->greeting;
529
530   $letter->greeting(t8('Dear Sir or Madam,'));
531 }
532
533 sub export_letter_to_form {
534   my ($self, $letter) = @_;
535   # nope, not pretty.
536
537   $letter ||= $self->letter;
538
539   for ($letter->meta->columns) {
540     if ((ref $_) =~ /Date/i) {
541       $::form->{$_->name} = $letter->$_->to_kivitendo;
542     } else {
543       $::form->{$_->name} = $letter->$_;
544     }
545   }
546 }
547
548 sub init_letter {
549   my ($self) = @_;
550
551   my $letter      = SL::DB::Manager::Letter->find_by_or_create(id => $::form->{letter}{id} || 0)
552                                            ->assign_attributes(%{ $::form->{letter} });
553
554   if ($letter->cp_id) {
555 #     $letter->customer_id($letter->contact->cp_cv_id);
556       # contacts don't have language_id yet
557 #     $letter->greeting(GenericTranslations->get(
558 #       translation_type => 'greetings::' . ($letter->contact->cp_gender eq 'f' ? 'female' : 'male'),
559 #       language_id      => $letter->contact->language_id,
560 #       allow_fallback   => 1
561 #     ));
562   }
563
564   $letter;
565 }
566
567 sub init_models {
568   my ($self) = @_;
569
570   SL::Controller::Helper::GetModels->new(
571     controller   => $self,
572     model        => 'Letter',
573     sorted       => \%sort_columns,
574     with_objects => [ 'contact', 'salesman', 'employee' ],
575   );
576 }
577
578 sub init_all_employees {
579   SL::DB::Manager::Employee->get_all(query => [ deleted => 0 ]);
580 }
581
582 sub init_webdav_objects {
583   my ($self) = @_;
584
585   return [] if !$self->letter || !$self->letter->letternumber || !$::instance_conf->get_webdav;
586
587   my $webdav = SL::Webdav->new(
588     type     => 'letter',
589     number   => $self->letter->letternumber,
590   );
591
592   my $webdav_path = $webdav->webdav_path;
593   my @all_objects = $webdav->get_all_objects;
594
595   return [ map {
596     +{ name => $_->filename,
597        type => t8('File'),
598        link => File::Spec->catdir($webdav_path, $_->filename),
599      }
600   } @all_objects ];
601 }
602
603 sub check_auth_edit {
604   $::auth->assert('sales_letter_edit');
605 }
606
607 sub check_auth_report {
608   $::auth->assert('sales_letter_report');
609 }
610
611 1;
612
613 __END__
614
615 =encoding utf-8
616
617 =head1 NAME
618
619 SL::Controller::Letter - Letters CRUD and printing
620
621 =head1 DESCRIPTION
622
623 Simple letter CRUD controller with drafting capabilities.
624
625 =head1 TODO
626
627   Customer/Vendor switch for dealing with vendor letters
628
629 copy to webdav is crap
630
631 customer/vendor stuff
632
633 =head1 AUTHOR
634
635 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
636
637 =cut