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>