1 package SL::BackgroundJob::CreatePeriodicInvoices;
 
   5 use parent qw(SL::BackgroundJob::Base);
 
   8 use English qw(-no_match_vars);
 
  14 use SL::DB::PeriodicInvoice;
 
  15 use SL::DB::PeriodicInvoicesConfig;
 
  19   $_[0]->create_standard_job('0 3 1 * *'); # first day of month at 3:00 am
 
  24   $self->{db_obj} = shift;
 
  26   my $configs = SL::DB::Manager::PeriodicInvoicesConfig->get_all(query => [ active => 1 ]);
 
  28   foreach my $config (@{ $configs }) {
 
  29     my $new_end_date = $config->handle_automatic_extension;
 
  30     _log_msg("Periodic invoice configuration ID " . $config->id . " extended through " . $new_end_date->strftime('%d.%m.%Y') . "\n") if $new_end_date;
 
  33   my (@new_invoices, @invoices_to_print);
 
  35   _log_msg("Number of configs: " . scalar(@{ $configs}));
 
  37   foreach my $config (@{ $configs }) {
 
  38     # A configuration can be set to inactive by
 
  39     # $config->handle_automatic_extension. Therefore the check in
 
  40     # ...->get_all() does not suffice.
 
  41     _log_msg("Config " . $config->id . " active " . $config->active);
 
  42     next unless $config->active;
 
  44     my @dates = _calculate_dates($config);
 
  46     _log_msg("Dates: " . join(' ', map { $_->to_lxoffice } @dates));
 
  48     foreach my $date (@dates) {
 
  49       my $invoice = $self->_create_periodic_invoice($config, $date);
 
  52       _log_msg("Invoice " . $invoice->invnumber . " posted for config ID " . $config->id . ", period start date " . $::locale->format_date(\%::myconfig, $date) . "\n");
 
  53       push @new_invoices,      $invoice;
 
  54       push @invoices_to_print, [ $invoice, $config ] if $config->print;
 
  60   map { _print_invoice(@{ $_ }) } @invoices_to_print;
 
  62   _send_email(\@new_invoices, [ map { $_->[0] } @invoices_to_print ]) if @new_invoices;
 
  68   my $message  = join('', @_);
 
  69   $message    .= "\n" unless $message =~ m/\n$/;
 
  70   $::lxdebug->message(LXDebug::DEBUG1(), $message);
 
  73 sub _generate_time_period_variables {
 
  75   my $period_start_date = shift;
 
  76   my $period_end_date   = $period_start_date->clone->truncate(to => 'month')->add(months => $config->get_period_length)->subtract(days => 1);
 
  78   my @month_names       = ('',
 
  79                            $::locale->text('January'), $::locale->text('February'), $::locale->text('March'),     $::locale->text('April'),   $::locale->text('May'),      $::locale->text('June'),
 
  80                            $::locale->text('July'),    $::locale->text('August'),   $::locale->text('September'), $::locale->text('October'), $::locale->text('November'), $::locale->text('December'));
 
  82   my $vars = { current_quarter     => $period_start_date->quarter,
 
  83                previous_quarter    => $period_start_date->clone->subtract(months => 3)->quarter,
 
  84                next_quarter        => $period_start_date->clone->add(     months => 3)->quarter,
 
  86                current_month       => $period_start_date->month,
 
  87                previous_month      => $period_start_date->clone->subtract(months => 1)->month,
 
  88                next_month          => $period_start_date->clone->add(     months => 1)->month,
 
  90                current_year        => $period_start_date->year,
 
  91                previous_year       => $period_start_date->year - 1,
 
  92                next_year           => $period_start_date->year + 1,
 
  94                period_start_date   => $::locale->format_date(\%::myconfig, $period_start_date),
 
  95                period_end_date     => $::locale->format_date(\%::myconfig, $period_end_date),
 
  98   map { $vars->{"${_}_month_long"} = $month_names[ $vars->{"${_}_month"} ] } qw(current previous next);
 
 107   my $str    = $object->$sub;
 
 110   $str =~ s|<\%${key}\%>|$value|g while ($key, $value) = each %{ $vars };
 
 114 sub _create_periodic_invoice {
 
 117   my $period_start_date = shift;
 
 119   my $time_period_vars  = _generate_time_period_variables($config, $period_start_date);
 
 121   my $invdate           = DateTime->today_local;
 
 123   my $order   = $config->order;
 
 125   if (!$self->{db_obj}->db->do_transaction(sub {
 
 126     1;                          # make Emacs happy
 
 128     $invoice = SL::DB::Invoice->new_from($order);
 
 130     my $intnotes  = $invoice->intnotes ? $invoice->intnotes . "\n\n" : '';
 
 131     $intnotes    .= "Automatisch am " . $invdate->to_lxoffice . " erzeugte Rechnung";
 
 133     $invoice->assign_attributes(deliverydate => $period_start_date,
 
 134                                 intnotes     => $intnotes,
 
 137     map { _replace_vars($invoice, $time_period_vars, $_) } qw(notes intnotes transaction_description);
 
 139     foreach my $item (@{ $invoice->items }) {
 
 140       map { _replace_vars($item, $time_period_vars, $_) } qw(description longdescription);
 
 143     $invoice->post(ar_id => $config->ar_chart_id) || die;
 
 145     # like $form->add_shipto, but we don't need to check for a manual exception,
 
 146     # because we can already assume this (otherwise no shipto_id from order)
 
 147     if ($order->shipto_id) {
 
 149       my $shipto_oe = SL::DB::Manager::Shipto->find_by(shipto_id => $order->shipto_id);
 
 150       my $shipto_ar = $shipto_oe->clone_and_reset;
 
 152       $shipto_ar->module('AR');            # alter module OE -> AR
 
 153       $shipto_ar->trans_id($invoice->id);  # alter trans_id -> new id from invoice
 
 157     $order->link_to_record($invoice);
 
 159     SL::DB::PeriodicInvoice->new(config_id         => $config->id,
 
 160                                  ar_id             => $invoice->id,
 
 161                                  period_start_date => $period_start_date)
 
 164     # die $invoice->transaction_description;
 
 166     $::lxdebug->message(LXDebug->WARN(), "_create_invoice failed: " . join("\n", (split(/\n/, $self->{db_obj}->db->error))[0..2]));
 
 173 sub _calculate_dates {
 
 176   my $cur_date   = $config->start_date;
 
 177   my $start_date = $config->get_previous_invoice_date || DateTime->new(year => 1970, month => 1, day => 1);
 
 178   my $end_date   = $config->end_date                  || DateTime->new(year => 2100, month => 1, day => 1);
 
 179   my $tomorrow   = DateTime->today_local->add(days => 1);
 
 180   my $period_len = $config->get_period_length;
 
 182   $end_date      = $tomorrow if $end_date > $tomorrow;
 
 187     last if $cur_date >= $end_date;
 
 189     push @dates, $cur_date->clone if $cur_date > $start_date;
 
 191     $cur_date->add(months => $period_len);
 
 198   my ($posted_invoices, $printed_invoices) = @_;
 
 200   my %config = %::lx_office_conf;
 
 202   return if !$config{periodic_invoices} || !$config{periodic_invoices}->{send_email_to} || !scalar @{ $posted_invoices };
 
 204   my $user  = SL::DB::Manager::AuthUser->find_by(login => $config{periodic_invoices}->{send_email_to});
 
 205   my $email = $user ? $user->get_config_value('email') : undef;
 
 207   return unless $email;
 
 209   my $template = Template->new({ 'INTERPOLATE' => 0,
 
 215   return unless $template;
 
 217   my $email_template = $config{periodic_invoices}->{email_template};
 
 218   my $filename       = $email_template || ( (SL::DB::Default->get->templates || "templates/webpages") . "/periodic_invoices_email.txt" );
 
 219   my %params         = ( POSTED_INVOICES  => $posted_invoices,
 
 220                          PRINTED_INVOICES => $printed_invoices );
 
 223   $template->process($filename, \%params, \$output);
 
 225   my $mail              = Mailer->new;
 
 226   $mail->{from}         = $config{periodic_invoices}->{email_from};
 
 227   $mail->{to}           = $email;
 
 228   $mail->{subject}      = $config{periodic_invoices}->{email_subject};
 
 229   $mail->{content_type} = $filename =~ m/.html$/ ? 'text/html' : 'text/plain';
 
 230   $mail->{message}      = $output;
 
 236   my ($invoice, $config) = @_;
 
 238   return unless $config->print && $config->printer_id && $config->printer->printer_command;
 
 240   my $form = Form->new;
 
 241   $invoice->flatten_to_form($form, format_amounts => 1);
 
 243   $form->{printer_code} = $config->printer->template_code;
 
 244   $form->{copies}       = $config->copies;
 
 245   $form->{formname}     = $form->{type};
 
 246   $form->{format}       = 'pdf';
 
 247   $form->{media}        = 'printer';
 
 248   $form->{OUT}          = $config->printer->printer_command;
 
 249   $form->{OUT_MODE}     = '|-';
 
 251   $form->prepare_for_printing;
 
 253   $form->throw_on_error(sub {
 
 255       $form->parse_template(\%::myconfig);
 
 257     } || die $EVAL_ERROR->getMessage;
 
 271 SL::BackgroundJob::CleanBackgroundJobHistory - Create periodic
 
 276 Iterate over all periodic invoice configurations, extend them if
 
 277 applicable, calculate the dates for which invoices have to be posted
 
 278 and post those invoices by converting the order into an invoice for
 
 287 Strings like month names are hardcoded to German in this file.
 
 293 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>