Bug 22690: (QA follow-up) Add TrackedLink classes and use them
[koha.git] / t / db_dependent / Koha / Item.t
1 #!/usr/bin/perl
2
3 # Copyright 2019 Koha Development team
4 #
5 # This file is part of Koha
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use Test::More tests => 12;
23 use Test::Exception;
24
25 use C4::Biblio qw( GetMarcSubfieldStructure );
26 use C4::Circulation qw( AddIssue AddReturn );
27
28 use Koha::Items;
29 use Koha::Database;
30 use Koha::DateUtils;
31 use Koha::Old::Items;
32
33 use List::MoreUtils qw(all);
34
35 use t::lib::TestBuilder;
36 use t::lib::Mocks;
37
38 my $schema  = Koha::Database->new->schema;
39 my $builder = t::lib::TestBuilder->new;
40
41 subtest 'tracked_links relationship' => sub {
42     plan tests => 3;
43
44     my $biblio = $builder->build_sample_biblio();
45     my $item   = $builder->build_sample_item({
46         biblionumber => $biblio->biblionumber,
47     });
48     my $tracked_links = $item->tracked_links;
49     is( ref($tracked_links), 'Koha::TrackedLinks', 'tracked_links returns a Koha::TrackedLinks object set' );
50     is($item->tracked_links->count, 0, "Empty Koha::TrackedLinks set returned if no tracked_links");
51     my $link1 = $builder->build({ source => 'Linktracker', value => { itemnumber => $item->itemnumber }});
52     my $link2 = $builder->build({ source => 'Linktracker', value => { itemnumber => $item->itemnumber }});
53
54     is($item->tracked_links()->count,2,"Two tracked links found");
55 };
56
57 subtest 'hidden_in_opac() tests' => sub {
58
59     plan tests => 4;
60
61     $schema->storage->txn_begin;
62
63     my $item  = $builder->build_sample_item({ itemlost => 2 });
64     my $rules = {};
65
66     # disable hidelostitems as it interteres with OpachiddenItems for the calculation
67     t::lib::Mocks::mock_preference( 'hidelostitems', 0 );
68
69     ok( !$item->hidden_in_opac, 'No rules passed, shouldn\'t hide' );
70     ok( !$item->hidden_in_opac({ rules => $rules }), 'Empty rules passed, shouldn\'t hide' );
71
72     # enable hidelostitems to verify correct behaviour
73     t::lib::Mocks::mock_preference( 'hidelostitems', 1 );
74     ok( $item->hidden_in_opac, 'Even with no rules, item should hide because of hidelostitems syspref' );
75
76     # disable hidelostitems
77     t::lib::Mocks::mock_preference( 'hidelostitems', 0 );
78     my $withdrawn = $item->withdrawn + 1; # make sure this attribute doesn't match
79
80     $rules = { withdrawn => [$withdrawn], itype => [ $item->itype ] };
81
82     ok( $item->hidden_in_opac({ rules => $rules }), 'Rule matching itype passed, should hide' );
83
84
85
86     $schema->storage->txn_rollback;
87 };
88
89 subtest 'has_pending_hold() tests' => sub {
90
91     plan tests => 2;
92
93     $schema->storage->txn_begin;
94
95     my $dbh = C4::Context->dbh;
96     my $item  = $builder->build_sample_item({ itemlost => 0 });
97     my $itemnumber = $item->itemnumber;
98
99     $dbh->do("INSERT INTO tmp_holdsqueue (surname,borrowernumber,itemnumber) VALUES ('Clamp',42,$itemnumber)");
100     ok( $item->has_pending_hold, "Yes, we have a pending hold");
101     $dbh->do("DELETE FROM tmp_holdsqueue WHERE itemnumber=$itemnumber");
102     ok( !$item->has_pending_hold, "We don't have a pending hold if nothing in the tmp_holdsqueue");
103
104     $schema->storage->txn_rollback;
105 };
106
107 subtest "as_marc_field() tests" => sub {
108
109     my $mss = C4::Biblio::GetMarcSubfieldStructure( '' );
110
111     my @schema_columns = $schema->resultset('Item')->result_source->columns;
112     my @mapped_columns = grep { exists $mss->{'items.'.$_} } @schema_columns;
113
114     plan tests => 2 * (scalar @mapped_columns + 1) + 2;
115
116     $schema->storage->txn_begin;
117
118     my $item = $builder->build_sample_item;
119     # Make sure it has at least one undefined attribute
120     $item->set({ replacementprice => undef })->store->discard_changes;
121
122     # Tests with the mss parameter
123     my $marc_field = $item->as_marc_field({ mss => $mss });
124
125     is(
126         $marc_field->tag,
127         $mss->{'items.itemnumber'}[0]->{tagfield},
128         'Generated field set the right tag number'
129     );
130
131     foreach my $column ( @mapped_columns ) {
132         my $tagsubfield = $mss->{ 'items.' . $column }[0]->{tagsubfield};
133         is( $marc_field->subfield($tagsubfield),
134             $item->$column, "Value is mapped correctly for column $column" );
135     }
136
137     # Tests without the mss parameter
138     $marc_field = $item->as_marc_field();
139
140     is(
141         $marc_field->tag,
142         $mss->{'items.itemnumber'}[0]->{tagfield},
143         'Generated field set the right tag number'
144     );
145
146     foreach my $column (@mapped_columns) {
147         my $tagsubfield = $mss->{ 'items.' . $column }[0]->{tagsubfield};
148         is( $marc_field->subfield($tagsubfield),
149             $item->$column, "Value is mapped correctly for column $column" );
150     }
151
152     my $unmapped_subfield = Koha::MarcSubfieldStructure->new(
153         {
154             frameworkcode => '',
155             tagfield      => $mss->{'items.itemnumber'}[0]->{tagfield},
156             tagsubfield   => 'X',
157         }
158     )->store;
159
160     $mss = C4::Biblio::GetMarcSubfieldStructure( '' );
161     my @unlinked_subfields;
162     push @unlinked_subfields, X => 'Something weird';
163     $item->more_subfields_xml( C4::Items::_get_unlinked_subfields_xml( \@unlinked_subfields ) )->store;
164
165     $marc_field = $item->as_marc_field;
166
167     my @subfields = $marc_field->subfields;
168     my $result = all { defined $_->[1] } @subfields;
169     ok( $result, 'There are no undef subfields' );
170
171     is( scalar $marc_field->subfield('X'), 'Something weird', 'more_subfield_xml is considered' );
172
173     $schema->storage->txn_rollback;
174 };
175
176 subtest 'pickup_locations' => sub {
177     plan tests => 66;
178
179     $schema->storage->txn_begin;
180
181     my $dbh = C4::Context->dbh;
182
183     my $root1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1, branchcode => undef } } );
184     my $root2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1, branchcode => undef } } );
185     my $library1 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, } } );
186     my $library2 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, } } );
187     my $library3 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, } } );
188     my $library4 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, } } );
189     my $group1_1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library1->branchcode } } );
190     my $group1_2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library2->branchcode } } );
191
192     my $group2_1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library3->branchcode } } );
193     my $group2_2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library4->branchcode } } );
194
195     our @branchcodes = (
196         $library1->branchcode, $library2->branchcode,
197         $library3->branchcode, $library4->branchcode
198     );
199
200     my $item1 = $builder->build_sample_item(
201         {
202             homebranch    => $library1->branchcode,
203             holdingbranch => $library2->branchcode,
204             copynumber    => 1,
205             ccode         => 'Gollum'
206         }
207     )->store;
208
209     my $item3 = $builder->build_sample_item(
210         {
211             homebranch    => $library3->branchcode,
212             holdingbranch => $library4->branchcode,
213             copynumber    => 3,
214             itype         => $item1->itype,
215         }
216     )->store;
217
218     Koha::CirculationRules->set_rules(
219         {
220             categorycode => undef,
221             itemtype     => $item1->itype,
222             branchcode   => undef,
223             rules        => {
224                 reservesallowed => 25,
225             }
226         }
227     );
228
229
230     my $patron1 = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library1->branchcode, firstname => '1' } } );
231     my $patron4 = $builder->build_object( { class => 'Koha::Patrons', value => { branchcode => $library4->branchcode, firstname => '4' } } );
232
233     my $results = {
234         "1-1-from_home_library-any"               => 3,
235         "1-1-from_home_library-holdgroup"         => 2,
236         "1-1-from_home_library-patrongroup"       => 2,
237         "1-1-from_home_library-homebranch"        => 1,
238         "1-1-from_home_library-holdingbranch"     => 1,
239         "1-1-from_any_library-any"                => 3,
240         "1-1-from_any_library-holdgroup"          => 2,
241         "1-1-from_any_library-patrongroup"        => 2,
242         "1-1-from_any_library-homebranch"         => 1,
243         "1-1-from_any_library-holdingbranch"      => 1,
244         "1-1-from_local_hold_group-any"           => 3,
245         "1-1-from_local_hold_group-holdgroup"     => 2,
246         "1-1-from_local_hold_group-patrongroup"   => 2,
247         "1-1-from_local_hold_group-homebranch"    => 1,
248         "1-1-from_local_hold_group-holdingbranch" => 1,
249         "1-4-from_home_library-any"               => 0,
250         "1-4-from_home_library-holdgroup"         => 0,
251         "1-4-from_home_library-patrongroup"       => 0,
252         "1-4-from_home_library-homebranch"        => 0,
253         "1-4-from_home_library-holdingbranch"     => 0,
254         "1-4-from_any_library-any"                => 3,
255         "1-4-from_any_library-holdgroup"          => 2,
256         "1-4-from_any_library-patrongroup"        => 1,
257         "1-4-from_any_library-homebranch"         => 1,
258         "1-4-from_any_library-holdingbranch"      => 1,
259         "1-4-from_local_hold_group-any"           => 0,
260         "1-4-from_local_hold_group-holdgroup"     => 0,
261         "1-4-from_local_hold_group-patrongroup"   => 0,
262         "1-4-from_local_hold_group-homebranch"    => 0,
263         "1-4-from_local_hold_group-holdingbranch" => 0,
264         "3-1-from_home_library-any"               => 0,
265         "3-1-from_home_library-holdgroup"         => 0,
266         "3-1-from_home_library-patrongroup"       => 0,
267         "3-1-from_home_library-homebranch"        => 0,
268         "3-1-from_home_library-holdingbranch"     => 0,
269         "3-1-from_any_library-any"                => 3,
270         "3-1-from_any_library-holdgroup"          => 1,
271         "3-1-from_any_library-patrongroup"        => 2,
272         "3-1-from_any_library-homebranch"         => 0,
273         "3-1-from_any_library-holdingbranch"      => 1,
274         "3-1-from_local_hold_group-any"           => 0,
275         "3-1-from_local_hold_group-holdgroup"     => 0,
276         "3-1-from_local_hold_group-patrongroup"   => 0,
277         "3-1-from_local_hold_group-homebranch"    => 0,
278         "3-1-from_local_hold_group-holdingbranch" => 0,
279         "3-4-from_home_library-any"               => 0,
280         "3-4-from_home_library-holdgroup"         => 0,
281         "3-4-from_home_library-patrongroup"       => 0,
282         "3-4-from_home_library-homebranch"        => 0,
283         "3-4-from_home_library-holdingbranch"     => 0,
284         "3-4-from_any_library-any"                => 3,
285         "3-4-from_any_library-holdgroup"          => 1,
286         "3-4-from_any_library-patrongroup"        => 1,
287         "3-4-from_any_library-homebranch"         => 0,
288         "3-4-from_any_library-holdingbranch"      => 1,
289         "3-4-from_local_hold_group-any"           => 3,
290         "3-4-from_local_hold_group-holdgroup"     => 1,
291         "3-4-from_local_hold_group-patrongroup"   => 1,
292         "3-4-from_local_hold_group-homebranch"    => 0,
293         "3-4-from_local_hold_group-holdingbranch" => 1
294     };
295
296     sub _doTest {
297         my ( $item, $patron, $ha, $hfp, $results ) = @_;
298
299         Koha::CirculationRules->set_rules(
300             {
301                 branchcode => undef,
302                 itemtype   => undef,
303                 rules => {
304                     holdallowed => $ha,
305                     hold_fulfillment_policy => $hfp,
306                     returnbranch => 'any'
307                 }
308             }
309         );
310         my $ha_value =
311           $ha eq 'from_local_hold_group' ? 'holdgroup'
312           : (
313             $ha eq 'from_any_library' ? 'any'
314             : 'homebranch'
315           );
316
317         my @pl = map {
318             my $pickup_location = $_;
319             grep { $pickup_location->branchcode eq $_ } @branchcodes
320         } $item->pickup_locations( { patron => $patron } )->as_list;
321
322         ok(
323             scalar(@pl) eq $results->{
324                     $item->copynumber . '-'
325                   . $patron->firstname . '-'
326                   . $ha . '-'
327                   . $hfp
328             },
329             'item'
330               . $item->copynumber
331               . ', patron'
332               . $patron->firstname
333               . ', holdallowed: '
334               . $ha_value
335               . ', hold_fulfillment_policy: '
336               . $hfp
337               . ' should return '
338               . $results->{
339                     $item->copynumber . '-'
340                   . $patron->firstname . '-'
341                   . $ha . '-'
342                   . $hfp
343               }
344               . ' and returns '
345               . scalar(@pl)
346         );
347
348     }
349
350
351     foreach my $item ($item1, $item3) {
352         foreach my $patron ($patron1, $patron4) {
353             #holdallowed 1: homebranch, 2: any, 3: holdgroup
354             foreach my $ha ('from_home_library', 'from_any_library', 'from_local_hold_group') {
355                 foreach my $hfp ('any', 'holdgroup', 'patrongroup', 'homebranch', 'holdingbranch') {
356                     _doTest($item, $patron, $ha, $hfp, $results);
357                 }
358             }
359         }
360     }
361
362     # Now test that branchtransferlimits will further filter the pickup locations
363
364     my $item_no_ccode = $builder->build_sample_item(
365         {
366             homebranch    => $library1->branchcode,
367             holdingbranch => $library2->branchcode,
368             itype         => $item1->itype,
369         }
370     )->store;
371
372     t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
373     t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
374     Koha::CirculationRules->set_rules(
375         {
376             branchcode => undef,
377             itemtype   => $item1->itype,
378             rules      => {
379                 holdallowed             => 'from_home_library',
380                 hold_fulfillment_policy => 1,
381                 returnbranch            => 'any'
382             }
383         }
384     );
385     $builder->build_object(
386         {
387             class => 'Koha::Item::Transfer::Limits',
388             value => {
389                 toBranch   => $library1->branchcode,
390                 fromBranch => $library2->branchcode,
391                 itemtype   => $item1->itype,
392                 ccode      => undef,
393             }
394         }
395     );
396
397     my @pickup_locations = map {
398         my $pickup_location = $_;
399         grep { $pickup_location->branchcode eq $_ } @branchcodes
400     } $item1->pickup_locations( { patron => $patron1 } )->as_list;
401
402     is( scalar @pickup_locations, 3 - 1, "With a transfer limits we get back the libraries that are pickup locations minus 1 limited library");
403
404     $builder->build_object(
405         {
406             class => 'Koha::Item::Transfer::Limits',
407             value => {
408                 toBranch   => $library4->branchcode,
409                 fromBranch => $library2->branchcode,
410                 itemtype   => $item1->itype,
411                 ccode      => undef,
412             }
413         }
414     );
415
416     @pickup_locations = map {
417         my $pickup_location = $_;
418         grep { $pickup_location->branchcode eq $_ } @branchcodes
419     } $item1->pickup_locations( { patron => $patron1 } )->as_list;
420
421     is( scalar @pickup_locations, 3 - 2, "With 2 transfer limits we get back the libraries that are pickup locations minus 2 limited libraries");
422
423     t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'ccode');
424     @pickup_locations = map {
425         my $pickup_location = $_;
426         grep { $pickup_location->branchcode eq $_ } @branchcodes
427     } $item1->pickup_locations( { patron => $patron1 } )->as_list;
428     is( scalar @pickup_locations, 3, "With no transfer limits of type ccode we get back the libraries that are pickup locations");
429
430     @pickup_locations = map {
431         my $pickup_location = $_;
432         grep { $pickup_location->branchcode eq $_ } @branchcodes
433     } $item_no_ccode->pickup_locations( { patron => $patron1 } )->as_list;
434     is( scalar @pickup_locations, 3, "With no transfer limits of type ccode and an item with no ccode we get back the libraries that are pickup locations");
435
436     $builder->build_object(
437         {
438             class => 'Koha::Item::Transfer::Limits',
439             value => {
440                 toBranch   => $library2->branchcode,
441                 fromBranch => $library2->branchcode,
442                 itemtype   => undef,
443                 ccode      => $item1->ccode,
444             }
445         }
446     );
447
448     @pickup_locations = map {
449         my $pickup_location = $_;
450         grep { $pickup_location->branchcode eq $_ } @branchcodes
451     } $item1->pickup_locations( { patron => $patron1 } )->as_list;
452     is( scalar @pickup_locations, 3 - 1, "With a transfer limits we get back the libraries that are pickup locations minus 1 limited library");
453
454     $builder->build_object(
455         {
456             class => 'Koha::Item::Transfer::Limits',
457             value => {
458                 toBranch   => $library4->branchcode,
459                 fromBranch => $library2->branchcode,
460                 itemtype   => undef,
461                 ccode      => $item1->ccode,
462             }
463         }
464     );
465
466     @pickup_locations = map {
467         my $pickup_location = $_;
468         grep { $pickup_location->branchcode eq $_ } @branchcodes
469     } $item1->pickup_locations( { patron => $patron1 } )->as_list;
470     is( scalar @pickup_locations, 3 - 2, "With 2 transfer limits we get back the libraries that are pickup locations minus 2 limited libraries");
471
472     t::lib::Mocks::mock_preference('UseBranchTransferLimits', 0);
473
474     $schema->storage->txn_rollback;
475 };
476
477 subtest 'request_transfer' => sub {
478     plan tests => 13;
479     $schema->storage->txn_begin;
480
481     my $library1 = $builder->build_object( { class => 'Koha::Libraries' } );
482     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
483     my $item     = $builder->build_sample_item(
484         {
485             homebranch    => $library1->branchcode,
486             holdingbranch => $library2->branchcode,
487         }
488     );
489
490     # Mandatory fields tests
491     throws_ok { $item->request_transfer( { to => $library1 } ) }
492     'Koha::Exceptions::MissingParameter',
493       'Exception thrown if `reason` parameter is missing';
494
495     throws_ok { $item->request_transfer( { reason => 'Manual' } ) }
496     'Koha::Exceptions::MissingParameter',
497       'Exception thrown if `to` parameter is missing';
498
499     # Successful request
500     my $transfer = $item->request_transfer({ to => $library1, reason => 'Manual' });
501     is( ref($transfer), 'Koha::Item::Transfer',
502         'Koha::Item->request_transfer should return a Koha::Item::Transfer object'
503     );
504     my $original_transfer = $transfer->get_from_storage;
505
506     # Transfer already in progress
507     throws_ok { $item->request_transfer( { to => $library2, reason => 'Manual' } ) }
508     'Koha::Exceptions::Item::Transfer::InQueue',
509       'Exception thrown if transfer is already in progress';
510
511     my $exception = $@;
512     is( ref( $exception->transfer ),
513         'Koha::Item::Transfer',
514         'The exception contains the found Koha::Item::Transfer' );
515
516     # Queue transfer
517     my $queued_transfer = $item->request_transfer(
518         { to => $library2, reason => 'Manual', enqueue => 1 } );
519     is( ref($queued_transfer), 'Koha::Item::Transfer',
520         'Koha::Item->request_transfer allowed when enqueue is set' );
521     my $transfers = $item->get_transfers;
522     is($transfers->count, 2, "There are now 2 live transfers in the queue");
523     $transfer = $transfer->get_from_storage;
524     is_deeply($transfer->unblessed, $original_transfer->unblessed, "Original transfer unchanged");
525     $queued_transfer->datearrived(dt_from_string)->store();
526
527     # Replace transfer
528     my $replaced_transfer = $item->request_transfer(
529         { to => $library2, reason => 'Manual', replace => 1 } );
530     is( ref($replaced_transfer), 'Koha::Item::Transfer',
531         'Koha::Item->request_transfer allowed when replace is set' );
532     $original_transfer->discard_changes;
533     ok($original_transfer->datecancelled, "Original transfer cancelled");
534     $transfers = $item->get_transfers;
535     is($transfers->count, 1, "There is only 1 live transfer in the queue");
536     $replaced_transfer->datearrived(dt_from_string)->store();
537
538     # BranchTransferLimits
539     t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
540     t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
541     my $limit = Koha::Item::Transfer::Limit->new({
542         fromBranch => $library2->branchcode,
543         toBranch => $library1->branchcode,
544         itemtype => $item->effective_itemtype,
545     })->store;
546
547     throws_ok { $item->request_transfer( { to => $library1, reason => 'Manual' } ) }
548     'Koha::Exceptions::Item::Transfer::Limit',
549       'Exception thrown if transfer is prevented by limits';
550
551     my $forced_transfer = $item->request_transfer( { to => $library1, reason => 'Manual', ignore_limits => 1 } );
552     is( ref($forced_transfer), 'Koha::Item::Transfer',
553         'Koha::Item->request_transfer allowed when ignore_limits is set'
554     );
555
556     $schema->storage->txn_rollback;
557 };
558
559 subtest 'deletion' => sub {
560     plan tests => 12;
561
562     $schema->storage->txn_begin;
563
564     my $biblio = $builder->build_sample_biblio();
565
566     my $item = $builder->build_sample_item(
567         {
568             biblionumber => $biblio->biblionumber,
569         }
570     );
571
572     is( ref( $item->move_to_deleted ), 'Koha::Schema::Result::Deleteditem', 'Koha::Item->move_to_deleted should return the Deleted item' )
573       ;    # FIXME This should be Koha::Deleted::Item
574     is( Koha::Old::Items->search({itemnumber => $item->itemnumber})->count, 1, '->move_to_deleted must have moved the item to deleteditem' );
575     $item = $builder->build_sample_item(
576         {
577             biblionumber => $biblio->biblionumber,
578         }
579     );
580     $item->delete;
581     is( Koha::Old::Items->search({itemnumber => $item->itemnumber})->count, 0, '->move_to_deleted must not have moved the item to deleteditem' );
582
583
584     my $library   = $builder->build_object({ class => 'Koha::Libraries' });
585     my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
586     t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
587
588     my $patron = $builder->build_object({class => 'Koha::Patrons'});
589     $item = $builder->build_sample_item({ library => $library->branchcode });
590
591     # book_on_loan
592     C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
593
594     is(
595         $item->safe_to_delete,
596         'book_on_loan',
597         'Koha::Item->safe_to_delete reports item on loan',
598     );
599
600     is(
601         $item->safe_delete,
602         'book_on_loan',
603         'item that is on loan cannot be deleted',
604     );
605
606     AddReturn( $item->barcode, $library->branchcode );
607
608     # book_reserved is tested in t/db_dependent/Reserves.t
609
610     # not_same_branch
611     t::lib::Mocks::mock_preference('IndependentBranches', 1);
612     my $item_2 = $builder->build_sample_item({ library => $library_2->branchcode });
613
614     is(
615         $item_2->safe_to_delete,
616         'not_same_branch',
617         'Koha::Item->safe_to_delete reports IndependentBranches restriction',
618     );
619
620     is(
621         $item_2->safe_delete,
622         'not_same_branch',
623         'IndependentBranches prevents deletion at another branch',
624     );
625
626     # linked_analytics
627
628     { # codeblock to limit scope of $module->mock
629
630         my $module = Test::MockModule->new('C4::Items');
631         $module->mock( GetAnalyticsCount => sub { return 1 } );
632
633         $item->discard_changes;
634         is(
635             $item->safe_to_delete,
636             'linked_analytics',
637             'Koha::Item->safe_to_delete reports linked analytics',
638         );
639
640         is(
641             $item->safe_delete,
642             'linked_analytics',
643             'Linked analytics prevents deletion of item',
644         );
645
646     }
647
648     { # last_item_for_hold
649         C4::Reserves::AddReserve({ branchcode => $patron->branchcode, borrowernumber => $patron->borrowernumber, biblionumber => $item->biblionumber });
650         is( $item->safe_to_delete, 'last_item_for_hold', 'Item cannot be deleted if a biblio-level is placed on the biblio and there is only 1 item attached to the biblio' );
651
652         # With another item attached to the biblio, the item can be deleted
653         $builder->build_sample_item({ biblionumber => $item->biblionumber });
654     }
655
656     is(
657         $item->safe_to_delete,
658         1,
659         'Koha::Item->safe_to_delete shows item safe to delete'
660     );
661
662     $item->safe_delete,
663
664     my $test_item = Koha::Items->find( $item->itemnumber );
665
666     is( $test_item, undef,
667         "Koha::Item->safe_delete should delete item if safe_to_delete returns true"
668     );
669
670     $schema->storage->txn_rollback;
671 };
672
673 subtest 'renewal_branchcode' => sub {
674     plan tests => 13;
675
676     $schema->storage->txn_begin;
677
678     my $item = $builder->build_sample_item();
679     my $branch = $builder->build_object({ class => 'Koha::Libraries' });
680     my $checkout = $builder->build_object({
681         class => 'Koha::Checkouts',
682         value => {
683             itemnumber => $item->itemnumber,
684         }
685     });
686
687
688     C4::Context->interface( 'intranet' );
689     t::lib::Mocks::mock_userenv({ branchcode => $branch->branchcode });
690
691     is( $item->renewal_branchcode, $branch->branchcode, "If interface not opac, we get the branch from context");
692     is( $item->renewal_branchcode({ branch => "PANDA"}), $branch->branchcode, "If interface not opac, we get the branch from context even if we pass one in");
693     C4::Context->set_userenv(51, 'userid4tests', undef, 'firstname', 'surname', undef, undef, 0, undef, undef, undef ); #mock userenv doesn't let us set null branch
694     is( $item->renewal_branchcode({ branch => "PANDA"}), "PANDA", "If interface not opac, we get the branch we pass one in if context not set");
695
696     C4::Context->interface( 'opac' );
697
698     t::lib::Mocks::mock_preference('OpacRenewalBranch', undef);
699     is( $item->renewal_branchcode, 'OPACRenew', "If interface opac and OpacRenewalBranch undef, we get OPACRenew");
700     is( $item->renewal_branchcode({branch=>'COW'}), 'OPACRenew', "If interface opac and OpacRenewalBranch undef, we get OPACRenew even if branch passed");
701
702     t::lib::Mocks::mock_preference('OpacRenewalBranch', 'none');
703     is( $item->renewal_branchcode, '', "If interface opac and OpacRenewalBranch is none, we get blank string");
704     is( $item->renewal_branchcode({branch=>'COW'}), '', "If interface opac and OpacRenewalBranch is none, we get blank string even if branch passed");
705
706     t::lib::Mocks::mock_preference('OpacRenewalBranch', 'checkoutbranch');
707     is( $item->renewal_branchcode, $checkout->branchcode, "If interface opac and OpacRenewalBranch set to checkoutbranch, we get branch of checkout");
708     is( $item->renewal_branchcode({branch=>'MONKEY'}), $checkout->branchcode, "If interface opac and OpacRenewalBranch set to checkoutbranch, we get branch of checkout even if branch passed");
709
710     t::lib::Mocks::mock_preference('OpacRenewalBranch','patronhomebranch');
711     is( $item->renewal_branchcode, $checkout->patron->branchcode, "If interface opac and OpacRenewalBranch set to patronbranch, we get branch of patron");
712     is( $item->renewal_branchcode({branch=>'TURKEY'}), $checkout->patron->branchcode, "If interface opac and OpacRenewalBranch set to patronbranch, we get branch of patron even if branch passed");
713
714     t::lib::Mocks::mock_preference('OpacRenewalBranch','itemhomebranch');
715     is( $item->renewal_branchcode, $item->homebranch, "If interface opac and OpacRenewalBranch set to itemhomebranch, we get homebranch of item");
716     is( $item->renewal_branchcode({branch=>'MANATEE'}), $item->homebranch, "If interface opac and OpacRenewalBranch set to itemhomebranch, we get homebranch of item even if branch passed");
717
718     $schema->storage->txn_rollback;
719 };
720
721 subtest 'Tests for itemtype' => sub {
722     plan tests => 2;
723     $schema->storage->txn_begin;
724
725     my $biblio = $builder->build_sample_biblio;
726     my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes' });
727     my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber, itype => $itemtype->itemtype });
728
729     t::lib::Mocks::mock_preference('item-level_itypes', 1);
730     is( $item->itemtype->itemtype, $item->itype, 'Pref enabled' );
731     t::lib::Mocks::mock_preference('item-level_itypes', 0);
732     is( $item->itemtype->itemtype, $biblio->biblioitem->itemtype, 'Pref disabled' );
733
734     $schema->storage->txn_rollback;
735 };
736
737 subtest 'get_transfers' => sub {
738     plan tests => 16;
739     $schema->storage->txn_begin;
740
741     my $item = $builder->build_sample_item();
742
743     my $transfers = $item->get_transfers();
744     is(ref($transfers), 'Koha::Item::Transfers', 'Koha::Item->get_transfer should return a Koha::Item::Transfers object' );
745     is($transfers->count, 0, 'When no transfers exist, the Koha::Item:Transfers object should be empty');
746
747     my $library_to = $builder->build_object( { class => 'Koha::Libraries' } );
748
749     my $transfer_1 = $builder->build_object(
750         {
751             class => 'Koha::Item::Transfers',
752             value => {
753                 itemnumber    => $item->itemnumber,
754                 frombranch    => $item->holdingbranch,
755                 tobranch      => $library_to->branchcode,
756                 reason        => 'Manual',
757                 datesent      => undef,
758                 datearrived   => undef,
759                 datecancelled => undef,
760                 daterequested => \'NOW()'
761             }
762         }
763     );
764
765     $transfers = $item->get_transfers();
766     is($transfers->count, 1, 'When one transfer has been requested, the Koha::Item:Transfers object should contain one result');
767
768     my $transfer_2 = $builder->build_object(
769         {
770             class => 'Koha::Item::Transfers',
771             value => {
772                 itemnumber    => $item->itemnumber,
773                 frombranch    => $item->holdingbranch,
774                 tobranch      => $library_to->branchcode,
775                 reason        => 'Manual',
776                 datesent      => undef,
777                 datearrived   => undef,
778                 datecancelled => undef,
779                 daterequested => \'NOW()'
780             }
781         }
782     );
783
784     my $transfer_3 = $builder->build_object(
785         {
786             class => 'Koha::Item::Transfers',
787             value => {
788                 itemnumber    => $item->itemnumber,
789                 frombranch    => $item->holdingbranch,
790                 tobranch      => $library_to->branchcode,
791                 reason        => 'Manual',
792                 datesent      => undef,
793                 datearrived   => undef,
794                 datecancelled => undef,
795                 daterequested => \'NOW()'
796             }
797         }
798     );
799
800     $transfers = $item->get_transfers();
801     is($transfers->count, 3, 'When there are multiple open transfer requests, the Koha::Item::Transfers object contains them all');
802     my $result_1 = $transfers->next;
803     my $result_2 = $transfers->next;
804     my $result_3 = $transfers->next;
805     is( $result_1->branchtransfer_id, $transfer_1->branchtransfer_id, 'Koha::Item->get_transfers returns the oldest transfer request first');
806     is( $result_2->branchtransfer_id, $transfer_2->branchtransfer_id, 'Koha::Item->get_transfers returns the newer transfer request second');
807     is( $result_3->branchtransfer_id, $transfer_3->branchtransfer_id, 'Koha::Item->get_transfers returns the newest transfer request last');
808
809     $transfer_2->datesent(\'NOW()')->store;
810     $transfers = $item->get_transfers();
811     is($transfers->count, 3, 'When one transfer is set to in_transit, the Koha::Item::Transfers object still contains them all');
812     $result_1 = $transfers->next;
813     $result_2 = $transfers->next;
814     $result_3 = $transfers->next;
815     is( $result_1->branchtransfer_id, $transfer_2->branchtransfer_id, 'Koha::Item->get_transfers returns the active transfer request first');
816     is( $result_2->branchtransfer_id, $transfer_1->branchtransfer_id, 'Koha::Item->get_transfers returns the other transfers oldest to newest');
817     is( $result_3->branchtransfer_id, $transfer_3->branchtransfer_id, 'Koha::Item->get_transfers returns the other transfers oldest to newest');
818
819     $transfer_2->datearrived(\'NOW()')->store;
820     $transfers = $item->get_transfers();
821     is($transfers->count, 2, 'Once a transfer is received, it no longer appears in the list from ->get_transfers()');
822     $result_1 = $transfers->next;
823     $result_2 = $transfers->next;
824     is( $result_1->branchtransfer_id, $transfer_1->branchtransfer_id, 'Koha::Item->get_transfers returns the other transfers oldest to newest');
825     is( $result_2->branchtransfer_id, $transfer_3->branchtransfer_id, 'Koha::Item->get_transfers returns the other transfers oldest to newest');
826
827     $transfer_1->datecancelled(\'NOW()')->store;
828     $transfers = $item->get_transfers();
829     is($transfers->count, 1, 'Once a transfer is cancelled, it no longer appears in the list from ->get_transfers()');
830     $result_1 = $transfers->next;
831     is( $result_1->branchtransfer_id, $transfer_3->branchtransfer_id, 'Koha::Item->get_transfers returns the only transfer that remains');
832
833     $schema->storage->txn_rollback;
834 };
835
836 subtest 'Tests for relationship between item and item_orders via aqorders_item' => sub {
837     plan tests => 2;
838
839     $schema->storage->txn_begin;
840
841     my $biblio = $builder->build_sample_biblio();
842     my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
843     my $aq_budget = $builder->build({
844         source => 'Aqbudget',
845         value  => {
846             budget_notes => 'test',
847         },
848     });
849
850     my $order_note = 'Order for ' . $item->itemnumber;
851
852     my $aq_order1 = $builder->build_object({
853         class => 'Koha::Acquisition::Orders',
854         value  => {
855             biblionumber => $biblio->biblionumber,
856             budget_id => $aq_budget->{budget_id},
857             order_internalnote => $order_note,
858         },
859     });
860     my $aq_order2 = $builder->build_object({
861         class => 'Koha::Acquisition::Orders',
862         value  => {
863             biblionumber => $biblio->biblionumber,
864             budget_id => $aq_budget->{budget_id},
865         },
866     });
867     my $aq_order_item1 = $builder->build({
868         source => 'AqordersItem',
869         value  => {
870             ordernumber => $aq_order1->ordernumber,
871             itemnumber => $item->itemnumber,
872         },
873     });
874
875     my $orders = $item->orders;
876     is ($orders->count, 1, 'One order found by item with the relationship');
877     is ($orders->next->order_internalnote, $order_note, 'Correct order found by item with the relationship');
878 };
879
880 subtest 'move_to_biblio() tests' => sub {
881     plan tests => 16;
882
883     $schema->storage->txn_begin;
884
885     my $dbh = C4::Context->dbh;
886
887     my $source_biblio = $builder->build_sample_biblio();
888     my $target_biblio = $builder->build_sample_biblio();
889
890     my $source_biblionumber = $source_biblio->biblionumber;
891     my $target_biblionumber = $target_biblio->biblionumber;
892
893     my $item1 = $builder->build_sample_item({ biblionumber => $source_biblionumber });
894     my $item2 = $builder->build_sample_item({ biblionumber => $source_biblionumber });
895     my $item3 = $builder->build_sample_item({ biblionumber => $source_biblionumber });
896
897     my $itemnumber1 = $item1->itemnumber;
898     my $itemnumber2 = $item2->itemnumber;
899
900     my $library = $builder->build_object({ class => 'Koha::Libraries' });
901
902     my $patron = $builder->build_object({
903         class => 'Koha::Patrons',
904         value => { branchcode => $library->branchcode }
905     });
906     my $borrowernumber = $patron->borrowernumber;
907
908     my $aq_budget = $builder->build({
909         source => 'Aqbudget',
910         value  => {
911             budget_notes => 'test',
912         },
913     });
914
915     my $aq_order1 = $builder->build_object({
916         class => 'Koha::Acquisition::Orders',
917         value  => {
918             biblionumber => $source_biblionumber,
919             budget_id => $aq_budget->{budget_id},
920         },
921     });
922     my $aq_order_item1 = $builder->build({
923         source => 'AqordersItem',
924         value  => {
925             ordernumber => $aq_order1->ordernumber,
926             itemnumber => $itemnumber1,
927         },
928     });
929     my $aq_order2 = $builder->build_object({
930         class => 'Koha::Acquisition::Orders',
931         value  => {
932             biblionumber => $source_biblionumber,
933             budget_id => $aq_budget->{budget_id},
934         },
935     });
936     my $aq_order_item2 = $builder->build({
937         source => 'AqordersItem',
938         value  => {
939             ordernumber => $aq_order2->ordernumber,
940             itemnumber => $itemnumber2,
941         },
942     });
943
944     my $bib_level_hold = $builder->build_object({
945         class => 'Koha::Holds',
946         value  => {
947             biblionumber => $source_biblionumber,
948             itemnumber => undef,
949         },
950     });
951     my $item_level_hold1 = $builder->build_object({
952         class => 'Koha::Holds',
953         value  => {
954             biblionumber => $source_biblionumber,
955             itemnumber => $itemnumber1,
956         },
957     });
958     my $item_level_hold2 = $builder->build_object({
959         class => 'Koha::Holds',
960         value  => {
961             biblionumber => $source_biblionumber,
962             itemnumber => $itemnumber2,
963         }
964     });
965
966     my $tmp_holdsqueue1 = $builder->build({
967         source => 'TmpHoldsqueue',
968         value  => {
969             borrowernumber => $borrowernumber,
970             biblionumber   => $source_biblionumber,
971             itemnumber     => $itemnumber1,
972         }
973     });
974     my $tmp_holdsqueue2 = $builder->build({
975         source => 'TmpHoldsqueue',
976         value  => {
977             borrowernumber => $borrowernumber,
978             biblionumber   => $source_biblionumber,
979             itemnumber     => $itemnumber2,
980         }
981     });
982     my $hold_fill_target1 = $builder->build({
983         source => 'HoldFillTarget',
984         value  => {
985             borrowernumber     => $borrowernumber,
986             biblionumber       => $source_biblionumber,
987             itemnumber         => $itemnumber1,
988         }
989     });
990     my $hold_fill_target2 = $builder->build({
991         source => 'HoldFillTarget',
992         value  => {
993             borrowernumber     => $borrowernumber,
994             biblionumber       => $source_biblionumber,
995             itemnumber         => $itemnumber2,
996         }
997     });
998     my $linktracker1 = $builder->build({
999         source => 'Linktracker',
1000         value  => {
1001             borrowernumber     => $borrowernumber,
1002             biblionumber       => $source_biblionumber,
1003             itemnumber         => $itemnumber1,
1004         }
1005     });
1006     my $linktracker2 = $builder->build({
1007         source => 'Linktracker',
1008         value  => {
1009             borrowernumber     => $borrowernumber,
1010             biblionumber       => $source_biblionumber,
1011             itemnumber         => $itemnumber2,
1012         }
1013     });
1014
1015     my $to_biblionumber_after_move = $item1->move_to_biblio($target_biblio);
1016     is($to_biblionumber_after_move, $target_biblionumber, 'move_to_biblio returns the target biblionumber if success');
1017
1018     $to_biblionumber_after_move = $item1->move_to_biblio($target_biblio);
1019     is($to_biblionumber_after_move, undef, 'move_to_biblio returns undef if the move has failed. If called twice, the item is not attached to the first biblio anymore');
1020
1021     my $get_item1 = Koha::Items->find( $item1->itemnumber );
1022     is($get_item1->biblionumber, $target_biblionumber, 'item1 is moved');
1023     my $get_item2 = Koha::Items->find( $item2->itemnumber );
1024     is($get_item2->biblionumber, $source_biblionumber, 'item2 is not moved');
1025     my $get_item3 = Koha::Items->find( $item3->itemnumber );
1026     is($get_item3->biblionumber, $source_biblionumber, 'item3 is not moved');
1027
1028     $aq_order1->discard_changes;
1029     $aq_order2->discard_changes;
1030     is($aq_order1->biblionumber, $target_biblionumber, 'move_to_biblio moves aq_orders for item 1');
1031     is($aq_order2->biblionumber, $source_biblionumber, 'move_to_biblio does not move aq_orders for item 2');
1032
1033     $bib_level_hold->discard_changes;
1034     $item_level_hold1->discard_changes;
1035     $item_level_hold2->discard_changes;
1036     is($bib_level_hold->biblionumber,   $source_biblionumber, 'move_to_biblio does not move the biblio-level hold');
1037     is($item_level_hold1->biblionumber, $target_biblionumber, 'move_to_biblio moves the item-level hold placed on item 1');
1038     is($item_level_hold2->biblionumber, $source_biblionumber, 'move_to_biblio does not move the item-level hold placed on item 2');
1039
1040     my $get_tmp_holdsqueue1 = $schema->resultset('TmpHoldsqueue')->search({ itemnumber => $tmp_holdsqueue1->{itemnumber} })->single;
1041     my $get_tmp_holdsqueue2 = $schema->resultset('TmpHoldsqueue')->search({ itemnumber => $tmp_holdsqueue2->{itemnumber} })->single;
1042     is($get_tmp_holdsqueue1->biblionumber, $target_biblionumber, 'move_to_biblio moves tmp_holdsqueue for item 1');
1043     is($get_tmp_holdsqueue2->biblionumber, $source_biblionumber, 'move_to_biblio does not move tmp_holdsqueue for item 2');
1044
1045     my $get_hold_fill_target1 = $schema->resultset('HoldFillTarget')->search({ itemnumber => $hold_fill_target1->{itemnumber} })->single;
1046     my $get_hold_fill_target2 = $schema->resultset('HoldFillTarget')->search({ itemnumber => $hold_fill_target2->{itemnumber} })->single;
1047     # Why does ->biblionumber return a Biblio object???
1048     is($get_hold_fill_target1->biblionumber->biblionumber, $target_biblionumber, 'move_to_biblio moves hold_fill_targets for item 1');
1049     is($get_hold_fill_target2->biblionumber->biblionumber, $source_biblionumber, 'move_to_biblio does not move hold_fill_targets for item 2');
1050
1051     my $get_linktracker1 = $schema->resultset('Linktracker')->search({ itemnumber => $linktracker1->{itemnumber} })->single;
1052     my $get_linktracker2 = $schema->resultset('Linktracker')->search({ itemnumber => $linktracker2->{itemnumber} })->single;
1053     is($get_linktracker1->biblionumber->biblionumber, $target_biblionumber, 'move_to_biblio moves linktracker for item 1');
1054     is($get_linktracker2->biblionumber->biblionumber, $source_biblionumber, 'move_to_biblio does not move linktracker for item 2');
1055
1056     $schema->storage->txn_rollback;
1057 };