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