7c6f558abb354e354534f1485e37accedaa0433a
[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         variable_content_types => { longdescription => 'html',
105                                     partnotes       => 'html',
106                                     notes           => 'html',}
107       );
108
109
110
111       $create_params{variables}->{$_} = $variables{$_} for keys %variables;
112
113       $invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
114       $create_params{variables}->prepare_for_printing;
115
116       push @pdf_file_names, $ctrl->create_pdf(%create_params);
117
118       # copy file to webdav folder
119       if ($::instance_conf->get_webdav_documents) {
120         my $webdav = SL::Webdav->new(
121           type     => 'invoice',
122           number   => $invoice->invnumber,
123         );
124         my $webdav_file = SL::Webdav::File->new(
125           webdav   => $webdav,
126           filename => t8('Invoice') . '_' . $invoice->invnumber . '.pdf',
127         );
128         eval {
129           $webdav_file->store(file => $pdf_file_names[-1]);
130           1;
131         } or do {
132           push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => $@ };
133         }
134       }
135
136       $data->{num_printed}++;
137
138       1;
139
140     } or do {
141       push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => $@ };
142     };
143
144     $job_obj->update_attributes(data_as_hash => $data);
145   }
146
147   if (@pdf_file_names) {
148     my $data = $job_obj->data_as_hash;
149
150     eval {
151       $self->{merged_pdf} = $ctrl->merge_pdfs(file_names => \@pdf_file_names);
152       unlink @pdf_file_names;
153
154       if (!$printer_id) {
155         my $file_name = 'mass_invoice' . $job_obj->id . '.pdf';
156         my $sfile     = SL::SessionFile->new($file_name, mode => 'w');
157         $sfile->fh->print($self->{merged_pdf});
158         $sfile->fh->close;
159
160         $data->{pdf_file_name} = $file_name;
161       }
162
163       1;
164
165     } or do {
166       push @{ $data->{print_errors} }, { message => $@ };
167     };
168
169     $job_obj->update_attributes(data_as_hash => $data);
170   }
171 }
172
173 sub print_pdfs {
174   my ($self)     = @_;
175
176   my $job_obj         = $self->{job_obj};
177   my $data            = $job_obj->data_as_hash;
178   my $printer_id      = $data->{printer_id};
179   my $copy_printer_id = $data->{copy_printer_id};
180
181   return if !$printer_id;
182
183   my $out;
184
185   foreach  my $local_printer_id ($printer_id, $copy_printer_id) {
186     next unless $local_printer_id;
187     my $printer = SL::DB::Printer->new(id => $local_printer_id)->load;
188     my $command = SL::Template::create(type => 'ShellCommand', form => Form->new(''))->parse($printer->printer_command);
189     if (!open $out, '|-', $command) {
190       push @{ $data->{print_errors} }, { message => $::locale->text('Could not execute printer command: #1', $!) };
191       $job_obj->update_attributes(data_as_hash => $data);
192       return;
193     }
194     binmode $out;
195     print $out $self->{merged_pdf};
196     close $out;
197   }
198
199 }
200
201 sub run {
202   my ($self, $job_obj) = @_;
203
204   $self->{job_obj}         = $job_obj;
205   $self->{invoices} = [];
206
207   $self->create_invoices;
208   $self->convert_invoices_to_pdf;
209   $self->print_pdfs;
210
211   $job_obj->set_data(status => DONE())->save;
212
213   return 1;
214 }
215
216 1;
217
218 __END__
219
220 =pod
221
222 =encoding utf8
223
224 =head1 NAME
225
226 SL::BackgroundJob::MassRecordCreationAndPrinting
227
228 =head1 SYNOPSIS
229
230 In controller:
231
232 use SL::BackgroundJob::MassRecordCreationAndPrinting
233
234 my $job              = SL::DB::BackgroundJob->new(
235     type               => 'once',
236     active             => 1,
237     package_name       => 'MassRecordCreationAndPrinting',
238
239   )->set_data(
240     record_ids         => [ map { $_->id } @records[0..$num - 1] ],
241     printer_id         => $::form->{printer_id},
242     copy_printer_id    => $::form->{copy_printer_id},
243     transdate          => $::form->{transdate} || undef,
244     status             => SL::BackgroundJob::MassRecordCreationAndPrinting->WAITING_FOR_EXECUTION(),
245     num_created        => 0,
246     num_printed        => 0,
247     invoice_ids        => [ ],
248     conversion_errors  => [ ],
249     print_errors       => [ ],
250
251   )->update_next_run_at;
252   SL::System::TaskServer->new->wake_up;
253
254 =head1 OVERVIEW
255
256 This background job has 4 states which are described by the four constants above.
257
258 =over 2
259
260 =item * WAITING_FOR_EXECUTION
261   Background has been initialised and needs to be picked up by the task_server
262
263 =item * CONVERTING_DELIVERY_ORDERS
264    Object conversion
265
266 =item * PRINTING_INVOICES
267   Printing, if done via print command
268
269 =item * DONE
270   To release the process and for the user information
271
272 =back
273
274 =head1 FUNCTIONS
275
276 =over 2
277
278 =item C<create_invoices>
279
280 Converts the source objects (DeliveryOrder) to destination objects (Invoice).
281 On success objects will be saved.
282 If param C<data->{transdate}> is set, this will be the transdate. No safety checks are done.
283 The original conversion from order to delivery order had a post_save_sanity_check
284 C<$delivery_order-E<gt>post_save_sanity_check; # just a hint at e8521eee (#90 od)>
285 The params of convert_to_invoice are created on the fly with a anonym sub, as a alternative check
286  perlsecret Enterprise ()x!!
287
288 =item C<convert_invoices_to_pdf>
289
290 Takes the new destination objects and merges them via print template in one pdf.
291
292 =item C<print_pdfs>
293
294 Sent the pdf to the printer command.
295 If param C<data->{copy_printer_id}> is set, the pdf will be sent to a second printer command.
296
297 =back
298
299 =head1 BUGS
300
301 Currently the calculation from the gui (form) differs from the calculation via convert (PTC).
302 Furthermore mass conversion with foreign currencies could lead to problems (daily rate check).
303
304 =head1 TODO
305
306 It would be great to extend this Job for general background printing. The original project
307 code converted sales order to delivery orders (84e7c540) this could be merged in unstable.
308 The states should be CONVERTING_SOURCE_RECORDS, PRINTING_DESTINATION_RECORDS etc
309
310 =head1 AUTHOR
311
312 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
313
314 Jan Büren E<lt>jan@kivitendo-premium.deE<gt>
315
316 =cut