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->setup_list_sales_delivery_orders_action_bar(show_creation_buttons => $show, num_rows => scalar(@{ $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->setup_list_invoices_action_bar(num_rows => scalar(@{ $self->invoice_models->get }));
 
 100   $self->render('mass_invoice_create_print_from_do/list_invoices',
 
 101                 title        => $::locale->text('Open invoice'),
 
 103                 selected_ids => \%selected_ids);
 
 109   my @invoices = map { SL::DB::Invoice->new(id => $_)->load } @{ $::form->{id} || [] };
 
 111     flash_later('error', t8('No invoices have been selected.'));
 
 112     return $self->redirect_to(action => 'list_invoices');
 
 115   $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices);
 
 118 sub action_create_print_all_start {
 
 121   $self->sales_delivery_order_models->disable_plugin('paginated');
 
 123   my @records          = @{ $self->sales_delivery_order_models->get };
 
 124   my $num              = min(scalar(@records), $::form->{number_of_invoices} // scalar(@records));
 
 126   my $job              = SL::DB::BackgroundJob->new(
 
 129     package_name       => 'MassRecordCreationAndPrinting',
 
 132     record_ids         => [ map { $_->id } @records[0..$num - 1] ],
 
 133     printer_id         => $::form->{printer_id},
 
 134     copy_printer_id    => $::form->{copy_printer_id},
 
 135     bothsided          => ($::form->{bothsided}?1:0),
 
 136     transdate          => $::form->{transdate},
 
 137     status             => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
 
 141     conversion_errors  => [ ],
 
 143     session_id         => $::auth->get_session_id,
 
 145   )->update_next_run_at;
 
 147   SL::System::TaskServer->new->wake_up;
 
 149   my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
 
 152     ->html('#create_print_all_dialog', $html)
 
 153     ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
 
 157 sub action_create_print_all_status {
 
 159   my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
 
 160   my $html   = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
 
 162   $self->js->html('#create_print_all_dialog', $html);
 
 163   $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
 
 167 sub action_create_print_all_download {
 
 169   my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
 
 171   my $sfile  = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
 
 172   die $! if !$sfile->fh;
 
 174   my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
 
 177   my $type      = 'Invoices';
 
 178   my $file_name =  t8($type) . '-' . DateTime->now_local->strftime('%Y%m%d%H%M%S') . '.pdf';
 
 179   $file_name    =~ s{[^\w\.]+}{_}g;
 
 181   return $self->send_file(
 
 183     type => 'application/pdf',
 
 192 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
 
 193 #sub init_att      { require SL::Controller::Attachments; SL::Controller::Attachments->new() }
 
 194 sub init_invoice_ids { [] }
 
 195 sub init_today         { DateTime->today_local }
 
 197 sub init_sales_delivery_order_models {
 
 199   return $self->_init_sales_delivery_order_models(sortby => 'donumber');
 
 202 sub _init_sales_delivery_order_models {
 
 203   my ($self, %params) = @_;
 
 205   SL::Controller::Helper::GetModels->new(
 
 207     model        => 'DeliveryOrder',
 
 211         by           => $params{sortby},
 
 214       customer     => t8('Customer'),
 
 215       employee     => t8('Employee'),
 
 216       transdate    => t8('Date'),
 
 217       donumber     => t8('Delivery Order Number'),
 
 218       ordnumber     => t8('Order Number'),
 
 220     with_objects => [ qw(customer employee) ],
 
 222       '!customer_id' => undef,
 
 223       or             => [ closed    => undef, closed    => 0 ],
 
 229 sub init_invoice_models {
 
 231   my @invoice_ids = @{ $self->invoice_ids };
 
 233   SL::Controller::Helper::GetModels->new(
 
 236     (paginated   => 0,) x !!@invoice_ids,
 
 242       customer     => t8('Customer'),
 
 243       invnumber    => t8('Invoice Number'),
 
 244       employee     => t8('Employee'),
 
 245       donumber     => t8('Delivery Order Number'),
 
 246       ordnumber    => t8('Order Number'),
 
 247       reqdate      => t8('Delivery Date'),
 
 248       transdate    => t8('Date'),
 
 250     with_objects => [ qw(customer employee) ],
 
 252       '!customer_id' => undef,
 
 253       (id            => \@invoice_ids) x !!@invoice_ids,
 
 259 sub init_default_printer_id {
 
 260   my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
 
 261   return $pr ? $pr->id : undef;
 
 266   $::auth->assert('invoice_edit');
 
 268   $::request->layout->use_javascript("${_}.js")  for qw(kivi.MassInvoiceCreatePrint);
 
 276 sub download_or_print_documents {
 
 277   my ($self, %params) = @_;
 
 283       documents       => $params{invoices},
 
 286         formname    => 'invoice',
 
 288         media       => $params{printer_id} ? 'printer' : 'file',
 
 289         printer_id  => $params{printer_id},
 
 292     @pdf_file_names = $self->create_pdfs(%pdf_params);
 
 293     my $merged_pdf  = $self->merge_pdfs(file_names => \@pdf_file_names);
 
 294     unlink @pdf_file_names;
 
 296     if (!$params{printer_id}) {
 
 297       my $file_name =  t8("Invoices") . '-' . DateTime->now_local->strftime('%Y%m%d%H%M%S') . '.pdf';
 
 298       $file_name    =~ s{[^\w\.]+}{_}g;
 
 300       return $self->send_file(
 
 302         type => 'application/pdf',
 
 307     my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
 
 308     $printer->print_document(content => $merged_pdf);
 
 310     flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
 
 311     return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
 
 314     unlink @pdf_file_names;
 
 315     $::form->error(t8("Creating the PDF failed:") . " " . $@);
 
 319 sub make_filter_summary {
 
 322   my $filter = $::form->{filter} || {};
 
 326     [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
 
 327     [ $filter->{"transdate:date::ge"},           t8('Transdate') . " " . t8('From Date') ],
 
 328     [ $filter->{"transdate:date::le"},           t8('Transdate') . " " . t8('To Date')   ],
 
 332     push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
 
 335   $self->{filter_summary} = join ', ', @filter_strings;
 
 338 sub setup_list_invoices_action_bar {
 
 339   my ($self, %params) = @_;
 
 341   for my $bar ($::request->layout->get('actionbar')) {
 
 345         submit    => [ '#search_form', { action => 'MassInvoiceCreatePrint/list_invoices' } ],
 
 346         accesskey => 'enter',
 
 349         $::locale->text('Print'),
 
 350         call     => [ 'kivi.MassInvoiceCreatePrint.showMassPrintOptionsOrDownloadDirectly' ],
 
 351         disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
 
 357 sub setup_list_sales_delivery_orders_action_bar {
 
 358   my ($self, %params) = @_;
 
 360   for my $bar ($::request->layout->get('actionbar')) {
 
 363         $params{show_creation_buttons} ? t8('Update') : t8('Search'),
 
 364         submit    => [ '#search_form', { action => 'MassInvoiceCreatePrint/list_sales_delivery_orders' } ],
 
 365         accesskey => 'enter',
 
 371           tooltip => t8("Create and print invoices")
 
 374           t8("Create and print invoices for all selected delivery orders"),
 
 375           call     => [ 'kivi.MassInvoiceCreatePrint.submitMassCreationForm' ],
 
 376           disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
 
 377           only_if  => $params{show_creation_buttons},
 
 381           t8("Create and print invoices for all delivery orders matching the filter"),
 
 382           call     => [ 'kivi.MassInvoiceCreatePrint.createPrintAllInitialize' ],
 
 383           disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
 
 384           only_if  => $params{show_creation_buttons},
 
 401 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
 
 405 Controller class for the conversion and processing (printing) of objects.
 
 408 Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
 
 409 In general there are two major distinctions:
 
 410 This class implements the conversion and the printing via clickable action AND triggers the same
 
 411 conversion towards a Background-Job with a good user interaction.
 
 413 Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
 
 414 in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
 
 415 ($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
 
 416 and in the background job, i.e. compare the actions create and print.
 
 417 From a reverse engineering point of view the actions in this controller were written before the
 
 418 background job existed, therefore if anything goes boom take a look at the single steps done via gui
 
 419 in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
 
 425 =item C<action_list_sales_delivery_orders>
 
 427 List all open sales delivery orders. The filter can be in two states show or "no show" the
 
 428 original, probably gorash idea, is to increase performance and not to be forced to click on the
 
 429 next button (like in all other reports). Therefore use this option and this filter for a good
 
 430 project default and hide it again. Filters can be added in _filter.html. Take a look at
 
 431   SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
 
 433 =item C<action_create_invoices>
 
 435 Creates or to be more correctly converts delivery orders to invoices. All items are
 
 436 converted 1:1 and after conversion the delivery order(s) are closed.
 
 438 =item C<action_list_invoices>
 
 440 List the created invoices, if created via gui (see action above)
 
 442 =item C<action_print>
 
 444 Print the selected invoices. Yes, it really is all boring linear (see action above).
 
 445 Calls download_or_print method.
 
 447 =item C<action_create_print_all_start>
 
 449 Initialises the webform for the creation and printing via background job. Now we get to
 
 450 the more fun part ...  Mosu did a great user interaction job here, we can choose how many
 
 451 objects are converted in one strike and if or if not they are downloadable or will be sent to
 
 452 a printer (if defined as a printing command) right away.
 
 453 Background job is started and status is watched via js and the next action.
 
 455 =item C<action_create_print_all_status>
 
 457 Action for watching status, default is refreshing every 5 seconds
 
 459 =item C<action_create_print_all_download>
 
 461 If the above is done (did I already said: boring linear?). Documents will
 
 462 be either printed or downloaded.
 
 464 =item C<init_printers>
 
 466 Gets all printer commands
 
 468 =item C<init_invoice_ids>
 
 470 Gets a list of (empty) invoice ids
 
 474 Gets the current day. Currently used in custom code.
 
 475 Has to be initialised (get_set_init) and can be used as default for
 
 476 a date tag like C<[% L.date_tag("transdate", SELF.today, id=transdate) %]>.
 
 478 =item C<init_sales_delivery_order_models>
 
 480 Calls _init_sales_delivery_order_models with a param
 
 482 =item C<_init_sales_delivery_order_models>
 
 484 Private function, called by init_sales_delivery_order_models.
 
 485 Gets all open sales delivery orders.
 
 487 =item C<init_invoice_models>
 
 489 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
 
 491 =item C<init_default_printer_id>
 
 493 Gets the default printer for sales_invoices. Currently this function is not called, but
 
 494 might be useful in the next version.Calling template code and Controller already expect a default:
 
 495 C<L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]>
 
 499 Currently sets / checks only the access right.
 
 503 =item C<download_or_print_documents>
 
 505 Backend function for printing or downloading docs. Only used for gui processing (called
 
 508 =item C<make_filter_summary>
 
 509 Creates the filter option summary in the header. By the time of writing three filters are
 
 510 supported: Customer and date from/to of the Delivery Order (database field transdate).
 
 516 pShould be more generalized. Right now just one conversion (delivery order to invoice) is supported.
 
 517 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
 
 518 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
 
 519 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
 
 524 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 526 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>