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 $order->link_to_record($invoice);
147 SL::DB::PeriodicInvoice->new(config_id => $config->id,
148 ar_id => $invoice->id,
149 period_start_date => $period_start_date)
152 # die $invoice->transaction_description;
154 $::lxdebug->message(LXDebug->WARN(), "_create_invoice failed: " . join("\n", (split(/\n/, $self->{db_obj}->db->error))[0..2]));
161 sub _calculate_dates {
164 my $cur_date = $config->start_date;
165 my $start_date = $config->get_previous_invoice_date || DateTime->new(year => 1970, month => 1, day => 1);
166 my $end_date = $config->end_date || DateTime->new(year => 2100, month => 1, day => 1);
167 my $tomorrow = DateTime->today_local->add(days => 1);
168 my $period_len = $config->get_period_length;
170 $end_date = $tomorrow if $end_date > $tomorrow;
175 last if $cur_date >= $end_date;
177 push @dates, $cur_date->clone if $cur_date > $start_date;
179 $cur_date->add(months => $period_len);
186 my ($posted_invoices, $printed_invoices) = @_;
188 my %config = %::lx_office_conf;
190 return if !$config{periodic_invoices} || !$config{periodic_invoices}->{send_email_to} || !scalar @{ $posted_invoices };
192 my $user = SL::DB::Manager::AuthUser->find_by(login => $config{periodic_invoices}->{send_email_to});
193 my $email = $user ? $user->get_config_value('email') : undef;
195 return unless $email;
197 my $template = Template->new({ 'INTERPOLATE' => 0,
203 return unless $template;
205 my $email_template = $config{periodic_invoices}->{email_template};
206 my $filename = $email_template || ( (SL::DB::Default->get->templates || "templates/webpages") . "/periodic_invoices_email.txt" );
207 my %params = ( POSTED_INVOICES => $posted_invoices,
208 PRINTED_INVOICES => $printed_invoices );
211 $template->process($filename, \%params, \$output);
213 my $mail = Mailer->new;
214 $mail->{from} = $config{periodic_invoices}->{email_from};
215 $mail->{to} = $email;
216 $mail->{subject} = $config{periodic_invoices}->{email_subject};
217 $mail->{content_type} = $filename =~ m/.html$/ ? 'text/html' : 'text/plain';
218 $mail->{message} = $output;
224 my ($invoice, $config) = @_;
226 return unless $config->print && $config->printer_id && $config->printer->printer_command;
228 my $form = Form->new;
229 $invoice->flatten_to_form($form, format_amounts => 1);
231 $form->{printer_code} = $config->printer->template_code;
232 $form->{copies} = $config->copies;
233 $form->{formname} = $form->{type};
234 $form->{format} = 'pdf';
235 $form->{media} = 'printer';
236 $form->{OUT} = $config->printer->printer_command;
237 $form->{OUT_MODE} = '|-';
239 $form->prepare_for_printing;
241 $form->throw_on_error(sub {
243 $form->parse_template(\%::myconfig);
245 } || die $EVAL_ERROR->getMessage;
259 SL::BackgroundJob::CleanBackgroundJobHistory - Create periodic
264 Iterate over all periodic invoice configurations, extend them if
265 applicable, calculate the dates for which invoices have to be posted
266 and post those invoices by converting the order into an invoice for
275 Strings like month names are hardcoded to German in this file.
281 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>