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