1 package Koha::CirculationRules;
3 # Copyright ByWater Solutions 2017
5 # This file is part of Koha.
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
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.
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.
24 use Koha::CirculationRule;
26 use base qw(Koha::Objects);
28 use constant GUESSED_ITEMTYPES_KEY => 'Koha_IssuingRules_last_guess';
32 Koha::CirculationRules - Koha CirculationRule Object set class
42 This structure describes the possible rules that may be set, and what scopes they can be set at.
44 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.
50 scope => [ 'branchcode' ],
53 patron_maxissueqty => {
54 scope => [ 'branchcode', 'categorycode' ],
56 patron_maxonsiteissueqty => {
57 scope => [ 'branchcode', 'categorycode' ],
60 scope => [ 'branchcode', 'categorycode' ],
64 scope => [ 'branchcode', 'itemtype' ],
66 hold_fulfillment_policy => {
67 scope => [ 'branchcode', 'itemtype' ],
70 scope => [ 'branchcode', 'itemtype' ],
74 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
77 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
79 cap_fine_to_replacement_price => {
80 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
83 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
85 chargeperiod_charge_at => {
86 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
89 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
92 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
95 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
98 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
100 hardduedatecompare => {
101 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
104 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
106 holds_per_record => {
107 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
110 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
113 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
116 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
118 maxonsiteissueqty => {
119 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
121 maxsuspensiondays => {
122 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
124 no_auto_renewal_after => {
125 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
127 no_auto_renewal_after_hard_limit => {
128 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
131 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
134 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
137 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
140 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
143 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
146 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
149 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
152 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
154 suspension_chargeperiod => {
155 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
157 note => { # This is not really a rule. Maybe we will want to separate this later.
158 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
160 # Not included (deprecated?):
170 =head3 get_effective_rule
174 sub get_effective_rule {
175 my ( $self, $params ) = @_;
177 $params->{categorycode} //= undef;
178 $params->{branchcode} //= undef;
179 $params->{itemtype} //= undef;
181 my $rule_name = $params->{rule_name};
182 my $categorycode = $params->{categorycode};
183 my $itemtype = $params->{itemtype};
184 my $branchcode = $params->{branchcode};
187 Koha::Exceptions::MissingParameter->throw(
188 "Required parameter 'rule_name' missing" . "@c")
191 for my $v ( $branchcode, $categorycode, $itemtype ) {
192 $v = undef if $v and $v eq '*';
195 my $order_by = $params->{order_by}
196 // { -desc => [ 'branchcode', 'categorycode', 'itemtype' ] };
199 $search_params->{rule_name} = $rule_name;
201 $search_params->{categorycode} = defined $categorycode ? [ $categorycode, undef ] : undef;
202 $search_params->{itemtype} = defined $itemtype ? [ $itemtype, undef ] : undef;
203 $search_params->{branchcode} = defined $branchcode ? [ $branchcode, undef ] : undef;
205 my $rule = $self->search(
208 order_by => $order_by,
216 =head3 get_effective_rules
220 sub get_effective_rules {
221 my ( $self, $params ) = @_;
223 my $rules = $params->{rules};
224 my $categorycode = $params->{categorycode};
225 my $itemtype = $params->{itemtype};
226 my $branchcode = $params->{branchcode};
229 foreach my $rule (@$rules) {
230 my $effective_rule = $self->get_effective_rule(
233 categorycode => $categorycode,
234 itemtype => $itemtype,
235 branchcode => $branchcode,
239 $r->{$rule} = $effective_rule->rule_value if $effective_rule;
250 my ( $self, $params ) = @_;
252 for my $mandatory_parameter (qw( rule_name rule_value ) ) {
253 Koha::Exceptions::MissingParameter->throw(
254 "Required parameter '$mandatory_parameter' missing")
255 unless exists $params->{$mandatory_parameter};
258 my $kind_info = $RULE_KINDS->{ $params->{rule_name} };
259 Koha::Exceptions::MissingParameter->throw(
260 "set_rule given unknown rule '$params->{rule_name}'!")
261 unless defined $kind_info;
263 # Enforce scope; a rule should be set for its defined scope, no more, no less.
264 foreach my $scope_level ( qw( branchcode categorycode itemtype ) ) {
265 if ( grep /$scope_level/, @{ $kind_info->{scope} } ) {
266 croak "set_rule needs '$scope_level' to set '$params->{rule_name}'!"
267 unless exists $params->{$scope_level};
269 croak "set_rule cannot set '$params->{rule_name}' for a '$scope_level'!"
270 if exists $params->{$scope_level};
274 my $branchcode = $params->{branchcode};
275 my $categorycode = $params->{categorycode};
276 my $itemtype = $params->{itemtype};
277 my $rule_name = $params->{rule_name};
278 my $rule_value = $params->{rule_value};
280 for my $v ( $branchcode, $categorycode, $itemtype ) {
281 $v = undef if $v and $v eq '*';
283 my $rule = $self->search(
285 rule_name => $rule_name,
286 branchcode => $branchcode,
287 categorycode => $categorycode,
288 itemtype => $itemtype,
293 if ( defined $rule_value ) {
294 $rule->rule_value($rule_value);
302 if ( defined $rule_value ) {
303 $rule = Koha::CirculationRule->new(
305 branchcode => $branchcode,
306 categorycode => $categorycode,
307 itemtype => $itemtype,
308 rule_name => $rule_name,
309 rule_value => $rule_value,
324 my ( $self, $params ) = @_;
327 $set_params{branchcode} = $params->{branchcode} if exists $params->{branchcode};
328 $set_params{categorycode} = $params->{categorycode} if exists $params->{categorycode};
329 $set_params{itemtype} = $params->{itemtype} if exists $params->{itemtype};
330 my $rules = $params->{rules};
332 my $rule_objects = [];
333 while ( my ( $rule_name, $rule_value ) = each %$rules ) {
334 my $rule_object = Koha::CirculationRules->set_rule(
337 rule_name => $rule_name,
338 rule_value => $rule_value,
341 push( @$rule_objects, $rule_object );
344 return $rule_objects;
349 Delete a set of circulation rules, needed for cleaning up when deleting issuingrules
356 while ( my $rule = $self->next ){
363 Clone a set of circulation rules to another branch
368 my ( $self, $to_branch ) = @_;
370 while ( my $rule = $self->next ){
371 $rule->clone($to_branch);
375 =head3 get_opacitemholds_policy
377 my $can_place_a_hold_at_item_level = Koha::CirculationRules->get_opacitemholds_policy( { patron => $patron, item => $item } );
379 Return 'Y' or 'F' if the patron can place a hold on this item according to the issuing rules
380 and the "Item level holds" (opacitemholds).
381 Can be 'N' - Don't allow, 'Y' - Allow, and 'F' - Force
385 sub get_opacitemholds_policy {
386 my ( $class, $params ) = @_;
388 my $item = $params->{item};
389 my $patron = $params->{patron};
391 return unless $item or $patron;
393 my $rule = Koha::CirculationRules->get_effective_rule(
395 categorycode => $patron->categorycode,
396 itemtype => $item->effective_itemtype,
397 branchcode => $item->homebranch,
398 rule_name => 'opacitemholds',
402 return $rule ? $rule->rule_value : undef;
405 =head3 get_onshelfholds_policy
407 my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy({ item => $item, patron => $patron });
411 sub get_onshelfholds_policy {
412 my ( $class, $params ) = @_;
413 my $item = $params->{item};
414 my $itemtype = $item->effective_itemtype;
415 my $patron = $params->{patron};
416 my $rule = Koha::CirculationRules->get_effective_rule(
418 categorycode => ( $patron ? $patron->categorycode : undef ),
419 itemtype => $itemtype,
420 branchcode => $item->holdingbranch,
421 rule_name => 'onshelfholds',
424 return $rule ? $rule->rule_value : undef;
427 =head3 article_requestable_rules
429 Return rules that allow article requests, optionally filtered by
432 Use with care; see guess_article_requestable_itemtypes.
436 sub article_requestable_rules {
437 my ( $class, $params ) = @_;
438 my $category = $params->{categorycode};
440 return if !C4::Context->preference('ArticleRequests');
441 return $class->search({
442 $category ? ( categorycode => [ $category, undef ] ) : (),
443 rule_name => 'article_requests',
444 rule_value => { '!=' => 'no' },
448 =head3 guess_article_requestable_itemtypes
450 Return item types in a hashref that are likely possible to be
451 'article requested'. Constructed by an intelligent guess in the
452 issuing rules (see article_requestable_rules).
454 Note: pref ArticleRequestsLinkControl overrides the algorithm.
456 Optional parameters: categorycode.
458 Note: the routine is used in opac-search to obtain a reasonable
459 estimate within performance borders (not looking at all items but
460 just using default itemtype). Also we are not looking at the
461 branchcode here, since home or holding branch of the item is
462 leading and branch may be unknown too (anonymous opac session).
466 sub guess_article_requestable_itemtypes {
467 my ( $class, $params ) = @_;
468 my $category = $params->{categorycode};
469 return {} if !C4::Context->preference('ArticleRequests');
470 return { '*' => 1 } if C4::Context->preference('ArticleRequestsLinkControl') eq 'always';
472 my $cache = Koha::Caches->get_instance;
473 my $last_article_requestable_guesses = $cache->get_from_cache(GUESSED_ITEMTYPES_KEY);
474 my $key = $category || '*';
475 return $last_article_requestable_guesses->{$key}
476 if $last_article_requestable_guesses && exists $last_article_requestable_guesses->{$key};
479 my $rules = $class->article_requestable_rules({
480 $category ? ( categorycode => $category ) : (),
482 return $res if !$rules;
483 foreach my $rule ( $rules->as_list ) {
484 $res->{ $rule->itemtype } = 1;
486 $last_article_requestable_guesses->{$key} = $res;
487 $cache->set_in_cache(GUESSED_ITEMTYPES_KEY, $last_article_requestable_guesses);
497 return 'CirculationRule';
505 return 'Koha::CirculationRule';