1 package SL::DB::PeriodicInvoicesConfig;
 
   5 use SL::DB::MetaSetup::PeriodicInvoicesConfig;
 
   7 use List::Util qw(max min);
 
   9 __PACKAGE__->meta->initialize;
 
  11 # Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
 
  12 __PACKAGE__->meta->make_manager_class;
 
  14 our %PERIOD_LENGTHS             = ( o => 0, m => 1, q => 3, b => 6, y => 12 );
 
  15 our %ORDER_VALUE_PERIOD_LENGTHS = ( %PERIOD_LENGTHS, 2 => 24, 3 => 36, 4 => 48, 5 => 60 );
 
  16 our @PERIODICITIES              = keys %PERIOD_LENGTHS;
 
  17 our @ORDER_VALUE_PERIODICITIES  = keys %ORDER_VALUE_PERIOD_LENGTHS;
 
  19 sub get_billing_period_length {
 
  21   return $PERIOD_LENGTHS{ $self->periodicity } || 1;
 
  24 sub get_order_value_period_length {
 
  26   return $self->get_billing_period_length if $self->order_value_periodicity eq 'p';
 
  27   return $ORDER_VALUE_PERIOD_LENGTHS{ $self->order_value_periodicity } || 1;
 
  31   $::lxdebug->message(LXDebug->DEBUG1(), join('', 'SL::DB::PeriodicInvoicesConfig: ', @_));
 
  34 sub handle_automatic_extension {
 
  37   _log_msg("HAE for " . $self->id . "\n");
 
  38   # Don't extend configs that have been terminated. There's nothing to
 
  39   # extend if there's no end date.
 
  40   return if $self->terminated || !$self->end_date;
 
  42   my $today    = DateTime->now_local;
 
  43   my $end_date = $self->end_date;
 
  45   _log_msg("today $today end_date $end_date\n");
 
  47   # The end date has not been reached yet, therefore no extension is
 
  49   return if $today <= $end_date;
 
  51   # The end date has been reached. If no automatic extension has been
 
  52   # set then terminate the config and return.
 
  53   if (!$self->extend_automatically_by) {
 
  54     _log_msg("setting inactive\n");
 
  60   # Add the automatic extension period to the new end date as long as
 
  61   # the new end date is in the past. Then save it and get out.
 
  62   $end_date->add(months => $self->extend_automatically_by) while $today > $end_date;
 
  63   _log_msg("new end date $end_date\n");
 
  65   $self->end_date($end_date);
 
  71 sub get_previous_billed_period_start_date {
 
  75     SELECT MAX(period_start_date)
 
  76     FROM periodic_invoices
 
  80   my ($date) = $self->dbh->selectrow_array($query, undef, $self->id);
 
  82   return undef unless $date;
 
  83   return ref $date ? $date : $self->db->parse_date($date);
 
  86 sub calculate_invoice_dates {
 
  87   my ($self, %params) = @_;
 
  89   my $period_len = $self->get_billing_period_length;
 
  90   my $cur_date   = ($self->first_billing_date || $self->start_date)->clone;
 
  91   my $end_date   = $self->terminated || !$self->extend_automatically_by ? $self->end_date : undef;
 
  92   $end_date    //= DateTime->today_local->add(years => 100);
 
  93   my $start_date = $params{past_dates} ? undef                              : $self->get_previous_billed_period_start_date;
 
  94   $start_date    = $start_date         ? $start_date->clone->add(days => 1) : $cur_date->clone;
 
  96   $start_date    = max($start_date, $params{start_date}) if $params{start_date};
 
  97   $end_date      = min($end_date,   $params{end_date})   if $params{end_date};
 
  99   if ($self->periodicity eq 'o') {
 
 100     return ($cur_date >= $start_date) && ($cur_date <= $end_date) ? ($cur_date) : ();
 
 105   while ($cur_date <= $end_date) {
 
 106     push @dates, $cur_date->clone if $cur_date >= $start_date;
 
 108     $cur_date->add(months => $period_len);
 
 114 sub is_last_bill_date_in_order_value_cycle {
 
 115   my ($self, %params)    = @_;
 
 117   my $months_billing     = $self->get_billing_period_length;
 
 118   my $months_order_value = $self->get_order_value_period_length;
 
 120   return 1 if $months_billing >= $months_order_value;
 
 122   my $next_billing_date = $params{date}->clone->add(months => $months_billing);
 
 123   my $date_itr          = max($self->start_date, $self->first_billing_date || $self->start_date)->clone;
 
 125   _log_msg("is_last_billing_date_in_order_value_cycle start: id " . $self->id . " date_itr $date_itr start " . $self->start_date);
 
 127   $date_itr->add(months => $months_order_value) while $date_itr < $next_billing_date;
 
 129   _log_msg("is_last_billing_date_in_order_value_cycle end: refdate $params{date} next_billing_date $next_billing_date date_itr $date_itr months_billing $months_billing months_order_value $months_order_value result "
 
 130            . ($date_itr == $next_billing_date));
 
 132   return $date_itr == $next_billing_date;
 
 135 sub disable_one_time_config {
 
 138   _log_msg("check one time for " . $self->id . "\n");
 
 140   # A periodicity of one time was set. Deactivate this config now.
 
 141   if ($self->periodicity eq 'o') {
 
 142     _log_msg("setting inactive\n");
 
 143     if (!$self->db->with_transaction(sub {
 
 144       1;                          # make Emacs happy
 
 146       $self->order->update_attributes(closed => 1);
 
 150       $::lxdebug->message(LXDebug->WARN(), "disalbe_one_time config failed: " . join("\n", (split(/\n/, $self->{db_obj}->db->error))[0..2]));
 
 153     return $self->order->ordnumber;
 
 166 SL::DB::PeriodicInvoicesConfig - DB model for the configuration for periodic invoices
 
 172 =item C<calculate_invoice_dates %params>
 
 174 Calculates dates for which invoices will have to be created. Returns a
 
 175 list of L<DateTime> objects.
 
 177 This function looks at the configuration settings and at the list of
 
 178 invoices that have already been created for this configuration. The
 
 179 date range for which dates are created are controlled by several
 
 184 =item * The properties C<first_billing_date> and C<start_date>
 
 185 determine the start date.
 
 187 =item * The properties C<end_date> and C<terminated> determine the end
 
 190 =item * The optional parameter C<past_dates> determines whether or not
 
 191 dates for which invoices have already been created will be included in
 
 192 the list. The default is not to include them.
 
 194 =item * The optional parameters C<start_date> and C<end_date> override
 
 195 the start and end dates from the configuration.
 
 197 =item * If no end date is set or implied via the configuration and no
 
 198 C<end_date> parameter is given then the function will use 100 years
 
 199 in the future as the end date.
 
 203 =item C<get_billing_period_length>
 
 205 Returns the number of months corresponding to the billing
 
 206 periodicity. This means that a new invoice has to be created every x
 
 207 months starting with the value in C<first_billing_date> (or
 
 208 C<start_date> if C<first_billing_date> is unset).
 
 210 =item C<get_order_value_period_length>
 
 212 Returns the number of months the order's value refers to. This looks
 
 213 at the C<order_value_periodicity>.
 
 215 Each invoice's value is calculated as C<order value *
 
 216 billing_period_length / order_value_period_length>.
 
 218 =item C<get_previous_billed_period_start_date>
 
 220 Returns the highest date (as an instance of L<DateTime>) for which an
 
 221 invoice has been created from this configuration.
 
 223 =item C<handle_automatic_extension>
 
 225 Configurations which haven't been terminated and which have an end
 
 226 date set may be eligible for automatic extension by a certain number
 
 227 of months. This what the function implements.
 
 229 If the configuration is not eligible or if the C<end_date> hasn't been
 
 230 reached yet then nothing is done and C<undef> is returned. Otherwise
 
 231 its behavior is determined by the C<extend_automatically_by> property.
 
 233 If the property C<extend_automatically_by> is not 0 then the
 
 234 C<end_date> will be extended by C<extend_automatically_by> months, and
 
 235 the configuration will be saved. In this case the new end date will be
 
 238 Otherwise (if C<extend_automatically_by> is 0) the property C<active>
 
 239 will be set to 1, and the configuration will be saved. In this case
 
 240 C<undef> will be returned.
 
 242 =item C<is_last_billing_date_in_order_value_cycle %params>
 
 244 Determines whether or not the mandatory parameter C<date>, an instance
 
 245 of L<DateTime>, is the last billing date within the cycle given by the
 
 246 order value periodicity. Returns a truish value if this is the case
 
 247 and a falsish value otherwise.
 
 249 This check is always true if the billing periodicity is longer than or
 
 250 equal to the order value periodicity. For example, if you have an
 
 251 order whose value is given for three months and you bill every six
 
 252 months and you have twice the order value on each invoice, meaning
 
 253 each invoice is itself the last invoice for not only one but two order
 
 256 Otherwise (if the order value periodicity is longer than the billing
 
 257 periodicity) this function iterates over all eligible dates starting
 
 258 with C<first_billing_date> (or C<start_date> if C<first_billing_date>
 
 259 is unset) and adding the order value length with each step. If the
 
 260 date given by the C<date> parameter plus the billing period length
 
 261 equals one of those dates then the given date is indeed the date of
 
 262 the last invoice in that particular order value cycle.
 
 264 =item C<sub disable_one_time_config>
 
 266 Sets the state of the periodic_invoices_configs to inactive
 
 267 (active => false) and closes the source order (closed => true)
 
 268 if the periodicity is <Co> (one time).
 
 270 Returns undef if the periodicity is not 'one time' otherwise the
 
 271 order number of the deactivated periodic order.
 
 281 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>