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