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