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