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 # Not included (deprecated?):
167 =head3 get_effective_rule
171 sub get_effective_rule {
172 my ( $self, $params ) = @_;
174 $params->{categorycode} //= undef;
175 $params->{branchcode} //= undef;
176 $params->{itemtype} //= undef;
178 my $rule_name = $params->{rule_name};
179 my $categorycode = $params->{categorycode};
180 my $itemtype = $params->{itemtype};
181 my $branchcode = $params->{branchcode};
183 Koha::Exceptions::MissingParameter->throw(
184 "Required parameter 'rule_name' missing")
187 for my $v ( $branchcode, $categorycode, $itemtype ) {
188 $v = undef if $v and $v eq '*';
191 my $order_by = $params->{order_by}
192 // { -desc => [ 'branchcode', 'categorycode', 'itemtype' ] };
195 $search_params->{rule_name} = $rule_name;
197 $search_params->{categorycode} = defined $categorycode ? [ $categorycode, undef ] : undef;
198 $search_params->{itemtype} = defined $itemtype ? [ $itemtype, undef ] : undef;
199 $search_params->{branchcode} = defined $branchcode ? [ $branchcode, undef ] : undef;
201 my $rule = $self->search(
204 order_by => $order_by,
212 =head3 get_effective_rule
216 sub get_effective_rules {
217 my ( $self, $params ) = @_;
219 my $rules = $params->{rules};
220 my $categorycode = $params->{categorycode};
221 my $itemtype = $params->{itemtype};
222 my $branchcode = $params->{branchcode};
225 foreach my $rule (@$rules) {
226 my $effective_rule = $self->get_effective_rule(
229 categorycode => $categorycode,
230 itemtype => $itemtype,
231 branchcode => $branchcode,
235 $r->{$rule} = $effective_rule->rule_value if $effective_rule;
246 my ( $self, $params ) = @_;
248 for my $mandatory_parameter (qw( rule_name rule_value ) ) {
249 Koha::Exceptions::MissingParameter->throw(
250 "Required parameter '$mandatory_parameter' missing")
251 unless exists $params->{$mandatory_parameter};
254 my $kind_info = $RULE_KINDS->{ $params->{rule_name} };
255 Koha::Exceptions::MissingParameter->throw(
256 "set_rule given unknown rule '$params->{rule_name}'!")
257 unless defined $kind_info;
259 # Enforce scope; a rule should be set for its defined scope, no more, no less.
260 foreach my $scope_level ( qw( branchcode categorycode itemtype ) ) {
261 if ( grep /$scope_level/, @{ $kind_info->{scope} } ) {
262 croak "set_rule needs '$scope_level' to set '$params->{rule_name}'!"
263 unless exists $params->{$scope_level};
265 croak "set_rule cannot set '$params->{rule_name}' for a '$scope_level'!"
266 if exists $params->{$scope_level};
270 my $branchcode = $params->{branchcode};
271 my $categorycode = $params->{categorycode};
272 my $itemtype = $params->{itemtype};
273 my $rule_name = $params->{rule_name};
274 my $rule_value = $params->{rule_value};
276 for my $v ( $branchcode, $categorycode, $itemtype ) {
277 $v = undef if $v and $v eq '*';
279 my $rule = $self->search(
281 rule_name => $rule_name,
282 branchcode => $branchcode,
283 categorycode => $categorycode,
284 itemtype => $itemtype,
289 if ( defined $rule_value ) {
290 $rule->rule_value($rule_value);
298 if ( defined $rule_value ) {
299 $rule = Koha::CirculationRule->new(
301 branchcode => $branchcode,
302 categorycode => $categorycode,
303 itemtype => $itemtype,
304 rule_name => $rule_name,
305 rule_value => $rule_value,
320 my ( $self, $params ) = @_;
323 $set_params{branchcode} = $params->{branchcode} if exists $params->{branchcode};
324 $set_params{categorycode} = $params->{categorycode} if exists $params->{categorycode};
325 $set_params{itemtype} = $params->{itemtype} if exists $params->{itemtype};
326 my $rules = $params->{rules};
328 my $rule_objects = [];
329 while ( my ( $rule_name, $rule_value ) = each %$rules ) {
330 my $rule_object = Koha::CirculationRules->set_rule(
333 rule_name => $rule_name,
334 rule_value => $rule_value,
337 push( @$rule_objects, $rule_object );
340 return $rule_objects;
345 Delete a set of circulation rules, needed for cleaning up when deleting issuingrules
352 while ( my $rule = $self->next ){
357 =head3 get_opacitemholds_policy
359 my $can_place_a_hold_at_item_level = Koha::CirculationRules->get_opacitemholds_policy( { patron => $patron, item => $item } );
361 Return 'Y' or 'F' if the patron can place a hold on this item according to the issuing rules
362 and the "Item level holds" (opacitemholds).
363 Can be 'N' - Don't allow, 'Y' - Allow, and 'F' - Force
367 sub get_opacitemholds_policy {
368 my ( $class, $params ) = @_;
370 my $item = $params->{item};
371 my $patron = $params->{patron};
373 return unless $item or $patron;
375 my $rule = Koha::CirculationRules->get_effective_issuing_rule(
377 categorycode => $patron->categorycode,
378 itemtype => $item->effective_itemtype,
379 branchcode => $item->homebranch,
380 rule_name => 'opacitemholds',
384 return $rule ? $rule->rule_value : undef;
387 =head3 get_onshelfholds_policy
389 my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy({ item => $item, patron => $patron });
393 sub get_onshelfholds_policy {
394 my ( $class, $params ) = @_;
395 my $item = $params->{item};
396 my $itemtype = $item->effective_itemtype;
397 my $patron = $params->{patron};
398 my $rule = Koha::CirculationRules->get_effective_rule(
400 categorycode => ( $patron ? $patron->categorycode : undef ),
401 itemtype => $itemtype,
402 branchcode => $item->holdingbranch,
403 rule_name => 'onshelfholds',
406 return $rule ? $rule->rule_value : undef;
409 =head3 article_requestable_rules
411 Return rules that allow article requests, optionally filtered by
414 Use with care; see guess_article_requestable_itemtypes.
418 sub article_requestable_rules {
419 my ( $class, $params ) = @_;
420 my $category = $params->{categorycode};
422 return if !C4::Context->preference('ArticleRequests');
423 return $class->search({
424 $category ? ( categorycode => [ $category, '*' ] ) : (),
425 rule_name => 'article_requests',
426 rule_value => { '!=' => 'no' },
430 =head3 guess_article_requestable_itemtypes
432 Return item types in a hashref that are likely possible to be
433 'article requested'. Constructed by an intelligent guess in the
434 issuing rules (see article_requestable_rules).
436 Note: pref ArticleRequestsLinkControl overrides the algorithm.
438 Optional parameters: categorycode.
440 Note: the routine is used in opac-search to obtain a reasonable
441 estimate within performance borders (not looking at all items but
442 just using default itemtype). Also we are not looking at the
443 branchcode here, since home or holding branch of the item is
444 leading and branch may be unknown too (anonymous opac session).
448 sub guess_article_requestable_itemtypes {
449 my ( $class, $params ) = @_;
450 my $category = $params->{categorycode};
451 return {} if !C4::Context->preference('ArticleRequests');
452 return { '*' => 1 } if C4::Context->preference('ArticleRequestsLinkControl') eq 'always';
454 my $cache = Koha::Caches->get_instance;
455 my $last_article_requestable_guesses = $cache->get_from_cache(GUESSED_ITEMTYPES_KEY);
456 my $key = $category || '*';
457 return $last_article_requestable_guesses->{$key}
458 if $last_article_requestable_guesses && exists $last_article_requestable_guesses->{$key};
461 my $rules = $class->article_requestable_rules({
462 $category ? ( categorycode => $category ) : (),
464 return $res if !$rules;
465 foreach my $rule ( $rules->as_list ) {
466 $res->{ $rule->itemtype } = 1;
468 $last_article_requestable_guesses->{$key} = $res;
469 $cache->set_in_cache(GUESSED_ITEMTYPES_KEY, $last_article_requestable_guesses);
479 return 'CirculationRule';
487 return 'Koha::CirculationRule';