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 = ( 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->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};
101 while ($cur_date <= $end_date) {
102 push @dates, $cur_date->clone if $cur_date >= $start_date;
104 $cur_date->add(months => $period_len);
110 sub is_last_bill_date_in_order_value_cycle {
111 my ($self, %params) = @_;
113 my $months_billing = $self->get_billing_period_length;
114 my $months_order_value = $self->get_order_value_period_length;
116 return 1 if $months_billing >= $months_order_value;
118 my $next_billing_date = $params{date}->clone->add(months => $months_billing);
119 my $date_itr = max($self->start_date, $self->first_billing_date || $self->start_date)->clone;
121 _log_msg("is_last_billing_date_in_order_value_cycle start: id " . $self->id . " date_itr $date_itr start " . $self->start_date);
123 $date_itr->add(months => $months_order_value) while $date_itr < $next_billing_date;
125 _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 "
126 . ($date_itr == $next_billing_date));
128 return $date_itr == $next_billing_date;
140 SL::DB::PeriodicInvoicesConfig - DB model for the configuration for periodic invoices
146 =item C<calculate_invoice_dates %params>
148 Calculates dates for which invoices will have to be created. Returns a
149 list of L<DateTime> objects.
151 This function looks at the configuration settings and at the list of
152 invoices that have already been created for this configuration. The
153 date range for which dates are created are controlled by several
158 =item * The properties C<first_billing_date> and C<start_date>
159 determine the start date.
161 =item * The properties C<end_date> and C<terminated> determine the end
164 =item * The optional parameter C<past_dates> determines whether or not
165 dates for which invoices have already been created will be included in
166 the list. The default is not to include them.
168 =item * The optional parameters C<start_date> and C<end_date> override
169 the start and end dates from the configuration.
171 =item * If no end date is set or implied via the configuration and no
172 C<end_date> parameter is given then the function will use 100 years
173 in the future as the end date.
177 =item C<get_billing_period_length>
179 Returns the number of months corresponding to the billing
180 periodicity. This means that a new invoice has to be created every x
181 months starting with the value in C<first_billing_date> (or
182 C<start_date> if C<first_billing_date> is unset).
184 =item C<get_order_value_period_length>
186 Returns the number of months the order's value refers to. This looks
187 at the C<order_value_periodicity>.
189 Each invoice's value is calculated as C<order value *
190 billing_period_length / order_value_period_length>.
192 =item C<get_previous_billed_period_start_date>
194 Returns the highest date (as an instance of L<DateTime>) for which an
195 invoice has been created from this configuration.
197 =item C<handle_automatic_extension>
199 Configurations which haven't been terminated and which have an end
200 date set may be eligible for automatic extension by a certain number
201 of months. This what the function implements.
203 If the configuration is not eligible or if the C<end_date> hasn't been
204 reached yet then nothing is done and C<undef> is returned. Otherwise
205 its behavior is determined by the C<extend_automatically_by> property.
207 If the property C<extend_automatically_by> is not 0 then the
208 C<end_date> will be extended by C<extend_automatically_by> months, and
209 the configuration will be saved. In this case the new end date will be
212 Otherwise (if C<extend_automatically_by> is 0) the property C<active>
213 will be set to 1, and the configuration will be saved. In this case
214 C<undef> will be returned.
216 =item C<is_last_billing_date_in_order_value_cycle %params>
218 Determines whether or not the mandatory parameter C<date>, an instance
219 of L<DateTime>, is the last billing date within the cycle given by the
220 order value periodicity. Returns a truish value if this is the case
221 and a falsish value otherwise.
223 This check is always true if the billing periodicity is longer than or
224 equal to the order value periodicity. For example, if you have an
225 order whose value is given for three months and you bill every six
226 months and you have twice the order value on each invoice, meaning
227 each invoice is itself the last invoice for not only one but two order
230 Otherwise (if the order value periodicity is longer than the billing
231 periodicity) this function iterates over all eligible dates starting
232 with C<first_billing_date> (or C<start_date> if C<first_billing_date>
233 is unset) and adding the order value length with each step. If the
234 date given by the C<date> parameter plus the billing period length
235 equals one of those dates then the given date is indeed the date of
236 the last invoice in that particular order value cycle.
246 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>