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) ],
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 status => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
136 conversion_errors => [ ],
139 )->update_next_run_at;
141 SL::System::TaskServer->new->wake_up;
143 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
146 ->html('#create_print_all_dialog', $html)
147 ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
151 sub action_create_print_all_status {
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);
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();
161 sub action_create_print_all_download {
163 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
165 my $sfile = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
166 die $! if !$sfile->fh;
168 my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
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;
175 return $self->send_file(
177 type => 'application/pdf',
186 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
187 sub init_invoice_ids { [] }
189 sub init_sales_delivery_order_models {
191 return $self->_init_sales_delivery_order_models(sortby => 'donumber');
194 sub _init_sales_delivery_order_models {
195 my ($self, %params) = @_;
197 SL::Controller::Helper::GetModels->new(
199 model => 'DeliveryOrder',
203 by => $params{sortby},
206 customer => t8('Customer'),
207 employee => t8('Employee'),
208 transdate => t8('Date'),
209 donumber => t8('Delivery Order Number'),
210 ordnumber => t8('Order Number'),
212 with_objects => [ qw(customer employee) ],
214 '!customer_id' => undef,
215 or => [ closed => undef, closed => 0 ],
216 or => [ delivered => undef, delivered => 0 ],
222 sub init_invoice_models {
224 my @invoice_ids = @{ $self->invoice_ids };
226 SL::Controller::Helper::GetModels->new(
229 (paginated => 0,) x !!@invoice_ids,
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'),
243 with_objects => [ qw(customer employee) ],
245 '!customer_id' => undef,
246 (id => \@invoice_ids) x !!@invoice_ids,
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;
259 $::auth->assert('invoice_edit');
261 $::request->layout->use_javascript("${_}.js") for qw(kivi.MassInvoiceCreatePrint);
269 my ($self, %params) = @_;
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',
279 $create_params{variables}->{$_} = $params{variables}->{$_} for keys %{ $params{variables} };
281 $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
282 $create_params{variables}->prepare_for_printing;
284 push @pdf_file_names, $self->create_pdf(%create_params);
287 return @pdf_file_names;
290 sub download_or_print_documents {
291 my ($self, %params) = @_;
297 invoices => $params{invoices},
298 printer_id => $params{printer_id},
301 formname => 'invoice',
303 media => $params{printer_id} ? 'printer' : 'file',
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;
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;
314 return $self->send_file(
316 type => 'application/pdf',
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);
324 open my $out, '|-', $command or die $!;
326 print $out $merged_pdf;
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});
333 unlink @pdf_file_names;
334 $::form->error(t8("Creating the PDF failed:") . " " . $@);
338 sub make_filter_summary {
341 my $filter = $::form->{filter} || {};
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') ],
351 push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
354 $self->{filter_summary} = join ', ', @filter_strings;
366 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
370 Controller class for the conversion and processing (printing) of objects.
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.
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.
390 =item C<action_list_sales_delivery_orders>
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.
398 =item C<action_create_invoices>
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.
403 =item C<action_list_invoices>
405 List the created invoices, if created via gui (see action above)
407 =item C<action_print>
409 Print the selected invoices. Yes, it really is all boring linear (see action above).
410 Calls download_or_print method.
412 =item C<action_create_print_all_start>
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.
420 =item C<action_create_print_all_status>
422 Action for watching status, default is refreshing every 5 seconds
424 =item C<action_create_print_all_download>
426 If the above is done (did I already said: boring linear?). Documents will
427 be either printed or downloaded.
429 =item C<init_printers>
431 Gets all printer commands
433 =item C<init_invoice_ids>
435 Gets a list of (empty) invoice ids
437 =item C<init_sales_delivery_order_models>
439 Calls _init_sales_delivery_order_models with a param
441 =item C<_init_sales_delivery_order_models>
443 Private function, called by init_sales_delivery_order_models.
444 Gets all open sales delivery orders.
446 =item C<init_invoice_models>
448 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
450 =item C<init_default_printer_id>
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).
457 Currently sets / checks only the access right.
461 =item C<download_or_print_documents>
463 Backend function for printing or downloading docs. Only used for gui processing (called
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).
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)).
479 Filtering needs to be extended for Delivery Order Number (Natural Sort).
481 A second printer (copy) needs to be implemented.
483 Both todos are marked in the template code.
488 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
490 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>