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' ],
103 holds_per_record => {
104 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
107 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
110 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
113 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
115 maxonsiteissueqty => {
116 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
118 maxsuspensiondays => {
119 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
121 no_auto_renewal_after => {
122 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
124 no_auto_renewal_after_hard_limit => {
125 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
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' ],
151 suspension_chargeperiod => {
152 scope => [ 'branchcode', 'categorycode', 'itemtype' ],
154 # Not included (deprecated?):
164 =head3 get_effective_rule
168 sub get_effective_rule {
169 my ( $self, $params ) = @_;
171 $params->{categorycode} //= undef;
172 $params->{branchcode} //= undef;
173 $params->{itemtype} //= undef;
175 my $rule_name = $params->{rule_name};
176 my $categorycode = $params->{categorycode};
177 my $itemtype = $params->{itemtype};
178 my $branchcode = $params->{branchcode};
180 Koha::Exceptions::MissingParameter->throw(
181 "Required parameter 'rule_name' missing")
184 for my $v ( $branchcode, $categorycode, $itemtype ) {
185 $v = undef if $v and $v eq '*';
188 my $order_by = $params->{order_by}
189 // { -desc => [ 'branchcode', 'categorycode', 'itemtype' ] };
192 $search_params->{rule_name} = $rule_name;
194 $search_params->{categorycode} = defined $categorycode ? [ $categorycode, undef ] : undef;
195 $search_params->{itemtype} = defined $itemtype ? [ $itemtype, undef ] : undef;
196 $search_params->{branchcode} = defined $branchcode ? [ $branchcode, undef ] : undef;
198 my $rule = $self->search(
201 order_by => $order_by,
209 =head3 get_effective_rule
213 sub get_effective_rules {
214 my ( $self, $params ) = @_;
216 my $rules = $params->{rules};
217 my $categorycode = $params->{categorycode};
218 my $itemtype = $params->{itemtype};
219 my $branchcode = $params->{branchcode};
222 foreach my $rule (@$rules) {
223 my $effective_rule = $self->get_effective_rule(
226 categorycode => $categorycode,
227 itemtype => $itemtype,
228 branchcode => $branchcode,
232 $r->{$rule} = $effective_rule->rule_value if $effective_rule;
243 my ( $self, $params ) = @_;
245 for my $mandatory_parameter (qw( rule_name rule_value ) ) {
246 Koha::Exceptions::MissingParameter->throw(
247 "Required parameter '$mandatory_parameter' missing")
248 unless exists $params->{$mandatory_parameter};
251 my $kind_info = $RULE_KINDS->{ $params->{rule_name} };
252 Koha::Exceptions::MissingParameter->throw(
253 "set_rule given unknown rule '$params->{rule_name}'!")
254 unless defined $kind_info;
256 # Enforce scope; a rule should be set for its defined scope, no more, no less.
257 foreach my $scope_level ( qw( branchcode categorycode itemtype ) ) {
258 if ( grep /$scope_level/, @{ $kind_info->{scope} } ) {
259 croak "set_rule needs '$scope_level' to set '$params->{rule_name}'!"
260 unless exists $params->{$scope_level};
262 croak "set_rule cannot set '$params->{rule_name}' for a '$scope_level'!"
263 if exists $params->{$scope_level};
267 my $branchcode = $params->{branchcode};
268 my $categorycode = $params->{categorycode};
269 my $itemtype = $params->{itemtype};
270 my $rule_name = $params->{rule_name};
271 my $rule_value = $params->{rule_value};
273 for my $v ( $branchcode, $categorycode, $itemtype ) {
274 $v = undef if $v and $v eq '*';
276 my $rule = $self->search(
278 rule_name => $rule_name,
279 branchcode => $branchcode,
280 categorycode => $categorycode,
281 itemtype => $itemtype,
286 if ( defined $rule_value ) {
287 $rule->rule_value($rule_value);
295 if ( defined $rule_value ) {
296 $rule = Koha::CirculationRule->new(
298 branchcode => $branchcode,
299 categorycode => $categorycode,
300 itemtype => $itemtype,
301 rule_name => $rule_name,
302 rule_value => $rule_value,
317 my ( $self, $params ) = @_;
320 $set_params{branchcode} = $params->{branchcode} if exists $params->{branchcode};
321 $set_params{categorycode} = $params->{categorycode} if exists $params->{categorycode};
322 $set_params{itemtype} = $params->{itemtype} if exists $params->{itemtype};
323 my $rules = $params->{rules};
325 my $rule_objects = [];
326 while ( my ( $rule_name, $rule_value ) = each %$rules ) {
327 my $rule_object = Koha::CirculationRules->set_rule(
330 rule_name => $rule_name,
331 rule_value => $rule_value,
334 push( @$rule_objects, $rule_object );
337 return $rule_objects;
342 Delete a set of circulation rules, needed for cleaning up when deleting issuingrules
349 while ( my $rule = $self->next ){
354 =head3 get_opacitemholds_policy
356 my $can_place_a_hold_at_item_level = Koha::CirculationRules->get_opacitemholds_policy( { patron => $patron, item => $item } );
358 Return 'Y' or 'F' if the patron can place a hold on this item according to the issuing rules
359 and the "Item level holds" (opacitemholds).
360 Can be 'N' - Don't allow, 'Y' - Allow, and 'F' - Force
364 sub get_opacitemholds_policy {
365 my ( $class, $params ) = @_;
367 my $item = $params->{item};
368 my $patron = $params->{patron};
370 return unless $item or $patron;
372 my $rule = Koha::CirculationRules->get_effective_issuing_rule(
374 categorycode => $patron->categorycode,
375 itemtype => $item->effective_itemtype,
376 branchcode => $item->homebranch,
377 rule_name => 'opacitemholds',
381 return $rule ? $rule->rule_value : undef;
384 =head3 get_onshelfholds_policy
386 my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy({ item => $item, patron => $patron });
390 sub get_onshelfholds_policy {
391 my ( $class, $params ) = @_;
392 my $item = $params->{item};
393 my $itemtype = $item->effective_itemtype;
394 my $patron = $params->{patron};
395 my $rule = Koha::CirculationRules->get_effective_rule(
397 categorycode => ( $patron ? $patron->categorycode : undef ),
398 itemtype => $itemtype,
399 branchcode => $item->holdingbranch,
400 rule_name => 'onshelfholds',
403 return $rule ? $rule->rule_value : undef;
406 =head3 article_requestable_rules
408 Return rules that allow article requests, optionally filtered by
411 Use with care; see guess_article_requestable_itemtypes.
415 sub article_requestable_rules {
416 my ( $class, $params ) = @_;
417 my $category = $params->{categorycode};
419 return if !C4::Context->preference('ArticleRequests');
420 return $class->search({
421 $category ? ( categorycode => [ $category, '*' ] ) : (),
422 rule_name => 'article_requests',
423 rule_value => { '!=' => 'no' },
427 =head3 guess_article_requestable_itemtypes
429 Return item types in a hashref that are likely possible to be
430 'article requested'. Constructed by an intelligent guess in the
431 issuing rules (see article_requestable_rules).
433 Note: pref ArticleRequestsLinkControl overrides the algorithm.
435 Optional parameters: categorycode.
437 Note: the routine is used in opac-search to obtain a reasonable
438 estimate within performance borders (not looking at all items but
439 just using default itemtype). Also we are not looking at the
440 branchcode here, since home or holding branch of the item is
441 leading and branch may be unknown too (anonymous opac session).
445 sub guess_article_requestable_itemtypes {
446 my ( $class, $params ) = @_;
447 my $category = $params->{categorycode};
448 return {} if !C4::Context->preference('ArticleRequests');
449 return { '*' => 1 } if C4::Context->preference('ArticleRequestsLinkControl') eq 'always';
451 my $cache = Koha::Caches->get_instance;
452 my $last_article_requestable_guesses = $cache->get_from_cache(GUESSED_ITEMTYPES_KEY);
453 my $key = $category || '*';
454 return $last_article_requestable_guesses->{$key}
455 if $last_article_requestable_guesses && exists $last_article_requestable_guesses->{$key};
458 my $rules = $class->article_requestable_rules({
459 $category ? ( categorycode => $category ) : (),
461 return $res if !$rules;
462 foreach my $rule ( $rules->as_list ) {
463 $res->{ $rule->itemtype } = 1;
465 $last_article_requestable_guesses->{$key} = $res;
466 $cache->set_in_cache(GUESSED_ITEMTYPES_KEY, $last_article_requestable_guesses);
476 return 'CirculationRule';
484 return 'Koha::CirculationRule';