Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision b7e394f2

Von Jan Büren vor mehr als 9 Jahren hinzugefügt

  • ID b7e394f250c6bfb1d8ec126d25e6fd5b40241ff0
  • Vorgänger f515825d
  • Nachfolger cc6d0e60

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

Unterschiede anzeigen:

SL/BackgroundJob/MassRecordCreationAndPrinting.pm
1
package SL::BackgroundJob::MassRecordCreationAndPrinting;
2

  
3
use strict;
4
use warnings;
5

  
6
use parent qw(SL::BackgroundJob::Base);
7

  
8
use SL::DB::DeliveryOrder;
9
use SL::DB::Order;  # origin order to delivery_order
10
use SL::DB::Invoice;
11
use SL::DB::Printer;
12
use SL::SessionFile;
13
use SL::Template;
14

  
15
use constant WAITING_FOR_EXECUTION       => 0;
16
use constant CONVERTING_DELIVERY_ORDERS  => 1;
17
use constant PRINTING_INVOICES           => 2;
18
use constant DONE                        => 3;
19
# Data format:
20
# my $data             = {
21
#   record_ids          => [ 123, 124, 127, ],
22
#   printer_id         => 4711,
23
#   num_created        => 0,
24
#   num_printed        => 0,
25
#   invoice_ids        => [ 234, 235, ],
26
#   conversion_errors  => [ { id => 124, number => 'A981723', message => "Stuff went boom" }, ],
27
#   print_errors       => [ { id => 234, number => 'L87123123', message => "Printer is out of coffee" }, ],
28
#   pdf_file_name      => 'qweqwe.pdf',
29
# };
30

  
31
sub create_invoices {
32
  my ($self)  = @_;
33

  
34
  my $job_obj = $self->{job_obj};
35
  my $db      = $job_obj->db;
36

  
37
  $job_obj->set_data(status => CONVERTING_DELIVERY_ORDERS())->save;
38

  
39
  foreach my $delivery_order_id (@{ $job_obj->data_as_hash->{record_ids} }) {
40
    my $number = $delivery_order_id;
41
    my $data   = $job_obj->data_as_hash;
42

  
43
    eval {
44
      my $invoice;
45
      my $sales_delivery_order = SL::DB::DeliveryOrder->new(id => $delivery_order_id)->load;
46
      $number                  = $sales_delivery_order->donumber;
47

  
48
      if (!$db->do_transaction(sub {
49
        $invoice = $sales_delivery_order->convert_to_invoice(item_filter => \&delivery_order_item_filter, queue_sort => 1) || die $db->error;
50
        # $delivery_order->post_save_sanity_check; # just a hint at e8521eee (#90 od)
51
        1;
52
      })) {
53
        die $db->error;
54
      }
55

  
56
      $data->{num_created}++;
57
      push @{ $data->{invoice_ids} }, $invoice->id;
58
      push @{ $self->{invoices}    }, $invoice;
59

  
60
      1;
61
    } or do {
62
      push @{ $data->{conversion_errors} }, { id => $delivery_order_id, number => $number, message => $@ };
63
    };
64

  
65
    $job_obj->update_attributes(data_as_hash => $data);
66
  }
67
}
68

  
69
sub convert_invoices_to_pdf {
70
  my ($self) = @_;
71

  
72
  return if !@{ $self->{invoices} };
73

  
74
  my $job_obj = $self->{job_obj};
75
  my $db      = $job_obj->db;
76

  
77
  $job_obj->set_data(status => PRINTING_INVOICES())->save;
78

  
79
  require SL::Controller::MassInvoiceCreatePrint;
80

  
81
  my $printer_id = $job_obj->data_as_hash->{printer_id};
82
  my $ctrl       = SL::Controller::MassInvoiceCreatePrint->new;
83
  my %variables  = (
84
    type         => 'invoice',
85
    formname     => 'invoice',
86
    format       => 'pdf',
87
    media        => $printer_id ? 'printer' : 'file',
88
  );
89

  
90
  my @pdf_file_names;
91

  
92
  foreach my $invoice (@{ $self->{invoices} }) {
93
    my $data = $job_obj->data_as_hash;
94

  
95
    eval {
96
      my %create_params = (
97
        template  => $ctrl->find_template(name => 'invoice', printer_id => $printer_id),
98
        variables => Form->new(''),
99
        return    => 'file_name',
100
      );
101

  
102
      $create_params{variables}->{$_} = $variables{$_} for keys %variables;
103

  
104
      $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
105
      $create_params{variables}->prepare_for_printing;
106

  
107
      push @pdf_file_names, $ctrl->create_pdf(%create_params);
108

  
109
      $data->{num_printed}++;
110

  
111
      1;
112

  
113
    } or do {
114
      push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => $@ };
115
    };
116

  
117
    $job_obj->update_attributes(data_as_hash => $data);
118
  }
119

  
120
  if (@pdf_file_names) {
121
    my $data = $job_obj->data_as_hash;
122

  
123
    eval {
124
      $self->{merged_pdf} = $ctrl->merge_pdfs(file_names => \@pdf_file_names);
125
      unlink @pdf_file_names;
126

  
127
      if (!$printer_id) {
128
        my $file_name = 'mass_invoice' . $job_obj->id . '.pdf';
129
        my $sfile     = SL::SessionFile->new($file_name, mode => 'w');
130
        $sfile->fh->print($self->{merged_pdf});
131
        $sfile->fh->close;
132

  
133
        $data->{pdf_file_name} = $file_name;
134
      }
135

  
136
      1;
137

  
138
    } or do {
139
      push @{ $data->{print_errors} }, { message => $@ };
140
    };
141

  
142
    $job_obj->update_attributes(data_as_hash => $data);
143
  }
144
}
145

  
146
sub print_pdfs {
147
  my ($self)     = @_;
148

  
149
  my $job_obj    = $self->{job_obj};
150
  my $data       = $job_obj->data_as_hash;
151
  my $printer_id = $data->{printer_id};
152

  
153
  return if !$printer_id;
154

  
155
  my $printer = SL::DB::Printer->new(id => $printer_id)->load;
156
  my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
157
  my $out;
158

  
159
  if (!open $out, '|-', $command) {
160
    push @{ $data->{print_errors} }, { message => $::locale->text('Could not execute printer command: #1', $!) };
161
    $job_obj->update_attributes(data_as_hash => $data);
162
    return;
163
  }
164

  
165
  binmode $out;
166
  print $out $self->{merged_pdf};
167
  close $out;
168
}
169

  
170
sub run {
171
  my ($self, $job_obj) = @_;
172

  
173
  $self->{job_obj}         = $job_obj;
174
  $self->{invoices} = [];
175

  
176
  $self->create_invoices;
177
  $self->convert_invoices_to_pdf;
178
  $self->print_pdfs;
179

  
180
  $job_obj->set_data(status => DONE())->save;
181

  
182
  return 1;
183
}
184

  
185
1;
186

  
187
__END__
188

  
189
=pod
190

  
191
=encoding utf8
192

  
193
=head1 NAME
194

  
195
SL::BackgroundJob::MassRecordCreationAndPrinting
196

  
197

  
198
=head1 SYNOPSIS
199

  
200
In controller:
201

  
202
use SL::BackgroundJob::MassRecordCreationAndPrinting
203

  
204
my $job              = SL::DB::BackgroundJob->new(
205
    type               => 'once',
206
    active             => 1,
207
    package_name       => 'MassRecordCreationAndPrinting',
208

  
209
  )->set_data(
210
    record_ids         => [ map { $_->id } @records[0..$num - 1] ],
211
    printer_id         => $::form->{printer_id},
212
    status             => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
213
    num_created        => 0,
214
    num_printed        => 0,
215
    invoice_ids        => [ ],
216
    conversion_errors  => [ ],
217
    print_errors       => [ ],
218

  
219
  )->update_next_run_at;
220
  SL::System::TaskServer->new->wake_up;
221

  
222
=head1 OVERVIEW
223

  
224
This background job has 4 states which are described by the four constants above.
225

  
226
=over 2
227

  
228
=item * WAITING_FOR_EXECUTION
229
  Background has been initialised and needs to be picked up by the task_server
230

  
231
=item * CONVERTING_DELIVERY_ORDERS
232
   Object conversion
233

  
234
=item * PRINTING_INVOICES
235
  Printing, if done via print command
236

  
237
=item * DONE
238
  To release the process and for the user information
239

  
240
=back
241

  
242
=head1 FUNCTIONS
243

  
244
=over 2
245

  
246
=item C<create_invoices>
247

  
248
Converts the source objects (DeliveryOrder) to destination objects (Invoice).
249
On success objects will be saved.
250

  
251
=item C<convert_invoices_to_pdf>
252

  
253
Takes the new destination objects and merges them via print template in one pdf.
254

  
255
=item C<print_pdfs>
256

  
257
Sent the pdf to the printer command (if checked).
258

  
259
=back
260

  
261
=head1 BUGS
262
Currently the calculation from the gui (form) differs from the calculation via convert (PTC).
263
Furthermore mass conversion with foreign currencies could lead to problems (daily rate check).
264

  
265
=head1 AUTHOR
266

  
267
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
268

  
269
Jan Büren E<lt>jan@kivitendo-premium.deE<gt>
270
=cut
SL/Controller/MassInvoiceCreatePrint.pm
1
package SL::Controller::MassInvoiceCreatePrint;
2

  
3
use strict;
4

  
5
use parent qw(SL::Controller::Base);
6

  
7
use File::Slurp ();
8
use List::MoreUtils qw(all uniq);
9
use List::Util qw(first min);
10

  
11
use SL::BackgroundJob::MassRecordCreationAndPrinting;
12
use SL::Controller::Helper::GetModels;
13
use SL::DB::DeliveryOrder;
14
use SL::DB::Order;
15
use SL::DB::Printer;
16
use SL::Helper::CreatePDF qw(:all);
17
use SL::Helper::Flash;
18
use SL::Locale::String;
19
use SL::SessionFile;
20
use SL::System::TaskServer;
21

  
22
use Rose::Object::MakeMethods::Generic
23
(
24
  'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id js) ],
25
);
26

  
27
__PACKAGE__->run_before('setup');
28

  
29
#
30
# actions
31
#
32

  
33
sub action_list_sales_delivery_orders {
34
  my ($self) = @_;
35

  
36
  # default is usually no show, exception here
37
  my $show = ($::form->{noshow} ? 0 : 1);
38
  delete $::form->{noshow};
39

  
40
  # if a filter is choosen, the filter info should be visible
41
  $self->make_filter_summary;
42
  $self->sales_delivery_order_models->get;
43
  $self->render('mass_invoice_create_print_from_do/list_sales_delivery_orders',
44
                noshow  => $show,
45
                title   => $::locale->text('Open sales delivery orders'));
46
}
47

  
48
sub action_create_invoices {
49
  my ($self) = @_;
50

  
51
  my @sales_delivery_order_ids = @{ $::form->{id} || [] };
52
  if (!@sales_delivery_order_ids) {
53
    # should never be executed, double catch via js
54
    flash_later('error', t8('No delivery orders have been selected.'));
55
    return $self->redirect_to(action => 'list_sales_delivery_orders');
56
  }
57

  
58
  my $db = SL::DB::Invoice->new->db;
59

  
60
  if (!$db->do_transaction(sub {
61
    my @invoices;
62
    foreach my $id (@sales_delivery_order_ids) {
63
      my $delivery_order    = SL::DB::DeliveryOrder->new(id => $id)->load;
64

  
65
      my $invoice = $delivery_order->convert_to_invoice() || die $db->error;
66
      push @invoices, $invoice;
67
    }
68

  
69
    my $key = sprintf('%d-%d', Time::HiRes::gettimeofday());
70
    $::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]);
71

  
72
    flash_later('info', t8('The invoices have been created. They\'re pre-selected below.'));
73
    $self->redirect_to(action => 'list_invoices', ids => $key);
74

  
75
    1;
76
  })) {
77
    $::lxdebug->message(LXDebug::WARN(), "Error: " . $db->error);
78
    $::form->error($db->error);
79
  }
80
}
81

  
82
sub action_list_invoices {
83
  my ($self) = @_;
84

  
85
  my $show = $::form->{noshow} ? 0 : 1;
86
  delete $::form->{noshow};
87

  
88
  if ($::form->{ids}) {
89
    my $key = 'MassInvoiceCreatePrint::ids-' . $::form->{ids};
90
    $self->invoice_ids($::auth->get_session_value($key) || []);
91
    $self->invoice_models->add_additional_url_params(ids => $::form->{ids});
92
  }
93

  
94
  my %selected_ids = map { +($_ => 1) } @{ $self->invoice_ids };
95

  
96
  $::form->{printer_id} ||= $self->default_printer_id;
97

  
98
  $self->render('mass_invoice_create_print_from_do/list_invoices',
99
                title        => $::locale->text('Open invoice'),
100
                noshow       => $show,
101
                selected_ids => \%selected_ids);
102
}
103

  
104
sub action_print {
105
  my ($self) = @_;
106

  
107
  my @invoices = map { SL::DB::Invoice->new(id => $_)->load } @{ $::form->{id} || [] };
108
  if (!@invoices) {
109
    flash_later('error', t8('No invoices have been selected.'));
110
    return $self->redirect_to(action => 'list_invoices');
111
  }
112

  
113
  $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices);
114
}
115

  
116
sub action_create_print_all_start {
117
  my ($self) = @_;
118

  
119
  $self->sales_delivery_order_models->disable_plugin('paginated');
120

  
121
  my @records           = @{ $self->sales_delivery_order_models->get };
122
  my $num              = min(scalar(@records), $::form->{number_of_invoices} // scalar(@records));
123

  
124
  my $job              = SL::DB::BackgroundJob->new(
125
    type               => 'once',
126
    active             => 1,
127
    package_name       => 'MassRecordCreationAndPrinting',
128

  
129
  )->set_data(
130
    record_ids         => [ map { $_->id } @records[0..$num - 1] ],
131
    printer_id         => $::form->{printer_id},
132
    status             => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
133
    num_created        => 0,
134
    num_printed        => 0,
135
    invoice_ids        => [ ],
136
    conversion_errors  => [ ],
137
    print_errors       => [ ],
138

  
139
  )->update_next_run_at;
140

  
141
  SL::System::TaskServer->new->wake_up;
142

  
143
  my $html = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
144

  
145
  $self->js
146
    ->html('#create_print_all_dialog', $html)
147
    ->run('kivi.MassInvoiceCreatePrint.massConversionStarted')
148
    ->render;
149
}
150

  
151
sub action_create_print_all_status {
152
  my ($self) = @_;
153
  my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
154
  my $html   = $self->render('mass_invoice_create_print_from_do/_create_print_all_status', { output => 0 }, job => $job);
155

  
156
  $self->js->html('#create_print_all_dialog', $html);
157
  $self->js->run('kivi.MassInvoiceCreatePrint.massConversionFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::MassRecordCreationAndPrinting->DONE();
158
  $self->js->render;
159
}
160

  
161
sub action_create_print_all_download {
162
  my ($self) = @_;
163
  my $job    = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
164

  
165
  my $sfile  = SL::SessionFile->new($job->data_as_hash->{pdf_file_name}, mode => 'r');
166
  die $! if !$sfile->fh;
167

  
168
  my $merged_pdf = do { local $/; my $fh = $sfile->fh; <$fh> };
169
  $sfile->fh->close;
170

  
171
  my $type      = 'Invoices';
172
  my $file_name =  t8($type) . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
173
  $file_name    =~ s{[^\w\.]+}{_}g;
174

  
175
  return $self->send_file(
176
    \$merged_pdf,
177
    type => 'application/pdf',
178
    name => $file_name,
179
  );
180
}
181

  
182
#
183
# filters
184
#
185

  
186
sub init_js       { SL::ClientJS->new(controller => $_[0]) }
187
sub init_printers { SL::DB::Manager::Printer->get_all_sorted }
188
sub init_invoice_ids { [] }
189

  
190
sub init_sales_delivery_order_models {
191
  my ($self) = @_;
192
  return $self->_init_sales_delivery_order_models(sortby => 'donumber');
193
}
194

  
195
sub _init_sales_delivery_order_models {
196
  my ($self, %params) = @_;
197

  
198
  SL::Controller::Helper::GetModels->new(
199
    controller   => $_[0],
200
    model        => 'DeliveryOrder',
201
    # model        => 'Order',
202
    sorted       => {
203
      _default     => {
204
        by           => $params{sortby},
205
        dir          => 1,
206
      },
207
      customer     => t8('Customer'),
208
      employee     => t8('Employee'),
209
      transdate    => t8('Date'),
210
      donumber     => t8('Delivery Order Number'),
211
      ordnumber     => t8('Order Number'),
212
    },
213
    with_objects => [ qw(customer employee) ],
214
   query        => [
215
      '!customer_id' => undef,
216
      or             => [ closed    => undef, closed    => 0 ],
217
      or             => [ delivered => undef, delivered => 0 ],
218
    ],
219
  );
220
}
221

  
222

  
223
sub init_invoice_models {
224
  my ($self)             = @_;
225
  my @invoice_ids = @{ $self->invoice_ids };
226

  
227
  SL::Controller::Helper::GetModels->new(
228
    controller   => $_[0],
229
    model        => 'Invoice',
230
    (paginated   => 0,) x !!@invoice_ids,
231
    sorted       => {
232
      _default     => {
233
        by           => 'transdate',
234
        dir          => 0,
235
      },
236
      customer     => t8('Customer'),
237
      invnumber    => t8('Invoice Number'),
238
      employee     => t8('Employee'),
239
      donumber     => t8('Delivery Order Number'),
240
      ordnumber    => t8('Order Number'),
241
      reqdate      => t8('Delivery Date'),
242
      transdate    => t8('Date'),
243
    },
244
    with_objects => [ qw(customer employee) ],
245
    query        => [
246
      '!customer_id' => undef,
247
      (id            => \@invoice_ids) x !!@invoice_ids,
248
    ],
249
  );
250
}
251

  
252

  
253
sub init_default_printer_id {
254
  my $pr = SL::DB::Manager::Printer->find_by(printer_description => $::locale->text("sales_invoice_printer"));
255
  return $pr ? $pr->id : undef;
256
}
257

  
258
sub setup {
259
  my ($self) = @_;
260
  $::auth->assert('invoice_edit');
261

  
262
  $::request->layout->use_javascript("${_}.js")  for qw(kivi.MassInvoiceCreatePrint);
263
}
264

  
265
#
266
# helpers
267
#
268

  
269
sub create_pdfs {
270
  my ($self, %params) = @_;
271

  
272
  my @pdf_file_names;
273
  foreach my $invoice (@{ $params{invoices} }) {
274
    my %create_params = (
275
      template  => $self->find_template(name => 'invoice', printer_id => $params{printer_id}),
276
      variables => Form->new(''),
277
      return    => 'file_name',
278
    );
279

  
280
    $create_params{variables}->{$_} = $params{variables}->{$_} for keys %{ $params{variables} };
281

  
282
    $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
283
    $create_params{variables}->prepare_for_printing;
284

  
285
    push @pdf_file_names, $self->create_pdf(%create_params);
286
  }
287

  
288
  return @pdf_file_names;
289
}
290

  
291
sub download_or_print_documents {
292
  my ($self, %params) = @_;
293

  
294
  my @pdf_file_names;
295

  
296
  eval {
297
    my %pdf_params = (
298
      invoices        => $params{invoices},
299
      printer_id      => $params{printer_id},
300
      variables       => {
301
        type        => 'invoice',
302
        formname    => 'invoice',
303
        format      => 'pdf',
304
        media       => $params{printer_id} ? 'printer' : 'file',
305
      });
306

  
307
    @pdf_file_names = $self->create_pdfs(%pdf_params);
308
    my $merged_pdf  = $self->merge_pdfs(file_names => \@pdf_file_names);
309
    unlink @pdf_file_names;
310

  
311
    if (!$params{printer_id}) {
312
      my $file_name =  t8("Invoices") . '-' . DateTime->today_local->strftime('%Y%m%d%H%M%S') . '.pdf';
313
      $file_name    =~ s{[^\w\.]+}{_}g;
314

  
315
      return $self->send_file(
316
        \$merged_pdf,
317
        type => 'application/pdf',
318
        name => $file_name,
319
      );
320
    }
321

  
322
    my $printer = SL::DB::Printer->new(id => $params{printer_id})->load;
323
    my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
324

  
325
    open my $out, '|-', $command or die $!;
326
    binmode $out;
327
    print $out $merged_pdf;
328
    close $out;
329

  
330
    flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
331
    return $self->redirect_to(action => 'list_invoices', printer_id => $params{printer_id});
332

  
333
  } or do {
334
    unlink @pdf_file_names;
335
    $::form->error(t8("Creating the PDF failed:") . " " . $@);
336
  };
337
}
338

  
339
sub make_filter_summary {
340
  my ($self) = @_;
341

  
342
  my $filter = $::form->{filter} || {};
343
  my @filter_strings;
344

  
345
  my @filters = (
346
    [ $filter->{customer}{"name:substr::ilike"}, t8('Customer') ],
347
    [ $filter->{"transdate:date::ge"},           t8('Delivery Date') . " " . t8('From Date') ],
348
    [ $filter->{"transdate:date::le"},           t8('Delivery Date') . " " . t8('To Date')   ],
349
  );
350

  
351
  for (@filters) {
352
    push @filter_strings, "$_->[1]: " . ($_->[2] ? $_->[2]->() : $_->[0]) if $_->[0];
353
  }
354

  
355
  $self->{filter_summary} = join ', ', @filter_strings;
356
}
357
1;
358

  
359
__END__
360

  
361
=pod
362

  
363
=encoding utf8
364

  
365
=head1 NAME
366

  
367
SL::Controller::MassInvoiceCreatePrint - Controller for Mass Create Print Sales Invoice from Delivery Order
368

  
369
=head2 OVERVIEW
370

  
371
Controller class for the conversion and processing (printing) of objects.
372

  
373

  
374
Inherited from the base controller class, this controller implements the Sales Mass Invoice Creation.
375
In general there are two major distinctions:
376
This class implements the conversion and the printing via clickable action AND triggers the same
377
conversion towards a Background-Job with a good user interaction.
378

  
379
Analysis hints: All this is more or less boilerplate code around the great convert_to_invoice method
380
in DeliverOrder.pm. If you need to debug stuff, take a look at the corresponding test case
381
($ t/test.pl t/db_helper/convert_invoices.t). There are some redundant code parts in this controller
382
and in the background job, i.e. compare the actions create and print.
383
From a reverse engineering point of view the actions in this controller were written before the
384
background job existed, therefore if anything goes boom take a look at the single steps done via gui
385
in this controller and after that take a deeper look at the MassRecordCreationAndPrinting job.
386

  
387

  
388
=head1 FUNCTIONS
389

  
390
=over 2
391

  
392
=item C<action_list_sales_delivery_orders>
393

  
394
List all open sales delivery orders. The filter can be in two states show or "no show" the
395
original, probably gorash idea, is to increase performance and not to be forced to click on the
396
next button (like in all other reports). Therefore use this option and this filter for a good
397
project default and hide it again. Filters can be added in _filter.html. Take a look at
398
  SL::Controlle::Helper::GetModels::Filtered.pm and SL::DB::Helper::Filtered.
399

  
400
=item C<action_create_invoices>
401

  
402
Creates or to be more correctly converts delivery orders to invoices. All items are
403
converted 1:1 and after conversion the delivery order(s) are closed.
404

  
405
=item C<action_list_invoices>
406

  
407
List the created invoices, if created via gui (see action above)
408

  
409
=item C<action_print>
410

  
411
Print the selected invoices. Yes, it really is all boring linear (see action above).
412
Calls download_or_print method.
413

  
414
=item C<action_create_print_all_start>
415

  
416
Initialises the webform for the creation and printing via background job. Now we get to
417
the more fun part ...  Mosu did a great user interaction job here, we can choose how many
418
objects are converted in one strike and if or if not they are downloadable or will be sent to
419
a printer (if defined as a printing command) right away.
420
Background job is started and status is watched via js and the next action.
421

  
422
=item C<action_create_print_all_status>
423

  
424
Action for watching status, default is refreshing every 5 seconds
425

  
426
=item C<action_create_print_all_download>
427

  
428
If the above is done (did I already said: boring linear?). Documents will
429
be either printed or downloaded.
430

  
431
=item C<init_js>
432

  
433
Inits js/kivi.MassInvoiceCreatePrint;
434

  
435
=item C<init_printers>
436

  
437
Gets all printer commands
438

  
439
=item C<init_invoice_ids>
440

  
441
Gets a list of (empty) invoice ids
442

  
443
=item C<init_sales_delivery_order_models>
444

  
445
Calls _init_sales_delivery_order_models with a param
446

  
447
=item C<_init_sales_delivery_order_models>
448

  
449
Private function, called by init_sales_delivery_order_models.
450
Gets all open sales delivery orders.
451

  
452
=item C<init_invoice_models>
453

  
454
Gets all invoice_models via the ids in invoice_ids (at the beginning no ids exist)
455

  
456
=item C<init_default_printer_id>
457

  
458
Gets the default printer for sales_invoices. Maybe this function is not used, but
459
might be useful in the next version (working in client project).
460

  
461
=item C<setup>
462

  
463
Currently sets / checks only the access right.
464

  
465
=item C<create_pdfs>
466

  
467
=item C<download_or_print_documents>
468

  
469
Backend function for printing or downloading docs. Only used for gui processing (called
470
via action_print).
471

  
472
=item C<make_filter_summary>
473
Creates the filter option summary in the header. By the time of writing three filters are
474
supported: Customer and date from/to of the Delivery Order (database field transdate).
475

  
476
=back
477

  
478
=head1 TODO
479

  
480
Should be more generalized. Right now just one conversion (delivery order to invoice) is supported.
481
Using BackgroundJobs to mass create / transfer stuff is the way to do it. The original idea
482
was taken from one client project (mosu) with some extra (maybe not standard compliant) customized
483
stuff (using cvars for extra filters and a very compressed Controller for linking (ODSalesOrder.pm)).
484

  
485
Filtering needs to be extended for Delivery Order Number (Natural Sort).
486

  
487
A second printer (copy) needs to be implemented.
488

  
489
Both todos are marked in the template code.
490

  
491

  
492
=head1 AUTHOR
493

  
494
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
495

  
496
Jan Büren E<lt>jan@kivitendo-premium.deE<gt>
497
=cut
js/kivi.MassInvoiceCreatePrint.js
1
namespace('kivi.MassInvoiceCreatePrint', function(ns) {
2
  this.checkSalesOrderSelection = function() {
3
    if ($("[data-checkall=1]:checked").size() > 0)
4
      return true;
5
    alert(kivi.t8('No delivery orders have been selected.'));
6
    return false;
7
  };
8

  
9
  this.checkDeliveryOrderSelection = function() {
10
    if ($("[data-checkall=1]:checked").size() > 0)
11
      return true;
12
    alert(kivi.t8('No delivery orders have been selected.'));
13
    return false;
14
  };
15
  this.checkInvoiceSelection = function() {
16
    if ($("[data-checkall=1]:checked").size() > 0)
17
      return true;
18
    alert(kivi.t8('No invoices have been selected.'));
19
    return false;
20
  };
21

  
22
  this.submitMassCreationForm = function() {
23
    if (!kivi.MassInvoiceCreatePrint.checkDeliveryOrderSelection())
24
      return false;
25

  
26
    $('body').addClass('loading');
27
    $('form').submit();
28
    return false;
29
  };
30

  
31
  this.createPrintAllInitialize = function() {
32
    kivi.popup_dialog({
33
      id: 'create_print_all_dialog',
34
      dialog: {
35
        title: kivi.t8('Create and print all invoices')
36
      }
37
    });
38
  };
39

  
40
  this.createPrintAllStartProcess = function() {
41
    $('#cpa_start_process_button,.ui-dialog-titlebar button.ui-dialog-titlebar-close').prop('disabled', 'disabled');
42
    $('#cpa_start_process_abort_link').remove();
43

  
44
    var data = {
45
      number_of_invoices: $('#cpa_number_of_invoices').val(),
46
      printer_id:         $('#cpa_printer_id').val()
47
    };
48
    kivi.submit_ajax_form('controller.pl?action=MassInvoiceCreatePrint/create_print_all_start', '[name^=filter\\.]', data);
49
  };
50

  
51
  this.createPrintAllFinishProcess = function() {
52
    $('#create_print_all_dialog').dialog('close');
53
    window.location.href = 'controller.pl?action=MassInvoiceCreatePrint%2flist_invoices&noshow=1';
54
  };
55

  
56
  this.massConversionStarted = function() {
57
    $('#create_print_all_dialog').data('timerId', setInterval(function() {
58
      $.get("controller.pl", {
59
        action: 'MassInvoiceCreatePrint/create_print_all_status',
60
        job_id: $('#cpa_job_id').val()
61
      }, kivi.eval_json_result);
62
    }, 5000));
63
  };
64

  
65
  this.massConversionFinished = function() {
66
    clearInterval($('#create_print_all_dialog').data('timerId'));
67
    $('.ui-dialog-titlebar button.ui-dialog-titlebar-close').prop('disabled', '')
68
  };
69

  
70
  this.setup = function() {
71
    $('#create_button').click(kivi.MassInvoiceCreatePrint.submitMassCreationForm);
72
    $('#create_print_all_button').click(kivi.MassInvoiceCreatePrint.createPrintAllInitialize);
73
    $('#action_print').click(kivi.MassInvoiceCreatePrint.checkInvoiceSelection);
74
  };
75
});
76

  
77
$(kivi.MassInvoiceCreatePrint.setup);
menus/user/00-erp.yaml
265 265
  module: letter.pl
266 266
  params:
267 267
    action: add
268
- parent: ar
269
  id: ar_invoices
270
  name: Invoices
271
  icon: sales_invoice_add
272
  order: 850
273
- parent: ar_invoices
274
  id: ar_invoices_mass_add_sales_invoice
275
  name: Mass Create Print Sales Invoice from Delivery Order
276
  order: 100
277
  access: invoice_edit
278
  params:
279
    noshow: 1
280
    action: MassInvoiceCreatePrint/list_sales_delivery_orders
268 281
- parent: ar
269 282
  id: ar_reports
270 283
  name: Reports
templates/webpages/mass_invoice_create_print_from_do/_create_print_all_status.html
1
[%- USE LxERP -%][%- USE L -%][%- USE HTML -%]
2
[% SET data = job.data_as_hash %]
3
<h2>[% LxERP.t8("Step 2 -- Watch status") %]</h2>
4

  
5
[% L.hidden_tag('', job.id, id="cpa_job_id") %]
6

  
7
<p>
8
 [% LxERP.t8("This status output will be refreshed every five seconds.") %]
9
</p>
10

  
11
<p>
12
 [% IF data.status < 3 %]
13
  [% L.link("login.pl?action=company_logo", LxERP.t8("Open new tab"), target="_blank") %]
14

  
15
 [% ELSE %]
16
 [% IF data.pdf_file_name %]
17
   [% L.link(SELF.url_for(action="create_print_all_download", job_id=job.id), LxERP.t8("Download PDF")) %]
18
 [% END %]
19
 [% L.link("#", LxERP.t8("Close window"), onclick="kivi.MassInvoiceCreatePrint.createPrintAllFinishProcess();") %]
20
[% END %]
21
</p>
22

  
23
<p>
24
 <table>
25
  <tr>
26
   <th valign="top" align="left">[% LxERP.t8("Current status:") %]</th>
27
   <td valign="top">
28
    [% IF !data.status %]
29
     [% LxERP.t8("waiting for job to be started") %]
30
    [% ELSIF data.status == 1 %]
31
     [% LxERP.t8("Creating invoices") %]
32
    [% ELSIF data.status == 2 %]
33
     [% LxERP.t8("Printing invoices (this can take a while)") %]
34
    [% ELSE %]
35
     [% LxERP.t8("Done.") %]
36
     [% IF data.pdf_file_name %]
37
      [% LxERP.t8("The file is available for download.") %]
38
     [% ELSIF data.printer_id %]
39
      [% LxERP.t8("The file has been sent to the printer.") %]
40
     [% END %]
41
    [% END %]
42
   </td>
43
  </tr>
44

  
45
  <tr>
46
   <th valign="top" align="left">[% LxERP.t8("Number of invoices created:") %]</th>
47
   <td valign="top">[% IF data.status > 0 %][% HTML.escape(data.num_created) %] / [% HTML.escape(data.record_ids.size) %][% ELSE %]–[% END %]</td>
48
  </tr>
49

  
50
  <tr>
51
   <th valign="top" align="left">[% LxERP.t8("Number of invoices printed:") %]</th>
52
   <td valign="top">[% IF data.status > 1 %][% HTML.escape(data.num_printed) %] / [% HTML.escape(data.invoice_ids.size) %][% ELSE %]–[% END %]</td>
53
  </tr>
54

  
55
  <tr>
56
   <th valign="top" align="left">[% LxERP.t8("Errors during conversion:") %]</th>
57
   <td valign="top">
58
[% IF !data.status %]
59
60
[% ELSIF !data.conversion_errors.size %]
61
 [% LxERP.t8("No errors have occurred.") %]
62
[% ELSE %]
63
    <table>
64
     <tr class="listheader">
65
      <th>[% LxERP.t8("Delivery Order") %]</th>
66
      <th>[% LxERP.t8("Error") %]</th>
67
     </tr>
68

  
69
 [% FOREACH error = data.conversion_errors %]
70
     <tr>
71
      <td valign="top">[% 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 %]</td>
72
      <td valign="top">[% HTML.escape(error.message) %]</td>
73
     </tr>
74
 [% END %]
75
    </table>
76
[% END %]
77
   </td>
78
  </tr>
79

  
80
  <tr>
81
   <th valign="top" align="left">[% LxERP.t8("Errors during printing:") %]</th>
82
   <td valign="top">
83
[% IF data.status < 2 %]
84
85
[% ELSIF !data.print_errors.size %]
86
 [% LxERP.t8("No errors have occurred.") %]
87
[% ELSE %]
88
    <table>
89
     <tr class="listheader">
90
      <th>[% LxERP.t8("Invoice") %]</th>
91
      <th>[% LxERP.t8("Error") %]</th>
92
     </tr>
93

  
94
 [% FOREACH error = data.print_errors %]
95
     <tr>
96
      <td valign="top">[% 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 %]</td>
97
      <td valign="top">[% HTML.escape(error.message) %]</td>
98
     </tr>
99
 [% END %]
100
    </table>
101
[% END %]
102
   </td>
103
  </tr>
104

  
105
 </table>
106
</p>
templates/webpages/mass_invoice_create_print_from_do/_create_print_all_step_1.html
1
[%- USE LxERP -%][%- USE L -%]
2
[%- SET num_delivery_orders = SELF.sales_delivery_order_models.count %]
3
<h2>[% LxERP.t8("Step 1 -- limit number of delivery orders to process") %]</h2>
4

  
5
<p>
6
 [% LxERP.t8("Currently #1 delivery orders can be converted into invoices and printed.", num_delivery_orders) %]
7
 [% LxERP.t8("How many do you want to create and print?") %]
8
</p>
9

  
10
<table>
11
 <tr>
12
  <td>[% LxERP.t8("Number of invoices to create") %]:</td>
13
  <td>[% L.input_tag('', num_delivery_orders, size="5", id="cpa_number_of_invoices") %]</td>
14
 </tr>
15

  
16
 <tr>
17
  <td>[% LxERP.t8("Print destination") %]:</td>
18
  <td>
19
    [% SET  printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ;
20
       CALL printers.import(SELF.printers);
21
       L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]
22
  </td>
23
 </tr>
24
 <!--tr>
25
  <td>[% LxERP.t8("Print destination (copy)") %]:</td>
26
  <td>
27
    [% SET  printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ;
28
       CALL printers.import(SELF.printers);
29
       L.select_tag("", printers, title_key="description", default=SELF.default_printer_id, id="cpa_printer_id") %]
30
  </td>
31
 </tr -->
32
</table>
33

  
34
<p>
35
 [% L.button_tag("kivi.MassInvoiceCreatePrint.createPrintAllStartProcess();", LxERP.t8("Start process"), id="cpa_start_process_button") %]
36
 [% L.link("#", LxERP.t8("Abort"), onclick="\$('#create_print_all_dialog').dialog('close');", id="cpa_start_process_abort_link") %]
37
</p>
templates/webpages/mass_invoice_create_print_from_do/_filter.html
1
[%- USE L %][%- USE LxERP %][%- USE HTML %]
2
<div>
3
 <form action="controller.pl" method="post">
4
  <div class="filter_toggle" [% IF noshow == 0 %]style="display:none"[% END %]>
5
   <a href="#" onClick="javascript:$('.filter_toggle').toggle()">[% LxERP.t8('Show Filter') %]</a>
6
      [% SELF.filter_summary %]
7
  </div>
8

  
9
  <div class="filter_toggle" [% IF noshow  == 1 %]style="display:none"[% END %]>
10
   <a href="#" onClick="javascript:$('.filter_toggle').toggle()">[% LxERP.t8('Hide Filter') %]</a>
11
   <table id="filter_table">
12
    <tr>
13
     <th align="right">[% LxERP.t8('Customer') %]</th>
14
     <td>[% L.input_tag('filter.customer.name:substr::ilike', filter.customer.name_substr__ilike, size = 20) %]</td>
15
    </tr>
16
     <th align="right">[% LxERP.t8('Delivery Date') %] [% LxERP.t8('From Date') %]</th>
17
     <td>[% L.date_tag('filter.transdate:date::ge', filter.transdate_date__ge) %]</td>
18
    </tr>
19
    <tr>
20
     <th align="right">[% LxERP.t8('Delivery Date') %] [% LxERP.t8('To Date') %]</th>
21
     <td>[% L.date_tag('filter.transdate:date::le', filter.transdate_date__le) %]</td>
22
    </tr>
23
    <!-- TODO implement helper function nat sort here -->
24
    <!-- tr>
25
     <th align="right">[% LxERP.t8('From') %] [% LxERP.t8('Delivery Order Number') %]</th>
26
     <td>[% L.input_tag('filter.donumber:number::ge', filter.donumber_number__ge) %]</td>
27
    </tr>
28
    <tr>
29
     <th align="right">[% LxERP.t8('To') %] [% LxERP.t8('Delivery Order Number') %]</th>
30
     <td>[% L.input_tag('filter.donumber:number::le', filter.donumber_number__le) %]</td>
31
    </tr -->
32

  
33
   </table>
34

  
35
   [% L.hidden_tag('action', 'MassInvoiceCreatePrint/dispatch') %]
36
   [% L.hidden_tag('sort_by', FORM.sort_by) %]
37
   [% L.hidden_tag('sort_dir', FORM.sort_dir) %]
38
   [% L.hidden_tag('page', FORM.page) %]
39
   [% L.submit_tag(LIST_ACTION, LxERP.t8('Continue'))%]
40

  
41
   <a href="#" onClick="javascript:$('#filter_table input,#filter_table select').val('');">[% LxERP.t8('Reset') %]</a>
42

  
43
  </div>
44

  
45
 </form>
46
</div>
templates/webpages/mass_invoice_create_print_from_do/list_invoices.html
1
[% USE HTML %][% USE L %][% USE LxERP %]
2

  
3
<h1>[% FORM.title %]</h1>
4

  
5
[%- INCLUDE "common/flash.html" %]
6

  
7
[% LIST_ACTION     = 'action_list_invoices' %]
8
[%- PROCESS 'mass_invoice_create_print_from_do/_filter.html' filter=SELF.filter %]
9

  
10
[% IF noshow == 1 %]
11
[% invoices = SELF.invoice_models.get;
12
   MODELS          = SELF.invoice_models %]
13
[%- IF !invoices.size %]
14
 <p>
15
  [%- LxERP.t8("There are currently no open invoices, or none matches your filter conditions.") %]
16
 </p>
17
[%- ELSE %]
18

  
19
 <form method="post" action="controller.pl">
20
  <table width="100%">
21
   <thead>
22
    <tr class="listheading">
23
     <th>[% L.checkbox_tag("", id="check_all", checkall="[data-checkall=1]") %]</th>
24
     <th>[% L.sortable_table_header("transdate") %]</th>
25
     <th>[% L.sortable_table_header("reqdate") %]</th>
26
     <th>[% L.sortable_table_header("invnumber") %]</th>
27
     <th>[% L.sortable_table_header("donumber") %]</th>
28
     <th>[% L.sortable_table_header("customer") %]</th>
29
     <th>[% LxERP.t8("Shipto") %]</th>
30
    </tr>
31
   </thead>
32

  
33
   <tbody>
34
    [%- FOREACH invoice = invoices %]
35
     [% invoice_id = invoice.id
36
        delivery_order    = invoice.delivery_order %]
37
     <tr class="listrow">
38
      <td>[% L.checkbox_tag('id[]', value=invoice.id, "data-checkall"=1, checked=selected_ids.$invoice_id) %]</td>
39
      <td>[% HTML.escape(invoice.transdate_as_date) %]</td>
40
      <td>[% HTML.escape(invoice.deliverydate_as_date) %]</td>
41
      <td>[% L.link(SELF.url_for(controller="is.pl", action="edit", type="sales_invoice", id=invoice.id), invoice.invnumber) %]</td>
42
      <td>
43
       [% IF delivery_order %]
44
        [% L.link(SELF.url_for(controller="do.pl", action="edit", id=delivery_order.id), delivery_order.donumber) %]
45
       [% ELSE %]
46
        [% HTML.escape(invoice.donumber) %]
47
       [% END %]
48
      </td>
49
      <td>[% HTML.escape(invoice.customer.name) %]</td>
50
      <td>[% HTML.escape(SELF.make_shipto_title(invoice.shipto || delivery_order.custom_shipto)) %]</td>
51
     </tr>
52
    [%- END %]
53
   </tbody>
54
  </table>
55

  
56
  [% IF !SELF.invoice_ids.size %]
57
   [% L.paginate_controls %]
58
  [% END %]
59

  
60
  <hr size="3" noshade>
61

  
62
  [% IF SELF.printers.size %]
63
   <p>
64
    [% LxERP.t8("Print destination") %]:
65
    [% SET  printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ;
66
       CALL printers.import(SELF.printers);
67
       L.select_tag("printer_id", printers, title_key="description", default=FORM.printer_id) %]
68
   </p>
69
  [% END %]
70

  
71
  <p>
72
   [% L.hidden_tag("action", "MassInvoiceCreatePrint/dispatch") %]
73
   [% L.submit_tag("action_print", LxERP.t8("Print")) %]
74
  </p>
75
 </form>
76
[%- END %]
77
[%- END %]
templates/webpages/mass_invoice_create_print_from_do/list_sales_delivery_orders.html
1
[% USE Dumper %][% USE HTML %][% USE L %][% USE LxERP %]
2

  
3

  
4
<h1>[% FORM.title %]</h1>
5

  
6
[%- INCLUDE "common/flash.html" %]
7

  
8
[% LIST_ACTION  = 'action_list_sales_delivery_orders' %]
9
[% SET MODELS = SELF.sales_delivery_order_models;
10
       dummy  = MODELS.finalize %]
11

  
12
[%- PROCESS 'mass_invoice_create_print_from_do/_filter.html' filter=SELF.sales_delivery_order_models.filtered.laundered  %]
13

  
14
[% IF noshow == 1 %]
15
  [% SET sales_delivery_orders = MODELS.get %]
16
   <form method="post" action="controller.pl">
17
  [% IF !sales_delivery_orders.size %]
18
     <p>
19
    [%- LxERP.t8("There are currently no open sales delivery orders.") %]
20
     </p>
21
  [%- ELSE %]
22
    <table width="100%">
23
     <thead>
24
      <tr class="listheading">
25
       <th>[% L.checkbox_tag("", id="check_all", checkall="[data-checkall=1]") %]</th>
26
       <th>[% L.sortable_table_header("transdate") %]</th>
27
       <th>[% L.sortable_table_header("donumber") %]</th>
28
       <th>[% L.sortable_table_header("ordnumber") %]</th>
29
      <th>[% L.sortable_table_header("customer") %]</th>
30
     </tr>
31
     </thead>
32

  
33
     <tbody>
34
     [%- FOREACH sales_delivery_order = sales_delivery_orders %]
35
     <tr class="listrow">
36
      <td>[% L.checkbox_tag('id[]', value=sales_delivery_order.id, "data-checkall"=1) %]</td>
37
      <td>[% HTML.escape(sales_delivery_order.transdate_as_date) %]</td>
38
      <td>[% L.link(SELF.url_for(controller="do.pl", action="edit", type="sales_delivery_order", id=sales_delivery_order.id), sales_delivery_order.donumber) %]</td>
39
      <td>[% L.link(SELF.url_for(controller="oe.pl", action="edit", type="sales_order", id=sales_delivery_order.sales_order.id), sales_delivery_order.ordnumber) %]</td>
40
      <td>[% HTML.escape(sales_delivery_order.customer.name) %]</td>
41
     </tr>
42
     [%- END %]
43
     </tbody>
44
    </table>
45

  
46
    [% L.paginate_controls %]
47

  
48
    <hr size="3" noshade>
49

  
50
    <p>
51
     [% L.hidden_tag("action", "MassInvoiceCreatePrint/create_invoices") %]
52
     [% L.button_tag("", LxERP.t8("Create invoices"), name="create_button") %]
53
     [% L.button_tag("", LxERP.t8("For all delivery orders create and print invoices"), name="create_print_all_button") %]
54
    </p>
55
    <div id="create_print_all_dialog" style="display: none;">
56
     [%- INCLUDE 'mass_invoice_create_print_from_do/_create_print_all_step_1.html' %]
57
    </div>
58
  [%- END %]
59
 </form>
60
[%- END %]

Auch abrufbar als: Unified diff