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"
820 #get_staff_to_address
821 # Mock a KohaAdminEmailAddress syspref
822 t::lib::Mocks::mock_preference(
823 'KohaAdminEmailAddress',
824 'kohaadmin@nowhere.com'
826 # No branch addresses defined and no ILLDefaultStaffEmail, so should
827 # fall back to Koha admin address
828 my $email_kohaadmin = $illrq_obj->get_staff_to_address;
830 $email_kohaadmin eq 'kohaadmin@nowhere.com',
831 'get_staff_to_address falls back to Koha admin in the absence of other alternatives'
833 # General branch address defined, should fall back to that
834 $builder->delete({ source => 'Branch', records => $illbrn });
835 $illbrn = $builder->build({
839 branchemail => 'branch@nowhere.com',
840 branchillemail => "",
844 my $email_gen_branch = $illrq_obj->get_staff_to_address;
846 $email_gen_branch eq 'branch@nowhere.com',
847 'get_staff_to_address falls back to general branch address when defined'
849 # ILL staff syspref address defined, should fall back to that
850 t::lib::Mocks::mock_preference(
851 'ILLDefaultStaffEmail',
852 'illstaff@nowhere.com'
854 my $email_syspref = $illrq_obj->get_staff_to_address;
856 $email_syspref eq 'illstaff@nowhere.com',
857 'get_staff_to_address falls back to ILLDefaultStaffEmail when defined'
859 # Branch ILL address defined, should use that
860 $builder->delete({ source => 'Branch', records => $illbrn });
861 $illbrn = $builder->build({
865 branchemail => 'branch@nowhere.com',
866 branchillemail => 'branchill@nowhere.com',
870 my $email_branch = $illrq_obj->get_staff_to_address;
872 $email_branch eq 'branchill@nowhere.com',
873 'get_staff_to_address uses branch ILL address when defined'
877 # Specify that no staff notices should be send
878 t::lib::Mocks::mock_preference('ILLSendStaffNotices', '');
879 my $return_staff_cancel_fail =
880 $illrq_obj->send_staff_notice('ILL_REQUEST_CANCEL');
882 $return_staff_cancel_fail,
883 { error => 'notice_not_enabled' },
884 "Does not send notices that are not enabled"
886 # Specify that the cancel notice can be sent
887 t::lib::Mocks::mock_preference('ILLSendStaffNotices', 'ILL_REQUEST_CANCEL');
888 my $return_staff_cancel = $illrq_obj->send_staff_notice(
891 my $cancel = $schema->resultset('MessageQueue')->search({
892 letter_code => 'ILL_REQUEST_CANCEL',
893 message_transport_type => 'email',
894 to_address => 'branchill@nowhere.com'
895 })->next()->letter_code;
897 $return_staff_cancel,
898 { success => 'notice_queued' },
899 "Correct return when staff notice created"
902 my $return_staff_fail = $illrq_obj->send_staff_notice();
905 { error => 'notice_no_type' },
906 "Correct error when missing type"
910 my $not = $illrq_obj->get_notice({
911 notice_code => 'ILL_REQUEST_CANCEL',
915 # We test the properties of the hashref separately because the random
916 # hash ordering of the metadata means we can't test the entire thing
919 $not->{module} eq 'ill',
920 'Correct module return from get_notice'
923 $not->{name} eq 'ILL request cancelled',
924 'Correct name return from get_notice'
927 $not->{message_transport_type} eq 'email',
928 'Correct message_transport_type return from get_notice'
931 $not->{title} eq 'Interlibrary loan request cancelled',
932 'Correct title return from get_notice'
935 $schema->storage->txn_rollback;
939 subtest 'Censorship' => sub {
943 $schema->storage->txn_begin;
945 # Build infrastructure
946 my $backend = Test::MockObject->new;
947 $backend->set_isa('Koha::Illbackends::Mock');
948 $backend->set_always('name', 'Mock');
950 my $config = Test::MockObject->new;
951 $config->set_always('backend_dir', "/tmp");
953 my $illrq = $builder->build({source => 'Illrequest'});
954 my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
955 $illrq_obj->_config($config);
956 $illrq_obj->_backend($backend);
958 $config->set_always('censorship', { censor_notes_staff => 1, censor_reply_date => 0 });
960 my $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564 });
961 is_deeply($censor_out, { foo => 'bar', baz => 564, display_reply_date => 1 },
962 "_censor: not OPAC, reply_date = 1");
964 $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564, opac => 1 });
965 is_deeply($censor_out, {
966 foo => 'bar', baz => 564, censor_notes_staff => 1,
967 display_reply_date => 1, opac => 1
968 }, "_censor: notes_staff = 0, reply_date = 0");
970 $schema->storage->txn_rollback;
973 subtest 'Checking out' => sub {
977 $schema->storage->txn_begin;
979 my $itemtype = $builder->build_object({
980 class => 'Koha::ItemTypes',
985 my $library = $builder->build_object({ class => 'Koha::Libraries' });
986 my $biblio = $builder->build_sample_biblio();
987 my $patron = $builder->build_object({
988 class => 'Koha::Patrons',
989 value => { category_type => 'x' }
991 my $request = $builder->build_object({
992 class => 'Koha::Illrequests',
994 borrowernumber => $patron->borrowernumber,
995 biblio_id => $biblio->biblionumber
999 # First test that calling check_out without a stage param returns
1000 # what's required to build the form
1001 my $no_stage = $request->check_out();
1002 is($no_stage->{method}, 'check_out');
1003 is($no_stage->{stage}, 'form');
1004 isa_ok($no_stage->{value}, 'HASH');
1005 isa_ok($no_stage->{value}->{itemtypes}, 'Koha::ItemTypes');
1006 isa_ok($no_stage->{value}->{libraries}, 'Koha::Libraries');
1007 isa_ok($no_stage->{value}->{statistical}, 'Koha::Patrons');
1008 isa_ok($no_stage->{value}->{biblio}, 'Koha::Biblio');
1010 # Now test that form validation works when we supply a 'form' stage
1013 my $form_stage_missing_params = $request->check_out({
1016 is_deeply($form_stage_missing_params->{value}->{errors}, {
1019 # inhouse passed but not a valid patron
1020 my $form_stage_bad_patron = $request->check_out({
1022 item_type => $itemtype->itemtype,
1023 inhouse => 'I_DONT_EXIST'
1025 is_deeply($form_stage_bad_patron->{value}->{errors}, {
1028 # Too many items attached to biblio
1029 my $item1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1030 my $item2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1031 my $form_stage_two_items = $request->check_out({
1033 item_type => $itemtype->itemtype,
1035 is_deeply($form_stage_two_items->{value}->{errors}, {
1039 # Delete the items we created, so we can test that we can create one
1043 # We need to mock the user environment for AddIssue
1044 t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
1047 # First we pass bad parameters to the item creation to test we're
1048 # catching the failure of item creation
1049 my $form_stage_bad_branchcode;
1051 $form_stage_bad_branchcode = $request->check_out({
1053 item_type => $itemtype->itemtype,
1056 } qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/,
1057 "Item creation fails on bad parameters";
1059 is_deeply($form_stage_bad_branchcode->{value}->{errors}, {
1061 },"We get expected failure of item creation");
1063 # Now create a proper item
1064 my $form_stage_good_branchcode = $request->check_out({
1066 item_type => $itemtype->itemtype,
1067 branchcode => $library->branchcode
1069 # By default, this item should not be loanable, so check that we're
1070 # informed of that fact
1072 $form_stage_good_branchcode->{value}->{check_out_errors},
1076 itemtype_notforloan => $itemtype->itemtype
1079 "We get expected error on notforloan of item"
1081 # Delete the item that was created
1082 $biblio->items->delete;
1083 # Now create an itemtype that is loanable
1084 my $itemtype_loanable = $builder->build_object({
1085 class => 'Koha::ItemTypes',
1090 # We need to mock the user environment for AddIssue
1091 t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
1092 my $form_stage_loanable = $request->check_out({
1094 item_type => $itemtype_loanable->itemtype,
1095 branchcode => $library->branchcode
1097 is($form_stage_loanable->{stage}, 'done_check_out');
1098 isa_ok($patron->checkouts, 'Koha::Checkouts');
1099 is($patron->checkouts->count, 1);
1100 is($request->status, 'CHK');
1102 $schema->storage->txn_rollback;
1105 subtest 'Checking Limits' => sub {
1109 $schema->storage->txn_begin;
1111 # Build infrastructure
1112 my $backend = Test::MockObject->new;
1113 $backend->set_isa('Koha::Illbackends::Mock');
1114 $backend->set_always('name', 'Mock');
1116 my $config = Test::MockObject->new;
1117 $config->set_always('backend_dir', "/tmp");
1119 my $illrq = $builder->build({source => 'Illrequest'});
1120 my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
1121 $illrq_obj->_config($config);
1122 $illrq_obj->_backend($backend);
1125 $config->set_series('getLimitRules',
1126 { CPL => { count => 1, method => 'test' } },
1127 { default => { count => 0, method => 'active' } });
1128 is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
1129 { count => 1, method => 'test' },
1130 "getLimits: by value.");
1131 is_deeply($illrq_obj->getLimits({ type => 'branch' }),
1132 { count => 0, method => 'active' },
1133 "getLimits: by default.");
1134 is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
1135 { count => -1, method => 'active' },
1136 "getLimits: by hard-coded.");
1139 is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1140 1, "_limit_counter: Initial branch annual count.");
1141 is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1142 1, "_limit_counter: Initial branch active count.");
1143 is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1144 1, "_limit_counter: Initial patron annual count.");
1145 is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1146 1, "_limit_counter: Initial patron active count.");
1148 source => 'Illrequest',
1150 branchcode => $illrq_obj->branchcode,
1151 borrowernumber => $illrq_obj->borrowernumber,
1154 is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1155 2, "_limit_counter: Add a qualifying request for branch annual count.");
1156 is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1157 2, "_limit_counter: Add a qualifying request for branch active count.");
1158 is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1159 2, "_limit_counter: Add a qualifying request for patron annual count.");
1160 is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1161 2, "_limit_counter: Add a qualifying request for patron active count.");
1163 source => 'Illrequest',
1165 branchcode => $illrq_obj->branchcode,
1166 borrowernumber => $illrq_obj->borrowernumber,
1167 placed => "2005-05-31",
1170 is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1171 2, "_limit_counter: Add an out-of-date branch request.");
1172 is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1173 3, "_limit_counter: Add a qualifying request for branch active count.");
1174 is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1175 2, "_limit_counter: Add an out-of-date patron request.");
1176 is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1177 3, "_limit_counter: Add a qualifying request for patron active count.");
1179 source => 'Illrequest',
1181 branchcode => $illrq_obj->branchcode,
1182 borrowernumber => $illrq_obj->borrowernumber,
1186 is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1187 3, "_limit_counter: Add a qualifying request for branch annual count.");
1188 is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1189 3, "_limit_counter: Add a completed request for branch active count.");
1190 is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1191 3, "_limit_counter: Add a qualifying request for patron annual count.");
1192 is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1193 3, "_limit_counter: Add a completed request for patron active count.");
1197 # We've tested _limit_counter, so all we need to test here is whether the
1198 # current counts of 3 for each work as they should against different
1199 # configuration declarations.
1202 $config->set_always('getLimitRules', undef);
1203 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1204 librarycode => $illrq_obj->branchcode}),
1205 1, "check_limits: no configuration => no limits.");
1208 $config->set_always('getLimitRules',
1209 { $illrq_obj->branchcode => { count => 1, method => 'active' } });
1210 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1211 librarycode => $illrq_obj->branchcode}),
1212 0, "check_limits: branch active limit exceeded.");
1213 $config->set_always('getLimitRules',
1214 { $illrq_obj->branchcode => { count => 1, method => 'annual' } });
1215 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1216 librarycode => $illrq_obj->branchcode}),
1217 0, "check_limits: branch annual limit exceeded.");
1218 $config->set_always('getLimitRules',
1219 { $illrq_obj->branchcode => { count => 4, method => 'active' } });
1220 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1221 librarycode => $illrq_obj->branchcode}),
1222 1, "check_limits: branch active limit OK.");
1223 $config->set_always('getLimitRules',
1224 { $illrq_obj->branchcode => { count => 4, method => 'annual' } });
1225 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1226 librarycode => $illrq_obj->branchcode}),
1227 1, "check_limits: branch annual limit OK.");
1230 $config->set_always('getLimitRules',
1231 { $illrq_obj->patron->categorycode => { count => 1, method => 'active' } });
1232 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1233 librarycode => $illrq_obj->branchcode}),
1234 0, "check_limits: patron category active limit exceeded.");
1235 $config->set_always('getLimitRules',
1236 { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
1237 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1238 librarycode => $illrq_obj->branchcode}),
1239 0, "check_limits: patron category annual limit exceeded.");
1240 $config->set_always('getLimitRules',
1241 { $illrq_obj->patron->categorycode => { count => 4, method => 'active' } });
1242 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1243 librarycode => $illrq_obj->branchcode}),
1244 1, "check_limits: patron category active limit OK.");
1245 $config->set_always('getLimitRules',
1246 { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
1247 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1248 librarycode => $illrq_obj->branchcode}),
1249 1, "check_limits: patron category annual limit OK.");
1251 # One rule cancels the other
1252 $config->set_series('getLimitRules',
1253 # Branch rules allow request
1254 { $illrq_obj->branchcode => { count => 4, method => 'active' } },
1255 # Patron rule forbids it
1256 { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
1257 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1258 librarycode => $illrq_obj->branchcode}),
1259 0, "check_limits: patron category veto overrides branch OK.");
1260 $config->set_series('getLimitRules',
1261 # Branch rules allow request
1262 { $illrq_obj->branchcode => { count => 1, method => 'active' } },
1263 # Patron rule forbids it
1264 { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
1265 is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1266 librarycode => $illrq_obj->branchcode}),
1267 0, "check_limits: branch veto overrides patron category OK.");
1269 $schema->storage->txn_rollback;
1272 subtest 'Custom statuses' => sub {
1276 $schema->storage->txn_begin;
1278 my $cat = Koha::AuthorisedValueCategories->search(
1280 category_name => 'ILLSTATUS'
1284 if ($cat->count == 0) {
1285 $cat = $builder->build_object(
1287 class => 'Koha::AuthorisedValueCategory',
1289 category_name => 'ILLSTATUS'
1295 my $av = $builder->build_object(
1297 class => 'Koha::AuthorisedValues',
1299 category => 'ILLSTATUS'
1304 is($av->category, 'ILLSTATUS',
1305 "Successfully created authorised value for custom status");
1307 my $ill_req = $builder->build_object(
1309 class => 'Koha::Illrequests',
1311 status_alias => $av->authorised_value
1315 isa_ok($ill_req->statusalias, 'Koha::AuthorisedValue',
1316 "statusalias correctly returning Koha::AuthorisedValue object");
1318 $ill_req->status("COMP");
1319 is($ill_req->statusalias, undef,
1320 "Koha::Illrequest->status overloading resetting status_alias");
1322 $schema->storage->txn_rollback;
1325 subtest 'Checking in hook' => sub {
1329 $schema->storage->txn_begin;
1331 # Build infrastructure
1332 my $backend = Test::MockObject->new;
1333 $backend->set_isa('Koha::Illbackends::Mock');
1334 $backend->set_always('name', 'Mock');
1336 my $config = Test::MockObject->new;
1337 $config->set_always('backend_dir', "/tmp");
1339 my $item = $builder->build_sample_item();
1340 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1342 t::lib::Mocks::mock_userenv(
1345 branchcode => $patron->branchcode
1349 my $illrq = $builder->build_object(
1351 class => 'Koha::Illrequests',
1353 biblio_id => $item->biblio->biblionumber,
1359 $illrq->_config($config);
1360 $illrq->_backend($backend);
1362 t::lib::Mocks::mock_preference('CirculateILL', 1);
1365 AddIssue( $patron->unblessed, $item->barcode );
1366 # Make the item withdrawn so checking-in is rejected
1367 t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', 1);
1368 $item->set({ withdrawn => 1 })->store;
1369 AddReturn( $item->barcode, $patron->branchcode );
1371 $illrq->discard_changes;
1372 isnt( $illrq->status, 'RET' );
1374 # allow the check-in
1375 $item->set({ withdrawn => 0 })->store;
1376 AddReturn( $item->barcode, $patron->branchcode );
1378 $illrq->discard_changes;
1379 is( $illrq->status, 'RET' );
1381 $schema->storage->txn_rollback;