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>.
20 use File::Basename qw/basename/;
22 use C4::Circulation qw(AddIssue AddReturn);
25 use Koha::Illrequestattributes;
26 use Koha::Illrequest::Config;
32 use Koha::MessageAttributes;
33 use Koha::MessageAttribute;
34 use Koha::Notice::Templates;
35 use Koha::AuthorisedValueCategories;
36 use Koha::AuthorisedValues;
38 use t::lib::TestBuilder;
42 use Test::Deep qw/ cmp_deeply ignore /;
45 use Test::More tests => 13;
47 my $schema = Koha::Database->new->schema;
48 my $builder = t::lib::TestBuilder->new;
49 use_ok('Koha::Illrequest');
50 use_ok('Koha::Illrequests');
52 subtest 'Basic object tests' => sub {
56 $schema->storage->txn_begin;
58 Koha::Illrequests->search->delete;
59 my $illrq = $builder->build({ source => 'Illrequest' });
60 my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
62 isa_ok($illrq_obj, 'Koha::Illrequest',
63 "Correctly create and load an illrequest object.");
64 isa_ok($illrq_obj->_config, 'Koha::Illrequest::Config',
65 "Created a config object as part of Illrequest creation.");
67 is($illrq_obj->illrequest_id, $illrq->{illrequest_id},
68 "Illrequest_id getter works.");
69 is($illrq_obj->borrowernumber, $illrq->{borrowernumber},
70 "Borrowernumber getter works.");
71 is($illrq_obj->biblio_id, $illrq->{biblio_id},
72 "Biblio_Id getter works.");
73 is($illrq_obj->branchcode, $illrq->{branchcode},
74 "Branchcode getter works.");
75 is($illrq_obj->status, $illrq->{status},
76 "Status getter works.");
77 is($illrq_obj->placed, $illrq->{placed},
78 "Placed getter works.");
79 is($illrq_obj->replied, $illrq->{replied},
80 "Replied getter works.");
81 is($illrq_obj->updated, $illrq->{updated},
82 "Updated getter works.");
83 is($illrq_obj->completed, $illrq->{completed},
84 "Completed getter works.");
85 is($illrq_obj->medium, $illrq->{medium},
86 "Medium getter works.");
87 is($illrq_obj->accessurl, $illrq->{accessurl},
88 "Accessurl getter works.");
89 is($illrq_obj->cost, $illrq->{cost},
90 "Cost getter works.");
91 is($illrq_obj->price_paid, $illrq->{price_paid},
92 "Price_paid getter works.");
93 is($illrq_obj->notesopac, $illrq->{notesopac},
94 "Notesopac getter works.");
95 is($illrq_obj->notesstaff, $illrq->{notesstaff},
96 "Notesstaff getter works.");
97 is($illrq_obj->orderid, $illrq->{orderid},
98 "Orderid getter works.");
99 is($illrq_obj->backend, $illrq->{backend},
100 "Backend getter works.");
102 is($illrq_obj->get_type, undef,
103 'get_type() returns undef if no type is set');
105 source => 'Illrequestattribute',
107 illrequest_id => $illrq_obj->illrequest_id,
112 is($illrq_obj->get_type, 'book',
113 'get_type() returns correct type if set');
115 isnt($illrq_obj->status, 'COMP',
116 "ILL is not currently marked complete.");
117 $illrq_obj->mark_completed;
118 is($illrq_obj->status, 'COMP',
119 "ILL is now marked complete.");
123 is(Koha::Illrequests->search->count, 0,
124 "No illrequest found after delete.");
126 $schema->storage->txn_rollback;
129 subtest 'Working with related objects' => sub {
133 $schema->storage->txn_begin;
135 Koha::Illrequests->search->delete;
137 my $patron = $builder->build({ source => 'Borrower' });
138 my $illrq = $builder->build({
139 source => 'Illrequest',
140 value => { borrowernumber => $patron->{borrowernumber} }
142 my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
144 isa_ok($illrq_obj->patron, 'Koha::Patron',
145 "OK accessing related patron.");
148 source => 'Illrequestattribute',
149 value => { illrequest_id => $illrq_obj->illrequest_id, type => 'X' }
152 source => 'Illrequestattribute',
153 value => { illrequest_id => $illrq_obj->illrequest_id, type => 'Y' }
156 source => 'Illrequestattribute',
157 value => { illrequest_id => $illrq_obj->illrequest_id, type => 'Z' }
160 is($illrq_obj->illrequestattributes->count, Koha::Illrequestattributes->search->count,
161 "Fetching expected number of Illrequestattributes for our request.");
163 my $illrq1 = $builder->build({ source => 'Illrequest' });
165 source => 'Illrequestattribute',
166 value => { illrequest_id => $illrq1->{illrequest_id}, type => 'X' }
169 is($illrq_obj->illrequestattributes->count + 1, Koha::Illrequestattributes->search->count,
170 "Fetching expected number of Illrequestattributes for our request.");
172 is($illrq_obj->biblio, undef, "->biblio returns undef if no biblio");
173 my $biblio = $builder->build_object({ class => 'Koha::Biblios' });
174 my $req_bib = $builder->build_object({
175 class => 'Koha::Illrequests',
177 biblio_id => $biblio->biblionumber
180 isa_ok($req_bib->biblio, 'Koha::Biblio', "OK accessing related biblio");
183 is(Koha::Illrequestattributes->search->count, 1,
184 "Correct number of illrequestattributes after delete.");
186 isa_ok(Koha::Patrons->find($patron->{borrowernumber}), 'Koha::Patron',
187 "Borrower was not deleted after illrq delete.");
189 $schema->storage->txn_rollback;
192 subtest 'Status Graph tests' => sub {
196 $schema->storage->txn_begin;
198 my $illrq = $builder->build({source => 'Illrequest'});
199 my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
201 # _core_status_graph tests: it's just a constant, so here we just make
202 # sure it returns a hashref.
203 is(ref $illrq_obj->_core_status_graph, "HASH",
204 "_core_status_graph returns a hash.");
206 # _status_graph_union: let's try different merge operations.
209 $illrq_obj->_status_graph_union($illrq_obj->_core_status_graph, {}),
210 $illrq_obj->_core_status_graph,
211 "core_status_graph + null = core_status_graph"
216 $illrq_obj->_status_graph_union({}, $illrq_obj->_core_status_graph),
217 $illrq_obj->_core_status_graph,
218 "null + core_status_graph = core_status_graph"
221 # Correct merge behaviour
223 $illrq_obj->_status_graph_union({
231 prev_actions => [ 'REQ' ],
233 next_actions => [ 'REQ' ],
238 prev_actions => [ 'QER' ],
240 next_actions => [ 'QER' ],
243 prev_actions => [ 'REQ' ],
245 next_actions => [ 'REQ' ],
248 "REQ atom + linking QER = cyclical status graph"
251 # Create a new node, with no prev_actions and no next_actions. This should
252 # protect us against regressions related to bug 22280.
260 # Add the new node to the core_status_grpah
261 my $new_graph = $illrq_obj->_status_graph_union( $new_node, $illrq_obj->_core_status_graph);
262 # Compare the updated graph to the expected graph
263 # The structure we compare against here is just a copy of the structure found
264 # in Koha::Illrequest::_core_status_graph() + the new node we created above
265 cmp_deeply( $new_graph,
273 prev_actions => [ ], # Actions containing buttons
274 # leading to this status
275 id => 'NEW', # ID of this status
276 name => 'New request', # UI name of this status
277 ui_method_name => 'New request', # UI name of method leading
279 method => 'create', # method to this status
280 next_actions => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
281 # requests with this status
282 ui_method_icon => 'fa-plus', # UI Style class
285 prev_actions => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
288 ui_method_name => 'Confirm request',
290 next_actions => [ 'REQREV', 'COMP', 'CHK' ],
291 ui_method_icon => 'fa-check',
294 prev_actions => [ 'NEW', 'REQREV' ],
296 name => 'Requested from partners',
297 ui_method_name => 'Place request with partners',
298 method => 'generic_confirm',
299 next_actions => [ 'COMP', 'CHK' ],
300 ui_method_icon => 'fa-send-o',
303 prev_actions => [ 'REQ' ],
305 name => 'Request reverted',
306 ui_method_name => 'Revert Request',
308 next_actions => [ 'REQ', 'GENREQ', 'KILL' ],
309 ui_method_icon => 'fa-times',
314 name => 'Queued request',
317 next_actions => [ 'REQ', 'KILL' ],
321 prev_actions => [ 'NEW' ],
323 name => 'Cancellation requested',
326 next_actions => [ 'KILL', 'REQ' ],
330 prev_actions => [ 'REQ' ],
333 ui_method_name => 'Mark completed',
334 method => 'mark_completed',
335 next_actions => [ 'CHK' ],
336 ui_method_icon => 'fa-check',
339 prev_actions => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
342 ui_method_name => 'Delete request',
345 ui_method_icon => 'fa-trash',
348 prev_actions => [ 'REQ', 'GENREQ', 'COMP' ],
350 name => 'Checked out',
351 ui_method_name => 'Check out',
352 needs_prefs => [ 'CirculateILL' ],
353 needs_perms => [ 'user_circulate_circulate_remaining_permissions' ],
354 needs_all => ignore(),
355 method => 'check_out',
357 ui_method_icon => 'fa-upload',
360 prev_actions => [ 'CHK' ],
362 name => 'Returned to library',
363 ui_method_name => 'Check in',
364 method => 'check_in',
365 next_actions => [ 'COMP' ],
366 ui_method_icon => 'fa-download',
369 "new node + core_status_graph = bigger status graph"
370 ) || diag explain $new_graph;
372 $schema->storage->txn_rollback;
375 subtest 'Backend testing (mocks)' => sub {
379 $schema->storage->txn_begin;
381 # testing load_backend & available_backends requires that we have at least
382 # the Dummy plugin installed. load_backend & available_backends don't
383 # currently have tests as a result.
385 t::lib::Mocks->mock_config('interlibrary_loans', { backend_dir => 'a_dir' } );
386 my $backend = Test::MockObject->new;
387 $backend->set_isa('Koha::Illbackends::Mock');
388 $backend->set_always('name', 'Mock');
390 my $patron = $builder->build({ source => 'Borrower' });
391 my $illrq = $builder->build_object({
392 class => 'Koha::Illrequests',
395 $illrq->_backend($backend);
397 isa_ok($illrq->_backend, 'Koha::Illbackends::Mock',
398 "OK accessing mocked backend.");
400 # _backend_capability tests:
401 # We need to test whether this optional feature of a mocked backend
402 # behaves as expected.
403 # 3 scenarios: feature not implemented, feature implemented, but requested
404 # capability is not provided by backend, & feature is implemented &
405 # capability exists. This method can be used to implement custom backend
406 # functionality, such as unmediated in the BLDSS backend (also see
408 $backend->set_always('capabilities', undef);
409 is($illrq->_backend_capability('Test'), 0,
410 "0 returned on Mock not implementing capabilities.");
412 $backend->set_always('capabilities', 0);
413 is($illrq->_backend_capability('Test'), 0,
414 "0 returned on Mock not implementing Test capability.");
416 $backend->set_always('capabilities', sub { return 'bar'; } );
417 is($illrq->_backend_capability('Test'), 'bar',
418 "'bar' returned on Mock implementing Test capability.");
420 # metadata test: we need to be sure that we return the arbitrary values
425 my ( $self, $rq ) = @_;
427 ID => $rq->illrequest_id,
428 Title => $rq->patron->borrowernumber
436 ID => $illrq->illrequest_id,
437 Title => $illrq->patron->borrowernumber
444 # No backend graph extension
445 $backend->set_always('status_graph', {});
446 is_deeply($illrq->capabilities('COMP'),
448 prev_actions => [ 'REQ' ],
451 ui_method_name => 'Mark completed',
452 method => 'mark_completed',
453 next_actions => [ 'CHK' ],
454 ui_method_icon => 'fa-check',
456 "Dummy status graph for COMP.");
457 is($illrq->capabilities('UNKNOWN'), undef,
458 "Dummy status graph for UNKNOWN.");
459 is_deeply($illrq->capabilities(),
460 $illrq->_core_status_graph,
461 "Dummy full status graph.");
462 # Simple backend graph extension
463 $backend->set_always('status_graph',
466 prev_actions => [ 'REQ' ],
468 next_actions => [ 'REQ' ],
471 is_deeply($illrq->capabilities('QER'),
473 prev_actions => [ 'REQ' ],
475 next_actions => [ 'REQ' ],
477 "Simple status graph for QER.");
478 is($illrq->capabilities('UNKNOWN'), undef,
479 "Simple status graph for UNKNOWN.");
480 is_deeply($illrq->capabilities(),
481 $illrq->_status_graph_union(
482 $illrq->_core_status_graph,
485 prev_actions => [ 'REQ' ],
487 next_actions => [ 'REQ' ],
491 "Simple full status graph.");
495 # No backend graph extension
496 $backend->set_always('status_graph', {});
497 is($illrq->custom_capability('unknown', {}), 0,
498 "Unknown candidate.");
500 # Simple backend graph extension
501 $backend->set_always('status_graph',
504 prev_actions => [ 'REQ' ],
506 method => 'identity',
507 next_actions => [ 'REQ' ],
510 $backend->mock('identity',
511 sub { my ( $self, $params ) = @_; return $params->{other}; });
512 is($illrq->custom_capability('identity', { test => 1, method => 'blah' })->{test}, 1,
513 "Resolve identity custom_capability");
515 $schema->storage->txn_rollback;
519 subtest 'Backend core methods' => sub {
523 $schema->storage->txn_begin;
525 # Build infrastructure
526 my $backend = Test::MockObject->new;
527 $backend->set_isa('Koha::Illbackends::Mock');
528 $backend->set_always('name', 'Mock');
529 $backend->mock('capabilities', sub { return 'Mock'; });
531 my $config = Test::MockObject->new;
532 $config->set_always('backend_dir', "/tmp");
533 $config->set_always('getLimitRules',
534 { default => { count => 0, method => 'active' } });
536 my $illrq = $builder->build_object({
537 class => 'Koha::Illrequests',
538 value => { backend => undef }
540 $illrq->_config($config);
542 # Test error conditions (no backend)
543 throws_ok { $illrq->load_backend; }
544 'Koha::Exceptions::Ill::InvalidBackendId',
545 'Exception raised correctly';
547 throws_ok { $illrq->load_backend(''); }
548 'Koha::Exceptions::Ill::InvalidBackendId',
549 'Exception raised correctly';
551 # Now load the mocked backend
552 $illrq->_backend($backend);
555 is_deeply($illrq->expandTemplate({ test => 1, method => "bar" }),
559 template => "/tmp/Mock/intra-includes/bar.inc",
560 opac_template => "/tmp/Mock/opac-includes/bar.inc",
565 # we are testing simple cases.
566 $backend->set_series('create',
567 { stage => 'bar', method => 'create' },
568 { stage => 'commit', method => 'create' },
569 { stage => 'commit', method => 'create' },
570 { stage => 'commit', method => 'create' },
571 { stage => 'commit', method => 'create' });
573 is_deeply($illrq->backend_create({test => 1}),
575 stage => 'bar', method => 'create',
576 template => "/tmp/Mock/intra-includes/create.inc",
577 opac_template => "/tmp/Mock/opac-includes/create.inc",
579 "Backend create: arbitrary stage.");
581 is_deeply($illrq->backend_create({test => 1}),
583 stage => 'commit', method => 'create', permitted => 0,
584 template => "/tmp/Mock/intra-includes/create.inc",
585 opac_template => "/tmp/Mock/opac-includes/create.inc",
587 "Backend create: arbitrary stage, not permitted.");
588 is($illrq->status, "QUEUED", "Backend create: queued if restricted.");
589 $config->set_always('getLimitRules', {});
590 $illrq->status('NEW');
591 is_deeply($illrq->backend_create({test => 1}),
593 stage => 'commit', method => 'create', permitted => 1,
594 template => "/tmp/Mock/intra-includes/create.inc",
595 opac_template => "/tmp/Mock/opac-includes/create.inc",
597 "Backend create: arbitrary stage, permitted.");
598 is($illrq->status, "NEW", "Backend create: not-queued.");
600 # Test that enabling the unmediated workflow causes the backend's
601 # 'unmediated_ill' method to be called
602 t::lib::Mocks::mock_preference('ILLModuleUnmediated', '1');
606 my ($self, $name) = @_;
607 if ($name eq 'unmediated_ill') {
609 return { unmediated_ill => 1 };
614 $illrq->status('NEW');
616 $illrq->backend_create({test => 1}),
618 'opac_template' => '/tmp/Mock/opac-includes/.inc',
619 'template' => '/tmp/Mock/intra-includes/.inc',
620 'unmediated_ill' => 1
622 "Backend create: commit stage, permitted, ILLModuleUnmediated enabled."
625 # Test that disabling the unmediated workflow causes the backend's
626 # 'unmediated_ill' method to be NOT called
627 t::lib::Mocks::mock_preference('ILLModuleUnmediated', '0');
628 $illrq->status('NEW');
630 $illrq->backend_create({test => 1}),
632 stage => 'commit', method => 'create', permitted => 1,
633 template => "/tmp/Mock/intra-includes/create.inc",
634 opac_template => "/tmp/Mock/opac-includes/create.inc",
636 "Backend create: commit stage, permitted, ILLModuleUnmediated disabled."
640 $backend->set_series('renew', { stage => 'bar', method => 'renew' });
641 is_deeply($illrq->backend_renew({test => 1}),
643 stage => 'bar', method => 'renew',
644 template => "/tmp/Mock/intra-includes/renew.inc",
645 opac_template => "/tmp/Mock/opac-includes/renew.inc",
647 "Backend renew: arbitrary stage.");
650 $backend->set_series('cancel', { stage => 'bar', method => 'cancel' });
651 is_deeply($illrq->backend_cancel({test => 1}),
653 stage => 'bar', method => 'cancel',
654 template => "/tmp/Mock/intra-includes/cancel.inc",
655 opac_template => "/tmp/Mock/opac-includes/cancel.inc",
657 "Backend cancel: arbitrary stage.");
659 # backend_update_status
660 $backend->set_series('update_status', { stage => 'bar', method => 'update_status' });
661 is_deeply($illrq->backend_update_status({test => 1}),
663 stage => 'bar', method => 'update_status',
664 template => "/tmp/Mock/intra-includes/update_status.inc",
665 opac_template => "/tmp/Mock/opac-includes/update_status.inc",
667 "Backend update_status: arbitrary stage.");
670 $backend->set_series('confirm', { stage => 'bar', method => 'confirm' });
671 is_deeply($illrq->backend_confirm({test => 1}),
673 stage => 'bar', method => 'confirm',
674 template => "/tmp/Mock/intra-includes/confirm.inc",
675 opac_template => "/tmp/Mock/opac-includes/confirm.inc",
677 "Backend confirm: arbitrary stage.");
679 $config->set_always('partner_code', "ILLTSTLIB");
680 $backend->set_always('metadata', { Test => "Foobar" });
681 my $illbrn = $builder->build({
683 value => { branchemail => "", branchreplyto => "" }
685 my $partner1 = $builder->build({
686 source => 'Borrower',
687 value => { categorycode => "ILLTSTLIB" },
689 my $partner2 = $builder->build({
690 source => 'Borrower',
691 value => { categorycode => "ILLTSTLIB" },
693 my $gen_conf = $illrq->generic_confirm({
694 current_branchcode => $illbrn->{branchcode}
696 isnt(index($gen_conf->{value}->{draft}->{body}, $backend->metadata->{Test}), -1,
697 "Generic confirm: draft contains metadata."
699 is($gen_conf->{value}->{partners}->next->borrowernumber, $partner1->{borrowernumber},
700 "Generic cofnirm: partner 1 is correct."
702 is($gen_conf->{value}->{partners}->next->borrowernumber, $partner2->{borrowernumber},
703 "Generic confirm: partner 2 is correct."
706 dies_ok { $illrq->generic_confirm({
707 current_branchcode => $illbrn->{branchcode},
710 "Generic confirm: missing to dies OK.";
712 $schema->storage->txn_rollback;
716 subtest 'Helpers' => sub {
720 $schema->storage->txn_begin;
722 # Build infrastructure
723 my $backend = Test::MockObject->new;
724 $backend->set_isa('Koha::Illbackends::Mock');
725 $backend->set_always('name', 'Mock');
729 my ( $self, $rq ) = @_;
737 my $config = Test::MockObject->new;
738 $config->set_always('backend_dir', "/tmp");
740 my $patron = $builder->build({
741 source => 'Borrower',
742 value => { categorycode => "A" }
744 # Create a mocked branch with no email addressed defined
745 my $illbrn = $builder->build({
750 branchillemail => "",
754 my $illrq = $builder->build({
755 source => 'Illrequest',
756 value => { branchcode => "HDE", borrowernumber => $patron->{borrowernumber} }
758 my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
759 $illrq_obj->_config($config);
760 $illrq_obj->_backend($backend);
763 $config->set_series('getPrefixes',
764 { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
765 { A => "ATEST", C => "CBAR", default => "DEFAULT" });
766 is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "HDE" }), "TEST",
767 "getPrefix: branch");
768 $config->set_series('getPrefixes',
769 { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
770 { A => "ATEST", C => "CBAR", default => "DEFAULT" });
771 is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
772 "getPrefix: default");
773 $config->set_always('getPrefixes', {});
774 is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
775 "getPrefix: the empty prefix");
778 $config->set_series('getPrefixes',
779 { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
780 { AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
781 is($illrq_obj->id_prefix, "TEST-", "id_prefix: branch");
782 $config->set_series('getPrefixes',
783 { HDET => "TEST", TSLT => "BAR", default => "DEFAULT" },
784 { AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
785 is($illrq_obj->id_prefix, "", "id_prefix: default");
787 # requires_moderation
788 $illrq_obj->status('NEW')->store;
789 is($illrq_obj->requires_moderation, undef, "requires_moderation: No.");
790 $illrq_obj->status('CANCREQ')->store;
791 is($illrq_obj->requires_moderation, 'CANCREQ', "requires_moderation: Yes.");
794 my $attr = Koha::MessageAttributes->find({ message_name => 'Ill_ready' });
795 C4::Members::Messaging::SetMessagingPreference({
796 borrowernumber => $patron->{borrowernumber},
797 message_attribute_id => $attr->message_attribute_id,
798 message_transport_types => ['email']
800 my $return_patron = $illrq_obj->send_patron_notice('ILL_PICKUP_READY');
801 my $notice = $schema->resultset('MessageQueue')->search({
802 letter_code => 'ILL_PICKUP_READY',
803 message_transport_type => 'email',
804 borrowernumber => $illrq_obj->borrowernumber
805 })->next()->letter_code;
808 { result => { success => ['email'], fail => [] } },
809 "Correct return when notice created"
811 is($notice, 'ILL_PICKUP_READY' ,"Notice is correctly created");
813 my $return_patron_fail = $illrq_obj->send_patron_notice();
816 { error => 'notice_no_type' },
817 "Correct error when missing type"
821 # Specify that no staff notices should be send
822 t::lib::Mocks::mock_preference('ILLSendStaffNotices', '');
823 my $return_staff_cancel_fail =
824 $illrq_obj->send_staff_notice('ILL_REQUEST_CANCEL');
826 $return_staff_cancel_fail,
827 { error => 'notice_not_enabled' },
828 "Does not send notices that are not enabled"
830 my $queue = $schema->resultset('MessageQueue')->search({
831 letter_code => 'ILL_REQUEST_CANCEL'
833 is($queue->count, 0, "Notice is not queued");
835 # Specify that the cancel notice can be sent
836 t::lib::Mocks::mock_preference('ILLSendStaffNotices', 'ILL_REQUEST_CANCEL');
837 my $return_staff_cancel = $illrq_obj->send_staff_notice(
841 $return_staff_cancel,
842 { success => 'notice_queued' },
843 "Correct return when staff notice created"
845 $queue = $schema->resultset('MessageQueue')->search({
846 letter_code => 'ILL_REQUEST_CANCEL'
848 is($queue->count, 1, "Notice queued as expected");
850 my $return_staff_fail = $illrq_obj->send_staff_notice();
853 { error => 'notice_no_type' },
854 "Correct error when missing type"
856 $queue = $schema->resultset('MessageQueue')->search({
857 letter_code => 'ILL_REQUEST_CANCEL'
859 is($queue->count, 1, "Notice is not queued");
862 my $not = $illrq_obj->get_notice({
863 notice_code => 'ILL_REQUEST_CANCEL',
867 # We test the properties of the hashref separately because the random
868 # hash ordering of the metadata means we can't test the entire thing
871 $not->{module} eq 'ill',
872 'Correct module return from get_notice'
875 $not->{name} eq 'ILL request cancelled',
876 'Correct name return from get_notice'
879 $not->{message_transport_type} eq 'email',
880 'Correct message_transport_type return from get_notice'
883 $not->{title} eq 'Interlibrary loan request cancelled',
884 'Correct title return from get_notice'
887 $schema->storage->txn_rollback;
891 subtest 'Censorship' => sub {
895 $schema->storage->txn_begin;
897 # Build infrastructure
898 my $backend = Test::MockObject->new;
899 $backend->set_isa('Koha::Illbackends::Mock');
900 $backend->set_always('name', 'Mock');
902 my $config = Test::MockObject->new;
903 $config->set_always('backend_dir', "/tmp");
905 my $illrq = $builder->build({source => 'Illrequest'});
906 my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
907 $illrq_obj->_config($config);
908 $illrq_obj->_backend($backend);
910 $config->set_always('censorship', { censor_notes_staff => 1, censor_reply_date => 0 });
912 my $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564 });
913 is_deeply($censor_out, { foo => 'bar', baz => 564, display_reply_date => 1 },
914 "_censor: not OPAC, reply_date = 1");
916 $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564, opac => 1 });
917 is_deeply($censor_out, {
918 foo => 'bar', baz => 564, censor_notes_staff => 1,
919 display_reply_date => 1, opac => 1
920 }, "_censor: notes_staff = 0, reply_date = 0");
922 $schema->storage->txn_rollback;
925 subtest 'Checking out' => sub {
929 $schema->storage->txn_begin;
931 my $itemtype = $builder->build_object({
932 class => 'Koha::ItemTypes',
937 my $library = $builder->build_object({ class => 'Koha::Libraries' });
938 my $biblio = $builder->build_sample_biblio();
939 my $patron = $builder->build_object({
940 class => 'Koha::Patrons',
941 value => { category_type => 'x' }
943 my $request = $builder->build_object({
944 class => 'Koha::Illrequests',
946 borrowernumber => $patron->borrowernumber,
947 biblio_id => $biblio->biblionumber
951 # First test that calling check_out without a stage param returns
952 # what's required to build the form
953 my $no_stage = $request->check_out();
954 is($no_stage->{method}, 'check_out');
955 is($no_stage->{stage}, 'form');
956 isa_ok($no_stage->{value}, 'HASH');
957 isa_ok($no_stage->{value}->{itemtypes}, 'Koha::ItemTypes');
958 isa_ok($no_stage->{value}->{libraries}, 'Koha::Libraries');
959 isa_ok($no_stage->{value}->{statistical}, 'Koha::Patrons');
960 isa_ok($no_stage->{value}->{biblio}, 'Koha::Biblio');
962 # Now test that form validation works when we supply a 'form' stage
965 my $form_stage_missing_params = $request->check_out({
968 is_deeply($form_stage_missing_params->{value}->{errors}, {
971 # inhouse passed but not a valid patron
972 my $form_stage_bad_patron = $request->check_out({
974 item_type => $itemtype->itemtype,
975 inhouse => 'I_DONT_EXIST'
977 is_deeply($form_stage_bad_patron->{value}->{errors}, {
980 # Too many items attached to biblio
981 my $item1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
982 my $item2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
983 my $form_stage_two_items = $request->check_out({
985 item_type => $itemtype->itemtype,
987 is_deeply($form_stage_two_items->{value}->{errors}, {
991 # Delete the items we created, so we can test that we can create one
995 # We need to mock the user environment for AddIssue
996 t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
999 # First we pass bad parameters to the item creation to test we're
1000 # catching the failure of item creation
1001 my $form_stage_bad_branchcode;
1003 $form_stage_bad_branchcode = $request->check_out({
1005 item_type => $itemtype->itemtype,
1008 } qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/,
1009 "Item creation fails on bad parameters";
1011 is_deeply($form_stage_bad_branchcode->{value}->{errors}, {
1013 },"We get expected failure of item creation");
1015 # Now create a proper item
1016 my $form_stage_good_branchcode = $request->check_out({
1018 item_type => $itemtype->itemtype,
1019 branchcode => $library->branchcode
1021 # By default, this item should not be loanable, so check that we're
1022 # informed of that fact
1024 $form_stage_good_branchcode->{value}->{check_out_errors},
1028 itemtype_notforloan => $itemtype->itemtype
1031 "We get expected error on notforloan of item"
1033 # Delete the item that was created
1034 $biblio->items->delete;
1035 # Now create an itemtype that is loanable
1036 my $itemtype_loanable = $builder->build_object({
1037 class => 'Koha::ItemTypes',
1042 # We need to mock the user environment for AddIssue
1043 t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
1044 my $form_stage_loanable = $request->check_out({
1046 item_type => $itemtype_loanable->itemtype,
1047 branchcode => $library->branchcode
1049 is($form_stage_loanable->{stage}, 'done_check_out');
1050 isa_ok($patron->checkouts, 'Koha::Checkouts');
1051 is($patron->checkouts->count, 1);
1052 is($request->status, 'CHK');
1054 $schema->storage->txn_rollback;
1057 subtest 'Checking Limits' => sub {
1061 $schema->storage->txn_begin;
1063 # Build infrastructure
1064 my $backend = Test::MockObject->new;
1065 $backend->set_isa('Koha::Illbackends::Mock');
1066 $backend->set_always('name', 'Mock');
1068 my $config = Test::MockObject->new;
1069 $config->set_always('backend_dir', "/tmp");
1071 my $illrq = $builder->build({source => 'Illrequest'});
1072 my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
1073 $illrq_obj->_config($config);
1074 $illrq_obj->_backend($backend);
1077 $config->set_series('getLimitRules',
1078 { CPL => { count => 1, method => 'test' } },
1079 { default => { count => 0, method => 'active' } });
1080 is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
1081 { count => 1, method => 'test' },
1082 "getLimits: by value.");
1083 is_deeply($illrq_obj->getLimits({ type => 'branch' }),
1084 { count => 0, method => 'active' },
1085 "getLimits: by default.");
1086 is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
1087 { count => -1, method => 'active' },
1088 "getLimits: by hard-coded.");
1091 is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1092 1, "_limit_counter: Initial branch annual count.");
1093 is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1094 1, "_limit_counter: Initial branch active count.");
1095 is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1096 1, "_limit_counter: Initial patron annual count.");
1097 is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1098 1, "_limit_counter: Initial patron active count.");
1100 source => 'Illrequest',
1102 branchcode => $illrq_obj->branchcode,
1103 borrowernumber => $illrq_obj->borrowernumber,
1106 is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1107 2, "_limit_counter: Add a qualifying request for branch annual count.");
1108 is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1109 2, "_limit_counter: Add a qualifying request for branch active count.");
1110 is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1111 2, "_limit_counter: Add a qualifying request for patron annual count.");
1112 is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1113 2, "_limit_counter: Add a qualifying request for patron active count.");
1115 source => 'Illrequest',
1117 branchcode => $illrq_obj->branchcode,
1118 borrowernumber => $illrq_obj->borrowernumber,
1119 placed => "2005-05-31",
1122 is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1123 2, "_limit_counter: Add an out-of-date branch request.");
1124 is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1125 3, "_limit_counter: Add a qualifying request for branch active count.");
1126 is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1127 2, "_limit_counter: Add an out-of-date patron request.");
1128 is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1129 3, "_limit_counter: Add a qualifying request for patron active count.");
1131 source => 'Illrequest',
1133 branchcode => $illrq_obj->branchcode,
1134 borrowernumber => $illrq_obj->borrowernumber,
1138 is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1139 3, "_limit_counter: Add a qualifying request for branch annual count.");
1140 is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1141 3, "_limit_counter: Add a completed request for branch active count.");
1142 is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1143 3, "_limit_counter: Add a qualifying request for patron annual count.");
1144 is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1145 3, "_limit_counter: Add a completed request for patron active count.");
1149 # We've tested _limit_counter, so all we need to test here is whether the
1150 # current counts of 3 for each work as they should against different
1151 # configuration declarations.
1154 $config->set_always('getLimitRules', undef);
1155 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1156 librarycode => $illrq_obj->branchcode}),
1157 1, "check_limits: no configuration => no limits.");
1160 $config->set_always('getLimitRules',
1161 { $illrq_obj->branchcode => { count => 1, method => 'active' } });
1162 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1163 librarycode => $illrq_obj->branchcode}),
1164 0, "check_limits: branch active limit exceeded.");
1165 $config->set_always('getLimitRules',
1166 { $illrq_obj->branchcode => { count => 1, method => 'annual' } });
1167 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1168 librarycode => $illrq_obj->branchcode}),
1169 0, "check_limits: branch annual limit exceeded.");
1170 $config->set_always('getLimitRules',
1171 { $illrq_obj->branchcode => { count => 4, method => 'active' } });
1172 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1173 librarycode => $illrq_obj->branchcode}),
1174 1, "check_limits: branch active limit OK.");
1175 $config->set_always('getLimitRules',
1176 { $illrq_obj->branchcode => { count => 4, method => 'annual' } });
1177 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1178 librarycode => $illrq_obj->branchcode}),
1179 1, "check_limits: branch annual limit OK.");
1182 $config->set_always('getLimitRules',
1183 { $illrq_obj->patron->categorycode => { count => 1, method => 'active' } });
1184 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1185 librarycode => $illrq_obj->branchcode}),
1186 0, "check_limits: patron category active limit exceeded.");
1187 $config->set_always('getLimitRules',
1188 { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
1189 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1190 librarycode => $illrq_obj->branchcode}),
1191 0, "check_limits: patron category annual limit exceeded.");
1192 $config->set_always('getLimitRules',
1193 { $illrq_obj->patron->categorycode => { count => 4, method => 'active' } });
1194 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1195 librarycode => $illrq_obj->branchcode}),
1196 1, "check_limits: patron category active limit OK.");
1197 $config->set_always('getLimitRules',
1198 { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
1199 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1200 librarycode => $illrq_obj->branchcode}),
1201 1, "check_limits: patron category annual limit OK.");
1203 # One rule cancels the other
1204 $config->set_series('getLimitRules',
1205 # Branch rules allow request
1206 { $illrq_obj->branchcode => { count => 4, method => 'active' } },
1207 # Patron rule forbids it
1208 { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
1209 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1210 librarycode => $illrq_obj->branchcode}),
1211 0, "check_limits: patron category veto overrides branch OK.");
1212 $config->set_series('getLimitRules',
1213 # Branch rules allow request
1214 { $illrq_obj->branchcode => { count => 1, method => 'active' } },
1215 # Patron rule forbids it
1216 { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
1217 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1218 librarycode => $illrq_obj->branchcode}),
1219 0, "check_limits: branch veto overrides patron category OK.");
1221 $schema->storage->txn_rollback;
1224 subtest 'Custom statuses' => sub {
1228 $schema->storage->txn_begin;
1230 my $cat = Koha::AuthorisedValueCategories->search(
1232 category_name => 'ILLSTATUS'
1236 if ($cat->count == 0) {
1237 $cat = $builder->build_object(
1239 class => 'Koha::AuthorisedValueCategory',
1241 category_name => 'ILLSTATUS'
1247 my $av = $builder->build_object(
1249 class => 'Koha::AuthorisedValues',
1251 category => 'ILLSTATUS'
1256 is($av->category, 'ILLSTATUS',
1257 "Successfully created authorised value for custom status");
1259 my $ill_req = $builder->build_object(
1261 class => 'Koha::Illrequests',
1263 status_alias => $av->authorised_value
1267 isa_ok($ill_req->statusalias, 'Koha::AuthorisedValue',
1268 "statusalias correctly returning Koha::AuthorisedValue object");
1270 $ill_req->status("COMP");
1271 is($ill_req->statusalias, undef,
1272 "Koha::Illrequest->status overloading resetting status_alias");
1274 $schema->storage->txn_rollback;
1277 subtest 'Checking in hook' => sub {
1281 $schema->storage->txn_begin;
1283 # Build infrastructure
1284 my $backend = Test::MockObject->new;
1285 $backend->set_isa('Koha::Illbackends::Mock');
1286 $backend->set_always('name', 'Mock');
1288 my $config = Test::MockObject->new;
1289 $config->set_always('backend_dir', "/tmp");
1291 my $item = $builder->build_sample_item();
1292 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1294 t::lib::Mocks::mock_userenv(
1297 branchcode => $patron->branchcode
1301 my $illrq = $builder->build_object(
1303 class => 'Koha::Illrequests',
1305 biblio_id => $item->biblio->biblionumber,
1311 $illrq->_config($config);
1312 $illrq->_backend($backend);
1314 t::lib::Mocks::mock_preference('CirculateILL', 1);
1317 AddIssue( $patron->unblessed, $item->barcode );
1318 # Make the item withdrawn so checking-in is rejected
1319 t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', 1);
1320 $item->set({ withdrawn => 1 })->store;
1321 AddReturn( $item->barcode, $patron->branchcode );
1323 $illrq->discard_changes;
1324 isnt( $illrq->status, 'RET' );
1326 # allow the check-in
1327 $item->set({ withdrawn => 0 })->store;
1328 AddReturn( $item->barcode, $patron->branchcode );
1330 $illrq->discard_changes;
1331 is( $illrq->status, 'RET' );
1333 $schema->storage->txn_rollback;