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;
61 if (!$db->with_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;
71 $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error);
72 $::form->error($db->error);
75 my $key = sprintf('%d-%d', Time::HiRes::gettimeofday());
76 $::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]);
78 flash_later('info', t8('The invoices have been created. They\'re pre-selected below.'));
79 $self->redirect_to(action => 'list_invoices', ids => $key);
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 => [ ],
140 session_id => $::auth->get_session_id,
142 )->update_next_run_at;
144 SL::System::TaskServer->new->wake_up;
146 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
149 ->html('#create_print_all_dialog', $html)
150 ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
154 sub action_create_print_all_status {
156 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
157 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
159 $self->js->html('#create_print_all_dialog', $html);
160 $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
164 sub action_create_print_all_download {
166 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
168 my $sfile = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
169 die $! if !$sfile->fh;
171 my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
174 my $type = 'Invoices';
175 my $file_name = t8($type) . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
176 $file_name =~ s{[^\w\.]+}{_}g;
178 return $self->send_file(
180 type => 'application/pdf',
189 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
190 sub init_invoice_ids { [] }
191 sub init_today { DateTime->today_local }
193 sub init_sales_delivery_order_models {
195 return $self->_init_sales_delivery_order_models(sortby => 'donumber');
198 sub _init_sales_delivery_order_models {
199 my ($self, %params) = @_;
201 SL::Controller::Helper::GetModels->new(
203 model => 'DeliveryOrder',
207 by => $params{sortby},
210 customer => t8('Customer'),
211 employee => t8('Employee'),
212 transdate => t8('Date'),
213 donumber => t8('Delivery Order Number'),
214 ordnumber => t8('Order Number'),
216 with_objects => [ qw(customer employee) ],
218 '!customer_id' => undef,
219 or => [ closed => undef, closed => 0 ],
225 sub init_invoice_models {
227 my @invoice_ids = @{ $self->invoice_ids };
229 SL::Controller::Helper::GetModels->new(
232 (paginated => 0,) x !!@invoice_ids,
238 customer => t8('Customer'),
239 invnumber => t8('Invoice Number'),
240 employee => t8('Employee'),
241 donumber => t8('Delivery Order Number'),
242 ordnumber => t8('Order Number'),
243 reqdate => t8('Delivery Date'),
244 transdate => t8('Date'),
246 with_objects => [ qw(customer employee) ],
248 '!customer_id' => undef,
249 (id => \@invoice_ids) x !!@invoice_ids,
255 sub init_default_printer_id {
256 my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
257 return $pr ? $pr->id : undef;
262 $::auth->assert('invoice_edit');
264 $::request->layout->use_javascript("${_}.js") for qw(kivi.MassInvoiceCreatePrint);
272 my ($self, %params) = @_;
275 foreach my $invoice (@{ $params{invoices} }) {
276 my %create_params = (
277 template => $self->find_template(name => 'invoice', printer_id => $params{printer_id}),
278 variables => Form->new(''),
279 return => 'file_name',
280 variable_content_types => { longdescription => 'html',
285 $create_params{variables}->{$_} = $params{variables}->{$_} for keys %{ $params{variables} };
287 $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
288 $create_params{variables}->prepare_for_printing;
290 push @pdf_file_names, $self->create_pdf(%create_params);
293 return @pdf_file_names;
296 sub download_or_print_documents {
297 my ($self, %params) = @_;
303 invoices => $params{invoices},
304 printer_id => $params{printer_id},
307 formname => 'invoice',
309 media => $params{printer_id} ? 'printer' : 'file',
312 @pdf_file_names = $self->create_pdfs(%pdf_params);
313 my $merged_pdf = $self->merge_pdfs(file_names => \@pdf_file_names);
314 unlink @pdf_file_names;
316 if (!$params{printer_id}) {
317 my $file_name = t8("Invoices") . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
318 $file_name =~ s{[^\w\.]+}{_}g;
320 return $self->send_file(
322 type => 'application/pdf',
327 my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
328 $printer->print_document(content => $merged_pdf);
330 flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
331 return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
334 unlink @pdf_file_names;
335 $::form->error(t8("Creating the PDF failed:") . " " . $@);
339 sub make_filter_summary {
342 my $filter = $::form->{filter} || {};
346 [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
347 [ $filter->{"transdate:date::ge"}, t8('Transdate') . " " . t8('From Date') ],
348 [ $filter->{"transdate:date::le"}, t8('Transdate') . " " . t8('To Date') ],
352 push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
355 $self->{filter_summary} = join ', ', @filter_strings;
367 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
371 Controller class for the conversion and processing (printing) of objects.
374 Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
375 In general there are two major distinctions:
376 This class implements the conversion and the printing via clickable action AND triggers the same
377 conversion towards a Background-Job with a good user interaction.
379 Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
380 in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
381 ($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
382 and in the background job, i.e. compare the actions create and print.
383 From a reverse engineering point of view the actions in this controller were written before the
384 background job existed, therefore if anything goes boom take a look at the single steps done via gui
385 in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
391 =item C<action_list_sales_delivery_orders>
393 List all open sales delivery orders. The filter can be in two states show or "no show" the
394 original, probably gorash idea, is to increase performance and not to be forced to click on the
395 next button (like in all other reports). Therefore use this option and this filter for a good
396 project default and hide it again. Filters can be added in _filter.html. Take a look at
397 SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
399 =item C<action_create_invoices>
401 Creates or to be more correctly converts delivery orders to invoices. All items are
402 converted 1:1 and after conversion the delivery order(s) are closed.
404 =item C<action_list_invoices>
406 List the created invoices, if created via gui (see action above)
408 =item C<action_print>
410 Print the selected invoices. Yes, it really is all boring linear (see action above).
411 Calls download_or_print method.
413 =item C<action_create_print_all_start>
415 Initialises the webform for the creation and printing via background job. Now we get to
416 the more fun part ... Mosu did a great user interaction job here, we can choose how many
417 objects are converted in one strike and if or if not they are downloadable or will be sent to
418 a printer (if defined as a printing command) right away.
419 Background job is started and status is watched via js and the next action.
421 =item C<action_create_print_all_status>
423 Action for watching status, default is refreshing every 5 seconds
425 =item C<action_create_print_all_download>
427 If the above is done (did I already said: boring linear?). Documents will
428 be either printed or downloaded.
430 =item C<init_printers>
432 Gets all printer commands
434 =item C<init_invoice_ids>
436 Gets a list of (empty) invoice ids
440 Gets the current day. Currently used in custom code.
441 Has to be initialised (get_set_init) and can be used as default for
442 a date tag like C<[% L.date_tag("transdate", SELF.today, id=transdate) %]>.
444 =item C<init_sales_delivery_order_models>
446 Calls _init_sales_delivery_order_models with a param
448 =item C<_init_sales_delivery_order_models>
450 Private function, called by init_sales_delivery_order_models.
451 Gets all open sales delivery orders.
453 =item C<init_invoice_models>
455 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
457 =item C<init_default_printer_id>
459 Gets the default printer for sales_invoices. Currently this function is not called, but
460 might be useful in the next version.Calling template code and Controller already expect a default:
461 C<L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]>
465 Currently sets / checks only the access right.
469 =item C<download_or_print_documents>
471 Backend function for printing or downloading docs. Only used for gui processing (called
474 =item C<make_filter_summary>
475 Creates the filter option summary in the header. By the time of writing three filters are
476 supported: Customer and date from/to of the Delivery Order (database field transdate).
482 Should be more generalized. Right now just one conversion (delivery order to invoice) is supported.
483 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
484 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
485 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
490 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
492 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>