#!/usr/bin/perl # Tests for SIP::ILS::Transaction # Current state is very rudimentary. Please help to extend it! use Modern::Perl; use Test::More tests => 19; use Test::Warn; use Koha::Database; use t::lib::TestBuilder; use t::lib::Mocks; use C4::SIP::ILS; use C4::SIP::ILS::Patron; use C4::SIP::ILS::Transaction::RenewAll; use C4::SIP::ILS::Transaction::Checkout; use C4::SIP::ILS::Transaction::FeePayment; use C4::SIP::ILS::Transaction::Hold; use C4::SIP::ILS::Transaction::Checkout; use C4::SIP::ILS::Transaction::Checkin; use C4::Reserves qw( AddReserve ModReserve ModReserveAffect RevertWaitingStatus ); use Koha::CirculationRules; use Koha::Item::Transfer; use Koha::DateUtils qw( dt_from_string output_pref ); use Koha::Recalls; my $schema = Koha::Database->new->schema; $schema->storage->txn_begin; my $builder = t::lib::TestBuilder->new(); my $borr1 = $builder->build( { source => 'Borrower' } ); my $card = $borr1->{cardnumber}; my $sip_patron = C4::SIP::ILS::Patron->new($card); # Create transaction RenewAll, assign patron, and run (no items) my $transaction = C4::SIP::ILS::Transaction::RenewAll->new(); is( ref $transaction, "C4::SIP::ILS::Transaction::RenewAll", "New transaction created" ); is( $transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); isnt( $transaction->do_renew_all, undef, "RenewAll on zero items" ); subtest fill_holds_at_checkout => sub { plan tests => 6; my $category = $builder->build( { source => 'Category', value => { category_type => 'A' } } ); my $branch = $builder->build( { source => 'Branch' } ); my $borrower = $builder->build( { source => 'Borrower', value => { branchcode => $branch->{branchcode}, categorycode => $category->{categorycode} } } ); t::lib::Mocks::mock_userenv( { branchcode => $branch->{branchcode}, flags => 1 } ); my $itype = $builder->build( { source => 'Itemtype', value => { notforloan => 0 } } ); my $item1 = $builder->build_sample_item( { barcode => 'barcode4test', homebranch => $branch->{branchcode}, holdingbranch => $branch->{branchcode}, itype => $itype->{itemtype}, notforloan => 0, } )->unblessed; my $item2 = $builder->build_sample_item( { homebranch => $branch->{branchcode}, holdingbranch => $branch->{branchcode}, biblionumber => $item1->{biblionumber}, itype => $itype->{itemtype}, notforloan => 0, } )->unblessed; Koha::CirculationRules->set_rules( { categorycode => $borrower->{categorycode}, branchcode => $branch->{branchcode}, itemtype => $itype->{itemtype}, rules => { onshelfholds => 1, reservesallowed => 3, holds_per_record => 3, issuelength => 5, lengthunit => 'days', } } ); my $reserve1 = AddReserve( { branchcode => $branch->{branchcode}, borrowernumber => $borrower->{borrowernumber}, biblionumber => $item1->{biblionumber} } ); my $reserve2 = AddReserve( { branchcode => $branch->{branchcode}, borrowernumber => $borrower->{borrowernumber}, biblionumber => $item1->{biblionumber} } ); my $bib = Koha::Biblios->find( $item1->{biblionumber} ); is( $bib->holds->count(), 2, "Bib has 2 holds" ); my $sip_patron = C4::SIP::ILS::Patron->new( $borrower->{cardnumber} ); my $sip_item = C4::SIP::ILS::Item->new( $item1->{barcode} ); my $transaction = C4::SIP::ILS::Transaction::Checkout->new(); is( ref $transaction, "C4::SIP::ILS::Transaction::Checkout", "New transaction created" ); is( $transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); my $checkout = $transaction->do_checkout(); use Data::Dumper; # Temporary debug statement is( $bib->holds->count(), 1, "Bib has 1 holds remaining" ) or diag Dumper $checkout; t::lib::Mocks::mock_preference( 'itemBarcodeInputFilter', 'whitespace' ); $sip_item = C4::SIP::ILS::Item->new(' barcode 4 test '); $transaction = C4::SIP::ILS::Transaction::Checkout->new(); is( $sip_item->{barcode}, $item1->{barcode}, "Item assigned to transaction" ); }; subtest "FeePayment->pay tests" => sub { plan tests => 5; # Create a borrower and add some outstanding debts to their account my $patron = $builder->build( { source => 'Borrower' } ); my $account = Koha::Account->new( { patron_id => $patron->{borrowernumber} } ); my $debt1 = $account->add_debit( { type => 'ACCOUNT', amount => 100, interface => 'commandline' } ); my $debt2 = $account->add_debit( { type => 'ACCOUNT', amount => 200, interface => 'commandline' } ); # Instantiate a new FeePayment transaction object my $trans = C4::SIP::ILS::Transaction::FeePayment->new(); is( ref $trans, "C4::SIP::ILS::Transaction::FeePayment", "New fee transaction created" ); # Test the 'pay' method # FIXME: pay should not require a borrowernumber # (we should reach out to the transaction which should contain a patron object) my $pay_type = '00'; # 00 - Cash, 01 - VISA, 02 - Creditcard my $ok = $trans->pay( $patron->{borrowernumber}, 100, $pay_type, $debt1->id, 0, 0 ); ok( $ok, "FeePayment transaction succeeded" ); $debt1->discard_changes; is( $debt1->amountoutstanding + 0, 0, "Debt1 was reduced to 0 as expected" ); my $offsets = Koha::Account::Offsets->search( { debit_id => $debt1->id, credit_id => { '!=' => undef } } ); is( $offsets->count, 1, "FeePayment produced an offset line correctly" ); my $credit = $offsets->next->credit; is( $credit->payment_type, 'SIP00', "Payment type was set correctly" ); }; subtest cancel_hold => sub { plan tests => 7; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); Koha::CirculationRules->set_rules( { categorycode => $patron->categorycode, branchcode => $library->branchcode, itemtype => $item->effective_itemtype, rules => { onshelfholds => 1, reservesallowed => 3, holds_per_record => 3, issuelength => 5, lengthunit => 'days', } } ); my $reserve1 = AddReserve( { branchcode => $library->branchcode, borrowernumber => $patron->borrowernumber, biblionumber => $item->biblio->biblionumber, itemnumber => $item->itemnumber, } ); is( $item->biblio->holds->count(), 1, "Hold was placed on bib" ); is( $item->holds->count(), 1, "Hold was placed on specific item" ); my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); my $transaction = C4::SIP::ILS::Transaction::Hold->new(); is( ref $transaction, "C4::SIP::ILS::Transaction::Hold", "New transaction created" ); is( $transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); my $hold = $transaction->drop_hold(); is( $item->biblio->holds->count(), 0, "Bib has 0 holds remaining" ); is( $item->holds->count(), 0, "Item has 0 holds remaining" ); }; subtest do_hold => sub { plan tests => 8; my $library = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1 } } ); my $patron_1 = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $patron_2 = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, categorycode => $patron_1->categorycode, } } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); my $reserve1 = AddReserve( { branchcode => $library->branchcode, borrowernumber => $patron_1->borrowernumber, biblionumber => $item->biblio->biblionumber, itemnumber => $item->itemnumber, } ); is( $item->biblio->holds->count(), 1, "Hold was placed on bib" ); is( $item->holds->count(), 1, "Hold was placed on specific item" ); my $sip_patron = C4::SIP::ILS::Patron->new( $patron_2->cardnumber ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); my $transaction = C4::SIP::ILS::Transaction::Hold->new(); is( ref $transaction, "C4::SIP::ILS::Transaction::Hold", "New transaction created" ); is( $transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); my $hold = $transaction->do_hold(); is( $item->biblio->holds->count(), 2, "Bib has 2 holds" ); my $THE_hold = $patron_2->holds->next; is( $THE_hold->priority, 2, 'Hold placed from SIP should have a correct priority of 2' ); is( $THE_hold->branchcode, $patron_2->branchcode, 'Hold placed from SIP should have the branchcode set' ); }; subtest "Placing holds via SIP check CanItemBeReserved" => sub { plan tests => 4; my $library = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0 } } ); my $patron_1 = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $patron_2 = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, categorycode => $patron_1->categorycode, } } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); my $sip_patron = C4::SIP::ILS::Patron->new( $patron_2->cardnumber ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); my $transaction = C4::SIP::ILS::Transaction::Hold->new(); is( ref $transaction, "C4::SIP::ILS::Transaction::Hold", "New transaction created" ); is( $transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); my $hold = $transaction->do_hold(); is( $transaction->ok, 0, "Hold was not allowed" ); }; subtest do_checkin => sub { plan tests => 14; my $mockILS = Test::MockObject->new; my $server = { ils => $mockILS }; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $library2 = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); # Checkout my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new(); is( $co_transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $co_transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); my $checkout = $co_transaction->do_checkout(); is( $patron->checkouts->count, 1, 'Checkout should have been done successfully' ); # Checkin my $ci_transaction = C4::SIP::ILS::Transaction::Checkin->new(); is( $ci_transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $ci_transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); my $checkin = $ci_transaction->do_checkin( $library->branchcode, C4::SIP::Sip::timestamp ); is( $patron->checkouts->count, 0, 'Checkin should have been done successfully' ); # Test checkin without return date $co_transaction->do_checkout; is( $patron->checkouts->count, 1, 'Checkout should have been done successfully' ); $ci_transaction->do_checkin( $library->branchcode, undef ); is( $patron->checkouts->count, 0, 'Checkin should have been done successfully' ); my $result = $ci_transaction->do_checkin( $library2->branchcode, undef ); is( $ci_transaction->alert_type, '04', "Checkin of item no issued at another branch succeeds" ); is_deeply( $result, { messages => { 'NotIssued' => $item->barcode, 'WasTransfered' => $library->branchcode, 'TransferTrigger' => 'ReturnToHome' } }, "Messages show not issued and transferred" ); is( $ci_transaction->item->destination_loc, $library->branchcode, "Item destination correctly set" ); subtest 'Checkin an in transit item' => sub { plan tests => 5; my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } ); my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library_1->branchcode, } } ); my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber ); my $item = $builder->build_sample_item( { library => $library_1->branchcode } ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); t::lib::Mocks::mock_userenv( { branchcode => $library_1->branchcode, flags => 1 } ); my $reserve = AddReserve( { branchcode => $library_1->branchcode, borrowernumber => $patron->borrowernumber, biblionumber => $item->biblionumber, } ); ModReserveAffect( $item->itemnumber, $patron->borrowernumber ); # Mark waiting my $ci_transaction = C4::SIP::ILS::Transaction::Checkin->new(); is( $ci_transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $ci_transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); my $checkin = $ci_transaction->do_checkin( $library_2->branchcode, C4::SIP::Sip::timestamp ); my $hold = Koha::Holds->find($reserve); is( $hold->found, 'T', ); is( $hold->itemnumber, $item->itemnumber, ); is( Koha::Checkouts->search( { itemnumber => $item->itemnumber } )->count, 0, ); }; subtest 'Checkin message' => sub { plan tests => 3; my $mockILS = Test::MockObject->new; my $server = { ils => $mockILS }; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); my $homebranch = $builder->build( { source => 'Branch', value => { branchcode => 'HMBR', branchname => 'Homebranch' } } ); my $institution = { id => $library->id, implementation => "ILS", policy => { checkin => "true", renewal => "true", checkout => "true", timeout => 100, retries => 5, } }; my $ils = C4::SIP::ILS->new($institution); my $item = $builder->build_sample_item( { library => $library->branchcode, homebranch => $homebranch->{'branchcode'}, location => 'My Location', permanent_location => 'My Permanent Location', } ); my $circ = $ils->checkout( $patron->cardnumber, $item->barcode, undef, undef, $server->{account} ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, '', "Checkin message is not displayed when show_checkin_message is disabled" ); $server->{account}->{show_checkin_message} = 1; $circ = $ils->checkout( $patron->cardnumber, $item->barcode, undef, undef, $server->{account} ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, 'Item checked-in: Homebranch - My Location.', "Checkin message when show_checkin_message is enabled and UseLocationAsAQInSIP is disabled" ); t::lib::Mocks::mock_preference( 'UseLocationAsAQInSIP', '1' ); $circ = $ils->checkout( $patron->cardnumber, $item->barcode, undef, undef, $server->{account} ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, 'Item checked-in: My Permanent Location - My Location.', "Checkin message when both show_checkin_message and UseLocationAsAQInSIP are enabled" ); }; subtest 'Checkin with fines' => sub { plan tests => 2; my $mockILS = Test::MockObject->new; my $server = { ils => $mockILS }; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $institution = { id => $library->id, implementation => "ILS", policy => { checkin => "true", renewal => "true", checkout => "true", timeout => 100, retries => 5, } }; my $ils = C4::SIP::ILS->new($institution); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); # show_outstanding_amount disabled my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $circ = $ils->checkout( $patron->cardnumber, $item->barcode, undef, undef, $server->{account} ); my $fee1 = $builder->build( { source => 'Accountline', value => { borrowernumber => $patron->borrowernumber, amountoutstanding => 12, debit_type_code => 'OVERDUE', itemnumber => $item->itemnumber } } ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, '', "The fine is not displayed on checkin when show_outstanding_amount is disabled" ); # show_outstanding_amount enabled $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); $circ = $ils->checkout( $patron->cardnumber, $item->barcode, undef, undef, $server->{account} ); $fee1 = $builder->build( { source => 'Accountline', value => { borrowernumber => $patron->borrowernumber, amountoutstanding => 12, debit_type_code => 'OVERDUE', itemnumber => $item->itemnumber } } ); $server->{account}->{show_outstanding_amount} = 1; $circ = $ils->checkout( $patron->cardnumber, $item->barcode, undef, undef, $server->{account} ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, 'You owe $12.00 for this item.', "The fine is displayed on checkin when show_outstanding_amount is enabled" ); }; }; subtest do_checkout_with_sysprefs_override => sub { plan tests => 8; my $mockILS = Test::MockObject->new; my $server = { ils => $mockILS }; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $institution = { id => $library->id, implementation => "ILS", policy => { checkin => "true", renewal => "true", checkout => "true", timeout => 100, retries => 5, } }; my $ils = C4::SIP::ILS->new($institution); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); my $patron_under_noissuescharge = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $fee_under_noissuescharge = $builder->build( { source => 'Accountline', value => { borrowernumber => $patron_under_noissuescharge->borrowernumber, amountoutstanding => 4, debit_type_code => 'OVERDUE', } } ); my $patron_over_noissuescharge = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $fee_over_noissuescharge = $builder->build( { source => 'Accountline', value => { borrowernumber => $patron_over_noissuescharge->borrowernumber, amountoutstanding => 6, debit_type_code => 'OVERDUE', } } ); $server->{account}->{override_fine_on_checkout} = 0; t::lib::Mocks::mock_preference( 'AllFinesNeedOverride', '0' ); t::lib::Mocks::mock_preference( 'AllowFineOverride', '0' ); my $circ = $ils->checkout( $patron_under_noissuescharge->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $patron_under_noissuescharge->checkouts->count, 1, 'Checkout is allowed when the amount is under noissuecharge, AllFinesNeedOverride and AllowFineOverride disabled' ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode ); $circ = $ils->checkout( $patron_over_noissuescharge->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $patron_over_noissuescharge->checkouts->count, 0, 'Checkout is blocked when the amount is over noissuecharge, AllFinesNeedOverride and AllowFineOverride disabled' ); t::lib::Mocks::mock_preference( 'AllFinesNeedOverride', '0' ); t::lib::Mocks::mock_preference( 'AllowFineOverride', '1' ); $circ = $ils->checkout( $patron_under_noissuescharge->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $patron_under_noissuescharge->checkouts->count, 1, 'Checkout is allowed when the amount is under noissuecharge, AllFinesNeedOverride disabled and AllowFineOverride enabled' ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode ); $circ = $ils->checkout( $patron_over_noissuescharge->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $patron_over_noissuescharge->checkouts->count, 0, 'Checkout is blocked when the amount is over noissuecharge, AllFinesNeedOverride disabled and AllowFineOverride enabled' ); t::lib::Mocks::mock_preference( 'AllFinesNeedOverride', '1' ); t::lib::Mocks::mock_preference( 'AllowFineOverride', '0' ); $circ = $ils->checkout( $patron_under_noissuescharge->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $patron_under_noissuescharge->checkouts->count, 0, 'Checkout is blocked when the amount is under noissuecharge, AllFinesNeedOverride enabled and AllowFineOverride disabled' ); $circ = $ils->checkout( $patron_over_noissuescharge->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $patron_over_noissuescharge->checkouts->count, 0, 'Checkout is blocked when the amount is over noissuecharge, AllFinesNeedOverride enabled and AllowFineOverride disabled' ); t::lib::Mocks::mock_preference( 'AllFinesNeedOverride', '1' ); t::lib::Mocks::mock_preference( 'AllowFineOverride', '1' ); $circ = $ils->checkout( $patron_under_noissuescharge->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $patron_under_noissuescharge->checkouts->count, 0, 'Checkout is blocked when the amount is under noissuecharge, AllFinesNeedOverride and AllowFineOverride enabled' ); $circ = $ils->checkout( $patron_over_noissuescharge->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $patron_over_noissuescharge->checkouts->count, 0, 'Checkout is blocked when the amount is over noissuecharge, AllFinesNeedOverride and AllowFineOverride enabled' ); }; subtest do_checkout_with_patron_blocked => sub { plan tests => 5; my $mockILS = Test::MockObject->new; my $server = { ils => $mockILS }; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $institution = { id => $library->id, implementation => "ILS", policy => { checkin => "true", renewal => "true", checkout => "true", timeout => 100, retries => 5, } }; my $ils = C4::SIP::ILS->new($institution); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); my $expired_patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, dateexpiry => '2020/01/03', } } ); my $circ = $ils->checkout( $expired_patron->cardnumber, $item->barcode ); is( $circ->{screen_msg}, 'Patron expired on 01/03/2020', "Got correct expired screen message" ); my $fines_patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $fee1 = $builder->build( { source => 'Accountline', value => { borrowernumber => $fines_patron->borrowernumber, amountoutstanding => 10, debit_type_code => 'OVERDUE', } } ); my $fines_sip_patron = C4::SIP::ILS::Patron->new( $fines_patron->cardnumber ); $circ = $ils->checkout( $fines_patron->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, 'Patron has fines', "Got correct fines screen message" ); $server->{account}->{show_outstanding_amount} = 1; $circ = $ils->checkout( $fines_patron->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, 'Patron has fines - You owe $10.00.', "Got correct fines with amount screen message" ); my $debarred_patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, debarred => '9999/01/01', } } ); my $debarred_sip_patron = C4::SIP::ILS::Patron->new( $debarred_patron->cardnumber ); $circ = $ils->checkout( $debarred_patron->cardnumber, $item->barcode ); is( $circ->{screen_msg}, 'Patron debarred', "Got correct debarred screen message" ); my $overdue_patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $odue = $builder->build( { source => 'Issue', value => { borrowernumber => $overdue_patron->borrowernumber, date_due => '2017-01-01', } } ); t::lib::Mocks::mock_preference( 'OverduesBlockCirc', 'block' ); my $overdue_sip_patron = C4::SIP::ILS::Patron->new( $overdue_patron->cardnumber ); $circ = $ils->checkout( $overdue_patron->cardnumber, $item->barcode ); is( $circ->{screen_msg}, 'Patron blocked', "Got correct blocked screen message" ); }; subtest do_checkout_with_noblock => sub { plan tests => 3; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, debarred => '9999/01/01', }, } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new(); is( $co_transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $co_transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); $co_transaction->do_checkout( undef, '19990102 030405' ); is( $patron->checkouts->count, 1, 'No Block checkout was performed for debarred patron' ); }; subtest do_checkout_with_holds => sub { plan tests => 7; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $patron2 = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); my $reserve = AddReserve( { branchcode => $library->branchcode, borrowernumber => $patron2->borrowernumber, biblionumber => $item->biblionumber, } ); my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new(); is( $co_transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $co_transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); # Test attached holds ModReserveAffect( $item->itemnumber, $patron->borrowernumber, 0, $reserve ); # Mark waiting (W) my $hold = Koha::Holds->find($reserve); $co_transaction->do_checkout(); is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (W)' ); $hold->set_transfer; $co_transaction->do_checkout(); is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (T)' ); $hold->set_processing; $co_transaction->do_checkout(); is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (P)' ); # Test non-attached holds C4::Reserves::RevertWaitingStatus( { itemnumber => $hold->itemnumber } ); t::lib::Mocks::mock_preference( 'AllowItemsOnHoldCheckoutSIP', '0' ); $co_transaction->do_checkout(); is( $patron->checkouts->count, 0, 'Checkout refused due to hold and AllowItemsOnHoldCheckoutSIP' ); t::lib::Mocks::mock_preference( 'AllowItemsOnHoldCheckoutSIP', '1' ); $co_transaction->do_checkout(); is( $patron->checkouts->count, 1, 'Checkout allowed due to hold and AllowItemsOnHoldCheckoutSIP' ); }; subtest checkin_lost => sub { plan tests => 2; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); $item->itemlost(1)->itemlost_on(dt_from_string)->store(); my $instituation = { id => $library->id, implementation => "ILS", policy => { checkin => "true", renewal => "true", checkout => "true", timeout => 100, retries => 5, } }; my $ils = C4::SIP::ILS->new($instituation); t::lib::Mocks::mock_preference( 'BlockReturnOfLostItems', '1' ); my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode ); is( $circ->{screen_msg}, 'Item lost, return not allowed', "Got correct screen message" ); t::lib::Mocks::mock_preference( 'BlockReturnOfLostItems', '0' ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode ); is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" ); }; subtest checkin_withdrawn => sub { plan tests => 2; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); $item->withdrawn(1)->withdrawn_on(dt_from_string)->store(); my $instituation = { id => $library->id, implementation => "ILS", policy => { checkin => "true", renewal => "true", checkout => "true", timeout => 100, retries => 5, } }; my $ils = C4::SIP::ILS->new($instituation); t::lib::Mocks::mock_preference( 'BlockReturnOfWithdrawnItems', '1' ); my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode ); is( $circ->{screen_msg}, 'Item withdrawn, return not allowed', "Got correct screen message" ); t::lib::Mocks::mock_preference( 'BlockReturnOfWithdrawnItems', '0' ); $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode ); is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" ); }; subtest _get_sort_bin => sub { plan tests => 6; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $branch = $library->branchcode; my $library2 = $builder->build_object( { class => 'Koha::Libraries' } ); my $branch2 = $library2->branchcode; # Rules starts with malformed line, empty line and a comment to prove they are skipped my $rules = <<"RULES"; $branch:homebranch:ne:\$holdingbranch:X\r $branch:effective_itemtype:eq:CD:0\r $branch:itemcallnumber:<:340:1\r $branch:itemcallnumber:<:370:2\r $branch:itemcallnumber:<:600:3\r $branch2:homebranch:ne:\$holdingbranch:X\r $branch2:effective_itemtype:eq:CD:4\r $branch2:itemcallnumber:>:600:5\r $branch2:effective_itemtype:eq:BOOK:ccode:eq:TEEN:6\r # Comment line to skip\r \r $branch:homebranch:Z\r RULES t::lib::Mocks::mock_preference( 'SIP2SortBinMapping', $rules ); my $item_cd = $builder->build_sample_item( { library => $library->branchcode, itype => 'CD' } ); my $item_book = $builder->build_sample_item( { library => $library->branchcode, itype => 'BOOK', itemcallnumber => '200.01' } ); my $item_book2 = $builder->build_sample_item( { library => $library2->branchcode, itype => 'BOOK', ccode => 'TEEN' } ); my $item_dvd = $builder->build_sample_item( { library => $library2->branchcode, itype => 'DVD' } ); my $bin; # Set holdingbranch as though item returned to library other than homebranch (As AddReturn would) $item_cd->holdingbranch( $library2->branchcode )->store(); $bin = C4::SIP::ILS::Transaction::Checkin::_get_sort_bin( $item_cd, $library2->branchcode ); is( $bin, 'X', "Item parameter on RHS of comparison works (ne comparator)" ); # Reset holdingbranch as though item returned to home library $item_cd->holdingbranch( $library->branchcode )->store(); $bin = C4::SIP::ILS::Transaction::Checkin::_get_sort_bin( $item_cd, $library->branchcode ); is( $bin, '0', "Fixed value on RHS of comparison works (eq comparator)" ); $bin = C4::SIP::ILS::Transaction::Checkin::_get_sort_bin( $item_book, $library->branchcode ); is( $bin, '1', "Rules applied in order (< comparator)" ); $item_book->itemcallnumber('350.20')->store(); $bin = C4::SIP::ILS::Transaction::Checkin::_get_sort_bin( $item_book, $library->branchcode ); is( $bin, '2', "Rules applied in order (< comparator)" ); $bin = C4::SIP::ILS::Transaction::Checkin::_get_sort_bin( $item_book2, $library2->branchcode ); is( $bin, '6', "Rules with multiple field matches" ); warnings_are { $bin = C4::SIP::ILS::Transaction::Checkin::_get_sort_bin( $item_dvd, $library2->branchcode ); } ["Malformed preference line found: '$branch:homebranch:Z'"], "Comments and blank lines are skipped, Malformed lines are warned and skipped"; }; subtest item_circulation_status => sub { plan tests => 8; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $library2 = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); my $status = $sip_item->sip_circulation_status; is( $status, '03', "Item circulation status is available" ); my $transfer = Koha::Item::Transfer->new( { itemnumber => $item->id, datesent => '2020-01-01', frombranch => $library->branchcode, tobranch => $library2->branchcode, } )->store(); $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); $status = $sip_item->sip_circulation_status; is( $status, '10', "Item circulation status is in transit" ); $transfer->delete; my $claim = Koha::Checkouts::ReturnClaim->new( { itemnumber => $item->id, borrowernumber => $patron->id, created_by => $patron->id, } )->store(); $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); $status = $sip_item->sip_circulation_status; is( $status, '11', "Item circulation status is claimed returned" ); $claim->delete; $item->itemlost(1)->store(); $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); $status = $sip_item->sip_circulation_status; is( $status, '12', "Item circulation status is lost" ); $status = $sip_item->sip_circulation_status( { account => { lost_status_for_missing => "1" } } ); is( $status, '13', "Item circulation status is missing" ); $item->itemlost(0)->store(); my $location = $item->location; $item->location("CART")->store(); $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); $status = $sip_item->sip_circulation_status; is( $status, '09', "Item circulation status is waiting to be re-shelved" ); $item->location($location)->store(); my $nfl = $item->notforloan; $item->notforloan(-1)->store(); $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); $status = $sip_item->sip_circulation_status; is( $status, '02', "Item circulation status is on order" ); $item->notforloan($nfl)->store(); my $damaged = $item->damaged; $item->damaged(1)->store(); $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); $status = $sip_item->sip_circulation_status; is( $status, '01', "Item circulation status is damaged" ); $item->damaged(0)->store(); }; subtest do_checkout_with_recalls => sub { plan tests => 7; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); my $patron2 = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library->branchcode, } } ); t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode, flags => 1 } ); my $item = $builder->build_sample_item( { library => $library->branchcode, } ); t::lib::Mocks::mock_preference( 'UseRecalls', 1 ); Koha::CirculationRules->set_rule( { branchcode => undef, categorycode => undef, itemtype => undef, rule_name => 'recalls_allowed', rule_value => '10', } ); my $recall1 = Koha::Recall->new( { patron_id => $patron2->borrowernumber, created_date => \'NOW()', biblio_id => $item->biblio->biblionumber, pickup_library_id => $library->branchcode, item_id => $item->itemnumber, expiration_date => undef, item_level => 1 } )->store; my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber ); my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new(); is( $co_transaction->patron($sip_patron), $sip_patron, "Patron assigned to transaction" ); is( $co_transaction->item($sip_item), $sip_item, "Item assigned to transaction" ); # Test recalls made by another patron $recall1->set_waiting( { item => $item } ); $co_transaction->do_checkout(); is( $patron->checkouts->count, 0, 'Checkout was not done due to waiting recall' ); $recall1->revert_waiting; $recall1->start_transfer( { item => $item } ); $co_transaction->do_checkout(); is( $patron->checkouts->count, 0, 'Checkout was not done due to recall in transit' ); $recall1->revert_transfer; $recall1->update( { item_id => undef, item_level => 0 } ); $co_transaction->do_checkout(); is( $patron->checkouts->count, 0, 'Checkout was not done due to a biblio-level recall that this item could fill' ); $recall1->set_cancelled; my $recall2 = Koha::Recall->new( { patron_id => $patron->borrowernumber, created_date => \'NOW()', biblio_id => $item->biblio->biblionumber, pickup_library_id => $library->branchcode, item_id => $item->itemnumber, expiration_date => undef, item_level => 1 } )->store; # Test recalls made by SIP patron $recall2->set_waiting( { item => $item } ); $co_transaction->do_checkout(); is( $patron->checkouts->count, 1, 'Checkout was done because recalled item was allocated to them' ); $recall2 = Koha::Recalls->find( $recall2->id ); is( $recall2->status, 'fulfilled', 'Recall is fulfilled by checked out item' ); }; subtest 'Checkin message' => sub { plan tests => 2; my $mockILS = Test::MockObject->new; my $server = { ils => $mockILS }; my $library = $builder->build_object( { class => 'Koha::Libraries' } ); my $patron1 = $builder->build_object( { class => 'Koha::Patrons' } ); my $patron2 = $builder->build_object( { class => 'Koha::Patrons' } ); my $institution = { id => $library->id, implementation => "ILS", policy => { checkin => "true", renewal => "true", checkout => "true", timeout => 100, retries => 5, } }; my $ils = C4::SIP::ILS->new($institution); my $item = $builder->build_sample_item( { library => $library->branchcode, location => 'My Location', permanent_location => 'My Permanent Location', } ); t::lib::Mocks::mock_preference( 'AllowItemsOnLoanCheckoutSIP', '' ); my $circ = $ils->checkout( $patron1->cardnumber, $item->barcode, undef, undef, $server->{account} ); $circ = $ils->checkout( $patron2->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, 'Item checked out to another patron', "Checked out item was not checked out to the next patron" ); t::lib::Mocks::mock_preference( 'AllowItemsOnLoanCheckoutSIP', '1' ); $circ = $ils->checkout( $patron2->cardnumber, $item->barcode, undef, undef, $server->{account} ); is( $circ->{screen_msg}, '', "Checked out item was checked out to the next patron" ); }; $schema->storage->txn_rollback;