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::File qw(store_pdf append_general_pdf_attachments);
 
  19 use SL::Helper::Flash;
 
  20 use SL::Locale::String;
 
  22 use SL::System::TaskServer;
 
  23 use Rose::Object::MakeMethods::Generic
 
  25   'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id today) ],
 
  28 __PACKAGE__->run_before('setup');
 
  34 sub action_list_sales_delivery_orders {
 
  37   # default is usually no show, exception here
 
  38   my $show = ($::form->{noshow} ? 0 : 1);
 
  39   delete $::form->{noshow};
 
  41   # if a filter is choosen, the filter info should be visible
 
  42   $self->make_filter_summary;
 
  43   $self->setup_list_sales_delivery_orders_action_bar(show_creation_buttons => $show, num_rows => scalar(@{ $self->sales_delivery_order_models->get }));
 
  44   $self->render('mass_invoice_create_print_from_do/list_sales_delivery_orders',
 
  46                 title   => $::locale->text('Open sales delivery orders'));
 
  49 sub action_create_invoices {
 
  52   my @sales_delivery_order_ids = @{ $::form->{id} || [] };
 
  53   if (!@sales_delivery_order_ids) {
 
  54     # should never be executed, double catch via js
 
  55     flash_later('error', t8('No delivery orders have been selected.'));
 
  56     return $self->redirect_to(action => 'list_sales_delivery_orders');
 
  59   my $db = SL::DB::Invoice->new->db;
 
  62   if (!$db->with_transaction(sub {
 
  63     foreach my $id (@sales_delivery_order_ids) {
 
  64       my $delivery_order    = SL::DB::DeliveryOrder->new(id => $id)->load;
 
  66       my $invoice = $delivery_order->convert_to_invoice() || die $db->error;
 
  67       push @invoices, $invoice;
 
  72     $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error);
 
  73     $::form->error($db->error);
 
  76   my $key = sprintf('%d-%d', Time::HiRes::gettimeofday());
 
  77   $::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]);
 
  79   flash_later('info', t8('The invoices have been created. They\'re pre-selected below.'));
 
  80   $self->redirect_to(action => 'list_invoices', ids => $key);
 
  83 sub action_list_invoices {
 
  86   my $show = $::form->{noshow} ? 0 : 1;
 
  87   delete $::form->{noshow};
 
  90     my $key = 'MassInvoiceCreatePrint::ids-' . $::form->{ids};
 
  91     $self->invoice_ids($::auth->get_session_value($key) || []);
 
  92     $self->invoice_models->add_additional_url_params(ids => $::form->{ids});
 
  95   my %selected_ids = map { +($_ => 1) } @{ $self->invoice_ids };
 
  97   $::form->{printer_id} ||= $self->default_printer_id;
 
  99   $self->setup_list_invoices_action_bar(num_rows => scalar(@{ $self->invoice_models->get }));
 
 101   $self->render('mass_invoice_create_print_from_do/list_invoices',
 
 102                 title        => $::locale->text('Open invoice'),
 
 104                 selected_ids => \%selected_ids);
 
 110   my @invoices = map { SL::DB::Invoice->new(id => $_)->load } @{ $::form->{id} || [] };
 
 112     flash_later('error', t8('No invoices have been selected.'));
 
 113     return $self->redirect_to(action => 'list_invoices');
 
 116   $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices);
 
 119 sub action_create_print_all_start {
 
 122   $self->sales_delivery_order_models->disable_plugin('paginated');
 
 124   my @records          = @{ $self->sales_delivery_order_models->get };
 
 125   my $num              = min(scalar(@records), $::form->{number_of_invoices} // scalar(@records));
 
 127   my $job              = SL::DB::BackgroundJob->new(
 
 130     package_name       => 'MassRecordCreationAndPrinting',
 
 133     record_ids         => [ map { $_->id } @records[0..$num - 1] ],
 
 134     printer_id         => $::form->{printer_id},
 
 135     copy_printer_id    => $::form->{copy_printer_id},
 
 136     bothsided          => ($::form->{bothsided}?1:0),
 
 137     transdate          => $::form->{transdate},
 
 138     status             => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
 
 142     conversion_errors  => [ ],
 
 144     session_id         => $::auth->get_session_id,
 
 146   )->update_next_run_at;
 
 148   SL::System::TaskServer->new->wake_up;
 
 150   my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
 
 153     ->html('#create_print_all_dialog', $html)
 
 154     ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
 
 158 sub action_create_print_all_status {
 
 160   my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
 
 161   my $html   = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
 
 163   $self->js->html('#create_print_all_dialog', $html);
 
 164   $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
 
 168 sub action_create_print_all_download {
 
 170   my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
 
 172   my $sfile  = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
 
 173   die $! if !$sfile->fh;
 
 175   my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
 
 178   my $type      = 'Invoices';
 
 179   my $file_name =  t8($type) . '-' . DateTime->now_local->strftime('%Y%m%d%H%M%S') . '.pdf';
 
 180   $file_name    =~ s{[^\w\.]+}{_}g;
 
 182   return $self->send_file(
 
 184     type => 'application/pdf',
 
 193 sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
 
 194 #sub init_att      { require SL::Controller::Attachments; SL::Controller::Attachments->new() }
 
 195 sub init_invoice_ids { [] }
 
 196 sub init_today         { DateTime->today_local }
 
 198 sub init_sales_delivery_order_models {
 
 200   return $self->_init_sales_delivery_order_models(sortby => 'donumber');
 
 203 sub _init_sales_delivery_order_models {
 
 204   my ($self, %params) = @_;
 
 206   SL::Controller::Helper::GetModels->new(
 
 208     model        => 'DeliveryOrder',
 
 212         by           => $params{sortby},
 
 215       customer     => t8('Customer'),
 
 216       employee     => t8('Employee'),
 
 217       transdate    => t8('Delivery Order Date'),
 
 218       donumber     => t8('Delivery Order Number'),
 
 219       ordnumber     => t8('Order Number'),
 
 221     with_objects => [ qw(customer employee) ],
 
 223       '!customer_id' => undef,
 
 224       or             => [ closed    => undef, closed    => 0 ],
 
 230 sub init_invoice_models {
 
 232   my @invoice_ids = @{ $self->invoice_ids };
 
 234   SL::Controller::Helper::GetModels->new(
 
 237     (paginated   => 0,) x !!@invoice_ids,
 
 243       customer     => t8('Customer'),
 
 244       invnumber    => t8('Invoice Number'),
 
 245       employee     => t8('Employee'),
 
 246       donumber     => t8('Delivery Order Number'),
 
 247       ordnumber    => t8('Order Number'),
 
 248       reqdate      => t8('Delivery Date'),
 
 249       transdate    => t8('Date'),
 
 251     with_objects => [ qw(customer employee) ],
 
 253       '!customer_id' => undef,
 
 254       (id            => \@invoice_ids) x !!@invoice_ids,
 
 260 sub init_default_printer_id {
 
 261   my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
 
 262   return $pr ? $pr->id : undef;
 
 267   $::auth->assert('invoice_edit');
 
 269   $::request->layout->use_javascript("${_}.js")  for qw(kivi.MassInvoiceCreatePrint);
 
 277 sub download_or_print_documents {
 
 278   my ($self, %params) = @_;
 
 284       documents       => $params{invoices},
 
 287         formname    => 'invoice',
 
 289         media       => $params{printer_id} ? 'printer' : 'file',
 
 290         printer_id  => $params{printer_id},
 
 293     @pdf_file_names = $self->create_pdfs(%pdf_params);
 
 294     my $merged_pdf  = $self->merge_pdfs(file_names => \@pdf_file_names);
 
 295     unlink @pdf_file_names;
 
 297     if (!$params{printer_id}) {
 
 298       my $file_name =  t8("Invoices") . '-' . DateTime->now_local->strftime('%Y%m%d%H%M%S') . '.pdf';
 
 299       $file_name    =~ s{[^\w\.]+}{_}g;
 
 301       return $self->send_file(
 
 303         type => 'application/pdf',
 
 308     my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
 
 309     $printer->print_document(content => $merged_pdf);
 
 311     flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
 
 312     return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
 
 315     unlink @pdf_file_names;
 
 316     $::form->error(t8("Creating the PDF failed:") . " " . $@);
 
 320 sub make_filter_summary {
 
 323   my $filter = $::form->{filter} || {};
 
 327     [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
 
 328     [ $filter->{"transdate:date::ge"},           t8('Delivery Order Date') . " " . t8('From Date') ],
 
 329     [ $filter->{"transdate:date::le"},           t8('Delivery Order Date') . " " . t8('To Date')   ],
 
 333     push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
 
 336   $self->{filter_summary} = join ', ', @filter_strings;
 
 339 sub setup_list_invoices_action_bar {
 
 340   my ($self, %params) = @_;
 
 342   for my $bar ($::request->layout->get('actionbar')) {
 
 346         submit    => [ '#search_form', { action => 'MassInvoiceCreatePrint/list_invoices' } ],
 
 347         accesskey => 'enter',
 
 350         $::locale->text('Print'),
 
 351         call     => [ 'kivi.MassInvoiceCreatePrint.showMassPrintOptionsOrDownloadDirectly' ],
 
 352         disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
 
 358 sub setup_list_sales_delivery_orders_action_bar {
 
 359   my ($self, %params) = @_;
 
 361   for my $bar ($::request->layout->get('actionbar')) {
 
 364         $params{show_creation_buttons} ? t8('Update') : t8('Search'),
 
 365         submit    => [ '#search_form', { action => 'MassInvoiceCreatePrint/list_sales_delivery_orders' } ],
 
 366         accesskey => 'enter',
 
 372           tooltip => t8("Create and print invoices")
 
 375           t8("Create and print invoices for all selected delivery orders"),
 
 376           call     => [ 'kivi.MassInvoiceCreatePrint.submitMassCreationForm' ],
 
 377           disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
 
 378           only_if  => $params{show_creation_buttons},
 
 382           t8("Create and print invoices for all delivery orders matching the filter"),
 
 383           call     => [ 'kivi.MassInvoiceCreatePrint.createPrintAllInitialize' ],
 
 384           disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
 
 385           only_if  => $params{show_creation_buttons},
 
 402 SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
 
 406 Controller class for the conversion and processing (printing) of objects.
 
 409 Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
 
 410 In general there are two major distinctions:
 
 411 This class implements the conversion and the printing via clickable action AND triggers the same
 
 412 conversion towards a Background-Job with a good user interaction.
 
 414 Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
 
 415 in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
 
 416 ($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
 
 417 and in the background job, i.e. compare the actions create and print.
 
 418 From a reverse engineering point of view the actions in this controller were written before the
 
 419 background job existed, therefore if anything goes boom take a look at the single steps done via gui
 
 420 in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
 
 426 =item C<action_list_sales_delivery_orders>
 
 428 List all open sales delivery orders. The filter can be in two states show or "no show" the
 
 429 original, probably gorash idea, is to increase performance and not to be forced to click on the
 
 430 next button (like in all other reports). Therefore use this option and this filter for a good
 
 431 project default and hide it again. Filters can be added in _filter.html. Take a look at
 
 432   SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
 
 434 =item C<action_create_invoices>
 
 436 Creates or to be more correctly converts delivery orders to invoices. All items are
 
 437 converted 1:1 and after conversion the delivery order(s) are closed.
 
 439 =item C<action_list_invoices>
 
 441 List the created invoices, if created via gui (see action above)
 
 443 =item C<action_print>
 
 445 Print the selected invoices. Yes, it really is all boring linear (see action above).
 
 446 Calls download_or_print method.
 
 448 =item C<action_create_print_all_start>
 
 450 Initialises the webform for the creation and printing via background job. Now we get to
 
 451 the more fun part ...  Mosu did a great user interaction job here, we can choose how many
 
 452 objects are converted in one strike and if or if not they are downloadable or will be sent to
 
 453 a printer (if defined as a printing command) right away.
 
 454 Background job is started and status is watched via js and the next action.
 
 456 =item C<action_create_print_all_status>
 
 458 Action for watching status, default is refreshing every 5 seconds
 
 460 =item C<action_create_print_all_download>
 
 462 If the above is done (did I already said: boring linear?). Documents will
 
 463 be either printed or downloaded.
 
 465 =item C<init_printers>
 
 467 Gets all printer commands
 
 469 =item C<init_invoice_ids>
 
 471 Gets a list of (empty) invoice ids
 
 475 Gets the current day. Currently used in custom code.
 
 476 Has to be initialised (get_set_init) and can be used as default for
 
 477 a date tag like C<[% L.date_tag("transdate", SELF.today, id=transdate) %]>.
 
 479 =item C<init_sales_delivery_order_models>
 
 481 Calls _init_sales_delivery_order_models with a param
 
 483 =item C<_init_sales_delivery_order_models>
 
 485 Private function, called by init_sales_delivery_order_models.
 
 486 Gets all open sales delivery orders.
 
 488 =item C<init_invoice_models>
 
 490 Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
 
 492 =item C<init_default_printer_id>
 
 494 Gets the default printer for sales_invoices. Currently this function is not called, but
 
 495 might be useful in the next version.Calling template code and Controller already expect a default:
 
 496 C<L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]>
 
 500 Currently sets / checks only the access right.
 
 504 =item C<download_or_print_documents>
 
 506 Backend function for printing or downloading docs. Only used for gui processing (called
 
 509 =item C<make_filter_summary>
 
 510 Creates the filter option summary in the header. By the time of writing three filters are
 
 511 supported: Customer and date from/to of the Delivery Order (database field transdate).
 
 517 pShould be more generalized. Right now just one conversion (delivery order to invoice) is supported.
 
 518 Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
 
 519 was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
 
 520 stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
 
 525 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 527 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>