1 package SL::Controller::MassInvoiceCreatePrint;
5 use parent qw(SL::Controller::Base);
8 use List::MoreUtils qw(all uniq);
9 use List::Util qw(first min);
11 use SL::BackgroundJob::MassRecordCreationAndPrinting;
12 use SL::Controller::Helper::GetModels;
13 use SL::DB::DeliveryOrder;
16 use SL::Helper::CreatePDF qw(:all);
17 use SL::Helper::Flash;
18 use SL::Locale::String;
20 use SL::System::TaskServer;
22 use Rose::Object::MakeMethods::Generic
24 'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id today) ],
27 __PACKAGE__->run_before('setup');
33 sub action_list_sales_delivery_orders {
36 # default is usually no show, exception here
37 my $show = ($::form->{noshow} ? 0 : 1);
38 delete $::form->{noshow};
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',
45 title => $::locale->text('Open sales delivery orders'));
48 sub action_create_invoices {
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');
58 my $db = SL::DB::Invoice->new->db;
60 if (!$db->do_transaction(sub {
62 foreach my $id (@sales_delivery_order_ids) {
63 my $delivery_order = SL::DB::DeliveryOrder->new(id => $id)->load;
65 my $invoice = $delivery_order->convert_to_invoice() || die $db->error;
66 push @invoices, $invoice;
69 my $key = sprintf('%d-%d', Time::HiRes::gettimeofday());
70 $::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]);
72 flash_later('info', t8('The invoices have been created. They\'re pre-selected below.'));
73 $self->redirect_to(action => 'list_invoices', ids => $key);
77 $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error);
78 $::form->error($db->error);
82 sub action_list_invoices {
85 my $show = $::form->{noshow} ? 0 : 1;
86 delete $::form->{noshow};
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});
94 my %selected_ids = map { +($_ => 1) } @{ $self->invoice_ids };
96 $::form->{printer_id} ||= $self->default_printer_id;
98 $self->render('mass_invoice_create_print_from_do/list_invoices',
99 title => $::locale->text('Open invoice'),
101 selected_ids => \%selected_ids);
107 my @invoices = map { SL::DB::Invoice->new(id => $_)->load } @{ $::form->{id} || [] };
109 flash_later('error', t8('No invoices have been selected.'));
110 return $self->redirect_to(action => 'list_invoices');
113 $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices);
116 sub action_create_print_all_start {
119 $self->sales_delivery_order_models->disable_plugin('paginated');
121 my @records = @{ $self->sales_delivery_order_models->get };
122 my $num = min(scalar(@records), $::form->{number_of_invoices} // scalar(@records));
124 my $job = SL::DB::BackgroundJob->new(
127 package_name => 'MassRecordCreationAndPrinting',
130 record_ids => [ map { $_->id } @records[0..$num - 1] ],
131 printer_id => $::form->{printer_id},
132 copy_printer_id => $::form->{copy_printer_id},
133 transdate => $::form->{transdate},
134 status => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
138 conversion_errors => [ ],
141 )->update_next_run_at;
143 SL::System::TaskServer->new->wake_up;
145 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
148 ->html('#create_print_all_dialog', $html)
149 ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
153 sub action_create_print_all_status {
155 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
156 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
158 $self->js->html('#create_print_all_dialog', $html);
159 $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
163 sub action_create_print_all_download {
165 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
167 my $sfile = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
168 die $! if !$sfile->fh;
170 my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
173 my $type = 'Invoices';
174 my $file_name = t8($type) . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
175 $file_name =~ s{[^\w\.]+}{_}g;
177 return $self->send_file(
179 type => 'application/pdf',
188 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
189 sub init_invoice_ids { [] }
190 sub init_today { DateTime->today_local }
192 sub init_sales_delivery_order_models {
194 return $self->_init_sales_delivery_order_models(sortby => 'donumber');
197 sub _init_sales_delivery_order_models {
198 my ($self, %params) = @_;
200 SL::Controller::Helper::GetModels->new(
202 model => 'DeliveryOrder',
206 by => $params{sortby},
209 customer => t8('Customer'),
210 employee => t8('Employee'),
211 transdate => t8('Date'),
212 donumber => t8('Delivery Order Number'),
213 ordnumber => t8('Order Number'),
215 with_objects => [ qw(customer employee) ],
217 '!customer_id' => undef,
218 or => [ closed => undef, closed => 0 ],
224 sub init_invoice_models {
226 my @invoice_ids = @{ $self->invoice_ids };
228 SL::Controller::Helper::GetModels->new(
231 (paginated => 0,) x !!@invoice_ids,
237 customer => t8('Customer'),
238 invnumber => t8('Invoice Number'),
239 employee => t8('Employee'),
240 donumber => t8('Delivery Order Number'),
241 ordnumber => t8('Order Number'),
242 reqdate => t8('Delivery Date'),
243 transdate => t8('Date'),
245 with_objects => [ qw(customer employee) ],
247 '!customer_id' => undef,
248 (id => \@invoice_ids) x !!@invoice_ids,
254 sub init_default_printer_id {
255 my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
256 return $pr ? $pr->id : undef;
261 $::auth->assert('invoice_edit');
263 $::request->layout->use_javascript("${_}.js") for qw(kivi.MassInvoiceCreatePrint);
271 my ($self, %params) = @_;
274 foreach my $invoice (@{ $params{invoices} }) {
275 my %create_params = (
276 template => $self->find_template(name => 'invoice', printer_id => $params{printer_id}),
277 variables => Form->new(''),
278 return => 'file_name',
279 variable_content_types => { longdescription => 'html',
284 $create_params{variables}->{$_} = $params{variables}->{$_} for keys %{ $params{variables} };
286 $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
287 $create_params{variables}->prepare_for_printing;
289 push @pdf_file_names, $self->create_pdf(%create_params);
292 return @pdf_file_names;
295 sub download_or_print_documents {
296 my ($self, %params) = @_;
302 invoices => $params{invoices},
303 printer_id => $params{printer_id},
306 formname => 'invoice',
308 media => $params{printer_id} ? 'printer' : 'file',
311 @pdf_file_names = $self->create_pdfs(%pdf_params);
312 my $merged_pdf = $self->merge_pdfs(file_names => \@pdf_file_names);
313 unlink @pdf_file_names;
315 if (!$params{printer_id}) {
316 my $file_name = t8("Invoices") . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
317 $file_name =~ s{[^\w\.]+}{_}g;
319 return $self->send_file(
321 type => 'application/pdf',
326 my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
327 my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
329 open my $out, '|-', $command or die $!;
331 print $out $merged_pdf;
334 flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
335 return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
338 unlink @pdf_file_names;
339 $::form->error(t8("Creating the PDF failed:") . " " . $@);
343 sub make_filter_summary {
346 my $filter = $::form->{filter} || {};
350 [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
351 [ $filter->{"transdate:date::ge"}, t8('Transdate') . " " . t8('From Date') ],
352 [ $filter->{"transdate:date::le"}, t8('Transdate') . " " . t8('To Date') ],
356 push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
359 $self->{filter_summary} = join ', ', @filter_strings;
371 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
375 Controller class for the conversion and processing (printing) of objects.
378 Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
379 In general there are two major distinctions:
380 This class implements the conversion and the printing via clickable action AND triggers the same
381 conversion towards a Background-Job with a good user interaction.
383 Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
384 in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
385 ($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
386 and in the background job, i.e. compare the actions create and print.
387 From a reverse engineering point of view the actions in this controller were written before the
388 background job existed, therefore if anything goes boom take a look at the single steps done via gui
389 in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
395 =item C<action_list_sales_delivery_orders>
397 List all open sales delivery orders. The filter can be in two states show or "no show" the
398 original, probably gorash idea, is to increase performance and not to be forced to click on the
399 next button (like in all other reports). Therefore use this option and this filter for a good
400 project default and hide it again. Filters can be added in _filter.html. Take a look at
401 SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
403 =item C<action_create_invoices>
405 Creates or to be more correctly converts delivery orders to invoices. All items are
406 converted 1:1 and after conversion the delivery order(s) are closed.
408 =item C<action_list_invoices>
410 List the created invoices, if created via gui (see action above)
412 =item C<action_print>
414 Print the selected invoices. Yes, it really is all boring linear (see action above).
415 Calls download_or_print method.
417 =item C<action_create_print_all_start>
419 Initialises the webform for the creation and printing via background job. Now we get to
420 the more fun part ... Mosu did a great user interaction job here, we can choose how many
421 objects are converted in one strike and if or if not they are downloadable or will be sent to
422 a printer (if defined as a printing command) right away.
423 Background job is started and status is watched via js and the next action.
425 =item C<action_create_print_all_status>
427 Action for watching status, default is refreshing every 5 seconds
429 =item C<action_create_print_all_download>
431 If the above is done (did I already said: boring linear?). Documents will
432 be either printed or downloaded.
434 =item C<init_printers>
436 Gets all printer commands
438 =item C<init_invoice_ids>
440 Gets a list of (empty) invoice ids
444 Gets the current day. Currently used in custom code.
445 Has to be initialised (get_set_init) and can be used as default for
446 a date tag like C<[% L.date_tag("transdate", SELF.today, id=transdate) %]>.
448 =item C<init_sales_delivery_order_models>
450 Calls _init_sales_delivery_order_models with a param
452 =item C<_init_sales_delivery_order_models>
454 Private function, called by init_sales_delivery_order_models.
455 Gets all open sales delivery orders.
457 =item C<init_invoice_models>
459 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
461 =item C<init_default_printer_id>
463 Gets the default printer for sales_invoices. Currently this function is not called, but
464 might be useful in the next version.Calling template code and Controller already expect a default:
465 C<L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]>
469 Currently sets / checks only the access right.
473 =item C<download_or_print_documents>
475 Backend function for printing or downloading docs. Only used for gui processing (called
478 =item C<make_filter_summary>
479 Creates the filter option summary in the header. By the time of writing three filters are
480 supported: Customer and date from/to of the Delivery Order (database field transdate).
486 Should be more generalized. Right now just one conversion (delivery order to invoice) is supported.
487 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
488 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
489 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
494 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
496 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>