MassInvoiceCreatePrint: js accessor angepasst
[kivitendo-erp.git] / SL / Controller / MassInvoiceCreatePrint.pm
1 package SL::Controller::MassInvoiceCreatePrint;
2
3 use strict;
4
5 use parent qw(SL::Controller::Base);
6
7 use File::Slurp ();
8 use List::MoreUtils qw(all uniq);
9 use List::Util qw(first min);
10
11 use SL::BackgroundJob::MassRecordCreationAndPrinting;
12 use SL::Controller::Helper::GetModels;
13 use SL::DB::DeliveryOrder;
14 use SL::DB::Order;
15 use SL::DB::Printer;
16 use SL::Helper::CreatePDF qw(:all);
17 use SL::Helper::Flash;
18 use SL::Locale::String;
19 use SL::SessionFile;
20 use SL::System::TaskServer;
21
22 use Rose::Object::MakeMethods::Generic
23 (
24   'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id) ],
25 );
26
27 __PACKAGE__->run_before('setup');
28
29 #
30 # actions
31 #
32
33 sub action_list_sales_delivery_orders {
34   my ($self) = @_;
35
36   # default is usually no show, exception here
37   my $show = ($::form->{noshow} ? 0 : 1);
38   delete $::form->{noshow};
39
40   # if a filter is choosen, the filter info should be visible
41   $self->make_filter_summary;
42   $self->sales_delivery_order_models->get;
43   $self->render('mass_invoice_create_print_from_do/list_sales_delivery_orders',
44                 noshow  => $show,
45                 title   => $::locale->text('Open sales delivery orders'));
46 }
47
48 sub action_create_invoices {
49   my ($self) = @_;
50
51   my @sales_delivery_order_ids = @{ $::form->{id} || [] };
52   if (!@sales_delivery_order_ids) {
53     # should never be executed, double catch via js
54     flash_later('error', t8('No delivery orders have been selected.'));
55     return $self->redirect_to(action => 'list_sales_delivery_orders');
56   }
57
58   my $db = SL::DB::Invoice->new->db;
59
60   if (!$db->do_transaction(sub {
61     my @invoices;
62     foreach my $id (@sales_delivery_order_ids) {
63       my $delivery_order    = SL::DB::DeliveryOrder->new(id => $id)->load;
64
65       my $invoice = $delivery_order->convert_to_invoice() || die $db->error;
66       push @invoices, $invoice;
67     }
68
69     my $key = sprintf('%d-%d', Time::HiRes::gettimeofday());
70     $::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]);
71
72     flash_later('info', t8('The invoices have been created. They\'re pre-selected below.'));
73     $self->redirect_to(action => 'list_invoices', ids => $key);
74
75     1;
76   })) {
77     $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error);
78     $::form->error($db->error);
79   }
80 }
81
82 sub action_list_invoices {
83   my ($self) = @_;
84
85   my $show = $::form->{noshow} ? 0 : 1;
86   delete $::form->{noshow};
87
88   if ($::form->{ids}) {
89     my $key = 'MassInvoiceCreatePrint::ids-' . $::form->{ids};
90     $self->invoice_ids($::auth->get_session_value($key) || []);
91     $self->invoice_models->add_additional_url_params(ids => $::form->{ids});
92   }
93
94   my %selected_ids = map { +($_ => 1) } @{ $self->invoice_ids };
95
96   $::form->{printer_id} ||= $self->default_printer_id;
97
98   $self->render('mass_invoice_create_print_from_do/list_invoices',
99                 title        => $::locale->text('Open invoice'),
100                 noshow       => $show,
101                 selected_ids => \%selected_ids);
102 }
103
104 sub action_print {
105   my ($self) = @_;
106
107   my @invoices = map { SL::DB::Invoice->new(id => $_)->load } @{ $::form->{id} || [] };
108   if (!@invoices) {
109     flash_later('error', t8('No invoices have been selected.'));
110     return $self->redirect_to(action => 'list_invoices');
111   }
112
113   $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices);
114 }
115
116 sub action_create_print_all_start {
117   my ($self) = @_;
118
119   $self->sales_delivery_order_models->disable_plugin('paginated');
120
121   my @records           = @{ $self->sales_delivery_order_models->get };
122   my $num              = min(scalar(@records), $::form->{number_of_invoices} // scalar(@records));
123
124   my $job              = SL::DB::BackgroundJob->new(
125     type               => 'once',
126     active             => 1,
127     package_name       => 'MassRecordCreationAndPrinting',
128
129   )->set_data(
130     record_ids         => [ map { $_->id } @records[0..$num - 1] ],
131     printer_id         => $::form->{printer_id},
132     status             => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
133     num_created        => 0,
134     num_printed        => 0,
135     invoice_ids        => [ ],
136     conversion_errors  => [ ],
137     print_errors       => [ ],
138
139   )->update_next_run_at;
140
141   SL::System::TaskServer->new->wake_up;
142
143   my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
144
145   $self->js
146     ->html('#create_print_all_dialog', $html)
147     ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
148     ->render;
149 }
150
151 sub action_create_print_all_status {
152   my ($self) = @_;
153   my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
154   my $html   = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
155
156   $self->js->html('#create_print_all_dialog', $html);
157   $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
158   $self->js->render;
159 }
160
161 sub action_create_print_all_download {
162   my ($self) = @_;
163   my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
164
165   my $sfile  = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
166   die $! if !$sfile->fh;
167
168   my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
169   $sfile->fh->close;
170
171   my $type      = 'Invoices';
172   my $file_name =  t8($type) . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
173   $file_name    =~ s{[^\w\.]+}{_}g;
174
175   return $self->send_file(
176     \$merged_pdf,
177     type => 'application/pdf',
178     name => $file_name,
179   );
180 }
181
182 #
183 # filters
184 #
185
186 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
187 sub init_invoice_ids { [] }
188
189 sub init_sales_delivery_order_models {
190   my ($self) = @_;
191   return $self->_init_sales_delivery_order_models(sortby => 'donumber');
192 }
193
194 sub _init_sales_delivery_order_models {
195   my ($self, %params) = @_;
196
197   SL::Controller::Helper::GetModels->new(
198     controller   => $_[0],
199     model        => 'DeliveryOrder',
200     # model        => 'Order',
201     sorted       => {
202       _default     => {
203         by           => $params{sortby},
204         dir          => 1,
205       },
206       customer     => t8('Customer'),
207       employee     => t8('Employee'),
208       transdate    => t8('Date'),
209       donumber     => t8('Delivery Order Number'),
210       ordnumber     => t8('Order Number'),
211     },
212     with_objects => [ qw(customer employee) ],
213    query        => [
214       '!customer_id' => undef,
215       or             => [ closed    => undef, closed    => 0 ],
216       or             => [ delivered => undef, delivered => 0 ],
217     ],
218   );
219 }
220
221
222 sub init_invoice_models {
223   my ($self)             = @_;
224   my @invoice_ids = @{ $self->invoice_ids };
225
226   SL::Controller::Helper::GetModels->new(
227     controller   => $_[0],
228     model        => 'Invoice',
229     (paginated   => 0,) x !!@invoice_ids,
230     sorted       => {
231       _default     => {
232         by           => 'transdate',
233         dir          => 0,
234       },
235       customer     => t8('Customer'),
236       invnumber    => t8('Invoice Number'),
237       employee     => t8('Employee'),
238       donumber     => t8('Delivery Order Number'),
239       ordnumber    => t8('Order Number'),
240       reqdate      => t8('Delivery Date'),
241       transdate    => t8('Date'),
242     },
243     with_objects => [ qw(customer employee) ],
244     query        => [
245       '!customer_id' => undef,
246       (id            => \@invoice_ids) x !!@invoice_ids,
247     ],
248   );
249 }
250
251
252 sub init_default_printer_id {
253   my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
254   return $pr ? $pr->id : undef;
255 }
256
257 sub setup {
258   my ($self) = @_;
259   $::auth->assert('invoice_edit');
260
261   $::request->layout->use_javascript("${_}.js")  for qw(kivi.MassInvoiceCreatePrint);
262 }
263
264 #
265 # helpers
266 #
267
268 sub create_pdfs {
269   my ($self, %params) = @_;
270
271   my @pdf_file_names;
272   foreach my $invoice (@{ $params{invoices} }) {
273     my %create_params = (
274       template  => $self->find_template(name => 'invoice', printer_id => $params{printer_id}),
275       variables => Form->new(''),
276       return    => 'file_name',
277     );
278
279     $create_params{variables}->{$_} = $params{variables}->{$_} for keys %{ $params{variables} };
280
281     $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
282     $create_params{variables}->prepare_for_printing;
283
284     push @pdf_file_names, $self->create_pdf(%create_params);
285   }
286
287   return @pdf_file_names;
288 }
289
290 sub download_or_print_documents {
291   my ($self, %params) = @_;
292
293   my @pdf_file_names;
294
295   eval {
296     my %pdf_params = (
297       invoices        => $params{invoices},
298       printer_id      => $params{printer_id},
299       variables       => {
300         type        => 'invoice',
301         formname    => 'invoice',
302         format      => 'pdf',
303         media       => $params{printer_id} ? 'printer' : 'file',
304       });
305
306     @pdf_file_names = $self->create_pdfs(%pdf_params);
307     my $merged_pdf  = $self->merge_pdfs(file_names => \@pdf_file_names);
308     unlink @pdf_file_names;
309
310     if (!$params{printer_id}) {
311       my $file_name =  t8("Invoices") . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
312       $file_name    =~ s{[^\w\.]+}{_}g;
313
314       return $self->send_file(
315         \$merged_pdf,
316         type => 'application/pdf',
317         name => $file_name,
318       );
319     }
320
321     my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
322     my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
323
324     open my $out, '|-', $command or die $!;
325     binmode $out;
326     print $out $merged_pdf;
327     close $out;
328
329     flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
330     return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
331
332   } or do {
333     unlink @pdf_file_names;
334     $::form->error(t8("Creating the PDF failed:") . " " . $@);
335   };
336 }
337
338 sub make_filter_summary {
339   my ($self) = @_;
340
341   my $filter = $::form->{filter} || {};
342   my @filter_strings;
343
344   my @filters = (
345     [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
346     [ $filter->{"transdate:date::ge"},           t8('Delivery Date') . " " . t8('From Date') ],
347     [ $filter->{"transdate:date::le"},           t8('Delivery Date') . " " . t8('To Date')   ],
348   );
349
350   for (@filters) {
351     push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
352   }
353
354   $self->{filter_summary} = join ', ', @filter_strings;
355 }
356 1;
357
358 __END__
359
360 =pod
361
362 =encoding utf8
363
364 =head1 NAME
365
366 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
367
368 =head2 OVERVIEW
369
370 Controller class for the conversion and processing (printing) of objects.
371
372
373 Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
374 In general there are two major distinctions:
375 This class implements the conversion and the printing via clickable action AND triggers the same
376 conversion towards a Background-Job with a good user interaction.
377
378 Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
379 in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
380 ($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
381 and in the background job, i.e. compare the actions create and print.
382 From a reverse engineering point of view the actions in this controller were written before the
383 background job existed, therefore if anything goes boom take a look at the single steps done via gui
384 in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
385
386 =head1 FUNCTIONS
387
388 =over 2
389
390 =item C<action_list_sales_delivery_orders>
391
392 List all open sales delivery orders. The filter can be in two states show or "no show" the
393 original, probably gorash idea, is to increase performance and not to be forced to click on the
394 next button (like in all other reports). Therefore use this option and this filter for a good
395 project default and hide it again. Filters can be added in _filter.html. Take a look at
396   SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
397
398 =item C<action_create_invoices>
399
400 Creates or to be more correctly converts delivery orders to invoices. All items are
401 converted 1:1 and after conversion the delivery order(s) are closed.
402
403 =item C<action_list_invoices>
404
405 List the created invoices, if created via gui (see action above)
406
407 =item C<action_print>
408
409 Print the selected invoices. Yes, it really is all boring linear (see action above).
410 Calls download_or_print method.
411
412 =item C<action_create_print_all_start>
413
414 Initialises the webform for the creation and printing via background job. Now we get to
415 the more fun part ...  Mosu did a great user interaction job here, we can choose how many
416 objects are converted in one strike and if or if not they are downloadable or will be sent to
417 a printer (if defined as a printing command) right away.
418 Background job is started and status is watched via js and the next action.
419
420 =item C<action_create_print_all_status>
421
422 Action for watching status, default is refreshing every 5 seconds
423
424 =item C<action_create_print_all_download>
425
426 If the above is done (did I already said: boring linear?). Documents will
427 be either printed or downloaded.
428
429 =item C<init_printers>
430
431 Gets all printer commands
432
433 =item C<init_invoice_ids>
434
435 Gets a list of (empty) invoice ids
436
437 =item C<init_sales_delivery_order_models>
438
439 Calls _init_sales_delivery_order_models with a param
440
441 =item C<_init_sales_delivery_order_models>
442
443 Private function, called by init_sales_delivery_order_models.
444 Gets all open sales delivery orders.
445
446 =item C<init_invoice_models>
447
448 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
449
450 =item C<init_default_printer_id>
451
452 Gets the default printer for sales_invoices. Maybe this function is not used, but
453 might be useful in the next version (working in client project).
454
455 =item C<setup>
456
457 Currently sets / checks only the access right.
458
459 =item C<create_pdfs>
460
461 =item C<download_or_print_documents>
462
463 Backend function for printing or downloading docs. Only used for gui processing (called
464 via action_print).
465
466 =item C<make_filter_summary>
467 Creates the filter option summary in the header. By the time of writing three filters are
468 supported: Customer and date from/to of the Delivery Order (database field transdate).
469
470 =back
471
472 =head1 TODO
473
474 Should be more generalized. Right now just one conversion (delivery order to invoice) is supported.
475 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
476 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
477 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
478
479 Filtering needs to be extended for Delivery Order Number (Natural Sort).
480
481 A second printer (copy) needs to be implemented.
482
483 Both todos are marked in the template code.
484
485
486 =head1 AUTHOR
487
488 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
489
490 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>
491
492 =cut