3 # This file is part of Koha.
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.
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.
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>.
22 use Test::More tests => 13;
25 use t::lib::TestBuilder;
29 use C4::Items qw( ModItemTransfer );
30 use C4::Circulation qw( AddIssue );
31 use C4::Reserves qw (AddReserve ModReserve ModReserveAffect ModReserveStatus);
34 use Koha::DateUtils qw( dt_from_string );
37 use_ok('C4::ILSDI::Services', qw( AuthenticatePatron GetPatronInfo LookupPatron HoldTitle HoldItem GetRecords RenewLoan GetAvailability ));
40 my $schema = Koha::Database->schema;
41 my $dbh = C4::Context->dbh;
42 my $builder = t::lib::TestBuilder->new;
44 subtest 'AuthenticatePatron test' => sub {
48 $schema->storage->txn_begin;
51 my $plain_password = 'tomasito';
60 my $borrower = $builder->build({
64 password => Koha::AuthUtils::hash_password( $plain_password ),
65 lastseen => "2001-01-01 12:34:56"
70 $query->param( 'username', $borrower->{userid});
71 $query->param( 'password', $plain_password);
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');
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");
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");
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' );
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" );
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" );
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");
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");
123 $schema->storage->txn_rollback;
126 subtest 'GetPatronInfo test for holds' => sub {
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;
137 # Configure Koha to enable ILS-DI server
138 t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
140 my $library = $builder->build_object({
141 class => 'Koha::Libraries',
145 my $brwr = $builder->build_object( {
146 class => 'Koha::Patrons',
148 branchcode => $library->branchcode,
151 my $brwr2 = $builder->build_object( {
152 class => 'Koha::Patrons',
154 branchcode => $library->branchcode,
157 my $brwr3 = $builder->build_object( {
158 class => 'Koha::Patrons',
160 branchcode => $library->branchcode,
164 my $module = Test::MockModule->new('C4::Context');
165 $module->mock('userenv', sub { { branch => $library->branchcode } });
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);
174 # Prepare and send web request for IL-SDI server:
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 );
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");
186 # Add a hold on the biblio
187 my $biblioreserve = AddReserve({ branchcode => $library->branchcode, borrowernumber => $brwr2->borrowernumber, biblionumber => $biblio->biblionumber });
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");
195 $schema->resultset( 'Reserve' )->delete_all;
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
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");
210 # Add another hold on the biblio
211 $biblioreserve = AddReserve({ branchcode => $library->branchcode, borrowernumber => $brwr3->borrowernumber, biblionumber => $biblio->biblionumber });
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");
219 $schema->storage->txn_rollback;
223 subtest 'GetPatronInfo/GetBorrowerAttributes test for extended patron attributes' => sub {
227 $schema->storage->txn_begin;
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;
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 );
242 # Set up a library/branch for our user to belong to:
243 my $lib = $builder->build( {
246 branchcode => 'T_ILSDI',
250 # Create a new category for user to belong to:
251 my $cat = $builder->build( {
252 source => 'Category',
254 category_type => 'A',
255 BlockExpiredPatronOpacActions => -1,
259 # Create a new attribute type:
260 my $attr_type = $builder->build( {
261 source => 'BorrowerAttributeType',
265 authorised_value_category => '',
269 my $attr_type_visible = $builder->build( {
270 source => 'BorrowerAttributeType',
274 authorised_value_category => '',
280 my $brwr = $builder->build( {
281 source => 'Borrower',
283 categorycode => $cat->{'categorycode'},
284 branchcode => $lib->{'branchcode'},
289 my $auth = $builder->build( {
290 source => 'AuthorisedValue',
292 category => $cat->{'categorycode'}
296 # Set the new attribute for our user:
297 my $attr_hidden = $builder->build( {
298 source => 'BorrowerAttribute',
300 borrowernumber => $brwr->{'borrowernumber'},
301 code => $attr_type->{'code'},
302 attribute => '1337 hidden',
305 my $attr_shown = $builder->build( {
306 source => 'BorrowerAttribute',
308 borrowernumber => $brwr->{'borrowernumber'},
309 code => $attr_type_visible->{'code'},
310 attribute => '1337 shown',
314 my $fine = $builder->build(
316 source => 'Accountline',
318 borrowernumber => $brwr->{borrowernumber},
319 debit_type_code => 'OVERDUE',
320 amountoutstanding => 10
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' );
332 my $reply = C4::ILSDI::Services::GetPatronInfo( $query );
334 # Build a structure for comparison:
336 borrowernumber => $brwr->{borrowernumber},
337 value => $attr_shown->{'attribute'},
338 value_description => $attr_shown->{'attribute'},
343 is( $reply->{'charges'}, '10.00',
344 'The \'charges\' attribute should be correctly filled (bug 17836)' );
346 is( scalar( @{$reply->{fines}->{fine}}), 1, 'There should be only 1 account line');
348 $reply->{fines}->{fine}->[0]->{accountlines_id},
349 $fine->{accountlines_id},
350 "The accountline should be the correct one"
354 is_deeply( $reply->{'attributes'}, [ $cmp ], 'Test GetPatronInfo - show_attributes parameter' );
356 ok( exists $reply->{is_expired}, 'There should be the is_expired information');
359 $schema->storage->txn_rollback;
362 subtest 'LookupPatron test' => sub {
366 $schema->storage->txn_begin;
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;
376 my $borrower = $builder->build({
377 source => 'Borrower',
380 my $query = CGI->new();
381 my $bad_result = C4::ILSDI::Services::LookupPatron($query);
382 is( $bad_result->{message}, 'PatronNotFound', 'No parameters' );
384 $query->delete_all();
385 $query->param( 'id', $borrower->{firstname} );
386 my $optional_result = C4::ILSDI::Services::LookupPatron($query);
388 $optional_result->{id},
389 $borrower->{borrowernumber},
390 'Valid Firstname only'
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' );
398 foreach my $id_type (
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" );
414 $schema->storage->txn_rollback;
417 subtest 'Holds test' => sub {
421 $schema->storage->txn_begin;
423 t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 0 );
425 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
427 my $item = $builder->build_sample_item(
433 my $query = CGI->new;
434 $query->param( 'patron_id', $patron->borrowernumber);
435 $query->param( 'bib_id', $item->biblionumber);
437 my $reply = C4::ILSDI::Services::HoldTitle( $query );
438 is( $reply->{code}, 'damaged', "Item damaged" );
440 $item->damaged(0)->store;
442 my $hold = $builder->build({
445 borrowernumber => $patron->borrowernumber,
446 biblionumber => $item->biblionumber,
447 itemnumber => $item->itemnumber
451 $reply = C4::ILSDI::Services::HoldTitle( $query );
452 is( $reply->{code}, 'itemAlreadyOnHold', "Item already on hold" );
454 my $biblio_with_no_item = $builder->build_sample_biblio;
457 $query->param( 'patron_id', $patron->borrowernumber);
458 $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
460 $reply = C4::ILSDI::Services::HoldTitle( $query );
461 is( $reply->{code}, 'NoItems', 'Biblio has no item' );
463 my $item2 = $builder->build_sample_item(
469 t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
470 Koha::CirculationRules->set_rule(
472 categorycode => $patron->categorycode,
473 itemtype => $item2->{itype},
474 branchcode => $patron->branchcode,
475 rule_name => 'reservesallowed',
481 $query->param( 'patron_id', $patron->borrowernumber);
482 $query->param( 'bib_id', $item2->biblionumber);
483 $query->param( 'item_id', $item2->itemnumber);
485 $reply = C4::ILSDI::Services::HoldItem( $query );
486 is( $reply->{code}, 'tooManyReserves', "Too many reserves" );
488 Koha::CirculationRules->set_rule(
490 categorycode => $patron->categorycode,
491 itemtype => $item2->{itype},
492 branchcode => $patron->branchcode,
493 rule_name => 'reservesallowed',
499 $query->param( 'patron_id', $patron->borrowernumber);
500 $query->param( 'bib_id', $item2->biblionumber);
501 $query->param( 'item_id', $item2->itemnumber);
503 $reply = C4::ILSDI::Services::HoldItem( $query );
504 is( $reply->{code}, 'noReservesAllowed', "No reserves allowed" );
506 my $origin_branch = $builder->build(
510 pickup_location => 1,
515 # Adding a holdable item.
516 my $item3 = $builder->build_sample_item(
518 barcode => '123456789',
519 library => $origin_branch->{branchcode}
522 my $item4 = $builder->build_sample_item(
524 biblionumber => $item3->biblionumber,
526 library => $origin_branch->{branchcode}
529 Koha::CirculationRules->set_rule(
531 categorycode => $patron->categorycode,
532 itemtype => $item3->{itype},
533 branchcode => $patron->branchcode,
534 rule_name => 'reservesallowed',
540 $query->param( 'patron_id', $patron->borrowernumber);
541 $query->param( 'bib_id', $item4->biblionumber);
542 $query->param( 'item_id', $item4->itemnumber);
544 $reply = C4::ILSDI::Services::HoldItem( $query );
545 is( $reply->{code}, 'damaged', "Item is damaged" );
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' );
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 );
559 is( $reply->{code}, 'alreadypossession', "Patron has issued same book" );
560 is( $reply->{pickup_location}, undef, "No reserve placed");
562 # Test Patron cannot reserve if expired and BlockExpiredPatronOpacActions
563 my $category = $builder->build({
564 source => 'Category',
565 value => { BlockExpiredPatronOpacActions => -1 }
568 my $branch_1 = $builder->build({ source => 'Branch' })->{ branchcode };
570 my $expired_borrowernumber = Koha::Patron->new({
571 firstname => 'Expired',
573 categorycode => $category->{categorycode},
574 branchcode => $branch_1,
575 dateexpiry => '2000-01-01',
576 })->store->borrowernumber;
578 t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
580 my $item5 = $builder->build({
583 biblionumber => $biblio_with_no_item->biblionumber,
589 $query->param( 'patron_id', $expired_borrowernumber);
590 $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
591 $query->param( 'item_id', $item5->{itemnumber});
593 $reply = C4::ILSDI::Services::HoldItem( $query );
594 is( $reply->{code}, 'PatronExpired', "Patron is expired" );
596 $schema->storage->txn_rollback;
599 subtest 'Holds test for branch transfer limits' => sub {
603 $schema->storage->txn_begin;
605 # Test enforement of branch transfer limits
606 t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
607 t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
609 my $patron = $builder->build_object({
610 class => 'Koha::Patrons',
613 my $origin_branch = $builder->build(
617 pickup_location => 1,
621 my $pickup_branch = $builder->build(
625 pickup_location => 1,
630 my $item = $builder->build_sample_item(
632 library => $origin_branch->{branchcode},
636 Koha::CirculationRules->set_rule(
638 categorycode => undef,
641 rule_name => 'reservesallowed',
646 my $limit = Koha::Item::Transfer::Limit->new({
647 toBranch => $pickup_branch->{branchcode},
648 fromBranch => $item->holdingbranch,
649 itemtype => $item->effective_itemtype,
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);
658 my $reply = C4::ILSDI::Services::HoldItem( $query );
659 is( $reply->{code}, 'cannotBeTransferred', "Item hold, Item cannot be transferred" );
661 $reply = C4::ILSDI::Services::HoldTitle( $query );
662 is( $reply->{code}, 'cannotBeTransferred', "Record hold, Item cannot be transferred" );
664 t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
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' );
671 Koha::Holds->search()->delete();
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' );
678 $schema->storage->txn_rollback;
681 subtest 'Holds test with start_date and end_date' => sub {
685 $schema->storage->txn_begin;
687 my $pickup_library = $builder->build_object(
689 class => 'Koha::Libraries',
691 pickup_location => 1,
696 my $patron = $builder->build_object({
697 class => 'Koha::Patrons',
700 my $item = $builder->build_sample_item({ library => $pickup_library->branchcode });
702 Koha::CirculationRules->set_rule(
704 categorycode => undef,
707 rule_name => 'reservesallowed',
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');
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" );
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" );
736 $schema->storage->txn_rollback;
739 subtest 'GetRecords' => sub {
743 $schema->storage->txn_begin;
745 t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
747 my $branch1 = $builder->build({
750 my $branch2 = $builder->build({
754 my $item = $builder->build_sample_item(
756 library => $branch1->{branchcode},
760 my $patron = $builder->build({
761 source => 'Borrower',
764 my $issue = $builder->build({
767 itemnumber => $item->itemnumber,
771 my $hold = $builder->build({
774 biblionumber => $item->biblionumber,
778 ModItemTransfer($item->itemnumber, $branch1->{branchcode}, $branch2->{branchcode}, 'Manual');
781 $cgi->param(service => 'GetRecords');
782 $cgi->param(id => $item->biblionumber);
784 my $reply = C4::ILSDI::Services::GetRecords($cgi);
786 my $transfer = $item->get_transfer;
788 datesent => $transfer->datesent,
789 frombranch => $transfer->frombranch,
790 tobranch => $transfer->tobranch,
792 is_deeply($reply->{record}->[0]->{items}->{item}->[0]->{transfer}, $expected,
793 'GetRecords returns transfer informations');
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');
806 $schema->storage->txn_rollback;
809 subtest 'RenewHold' => sub {
812 $schema->storage->txn_begin;
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 );
820 t::lib::Mocks::mock_userenv( { patron => $patron } ); # For AddIssue
821 my $checkout = C4::Circulation::AddIssue( $patron, $item->barcode );
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' );
827 # The item is not checked out
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
832 # The item does not exist
834 $reply = C4::ILSDI::Services::RenewLoan($cgi);
835 is( $reply->{code}, 'RecordNotFound', 'If the item does not exist, RecordNotFound should be returned');
838 $reply = C4::ILSDI::Services::RenewLoan($cgi);
839 is( $reply->{code}, 'PatronNotFound', 'If the patron does not exist, PatronNotFound should be returned');
841 $schema->storage->txn_rollback;
844 subtest 'CancelHold' => sub {
847 $schema->storage->txn_begin;
851 my $library = $builder->build_object({
852 class => 'Koha::Libraries',
855 my $patron = $builder->build_object( { class => 'Koha::Patrons',
857 branchcode => $library->branchcode,
861 my $patron2 = $builder->build_object( { class => 'Koha::Patrons',
863 branchcode => $library->branchcode,
867 my $item = $builder->build_sample_item({ library => $library->branchcode });
869 my $reserve = C4::Reserves::AddReserve({branchcode => $library->branchcode,
870 borrowernumber => $patron->borrowernumber,
871 biblionumber => $item->biblionumber });
873 # Affecting the reserve sets it to a waiting state
874 C4::Reserves::ModReserveAffect( $item->itemnumber,
875 $patron->borrowernumber,
880 $cgi->param( patron_id => $patron2->borrowernumber );
881 $cgi->param( item_id => $reserve );
884 my $reply = C4::ILSDI::Services::CancelHold($cgi);
885 is( $reply->{code}, 'BorrowerCannotCancelHold', 'If the patron is wrong, BorrowerCannotCancelHold should be returned');
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');
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');
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');
900 $schema->storage->txn_rollback;
903 subtest 'GetPatronInfo paginated loans' => sub {
906 $schema->storage->txn_begin;
908 my $library = $builder->build_object({
909 class => 'Koha::Libraries',
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',
918 branchcode => $library->branchcode,
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 );
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);
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);
945 $cgi->param( 'loans_page', '2' );
946 $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
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);
952 $schema->storage->txn_rollback;
955 subtest 'GetAvailability itemcallnumber' => sub {
959 $schema->storage->txn_begin;
961 t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
963 my $item1 = $builder->build_sample_item(
965 itemcallnumber => "callnumber",
969 my $item2 = $builder->build_sample_item( {} );
973 $cgi->param( service => 'GetAvailability' );
974 $cgi->param( id => $item1->itemnumber );
975 $cgi->param( id_type => 'item' );
977 # Output of GetAvailability is a string containing XML
978 my $reply = C4::ILSDI::Services::GetAvailability($cgi);
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();
986 is( $reply_callnumber, $item1->itemcallnumber,
987 "GetAvailability item has an itemcallnumber tag" );
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 );
996 $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
997 is( $reply_callnumber, '',
998 "As expected, GetAvailability item has no itemcallnumber tag" );
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 );
1007 $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
1008 is( $reply_callnumber, $item1->itemcallnumber,
1009 "GetAvailability biblio has an itemcallnumber tag" );
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 );
1018 $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
1019 is( $reply_callnumber, '',
1020 "As expected, GetAvailability biblio has no itemcallnumber tag" );
1022 $schema->storage->txn_rollback;