Bug 21284: (QA follow-up) Rename itemonhold and recordonhold
[koha.git] / t / db_dependent / ILSDI_Services.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use CGI qw ( -utf8 );
21
22 use Test::More tests => 13;
23 use Test::MockModule;
24 use t::lib::Mocks;
25 use t::lib::TestBuilder;
26 use t::lib::Dates;
27 use XML::LibXML;
28
29 use C4::Items qw( ModItemTransfer );
30 use C4::Circulation qw( AddIssue );
31 use C4::Reserves qw (AddReserve ModReserve ModReserveAffect ModReserveStatus);
32
33 use Koha::AuthUtils;
34 use Koha::DateUtils qw( dt_from_string );
35
36 BEGIN {
37     use_ok('C4::ILSDI::Services', qw( AuthenticatePatron GetPatronInfo LookupPatron HoldTitle HoldItem GetRecords RenewLoan GetAvailability ));
38 }
39
40 my $schema  = Koha::Database->schema;
41 my $dbh     = C4::Context->dbh;
42 my $builder = t::lib::TestBuilder->new;
43
44 subtest 'AuthenticatePatron test' => sub {
45
46     plan tests => 18;
47
48     $schema->storage->txn_begin;
49
50
51     my $plain_password = 'tomasito';
52
53     $builder->build({
54         source => 'Borrower',
55         value => {
56             cardnumber => undef,
57         }
58     });
59
60     my $borrower = $builder->build({
61         source => 'Borrower',
62         value  => {
63             cardnumber => undef,
64             password => Koha::AuthUtils::hash_password( $plain_password ),
65             lastseen => "2001-01-01 12:34:56"
66         }
67     });
68
69     my $query = CGI->new;
70     $query->param( 'username', $borrower->{userid});
71     $query->param( 'password', $plain_password);
72
73     t::lib::Mocks::mock_preference( 'TrackLastPatronActivityTriggers', '' );
74     my $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
75     is( $reply->{id}, $borrower->{borrowernumber}, "userid and password - Patron authenticated" );
76     is( $reply->{code}, undef, "Error code undef");
77     my $seen_patron = Koha::Patrons->find({ borrowernumber => $reply->{id} });
78     is( $seen_patron->lastseen(), '2001-01-01 12:34:56','Last seen not updated if not tracking patrons');
79
80     $query->param('password','ilsdi-passworD');
81     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
82     is( $reply->{code}, 'PatronNotFound', "userid and wrong password - PatronNotFound" );
83     is( $reply->{id}, undef, "id undef");
84
85     $query->param( 'password', $plain_password );
86     $query->param( 'username', 'wrong-ilsdi-useriD' );
87     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
88     is( $reply->{code}, 'PatronNotFound', "non-existing userid - PatronNotFound" );
89     is( $reply->{id}, undef, "id undef");
90
91     t::lib::Mocks::mock_preference( 'TrackLastPatronActivityTriggers', 'connection' );
92     $query->param( 'username', uc( $borrower->{userid} ));
93     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
94     my $now = dt_from_string;
95     is( $reply->{id}, $borrower->{borrowernumber}, "userid is not case sensitive - Patron authenticated" );
96     is( $reply->{code}, undef, "Error code undef");
97     $seen_patron = Koha::Patrons->find({ borrowernumber => $reply->{id} });
98     is( t::lib::Dates::compare( $seen_patron->lastseen, $now), 0, 'Last seen updated to today if tracking patrons' );
99
100     $query->param( 'username', $borrower->{cardnumber} );
101     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
102     is( $reply->{id}, $borrower->{borrowernumber}, "cardnumber and password - Patron authenticated" );
103     is( $reply->{code}, undef, "Error code undef" );
104
105     $query->param( 'password', 'ilsdi-passworD' );
106     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
107     is( $reply->{code}, 'PatronNotFound', "cardnumber and wrong password - PatronNotFount" );
108     is( $reply->{id}, undef, "id undef" );
109
110     $query->param( 'username', 'randomcardnumber1234' );
111     $query->param( 'password', $plain_password );
112     $reply = C4::ILSDI::Services::AuthenticatePatron($query);
113     is( $reply->{code}, 'PatronNotFound', "non-existing cardnumer/userid - PatronNotFound" );
114     is( $reply->{id}, undef, "id undef");
115
116     $query->param( 'username', $borrower->{userid} );
117     $query->param( 'password', $plain_password );
118     $seen_patron->password_expiration_date('2020-01-01')->store;
119     $reply = C4::ILSDI::Services::AuthenticatePatron($query);
120     is( $reply->{code}, 'PasswordExpired', "correct credentials, expired password not authenticated" );
121     is( $reply->{id}, undef, "id undef");
122
123     $schema->storage->txn_rollback;
124 };
125
126 subtest 'GetPatronInfo test for holds' => sub {
127     plan tests => 8;
128
129     $schema->storage->txn_begin;
130     $schema->resultset( 'Issue' )->delete_all;
131     $schema->resultset( 'Reserve' )->delete_all;
132     $schema->resultset( 'Borrower' )->delete_all;
133     $schema->resultset( 'Category' )->delete_all;
134     $schema->resultset( 'Item' )->delete_all; # 'Branch' deps. on this
135     $schema->resultset( 'Branch' )->delete_all;
136
137     # Configure Koha to enable ILS-DI server
138     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
139
140     my $library = $builder->build_object({
141         class => 'Koha::Libraries',
142     });
143
144     # Create new users:
145     my $brwr = $builder->build_object( {
146         class => 'Koha::Patrons',
147         value  => {
148             branchcode   => $library->branchcode,
149         }
150     } );
151     my $brwr2 = $builder->build_object( {
152         class => 'Koha::Patrons',
153         value  => {
154             branchcode   => $library->branchcode,
155         }
156     } );
157     my $brwr3 = $builder->build_object( {
158         class => 'Koha::Patrons',
159         value  => {
160             branchcode   => $library->branchcode,
161         }
162     } );
163
164     my $module = Test::MockModule->new('C4::Context');
165     $module->mock('userenv', sub { { branch => $library->branchcode } });
166
167     # Place a loan
168     my $biblio = $builder->build_object( { class => 'Koha::Biblios' } );
169     my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes' });
170     my $biblioitem = $builder->build_object( { class => 'Koha::Biblioitems', value => { biblionumber => $biblio->biblionumber } } );
171     my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber, library => $library->branchcode, itype => $itemtype->itemtype });
172     my $issue = AddIssue($brwr, $item->barcode);
173
174     # Prepare and send web request for IL-SDI server:
175     my $query = new CGI;
176     $query->param( 'service', 'GetPatronInfo' );
177     $query->param( 'patron_id', $brwr->borrowernumber );
178     $query->param( 'show_loans', '1' );
179     my $reply = C4::ILSDI::Services::GetPatronInfo( $query );
180
181     # Check that this loan is not on hold
182     is ( $reply->{loans}->{loan}[0]->{holds_on_record}, "0", "Record is not on hold");
183     is ( $reply->{loans}->{loan}[0]->{holds_on_item}, "0", "Item is not on hold");
184
185     # Place a loan
186     # Add a hold on the biblio
187     my $biblioreserve = AddReserve({ branchcode => $library->branchcode, borrowernumber => $brwr2->borrowernumber, biblionumber => $biblio->biblionumber });
188
189     # Check that it is on hold on biblio level
190     $reply = C4::ILSDI::Services::GetPatronInfo( $query );
191     is ( $reply->{loans}->{loan}[0]->{holds_on_record}, "1", "Record is on hold");
192     is ( $reply->{loans}->{loan}[0]->{holds_on_item}, "0", "Item is on hold");
193
194     # Delete holds
195     $schema->resultset( 'Reserve' )->delete_all;
196
197     # Add a hold on the item
198     my $itemreserve = AddReserve({
199              branchcode => $library->branchcode,
200              borrowernumber => $brwr2->borrowernumber,
201              biblionumber => $biblio->biblionumber,
202              itemnumber => $item->itemnumber
203      });
204
205     # When a specific item has a reserve, the item is on hold as well as the record
206     $reply = C4::ILSDI::Services::GetPatronInfo( $query );
207     is ( $reply->{loans}->{loan}[0]->{holds_on_record}, "1", "Record is on hold");
208     is ( $reply->{loans}->{loan}[0]->{holds_on_item}, "1", "Item is on hold");
209
210     # Add another hold on the biblio
211     $biblioreserve = AddReserve({ branchcode => $library->branchcode, borrowernumber => $brwr3->borrowernumber, biblionumber => $biblio->biblionumber });
212
213     # Check that there are 2 holds on the biblio and 1 on this specific item
214     $reply = C4::ILSDI::Services::GetPatronInfo( $query );
215     is ( $reply->{loans}->{loan}[0]->{holds_on_record}, "2", "Record is on hold twice");
216     is ( $reply->{loans}->{loan}[0]->{holds_on_item}, "1", "Item is on hold");
217
218     # Cleanup
219     $schema->storage->txn_rollback;
220
221 };
222
223 subtest 'GetPatronInfo/GetBorrowerAttributes test for extended patron attributes' => sub {
224
225     plan tests => 5;
226
227     $schema->storage->txn_begin;
228
229     $schema->resultset( 'Issue' )->delete_all;
230     $schema->resultset( 'Borrower' )->delete_all;
231     $schema->resultset( 'BorrowerAttribute' )->delete_all;
232     $schema->resultset( 'BorrowerAttributeType' )->delete_all;
233     $schema->resultset( 'Category' )->delete_all;
234     $schema->resultset( 'Item' )->delete_all; # 'Branch' deps. on this
235     $schema->resultset( 'Club' )->delete_all;
236     $schema->resultset( 'Branch' )->delete_all;
237
238     # Configure Koha to enable ILS-DI server and extended attributes:
239     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
240     t::lib::Mocks::mock_preference( 'ExtendedPatronAttributes', 1 );
241
242     # Set up a library/branch for our user to belong to:
243     my $lib = $builder->build( {
244         source => 'Branch',
245         value => {
246             branchcode => 'T_ILSDI',
247         }
248     } );
249
250     # Create a new category for user to belong to:
251     my $cat = $builder->build( {
252         source => 'Category',
253         value  => {
254             category_type                 => 'A',
255             BlockExpiredPatronOpacActions => -1,
256         }
257     } );
258
259     # Create a new attribute type:
260     my $attr_type = $builder->build( {
261         source => 'BorrowerAttributeType',
262         value  => {
263             code                      => 'HIDEME',
264             opac_display              => 0,
265             authorised_value_category => '',
266             class                     => '',
267         }
268     } );
269     my $attr_type_visible = $builder->build( {
270         source => 'BorrowerAttributeType',
271         value  => {
272             code                      => 'SHOWME',
273             opac_display              => 1,
274             authorised_value_category => '',
275             class                     => '',
276         }
277     } );
278
279     # Create a new user:
280     my $brwr = $builder->build( {
281         source => 'Borrower',
282         value  => {
283             categorycode => $cat->{'categorycode'},
284             branchcode   => $lib->{'branchcode'},
285         }
286     } );
287
288     # Authorised value:
289     my $auth = $builder->build( {
290         source => 'AuthorisedValue',
291         value  => {
292             category => $cat->{'categorycode'}
293         }
294     } );
295
296     # Set the new attribute for our user:
297     my $attr_hidden = $builder->build( {
298         source => 'BorrowerAttribute',
299         value  => {
300             borrowernumber => $brwr->{'borrowernumber'},
301             code           => $attr_type->{'code'},
302             attribute      => '1337 hidden',
303         }
304     } );
305     my $attr_shown = $builder->build( {
306         source => 'BorrowerAttribute',
307         value  => {
308             borrowernumber => $brwr->{'borrowernumber'},
309             code           => $attr_type_visible->{'code'},
310             attribute      => '1337 shown',
311         }
312     } );
313
314     my $fine = $builder->build(
315         {
316             source => 'Accountline',
317             value  => {
318                 borrowernumber    => $brwr->{borrowernumber},
319                 debit_type_code   => 'OVERDUE',
320                 amountoutstanding => 10
321             }
322         }
323     );
324
325     # Prepare and send web request for IL-SDI server:
326     my $query = CGI->new;
327     $query->param( 'service', 'GetPatronInfo' );
328     $query->param( 'patron_id', $brwr->{'borrowernumber'} );
329     $query->param( 'show_attributes', '1' );
330     $query->param( 'show_fines', '1' );
331
332     my $reply = C4::ILSDI::Services::GetPatronInfo( $query );
333
334     # Build a structure for comparison:
335     my $cmp = {
336         borrowernumber    => $brwr->{borrowernumber},
337         value             => $attr_shown->{'attribute'},
338         value_description => $attr_shown->{'attribute'},
339         %$attr_type_visible,
340         %$attr_shown,
341     };
342
343     is( $reply->{'charges'}, '10.00',
344         'The \'charges\' attribute should be correctly filled (bug 17836)' );
345
346     is( scalar( @{$reply->{fines}->{fine}}), 1, 'There should be only 1 account line');
347     is(
348         $reply->{fines}->{fine}->[0]->{accountlines_id},
349         $fine->{accountlines_id},
350         "The accountline should be the correct one"
351     );
352
353     # Check results:
354     is_deeply( $reply->{'attributes'}, [ $cmp ], 'Test GetPatronInfo - show_attributes parameter' );
355
356     ok( exists $reply->{is_expired}, 'There should be the is_expired information');
357
358     # Cleanup
359     $schema->storage->txn_rollback;
360 };
361
362 subtest 'LookupPatron test' => sub {
363
364     plan tests => 9;
365
366     $schema->storage->txn_begin;
367
368     $schema->resultset( 'Issue' )->delete_all;
369     $schema->resultset( 'Borrower' )->delete_all;
370     $schema->resultset( 'BorrowerAttribute' )->delete_all;
371     $schema->resultset( 'BorrowerAttributeType' )->delete_all;
372     $schema->resultset( 'Category' )->delete_all;
373     $schema->resultset( 'Item' )->delete_all; # 'Branch' deps. on this
374     $schema->resultset( 'Branch' )->delete_all;
375
376     my $borrower = $builder->build({
377         source => 'Borrower',
378     });
379
380     my $query = CGI->new();
381     my $bad_result = C4::ILSDI::Services::LookupPatron($query);
382     is( $bad_result->{message}, 'PatronNotFound', 'No parameters' );
383
384     $query->delete_all();
385     $query->param( 'id', $borrower->{firstname} );
386     my $optional_result = C4::ILSDI::Services::LookupPatron($query);
387     is(
388         $optional_result->{id},
389         $borrower->{borrowernumber},
390         'Valid Firstname only'
391     );
392
393     $query->delete_all();
394     $query->param( 'id', 'ThereIsNoWayThatThisCouldPossiblyBeValid' );
395     my $bad_optional_result = C4::ILSDI::Services::LookupPatron($query);
396     is( $bad_optional_result->{message}, 'PatronNotFound', 'Invalid ID' );
397
398     foreach my $id_type (
399         'cardnumber',
400         'userid',
401         'email',
402         'borrowernumber',
403         'surname',
404         'firstname'
405     ) {
406         $query->delete_all();
407         $query->param( 'id_type', $id_type );
408         $query->param( 'id', $borrower->{$id_type} );
409         my $result = C4::ILSDI::Services::LookupPatron($query);
410         is( $result->{'id'}, $borrower->{borrowernumber}, "Checking $id_type" );
411     }
412
413     # Cleanup
414     $schema->storage->txn_rollback;
415 };
416
417 subtest 'Holds test' => sub {
418
419     plan tests => 9;
420
421     $schema->storage->txn_begin;
422
423     t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 0 );
424
425     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
426
427     my $item = $builder->build_sample_item(
428         {
429             damaged => 1
430         }
431     );
432
433     my $query = CGI->new;
434     $query->param( 'patron_id', $patron->borrowernumber);
435     $query->param( 'bib_id', $item->biblionumber);
436
437     my $reply = C4::ILSDI::Services::HoldTitle( $query );
438     is( $reply->{code}, 'damaged', "Item damaged" );
439
440     $item->damaged(0)->store;
441
442     my $hold = $builder->build({
443         source => 'Reserve',
444         value => {
445             borrowernumber => $patron->borrowernumber,
446             biblionumber => $item->biblionumber,
447             itemnumber => $item->itemnumber
448         }
449     });
450
451     $reply = C4::ILSDI::Services::HoldTitle( $query );
452     is( $reply->{code}, 'itemAlreadyOnHold', "Item already on hold" );
453
454     my $biblio_with_no_item = $builder->build_sample_biblio;
455
456     $query = CGI->new;
457     $query->param( 'patron_id', $patron->borrowernumber);
458     $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
459
460     $reply = C4::ILSDI::Services::HoldTitle( $query );
461     is( $reply->{code}, 'NoItems', 'Biblio has no item' );
462
463     my $item2 = $builder->build_sample_item(
464         {
465             damaged => 0,
466         }
467     );
468
469     t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
470     Koha::CirculationRules->set_rule(
471         {
472             categorycode => $patron->categorycode,
473             itemtype     => $item2->{itype},
474             branchcode   => $patron->branchcode,
475             rule_name    => 'reservesallowed',
476             rule_value   => 1,
477         }
478     );
479
480     $query = CGI->new;
481     $query->param( 'patron_id', $patron->borrowernumber);
482     $query->param( 'bib_id', $item2->biblionumber);
483     $query->param( 'item_id', $item2->itemnumber);
484
485     $reply = C4::ILSDI::Services::HoldItem( $query );
486     is( $reply->{code}, 'tooManyReserves', "Too many reserves" );
487
488     Koha::CirculationRules->set_rule(
489         {
490             categorycode => $patron->categorycode,
491             itemtype     => $item2->{itype},
492             branchcode   => $patron->branchcode,
493             rule_name    => 'reservesallowed',
494             rule_value   => 0,
495         }
496     );
497
498     $query = CGI->new;
499     $query->param( 'patron_id', $patron->borrowernumber);
500     $query->param( 'bib_id', $item2->biblionumber);
501     $query->param( 'item_id', $item2->itemnumber);
502
503     $reply = C4::ILSDI::Services::HoldItem( $query );
504     is( $reply->{code}, 'noReservesAllowed', "No reserves allowed" );
505
506     my $origin_branch = $builder->build(
507         {
508             source => 'Branch',
509             value  => {
510                 pickup_location => 1,
511             }
512         }
513     );
514
515     # Adding a holdable item.
516     my $item3 = $builder->build_sample_item(
517        {
518            barcode => '123456789',
519            library => $origin_branch->{branchcode}
520        });
521
522     my $item4 = $builder->build_sample_item(
523         {
524            biblionumber => $item3->biblionumber,
525            damaged => 1,
526            library => $origin_branch->{branchcode}
527        });
528
529     Koha::CirculationRules->set_rule(
530         {
531             categorycode => $patron->categorycode,
532             itemtype     => $item3->{itype},
533             branchcode   => $patron->branchcode,
534             rule_name    => 'reservesallowed',
535             rule_value   => 10,
536         }
537     );
538
539     $query = CGI->new;
540     $query->param( 'patron_id', $patron->borrowernumber);
541     $query->param( 'bib_id', $item4->biblionumber);
542     $query->param( 'item_id', $item4->itemnumber);
543
544     $reply = C4::ILSDI::Services::HoldItem( $query );
545     is( $reply->{code}, 'damaged', "Item is damaged" );
546
547     my $module = Test::MockModule->new('C4::Context');
548     $module->mock('userenv', sub { { patron => $patron->unblessed } });
549     my $issue = C4::Circulation::AddIssue($patron, $item3->barcode);
550     t::lib::Mocks::mock_preference( 'AllowHoldsOnPatronsPossessions', '0' );
551
552     $query = CGI->new;
553     $query->param( 'patron_id', $patron->borrowernumber);
554     $query->param( 'bib_id', $item3->biblionumber);
555     $query->param( 'item_id', $item3->itemnumber);
556     $query->param( 'pickup_location', $origin_branch->{branchcode});
557     $reply = C4::ILSDI::Services::HoldItem( $query );
558
559     is( $reply->{code}, 'alreadypossession', "Patron has issued same book" );
560     is( $reply->{pickup_location}, undef, "No reserve placed");
561
562     # Test Patron cannot reserve if expired and BlockExpiredPatronOpacActions
563     my $category = $builder->build({
564         source => 'Category',
565         value => { BlockExpiredPatronOpacActions => -1 }
566         });
567
568     my $branch_1 = $builder->build({ source => 'Branch' })->{ branchcode };
569
570     my $expired_borrowernumber = Koha::Patron->new({
571         firstname =>  'Expired',
572         surname => 'Patron',
573         categorycode => $category->{categorycode},
574         branchcode => $branch_1,
575         dateexpiry => '2000-01-01',
576     })->store->borrowernumber;
577
578     t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
579
580     my $item5 = $builder->build({
581         source => 'Item',
582         value => {
583             biblionumber => $biblio_with_no_item->biblionumber,
584             damaged => 0,
585         }
586     });
587
588     $query = CGI->new;
589     $query->param( 'patron_id', $expired_borrowernumber);
590     $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
591     $query->param( 'item_id', $item5->{itemnumber});
592
593     $reply = C4::ILSDI::Services::HoldItem( $query );
594     is( $reply->{code}, 'PatronExpired', "Patron is expired" );
595
596     $schema->storage->txn_rollback;
597 };
598
599 subtest 'Holds test for branch transfer limits' => sub {
600
601     plan tests => 6;
602
603     $schema->storage->txn_begin;
604
605     # Test enforement of branch transfer limits
606     t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
607     t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
608
609     my $patron = $builder->build_object({
610         class => 'Koha::Patrons',
611     });
612
613     my $origin_branch = $builder->build(
614         {
615             source => 'Branch',
616             value  => {
617                 pickup_location => 1,
618             }
619         }
620     );
621     my $pickup_branch = $builder->build(
622         {
623             source => 'Branch',
624             value  => {
625                 pickup_location => 1,
626             }
627         }
628     );
629
630     my $item = $builder->build_sample_item(
631         {
632             library => $origin_branch->{branchcode},
633         }
634     );
635
636     Koha::CirculationRules->set_rule(
637         {
638             categorycode => undef,
639             itemtype     => undef,
640             branchcode   => undef,
641             rule_name    => 'reservesallowed',
642             rule_value   => 99,
643         }
644     );
645
646     my $limit = Koha::Item::Transfer::Limit->new({
647         toBranch => $pickup_branch->{branchcode},
648         fromBranch => $item->holdingbranch,
649         itemtype => $item->effective_itemtype,
650     })->store();
651
652     my $query = CGI->new;
653     $query->param( 'pickup_location', $pickup_branch->{branchcode} );
654     $query->param( 'patron_id', $patron->borrowernumber);
655     $query->param( 'bib_id', $item->biblionumber);
656     $query->param( 'item_id', $item->itemnumber);
657
658     my $reply = C4::ILSDI::Services::HoldItem( $query );
659     is( $reply->{code}, 'cannotBeTransferred', "Item hold, Item cannot be transferred" );
660
661     $reply = C4::ILSDI::Services::HoldTitle( $query );
662     is( $reply->{code}, 'cannotBeTransferred', "Record hold, Item cannot be transferred" );
663
664     t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
665
666     $reply = C4::ILSDI::Services::HoldItem( $query );
667     is( $reply->{code}, undef, "Item hold, Item can be transferred" );
668     my $hold = Koha::Holds->search({ itemnumber => $item->itemnumber, borrowernumber => $patron->borrowernumber })->next;
669     is( $hold->branchcode, $pickup_branch->{branchcode}, 'The library id is correctly set' );
670
671     Koha::Holds->search()->delete();
672
673     $reply = C4::ILSDI::Services::HoldTitle( $query );
674     is( $reply->{code}, undef, "Record hold, Item con be transferred" );
675     $hold = Koha::Holds->search({ biblionumber => $item->biblionumber, borrowernumber => $patron->borrowernumber })->next;
676     is( $hold->branchcode, $pickup_branch->{branchcode}, 'The library id is correctly set' );
677
678     $schema->storage->txn_rollback;
679 };
680
681 subtest 'Holds test with start_date and end_date' => sub {
682
683     plan tests => 8;
684
685     $schema->storage->txn_begin;
686
687     my $pickup_library = $builder->build_object(
688         {
689             class  => 'Koha::Libraries',
690             value  => {
691                 pickup_location => 1,
692             }
693         }
694     );
695
696     my $patron = $builder->build_object({
697         class => 'Koha::Patrons',
698     });
699
700     my $item = $builder->build_sample_item({ library => $pickup_library->branchcode });
701
702     Koha::CirculationRules->set_rule(
703         {
704             categorycode => undef,
705             itemtype     => undef,
706             branchcode   => undef,
707             rule_name    => 'reservesallowed',
708             rule_value   => 99,
709         }
710     );
711
712     my $query = CGI->new;
713     $query->param( 'pickup_location', $pickup_library->branchcode );
714     $query->param( 'patron_id', $patron->borrowernumber);
715     $query->param( 'bib_id', $item->biblionumber);
716     $query->param( 'item_id', $item->itemnumber);
717     $query->param( 'start_date', '2020-03-20');
718     $query->param( 'expiry_date', '2020-04-22');
719
720     my $reply = C4::ILSDI::Services::HoldItem( $query );
721     is ($reply->{pickup_location}, $pickup_library->branchname, "Item hold with date parameters was placed");
722     my $hold = Koha::Holds->search({ biblionumber => $item->biblionumber})->next();
723     is( $hold->biblionumber, $item->biblionumber, "correct biblionumber");
724     is( $hold->reservedate, '2020-03-20', "Item hold has correct start date" );
725     is( $hold->expirationdate, '2020-04-22', "Item hold has correct end date" );
726
727     $hold->delete();
728
729     $reply = C4::ILSDI::Services::HoldTitle( $query );
730     is ($reply->{pickup_location}, $pickup_library->branchname, "Record hold with date parameters was placed");
731     $hold = Koha::Holds->search({ biblionumber => $item->biblionumber})->next();
732     is( $hold->biblionumber, $item->biblionumber, "correct biblionumber");
733     is( $hold->reservedate, '2020-03-20', "Record hold has correct start date" );
734     is( $hold->expirationdate, '2020-04-22', "Record hold has correct end date" );
735
736     $schema->storage->txn_rollback;
737 };
738
739 subtest 'GetRecords' => sub {
740
741     plan tests => 8;
742
743     $schema->storage->txn_begin;
744
745     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
746
747     my $branch1 = $builder->build({
748         source => 'Branch',
749     });
750     my $branch2 = $builder->build({
751         source => 'Branch',
752     });
753
754     my $item = $builder->build_sample_item(
755         {
756             library => $branch1->{branchcode},
757         }
758     );
759
760     my $patron = $builder->build({
761         source => 'Borrower',
762     });
763
764     my $issue = $builder->build({
765         source => 'Issue',
766         value => {
767             itemnumber => $item->itemnumber,
768         }
769     });
770
771     my $hold = $builder->build({
772         source => 'Reserve',
773         value => {
774             biblionumber => $item->biblionumber,
775         }
776     });
777
778     ModItemTransfer($item->itemnumber, $branch1->{branchcode}, $branch2->{branchcode}, 'Manual');
779
780     my $cgi = CGI->new;
781     $cgi->param(service => 'GetRecords');
782     $cgi->param(id => $item->biblionumber);
783
784     my $reply = C4::ILSDI::Services::GetRecords($cgi);
785
786     my $transfer = $item->get_transfer;
787     my $expected = {
788         datesent => $transfer->datesent,
789         frombranch => $transfer->frombranch,
790         tobranch => $transfer->tobranch,
791     };
792     is_deeply($reply->{record}->[0]->{items}->{item}->[0]->{transfer}, $expected,
793         'GetRecords returns transfer informations');
794
795     # Check informations exposed
796     my $reply_issue = $reply->{record}->[0]->{issues}->{issue}->[0];
797     is($reply_issue->{itemnumber}, $item->itemnumber, 'GetRecords has an issue tag');
798     is($reply_issue->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in issue tag');
799     is($reply_issue->{surname}, undef, 'GetRecords does not expose surname in issue tag');
800     is($reply_issue->{firstname}, undef, 'GetRecords does not expose firstname in issue tag');
801     is($reply_issue->{cardnumber}, undef, 'GetRecords does not expose cardnumber in issue tag');
802     my $reply_reserve = $reply->{record}->[0]->{reserves}->{reserve}->[0];
803     is($reply_reserve->{biblionumber}, $item->biblionumber, 'GetRecords has a reserve tag');
804     is($reply_reserve->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in reserve tag');
805
806     $schema->storage->txn_rollback;
807 };
808
809 subtest 'RenewHold' => sub {
810     plan tests => 4;
811
812     $schema->storage->txn_begin;
813
814     my $cgi    = CGI->new;
815     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
816     my $item   = $builder->build_sample_item;
817     $cgi->param( patron_id => $patron->borrowernumber );
818     $cgi->param( item_id   => $item->itemnumber );
819
820     t::lib::Mocks::mock_userenv( { patron => $patron } );    # For AddIssue
821     my $checkout = C4::Circulation::AddIssue( $patron, $item->barcode );
822
823     # Everything is ok
824     my $reply = C4::ILSDI::Services::RenewLoan($cgi);
825     is( exists $reply->{date_due}, 1, 'If the item is checked out, the date_due key should exist' );
826
827     # The item is not checked out
828     $checkout->delete;
829     $reply = C4::ILSDI::Services::RenewLoan($cgi);
830     is( $reply, undef, 'If the item is not checked out, we should not explode.');    # FIXME We should return an error code instead
831
832     # The item does not exist
833     $item->delete;
834     $reply = C4::ILSDI::Services::RenewLoan($cgi);
835     is( $reply->{code}, 'RecordNotFound', 'If the item does not exist, RecordNotFound should be returned');
836
837     $patron->delete;
838     $reply = C4::ILSDI::Services::RenewLoan($cgi);
839     is( $reply->{code}, 'PatronNotFound', 'If the patron does not exist, PatronNotFound should be returned');
840
841     $schema->storage->txn_rollback;
842 };
843
844 subtest 'CancelHold' => sub {
845     plan tests => 4;
846
847     $schema->storage->txn_begin;
848
849     my $cgi = CGI->new;
850
851     my $library = $builder->build_object({
852         class => 'Koha::Libraries',
853     });
854
855     my $patron = $builder->build_object( { class => 'Koha::Patrons',
856                                            value => {
857                                                branchcode => $library->branchcode,
858                                            },
859                                          } );
860
861     my $patron2 = $builder->build_object( { class => 'Koha::Patrons',
862                                            value => {
863                                                branchcode => $library->branchcode,
864                                            },
865                                          } );
866
867     my $item = $builder->build_sample_item({ library => $library->branchcode });
868
869     my $reserve = C4::Reserves::AddReserve({branchcode => $library->branchcode,
870                                             borrowernumber => $patron->borrowernumber,
871                                             biblionumber => $item->biblionumber });
872
873     # Affecting the reserve sets it to a waiting state
874     C4::Reserves::ModReserveAffect( $item->itemnumber,
875                                     $patron->borrowernumber,
876                                     undef,
877                                     $reserve,
878                                    );
879
880     $cgi->param( patron_id => $patron2->borrowernumber );
881     $cgi->param( item_id   => $reserve );
882
883
884     my $reply = C4::ILSDI::Services::CancelHold($cgi);
885     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If the patron is wrong, BorrowerCannotCancelHold should be returned');
886
887     $cgi->param( patron_id => $patron->borrowernumber );
888     $reply = C4::ILSDI::Services::CancelHold($cgi);
889     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If reserve in a Waiting state, patron cannot cancel');
890
891     C4::Reserves::ModReserveStatus( $item->itemnumber, 'T' );
892     $reply = C4::ILSDI::Services::CancelHold($cgi);
893     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If reserve in a Transfer state, patron cannot cancel');
894
895     C4::Reserves::ModReserve( {rank => 1, reserve_id => $reserve, branchcode => $library->branchcode} );
896     $cgi->param( item_id => $reserve );
897     $reply = C4::ILSDI::Services::CancelHold($cgi);
898     is( $reply->{code}, 'Canceled', 'If the patron is fine and reserve not waiting, Canceled should be returned and reserve canceled');
899
900     $schema->storage->txn_rollback;
901 };
902
903 subtest 'GetPatronInfo paginated loans' => sub {
904     plan tests => 7;
905
906     $schema->storage->txn_begin;
907
908     my $library = $builder->build_object({
909         class => 'Koha::Libraries',
910     });
911
912     my $item1 = $builder->build_sample_item({ library => $library->branchcode });
913     my $item2 = $builder->build_sample_item({ library => $library->branchcode });
914     my $item3 = $builder->build_sample_item({ library => $library->branchcode });
915     my $patron = $builder->build_object({
916         class => 'Koha::Patrons',
917         value => {
918             branchcode => $library->branchcode,
919         },
920     });
921     my $module = Test::MockModule->new('C4::Context');
922     $module->mock('userenv', sub { { branch => $library->branchcode } });
923     my $date_due = Koha::DateUtils::dt_from_string()->add(weeks => 2);
924     my $issue1 = C4::Circulation::AddIssue($patron, $item1->barcode, $date_due);
925     my $date_due1 = Koha::DateUtils::dt_from_string( $issue1->date_due );
926     my $issue2 = C4::Circulation::AddIssue($patron, $item2->barcode, $date_due);
927     my $date_due2 = Koha::DateUtils::dt_from_string( $issue2->date_due );
928     my $issue3 = C4::Circulation::AddIssue($patron, $item3->barcode, $date_due);
929     my $date_due3 = Koha::DateUtils::dt_from_string( $issue3->date_due );
930
931     my $cgi = CGI->new;
932
933     $cgi->param( 'service', 'GetPatronInfo' );
934     $cgi->param( 'patron_id', $patron->borrowernumber );
935     $cgi->param( 'show_loans', '1' );
936     $cgi->param( 'loans_per_page', '2' );
937     $cgi->param( 'loans_page', '1' );
938     my $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
939
940     is($reply->{total_loans}, 3, 'total_loans == 3');
941     is(scalar @{ $reply->{loans}->{loan} }, 2, 'GetPatronInfo returned only 2 loans');
942     is($reply->{loans}->{loan}->[0]->{itemnumber}, $item3->itemnumber);
943     is($reply->{loans}->{loan}->[1]->{itemnumber}, $item2->itemnumber);
944
945     $cgi->param( 'loans_page', '2' );
946     $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
947
948     is($reply->{total_loans}, 3, 'total_loans == 3');
949     is(scalar @{ $reply->{loans}->{loan} }, 1, 'GetPatronInfo returned only 1 loan');
950     is($reply->{loans}->{loan}->[0]->{itemnumber}, $item1->itemnumber);
951
952     $schema->storage->txn_rollback;
953 };
954
955 subtest 'GetAvailability itemcallnumber' => sub {
956
957     plan tests => 4;
958
959     $schema->storage->txn_begin;
960
961     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
962
963     my $item1 = $builder->build_sample_item(
964         {
965             itemcallnumber => "callnumber",
966         }
967     );
968
969     my $item2 = $builder->build_sample_item( {} );
970
971     # Build the query
972     my $cgi = CGI->new;
973     $cgi->param( service => 'GetAvailability' );
974     $cgi->param( id      => $item1->itemnumber );
975     $cgi->param( id_type => 'item' );
976
977     # Output of GetAvailability is a string containing XML
978     my $reply = C4::ILSDI::Services::GetAvailability($cgi);
979
980     # Parse the output and get info
981     my $result_XML = XML::LibXML->load_xml( string => $reply );
982     my $reply_callnumber =
983       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
984
985     # Test the output
986     is( $reply_callnumber, $item1->itemcallnumber,
987         "GetAvailability item has an itemcallnumber tag" );
988
989     $cgi = CGI->new;
990     $cgi->param( service => 'GetAvailability' );
991     $cgi->param( id      => $item2->itemnumber );
992     $cgi->param( id_type => 'item' );
993     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
994     $result_XML = XML::LibXML->load_xml( string => $reply );
995     $reply_callnumber =
996       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
997     is( $reply_callnumber, '',
998         "As expected, GetAvailability item has no itemcallnumber tag" );
999
1000     $cgi = CGI->new;
1001     $cgi->param( service => 'GetAvailability' );
1002     $cgi->param( id      => $item1->biblionumber );
1003     $cgi->param( id_type => 'biblio' );
1004     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
1005     $result_XML = XML::LibXML->load_xml( string => $reply );
1006     $reply_callnumber =
1007       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
1008     is( $reply_callnumber, $item1->itemcallnumber,
1009         "GetAvailability biblio has an itemcallnumber tag" );
1010
1011     $cgi = CGI->new;
1012     $cgi->param( service => 'GetAvailability' );
1013     $cgi->param( id      => $item2->biblionumber );
1014     $cgi->param( id_type => 'biblio' );
1015     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
1016     $result_XML = XML::LibXML->load_xml( string => $reply );
1017     $reply_callnumber =
1018       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
1019     is( $reply_callnumber, '',
1020         "As expected, GetAvailability biblio has no itemcallnumber tag" );
1021
1022     $schema->storage->txn_rollback;
1023 };