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;
 
  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     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     my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
 
 330     open my $out, '|-', $command or die $!;
 
 332     print $out $merged_pdf;
 
 335     flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
 
 336     return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
 
 339     unlink @pdf_file_names;
 
 340     $::form->error(t8("Creating the PDF failed:") . " " . $@);
 
 344 sub make_filter_summary {
 
 347   my $filter = $::form->{filter} || {};
 
 351     [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
 
 352     [ $filter->{"transdate:date::ge"},           t8('Transdate') . " " . t8('From Date') ],
 
 353     [ $filter->{"transdate:date::le"},           t8('Transdate') . " " . t8('To Date')   ],
 
 357     push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
 
 360   $self->{filter_summary} = join ', ', @filter_strings;
 
 372 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
 
 376 Controller class for the conversion and processing (printing) of objects.
 
 379 Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
 
 380 In general there are two major distinctions:
 
 381 This class implements the conversion and the printing via clickable action AND triggers the same
 
 382 conversion towards a Background-Job with a good user interaction.
 
 384 Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
 
 385 in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
 
 386 ($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
 
 387 and in the background job, i.e. compare the actions create and print.
 
 388 From a reverse engineering point of view the actions in this controller were written before the
 
 389 background job existed, therefore if anything goes boom take a look at the single steps done via gui
 
 390 in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
 
 396 =item C<action_list_sales_delivery_orders>
 
 398 List all open sales delivery orders. The filter can be in two states show or "no show" the
 
 399 original, probably gorash idea, is to increase performance and not to be forced to click on the
 
 400 next button (like in all other reports). Therefore use this option and this filter for a good
 
 401 project default and hide it again. Filters can be added in _filter.html. Take a look at
 
 402   SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
 
 404 =item C<action_create_invoices>
 
 406 Creates or to be more correctly converts delivery orders to invoices. All items are
 
 407 converted 1:1 and after conversion the delivery order(s) are closed.
 
 409 =item C<action_list_invoices>
 
 411 List the created invoices, if created via gui (see action above)
 
 413 =item C<action_print>
 
 415 Print the selected invoices. Yes, it really is all boring linear (see action above).
 
 416 Calls download_or_print method.
 
 418 =item C<action_create_print_all_start>
 
 420 Initialises the webform for the creation and printing via background job. Now we get to
 
 421 the more fun part ...  Mosu did a great user interaction job here, we can choose how many
 
 422 objects are converted in one strike and if or if not they are downloadable or will be sent to
 
 423 a printer (if defined as a printing command) right away.
 
 424 Background job is started and status is watched via js and the next action.
 
 426 =item C<action_create_print_all_status>
 
 428 Action for watching status, default is refreshing every 5 seconds
 
 430 =item C<action_create_print_all_download>
 
 432 If the above is done (did I already said: boring linear?). Documents will
 
 433 be either printed or downloaded.
 
 435 =item C<init_printers>
 
 437 Gets all printer commands
 
 439 =item C<init_invoice_ids>
 
 441 Gets a list of (empty) invoice ids
 
 445 Gets the current day. Currently used in custom code.
 
 446 Has to be initialised (get_set_init) and can be used as default for
 
 447 a date tag like C<[% L.date_tag("transdate", SELF.today, id=transdate) %]>.
 
 449 =item C<init_sales_delivery_order_models>
 
 451 Calls _init_sales_delivery_order_models with a param
 
 453 =item C<_init_sales_delivery_order_models>
 
 455 Private function, called by init_sales_delivery_order_models.
 
 456 Gets all open sales delivery orders.
 
 458 =item C<init_invoice_models>
 
 460 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
 
 462 =item C<init_default_printer_id>
 
 464 Gets the default printer for sales_invoices. Currently this function is not called, but
 
 465 might be useful in the next version.Calling template code and Controller already expect a default:
 
 466 C<L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]>
 
 470 Currently sets / checks only the access right.
 
 474 =item C<download_or_print_documents>
 
 476 Backend function for printing or downloading docs. Only used for gui processing (called
 
 479 =item C<make_filter_summary>
 
 480 Creates the filter option summary in the header. By the time of writing three filters are
 
 481 supported: Customer and date from/to of the Delivery Order (database field transdate).
 
 487 Should be more generalized. Right now just one conversion (delivery order to invoice) is supported.
 
 488 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
 
 489 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
 
 490 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
 
 495 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 497 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>