Massendruck weitere Optionen (zweiter Druckbefehl) implementiert
[kivitendo-erp.git] / 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 use SL::Locale::String qw(t8);
15 use SL::Webdav;
16
17 use constant WAITING_FOR_EXECUTION       => 0;
18 use constant CONVERTING_DELIVERY_ORDERS  => 1;
19 use constant PRINTING_INVOICES           => 2;
20 use constant DONE                        => 3;
21 # Data format:
22 # my $data             = {
23 #   record_ids          => [ 123, 124, 127, ],
24 #   printer_id         => 4711,
25 #   copy_printer_id    => 4711,
26 #   transdate          => $today || $custom_transdate,
27 #   num_created        => 0,
28 #   num_printed        => 0,
29 #   invoice_ids        => [ 234, 235, ],
30 #   conversion_errors  => [ { id => 124, number => 'A981723', message => "Stuff went boom" }, ],
31 #   print_errors       => [ { id => 234, number => 'L87123123', message => "Printer is out of coffee" }, ],
32 #   pdf_file_name      => 'qweqwe.pdf',
33 # };
34
35 sub create_invoices {
36   my ($self)  = @_;
37
38   my $job_obj = $self->{job_obj};
39   my $db      = $job_obj->db;
40
41   $job_obj->set_data(status => CONVERTING_DELIVERY_ORDERS())->save;
42
43   foreach my $delivery_order_id (@{ $job_obj->data_as_hash->{record_ids} }) {
44     my $number = $delivery_order_id;
45     my $data   = $job_obj->data_as_hash;
46
47     eval {
48       my $invoice;
49       my $sales_delivery_order = SL::DB::DeliveryOrder->new(id => $delivery_order_id)->load;
50       $number                  = $sales_delivery_order->donumber;
51
52       if (!$db->do_transaction(sub {
53         $invoice = $sales_delivery_order->convert_to_invoice(sub { $data->{transdate} ? ('attributes' => { transdate => $data->{transdate} }) :
54                                                                          undef }->() ) || die $db->error;
55         1;
56       })) {
57         die $db->error;
58       }
59
60       $data->{num_created}++;
61       push @{ $data->{invoice_ids} }, $invoice->id;
62       push @{ $self->{invoices}    }, $invoice;
63
64       1;
65     } or do {
66       push @{ $data->{conversion_errors} }, { id => $delivery_order_id, number => $number, message => $@ };
67     };
68
69     $job_obj->update_attributes(data_as_hash => $data);
70   }
71 }
72
73 sub convert_invoices_to_pdf {
74   my ($self) = @_;
75
76   return if !@{ $self->{invoices} };
77
78   my $job_obj = $self->{job_obj};
79   my $db      = $job_obj->db;
80
81   $job_obj->set_data(status => PRINTING_INVOICES())->save;
82
83   require SL::Controller::MassInvoiceCreatePrint;
84
85   my $printer_id = $job_obj->data_as_hash->{printer_id};
86   my $ctrl       = SL::Controller::MassInvoiceCreatePrint->new;
87   my %variables  = (
88     type         => 'invoice',
89     formname     => 'invoice',
90     format       => 'pdf',
91     media        => $printer_id ? 'printer' : 'file',
92   );
93
94   my @pdf_file_names;
95
96   foreach my $invoice (@{ $self->{invoices} }) {
97     my $data = $job_obj->data_as_hash;
98
99     eval {
100       my %create_params = (
101         template  => $ctrl->find_template(name => 'invoice', printer_id => $printer_id),
102         variables => Form->new(''),
103         return    => 'file_name',
104       );
105
106       $create_params{variables}->{$_} = $variables{$_} for keys %variables;
107
108       $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
109       $create_params{variables}->prepare_for_printing;
110
111       push @pdf_file_names, $ctrl->create_pdf(%create_params);
112
113       # copy file to webdav folder
114       if ($::instance_conf->get_webdav_documents) {
115         my $webdav = SL::Webdav->new(
116           type     => 'invoice',
117           number   => $invoice->invnumber,
118         );
119         my $webdav_file = SL::Webdav::File->new(
120           webdav   => $webdav,
121           filename => t8('Invoice') . '_' . $invoice->invnumber . '.pdf',
122         );
123         eval {
124           $webdav_file->store(file => $pdf_file_names[-1]);
125           1;
126         } or do {
127           push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => $@ };
128         }
129       }
130
131       $data->{num_printed}++;
132
133       1;
134
135     } or do {
136       push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => $@ };
137     };
138
139     $job_obj->update_attributes(data_as_hash => $data);
140   }
141
142   if (@pdf_file_names) {
143     my $data = $job_obj->data_as_hash;
144
145     eval {
146       $self->{merged_pdf} = $ctrl->merge_pdfs(file_names => \@pdf_file_names);
147       unlink @pdf_file_names;
148
149       if (!$printer_id) {
150         my $file_name = 'mass_invoice' . $job_obj->id . '.pdf';
151         my $sfile     = SL::SessionFile->new($file_name, mode => 'w');
152         $sfile->fh->print($self->{merged_pdf});
153         $sfile->fh->close;
154
155         $data->{pdf_file_name} = $file_name;
156       }
157
158       1;
159
160     } or do {
161       push @{ $data->{print_errors} }, { message => $@ };
162     };
163
164     $job_obj->update_attributes(data_as_hash => $data);
165   }
166 }
167
168 sub print_pdfs {
169   my ($self)     = @_;
170
171   my $job_obj         = $self->{job_obj};
172   my $data            = $job_obj->data_as_hash;
173   my $printer_id      = $data->{printer_id};
174   my $copy_printer_id = $data->{copy_printer_id};
175
176   return if !$printer_id;
177
178   my $out;
179
180   foreach  my $local_printer_id ($printer_id, $copy_printer_id) {
181     next unless $local_printer_id;
182     my $printer = SL::DB::Printer->new(id => $local_printer_id)->load;
183     my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
184     if (!open $out, '|-', $command) {
185       push @{ $data->{print_errors} }, { message => $::locale->text('Could not execute printer command: #1', $!) };
186       $job_obj->update_attributes(data_as_hash => $data);
187       return;
188     }
189     binmode $out;
190     print $out $self->{merged_pdf};
191     close $out;
192   }
193
194 }
195
196 sub run {
197   my ($self, $job_obj) = @_;
198
199   $self->{job_obj}         = $job_obj;
200   $self->{invoices} = [];
201
202   $self->create_invoices;
203   $self->convert_invoices_to_pdf;
204   $self->print_pdfs;
205
206   $job_obj->set_data(status => DONE())->save;
207
208   return 1;
209 }
210
211 1;
212
213 __END__
214
215 =pod
216
217 =encoding utf8
218
219 =head1 NAME
220
221 SL::BackgroundJob::MassRecordCreationAndPrinting
222
223 =head1 SYNOPSIS
224
225 In controller:
226
227 use SL::BackgroundJob::MassRecordCreationAndPrinting
228
229 my $job              = SL::DB::BackgroundJob->new(
230     type               => 'once',
231     active             => 1,
232     package_name       => 'MassRecordCreationAndPrinting',
233
234   )->set_data(
235     record_ids         => [ map { $_->id } @records[0..$num - 1] ],
236     printer_id         => $::form->{printer_id},
237     copy_printer_id    => $::form->{copy_printer_id},
238     transdate          => $::form->{transdate} || undef,
239     status             => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
240     num_created        => 0,
241     num_printed        => 0,
242     invoice_ids        => [ ],
243     conversion_errors  => [ ],
244     print_errors       => [ ],
245
246   )->update_next_run_at;
247   SL::System::TaskServer->new->wake_up;
248
249 =head1 OVERVIEW
250
251 This background job has 4 states which are described by the four constants above.
252
253 =over 2
254
255 =item * WAITING_FOR_EXECUTION
256   Background has been initialised and needs to be picked up by the task_server
257
258 =item * CONVERTING_DELIVERY_ORDERS
259    Object conversion
260
261 =item * PRINTING_INVOICES
262   Printing, if done via print command
263
264 =item * DONE
265   To release the process and for the user information
266
267 =back
268
269 =head1 FUNCTIONS
270
271 =over 2
272
273 =item C<create_invoices>
274
275 Converts the source objects (DeliveryOrder) to destination objects (Invoice).
276 On success objects will be saved.
277 If param C<data->{transdate}> is set, this will be the transdate. No safety checks are done.
278 The original conversion from order to delivery order had a post_save_sanity_check
279 C<$delivery_order-E<gt>post_save_sanity_check; # just a hint at e8521eee (#90 od)>
280 The params of convert_to_invoice are created on the fly with a anonym sub, as a alternative check
281  perlsecret Enterprise ()x!!
282
283 =item C<convert_invoices_to_pdf>
284
285 Takes the new destination objects and merges them via print template in one pdf.
286
287 =item C<print_pdfs>
288
289 Sent the pdf to the printer command.
290 If param C<data->{copy_printer_id}> is set, the pdf will be sent to a second printer command.
291
292 =back
293
294 =head1 BUGS
295
296 Currently the calculation from the gui (form) differs from the calculation via convert (PTC).
297 Furthermore mass conversion with foreign currencies could lead to problems (daily rate check).
298
299 =head1 TODO
300
301 It would be great to extend this Job for general background printing. The original project
302 code converted sales order to delivery orders (84e7c540) this could be merged in unstable.
303 The states should be CONVERTING_SOURCE_RECORDS, PRINTING_DESTINATION_RECORDS etc
304
305 =head1 AUTHOR
306
307 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
308
309 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>
310
311 =cut