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