Bug 17411: Remove 3 other occurrences of exit 1
[koha-equinox.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.marcxml> are populated, but it
339 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         }
873         else {
874
875             #No authvalue list
876             # build default
877         }
878     }
879
880     #No authvalue list
881     #build default
882     $itemstatus{"1"} = "Not For Loan";
883     return \%itemstatus;
884 }
885
886 =head2 GetItemLocation
887
888   $itemlochash = GetItemLocation($fwk);
889
890 Returns a list of valid values for the
891 C<items.location> field.
892
893 NOTE: does B<not> return an individual item's
894 location.
895
896 where fwk stands for an optional framework code.
897 Create a location selector with the following code
898
899 =head3 in PERL SCRIPT
900
901   my $itemlochash = getitemlocation;
902   my @itemlocloop;
903   foreach my $thisloc (keys %$itemlochash) {
904       my $selected = 1 if $thisbranch eq $branch;
905       my %row =(locval => $thisloc,
906                   selected => $selected,
907                   locname => $itemlochash->{$thisloc},
908                );
909       push @itemlocloop, \%row;
910   }
911   $template->param(itemlocationloop => \@itemlocloop);
912
913 =head3 in TEMPLATE
914
915   <select name="location">
916       <option value="">Default</option>
917   <!-- TMPL_LOOP name="itemlocationloop" -->
918       <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
919   <!-- /TMPL_LOOP -->
920   </select>
921
922 =cut
923
924 sub GetItemLocation {
925
926     # returns a reference to a hash of references to location...
927     my ($fwk) = @_;
928     my %itemlocation;
929     my $dbh = C4::Context->dbh;
930     my $sth;
931     $fwk = '' unless ($fwk);
932     my ( $tag, $subfield ) =
933       GetMarcFromKohaField( "items.location", $fwk );
934     if ( $tag and $subfield ) {
935         my $sth =
936           $dbh->prepare(
937             "SELECT authorised_value
938             FROM marc_subfield_structure 
939             WHERE tagfield=? 
940                 AND tagsubfield=? 
941                 AND frameworkcode=?"
942           );
943         $sth->execute( $tag, $subfield, $fwk );
944         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
945             my $authvalsth =
946               $dbh->prepare(
947                 "SELECT authorised_value,lib
948                 FROM authorised_values
949                 WHERE category=?
950                 ORDER BY lib"
951               );
952             $authvalsth->execute($authorisedvaluecat);
953             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
954                 $itemlocation{$authorisedvalue} = $lib;
955             }
956             return \%itemlocation;
957         }
958         else {
959
960             #No authvalue list
961             # build default
962         }
963     }
964
965     #No authvalue list
966     #build default
967     $itemlocation{"1"} = "Not For Loan";
968     return \%itemlocation;
969 }
970
971 =head2 GetLostItems
972
973   $items = GetLostItems( $where );
974
975 This function gets a list of lost items.
976
977 =over 2
978
979 =item input:
980
981 C<$where> is a hashref. it containts a field of the items table as key
982 and the value to match as value. For example:
983
984 { barcode    => 'abc123',
985   homebranch => 'CPL',    }
986
987 =item return:
988
989 C<$items> is a reference to an array full of hashrefs with columns
990 from the "items" table as keys.
991
992 =item usage in the perl script:
993
994   my $where = { barcode => '0001548' };
995   my $items = GetLostItems( $where );
996   $template->param( itemsloop => $items );
997
998 =back
999
1000 =cut
1001
1002 sub GetLostItems {
1003     # Getting input args.
1004     my $where   = shift;
1005     my $dbh     = C4::Context->dbh;
1006
1007     my $query   = "
1008         SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
1009                itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
1010         FROM   items
1011             LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
1012             LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
1013             LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
1014         WHERE
1015                 authorised_values.category = 'LOST'
1016                 AND itemlost IS NOT NULL
1017                 AND itemlost <> 0
1018     ";
1019     my @query_parameters;
1020     foreach my $key (keys %$where) {
1021         $query .= " AND $key LIKE ?";
1022         push @query_parameters, "%$where->{$key}%";
1023     }
1024
1025     my $sth = $dbh->prepare($query);
1026     $sth->execute( @query_parameters );
1027     my $items = [];
1028     while ( my $row = $sth->fetchrow_hashref ){
1029         push @$items, $row;
1030     }
1031     return $items;
1032 }
1033
1034 =head2 GetItemsForInventory
1035
1036 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
1037   minlocation  => $minlocation,
1038   maxlocation  => $maxlocation,
1039   location     => $location,
1040   itemtype     => $itemtype,
1041   ignoreissued => $ignoreissued,
1042   datelastseen => $datelastseen,
1043   branchcode   => $branchcode,
1044   branch       => $branch,
1045   offset       => $offset,
1046   size         => $size,
1047   statushash   => $statushash,
1048   interface    => $interface,
1049 } );
1050
1051 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
1052
1053 The sub returns a reference to a list of hashes, each containing
1054 itemnumber, author, title, barcode, item callnumber, and date last
1055 seen. It is ordered by callnumber then title.
1056
1057 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
1058 the datelastseen can be used to specify that you want to see items not seen since a past date only.
1059 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
1060 $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.
1061
1062 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
1063
1064 =cut
1065
1066 sub GetItemsForInventory {
1067     my ( $parameters ) = @_;
1068     my $minlocation  = $parameters->{'minlocation'}  // '';
1069     my $maxlocation  = $parameters->{'maxlocation'}  // '';
1070     my $location     = $parameters->{'location'}     // '';
1071     my $itemtype     = $parameters->{'itemtype'}     // '';
1072     my $ignoreissued = $parameters->{'ignoreissued'} // '';
1073     my $datelastseen = $parameters->{'datelastseen'} // '';
1074     my $branchcode   = $parameters->{'branchcode'}   // '';
1075     my $branch       = $parameters->{'branch'}       // '';
1076     my $offset       = $parameters->{'offset'}       // '';
1077     my $size         = $parameters->{'size'}         // '';
1078     my $statushash   = $parameters->{'statushash'}   // '';
1079     my $interface    = $parameters->{'interface'}    // '';
1080
1081     my $dbh = C4::Context->dbh;
1082     my ( @bind_params, @where_strings );
1083
1084     my $select_columns = q{
1085         SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
1086     };
1087     my $select_count = q{SELECT COUNT(*)};
1088     my $query = q{
1089         FROM items
1090         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
1091         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
1092     };
1093     if ($statushash){
1094         for my $authvfield (keys %$statushash){
1095             if ( scalar @{$statushash->{$authvfield}} > 0 ){
1096                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
1097                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
1098             }
1099         }
1100     }
1101
1102     if ($minlocation) {
1103         push @where_strings, 'itemcallnumber >= ?';
1104         push @bind_params, $minlocation;
1105     }
1106
1107     if ($maxlocation) {
1108         push @where_strings, 'itemcallnumber <= ?';
1109         push @bind_params, $maxlocation;
1110     }
1111
1112     if ($datelastseen) {
1113         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
1114         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
1115         push @bind_params, $datelastseen;
1116     }
1117
1118     if ( $location ) {
1119         push @where_strings, 'items.location = ?';
1120         push @bind_params, $location;
1121     }
1122
1123     if ( $branchcode ) {
1124         if($branch eq "homebranch"){
1125         push @where_strings, 'items.homebranch = ?';
1126         }else{
1127             push @where_strings, 'items.holdingbranch = ?';
1128         }
1129         push @bind_params, $branchcode;
1130     }
1131
1132     if ( $itemtype ) {
1133         push @where_strings, 'biblioitems.itemtype = ?';
1134         push @bind_params, $itemtype;
1135     }
1136
1137     if ( $ignoreissued) {
1138         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1139         push @where_strings, 'issues.date_due IS NULL';
1140     }
1141
1142     if ( @where_strings ) {
1143         $query .= 'WHERE ';
1144         $query .= join ' AND ', @where_strings;
1145     }
1146     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1147     my $count_query = $select_count . $query;
1148     $query .= " LIMIT $offset, $size" if ($offset and $size);
1149     $query = $select_columns . $query;
1150     my $sth = $dbh->prepare($query);
1151     $sth->execute( @bind_params );
1152
1153     my @results = ();
1154     my $tmpresults = $sth->fetchall_arrayref({});
1155     $sth = $dbh->prepare( $count_query );
1156     $sth->execute( @bind_params );
1157     my ($iTotalRecords) = $sth->fetchrow_array();
1158
1159     my $avmapping = C4::Koha::GetKohaAuthorisedValuesMapping( {
1160                       interface => $interface
1161                     } );
1162     foreach my $row (@$tmpresults) {
1163
1164         # Auth values
1165         foreach (keys %$row) {
1166             if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
1167                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
1168             }
1169         }
1170         push @results, $row;
1171     }
1172
1173     return (\@results, $iTotalRecords);
1174 }
1175
1176 =head2 GetItemsCount
1177
1178   $count = &GetItemsCount( $biblionumber);
1179
1180 This function return count of item with $biblionumber
1181
1182 =cut
1183
1184 sub GetItemsCount {
1185     my ( $biblionumber ) = @_;
1186     my $dbh = C4::Context->dbh;
1187     my $query = "SELECT count(*)
1188           FROM  items 
1189           WHERE biblionumber=?";
1190     my $sth = $dbh->prepare($query);
1191     $sth->execute($biblionumber);
1192     my $count = $sth->fetchrow;  
1193     return ($count);
1194 }
1195
1196 =head2 GetItemInfosOf
1197
1198   GetItemInfosOf(@itemnumbers);
1199
1200 =cut
1201
1202 sub GetItemInfosOf {
1203     my @itemnumbers = @_;
1204
1205     my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
1206
1207     my $query = "
1208         SELECT *
1209         FROM items
1210         WHERE itemnumber IN ($itemnumber_values)
1211     ";
1212     return get_infos_of( $query, 'itemnumber' );
1213 }
1214
1215 =head2 GetItemsByBiblioitemnumber
1216
1217   GetItemsByBiblioitemnumber($biblioitemnumber);
1218
1219 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1220 Called by C<C4::XISBN>
1221
1222 =cut
1223
1224 sub GetItemsByBiblioitemnumber {
1225     my ( $bibitem ) = @_;
1226     my $dbh = C4::Context->dbh;
1227     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1228     # Get all items attached to a biblioitem
1229     my $i = 0;
1230     my @results; 
1231     $sth->execute($bibitem) || die $sth->errstr;
1232     while ( my $data = $sth->fetchrow_hashref ) {  
1233         # Foreach item, get circulation information
1234         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1235                                    WHERE itemnumber = ?
1236                                    AND issues.borrowernumber = borrowers.borrowernumber"
1237         );
1238         $sth2->execute( $data->{'itemnumber'} );
1239         if ( my $data2 = $sth2->fetchrow_hashref ) {
1240             # if item is out, set the due date and who it is out too
1241             $data->{'date_due'}   = $data2->{'date_due'};
1242             $data->{'cardnumber'} = $data2->{'cardnumber'};
1243             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1244         }
1245         else {
1246             # set date_due to blank, so in the template we check itemlost, and withdrawn
1247             $data->{'date_due'} = '';                                                                                                         
1248         }    # else         
1249         # Find the last 3 people who borrowed this item.                  
1250         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1251                       AND old_issues.borrowernumber = borrowers.borrowernumber
1252                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1253         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1254         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1255         my $i2 = 0;
1256         while ( my $data2 = $sth2->fetchrow_hashref ) {
1257             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1258             $data->{"card$i2"}      = $data2->{'cardnumber'};
1259             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1260             $i2++;
1261         }
1262         push(@results,$data);
1263     } 
1264     return (\@results); 
1265 }
1266
1267 =head2 GetItemsInfo
1268
1269   @results = GetItemsInfo($biblionumber);
1270
1271 Returns information about items with the given biblionumber.
1272
1273 C<GetItemsInfo> returns a list of references-to-hash. Each element
1274 contains a number of keys. Most of them are attributes from the
1275 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1276 Koha database. Other keys include:
1277
1278 =over 2
1279
1280 =item C<$data-E<gt>{branchname}>
1281
1282 The name (not the code) of the branch to which the book belongs.
1283
1284 =item C<$data-E<gt>{datelastseen}>
1285
1286 This is simply C<items.datelastseen>, except that while the date is
1287 stored in YYYY-MM-DD format in the database, here it is converted to
1288 DD/MM/YYYY format. A NULL date is returned as C<//>.
1289
1290 =item C<$data-E<gt>{datedue}>
1291
1292 =item C<$data-E<gt>{class}>
1293
1294 This is the concatenation of C<biblioitems.classification>, the book's
1295 Dewey code, and C<biblioitems.subclass>.
1296
1297 =item C<$data-E<gt>{ocount}>
1298
1299 I think this is the number of copies of the book available.
1300
1301 =item C<$data-E<gt>{order}>
1302
1303 If this is set, it is set to C<One Order>.
1304
1305 =back
1306
1307 =cut
1308
1309 sub GetItemsInfo {
1310     my ( $biblionumber ) = @_;
1311     my $dbh   = C4::Context->dbh;
1312     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1313     require C4::Languages;
1314     my $language = C4::Languages::getlanguage();
1315     my $query = "
1316     SELECT items.*,
1317            biblio.*,
1318            biblioitems.volume,
1319            biblioitems.number,
1320            biblioitems.itemtype,
1321            biblioitems.isbn,
1322            biblioitems.issn,
1323            biblioitems.publicationyear,
1324            biblioitems.publishercode,
1325            biblioitems.volumedate,
1326            biblioitems.volumedesc,
1327            biblioitems.lccn,
1328            biblioitems.url,
1329            items.notforloan as itemnotforloan,
1330            issues.borrowernumber,
1331            issues.date_due as datedue,
1332            issues.onsite_checkout,
1333            borrowers.cardnumber,
1334            borrowers.surname,
1335            borrowers.firstname,
1336            borrowers.branchcode as bcode,
1337            serial.serialseq,
1338            serial.publisheddate,
1339            itemtypes.description,
1340            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1341            itemtypes.notforloan as notforloan_per_itemtype,
1342            holding.branchurl,
1343            holding.branchcode,
1344            holding.branchname,
1345            holding.opac_info as holding_branch_opac_info,
1346            home.opac_info as home_branch_opac_info
1347     ";
1348     $query .= "
1349      FROM items
1350      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1351      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1352      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
1353      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1354      LEFT JOIN issues USING (itemnumber)
1355      LEFT JOIN borrowers USING (borrowernumber)
1356      LEFT JOIN serialitems USING (itemnumber)
1357      LEFT JOIN serial USING (serialid)
1358      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
1359      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1360     $query .= q|
1361     LEFT JOIN localization ON itemtypes.itemtype = localization.code
1362         AND localization.entity = 'itemtypes'
1363         AND localization.lang = ?
1364     |;
1365
1366     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1367     my $sth = $dbh->prepare($query);
1368     $sth->execute($language, $biblionumber);
1369     my $i = 0;
1370     my @results;
1371     my $serial;
1372
1373     my $userenv = C4::Context->userenv;
1374     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
1375     while ( my $data = $sth->fetchrow_hashref ) {
1376         if ( $data->{borrowernumber} && $want_not_same_branch) {
1377             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
1378         }
1379
1380         $serial ||= $data->{'serial'};
1381
1382         # get notforloan complete status if applicable
1383         if ( my $code = C4::Koha::GetAuthValCode( 'items.notforloan', $data->{frameworkcode} ) ) {
1384             my $av = Koha::AuthorisedValues->search({ category => $code, authorised_value => $data->{itemnotforloan} });
1385             $av = $av->count ? $av->next : undef;
1386             $data->{notforloanvalue}     = $av ? $av->lib : '';
1387             $data->{notforloanvalueopac} = $av ? $av->opac_description : '';
1388         }
1389
1390         # get restricted status and description if applicable
1391         if ( my $code = C4::Koha::GetAuthValCode( 'items.restricted', $data->{frameworkcode} ) ) {
1392             my $av = Koha::AuthorisedValues->search({ category => $code, authorised_value => $data->{restricted} });
1393             $av = $av->count ? $av->next : undef;
1394             $data->{restricted}     = $av ? $av->lib : '';
1395             $data->{restrictedopac} = $av ? $av->opac_description : '';
1396         }
1397
1398         # my stack procedures
1399         if ( my $code = C4::Koha::GetAuthValCode( 'items.stack', $data->{frameworkcode} ) ) {
1400             my $av = Koha::AuthorisedValues->search({ category => $code, authorised_value => $data->{stack} });
1401             $data->{stack}          = $av->count ? $av->next->lib : '';
1402         }
1403
1404         # Find the last 3 people who borrowed this item.
1405         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1406                                     WHERE itemnumber = ?
1407                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1408                                     ORDER BY returndate DESC
1409                                     LIMIT 3");
1410         $sth2->execute($data->{'itemnumber'});
1411         my $ii = 0;
1412         while (my $data2 = $sth2->fetchrow_hashref()) {
1413             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1414             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1415             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1416             $ii++;
1417         }
1418
1419         $results[$i] = $data;
1420         $i++;
1421     }
1422
1423     return $serial
1424         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1425         : @results;
1426 }
1427
1428 =head2 GetItemsLocationInfo
1429
1430   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1431
1432 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1433
1434 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1435
1436 =over 2
1437
1438 =item C<$data-E<gt>{homebranch}>
1439
1440 Branch Name of the item's homebranch
1441
1442 =item C<$data-E<gt>{holdingbranch}>
1443
1444 Branch Name of the item's holdingbranch
1445
1446 =item C<$data-E<gt>{location}>
1447
1448 Item's shelving location code
1449
1450 =item C<$data-E<gt>{location_intranet}>
1451
1452 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1453
1454 =item C<$data-E<gt>{location_opac}>
1455
1456 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
1457 description is set.
1458
1459 =item C<$data-E<gt>{itemcallnumber}>
1460
1461 Item's itemcallnumber
1462
1463 =item C<$data-E<gt>{cn_sort}>
1464
1465 Item's call number normalized for sorting
1466
1467 =back
1468   
1469 =cut
1470
1471 sub GetItemsLocationInfo {
1472         my $biblionumber = shift;
1473         my @results;
1474
1475         my $dbh = C4::Context->dbh;
1476         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
1477                             location, itemcallnumber, cn_sort
1478                      FROM items, branches as a, branches as b
1479                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
1480                      AND biblionumber = ?
1481                      ORDER BY cn_sort ASC";
1482         my $sth = $dbh->prepare($query);
1483         $sth->execute($biblionumber);
1484
1485         while ( my $data = $sth->fetchrow_hashref ) {
1486              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1487              $av = $av->count ? $av->next : undef;
1488              $data->{location_intranet} = $av ? $av->lib : '';
1489              $data->{location_opac}     = $av ? $av->opac_description : '';
1490              push @results, $data;
1491         }
1492         return @results;
1493 }
1494
1495 =head2 GetHostItemsInfo
1496
1497         $hostiteminfo = GetHostItemsInfo($hostfield);
1498         Returns the iteminfo for items linked to records via a host field
1499
1500 =cut
1501
1502 sub GetHostItemsInfo {
1503         my ($record) = @_;
1504         my @returnitemsInfo;
1505
1506         if (C4::Context->preference('marcflavour') eq 'MARC21' ||
1507         C4::Context->preference('marcflavour') eq 'NORMARC'){
1508             foreach my $hostfield ( $record->field('773') ) {
1509                 my $hostbiblionumber = $hostfield->subfield("0");
1510                 my $linkeditemnumber = $hostfield->subfield("9");
1511                 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1512                 foreach my $hostitemInfo (@hostitemInfos){
1513                         if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1514                                 push (@returnitemsInfo,$hostitemInfo);
1515                                 last;
1516                         }
1517                 }
1518             }
1519         } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
1520             foreach my $hostfield ( $record->field('461') ) {
1521                 my $hostbiblionumber = $hostfield->subfield("0");
1522                 my $linkeditemnumber = $hostfield->subfield("9");
1523                 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1524                 foreach my $hostitemInfo (@hostitemInfos){
1525                         if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1526                                 push (@returnitemsInfo,$hostitemInfo);
1527                                 last;
1528                         }
1529                 }
1530             }
1531         }
1532         return @returnitemsInfo;
1533 }
1534
1535
1536 =head2 GetLastAcquisitions
1537
1538   my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 
1539                                     'itemtypes' => ('BK','BD')}, 10);
1540
1541 =cut
1542
1543 sub  GetLastAcquisitions {
1544         my ($data,$max) = @_;
1545
1546         my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1547         
1548         my $number_of_branches = @{$data->{branches}};
1549         my $number_of_itemtypes   = @{$data->{itemtypes}};
1550         
1551         
1552         my @where = ('WHERE 1 '); 
1553         $number_of_branches and push @where
1554            , 'AND holdingbranch IN (' 
1555            , join(',', ('?') x $number_of_branches )
1556            , ')'
1557          ;
1558         
1559         $number_of_itemtypes and push @where
1560            , "AND $itemtype IN (" 
1561            , join(',', ('?') x $number_of_itemtypes )
1562            , ')'
1563          ;
1564
1565         my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1566                                  FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
1567                                     RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1568                                     @where
1569                                     GROUP BY biblio.biblionumber 
1570                                     ORDER BY dateaccessioned DESC LIMIT $max";
1571
1572         my $dbh = C4::Context->dbh;
1573         my $sth = $dbh->prepare($query);
1574     
1575     $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1576         
1577         my @results;
1578         while( my $row = $sth->fetchrow_hashref){
1579                 push @results, {date => $row->{dateaccessioned} 
1580                                                 , biblionumber => $row->{biblionumber}
1581                                                 , title => $row->{title}};
1582         }
1583         
1584         return @results;
1585 }
1586
1587 =head2 GetItemnumbersForBiblio
1588
1589   my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1590
1591 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1592
1593 =cut
1594
1595 sub GetItemnumbersForBiblio {
1596     my $biblionumber = shift;
1597     my @items;
1598     my $dbh = C4::Context->dbh;
1599     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1600     $sth->execute($biblionumber);
1601     while (my $result = $sth->fetchrow_hashref) {
1602         push @items, $result->{'itemnumber'};
1603     }
1604     return \@items;
1605 }
1606
1607 =head2 get_itemnumbers_of
1608
1609   my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1610
1611 Given a list of biblionumbers, return the list of corresponding itemnumbers
1612 for each biblionumber.
1613
1614 Return a reference on a hash where keys are biblionumbers and values are
1615 references on array of itemnumbers.
1616
1617 =cut
1618
1619 sub get_itemnumbers_of {
1620     my @biblionumbers = @_;
1621
1622     my $dbh = C4::Context->dbh;
1623
1624     my $query = '
1625         SELECT itemnumber,
1626             biblionumber
1627         FROM items
1628         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1629     ';
1630     my $sth = $dbh->prepare($query);
1631     $sth->execute(@biblionumbers);
1632
1633     my %itemnumbers_of;
1634
1635     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1636         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1637     }
1638
1639     return \%itemnumbers_of;
1640 }
1641
1642 =head2 get_hostitemnumbers_of
1643
1644   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1645
1646 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1647
1648 Return a reference on a hash where key is a biblionumber and values are
1649 references on array of itemnumbers.
1650
1651 =cut
1652
1653
1654 sub get_hostitemnumbers_of {
1655     my ($biblionumber) = @_;
1656     my $marcrecord = GetMarcBiblio($biblionumber);
1657
1658     return unless $marcrecord;
1659
1660     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1661
1662     my $marcflavor = C4::Context->preference('marcflavour');
1663     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1664         $tag      = '773';
1665         $biblio_s = '0';
1666         $item_s   = '9';
1667     }
1668     elsif ( $marcflavor eq 'UNIMARC' ) {
1669         $tag      = '461';
1670         $biblio_s = '0';
1671         $item_s   = '9';
1672     }
1673
1674     foreach my $hostfield ( $marcrecord->field($tag) ) {
1675         my $hostbiblionumber = $hostfield->subfield($biblio_s);
1676         my $linkeditemnumber = $hostfield->subfield($item_s);
1677         my @itemnumbers;
1678         if ( my $itemnumbers =
1679             get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber} )
1680         {
1681             @itemnumbers = @$itemnumbers;
1682         }
1683         foreach my $itemnumber (@itemnumbers) {
1684             if ( $itemnumber eq $linkeditemnumber ) {
1685                 push( @returnhostitemnumbers, $itemnumber );
1686                 last;
1687             }
1688         }
1689     }
1690
1691     return @returnhostitemnumbers;
1692 }
1693
1694
1695 =head2 GetItemnumberFromBarcode
1696
1697   $result = GetItemnumberFromBarcode($barcode);
1698
1699 =cut
1700
1701 sub GetItemnumberFromBarcode {
1702     my ($barcode) = @_;
1703     my $dbh = C4::Context->dbh;
1704
1705     my $rq =
1706       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1707     $rq->execute($barcode);
1708     my ($result) = $rq->fetchrow;
1709     return ($result);
1710 }
1711
1712 =head2 GetBarcodeFromItemnumber
1713
1714   $result = GetBarcodeFromItemnumber($itemnumber);
1715
1716 =cut
1717
1718 sub GetBarcodeFromItemnumber {
1719     my ($itemnumber) = @_;
1720     my $dbh = C4::Context->dbh;
1721
1722     my $rq =
1723       $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1724     $rq->execute($itemnumber);
1725     my ($result) = $rq->fetchrow;
1726     return ($result);
1727 }
1728
1729 =head2 GetHiddenItemnumbers
1730
1731     my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1732
1733 Given a list of items it checks which should be hidden from the OPAC given
1734 the current configuration. Returns a list of itemnumbers corresponding to
1735 those that should be hidden.
1736
1737 =cut
1738
1739 sub GetHiddenItemnumbers {
1740     my (@items) = @_;
1741     my @resultitems;
1742
1743     my $yaml = C4::Context->preference('OpacHiddenItems');
1744     return () if (! $yaml =~ /\S/ );
1745     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1746     my $hidingrules;
1747     eval {
1748         $hidingrules = YAML::Load($yaml);
1749     };
1750     if ($@) {
1751         warn "Unable to parse OpacHiddenItems syspref : $@";
1752         return ();
1753     }
1754     my $dbh = C4::Context->dbh;
1755
1756     # For each item
1757     foreach my $item (@items) {
1758
1759         # We check each rule
1760         foreach my $field (keys %$hidingrules) {
1761             my $val;
1762             if (exists $item->{$field}) {
1763                 $val = $item->{$field};
1764             }
1765             else {
1766                 my $query = "SELECT $field from items where itemnumber = ?";
1767                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1768             }
1769             $val = '' unless defined $val;
1770
1771             # If the results matches the values in the yaml file
1772             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1773
1774                 # We add the itemnumber to the list
1775                 push @resultitems, $item->{'itemnumber'};
1776
1777                 # If at least one rule matched for an item, no need to test the others
1778                 last;
1779             }
1780         }
1781     }
1782     return @resultitems;
1783 }
1784
1785 =head1 LIMITED USE FUNCTIONS
1786
1787 The following functions, while part of the public API,
1788 are not exported.  This is generally because they are
1789 meant to be used by only one script for a specific
1790 purpose, and should not be used in any other context
1791 without careful thought.
1792
1793 =cut
1794
1795 =head2 GetMarcItem
1796
1797   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1798
1799 Returns MARC::Record of the item passed in parameter.
1800 This function is meant for use only in C<cataloguing/additem.pl>,
1801 where it is needed to support that script's MARC-like
1802 editor.
1803
1804 =cut
1805
1806 sub GetMarcItem {
1807     my ( $biblionumber, $itemnumber ) = @_;
1808
1809     # GetMarcItem has been revised so that it does the following:
1810     #  1. Gets the item information from the items table.
1811     #  2. Converts it to a MARC field for storage in the bib record.
1812     #
1813     # The previous behavior was:
1814     #  1. Get the bib record.
1815     #  2. Return the MARC tag corresponding to the item record.
1816     #
1817     # The difference is that one treats the items row as authoritative,
1818     # while the other treats the MARC representation as authoritative
1819     # under certain circumstances.
1820
1821     my $itemrecord = GetItem($itemnumber);
1822
1823     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1824     # Also, don't emit a subfield if the underlying field is blank.
1825
1826     
1827     return Item2Marc($itemrecord,$biblionumber);
1828
1829 }
1830 sub Item2Marc {
1831         my ($itemrecord,$biblionumber)=@_;
1832     my $mungeditem = { 
1833         map {  
1834             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1835         } keys %{ $itemrecord } 
1836     };
1837     my $itemmarc = TransformKohaToMarc($mungeditem);
1838     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1839
1840     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1841     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1842                 foreach my $field ($itemmarc->field($itemtag)){
1843             $field->add_subfields(@$unlinked_item_subfields);
1844         }
1845     }
1846         return $itemmarc;
1847 }
1848
1849 =head1 PRIVATE FUNCTIONS AND VARIABLES
1850
1851 The following functions are not meant to be called
1852 directly, but are documented in order to explain
1853 the inner workings of C<C4::Items>.
1854
1855 =cut
1856
1857 =head2 %derived_columns
1858
1859 This hash keeps track of item columns that
1860 are strictly derived from other columns in
1861 the item record and are not meant to be set
1862 independently.
1863
1864 Each key in the hash should be the name of a
1865 column (as named by TransformMarcToKoha).  Each
1866 value should be hashref whose keys are the
1867 columns on which the derived column depends.  The
1868 hashref should also contain a 'BUILDER' key
1869 that is a reference to a sub that calculates
1870 the derived value.
1871
1872 =cut
1873
1874 my %derived_columns = (
1875     'items.cn_sort' => {
1876         'itemcallnumber' => 1,
1877         'items.cn_source' => 1,
1878         'BUILDER' => \&_calc_items_cn_sort,
1879     }
1880 );
1881
1882 =head2 _set_derived_columns_for_add 
1883
1884   _set_derived_column_for_add($item);
1885
1886 Given an item hash representing a new item to be added,
1887 calculate any derived columns.  Currently the only
1888 such column is C<items.cn_sort>.
1889
1890 =cut
1891
1892 sub _set_derived_columns_for_add {
1893     my $item = shift;
1894
1895     foreach my $column (keys %derived_columns) {
1896         my $builder = $derived_columns{$column}->{'BUILDER'};
1897         my $source_values = {};
1898         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1899             next if $source_column eq 'BUILDER';
1900             $source_values->{$source_column} = $item->{$source_column};
1901         }
1902         $builder->($item, $source_values);
1903     }
1904 }
1905
1906 =head2 _set_derived_columns_for_mod 
1907
1908   _set_derived_column_for_mod($item);
1909
1910 Given an item hash representing a new item to be modified.
1911 calculate any derived columns.  Currently the only
1912 such column is C<items.cn_sort>.
1913
1914 This routine differs from C<_set_derived_columns_for_add>
1915 in that it needs to handle partial item records.  In other
1916 words, the caller of C<ModItem> may have supplied only one
1917 or two columns to be changed, so this function needs to
1918 determine whether any of the columns to be changed affect
1919 any of the derived columns.  Also, if a derived column
1920 depends on more than one column, but the caller is not
1921 changing all of then, this routine retrieves the unchanged
1922 values from the database in order to ensure a correct
1923 calculation.
1924
1925 =cut
1926
1927 sub _set_derived_columns_for_mod {
1928     my $item = shift;
1929
1930     foreach my $column (keys %derived_columns) {
1931         my $builder = $derived_columns{$column}->{'BUILDER'};
1932         my $source_values = {};
1933         my %missing_sources = ();
1934         my $must_recalc = 0;
1935         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1936             next if $source_column eq 'BUILDER';
1937             if (exists $item->{$source_column}) {
1938                 $must_recalc = 1;
1939                 $source_values->{$source_column} = $item->{$source_column};
1940             } else {
1941                 $missing_sources{$source_column} = 1;
1942             }
1943         }
1944         if ($must_recalc) {
1945             foreach my $source_column (keys %missing_sources) {
1946                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1947             }
1948             $builder->($item, $source_values);
1949         }
1950     }
1951 }
1952
1953 =head2 _do_column_fixes_for_mod
1954
1955   _do_column_fixes_for_mod($item);
1956
1957 Given an item hashref containing one or more
1958 columns to modify, fix up certain values.
1959 Specifically, set to 0 any passed value
1960 of C<notforloan>, C<damaged>, C<itemlost>, or
1961 C<withdrawn> that is either undefined or
1962 contains the empty string.
1963
1964 =cut
1965
1966 sub _do_column_fixes_for_mod {
1967     my $item = shift;
1968
1969     if (exists $item->{'notforloan'} and
1970         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1971         $item->{'notforloan'} = 0;
1972     }
1973     if (exists $item->{'damaged'} and
1974         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1975         $item->{'damaged'} = 0;
1976     }
1977     if (exists $item->{'itemlost'} and
1978         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1979         $item->{'itemlost'} = 0;
1980     }
1981     if (exists $item->{'withdrawn'} and
1982         (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1983         $item->{'withdrawn'} = 0;
1984     }
1985     if (exists $item->{location}
1986         and $item->{location} ne 'CART'
1987         and $item->{location} ne 'PROC'
1988         and not $item->{permanent_location}
1989     ) {
1990         $item->{'permanent_location'} = $item->{'location'};
1991     }
1992     if (exists $item->{'timestamp'}) {
1993         delete $item->{'timestamp'};
1994     }
1995 }
1996
1997 =head2 _get_single_item_column
1998
1999   _get_single_item_column($column, $itemnumber);
2000
2001 Retrieves the value of a single column from an C<items>
2002 row specified by C<$itemnumber>.
2003
2004 =cut
2005
2006 sub _get_single_item_column {
2007     my $column = shift;
2008     my $itemnumber = shift;
2009     
2010     my $dbh = C4::Context->dbh;
2011     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
2012     $sth->execute($itemnumber);
2013     my ($value) = $sth->fetchrow();
2014     return $value; 
2015 }
2016
2017 =head2 _calc_items_cn_sort
2018
2019   _calc_items_cn_sort($item, $source_values);
2020
2021 Helper routine to calculate C<items.cn_sort>.
2022
2023 =cut
2024
2025 sub _calc_items_cn_sort {
2026     my $item = shift;
2027     my $source_values = shift;
2028
2029     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
2030 }
2031
2032 =head2 _set_defaults_for_add 
2033
2034   _set_defaults_for_add($item_hash);
2035
2036 Given an item hash representing an item to be added, set
2037 correct default values for columns whose default value
2038 is not handled by the DBMS.  This includes the following
2039 columns:
2040
2041 =over 2
2042
2043 =item * 
2044
2045 C<items.dateaccessioned>
2046
2047 =item *
2048
2049 C<items.notforloan>
2050
2051 =item *
2052
2053 C<items.damaged>
2054
2055 =item *
2056
2057 C<items.itemlost>
2058
2059 =item *
2060
2061 C<items.withdrawn>
2062
2063 =back
2064
2065 =cut
2066
2067 sub _set_defaults_for_add {
2068     my $item = shift;
2069     $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2070     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
2071 }
2072
2073 =head2 _koha_new_item
2074
2075   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
2076
2077 Perform the actual insert into the C<items> table.
2078
2079 =cut
2080
2081 sub _koha_new_item {
2082     my ( $item, $barcode ) = @_;
2083     my $dbh=C4::Context->dbh;  
2084     my $error;
2085     $item->{permanent_location} //= $item->{location};
2086     my $query =
2087            "INSERT INTO items SET
2088             biblionumber        = ?,
2089             biblioitemnumber    = ?,
2090             barcode             = ?,
2091             dateaccessioned     = ?,
2092             booksellerid        = ?,
2093             homebranch          = ?,
2094             price               = ?,
2095             replacementprice    = ?,
2096             replacementpricedate = ?,
2097             datelastborrowed    = ?,
2098             datelastseen        = ?,
2099             stack               = ?,
2100             notforloan          = ?,
2101             damaged             = ?,
2102             itemlost            = ?,
2103             withdrawn           = ?,
2104             itemcallnumber      = ?,
2105             coded_location_qualifier = ?,
2106             restricted          = ?,
2107             itemnotes           = ?,
2108             itemnotes_nonpublic = ?,
2109             holdingbranch       = ?,
2110             paidfor             = ?,
2111             location            = ?,
2112             permanent_location  = ?,
2113             onloan              = ?,
2114             issues              = ?,
2115             renewals            = ?,
2116             reserves            = ?,
2117             cn_source           = ?,
2118             cn_sort             = ?,
2119             ccode               = ?,
2120             itype               = ?,
2121             materials           = ?,
2122             uri                 = ?,
2123             enumchron           = ?,
2124             more_subfields_xml  = ?,
2125             copynumber          = ?,
2126             stocknumber         = ?,
2127             new_status          = ?
2128           ";
2129     my $sth = $dbh->prepare($query);
2130     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2131    $sth->execute(
2132             $item->{'biblionumber'},
2133             $item->{'biblioitemnumber'},
2134             $barcode,
2135             $item->{'dateaccessioned'},
2136             $item->{'booksellerid'},
2137             $item->{'homebranch'},
2138             $item->{'price'},
2139             $item->{'replacementprice'},
2140             $item->{'replacementpricedate'} || $today,
2141             $item->{datelastborrowed},
2142             $item->{datelastseen} || $today,
2143             $item->{stack},
2144             $item->{'notforloan'},
2145             $item->{'damaged'},
2146             $item->{'itemlost'},
2147             $item->{'withdrawn'},
2148             $item->{'itemcallnumber'},
2149             $item->{'coded_location_qualifier'},
2150             $item->{'restricted'},
2151             $item->{'itemnotes'},
2152             $item->{'itemnotes_nonpublic'},
2153             $item->{'holdingbranch'},
2154             $item->{'paidfor'},
2155             $item->{'location'},
2156             $item->{'permanent_location'},
2157             $item->{'onloan'},
2158             $item->{'issues'},
2159             $item->{'renewals'},
2160             $item->{'reserves'},
2161             $item->{'items.cn_source'},
2162             $item->{'items.cn_sort'},
2163             $item->{'ccode'},
2164             $item->{'itype'},
2165             $item->{'materials'},
2166             $item->{'uri'},
2167             $item->{'enumchron'},
2168             $item->{'more_subfields_xml'},
2169             $item->{'copynumber'},
2170             $item->{'stocknumber'},
2171             $item->{'new_status'},
2172     );
2173
2174     my $itemnumber;
2175     if ( defined $sth->errstr ) {
2176         $error.="ERROR in _koha_new_item $query".$sth->errstr;
2177     }
2178     else {
2179         $itemnumber = $dbh->{'mysql_insertid'};
2180     }
2181
2182     return ( $itemnumber, $error );
2183 }
2184
2185 =head2 MoveItemFromBiblio
2186
2187   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2188
2189 Moves an item from a biblio to another
2190
2191 Returns undef if the move failed or the biblionumber of the destination record otherwise
2192
2193 =cut
2194
2195 sub MoveItemFromBiblio {
2196     my ($itemnumber, $frombiblio, $tobiblio) = @_;
2197     my $dbh = C4::Context->dbh;
2198     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
2199         SELECT biblioitemnumber
2200         FROM biblioitems
2201         WHERE biblionumber = ?
2202     |, undef, $tobiblio );
2203     my $return = $dbh->do(q|
2204         UPDATE items
2205         SET biblioitemnumber = ?,
2206             biblionumber = ?
2207         WHERE itemnumber = ?
2208             AND biblionumber = ?
2209     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
2210     if ($return == 1) {
2211         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
2212         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
2213             # Checking if the item we want to move is in an order 
2214         require C4::Acquisition;
2215         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
2216             if ($order) {
2217                     # Replacing the biblionumber within the order if necessary
2218                     $order->{'biblionumber'} = $tobiblio;
2219                 C4::Acquisition::ModOrder($order);
2220             }
2221
2222         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
2223         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
2224             $dbh->do( qq|
2225                 UPDATE $table_name
2226                 SET biblionumber = ?
2227                 WHERE itemnumber = ?
2228             |, undef, $tobiblio, $itemnumber );
2229         }
2230         return $tobiblio;
2231         }
2232     return;
2233 }
2234
2235 =head2 ItemSafeToDelete
2236
2237    ItemSafeToDelete( $biblionumber, $itemnumber);
2238
2239 Exported function (core API) for checking whether an item record is safe to delete.
2240
2241 returns 1 if the item is safe to delete,
2242
2243 "book_on_loan" if the item is checked out,
2244
2245 "not_same_branch" if the item is blocked by independent branches,
2246
2247 "book_reserved" if the there are holds aganst the item, or
2248
2249 "linked_analytics" if the item has linked analytic records.
2250
2251 =cut
2252
2253 sub ItemSafeToDelete {
2254     my ( $biblionumber, $itemnumber ) = @_;
2255     my $status;
2256     my $dbh = C4::Context->dbh;
2257
2258     my $error;
2259
2260     my $countanalytics = GetAnalyticsCount($itemnumber);
2261
2262     # check that there is no issue on this item before deletion.
2263     my $sth = $dbh->prepare(
2264         q{
2265         SELECT COUNT(*) FROM issues
2266         WHERE itemnumber = ?
2267     }
2268     );
2269     $sth->execute($itemnumber);
2270     my ($onloan) = $sth->fetchrow;
2271
2272     my $item = GetItem($itemnumber);
2273
2274     if ($onloan) {
2275         $status = "book_on_loan";
2276     }
2277     elsif ( defined C4::Context->userenv
2278         and !C4::Context->IsSuperLibrarian()
2279         and C4::Context->preference("IndependentBranches")
2280         and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
2281     {
2282         $status = "not_same_branch";
2283     }
2284     else {
2285         # check it doesn't have a waiting reserve
2286         $sth = $dbh->prepare(
2287             q{
2288             SELECT COUNT(*) FROM reserves
2289             WHERE (found = 'W' OR found = 'T')
2290             AND itemnumber = ?
2291         }
2292         );
2293         $sth->execute($itemnumber);
2294         my ($reserve) = $sth->fetchrow;
2295         if ($reserve) {
2296             $status = "book_reserved";
2297         }
2298         elsif ( $countanalytics > 0 ) {
2299             $status = "linked_analytics";
2300         }
2301         else {
2302             $status = 1;
2303         }
2304     }
2305     return $status;
2306 }
2307
2308 =head2 DelItemCheck
2309
2310    DelItemCheck( $biblionumber, $itemnumber);
2311
2312 Exported function (core API) for deleting an item record in Koha if there no current issue.
2313
2314 DelItemCheck wraps ItemSafeToDelete around DelItem.
2315
2316 =cut
2317
2318 sub DelItemCheck {
2319     my ( $biblionumber, $itemnumber ) = @_;
2320     my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
2321
2322     if ( $status == 1 ) {
2323         DelItem(
2324             {
2325                 biblionumber => $biblionumber,
2326                 itemnumber   => $itemnumber
2327             }
2328         );
2329     }
2330     return $status;
2331 }
2332
2333 =head2 _koha_modify_item
2334
2335   my ($itemnumber,$error) =_koha_modify_item( $item );
2336
2337 Perform the actual update of the C<items> row.  Note that this
2338 routine accepts a hashref specifying the columns to update.
2339
2340 =cut
2341
2342 sub _koha_modify_item {
2343     my ( $item ) = @_;
2344     my $dbh=C4::Context->dbh;  
2345     my $error;
2346
2347     my $query = "UPDATE items SET ";
2348     my @bind;
2349     for my $key ( keys %$item ) {
2350         next if ( $key eq 'itemnumber' );
2351         $query.="$key=?,";
2352         push @bind, $item->{$key};
2353     }
2354     $query =~ s/,$//;
2355     $query .= " WHERE itemnumber=?";
2356     push @bind, $item->{'itemnumber'};
2357     my $sth = $dbh->prepare($query);
2358     $sth->execute(@bind);
2359     if ( $sth->err ) {
2360         $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2361         warn $error;
2362     }
2363     return ($item->{'itemnumber'},$error);
2364 }
2365
2366 =head2 _koha_delete_item
2367
2368   _koha_delete_item( $itemnum );
2369
2370 Internal function to delete an item record from the koha tables
2371
2372 =cut
2373
2374 sub _koha_delete_item {
2375     my ( $itemnum ) = @_;
2376
2377     my $dbh = C4::Context->dbh;
2378     # save the deleted item to deleteditems table
2379     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2380     $sth->execute($itemnum);
2381     my $data = $sth->fetchrow_hashref();
2382
2383     # There is no item to delete
2384     return 0 unless $data;
2385
2386     my $query = "INSERT INTO deleteditems SET ";
2387     my @bind  = ();
2388     foreach my $key ( keys %$data ) {
2389         next if ( $key eq 'timestamp' ); # timestamp will be set by db
2390         $query .= "$key = ?,";
2391         push( @bind, $data->{$key} );
2392     }
2393     $query =~ s/\,$//;
2394     $sth = $dbh->prepare($query);
2395     $sth->execute(@bind);
2396
2397     # delete from items table
2398     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2399     my $deleted = $sth->execute($itemnum);
2400     return ( $deleted == 1 ) ? 1 : 0;
2401 }
2402
2403 =head2 _marc_from_item_hash
2404
2405   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2406
2407 Given an item hash representing a complete item record,
2408 create a C<MARC::Record> object containing an embedded
2409 tag representing that item.
2410
2411 The third, optional parameter C<$unlinked_item_subfields> is
2412 an arrayref of subfields (not mapped to C<items> fields per the
2413 framework) to be added to the MARC representation
2414 of the item.
2415
2416 =cut
2417
2418 sub _marc_from_item_hash {
2419     my $item = shift;
2420     my $frameworkcode = shift;
2421     my $unlinked_item_subfields;
2422     if (@_) {
2423         $unlinked_item_subfields = shift;
2424     }
2425    
2426     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2427     # Also, don't emit a subfield if the underlying field is blank.
2428     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
2429                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
2430                                 : ()  } keys %{ $item } }; 
2431
2432     my $item_marc = MARC::Record->new();
2433     foreach my $item_field ( keys %{$mungeditem} ) {
2434         my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2435         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
2436         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2437         foreach my $value (@values){
2438             if ( my $field = $item_marc->field($tag) ) {
2439                     $field->add_subfields( $subfield => $value );
2440             } else {
2441                 my $add_subfields = [];
2442                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2443                     $add_subfields = $unlinked_item_subfields;
2444             }
2445             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2446             }
2447         }
2448     }
2449
2450     return $item_marc;
2451 }
2452
2453 =head2 _repack_item_errors
2454
2455 Add an error message hash generated by C<CheckItemPreSave>
2456 to a list of errors.
2457
2458 =cut
2459
2460 sub _repack_item_errors {
2461     my $item_sequence_num = shift;
2462     my $item_ref = shift;
2463     my $error_ref = shift;
2464
2465     my @repacked_errors = ();
2466
2467     foreach my $error_code (sort keys %{ $error_ref }) {
2468         my $repacked_error = {};
2469         $repacked_error->{'item_sequence'} = $item_sequence_num;
2470         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2471         $repacked_error->{'error_code'} = $error_code;
2472         $repacked_error->{'error_information'} = $error_ref->{$error_code};
2473         push @repacked_errors, $repacked_error;
2474     } 
2475
2476     return @repacked_errors;
2477 }
2478
2479 =head2 _get_unlinked_item_subfields
2480
2481   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2482
2483 =cut
2484
2485 sub _get_unlinked_item_subfields {
2486     my $original_item_marc = shift;
2487     my $frameworkcode = shift;
2488
2489     my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
2490
2491     # assume that this record has only one field, and that that
2492     # field contains only the item information
2493     my $subfields = [];
2494     my @fields = $original_item_marc->fields();
2495     if ($#fields > -1) {
2496         my $field = $fields[0];
2497             my $tag = $field->tag();
2498         foreach my $subfield ($field->subfields()) {
2499             if (defined $subfield->[1] and
2500                 $subfield->[1] ne '' and
2501                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2502                 push @$subfields, $subfield->[0] => $subfield->[1];
2503             }
2504         }
2505     }
2506     return $subfields;
2507 }
2508
2509 =head2 _get_unlinked_subfields_xml
2510
2511   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2512
2513 =cut
2514
2515 sub _get_unlinked_subfields_xml {
2516     my $unlinked_item_subfields = shift;
2517
2518     my $xml;
2519     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2520         my $marc = MARC::Record->new();
2521         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2522         # used in the framework
2523         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2524         $marc->encoding("UTF-8");    
2525         $xml = $marc->as_xml("USMARC");
2526     }
2527
2528     return $xml;
2529 }
2530
2531 =head2 _parse_unlinked_item_subfields_from_xml
2532
2533   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2534
2535 =cut
2536
2537 sub  _parse_unlinked_item_subfields_from_xml {
2538     my $xml = shift;
2539     require C4::Charset;
2540     return unless defined $xml and $xml ne "";
2541     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2542     my $unlinked_subfields = [];
2543     my @fields = $marc->fields();
2544     if ($#fields > -1) {
2545         foreach my $subfield ($fields[0]->subfields()) {
2546             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2547         }
2548     }
2549     return $unlinked_subfields;
2550 }
2551
2552 =head2 GetAnalyticsCount
2553
2554   $count= &GetAnalyticsCount($itemnumber)
2555
2556 counts Usage of itemnumber in Analytical bibliorecords. 
2557
2558 =cut
2559
2560 sub GetAnalyticsCount {
2561     my ($itemnumber) = @_;
2562
2563     ### ZOOM search here
2564     my $query;
2565     $query= "hi=".$itemnumber;
2566     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2567     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2568     return ($result);
2569 }
2570
2571 =head2 GetItemHolds
2572
2573   $holds = &GetItemHolds($biblionumber, $itemnumber);
2574
2575 This function return the count of holds with $biblionumber and $itemnumber
2576
2577 =cut
2578
2579 sub GetItemHolds {
2580     my ($biblionumber, $itemnumber) = @_;
2581     my $holds;
2582     my $dbh            = C4::Context->dbh;
2583     my $query          = "SELECT count(*)
2584         FROM  reserves
2585         WHERE biblionumber=? AND itemnumber=?";
2586     my $sth = $dbh->prepare($query);
2587     $sth->execute($biblionumber, $itemnumber);
2588     $holds = $sth->fetchrow;
2589     return $holds;
2590 }
2591
2592 =head2 SearchItemsByField
2593
2594     my $items = SearchItemsByField($field, $value);
2595
2596 SearchItemsByField will search for items on a specific given field.
2597 For instance you can search all items with a specific stocknumber like this:
2598
2599     my $items = SearchItemsByField('stocknumber', $stocknumber);
2600
2601 =cut
2602
2603 sub SearchItemsByField {
2604     my ($field, $value) = @_;
2605
2606     my $filters = {
2607         field => $field,
2608         query => $value,
2609     };
2610
2611     my ($results) = SearchItems($filters);
2612     return $results;
2613 }
2614
2615 sub _SearchItems_build_where_fragment {
2616     my ($filter) = @_;
2617
2618     my $dbh = C4::Context->dbh;
2619
2620     my $where_fragment;
2621     if (exists($filter->{conjunction})) {
2622         my (@where_strs, @where_args);
2623         foreach my $f (@{ $filter->{filters} }) {
2624             my $fragment = _SearchItems_build_where_fragment($f);
2625             if ($fragment) {
2626                 push @where_strs, $fragment->{str};
2627                 push @where_args, @{ $fragment->{args} };
2628             }
2629         }
2630         my $where_str = '';
2631         if (@where_strs) {
2632             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2633             $where_fragment = {
2634                 str => $where_str,
2635                 args => \@where_args,
2636             };
2637         }
2638     } else {
2639         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2640         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2641         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2642         my @operators = qw(= != > < >= <= like);
2643         my $field = $filter->{field};
2644         if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2645             my $op = $filter->{operator};
2646             my $query = $filter->{query};
2647
2648             if (!$op or (0 == grep /^$op$/, @operators)) {
2649                 $op = '='; # default operator
2650             }
2651
2652             my $column;
2653             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2654                 my $marcfield = $1;
2655                 my $marcsubfield = $2;
2656                 my ($kohafield) = $dbh->selectrow_array(q|
2657                     SELECT kohafield FROM marc_subfield_structure
2658                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2659                 |, undef, $marcfield, $marcsubfield);
2660
2661                 if ($kohafield) {
2662                     $column = $kohafield;
2663                 } else {
2664                     # MARC field is not linked to a DB field so we need to use
2665                     # ExtractValue on biblioitems.marcxml or
2666                     # items.more_subfields_xml, depending on the MARC field.
2667                     my $xpath;
2668                     my $sqlfield;
2669                     my ($itemfield) = GetMarcFromKohaField('items.itemnumber');
2670                     if ($marcfield eq $itemfield) {
2671                         $sqlfield = 'more_subfields_xml';
2672                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2673                     } else {
2674                         $sqlfield = 'marcxml';
2675                         if ($marcfield < 10) {
2676                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2677                         } else {
2678                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2679                         }
2680                     }
2681                     $column = "ExtractValue($sqlfield, '$xpath')";
2682                 }
2683             } else {
2684                 $column = $field;
2685             }
2686
2687             if (ref $query eq 'ARRAY') {
2688                 if ($op eq '=') {
2689                     $op = 'IN';
2690                 } elsif ($op eq '!=') {
2691                     $op = 'NOT IN';
2692                 }
2693                 $where_fragment = {
2694                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
2695                     args => $query,
2696                 };
2697             } else {
2698                 $where_fragment = {
2699                     str => "$column $op ?",
2700                     args => [ $query ],
2701                 };
2702             }
2703         }
2704     }
2705
2706     return $where_fragment;
2707 }
2708
2709 =head2 SearchItems
2710
2711     my ($items, $total) = SearchItems($filter, $params);
2712
2713 Perform a search among items
2714
2715 $filter is a reference to a hash which can be a filter, or a combination of filters.
2716
2717 A filter has the following keys:
2718
2719 =over 2
2720
2721 =item * field: the name of a SQL column in table items
2722
2723 =item * query: the value to search in this column
2724
2725 =item * operator: comparison operator. Can be one of = != > < >= <= like
2726
2727 =back
2728
2729 A combination of filters hash the following keys:
2730
2731 =over 2
2732
2733 =item * conjunction: 'AND' or 'OR'
2734
2735 =item * filters: array ref of filters
2736
2737 =back
2738
2739 $params is a reference to a hash that can contain the following parameters:
2740
2741 =over 2
2742
2743 =item * rows: Number of items to return. 0 returns everything (default: 0)
2744
2745 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2746                (default: 1)
2747
2748 =item * sortby: A SQL column name in items table to sort on
2749
2750 =item * sortorder: 'ASC' or 'DESC'
2751
2752 =back
2753
2754 =cut
2755
2756 sub SearchItems {
2757     my ($filter, $params) = @_;
2758
2759     $filter //= {};
2760     $params //= {};
2761     return unless ref $filter eq 'HASH';
2762     return unless ref $params eq 'HASH';
2763
2764     # Default parameters
2765     $params->{rows} ||= 0;
2766     $params->{page} ||= 1;
2767     $params->{sortby} ||= 'itemnumber';
2768     $params->{sortorder} ||= 'ASC';
2769
2770     my ($where_str, @where_args);
2771     my $where_fragment = _SearchItems_build_where_fragment($filter);
2772     if ($where_fragment) {
2773         $where_str = $where_fragment->{str};
2774         @where_args = @{ $where_fragment->{args} };
2775     }
2776
2777     my $dbh = C4::Context->dbh;
2778     my $query = q{
2779         SELECT SQL_CALC_FOUND_ROWS items.*
2780         FROM items
2781           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2782           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2783     };
2784     if (defined $where_str and $where_str ne '') {
2785         $query .= qq{ WHERE $where_str };
2786     }
2787
2788     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2789     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2790     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2791     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2792         ? $params->{sortby} : 'itemnumber';
2793     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2794     $query .= qq{ ORDER BY $sortby $sortorder };
2795
2796     my $rows = $params->{rows};
2797     my @limit_args;
2798     if ($rows > 0) {
2799         my $offset = $rows * ($params->{page}-1);
2800         $query .= qq { LIMIT ?, ? };
2801         push @limit_args, $offset, $rows;
2802     }
2803
2804     my $sth = $dbh->prepare($query);
2805     my $rv = $sth->execute(@where_args, @limit_args);
2806
2807     return unless ($rv);
2808     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2809
2810     return ($sth->fetchall_arrayref({}), $total_rows);
2811 }
2812
2813
2814 =head1  OTHER FUNCTIONS
2815
2816 =head2 _find_value
2817
2818   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2819
2820 Find the given $subfield in the given $tag in the given
2821 MARC::Record $record.  If the subfield is found, returns
2822 the (indicators, value) pair; otherwise, (undef, undef) is
2823 returned.
2824
2825 PROPOSITION :
2826 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2827 I suggest we export it from this module.
2828
2829 =cut
2830
2831 sub _find_value {
2832     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2833     my @result;
2834     my $indicator;
2835     if ( $tagfield < 10 ) {
2836         if ( $record->field($tagfield) ) {
2837             push @result, $record->field($tagfield)->data();
2838         } else {
2839             push @result, "";
2840         }
2841     } else {
2842         foreach my $field ( $record->field($tagfield) ) {
2843             my @subfields = $field->subfields();
2844             foreach my $subfield (@subfields) {
2845                 if ( @$subfield[0] eq $insubfield ) {
2846                     push @result, @$subfield[1];
2847                     $indicator = $field->indicator(1) . $field->indicator(2);
2848                 }
2849             }
2850         }
2851     }
2852     return ( $indicator, @result );
2853 }
2854
2855
2856 =head2 PrepareItemrecordDisplay
2857
2858   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2859
2860 Returns a hash with all the fields for Display a given item data in a template
2861
2862 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2863
2864 =cut
2865
2866 sub PrepareItemrecordDisplay {
2867
2868     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2869
2870     my $dbh = C4::Context->dbh;
2871     $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2872     my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2873
2874     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2875     # a shared data structure. No plugin (including custom ones) should change
2876     # its contents. See also GetMarcStructure.
2877     my $tagslib = &GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2878
2879     # return nothing if we don't have found an existing framework.
2880     return q{} unless $tagslib;
2881     my $itemrecord;
2882     if ($itemnum) {
2883         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2884     }
2885     my @loop_data;
2886
2887     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2888     my $query = qq{
2889         SELECT authorised_value,lib FROM authorised_values
2890     };
2891     $query .= qq{
2892         LEFT JOIN authorised_values_branches ON ( id = av_id )
2893     } if $branch_limit;
2894     $query .= qq{
2895         WHERE category = ?
2896     };
2897     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2898     $query .= qq{ ORDER BY lib};
2899     my $authorised_values_sth = $dbh->prepare( $query );
2900     foreach my $tag ( sort keys %{$tagslib} ) {
2901         if ( $tag ne '' ) {
2902
2903             # loop through each subfield
2904             my $cntsubf;
2905             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2906                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2907                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2908                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2909                 my %subfield_data;
2910                 $subfield_data{tag}           = $tag;
2911                 $subfield_data{subfield}      = $subfield;
2912                 $subfield_data{countsubfield} = $cntsubf++;
2913                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2914                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2915
2916                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2917                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2918                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2919                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2920                 $subfield_data{hidden}     = "display:none"
2921                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2922                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2923                 my ( $x, $defaultvalue );
2924                 if ($itemrecord) {
2925                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2926                 }
2927                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2928                 if ( !defined $defaultvalue ) {
2929                     $defaultvalue = q||;
2930                 } else {
2931                     $defaultvalue =~ s/"/&quot;/g;
2932                 }
2933
2934                 # search for itemcallnumber if applicable
2935                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2936                     && C4::Context->preference('itemcallnumber') ) {
2937                     my $CNtag      = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2938                     my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2939                     if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2940                         $defaultvalue = $field->subfield($CNsubfield);
2941                     }
2942                 }
2943                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2944                     && $defaultvalues
2945                     && $defaultvalues->{'callnumber'} ) {
2946                     if( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ){
2947                         # if the item record exists, only use default value if the item has no callnumber
2948                         $defaultvalue = $defaultvalues->{callnumber};
2949                     } elsif ( !$itemrecord and $defaultvalues ) {
2950                         # if the item record *doesn't* exists, always use the default value
2951                         $defaultvalue = $defaultvalues->{callnumber};
2952                     }
2953                 }
2954                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2955                     && $defaultvalues
2956                     && $defaultvalues->{'branchcode'} ) {
2957                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2958                         $defaultvalue = $defaultvalues->{branchcode};
2959                     }
2960                 }
2961                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2962                     && $defaultvalues
2963                     && $defaultvalues->{'location'} ) {
2964
2965                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2966                         # if the item record exists, only use default value if the item has no locationr
2967                         $defaultvalue = $defaultvalues->{location};
2968                     } elsif ( !$itemrecord and $defaultvalues ) {
2969                         # if the item record *doesn't* exists, always use the default value
2970                         $defaultvalue = $defaultvalues->{location};
2971                     }
2972                 }
2973                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2974                     my @authorised_values;
2975                     my %authorised_lib;
2976
2977                     # builds list, depending on authorised value...
2978                     #---- branch
2979                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2980                         if (   ( C4::Context->preference("IndependentBranches") )
2981                             && !C4::Context->IsSuperLibrarian() ) {
2982                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2983                             $sth->execute( C4::Context->userenv->{branch} );
2984                             push @authorised_values, ""
2985                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2986                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2987                                 push @authorised_values, $branchcode;
2988                                 $authorised_lib{$branchcode} = $branchname;
2989                             }
2990                         } else {
2991                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2992                             $sth->execute;
2993                             push @authorised_values, ""
2994                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2995                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2996                                 push @authorised_values, $branchcode;
2997                                 $authorised_lib{$branchcode} = $branchname;
2998                             }
2999                         }
3000
3001                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
3002                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
3003                             $defaultvalue = $defaultvalues->{branchcode};
3004                         }
3005
3006                         #----- itemtypes
3007                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
3008                         my $itemtypes = GetItemTypes( style => 'array' );
3009                         push @authorised_values, ""
3010                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3011                         for my $itemtype ( @$itemtypes ) {
3012                             push @authorised_values, $itemtype->{itemtype};
3013                             $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
3014                         }
3015                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
3016                             $defaultvalue = $defaultvalues->{'itemtype'};
3017                         }
3018
3019                         #---- class_sources
3020                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
3021                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3022
3023                         my $class_sources = GetClassSources();
3024                         my $default_source = C4::Context->preference("DefaultClassificationSource");
3025
3026                         foreach my $class_source (sort keys %$class_sources) {
3027                             next unless $class_sources->{$class_source}->{'used'} or
3028                                         ($class_source eq $default_source);
3029                             push @authorised_values, $class_source;
3030                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
3031                         }
3032
3033                         $defaultvalue = $default_source;
3034
3035                         #---- "true" authorised value
3036                     } else {
3037                         $authorised_values_sth->execute(
3038                             $tagslib->{$tag}->{$subfield}->{authorised_value},
3039                             $branch_limit ? $branch_limit : ()
3040                         );
3041                         push @authorised_values, ""
3042                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3043                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
3044                             push @authorised_values, $value;
3045                             $authorised_lib{$value} = $lib;
3046                         }
3047                     }
3048                     $subfield_data{marc_value} = {
3049                         type    => 'select',
3050                         values  => \@authorised_values,
3051                         default => "$defaultvalue",
3052                         labels  => \%authorised_lib,
3053                     };
3054                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
3055                 # it is a plugin
3056                     require Koha::FrameworkPlugin;
3057                     my $plugin = Koha::FrameworkPlugin->new({
3058                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
3059                         item_style => 1,
3060                     });
3061                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
3062                     $plugin->build( $pars );
3063                     if( !$plugin->errstr ) {
3064                         #TODO Move html to template; see report 12176/13397
3065                         my $tab= $plugin->noclick? '-1': '';
3066                         my $class= $plugin->noclick? ' disabled': '';
3067                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
3068                         $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;
3069                     } else {
3070                         warn $plugin->errstr;
3071                         $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
3072                     }
3073                 }
3074                 elsif ( $tag eq '' ) {       # it's an hidden field
3075                     $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" />);
3076                 }
3077                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
3078                     $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" />);
3079                 }
3080                 elsif ( length($defaultvalue) > 100
3081                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
3082                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
3083                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
3084                                   500 <= $tag && $tag < 600                     )
3085                           ) {
3086                     # oversize field (textarea)
3087                     $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");
3088                 } else {
3089                     $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
3090                 }
3091                 push( @loop_data, \%subfield_data );
3092             }
3093         }
3094     }
3095     my $itemnumber;
3096     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
3097         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
3098     }
3099     return {
3100         'itemtagfield'    => $itemtagfield,
3101         'itemtagsubfield' => $itemtagsubfield,
3102         'itemnumber'      => $itemnumber,
3103         'iteminformation' => \@loop_data
3104     };
3105 }
3106
3107 sub ToggleNewStatus {
3108     my ( $params ) = @_;
3109     my @rules = @{ $params->{rules} };
3110     my $report_only = $params->{report_only};
3111
3112     my $dbh = C4::Context->dbh;
3113     my @errors;
3114     my @item_columns = map { "items.$_" } Koha::Items->columns;
3115     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
3116     my $report;
3117     for my $rule ( @rules ) {
3118         my $age = $rule->{age};
3119         my $conditions = $rule->{conditions};
3120         my $substitutions = $rule->{substitutions};
3121         my @params;
3122
3123         my $query = q|
3124             SELECT items.biblionumber, items.itemnumber
3125             FROM items
3126             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
3127             WHERE 1
3128         |;
3129         for my $condition ( @$conditions ) {
3130             if (
3131                  grep {/^$condition->{field}$/} @item_columns
3132               or grep {/^$condition->{field}$/} @biblioitem_columns
3133             ) {
3134                 if ( $condition->{value} =~ /\|/ ) {
3135                     my @values = split /\|/, $condition->{value};
3136                     $query .= qq| AND $condition->{field} IN (|
3137                         . join( ',', ('?') x scalar @values )
3138                         . q|)|;
3139                     push @params, @values;
3140                 } else {
3141                     $query .= qq| AND $condition->{field} = ?|;
3142                     push @params, $condition->{value};
3143                 }
3144             }
3145         }
3146         if ( defined $age ) {
3147             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
3148             push @params, $age;
3149         }
3150         my $sth = $dbh->prepare($query);
3151         $sth->execute( @params );
3152         while ( my $values = $sth->fetchrow_hashref ) {
3153             my $biblionumber = $values->{biblionumber};
3154             my $itemnumber = $values->{itemnumber};
3155             my $item = C4::Items::GetItem( $itemnumber );
3156             for my $substitution ( @$substitutions ) {
3157                 next unless $substitution->{field};
3158                 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
3159                     unless $report_only;
3160                 push @{ $report->{$itemnumber} }, $substitution;
3161             }
3162         }
3163     }
3164
3165     return $report;
3166 }
3167
3168
3169 1;