From: Jan Büren
Date: Fri, 4 Sep 2015 11:14:10 +0000 (+0200)
Subject: Massenkonvertierung von Lieferscheinen nach Rechnung ink. Druck
X-Git-Tag: release-3.4.1~752^2
X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=b7e394f250c6bfb1d8ec126d25e6fd5b40241ff0;p=kivitendo-erp.git
Massenkonvertierung von Lieferscheinen nach Rechnung ink. Druck
Die Konvertierung als auch das Generieren des PDFs erfolgt als
Background-Job. Entsprechend muss der task_server für den.
Mandanten eingestellt sein.
Details und bekannte offene Punkte im POD der beiden Perl-Module.
Folgecommit: changelog und all
---
diff --git a/SL/BackgroundJob/MassRecordCreationAndPrinting.pm b/SL/BackgroundJob/MassRecordCreationAndPrinting.pm
new file mode 100644
index 000000000..458d47a4f
--- /dev/null
+++ b/SL/BackgroundJob/MassRecordCreationAndPrinting.pm
@@ -0,0 +1,270 @@
+package SL::BackgroundJob::MassRecordCreationAndPrinting;
+
+use strict;
+use warnings;
+
+use parent qw(SL::BackgroundJob::Base);
+
+use SL::DB::DeliveryOrder;
+use SL::DB::Order; # origin order to delivery_order
+use SL::DB::Invoice;
+use SL::DB::Printer;
+use SL::SessionFile;
+use SL::Template;
+
+use constant WAITING_FOR_EXECUTION => 0;
+use constant CONVERTING_DELIVERY_ORDERS => 1;
+use constant PRINTING_INVOICES => 2;
+use constant DONE => 3;
+# Data format:
+# my $data = {
+# record_ids => [ 123, 124, 127, ],
+# printer_id => 4711,
+# num_created => 0,
+# num_printed => 0,
+# invoice_ids => [ 234, 235, ],
+# conversion_errors => [ { id => 124, number => 'A981723', message => "Stuff went boom" }, ],
+# print_errors => [ { id => 234, number => 'L87123123', message => "Printer is out of coffee" }, ],
+# pdf_file_name => 'qweqwe.pdf',
+# };
+
+sub create_invoices {
+ my ($self) = @_;
+
+ my $job_obj = $self->{job_obj};
+ my $db = $job_obj->db;
+
+ $job_obj->set_data(status => CONVERTING_DELIVERY_ORDERS())->save;
+
+ foreach my $delivery_order_id (@{ $job_obj->data_as_hash->{record_ids} }) {
+ my $number = $delivery_order_id;
+ my $data = $job_obj->data_as_hash;
+
+ eval {
+ my $invoice;
+ my $sales_delivery_order = SL::DB::DeliveryOrder->new(id => $delivery_order_id)->load;
+ $number = $sales_delivery_order->donumber;
+
+ if (!$db->do_transaction(sub {
+ $invoice = $sales_delivery_order->convert_to_invoice(item_filter => \&delivery_order_item_filter, queue_sort => 1) || die $db->error;
+ # $delivery_order->post_save_sanity_check; # just a hint at e8521eee (#90 od)
+ 1;
+ })) {
+ die $db->error;
+ }
+
+ $data->{num_created}++;
+ push @{ $data->{invoice_ids} }, $invoice->id;
+ push @{ $self->{invoices} }, $invoice;
+
+ 1;
+ } or do {
+ push @{ $data->{conversion_errors} }, { id => $delivery_order_id, number => $number, message => $@ };
+ };
+
+ $job_obj->update_attributes(data_as_hash => $data);
+ }
+}
+
+sub convert_invoices_to_pdf {
+ my ($self) = @_;
+
+ return if !@{ $self->{invoices} };
+
+ my $job_obj = $self->{job_obj};
+ my $db = $job_obj->db;
+
+ $job_obj->set_data(status => PRINTING_INVOICES())->save;
+
+ require SL::Controller::MassInvoiceCreatePrint;
+
+ my $printer_id = $job_obj->data_as_hash->{printer_id};
+ my $ctrl = SL::Controller::MassInvoiceCreatePrint->new;
+ my %variables = (
+ type => 'invoice',
+ formname => 'invoice',
+ format => 'pdf',
+ media => $printer_id ? 'printer' : 'file',
+ );
+
+ my @pdf_file_names;
+
+ foreach my $invoice (@{ $self->{invoices} }) {
+ my $data = $job_obj->data_as_hash;
+
+ eval {
+ my %create_params = (
+ template => $ctrl->find_template(name => 'invoice', printer_id => $printer_id),
+ variables => Form->new(''),
+ return => 'file_name',
+ );
+
+ $create_params{variables}->{$_} = $variables{$_} for keys %variables;
+
+ $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
+ $create_params{variables}->prepare_for_printing;
+
+ push @pdf_file_names, $ctrl->create_pdf(%create_params);
+
+ $data->{num_printed}++;
+
+ 1;
+
+ } or do {
+ push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => $@ };
+ };
+
+ $job_obj->update_attributes(data_as_hash => $data);
+ }
+
+ if (@pdf_file_names) {
+ my $data = $job_obj->data_as_hash;
+
+ eval {
+ $self->{merged_pdf} = $ctrl->merge_pdfs(file_names => \@pdf_file_names);
+ unlink @pdf_file_names;
+
+ if (!$printer_id) {
+ my $file_name = 'mass_invoice' . $job_obj->id . '.pdf';
+ my $sfile = SL::SessionFile->new($file_name, mode => 'w');
+ $sfile->fh->print($self->{merged_pdf});
+ $sfile->fh->close;
+
+ $data->{pdf_file_name} = $file_name;
+ }
+
+ 1;
+
+ } or do {
+ push @{ $data->{print_errors} }, { message => $@ };
+ };
+
+ $job_obj->update_attributes(data_as_hash => $data);
+ }
+}
+
+sub print_pdfs {
+ my ($self) = @_;
+
+ my $job_obj = $self->{job_obj};
+ my $data = $job_obj->data_as_hash;
+ my $printer_id = $data->{printer_id};
+
+ return if !$printer_id;
+
+ my $printer = SL::DB::Printer->new(id => $printer_id)->load;
+ my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
+ my $out;
+
+ if (!open $out, '|-', $command) {
+ push @{ $data->{print_errors} }, { message => $::locale->text('Could not execute printer command: #1', $!) };
+ $job_obj->update_attributes(data_as_hash => $data);
+ return;
+ }
+
+ binmode $out;
+ print $out $self->{merged_pdf};
+ close $out;
+}
+
+sub run {
+ my ($self, $job_obj) = @_;
+
+ $self->{job_obj} = $job_obj;
+ $self->{invoices} = [];
+
+ $self->create_invoices;
+ $self->convert_invoices_to_pdf;
+ $self->print_pdfs;
+
+ $job_obj->set_data(status => DONE())->save;
+
+ return 1;
+}
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::BackgroundJob::MassRecordCreationAndPrinting
+
+
+=head1 SYNOPSIS
+
+In controller:
+
+use SL::BackgroundJob::MassRecordCreationAndPrinting
+
+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;
+
+=head1 OVERVIEW
+
+This background job has 4 states which are described by the four constants above.
+
+=over 2
+
+=item * WAITING_FOR_EXECUTION
+ Background has been initialised and needs to be picked up by the task_server
+
+=item * CONVERTING_DELIVERY_ORDERS
+ Object conversion
+
+=item * PRINTING_INVOICES
+ Printing, if done via print command
+
+=item * DONE
+ To release the process and for the user information
+
+=back
+
+=head1 FUNCTIONS
+
+=over 2
+
+=item C
+
+Converts the source objects (DeliveryOrder) to destination objects (Invoice).
+On success objects will be saved.
+
+=item C
+
+Takes the new destination objects and merges them via print template in one pdf.
+
+=item C
+
+Sent the pdf to the printer command (if checked).
+
+=back
+
+=head1 BUGS
+Currently the calculation from the gui (form) differs from the calculation via convert (PTC).
+Furthermore mass conversion with foreign currencies could lead to problems (daily rate check).
+
+=head1 AUTHOR
+
+Moritz Bunkus Em.bunkus@linet-services.deE
+
+Jan Büren Ejan@kivitendo-premium.deE
+=cut
diff --git a/SL/Controller/MassInvoiceCreatePrint.pm b/SL/Controller/MassInvoiceCreatePrint.pm
new file mode 100644
index 000000000..07147575f
--- /dev/null
+++ b/SL/Controller/MassInvoiceCreatePrint.pm
@@ -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
+
+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
+
+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
+
+List the created invoices, if created via gui (see action above)
+
+=item C
+
+Print the selected invoices. Yes, it really is all boring linear (see action above).
+Calls download_or_print method.
+
+=item C
+
+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 for watching status, default is refreshing every 5 seconds
+
+=item C
+
+If the above is done (did I already said: boring linear?). Documents will
+be either printed or downloaded.
+
+=item C
+
+Inits js/kivi.MassInvoiceCreatePrint;
+
+=item C
+
+Gets all printer commands
+
+=item C
+
+Gets a list of (empty) invoice ids
+
+=item C
+
+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
+
+Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
+
+=item C
+
+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
+
+Currently sets / checks only the access right.
+
+=item C
+
+=item C
+
+Backend function for printing or downloading docs. Only used for gui processing (called
+via action_print).
+
+=item C
+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 Em.bunkus@linet-services.deE
+
+Jan Büren Ejan@kivitendo-premium.deE
+=cut
diff --git a/js/kivi.MassInvoiceCreatePrint.js b/js/kivi.MassInvoiceCreatePrint.js
new file mode 100644
index 000000000..b27ab6a81
--- /dev/null
+++ b/js/kivi.MassInvoiceCreatePrint.js
@@ -0,0 +1,77 @@
+namespace('kivi.MassInvoiceCreatePrint', function(ns) {
+ this.checkSalesOrderSelection = function() {
+ if ($("[data-checkall=1]:checked").size() > 0)
+ return true;
+ alert(kivi.t8('No delivery orders have been selected.'));
+ return false;
+ };
+
+ this.checkDeliveryOrderSelection = function() {
+ if ($("[data-checkall=1]:checked").size() > 0)
+ return true;
+ alert(kivi.t8('No delivery orders have been selected.'));
+ return false;
+ };
+ this.checkInvoiceSelection = function() {
+ if ($("[data-checkall=1]:checked").size() > 0)
+ return true;
+ alert(kivi.t8('No invoices have been selected.'));
+ return false;
+ };
+
+ this.submitMassCreationForm = function() {
+ if (!kivi.MassInvoiceCreatePrint.checkDeliveryOrderSelection())
+ return false;
+
+ $('body').addClass('loading');
+ $('form').submit();
+ return false;
+ };
+
+ this.createPrintAllInitialize = function() {
+ kivi.popup_dialog({
+ id: 'create_print_all_dialog',
+ dialog: {
+ title: kivi.t8('Create and print all invoices')
+ }
+ });
+ };
+
+ this.createPrintAllStartProcess = function() {
+ $('#cpa_start_process_button,.ui-dialog-titlebar button.ui-dialog-titlebar-close').prop('disabled', 'disabled');
+ $('#cpa_start_process_abort_link').remove();
+
+ var data = {
+ number_of_invoices: $('#cpa_number_of_invoices').val(),
+ printer_id: $('#cpa_printer_id').val()
+ };
+ kivi.submit_ajax_form('controller.pl?action=MassInvoiceCreatePrint/create_print_all_start', '[name^=filter\\.]', data);
+ };
+
+ this.createPrintAllFinishProcess = function() {
+ $('#create_print_all_dialog').dialog('close');
+ window.location.href = 'controller.pl?action=MassInvoiceCreatePrint%2flist_invoices&noshow=1';
+ };
+
+ this.massConversionStarted = function() {
+ $('#create_print_all_dialog').data('timerId', setInterval(function() {
+ $.get("controller.pl", {
+ action: 'MassInvoiceCreatePrint/create_print_all_status',
+ job_id: $('#cpa_job_id').val()
+ }, kivi.eval_json_result);
+ }, 5000));
+ };
+
+ this.massConversionFinished = function() {
+ clearInterval($('#create_print_all_dialog').data('timerId'));
+ $('.ui-dialog-titlebar button.ui-dialog-titlebar-close').prop('disabled', '')
+ };
+
+ this.setup = function() {
+ $('#create_button').click(kivi.MassInvoiceCreatePrint.submitMassCreationForm);
+ $('#create_print_all_button').click(kivi.MassInvoiceCreatePrint.createPrintAllInitialize);
+ $('#action_print').click(kivi.MassInvoiceCreatePrint.checkInvoiceSelection);
+ };
+});
+
+$(kivi.MassInvoiceCreatePrint.setup);
diff --git a/menus/user/00-erp.yaml b/menus/user/00-erp.yaml
index 4afdae154..035d562c2 100644
--- a/menus/user/00-erp.yaml
+++ b/menus/user/00-erp.yaml
@@ -265,6 +265,19 @@
module: letter.pl
params:
action: add
+- parent: ar
+ id: ar_invoices
+ name: Invoices
+ icon: sales_invoice_add
+ order: 850
+- parent: ar_invoices
+ id: ar_invoices_mass_add_sales_invoice
+ name: Mass Create Print Sales Invoice from Delivery Order
+ order: 100
+ access: invoice_edit
+ params:
+ noshow: 1
+ action: MassInvoiceCreatePrint/list_sales_delivery_orders
- parent: ar
id: ar_reports
name: Reports
diff --git a/templates/webpages/mass_invoice_create_print_from_do/_create_print_all_status.html b/templates/webpages/mass_invoice_create_print_from_do/_create_print_all_status.html
new file mode 100644
index 000000000..15f25f0f7
--- /dev/null
+++ b/templates/webpages/mass_invoice_create_print_from_do/_create_print_all_status.html
@@ -0,0 +1,106 @@
+[%- USE LxERP -%][%- USE L -%][%- USE HTML -%]
+[% SET data = job.data_as_hash %]
+[% LxERP.t8("Step 2 -- Watch status") %]
+
+[% L.hidden_tag('', job.id, id="cpa_job_id") %]
+
+
+ [% LxERP.t8("This status output will be refreshed every five seconds.") %]
+
+
+
+ [% IF data.status < 3 %]
+ [% L.link("login.pl?action=company_logo", LxERP.t8("Open new tab"), target="_blank") %]
+
+ [% ELSE %]
+ [% IF data.pdf_file_name %]
+ [% L.link(SELF.url_for(action="create_print_all_download", job_id=job.id), LxERP.t8("Download PDF")) %]
+ [% END %]
+ [% L.link("#", LxERP.t8("Close window"), onclick="kivi.MassInvoiceCreatePrint.createPrintAllFinishProcess();") %]
+[% END %]
+
+
+
+
+
+ [% LxERP.t8("Current status:") %] |
+
+ [% IF !data.status %]
+ [% LxERP.t8("waiting for job to be started") %]
+ [% ELSIF data.status == 1 %]
+ [% LxERP.t8("Creating invoices") %]
+ [% ELSIF data.status == 2 %]
+ [% LxERP.t8("Printing invoices (this can take a while)") %]
+ [% ELSE %]
+ [% LxERP.t8("Done.") %]
+ [% IF data.pdf_file_name %]
+ [% LxERP.t8("The file is available for download.") %]
+ [% ELSIF data.printer_id %]
+ [% LxERP.t8("The file has been sent to the printer.") %]
+ [% END %]
+ [% END %]
+ |
+
+
+
+ [% LxERP.t8("Number of invoices created:") %] |
+ [% IF data.status > 0 %][% HTML.escape(data.num_created) %] / [% HTML.escape(data.record_ids.size) %][% ELSE %]â[% END %] |
+
+
+
+ [% LxERP.t8("Number of invoices printed:") %] |
+ [% IF data.status > 1 %][% HTML.escape(data.num_printed) %] / [% HTML.escape(data.invoice_ids.size) %][% ELSE %]â[% END %] |
+
+
+
+ [% LxERP.t8("Errors during conversion:") %] |
+
+[% IF !data.status %]
+ â
+[% ELSIF !data.conversion_errors.size %]
+ [% LxERP.t8("No errors have occurred.") %]
+[% ELSE %]
+
+
+
+ [% FOREACH error = data.conversion_errors %]
+
+ [% IF error.id %][% L.link(SELF.url_for(controller='do.pl', action='edit', type='sales_delivery_order', id=error.id), HTML.escape(error.number), target="_blank") %][% ELSE %]â[% END %] |
+ [% HTML.escape(error.message) %] |
+
+ [% END %]
+
+[% END %]
+ |
+
+
+
+ [% LxERP.t8("Errors during printing:") %] |
+
+[% IF data.status < 2 %]
+ â
+[% ELSIF !data.print_errors.size %]
+ [% LxERP.t8("No errors have occurred.") %]
+[% ELSE %]
+
+
+
+ [% FOREACH error = data.print_errors %]
+
+ [% IF error.id %][% L.link(SELF.url_for(controller='is.pl', action='edit', type='sales_invoice',id=error.id), HTML.escape(error.number), target="_blank") %][% ELSE %]â[% END %] |
+ [% HTML.escape(error.message) %] |
+
+ [% END %]
+
+[% END %]
+ |
+
+
+
+
diff --git a/templates/webpages/mass_invoice_create_print_from_do/_create_print_all_step_1.html b/templates/webpages/mass_invoice_create_print_from_do/_create_print_all_step_1.html
new file mode 100644
index 000000000..794f51865
--- /dev/null
+++ b/templates/webpages/mass_invoice_create_print_from_do/_create_print_all_step_1.html
@@ -0,0 +1,37 @@
+[%- USE LxERP -%][%- USE L -%]
+[%- SET num_delivery_orders = SELF.sales_delivery_order_models.count %]
+[% LxERP.t8("Step 1 -- limit number of delivery orders to process") %]
+
+
+ [% LxERP.t8("Currently #1 delivery orders can be converted into invoices and printed.", num_delivery_orders) %]
+ [% LxERP.t8("How many do you want to create and print?") %]
+
+
+
+
+ [% LxERP.t8("Number of invoices to create") %]: |
+ [% L.input_tag('', num_delivery_orders, size="5", id="cpa_number_of_invoices") %] |
+
+
+
+ [% LxERP.t8("Print destination") %]: |
+
+ [% SET printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ;
+ CALL printers.import(SELF.printers);
+ L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]
+ |
+
+
+
+
+
+ [% L.button_tag("kivi.MassInvoiceCreatePrint.createPrintAllStartProcess();", LxERP.t8("Start process"), id="cpa_start_process_button") %]
+ [% L.link("#", LxERP.t8("Abort"), onclick="\$('#create_print_all_dialog').dialog('close');", id="cpa_start_process_abort_link") %]
+
diff --git a/templates/webpages/mass_invoice_create_print_from_do/_filter.html b/templates/webpages/mass_invoice_create_print_from_do/_filter.html
new file mode 100644
index 000000000..fbddc5b62
--- /dev/null
+++ b/templates/webpages/mass_invoice_create_print_from_do/_filter.html
@@ -0,0 +1,46 @@
+[%- USE L %][%- USE LxERP %][%- USE HTML %]
+
diff --git a/templates/webpages/mass_invoice_create_print_from_do/list_invoices.html b/templates/webpages/mass_invoice_create_print_from_do/list_invoices.html
new file mode 100644
index 000000000..8bec62a02
--- /dev/null
+++ b/templates/webpages/mass_invoice_create_print_from_do/list_invoices.html
@@ -0,0 +1,77 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+
+[% FORM.title %]
+
+[%- INCLUDE "common/flash.html" %]
+
+[% LIST_ACTION = 'action_list_invoices' %]
+[%- PROCESS 'mass_invoice_create_print_from_do/_filter.html' filter=SELF.filter %]
+
+[% IF noshow == 1 %]
+[% invoices = SELF.invoice_models.get;
+ MODELS = SELF.invoice_models %]
+[%- IF !invoices.size %]
+
+ [%- LxERP.t8("There are currently no open invoices, or none matches your filter conditions.") %]
+
+[%- ELSE %]
+
+
+[%- END %]
+[%- END %]
diff --git a/templates/webpages/mass_invoice_create_print_from_do/list_sales_delivery_orders.html b/templates/webpages/mass_invoice_create_print_from_do/list_sales_delivery_orders.html
new file mode 100644
index 000000000..7d2ac038b
--- /dev/null
+++ b/templates/webpages/mass_invoice_create_print_from_do/list_sales_delivery_orders.html
@@ -0,0 +1,60 @@
+[% USE Dumper %][% USE HTML %][% USE L %][% USE LxERP %]
+
+
+[% FORM.title %]
+
+[%- INCLUDE "common/flash.html" %]
+
+[% LIST_ACTION = 'action_list_sales_delivery_orders' %]
+[% SET MODELS = SELF.sales_delivery_order_models;
+ dummy = MODELS.finalize %]
+
+[%- PROCESS 'mass_invoice_create_print_from_do/_filter.html' filter=SELF.sales_delivery_order_models.filtered.laundered %]
+
+[% IF noshow == 1 %]
+ [% SET sales_delivery_orders = MODELS.get %]
+
+[%- END %]