From b7e394f250c6bfb1d8ec126d25e6fd5b40241ff0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jan=20B=C3=BCren?= Date: Fri, 4 Sep 2015 13:14:10 +0200 Subject: [PATCH] Massenkonvertierung von Lieferscheinen nach Rechnung ink. Druck MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 --- .../MassRecordCreationAndPrinting.pm | 270 ++++++++++ SL/Controller/MassInvoiceCreatePrint.pm | 497 ++++++++++++++++++ js/kivi.MassInvoiceCreatePrint.js | 77 +++ menus/user/00-erp.yaml | 13 + .../_create_print_all_status.html | 106 ++++ .../_create_print_all_step_1.html | 37 ++ .../_filter.html | 46 ++ .../list_invoices.html | 77 +++ .../list_sales_delivery_orders.html | 60 +++ 9 files changed, 1183 insertions(+) create mode 100644 SL/BackgroundJob/MassRecordCreationAndPrinting.pm create mode 100644 SL/Controller/MassInvoiceCreatePrint.pm create mode 100644 js/kivi.MassInvoiceCreatePrint.js create mode 100644 templates/webpages/mass_invoice_create_print_from_do/_create_print_all_status.html create mode 100644 templates/webpages/mass_invoice_create_print_from_do/_create_print_all_step_1.html create mode 100644 templates/webpages/mass_invoice_create_print_from_do/_filter.html create mode 100644 templates/webpages/mass_invoice_create_print_from_do/list_invoices.html create mode 100644 templates/webpages/mass_invoice_create_print_from_do/list_sales_delivery_orders.html 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 %] + + + + + [% END %] +
[% LxERP.t8("Delivery Order") %][% LxERP.t8("Error") %]
[% 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 %] +
[% 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 %] + + + + + [% END %] +
[% LxERP.t8("Invoice") %][% LxERP.t8("Error") %]
[% 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 %] +
+

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 %] +
+
+
+ [% LxERP.t8('Show Filter') %] + [% SELF.filter_summary %] +
+ +
+ [% LxERP.t8('Hide Filter') %] + + + + + + + + + + + + + + + +
[% LxERP.t8('Customer') %][% L.input_tag('filter.customer.name:substr::ilike', filter.customer.name_substr__ilike, size = 20) %]
[% LxERP.t8('Delivery Date') %] [% LxERP.t8('From Date') %][% L.date_tag('filter.transdate:date::ge', filter.transdate_date__ge) %]
[% LxERP.t8('Delivery Date') %] [% LxERP.t8('To Date') %][% L.date_tag('filter.transdate:date::le', filter.transdate_date__le) %]
+ + [% L.hidden_tag('action', 'MassInvoiceCreatePrint/dispatch') %] + [% L.hidden_tag('sort_by', FORM.sort_by) %] + [% L.hidden_tag('sort_dir', FORM.sort_dir) %] + [% L.hidden_tag('page', FORM.page) %] + [% L.submit_tag(LIST_ACTION, LxERP.t8('Continue'))%] + + [% LxERP.t8('Reset') %] + +
+ +
+
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 %] + +
+ + + + + + + + + + + + + + + [%- FOREACH invoice = invoices %] + [% invoice_id = invoice.id + delivery_order = invoice.delivery_order %] + + + + + + + + + + [%- END %] + +
[% L.checkbox_tag("", id="check_all", checkall="[data-checkall=1]") %][% L.sortable_table_header("transdate") %][% L.sortable_table_header("reqdate") %][% L.sortable_table_header("invnumber") %][% L.sortable_table_header("donumber") %][% L.sortable_table_header("customer") %][% LxERP.t8("Shipto") %]
[% L.checkbox_tag('id[]', value=invoice.id, "data-checkall"=1, checked=selected_ids.$invoice_id) %][% HTML.escape(invoice.transdate_as_date) %][% HTML.escape(invoice.deliverydate_as_date) %][% L.link(SELF.url_for(controller="is.pl", action="edit", type="sales_invoice", id=invoice.id), invoice.invnumber) %] + [% IF delivery_order %] + [% L.link(SELF.url_for(controller="do.pl", action="edit", id=delivery_order.id), delivery_order.donumber) %] + [% ELSE %] + [% HTML.escape(invoice.donumber) %] + [% END %] + [% HTML.escape(invoice.customer.name) %][% HTML.escape(SELF.make_shipto_title(invoice.shipto || delivery_order.custom_shipto)) %]
+ + [% IF !SELF.invoice_ids.size %] + [% L.paginate_controls %] + [% END %] + +
+ + [% IF SELF.printers.size %] +

+ [% LxERP.t8("Print destination") %]: + [% SET printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ; + CALL printers.import(SELF.printers); + L.select_tag("printer_id", printers, title_key="description", default=FORM.printer_id) %] +

+ [% END %] + +

+ [% L.hidden_tag("action", "MassInvoiceCreatePrint/dispatch") %] + [% L.submit_tag("action_print", LxERP.t8("Print")) %] +

+
+[%- 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 %] +
+ [% IF !sales_delivery_orders.size %] +

+ [%- LxERP.t8("There are currently no open sales delivery orders.") %] +

+ [%- ELSE %] + + + + + + + + + + + + + [%- FOREACH sales_delivery_order = sales_delivery_orders %] + + + + + + + + [%- END %] + +
[% L.checkbox_tag("", id="check_all", checkall="[data-checkall=1]") %][% L.sortable_table_header("transdate") %][% L.sortable_table_header("donumber") %][% L.sortable_table_header("ordnumber") %][% L.sortable_table_header("customer") %]
[% L.checkbox_tag('id[]', value=sales_delivery_order.id, "data-checkall"=1) %][% HTML.escape(sales_delivery_order.transdate_as_date) %][% L.link(SELF.url_for(controller="do.pl", action="edit", type="sales_delivery_order", id=sales_delivery_order.id), sales_delivery_order.donumber) %][% L.link(SELF.url_for(controller="oe.pl", action="edit", type="sales_order", id=sales_delivery_order.sales_order.id), sales_delivery_order.ordnumber) %][% HTML.escape(sales_delivery_order.customer.name) %]
+ + [% L.paginate_controls %] + +
+ +

+ [% L.hidden_tag("action", "MassInvoiceCreatePrint/create_invoices") %] + [% L.button_tag("", LxERP.t8("Create invoices"), name="create_button") %] + [% L.button_tag("", LxERP.t8("For all delivery orders create and print invoices"), name="create_print_all_button") %] +

+ + [%- END %] +
+[%- END %] -- 2.20.1