Bug 17835: Replace GetItemTypes with Koha::ItemTypes
[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 strict;
22 #use warnings; FIXME - Bug 2505
23
24 use Carp;
25 use C4::Context;
26 use C4::Koha;
27 use C4::Biblio;
28 use Koha::DateUtils;
29 use MARC::Record;
30 use C4::ClassSource;
31 use C4::Log;
32 use List::MoreUtils qw/any/;
33 use YAML qw/Load/;
34 use DateTime::Format::MySQL;
35 use Data::Dumper; # used as part of logging item record changes, not just for
36                   # debugging; so please don't remove this
37
38 use Koha::AuthorisedValues;
39 use Koha::DateUtils qw/dt_from_string/;
40 use Koha::Database;
41
42 use Koha::Biblioitems;
43 use Koha::Items;
44 use Koha::ItemTypes;
45 use Koha::SearchEngine;
46 use Koha::SearchEngine::Search;
47 use Koha::Libraries;
48
49 use vars qw(@ISA @EXPORT);
50
51 BEGIN {
52
53         require Exporter;
54     @ISA = qw( Exporter );
55
56     # function exports
57     @EXPORT = qw(
58         GetItem
59         AddItemFromMarc
60         AddItem
61         AddItemBatchFromMarc
62         ModItemFromMarc
63     Item2Marc
64         ModItem
65         ModDateLastSeen
66         ModItemTransfer
67         DelItem
68     
69         CheckItemPreSave
70     
71         GetItemLocation
72         GetLostItems
73         GetItemsForInventory
74         GetItemInfosOf
75         GetItemsByBiblioitemnumber
76         GetItemsInfo
77         GetItemsLocationInfo
78         GetHostItemsInfo
79         GetItemnumbersForBiblio
80         get_itemnumbers_of
81         get_hostitemnumbers_of
82         GetItemnumberFromBarcode
83         GetBarcodeFromItemnumber
84         GetHiddenItemnumbers
85         ItemSafeToDelete
86         DelItemCheck
87     MoveItemFromBiblio
88     GetLatestAcquisitions
89
90         CartToShelf
91         ShelfToCart
92
93         GetAnalyticsCount
94
95         SearchItemsByField
96         SearchItems
97
98         PrepareItemrecordDisplay
99
100     );
101 }
102
103 =head1 NAME
104
105 C4::Items - item management functions
106
107 =head1 DESCRIPTION
108
109 This module contains an API for manipulating item 
110 records in Koha, and is used by cataloguing, circulation,
111 acquisitions, and serials management.
112
113 # FIXME This POD is not up-to-date
114 A Koha item record is stored in two places: the
115 items table and embedded in a MARC tag in the XML
116 version of the associated bib record in C<biblioitems.marcxml>.
117 This is done to allow the item information to be readily
118 indexed (e.g., by Zebra), but means that each item
119 modification transaction must keep the items table
120 and the MARC XML in sync at all times.
121
122 Consequently, all code that creates, modifies, or deletes
123 item records B<must> use an appropriate function from 
124 C<C4::Items>.  If no existing function is suitable, it is
125 better to add one to C<C4::Items> than to use add
126 one-off SQL statements to add or modify items.
127
128 The items table will be considered authoritative.  In other
129 words, if there is ever a discrepancy between the items
130 table and the MARC XML, the items table should be considered
131 accurate.
132
133 =head1 HISTORICAL NOTE
134
135 Most of the functions in C<C4::Items> were originally in
136 the C<C4::Biblio> module.
137
138 =head1 CORE EXPORTED FUNCTIONS
139
140 The following functions are meant for use by users
141 of C<C4::Items>
142
143 =cut
144
145 =head2 GetItem
146
147   $item = GetItem($itemnumber,$barcode,$serial);
148
149 Return item information, for a given itemnumber or barcode.
150 The return value is a hashref mapping item column
151 names to values.  If C<$serial> is true, include serial publication data.
152
153 =cut
154
155 sub GetItem {
156     my ($itemnumber,$barcode, $serial) = @_;
157     my $dbh = C4::Context->dbh;
158
159     my $item;
160     if ($itemnumber) {
161         $item = Koha::Items->find( $itemnumber );
162     } else {
163         $item = Koha::Items->find( { barcode => $barcode } );
164     }
165
166     return unless ( $item );
167
168     my $data = $item->unblessed();
169     $data->{itype} = $item->effective_itemtype(); # set the correct itype
170
171     if ($serial) {
172         my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
173         $ssth->execute( $data->{'itemnumber'} );
174         ( $data->{'serialseq'}, $data->{'publisheddate'} ) = $ssth->fetchrow_array();
175     }
176
177     return $data;
178 }    # sub GetItem
179
180 =head2 CartToShelf
181
182   CartToShelf($itemnumber);
183
184 Set the current shelving location of the item record
185 to its stored permanent shelving location.  This is
186 primarily used to indicate when an item whose current
187 location is a special processing ('PROC') or shelving cart
188 ('CART') location is back in the stacks.
189
190 =cut
191
192 sub CartToShelf {
193     my ( $itemnumber ) = @_;
194
195     unless ( $itemnumber ) {
196         croak "FAILED CartToShelf() - no itemnumber supplied";
197     }
198
199     my $item = GetItem($itemnumber);
200     if ( $item->{location} eq 'CART' ) {
201         $item->{location} = $item->{permanent_location};
202         ModItem($item, undef, $itemnumber);
203     }
204 }
205
206 =head2 ShelfToCart
207
208   ShelfToCart($itemnumber);
209
210 Set the current shelving location of the item
211 to shelving cart ('CART').
212
213 =cut
214
215 sub ShelfToCart {
216     my ( $itemnumber ) = @_;
217
218     unless ( $itemnumber ) {
219         croak "FAILED ShelfToCart() - no itemnumber supplied";
220     }
221
222     my $item = GetItem($itemnumber);
223     $item->{'location'} = 'CART';
224     ModItem($item, undef, $itemnumber);
225 }
226
227 =head2 AddItemFromMarc
228
229   my ($biblionumber, $biblioitemnumber, $itemnumber) 
230       = AddItemFromMarc($source_item_marc, $biblionumber);
231
232 Given a MARC::Record object containing an embedded item
233 record and a biblionumber, create a new item record.
234
235 =cut
236
237 sub AddItemFromMarc {
238     my ( $source_item_marc, $biblionumber ) = @_;
239     my $dbh = C4::Context->dbh;
240
241     # parse item hash from MARC
242     my $frameworkcode = GetFrameworkCode( $biblionumber );
243         my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
244         
245         my $localitemmarc=MARC::Record->new;
246         $localitemmarc->append_fields($source_item_marc->field($itemtag));
247     my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
248     my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
249     return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
250 }
251
252 =head2 AddItem
253
254   my ($biblionumber, $biblioitemnumber, $itemnumber) 
255       = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
256
257 Given a hash containing item column names as keys,
258 create a new Koha item record.
259
260 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
261 do not need to be supplied for general use; they exist
262 simply to allow them to be picked up from AddItemFromMarc.
263
264 The final optional parameter, C<$unlinked_item_subfields>, contains
265 an arrayref containing subfields present in the original MARC
266 representation of the item (e.g., from the item editor) that are
267 not mapped to C<items> columns directly but should instead
268 be stored in C<items.more_subfields_xml> and included in 
269 the biblio items tag for display and indexing.
270
271 =cut
272
273 sub AddItem {
274     my $item         = shift;
275     my $biblionumber = shift;
276
277     my $dbh           = @_ ? shift : C4::Context->dbh;
278     my $frameworkcode = @_ ? shift : GetFrameworkCode($biblionumber);
279     my $unlinked_item_subfields;
280     if (@_) {
281         $unlinked_item_subfields = shift;
282     }
283
284     # needs old biblionumber and biblioitemnumber
285     $item->{'biblionumber'} = $biblionumber;
286     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
287     $sth->execute( $item->{'biblionumber'} );
288     ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
289
290     _set_defaults_for_add($item);
291     _set_derived_columns_for_add($item);
292     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
293
294     # FIXME - checks here
295     unless ( $item->{itype} ) {    # default to biblioitem.itemtype if no itype
296         my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
297         $itype_sth->execute( $item->{'biblionumber'} );
298         ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
299     }
300
301     my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
302     return if $error;
303
304     $item->{'itemnumber'} = $itemnumber;
305
306     ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
307
308     logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
309       if C4::Context->preference("CataloguingLog");
310
311     return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
312 }
313
314 =head2 AddItemBatchFromMarc
315
316   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
317              $biblionumber, $biblioitemnumber, $frameworkcode);
318
319 Efficiently create item records from a MARC biblio record with
320 embedded item fields.  This routine is suitable for batch jobs.
321
322 This API assumes that the bib record has already been
323 saved to the C<biblio> and C<biblioitems> tables.  It does
324 not expect that C<biblio_metadata.metadata> is populated, but it
325 will do so via a call to ModBibiloMarc.
326
327 The goal of this API is to have a similar effect to using AddBiblio
328 and AddItems in succession, but without inefficient repeated
329 parsing of the MARC XML bib record.
330
331 This function returns an arrayref of new itemsnumbers and an arrayref of item
332 errors encountered during the processing.  Each entry in the errors
333 list is a hashref containing the following keys:
334
335 =over
336
337 =item item_sequence
338
339 Sequence number of original item tag in the MARC record.
340
341 =item item_barcode
342
343 Item barcode, provide to assist in the construction of
344 useful error messages.
345
346 =item error_code
347
348 Code representing the error condition.  Can be 'duplicate_barcode',
349 'invalid_homebranch', or 'invalid_holdingbranch'.
350
351 =item error_information
352
353 Additional information appropriate to the error condition.
354
355 =back
356
357 =cut
358
359 sub AddItemBatchFromMarc {
360     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
361     my $error;
362     my @itemnumbers = ();
363     my @errors = ();
364     my $dbh = C4::Context->dbh;
365
366     # We modify the record, so lets work on a clone so we don't change the
367     # original.
368     $record = $record->clone();
369     # loop through the item tags and start creating items
370     my @bad_item_fields = ();
371     my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
372     my $item_sequence_num = 0;
373     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
374         $item_sequence_num++;
375         # we take the item field and stick it into a new
376         # MARC record -- this is required so far because (FIXME)
377         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
378         # and there is no TransformMarcFieldToKoha
379         my $temp_item_marc = MARC::Record->new();
380         $temp_item_marc->append_fields($item_field);
381     
382         # add biblionumber and biblioitemnumber
383         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
384         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
385         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
386         $item->{'biblionumber'} = $biblionumber;
387         $item->{'biblioitemnumber'} = $biblioitemnumber;
388
389         # check for duplicate barcode
390         my %item_errors = CheckItemPreSave($item);
391         if (%item_errors) {
392             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
393             push @bad_item_fields, $item_field;
394             next ITEMFIELD;
395         }
396
397         _set_defaults_for_add($item);
398         _set_derived_columns_for_add($item);
399         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
400         warn $error if $error;
401         push @itemnumbers, $itemnumber; # FIXME not checking error
402         $item->{'itemnumber'} = $itemnumber;
403
404         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
405
406         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
407         $item_field->replace_with($new_item_marc->field($itemtag));
408     }
409
410     # remove any MARC item fields for rejected items
411     foreach my $item_field (@bad_item_fields) {
412         $record->delete_field($item_field);
413     }
414
415     # update the MARC biblio
416  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
417
418     return (\@itemnumbers, \@errors);
419 }
420
421 =head2 ModItemFromMarc
422
423   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
424
425 This function updates an item record based on a supplied
426 C<MARC::Record> object containing an embedded item field.
427 This API is meant for the use of C<additem.pl>; for 
428 other purposes, C<ModItem> should be used.
429
430 This function uses the hash %default_values_for_mod_from_marc,
431 which contains default values for item fields to
432 apply when modifying an item.  This is needed because
433 if an item field's value is cleared, TransformMarcToKoha
434 does not include the column in the
435 hash that's passed to ModItem, which without
436 use of this hash makes it impossible to clear
437 an item field's value.  See bug 2466.
438
439 Note that only columns that can be directly
440 changed from the cataloging and serials
441 item editors are included in this hash.
442
443 Returns item record
444
445 =cut
446
447 sub _build_default_values_for_mod_marc {
448     my ($frameworkcode) = @_;
449
450     my $cache     = Koha::Caches->get_instance();
451     my $cache_key = "default_value_for_mod_marc-$frameworkcode";
452     my $cached    = $cache->get_from_cache($cache_key);
453     return $cached if $cached;
454
455     my $default_values = {
456         barcode                  => undef,
457         booksellerid             => undef,
458         ccode                    => undef,
459         'items.cn_source'        => undef,
460         coded_location_qualifier => undef,
461         copynumber               => undef,
462         damaged                  => 0,
463         enumchron                => undef,
464         holdingbranch            => undef,
465         homebranch               => undef,
466         itemcallnumber           => undef,
467         itemlost                 => 0,
468         itemnotes                => undef,
469         itemnotes_nonpublic      => undef,
470         itype                    => undef,
471         location                 => undef,
472         permanent_location       => undef,
473         materials                => undef,
474         new_status               => undef,
475         notforloan               => 0,
476         # paidfor => undef, # commented, see bug 12817
477         price                    => undef,
478         replacementprice         => undef,
479         replacementpricedate     => undef,
480         restricted               => undef,
481         stack                    => undef,
482         stocknumber              => undef,
483         uri                      => undef,
484         withdrawn                => 0,
485     };
486     my %default_values_for_mod_from_marc;
487     while ( my ( $field, $default_value ) = each %$default_values ) {
488         my $kohafield = $field;
489         $kohafield =~ s|^([^\.]+)$|items.$1|;
490         $default_values_for_mod_from_marc{$field} =
491           $default_value
492           if C4::Koha::IsKohaFieldLinked(
493             { kohafield => $kohafield, frameworkcode => $frameworkcode } );
494     }
495
496     $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
497     return \%default_values_for_mod_from_marc;
498 }
499
500 sub ModItemFromMarc {
501     my $item_marc = shift;
502     my $biblionumber = shift;
503     my $itemnumber = shift;
504
505     my $dbh           = C4::Context->dbh;
506     my $frameworkcode = GetFrameworkCode($biblionumber);
507     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
508
509     my $localitemmarc = MARC::Record->new;
510     $localitemmarc->append_fields( $item_marc->field($itemtag) );
511     my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
512     my $default_values = _build_default_values_for_mod_marc($frameworkcode);
513     foreach my $item_field ( keys %$default_values ) {
514         $item->{$item_field} = $default_values->{$item_field}
515           unless exists $item->{$item_field};
516     }
517     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
518
519     ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
520     return $item;
521 }
522
523 =head2 ModItem
524
525   ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
526
527 Change one or more columns in an item record and update
528 the MARC representation of the item.
529
530 The first argument is a hashref mapping from item column
531 names to the new values.  The second and third arguments
532 are the biblionumber and itemnumber, respectively.
533
534 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
535 an arrayref containing subfields present in the original MARC
536 representation of the item (e.g., from the item editor) that are
537 not mapped to C<items> columns directly but should instead
538 be stored in C<items.more_subfields_xml> and included in 
539 the biblio items tag for display and indexing.
540
541 If one of the changed columns is used to calculate
542 the derived value of a column such as C<items.cn_sort>, 
543 this routine will perform the necessary calculation
544 and set the value.
545
546 =cut
547
548 sub ModItem {
549     my $item = shift;
550     my $biblionumber = shift;
551     my $itemnumber = shift;
552
553     # if $biblionumber is undefined, get it from the current item
554     unless (defined $biblionumber) {
555         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
556     }
557
558     my $dbh           = @_ ? shift : C4::Context->dbh;
559     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
560     
561     my $unlinked_item_subfields;  
562     if (@_) {
563         $unlinked_item_subfields = shift;
564         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
565     };
566
567     $item->{'itemnumber'} = $itemnumber or return;
568
569     my @fields = qw( itemlost withdrawn );
570
571     # Only call GetItem if we need to set an "on" date field
572     if ( $item->{itemlost} || $item->{withdrawn} ) {
573         my $pre_mod_item = GetItem( $item->{'itemnumber'} );
574         for my $field (@fields) {
575             if (    defined( $item->{$field} )
576                 and not $pre_mod_item->{$field}
577                 and $item->{$field} )
578             {
579                 $item->{ $field . '_on' } =
580                   DateTime::Format::MySQL->format_datetime( dt_from_string() );
581             }
582         }
583     }
584
585     # If the field is defined but empty, we are removing and,
586     # and thus need to clear out the 'on' field as well
587     for my $field (@fields) {
588         if ( defined( $item->{$field} ) && !$item->{$field} ) {
589             $item->{ $field . '_on' } = undef;
590         }
591     }
592
593
594     _set_derived_columns_for_mod($item);
595     _do_column_fixes_for_mod($item);
596     # FIXME add checks
597     # duplicate barcode
598     # attempt to change itemnumber
599     # attempt to change biblionumber (if we want
600     # an API to relink an item to a different bib,
601     # it should be a separate function)
602
603     # update items table
604     _koha_modify_item($item);
605
606     # request that bib be reindexed so that searching on current
607     # item status is possible
608     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
609
610     logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
611 }
612
613 =head2 ModItemTransfer
614
615   ModItemTransfer($itenumber, $frombranch, $tobranch);
616
617 Marks an item as being transferred from one branch
618 to another.
619
620 =cut
621
622 sub ModItemTransfer {
623     my ( $itemnumber, $frombranch, $tobranch ) = @_;
624
625     my $dbh = C4::Context->dbh;
626
627     # Remove the 'shelving cart' location status if it is being used.
628     CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
629
630     #new entry in branchtransfers....
631     my $sth = $dbh->prepare(
632         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
633         VALUES (?, ?, NOW(), ?)");
634     $sth->execute($itemnumber, $frombranch, $tobranch);
635
636     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
637     ModDateLastSeen($itemnumber);
638     return;
639 }
640
641 =head2 ModDateLastSeen
642
643   ModDateLastSeen($itemnum);
644
645 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
646 C<$itemnum> is the item number
647
648 =cut
649
650 sub ModDateLastSeen {
651     my ($itemnumber) = @_;
652     
653     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
654     ModItem({ itemlost => 0, datelastseen => $today }, undef, $itemnumber);
655 }
656
657 =head2 DelItem
658
659   DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
660
661 Exported function (core API) for deleting an item record in Koha.
662
663 =cut
664
665 sub DelItem {
666     my ( $params ) = @_;
667
668     my $itemnumber   = $params->{itemnumber};
669     my $biblionumber = $params->{biblionumber};
670
671     unless ($biblionumber) {
672         $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
673     }
674
675     # If there is no biblionumber for the given itemnumber, there is nothing to delete
676     return 0 unless $biblionumber;
677
678     # FIXME check the item has no current issues
679     my $deleted = _koha_delete_item( $itemnumber );
680
681     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
682
683     #search item field code
684     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
685     return $deleted;
686 }
687
688 =head2 CheckItemPreSave
689
690     my $item_ref = TransformMarcToKoha($marc, 'items');
691     # do stuff
692     my %errors = CheckItemPreSave($item_ref);
693     if (exists $errors{'duplicate_barcode'}) {
694         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
695     } elsif (exists $errors{'invalid_homebranch'}) {
696         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
697     } elsif (exists $errors{'invalid_holdingbranch'}) {
698         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
699     } else {
700         print "item is OK";
701     }
702
703 Given a hashref containing item fields, determine if it can be
704 inserted or updated in the database.  Specifically, checks for
705 database integrity issues, and returns a hash containing any
706 of the following keys, if applicable.
707
708 =over 2
709
710 =item duplicate_barcode
711
712 Barcode, if it duplicates one already found in the database.
713
714 =item invalid_homebranch
715
716 Home branch, if not defined in branches table.
717
718 =item invalid_holdingbranch
719
720 Holding branch, if not defined in branches table.
721
722 =back
723
724 This function does NOT implement any policy-related checks,
725 e.g., whether current operator is allowed to save an
726 item that has a given branch code.
727
728 =cut
729
730 sub CheckItemPreSave {
731     my $item_ref = shift;
732
733     my %errors = ();
734
735     # check for duplicate barcode
736     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
737         my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
738         if ($existing_itemnumber) {
739             if (!exists $item_ref->{'itemnumber'}                       # new item
740                 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
741                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
742             }
743         }
744     }
745
746     # check for valid home branch
747     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
748         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
749         unless (defined $home_library) {
750             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
751         }
752     }
753
754     # check for valid holding branch
755     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
756         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
757         unless (defined $holding_library) {
758             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
759         }
760     }
761
762     return %errors;
763
764 }
765
766 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
767
768 The following functions provide various ways of 
769 getting an item record, a set of item records, or
770 lists of authorized values for certain item fields.
771
772 Some of the functions in this group are candidates
773 for refactoring -- for example, some of the code
774 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
775 has copy-and-paste work.
776
777 =cut
778
779 =head2 GetItemLocation
780
781   $itemlochash = GetItemLocation($fwk);
782
783 Returns a list of valid values for the
784 C<items.location> field.
785
786 NOTE: does B<not> return an individual item's
787 location.
788
789 where fwk stands for an optional framework code.
790 Create a location selector with the following code
791
792 =head3 in PERL SCRIPT
793
794   my $itemlochash = getitemlocation;
795   my @itemlocloop;
796   foreach my $thisloc (keys %$itemlochash) {
797       my $selected = 1 if $thisbranch eq $branch;
798       my %row =(locval => $thisloc,
799                   selected => $selected,
800                   locname => $itemlochash->{$thisloc},
801                );
802       push @itemlocloop, \%row;
803   }
804   $template->param(itemlocationloop => \@itemlocloop);
805
806 =head3 in TEMPLATE
807
808   <select name="location">
809       <option value="">Default</option>
810   <!-- TMPL_LOOP name="itemlocationloop" -->
811       <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
812   <!-- /TMPL_LOOP -->
813   </select>
814
815 =cut
816
817 sub GetItemLocation {
818
819     # returns a reference to a hash of references to location...
820     my ($fwk) = @_;
821     my %itemlocation;
822     my $dbh = C4::Context->dbh;
823     my $sth;
824     $fwk = '' unless ($fwk);
825     my ( $tag, $subfield ) =
826       GetMarcFromKohaField( "items.location", $fwk );
827     if ( $tag and $subfield ) {
828         my $sth =
829           $dbh->prepare(
830             "SELECT authorised_value
831             FROM marc_subfield_structure 
832             WHERE tagfield=? 
833                 AND tagsubfield=? 
834                 AND frameworkcode=?"
835           );
836         $sth->execute( $tag, $subfield, $fwk );
837         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
838             my $authvalsth =
839               $dbh->prepare(
840                 "SELECT authorised_value,lib
841                 FROM authorised_values
842                 WHERE category=?
843                 ORDER BY lib"
844               );
845             $authvalsth->execute($authorisedvaluecat);
846             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
847                 $itemlocation{$authorisedvalue} = $lib;
848             }
849             return \%itemlocation;
850         }
851         else {
852
853             #No authvalue list
854             # build default
855         }
856     }
857
858     #No authvalue list
859     #build default
860     $itemlocation{"1"} = "Not For Loan";
861     return \%itemlocation;
862 }
863
864 =head2 GetLostItems
865
866   $items = GetLostItems( $where );
867
868 This function gets a list of lost items.
869
870 =over 2
871
872 =item input:
873
874 C<$where> is a hashref. it containts a field of the items table as key
875 and the value to match as value. For example:
876
877 { barcode    => 'abc123',
878   homebranch => 'CPL',    }
879
880 =item return:
881
882 C<$items> is a reference to an array full of hashrefs with columns
883 from the "items" table as keys.
884
885 =item usage in the perl script:
886
887   my $where = { barcode => '0001548' };
888   my $items = GetLostItems( $where );
889   $template->param( itemsloop => $items );
890
891 =back
892
893 =cut
894
895 sub GetLostItems {
896     # Getting input args.
897     my $where   = shift;
898     my $dbh     = C4::Context->dbh;
899
900     my $query   = "
901         SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
902                itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
903         FROM   items
904             LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
905             LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
906             LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
907         WHERE
908                 authorised_values.category = 'LOST'
909                 AND itemlost IS NOT NULL
910                 AND itemlost <> 0
911     ";
912     my @query_parameters;
913     foreach my $key (keys %$where) {
914         $query .= " AND $key LIKE ?";
915         push @query_parameters, "%$where->{$key}%";
916     }
917
918     my $sth = $dbh->prepare($query);
919     $sth->execute( @query_parameters );
920     my $items = [];
921     while ( my $row = $sth->fetchrow_hashref ){
922         push @$items, $row;
923     }
924     return $items;
925 }
926
927 =head2 GetItemsForInventory
928
929 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
930   minlocation  => $minlocation,
931   maxlocation  => $maxlocation,
932   location     => $location,
933   itemtype     => $itemtype,
934   ignoreissued => $ignoreissued,
935   datelastseen => $datelastseen,
936   branchcode   => $branchcode,
937   branch       => $branch,
938   offset       => $offset,
939   size         => $size,
940   statushash   => $statushash,
941 } );
942
943 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
944
945 The sub returns a reference to a list of hashes, each containing
946 itemnumber, author, title, barcode, item callnumber, and date last
947 seen. It is ordered by callnumber then title.
948
949 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
950 the datelastseen can be used to specify that you want to see items not seen since a past date only.
951 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
952 $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.
953
954 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
955
956 =cut
957
958 sub GetItemsForInventory {
959     my ( $parameters ) = @_;
960     my $minlocation  = $parameters->{'minlocation'}  // '';
961     my $maxlocation  = $parameters->{'maxlocation'}  // '';
962     my $location     = $parameters->{'location'}     // '';
963     my $itemtype     = $parameters->{'itemtype'}     // '';
964     my $ignoreissued = $parameters->{'ignoreissued'} // '';
965     my $datelastseen = $parameters->{'datelastseen'} // '';
966     my $branchcode   = $parameters->{'branchcode'}   // '';
967     my $branch       = $parameters->{'branch'}       // '';
968     my $offset       = $parameters->{'offset'}       // '';
969     my $size         = $parameters->{'size'}         // '';
970     my $statushash   = $parameters->{'statushash'}   // '';
971
972     my $dbh = C4::Context->dbh;
973     my ( @bind_params, @where_strings );
974
975     my $select_columns = q{
976         SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
977     };
978     my $select_count = q{SELECT COUNT(*)};
979     my $query = q{
980         FROM items
981         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
982         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
983     };
984     if ($statushash){
985         for my $authvfield (keys %$statushash){
986             if ( scalar @{$statushash->{$authvfield}} > 0 ){
987                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
988                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
989             }
990         }
991     }
992
993     if ($minlocation) {
994         push @where_strings, 'itemcallnumber >= ?';
995         push @bind_params, $minlocation;
996     }
997
998     if ($maxlocation) {
999         push @where_strings, 'itemcallnumber <= ?';
1000         push @bind_params, $maxlocation;
1001     }
1002
1003     if ($datelastseen) {
1004         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
1005         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
1006         push @bind_params, $datelastseen;
1007     }
1008
1009     if ( $location ) {
1010         push @where_strings, 'items.location = ?';
1011         push @bind_params, $location;
1012     }
1013
1014     if ( $branchcode ) {
1015         if($branch eq "homebranch"){
1016         push @where_strings, 'items.homebranch = ?';
1017         }else{
1018             push @where_strings, 'items.holdingbranch = ?';
1019         }
1020         push @bind_params, $branchcode;
1021     }
1022
1023     if ( $itemtype ) {
1024         push @where_strings, 'biblioitems.itemtype = ?';
1025         push @bind_params, $itemtype;
1026     }
1027
1028     if ( $ignoreissued) {
1029         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1030         push @where_strings, 'issues.date_due IS NULL';
1031     }
1032
1033     if ( @where_strings ) {
1034         $query .= 'WHERE ';
1035         $query .= join ' AND ', @where_strings;
1036     }
1037     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1038     my $count_query = $select_count . $query;
1039     $query .= " LIMIT $offset, $size" if ($offset and $size);
1040     $query = $select_columns . $query;
1041     my $sth = $dbh->prepare($query);
1042     $sth->execute( @bind_params );
1043
1044     my @results = ();
1045     my $tmpresults = $sth->fetchall_arrayref({});
1046     $sth = $dbh->prepare( $count_query );
1047     $sth->execute( @bind_params );
1048     my ($iTotalRecords) = $sth->fetchrow_array();
1049
1050     my @avs = Koha::AuthorisedValues->search(
1051         {   'marc_subfield_structures.kohafield' => { '>' => '' },
1052             'me.authorised_value'                => { '>' => '' },
1053         },
1054         {   join     => { category => 'marc_subfield_structures' },
1055             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
1056             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
1057             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
1058         }
1059     );
1060
1061     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
1062
1063     foreach my $row (@$tmpresults) {
1064
1065         # Auth values
1066         foreach (keys %$row) {
1067             if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
1068                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
1069             }
1070         }
1071         push @results, $row;
1072     }
1073
1074     return (\@results, $iTotalRecords);
1075 }
1076
1077 =head2 GetItemInfosOf
1078
1079   GetItemInfosOf(@itemnumbers);
1080
1081 =cut
1082
1083 sub GetItemInfosOf {
1084     my @itemnumbers = @_;
1085
1086     my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
1087
1088     my $dbh = C4::Context->dbh;
1089     my $query = "
1090         SELECT *
1091         FROM items
1092         WHERE itemnumber IN ($itemnumber_values)
1093     ";
1094     return $dbh->selectall_hashref($query, 'itemnumber');
1095 }
1096
1097 =head2 GetItemsByBiblioitemnumber
1098
1099   GetItemsByBiblioitemnumber($biblioitemnumber);
1100
1101 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1102 Called by C<C4::XISBN>
1103
1104 =cut
1105
1106 sub GetItemsByBiblioitemnumber {
1107     my ( $bibitem ) = @_;
1108     my $dbh = C4::Context->dbh;
1109     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1110     # Get all items attached to a biblioitem
1111     my $i = 0;
1112     my @results; 
1113     $sth->execute($bibitem) || die $sth->errstr;
1114     while ( my $data = $sth->fetchrow_hashref ) {  
1115         # Foreach item, get circulation information
1116         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1117                                    WHERE itemnumber = ?
1118                                    AND issues.borrowernumber = borrowers.borrowernumber"
1119         );
1120         $sth2->execute( $data->{'itemnumber'} );
1121         if ( my $data2 = $sth2->fetchrow_hashref ) {
1122             # if item is out, set the due date and who it is out too
1123             $data->{'date_due'}   = $data2->{'date_due'};
1124             $data->{'cardnumber'} = $data2->{'cardnumber'};
1125             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1126         }
1127         else {
1128             # set date_due to blank, so in the template we check itemlost, and withdrawn
1129             $data->{'date_due'} = '';                                                                                                         
1130         }    # else         
1131         # Find the last 3 people who borrowed this item.                  
1132         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1133                       AND old_issues.borrowernumber = borrowers.borrowernumber
1134                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1135         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1136         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1137         my $i2 = 0;
1138         while ( my $data2 = $sth2->fetchrow_hashref ) {
1139             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1140             $data->{"card$i2"}      = $data2->{'cardnumber'};
1141             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1142             $i2++;
1143         }
1144         push(@results,$data);
1145     } 
1146     return (\@results); 
1147 }
1148
1149 =head2 GetItemsInfo
1150
1151   @results = GetItemsInfo($biblionumber);
1152
1153 Returns information about items with the given biblionumber.
1154
1155 C<GetItemsInfo> returns a list of references-to-hash. Each element
1156 contains a number of keys. Most of them are attributes from the
1157 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1158 Koha database. Other keys include:
1159
1160 =over 2
1161
1162 =item C<$data-E<gt>{branchname}>
1163
1164 The name (not the code) of the branch to which the book belongs.
1165
1166 =item C<$data-E<gt>{datelastseen}>
1167
1168 This is simply C<items.datelastseen>, except that while the date is
1169 stored in YYYY-MM-DD format in the database, here it is converted to
1170 DD/MM/YYYY format. A NULL date is returned as C<//>.
1171
1172 =item C<$data-E<gt>{datedue}>
1173
1174 =item C<$data-E<gt>{class}>
1175
1176 This is the concatenation of C<biblioitems.classification>, the book's
1177 Dewey code, and C<biblioitems.subclass>.
1178
1179 =item C<$data-E<gt>{ocount}>
1180
1181 I think this is the number of copies of the book available.
1182
1183 =item C<$data-E<gt>{order}>
1184
1185 If this is set, it is set to C<One Order>.
1186
1187 =back
1188
1189 =cut
1190
1191 sub GetItemsInfo {
1192     my ( $biblionumber ) = @_;
1193     my $dbh   = C4::Context->dbh;
1194     require C4::Languages;
1195     my $language = C4::Languages::getlanguage();
1196     my $query = "
1197     SELECT items.*,
1198            biblio.*,
1199            biblioitems.volume,
1200            biblioitems.number,
1201            biblioitems.itemtype,
1202            biblioitems.isbn,
1203            biblioitems.issn,
1204            biblioitems.publicationyear,
1205            biblioitems.publishercode,
1206            biblioitems.volumedate,
1207            biblioitems.volumedesc,
1208            biblioitems.lccn,
1209            biblioitems.url,
1210            items.notforloan as itemnotforloan,
1211            issues.borrowernumber,
1212            issues.date_due as datedue,
1213            issues.onsite_checkout,
1214            borrowers.cardnumber,
1215            borrowers.surname,
1216            borrowers.firstname,
1217            borrowers.branchcode as bcode,
1218            serial.serialseq,
1219            serial.publisheddate,
1220            itemtypes.description,
1221            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1222            itemtypes.notforloan as notforloan_per_itemtype,
1223            holding.branchurl,
1224            holding.branchcode,
1225            holding.branchname,
1226            holding.opac_info as holding_branch_opac_info,
1227            home.opac_info as home_branch_opac_info
1228     ";
1229     $query .= "
1230      FROM items
1231      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1232      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1233      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
1234      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1235      LEFT JOIN issues USING (itemnumber)
1236      LEFT JOIN borrowers USING (borrowernumber)
1237      LEFT JOIN serialitems USING (itemnumber)
1238      LEFT JOIN serial USING (serialid)
1239      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
1240      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1241     $query .= q|
1242     LEFT JOIN localization ON itemtypes.itemtype = localization.code
1243         AND localization.entity = 'itemtypes'
1244         AND localization.lang = ?
1245     |;
1246
1247     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1248     my $sth = $dbh->prepare($query);
1249     $sth->execute($language, $biblionumber);
1250     my $i = 0;
1251     my @results;
1252     my $serial;
1253
1254     my $userenv = C4::Context->userenv;
1255     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
1256     while ( my $data = $sth->fetchrow_hashref ) {
1257         if ( $data->{borrowernumber} && $want_not_same_branch) {
1258             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
1259         }
1260
1261         $serial ||= $data->{'serial'};
1262
1263         my $descriptions;
1264         # get notforloan complete status if applicable
1265         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
1266         $data->{notforloanvalue}     = $descriptions->{lib} // '';
1267         $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
1268
1269         # get restricted status and description if applicable
1270         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
1271         $data->{restricted}     = $descriptions->{lib} // '';
1272         $data->{restrictedopac} = $descriptions->{opac_description} // '';
1273
1274         # my stack procedures
1275         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
1276         $data->{stack}          = $descriptions->{lib} // '';
1277
1278         # Find the last 3 people who borrowed this item.
1279         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1280                                     WHERE itemnumber = ?
1281                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1282                                     ORDER BY returndate DESC
1283                                     LIMIT 3");
1284         $sth2->execute($data->{'itemnumber'});
1285         my $ii = 0;
1286         while (my $data2 = $sth2->fetchrow_hashref()) {
1287             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1288             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1289             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1290             $ii++;
1291         }
1292
1293         $results[$i] = $data;
1294         $i++;
1295     }
1296
1297     return $serial
1298         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1299         : @results;
1300 }
1301
1302 =head2 GetItemsLocationInfo
1303
1304   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1305
1306 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1307
1308 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1309
1310 =over 2
1311
1312 =item C<$data-E<gt>{homebranch}>
1313
1314 Branch Name of the item's homebranch
1315
1316 =item C<$data-E<gt>{holdingbranch}>
1317
1318 Branch Name of the item's holdingbranch
1319
1320 =item C<$data-E<gt>{location}>
1321
1322 Item's shelving location code
1323
1324 =item C<$data-E<gt>{location_intranet}>
1325
1326 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1327
1328 =item C<$data-E<gt>{location_opac}>
1329
1330 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
1331 description is set.
1332
1333 =item C<$data-E<gt>{itemcallnumber}>
1334
1335 Item's itemcallnumber
1336
1337 =item C<$data-E<gt>{cn_sort}>
1338
1339 Item's call number normalized for sorting
1340
1341 =back
1342   
1343 =cut
1344
1345 sub GetItemsLocationInfo {
1346         my $biblionumber = shift;
1347         my @results;
1348
1349         my $dbh = C4::Context->dbh;
1350         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
1351                             location, itemcallnumber, cn_sort
1352                      FROM items, branches as a, branches as b
1353                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
1354                      AND biblionumber = ?
1355                      ORDER BY cn_sort ASC";
1356         my $sth = $dbh->prepare($query);
1357         $sth->execute($biblionumber);
1358
1359         while ( my $data = $sth->fetchrow_hashref ) {
1360              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1361              $av = $av->count ? $av->next : undef;
1362              $data->{location_intranet} = $av ? $av->lib : '';
1363              $data->{location_opac}     = $av ? $av->opac_description : '';
1364              push @results, $data;
1365         }
1366         return @results;
1367 }
1368
1369 =head2 GetHostItemsInfo
1370
1371         $hostiteminfo = GetHostItemsInfo($hostfield);
1372         Returns the iteminfo for items linked to records via a host field
1373
1374 =cut
1375
1376 sub GetHostItemsInfo {
1377         my ($record) = @_;
1378         my @returnitemsInfo;
1379
1380         if (C4::Context->preference('marcflavour') eq 'MARC21' ||
1381         C4::Context->preference('marcflavour') eq 'NORMARC'){
1382             foreach my $hostfield ( $record->field('773') ) {
1383                 my $hostbiblionumber = $hostfield->subfield("0");
1384                 my $linkeditemnumber = $hostfield->subfield("9");
1385                 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1386                 foreach my $hostitemInfo (@hostitemInfos){
1387                         if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1388                                 push (@returnitemsInfo,$hostitemInfo);
1389                                 last;
1390                         }
1391                 }
1392             }
1393         } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
1394             foreach my $hostfield ( $record->field('461') ) {
1395                 my $hostbiblionumber = $hostfield->subfield("0");
1396                 my $linkeditemnumber = $hostfield->subfield("9");
1397                 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1398                 foreach my $hostitemInfo (@hostitemInfos){
1399                         if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1400                                 push (@returnitemsInfo,$hostitemInfo);
1401                                 last;
1402                         }
1403                 }
1404             }
1405         }
1406         return @returnitemsInfo;
1407 }
1408
1409
1410 =head2 GetLastAcquisitions
1411
1412   my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 
1413                                     'itemtypes' => ('BK','BD')}, 10);
1414
1415 =cut
1416
1417 sub  GetLastAcquisitions {
1418         my ($data,$max) = @_;
1419
1420         my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1421         
1422         my $number_of_branches = @{$data->{branches}};
1423         my $number_of_itemtypes   = @{$data->{itemtypes}};
1424         
1425         
1426         my @where = ('WHERE 1 '); 
1427         $number_of_branches and push @where
1428            , 'AND holdingbranch IN (' 
1429            , join(',', ('?') x $number_of_branches )
1430            , ')'
1431          ;
1432         
1433         $number_of_itemtypes and push @where
1434            , "AND $itemtype IN (" 
1435            , join(',', ('?') x $number_of_itemtypes )
1436            , ')'
1437          ;
1438
1439         my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1440                                  FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
1441                                     RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1442                                     @where
1443                                     GROUP BY biblio.biblionumber 
1444                                     ORDER BY dateaccessioned DESC LIMIT $max";
1445
1446         my $dbh = C4::Context->dbh;
1447         my $sth = $dbh->prepare($query);
1448     
1449     $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1450         
1451         my @results;
1452         while( my $row = $sth->fetchrow_hashref){
1453                 push @results, {date => $row->{dateaccessioned} 
1454                                                 , biblionumber => $row->{biblionumber}
1455                                                 , title => $row->{title}};
1456         }
1457         
1458         return @results;
1459 }
1460
1461 =head2 GetItemnumbersForBiblio
1462
1463   my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1464
1465 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1466
1467 =cut
1468
1469 sub GetItemnumbersForBiblio {
1470     my $biblionumber = shift;
1471     my @items;
1472     my $dbh = C4::Context->dbh;
1473     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1474     $sth->execute($biblionumber);
1475     while (my $result = $sth->fetchrow_hashref) {
1476         push @items, $result->{'itemnumber'};
1477     }
1478     return \@items;
1479 }
1480
1481 =head2 get_itemnumbers_of
1482
1483   my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1484
1485 Given a list of biblionumbers, return the list of corresponding itemnumbers
1486 for each biblionumber.
1487
1488 Return a reference on a hash where keys are biblionumbers and values are
1489 references on array of itemnumbers.
1490
1491 =cut
1492
1493 sub get_itemnumbers_of {
1494     my @biblionumbers = @_;
1495
1496     my $dbh = C4::Context->dbh;
1497
1498     my $query = '
1499         SELECT itemnumber,
1500             biblionumber
1501         FROM items
1502         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1503     ';
1504     my $sth = $dbh->prepare($query);
1505     $sth->execute(@biblionumbers);
1506
1507     my %itemnumbers_of;
1508
1509     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1510         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1511     }
1512
1513     return \%itemnumbers_of;
1514 }
1515
1516 =head2 get_hostitemnumbers_of
1517
1518   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1519
1520 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1521
1522 Return a reference on a hash where key is a biblionumber and values are
1523 references on array of itemnumbers.
1524
1525 =cut
1526
1527
1528 sub get_hostitemnumbers_of {
1529     my ($biblionumber) = @_;
1530     my $marcrecord = GetMarcBiblio($biblionumber);
1531
1532     return unless $marcrecord;
1533
1534     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1535
1536     my $marcflavor = C4::Context->preference('marcflavour');
1537     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1538         $tag      = '773';
1539         $biblio_s = '0';
1540         $item_s   = '9';
1541     }
1542     elsif ( $marcflavor eq 'UNIMARC' ) {
1543         $tag      = '461';
1544         $biblio_s = '0';
1545         $item_s   = '9';
1546     }
1547
1548     foreach my $hostfield ( $marcrecord->field($tag) ) {
1549         my $hostbiblionumber = $hostfield->subfield($biblio_s);
1550         my $linkeditemnumber = $hostfield->subfield($item_s);
1551         my @itemnumbers;
1552         if ( my $itemnumbers =
1553             get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber} )
1554         {
1555             @itemnumbers = @$itemnumbers;
1556         }
1557         foreach my $itemnumber (@itemnumbers) {
1558             if ( $itemnumber eq $linkeditemnumber ) {
1559                 push( @returnhostitemnumbers, $itemnumber );
1560                 last;
1561             }
1562         }
1563     }
1564
1565     return @returnhostitemnumbers;
1566 }
1567
1568
1569 =head2 GetItemnumberFromBarcode
1570
1571   $result = GetItemnumberFromBarcode($barcode);
1572
1573 =cut
1574
1575 sub GetItemnumberFromBarcode {
1576     my ($barcode) = @_;
1577     my $dbh = C4::Context->dbh;
1578
1579     my $rq =
1580       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1581     $rq->execute($barcode);
1582     my ($result) = $rq->fetchrow;
1583     return ($result);
1584 }
1585
1586 =head2 GetBarcodeFromItemnumber
1587
1588   $result = GetBarcodeFromItemnumber($itemnumber);
1589
1590 =cut
1591
1592 sub GetBarcodeFromItemnumber {
1593     my ($itemnumber) = @_;
1594     my $dbh = C4::Context->dbh;
1595
1596     my $rq =
1597       $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1598     $rq->execute($itemnumber);
1599     my ($result) = $rq->fetchrow;
1600     return ($result);
1601 }
1602
1603 =head2 GetHiddenItemnumbers
1604
1605     my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1606
1607 Given a list of items it checks which should be hidden from the OPAC given
1608 the current configuration. Returns a list of itemnumbers corresponding to
1609 those that should be hidden.
1610
1611 =cut
1612
1613 sub GetHiddenItemnumbers {
1614     my (@items) = @_;
1615     my @resultitems;
1616
1617     my $yaml = C4::Context->preference('OpacHiddenItems');
1618     return () if (! $yaml =~ /\S/ );
1619     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1620     my $hidingrules;
1621     eval {
1622         $hidingrules = YAML::Load($yaml);
1623     };
1624     if ($@) {
1625         warn "Unable to parse OpacHiddenItems syspref : $@";
1626         return ();
1627     }
1628     my $dbh = C4::Context->dbh;
1629
1630     # For each item
1631     foreach my $item (@items) {
1632
1633         # We check each rule
1634         foreach my $field (keys %$hidingrules) {
1635             my $val;
1636             if (exists $item->{$field}) {
1637                 $val = $item->{$field};
1638             }
1639             else {
1640                 my $query = "SELECT $field from items where itemnumber = ?";
1641                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1642             }
1643             $val = '' unless defined $val;
1644
1645             # If the results matches the values in the yaml file
1646             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1647
1648                 # We add the itemnumber to the list
1649                 push @resultitems, $item->{'itemnumber'};
1650
1651                 # If at least one rule matched for an item, no need to test the others
1652                 last;
1653             }
1654         }
1655     }
1656     return @resultitems;
1657 }
1658
1659 =head1 LIMITED USE FUNCTIONS
1660
1661 The following functions, while part of the public API,
1662 are not exported.  This is generally because they are
1663 meant to be used by only one script for a specific
1664 purpose, and should not be used in any other context
1665 without careful thought.
1666
1667 =cut
1668
1669 =head2 GetMarcItem
1670
1671   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1672
1673 Returns MARC::Record of the item passed in parameter.
1674 This function is meant for use only in C<cataloguing/additem.pl>,
1675 where it is needed to support that script's MARC-like
1676 editor.
1677
1678 =cut
1679
1680 sub GetMarcItem {
1681     my ( $biblionumber, $itemnumber ) = @_;
1682
1683     # GetMarcItem has been revised so that it does the following:
1684     #  1. Gets the item information from the items table.
1685     #  2. Converts it to a MARC field for storage in the bib record.
1686     #
1687     # The previous behavior was:
1688     #  1. Get the bib record.
1689     #  2. Return the MARC tag corresponding to the item record.
1690     #
1691     # The difference is that one treats the items row as authoritative,
1692     # while the other treats the MARC representation as authoritative
1693     # under certain circumstances.
1694
1695     my $itemrecord = GetItem($itemnumber);
1696
1697     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1698     # Also, don't emit a subfield if the underlying field is blank.
1699
1700     
1701     return Item2Marc($itemrecord,$biblionumber);
1702
1703 }
1704 sub Item2Marc {
1705         my ($itemrecord,$biblionumber)=@_;
1706     my $mungeditem = { 
1707         map {  
1708             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1709         } keys %{ $itemrecord } 
1710     };
1711     my $itemmarc = TransformKohaToMarc($mungeditem);
1712     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1713
1714     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1715     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1716                 foreach my $field ($itemmarc->field($itemtag)){
1717             $field->add_subfields(@$unlinked_item_subfields);
1718         }
1719     }
1720         return $itemmarc;
1721 }
1722
1723 =head1 PRIVATE FUNCTIONS AND VARIABLES
1724
1725 The following functions are not meant to be called
1726 directly, but are documented in order to explain
1727 the inner workings of C<C4::Items>.
1728
1729 =cut
1730
1731 =head2 %derived_columns
1732
1733 This hash keeps track of item columns that
1734 are strictly derived from other columns in
1735 the item record and are not meant to be set
1736 independently.
1737
1738 Each key in the hash should be the name of a
1739 column (as named by TransformMarcToKoha).  Each
1740 value should be hashref whose keys are the
1741 columns on which the derived column depends.  The
1742 hashref should also contain a 'BUILDER' key
1743 that is a reference to a sub that calculates
1744 the derived value.
1745
1746 =cut
1747
1748 my %derived_columns = (
1749     'items.cn_sort' => {
1750         'itemcallnumber' => 1,
1751         'items.cn_source' => 1,
1752         'BUILDER' => \&_calc_items_cn_sort,
1753     }
1754 );
1755
1756 =head2 _set_derived_columns_for_add 
1757
1758   _set_derived_column_for_add($item);
1759
1760 Given an item hash representing a new item to be added,
1761 calculate any derived columns.  Currently the only
1762 such column is C<items.cn_sort>.
1763
1764 =cut
1765
1766 sub _set_derived_columns_for_add {
1767     my $item = shift;
1768
1769     foreach my $column (keys %derived_columns) {
1770         my $builder = $derived_columns{$column}->{'BUILDER'};
1771         my $source_values = {};
1772         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1773             next if $source_column eq 'BUILDER';
1774             $source_values->{$source_column} = $item->{$source_column};
1775         }
1776         $builder->($item, $source_values);
1777     }
1778 }
1779
1780 =head2 _set_derived_columns_for_mod 
1781
1782   _set_derived_column_for_mod($item);
1783
1784 Given an item hash representing a new item to be modified.
1785 calculate any derived columns.  Currently the only
1786 such column is C<items.cn_sort>.
1787
1788 This routine differs from C<_set_derived_columns_for_add>
1789 in that it needs to handle partial item records.  In other
1790 words, the caller of C<ModItem> may have supplied only one
1791 or two columns to be changed, so this function needs to
1792 determine whether any of the columns to be changed affect
1793 any of the derived columns.  Also, if a derived column
1794 depends on more than one column, but the caller is not
1795 changing all of then, this routine retrieves the unchanged
1796 values from the database in order to ensure a correct
1797 calculation.
1798
1799 =cut
1800
1801 sub _set_derived_columns_for_mod {
1802     my $item = shift;
1803
1804     foreach my $column (keys %derived_columns) {
1805         my $builder = $derived_columns{$column}->{'BUILDER'};
1806         my $source_values = {};
1807         my %missing_sources = ();
1808         my $must_recalc = 0;
1809         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1810             next if $source_column eq 'BUILDER';
1811             if (exists $item->{$source_column}) {
1812                 $must_recalc = 1;
1813                 $source_values->{$source_column} = $item->{$source_column};
1814             } else {
1815                 $missing_sources{$source_column} = 1;
1816             }
1817         }
1818         if ($must_recalc) {
1819             foreach my $source_column (keys %missing_sources) {
1820                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1821             }
1822             $builder->($item, $source_values);
1823         }
1824     }
1825 }
1826
1827 =head2 _do_column_fixes_for_mod
1828
1829   _do_column_fixes_for_mod($item);
1830
1831 Given an item hashref containing one or more
1832 columns to modify, fix up certain values.
1833 Specifically, set to 0 any passed value
1834 of C<notforloan>, C<damaged>, C<itemlost>, or
1835 C<withdrawn> that is either undefined or
1836 contains the empty string.
1837
1838 =cut
1839
1840 sub _do_column_fixes_for_mod {
1841     my $item = shift;
1842
1843     if (exists $item->{'notforloan'} and
1844         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1845         $item->{'notforloan'} = 0;
1846     }
1847     if (exists $item->{'damaged'} and
1848         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1849         $item->{'damaged'} = 0;
1850     }
1851     if (exists $item->{'itemlost'} and
1852         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1853         $item->{'itemlost'} = 0;
1854     }
1855     if (exists $item->{'withdrawn'} and
1856         (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1857         $item->{'withdrawn'} = 0;
1858     }
1859     if (exists $item->{location}
1860         and $item->{location} ne 'CART'
1861         and $item->{location} ne 'PROC'
1862         and not $item->{permanent_location}
1863     ) {
1864         $item->{'permanent_location'} = $item->{'location'};
1865     }
1866     if (exists $item->{'timestamp'}) {
1867         delete $item->{'timestamp'};
1868     }
1869 }
1870
1871 =head2 _get_single_item_column
1872
1873   _get_single_item_column($column, $itemnumber);
1874
1875 Retrieves the value of a single column from an C<items>
1876 row specified by C<$itemnumber>.
1877
1878 =cut
1879
1880 sub _get_single_item_column {
1881     my $column = shift;
1882     my $itemnumber = shift;
1883     
1884     my $dbh = C4::Context->dbh;
1885     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1886     $sth->execute($itemnumber);
1887     my ($value) = $sth->fetchrow();
1888     return $value; 
1889 }
1890
1891 =head2 _calc_items_cn_sort
1892
1893   _calc_items_cn_sort($item, $source_values);
1894
1895 Helper routine to calculate C<items.cn_sort>.
1896
1897 =cut
1898
1899 sub _calc_items_cn_sort {
1900     my $item = shift;
1901     my $source_values = shift;
1902
1903     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1904 }
1905
1906 =head2 _set_defaults_for_add 
1907
1908   _set_defaults_for_add($item_hash);
1909
1910 Given an item hash representing an item to be added, set
1911 correct default values for columns whose default value
1912 is not handled by the DBMS.  This includes the following
1913 columns:
1914
1915 =over 2
1916
1917 =item * 
1918
1919 C<items.dateaccessioned>
1920
1921 =item *
1922
1923 C<items.notforloan>
1924
1925 =item *
1926
1927 C<items.damaged>
1928
1929 =item *
1930
1931 C<items.itemlost>
1932
1933 =item *
1934
1935 C<items.withdrawn>
1936
1937 =back
1938
1939 =cut
1940
1941 sub _set_defaults_for_add {
1942     my $item = shift;
1943     $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1944     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1945 }
1946
1947 =head2 _koha_new_item
1948
1949   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1950
1951 Perform the actual insert into the C<items> table.
1952
1953 =cut
1954
1955 sub _koha_new_item {
1956     my ( $item, $barcode ) = @_;
1957     my $dbh=C4::Context->dbh;  
1958     my $error;
1959     $item->{permanent_location} //= $item->{location};
1960     _mod_item_dates( $item );
1961     my $query =
1962            "INSERT INTO items SET
1963             biblionumber        = ?,
1964             biblioitemnumber    = ?,
1965             barcode             = ?,
1966             dateaccessioned     = ?,
1967             booksellerid        = ?,
1968             homebranch          = ?,
1969             price               = ?,
1970             replacementprice    = ?,
1971             replacementpricedate = ?,
1972             datelastborrowed    = ?,
1973             datelastseen        = ?,
1974             stack               = ?,
1975             notforloan          = ?,
1976             damaged             = ?,
1977             itemlost            = ?,
1978             withdrawn           = ?,
1979             itemcallnumber      = ?,
1980             coded_location_qualifier = ?,
1981             restricted          = ?,
1982             itemnotes           = ?,
1983             itemnotes_nonpublic = ?,
1984             holdingbranch       = ?,
1985             paidfor             = ?,
1986             location            = ?,
1987             permanent_location  = ?,
1988             onloan              = ?,
1989             issues              = ?,
1990             renewals            = ?,
1991             reserves            = ?,
1992             cn_source           = ?,
1993             cn_sort             = ?,
1994             ccode               = ?,
1995             itype               = ?,
1996             materials           = ?,
1997             uri                 = ?,
1998             enumchron           = ?,
1999             more_subfields_xml  = ?,
2000             copynumber          = ?,
2001             stocknumber         = ?,
2002             new_status          = ?
2003           ";
2004     my $sth = $dbh->prepare($query);
2005     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2006    $sth->execute(
2007             $item->{'biblionumber'},
2008             $item->{'biblioitemnumber'},
2009             $barcode,
2010             $item->{'dateaccessioned'},
2011             $item->{'booksellerid'},
2012             $item->{'homebranch'},
2013             $item->{'price'},
2014             $item->{'replacementprice'},
2015             $item->{'replacementpricedate'} || $today,
2016             $item->{datelastborrowed},
2017             $item->{datelastseen} || $today,
2018             $item->{stack},
2019             $item->{'notforloan'},
2020             $item->{'damaged'},
2021             $item->{'itemlost'},
2022             $item->{'withdrawn'},
2023             $item->{'itemcallnumber'},
2024             $item->{'coded_location_qualifier'},
2025             $item->{'restricted'},
2026             $item->{'itemnotes'},
2027             $item->{'itemnotes_nonpublic'},
2028             $item->{'holdingbranch'},
2029             $item->{'paidfor'},
2030             $item->{'location'},
2031             $item->{'permanent_location'},
2032             $item->{'onloan'},
2033             $item->{'issues'},
2034             $item->{'renewals'},
2035             $item->{'reserves'},
2036             $item->{'items.cn_source'},
2037             $item->{'items.cn_sort'},
2038             $item->{'ccode'},
2039             $item->{'itype'},
2040             $item->{'materials'},
2041             $item->{'uri'},
2042             $item->{'enumchron'},
2043             $item->{'more_subfields_xml'},
2044             $item->{'copynumber'},
2045             $item->{'stocknumber'},
2046             $item->{'new_status'},
2047     );
2048
2049     my $itemnumber;
2050     if ( defined $sth->errstr ) {
2051         $error.="ERROR in _koha_new_item $query".$sth->errstr;
2052     }
2053     else {
2054         $itemnumber = $dbh->{'mysql_insertid'};
2055     }
2056
2057     return ( $itemnumber, $error );
2058 }
2059
2060 =head2 MoveItemFromBiblio
2061
2062   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2063
2064 Moves an item from a biblio to another
2065
2066 Returns undef if the move failed or the biblionumber of the destination record otherwise
2067
2068 =cut
2069
2070 sub MoveItemFromBiblio {
2071     my ($itemnumber, $frombiblio, $tobiblio) = @_;
2072     my $dbh = C4::Context->dbh;
2073     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
2074         SELECT biblioitemnumber
2075         FROM biblioitems
2076         WHERE biblionumber = ?
2077     |, undef, $tobiblio );
2078     my $return = $dbh->do(q|
2079         UPDATE items
2080         SET biblioitemnumber = ?,
2081             biblionumber = ?
2082         WHERE itemnumber = ?
2083             AND biblionumber = ?
2084     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
2085     if ($return == 1) {
2086         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
2087         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
2088             # Checking if the item we want to move is in an order 
2089         require C4::Acquisition;
2090         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
2091             if ($order) {
2092                     # Replacing the biblionumber within the order if necessary
2093                     $order->{'biblionumber'} = $tobiblio;
2094                 C4::Acquisition::ModOrder($order);
2095             }
2096
2097         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
2098         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
2099             $dbh->do( qq|
2100                 UPDATE $table_name
2101                 SET biblionumber = ?
2102                 WHERE itemnumber = ?
2103             |, undef, $tobiblio, $itemnumber );
2104         }
2105         return $tobiblio;
2106         }
2107     return;
2108 }
2109
2110 =head2 ItemSafeToDelete
2111
2112    ItemSafeToDelete( $biblionumber, $itemnumber);
2113
2114 Exported function (core API) for checking whether an item record is safe to delete.
2115
2116 returns 1 if the item is safe to delete,
2117
2118 "book_on_loan" if the item is checked out,
2119
2120 "not_same_branch" if the item is blocked by independent branches,
2121
2122 "book_reserved" if the there are holds aganst the item, or
2123
2124 "linked_analytics" if the item has linked analytic records.
2125
2126 =cut
2127
2128 sub ItemSafeToDelete {
2129     my ( $biblionumber, $itemnumber ) = @_;
2130     my $status;
2131     my $dbh = C4::Context->dbh;
2132
2133     my $error;
2134
2135     my $countanalytics = GetAnalyticsCount($itemnumber);
2136
2137     # check that there is no issue on this item before deletion.
2138     my $sth = $dbh->prepare(
2139         q{
2140         SELECT COUNT(*) FROM issues
2141         WHERE itemnumber = ?
2142     }
2143     );
2144     $sth->execute($itemnumber);
2145     my ($onloan) = $sth->fetchrow;
2146
2147     my $item = GetItem($itemnumber);
2148
2149     if ($onloan) {
2150         $status = "book_on_loan";
2151     }
2152     elsif ( defined C4::Context->userenv
2153         and !C4::Context->IsSuperLibrarian()
2154         and C4::Context->preference("IndependentBranches")
2155         and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
2156     {
2157         $status = "not_same_branch";
2158     }
2159     else {
2160         # check it doesn't have a waiting reserve
2161         $sth = $dbh->prepare(
2162             q{
2163             SELECT COUNT(*) FROM reserves
2164             WHERE (found = 'W' OR found = 'T')
2165             AND itemnumber = ?
2166         }
2167         );
2168         $sth->execute($itemnumber);
2169         my ($reserve) = $sth->fetchrow;
2170         if ($reserve) {
2171             $status = "book_reserved";
2172         }
2173         elsif ( $countanalytics > 0 ) {
2174             $status = "linked_analytics";
2175         }
2176         else {
2177             $status = 1;
2178         }
2179     }
2180     return $status;
2181 }
2182
2183 =head2 DelItemCheck
2184
2185    DelItemCheck( $biblionumber, $itemnumber);
2186
2187 Exported function (core API) for deleting an item record in Koha if there no current issue.
2188
2189 DelItemCheck wraps ItemSafeToDelete around DelItem.
2190
2191 =cut
2192
2193 sub DelItemCheck {
2194     my ( $biblionumber, $itemnumber ) = @_;
2195     my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
2196
2197     if ( $status == 1 ) {
2198         DelItem(
2199             {
2200                 biblionumber => $biblionumber,
2201                 itemnumber   => $itemnumber
2202             }
2203         );
2204     }
2205     return $status;
2206 }
2207
2208 =head2 _koha_modify_item
2209
2210   my ($itemnumber,$error) =_koha_modify_item( $item );
2211
2212 Perform the actual update of the C<items> row.  Note that this
2213 routine accepts a hashref specifying the columns to update.
2214
2215 =cut
2216
2217 sub _koha_modify_item {
2218     my ( $item ) = @_;
2219     my $dbh=C4::Context->dbh;  
2220     my $error;
2221
2222     my $query = "UPDATE items SET ";
2223     my @bind;
2224     _mod_item_dates( $item );
2225     for my $key ( keys %$item ) {
2226         next if ( $key eq 'itemnumber' );
2227         $query.="$key=?,";
2228         push @bind, $item->{$key};
2229     }
2230     $query =~ s/,$//;
2231     $query .= " WHERE itemnumber=?";
2232     push @bind, $item->{'itemnumber'};
2233     my $sth = $dbh->prepare($query);
2234     $sth->execute(@bind);
2235     if ( $sth->err ) {
2236         $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2237         warn $error;
2238     }
2239     return ($item->{'itemnumber'},$error);
2240 }
2241
2242 sub _mod_item_dates { # date formatting for date fields in item hash
2243     my ( $item ) = @_;
2244     return if !$item || ref($item) ne 'HASH';
2245
2246     my @keys = grep
2247         { $_ =~ /^onloan$|^date|date$|datetime$/ }
2248         keys %$item;
2249     # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
2250     # NOTE: We do not (yet) have items fields ending with datetime
2251     # Fields with _on$ have been handled already
2252
2253     foreach my $key ( @keys ) {
2254         next if !defined $item->{$key}; # skip undefs
2255         my $dt = eval { dt_from_string( $item->{$key} ) };
2256             # eval: dt_from_string will die on us if we pass illegal dates
2257
2258         my $newstr;
2259         if( defined $dt  && ref($dt) eq 'DateTime' ) {
2260             if( $key =~ /datetime/ ) {
2261                 $newstr = DateTime::Format::MySQL->format_datetime($dt);
2262             } else {
2263                 $newstr = DateTime::Format::MySQL->format_date($dt);
2264             }
2265         }
2266         $item->{$key} = $newstr; # might be undef to clear garbage
2267     }
2268 }
2269
2270 =head2 _koha_delete_item
2271
2272   _koha_delete_item( $itemnum );
2273
2274 Internal function to delete an item record from the koha tables
2275
2276 =cut
2277
2278 sub _koha_delete_item {
2279     my ( $itemnum ) = @_;
2280
2281     my $dbh = C4::Context->dbh;
2282     # save the deleted item to deleteditems table
2283     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2284     $sth->execute($itemnum);
2285     my $data = $sth->fetchrow_hashref();
2286
2287     # There is no item to delete
2288     return 0 unless $data;
2289
2290     my $query = "INSERT INTO deleteditems SET ";
2291     my @bind  = ();
2292     foreach my $key ( keys %$data ) {
2293         next if ( $key eq 'timestamp' ); # timestamp will be set by db
2294         $query .= "$key = ?,";
2295         push( @bind, $data->{$key} );
2296     }
2297     $query =~ s/\,$//;
2298     $sth = $dbh->prepare($query);
2299     $sth->execute(@bind);
2300
2301     # delete from items table
2302     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2303     my $deleted = $sth->execute($itemnum);
2304     return ( $deleted == 1 ) ? 1 : 0;
2305 }
2306
2307 =head2 _marc_from_item_hash
2308
2309   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2310
2311 Given an item hash representing a complete item record,
2312 create a C<MARC::Record> object containing an embedded
2313 tag representing that item.
2314
2315 The third, optional parameter C<$unlinked_item_subfields> is
2316 an arrayref of subfields (not mapped to C<items> fields per the
2317 framework) to be added to the MARC representation
2318 of the item.
2319
2320 =cut
2321
2322 sub _marc_from_item_hash {
2323     my $item = shift;
2324     my $frameworkcode = shift;
2325     my $unlinked_item_subfields;
2326     if (@_) {
2327         $unlinked_item_subfields = shift;
2328     }
2329    
2330     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2331     # Also, don't emit a subfield if the underlying field is blank.
2332     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
2333                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
2334                                 : ()  } keys %{ $item } }; 
2335
2336     my $item_marc = MARC::Record->new();
2337     foreach my $item_field ( keys %{$mungeditem} ) {
2338         my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2339         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
2340         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2341         foreach my $value (@values){
2342             if ( my $field = $item_marc->field($tag) ) {
2343                     $field->add_subfields( $subfield => $value );
2344             } else {
2345                 my $add_subfields = [];
2346                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2347                     $add_subfields = $unlinked_item_subfields;
2348             }
2349             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2350             }
2351         }
2352     }
2353
2354     return $item_marc;
2355 }
2356
2357 =head2 _repack_item_errors
2358
2359 Add an error message hash generated by C<CheckItemPreSave>
2360 to a list of errors.
2361
2362 =cut
2363
2364 sub _repack_item_errors {
2365     my $item_sequence_num = shift;
2366     my $item_ref = shift;
2367     my $error_ref = shift;
2368
2369     my @repacked_errors = ();
2370
2371     foreach my $error_code (sort keys %{ $error_ref }) {
2372         my $repacked_error = {};
2373         $repacked_error->{'item_sequence'} = $item_sequence_num;
2374         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2375         $repacked_error->{'error_code'} = $error_code;
2376         $repacked_error->{'error_information'} = $error_ref->{$error_code};
2377         push @repacked_errors, $repacked_error;
2378     } 
2379
2380     return @repacked_errors;
2381 }
2382
2383 =head2 _get_unlinked_item_subfields
2384
2385   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2386
2387 =cut
2388
2389 sub _get_unlinked_item_subfields {
2390     my $original_item_marc = shift;
2391     my $frameworkcode = shift;
2392
2393     my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
2394
2395     # assume that this record has only one field, and that that
2396     # field contains only the item information
2397     my $subfields = [];
2398     my @fields = $original_item_marc->fields();
2399     if ($#fields > -1) {
2400         my $field = $fields[0];
2401             my $tag = $field->tag();
2402         foreach my $subfield ($field->subfields()) {
2403             if (defined $subfield->[1] and
2404                 $subfield->[1] ne '' and
2405                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2406                 push @$subfields, $subfield->[0] => $subfield->[1];
2407             }
2408         }
2409     }
2410     return $subfields;
2411 }
2412
2413 =head2 _get_unlinked_subfields_xml
2414
2415   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2416
2417 =cut
2418
2419 sub _get_unlinked_subfields_xml {
2420     my $unlinked_item_subfields = shift;
2421
2422     my $xml;
2423     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2424         my $marc = MARC::Record->new();
2425         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2426         # used in the framework
2427         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2428         $marc->encoding("UTF-8");    
2429         $xml = $marc->as_xml("USMARC");
2430     }
2431
2432     return $xml;
2433 }
2434
2435 =head2 _parse_unlinked_item_subfields_from_xml
2436
2437   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2438
2439 =cut
2440
2441 sub  _parse_unlinked_item_subfields_from_xml {
2442     my $xml = shift;
2443     require C4::Charset;
2444     return unless defined $xml and $xml ne "";
2445     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2446     my $unlinked_subfields = [];
2447     my @fields = $marc->fields();
2448     if ($#fields > -1) {
2449         foreach my $subfield ($fields[0]->subfields()) {
2450             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2451         }
2452     }
2453     return $unlinked_subfields;
2454 }
2455
2456 =head2 GetAnalyticsCount
2457
2458   $count= &GetAnalyticsCount($itemnumber)
2459
2460 counts Usage of itemnumber in Analytical bibliorecords. 
2461
2462 =cut
2463
2464 sub GetAnalyticsCount {
2465     my ($itemnumber) = @_;
2466
2467     ### ZOOM search here
2468     my $query;
2469     $query= "hi=".$itemnumber;
2470     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2471     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2472     return ($result);
2473 }
2474
2475 =head2 SearchItemsByField
2476
2477     my $items = SearchItemsByField($field, $value);
2478
2479 SearchItemsByField will search for items on a specific given field.
2480 For instance you can search all items with a specific stocknumber like this:
2481
2482     my $items = SearchItemsByField('stocknumber', $stocknumber);
2483
2484 =cut
2485
2486 sub SearchItemsByField {
2487     my ($field, $value) = @_;
2488
2489     my $filters = {
2490         field => $field,
2491         query => $value,
2492     };
2493
2494     my ($results) = SearchItems($filters);
2495     return $results;
2496 }
2497
2498 sub _SearchItems_build_where_fragment {
2499     my ($filter) = @_;
2500
2501     my $dbh = C4::Context->dbh;
2502
2503     my $where_fragment;
2504     if (exists($filter->{conjunction})) {
2505         my (@where_strs, @where_args);
2506         foreach my $f (@{ $filter->{filters} }) {
2507             my $fragment = _SearchItems_build_where_fragment($f);
2508             if ($fragment) {
2509                 push @where_strs, $fragment->{str};
2510                 push @where_args, @{ $fragment->{args} };
2511             }
2512         }
2513         my $where_str = '';
2514         if (@where_strs) {
2515             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2516             $where_fragment = {
2517                 str => $where_str,
2518                 args => \@where_args,
2519             };
2520         }
2521     } else {
2522         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2523         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2524         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2525         my @operators = qw(= != > < >= <= like);
2526         my $field = $filter->{field};
2527         if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2528             my $op = $filter->{operator};
2529             my $query = $filter->{query};
2530
2531             if (!$op or (0 == grep /^$op$/, @operators)) {
2532                 $op = '='; # default operator
2533             }
2534
2535             my $column;
2536             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2537                 my $marcfield = $1;
2538                 my $marcsubfield = $2;
2539                 my ($kohafield) = $dbh->selectrow_array(q|
2540                     SELECT kohafield FROM marc_subfield_structure
2541                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2542                 |, undef, $marcfield, $marcsubfield);
2543
2544                 if ($kohafield) {
2545                     $column = $kohafield;
2546                 } else {
2547                     # MARC field is not linked to a DB field so we need to use
2548                     # ExtractValue on marcxml from biblio_metadata or
2549                     # items.more_subfields_xml, depending on the MARC field.
2550                     my $xpath;
2551                     my $sqlfield;
2552                     my ($itemfield) = GetMarcFromKohaField('items.itemnumber');
2553                     if ($marcfield eq $itemfield) {
2554                         $sqlfield = 'more_subfields_xml';
2555                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2556                     } else {
2557                         $sqlfield = 'metadata'; # From biblio_metadata
2558                         if ($marcfield < 10) {
2559                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2560                         } else {
2561                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2562                         }
2563                     }
2564                     $column = "ExtractValue($sqlfield, '$xpath')";
2565                 }
2566             } else {
2567                 $column = $field;
2568             }
2569
2570             if (ref $query eq 'ARRAY') {
2571                 if ($op eq '=') {
2572                     $op = 'IN';
2573                 } elsif ($op eq '!=') {
2574                     $op = 'NOT IN';
2575                 }
2576                 $where_fragment = {
2577                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
2578                     args => $query,
2579                 };
2580             } else {
2581                 $where_fragment = {
2582                     str => "$column $op ?",
2583                     args => [ $query ],
2584                 };
2585             }
2586         }
2587     }
2588
2589     return $where_fragment;
2590 }
2591
2592 =head2 SearchItems
2593
2594     my ($items, $total) = SearchItems($filter, $params);
2595
2596 Perform a search among items
2597
2598 $filter is a reference to a hash which can be a filter, or a combination of filters.
2599
2600 A filter has the following keys:
2601
2602 =over 2
2603
2604 =item * field: the name of a SQL column in table items
2605
2606 =item * query: the value to search in this column
2607
2608 =item * operator: comparison operator. Can be one of = != > < >= <= like
2609
2610 =back
2611
2612 A combination of filters hash the following keys:
2613
2614 =over 2
2615
2616 =item * conjunction: 'AND' or 'OR'
2617
2618 =item * filters: array ref of filters
2619
2620 =back
2621
2622 $params is a reference to a hash that can contain the following parameters:
2623
2624 =over 2
2625
2626 =item * rows: Number of items to return. 0 returns everything (default: 0)
2627
2628 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2629                (default: 1)
2630
2631 =item * sortby: A SQL column name in items table to sort on
2632
2633 =item * sortorder: 'ASC' or 'DESC'
2634
2635 =back
2636
2637 =cut
2638
2639 sub SearchItems {
2640     my ($filter, $params) = @_;
2641
2642     $filter //= {};
2643     $params //= {};
2644     return unless ref $filter eq 'HASH';
2645     return unless ref $params eq 'HASH';
2646
2647     # Default parameters
2648     $params->{rows} ||= 0;
2649     $params->{page} ||= 1;
2650     $params->{sortby} ||= 'itemnumber';
2651     $params->{sortorder} ||= 'ASC';
2652
2653     my ($where_str, @where_args);
2654     my $where_fragment = _SearchItems_build_where_fragment($filter);
2655     if ($where_fragment) {
2656         $where_str = $where_fragment->{str};
2657         @where_args = @{ $where_fragment->{args} };
2658     }
2659
2660     my $dbh = C4::Context->dbh;
2661     my $query = q{
2662         SELECT SQL_CALC_FOUND_ROWS items.*
2663         FROM items
2664           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2665           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2666           LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2667           WHERE 1
2668     };
2669     if (defined $where_str and $where_str ne '') {
2670         $query .= qq{ AND $where_str };
2671     }
2672
2673     $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? };
2674     push @where_args, C4::Context->preference('marcflavour');
2675
2676     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2677     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2678     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2679     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2680         ? $params->{sortby} : 'itemnumber';
2681     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2682     $query .= qq{ ORDER BY $sortby $sortorder };
2683
2684     my $rows = $params->{rows};
2685     my @limit_args;
2686     if ($rows > 0) {
2687         my $offset = $rows * ($params->{page}-1);
2688         $query .= qq { LIMIT ?, ? };
2689         push @limit_args, $offset, $rows;
2690     }
2691
2692     my $sth = $dbh->prepare($query);
2693     my $rv = $sth->execute(@where_args, @limit_args);
2694
2695     return unless ($rv);
2696     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2697
2698     return ($sth->fetchall_arrayref({}), $total_rows);
2699 }
2700
2701
2702 =head1  OTHER FUNCTIONS
2703
2704 =head2 _find_value
2705
2706   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2707
2708 Find the given $subfield in the given $tag in the given
2709 MARC::Record $record.  If the subfield is found, returns
2710 the (indicators, value) pair; otherwise, (undef, undef) is
2711 returned.
2712
2713 PROPOSITION :
2714 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2715 I suggest we export it from this module.
2716
2717 =cut
2718
2719 sub _find_value {
2720     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2721     my @result;
2722     my $indicator;
2723     if ( $tagfield < 10 ) {
2724         if ( $record->field($tagfield) ) {
2725             push @result, $record->field($tagfield)->data();
2726         } else {
2727             push @result, "";
2728         }
2729     } else {
2730         foreach my $field ( $record->field($tagfield) ) {
2731             my @subfields = $field->subfields();
2732             foreach my $subfield (@subfields) {
2733                 if ( @$subfield[0] eq $insubfield ) {
2734                     push @result, @$subfield[1];
2735                     $indicator = $field->indicator(1) . $field->indicator(2);
2736                 }
2737             }
2738         }
2739     }
2740     return ( $indicator, @result );
2741 }
2742
2743
2744 =head2 PrepareItemrecordDisplay
2745
2746   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2747
2748 Returns a hash with all the fields for Display a given item data in a template
2749
2750 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2751
2752 =cut
2753
2754 sub PrepareItemrecordDisplay {
2755
2756     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2757
2758     my $dbh = C4::Context->dbh;
2759     $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2760     my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2761
2762     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2763     # a shared data structure. No plugin (including custom ones) should change
2764     # its contents. See also GetMarcStructure.
2765     my $tagslib = &GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2766
2767     # return nothing if we don't have found an existing framework.
2768     return q{} unless $tagslib;
2769     my $itemrecord;
2770     if ($itemnum) {
2771         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2772     }
2773     my @loop_data;
2774
2775     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2776     my $query = qq{
2777         SELECT authorised_value,lib FROM authorised_values
2778     };
2779     $query .= qq{
2780         LEFT JOIN authorised_values_branches ON ( id = av_id )
2781     } if $branch_limit;
2782     $query .= qq{
2783         WHERE category = ?
2784     };
2785     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2786     $query .= qq{ ORDER BY lib};
2787     my $authorised_values_sth = $dbh->prepare( $query );
2788     foreach my $tag ( sort keys %{$tagslib} ) {
2789         if ( $tag ne '' ) {
2790
2791             # loop through each subfield
2792             my $cntsubf;
2793             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2794                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2795                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2796                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2797                 my %subfield_data;
2798                 $subfield_data{tag}           = $tag;
2799                 $subfield_data{subfield}      = $subfield;
2800                 $subfield_data{countsubfield} = $cntsubf++;
2801                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2802                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2803
2804                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2805                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2806                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2807                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2808                 $subfield_data{hidden}     = "display:none"
2809                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2810                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2811                 my ( $x, $defaultvalue );
2812                 if ($itemrecord) {
2813                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2814                 }
2815                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2816                 if ( !defined $defaultvalue ) {
2817                     $defaultvalue = q||;
2818                 } else {
2819                     $defaultvalue =~ s/"/&quot;/g;
2820                 }
2821
2822                 # search for itemcallnumber if applicable
2823                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2824                     && C4::Context->preference('itemcallnumber') ) {
2825                     my $CNtag      = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2826                     my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2827                     if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2828                         $defaultvalue = $field->subfield($CNsubfield);
2829                     }
2830                 }
2831                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2832                     && $defaultvalues
2833                     && $defaultvalues->{'callnumber'} ) {
2834                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2835                         # if the item record exists, only use default value if the item has no callnumber
2836                         $defaultvalue = $defaultvalues->{callnumber};
2837                     } elsif ( !$itemrecord and $defaultvalues ) {
2838                         # if the item record *doesn't* exists, always use the default value
2839                         $defaultvalue = $defaultvalues->{callnumber};
2840                     }
2841                 }
2842                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2843                     && $defaultvalues
2844                     && $defaultvalues->{'branchcode'} ) {
2845                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2846                         $defaultvalue = $defaultvalues->{branchcode};
2847                     }
2848                 }
2849                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2850                     && $defaultvalues
2851                     && $defaultvalues->{'location'} ) {
2852
2853                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2854                         # if the item record exists, only use default value if the item has no locationr
2855                         $defaultvalue = $defaultvalues->{location};
2856                     } elsif ( !$itemrecord and $defaultvalues ) {
2857                         # if the item record *doesn't* exists, always use the default value
2858                         $defaultvalue = $defaultvalues->{location};
2859                     }
2860                 }
2861                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2862                     my @authorised_values;
2863                     my %authorised_lib;
2864
2865                     # builds list, depending on authorised value...
2866                     #---- branch
2867                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2868                         if (   ( C4::Context->preference("IndependentBranches") )
2869                             && !C4::Context->IsSuperLibrarian() ) {
2870                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2871                             $sth->execute( C4::Context->userenv->{branch} );
2872                             push @authorised_values, ""
2873                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2874                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2875                                 push @authorised_values, $branchcode;
2876                                 $authorised_lib{$branchcode} = $branchname;
2877                             }
2878                         } else {
2879                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2880                             $sth->execute;
2881                             push @authorised_values, ""
2882                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2883                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2884                                 push @authorised_values, $branchcode;
2885                                 $authorised_lib{$branchcode} = $branchname;
2886                             }
2887                         }
2888
2889                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2890                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2891                             $defaultvalue = $defaultvalues->{branchcode};
2892                         }
2893
2894                         #----- itemtypes
2895                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2896                         my $itemtypes = Koha::ItemTypes->search_with_localization;
2897                         push @authorised_values, ""
2898                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2899                         while ( my $itemtype = $itemtypes->next ) {
2900                             push @authorised_values, $itemtype->itemtype;
2901                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2902                         }
2903                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2904                             $defaultvalue = $defaultvalues->{'itemtype'};
2905                         }
2906
2907                         #---- class_sources
2908                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2909                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2910
2911                         my $class_sources = GetClassSources();
2912                         my $default_source = C4::Context->preference("DefaultClassificationSource");
2913
2914                         foreach my $class_source (sort keys %$class_sources) {
2915                             next unless $class_sources->{$class_source}->{'used'} or
2916                                         ($class_source eq $default_source);
2917                             push @authorised_values, $class_source;
2918                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2919                         }
2920
2921                         $defaultvalue = $default_source;
2922
2923                         #---- "true" authorised value
2924                     } else {
2925                         $authorised_values_sth->execute(
2926                             $tagslib->{$tag}->{$subfield}->{authorised_value},
2927                             $branch_limit ? $branch_limit : ()
2928                         );
2929                         push @authorised_values, ""
2930                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2931                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2932                             push @authorised_values, $value;
2933                             $authorised_lib{$value} = $lib;
2934                         }
2935                     }
2936                     $subfield_data{marc_value} = {
2937                         type    => 'select',
2938                         values  => \@authorised_values,
2939                         default => "$defaultvalue",
2940                         labels  => \%authorised_lib,
2941                     };
2942                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2943                 # it is a plugin
2944                     require Koha::FrameworkPlugin;
2945                     my $plugin = Koha::FrameworkPlugin->new({
2946                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
2947                         item_style => 1,
2948                     });
2949                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2950                     $plugin->build( $pars );
2951                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2952                         $defaultvalue = $field->subfield($subfield);
2953                     }
2954                     if( !$plugin->errstr ) {
2955                         #TODO Move html to template; see report 12176/13397
2956                         my $tab= $plugin->noclick? '-1': '';
2957                         my $class= $plugin->noclick? ' disabled': '';
2958                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
2959                         $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
2960                     } else {
2961                         warn $plugin->errstr;
2962                         $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />); # supply default input form
2963                     }
2964                 }
2965                 elsif ( $tag eq '' ) {       # it's an hidden field
2966                     $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
2967                 }
2968                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
2969                     $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
2970                 }
2971                 elsif ( length($defaultvalue) > 100
2972                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2973                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
2974                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
2975                                   500 <= $tag && $tag < 600                     )
2976                           ) {
2977                     # oversize field (textarea)
2978                     $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255">$defaultvalue</textarea>\n");
2979                 } else {
2980                     $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
2981                 }
2982                 push( @loop_data, \%subfield_data );
2983             }
2984         }
2985     }
2986     my $itemnumber;
2987     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2988         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2989     }
2990     return {
2991         'itemtagfield'    => $itemtagfield,
2992         'itemtagsubfield' => $itemtagsubfield,
2993         'itemnumber'      => $itemnumber,
2994         'iteminformation' => \@loop_data
2995     };
2996 }
2997
2998 sub ToggleNewStatus {
2999     my ( $params ) = @_;
3000     my @rules = @{ $params->{rules} };
3001     my $report_only = $params->{report_only};
3002
3003     my $dbh = C4::Context->dbh;
3004     my @errors;
3005     my @item_columns = map { "items.$_" } Koha::Items->columns;
3006     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
3007     my $report;
3008     for my $rule ( @rules ) {
3009         my $age = $rule->{age};
3010         my $conditions = $rule->{conditions};
3011         my $substitutions = $rule->{substitutions};
3012         my @params;
3013
3014         my $query = q|
3015             SELECT items.biblionumber, items.itemnumber
3016             FROM items
3017             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
3018             WHERE 1
3019         |;
3020         for my $condition ( @$conditions ) {
3021             if (
3022                  grep {/^$condition->{field}$/} @item_columns
3023               or grep {/^$condition->{field}$/} @biblioitem_columns
3024             ) {
3025                 if ( $condition->{value} =~ /\|/ ) {
3026                     my @values = split /\|/, $condition->{value};
3027                     $query .= qq| AND $condition->{field} IN (|
3028                         . join( ',', ('?') x scalar @values )
3029                         . q|)|;
3030                     push @params, @values;
3031                 } else {
3032                     $query .= qq| AND $condition->{field} = ?|;
3033                     push @params, $condition->{value};
3034                 }
3035             }
3036         }
3037         if ( defined $age ) {
3038             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
3039             push @params, $age;
3040         }
3041         my $sth = $dbh->prepare($query);
3042         $sth->execute( @params );
3043         while ( my $values = $sth->fetchrow_hashref ) {
3044             my $biblionumber = $values->{biblionumber};
3045             my $itemnumber = $values->{itemnumber};
3046             my $item = C4::Items::GetItem( $itemnumber );
3047             for my $substitution ( @$substitutions ) {
3048                 next unless $substitution->{field};
3049                 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
3050                     unless $report_only;
3051                 push @{ $report->{$itemnumber} }, $substitution;
3052             }
3053         }
3054     }
3055
3056     return $report;
3057 }
3058
3059
3060 1;