Bug 18936: (follow-up) Fix tests, replace old get_onshelfholds_policy method
[koha-equinox.git] / Koha / CirculationRules.pm
1 package Koha::CirculationRules;
2
3 # Copyright ByWater Solutions 2017
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 3 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 use Modern::Perl;
21
22 use Koha::Exceptions;
23 use Koha::CirculationRule;
24
25 use base qw(Koha::Objects);
26
27 =head1 NAME
28
29 Koha::CirculationRules - Koha CirculationRule Object set class
30
31 =head1 API
32
33 =head2 Class Methods
34
35 =cut
36
37 =head3 rule_kinds
38
39 This structure describes the possible rules that may be set, and what scopes they can be set at.
40
41 Any attempt to set a rule with a nonsensical scope (for instance, setting the C<patron_maxissueqty> for a branchcode and itemtype), is an error.
42
43 =cut
44
45 our $RULE_KINDS = {
46     refund => {
47         scope => [ 'branchcode' ],
48     },
49
50     patron_maxissueqty => {
51         scope => [ 'branchcode', 'categorycode' ],
52     },
53     patron_maxonsiteissueqty => {
54         scope => [ 'branchcode', 'categorycode' ],
55     },
56     max_holds => {
57         scope => [ 'branchcode', 'categorycode' ],
58     },
59
60     holdallowed => {
61         scope => [ 'branchcode', 'itemtype' ],
62     },
63     hold_fulfillment_policy => {
64         scope => [ 'branchcode', 'itemtype' ],
65     },
66     returnbranch => {
67         scope => [ 'branchcode', 'itemtype' ],
68     },
69
70     article_requests => {
71         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
72     },
73     auto_renew => {
74         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
75     },
76     cap_fine_to_replacement_price => {
77         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
78     },
79     chargeperiod => {
80         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
81     },
82     chargeperiod_charge_at => {
83         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
84     },
85     fine => {
86         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
87     },
88     finedays => {
89         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
90     },
91     firstremind => {
92         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
93     },
94     hardduedate => {
95         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
96     },
97     hardduedatecompare => {
98         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
99     },
100     holds_per_record => {
101         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
102     },
103     issuelength => {
104         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
105     },
106     lengthunit => {
107         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
108     },
109     maxissueqty => {
110         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
111     },
112     maxonsiteissueqty => {
113         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
114     },
115     maxsuspensiondays => {
116         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
117     },
118     no_auto_renewal_after => {
119         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
120     },
121     no_auto_renewal_after_hard_limit => {
122         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
123     },
124     norenewalbefore => {
125         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
126     },
127     onshelfholds => {
128         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
129     },
130     opacitemholds => {
131         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
132     },
133     overduefinescap => {
134         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
135     },
136     renewalperiod => {
137         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
138     },
139     renewalsallowed => {
140         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
141     },
142     rentaldiscount => {
143         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
144     },
145     reservesallowed => {
146         scope => [ 'branchcode', 'categorycode', 'itemtype' ],
147     },
148     # Not included (deprecated?):
149     #   * accountsent
150     #   * chargename
151     #   * reservecharge
152     #   * restrictedtype
153 };
154
155 sub rule_kinds {
156     return $RULE_KINDS;
157 }
158
159 =head3 get_effective_rule
160
161 =cut
162
163 sub get_effective_rule {
164     my ( $self, $params ) = @_;
165
166     $params->{categorycode} //= undef;
167     $params->{branchcode}   //= undef;
168     $params->{itemtype}     //= undef;
169
170     my $rule_name    = $params->{rule_name};
171     my $categorycode = $params->{categorycode};
172     my $itemtype     = $params->{itemtype};
173     my $branchcode   = $params->{branchcode};
174
175     Koha::Exceptions::MissingParameter->throw(
176         "Required parameter 'rule_name' missing")
177       unless $rule_name;
178
179     for my $v ( $branchcode, $categorycode, $itemtype ) {
180         $v = undef if $v and $v eq '*';
181     }
182
183     my $order_by = $params->{order_by}
184       // { -desc => [ 'branchcode', 'categorycode', 'itemtype' ] };
185
186     my $search_params;
187     $search_params->{rule_name} = $rule_name;
188
189     $search_params->{categorycode} = defined $categorycode ? [ $categorycode, undef ] : undef;
190     $search_params->{itemtype}     = defined $itemtype     ? [ $itemtype, undef ] : undef;
191     $search_params->{branchcode}   = defined $branchcode   ? [ $branchcode,   undef ] : undef;
192
193     my $rule = $self->search(
194         $search_params,
195         {
196             order_by => $order_by,
197             rows => 1,
198         }
199     )->single;
200
201     return $rule;
202 }
203
204 =head3 get_effective_rule
205
206 =cut
207
208 sub get_effective_rules {
209     my ( $self, $params ) = @_;
210
211     my $rules        = $params->{rules};
212     my $categorycode = $params->{categorycode};
213     my $itemtype     = $params->{itemtype};
214     my $branchcode   = $params->{branchcode};
215
216     my $r;
217     foreach my $rule (@$rules) {
218         my $effective_rule = $self->get_effective_rule(
219             {
220                 rule_name    => $rule,
221                 categorycode => $categorycode,
222                 itemtype     => $itemtype,
223                 branchcode   => $branchcode,
224             }
225         );
226
227         $r->{$rule} = $effective_rule->rule_value if $effective_rule;
228     }
229
230     return $r;
231 }
232
233 =head3 set_rule
234
235 =cut
236
237 sub set_rule {
238     my ( $self, $params ) = @_;
239
240     for my $mandatory_parameter (qw( branchcode categorycode itemtype rule_name rule_value ) ){
241         Koha::Exceptions::MissingParameter->throw(
242             "Required parameter 'branchcode' missing")
243           unless exists $params->{$mandatory_parameter};
244
245     my $kind_info = $RULE_KINDS->{ $params->{rule_name} };
246     croak "set_rule given unknown rule '$params->{rule_name}'!"
247         unless defined $kind_info;
248
249     # Enforce scope; a rule should be set for its defined scope, no more, no less.
250     foreach my $scope_level ( qw( branchcode categorycode itemtype ) ) {
251         if ( grep /$scope_level/, @{ $kind_info->{scope} } ) {
252             croak "set_rule needs '$scope_level' to set '$params->{rule_name}'!"
253                 unless exists $params->{$scope_level};
254         } else {
255             croak "set_rule cannot set '$params->{rule_name}' for a '$scope_level'!"
256                 if exists $params->{$scope_level};
257         }
258     }
259
260     my $branchcode   = $params->{branchcode};
261     my $categorycode = $params->{categorycode};
262     my $itemtype     = $params->{itemtype};
263     my $rule_name    = $params->{rule_name};
264     my $rule_value   = $params->{rule_value};
265
266     for my $v ( $branchcode, $categorycode, $itemtype ) {
267         $v = undef if $v and $v eq '*';
268     }
269     my $rule = $self->search(
270         {
271             rule_name    => $rule_name,
272             branchcode   => $branchcode,
273             categorycode => $categorycode,
274             itemtype     => $itemtype,
275         }
276     )->next();
277
278     if ($rule) {
279         if ( defined $rule_value ) {
280             $rule->rule_value($rule_value);
281             $rule->update();
282         }
283         else {
284             $rule->delete();
285         }
286     }
287     else {
288         if ( defined $rule_value ) {
289             $rule = Koha::CirculationRule->new(
290                 {
291                     branchcode   => $branchcode,
292                     categorycode => $categorycode,
293                     itemtype     => $itemtype,
294                     rule_name    => $rule_name,
295                     rule_value   => $rule_value,
296                 }
297             );
298             $rule->store();
299         }
300     }
301
302     return $rule;
303 }
304
305 =head3 set_rules
306
307 =cut
308
309 sub set_rules {
310     my ( $self, $params ) = @_;
311
312     my %set_params;
313     $set_params{branchcode} = $params->{branchcode} if exists $params->{branchcode};
314     $set_params{categorycode} = $params->{categorycode} if exists $params->{categorycode};
315     $set_params{itemtype} = $params->{itemtype} if exists $params->{itemtype};
316     my $rules        = $params->{rules};
317
318     my $rule_objects = [];
319     while ( my ( $rule_name, $rule_value ) = each %$rules ) {
320         my $rule_object = Koha::CirculationRules->set_rule(
321             {
322                 %set_params,
323                 rule_name    => $rule_name,
324                 rule_value   => $rule_value,
325             }
326         );
327         push( @$rule_objects, $rule_object );
328     }
329
330     return $rule_objects;
331 }
332
333 =head3 delete
334
335 Delete a set of circulation rules, needed for cleaning up when deleting issuingrules
336
337 =cut
338
339 sub delete {
340     my ( $self ) = @_;
341
342     while ( my $rule = $self->next ){
343         $rule->delete;
344     }
345 }
346
347 =head3 get_onshelfholds_policy
348
349     my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy({ item => $item, patron => $patron });
350
351 =cut
352
353 sub get_onshelfholds_policy {
354     my ( $class, $params ) = @_;
355     my $item = $params->{item};
356     my $itemtype = $item->effective_itemtype;
357     my $patron = $params->{patron};
358     my $rule = Koha::CirculationRules->get_effective_rule(
359         {
360             categorycode => ( $patron ? $patron->categorycode : undef ),
361             itemtype     => $itemtype,
362             branchcode   => $item->holdingbranch,
363             rule_name    => 'onshelfholds',
364         }
365     );
366     return $rule ? $rule->rule_value : undef;
367 }
368
369 =head3 type
370
371 =cut
372
373 sub _type {
374     return 'CirculationRule';
375 }
376
377 =head3 object_class
378
379 =cut
380
381 sub object_class {
382     return 'Koha::CirculationRule';
383 }
384
385 1;