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