Bug 23012: Set policy for lost item processing fee
[koha.git] / t / db_dependent / Koha / CirculationRules.t
1 #!/usr/bin/perl
2
3 # Copyright 2020 Koha Development team
4 #
5 # This file is part of Koha
6 #
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.
11 #
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.
16 #
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>.
19
20 use Modern::Perl;
21
22 use Benchmark;
23 use Test::More tests => 8;
24 use Test::Deep qw( cmp_methods );
25 use Test::Exception;
26
27 use Koha::CirculationRules;
28 use Koha::Database;
29
30 use t::lib::Mocks;
31 use t::lib::TestBuilder;
32
33 my $schema = Koha::Database->new->schema;
34 my $builder = t::lib::TestBuilder->new;
35
36 subtest 'get_effective_issuing_rule' => sub {
37     plan tests => 2;
38
39     $schema->storage->txn_begin;
40
41     my $categorycode = $builder->build({ source => 'Category' })->{'categorycode'};
42     my $itemtype     = $builder->build({ source => 'Itemtype' })->{'itemtype'};
43     my $branchcode   = $builder->build({ source => 'Branch' })->{'branchcode'};
44
45     subtest 'Call with undefined values' => sub {
46         plan tests => 5;
47
48         my $rule;
49         Koha::CirculationRules->delete;
50
51         is(Koha::CirculationRules->search->count, 0, 'There are no issuing rules.');
52         # undef, undef, undef => 1
53         $rule = Koha::CirculationRules->get_effective_rule({
54             branchcode   => undef,
55             categorycode => undef,
56             itemtype     => undef,
57             rule_name    => 'fine',
58             rule_value   => 1,
59         });
60         is($rule, undef, 'When I attempt to get effective issuing rule by'
61            .' providing undefined values, then undef is returned.');
62
63        # undef, undef, undef => 2
64         ok(
65             Koha::CirculationRule->new(
66                 {
67                     branchcode   => undef,
68                     categorycode => undef,
69                     itemtype     => undef,
70                     rule_name    => 'fine',
71                     rule_value   => 2,
72                 }
73               )->store,
74             'Given I added an issuing rule branchcode => undef,'
75            .' categorycode => undef, itemtype => undef,');
76         $rule = Koha::CirculationRules->get_effective_rule({
77             branchcode   => undef,
78             categorycode => undef,
79             itemtype     => undef,
80             rule_name    => 'fine',
81         });
82         _is_row_match(
83             $rule,
84             {
85                 branchcode   => undef,
86                 categorycode => undef,
87                 itemtype     => undef,
88                 rule_name    => 'fine',
89                 rule_value   => 2,
90             },
91             'When I attempt to get effective'
92            .' issuing rule by providing undefined values, then the above one is'
93            .' returned.'
94         );
95     };
96
97     subtest 'Performance' => sub {
98         plan tests => 4;
99
100         my $worst_case = timethis(500,
101                     sub { Koha::CirculationRules->get_effective_rule({
102                             branchcode   => 'nonexistent',
103                             categorycode => 'nonexistent',
104                             itemtype     => 'nonexistent',
105                             rule_name    => 'nonexistent',
106                         });
107                     }
108                 );
109         my $mid_case = timethis(500,
110                     sub { Koha::CirculationRules->get_effective_rule({
111                             branchcode   => $branchcode,
112                             categorycode => 'nonexistent',
113                             itemtype     => 'nonexistent',
114                             rule_name    => 'nonexistent',
115                         });
116                     }
117                 );
118         my $sec_best_case = timethis(500,
119                     sub { Koha::CirculationRules->get_effective_rule({
120                             branchcode   => $branchcode,
121                             categorycode => $categorycode,
122                             itemtype     => 'nonexistent',
123                             rule_name    => 'nonexistent',
124                         });
125                     }
126                 );
127         my $best_case = timethis(500,
128                     sub { Koha::CirculationRules->get_effective_rule({
129                             branchcode   => $branchcode,
130                             categorycode => $categorycode,
131                             itemtype     => $itemtype,
132                             rule_name    => 'nonexistent',
133                         });
134                     }
135                 );
136         ok($worst_case, 'In worst case, get_effective_issuing_rule finds matching'
137            .' rule '.sprintf('%.2f', $worst_case->iters/$worst_case->cpu_a)
138            .' times per second.');
139         ok($mid_case, 'In mid case, get_effective_issuing_rule finds matching'
140            .' rule '.sprintf('%.2f', $mid_case->iters/$mid_case->cpu_a)
141            .' times per second.');
142         ok($sec_best_case, 'In second best case, get_effective_issuing_rule finds matching'
143            .' rule '.sprintf('%.2f', $sec_best_case->iters/$sec_best_case->cpu_a)
144            .' times per second.');
145         ok($best_case, 'In best case, get_effective_issuing_rule finds matching'
146            .' rule '.sprintf('%.2f', $best_case->iters/$best_case->cpu_a)
147            .' times per second.');
148     };
149
150     $schema->storage->txn_rollback;
151
152 };
153
154 subtest 'set_rule' => sub {
155     plan tests => 3;
156
157     $schema->storage->txn_begin;
158
159     my $branchcode   = $builder->build({ source => 'Branch' })->{'branchcode'};
160     my $categorycode = $builder->build({ source => 'Category' })->{'categorycode'};
161     my $itemtype     = $builder->build({ source => 'Itemtype' })->{'itemtype'};
162
163     subtest 'Correct call' => sub {
164         plan tests => 5;
165
166         Koha::CirculationRules->delete;
167
168         lives_ok( sub {
169             Koha::CirculationRules->set_rule( {
170                 branchcode => $branchcode,
171                 rule_name => 'lostreturn',
172                 rule_value => '',
173             } );
174         }, 'setting lostreturn with branch' );
175
176         lives_ok( sub {
177             Koha::CirculationRules->set_rule( {
178                 branchcode => $branchcode,
179                 rule_name => 'processingreturn',
180                 rule_value => '',
181             } );
182         }, 'setting processingreturn with branch' );
183
184         lives_ok( sub {
185             Koha::CirculationRules->set_rule( {
186                 branchcode => $branchcode,
187                 categorycode => $categorycode,
188                 rule_name => 'patron_maxissueqty',
189                 rule_value => '',
190             } );
191         }, 'setting patron_maxissueqty with branch/category succeeds' );
192
193         lives_ok( sub {
194             Koha::CirculationRules->set_rule( {
195                 branchcode => $branchcode,
196                 itemtype => $itemtype,
197                 rule_name => 'holdallowed',
198                 rule_value => '',
199             } );
200         }, 'setting holdallowed with branch/itemtype succeeds' );
201
202         lives_ok( sub {
203             Koha::CirculationRules->set_rule( {
204                 branchcode => $branchcode,
205                 categorycode => $categorycode,
206                 itemtype => $itemtype,
207                 rule_name => 'fine',
208                 rule_value => '',
209             } );
210         }, 'setting fine with branch/category/itemtype succeeds' );
211     };
212
213     subtest 'Call with missing params' => sub {
214         plan tests => 5;
215
216         Koha::CirculationRules->delete;
217
218         throws_ok( sub {
219             Koha::CirculationRules->set_rule( {
220                 rule_name => 'lostreturn',
221                 rule_value => '',
222             } );
223         }, qr/branchcode/, 'setting lostreturn without branch fails' );
224
225         throws_ok( sub {
226             Koha::CirculationRules->set_rule( {
227                 rule_name => 'processingreturn',
228                 rule_value => '',
229             } );
230         }, qr/branchcode/, 'setting processingreturn without branch fails' );
231
232         throws_ok( sub {
233             Koha::CirculationRules->set_rule( {
234                 branchcode => $branchcode,
235                 rule_name => 'patron_maxissueqty',
236                 rule_value => '',
237             } );
238         }, qr/categorycode/, 'setting patron_maxissueqty without categorycode fails' );
239
240         throws_ok( sub {
241             Koha::CirculationRules->set_rule( {
242                 branchcode => $branchcode,
243                 rule_name => 'holdallowed',
244                 rule_value => '',
245             } );
246         }, qr/itemtype/, 'setting holdallowed without itemtype fails' );
247
248         throws_ok( sub {
249             Koha::CirculationRules->set_rule( {
250                 branchcode => $branchcode,
251                 categorycode => $categorycode,
252                 rule_name => 'fine',
253                 rule_value => '',
254             } );
255         }, qr/itemtype/, 'setting fine without itemtype fails' );
256     };
257
258     subtest 'Call with extra params' => sub {
259         plan tests => 4;
260
261         Koha::CirculationRules->delete;
262
263         throws_ok( sub {
264             Koha::CirculationRules->set_rule( {
265                 branchcode => $branchcode,
266                 categorycode => $categorycode,
267                 rule_name => 'lostreturn',
268                 rule_value => '',
269             } );
270         }, qr/categorycode/, 'setting lostreturn with categorycode fails' );
271
272         throws_ok( sub {
273             Koha::CirculationRules->set_rule( {
274                 branchcode => $branchcode,
275                 categorycode => $categorycode,
276                 rule_name => 'processingreturn',
277                 rule_value => '',
278             } );
279         }, qr/categorycode/, 'setting processingreturn with categorycode fails' );
280
281         throws_ok( sub {
282             Koha::CirculationRules->set_rule( {
283                 branchcode => $branchcode,
284                 categorycode => $categorycode,
285                 itemtype => $itemtype,
286                 rule_name => 'patron_maxissueqty',
287                 rule_value => '',
288             } );
289         }, qr/itemtype/, 'setting patron_maxissueqty with itemtype fails' );
290
291         throws_ok( sub {
292             Koha::CirculationRules->set_rule( {
293                 branchcode => $branchcode,
294                 rule_name => 'holdallowed',
295                 categorycode => $categorycode,
296                 itemtype => $itemtype,
297                 rule_value => '',
298             } );
299         }, qr/categorycode/, 'setting holdallowed with categorycode fails' );
300     };
301
302     $schema->storage->txn_rollback;
303 };
304
305 subtest 'clone' => sub {
306     plan tests => 2;
307
308     $schema->storage->txn_begin;
309
310     my $branchcode   = $builder->build({ source => 'Branch' })->{'branchcode'};
311     my $categorycode = $builder->build({ source => 'Category' })->{'categorycode'};
312     my $itemtype     = $builder->build({ source => 'Itemtype' })->{'itemtype'};
313
314     subtest 'Clone multiple rules' => sub {
315         plan tests => 4;
316
317         Koha::CirculationRules->delete;
318
319         Koha::CirculationRule->new({
320             branchcode   => undef,
321             categorycode => $categorycode,
322             itemtype     => $itemtype,
323             rule_name    => 'fine',
324             rule_value   => 5,
325         })->store;
326
327         Koha::CirculationRule->new({
328             branchcode   => undef,
329             categorycode => $categorycode,
330             itemtype     => $itemtype,
331             rule_name    => 'lengthunit',
332             rule_value   => 'days',
333         })->store;
334
335         Koha::CirculationRules->search({ branchcode => undef })->clone($branchcode);
336
337         my $rule_fine = Koha::CirculationRules->get_effective_rule({
338             branchcode   => $branchcode,
339             categorycode => $categorycode,
340             itemtype     => $itemtype,
341             rule_name    => 'fine',
342         });
343         my $rule_lengthunit = Koha::CirculationRules->get_effective_rule({
344             branchcode   => $branchcode,
345             categorycode => $categorycode,
346             itemtype     => $itemtype,
347             rule_name    => 'lengthunit',
348         });
349
350         _is_row_match(
351             $rule_fine,
352             {
353                 branchcode   => $branchcode,
354                 categorycode => $categorycode,
355                 itemtype     => $itemtype,
356                 rule_name    => 'fine',
357                 rule_value   => 5,
358             },
359             'When I attempt to get cloned fine rule,'
360            .' then the above one is returned.'
361         );
362         _is_row_match(
363             $rule_lengthunit,
364             {
365                 branchcode   => $branchcode,
366                 categorycode => $categorycode,
367                 itemtype     => $itemtype,
368                 rule_name    => 'lengthunit',
369                 rule_value   => 'days',
370             },
371             'When I attempt to get cloned lengthunit rule,'
372            .' then the above one is returned.'
373         );
374
375     };
376
377     subtest 'Clone one rule' => sub {
378         plan tests => 2;
379
380         Koha::CirculationRules->delete;
381
382         Koha::CirculationRule->new({
383             branchcode   => undef,
384             categorycode => $categorycode,
385             itemtype     => $itemtype,
386             rule_name    => 'fine',
387             rule_value   => 5,
388         })->store;
389
390         my $rule = Koha::CirculationRules->search({ branchcode => undef })->next;
391         $rule->clone($branchcode);
392
393         my $cloned_rule = Koha::CirculationRules->get_effective_rule({
394             branchcode   => $branchcode,
395             categorycode => $categorycode,
396             itemtype     => $itemtype,
397             rule_name    => 'fine',
398         });
399
400         _is_row_match(
401             $cloned_rule,
402             {
403                 branchcode   => $branchcode,
404                 categorycode => $categorycode,
405                 itemtype     => $itemtype,
406                 rule_name    => 'fine',
407                 rule_value   => '5',
408             },
409             'When I attempt to get cloned fine rule,'
410            .' then the above one is returned.'
411         );
412
413     };
414
415     $schema->storage->txn_rollback;
416 };
417
418 subtest 'set_rule + get_effective_rule' => sub {
419     plan tests => 9;
420
421     $schema->storage->txn_begin;
422
423     my $categorycode = $builder->build_object( { class => 'Koha::Patron::Categories' } )->categorycode;
424     my $itemtype     = $builder->build_object( { class => 'Koha::ItemTypes' } )->itemtype;
425     my $branchcode   = $builder->build_object( { class => 'Koha::Libraries' } )->branchcode;
426     my $branchcode_2 = $builder->build_object( { class => 'Koha::Libraries' } )->branchcode;
427     my $rule_name    = 'maxissueqty';
428     my $default_rule_value = 1;
429
430     my $rule;
431     Koha::CirculationRules->delete;
432
433     throws_ok { Koha::CirculationRules->get_effective_rule }
434     'Koha::Exceptions::MissingParameter',
435     "Exception should be raised if get_effective_rule is called without rule_name parameter";
436
437     $rule = Koha::CirculationRules->get_effective_rule(
438         {
439             branchcode   => $branchcode,
440             categorycode => $categorycode,
441             itemtype     => $itemtype,
442             rule_name    => $rule_name,
443         }
444     );
445     is( $rule, undef, 'Undef should be returned if no rule exist' );
446
447     Koha::CirculationRules->set_rule(
448         {
449             branchcode   => '*',
450             categorycode => '*',
451             itemtype     => '*',
452             rule_name    => $rule_name,
453             rule_value   => $default_rule_value,
454         }
455     );
456
457     $rule = Koha::CirculationRules->get_effective_rule(
458         {
459             branchcode   => undef,
460             categorycode => undef,
461             itemtype     => undef,
462             rule_name    => $rule_name,
463         }
464     );
465     is( $rule->rule_value, $default_rule_value, 'undef means default' );
466     $rule = Koha::CirculationRules->get_effective_rule(
467         {
468             branchcode   => '*',
469             categorycode => '*',
470             itemtype     => '*',
471             rule_name    => $rule_name,
472         }
473     );
474
475     is( $rule->rule_value, $default_rule_value, '* means default' );
476
477     $rule = Koha::CirculationRules->get_effective_rule(
478         {
479             branchcode   => $branchcode_2,
480             categorycode => '*',
481             itemtype     => '*',
482             rule_name    => $rule_name,
483         }
484     );
485     is( $rule->rule_value, 1,
486         'Default rule is returned if there is no rule for this branchcode' );
487
488     subtest 'test rules that cannot be blank' => sub {
489         plan tests => 3;
490         foreach my $no_blank_rule ( ('holdallowed','hold_fulfillment_policy','returnbranch') ){
491             Koha::CirculationRules->set_rule(
492                 {
493                     branchcode   => $branchcode,
494                     itemtype     => '*',
495                     rule_name    => $no_blank_rule,
496                     rule_value   => '',
497                 }
498             );
499
500             $rule = Koha::CirculationRules->get_effective_rule(
501                 {
502                     branchcode   => $branchcode,
503                     categorycode => undef,
504                     itemtype     => undef,
505                     rule_name    => $no_blank_rule,
506                 }
507             );
508             is( $rule, undef, 'Rules that cannot be blank are not set when passed blank string' );
509         }
510     };
511
512
513     subtest 'test rule matching with different combinations of rule scopes' => sub {
514         my ( $tests, $order ) = _prepare_tests_for_rule_scope_combinations(
515             {
516                 branchcode   => $branchcode,
517                 categorycode => $categorycode,
518                 itemtype     => $itemtype,
519             },
520             'maxissueqty'
521         );
522
523         plan tests => 2**scalar @$order;
524
525         foreach my $test (@$tests) {
526             my $rule_params = {%$test};
527             $rule_params->{rule_name} = $rule_name;
528             my $rule_value = $rule_params->{rule_value} = int( rand(10) );
529
530             Koha::CirculationRules->set_rule($rule_params);
531
532             my $rule = Koha::CirculationRules->get_effective_rule(
533                 {
534                     branchcode   => $branchcode,
535                     categorycode => $categorycode,
536                     itemtype     => $itemtype,
537                     rule_name    => $rule_name,
538                 }
539             );
540
541             my $scope_output = '';
542             foreach my $key ( values @$order ) {
543                 $scope_output .= " $key" if $test->{$key} ne '*';
544             }
545
546             is( $rule->rule_value, $rule_value,
547                 'Explicitly scoped'
548                   . ( $scope_output ? $scope_output : ' nothing' ) );
549         }
550     };
551
552     my $our_branch_rules = Koha::CirculationRules->search({branchcode => $branchcode});
553     is( $our_branch_rules->count, 4, "We added 8 rules");
554     $our_branch_rules->delete;
555     is( $our_branch_rules->count, 0, "We deleted 8 rules");
556
557     $schema->storage->txn_rollback;
558 };
559
560 subtest 'get_onshelfholds_policy() tests' => sub {
561
562     plan tests => 2;
563
564     $schema->storage->txn_begin;
565
566     my $item = $builder->build_sample_item();
567
568     my $circ_rules = Koha::CirculationRules->new;
569     # Cleanup
570     $circ_rules->search({ rule_name => 'onshelfholds' })->delete;
571
572     $circ_rules->set_rule(
573         {
574             branchcode   => '*',
575             categorycode => '*',
576             itemtype     => '*',
577             rule_name    => 'onshelfholds',
578             rule_value   => 1,
579         }
580     );
581
582     is( $circ_rules->get_onshelfholds_policy({ item => $item }), 1, 'If rule_value is set on a matching rule, return it' );
583     # Delete the rule (i.e. get_effective_rule returns undef)
584     $circ_rules->delete;
585     is( $circ_rules->get_onshelfholds_policy({ item => $item }), 0, 'If no matching rule, fallback to 0' );
586
587     $schema->storage->txn_rollback;
588 };
589
590 subtest 'get_effective_daysmode' => sub {
591     plan tests => 4;
592
593     $schema->storage->txn_begin;
594
595     my $item_1 = $builder->build_sample_item();
596     my $item_2 = $builder->build_sample_item();
597
598     my $circ_rules =
599       Koha::CirculationRules->search( { rule_name => 'daysmode' } )->delete;
600
601     # Default value 'Datedue' at pref level
602     t::lib::Mocks::mock_preference( 'useDaysMode', 'Datedue' );
603
604     is(
605         Koha::CirculationRules->get_effective_daysmode(
606             {
607                 categorycode => undef,
608                 itemtype     => $item_1->effective_itemtype,
609                 branchcode   => undef
610             }
611         ),
612         'Datedue',
613         'daysmode default to pref value if the rule does not exist'
614     );
615
616     Koha::CirculationRules->set_rule(
617         {
618             branchcode   => '*',
619             categorycode => '*',
620             itemtype     => '*',
621             rule_name    => 'daysmode',
622             rule_value   => 'Calendar',
623         }
624     );
625     Koha::CirculationRules->set_rule(
626         {
627             branchcode   => '*',
628             categorycode => '*',
629             itemtype     => $item_1->effective_itemtype,
630             rule_name    => 'daysmode',
631             rule_value   => 'Days',
632         }
633     );
634
635     is(
636         Koha::CirculationRules->get_effective_daysmode(
637             {
638                 categorycode => undef,
639                 itemtype     => $item_1->effective_itemtype,
640                 branchcode   => undef
641             }
642         ),
643         'Days',
644         "daysmode for item_1 is the specific rule"
645     );
646     is(
647         Koha::CirculationRules->get_effective_daysmode(
648             {
649                 categorycode => undef,
650                 itemtype     => $item_2->effective_itemtype,
651                 branchcode   => undef
652             }
653         ),
654         'Calendar',
655         "daysmode for item_2 is the one defined for the default circ rule"
656     );
657
658     Koha::CirculationRules->set_rule(
659         {
660             branchcode   => '*',
661             categorycode => '*',
662             itemtype     => $item_2->effective_itemtype,
663             rule_name    => 'daysmode',
664             rule_value   => '',
665         }
666     );
667
668     is(
669         Koha::CirculationRules->get_effective_daysmode(
670             {
671                 categorycode => undef,
672                 itemtype     => $item_2->effective_itemtype,
673                 branchcode   => undef
674             }
675         ),
676         'Datedue',
677         'daysmode default to pref value if the rule exists but set to""'
678     );
679
680     $schema->storage->txn_rollback;
681 };
682
683 subtest 'get_lostreturn_policy() tests' => sub {
684     plan tests => 7;
685
686     $schema->storage->txn_begin;
687
688     $schema->resultset('CirculationRule')->search()->delete;
689
690     my $default_rule_charge = $builder->build(
691         {
692             source => 'CirculationRule',
693             value  => {
694                 branchcode   => undef,
695                 categorycode => undef,
696                 itemtype     => undef,
697                 rule_name    => 'lostreturn',
698                 rule_value   => 'charge'
699             }
700         }
701     );
702     my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
703     my $specific_rule_false = $builder->build(
704         {
705             source => 'CirculationRule',
706             value  => {
707                 branchcode   => $branchcode,
708                 categorycode => undef,
709                 itemtype     => undef,
710                 rule_name    => 'lostreturn',
711                 rule_value   => 0
712             }
713         }
714     );
715     my $branchcode2 = $builder->build( { source => 'Branch' } )->{branchcode};
716     my $specific_rule_refund = $builder->build(
717         {
718             source => 'CirculationRule',
719             value  => {
720                 branchcode   => $branchcode2,
721                 categorycode => undef,
722                 itemtype     => undef,
723                 rule_name    => 'lostreturn',
724                 rule_value   => 'refund'
725             }
726         }
727     );
728     my $branchcode3 = $builder->build( { source => 'Branch' } )->{branchcode};
729     my $specific_rule_restore = $builder->build(
730         {
731             source => 'CirculationRule',
732             value  => {
733                 branchcode   => $branchcode3,
734                 categorycode => undef,
735                 itemtype     => undef,
736                 rule_name    => 'lostreturn',
737                 rule_value   => 'restore'
738             }
739         }
740     );
741
742     # Make sure we have an unused branchcode
743     my $branchcode4 = $builder->build( { source => 'Branch' } )->{branchcode};
744     my $specific_rule_dummy = $builder->build(
745         {
746             source => 'CirculationRule',
747             value  => {
748                 branchcode   => $branchcode4,
749                 categorycode => undef,
750                 itemtype     => undef,
751                 rule_name    => 'lostreturn',
752                 rule_value   => 'refund'
753             }
754         }
755     );
756     my $branch_without_rule = $specific_rule_dummy->{ branchcode };
757     Koha::CirculationRules
758         ->search(
759             {
760                 branchcode   => $branch_without_rule,
761                 categorycode => undef,
762                 itemtype     => undef,
763                 rule_name    => 'lostreturn',
764                 rule_value   => 'refund'
765             }
766           )
767         ->next
768         ->delete;
769
770     my $item = $builder->build_sample_item(
771         {
772             homebranch    => $specific_rule_restore->{branchcode},
773             holdingbranch => $specific_rule_false->{branchcode}
774         }
775     );
776     my $params = {
777         return_branch => $specific_rule_refund->{ branchcode },
778         item          => $item
779     };
780
781     # Specific rules
782     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
783     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
784         'refund','Specific rule for checkin branch is applied (refund)');
785
786     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHomeBranch' );
787     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
788          'restore','Specific rule for home branch is applied (restore)');
789
790     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHoldingBranch' );
791     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
792          0,'Specific rule for holding branch is applied (false)');
793
794     # Default rule check
795     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
796     $params->{return_branch} = $branch_without_rule;
797     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
798          'charge','No rule for branch, global rule applied (charge)');
799
800     # Change the default value just to try
801     Koha::CirculationRules->search({ branchcode => undef, rule_name => 'lostreturn' })->next->rule_value(0)->store;
802     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
803          0,'No rule for branch, global rule applied (false)');
804
805     # No default rule defined check
806     Koha::CirculationRules
807         ->search(
808             {
809                 branchcode   => undef,
810                 categorycode => undef,
811                 itemtype     => undef,
812                 rule_name    => 'lostreturn'
813             }
814           )
815         ->next
816         ->delete;
817     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
818          'refund','No rule for branch, no default rule, fallback default (refund)');
819
820     # Fallback to ItemHoldBranch if CheckinLibrary is undefined
821     $params->{return_branch} = undef;
822     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
823          'restore','return_branch undefined, fallback to ItemHomeBranch rule (restore)');
824
825     $schema->storage->txn_rollback;
826 };
827
828 subtest 'get_processingreturn_policy() tests' => sub {
829     plan tests => 7;
830
831     $schema->storage->txn_begin;
832
833     $schema->resultset('CirculationRule')->search()->delete;
834
835     my $default_rule_charge = $builder->build(
836         {
837             source => 'CirculationRule',
838             value  => {
839                 branchcode   => undef,
840                 categorycode => undef,
841                 itemtype     => undef,
842                 rule_name    => 'processingreturn',
843                 rule_value   => 'charge'
844             }
845         }
846     );
847     my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
848     my $specific_rule_false = $builder->build(
849         {
850             source => 'CirculationRule',
851             value  => {
852                 branchcode   => $branchcode,
853                 categorycode => undef,
854                 itemtype     => undef,
855                 rule_name    => 'processingreturn',
856                 rule_value   => 0
857             }
858         }
859     );
860     my $branchcode2 = $builder->build( { source => 'Branch' } )->{branchcode};
861     my $specific_rule_refund = $builder->build(
862         {
863             source => 'CirculationRule',
864             value  => {
865                 branchcode   => $branchcode2,
866                 categorycode => undef,
867                 itemtype     => undef,
868                 rule_name    => 'processingreturn',
869                 rule_value   => 'refund'
870             }
871         }
872     );
873     my $branchcode3 = $builder->build( { source => 'Branch' } )->{branchcode};
874     my $specific_rule_restore = $builder->build(
875         {
876             source => 'CirculationRule',
877             value  => {
878                 branchcode   => $branchcode3,
879                 categorycode => undef,
880                 itemtype     => undef,
881                 rule_name    => 'processingreturn',
882                 rule_value   => 'restore'
883             }
884         }
885     );
886
887     # Make sure we have an unused branchcode
888     my $branchcode4 = $builder->build( { source => 'Branch' } )->{branchcode};
889     my $specific_rule_dummy = $builder->build(
890         {
891             source => 'CirculationRule',
892             value  => {
893                 branchcode   => $branchcode4,
894                 categorycode => undef,
895                 itemtype     => undef,
896                 rule_name    => 'processingreturn',
897                 rule_value   => 'refund'
898             }
899         }
900     );
901     my $branch_without_rule = $specific_rule_dummy->{ branchcode };
902     Koha::CirculationRules
903         ->search(
904             {
905                 branchcode   => $branch_without_rule,
906                 categorycode => undef,
907                 itemtype     => undef,
908                 rule_name    => 'processingreturn',
909                 rule_value   => 'refund'
910             }
911           )
912         ->next
913         ->delete;
914
915     my $item = $builder->build_sample_item(
916         {
917             homebranch    => $specific_rule_restore->{branchcode},
918             holdingbranch => $specific_rule_false->{branchcode}
919         }
920     );
921     my $params = {
922         return_branch => $specific_rule_refund->{ branchcode },
923         item          => $item
924     };
925
926     # Specific rules
927     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
928     is( Koha::CirculationRules->get_processingreturn_policy( $params ),
929         'refund','Specific rule for checkin branch is applied (refund)');
930
931     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHomeBranch' );
932     is( Koha::CirculationRules->get_processingreturn_policy( $params ),
933          'restore','Specific rule for home branch is applied (restore)');
934
935     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHoldingBranch' );
936     is( Koha::CirculationRules->get_processingreturn_policy( $params ),
937          0,'Specific rule for holding branch is applied (false)');
938
939     # Default rule check
940     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
941     $params->{return_branch} = $branch_without_rule;
942     is( Koha::CirculationRules->get_processingreturn_policy( $params ),
943          'charge','No rule for branch, global rule applied (charge)');
944
945     # Change the default value just to try
946     Koha::CirculationRules->search({ branchcode => undef, rule_name => 'processingreturn' })->next->rule_value(0)->store;
947     is( Koha::CirculationRules->get_processingreturn_policy( $params ),
948          0,'No rule for branch, global rule applied (false)');
949
950     # No default rule defined check
951     Koha::CirculationRules
952         ->search(
953             {
954                 branchcode   => undef,
955                 categorycode => undef,
956                 itemtype     => undef,
957                 rule_name    => 'processingreturn'
958             }
959           )
960         ->next
961         ->delete;
962     is( Koha::CirculationRules->get_processingreturn_policy( $params ),
963          'refund','No rule for branch, no default rule, fallback default (refund)');
964
965     # Fallback to ItemHoldBranch if CheckinLibrary is undefined
966     $params->{return_branch} = undef;
967     is( Koha::CirculationRules->get_processingreturn_policy( $params ),
968          'restore','return_branch undefined, fallback to ItemHomeBranch rule (restore)');
969
970     $schema->storage->txn_rollback;
971 };
972
973 sub _is_row_match {
974     my ( $rule, $expected, $message ) = @_;
975
976     ok( $rule, $message ) ?
977         cmp_methods( $rule, [ %$expected ], $message ) :
978         fail( $message );
979 }
980
981 sub _prepare_tests_for_rule_scope_combinations {
982     my ( $scope, $rule_name ) = @_;
983
984     # Here we create a combinations of 1s and 0s the following way
985     #
986     # 000...
987     # 001...
988     # 010...
989     # 011...
990     # 100...
991     # 101...
992     # 110...
993     # 111...
994     #
995     # (the number of columns equals to the amount of rule scopes)
996     # The ... symbolizes possible future scopes.
997     #
998     # - 0 equals to circulation rule scope with any value (aka. *)
999     # - 1 equals to circulation rule scope exact value, e.g.
1000     #     "CPL" (for branchcode).
1001     #
1002     # The order is the same as the weight of scopes when sorting circulation
1003     # rules. So the first column of numbers is the scope with most weight.
1004     # This is defined by C<$order> which will be assigned next.
1005     #
1006     # We must maintain the order in order to keep the test valid. This should be
1007     # equal to Koha/CirculationRules.pm "order_by" of C<get_effective_rule> sub.
1008     # Let's explicitly define the order and fail test if we are missing a scope:
1009     my $order = [ 'branchcode', 'categorycode', 'itemtype' ];
1010     is( join(", ", sort keys %$scope),
1011        join(", ", sort @$order), 'Missing a scope!' ) if keys %$scope ne scalar @$order;
1012
1013     my @tests = ();
1014     foreach my $value ( glob( "{0,1}" x keys %$scope || 1 ) ) {
1015         my $test = { %$scope };
1016         for ( my $i=0; $i < keys %$scope; $i++ ) {
1017             $test->{$order->[$i]} = '*' unless substr( $value, $i, 1 );
1018         }
1019         push @tests, $test;
1020     }
1021
1022     return \@tests, $order;
1023 }