Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 0cdb369e

Von Tamino Steinert vor etwa 2 Monaten hinzugefügt

  • ID 0cdb369e6aa60b6e1a5a2597312b2ebd2da1544f
  • Vorgänger d24e5589
  • Nachfolger 7589c136

S:D:PeriodicInvoicesConfig: Positionskonfiguration beachten

Unterschiede anzeigen:

SL/BackgroundJob/CreatePeriodicInvoices.pm
40 40
    my $configs = SL::DB::Manager::PeriodicInvoicesConfig->get_all(query => [ active => 1 ]);
41 41

  
42 42
    foreach my $config (@{ $configs }) {
43
      my $new_end_date = $config->handle_automatic_extension;
44
      _log_msg("Periodic invoice configuration ID " . $config->id . " extended through " . $new_end_date->strftime('%d.%m.%Y') . "\n") if $new_end_date;
43
      $config->handle_automatic_extension();
45 44
    }
46 45

  
47 46
    my (@invoices_to_print, @invoices_to_email);
SL/DB/PeriodicInvoiceItemsConfig.pm
1
# This file has been auto-generated only because it didn't exist.
2
# Feel free to modify it at will; it will not be overwritten automatically.
3

  
4 1
package SL::DB::PeriodicInvoiceItemsConfig;
5 2

  
6 3
use strict;
......
8 5
use SL::DB::MetaSetup::PeriodicInvoiceItemsConfig;
9 6
use SL::DB::Manager::PeriodicInvoiceItemsConfig;
10 7

  
8
use SL::DB::PeriodicInvoicesConfig;
9

  
11 10
__PACKAGE__->meta->initialize;
12 11

  
12
our %ITEM_PERIOD_LENGTHS = ( %SL::DB::PeriodicInvoicesConfig::PERIOD_LENGTHS, n => -1 );
13

  
14
sub get_item_period_length {
15
  my ($self) = @_;
16
  return $self->order_item->order->periodic_invoices_config->get_billing_period_length if $self->periodicity eq 'p';
17
  return $ITEM_PERIOD_LENGTHS{ $self->periodicity };
18
}
19

  
13 20
1;
SL/DB/PeriodicInvoicesConfig.pm
31 31
      default   => DateTime->today_local,
32 32
    },
33 33
  });
34
  return [] unless $self->active;
35 34

  
36 35
  my @start_dates = $self->calculate_invoice_dates(%params);
37 36
  return [] unless scalar @start_dates;
38 37

  
39 38
  my $orig_order = $self->order;
40 39

  
41
  my $next_period_start_date = $self->get_next_period_start_date;
42

  
43 40
  my @orders;
44 41
  foreach my $period_start_date (@start_dates) {
45 42
    my $new_order = clone($orig_order);
......
51 48
    );
52 49
    my @items;
53 50
    for my $item ($orig_order->items) {
54
      if ($item->periodic_invoice_items_config) {
55
        next if $item->periodic_invoice_items_config->periodicity eq 'n';
56
        next if $item->periodic_invoice_items_config->periodicity eq 'o' && (
57
          $item->periodic_invoice_items_config->once_invoice_id
58
          || $period_start_date != $next_period_start_date
59
        );
60
      }
61 51

  
62
      my $new_item = clone($item);
63

  
64
      $new_item = $self->_adjust_sellprices_for_period(
65
          order_item => $new_item,
66
          period_start_date => $period_start_date,
52
      my $new_item = $self->_create_item_for_period(
53
        order_item => $item,
54
        period_start_date => $period_start_date,
67 55
      );
68 56

  
69
      push @items, $new_item;
57
      push @items, $new_item if $new_item;
70 58
    }
71 59
    if (scalar @items) { # don't return empty orders
72 60
      $new_order->items(@items);
......
77 65
  return \@orders;
78 66
}
79 67

  
68
sub _create_item_for_period {
69
  my $self = shift;
70

  
71
  my %params = validate(@_, {
72
    period_start_date => { callbacks => { is_date => \&_is_date, } },
73
    order_item => { isa => 'SL::DB::OrderItem' },
74
  });
75

  
76
  my $item = $params{order_item};
77
  my $period_start_date = $params{period_start_date};
78

  
79
  my $new_item = clone($item);
80

  
81
  my $item_config = $item->periodic_invoice_items_config;
82
  if ($item_config) {
83
    return if $item_config->periodicity eq 'n';
84
    if ($item_config->periodicity eq 'o') {
85
      return if $item->periodic_invoice_items_config->once_invoice_id;
86
      my $next_period_start_date = $self->get_next_period_start_date(order_item => $item);
87
      my $period = $self->get_billing_period_length || 1;
88
      return if $period_start_date < $next_period_start_date
89
        || $period_start_date > add_months($next_period_start_date, $period);
90
    }
91
    return if $item_config->start_date && $item_config->start_date > $period_start_date;
92
    if ($item_config->terminated || !$item_config->extend_automatically_by) {
93
      return if $item_config->end_date   && $item_config->end_date   < $period_start_date;
94
    }
95

  
96
    my $i_period = $item_config->get_item_period_length;
97
    my $b_period = $self->get_billing_period_length;
98
    return $new_item unless $i_period && $b_period;
99

  
100
    if ($i_period > $b_period) {
101
      my $start_date = $item_config->start_date
102
        || $self->first_billing_date || $self->start_date;
103
      my $months_from_start_date =
104
          ($period_start_date->year  - $start_date->year) * 12
105
        + ($period_start_date->month - $start_date->month);
106
      my $first_in_sub_period = $months_from_start_date % ($i_period / $b_period) == 0 ? 1 : 0;
107
      return if !$first_in_sub_period;
108
    } elsif ($i_period < $b_period) {
109
      $new_item->qty($new_item->qty * $b_period / $i_period);
110
    }
111
  }
112

  
113
  $new_item = $self->_adjust_sellprices_for_period(
114
      order_item => $new_item,
115
      period_start_date => $period_start_date,
116
  );
117
  return $new_item
118
}
119

  
80 120
sub _adjust_sellprices_for_period {
81 121
  my $self = shift;
82 122

  
......
179 219
sub get_next_period_start_date {
180 220
  my $self = shift;
181 221

  
222
  my %params = validate(@_, {
223
    order_item => { isa => 'SL::DB::OrderItem' },
224
  });
225

  
226
  my $item = $params{order_item};
227

  
182 228
  my $last_created_on_date = $self->get_previous_billed_period_start_date;
183 229

  
184
  return $self->first_billing_date || $self->start_date unless $last_created_on_date;
230
  return (
231
      $item->periodic_invoice_items_config ?
232
        $item->periodic_invoice_items_config->start_date
233
      : undef
234
    )
235
    || $self->first_billing_date || $self->start_date unless $last_created_on_date;
236

  
237
  my $billing_len = $item->periodic_invoice_items_config ?
238
      $item->periodic_invoice_items_config->get_item_period_length
239
    : $self->get_billing_period_length;
240

  
185 241

  
186 242
  my @dates = $self->calculate_invoice_dates(
187
    end_date => add_months($last_created_on_date, $self->get_billing_period_length)
243
    end_date => add_months($last_created_on_date, $billing_len)
188 244
  );
189 245

  
190 246
  return scalar @dates ? $dates[0] : undef;
......
232 288
sub handle_automatic_extension {
233 289
  my $self = shift;
234 290

  
235
  _log_msg("HAE for " . $self->id . "\n");
236
  # Don't extend configs that have been terminated. There's nothing to
237
  # extend if there's no end date.
238
  return if $self->terminated || !$self->end_date;
291
  my $today = DateTime->now_local;
292
  my $active = 0; # inactivate if end date is reached (for self and all positions)
239 293

  
240
  my $today    = DateTime->now_local;
241
  my $end_date = $self->end_date;
294
  if ($self->end_date && $self->end_date < $today) {
295
    if (!$self->terminated && $self->extend_automatically_by) {
296
      $active = 1;
242 297

  
243
  _log_msg("today $today end_date $end_date\n");
244

  
245
  # The end date has not been reached yet, therefore no extension is
246
  # needed.
247
  return if $today <= $end_date;
248

  
249
  # The end date has been reached. If no automatic extension has been
250
  # set then terminate the config and return.
251
  if (!$self->extend_automatically_by) {
252
    _log_msg("setting inactive\n");
253
    $self->active(0);
254
    $self->save;
255
    return;
298
      my $end_date = $self->end_date;
299
      $end_date = add_months($end_date, $self->extend_automatically_by) while $today > $end_date;
300
      _log_msg("HAE for " . $self->id . " from " . $self->end_date . " to " . $end_date . " on " . $today . "\n");
301
      $self->update_attributes(end_date => $end_date);
302
    }
303
  } else {
304
    $active = 1;
256 305
  }
257 306

  
258
  # Add the automatic extension period to the new end date as long as
259
  # the new end date is in the past. Then save it and get out.
260
  $end_date = add_months($end_date, $self->extend_automatically_by) while $today > $end_date;
261
  _log_msg("new end date $end_date\n");
307
  # check for positions with separate config
308
  for my $item ($self->order->items()) {
309
    my $item_config = $item->periodic_invoice_items_config;
310
    next unless $item_config;
311
    if ($item_config->end_date && $item_config->end_date < $today) {
312
      if (!$item_config->terminated && $item_config->extend_automatically_by) {
313
        $active = 1;
314

  
315
        my $end_date = $item_config->end_date;
316
        $end_date = add_months($end_date, $item_config->extend_automatically_by) while $today > $end_date;
317
        _log_msg("HAE for item " . $item->id . " from " . $item_config->end_date . " to " . $end_date . " on " . $today . "\n");
318
        $item_config->update_attributes(end_date => $end_date);
319
      }
320
    } else {
321
      $active = 1;
322
    }
323
  }
262 324

  
263
  $self->end_date($end_date);
264
  $self->save;
325
  unless ($active) {
326
    $self->update_attributes(active => 0)
327
  }
265 328

  
266
  return $end_date;
329
  return;
267 330
}
268 331

  
269 332
sub get_previous_billed_period_start_date {
......
333 396

  
334 397
SL::DB::PeriodicInvoicesConfig - DB model for the configuration for periodic invoices
335 398

  
399
=head1 SYNOPSIS
400

  
401
  $open_orders = $config->get_open_orders_for_period(
402
    start_date => $config->start_date,
403
    end_date   => DateTime->today_local,
404
  );
405

  
406
  # Same as:
407
  $open_orders = $config->get_open_orders_for_period();
408

  
409
  # create invoices
410
  SL::DB::Invoice->new_from($_)->post() for @{$open_orders}
411
  # TODO: update configs with periodicity once
412

  
413
  # sum netamount
414
  my $netamount = 0;
415
  $netamount += $_->netamount for @{$open_orders};
416

  
336 417
=head1 FUNCTIONS
337 418

  
338 419
=over 4
339 420

  
421
=item C<get_open_orders_for_period %params>
422

  
423
Creates a list of copies of the order associated with this configuration for
424
each date a invoice would be created in the given period. Each copie has the
425
correct dates and items set to be converted to a invoice or used in a report.
426
The period can be specified using the parameters C<start_date> and C<end_date>.
427

  
428
Parameters:
429

  
430
=over 2
431

  
432
=item * C<start_date> specifies the start of the period. It can be a L<DateTime>
433
object or a string in the fromat C<YYYY-MM-DD>. It defaults to the C<start_date>
434
of the configuration.
435

  
436
=item * C<end_date> specifies the end of the period. It has the same type as
437
C<start_date>. It defaults to the current local time.
438

  
439
=back
440

  
340 441
=item C<calculate_invoice_dates %params>
341 442

  
342 443
Calculates dates for which invoices will have to be created. Returns a
......
390 491

  
391 492
=item C<handle_automatic_extension>
392 493

  
393
Configurations which haven't been terminated and which have an end
394
date set may be eligible for automatic extension by a certain number
395
of months. This what the function implements.
396

  
397
If the configuration is not eligible or if the C<end_date> hasn't been
398
reached yet then nothing is done and C<undef> is returned. Otherwise
399
its behavior is determined by the C<extend_automatically_by> property.
400

  
401
If the property C<extend_automatically_by> is not 0 then the
402
C<end_date> will be extended by C<extend_automatically_by> months, and
403
the configuration will be saved. In this case the new end date will be
404
returned.
405

  
406
Otherwise (if C<extend_automatically_by> is 0) the property C<active>
407
will be set to 1, and the configuration will be saved. In this case
408
C<undef> will be returned.
494
Updates the C<end_date>s according to C<terminated> and
495
C<extend_automatically_by> of this configuration and the corresponding item
496
configuration if the C<end_date> is after the current date. If at the end all
497
configurations are after the corresponding C<end_date>, e.g. C<end_date> is
498
reached and C<terminated> is set to true or C<extend_automatically_by> is set to
499
0, this configuration is set to inactive.
409 500

  
410 501
=item C<is_last_billing_date_in_order_value_cycle %params>
411 502

  
t/background_job/create_periodic_invoices.t
123 123
    terminated => 1,
124 124
  });
125 125
are_invoices 'p=m ovp=p not extend',[ '01.01.2014', 333.33 ];
126
is '2014-01-31T00:00:00', SL::DB::Manager::PeriodicInvoicesConfig->get_all(query => [ active => 1 ])->[0]->end_date, 'check automatically extended end date';
126
is '2014-01-31T00:00:00', SL::DB::Manager::PeriodicInvoicesConfig->get_all(query => [ active => 0 ])->[0]->end_date, 'check automatically extended end date';
127 127

  
128 128
# order_value_periodicity=y
129 129
create_invoices(periodic_invoices_config => { periodicity => 'm', order_value_periodicity => 'y', start_date => DateTime->from_kivitendo('01.01.2013') });

Auch abrufbar als: Unified diff