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