Bug 23463: Remove DelItem
[koha.git] / C4 / Items.pm
1 package C4::Items;
2
3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use Modern::Perl;
22
23 use vars qw(@ISA @EXPORT);
24 BEGIN {
25     require Exporter;
26     @ISA = qw(Exporter);
27
28     @EXPORT = qw(
29         AddItemFromMarc
30         AddItemBatchFromMarc
31         ModItemFromMarc
32         Item2Marc
33         ModDateLastSeen
34         ModItemTransfer
35         CheckItemPreSave
36         GetItemsForInventory
37         GetItemsInfo
38         GetItemsLocationInfo
39         GetHostItemsInfo
40         get_hostitemnumbers_of
41         GetHiddenItemnumbers
42         ItemSafeToDelete
43         DelItemCheck
44         MoveItemFromBiblio
45         CartToShelf
46         GetAnalyticsCount
47         SearchItems
48         PrepareItemrecordDisplay
49     );
50 }
51
52 use Carp;
53 use Try::Tiny;
54 use C4::Context;
55 use C4::Koha;
56 use C4::Biblio;
57 use Koha::DateUtils;
58 use MARC::Record;
59 use C4::ClassSource;
60 use C4::Log;
61 use List::MoreUtils qw(any);
62 use YAML qw(Load);
63 use DateTime::Format::MySQL;
64 use Data::Dumper; # used as part of logging item record changes, not just for
65                   # debugging; so please don't remove this
66
67 use Koha::AuthorisedValues;
68 use Koha::DateUtils qw(dt_from_string);
69 use Koha::Database;
70
71 use Koha::Biblioitems;
72 use Koha::Items;
73 use Koha::ItemTypes;
74 use Koha::SearchEngine;
75 use Koha::SearchEngine::Search;
76 use Koha::Libraries;
77
78 =head1 NAME
79
80 C4::Items - item management functions
81
82 =head1 DESCRIPTION
83
84 This module contains an API for manipulating item 
85 records in Koha, and is used by cataloguing, circulation,
86 acquisitions, and serials management.
87
88 # FIXME This POD is not up-to-date
89 A Koha item record is stored in two places: the
90 items table and embedded in a MARC tag in the XML
91 version of the associated bib record in C<biblioitems.marcxml>.
92 This is done to allow the item information to be readily
93 indexed (e.g., by Zebra), but means that each item
94 modification transaction must keep the items table
95 and the MARC XML in sync at all times.
96
97 The items table will be considered authoritative.  In other
98 words, if there is ever a discrepancy between the items
99 table and the MARC XML, the items table should be considered
100 accurate.
101
102 =head1 HISTORICAL NOTE
103
104 Most of the functions in C<C4::Items> were originally in
105 the C<C4::Biblio> module.
106
107 =head1 CORE EXPORTED FUNCTIONS
108
109 The following functions are meant for use by users
110 of C<C4::Items>
111
112 =cut
113
114 =head2 CartToShelf
115
116   CartToShelf($itemnumber);
117
118 Set the current shelving location of the item record
119 to its stored permanent shelving location.  This is
120 primarily used to indicate when an item whose current
121 location is a special processing ('PROC') or shelving cart
122 ('CART') location is back in the stacks.
123
124 =cut
125
126 sub CartToShelf {
127     my ( $itemnumber ) = @_;
128
129     unless ( $itemnumber ) {
130         croak "FAILED CartToShelf() - no itemnumber supplied";
131     }
132
133     my $item = Koha::Items->find($itemnumber);
134     if ( $item->location eq 'CART' ) {
135         $item->location($item->permanent_location)->store;
136     }
137 }
138
139 =head2 AddItemFromMarc
140
141   my ($biblionumber, $biblioitemnumber, $itemnumber) 
142       = AddItemFromMarc($source_item_marc, $biblionumber);
143
144 Given a MARC::Record object containing an embedded item
145 record and a biblionumber, create a new item record.
146
147 =cut
148
149 sub AddItemFromMarc {
150     my ( $source_item_marc, $biblionumber ) = @_;
151     my $dbh = C4::Context->dbh;
152
153     # parse item hash from MARC
154     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
155     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
156
157     my $localitemmarc = MARC::Record->new;
158     $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
159
160     my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
161     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
162     $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
163     $item_values->{biblionumber} = $biblionumber;
164     my $item = Koha::Item->new( $item_values )->store;
165     return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
166 }
167
168 =head2 AddItemBatchFromMarc
169
170   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
171              $biblionumber, $biblioitemnumber, $frameworkcode);
172
173 Efficiently create item records from a MARC biblio record with
174 embedded item fields.  This routine is suitable for batch jobs.
175
176 This API assumes that the bib record has already been
177 saved to the C<biblio> and C<biblioitems> tables.  It does
178 not expect that C<biblio_metadata.metadata> is populated, but it
179 will do so via a call to ModBibiloMarc.
180
181 The goal of this API is to have a similar effect to using AddBiblio
182 and AddItems in succession, but without inefficient repeated
183 parsing of the MARC XML bib record.
184
185 This function returns an arrayref of new itemsnumbers and an arrayref of item
186 errors encountered during the processing.  Each entry in the errors
187 list is a hashref containing the following keys:
188
189 =over
190
191 =item item_sequence
192
193 Sequence number of original item tag in the MARC record.
194
195 =item item_barcode
196
197 Item barcode, provide to assist in the construction of
198 useful error messages.
199
200 =item error_code
201
202 Code representing the error condition.  Can be 'duplicate_barcode',
203 'invalid_homebranch', or 'invalid_holdingbranch'.
204
205 =item error_information
206
207 Additional information appropriate to the error condition.
208
209 =back
210
211 =cut
212
213 sub AddItemBatchFromMarc {
214     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
215     my $error;
216     my @itemnumbers = ();
217     my @errors = ();
218     my $dbh = C4::Context->dbh;
219
220     # We modify the record, so lets work on a clone so we don't change the
221     # original.
222     $record = $record->clone();
223     # loop through the item tags and start creating items
224     my @bad_item_fields = ();
225     my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
226     my $item_sequence_num = 0;
227     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
228         $item_sequence_num++;
229         # we take the item field and stick it into a new
230         # MARC record -- this is required so far because (FIXME)
231         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
232         # and there is no TransformMarcFieldToKoha
233         my $temp_item_marc = MARC::Record->new();
234         $temp_item_marc->append_fields($item_field);
235     
236         # add biblionumber and biblioitemnumber
237         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
238         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
239         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
240         $item->{'biblionumber'} = $biblionumber;
241         $item->{'biblioitemnumber'} = $biblioitemnumber;
242
243         # check for duplicate barcode
244         my %item_errors = CheckItemPreSave($item);
245         if (%item_errors) {
246             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
247             push @bad_item_fields, $item_field;
248             next ITEMFIELD;
249         }
250
251         _set_derived_columns_for_add($item);
252         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
253         warn $error if $error;
254         push @itemnumbers, $itemnumber; # FIXME not checking error
255         $item->{'itemnumber'} = $itemnumber;
256
257         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
258
259         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
260         $item_field->replace_with($new_item_marc->field($itemtag));
261     }
262
263     # remove any MARC item fields for rejected items
264     foreach my $item_field (@bad_item_fields) {
265         $record->delete_field($item_field);
266     }
267
268     # update the MARC biblio
269  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
270
271     return (\@itemnumbers, \@errors);
272 }
273
274 =head2 ModItemFromMarc
275
276   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
277
278 This function updates an item record based on a supplied
279 C<MARC::Record> object containing an embedded item field.
280 This API is meant for the use of C<additem.pl>; for 
281 other purposes, C<ModItem> should be used.
282
283 This function uses the hash %default_values_for_mod_from_marc,
284 which contains default values for item fields to
285 apply when modifying an item.  This is needed because
286 if an item field's value is cleared, TransformMarcToKoha
287 does not include the column in the
288 hash that's passed to ModItem, which without
289 use of this hash makes it impossible to clear
290 an item field's value.  See bug 2466.
291
292 Note that only columns that can be directly
293 changed from the cataloging and serials
294 item editors are included in this hash.
295
296 Returns item record
297
298 =cut
299
300 sub _build_default_values_for_mod_marc {
301     # Has no framework parameter anymore, since Default is authoritative
302     # for Koha to MARC mappings.
303
304     my $cache     = Koha::Caches->get_instance();
305     my $cache_key = "default_value_for_mod_marc-";
306     my $cached    = $cache->get_from_cache($cache_key);
307     return $cached if $cached;
308
309     my $default_values = {
310         barcode                  => undef,
311         booksellerid             => undef,
312         ccode                    => undef,
313         'items.cn_source'        => undef,
314         coded_location_qualifier => undef,
315         copynumber               => undef,
316         damaged                  => 0,
317         enumchron                => undef,
318         holdingbranch            => undef,
319         homebranch               => undef,
320         itemcallnumber           => undef,
321         itemlost                 => 0,
322         itemnotes                => undef,
323         itemnotes_nonpublic      => undef,
324         itype                    => undef,
325         location                 => undef,
326         permanent_location       => undef,
327         materials                => undef,
328         new_status               => undef,
329         notforloan               => 0,
330         price                    => undef,
331         replacementprice         => undef,
332         replacementpricedate     => undef,
333         restricted               => undef,
334         stack                    => undef,
335         stocknumber              => undef,
336         uri                      => undef,
337         withdrawn                => 0,
338     };
339     my %default_values_for_mod_from_marc;
340     while ( my ( $field, $default_value ) = each %$default_values ) {
341         my $kohafield = $field;
342         $kohafield =~ s|^([^\.]+)$|items.$1|;
343         $default_values_for_mod_from_marc{$field} = $default_value
344             if C4::Biblio::GetMarcFromKohaField( $kohafield );
345     }
346
347     $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
348     return \%default_values_for_mod_from_marc;
349 }
350
351 sub ModItemFromMarc {
352     my $item_marc = shift;
353     my $biblionumber = shift;
354     my $itemnumber = shift;
355
356     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
357     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
358
359     my $localitemmarc = MARC::Record->new;
360     $localitemmarc->append_fields( $item_marc->field($itemtag) );
361     my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
362     my $default_values = _build_default_values_for_mod_marc();
363     foreach my $item_field ( keys %$default_values ) {
364         $item->{$item_field} = $default_values->{$item_field}
365           unless exists $item->{$item_field};
366     }
367     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
368
369     my $item_object = Koha::Items->find($itemnumber);
370     $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
371
372     return $item_object->get_from_storage->unblessed;
373 }
374
375 =head2 ModItemTransfer
376
377   ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
378
379 Marks an item as being transferred from one branch to another and records the trigger.
380
381 =cut
382
383 sub ModItemTransfer {
384     my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
385
386     my $dbh = C4::Context->dbh;
387     my $item = Koha::Items->find( $itemnumber );
388
389     # Remove the 'shelving cart' location status if it is being used.
390     CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
391
392     $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
393
394     #new entry in branchtransfers....
395     my $sth = $dbh->prepare(
396         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
397         VALUES (?, ?, NOW(), ?, ?)");
398     $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
399
400     # FIXME we are fetching the item twice in the 2 next statements!
401     Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
402     ModDateLastSeen($itemnumber);
403     return;
404 }
405
406 =head2 ModDateLastSeen
407
408 ModDateLastSeen( $itemnumber, $leave_item_lost );
409
410 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
411 C<$itemnumber> is the item number
412 C<$leave_item_lost> determines if a lost item will be found or remain lost
413
414 =cut
415
416 sub ModDateLastSeen {
417     my ( $itemnumber, $leave_item_lost ) = @_;
418
419     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
420
421     my $item = Koha::Items->find($itemnumber);
422     $item->datelastseen($today);
423     $item->itemlost(0) unless $leave_item_lost;
424     $item->store({ log_action => 0 });
425 }
426
427 =head2 CheckItemPreSave
428
429     my $item_ref = TransformMarcToKoha($marc, 'items');
430     # do stuff
431     my %errors = CheckItemPreSave($item_ref);
432     if (exists $errors{'duplicate_barcode'}) {
433         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
434     } elsif (exists $errors{'invalid_homebranch'}) {
435         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
436     } elsif (exists $errors{'invalid_holdingbranch'}) {
437         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
438     } else {
439         print "item is OK";
440     }
441
442 Given a hashref containing item fields, determine if it can be
443 inserted or updated in the database.  Specifically, checks for
444 database integrity issues, and returns a hash containing any
445 of the following keys, if applicable.
446
447 =over 2
448
449 =item duplicate_barcode
450
451 Barcode, if it duplicates one already found in the database.
452
453 =item invalid_homebranch
454
455 Home branch, if not defined in branches table.
456
457 =item invalid_holdingbranch
458
459 Holding branch, if not defined in branches table.
460
461 =back
462
463 This function does NOT implement any policy-related checks,
464 e.g., whether current operator is allowed to save an
465 item that has a given branch code.
466
467 =cut
468
469 sub CheckItemPreSave {
470     my $item_ref = shift;
471
472     my %errors = ();
473
474     # check for duplicate barcode
475     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
476         my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
477         if ($existing_item) {
478             if (!exists $item_ref->{'itemnumber'}                       # new item
479                 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
480                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
481             }
482         }
483     }
484
485     # check for valid home branch
486     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
487         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
488         unless (defined $home_library) {
489             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
490         }
491     }
492
493     # check for valid holding branch
494     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
495         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
496         unless (defined $holding_library) {
497             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
498         }
499     }
500
501     return %errors;
502
503 }
504
505 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
506
507 The following functions provide various ways of 
508 getting an item record, a set of item records, or
509 lists of authorized values for certain item fields.
510
511 =cut
512
513 =head2 GetItemsForInventory
514
515 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
516   minlocation  => $minlocation,
517   maxlocation  => $maxlocation,
518   location     => $location,
519   itemtype     => $itemtype,
520   ignoreissued => $ignoreissued,
521   datelastseen => $datelastseen,
522   branchcode   => $branchcode,
523   branch       => $branch,
524   offset       => $offset,
525   size         => $size,
526   statushash   => $statushash,
527 } );
528
529 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
530
531 The sub returns a reference to a list of hashes, each containing
532 itemnumber, author, title, barcode, item callnumber, and date last
533 seen. It is ordered by callnumber then title.
534
535 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
536 the datelastseen can be used to specify that you want to see items not seen since a past date only.
537 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
538 $statushash requires a hashref that has the authorized values fieldname (intems.notforloan, etc...) as keys, and an arrayref of statuscodes we are searching for as values.
539
540 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
541
542 =cut
543
544 sub GetItemsForInventory {
545     my ( $parameters ) = @_;
546     my $minlocation  = $parameters->{'minlocation'}  // '';
547     my $maxlocation  = $parameters->{'maxlocation'}  // '';
548     my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
549     my $location     = $parameters->{'location'}     // '';
550     my $itemtype     = $parameters->{'itemtype'}     // '';
551     my $ignoreissued = $parameters->{'ignoreissued'} // '';
552     my $datelastseen = $parameters->{'datelastseen'} // '';
553     my $branchcode   = $parameters->{'branchcode'}   // '';
554     my $branch       = $parameters->{'branch'}       // '';
555     my $offset       = $parameters->{'offset'}       // '';
556     my $size         = $parameters->{'size'}         // '';
557     my $statushash   = $parameters->{'statushash'}   // '';
558     my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
559
560     my $dbh = C4::Context->dbh;
561     my ( @bind_params, @where_strings );
562
563     my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
564     my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
565
566     my $select_columns = q{
567         SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
568     };
569     my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
570     my $query = q{
571         FROM items
572         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
573         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
574     };
575     if ($statushash){
576         for my $authvfield (keys %$statushash){
577             if ( scalar @{$statushash->{$authvfield}} > 0 ){
578                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
579                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
580             }
581         }
582     }
583
584     if ($minlocation) {
585         push @where_strings, 'items.cn_sort >= ?';
586         push @bind_params, $min_cnsort;
587     }
588
589     if ($maxlocation) {
590         push @where_strings, 'items.cn_sort <= ?';
591         push @bind_params, $max_cnsort;
592     }
593
594     if ($datelastseen) {
595         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
596         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
597         push @bind_params, $datelastseen;
598     }
599
600     if ( $location ) {
601         push @where_strings, 'items.location = ?';
602         push @bind_params, $location;
603     }
604
605     if ( $branchcode ) {
606         if($branch eq "homebranch"){
607         push @where_strings, 'items.homebranch = ?';
608         }else{
609             push @where_strings, 'items.holdingbranch = ?';
610         }
611         push @bind_params, $branchcode;
612     }
613
614     if ( $itemtype ) {
615         push @where_strings, 'biblioitems.itemtype = ?';
616         push @bind_params, $itemtype;
617     }
618
619     if ( $ignoreissued) {
620         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
621         push @where_strings, 'issues.date_due IS NULL';
622     }
623
624     if ( $ignore_waiting_holds ) {
625         $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
626         push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
627     }
628
629     if ( @where_strings ) {
630         $query .= 'WHERE ';
631         $query .= join ' AND ', @where_strings;
632     }
633     my $count_query = $select_count . $query;
634     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
635     $query .= " LIMIT $offset, $size" if ($offset and $size);
636     $query = $select_columns . $query;
637     my $sth = $dbh->prepare($query);
638     $sth->execute( @bind_params );
639
640     my @results = ();
641     my $tmpresults = $sth->fetchall_arrayref({});
642     $sth = $dbh->prepare( $count_query );
643     $sth->execute( @bind_params );
644     my ($iTotalRecords) = $sth->fetchrow_array();
645
646     my @avs = Koha::AuthorisedValues->search(
647         {   'marc_subfield_structures.kohafield' => { '>' => '' },
648             'me.authorised_value'                => { '>' => '' },
649         },
650         {   join     => { category => 'marc_subfield_structures' },
651             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
652             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
653             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
654         }
655     );
656
657     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
658
659     foreach my $row (@$tmpresults) {
660
661         # Auth values
662         foreach (keys %$row) {
663             if (
664                 defined(
665                     $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
666                 )
667             ) {
668                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
669             }
670         }
671         push @results, $row;
672     }
673
674     return (\@results, $iTotalRecords);
675 }
676
677 =head2 GetItemsInfo
678
679   @results = GetItemsInfo($biblionumber);
680
681 Returns information about items with the given biblionumber.
682
683 C<GetItemsInfo> returns a list of references-to-hash. Each element
684 contains a number of keys. Most of them are attributes from the
685 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
686 Koha database. Other keys include:
687
688 =over 2
689
690 =item C<$data-E<gt>{branchname}>
691
692 The name (not the code) of the branch to which the book belongs.
693
694 =item C<$data-E<gt>{datelastseen}>
695
696 This is simply C<items.datelastseen>, except that while the date is
697 stored in YYYY-MM-DD format in the database, here it is converted to
698 DD/MM/YYYY format. A NULL date is returned as C<//>.
699
700 =item C<$data-E<gt>{datedue}>
701
702 =item C<$data-E<gt>{class}>
703
704 This is the concatenation of C<biblioitems.classification>, the book's
705 Dewey code, and C<biblioitems.subclass>.
706
707 =item C<$data-E<gt>{ocount}>
708
709 I think this is the number of copies of the book available.
710
711 =item C<$data-E<gt>{order}>
712
713 If this is set, it is set to C<One Order>.
714
715 =back
716
717 =cut
718
719 sub GetItemsInfo {
720     my ( $biblionumber ) = @_;
721     my $dbh   = C4::Context->dbh;
722     require C4::Languages;
723     my $language = C4::Languages::getlanguage();
724     my $query = "
725     SELECT items.*,
726            biblio.*,
727            biblioitems.volume,
728            biblioitems.number,
729            biblioitems.itemtype,
730            biblioitems.isbn,
731            biblioitems.issn,
732            biblioitems.publicationyear,
733            biblioitems.publishercode,
734            biblioitems.volumedate,
735            biblioitems.volumedesc,
736            biblioitems.lccn,
737            biblioitems.url,
738            items.notforloan as itemnotforloan,
739            issues.borrowernumber,
740            issues.date_due as datedue,
741            issues.onsite_checkout,
742            borrowers.cardnumber,
743            borrowers.surname,
744            borrowers.firstname,
745            borrowers.branchcode as bcode,
746            serial.serialseq,
747            serial.publisheddate,
748            itemtypes.description,
749            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
750            itemtypes.notforloan as notforloan_per_itemtype,
751            holding.branchurl,
752            holding.branchcode,
753            holding.branchname,
754            holding.opac_info as holding_branch_opac_info,
755            home.opac_info as home_branch_opac_info,
756            IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
757      FROM items
758      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
759      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
760      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
761      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
762      LEFT JOIN issues USING (itemnumber)
763      LEFT JOIN borrowers USING (borrowernumber)
764      LEFT JOIN serialitems USING (itemnumber)
765      LEFT JOIN serial USING (serialid)
766      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
767      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
768     $query .= q|
769     LEFT JOIN tmp_holdsqueue USING (itemnumber)
770     LEFT JOIN localization ON itemtypes.itemtype = localization.code
771         AND localization.entity = 'itemtypes'
772         AND localization.lang = ?
773     |;
774
775     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
776     my $sth = $dbh->prepare($query);
777     $sth->execute($language, $biblionumber);
778     my $i = 0;
779     my @results;
780     my $serial;
781
782     my $userenv = C4::Context->userenv;
783     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
784     while ( my $data = $sth->fetchrow_hashref ) {
785         if ( $data->{borrowernumber} && $want_not_same_branch) {
786             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
787         }
788
789         $serial ||= $data->{'serial'};
790
791         my $descriptions;
792         # get notforloan complete status if applicable
793         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
794         $data->{notforloanvalue}     = $descriptions->{lib} // '';
795         $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
796
797         # get restricted status and description if applicable
798         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
799         $data->{restrictedvalue}     = $descriptions->{lib} // '';
800         $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
801
802         # my stack procedures
803         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
804         $data->{stack}          = $descriptions->{lib} // '';
805
806         # Find the last 3 people who borrowed this item.
807         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
808                                     WHERE itemnumber = ?
809                                     AND old_issues.borrowernumber = borrowers.borrowernumber
810                                     ORDER BY returndate DESC
811                                     LIMIT 3");
812         $sth2->execute($data->{'itemnumber'});
813         my $ii = 0;
814         while (my $data2 = $sth2->fetchrow_hashref()) {
815             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
816             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
817             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
818             $ii++;
819         }
820
821         $results[$i] = $data;
822         $i++;
823     }
824
825     return $serial
826         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
827         : @results;
828 }
829
830 =head2 GetItemsLocationInfo
831
832   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
833
834 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
835
836 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
837
838 =over 2
839
840 =item C<$data-E<gt>{homebranch}>
841
842 Branch Name of the item's homebranch
843
844 =item C<$data-E<gt>{holdingbranch}>
845
846 Branch Name of the item's holdingbranch
847
848 =item C<$data-E<gt>{location}>
849
850 Item's shelving location code
851
852 =item C<$data-E<gt>{location_intranet}>
853
854 The intranet description for the Shelving Location as set in authorised_values 'LOC'
855
856 =item C<$data-E<gt>{location_opac}>
857
858 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
859 description is set.
860
861 =item C<$data-E<gt>{itemcallnumber}>
862
863 Item's itemcallnumber
864
865 =item C<$data-E<gt>{cn_sort}>
866
867 Item's call number normalized for sorting
868
869 =back
870   
871 =cut
872
873 sub GetItemsLocationInfo {
874         my $biblionumber = shift;
875         my @results;
876
877         my $dbh = C4::Context->dbh;
878         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
879                             location, itemcallnumber, cn_sort
880                      FROM items, branches as a, branches as b
881                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
882                      AND biblionumber = ?
883                      ORDER BY cn_sort ASC";
884         my $sth = $dbh->prepare($query);
885         $sth->execute($biblionumber);
886
887         while ( my $data = $sth->fetchrow_hashref ) {
888              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
889              $av = $av->count ? $av->next : undef;
890              $data->{location_intranet} = $av ? $av->lib : '';
891              $data->{location_opac}     = $av ? $av->opac_description : '';
892              push @results, $data;
893         }
894         return @results;
895 }
896
897 =head2 GetHostItemsInfo
898
899     $hostiteminfo = GetHostItemsInfo($hostfield);
900     Returns the iteminfo for items linked to records via a host field
901
902 =cut
903
904 sub GetHostItemsInfo {
905     my ($record) = @_;
906     my @returnitemsInfo;
907
908     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
909         return @returnitemsInfo;
910     }
911
912     my @fields;
913     if( C4::Context->preference('marcflavour') eq 'MARC21' ||
914       C4::Context->preference('marcflavour') eq 'NORMARC') {
915         @fields = $record->field('773');
916     } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
917         @fields = $record->field('461');
918     }
919
920     foreach my $hostfield ( @fields ) {
921         my $hostbiblionumber = $hostfield->subfield("0");
922         my $linkeditemnumber = $hostfield->subfield("9");
923         my @hostitemInfos = GetItemsInfo($hostbiblionumber);
924         foreach my $hostitemInfo (@hostitemInfos) {
925             if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
926                 push @returnitemsInfo, $hostitemInfo;
927                 last;
928             }
929         }
930     }
931     return @returnitemsInfo;
932 }
933
934 =head2 get_hostitemnumbers_of
935
936   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
937
938 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
939
940 Return a reference on a hash where key is a biblionumber and values are
941 references on array of itemnumbers.
942
943 =cut
944
945
946 sub get_hostitemnumbers_of {
947     my ($biblionumber) = @_;
948
949     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
950         return ();
951     }
952
953     my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
954     return unless $marcrecord;
955
956     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
957
958     my $marcflavor = C4::Context->preference('marcflavour');
959     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
960         $tag      = '773';
961         $biblio_s = '0';
962         $item_s   = '9';
963     }
964     elsif ( $marcflavor eq 'UNIMARC' ) {
965         $tag      = '461';
966         $biblio_s = '0';
967         $item_s   = '9';
968     }
969
970     foreach my $hostfield ( $marcrecord->field($tag) ) {
971         my $hostbiblionumber = $hostfield->subfield($biblio_s);
972         next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
973         my $linkeditemnumber = $hostfield->subfield($item_s);
974         if ( ! $linkeditemnumber ) {
975             warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
976             next;
977         }
978         my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
979         push @returnhostitemnumbers, $linkeditemnumber
980           if $is_from_biblio;
981     }
982
983     return @returnhostitemnumbers;
984 }
985
986 =head2 GetHiddenItemnumbers
987
988     my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
989
990 Given a list of items it checks which should be hidden from the OPAC given
991 the current configuration. Returns a list of itemnumbers corresponding to
992 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
993 to be excluded
994
995 =cut
996
997 sub GetHiddenItemnumbers {
998     my $params = shift;
999     my $items = $params->{items};
1000     if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1001         foreach my $except (split(/\|/, $exceptions)){
1002             if ($params->{'borcat'} eq $except){
1003                 return; # we don't hide anything for this borrower category
1004             }
1005         }
1006     }
1007     my @resultitems;
1008
1009     my $yaml = C4::Context->preference('OpacHiddenItems');
1010     return () if (! $yaml =~ /\S/ );
1011     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1012     my $hidingrules;
1013     eval {
1014         $hidingrules = YAML::Load($yaml);
1015     };
1016     if ($@) {
1017         warn "Unable to parse OpacHiddenItems syspref : $@";
1018         return ();
1019     }
1020     my $dbh = C4::Context->dbh;
1021
1022     # For each item
1023     foreach my $item (@$items) {
1024
1025         # We check each rule
1026         foreach my $field (keys %$hidingrules) {
1027             my $val;
1028             if (exists $item->{$field}) {
1029                 $val = $item->{$field};
1030             }
1031             else {
1032                 my $query = "SELECT $field from items where itemnumber = ?";
1033                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1034             }
1035             $val = '' unless defined $val;
1036
1037             # If the results matches the values in the yaml file
1038             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1039
1040                 # We add the itemnumber to the list
1041                 push @resultitems, $item->{'itemnumber'};
1042
1043                 # If at least one rule matched for an item, no need to test the others
1044                 last;
1045             }
1046         }
1047     }
1048     return @resultitems;
1049 }
1050
1051 =head1 LIMITED USE FUNCTIONS
1052
1053 The following functions, while part of the public API,
1054 are not exported.  This is generally because they are
1055 meant to be used by only one script for a specific
1056 purpose, and should not be used in any other context
1057 without careful thought.
1058
1059 =cut
1060
1061 =head2 GetMarcItem
1062
1063   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1064
1065 Returns MARC::Record of the item passed in parameter.
1066 This function is meant for use only in C<cataloguing/additem.pl>,
1067 where it is needed to support that script's MARC-like
1068 editor.
1069
1070 =cut
1071
1072 sub GetMarcItem {
1073     my ( $biblionumber, $itemnumber ) = @_;
1074
1075     # GetMarcItem has been revised so that it does the following:
1076     #  1. Gets the item information from the items table.
1077     #  2. Converts it to a MARC field for storage in the bib record.
1078     #
1079     # The previous behavior was:
1080     #  1. Get the bib record.
1081     #  2. Return the MARC tag corresponding to the item record.
1082     #
1083     # The difference is that one treats the items row as authoritative,
1084     # while the other treats the MARC representation as authoritative
1085     # under certain circumstances.
1086
1087     my $item = Koha::Items->find($itemnumber) or return;
1088
1089     # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1090     # Also, don't emit a subfield if the underlying field is blank.
1091
1092     return Item2Marc($item->unblessed, $biblionumber);
1093
1094 }
1095 sub Item2Marc {
1096         my ($itemrecord,$biblionumber)=@_;
1097     my $mungeditem = { 
1098         map {  
1099             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1100         } keys %{ $itemrecord } 
1101     };
1102     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1103     my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1104     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1105         "items.itemnumber", $framework,
1106     );
1107
1108     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1109     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1110                 foreach my $field ($itemmarc->field($itemtag)){
1111             $field->add_subfields(@$unlinked_item_subfields);
1112         }
1113     }
1114         return $itemmarc;
1115 }
1116
1117 =head1 PRIVATE FUNCTIONS AND VARIABLES
1118
1119 The following functions are not meant to be called
1120 directly, but are documented in order to explain
1121 the inner workings of C<C4::Items>.
1122
1123 =cut
1124
1125 =head2 %derived_columns
1126
1127 This hash keeps track of item columns that
1128 are strictly derived from other columns in
1129 the item record and are not meant to be set
1130 independently.
1131
1132 Each key in the hash should be the name of a
1133 column (as named by TransformMarcToKoha).  Each
1134 value should be hashref whose keys are the
1135 columns on which the derived column depends.  The
1136 hashref should also contain a 'BUILDER' key
1137 that is a reference to a sub that calculates
1138 the derived value.
1139
1140 =cut
1141
1142 my %derived_columns = (
1143     'items.cn_sort' => {
1144         'itemcallnumber' => 1,
1145         'items.cn_source' => 1,
1146         'BUILDER' => \&_calc_items_cn_sort,
1147     }
1148 );
1149
1150 =head2 _set_derived_columns_for_add 
1151
1152   _set_derived_column_for_add($item);
1153
1154 Given an item hash representing a new item to be added,
1155 calculate any derived columns.  Currently the only
1156 such column is C<items.cn_sort>.
1157
1158 =cut
1159
1160 sub _set_derived_columns_for_add {
1161     my $item = shift;
1162
1163     foreach my $column (keys %derived_columns) {
1164         my $builder = $derived_columns{$column}->{'BUILDER'};
1165         my $source_values = {};
1166         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1167             next if $source_column eq 'BUILDER';
1168             $source_values->{$source_column} = $item->{$source_column};
1169         }
1170         $builder->($item, $source_values);
1171     }
1172 }
1173
1174 =head2 _get_single_item_column
1175
1176   _get_single_item_column($column, $itemnumber);
1177
1178 Retrieves the value of a single column from an C<items>
1179 row specified by C<$itemnumber>.
1180
1181 =cut
1182
1183 sub _get_single_item_column {
1184     my $column = shift;
1185     my $itemnumber = shift;
1186     
1187     my $dbh = C4::Context->dbh;
1188     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1189     $sth->execute($itemnumber);
1190     my ($value) = $sth->fetchrow();
1191     return $value; 
1192 }
1193
1194 =head2 _calc_items_cn_sort
1195
1196   _calc_items_cn_sort($item, $source_values);
1197
1198 Helper routine to calculate C<items.cn_sort>.
1199
1200 =cut
1201
1202 sub _calc_items_cn_sort {
1203     my $item = shift;
1204     my $source_values = shift;
1205
1206     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1207 }
1208
1209 =head2 _koha_new_item
1210
1211   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1212
1213 Perform the actual insert into the C<items> table.
1214
1215 =cut
1216
1217 sub _koha_new_item {
1218     my ( $item, $barcode ) = @_;
1219     my $dbh=C4::Context->dbh;  
1220     my $error;
1221     $item->{permanent_location} //= $item->{location};
1222     _mod_item_dates( $item );
1223     my $query =
1224            "INSERT INTO items SET
1225             biblionumber        = ?,
1226             biblioitemnumber    = ?,
1227             barcode             = ?,
1228             dateaccessioned     = ?,
1229             booksellerid        = ?,
1230             homebranch          = ?,
1231             price               = ?,
1232             replacementprice    = ?,
1233             replacementpricedate = ?,
1234             datelastborrowed    = ?,
1235             datelastseen        = ?,
1236             stack               = ?,
1237             notforloan          = ?,
1238             damaged             = ?,
1239             itemlost            = ?,
1240             withdrawn           = ?,
1241             itemcallnumber      = ?,
1242             coded_location_qualifier = ?,
1243             restricted          = ?,
1244             itemnotes           = ?,
1245             itemnotes_nonpublic = ?,
1246             holdingbranch       = ?,
1247             location            = ?,
1248             permanent_location  = ?,
1249             onloan              = ?,
1250             issues              = ?,
1251             renewals            = ?,
1252             reserves            = ?,
1253             cn_source           = ?,
1254             cn_sort             = ?,
1255             ccode               = ?,
1256             itype               = ?,
1257             materials           = ?,
1258             uri                 = ?,
1259             enumchron           = ?,
1260             more_subfields_xml  = ?,
1261             copynumber          = ?,
1262             stocknumber         = ?,
1263             new_status          = ?
1264           ";
1265     my $sth = $dbh->prepare($query);
1266     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1267    $sth->execute(
1268             $item->{'biblionumber'},
1269             $item->{'biblioitemnumber'},
1270             $barcode,
1271             $item->{'dateaccessioned'},
1272             $item->{'booksellerid'},
1273             $item->{'homebranch'},
1274             $item->{'price'},
1275             $item->{'replacementprice'},
1276             $item->{'replacementpricedate'} || $today,
1277             $item->{datelastborrowed},
1278             $item->{datelastseen} || $today,
1279             $item->{stack},
1280             $item->{'notforloan'},
1281             $item->{'damaged'},
1282             $item->{'itemlost'},
1283             $item->{'withdrawn'},
1284             $item->{'itemcallnumber'},
1285             $item->{'coded_location_qualifier'},
1286             $item->{'restricted'},
1287             $item->{'itemnotes'},
1288             $item->{'itemnotes_nonpublic'},
1289             $item->{'holdingbranch'},
1290             $item->{'location'},
1291             $item->{'permanent_location'},
1292             $item->{'onloan'},
1293             $item->{'issues'},
1294             $item->{'renewals'},
1295             $item->{'reserves'},
1296             $item->{'items.cn_source'},
1297             $item->{'items.cn_sort'},
1298             $item->{'ccode'},
1299             $item->{'itype'},
1300             $item->{'materials'},
1301             $item->{'uri'},
1302             $item->{'enumchron'},
1303             $item->{'more_subfields_xml'},
1304             $item->{'copynumber'},
1305             $item->{'stocknumber'},
1306             $item->{'new_status'},
1307     );
1308
1309     my $itemnumber;
1310     if ( defined $sth->errstr ) {
1311         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1312     }
1313     else {
1314         $itemnumber = $dbh->{'mysql_insertid'};
1315     }
1316
1317     return ( $itemnumber, $error );
1318 }
1319
1320 =head2 MoveItemFromBiblio
1321
1322   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1323
1324 Moves an item from a biblio to another
1325
1326 Returns undef if the move failed or the biblionumber of the destination record otherwise
1327
1328 =cut
1329
1330 sub MoveItemFromBiblio {
1331     my ($itemnumber, $frombiblio, $tobiblio) = @_;
1332     my $dbh = C4::Context->dbh;
1333     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1334         SELECT biblioitemnumber
1335         FROM biblioitems
1336         WHERE biblionumber = ?
1337     |, undef, $tobiblio );
1338     my $return = $dbh->do(q|
1339         UPDATE items
1340         SET biblioitemnumber = ?,
1341             biblionumber = ?
1342         WHERE itemnumber = ?
1343             AND biblionumber = ?
1344     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1345     if ($return == 1) {
1346         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1347         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1348             # Checking if the item we want to move is in an order 
1349         require C4::Acquisition;
1350         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1351             if ($order) {
1352                     # Replacing the biblionumber within the order if necessary
1353                     $order->{'biblionumber'} = $tobiblio;
1354                 C4::Acquisition::ModOrder($order);
1355             }
1356
1357         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1358         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1359             $dbh->do( qq|
1360                 UPDATE $table_name
1361                 SET biblionumber = ?
1362                 WHERE itemnumber = ?
1363             |, undef, $tobiblio, $itemnumber );
1364         }
1365         return $tobiblio;
1366         }
1367     return;
1368 }
1369
1370 =head2 ItemSafeToDelete
1371
1372    ItemSafeToDelete( $biblionumber, $itemnumber);
1373
1374 Exported function (core API) for checking whether an item record is safe to delete.
1375
1376 returns 1 if the item is safe to delete,
1377
1378 "book_on_loan" if the item is checked out,
1379
1380 "not_same_branch" if the item is blocked by independent branches,
1381
1382 "book_reserved" if the there are holds aganst the item, or
1383
1384 "linked_analytics" if the item has linked analytic records.
1385
1386 =cut
1387
1388 sub ItemSafeToDelete {
1389     my ( $biblionumber, $itemnumber ) = @_;
1390     my $status;
1391     my $dbh = C4::Context->dbh;
1392
1393     my $error;
1394
1395     my $countanalytics = GetAnalyticsCount($itemnumber);
1396
1397     my $item = Koha::Items->find($itemnumber) or return;
1398
1399     if ($item->checkout) {
1400         $status = "book_on_loan";
1401     }
1402     elsif ( defined C4::Context->userenv
1403         and !C4::Context->IsSuperLibrarian()
1404         and C4::Context->preference("IndependentBranches")
1405         and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1406     {
1407         $status = "not_same_branch";
1408     }
1409     else {
1410         # check it doesn't have a waiting reserve
1411         my $sth = $dbh->prepare(
1412             q{
1413             SELECT COUNT(*) FROM reserves
1414             WHERE (found = 'W' OR found = 'T')
1415             AND itemnumber = ?
1416         }
1417         );
1418         $sth->execute($itemnumber);
1419         my ($reserve) = $sth->fetchrow;
1420         if ($reserve) {
1421             $status = "book_reserved";
1422         }
1423         elsif ( $countanalytics > 0 ) {
1424             $status = "linked_analytics";
1425         }
1426         else {
1427             $status = 1;
1428         }
1429     }
1430     return $status;
1431 }
1432
1433 =head2 DelItemCheck
1434
1435    DelItemCheck( $biblionumber, $itemnumber);
1436
1437 Exported function (core API) for deleting an item record in Koha if there no current issue.
1438
1439 DelItemCheck wraps ItemSafeToDelete around DelItem.
1440
1441 =cut
1442
1443 sub DelItemCheck {
1444     my ( $biblionumber, $itemnumber ) = @_;
1445     my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1446
1447     if ( $status == 1 ) {
1448         my $item = Koha::Items->find($itemnumber);
1449         $item->move_to_deleted;
1450         $item->delete;
1451     }
1452     return $status;
1453 }
1454
1455 sub _mod_item_dates { # date formatting for date fields in item hash
1456     my ( $item ) = @_;
1457     return if !$item || ref($item) ne 'HASH';
1458
1459     my @keys = grep
1460         { $_ =~ /^onloan$|^date|date$|datetime$/ }
1461         keys %$item;
1462     # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1463     # NOTE: We do not (yet) have items fields ending with datetime
1464     # Fields with _on$ have been handled already
1465
1466     foreach my $key ( @keys ) {
1467         next if !defined $item->{$key}; # skip undefs
1468         my $dt = eval { dt_from_string( $item->{$key} ) };
1469             # eval: dt_from_string will die on us if we pass illegal dates
1470
1471         my $newstr;
1472         if( defined $dt  && ref($dt) eq 'DateTime' ) {
1473             if( $key =~ /datetime/ ) {
1474                 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1475             } else {
1476                 $newstr = DateTime::Format::MySQL->format_date($dt);
1477             }
1478         }
1479         $item->{$key} = $newstr; # might be undef to clear garbage
1480     }
1481 }
1482
1483 =head2 _marc_from_item_hash
1484
1485   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1486
1487 Given an item hash representing a complete item record,
1488 create a C<MARC::Record> object containing an embedded
1489 tag representing that item.
1490
1491 The third, optional parameter C<$unlinked_item_subfields> is
1492 an arrayref of subfields (not mapped to C<items> fields per the
1493 framework) to be added to the MARC representation
1494 of the item.
1495
1496 =cut
1497
1498 sub _marc_from_item_hash {
1499     my $item = shift;
1500     my $frameworkcode = shift;
1501     my $unlinked_item_subfields;
1502     if (@_) {
1503         $unlinked_item_subfields = shift;
1504     }
1505    
1506     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1507     # Also, don't emit a subfield if the underlying field is blank.
1508     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1509                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1510                                 : ()  } keys %{ $item } }; 
1511
1512     my $item_marc = MARC::Record->new();
1513     foreach my $item_field ( keys %{$mungeditem} ) {
1514         my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1515         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
1516         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1517         foreach my $value (@values){
1518             if ( my $field = $item_marc->field($tag) ) {
1519                     $field->add_subfields( $subfield => $value );
1520             } else {
1521                 my $add_subfields = [];
1522                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1523                     $add_subfields = $unlinked_item_subfields;
1524             }
1525             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1526             }
1527         }
1528     }
1529
1530     return $item_marc;
1531 }
1532
1533 =head2 _repack_item_errors
1534
1535 Add an error message hash generated by C<CheckItemPreSave>
1536 to a list of errors.
1537
1538 =cut
1539
1540 sub _repack_item_errors {
1541     my $item_sequence_num = shift;
1542     my $item_ref = shift;
1543     my $error_ref = shift;
1544
1545     my @repacked_errors = ();
1546
1547     foreach my $error_code (sort keys %{ $error_ref }) {
1548         my $repacked_error = {};
1549         $repacked_error->{'item_sequence'} = $item_sequence_num;
1550         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1551         $repacked_error->{'error_code'} = $error_code;
1552         $repacked_error->{'error_information'} = $error_ref->{$error_code};
1553         push @repacked_errors, $repacked_error;
1554     } 
1555
1556     return @repacked_errors;
1557 }
1558
1559 =head2 _get_unlinked_item_subfields
1560
1561   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1562
1563 =cut
1564
1565 sub _get_unlinked_item_subfields {
1566     my $original_item_marc = shift;
1567     my $frameworkcode = shift;
1568
1569     my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1570
1571     # assume that this record has only one field, and that that
1572     # field contains only the item information
1573     my $subfields = [];
1574     my @fields = $original_item_marc->fields();
1575     if ($#fields > -1) {
1576         my $field = $fields[0];
1577             my $tag = $field->tag();
1578         foreach my $subfield ($field->subfields()) {
1579             if (defined $subfield->[1] and
1580                 $subfield->[1] ne '' and
1581                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1582                 push @$subfields, $subfield->[0] => $subfield->[1];
1583             }
1584         }
1585     }
1586     return $subfields;
1587 }
1588
1589 =head2 _get_unlinked_subfields_xml
1590
1591   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1592
1593 =cut
1594
1595 sub _get_unlinked_subfields_xml {
1596     my $unlinked_item_subfields = shift;
1597
1598     my $xml;
1599     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1600         my $marc = MARC::Record->new();
1601         # use of tag 999 is arbitrary, and doesn't need to match the item tag
1602         # used in the framework
1603         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1604         $marc->encoding("UTF-8");    
1605         $xml = $marc->as_xml("USMARC");
1606     }
1607
1608     return $xml;
1609 }
1610
1611 =head2 _parse_unlinked_item_subfields_from_xml
1612
1613   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1614
1615 =cut
1616
1617 sub  _parse_unlinked_item_subfields_from_xml {
1618     my $xml = shift;
1619     require C4::Charset;
1620     return unless defined $xml and $xml ne "";
1621     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1622     my $unlinked_subfields = [];
1623     my @fields = $marc->fields();
1624     if ($#fields > -1) {
1625         foreach my $subfield ($fields[0]->subfields()) {
1626             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1627         }
1628     }
1629     return $unlinked_subfields;
1630 }
1631
1632 =head2 GetAnalyticsCount
1633
1634   $count= &GetAnalyticsCount($itemnumber)
1635
1636 counts Usage of itemnumber in Analytical bibliorecords. 
1637
1638 =cut
1639
1640 sub GetAnalyticsCount {
1641     my ($itemnumber) = @_;
1642
1643     ### ZOOM search here
1644     my $query;
1645     $query= "hi=".$itemnumber;
1646     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1647     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1648     return ($result);
1649 }
1650
1651 sub _SearchItems_build_where_fragment {
1652     my ($filter) = @_;
1653
1654     my $dbh = C4::Context->dbh;
1655
1656     my $where_fragment;
1657     if (exists($filter->{conjunction})) {
1658         my (@where_strs, @where_args);
1659         foreach my $f (@{ $filter->{filters} }) {
1660             my $fragment = _SearchItems_build_where_fragment($f);
1661             if ($fragment) {
1662                 push @where_strs, $fragment->{str};
1663                 push @where_args, @{ $fragment->{args} };
1664             }
1665         }
1666         my $where_str = '';
1667         if (@where_strs) {
1668             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1669             $where_fragment = {
1670                 str => $where_str,
1671                 args => \@where_args,
1672             };
1673         }
1674     } else {
1675         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1676         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1677         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1678         my @operators = qw(= != > < >= <= like);
1679         my $field = $filter->{field} // q{};
1680         if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1681             my $op = $filter->{operator};
1682             my $query = $filter->{query};
1683
1684             if (!$op or (0 == grep { $_ eq $op } @operators)) {
1685                 $op = '='; # default operator
1686             }
1687
1688             my $column;
1689             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1690                 my $marcfield = $1;
1691                 my $marcsubfield = $2;
1692                 my ($kohafield) = $dbh->selectrow_array(q|
1693                     SELECT kohafield FROM marc_subfield_structure
1694                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1695                 |, undef, $marcfield, $marcsubfield);
1696
1697                 if ($kohafield) {
1698                     $column = $kohafield;
1699                 } else {
1700                     # MARC field is not linked to a DB field so we need to use
1701                     # ExtractValue on marcxml from biblio_metadata or
1702                     # items.more_subfields_xml, depending on the MARC field.
1703                     my $xpath;
1704                     my $sqlfield;
1705                     my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1706                     if ($marcfield eq $itemfield) {
1707                         $sqlfield = 'more_subfields_xml';
1708                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1709                     } else {
1710                         $sqlfield = 'metadata'; # From biblio_metadata
1711                         if ($marcfield < 10) {
1712                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1713                         } else {
1714                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1715                         }
1716                     }
1717                     $column = "ExtractValue($sqlfield, '$xpath')";
1718                 }
1719             } elsif ($field eq 'issues') {
1720                 # Consider NULL as 0 for issues count
1721                 $column = 'COALESCE(issues,0)';
1722             } else {
1723                 $column = $field;
1724             }
1725
1726             if (ref $query eq 'ARRAY') {
1727                 if ($op eq '=') {
1728                     $op = 'IN';
1729                 } elsif ($op eq '!=') {
1730                     $op = 'NOT IN';
1731                 }
1732                 $where_fragment = {
1733                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
1734                     args => $query,
1735                 };
1736             } else {
1737                 $where_fragment = {
1738                     str => "$column $op ?",
1739                     args => [ $query ],
1740                 };
1741             }
1742         }
1743     }
1744
1745     return $where_fragment;
1746 }
1747
1748 =head2 SearchItems
1749
1750     my ($items, $total) = SearchItems($filter, $params);
1751
1752 Perform a search among items
1753
1754 $filter is a reference to a hash which can be a filter, or a combination of filters.
1755
1756 A filter has the following keys:
1757
1758 =over 2
1759
1760 =item * field: the name of a SQL column in table items
1761
1762 =item * query: the value to search in this column
1763
1764 =item * operator: comparison operator. Can be one of = != > < >= <= like
1765
1766 =back
1767
1768 A combination of filters hash the following keys:
1769
1770 =over 2
1771
1772 =item * conjunction: 'AND' or 'OR'
1773
1774 =item * filters: array ref of filters
1775
1776 =back
1777
1778 $params is a reference to a hash that can contain the following parameters:
1779
1780 =over 2
1781
1782 =item * rows: Number of items to return. 0 returns everything (default: 0)
1783
1784 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1785                (default: 1)
1786
1787 =item * sortby: A SQL column name in items table to sort on
1788
1789 =item * sortorder: 'ASC' or 'DESC'
1790
1791 =back
1792
1793 =cut
1794
1795 sub SearchItems {
1796     my ($filter, $params) = @_;
1797
1798     $filter //= {};
1799     $params //= {};
1800     return unless ref $filter eq 'HASH';
1801     return unless ref $params eq 'HASH';
1802
1803     # Default parameters
1804     $params->{rows} ||= 0;
1805     $params->{page} ||= 1;
1806     $params->{sortby} ||= 'itemnumber';
1807     $params->{sortorder} ||= 'ASC';
1808
1809     my ($where_str, @where_args);
1810     my $where_fragment = _SearchItems_build_where_fragment($filter);
1811     if ($where_fragment) {
1812         $where_str = $where_fragment->{str};
1813         @where_args = @{ $where_fragment->{args} };
1814     }
1815
1816     my $dbh = C4::Context->dbh;
1817     my $query = q{
1818         SELECT SQL_CALC_FOUND_ROWS items.*
1819         FROM items
1820           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1821           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1822           LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1823           WHERE 1
1824     };
1825     if (defined $where_str and $where_str ne '') {
1826         $query .= qq{ AND $where_str };
1827     }
1828
1829     $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1830     push @where_args, C4::Context->preference('marcflavour');
1831
1832     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1833     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1834     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1835     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1836         ? $params->{sortby} : 'itemnumber';
1837     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1838     $query .= qq{ ORDER BY $sortby $sortorder };
1839
1840     my $rows = $params->{rows};
1841     my @limit_args;
1842     if ($rows > 0) {
1843         my $offset = $rows * ($params->{page}-1);
1844         $query .= qq { LIMIT ?, ? };
1845         push @limit_args, $offset, $rows;
1846     }
1847
1848     my $sth = $dbh->prepare($query);
1849     my $rv = $sth->execute(@where_args, @limit_args);
1850
1851     return unless ($rv);
1852     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1853
1854     return ($sth->fetchall_arrayref({}), $total_rows);
1855 }
1856
1857
1858 =head1  OTHER FUNCTIONS
1859
1860 =head2 _find_value
1861
1862   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1863
1864 Find the given $subfield in the given $tag in the given
1865 MARC::Record $record.  If the subfield is found, returns
1866 the (indicators, value) pair; otherwise, (undef, undef) is
1867 returned.
1868
1869 PROPOSITION :
1870 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1871 I suggest we export it from this module.
1872
1873 =cut
1874
1875 sub _find_value {
1876     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1877     my @result;
1878     my $indicator;
1879     if ( $tagfield < 10 ) {
1880         if ( $record->field($tagfield) ) {
1881             push @result, $record->field($tagfield)->data();
1882         } else {
1883             push @result, "";
1884         }
1885     } else {
1886         foreach my $field ( $record->field($tagfield) ) {
1887             my @subfields = $field->subfields();
1888             foreach my $subfield (@subfields) {
1889                 if ( @$subfield[0] eq $insubfield ) {
1890                     push @result, @$subfield[1];
1891                     $indicator = $field->indicator(1) . $field->indicator(2);
1892                 }
1893             }
1894         }
1895     }
1896     return ( $indicator, @result );
1897 }
1898
1899
1900 =head2 PrepareItemrecordDisplay
1901
1902   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1903
1904 Returns a hash with all the fields for Display a given item data in a template
1905
1906 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1907
1908 =cut
1909
1910 sub PrepareItemrecordDisplay {
1911
1912     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1913
1914     my $dbh = C4::Context->dbh;
1915     $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1916     my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1917
1918     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1919     # a shared data structure. No plugin (including custom ones) should change
1920     # its contents. See also GetMarcStructure.
1921     my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1922
1923     # return nothing if we don't have found an existing framework.
1924     return q{} unless $tagslib;
1925     my $itemrecord;
1926     if ($itemnum) {
1927         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1928     }
1929     my @loop_data;
1930
1931     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1932     my $query = qq{
1933         SELECT authorised_value,lib FROM authorised_values
1934     };
1935     $query .= qq{
1936         LEFT JOIN authorised_values_branches ON ( id = av_id )
1937     } if $branch_limit;
1938     $query .= qq{
1939         WHERE category = ?
1940     };
1941     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1942     $query .= qq{ ORDER BY lib};
1943     my $authorised_values_sth = $dbh->prepare( $query );
1944     foreach my $tag ( sort keys %{$tagslib} ) {
1945         if ( $tag ne '' ) {
1946
1947             # loop through each subfield
1948             my $cntsubf;
1949             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1950                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1951                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1952                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1953                 my %subfield_data;
1954                 $subfield_data{tag}           = $tag;
1955                 $subfield_data{subfield}      = $subfield;
1956                 $subfield_data{countsubfield} = $cntsubf++;
1957                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1958                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1959
1960                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1961                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
1962                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
1963                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1964                 $subfield_data{hidden}     = "display:none"
1965                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1966                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1967                 my ( $x, $defaultvalue );
1968                 if ($itemrecord) {
1969                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1970                 }
1971                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1972                 if ( !defined $defaultvalue ) {
1973                     $defaultvalue = q||;
1974                 } else {
1975                     $defaultvalue =~ s/"/&quot;/g;
1976                 }
1977
1978                 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1979
1980                 # search for itemcallnumber if applicable
1981                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1982                     && C4::Context->preference('itemcallnumber') && $itemrecord) {
1983                     foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1984                         my $CNtag      = substr( $itemcn_pref, 0, 3 );
1985                         next unless my $field = $itemrecord->field($CNtag);
1986                         my $CNsubfields = substr( $itemcn_pref, 3 );
1987                         $defaultvalue = $field->as_string( $CNsubfields, ' ');
1988                         last if $defaultvalue;
1989                     }
1990                 }
1991                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1992                     && $defaultvalues
1993                     && $defaultvalues->{'callnumber'} ) {
1994                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1995                         # if the item record exists, only use default value if the item has no callnumber
1996                         $defaultvalue = $defaultvalues->{callnumber};
1997                     } elsif ( !$itemrecord and $defaultvalues ) {
1998                         # if the item record *doesn't* exists, always use the default value
1999                         $defaultvalue = $defaultvalues->{callnumber};
2000                     }
2001                 }
2002                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2003                     && $defaultvalues
2004                     && $defaultvalues->{'branchcode'} ) {
2005                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2006                         $defaultvalue = $defaultvalues->{branchcode};
2007                     }
2008                 }
2009                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2010                     && $defaultvalues
2011                     && $defaultvalues->{'location'} ) {
2012
2013                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2014                         # if the item record exists, only use default value if the item has no locationr
2015                         $defaultvalue = $defaultvalues->{location};
2016                     } elsif ( !$itemrecord and $defaultvalues ) {
2017                         # if the item record *doesn't* exists, always use the default value
2018                         $defaultvalue = $defaultvalues->{location};
2019                     }
2020                 }
2021                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2022                     my @authorised_values;
2023                     my %authorised_lib;
2024
2025                     # builds list, depending on authorised value...
2026                     #---- branch
2027                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2028                         if (   ( C4::Context->preference("IndependentBranches") )
2029                             && !C4::Context->IsSuperLibrarian() ) {
2030                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2031                             $sth->execute( C4::Context->userenv->{branch} );
2032                             push @authorised_values, ""
2033                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2034                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2035                                 push @authorised_values, $branchcode;
2036                                 $authorised_lib{$branchcode} = $branchname;
2037                             }
2038                         } else {
2039                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2040                             $sth->execute;
2041                             push @authorised_values, ""
2042                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2043                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2044                                 push @authorised_values, $branchcode;
2045                                 $authorised_lib{$branchcode} = $branchname;
2046                             }
2047                         }
2048
2049                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2050                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2051                             $defaultvalue = $defaultvalues->{branchcode};
2052                         }
2053
2054                         #----- itemtypes
2055                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2056                         my $itemtypes = Koha::ItemTypes->search_with_localization;
2057                         push @authorised_values, ""
2058                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2059                         while ( my $itemtype = $itemtypes->next ) {
2060                             push @authorised_values, $itemtype->itemtype;
2061                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2062                         }
2063                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2064                             $defaultvalue = $defaultvalues->{'itemtype'};
2065                         }
2066
2067                         #---- class_sources
2068                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2069                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2070
2071                         my $class_sources = GetClassSources();
2072                         my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
2073
2074                         foreach my $class_source (sort keys %$class_sources) {
2075                             next unless $class_sources->{$class_source}->{'used'} or
2076                                         ($class_source eq $default_source);
2077                             push @authorised_values, $class_source;
2078                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2079                         }
2080
2081                         $defaultvalue = $default_source;
2082
2083                         #---- "true" authorised value
2084                     } else {
2085                         $authorised_values_sth->execute(
2086                             $tagslib->{$tag}->{$subfield}->{authorised_value},
2087                             $branch_limit ? $branch_limit : ()
2088                         );
2089                         push @authorised_values, ""
2090                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2091                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2092                             push @authorised_values, $value;
2093                             $authorised_lib{$value} = $lib;
2094                         }
2095                     }
2096                     $subfield_data{marc_value} = {
2097                         type    => 'select',
2098                         values  => \@authorised_values,
2099                         default => $defaultvalue // q{},
2100                         labels  => \%authorised_lib,
2101                     };
2102                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2103                 # it is a plugin
2104                     require Koha::FrameworkPlugin;
2105                     my $plugin = Koha::FrameworkPlugin->new({
2106                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
2107                         item_style => 1,
2108                     });
2109                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2110                     $plugin->build( $pars );
2111                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2112                         $defaultvalue = $field->subfield($subfield) || q{};
2113                     }
2114                     if( !$plugin->errstr ) {
2115                         #TODO Move html to template; see report 12176/13397
2116                         my $tab= $plugin->noclick? '-1': '';
2117                         my $class= $plugin->noclick? ' disabled': '';
2118                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
2119                         $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
2120                     } else {
2121                         warn $plugin->errstr;
2122                         $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
2123                     }
2124                 }
2125                 elsif ( $tag eq '' ) {       # it's an hidden field
2126                     $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2127                 }
2128                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
2129                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2130                 }
2131                 elsif ( length($defaultvalue) > 100
2132                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2133                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
2134                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
2135                                   500 <= $tag && $tag < 600                     )
2136                           ) {
2137                     # oversize field (textarea)
2138                     $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
2139                 } else {
2140                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2141                 }
2142                 push( @loop_data, \%subfield_data );
2143             }
2144         }
2145     }
2146     my $itemnumber;
2147     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2148         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2149     }
2150     return {
2151         'itemtagfield'    => $itemtagfield,
2152         'itemtagsubfield' => $itemtagsubfield,
2153         'itemnumber'      => $itemnumber,
2154         'iteminformation' => \@loop_data
2155     };
2156 }
2157
2158 sub ToggleNewStatus {
2159     my ( $params ) = @_;
2160     my @rules = @{ $params->{rules} };
2161     my $report_only = $params->{report_only};
2162
2163     my $dbh = C4::Context->dbh;
2164     my @errors;
2165     my @item_columns = map { "items.$_" } Koha::Items->columns;
2166     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2167     my $report;
2168     for my $rule ( @rules ) {
2169         my $age = $rule->{age};
2170         my $conditions = $rule->{conditions};
2171         my $substitutions = $rule->{substitutions};
2172         foreach ( @$substitutions ) {
2173             ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2174         }
2175         my @params;
2176
2177         my $query = q|
2178             SELECT items.*
2179             FROM items
2180             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2181             WHERE 1
2182         |;
2183         for my $condition ( @$conditions ) {
2184             if (
2185                  grep { $_ eq $condition->{field} } @item_columns
2186               or grep { $_ eq $condition->{field} } @biblioitem_columns
2187             ) {
2188                 if ( $condition->{value} =~ /\|/ ) {
2189                     my @values = split /\|/, $condition->{value};
2190                     $query .= qq| AND $condition->{field} IN (|
2191                         . join( ',', ('?') x scalar @values )
2192                         . q|)|;
2193                     push @params, @values;
2194                 } else {
2195                     $query .= qq| AND $condition->{field} = ?|;
2196                     push @params, $condition->{value};
2197                 }
2198             }
2199         }
2200         if ( defined $age ) {
2201             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2202             push @params, $age;
2203         }
2204         my $sth = $dbh->prepare($query);
2205         $sth->execute( @params );
2206         while ( my $values = $sth->fetchrow_hashref ) {
2207             my $biblionumber = $values->{biblionumber};
2208             my $itemnumber = $values->{itemnumber};
2209             my $item = Koha::Items->find($itemnumber);
2210             for my $substitution ( @$substitutions ) {
2211                 my $field = $substitution->{item_field};
2212                 my $value = $substitution->{value};
2213                 next unless $substitution->{field};
2214                 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2215                 $item->$field($value);
2216                 push @{ $report->{$itemnumber} }, $substitution;
2217             }
2218             $item->store unless $report_only;
2219         }
2220     }
2221
2222     return $report;
2223 }
2224
2225 1;