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