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::Flash;
19 use SL::Locale::String;
21 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 bothsided => ($::form->{bothsided}?1:0),
134 transdate => $::form->{transdate},
135 status => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
139 conversion_errors => [ ],
141 session_id => $::auth->get_session_id,
143 )->update_next_run_at;
145 SL::System::TaskServer->new->wake_up;
147 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
150 ->html('#create_print_all_dialog', $html)
151 ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
155 sub action_create_print_all_status {
157 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
158 my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
160 $self->js->html('#create_print_all_dialog', $html);
161 $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
165 sub action_create_print_all_download {
167 my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
169 my $sfile = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
170 die $! if !$sfile->fh;
172 my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
175 my $type = 'Invoices';
176 my $file_name = t8($type) . '-' . DateTime->now_local->strftime('%Y%m%d%H%M%S') . '.pdf';
177 $file_name =~ s{[^\w\.]+}{_}g;
179 return $self->send_file(
181 type => 'application/pdf',
190 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
191 #sub init_att { require SL::Controller::Attachments; SL::Controller::Attachments->new() }
192 sub init_invoice_ids { [] }
193 sub init_today { DateTime->today_local }
195 sub init_sales_delivery_order_models {
197 return $self->_init_sales_delivery_order_models(sortby => 'donumber');
200 sub _init_sales_delivery_order_models {
201 my ($self, %params) = @_;
203 SL::Controller::Helper::GetModels->new(
205 model => 'DeliveryOrder',
209 by => $params{sortby},
212 customer => t8('Customer'),
213 employee => t8('Employee'),
214 transdate => t8('Date'),
215 donumber => t8('Delivery Order Number'),
216 ordnumber => t8('Order Number'),
218 with_objects => [ qw(customer employee) ],
220 '!customer_id' => undef,
221 or => [ closed => undef, closed => 0 ],
227 sub init_invoice_models {
229 my @invoice_ids = @{ $self->invoice_ids };
231 SL::Controller::Helper::GetModels->new(
234 (paginated => 0,) x !!@invoice_ids,
240 customer => t8('Customer'),
241 invnumber => t8('Invoice Number'),
242 employee => t8('Employee'),
243 donumber => t8('Delivery Order Number'),
244 ordnumber => t8('Order Number'),
245 reqdate => t8('Delivery Date'),
246 transdate => t8('Date'),
248 with_objects => [ qw(customer employee) ],
250 '!customer_id' => undef,
251 (id => \@invoice_ids) x !!@invoice_ids,
257 sub init_default_printer_id {
258 my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
259 return $pr ? $pr->id : undef;
264 $::auth->assert('invoice_edit');
266 $::request->layout->use_javascript("${_}.js") for qw(kivi.MassInvoiceCreatePrint);
274 sub download_or_print_documents {
275 my ($self, %params) = @_;
281 documents => $params{invoices},
284 formname => 'invoice',
286 media => $params{printer_id} ? 'printer' : 'file',
287 printer_id => $params{printer_id},
290 @pdf_file_names = $self->create_pdfs(%pdf_params);
291 my $merged_pdf = $self->merge_pdfs(file_names => \@pdf_file_names);
292 unlink @pdf_file_names;
294 if (!$params{printer_id}) {
295 my $file_name = t8("Invoices") . '-' . DateTime->now_local->strftime('%Y%m%d%H%M%S') . '.pdf';
296 $file_name =~ s{[^\w\.]+}{_}g;
298 return $self->send_file(
300 type => 'application/pdf',
305 my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
306 $printer->print_document(content => $merged_pdf);
308 flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
309 return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
312 unlink @pdf_file_names;
313 $::form->error(t8("Creating the PDF failed:") . " " . $@);
317 sub make_filter_summary {
320 my $filter = $::form->{filter} || {};
324 [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
325 [ $filter->{"transdate:date::ge"}, t8('Transdate') . " " . t8('From Date') ],
326 [ $filter->{"transdate:date::le"}, t8('Transdate') . " " . t8('To Date') ],
330 push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
333 $self->{filter_summary} = join ', ', @filter_strings;
345 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
349 Controller class for the conversion and processing (printing) of objects.
352 Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
353 In general there are two major distinctions:
354 This class implements the conversion and the printing via clickable action AND triggers the same
355 conversion towards a Background-Job with a good user interaction.
357 Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
358 in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
359 ($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
360 and in the background job, i.e. compare the actions create and print.
361 From a reverse engineering point of view the actions in this controller were written before the
362 background job existed, therefore if anything goes boom take a look at the single steps done via gui
363 in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
369 =item C<action_list_sales_delivery_orders>
371 List all open sales delivery orders. The filter can be in two states show or "no show" the
372 original, probably gorash idea, is to increase performance and not to be forced to click on the
373 next button (like in all other reports). Therefore use this option and this filter for a good
374 project default and hide it again. Filters can be added in _filter.html. Take a look at
375 SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
377 =item C<action_create_invoices>
379 Creates or to be more correctly converts delivery orders to invoices. All items are
380 converted 1:1 and after conversion the delivery order(s) are closed.
382 =item C<action_list_invoices>
384 List the created invoices, if created via gui (see action above)
386 =item C<action_print>
388 Print the selected invoices. Yes, it really is all boring linear (see action above).
389 Calls download_or_print method.
391 =item C<action_create_print_all_start>
393 Initialises the webform for the creation and printing via background job. Now we get to
394 the more fun part ... Mosu did a great user interaction job here, we can choose how many
395 objects are converted in one strike and if or if not they are downloadable or will be sent to
396 a printer (if defined as a printing command) right away.
397 Background job is started and status is watched via js and the next action.
399 =item C<action_create_print_all_status>
401 Action for watching status, default is refreshing every 5 seconds
403 =item C<action_create_print_all_download>
405 If the above is done (did I already said: boring linear?). Documents will
406 be either printed or downloaded.
408 =item C<init_printers>
410 Gets all printer commands
412 =item C<init_invoice_ids>
414 Gets a list of (empty) invoice ids
418 Gets the current day. Currently used in custom code.
419 Has to be initialised (get_set_init) and can be used as default for
420 a date tag like C<[% L.date_tag("transdate", SELF.today, id=transdate) %]>.
422 =item C<init_sales_delivery_order_models>
424 Calls _init_sales_delivery_order_models with a param
426 =item C<_init_sales_delivery_order_models>
428 Private function, called by init_sales_delivery_order_models.
429 Gets all open sales delivery orders.
431 =item C<init_invoice_models>
433 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
435 =item C<init_default_printer_id>
437 Gets the default printer for sales_invoices. Currently this function is not called, but
438 might be useful in the next version.Calling template code and Controller already expect a default:
439 C<L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]>
443 Currently sets / checks only the access right.
447 =item C<download_or_print_documents>
449 Backend function for printing or downloading docs. Only used for gui processing (called
452 =item C<make_filter_summary>
453 Creates the filter option summary in the header. By the time of writing three filters are
454 supported: Customer and date from/to of the Delivery Order (database field transdate).
460 Should be more generalized. Right now just one conversion (delivery order to invoice) is supported.
461 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
462 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
463 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
468 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
470 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>