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 Test::More tests => 23; # +1
23 use C4::Biblio qw( AddBiblio ModBiblio ModBiblioMarc );
24 use C4::Circulation qw( AddIssue AddReturn );
28 use Koha::Acquisition::Orders;
29 use Koha::AuthorisedValueCategories;
30 use Koha::AuthorisedValues;
31 use Koha::MarcSubfieldStructures;
37 use t::lib::TestBuilder;
42 use_ok('Koha::Biblio');
43 use_ok('Koha::Biblios');
46 my $schema = Koha::Database->new->schema;
47 my $builder = t::lib::TestBuilder->new;
49 subtest 'metadata() tests' => sub {
53 $schema->storage->txn_begin;
55 my $title = 'Oranges and Peaches';
57 my $record = MARC::Record->new();
58 my $field = MARC::Field->new('245','','','a' => $title);
59 $record->append_fields( $field );
60 my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
62 my $biblio = Koha::Biblios->find( $biblionumber );
63 is( ref $biblio, 'Koha::Biblio', 'Found a Koha::Biblio object' );
65 my $metadata = $biblio->metadata;
66 is( ref $metadata, 'Koha::Biblio::Metadata', 'Method metadata() returned a Koha::Biblio::Metadata object' );
68 my $record2 = $metadata->record;
69 is( ref $record2, 'MARC::Record', 'Method record() returned a MARC::Record object' );
71 is( $record2->field('245')->subfield("a"), $title, 'Title in 245$a matches title from original record object' );
73 $schema->storage->txn_rollback;
76 subtest 'hidden_in_opac() tests' => sub {
80 $schema->storage->txn_begin;
82 my $biblio = $builder->build_sample_biblio();
83 my $rules = { withdrawn => [ 2 ] };
85 t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 0 );
88 !$biblio->hidden_in_opac({ rules => $rules }),
89 'Biblio not hidden if there is no item attached (!OpacHiddenItemsHidesRecord)'
92 t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 1 );
95 !$biblio->hidden_in_opac({ rules => $rules }),
96 'Biblio not hidden if there is no item attached (OpacHiddenItemsHidesRecord)'
99 my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
100 my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
102 $item_1->withdrawn( 1 )->store->discard_changes;
103 $item_2->withdrawn( 1 )->store->discard_changes;
105 ok( !$biblio->hidden_in_opac({ rules => $rules }), 'Biblio not hidden' );
107 $item_2->withdrawn( 2 )->store->discard_changes;
108 $biblio->discard_changes; # refresh
110 ok( !$biblio->hidden_in_opac({ rules => $rules }), 'Biblio not hidden' );
112 $item_1->withdrawn( 2 )->store->discard_changes;
113 $biblio->discard_changes; # refresh
115 ok( $biblio->hidden_in_opac({ rules => $rules }), 'Biblio hidden' );
117 t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 0 );
119 !$biblio->hidden_in_opac( { rules => $rules } ),
120 'Biblio hidden (!OpacHiddenItemsHidesRecord)'
124 $schema->storage->txn_rollback;
127 subtest 'items() tests' => sub {
131 $schema->storage->txn_begin;
133 my $biblio = $builder->build_sample_biblio();
135 is( $biblio->items->count, 0, 'No items, count is 0' );
137 my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
138 my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
140 my $items = $biblio->items;
141 is( ref($items), 'Koha::Items', 'Returns a Koha::Items resultset' );
142 is( $items->count, 2, 'Two items in resultset' );
144 $schema->storage->txn_rollback;
148 subtest 'get_coins and get_openurl' => sub {
152 $schema->storage->txn_begin;
154 my $builder = t::lib::TestBuilder->new;
155 my $biblio = $builder->build_sample_biblio({
161 'ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=book&rft.btitle=Title%201&rft.au=Author%201',
162 'GetCOinsBiblio returned right metadata'
165 my $record = MARC::Record->new();
166 $record->append_fields( MARC::Field->new('100','','','a' => 'Author 2'), MARC::Field->new('880','','','a' => 'Something') );
167 my ( $biblionumber ) = C4::Biblio::AddBiblio($record, '');
168 my $biblio_no_title = Koha::Biblios->find($biblionumber);
170 $biblio_no_title->get_coins,
171 'ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=book&rft.au=Author%202',
172 'GetCOinsBiblio returned right metadata if biblio does not have a title'
175 t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/");
177 $biblio->get_openurl,
178 'https://koha.example.com/?ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=book&rft.btitle=Title%201&rft.au=Author%201',
179 'Koha::Biblio->get_openurl returned right URL'
182 t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/?client_id=ci1");
184 $biblio->get_openurl,
185 'https://koha.example.com/?client_id=ci1&ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=book&rft.btitle=Title%201&rft.au=Author%201',
186 'Koha::Biblio->get_openurl returned right URL'
189 $schema->storage->txn_rollback;
192 subtest 'is_serial() tests' => sub {
196 $schema->storage->txn_begin;
198 my $biblio = $builder->build_sample_biblio();
200 $biblio->serial( 1 )->store->discard_changes;
201 ok( $biblio->is_serial, 'Bibliographic record is serial' );
203 $biblio->serial( 0 )->store->discard_changes;
204 ok( !$biblio->is_serial, 'Bibliographic record is not serial' );
206 my $record = $biblio->metadata->record;
207 $record->leader('00142nas a22 7a 4500');
208 ModBiblio($record, $biblio->biblionumber );
209 $biblio = Koha::Biblios->find($biblio->biblionumber);
211 ok( $biblio->is_serial, 'Bibliographic record is serial' );
213 $schema->storage->txn_rollback;
216 subtest 'pickup_locations' => sub {
219 $schema->storage->txn_begin;
221 Koha::CirculationRules->search->delete;
222 Koha::CirculationRules->set_rules(
224 categorycode => undef,
228 reservesallowed => 25,
233 my $root1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
234 my $root2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
235 my $root3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
237 my $library1 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'zzz' } } );
238 my $library2 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'AAA' } } );
239 my $library3 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'FFF' } } );
240 my $library4 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'CCC' } } );
241 my $library5 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'eee' } } );
242 my $library6 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'BBB' } } );
243 my $library7 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'DDD' } } );
244 my $library8 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'GGG' } } );
246 our @branchcodes = map { $_->branchcode } ($library1, $library2, $library3, $library4, $library5, $library6, $library7, $library8);
248 Koha::CirculationRules->set_rules(
250 branchcode => $library1->branchcode,
253 holdallowed => 'from_home_library',
254 hold_fulfillment_policy => 'any',
255 returnbranch => 'any'
260 Koha::CirculationRules->set_rules(
262 branchcode => $library2->branchcode,
265 holdallowed => 'from_local_hold_group',
266 hold_fulfillment_policy => 'holdgroup',
267 returnbranch => 'any'
272 Koha::CirculationRules->set_rules(
274 branchcode => $library3->branchcode,
277 holdallowed => 'from_local_hold_group',
278 hold_fulfillment_policy => 'patrongroup',
279 returnbranch => 'any'
284 Koha::CirculationRules->set_rules(
286 branchcode => $library4->branchcode,
289 holdallowed => 'from_any_library',
290 hold_fulfillment_policy => 'holdingbranch',
291 returnbranch => 'any'
296 Koha::CirculationRules->set_rules(
298 branchcode => $library5->branchcode,
301 holdallowed => 'from_any_library',
302 hold_fulfillment_policy => 'homebranch',
303 returnbranch => 'any'
308 Koha::CirculationRules->set_rules(
310 branchcode => $library6->branchcode,
313 holdallowed => 'from_home_library',
314 hold_fulfillment_policy => 'holdgroup',
315 returnbranch => 'any'
320 Koha::CirculationRules->set_rules(
322 branchcode => $library7->branchcode,
325 holdallowed => 'from_local_hold_group',
326 hold_fulfillment_policy => 'holdingbranch',
327 returnbranch => 'any'
333 Koha::CirculationRules->set_rules(
335 branchcode => $library8->branchcode,
338 holdallowed => 'from_any_library',
339 hold_fulfillment_policy => 'patrongroup',
340 returnbranch => 'any'
345 my $group1_1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library1->branchcode } } );
346 my $group1_2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library2->branchcode } } );
348 my $group2_3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library3->branchcode } } );
349 my $group2_4 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library4->branchcode } } );
351 my $group3_5 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library5->branchcode } } );
352 my $group3_6 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library6->branchcode } } );
353 my $group3_7 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library7->branchcode } } );
354 my $group3_8 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library8->branchcode } } );
356 my $biblio1 = $builder->build_sample_biblio({ title => '1' });
357 my $biblio2 = $builder->build_sample_biblio({ title => '2' });
359 my $item1_1 = $builder->build_sample_item({
360 biblionumber => $biblio1->biblionumber,
361 homebranch => $library1->branchcode,
362 holdingbranch => $library2->branchcode,
365 my $item1_3 = $builder->build_sample_item({
366 biblionumber => $biblio1->biblionumber,
367 homebranch => $library3->branchcode,
368 holdingbranch => $library4->branchcode,
371 my $item1_7 = $builder->build_sample_item({
372 biblionumber => $biblio1->biblionumber,
373 homebranch => $library7->branchcode,
374 holdingbranch => $library4->branchcode,
377 my $item2_2 = $builder->build_sample_item({
378 biblionumber => $biblio2->biblionumber,
379 homebranch => $library2->branchcode,
380 holdingbranch => $library1->branchcode,
383 my $item2_4 = $builder->build_sample_item({
384 biblionumber => $biblio2->biblionumber,
385 homebranch => $library4->branchcode,
386 holdingbranch => $library3->branchcode,
389 my $item2_6 = $builder->build_sample_item({
390 biblionumber => $biblio2->biblionumber,
391 homebranch => $library6->branchcode,
392 holdingbranch => $library4->branchcode,
395 my $patron1 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'1', branchcode => $library1->branchcode } } );
396 my $patron8 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'8', branchcode => $library8->branchcode } } );
399 "ItemHomeLibrary-1-1" => 6,
400 "ItemHomeLibrary-1-8" => 1,
401 "ItemHomeLibrary-2-1" => 2,
402 "ItemHomeLibrary-2-8" => 0,
403 "PatronLibrary-1-1" => 6,
404 "PatronLibrary-1-8" => 3,
405 "PatronLibrary-2-1" => 0,
406 "PatronLibrary-2-8" => 3,
410 my ( $cbranch, $biblio, $patron, $results ) = @_;
411 t::lib::Mocks::mock_preference('ReservesControlBranch', $cbranch);
414 my $pickup_location = $_;
415 grep { $pickup_location->branchcode eq $_ } @branchcodes
416 } $biblio->pickup_locations( { patron => $patron } )->as_list;
419 scalar(@pl) == $results->{ $cbranch . '-'
420 . $biblio->title . '-'
421 . $patron->firstname },
422 'ReservesControlBranch: '
429 . $results->{ $cbranch . '-'
430 . $biblio->title . '-'
431 . $patron->firstname }
437 foreach my $cbranch ('ItemHomeLibrary','PatronLibrary') {
438 foreach my $biblio ($biblio1, $biblio2) {
439 foreach my $patron ($patron1, $patron8) {
440 _doTest($cbranch, $biblio, $patron, $results);
445 my @pl_names = map { $_->branchname } $biblio1->pickup_locations( { patron => $patron1 } )->as_list;
446 my $pl_ori_str = join('|', @pl_names);
447 my $pl_sorted_str = join('|', sort { lc($a) cmp lc($b) } @pl_names);
449 $pl_ori_str eq $pl_sorted_str,
450 'Libraries must be sorted by name'
452 $schema->storage->txn_rollback;
455 subtest 'to_api() tests' => sub {
457 $schema->storage->txn_begin;
459 my $biblio = $builder->build_sample_biblio();
460 my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
462 my $biblioitem_api = $biblio->biblioitem->to_api;
463 my $biblio_api = $biblio->to_api;
465 plan tests => (scalar keys %{ $biblioitem_api }) + 1;
467 foreach my $key ( keys %{ $biblioitem_api } ) {
468 is( $biblio_api->{$key}, $biblioitem_api->{$key}, "$key is added to the biblio object" );
471 $biblio_api = $biblio->to_api({ embed => { items => {} } });
472 is_deeply( $biblio_api->{items}, [ $item->to_api ], 'Item correctly embedded' );
474 $schema->storage->txn_rollback;
477 subtest 'suggestions() tests' => sub {
481 $schema->storage->txn_begin;
483 my $biblio = $builder->build_sample_biblio();
485 is( ref($biblio->suggestions), 'Koha::Suggestions', 'Return type is correct' );
488 $biblio->suggestions->unblessed,
490 '->suggestions returns an empty Koha::Suggestions resultset'
493 my $suggestion = $builder->build_object(
495 class => 'Koha::Suggestions',
496 value => { biblionumber => $biblio->biblionumber }
500 my $suggestions = $biblio->suggestions->unblessed;
503 $biblio->suggestions->unblessed,
504 [ $suggestion->unblessed ],
505 '->suggestions returns the related Koha::Suggestion objects'
508 $schema->storage->txn_rollback;
511 subtest 'get_marc_components() tests' => sub {
515 $schema->storage->txn_begin;
517 my ($host_bibnum) = C4::Biblio::AddBiblio(host_record(), '');
518 my $host_biblio = Koha::Biblios->find($host_bibnum);
519 t::lib::Mocks::mock_preference( 'SearchEngine', 'Zebra' );
520 my $search_mod = Test::MockModule->new( 'Koha::SearchEngine::Zebra::Search' );
521 $search_mod->mock( 'search_compat', \&search_component_record2 );
523 my $components = $host_biblio->get_marc_components;
524 is( ref($components), 'ARRAY', 'Return type is correct' );
529 '->get_marc_components returns an empty ARRAY'
532 $search_mod->unmock( 'search_compat');
533 $search_mod->mock( 'search_compat', \&search_component_record1 );
534 my $component_record = component_record1()->as_xml();
537 $host_biblio->get_marc_components,
539 '->get_marc_components returns the related component part record'
541 $search_mod->unmock( 'search_compat');
543 $search_mod->mock( 'search_compat',
544 sub { Koha::Exception->throw("error searching analytics") }
546 warning_like { $components = $host_biblio->get_marc_components }
547 qr{Warning from search_compat: .* 'error searching analytics'};
550 $host_biblio->object_messages,
554 message => 'component_search',
555 payload => "Exception 'Koha::Exception' thrown 'error searching analytics'\n"
559 $search_mod->unmock( 'search_compat');
561 $schema->storage->txn_rollback;
564 subtest 'get_components_query' => sub {
567 my $biblio = $builder->build_sample_biblio();
568 my $biblionumber = $biblio->biblionumber;
569 my $record = $biblio->metadata->record;
571 foreach my $engine ('Zebra','Elasticsearch'){
572 t::lib::Mocks::mock_preference( 'SearchEngine', $engine );
574 t::lib::Mocks::mock_preference( 'UseControlNumber', '0' );
575 t::lib::Mocks::mock_preference( 'ComponentSortField', 'author' );
576 t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'za' );
577 my ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
578 is($comp_query_str, 'Host-item:("Some boring read")', "$engine: UseControlNumber disabled");
579 is($comp_sort, "author_za", "$engine: UseControlNumber disabled sort is correct");
581 t::lib::Mocks::mock_preference( 'UseControlNumber', '1' );
582 t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'az' );
583 my $marc_001_field = MARC::Field->new('001', $biblionumber);
584 $record->append_fields($marc_001_field);
585 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
586 $biblio = Koha::Biblios->find( $biblio->biblionumber);
588 ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
589 is($comp_query_str, "(rcn:\"$biblionumber\" AND (bib-level:a OR bib-level:b))", "$engine: UseControlNumber enabled without MarcOrgCode");
590 is($comp_sort, "author_az", "$engine: UseControlNumber enabled without MarcOrgCode sort is correct");
592 my $marc_003_field = MARC::Field->new('003', 'OSt');
593 $record->append_fields($marc_003_field);
594 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
595 $biblio = Koha::Biblios->find( $biblio->biblionumber);
597 t::lib::Mocks::mock_preference( 'ComponentSortField', 'title' );
598 t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'asc' );
599 ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
600 is($comp_query_str, "(((rcn:\"$biblionumber\" AND cni:\"OSt\") OR rcn:\"OSt $biblionumber\") AND (bib-level:a OR bib-level:b))", "$engine: UseControlNumber enabled with MarcOrgCode");
601 is($comp_sort, "title_asc", "$engine: UseControlNumber enabled with MarcOrgCode sort if correct");
602 $record->delete_field($marc_003_field);
606 subtest 'orders() and active_orders() tests' => sub {
610 $schema->storage->txn_begin;
612 my $biblio = $builder->build_sample_biblio();
614 my $orders = $biblio->orders;
615 my $active_orders = $biblio->active_orders;
617 is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
618 is( $biblio->orders->count, $biblio->active_orders->count, '->orders->count returns the count for the resultset' );
620 # Add a couple orders
622 $builder->build_object(
624 class => 'Koha::Acquisition::Orders',
626 biblionumber => $biblio->biblionumber,
627 datecancellationprinted => '2019-12-31'
633 $builder->build_object(
635 class => 'Koha::Acquisition::Orders',
637 biblionumber => $biblio->biblionumber,
638 datecancellationprinted => undef
643 $orders = $biblio->orders;
644 $active_orders = $biblio->active_orders;
646 is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
647 is( ref($active_orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
648 is( $orders->count, $active_orders->count + 2, '->active_orders->count returns the rigt count' );
650 $schema->storage->txn_rollback;
653 subtest 'tickets() tests' => sub {
657 $schema->storage->txn_begin;
659 my $biblio = $builder->build_sample_biblio();
660 my $tickets = $biblio->tickets;
661 is( ref($tickets), 'Koha::Tickets', 'Koha::Biblio->tickets should return a Koha::Tickets object' );
662 is( $tickets->count, 0, 'Koha::Biblio->tickets should return a count of 0 when there are no related tickets' );
666 $builder->build_object(
668 class => 'Koha::Tickets',
669 value => { biblio_id => $biblio->biblionumber }
674 $tickets = $biblio->tickets;
675 is( ref($tickets), 'Koha::Tickets', 'Koha::Biblio->tickets should return a Koha::Tickets object' );
676 is( $tickets->count, 2, 'Koha::Biblio->tickets should return the correct number of tickets' );
678 $schema->storage->txn_rollback;
681 subtest 'subscriptions() tests' => sub {
685 $schema->storage->txn_begin;
687 my $biblio = $builder->build_sample_biblio;
689 my $subscriptions = $biblio->subscriptions;
690 is( ref($subscriptions), 'Koha::Subscriptions',
691 'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
693 is( $subscriptions->count, 0, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
695 # Add two subscriptions
697 $builder->build_object(
699 class => 'Koha::Subscriptions',
700 value => { biblionumber => $biblio->biblionumber }
705 $subscriptions = $biblio->subscriptions;
706 is( ref($subscriptions), 'Koha::Subscriptions',
707 'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
709 is( $subscriptions->count, 2, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
711 $schema->storage->txn_rollback;
714 subtest 'get_marc_notes() MARC21 tests' => sub {
717 $schema->storage->txn_begin;
719 t::lib::Mocks::mock_preference( 'NotesToHide', '520' );
721 my $av = $builder->build_object( { class => 'Koha::AuthorisedValues' } );
723 my $biblio = $builder->build_sample_biblio;
724 my $record = $biblio->metadata->record;
725 $record->append_fields(
726 MARC::Field->new( '500', '', '', a => 'Note1' ),
727 MARC::Field->new( '505', '', '', a => 'Note2', u => 'http://someserver.com' ),
728 MARC::Field->new( '520', '', '', a => 'Note3 skipped' ),
729 MARC::Field->new( '541', '0', '', a => 'Note4 skipped on opac' ),
730 MARC::Field->new( '544', '', '', a => 'Note5' ),
731 MARC::Field->new( '590', '', '', a => $av->authorised_value ),
732 MARC::Field->new( '545', '', '', a => 'Invisible on OPAC' ),
735 my $mss = Koha::MarcSubfieldStructures->find({tagfield => "590", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
736 $mss->update({ authorised_value => $av->category });
738 $mss = Koha::MarcSubfieldStructures->find({tagfield => "545", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
739 $mss->update({ hidden => 1 });
741 my $cache = Koha::Caches->get_instance;
742 $cache->clear_from_cache("MarcStructure-0-");
743 $cache->clear_from_cache("MarcStructure-1-");
744 $cache->clear_from_cache("MarcSubfieldStructure-");
745 $cache->clear_from_cache("MarcCodedFields-");
747 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
748 $biblio = Koha::Biblios->find( $biblio->biblionumber);
750 my $notes = $biblio->get_marc_notes;
751 is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
752 is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
753 is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
754 is( $notes->[3]->{marcnote}, 'Note4 skipped on opac',"Note shows if not opac (Hidden by Indicator)" );
755 is( $notes->[4]->{marcnote}, 'Note5', 'Fifth note' );
756 is( $notes->[5]->{marcnote}, $av->lib, 'Authorised value is correctly parsed to show description rather than code' );
757 is( $notes->[6]->{marcnote}, 'Invisible on OPAC', 'Note shows if not opac (Hidden by framework)' );
758 is( @$notes, 7, 'No more notes' );
759 $notes = $biblio->get_marc_notes({ opac => 1 });
760 is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
761 is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
762 is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
763 is( $notes->[3]->{marcnote}, 'Note5', 'Fifth note shows after fourth skipped' );
764 is( $notes->[4]->{marcnote}, $av->lib_opac, 'Authorised value is correctly parsed for OPAC to show description rather than code' );
765 is( @$notes, 5, 'No more notes' );
767 $cache->clear_from_cache("MarcStructure-0-");
768 $cache->clear_from_cache("MarcStructure-1-");
769 $cache->clear_from_cache("MarcSubfieldStructure-");
770 $cache->clear_from_cache("MarcCodedFields-");
772 $schema->storage->txn_rollback;
775 subtest 'get_marc_notes() UNIMARC tests' => sub {
778 $schema->storage->txn_begin;
780 t::lib::Mocks::mock_preference( 'NotesToHide', '310' );
781 t::lib::Mocks::mock_preference( 'marcflavour', 'UNIMARC' );
783 my $biblio = $builder->build_sample_biblio;
784 my $record = $biblio->metadata->record;
785 $record->append_fields(
786 MARC::Field->new( '300', '', '', a => 'Note1' ),
787 MARC::Field->new( '300', '', '', a => 'Note2' ),
788 MARC::Field->new( '310', '', '', a => 'Note3 skipped' ),
790 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
791 $biblio = Koha::Biblios->find( $biblio->biblionumber);
792 my $notes = $biblio->get_marc_notes({ marcflavour => 'UNIMARC' });
793 is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
794 is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
795 is( @$notes, 2, 'No more notes' );
797 t::lib::Mocks::mock_preference( 'marcflavour', 'MARC21' );
798 $schema->storage->txn_rollback;
801 subtest 'host_items() tests' => sub {
804 $schema->storage->txn_begin;
806 my $biblio = $builder->build_sample_biblio( { frameworkcode => '' } );
808 t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 1 );
809 my $host_items = $biblio->host_items;
810 is( ref($host_items), 'Koha::Items' );
811 is( $host_items->count, 0 );
814 $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
815 my $host_item_1 = $builder->build_sample_item;
816 my $host_item_2 = $builder->build_sample_item;
818 my $record = $biblio->metadata->record;
819 $record->append_fields(
822 9 => $host_item_1->itemnumber,
823 9 => $host_item_2->itemnumber
826 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
827 $biblio = $biblio->get_from_storage;
828 $host_items = $biblio->host_items;
829 is( $host_items->count, 2 );
830 is_deeply( [ $host_items->get_column('itemnumber') ],
831 [ $host_item_1->itemnumber, $host_item_2->itemnumber ] );
833 t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 0 );
834 $host_items = $biblio->host_items;
835 is( ref($host_items), 'Koha::Items' );
836 is( $host_items->count, 0 );
838 $schema->storage->txn_rollback;
841 subtest 'article_requests() tests' => sub {
845 $schema->storage->txn_begin;
847 my $item = $builder->build_sample_item;
848 my $biblio = $item->biblio;
850 my $article_requests = $biblio->article_requests;
851 is( ref($article_requests), 'Koha::ArticleRequests',
852 'In scalar context, type is correct' );
853 is( $article_requests->count, 0, 'No article requests' );
855 foreach my $i ( 0 .. 3 ) {
857 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
859 Koha::ArticleRequest->new(
861 borrowernumber => $patron->id,
862 biblionumber => $biblio->id,
863 itemnumber => $item->id,
864 title => $biblio->title,
869 $article_requests = $biblio->article_requests;
870 is( $article_requests->count, 4, '4 article requests' );
872 $schema->storage->txn_rollback;
875 subtest 'current_checkouts() and old_checkouts() tests' => sub {
879 $schema->storage->txn_begin;
881 my $library = $builder->build_object({ class => 'Koha::Libraries' });
883 my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' })->unblessed;
884 my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' })->unblessed;
886 my $item_1 = $builder->build_sample_item;
887 my $item_2 = $builder->build_sample_item({ biblionumber => $item_1->biblionumber });
889 t::lib::Mocks::mock_userenv({ branchcode => $library->id });
891 AddIssue( $patron_1, $item_1->barcode );
892 AddIssue( $patron_1, $item_2->barcode );
894 AddReturn( $item_1->barcode );
895 AddIssue( $patron_2, $item_1->barcode );
897 my $biblio = $item_1->biblio;
898 my $current_checkouts = $biblio->current_checkouts;
899 my $old_checkouts = $biblio->old_checkouts;
901 is( ref($current_checkouts), 'Koha::Checkouts', 'Type is correct' );
902 is( ref($old_checkouts), 'Koha::Old::Checkouts', 'Type is correct' );
904 is( $current_checkouts->count, 2, 'Count is correct for current checkouts' );
905 is( $old_checkouts->count, 1, 'Count is correct for old checkouts' );
907 $schema->storage->txn_rollback;
910 subtest 'get_marc_contributors() tests' => sub {
914 $schema->storage->txn_begin;
916 my $biblio = $builder->build_sample_biblio({ author => 'Main author' });
917 my $record = $biblio->metadata->record;
919 # add author information
920 my $field = MARC::Field->new('700','1','','a' => 'Jefferson, Thomas');
921 $record->append_fields($field);
922 $field = MARC::Field->new('701','1','','d' => 'Secondary author 2');
923 $record->append_fields($field);
926 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
927 $biblio = Koha::Biblios->find( $biblio->biblionumber );
929 is( @{$biblio->get_marc_authors}, 3, 'get_marc_authors retrieves correct number of author subfields' );
930 is( @{$biblio->get_marc_contributors}, 2, 'get_marc_contributors retrieves correct number of author subfields' );
931 $schema->storage->txn_rollback;
934 subtest 'Recalls tests' => sub {
938 $schema->storage->txn_begin;
940 my $item1 = $builder->build_sample_item;
941 my $biblio = $item1->biblio;
942 my $branchcode = $item1->holdingbranch;
943 my $patron1 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
944 my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
945 my $patron3 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
946 my $item2 = $builder->build_object({ class => 'Koha::Items', value => { holdingbranch => $branchcode, homebranch => $branchcode, biblionumber => $biblio->biblionumber, itype => $item1->effective_itemtype } });
947 t::lib::Mocks::mock_userenv({ patron => $patron1 });
949 my $recall1 = Koha::Recall->new(
950 { patron_id => $patron1->borrowernumber,
951 created_date => \'NOW()',
952 biblio_id => $biblio->biblionumber,
953 pickup_library_id => $branchcode,
954 item_id => $item1->itemnumber,
955 expiration_date => undef,
959 my $recall2 = Koha::Recall->new(
960 { patron_id => $patron2->borrowernumber,
961 created_date => \'NOW()',
962 biblio_id => $biblio->biblionumber,
963 pickup_library_id => $branchcode,
965 expiration_date => undef,
969 my $recall3 = Koha::Recall->new(
970 { patron_id => $patron3->borrowernumber,
971 created_date => \'NOW()',
972 biblio_id => $biblio->biblionumber,
973 pickup_library_id => $branchcode,
974 item_id => $item1->itemnumber,
975 expiration_date => undef,
980 my $recalls = $biblio->recalls;
981 is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
983 $recall1->set_cancelled;
984 $recall2->set_expired({ interface => 'COMMANDLINE' });
986 is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
987 is( $recalls->filter_by_current->count, 1, 'Correctly get number of active recalls for biblio' );
989 t::lib::Mocks::mock_preference('UseRecalls', 0);
990 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with UseRecalls disabled" );
992 t::lib::Mocks::mock_preference("UseRecalls", 1);
993 $item1->update({ notforloan => 1 });
994 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with no available items" );
996 $item1->update({ notforloan => 0 });
997 Koha::CirculationRules->set_rules({
998 branchcode => $branchcode,
999 categorycode => $patron1->categorycode,
1000 itemtype => $item1->effective_itemtype,
1002 recalls_allowed => 0,
1003 recalls_per_record => 1,
1004 on_shelf_recalls => 'all',
1007 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if recalls_allowed = 0" );
1009 Koha::CirculationRules->set_rules({
1010 branchcode => $branchcode,
1011 categorycode => $patron1->categorycode,
1012 itemtype => $item1->effective_itemtype,
1014 recalls_allowed => 1,
1015 recalls_per_record => 1,
1016 on_shelf_recalls => 'all',
1019 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_allowed" );
1020 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_per_record" );
1022 $recall1->set_cancelled;
1023 C4::Circulation::AddIssue( $patron1->unblessed, $item2->barcode );
1024 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has already checked out an item attached to this biblio" );
1026 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if on_shelf_recalls = all and items are still available" );
1028 Koha::CirculationRules->set_rules({
1029 branchcode => $branchcode,
1030 categorycode => $patron1->categorycode,
1031 itemtype => $item1->effective_itemtype,
1033 recalls_allowed => 1,
1034 recalls_per_record => 1,
1035 on_shelf_recalls => 'any',
1038 C4::Circulation::AddReturn( $item2->barcode, $branchcode );
1039 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if no items are checked out" );
1041 $recall2->set_cancelled;
1042 C4::Circulation::AddIssue( $patron2->unblessed, $item2->barcode );
1043 C4::Circulation::AddIssue( $patron2->unblessed, $item1->barcode );
1044 is( $biblio->can_be_recalled({ patron => $patron1 }), 2, "Can recall two items" );
1046 $item1->update({ withdrawn => 1 });
1047 is( $biblio->can_be_recalled({ patron => $patron1 }), 1, "Can recall one item" );
1049 $schema->storage->txn_rollback;
1052 subtest 'item_groups() tests' => sub {
1056 $schema->storage->txn_begin;
1058 my $biblio = $builder->build_sample_biblio();
1060 my @item_groups = $biblio->item_groups->as_list;
1061 is( scalar(@item_groups), 0, 'Got zero item groups');
1063 my $item_group_1 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1065 @item_groups = $biblio->item_groups->as_list;
1066 is( scalar(@item_groups), 1, 'Got one item group');
1067 is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group');
1069 my $item_group_2 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1071 @item_groups = $biblio->item_groups->as_list;
1072 is( scalar(@item_groups), 2, 'Got two item groups');
1073 is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group 1');
1074 is( $item_groups[1]->id, $item_group_2->id, 'Got correct item group 2');
1076 $schema->storage->txn_rollback;
1079 sub component_record1 {
1080 my $marc = MARC::Record->new;
1081 $marc->append_fields(
1082 MARC::Field->new( '001', '3456' ),
1083 MARC::Field->new( '245', '', '', a => 'Some title 1' ),
1084 MARC::Field->new( '773', '', '', w => '(FIRST)1234' ),
1088 sub search_component_record1 {
1089 my @results = ( component_record1()->as_xml() );
1090 return ( undef, { biblioserver => { RECORDS => \@results, hits => 1 } }, 1 );
1093 sub search_component_record2 {
1095 return ( undef, { biblioserver => { RECORDS => \@results, hits => 0 } }, 0 );
1099 my $marc = MARC::Record->new;
1100 $marc->append_fields(
1101 MARC::Field->new( '001', '1234' ),
1102 MARC::Field->new( '003', 'FIRST' ),
1103 MARC::Field->new( '245', '', '', a => 'Some title' ),