+our %PERIOD_LENGTHS = ( m => 1, q => 3, b => 6, y => 12 );
+our %ORDER_VALUE_PERIOD_LENGTHS = ( %PERIOD_LENGTHS, 2 => 24, 3 => 36, 4 => 48, 5 => 60 );
+our @PERIODICITIES = keys %PERIOD_LENGTHS;
+our @ORDER_VALUE_PERIODICITIES = keys %ORDER_VALUE_PERIOD_LENGTHS;
+
+sub get_billing_period_length {
+ my $self = shift;
+ return $PERIOD_LENGTHS{ $self->periodicity } || 1;
+}
+
+sub get_order_value_period_length {
+ my $self = shift;
+ return $self->get_billing_period_length if $self->order_value_periodicity eq 'p';
+ return $ORDER_VALUE_PERIOD_LENGTHS{ $self->order_value_periodicity } || 1;
+}
+
+sub _log_msg {
+ $::lxdebug->message(LXDebug->DEBUG1(), join('', 'SL::DB::PeriodicInvoicesConfig: ', @_));
+}
+
+sub handle_automatic_extension {
+ my $self = shift;
+
+ _log_msg("HAE for " . $self->id . "\n");
+ # Don't extend configs that have been terminated. There's nothing to
+ # extend if there's no end date.
+ return if $self->terminated || !$self->end_date;
+
+ my $today = DateTime->now_local;
+ my $end_date = $self->end_date;
+
+ _log_msg("today $today end_date $end_date\n");
+
+ # The end date has not been reached yet, therefore no extension is
+ # needed.
+ return if $today <= $end_date;
+
+ # The end date has been reached. If no automatic extension has been
+ # set then terminate the config and return.
+ if (!$self->extend_automatically_by) {
+ _log_msg("setting inactive\n");
+ $self->active(0);
+ $self->save;
+ return;
+ }
+
+ # Add the automatic extension period to the new end date as long as
+ # the new end date is in the past. Then save it and get out.
+ $end_date->add(months => $self->extend_automatically_by) while $today > $end_date;
+ _log_msg("new end date $end_date\n");
+
+ $self->end_date($end_date);
+ $self->save;
+
+ return $end_date;
+}
+
+sub get_previous_billed_period_start_date {
+ my $self = shift;
+
+ my $query = <<SQL;
+ SELECT MAX(period_start_date)
+ FROM periodic_invoices
+ WHERE config_id = ?
+SQL
+
+ my ($date) = $self->dbh->selectrow_array($query, undef, $self->id);
+
+ return undef unless $date;
+ return ref $date ? $date : $self->db->parse_date($date);
+}
+
+sub calculate_invoice_dates {
+ my ($self, %params) = @_;
+
+ my $period_len = $self->get_billing_period_length;
+ my $cur_date = ($self->first_billing_date || $self->start_date)->clone;
+ my $end_date = $self->terminated ? $self->end_date : undef;
+ $end_date //= DateTime->today_local->add(years => 100);
+ my $start_date = $params{past_dates} ? undef : $self->get_previous_billed_period_start_date;
+ $start_date = $start_date ? $start_date->clone->add(days => 1) : $cur_date->clone;
+
+ $start_date = max($start_date, $params{start_date}) if $params{start_date};
+ $end_date = min($end_date, $params{end_date}) if $params{end_date};
+
+ my @dates;
+
+ while ($cur_date <= $end_date) {
+ push @dates, $cur_date->clone if $cur_date >= $start_date;
+
+ $cur_date->add(months => $period_len);
+ }
+
+ return @dates;
+}
+
+sub is_last_bill_date_in_order_value_cycle {
+ my ($self, %params) = @_;
+
+ my $months_billing = $self->get_billing_period_length;
+ my $months_order_value = $self->get_order_value_period_length;
+
+ return 1 if $months_billing >= $months_order_value;
+
+ my $next_billing_date = $params{date}->clone->add(months => $months_billing);
+ my $date_itr = max($self->start_date, $self->first_billing_date || $self->start_date)->clone;
+
+ _log_msg("is_last_billing_date_in_order_value_cycle start: id " . $self->id . " date_itr $date_itr start " . $self->start_date);
+
+ $date_itr->add(months => $months_order_value) while $date_itr < $next_billing_date;
+
+ _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 "
+ . ($date_itr == $next_billing_date));
+
+ return $date_itr == $next_billing_date;
+}
+