ca10733a603aafbc6f45da1bbd2cd007a8307b80
[koha.git] / t / db_dependent / Patron / Borrower_PrevCheckout.t
1 #!/usr/bin/perl
2 use Modern::Perl;
3
4 use C4::Members;
5 use C4::Circulation qw( AddIssue AddReturn CanBookBeIssued );
6 use Koha::Database;
7 use Koha::DateUtils qw( dt_from_string );
8 use Koha::Patrons;
9
10 use Test::More tests => 61;
11
12 use_ok('Koha::Patron');
13
14 use t::lib::TestBuilder;
15 use t::lib::Mocks;
16
17 my $schema = Koha::Database->new->schema;
18 $schema->storage->txn_begin;
19
20 my $builder = t::lib::TestBuilder->new;
21 my $yesCatCode = $builder->build({
22     source => 'Category',
23     value => {
24         categorycode => 'yesCat',
25         checkprevcheckout => 'yes',
26     },
27 });
28
29 my $noCatCode = $builder->build({
30     source => 'Category',
31     value => {
32         categorycode => 'noCat',
33         checkprevcheckout => 'no',
34     },
35 });
36
37 my $inheritCatCode = $builder->build({
38     source => 'Category',
39     value => {
40         categorycode => 'inheritCat',
41         checkprevcheckout => 'inherit',
42     },
43 });
44
45 # Create context for some tests late on in the file.
46 my $library = $builder->build({ source => 'Branch' });
47 my $staff = $builder->build({source => 'Borrower'});
48
49 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
50
51 # wants_check_for_previous_checkout
52
53 # We expect the following result matrix:
54 #
55 # (1/0 indicates the return value of WantsCheckPrevCheckout; i.e. 1 says we
56 # should check whether the item was previously issued)
57 #
58 # | System Preference | hardyes                           | softyes                           | softno                            | hardno                            |
59 # |-------------------+-----------------------------------+-----------------------------------+-----------------------------------+-----------------------------------|
60 # | Category Setting  | yes       | no        | inherit   | yes       | no        | inherit   | yes       | no        | inherit   | yes       | no        | inherit   |
61 # |-------------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------|
62 # | Patron Setting    | y | n | i | y | n | i | y | n | i | y | n | i | y | n | i | y | n | i | y | n | i | y | n | i | y | n | i | y | n | i | y | n | i | y | n | i |
63 # |-------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
64 # | Expected Result   | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
65
66 my $mappings = [
67     {
68         syspref    => 'hardyes',
69         categories => [
70             {
71                 setting => 'yes',
72                 patrons => [
73                     {setting => 'yes',     result => 1},
74                     {setting => 'no',      result => 1},
75                     {setting => 'inherit', result => 1},
76                 ],
77             },
78             {
79                 setting => 'no',
80                 patrons => [
81                     {setting => 'yes',     result => 1},
82                     {setting => 'no',      result => 1},
83                     {setting => 'inherit', result => 1},
84                 ],
85             },
86             {
87                 setting => 'inherit',
88                 patrons => [
89                     {setting => 'yes',     result => 1},
90                     {setting => 'no',      result => 1},
91                     {setting => 'inherit', result => 1},
92                 ],
93             },
94         ],
95     },
96     {
97         syspref    => 'softyes',
98         categories => [
99             {
100                 setting => 'yes',
101                 patrons => [
102                     {setting => 'yes',     result => 1},
103                     {setting => 'no',      result => 0},
104                     {setting => 'inherit', result => 1},
105                 ],
106             },
107             {
108                 setting => 'no',
109                 patrons => [
110                     {setting => 'yes',     result => 1},
111                     {setting => 'no',      result => 0},
112                     {setting => 'inherit', result => 0},
113                 ],
114             },
115             {
116                 setting => 'inherit',
117                 patrons => [
118                     {setting => 'yes',     result => 1},
119                     {setting => 'no',      result => 0},
120                     {setting => 'inherit', result => 1},
121                 ],
122             },
123         ],
124     },
125     {
126         syspref    => 'softno',
127         categories => [
128             {
129                 setting => 'yes',
130                 patrons => [
131                     {setting => 'yes',     result => 1},
132                     {setting => 'no',      result => 0},
133                     {setting => 'inherit', result => 1},
134                 ],
135             },
136             {
137                 setting => 'no',
138                 patrons => [
139                     {setting => 'yes',     result => 1},
140                     {setting => 'no',      result => 0},
141                     {setting => 'inherit', result => 0},
142                 ],
143             },
144             {
145                 setting => 'inherit',
146                 patrons => [
147                     {setting => 'yes',     result => 1},
148                     {setting => 'no',      result => 0},
149                     {setting => 'inherit', result => 0},
150                 ],
151             },
152         ],
153     },
154     {
155         syspref    => 'hardno',
156         categories => [
157             {
158                 setting => 'yes',
159                 patrons => [
160                     {setting => 'yes',     result => 0},
161                     {setting => 'no',      result => 0},
162                     {setting => 'inherit', result => 0},
163                 ],
164             },
165             {
166                 setting => 'no',
167                 patrons => [
168                     {setting => 'yes',     result => 0},
169                     {setting => 'no',      result => 0},
170                     {setting => 'inherit', result => 0},
171                 ],
172             },
173             {
174                 setting => 'inherit',
175                 patrons => [
176                     {setting => 'yes',     result => 0},
177                     {setting => 'no',      result => 0},
178                     {setting => 'inherit', result => 0},
179                 ],
180             },
181         ],
182     },
183 ];
184
185 map {
186     my $syspref = $_->{syspref};
187     t::lib::Mocks::mock_preference('checkprevcheckout', $syspref);
188     map {
189         my $code = $_->{setting} . 'Cat';
190         map {
191             my $kpatron = $builder->build({
192                 source => 'Borrower',
193                 value  => {
194                     checkprevcheckout => $_->{setting},
195                     categorycode => $code,
196                 },
197             });
198             my $patron = Koha::Patrons->find($kpatron->{borrowernumber});
199             is(
200                 $patron->wants_check_for_previous_checkout, $_->{result},
201                 "Predicate with syspref " . $syspref . ", cat " . $code
202                     . ", patron " . $_->{setting}
203               );
204         } @{$_->{patrons}};
205     } @{$_->{categories}};
206 } @{$mappings};
207
208 # do_check_for_previous_checkout
209
210 # We want to test:
211 # - DESCRIPTION [RETURNVALUE (0/1)]
212 ## PreIssue (sanity checks)
213 # - Item, patron [0]
214 # - Diff item, same bib, same patron [0]
215 # - Diff item, diff bib, same patron [0]
216 # - Same item, diff patron [0]
217 # - Diff item, same bib, diff patron [0]
218 # - Diff item, diff bib, diff patron [0]
219 ## PostIssue
220 # - Same item, same patron [1]
221 # - Diff item, same bib, same patron [1]
222 # - Diff item, diff bib, same patron [0]
223 # - Same item, diff patron [0]
224 # - Diff item, same bib, diff patron [0]
225 # - Diff item, diff bib, diff patron [0]
226 ## PostReturn
227 # - Same item, same patron [1]
228 # - Diff item, same bib, same patron [1]
229 # - Diff item, diff bib, same patron [0]
230 # - Same item, diff patron [0]
231 # - Diff item, same bib, diff patron [0]
232 # - Diff item, diff bib, diff patron [0]
233
234 # Requirements:
235 # $patron, $different_patron, $items (same bib number), $different_item
236 my $patron = $builder->build({source => 'Borrower'});
237 my $patron_d = $builder->build({source => 'Borrower'});
238
239 my $biblio = $builder->build_sample_biblio;
240 $biblio->serial(0)->store;
241 my $item_1 = $builder->build_sample_item({biblionumber => $biblio->biblionumber})->unblessed;
242 my $item_2 = $builder->build_sample_item({biblionumber => $biblio->biblionumber})->unblessed;
243 my $item_d = $builder->build_sample_item->unblessed;
244
245 ## Testing Sub
246 sub test_it {
247     my ($mapping, $stage) = @_;
248     map {
249         my $patron = Koha::Patrons->find($_->{patron}->{borrowernumber});
250         is(
251             $patron->do_check_for_previous_checkout($_->{item}),
252             $_->{result}, $stage . ": " . $_->{msg}
253         );
254     } @{$mapping};
255 };
256
257 ## Initial Mappings
258 my $cpvmappings = [
259     {
260         msg => "Item, patron [0]",
261         item => $item_1,
262         patron => $patron,
263         result => 0,
264     },
265     {
266         msg => "Diff item, same bib, same patron [0]",
267         item => $item_2,
268         patron => $patron,
269         result => 0,
270     },
271     {
272         msg => "Diff item, diff bib, same patron [0]",
273         item => $item_d,
274         patron => $patron,
275         result => 0,
276     },
277     {
278         msg => "Same item, diff patron [0]",
279         item => $item_1,
280         patron => $patron_d,
281         result => 0,
282     },
283     {
284         msg => "Diff item, same bib, diff patron [0]",
285         item => $item_2,
286         patron => $patron_d,
287         result => 0,
288     },
289     {
290         msg => "Diff item, diff bib, diff patron [0]",
291         item => $item_d,
292         patron => $patron_d,
293         result => 0,
294     },
295 ];
296
297 test_it($cpvmappings, "PreIssue");
298
299 # Issue item_1 to $patron:
300 my $patron_get_mem = Koha::Patrons->find( $patron->{borrowernumber} )->unblessed;
301 BAIL_OUT("Issue failed")
302     unless AddIssue($patron_get_mem, $item_1->{barcode});
303
304 # Then test:
305 my $cpvPmappings = [
306     {
307         msg => "Same item, same patron [1]",
308         item => $item_1,
309         patron => $patron,
310         result => 1,
311     },
312     {
313         msg => "Diff item, same bib, same patron [1]",
314         item => $item_2,
315         patron => $patron,
316         result => 1,
317     },
318     {
319         msg => "Diff item, diff bib, same patron [0]",
320         item => $item_d,
321         patron => $patron,
322         result => 0,
323     },
324     {
325         msg => "Same item, diff patron [0]",
326         item => $item_1,
327         patron => $patron_d,
328         result => 0,
329     },
330     {
331         msg => "Diff item, same bib, diff patron [0]",
332         item => $item_2,
333         patron => $patron_d,
334         result => 0,
335     },
336     {
337         msg => "Diff item, diff bib, diff patron [0]",
338         item => $item_d,
339         patron => $patron_d,
340         result => 0,
341     },
342 ];
343
344 test_it($cpvPmappings, "PostIssue");
345
346 # Return item_1 from patron:
347 BAIL_OUT("Return Failed") unless AddReturn($item_1->{barcode}, $patron->{branchcode});
348
349 # Then:
350 test_it($cpvPmappings, "PostReturn");
351
352 # Finally test C4::Circulation::CanBookBeIssued
353
354 # We have already tested ->wants_check_for_previous_checkout and
355 # ->do_check_for_previous_checkout, so all that remains to be tested is
356 # whetherthe different combinational outcomes of the above return values in
357 # CanBookBeIssued result in the approriate $needsconfirmation.
358
359 # We want to test:
360 # - DESCRIPTION [RETURNVALUE (0/1)]
361 # - patron, !wants_check_for_previous_checkout, !do_check_for_previous_checkout
362 #   [!$issuingimpossible,!$needsconfirmation->{PREVISSUE}]
363 # - patron, wants_check_for_previous_checkout, !do_check_for_previous_checkout
364 #   [!$issuingimpossible,!$needsconfirmation->{PREVISSUE}]
365 # - patron, !wants_check_for_previous_checkout, do_check_for_previous_checkout
366 #   [!$issuingimpossible,!$needsconfirmation->{PREVISSUE}]
367 # - patron, wants_check_for_previous_checkout, do_check_for_previous_checkout
368 #   [!$issuingimpossible,$needsconfirmation->{PREVISSUE}]
369
370 # Needs:
371 # - $patron
372 # - $item objects (one not issued, another prevIssued)
373 # - $checkprevcheckout pref (first hardno, then hardyes)
374
375 # Our Patron
376 my $patron_category = $builder->build({ source => 'Category', value => { category_type => 'P', enrolmentfee => 0 } });
377 my $CBBI_patron = $builder->build({source => 'Borrower', value => { categorycode => $patron_category->{categorycode} }});
378 $patron = Koha::Patrons->find( $CBBI_patron->{borrowernumber} );
379 # Our Items
380
381 my $new_item = $builder->build_sample_item->unblessed;
382 my $prev_item = $builder->build_sample_item->unblessed;
383 # Second is Checked Out
384 BAIL_OUT("CanBookBeIssued Issue failed")
385     unless AddIssue($patron->unblessed, $prev_item->{barcode});
386
387 # Mappings
388 my $CBBI_mappings = [
389     {
390         syspref => 'hardno',
391         item    => $new_item,
392         result  => undef,
393         msg     => "patron, !wants_check_for_previous_checkout, !do_check_for_previous_checkout"
394
395     },
396     {
397         syspref => 'hardyes',
398         item    => $new_item,
399         result  => undef,
400         msg     => "patron, wants_check_for_previous_checkout, !do_check_for_previous_checkout"
401     },
402     {
403         syspref => 'hardno',
404         item    => $prev_item,
405         result  => undef,
406         msg     => "patron, !wants_check_for_previous_checkout, do_check_for_previous_checkout"
407     },
408     {
409         syspref => 'hardyes',
410         item    => $prev_item,
411         result  => 1,
412         msg     => "patron, wants_check_for_previous_checkout, do_check_for_previous_checkout"
413     },
414 ];
415
416 # Tests
417 map {
418     t::lib::Mocks::mock_preference('checkprevcheckout', $_->{syspref});
419     my ( $issuingimpossible, $needsconfirmation ) =
420         C4::Circulation::CanBookBeIssued(
421             $patron, $_->{item}->{barcode}
422         );
423     is($needsconfirmation->{PREVISSUE}, $_->{result}, $_->{msg});
424 } @{$CBBI_mappings};
425
426 $schema->storage->txn_rollback;
427
428 subtest 'Check previous checkouts for serial' => sub {
429     plan tests => 2;
430     $schema->storage->txn_begin;
431
432     my $library = $builder->build_object({ class => 'Koha::Libraries'});
433
434     my $patron = $builder->build_object({
435             class => 'Koha::Patrons',
436             value => {
437                 branchcode => $library->branchcode
438             }
439         });
440     t::lib::Mocks::mock_userenv({ patron => $patron });
441
442     my $biblio = $builder->build_sample_biblio;
443     $biblio->serial(1)->store;
444
445     my $item1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
446     my $item2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
447
448     AddIssue($patron->unblessed, $item1->barcode);
449
450     is($patron->do_check_for_previous_checkout($item1->unblessed), 1, 'Check only one item if bibliographic record is serial');
451     is($patron->do_check_for_previous_checkout($item2->unblessed), 0, 'Check only one item if bibliographic record is serial');
452
453     $schema->storage->txn_rollback;
454 };
455
456 subtest 'Check previous checkouts with delay' => sub {
457     plan tests => 3;
458     $schema->storage->txn_begin;
459     my $library = $builder->build_object({ class => 'Koha::Libraries'});
460     my $biblio = $builder->build_sample_biblio;
461     my $patron = $builder->build({source => 'Borrower'});
462     my $item_object = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
463
464     my $issue = Koha::Checkout->new({ branchcode => $library->branchcode, borrowernumber => $patron->{borrowernumber}, itemnumber => $item_object->itemnumber })->store;
465     my $returndate = dt_from_string()->subtract( days => 3 );
466     my $return = AddReturn($item_object->barcode, $library->branchcode, undef, $returndate);
467
468     t::lib::Mocks::mock_preference('checkprevcheckout', 'hardyes');
469     t::lib::Mocks::mock_preference('checkprevcheckoutdelay', 0);
470     my $patron1 = Koha::Patrons->find($patron->{borrowernumber});
471     is(
472             $patron1->do_check_for_previous_checkout($item_object->unblessed),
473             1, "Checking CheckPrevCheckoutDelay disabled"
474     );
475     t::lib::Mocks::mock_preference('checkprevcheckoutdelay', 5);
476     is(
477             $patron1->do_check_for_previous_checkout($item_object->unblessed),
478             1, "Checking CheckPrevCheckoutDelay enabled within delay"
479     );
480     t::lib::Mocks::mock_preference('checkprevcheckoutdelay', 1);
481     is(
482             $patron1->do_check_for_previous_checkout($item_object->unblessed),
483             0, "Checking CheckPrevCheckoutDelay enabled after delay"
484     );
485     $schema->storage->txn_rollback;
486 }