3 # Tests for SIP::ILS::Transaction
4 # Current state is very rudimentary. Please help to extend it!
7 use Test::More tests => 12;
10 use t::lib::TestBuilder;
13 use C4::SIP::ILS::Patron;
14 use C4::SIP::ILS::Transaction::RenewAll;
15 use C4::SIP::ILS::Transaction::Checkout;
16 use C4::SIP::ILS::Transaction::FeePayment;
17 use C4::SIP::ILS::Transaction::Hold;
18 use C4::SIP::ILS::Transaction::Checkout;
19 use C4::SIP::ILS::Transaction::Checkin;
22 use Koha::CirculationRules;
23 use Koha::Item::Transfer;
24 use Koha::DateUtils qw( dt_from_string output_pref );
26 my $schema = Koha::Database->new->schema;
27 $schema->storage->txn_begin;
29 my $builder = t::lib::TestBuilder->new();
30 my $borr1 = $builder->build({ source => 'Borrower' });
31 my $card = $borr1->{cardnumber};
32 my $sip_patron = C4::SIP::ILS::Patron->new( $card );
34 # Create transaction RenewAll, assign patron, and run (no items)
35 my $transaction = C4::SIP::ILS::Transaction::RenewAll->new();
36 is( ref $transaction, "C4::SIP::ILS::Transaction::RenewAll", "New transaction created" );
37 is( $transaction->patron( $sip_patron ), $sip_patron, "Patron assigned to transaction" );
38 isnt( $transaction->do_renew_all, undef, "RenewAll on zero items" );
40 subtest fill_holds_at_checkout => sub {
44 my $category = $builder->build({ source => 'Category', value => { category_type => 'A' }});
45 my $branch = $builder->build({ source => 'Branch' });
46 my $borrower = $builder->build({ source => 'Borrower', value =>{
47 branchcode => $branch->{branchcode},
48 categorycode=>$category->{categorycode}
51 t::lib::Mocks::mock_userenv({ branchcode => $branch->{branchcode}, flags => 1 });
53 my $itype = $builder->build({ source => 'Itemtype', value =>{notforloan=>0} });
54 my $item1 = $builder->build_sample_item({
55 barcode => 'barcode4test',
56 homebranch => $branch->{branchcode},
57 holdingbranch => $branch->{branchcode},
58 itype => $itype->{itemtype},
61 my $item2 = $builder->build_sample_item({
62 homebranch => $branch->{branchcode},
63 holdingbranch => $branch->{branchcode},
64 biblionumber => $item1->{biblionumber},
65 itype => $itype->{itemtype},
69 Koha::CirculationRules->set_rules(
71 categorycode => $borrower->{categorycode},
72 branchcode => $branch->{branchcode},
73 itemtype => $itype->{itemtype},
77 holds_per_record => 3,
84 my $reserve1 = AddReserve(
86 branchcode => $branch->{branchcode},
87 borrowernumber => $borrower->{borrowernumber},
88 biblionumber => $item1->{biblionumber}
91 my $reserve2 = AddReserve(
93 branchcode => $branch->{branchcode},
94 borrowernumber => $borrower->{borrowernumber},
95 biblionumber => $item1->{biblionumber}
99 my $bib = Koha::Biblios->find( $item1->{biblionumber} );
100 is( $bib->holds->count(), 2, "Bib has 2 holds");
102 my $sip_patron = C4::SIP::ILS::Patron->new( $borrower->{cardnumber} );
103 my $sip_item = C4::SIP::ILS::Item->new( $item1->{barcode} );
104 my $transaction = C4::SIP::ILS::Transaction::Checkout->new();
105 is( ref $transaction, "C4::SIP::ILS::Transaction::Checkout", "New transaction created" );
106 is( $transaction->patron( $sip_patron ), $sip_patron, "Patron assigned to transaction" );
107 is( $transaction->item( $sip_item ), $sip_item, "Item assigned to transaction" );
108 my $checkout = $transaction->do_checkout();
109 use Data::Dumper; # Temporary debug statement
110 is( $bib->holds->count(), 1, "Bib has 1 holds remaining") or diag Dumper $checkout;
112 t::lib::Mocks::mock_preference('itemBarcodeInputFilter', 'whitespace');
113 $sip_item = C4::SIP::ILS::Item->new( ' barcode 4 test ');
114 $transaction = C4::SIP::ILS::Transaction::Checkout->new();
115 is( $sip_item->{barcode}, $item1->{barcode}, "Item assigned to transaction" );
118 subtest "FeePayment->pay tests" => sub {
122 # Create a borrower and add some outstanding debts to their account
123 my $patron = $builder->build( { source => 'Borrower' } );
125 Koha::Account->new( { patron_id => $patron->{borrowernumber} } );
126 my $debt1 = $account->add_debit(
127 { type => 'ACCOUNT', amount => 100, interface => 'commandline' } );
128 my $debt2 = $account->add_debit(
129 { type => 'ACCOUNT', amount => 200, interface => 'commandline' } );
131 # Instantiate a new FeePayment transaction object
132 my $trans = C4::SIP::ILS::Transaction::FeePayment->new();
135 "C4::SIP::ILS::Transaction::FeePayment",
136 "New fee transaction created"
139 # Test the 'pay' method
140 # FIXME: pay should not require a borrowernumber
141 # (we should reach out to the transaction which should contain a patron object)
142 my $pay_type = '00'; # 00 - Cash, 01 - VISA, 02 - Creditcard
144 $trans->pay( $patron->{borrowernumber}, 100, $pay_type, $debt1->id, 0,
146 ok( $ok, "FeePayment transaction succeeded" );
147 $debt1->discard_changes;
148 is( $debt1->amountoutstanding + 0, 0,
149 "Debt1 was reduced to 0 as expected" );
150 my $offsets = Koha::Account::Offsets->search(
151 { debit_id => $debt1->id, credit_id => { '!=' => undef } } );
152 is( $offsets->count, 1, "FeePayment produced an offset line correctly" );
153 my $credit = $offsets->next->credit;
154 is( $credit->payment_type, 'SIP00', "Payment type was set correctly" );
157 subtest cancel_hold => sub {
160 my $library = $builder->build_object ({ class => 'Koha::Libraries' });
161 my $patron = $builder->build_object(
163 class => 'Koha::Patrons',
165 branchcode => $library->branchcode,
169 t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode, flags => 1 });
171 my $item = $builder->build_sample_item({
172 library => $library->branchcode,
175 Koha::CirculationRules->set_rules(
177 categorycode => $patron->categorycode,
178 branchcode => $library->branchcode,
179 itemtype => $item->effective_itemtype,
182 reservesallowed => 3,
183 holds_per_record => 3,
185 lengthunit => 'days',
190 my $reserve1 = AddReserve(
192 branchcode => $library->branchcode,
193 borrowernumber => $patron->borrowernumber,
194 biblionumber => $item->biblio->biblionumber,
195 itemnumber => $item->itemnumber,
198 is( $item->biblio->holds->count(), 1, "Hold was placed on bib");
199 is( $item->holds->count(),1,"Hold was placed on specific item");
201 my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber );
202 my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
203 my $transaction = C4::SIP::ILS::Transaction::Hold->new();
204 is( ref $transaction, "C4::SIP::ILS::Transaction::Hold", "New transaction created" );
205 is( $transaction->patron( $sip_patron ), $sip_patron, "Patron assigned to transaction" );
206 is( $transaction->item( $sip_item ), $sip_item, "Item assigned to transaction" );
207 my $hold = $transaction->drop_hold();
208 is( $item->biblio->holds->count(), 0, "Bib has 0 holds remaining");
209 is( $item->holds->count(), 0, "Item has 0 holds remaining");
212 subtest do_hold => sub {
215 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
216 my $patron_1 = $builder->build_object(
218 class => 'Koha::Patrons',
220 branchcode => $library->branchcode,
224 my $patron_2 = $builder->build_object(
226 class => 'Koha::Patrons',
228 branchcode => $library->branchcode,
229 categorycode => $patron_1->categorycode,
234 t::lib::Mocks::mock_userenv(
235 { branchcode => $library->branchcode, flags => 1 } );
237 my $item = $builder->build_sample_item(
239 library => $library->branchcode,
243 my $reserve1 = AddReserve(
245 branchcode => $library->branchcode,
246 borrowernumber => $patron_1->borrowernumber,
247 biblionumber => $item->biblio->biblionumber,
248 itemnumber => $item->itemnumber,
251 is( $item->biblio->holds->count(), 1, "Hold was placed on bib" );
252 is( $item->holds->count(), 1, "Hold was placed on specific item" );
254 my $sip_patron = C4::SIP::ILS::Patron->new( $patron_2->cardnumber );
255 my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
256 my $transaction = C4::SIP::ILS::Transaction::Hold->new();
259 "C4::SIP::ILS::Transaction::Hold",
260 "New transaction created"
262 is( $transaction->patron($sip_patron),
263 $sip_patron, "Patron assigned to transaction" );
264 is( $transaction->item($sip_item),
265 $sip_item, "Item assigned to transaction" );
266 my $hold = $transaction->do_hold();
267 is( $item->biblio->holds->count(), 2, "Bib has 2 holds" );
269 my $THE_hold = $patron_2->holds->next;
270 is( $THE_hold->priority, 2, 'Hold placed from SIP should have a correct priority of 2');
271 is( $THE_hold->branchcode, $patron_2->branchcode, 'Hold placed from SIP should have the branchcode set' );
274 subtest do_checkin => sub {
277 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
278 my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
279 my $patron = $builder->build_object(
281 class => 'Koha::Patrons',
283 branchcode => $library->branchcode,
288 t::lib::Mocks::mock_userenv(
289 { branchcode => $library->branchcode, flags => 1 } );
291 my $item = $builder->build_sample_item(
293 library => $library->branchcode,
299 my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber );
300 my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
301 my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new();
302 is( $co_transaction->patron($sip_patron),
303 $sip_patron, "Patron assigned to transaction" );
304 is( $co_transaction->item($sip_item),
305 $sip_item, "Item assigned to transaction" );
306 my $checkout = $co_transaction->do_checkout();
307 is( $patron->checkouts->count, 1, 'Checkout should have been done successfully');
310 my $ci_transaction = C4::SIP::ILS::Transaction::Checkin->new();
311 is( $ci_transaction->patron($sip_patron),
312 $sip_patron, "Patron assigned to transaction" );
313 is( $ci_transaction->item($sip_item),
314 $sip_item, "Item assigned to transaction" );
316 my $checkin = $ci_transaction->do_checkin($library->branchcode, C4::SIP::Sip::timestamp);
317 is( $patron->checkouts->count, 0, 'Checkin should have been done successfully');
319 # Test checkin without return date
320 $co_transaction->do_checkout;
321 is( $patron->checkouts->count, 1, 'Checkout should have been done successfully');
322 $ci_transaction->do_checkin($library->branchcode, undef);
323 is( $patron->checkouts->count, 0, 'Checkin should have been done successfully');
325 my $result = $ci_transaction->do_checkin($library2->branchcode, undef);
326 is($ci_transaction->alert_type,'04',"Checkin of item no issued at another branch succeeds");
327 is_deeply($result,{ messages => { 'NotIssued' => $item->barcode, 'WasTransfered' => 1 } },"Messages show not issued and transferred");
328 is( $ci_transaction->item->destination_loc,$library->branchcode,"Item destination correctly set");
330 subtest 'Checkin an in transit item' => sub {
334 my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
335 my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
337 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {branchcode => $library_1->branchcode, }});
338 my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber );
339 my $item = $builder->build_sample_item({ library => $library_1->branchcode });
340 my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
342 t::lib::Mocks::mock_userenv(
343 { branchcode => $library_1->branchcode, flags => 1 } );
345 my $reserve = AddReserve(
347 branchcode => $library_1->branchcode,
348 borrowernumber => $patron->borrowernumber,
349 biblionumber => $item->biblionumber,
353 ModReserveAffect( $item->itemnumber, $patron->borrowernumber ); # Mark waiting
355 my $ci_transaction = C4::SIP::ILS::Transaction::Checkin->new();
356 is( $ci_transaction->patron($sip_patron),
357 $sip_patron, "Patron assigned to transaction" );
358 is( $ci_transaction->item($sip_item),
359 $sip_item, "Item assigned to transaction" );
361 my $checkin = $ci_transaction->do_checkin($library_2->branchcode, C4::SIP::Sip::timestamp);
363 my $hold = Koha::Holds->find($reserve);
364 is( $hold->found, 'T', );
365 is( $hold->itemnumber, $item->itemnumber, );
366 is( Koha::Checkouts->search({itemnumber => $item->itemnumber})->count, 0, );
370 subtest do_checkout_with_holds => sub {
373 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
374 my $patron = $builder->build_object(
376 class => 'Koha::Patrons',
378 branchcode => $library->branchcode,
382 my $patron2 = $builder->build_object(
384 class => 'Koha::Patrons',
386 branchcode => $library->branchcode,
391 t::lib::Mocks::mock_userenv(
392 { branchcode => $library->branchcode, flags => 1 } );
394 my $item = $builder->build_sample_item(
396 library => $library->branchcode,
400 my $reserve = AddReserve(
402 branchcode => $library->branchcode,
403 borrowernumber => $patron2->borrowernumber,
404 biblionumber => $item->biblionumber,
408 my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber );
409 my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
410 my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new();
411 is( $co_transaction->patron($sip_patron),
412 $sip_patron, "Patron assigned to transaction" );
413 is( $co_transaction->item($sip_item),
414 $sip_item, "Item assigned to transaction" );
416 # Test attached holds
417 ModReserveAffect( $item->itemnumber, $patron->borrowernumber, 0, $reserve ); # Mark waiting (W)
418 my $hold = Koha::Holds->find($reserve);
419 $co_transaction->do_checkout();
420 is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (W)');
423 $co_transaction->do_checkout();
424 is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (T)');
426 $hold->set_processing;
427 $co_transaction->do_checkout();
428 is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (P)');
430 # Test non-attached holds
431 C4::Reserves::RevertWaitingStatus({ itemnumber => $hold->itemnumber });
432 t::lib::Mocks::mock_preference('AllowItemsOnHoldCheckoutSIP', '0');
433 $co_transaction->do_checkout();
434 is( $patron->checkouts->count, 0, 'Checkout refused due to hold and AllowItemsOnHoldCheckoutSIP');
436 t::lib::Mocks::mock_preference('AllowItemsOnHoldCheckoutSIP', '1');
437 $co_transaction->do_checkout();
438 is( $patron->checkouts->count, 1, 'Checkout allowed due to hold and AllowItemsOnHoldCheckoutSIP');
441 subtest checkin_lost => sub {
444 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
446 t::lib::Mocks::mock_userenv(
447 { branchcode => $library->branchcode, flags => 1 } );
449 my $item = $builder->build_sample_item(
451 library => $library->branchcode,
455 $item->itemlost(1)->itemlost_on(dt_from_string)->store();
459 implementation => "ILS",
468 my $ils = C4::SIP::ILS->new( $instituation );
470 t::lib::Mocks::mock_preference('BlockReturnOfLostItems', '1');
471 my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
472 is( $circ->{screen_msg}, 'Item lost, return not allowed', "Got correct screen message" );
474 t::lib::Mocks::mock_preference('BlockReturnOfLostItems', '0');
475 $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
476 is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" );
479 subtest checkin_withdrawn => sub {
482 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
484 t::lib::Mocks::mock_userenv(
485 { branchcode => $library->branchcode, flags => 1 } );
487 my $item = $builder->build_sample_item(
489 library => $library->branchcode,
493 $item->withdrawn(1)->withdrawn_on(dt_from_string)->store();
497 implementation => "ILS",
506 my $ils = C4::SIP::ILS->new( $instituation );
508 t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', '1');
509 my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
510 is( $circ->{screen_msg}, 'Item withdrawn, return not allowed', "Got correct screen message" );
512 t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', '0');
513 $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
514 is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" );
517 subtest item_circulation_status => sub {
520 my $library = $builder->build_object( { class => 'Koha::Libraries' } );
521 my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
523 my $patron = $builder->build_object(
525 class => 'Koha::Patrons',
527 branchcode => $library->branchcode,
532 t::lib::Mocks::mock_userenv(
533 { branchcode => $library->branchcode, flags => 1 } );
535 my $item = $builder->build_sample_item(
537 library => $library->branchcode,
541 my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
542 my $status = $sip_item->sip_circulation_status;
543 is( $status, '03', "Item circulation status is available");
545 my $transfer = Koha::Item::Transfer->new({
546 itemnumber => $item->id,
547 datesent => '2020-01-01',
548 frombranch => $library->branchcode,
549 tobranch => $library2->branchcode,
552 $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
553 $status = $sip_item->sip_circulation_status;
554 is( $status, '10', "Item circulation status is in transit" );
558 my $claim = Koha::Checkouts::ReturnClaim->new({
559 itemnumber => $item->id,
560 borrowernumber => $patron->id,
561 created_by => $patron->id,
564 $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
565 $status = $sip_item->sip_circulation_status;
566 is( $status, '11', "Item circulation status is claimed returned" );
570 $item->itemlost(1)->store();
571 $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
572 $status = $sip_item->sip_circulation_status;
573 is( $status, '12', "Item circulation status is lost" );
574 $item->itemlost(0)->store();
576 my $location = $item->location;
577 $item->location("CART")->store();
578 $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
579 $status = $sip_item->sip_circulation_status;
580 is( $status, '09', "Item circulation status is waiting to be re-shelved" );
581 $item->location($location)->store();
583 my $nfl = $item->notforloan;
584 $item->notforloan(-1)->store();
585 $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
586 $status = $sip_item->sip_circulation_status;
587 is( $status, '02', "Item circulation status is on order" );
588 $item->notforloan($nfl)->store();
590 $schema->storage->txn_rollback;