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