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