1 package SL::BackgroundJob::CreatePeriodicInvoices;
 
   5 use parent qw(SL::BackgroundJob::Base);
 
   8 use DateTime::Format::Strptime;
 
   9 use English qw(-no_match_vars);
 
  15 use SL::DB::PeriodicInvoice;
 
  16 use SL::DB::PeriodicInvoicesConfig;
 
  20   $_[0]->create_standard_job('0 3 1 * *'); # first day of month at 3:00 am
 
  25   $self->{db_obj} = shift;
 
  27   my $configs = SL::DB::Manager::PeriodicInvoicesConfig->get_all(query => [ active => 1 ]);
 
  29   foreach my $config (@{ $configs }) {
 
  30     my $new_end_date = $config->handle_automatic_extension;
 
  31     _log_msg("Periodic invoice configuration ID " . $config->id . " extended through " . $new_end_date->strftime('%d.%m.%Y') . "\n") if $new_end_date;
 
  34   my (@new_invoices, @invoices_to_print);
 
  36   _log_msg("Number of configs: " . scalar(@{ $configs}));
 
  38   foreach my $config (@{ $configs }) {
 
  39     # A configuration can be set to inactive by
 
  40     # $config->handle_automatic_extension. Therefore the check in
 
  41     # ...->get_all() does not suffice.
 
  42     _log_msg("Config " . $config->id . " active " . $config->active);
 
  43     next unless $config->active;
 
  45     my @dates = _calculate_dates($config);
 
  47     _log_msg("Dates: " . join(' ', map { $_->to_lxoffice } @dates));
 
  49     foreach my $date (@dates) {
 
  50       my $invoice = $self->_create_periodic_invoice($config, $date);
 
  53       _log_msg("Invoice " . $invoice->invnumber . " posted for config ID " . $config->id . ", period start date " . $::locale->format_date(\%::myconfig, $date) . "\n");
 
  54       push @new_invoices,      $invoice;
 
  55       push @invoices_to_print, [ $invoice, $config ] if $config->print;
 
  61   map { _print_invoice(@{ $_ }) } @invoices_to_print;
 
  63   _send_email(\@new_invoices, [ map { $_->[0] } @invoices_to_print ]) if @new_invoices;
 
  69   my $message  = join('', @_);
 
  70   $message    .= "\n" unless $message =~ m/\n$/;
 
  71   $::lxdebug->message(LXDebug::DEBUG1(), $message);
 
  74 sub _generate_time_period_variables {
 
  76   my $period_start_date = shift;
 
  77   my $period_end_date   = $period_start_date->clone->truncate(to => 'month')->add(months => $config->get_period_length)->subtract(days => 1);
 
  79   my @month_names       = ('',
 
  80                            $::locale->text('January'), $::locale->text('February'), $::locale->text('March'),     $::locale->text('April'),   $::locale->text('May'),      $::locale->text('June'),
 
  81                            $::locale->text('July'),    $::locale->text('August'),   $::locale->text('September'), $::locale->text('October'), $::locale->text('November'), $::locale->text('December'));
 
  84     current_quarter     => [ $period_start_date->clone->truncate(to => 'month'),                        sub { $_[0]->quarter } ],
 
  85     previous_quarter    => [ $period_start_date->clone->truncate(to => 'month')->subtract(months => 3), sub { $_[0]->quarter } ],
 
  86     next_quarter        => [ $period_start_date->clone->truncate(to => 'month')->add(     months => 3), sub { $_[0]->quarter } ],
 
  88     current_month       => [ $period_start_date->clone->truncate(to => 'month'),                        sub { $_[0]->month } ],
 
  89     previous_month      => [ $period_start_date->clone->truncate(to => 'month')->subtract(months => 1), sub { $_[0]->month } ],
 
  90     next_month          => [ $period_start_date->clone->truncate(to => 'month')->add(     months => 1), sub { $_[0]->month } ],
 
  92     current_year        => [ $period_start_date->clone->truncate(to => 'year'),                         sub { $_[0]->year } ],
 
  93     previous_year       => [ $period_start_date->clone->truncate(to => 'year')->subtract(years => 1),   sub { $_[0]->year } ],
 
  94     next_year           => [ $period_start_date->clone->truncate(to => 'year')->add(     years => 1),   sub { $_[0]->year } ],
 
  96     period_start_date   => [ $period_start_date->clone->truncate(to => 'month'), sub { $::locale->format_date(\%::myconfig, $_[0]) } ],
 
  97     period_end_date     => [ $period_end_date  ->clone->truncate(to => 'month'), sub { $::locale->format_date(\%::myconfig, $_[0]) } ],
 
 100   map { $vars->{"${_}_month_long"} = $month_names[ $vars->{"${_}_month"} ] } qw(current previous next);
 
 109   my $str    = $object->$sub;
 
 111   $str =~ s{ <\% ([a-z0-9_]+) ( \s+ format \s*=\s* (.*?) \s* )? \%>}{
 
 112     my ($key, $format) = ($1, $3);
 
 113     if (!$vars->{$key}) {
 
 117       DateTime::Format::Strptime->new(
 
 120         time_zone   => 'local',
 
 121       )->format_datetime($vars->{$key}->[0]);
 
 124       $vars->{$1}->[1]->($vars->{$1}->[0]);
 
 131 sub _create_periodic_invoice {
 
 134   my $period_start_date = shift;
 
 136   my $time_period_vars  = _generate_time_period_variables($config, $period_start_date);
 
 138   my $invdate           = DateTime->today_local;
 
 140   my $order   = $config->order;
 
 142   if (!$self->{db_obj}->db->do_transaction(sub {
 
 143     1;                          # make Emacs happy
 
 145     $invoice = SL::DB::Invoice->new_from($order);
 
 147     my $intnotes  = $invoice->intnotes ? $invoice->intnotes . "\n\n" : '';
 
 148     $intnotes    .= "Automatisch am " . $invdate->to_lxoffice . " erzeugte Rechnung";
 
 150     $invoice->assign_attributes(deliverydate => $period_start_date,
 
 151                                 intnotes     => $intnotes,
 
 154     map { _replace_vars($invoice, $time_period_vars, $_) } qw(notes intnotes transaction_description);
 
 156     foreach my $item (@{ $invoice->items }) {
 
 157       map { _replace_vars($item, $time_period_vars, $_) } qw(description longdescription);
 
 160     $invoice->post(ar_id => $config->ar_chart_id) || die;
 
 162     # like $form->add_shipto, but we don't need to check for a manual exception,
 
 163     # because we can already assume this (otherwise no shipto_id from order)
 
 164     if ($order->shipto_id) {
 
 166       my $shipto_oe = SL::DB::Manager::Shipto->find_by(shipto_id => $order->shipto_id);
 
 167       my $shipto_ar = $shipto_oe->clone_and_reset;
 
 169       $shipto_ar->module('AR');            # alter module OE -> AR
 
 170       $shipto_ar->trans_id($invoice->id);  # alter trans_id -> new id from invoice
 
 174     $order->link_to_record($invoice);
 
 176     SL::DB::PeriodicInvoice->new(config_id         => $config->id,
 
 177                                  ar_id             => $invoice->id,
 
 178                                  period_start_date => $period_start_date)
 
 181     # die $invoice->transaction_description;
 
 183     $::lxdebug->message(LXDebug->WARN(), "_create_invoice failed: " . join("\n", (split(/\n/, $self->{db_obj}->db->error))[0..2]));
 
 190 sub _calculate_dates {
 
 192   return $config->calculate_invoice_dates(end_date => DateTime->today_local->add(days => 1));
 
 196   my ($posted_invoices, $printed_invoices) = @_;
 
 198   my %config = %::lx_office_conf;
 
 200   return if !$config{periodic_invoices} || !$config{periodic_invoices}->{send_email_to} || !scalar @{ $posted_invoices };
 
 202   my $user  = SL::DB::Manager::AuthUser->find_by(login => $config{periodic_invoices}->{send_email_to});
 
 203   my $email = $user ? $user->get_config_value('email') : undef;
 
 205   return unless $email;
 
 207   my $template = Template->new({ 'INTERPOLATE' => 0,
 
 213   return unless $template;
 
 215   my $email_template = $config{periodic_invoices}->{email_template};
 
 216   my $filename       = $email_template || ( (SL::DB::Default->get->templates || "templates/webpages") . "/periodic_invoices_email.txt" );
 
 217   my %params         = ( POSTED_INVOICES  => $posted_invoices,
 
 218                          PRINTED_INVOICES => $printed_invoices );
 
 221   $template->process($filename, \%params, \$output);
 
 223   my $mail              = Mailer->new;
 
 224   $mail->{from}         = $config{periodic_invoices}->{email_from};
 
 225   $mail->{to}           = $email;
 
 226   $mail->{subject}      = $config{periodic_invoices}->{email_subject};
 
 227   $mail->{content_type} = $filename =~ m/.html$/ ? 'text/html' : 'text/plain';
 
 228   $mail->{message}      = $output;
 
 234   my ($invoice, $config) = @_;
 
 236   return unless $config->print && $config->printer_id && $config->printer->printer_command;
 
 238   my $form = Form->new;
 
 239   $invoice->flatten_to_form($form, format_amounts => 1);
 
 241   $form->{printer_code} = $config->printer->template_code;
 
 242   $form->{copies}       = $config->copies;
 
 243   $form->{formname}     = $form->{type};
 
 244   $form->{format}       = 'pdf';
 
 245   $form->{media}        = 'printer';
 
 246   $form->{OUT}          = $config->printer->printer_command;
 
 247   $form->{OUT_MODE}     = '|-';
 
 249   $form->prepare_for_printing;
 
 251   $form->throw_on_error(sub {
 
 253       $form->parse_template(\%::myconfig);
 
 255     } || die $EVAL_ERROR->getMessage;
 
 269 SL::BackgroundJob::CleanBackgroundJobHistory - Create periodic
 
 274 Iterate over all periodic invoice configurations, extend them if
 
 275 applicable, calculate the dates for which invoices have to be posted
 
 276 and post those invoices by converting the order into an invoice for
 
 285 Strings like month names are hardcoded to German in this file.
 
 291 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>