Bug 18816: Make CataloguingLog work in production by preventing circulation from...
[koha.git] / t / db_dependent / Items.t
1 #!/usr/bin/perl
2 #
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 2 of the License, or (at your option) any later
8 # version.
9 #
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with Koha; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 #
18
19 use Modern::Perl;
20 use Data::Dumper;
21
22 use MARC::Record;
23 use C4::Biblio;
24 use Koha::Database;
25 use Koha::DateUtils qw( dt_from_string );
26 use Koha::Library;
27 use Koha::DateUtils;
28 use Koha::MarcSubfieldStructures;
29 use Koha::Caches;
30
31 use t::lib::Mocks;
32 use t::lib::TestBuilder;
33
34 use Test::More tests => 13;
35
36 use Test::Warn;
37
38 BEGIN {
39     use_ok('C4::Items');
40     use_ok('Koha::Items');
41 }
42
43 my $schema = Koha::Database->new->schema;
44 my $location = 'My Location';
45
46 subtest 'General Add, Get and Del tests' => sub {
47
48     plan tests => 16;
49
50     $schema->storage->txn_begin;
51
52     my $builder = t::lib::TestBuilder->new;
53     my $library = $builder->build({
54         source => 'Branch',
55     });
56     my $itemtype = $builder->build({
57         source => 'Itemtype',
58     });
59
60     # Create a biblio instance for testing
61     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
62     my ($bibnum, $bibitemnum) = get_biblio();
63
64     # Add an item.
65     my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $library->{branchcode}, holdingbranch => $library->{branchcode}, location => $location, itype => $itemtype->{itemtype} } , $bibnum);
66     cmp_ok($item_bibnum, '==', $bibnum, "New item is linked to correct biblionumber.");
67     cmp_ok($item_bibitemnum, '==', $bibitemnum, "New item is linked to correct biblioitemnumber.");
68
69     # Get item.
70     my $getitem = GetItem($itemnumber);
71     cmp_ok($getitem->{'itemnumber'}, '==', $itemnumber, "Retrieved item has correct itemnumber.");
72     cmp_ok($getitem->{'biblioitemnumber'}, '==', $item_bibitemnum, "Retrieved item has correct biblioitemnumber.");
73     is( $getitem->{location}, $location, "The location should not have been modified" );
74     is( $getitem->{permanent_location}, $location, "The permanent_location should have been set to the location value" );
75
76     # Modify item; setting barcode.
77     ModItem({ barcode => '987654321' }, $bibnum, $itemnumber);
78     my $moditem = GetItem($itemnumber);
79     cmp_ok($moditem->{'barcode'}, '==', '987654321', 'Modified item barcode successfully to: '.$moditem->{'barcode'} . '.');
80
81     # Delete item.
82     DelItem({ biblionumber => $bibnum, itemnumber => $itemnumber });
83     my $getdeleted = GetItem($itemnumber);
84     is($getdeleted->{'itemnumber'}, undef, "Item deleted as expected.");
85
86     ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $library->{branchcode}, holdingbranch => $library->{branchcode}, location => $location, permanent_location => 'my permanent location', itype => $itemtype->{itemtype} } , $bibnum);
87     $getitem = GetItem($itemnumber);
88     is( $getitem->{location}, $location, "The location should not have been modified" );
89     is( $getitem->{permanent_location}, 'my permanent location', "The permanent_location should not have modified" );
90
91     ModItem({ location => $location }, $bibnum, $itemnumber);
92     $getitem = GetItem($itemnumber);
93     is( $getitem->{location}, $location, "The location should have been set to correct location" );
94     is( $getitem->{permanent_location}, $location, "The permanent_location should have been set to location" );
95
96     ModItem({ location => 'CART' }, $bibnum, $itemnumber);
97     $getitem = GetItem($itemnumber);
98     is( $getitem->{location}, 'CART', "The location should have been set to CART" );
99     is( $getitem->{permanent_location}, $location, "The permanent_location should not have been set to CART" );
100
101     t::lib::Mocks::mock_preference('item-level_itypes', '1');
102     $getitem = GetItem($itemnumber);
103     is( $getitem->{itype}, $itemtype->{itemtype}, "Itemtype set correctly when using item-level_itypes" );
104     t::lib::Mocks::mock_preference('item-level_itypes', '0');
105     $getitem = GetItem($itemnumber);
106     is( $getitem->{itype}, undef, "Itemtype set correctly when not using item-level_itypes" );
107
108     $schema->storage->txn_rollback;
109 };
110
111 subtest 'ModItem tests' => sub {
112     plan tests => 6;
113
114     $schema->storage->txn_begin;
115
116     my $builder = t::lib::TestBuilder->new;
117     my $item = $builder->build({
118         source => 'Item',
119         value  => {
120             itemlost     => 0,
121             damaged      => 0,
122             withdrawn    => 0,
123             itemlost_on  => undef,
124             damaged_on   => undef,
125             withdrawn_on => undef,
126         }
127     });
128
129     my @fields = qw( itemlost withdrawn damaged );
130     for my $field (@fields) {
131         $item->{$field} = 1;
132         ModItem( $item, $item->{biblionumber}, $item->{itemnumber} );
133         my $post_mod_item = Koha::Items->find({ itemnumber => $item->{itemnumber} })->unblessed;
134         is( output_pref({ str => $post_mod_item->{$field."_on"}, dateonly => 1 }), output_pref({ dt => dt_from_string(), dateonly => 1 }), "When updating $field, $field"."_on is updated" );
135
136         $item->{$field} = 0;
137         ModItem( $item, $item->{biblionumber}, $item->{itemnumber} );
138         $post_mod_item = Koha::Items->find({ itemnumber => $item->{itemnumber} })->unblessed;
139         is( $post_mod_item->{$field."_on"}, undef, "When clearing $field, $field"."_on is cleared" );
140     }
141
142     $schema->storage->txn_rollback;
143
144 };
145
146 subtest 'GetHiddenItemnumbers tests' => sub {
147
148     plan tests => 9;
149
150     # This sub is controlled by the OpacHiddenItems system preference.
151
152     $schema->storage->txn_begin;
153
154     my $builder = t::lib::TestBuilder->new;
155     my $library1 = $builder->build({
156         source => 'Branch',
157     });
158
159     my $library2 = $builder->build({
160         source => 'Branch',
161     });
162     my $itemtype = $builder->build({
163         source => 'Itemtype',
164     });
165
166     # Create a new biblio
167     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
168     my ($biblionumber, $biblioitemnumber) = get_biblio();
169
170     # Add two items
171     my ( $item1_bibnum, $item1_bibitemnum, $item1_itemnumber ) = AddItem(
172         {
173             homebranch    => $library1->{branchcode},
174             holdingbranch => $library1->{branchcode},
175             withdrawn     => 1,
176             itype         => $itemtype->{itemtype},
177         },
178         $biblionumber
179     );
180     my ( $item2_bibnum, $item2_bibitemnum, $item2_itemnumber ) = AddItem(
181         {
182             homebranch    => $library2->{branchcode},
183             holdingbranch => $library2->{branchcode},
184             withdrawn     => 0,
185             itype         => $itemtype->{itemtype},
186         },
187         $biblionumber
188     );
189
190     my $opachiddenitems;
191     my @itemnumbers = ($item1_itemnumber,$item2_itemnumber);
192     my @hidden;
193     my @items;
194     push @items, GetItem( $item1_itemnumber );
195     push @items, GetItem( $item2_itemnumber );
196
197     # Empty OpacHiddenItems
198     t::lib::Mocks::mock_preference('OpacHiddenItems','');
199     ok( !defined( GetHiddenItemnumbers( @items ) ),
200         "Hidden items list undef if OpacHiddenItems empty");
201
202     # Blank spaces
203     t::lib::Mocks::mock_preference('OpacHiddenItems','  ');
204     ok( scalar GetHiddenItemnumbers( @items ) == 0,
205         "Hidden items list empty if OpacHiddenItems only contains blanks");
206
207     # One variable / value
208     $opachiddenitems = "
209         withdrawn: [1]";
210     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
211     @hidden = GetHiddenItemnumbers( @items );
212     ok( scalar @hidden == 1, "Only one hidden item");
213     is( $hidden[0], $item1_itemnumber, "withdrawn=1 is hidden");
214
215     # One variable, two values
216     $opachiddenitems = "
217         withdrawn: [1,0]";
218     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
219     @hidden = GetHiddenItemnumbers( @items );
220     ok( scalar @hidden == 2, "Two items hidden");
221     is_deeply( \@hidden, \@itemnumbers, "withdrawn=1 and withdrawn=0 hidden");
222
223     # Two variables, a value each
224     $opachiddenitems = "
225         withdrawn: [1]
226         homebranch: [$library2->{branchcode}]
227     ";
228     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
229     @hidden = GetHiddenItemnumbers( @items );
230     ok( scalar @hidden == 2, "Two items hidden");
231     is_deeply( \@hidden, \@itemnumbers, "withdrawn=1 and homebranch library2 hidden");
232
233     # Valid OpacHiddenItems, empty list
234     @items = ();
235     @hidden = GetHiddenItemnumbers( @items );
236     ok( scalar @hidden == 0, "Empty items list, no item hidden");
237
238     $schema->storage->txn_rollback;
239 };
240
241 subtest 'GetItemsInfo tests' => sub {
242
243     plan tests => 4;
244
245     $schema->storage->txn_begin;
246
247     my $builder = t::lib::TestBuilder->new;
248     my $library1 = $builder->build({
249         source => 'Branch',
250     });
251     my $library2 = $builder->build({
252         source => 'Branch',
253     });
254     my $itemtype = $builder->build({
255         source => 'Itemtype',
256     });
257
258     # Add a biblio
259     my ($biblionumber, $biblioitemnumber) = get_biblio();
260     # Add an item
261     my ( $item_bibnum, $item_bibitemnum, $itemnumber ) = AddItem(
262         {
263             homebranch    => $library1->{branchcode},
264             holdingbranch => $library2->{branchcode},
265             itype         => $itemtype->{itemtype},
266         },
267         $biblionumber
268     );
269
270     my $library = Koha::Libraries->find( $library1->{branchcode} );
271     $library->opac_info("homebranch OPAC info");
272     $library->store;
273
274     $library = Koha::Libraries->find( $library2->{branchcode} );
275     $library->opac_info("holdingbranch OPAC info");
276     $library->store;
277
278     my @results = GetItemsInfo( $biblionumber );
279     ok( @results, 'GetItemsInfo returns results');
280     is( $results[0]->{ home_branch_opac_info }, "homebranch OPAC info",
281         'GetItemsInfo returns the correct home branch OPAC info notice' );
282     is( $results[0]->{ holding_branch_opac_info }, "holdingbranch OPAC info",
283         'GetItemsInfo returns the correct holding branch OPAC info notice' );
284     is( exists( $results[0]->{ onsite_checkout } ), 1,
285         'GetItemsInfo returns a onsite_checkout key' );
286
287     $schema->storage->txn_rollback;
288 };
289
290 subtest q{Test Koha::Database->schema()->resultset('Item')->itemtype()} => sub {
291
292     plan tests => 4;
293
294     $schema->storage->txn_begin;
295
296     my $biblio = $schema->resultset('Biblio')->create({
297         title       => "Test title",
298         datecreated => dt_from_string,
299         biblioitems => [ { itemtype => 'BIB_LEVEL' } ],
300     });
301     my $biblioitem = $biblio->biblioitems->first;
302     my $item = $schema->resultset('Item')->create({
303         biblioitemnumber => $biblioitem->biblioitemnumber,
304         biblionumber     => $biblio->biblionumber,
305         itype            => "ITEM_LEVEL",
306     });
307
308     t::lib::Mocks::mock_preference( 'item-level_itypes', 0 );
309     is( $item->effective_itemtype(), 'BIB_LEVEL', '$item->itemtype() returns biblioitem.itemtype when item-level_itypes is disabled' );
310
311     t::lib::Mocks::mock_preference( 'item-level_itypes', 1 );
312     is( $item->effective_itemtype(), 'ITEM_LEVEL', '$item->itemtype() returns items.itype when item-level_itypes is enabled' );
313
314     # If itemtype is not defined and item-level_level item types are set
315     # fallback to biblio-level itemtype (Bug 14651) and warn
316     $item->itype( undef );
317     $item->update();
318     my $effective_itemtype;
319     warning_is { $effective_itemtype = $item->effective_itemtype() }
320                 "item-level_itypes set but no itemtype set for item (".$item->itemnumber.")",
321                 '->effective_itemtype() raises a warning when falling back to bib-level';
322
323     ok( defined $effective_itemtype &&
324                 $effective_itemtype eq 'BIB_LEVEL',
325         '$item->effective_itemtype() falls back to biblioitems.itemtype when item-level_itypes is enabled but undef' );
326
327     $schema->storage->txn_rollback;
328 };
329
330 subtest 'SearchItems test' => sub {
331     plan tests => 14;
332
333     $schema->storage->txn_begin;
334     my $dbh = C4::Context->dbh;
335     my $builder = t::lib::TestBuilder->new;
336
337     my $library1 = $builder->build({
338         source => 'Branch',
339     });
340     my $library2 = $builder->build({
341         source => 'Branch',
342     });
343     my $itemtype = $builder->build({
344         source => 'Itemtype',
345     });
346
347     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
348     my $cpl_items_before = SearchItemsByField( 'homebranch', $library1->{branchcode});
349
350     my ($biblionumber) = get_biblio();
351
352     my (undef, $initial_items_count) = SearchItems(undef, {rows => 1});
353
354     # Add two items
355     my (undef, undef, $item1_itemnumber) = AddItem({
356         homebranch => $library1->{branchcode},
357         holdingbranch => $library1->{branchcode},
358         itype => $itemtype->{itemtype},
359     }, $biblionumber);
360     my (undef, undef, $item2_itemnumber) = AddItem({
361         homebranch => $library2->{branchcode},
362         holdingbranch => $library2->{branchcode},
363         itype => $itemtype->{itemtype},
364     }, $biblionumber);
365
366     my ($items, $total_results);
367
368     ($items, $total_results) = SearchItems();
369     is($total_results, $initial_items_count + 2, "Created 2 new items");
370     is(scalar @$items, $total_results, "SearchItems() returns all items");
371
372     ($items, $total_results) = SearchItems(undef, {rows => 1});
373     is($total_results, $initial_items_count + 2);
374     is(scalar @$items, 1, "SearchItems(undef, {rows => 1}) returns only 1 item");
375
376     # Search all items where homebranch = 'CPL'
377     my $filter = {
378         field => 'homebranch',
379         query => $library1->{branchcode},
380         operator => '=',
381     };
382     ($items, $total_results) = SearchItems($filter);
383     ok($total_results > 0, "There is at least one CPL item");
384     my $all_items_are_CPL = 1;
385     foreach my $item (@$items) {
386         if ($item->{homebranch} ne $library1->{branchcode}) {
387             $all_items_are_CPL = 0;
388             last;
389         }
390     }
391     ok($all_items_are_CPL, "All items returned by SearchItems are from CPL");
392
393     # Search all items where homebranch != 'CPL'
394     $filter = {
395         field => 'homebranch',
396         query => $library1->{branchcode},
397         operator => '!=',
398     };
399     ($items, $total_results) = SearchItems($filter);
400     ok($total_results > 0, "There is at least one non-CPL item");
401     my $all_items_are_not_CPL = 1;
402     foreach my $item (@$items) {
403         if ($item->{homebranch} eq $library1->{branchcode}) {
404             $all_items_are_not_CPL = 0;
405             last;
406         }
407     }
408     ok($all_items_are_not_CPL, "All items returned by SearchItems are not from CPL");
409
410     # Search all items where biblio title (245$a) is like 'Silence in the %'
411     $filter = {
412         field => 'marc:245$a',
413         query => 'Silence in the %',
414         operator => 'like',
415     };
416     ($items, $total_results) = SearchItems($filter);
417     ok($total_results >= 2, "There is at least 2 items with a biblio title like 'Silence in the %'");
418
419     # Search all items where biblio title is 'Silence in the library'
420     # and homebranch is 'CPL'
421     $filter = {
422         conjunction => 'AND',
423         filters => [
424             {
425                 field => 'marc:245$a',
426                 query => 'Silence in the %',
427                 operator => 'like',
428             },
429             {
430                 field => 'homebranch',
431                 query => $library1->{branchcode},
432                 operator => '=',
433             },
434         ],
435     };
436     ($items, $total_results) = SearchItems($filter);
437     my $found = 0;
438     foreach my $item (@$items) {
439         if ($item->{itemnumber} == $item1_itemnumber) {
440             $found = 1;
441             last;
442         }
443     }
444     ok($found, "item1 found");
445
446     my $frameworkcode = q||;
447     my ($itemfield) = GetMarcFromKohaField('items.itemnumber', $frameworkcode);
448
449     # Create item subfield 'z' without link
450     $dbh->do('DELETE FROM marc_subfield_structure WHERE tagfield=? AND tagsubfield="z" AND frameworkcode=?', undef, $itemfield, $frameworkcode);
451     $dbh->do('INSERT INTO marc_subfield_structure (tagfield, tagsubfield, frameworkcode) VALUES (?, "z", ?)', undef, $itemfield, $frameworkcode);
452
453     # Clear cache
454     my $cache = Koha::Caches->get_instance();
455     $cache->clear_from_cache("MarcStructure-0-$frameworkcode");
456     $cache->clear_from_cache("MarcStructure-1-$frameworkcode");
457     $cache->clear_from_cache("default_value_for_mod_marc-");
458     $cache->clear_from_cache("MarcSubfieldStructure-$frameworkcode");
459
460     my $item3_record = new MARC::Record;
461     $item3_record->append_fields(
462         new MARC::Field(
463             $itemfield, '', '',
464             'z' => 'foobar',
465             'y' => $itemtype->{itemtype}
466         )
467     );
468     my (undef, undef, $item3_itemnumber) = AddItemFromMarc($item3_record,
469         $biblionumber);
470
471     # Search item where item subfield z is "foobar"
472     $filter = {
473         field => 'marc:' . $itemfield . '$z',
474         query => 'foobar',
475         operator => 'like',
476     };
477     ($items, $total_results) = SearchItems($filter);
478     ok(scalar @$items == 1, 'found 1 item with $z = "foobar"');
479
480     # Link $z to items.itemnotes (and make sure there is no other subfields
481     # linked to it)
482     $dbh->do('DELETE FROM marc_subfield_structure WHERE kohafield="items.itemnotes" AND frameworkcode=?', undef, $itemfield, $frameworkcode);
483     $dbh->do('UPDATE marc_subfield_structure SET kohafield="items.itemnotes" WHERE tagfield=? AND tagsubfield="z" AND frameworkcode=?', undef, $itemfield, $frameworkcode);
484
485     # Clear cache
486     $cache->clear_from_cache("MarcStructure-0-$frameworkcode");
487     $cache->clear_from_cache("MarcStructure-1-$frameworkcode");
488     $cache->clear_from_cache("default_value_for_mod_marc-");
489     $cache->clear_from_cache("MarcSubfieldStructure-$frameworkcode");
490
491     ModItemFromMarc($item3_record, $biblionumber, $item3_itemnumber);
492
493     # Make sure the link is used
494     my $item3 = GetItem($item3_itemnumber);
495     ok($item3->{itemnotes} eq 'foobar', 'itemnotes eq "foobar"');
496
497     # Do the same search again.
498     # This time it will search in items.itemnotes
499     ($items, $total_results) = SearchItems($filter);
500     ok(scalar @$items == 1, 'found 1 item with itemnotes = "foobar"');
501
502     my $cpl_items_after = SearchItemsByField( 'homebranch', $library1->{branchcode});
503     is( ( scalar( @$cpl_items_after ) - scalar ( @$cpl_items_before ) ), 1, 'SearchItemsByField should return something' );
504
505     $schema->storage->txn_rollback;
506 };
507
508 subtest 'Koha::Item(s) tests' => sub {
509
510     plan tests => 5;
511
512     $schema->storage->txn_begin();
513
514     my $builder = t::lib::TestBuilder->new;
515     my $library1 = $builder->build({
516         source => 'Branch',
517     });
518     my $library2 = $builder->build({
519         source => 'Branch',
520     });
521     my $itemtype = $builder->build({
522         source => 'Itemtype',
523     });
524
525     # Create a biblio and item for testing
526     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
527     my ($bibnum, $bibitemnum) = get_biblio();
528     my ( $item_bibnum, $item_bibitemnum, $itemnumber ) = AddItem(
529         {
530             homebranch    => $library1->{branchcode},
531             holdingbranch => $library2->{branchcode},
532             itype         => $itemtype->{itemtype},
533         },
534         $bibnum
535     );
536
537     # Get item.
538     my $item = Koha::Items->find( $itemnumber );
539     is( ref($item), 'Koha::Item', "Got Koha::Item" );
540
541     my $homebranch = $item->home_branch();
542     is( ref($homebranch), 'Koha::Library', "Got Koha::Library from home_branch method" );
543     is( $homebranch->branchcode(), $library1->{branchcode}, "Home branch code matches homebranch" );
544
545     my $holdingbranch = $item->holding_branch();
546     is( ref($holdingbranch), 'Koha::Library', "Got Koha::Library from holding_branch method" );
547     is( $holdingbranch->branchcode(), $library2->{branchcode}, "Home branch code matches holdingbranch" );
548
549     $schema->storage->txn_rollback;
550 };
551
552 subtest 'C4::Biblio::EmbedItemsInMarcBiblio' => sub {
553     plan tests => 7;
554
555     $schema->storage->txn_begin();
556
557     my $builder = t::lib::TestBuilder->new;
558     my $library1 = $builder->build({
559         source => 'Branch',
560     });
561     my $library2 = $builder->build({
562         source => 'Branch',
563     });
564     my $itemtype = $builder->build({
565         source => 'Itemtype',
566     });
567
568     my ( $biblionumber, $biblioitemnumber ) = get_biblio();
569     my $item_infos = [
570         { homebranch => $library1->{branchcode}, holdingbranch => $library1->{branchcode} },
571         { homebranch => $library1->{branchcode}, holdingbranch => $library1->{branchcode} },
572         { homebranch => $library1->{branchcode}, holdingbranch => $library1->{branchcode} },
573         { homebranch => $library2->{branchcode}, holdingbranch => $library2->{branchcode} },
574         { homebranch => $library2->{branchcode}, holdingbranch => $library2->{branchcode} },
575         { homebranch => $library1->{branchcode}, holdingbranch => $library2->{branchcode} },
576         { homebranch => $library1->{branchcode}, holdingbranch => $library2->{branchcode} },
577         { homebranch => $library1->{branchcode}, holdingbranch => $library2->{branchcode} },
578     ];
579     my $number_of_items = scalar @$item_infos;
580     my $number_of_items_with_homebranch_is_CPL =
581       grep { $_->{homebranch} eq $library1->{branchcode} } @$item_infos;
582
583     my @itemnumbers;
584     for my $item_info (@$item_infos) {
585         my ( undef, undef, $itemnumber ) = AddItem(
586             {
587                 homebranch    => $item_info->{homebranch},
588                 holdingbranch => $item_info->{holdingbanch},
589                 itype         => $itemtype->{itemtype},
590             },
591             $biblionumber
592         );
593         push @itemnumbers, $itemnumber;
594     }
595
596     # Emptied the OpacHiddenItems pref
597     t::lib::Mocks::mock_preference( 'OpacHiddenItems', '' );
598
599     my ($itemfield) =
600       C4::Biblio::GetMarcFromKohaField( 'items.itemnumber', '' );
601     my $record = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
602     warning_is { C4::Biblio::EmbedItemsInMarcBiblio() }
603     { carped => 'EmbedItemsInMarcBiblio: No MARC record passed' },
604       'Should crap is no record passed.';
605
606     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber );
607     my @items = $record->field($itemfield);
608     is( scalar @items, $number_of_items, 'Should return all items' );
609
610     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber,
611         [ $itemnumbers[1], $itemnumbers[3] ] );
612     @items = $record->field($itemfield);
613     is( scalar @items, 2, 'Should return all items present in the list' );
614
615     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, 1 );
616     @items = $record->field($itemfield);
617     is( scalar @items, $number_of_items, 'Should return all items for opac' );
618
619     my $opachiddenitems = "
620         homebranch: ['$library1->{branchcode}']";
621     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
622
623     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber );
624     @items = $record->field($itemfield);
625     is( scalar @items,
626         $number_of_items,
627         'Even with OpacHiddenItems set, all items should have been embedded' );
628
629     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, 1 );
630     @items = $record->field($itemfield);
631     is(
632         scalar @items,
633         $number_of_items - $number_of_items_with_homebranch_is_CPL,
634 'For OPAC, the pref OpacHiddenItems should have been take into account. Only items with homebranch ne CPL should have been embedded'
635     );
636
637     $opachiddenitems = "
638         homebranch: ['$library1->{branchcode}', '$library2->{branchcode}']";
639     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
640     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, 1 );
641     @items = $record->field($itemfield);
642     is(
643         scalar @items,
644         0,
645 'For OPAC, If all items are hidden, no item should have been embedded'
646     );
647
648     $schema->storage->txn_rollback;
649 };
650
651
652 subtest 'C4::Items::_build_default_values_for_mod_marc' => sub {
653     plan tests => 4;
654
655     $schema->storage->txn_begin();
656
657     my $builder = t::lib::TestBuilder->new;
658     my $framework = $builder->build({ source => 'BiblioFramework' });
659
660     # Link biblio.biblionumber and biblioitems.biblioitemnumber to avoid _koha_marc_update_bib_ids to fail with 'no biblio[item]number tag for framework"
661     Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '999', tagsubfield => [ 'c', 'd' ] })->delete;
662     Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '999', tagsubfield => 'c', kohafield => 'biblio.biblionumber' })->store;
663     Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '999', tagsubfield => 'd', kohafield => 'biblioitems.biblioitemnumber' })->store;
664
665     # Same for item fields: itemnumber, barcode, itype
666     Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '952', tagsubfield => [ '9', 'p', 'y' ] })->delete;
667     Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => '9', kohafield => 'items.itemnumber' })->store;
668     Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => 'p', kohafield => 'items.barcode' })->store;
669     Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => 'y', kohafield => 'items.itype' })->store;
670     Koha::Caches->get_instance->clear_from_cache( "MarcSubfieldStructure-" );
671
672     my $itemtype = $builder->build({ source => 'Itemtype' })->{itemtype};
673
674     # Create a record with a barcode
675     my ($biblionumber) = get_biblio( $framework->{frameworkcode} );
676     my $item_record = new MARC::Record;
677     my $a_barcode = 'a barcode';
678     my $barcode_field = MARC::Field->new(
679         '952', ' ', ' ',
680         p => $a_barcode,
681         y => $itemtype
682     );
683     my $itemtype_field = MARC::Field->new(
684         '952', ' ', ' ',
685         y => $itemtype
686     );
687     $item_record->append_fields( $barcode_field );
688     my (undef, undef, $item_itemnumber) = AddItemFromMarc($item_record, $biblionumber);
689
690     # Make sure everything has been set up
691     my $item = GetItem($item_itemnumber);
692     is( $item->{barcode}, $a_barcode, 'Everything has been set up correctly, the barcode is defined as expected' );
693
694     # Delete the barcode field and save the record
695     $item_record->delete_fields( $barcode_field );
696     $item_record->append_fields( $itemtype_field ); # itemtype is mandatory
697     ModItemFromMarc($item_record, $biblionumber, $item_itemnumber);
698     $item = GetItem($item_itemnumber);
699     is( $item->{barcode}, undef, 'The default value should have been set to the barcode, the field is mapped to a kohafield' );
700
701     # Re-add the barcode field and save the record
702     $item_record->append_fields( $barcode_field );
703     ModItemFromMarc($item_record, $biblionumber, $item_itemnumber);
704     $item = GetItem($item_itemnumber);
705     is( $item->{barcode}, $a_barcode, 'Everything has been set up correctly, the barcode is defined as expected' );
706
707     # Remove the mapping for barcode
708     Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '952', tagsubfield => 'p' })->delete;
709
710     # And make sure the caches are cleared
711     my $cache = Koha::Caches->get_instance();
712     $cache->clear_from_cache("default_value_for_mod_marc-");
713     $cache->clear_from_cache("MarcSubfieldStructure-");
714
715     # Update the MARC field with another value
716     $item_record->delete_fields( $barcode_field );
717     my $another_barcode = 'another_barcode';
718     my $another_barcode_field = MARC::Field->new(
719         '952', ' ', ' ',
720         p => $another_barcode,
721     );
722     $item_record->append_fields( $another_barcode_field );
723     # The DB value should not have been updated
724     ModItemFromMarc($item_record, $biblionumber, $item_itemnumber);
725     $item = GetItem($item_itemnumber);
726     is ( $item->{barcode}, $a_barcode, 'items.barcode is not mapped anymore, so the DB column has not been updated' );
727
728     $cache->clear_from_cache("default_value_for_mod_marc-");
729     $cache->clear_from_cache( "MarcSubfieldStructure-" );
730     $schema->storage->txn_rollback;
731 };
732
733 subtest '_mod_item_dates' => sub {
734     plan tests => 11;
735
736     is( C4::Items::_mod_item_dates(), undef, 'Call without parameters' );
737     is( C4::Items::_mod_item_dates(1), undef, 'Call without hashref' );
738
739     my $orgitem;
740     my $item = {
741         itemcallnumber  => 'V II 149 1963',
742         barcode         => '109304',
743     };
744     $orgitem = { %$item };
745     C4::Items::_mod_item_dates($item);
746     is_deeply( $item, $orgitem, 'No dates passed to _mod_item_dates' );
747
748     # add two correct dates
749     t::lib::Mocks::mock_preference('dateformat', 'us');
750     $item->{dateaccessioned} = '01/31/2016';
751     $item->{onloan} =  $item->{dateaccessioned};
752     $orgitem = { %$item };
753     C4::Items::_mod_item_dates($item);
754     is( $item->{dateaccessioned}, '2016-01-31', 'dateaccessioned is fine' );
755     is( $item->{onloan}, '2016-01-31', 'onloan is fine too' );
756
757
758     # add some invalid dates
759     $item->{notexistingcolumndate} = '13/1/2015'; # wrong format
760     $item->{anotherdate} = 'tralala'; # even worse
761     $item->{myzerodate} = '0000-00-00'; # wrong too
762     C4::Items::_mod_item_dates($item);
763     is( $item->{notexistingcolumndate}, undef, 'Invalid date became NULL' );
764     is( $item->{anotherdate}, undef, 'Second invalid date became NULL too' );
765     is( $item->{myzerodate}, undef, '0000-00-00 became NULL too' );
766
767     # check if itemlost_on was not touched
768     $item->{itemlost_on} = '12345678';
769     $item->{withdrawn_on} = '12/31/2015 23:59:00';
770     $item->{damaged_on} = '01/20/2017 09:00:00';
771     $orgitem = { %$item };
772     C4::Items::_mod_item_dates($item);
773     is_deeply( $item, $orgitem, 'Colums with _on are not touched' );
774
775     t::lib::Mocks::mock_preference('dateformat', 'metric');
776     $item->{dateaccessioned} = '01/31/2016'; #wrong
777     $item->{yetanotherdatetime} = '20/01/2016 13:58:00'; #okay
778     C4::Items::_mod_item_dates($item);
779     is( $item->{dateaccessioned}, undef, 'dateaccessioned wrong format' );
780     is( $item->{yetanotherdatetime}, '2016-01-20 13:58:00',
781         'yetanotherdatetime is ok' );
782 };
783
784 subtest 'get_hostitemnumbers_of' => sub {
785     plan tests => 1;
786
787     my $bib = MARC::Record->new();
788     $bib->append_fields(
789         MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'),
790         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
791         MARC::Field->new('773', ' ', ' ', b => 'b without 0 or 9'),
792     );
793     my ($biblionumber, $bibitemnum) = AddBiblio($bib, '');
794
795     my @itemnumbers = C4::Items::get_hostitemnumbers_of( $biblionumber );
796     is( @itemnumbers, 0, );
797 };
798
799 subtest 'Test logging for AddItem' => sub {
800
801     plan tests => 3;
802
803     t::lib::Mocks::mock_preference('CataloguingLog', 1);
804
805     $schema->storage->txn_begin;
806
807     my $builder = t::lib::TestBuilder->new;
808     my $library = $builder->build({
809         source => 'Branch',
810     });
811     my $itemtype = $builder->build({
812         source => 'Itemtype',
813     });
814
815     # Create a biblio instance for testing
816     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
817     my ($bibnum, $bibitemnum) = get_biblio();
818
819     # Add an item.
820     my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $library->{branchcode}, holdingbranch => $library->{branchcode}, location => $location, itype => $itemtype->{itemtype} } , $bibnum);
821
822     # False means no logging
823     $schema->resultset('ActionLog')->search()->delete();
824     ModItem({ location => $location }, $bibnum, $itemnumber, 0);
825     is( $schema->resultset('ActionLog')->count(), 0, 'False value does not trigger logging' );
826
827     # True means logging
828     $schema->resultset('ActionLog')->search()->delete();
829     ModItem({ location => $location }, $bibnum, $itemnumber, 1, 'True value does trigger logging');
830     is( $schema->resultset('ActionLog')->count(), 1 );
831
832     # Undefined defaults to true
833     $schema->resultset('ActionLog')->search()->delete();
834     ModItem({ location => $location }, $bibnum, $itemnumber);
835     is( $schema->resultset('ActionLog')->count(), 1, 'Undefined value defaults to true, triggers logging' );
836
837     $schema->storage->txn_rollback;
838 };
839
840 # Helper method to set up a Biblio.
841 sub get_biblio {
842     my ( $frameworkcode ) = @_;
843     $frameworkcode //= '';
844     my $bib = MARC::Record->new();
845     $bib->append_fields(
846         MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'),
847         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
848     );
849     my ($bibnum, $bibitemnum) = AddBiblio($bib, $frameworkcode);
850     return ($bibnum, $bibitemnum);
851 }