X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/mfinanz.git/blobdiff_plain/f515825d424cb58707e50d2bcfc4f6093876917c..b7e394f250c6bfb1d8ec126d25e6fd5b40241ff0:/SL/Controller/MassInvoiceCreatePrint.pm 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