3 # Copyright 2020 Koha Development team
5 # This file is part of Koha
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22 use Test::More tests => 4;
25 use Koha::CirculationRules;
29 use t::lib::TestBuilder;
32 my $schema = Koha::Database->new->schema;
33 my $builder = t::lib::TestBuilder->new;
35 subtest 'set_rule + get_effective_rule' => sub {
38 $schema->storage->txn_begin;
40 my $categorycode = $builder->build_object( { class => 'Koha::Patron::Categories' } )->categorycode;
41 my $itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } )->itemtype;
42 my $branchcode = $builder->build_object( { class => 'Koha::Libraries' } )->branchcode;
43 my $branchcode_2 = $builder->build_object( { class => 'Koha::Libraries' } )->branchcode;
44 my $rule_name = 'maxissueqty';
45 my $default_rule_value = 1;
48 Koha::CirculationRules->delete;
50 throws_ok { Koha::CirculationRules->get_effective_rule }
51 'Koha::Exceptions::MissingParameter',
52 "Exception should be raised if get_effective_rule is called without rule_name parameter";
54 $rule = Koha::CirculationRules->get_effective_rule(
56 branchcode => $branchcode,
57 categorycode => $categorycode,
58 itemtype => $itemtype,
59 rule_name => $rule_name,
62 is( $rule, undef, 'Undef should be returned if no rule exist' );
64 Koha::CirculationRules->set_rule(
69 rule_name => $rule_name,
70 rule_value => $default_rule_value,
74 $rule = Koha::CirculationRules->get_effective_rule(
77 categorycode => undef,
79 rule_name => $rule_name,
82 is( $rule->rule_value, $default_rule_value, 'undef means default' );
83 $rule = Koha::CirculationRules->get_effective_rule(
88 rule_name => $rule_name,
92 is( $rule->rule_value, $default_rule_value, '* means default' );
94 $rule = Koha::CirculationRules->get_effective_rule(
96 branchcode => $branchcode_2,
99 rule_name => $rule_name,
102 is( $rule->rule_value, 1,
103 'Default rule is returned if there is no rule for this branchcode' );
105 subtest 'test rules that cannot be blank' => sub {
107 foreach my $no_blank_rule ( ('holdallowed','hold_fulfillment_policy','returnbranch') ){
108 Koha::CirculationRules->set_rule(
110 branchcode => $branchcode,
112 rule_name => $no_blank_rule,
117 $rule = Koha::CirculationRules->get_effective_rule(
119 branchcode => $branchcode,
120 categorycode => undef,
122 rule_name => $no_blank_rule,
125 is( $rule, undef, 'Rules that cannot be blank are not set when passed blank string' );
130 subtest 'test rule matching with different combinations of rule scopes' => sub {
131 my ( $tests, $order ) = prepare_tests_for_rule_scope_combinations(
133 branchcode => $branchcode,
134 categorycode => $categorycode,
135 itemtype => $itemtype,
140 plan tests => 2**scalar @$order;
142 foreach my $test (@$tests) {
143 my $rule_params = {%$test};
144 $rule_params->{rule_name} = $rule_name;
145 my $rule_value = $rule_params->{rule_value} = int( rand(10) );
147 Koha::CirculationRules->set_rule($rule_params);
149 my $rule = Koha::CirculationRules->get_effective_rule(
151 branchcode => $branchcode,
152 categorycode => $categorycode,
153 itemtype => $itemtype,
154 rule_name => $rule_name,
158 my $scope_output = '';
159 foreach my $key ( values @$order ) {
160 $scope_output .= " $key" if $test->{$key} ne '*';
163 is( $rule->rule_value, $rule_value,
165 . ( $scope_output ? $scope_output : ' nothing' ) );
169 my $our_branch_rules = Koha::CirculationRules->search({branchcode => $branchcode});
170 is( $our_branch_rules->count, 4, "We added 8 rules");
171 $our_branch_rules->delete;
172 is( $our_branch_rules->count, 0, "We deleted 8 rules");
174 $schema->storage->txn_rollback;
177 subtest 'get_onshelfholds_policy() tests' => sub {
181 $schema->storage->txn_begin;
183 my $item = $builder->build_sample_item();
185 my $circ_rules = Koha::CirculationRules->new;
187 $circ_rules->search({ rule_name => 'onshelfholds' })->delete;
189 $circ_rules->set_rule(
194 rule_name => 'onshelfholds',
199 is( $circ_rules->get_onshelfholds_policy({ item => $item }), 1, 'If rule_value is set on a matching rule, return it' );
200 # Delete the rule (i.e. get_effective_rule returns undef)
202 is( $circ_rules->get_onshelfholds_policy({ item => $item }), 0, 'If no matching rule, fallback to 0' );
204 $schema->storage->txn_rollback;
207 subtest 'get_effective_daysmode' => sub {
210 $schema->storage->txn_begin;
212 my $item_1 = $builder->build_sample_item();
213 my $item_2 = $builder->build_sample_item();
216 Koha::CirculationRules->search( { rule_name => 'daysmode' } )->delete;
218 # Default value 'Datedue' at pref level
219 t::lib::Mocks::mock_preference( 'useDaysMode', 'Datedue' );
222 Koha::CirculationRules->get_effective_daysmode(
224 categorycode => undef,
225 itemtype => $item_1->effective_itemtype,
230 'daysmode default to pref value if the rule does not exist'
233 Koha::CirculationRules->set_rule(
238 rule_name => 'daysmode',
239 rule_value => 'Calendar',
242 Koha::CirculationRules->set_rule(
246 itemtype => $item_1->effective_itemtype,
247 rule_name => 'daysmode',
248 rule_value => 'Days',
253 Koha::CirculationRules->get_effective_daysmode(
255 categorycode => undef,
256 itemtype => $item_1->effective_itemtype,
261 "daysmode for item_1 is the specific rule"
264 Koha::CirculationRules->get_effective_daysmode(
266 categorycode => undef,
267 itemtype => $item_2->effective_itemtype,
272 "daysmode for item_2 is the one defined for the default circ rule"
275 Koha::CirculationRules->set_rule(
279 itemtype => $item_2->effective_itemtype,
280 rule_name => 'daysmode',
286 Koha::CirculationRules->get_effective_daysmode(
288 categorycode => undef,
289 itemtype => $item_2->effective_itemtype,
294 'daysmode default to pref value if the rule exists but set to""'
297 $schema->storage->txn_rollback;
300 subtest 'get_lostreturn_policy() tests' => sub {
303 $schema->storage->txn_begin;
305 $schema->resultset('CirculationRule')->search()->delete;
307 my $default_rule_charge = $builder->build(
309 source => 'CirculationRule',
312 categorycode => undef,
314 rule_name => 'lostreturn',
315 rule_value => 'charge'
319 my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
320 my $specific_rule_false = $builder->build(
322 source => 'CirculationRule',
324 branchcode => $branchcode,
325 categorycode => undef,
327 rule_name => 'lostreturn',
332 my $branchcode2 = $builder->build( { source => 'Branch' } )->{branchcode};
333 my $specific_rule_refund = $builder->build(
335 source => 'CirculationRule',
337 branchcode => $branchcode2,
338 categorycode => undef,
340 rule_name => 'lostreturn',
341 rule_value => 'refund'
345 my $branchcode3 = $builder->build( { source => 'Branch' } )->{branchcode};
346 my $specific_rule_restore = $builder->build(
348 source => 'CirculationRule',
350 branchcode => $branchcode3,
351 categorycode => undef,
353 rule_name => 'lostreturn',
354 rule_value => 'restore'
359 # Make sure we have an unused branchcode
360 my $branchcode4 = $builder->build( { source => 'Branch' } )->{branchcode};
361 my $specific_rule_dummy = $builder->build(
363 source => 'CirculationRule',
365 branchcode => $branchcode4,
366 categorycode => undef,
368 rule_name => 'lostreturn',
369 rule_value => 'refund'
373 my $branch_without_rule = $specific_rule_dummy->{ branchcode };
374 Koha::CirculationRules
377 branchcode => $branch_without_rule,
378 categorycode => undef,
380 rule_name => 'lostreturn',
381 rule_value => 'refund'
387 my $item = $builder->build_sample_item(
389 homebranch => $specific_rule_restore->{branchcode},
390 holdingbranch => $specific_rule_false->{branchcode}
394 return_branch => $specific_rule_refund->{ branchcode },
399 t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
400 is( Koha::CirculationRules->get_lostreturn_policy( $params ),
401 'refund','Specific rule for checkin branch is applied (refund)');
403 t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHomeBranch' );
404 is( Koha::CirculationRules->get_lostreturn_policy( $params ),
405 'restore','Specific rule for home branch is applied (restore)');
407 t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHoldingBranch' );
408 is( Koha::CirculationRules->get_lostreturn_policy( $params ),
409 0,'Specific rule for holding branch is applied (false)');
412 t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
413 $params->{return_branch} = $branch_without_rule;
414 is( Koha::CirculationRules->get_lostreturn_policy( $params ),
415 'charge','No rule for branch, global rule applied (charge)');
417 # Change the default value just to try
418 Koha::CirculationRules->search({ branchcode => undef, rule_name => 'lostreturn' })->next->rule_value(0)->store;
419 is( Koha::CirculationRules->get_lostreturn_policy( $params ),
420 0,'No rule for branch, global rule applied (false)');
422 # No default rule defined check
423 Koha::CirculationRules
427 categorycode => undef,
429 rule_name => 'lostreturn'
434 is( Koha::CirculationRules->get_lostreturn_policy( $params ),
435 'refund','No rule for branch, no default rule, fallback default (refund)');
437 # Fallback to ItemHoldBranch if CheckinLibrary is undefined
438 $params->{return_branch} = undef;
439 is( Koha::CirculationRules->get_lostreturn_policy( $params ),
440 'restore','return_branch undefined, fallback to ItemHomeBranch rule (restore)');
442 $schema->storage->txn_rollback;
445 sub prepare_tests_for_rule_scope_combinations {
446 my ( $scope, $rule_name ) = @_;
448 # Here we create a combinations of 1s and 0s the following way
459 # (the number of columns equals to the amount of rule scopes)
460 # The ... symbolizes possible future scopes.
462 # - 0 equals to circulation rule scope with any value (aka. *)
463 # - 1 equals to circulation rule scope exact value, e.g.
464 # "CPL" (for branchcode).
466 # The order is the same as the weight of scopes when sorting circulation
467 # rules. So the first column of numbers is the scope with most weight.
468 # This is defined by C<$order> which will be assigned next.
470 # We must maintain the order in order to keep the test valid. This should be
471 # equal to Koha/CirculationRules.pm "order_by" of C<get_effective_rule> sub.
472 # Let's explicitly define the order and fail test if we are missing a scope:
473 my $order = [ 'branchcode', 'categorycode', 'itemtype' ];
474 is( join(", ", sort keys %$scope),
475 join(", ", sort @$order), 'Missing a scope!' ) if keys %$scope ne scalar @$order;
478 foreach my $value ( glob( "{0,1}" x keys %$scope || 1 ) ) {
479 my $test = { %$scope };
480 for ( my $i=0; $i < keys %$scope; $i++ ) {
481 $test->{$order->[$i]} = '*' unless substr( $value, $i, 1 );
486 return \@tests, $order;