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