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);
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") . "/oe/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>