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::MassPrintCreatePDF qw(:all);
17 use SL::Helper::CreatePDF qw(:all);
18 use SL::Helper::File qw(store_pdf append_general_pdf_attachments doc_storage_enabled);
19 use SL::Helper::Flash;
20 use SL::Locale::String;
22 use SL::System::TaskServer;
23 use Rose::Object::MakeMethods::Generic
25 'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id today all_businesses) ],
28 __PACKAGE__->run_before('setup');
34 sub action_list_sales_delivery_orders {
37 # default is usually no show, exception here
38 my $show = ($::form->{noshow} ? 0 : 1);
39 delete $::form->{noshow};
41 # if a filter is choosen, the filter info should be visible
42 $self->make_filter_summary;
43 $self->setup_list_sales_delivery_orders_action_bar(show_creation_buttons => $show, num_rows => scalar(@{ $self->sales_delivery_order_models->get }));
44 $self->render('mass_invoice_create_print_from_do/list_sales_delivery_orders',
46 title => $::locale->text('Open sales delivery orders'));
49 sub action_create_invoices {
52 my @sales_delivery_order_ids = @{ $::form->{id} || [] };
53 if (!@sales_delivery_order_ids) {
54 # should never be executed, double catch via js
55 flash_later('error', t8('No delivery orders have been selected.'));
56 return $self->redirect_to(action => 'list_sales_delivery_orders');
59 my $db = SL::DB::Invoice->new->db;
62 if (!$db->with_transaction(sub {
63 foreach my $id (@sales_delivery_order_ids) {
64 my $delivery_order = SL::DB::DeliveryOrder->new(id => $id)->load;
66 my $invoice = $delivery_order->convert_to_invoice() || die $db->error;
67 push @invoices, $invoice;
72 $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error);
73 $::form->error($db->error);
76 my $key = sprintf('%d-%d', Time::HiRes::gettimeofday());
77 $::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]);
79 flash_later('info', t8('The invoices have been created. They\'re pre-selected below.'));
80 $self->redirect_to(action => 'list_invoices', ids => $key);
83 sub action_list_invoices {
86 my $show = $::form->{noshow} ? 0 : 1;
87 delete $::form->{noshow};
90 my $key = 'MassInvoiceCreatePrint::ids-' . $::form->{ids};
91 $self->invoice_ids($::auth->get_session_value($key) || []);
92 $self->invoice_models->add_additional_url_params(ids => $::form->{ids});
95 my %selected_ids = map { +($_ => 1) } @{ $self->invoice_ids };
97 $::form->{printer_id} ||= $self->default_printer_id;
99 $self->setup_list_invoices_action_bar(num_rows => scalar(@{ $self->invoice_models->get }));
101 $self->render('mass_invoice_create_print_from_do/list_invoices',
102 title => $::locale->text('Open invoice'),
104 selected_ids => \%selected_ids);
110 my @invoices = map { SL::DB::Invoice->new(id => $_)->load } @{ $::form->{id} || [] };
112 flash_later('error', t8('No invoices have been selected.'));
113 return $self->redirect_to(action => 'list_invoices');
116 $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices, bothsided => $::form->{bothsided});
119 sub action_create_print_all_start {
122 $self->sales_delivery_order_models->disable_plugin('paginated');
124 my @records = @{ $self->sales_delivery_order_models->get };
125 my $num = min(scalar(@records), $::form->{number_of_invoices} // scalar(@records));
127 my $job = SL::DB::BackgroundJob->new(
130 package_name => 'MassRecordCreationAndPrinting',
133 record_ids => [ map { $_->id } @records[0..$num - 1] ],
134 printer_id => $::form->{printer_id},
135 copy_printer_id => $::form->{copy_printer_id},
136 bothsided => ($::form->{bothsided}?1:0),
137 transdate => $::form->{transdate},
138 status => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
142 conversion_errors => [ ],
144 session_id => $::auth->get_session_id,
146 )->update_next_run_at;
148 SL::System::TaskServer->new->wake_up;
150 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
153 ->html('#create_print_all_dialog', $html)
154 ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
158 sub action_create_print_all_status {
160 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
161 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
163 $self->js->html('#create_print_all_dialog', $html);
164 $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
168 sub action_create_print_all_download {
170 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
172 my $sfile = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
173 die $! if !$sfile->fh;
175 my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
178 my $type = 'Invoices';
179 my $file_name = t8($type) . '-' . DateTime->now_local->strftime('%Y%m%d%H%M%S') . '.pdf';
180 $file_name =~ s{[^\w\.]+}{_}g;
182 return $self->send_file(
184 type => 'application/pdf',
193 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
194 #sub init_att { require SL::Controller::Attachments; SL::Controller::Attachments->new() }
195 sub init_invoice_ids { [] }
196 sub init_today { DateTime->today_local }
198 sub init_sales_delivery_order_models {
200 return $self->_init_sales_delivery_order_models(sortby => 'donumber');
203 sub _init_sales_delivery_order_models {
204 my ($self, %params) = @_;
206 SL::Controller::Helper::GetModels->new(
208 model => 'DeliveryOrder',
212 by => $params{sortby},
215 customer => t8('Customer'),
216 employee => t8('Employee'),
217 transdate => t8('Delivery Order Date'),
218 donumber => t8('Delivery Order Number'),
219 ordnumber => t8('Order Number'),
221 with_objects => [ qw(customer employee) ],
223 '!customer_id' => undef,
224 or => [ closed => undef, closed => 0 ],
230 sub init_invoice_models {
232 my @invoice_ids = @{ $self->invoice_ids };
234 SL::Controller::Helper::GetModels->new(
237 (paginated => 0,) x !!@invoice_ids,
243 customer => t8('Customer'),
244 invnumber => t8('Invoice Number'),
245 employee => t8('Employee'),
246 donumber => t8('Delivery Order Number'),
247 ordnumber => t8('Order Number'),
248 reqdate => t8('Delivery Date'),
249 transdate => t8('Date'),
251 with_objects => [ qw(customer employee) ],
253 '!customer_id' => undef,
254 (id => \@invoice_ids) x !!@invoice_ids,
260 sub init_default_printer_id {
261 my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
262 return $pr ? $pr->id : undef;
265 sub init_all_businesses {
266 return SL::DB::Manager::Business->get_all_sorted;
271 $::auth->assert('invoice_edit');
273 $::request->layout->use_javascript("${_}.js") for qw(kivi.MassInvoiceCreatePrint);
281 sub download_or_print_documents {
282 my ($self, %params) = @_;
288 documents => $params{invoices},
291 formname => 'invoice',
293 media => $params{printer_id} ? 'printer' : 'file',
294 printer_id => $params{printer_id},
297 @pdf_file_names = $self->create_pdfs(%pdf_params);
298 my $merged_pdf = $self->merge_pdfs(file_names => \@pdf_file_names, bothsided => $params{bothsided});
299 unlink @pdf_file_names;
301 if (!$params{printer_id}) {
302 my $file_name = t8("Invoices") . '-' . DateTime->now_local->strftime('%Y%m%d%H%M%S') . '.pdf';
303 $file_name =~ s{[^\w\.]+}{_}g;
305 return $self->send_file(
307 type => 'application/pdf',
312 my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
313 $printer->print_document(content => $merged_pdf);
315 flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
316 return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
319 unlink @pdf_file_names;
320 $::form->error(t8("Creating the PDF failed:") . " " . $@);
324 sub make_filter_summary {
327 my $filter = $::form->{filter} || {};
331 [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
332 [ $filter->{"transdate:date::ge"}, t8('Delivery Order Date') . " " . t8('From Date') ],
333 [ $filter->{"transdate:date::le"}, t8('Delivery Order Date') . " " . t8('To Date') ],
337 push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
340 $self->{filter_summary} = join ', ', @filter_strings;
343 sub setup_list_invoices_action_bar {
344 my ($self, %params) = @_;
346 for my $bar ($::request->layout->get('actionbar')) {
350 submit => [ '#search_form', { action => 'MassInvoiceCreatePrint/list_invoices' } ],
351 accesskey => 'enter',
354 $::locale->text('Print'),
355 call => [ 'kivi.MassInvoiceCreatePrint.showMassPrintOptionsOrDownloadDirectly' ],
356 disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
362 sub setup_list_sales_delivery_orders_action_bar {
363 my ($self, %params) = @_;
365 for my $bar ($::request->layout->get('actionbar')) {
368 $params{show_creation_buttons} ? t8('Update') : t8('Search'),
369 submit => [ '#search_form', { action => 'MassInvoiceCreatePrint/list_sales_delivery_orders' } ],
370 accesskey => 'enter',
376 tooltip => t8("Create and print invoices")
379 t8("Create and print invoices for all selected delivery orders"),
380 submit => [ 'form', { action => 'MassInvoiceCreatePrint/create_invoices' } ],
381 disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
382 only_if => $params{show_creation_buttons},
383 checks => [ 'kivi.MassInvoiceCreatePrint.checkDeliveryOrderSelection' ],
388 t8("Create and print invoices for all delivery orders matching the filter"),
389 call => [ 'kivi.MassInvoiceCreatePrint.createPrintAllInitialize' ],
390 disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
391 only_if => $params{show_creation_buttons},
408 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
412 Controller class for the conversion and processing (printing) of objects.
415 Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
416 In general there are two major distinctions:
417 This class implements the conversion and the printing via clickable action AND triggers the same
418 conversion towards a Background-Job with a good user interaction.
420 Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
421 in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
422 ($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
423 and in the background job, i.e. compare the actions create and print.
424 From a reverse engineering point of view the actions in this controller were written before the
425 background job existed, therefore if anything goes boom take a look at the single steps done via gui
426 in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
432 =item C<action_list_sales_delivery_orders>
434 List all open sales delivery orders. The filter can be in two states show or "no show" the
435 original, probably gorash idea, is to increase performance and not to be forced to click on the
436 next button (like in all other reports). Therefore use this option and this filter for a good
437 project default and hide it again. Filters can be added in _filter.html. Take a look at
438 SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
440 =item C<action_create_invoices>
442 Creates or to be more correctly converts delivery orders to invoices. All items are
443 converted 1:1 and after conversion the delivery order(s) are closed.
445 =item C<action_list_invoices>
447 List the created invoices, if created via gui (see action above)
449 =item C<action_print>
451 Print the selected invoices. Yes, it really is all boring linear (see action above).
452 Calls download_or_print method.
454 =item C<action_create_print_all_start>
456 Initialises the webform for the creation and printing via background job. Now we get to
457 the more fun part ... Mosu did a great user interaction job here, we can choose how many
458 objects are converted in one strike and if or if not they are downloadable or will be sent to
459 a printer (if defined as a printing command) right away.
460 Background job is started and status is watched via js and the next action.
462 =item C<action_create_print_all_status>
464 Action for watching status, default is refreshing every 5 seconds
466 =item C<action_create_print_all_download>
468 If the above is done (did I already said: boring linear?). Documents will
469 be either printed or downloaded.
471 =item C<init_printers>
473 Gets all printer commands
475 =item C<init_invoice_ids>
477 Gets a list of (empty) invoice ids
481 Gets the current day. Currently used in custom code.
482 Has to be initialised (get_set_init) and can be used as default for
483 a date tag like C<[% L.date_tag("transdate", SELF.today, id=transdate) %]>.
485 =item C<init_sales_delivery_order_models>
487 Calls _init_sales_delivery_order_models with a param
489 =item C<_init_sales_delivery_order_models>
491 Private function, called by init_sales_delivery_order_models.
492 Gets all open sales delivery orders.
494 =item C<init_invoice_models>
496 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
498 =item C<init_default_printer_id>
500 Gets the default printer for sales_invoices. Currently this function is not called, but
501 might be useful in the next version.Calling template code and Controller already expect a default:
502 C<L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]>
506 Currently sets / checks only the access right.
510 =item C<download_or_print_documents>
512 Backend function for printing or downloading docs. Only used for gui processing (called
515 =item C<make_filter_summary>
516 Creates the filter option summary in the header. By the time of writing three filters are
517 supported: Customer and date from/to of the Delivery Order (database field transdate).
523 pShould be more generalized. Right now just one conversion (delivery order to invoice) is supported.
524 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
525 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
526 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
531 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
533 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>