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 js) ],
 
  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_js       { SL::ClientJS->new(controller => $_[0]) }
 
 187 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
 
 188 sub init_invoice_ids { [] }
 
 190 sub init_sales_delivery_order_models {
 
 192   return $self->_init_sales_delivery_order_models(sortby => 'donumber');
 
 195 sub _init_sales_delivery_order_models {
 
 196   my ($self, %params) = @_;
 
 198   SL::Controller::Helper::GetModels->new(
 
 200     model        => 'DeliveryOrder',
 
 204         by           => $params{sortby},
 
 207       customer     => t8('Customer'),
 
 208       employee     => t8('Employee'),
 
 209       transdate    => t8('Date'),
 
 210       donumber     => t8('Delivery Order Number'),
 
 211       ordnumber     => t8('Order Number'),
 
 213     with_objects => [ qw(customer employee) ],
 
 215       '!customer_id' => undef,
 
 216       or             => [ closed    => undef, closed    => 0 ],
 
 217       or             => [ delivered => undef, delivered => 0 ],
 
 223 sub init_invoice_models {
 
 225   my @invoice_ids = @{ $self->invoice_ids };
 
 227   SL::Controller::Helper::GetModels->new(
 
 230     (paginated   => 0,) x !!@invoice_ids,
 
 236       customer     => t8('Customer'),
 
 237       invnumber    => t8('Invoice Number'),
 
 238       employee     => t8('Employee'),
 
 239       donumber     => t8('Delivery Order Number'),
 
 240       ordnumber    => t8('Order Number'),
 
 241       reqdate      => t8('Delivery Date'),
 
 242       transdate    => t8('Date'),
 
 244     with_objects => [ qw(customer employee) ],
 
 246       '!customer_id' => undef,
 
 247       (id            => \@invoice_ids) x !!@invoice_ids,
 
 253 sub init_default_printer_id {
 
 254   my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
 
 255   return $pr ? $pr->id : undef;
 
 260   $::auth->assert('invoice_edit');
 
 262   $::request->layout->use_javascript("${_}.js")  for qw(kivi.MassInvoiceCreatePrint);
 
 270   my ($self, %params) = @_;
 
 273   foreach my $invoice (@{ $params{invoices} }) {
 
 274     my %create_params = (
 
 275       template  => $self->find_template(name => 'invoice', printer_id => $params{printer_id}),
 
 276       variables => Form->new(''),
 
 277       return    => 'file_name',
 
 280     $create_params{variables}->{$_} = $params{variables}->{$_} for keys %{ $params{variables} };
 
 282     $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
 
 283     $create_params{variables}->prepare_for_printing;
 
 285     push @pdf_file_names, $self->create_pdf(%create_params);
 
 288   return @pdf_file_names;
 
 291 sub download_or_print_documents {
 
 292   my ($self, %params) = @_;
 
 298       invoices        => $params{invoices},
 
 299       printer_id      => $params{printer_id},
 
 302         formname    => 'invoice',
 
 304         media       => $params{printer_id} ? 'printer' : 'file',
 
 307     @pdf_file_names = $self->create_pdfs(%pdf_params);
 
 308     my $merged_pdf  = $self->merge_pdfs(file_names => \@pdf_file_names);
 
 309     unlink @pdf_file_names;
 
 311     if (!$params{printer_id}) {
 
 312       my $file_name =  t8("Invoices") . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
 
 313       $file_name    =~ s{[^\w\.]+}{_}g;
 
 315       return $self->send_file(
 
 317         type => 'application/pdf',
 
 322     my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
 
 323     my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
 
 325     open my $out, '|-', $command or die $!;
 
 327     print $out $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('Delivery Date') . " " . t8('From Date') ],
 
 348     [ $filter->{"transdate:date::le"},           t8('Delivery Date') . " " . 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.
 
 432 Inits js/kivi.MassInvoiceCreatePrint;
 
 434 =item C<init_printers>
 
 436 Gets all printer commands
 
 438 =item C<init_invoice_ids>
 
 440 Gets a list of (empty) invoice ids
 
 442 =item C<init_sales_delivery_order_models>
 
 444 Calls _init_sales_delivery_order_models with a param
 
 446 =item C<_init_sales_delivery_order_models>
 
 448 Private function, called by init_sales_delivery_order_models.
 
 449 Gets all open sales delivery orders.
 
 451 =item C<init_invoice_models>
 
 453 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
 
 455 =item C<init_default_printer_id>
 
 457 Gets the default printer for sales_invoices. Maybe this function is not used, but
 
 458 might be useful in the next version (working in client project).
 
 462 Currently sets / checks only the access right.
 
 466 =item C<download_or_print_documents>
 
 468 Backend function for printing or downloading docs. Only used for gui processing (called
 
 471 =item C<make_filter_summary>
 
 472 Creates the filter option summary in the header. By the time of writing three filters are
 
 473 supported: Customer and date from/to of the Delivery Order (database field transdate).
 
 479 Should be more generalized. Right now just one conversion (delivery order to invoice) is supported.
 
 480 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
 
 481 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
 
 482 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
 
 484 Filtering needs to be extended for Delivery Order Number (Natural Sort).
 
 486 A second printer (copy) needs to be implemented.
 
 488 Both todos are marked in the template code.
 
 493 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 495 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>