]> wagnertech.de Git - mfinanz.git/blobdiff - SL/Controller/MassInvoiceCreatePrint.pm
Massenkonvertierung von Lieferscheinen nach Rechnung ink. Druck
[mfinanz.git] / SL / Controller / MassInvoiceCreatePrint.pm
diff --git a/SL/Controller/MassInvoiceCreatePrint.pm b/SL/Controller/MassInvoiceCreatePrint.pm
new file mode 100644 (file)
index 0000000..0714757
--- /dev/null
@@ -0,0 +1,497 @@
+package SL::Controller::MassInvoiceCreatePrint;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use File::Slurp ();
+use List::MoreUtils qw(all uniq);
+use List::Util qw(first min);
+
+use SL::BackgroundJob::MassRecordCreationAndPrinting;
+use SL::Controller::Helper::GetModels;
+use SL::DB::DeliveryOrder;
+use SL::DB::Order;
+use SL::DB::Printer;
+use SL::Helper::CreatePDF qw(:all);
+use SL::Helper::Flash;
+use SL::Locale::String;
+use SL::SessionFile;
+use SL::System::TaskServer;
+
+use Rose::Object::MakeMethods::Generic
+(
+  'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id js) ],
+);
+
+__PACKAGE__->run_before('setup');
+
+#
+# actions
+#
+
+sub action_list_sales_delivery_orders {
+  my ($self) = @_;
+
+  # default is usually no show, exception here
+  my $show = ($::form->{noshow} ? 0 : 1);
+  delete $::form->{noshow};
+
+  # if a filter is choosen, the filter info should be visible
+  $self->make_filter_summary;
+  $self->sales_delivery_order_models->get;
+  $self->render('mass_invoice_create_print_from_do/list_sales_delivery_orders',
+                noshow  => $show,
+                title   => $::locale->text('Open sales delivery orders'));
+}
+
+sub action_create_invoices {
+  my ($self) = @_;
+
+  my @sales_delivery_order_ids = @{ $::form->{id} || [] };
+  if (!@sales_delivery_order_ids) {
+    # should never be executed, double catch via js
+    flash_later('error', t8('No delivery orders have been selected.'));
+    return $self->redirect_to(action => 'list_sales_delivery_orders');
+  }
+
+  my $db = SL::DB::Invoice->new->db;
+
+  if (!$db->do_transaction(sub {
+    my @invoices;
+    foreach my $id (@sales_delivery_order_ids) {
+      my $delivery_order    = SL::DB::DeliveryOrder->new(id => $id)->load;
+
+      my $invoice = $delivery_order->convert_to_invoice() || die $db->error;
+      push @invoices, $invoice;
+    }
+
+    my $key = sprintf('%d-%d', Time::HiRes::gettimeofday());
+    $::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]);
+
+    flash_later('info', t8('The invoices have been created. They\'re pre-selected below.'));
+    $self->redirect_to(action => 'list_invoices', ids => $key);
+
+    1;
+  })) {
+    $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error);
+    $::form->error($db->error);
+  }
+}
+
+sub action_list_invoices {
+  my ($self) = @_;
+
+  my $show = $::form->{noshow} ? 0 : 1;
+  delete $::form->{noshow};
+
+  if ($::form->{ids}) {
+    my $key = 'MassInvoiceCreatePrint::ids-' . $::form->{ids};
+    $self->invoice_ids($::auth->get_session_value($key) || []);
+    $self->invoice_models->add_additional_url_params(ids => $::form->{ids});
+  }
+
+  my %selected_ids = map { +($_ => 1) } @{ $self->invoice_ids };
+
+  $::form->{printer_id} ||= $self->default_printer_id;
+
+  $self->render('mass_invoice_create_print_from_do/list_invoices',
+                title        => $::locale->text('Open invoice'),
+                noshow       => $show,
+                selected_ids => \%selected_ids);
+}
+
+sub action_print {
+  my ($self) = @_;
+
+  my @invoices = map { SL::DB::Invoice->new(id => $_)->load } @{ $::form->{id} || [] };
+  if (!@invoices) {
+    flash_later('error', t8('No invoices have been selected.'));
+    return $self->redirect_to(action => 'list_invoices');
+  }
+
+  $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices);
+}
+
+sub action_create_print_all_start {
+  my ($self) = @_;
+
+  $self->sales_delivery_order_models->disable_plugin('paginated');
+
+  my @records           = @{ $self->sales_delivery_order_models->get };
+  my $num              = min(scalar(@records), $::form->{number_of_invoices} // scalar(@records));
+
+  my $job              = SL::DB::BackgroundJob->new(
+    type               => 'once',
+    active             => 1,
+    package_name       => 'MassRecordCreationAndPrinting',
+
+  )->set_data(
+    record_ids         => [ map { $_->id } @records[0..$num - 1] ],
+    printer_id         => $::form->{printer_id},
+    status             => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
+    num_created        => 0,
+    num_printed        => 0,
+    invoice_ids        => [ ],
+    conversion_errors  => [ ],
+    print_errors       => [ ],
+
+  )->update_next_run_at;
+
+  SL::System::TaskServer->new->wake_up;
+
+  my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
+
+  $self->js
+    ->html('#create_print_all_dialog', $html)
+    ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
+    ->render;
+}
+
+sub action_create_print_all_status {
+  my ($self) = @_;
+  my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
+  my $html   = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
+
+  $self->js->html('#create_print_all_dialog', $html);
+  $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
+  $self->js->render;
+}
+
+sub action_create_print_all_download {
+  my ($self) = @_;
+  my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
+
+  my $sfile  = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
+  die $! if !$sfile->fh;
+
+  my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
+  $sfile->fh->close;
+
+  my $type      = 'Invoices';
+  my $file_name =  t8($type) . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
+  $file_name    =~ s{[^\w\.]+}{_}g;
+
+  return $self->send_file(
+    \$merged_pdf,
+    type => 'application/pdf',
+    name => $file_name,
+  );
+}
+
+#
+# filters
+#
+
+sub init_js       { SL::ClientJS->new(controller => $_[0]) }
+sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
+sub init_invoice_ids { [] }
+
+sub init_sales_delivery_order_models {
+  my ($self) = @_;
+  return $self->_init_sales_delivery_order_models(sortby => 'donumber');
+}
+
+sub _init_sales_delivery_order_models {
+  my ($self, %params) = @_;
+
+  SL::Controller::Helper::GetModels->new(
+    controller   => $_[0],
+    model        => 'DeliveryOrder',
+    # model        => 'Order',
+    sorted       => {
+      _default     => {
+        by           => $params{sortby},
+        dir          => 1,
+      },
+      customer     => t8('Customer'),
+      employee     => t8('Employee'),
+      transdate    => t8('Date'),
+      donumber     => t8('Delivery Order Number'),
+      ordnumber     => t8('Order Number'),
+    },
+    with_objects => [ qw(customer employee) ],
+   query        => [
+      '!customer_id' => undef,
+      or             => [ closed    => undef, closed    => 0 ],
+      or             => [ delivered => undef, delivered => 0 ],
+    ],
+  );
+}
+
+
+sub init_invoice_models {
+  my ($self)             = @_;
+  my @invoice_ids = @{ $self->invoice_ids };
+
+  SL::Controller::Helper::GetModels->new(
+    controller   => $_[0],
+    model        => 'Invoice',
+    (paginated   => 0,) x !!@invoice_ids,
+    sorted       => {
+      _default     => {
+        by           => 'transdate',
+        dir          => 0,
+      },
+      customer     => t8('Customer'),
+      invnumber    => t8('Invoice Number'),
+      employee     => t8('Employee'),
+      donumber     => t8('Delivery Order Number'),
+      ordnumber    => t8('Order Number'),
+      reqdate      => t8('Delivery Date'),
+      transdate    => t8('Date'),
+    },
+    with_objects => [ qw(customer employee) ],
+    query        => [
+      '!customer_id' => undef,
+      (id            => \@invoice_ids) x !!@invoice_ids,
+    ],
+  );
+}
+
+
+sub init_default_printer_id {
+  my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
+  return $pr ? $pr->id : undef;
+}
+
+sub setup {
+  my ($self) = @_;
+  $::auth->assert('invoice_edit');
+
+  $::request->layout->use_javascript("${_}.js")  for qw(kivi.MassInvoiceCreatePrint);
+}
+
+#
+# helpers
+#
+
+sub create_pdfs {
+  my ($self, %params) = @_;
+
+  my @pdf_file_names;
+  foreach my $invoice (@{ $params{invoices} }) {
+    my %create_params = (
+      template  => $self->find_template(name => 'invoice', printer_id => $params{printer_id}),
+      variables => Form->new(''),
+      return    => 'file_name',
+    );
+
+    $create_params{variables}->{$_} = $params{variables}->{$_} for keys %{ $params{variables} };
+
+    $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
+    $create_params{variables}->prepare_for_printing;
+
+    push @pdf_file_names, $self->create_pdf(%create_params);
+  }
+
+  return @pdf_file_names;
+}
+
+sub download_or_print_documents {
+  my ($self, %params) = @_;
+
+  my @pdf_file_names;
+
+  eval {
+    my %pdf_params = (
+      invoices        => $params{invoices},
+      printer_id      => $params{printer_id},
+      variables       => {
+        type        => 'invoice',
+        formname    => 'invoice',
+        format      => 'pdf',
+        media       => $params{printer_id} ? 'printer' : 'file',
+      });
+
+    @pdf_file_names = $self->create_pdfs(%pdf_params);
+    my $merged_pdf  = $self->merge_pdfs(file_names => \@pdf_file_names);
+    unlink @pdf_file_names;
+
+    if (!$params{printer_id}) {
+      my $file_name =  t8("Invoices") . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
+      $file_name    =~ s{[^\w\.]+}{_}g;
+
+      return $self->send_file(
+        \$merged_pdf,
+        type => 'application/pdf',
+        name => $file_name,
+      );
+    }
+
+    my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
+    my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
+
+    open my $out, '|-', $command or die $!;
+    binmode $out;
+    print $out $merged_pdf;
+    close $out;
+
+    flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
+    return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
+
+  } or do {
+    unlink @pdf_file_names;
+    $::form->error(t8("Creating the PDF failed:") . " " . $@);
+  };
+}
+
+sub make_filter_summary {
+  my ($self) = @_;
+
+  my $filter = $::form->{filter} || {};
+  my @filter_strings;
+
+  my @filters = (
+    [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
+    [ $filter->{"transdate:date::ge"},           t8('Delivery Date') . " " . t8('From Date') ],
+    [ $filter->{"transdate:date::le"},           t8('Delivery Date') . " " . t8('To Date')   ],
+  );
+
+  for (@filters) {
+    push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
+  }
+
+  $self->{filter_summary} = join ', ', @filter_strings;
+}
+1;
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
+
+=head2 OVERVIEW
+
+Controller class for the conversion and processing (printing) of objects.
+
+
+Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
+In general there are two major distinctions:
+This class implements the conversion and the printing via clickable action AND triggers the same
+conversion towards a Background-Job with a good user interaction.
+
+Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
+in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
+($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
+and in the background job, i.e. compare the actions create and print.
+From a reverse engineering point of view the actions in this controller were written before the
+background job existed, therefore if anything goes boom take a look at the single steps done via gui
+in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
+
+
+=head1 FUNCTIONS
+
+=over 2
+
+=item C<action_list_sales_delivery_orders>
+
+List all open sales delivery orders. The filter can be in two states show or "no show" the
+original, probably gorash idea, is to increase performance and not to be forced to click on the
+next button (like in all other reports). Therefore use this option and this filter for a good
+project default and hide it again. Filters can be added in _filter.html. Take a look at
+  SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
+
+=item C<action_create_invoices>
+
+Creates or to be more correctly converts delivery orders to invoices. All items are
+converted 1:1 and after conversion the delivery order(s) are closed.
+
+=item C<action_list_invoices>
+
+List the created invoices, if created via gui (see action above)
+
+=item C<action_print>
+
+Print the selected invoices. Yes, it really is all boring linear (see action above).
+Calls download_or_print method.
+
+=item C<action_create_print_all_start>
+
+Initialises the webform for the creation and printing via background job. Now we get to
+the more fun part ...  Mosu did a great user interaction job here, we can choose how many
+objects are converted in one strike and if or if not they are downloadable or will be sent to
+a printer (if defined as a printing command) right away.
+Background job is started and status is watched via js and the next action.
+
+=item C<action_create_print_all_status>
+
+Action for watching status, default is refreshing every 5 seconds
+
+=item C<action_create_print_all_download>
+
+If the above is done (did I already said: boring linear?). Documents will
+be either printed or downloaded.
+
+=item C<init_js>
+
+Inits js/kivi.MassInvoiceCreatePrint;
+
+=item C<init_printers>
+
+Gets all printer commands
+
+=item C<init_invoice_ids>
+
+Gets a list of (empty) invoice ids
+
+=item C<init_sales_delivery_order_models>
+
+Calls _init_sales_delivery_order_models with a param
+
+=item C<_init_sales_delivery_order_models>
+
+Private function, called by init_sales_delivery_order_models.
+Gets all open sales delivery orders.
+
+=item C<init_invoice_models>
+
+Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
+
+=item C<init_default_printer_id>
+
+Gets the default printer for sales_invoices. Maybe this function is not used, but
+might be useful in the next version (working in client project).
+
+=item C<setup>
+
+Currently sets / checks only the access right.
+
+=item C<create_pdfs>
+
+=item C<download_or_print_documents>
+
+Backend function for printing or downloading docs. Only used for gui processing (called
+via action_print).
+
+=item C<make_filter_summary>
+Creates the filter option summary in the header. By the time of writing three filters are
+supported: Customer and date from/to of the Delivery Order (database field transdate).
+
+=back
+
+=head1 TODO
+
+Should be more generalized. Right now just one conversion (delivery order to invoice) is supported.
+Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
+was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
+stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
+
+Filtering needs to be extended for Delivery Order Number (Natural Sort).
+
+A second printer (copy) needs to be implemented.
+
+Both todos are marked in the template code.
+
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+Jan Büren E<lt>jan@kivitendo-premium.deE<gt>
+=cut