Wiederkehrende Rechnungen: Auftragswerts-Periodizität setzen können
[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             = ( 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('', @_));
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;
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->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 1;
111 __END__
112
113 =pod
114
115 =encoding utf8
116
117 =head1 NAME
118
119 SL::DB::PeriodicInvoicesConfig - DB model for the configuration for periodic invoices
120
121 =head1 FUNCTIONS
122
123 =over 4
124
125 =item C<calculate_invoice_dates %params>
126
127 Calculates dates for which invoices will have to be created. Returns a
128 list of L<DateTime> objects.
129
130 This function looks at the configuration settings and at the list of
131 invoices that have already been created for this configuration. The
132 date range for which dates are created are controlled by several
133 values:
134
135 =over 2
136
137 =item * The properties C<first_billing_date> and C<start_date>
138 determine the start date.
139
140 =item * The properties C<end_date> and C<terminated> determine the end
141 date.
142
143 =item * The optional parameter C<past_dates> determines whether or not
144 dates for which invoices have already been created will be included in
145 the list. The default is not to include them.
146
147 =item * The optional parameters C<start_date> and C<end_date> override
148 the start and end dates from the configuration.
149
150 =item * If no end date is set or implied via the configuration and no
151 C<end_date> parameter is given then the function will use 100 years
152 in the future as the end date.
153
154 =back
155
156 =item C<get_billing_period_length>
157
158 Returns the number of months corresponding to the billing
159 periodicity. This means that a new invoice has to be created every x
160 months starting with the value in C<first_billing_date> (or
161 C<start_date> if C<first_billing_date> is unset).
162
163 =item C<get_order_value_period_length>
164
165 Returns the number of months the order's value refers to. This looks
166 at the C<order_value_periodicity>.
167
168 Each invoice's value is calculated as C<order value *
169 billing_period_length / order_value_period_length>.
170
171 =item C<get_previous_billed_period_start_date>
172
173 Returns the highest date (as an instance of L<DateTime>) for which an
174 invoice has been created from this configuration.
175
176 =item C<handle_automatic_extension>
177
178 Configurations which haven't been terminated and which have an end
179 date set may be eligible for automatic extension by a certain number
180 of months. This what the function implements.
181
182 If the configuration is not eligible or if the C<end_date> hasn't been
183 reached yet then nothing is done and C<undef> is returned. Otherwise
184 its behavior is determined by the C<extend_automatically_by> property.
185
186 If the property C<extend_automatically_by> is not 0 then the
187 C<end_date> will be extended by C<extend_automatically_by> months, and
188 the configuration will be saved. In this case the new end date will be
189 returned.
190
191 Otherwise (if C<extend_automatically_by> is 0) the property C<active>
192 will be set to 1, and the configuration will be saved. In this case
193 C<undef> will be returned.
194
195 =back
196
197 =head1 BUGS
198
199 Nothing here yet.
200
201 =head1 AUTHOR
202
203 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
204
205 =cut