Merge branch 'mass_convert_delivery_orders_to_invoice'
[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
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