Revision 429bf6a8
Von Tamino Steinert vor 5 Monaten hinzugefügt
SL/BackgroundJob/CreatePeriodicInvoices.pm | ||
---|---|---|
8 | 8 |
use DateTime::Format::Strptime; |
9 | 9 |
use English qw(-no_match_vars); |
10 | 10 |
use List::MoreUtils qw(uniq); |
11 |
use Params::Validate qw(:all); |
|
11 | 12 |
|
12 | 13 |
use SL::Common; |
13 | 14 |
use SL::DB::AuthUser; |
... | ... | |
47 | 48 |
_log_msg("Number of configs: " . scalar(@{ $configs})); |
48 | 49 |
|
49 | 50 |
foreach my $config (@{ $configs }) { |
50 |
# A configuration can be set to inactive by |
|
51 |
# $config->handle_automatic_extension. Therefore the check in |
|
52 |
# ...->get_all() does not suffice. |
|
53 |
_log_msg("Config " . $config->id . " active " . $config->active); |
|
54 |
next unless $config->active; |
|
51 |
my $open_orders = $config->get_open_orders_for_period(); |
|
52 |
_log_msg("Dates: " . join(' ', map { $_->reqdate->to_lxoffice } @$open_orders)); |
|
55 | 53 |
|
56 |
my @dates = _calculate_dates($config); |
|
54 |
foreach my $order (@$open_orders) { |
|
55 |
my $data = $self->_create_periodic_invoice(order => $order); |
|
56 |
my $invoice = $data->{invoice}; |
|
57 |
next unless $invoice; |
|
57 | 58 |
|
58 |
_log_msg("Dates: " . join(' ', map { $_->to_lxoffice } @dates));
|
|
59 |
_log_msg("Invoice " . $invoice->invnumber . " posted for config ID " . $config->id . ", period start date " . $::locale->format_date(\%::myconfig, $invoice->deliverydate) . "\n");
|
|
59 | 60 |
|
60 |
foreach my $date (@dates) { |
|
61 |
my $data = $self->_create_periodic_invoice($config, $date); |
|
62 |
next unless $data; |
|
63 |
|
|
64 |
_log_msg("Invoice " . $data->{invoice}->invnumber . " posted for config ID " . $config->id . ", period start date " . $::locale->format_date(\%::myconfig, $date) . "\n"); |
|
65 |
|
|
66 |
push @{ $self->{posted_invoices} }, $data->{invoice}; |
|
61 |
push @{ $self->{posted_invoices} }, $invoice; |
|
67 | 62 |
push @invoices_to_print, $data if $config->print; |
68 | 63 |
push @invoices_to_email, $data if $config->send_email; |
69 | 64 |
|
... | ... | |
77 | 72 |
} |
78 | 73 |
} |
79 | 74 |
|
80 |
foreach my $inv ( @invoices_to_print ) { $self->_print_invoice($inv); }
|
|
81 |
foreach my $inv ( @invoices_to_email ) { $self->_email_invoice($inv); }
|
|
75 |
foreach my $inv_data ( @invoices_to_print ) { $self->_print_invoice($inv_data); }
|
|
76 |
foreach my $inv_data ( @invoices_to_email ) { $self->_email_invoice($inv_data); }
|
|
82 | 77 |
|
83 | 78 |
$self->_send_summary_email; |
84 | 79 |
|
... | ... | |
179 | 174 |
$params{object}->$sub($str); |
180 | 175 |
} |
181 | 176 |
|
182 |
sub _adjust_sellprices_for_period_lengths { |
|
183 |
my (%params) = @_; |
|
184 |
|
|
185 |
return if $params{config}->periodicity eq 'o'; |
|
186 |
|
|
187 |
my $billing_len = $params{config}->get_billing_period_length; |
|
188 |
my $order_value_len = $params{config}->get_order_value_period_length; |
|
189 |
|
|
190 |
return if $billing_len == $order_value_len; |
|
191 |
|
|
192 |
my $is_last_invoice_in_cycle = $params{config}->is_last_bill_date_in_order_value_cycle(date => $params{period_start_date}); |
|
193 |
|
|
194 |
_log_msg("_adjust_sellprices_for_period_lengths: period_start_date $params{period_start_date} is_last_invoice_in_cycle $is_last_invoice_in_cycle billing_len $billing_len order_value_len $order_value_len"); |
|
195 |
|
|
196 |
if ($order_value_len < $billing_len) { |
|
197 |
my $num_orders_per_invoice = $billing_len / $order_value_len; |
|
198 |
|
|
199 |
$_->sellprice($_->sellprice * $num_orders_per_invoice) for @{ $params{invoice}->items }; |
|
200 |
|
|
201 |
return; |
|
202 |
} |
|
203 |
|
|
204 |
my $num_invoices_in_cycle = $order_value_len / $billing_len; |
|
205 |
|
|
206 |
foreach my $item (@{ $params{invoice}->items }) { |
|
207 |
my $sellprice_one_invoice = $::form->round_amount($item->sellprice * $billing_len / $order_value_len, 2); |
|
208 |
|
|
209 |
if ($is_last_invoice_in_cycle) { |
|
210 |
$item->sellprice($item->sellprice - ($num_invoices_in_cycle - 1) * $sellprice_one_invoice); |
|
211 |
|
|
212 |
} else { |
|
213 |
$item->sellprice($sellprice_one_invoice); |
|
214 |
} |
|
215 |
} |
|
216 |
} |
|
217 |
|
|
218 | 177 |
sub _create_periodic_invoice { |
219 |
my $self = shift; |
|
220 |
my $config = shift; |
|
221 |
my $period_start_date = shift; |
|
178 |
my $self = shift; |
|
222 | 179 |
|
223 |
my $time_period_vars = _generate_time_period_variables($config, $period_start_date); |
|
180 |
my %params = validate(@_, { |
|
181 |
order => { isa => 'SL::DB::Order' }, |
|
182 |
}); |
|
183 |
my $order = $params{order}; |
|
224 | 184 |
|
225 |
my $invdate = DateTime->today_local; |
|
185 |
my $period_start_date = $order->reqdate; |
|
186 |
my $config = $order->periodic_invoices_config; |
|
187 |
my $time_period_vars = _generate_time_period_variables($config, $period_start_date); |
|
226 | 188 |
|
227 |
my $order = $config->order; |
|
228 | 189 |
my $invoice; |
229 | 190 |
if (!$self->{db_obj}->db->with_transaction(sub { |
230 | 191 |
1; # make Emacs happy |
231 | 192 |
|
232 | 193 |
$invoice = SL::DB::Invoice->new_from($order, honor_recurring_billing_mode => 1); |
233 | 194 |
|
234 |
my $tax_point = ($invoice->tax_point // $time_period_vars->{period_end_date}->[0])->clone; |
|
235 |
|
|
236 |
while ($tax_point < $period_start_date) { |
|
237 |
$tax_point->add(months => $config->get_billing_period_length); |
|
238 |
} |
|
239 |
|
|
240 | 195 |
my $intnotes = $invoice->intnotes ? $invoice->intnotes . "\n\n" : ''; |
241 |
$intnotes .= "Automatisch am " . $invdate->to_lxoffice . " erzeugte Rechnung";
|
|
196 |
$intnotes .= "Automatisch am " . DateTime->today_local->to_lxoffice . " erzeugte Rechnung";
|
|
242 | 197 |
|
243 |
$invoice->assign_attributes(deliverydate => $period_start_date, |
|
244 |
tax_point => $tax_point, |
|
245 |
intnotes => $intnotes, |
|
246 |
employee => $order->employee, # new_from sets employee to import user |
|
247 |
direct_debit => $config->direct_debit, |
|
248 |
); |
|
198 |
$invoice->assign_attributes( |
|
199 |
intnotes => $intnotes, |
|
200 |
employee => $order->employee, # new_from sets employee to import user |
|
201 |
direct_debit => $config->direct_debit, |
|
202 |
); |
|
249 | 203 |
|
250 | 204 |
_replace_vars(object => $invoice, vars => $time_period_vars, attribute => $_, attribute_format => ($_ eq 'notes' ? 'html' : 'text')) for qw(notes intnotes transaction_description); |
251 | 205 |
|
... | ... | |
253 | 207 |
_replace_vars(object => $item, vars => $time_period_vars, attribute => $_, attribute_format => ($_ eq 'longdescription' ? 'html' : 'text')) for qw(description longdescription); |
254 | 208 |
} |
255 | 209 |
|
256 |
_adjust_sellprices_for_period_lengths(invoice => $invoice, config => $config, period_start_date => $period_start_date); |
|
257 |
|
|
258 | 210 |
$invoice->post(ar_id => $config->ar_chart_id) || die; |
259 | 211 |
|
260 | 212 |
foreach my $item (grep { ($_->recurring_billing_mode eq 'once') && !$_->recurring_billing_invoice_id } @{ $order->orderitems }) { |
... | ... | |
278 | 230 |
|
279 | 231 |
return { |
280 | 232 |
config => $config, |
281 |
period_start_date => $period_start_date, |
|
282 | 233 |
invoice => $invoice, |
283 | 234 |
time_period_vars => $time_period_vars, |
284 | 235 |
}; |
285 | 236 |
} |
286 | 237 |
|
287 |
sub _calculate_dates { |
|
288 |
my ($config) = @_; |
|
289 |
return $config->calculate_invoice_dates(end_date => DateTime->today_local); |
|
290 |
} |
|
291 |
|
|
292 | 238 |
sub _send_summary_email { |
293 | 239 |
my ($self) = @_; |
294 | 240 |
my %config = %::lx_office_conf; |
... | ... | |
376 | 322 |
} |
377 | 323 |
|
378 | 324 |
sub _print_invoice { |
379 |
my ($self, $data) = @_;
|
|
325 |
my $self = shift;
|
|
380 | 326 |
|
381 |
my $invoice = $data->{invoice}; |
|
382 |
my $config = $data->{config}; |
|
327 |
my %params = validate_with( |
|
328 |
params => \@_, |
|
329 |
spec => { |
|
330 |
invoice => { isa => 'SL::DB::Invoice' }, |
|
331 |
config => { isa => 'SL::DB::PeriodicInvoicesConfig' }, |
|
332 |
}, |
|
333 |
allow_extra => 1, |
|
334 |
); |
|
335 |
|
|
336 |
my $invoice = $params{invoice}; |
|
337 |
my $config = $params{config}; |
|
383 | 338 |
|
384 | 339 |
return unless $config->print && $config->printer_id && $config->printer->printer_command; |
385 | 340 |
|
... | ... | |
412 | 367 |
} |
413 | 368 |
|
414 | 369 |
sub _email_invoice { |
415 |
my ($self, $data) = @_; |
|
416 |
|
|
417 |
$data->{config}->load; |
|
370 |
my $self = shift; |
|
371 |
|
|
372 |
my %params = validate_with( |
|
373 |
params => \@_, |
|
374 |
spec => { |
|
375 |
invoice => { isa => 'SL::DB::Invoice' }, |
|
376 |
config => { isa => 'SL::DB::PeriodicInvoicesConfig' }, |
|
377 |
time_period_vars => { type => HASHREF }, |
|
378 |
}, |
|
379 |
allow_extra => 1, |
|
380 |
); |
|
418 | 381 |
|
419 |
return unless $data->{config}->send_email; |
|
382 |
my $invoice = $params{invoice}; |
|
383 |
my $config = $params{config}; |
|
384 |
my $time_period_vars = $params{time_period_vars}; |
|
420 | 385 |
|
421 | 386 |
my @recipients = |
422 | 387 |
uniq |
423 | 388 |
map { lc } |
424 | 389 |
grep { $_ } |
425 | 390 |
map { trim($_) } |
426 |
(split(m{,}, $data->{config}->email_recipient_address),
|
|
427 |
$data->{config}->email_recipient_contact ? ($data->{config}->email_recipient_contact->cp_email) : (),
|
|
428 |
$data->{invoice}->{customer}->invoice_mail ? ($data->{invoice}->{customer}->invoice_mail) : ()
|
|
391 |
(split(m{,}, $config->email_recipient_address),
|
|
392 |
$config->email_recipient_contact ? ($config->email_recipient_contact->cp_email) : (),
|
|
393 |
$invoice->{customer}->invoice_mail ? ($invoice->{customer}->invoice_mail) : ()
|
|
429 | 394 |
); |
430 | 395 |
|
431 | 396 |
return unless @recipients; |
432 | 397 |
|
433 |
my $language = $data->{invoice}->language ? $data->{invoice}->language->template_code : undef;
|
|
398 |
my $language = $invoice->language ? $invoice->language->template_code : undef;
|
|
434 | 399 |
my %create_params = ( |
435 | 400 |
template => scalar($self->find_template(name => 'invoice', language => $language)), |
436 | 401 |
variables => Form->new(''), |
437 | 402 |
return => 'file_name', |
438 |
record => $data->{invoice},
|
|
403 |
record => $invoice,
|
|
439 | 404 |
variable_content_types => { |
440 | 405 |
longdescription => 'html', |
441 | 406 |
partnotes => 'html', |
... | ... | |
444 | 409 |
}, |
445 | 410 |
); |
446 | 411 |
|
447 |
$data->{invoice}->flatten_to_form($create_params{variables}, format_amounts => 1);
|
|
412 |
$invoice->flatten_to_form($create_params{variables}, format_amounts => 1);
|
|
448 | 413 |
$create_params{variables}->prepare_for_printing; |
449 | 414 |
|
450 | 415 |
my $pdf_file_name; |
... | ... | |
453 | 418 |
eval { |
454 | 419 |
$pdf_file_name = $self->create_pdf(%create_params); |
455 | 420 |
|
456 |
$self->_store_pdf_in_webdav ($pdf_file_name, $data->{invoice});
|
|
457 |
$self->_store_pdf_in_filemanagement($pdf_file_name, $data->{invoice});
|
|
421 |
$self->_store_pdf_in_webdav ($pdf_file_name, $invoice);
|
|
422 |
$self->_store_pdf_in_filemanagement($pdf_file_name, $invoice);
|
|
458 | 423 |
|
459 | 424 |
for (qw(email_subject email_body)) { |
460 | 425 |
_replace_vars( |
461 |
object => $data->{config},
|
|
462 |
invoice => $data->{invoice},
|
|
463 |
vars => $data->{time_period_vars},
|
|
426 |
object => $config,
|
|
427 |
invoice => $invoice,
|
|
428 |
vars => $time_period_vars,
|
|
464 | 429 |
attribute => $_, |
465 | 430 |
attribute_format => ($_ eq 'email_body' ? 'html' : 'text') |
466 | 431 |
); |
... | ... | |
471 | 436 |
|
472 | 437 |
for my $recipient (@recipients) { |
473 | 438 |
my $mail = Mailer->new; |
474 |
$mail->{record_id} = $data->{invoice}->id,
|
|
439 |
$mail->{record_id} = $invoice->id,
|
|
475 | 440 |
$mail->{record_type} = 'invoice', |
476 |
$mail->{from} = $data->{config}->email_sender || $::lx_office_conf{periodic_invoices}->{email_from};
|
|
441 |
$mail->{from} = $config->email_sender || $::lx_office_conf{periodic_invoices}->{email_from};
|
|
477 | 442 |
$mail->{to} = $recipient; |
478 | 443 |
$mail->{bcc} = $global_bcc; |
479 |
$mail->{subject} = $data->{config}->email_subject;
|
|
480 |
$mail->{message} = $data->{config}->email_body;
|
|
444 |
$mail->{subject} = $config->email_subject;
|
|
445 |
$mail->{message} = $config->email_body;
|
|
481 | 446 |
$mail->{message} .= SL::DB::Default->get->signature; |
482 | 447 |
$mail->{content_type} = 'text/html'; |
483 | 448 |
$mail->{attachments} = [{ |
484 | 449 |
path => $pdf_file_name, |
485 |
name => sprintf('%s %s.pdf', $label, $data->{invoice}->invnumber),
|
|
450 |
name => sprintf('%s %s.pdf', $label, $invoice->invnumber),
|
|
486 | 451 |
}]; |
487 | 452 |
|
488 | 453 |
my $error = $mail->send; |
489 | 454 |
|
490 | 455 |
if ($error) { |
491 | 456 |
push @{ $self->{job_errors} }, $error; |
492 |
push @{ $self->{emailed_failed} }, [ $data->{invoice}, $error ];
|
|
457 |
push @{ $self->{emailed_failed} }, [ $invoice, $error ];
|
|
493 | 458 |
$overall_error = 1; |
494 | 459 |
} |
495 | 460 |
} |
496 | 461 |
|
497 |
push @{ $self->{emailed_invoices} }, $data->{invoice} unless $overall_error;
|
|
462 |
push @{ $self->{emailed_invoices} }, $invoice unless $overall_error;
|
|
498 | 463 |
|
499 | 464 |
1; |
500 | 465 |
|
501 | 466 |
} or do { |
502 | 467 |
push @{ $self->{job_errors} }, $EVAL_ERROR; |
503 |
push @{ $self->{emailed_failed} }, [ $data->{invoice}, $EVAL_ERROR ];
|
|
468 |
push @{ $self->{emailed_failed} }, [ $invoice, $EVAL_ERROR ];
|
|
504 | 469 |
}; |
505 | 470 |
|
506 | 471 |
unlink $pdf_file_name if $pdf_file_name; |
... | ... | |
521 | 486 |
|
522 | 487 |
=head1 SYNOPSIS |
523 | 488 |
|
524 |
Iterate over all periodic invoice configurations, extend them if |
|
525 |
applicable, calculate the dates for which invoices have to be posted |
|
526 |
and post those invoices by converting the order into an invoice for |
|
527 |
each date. |
|
489 |
Iterate over all periodic invoice configurations, extend the end date if |
|
490 |
applicable, get all open orders from the |
|
528 | 491 |
|
529 | 492 |
=head1 TOTO |
530 | 493 |
|
Auch abrufbar als: Unified diff
S:B:CreatePeriodicInvoices: Nutze Auftragshelferfunktion von Config