Fehler im POD: fehlende Leerzeile
[kivitendo-erp.git] / SL / DB / PeriodicInvoicesConfig.pm
1 package SL::DB::PeriodicInvoicesConfig;
2
3 use strict;
4
5 use SL::DB::MetaSetup::PeriodicInvoicesConfig;
6
7 use List::Util qw(max min);
8
9 __PACKAGE__->meta->initialize;
10
11 # Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
12 __PACKAGE__->meta->make_manager_class;
13
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;
18
19 sub get_billing_period_length {
20   my $self = shift;
21   return $PERIOD_LENGTHS{ $self->periodicity } || 1;
22 }
23
24 sub get_order_value_period_length {
25   my $self = shift;
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;
28 }
29
30 sub _log_msg {
31   $::lxdebug->message(LXDebug->DEBUG1(), join('', 'SL::DB::PeriodicInvoicesConfig: ', @_));
32 }
33
34 sub handle_automatic_extension {
35   my $self = shift;
36
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;
41
42   my $today    = DateTime->now_local;
43   my $end_date = $self->end_date;
44
45   _log_msg("today $today end_date $end_date\n");
46
47   # The end date has not been reached yet, therefore no extension is
48   # needed.
49   return if $today <= $end_date;
50
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");
55     $self->active(0);
56     $self->save;
57     return;
58   }
59
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");
64
65   $self->end_date($end_date);
66   $self->save;
67
68   return $end_date;
69 }
70
71 sub get_previous_billed_period_start_date {
72   my $self  = shift;
73
74   my $query = <<SQL;
75     SELECT MAX(period_start_date)
76     FROM periodic_invoices
77     WHERE config_id = ?
78 SQL
79
80   my ($date) = $self->dbh->selectrow_array($query, undef, $self->id);
81
82   return undef unless $date;
83   return ref $date ? $date : $self->db->parse_date($date);
84 }
85
86 sub calculate_invoice_dates {
87   my ($self, %params) = @_;
88
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;
95
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};
98
99   my @dates;
100
101   while ($cur_date <= $end_date) {
102     push @dates, $cur_date->clone if $cur_date >= $start_date;
103
104     $cur_date->add(months => $period_len);
105   }
106
107   return @dates;
108 }
109
110 sub is_last_bill_date_in_order_value_cycle {
111   my ($self, %params)    = @_;
112
113   my $months_billing     = $self->get_billing_period_length;
114   my $months_order_value = $self->get_order_value_period_length;
115
116   return 1 if $months_billing >= $months_order_value;
117
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;
120
121   _log_msg("is_last_billing_date_in_order_value_cycle start: id " . $self->id . " date_itr $date_itr start " . $self->start_date);
122
123   $date_itr->add(months => $months_order_value) while $date_itr < $next_billing_date;
124
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));
127
128   return $date_itr == $next_billing_date;
129 }
130
131 sub disable_one_time_config {
132   my $self = shift;
133
134   _log_msg("check one time for " . $self->id . "\n");
135
136   # A periodicity of one time was set. Deactivate this config now.
137   if ($self->periodicity eq 'o') {
138     _log_msg("setting inactive\n");
139     $self->active(0);
140     $self->save;
141     return $self->order->ordnumber;
142   }
143   return undef;
144 }
145 1;
146 __END__
147
148 =pod
149
150 =encoding utf8
151
152 =head1 NAME
153
154 SL::DB::PeriodicInvoicesConfig - DB model for the configuration for periodic invoices
155
156 =head1 FUNCTIONS
157
158 =over 4
159
160 =item C<calculate_invoice_dates %params>
161
162 Calculates dates for which invoices will have to be created. Returns a
163 list of L<DateTime> objects.
164
165 This function looks at the configuration settings and at the list of
166 invoices that have already been created for this configuration. The
167 date range for which dates are created are controlled by several
168 values:
169
170 =over 2
171
172 =item * The properties C<first_billing_date> and C<start_date>
173 determine the start date.
174
175 =item * The properties C<end_date> and C<terminated> determine the end
176 date.
177
178 =item * The optional parameter C<past_dates> determines whether or not
179 dates for which invoices have already been created will be included in
180 the list. The default is not to include them.
181
182 =item * The optional parameters C<start_date> and C<end_date> override
183 the start and end dates from the configuration.
184
185 =item * If no end date is set or implied via the configuration and no
186 C<end_date> parameter is given then the function will use 100 years
187 in the future as the end date.
188
189 =back
190
191 =item C<get_billing_period_length>
192
193 Returns the number of months corresponding to the billing
194 periodicity. This means that a new invoice has to be created every x
195 months starting with the value in C<first_billing_date> (or
196 C<start_date> if C<first_billing_date> is unset).
197
198 =item C<get_order_value_period_length>
199
200 Returns the number of months the order's value refers to. This looks
201 at the C<order_value_periodicity>.
202
203 Each invoice's value is calculated as C<order value *
204 billing_period_length / order_value_period_length>.
205
206 =item C<get_previous_billed_period_start_date>
207
208 Returns the highest date (as an instance of L<DateTime>) for which an
209 invoice has been created from this configuration.
210
211 =item C<handle_automatic_extension>
212
213 Configurations which haven't been terminated and which have an end
214 date set may be eligible for automatic extension by a certain number
215 of months. This what the function implements.
216
217 If the configuration is not eligible or if the C<end_date> hasn't been
218 reached yet then nothing is done and C<undef> is returned. Otherwise
219 its behavior is determined by the C<extend_automatically_by> property.
220
221 If the property C<extend_automatically_by> is not 0 then the
222 C<end_date> will be extended by C<extend_automatically_by> months, and
223 the configuration will be saved. In this case the new end date will be
224 returned.
225
226 Otherwise (if C<extend_automatically_by> is 0) the property C<active>
227 will be set to 1, and the configuration will be saved. In this case
228 C<undef> will be returned.
229
230 =item C<is_last_billing_date_in_order_value_cycle %params>
231
232 Determines whether or not the mandatory parameter C<date>, an instance
233 of L<DateTime>, is the last billing date within the cycle given by the
234 order value periodicity. Returns a truish value if this is the case
235 and a falsish value otherwise.
236
237 This check is always true if the billing periodicity is longer than or
238 equal to the order value periodicity. For example, if you have an
239 order whose value is given for three months and you bill every six
240 months and you have twice the order value on each invoice, meaning
241 each invoice is itself the last invoice for not only one but two order
242 value cycles.
243
244 Otherwise (if the order value periodicity is longer than the billing
245 periodicity) this function iterates over all eligible dates starting
246 with C<first_billing_date> (or C<start_date> if C<first_billing_date>
247 is unset) and adding the order value length with each step. If the
248 date given by the C<date> parameter plus the billing period length
249 equals one of those dates then the given date is indeed the date of
250 the last invoice in that particular order value cycle.
251
252 =item C<sub disable_one_time_config>
253
254 Sets the state of the periodic_invoices_configs to inactive
255 (active => false) if the periodicity is <Co> (one time).
256 Returns undef if the periodicity is not 'one time' otherwise the
257 order number of the deactivated periodic order.
258
259 =back
260
261 =head1 BUGS
262
263 Nothing here yet.
264
265 =head1 AUTHOR
266
267 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
268
269 =cut