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