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