Bug 16365: Selectively introduce GetMarcStructure() "unsafe" variant for better perfo...
[koha.git] / C4 / Items.pm
1 package C4::Items;
2
3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use strict;
22 #use warnings; FIXME - Bug 2505
23
24 use Carp;
25 use C4::Context;
26 use C4::Koha;
27 use C4::Biblio;
28 use Koha::DateUtils;
29 use MARC::Record;
30 use C4::ClassSource;
31 use C4::Log;
32 use List::MoreUtils qw/any/;
33 use YAML qw/Load/;
34 use DateTime::Format::MySQL;
35 use Data::Dumper; # used as part of logging item record changes, not just for
36                   # debugging; so please don't remove this
37 use Koha::DateUtils qw/dt_from_string/;
38 use Koha::Database;
39
40 use Koha::Biblioitems;
41 use Koha::Items;
42 use Koha::SearchEngine;
43 use Koha::SearchEngine::Search;
44 use Koha::Libraries;
45
46 use vars qw(@ISA @EXPORT);
47
48 BEGIN {
49
50         require Exporter;
51     @ISA = qw( Exporter );
52
53     # function exports
54     @EXPORT = qw(
55         GetItem
56         AddItemFromMarc
57         AddItem
58         AddItemBatchFromMarc
59         ModItemFromMarc
60     Item2Marc
61         ModItem
62         ModDateLastSeen
63         ModItemTransfer
64         DelItem
65     
66         CheckItemPreSave
67     
68         GetItemStatus
69         GetItemLocation
70         GetLostItems
71         GetItemsForInventory
72         GetItemsCount
73         GetItemInfosOf
74         GetItemsByBiblioitemnumber
75         GetItemsInfo
76         GetItemsLocationInfo
77         GetHostItemsInfo
78         GetItemnumbersForBiblio
79         get_itemnumbers_of
80         get_hostitemnumbers_of
81         GetItemnumberFromBarcode
82         GetBarcodeFromItemnumber
83         GetHiddenItemnumbers
84         ItemSafeToDelete
85         DelItemCheck
86     MoveItemFromBiblio
87     GetLatestAcquisitions
88
89         CartToShelf
90         ShelfToCart
91
92         GetAnalyticsCount
93         GetItemHolds
94
95         SearchItemsByField
96         SearchItems
97
98         PrepareItemrecordDisplay
99
100     );
101 }
102
103 =head1 NAME
104
105 C4::Items - item management functions
106
107 =head1 DESCRIPTION
108
109 This module contains an API for manipulating item 
110 records in Koha, and is used by cataloguing, circulation,
111 acquisitions, and serials management.
112
113 A Koha item record is stored in two places: the
114 items table and embedded in a MARC tag in the XML
115 version of the associated bib record in C<biblioitems.marcxml>.
116 This is done to allow the item information to be readily
117 indexed (e.g., by Zebra), but means that each item
118 modification transaction must keep the items table
119 and the MARC XML in sync at all times.
120
121 Consequently, all code that creates, modifies, or deletes
122 item records B<must> use an appropriate function from 
123 C<C4::Items>.  If no existing function is suitable, it is
124 better to add one to C<C4::Items> than to use add
125 one-off SQL statements to add or modify items.
126
127 The items table will be considered authoritative.  In other
128 words, if there is ever a discrepancy between the items
129 table and the MARC XML, the items table should be considered
130 accurate.
131
132 =head1 HISTORICAL NOTE
133
134 Most of the functions in C<C4::Items> were originally in
135 the C<C4::Biblio> module.
136
137 =head1 CORE EXPORTED FUNCTIONS
138
139 The following functions are meant for use by users
140 of C<C4::Items>
141
142 =cut
143
144 =head2 GetItem
145
146   $item = GetItem($itemnumber,$barcode,$serial);
147
148 Return item information, for a given itemnumber or barcode.
149 The return value is a hashref mapping item column
150 names to values.  If C<$serial> is true, include serial publication data.
151
152 =cut
153
154 sub GetItem {
155     my ($itemnumber,$barcode, $serial) = @_;
156     my $dbh = C4::Context->dbh;
157         my $data;
158
159     if ($itemnumber) {
160         my $sth = $dbh->prepare("
161             SELECT * FROM items 
162             WHERE itemnumber = ?");
163         $sth->execute($itemnumber);
164         $data = $sth->fetchrow_hashref;
165     } else {
166         my $sth = $dbh->prepare("
167             SELECT * FROM items 
168             WHERE barcode = ?"
169             );
170         $sth->execute($barcode);                
171         $data = $sth->fetchrow_hashref;
172     }
173
174     return unless ( $data );
175
176     if ( $serial) {      
177     my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
178         $ssth->execute($data->{'itemnumber'}) ;
179         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
180     }
181         #if we don't have an items.itype, use biblioitems.itemtype.
182     # FIXME this should respect the itypes systempreference
183     # if (C4::Context->preference('item-level_itypes')) {
184         if( ! $data->{'itype'} ) {
185                 my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems  WHERE biblionumber = ?");
186                 $sth->execute($data->{'biblionumber'});
187                 ($data->{'itype'}) = $sth->fetchrow_array;
188         }
189     return $data;
190 }    # sub GetItem
191
192 =head2 CartToShelf
193
194   CartToShelf($itemnumber);
195
196 Set the current shelving location of the item record
197 to its stored permanent shelving location.  This is
198 primarily used to indicate when an item whose current
199 location is a special processing ('PROC') or shelving cart
200 ('CART') location is back in the stacks.
201
202 =cut
203
204 sub CartToShelf {
205     my ( $itemnumber ) = @_;
206
207     unless ( $itemnumber ) {
208         croak "FAILED CartToShelf() - no itemnumber supplied";
209     }
210
211     my $item = GetItem($itemnumber);
212     if ( $item->{location} eq 'CART' ) {
213         $item->{location} = $item->{permanent_location};
214         ModItem($item, undef, $itemnumber);
215     }
216 }
217
218 =head2 ShelfToCart
219
220   ShelfToCart($itemnumber);
221
222 Set the current shelving location of the item
223 to shelving cart ('CART').
224
225 =cut
226
227 sub ShelfToCart {
228     my ( $itemnumber ) = @_;
229
230     unless ( $itemnumber ) {
231         croak "FAILED ShelfToCart() - no itemnumber supplied";
232     }
233
234     my $item = GetItem($itemnumber);
235     $item->{'location'} = 'CART';
236     ModItem($item, undef, $itemnumber);
237 }
238
239 =head2 AddItemFromMarc
240
241   my ($biblionumber, $biblioitemnumber, $itemnumber) 
242       = AddItemFromMarc($source_item_marc, $biblionumber);
243
244 Given a MARC::Record object containing an embedded item
245 record and a biblionumber, create a new item record.
246
247 =cut
248
249 sub AddItemFromMarc {
250     my ( $source_item_marc, $biblionumber ) = @_;
251     my $dbh = C4::Context->dbh;
252
253     # parse item hash from MARC
254     my $frameworkcode = GetFrameworkCode( $biblionumber );
255         my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
256         
257         my $localitemmarc=MARC::Record->new;
258         $localitemmarc->append_fields($source_item_marc->field($itemtag));
259     my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
260     my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
261     return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
262 }
263
264 =head2 AddItem
265
266   my ($biblionumber, $biblioitemnumber, $itemnumber) 
267       = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
268
269 Given a hash containing item column names as keys,
270 create a new Koha item record.
271
272 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
273 do not need to be supplied for general use; they exist
274 simply to allow them to be picked up from AddItemFromMarc.
275
276 The final optional parameter, C<$unlinked_item_subfields>, contains
277 an arrayref containing subfields present in the original MARC
278 representation of the item (e.g., from the item editor) that are
279 not mapped to C<items> columns directly but should instead
280 be stored in C<items.more_subfields_xml> and included in 
281 the biblio items tag for display and indexing.
282
283 =cut
284
285 sub AddItem {
286     my $item = shift;
287     my $biblionumber = shift;
288
289     my $dbh           = @_ ? shift : C4::Context->dbh;
290     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
291     my $unlinked_item_subfields;  
292     if (@_) {
293         $unlinked_item_subfields = shift
294     };
295
296     # needs old biblionumber and biblioitemnumber
297     $item->{'biblionumber'} = $biblionumber;
298     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
299     $sth->execute( $item->{'biblionumber'} );
300     ($item->{'biblioitemnumber'}) = $sth->fetchrow;
301
302     _set_defaults_for_add($item);
303     _set_derived_columns_for_add($item);
304     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
305     # FIXME - checks here
306     unless ( $item->{itype} ) {  # default to biblioitem.itemtype if no itype
307         my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
308         $itype_sth->execute( $item->{'biblionumber'} );
309         ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
310     }
311
312         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
313     $item->{'itemnumber'} = $itemnumber;
314
315     ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
316    
317     logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
318     
319     return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
320 }
321
322 =head2 AddItemBatchFromMarc
323
324   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
325              $biblionumber, $biblioitemnumber, $frameworkcode);
326
327 Efficiently create item records from a MARC biblio record with
328 embedded item fields.  This routine is suitable for batch jobs.
329
330 This API assumes that the bib record has already been
331 saved to the C<biblio> and C<biblioitems> tables.  It does
332 not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
333 are populated, but it will do so via a call to ModBibiloMarc.
334
335 The goal of this API is to have a similar effect to using AddBiblio
336 and AddItems in succession, but without inefficient repeated
337 parsing of the MARC XML bib record.
338
339 This function returns an arrayref of new itemsnumbers and an arrayref of item
340 errors encountered during the processing.  Each entry in the errors
341 list is a hashref containing the following keys:
342
343 =over
344
345 =item item_sequence
346
347 Sequence number of original item tag in the MARC record.
348
349 =item item_barcode
350
351 Item barcode, provide to assist in the construction of
352 useful error messages.
353
354 =item error_code
355
356 Code representing the error condition.  Can be 'duplicate_barcode',
357 'invalid_homebranch', or 'invalid_holdingbranch'.
358
359 =item error_information
360
361 Additional information appropriate to the error condition.
362
363 =back
364
365 =cut
366
367 sub AddItemBatchFromMarc {
368     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
369     my $error;
370     my @itemnumbers = ();
371     my @errors = ();
372     my $dbh = C4::Context->dbh;
373
374     # We modify the record, so lets work on a clone so we don't change the
375     # original.
376     $record = $record->clone();
377     # loop through the item tags and start creating items
378     my @bad_item_fields = ();
379     my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
380     my $item_sequence_num = 0;
381     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
382         $item_sequence_num++;
383         # we take the item field and stick it into a new
384         # MARC record -- this is required so far because (FIXME)
385         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
386         # and there is no TransformMarcFieldToKoha
387         my $temp_item_marc = MARC::Record->new();
388         $temp_item_marc->append_fields($item_field);
389     
390         # add biblionumber and biblioitemnumber
391         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
392         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
393         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
394         $item->{'biblionumber'} = $biblionumber;
395         $item->{'biblioitemnumber'} = $biblioitemnumber;
396
397         # check for duplicate barcode
398         my %item_errors = CheckItemPreSave($item);
399         if (%item_errors) {
400             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
401             push @bad_item_fields, $item_field;
402             next ITEMFIELD;
403         }
404
405         _set_defaults_for_add($item);
406         _set_derived_columns_for_add($item);
407         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
408         warn $error if $error;
409         push @itemnumbers, $itemnumber; # FIXME not checking error
410         $item->{'itemnumber'} = $itemnumber;
411
412         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
413
414         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
415         $item_field->replace_with($new_item_marc->field($itemtag));
416     }
417
418     # remove any MARC item fields for rejected items
419     foreach my $item_field (@bad_item_fields) {
420         $record->delete_field($item_field);
421     }
422
423     # update the MARC biblio
424  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
425
426     return (\@itemnumbers, \@errors);
427 }
428
429 =head2 ModItemFromMarc
430
431   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
432
433 This function updates an item record based on a supplied
434 C<MARC::Record> object containing an embedded item field.
435 This API is meant for the use of C<additem.pl>; for 
436 other purposes, C<ModItem> should be used.
437
438 This function uses the hash %default_values_for_mod_from_marc,
439 which contains default values for item fields to
440 apply when modifying an item.  This is needed because
441 if an item field's value is cleared, TransformMarcToKoha
442 does not include the column in the
443 hash that's passed to ModItem, which without
444 use of this hash makes it impossible to clear
445 an item field's value.  See bug 2466.
446
447 Note that only columns that can be directly
448 changed from the cataloging and serials
449 item editors are included in this hash.
450
451 Returns item record
452
453 =cut
454
455 sub _build_default_values_for_mod_marc {
456     my ($frameworkcode) = @_;
457
458     my $cache     = Koha::Caches->get_instance();
459     my $cache_key = "default_value_for_mod_marc-$frameworkcode";
460     my $cached    = $cache->get_from_cache($cache_key);
461     return $cached if $cached;
462
463     my $default_values = {
464         barcode                  => undef,
465         booksellerid             => undef,
466         ccode                    => undef,
467         'items.cn_source'        => undef,
468         coded_location_qualifier => undef,
469         copynumber               => undef,
470         damaged                  => 0,
471         enumchron                => undef,
472         holdingbranch            => undef,
473         homebranch               => undef,
474         itemcallnumber           => undef,
475         itemlost                 => 0,
476         itemnotes                => undef,
477         itemnotes_nonpublic      => undef,
478         itype                    => undef,
479         location                 => undef,
480         permanent_location       => undef,
481         materials                => undef,
482         new_status               => undef,
483         notforloan               => 0,
484         # paidfor => undef, # commented, see bug 12817
485         price                    => undef,
486         replacementprice         => undef,
487         replacementpricedate     => undef,
488         restricted               => undef,
489         stack                    => undef,
490         stocknumber              => undef,
491         uri                      => undef,
492         withdrawn                => 0,
493     };
494     my %default_values_for_mod_from_marc;
495     while ( my ( $field, $default_value ) = each %$default_values ) {
496         my $kohafield = $field;
497         $kohafield =~ s|^([^\.]+)$|items.$1|;
498         $default_values_for_mod_from_marc{$field} =
499           $default_value
500           if C4::Koha::IsKohaFieldLinked(
501             { kohafield => $kohafield, frameworkcode => $frameworkcode } );
502     }
503
504     $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
505     return \%default_values_for_mod_from_marc;
506 }
507
508 sub ModItemFromMarc {
509     my $item_marc = shift;
510     my $biblionumber = shift;
511     my $itemnumber = shift;
512
513     my $dbh           = C4::Context->dbh;
514     my $frameworkcode = GetFrameworkCode($biblionumber);
515     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
516
517     my $localitemmarc = MARC::Record->new;
518     $localitemmarc->append_fields( $item_marc->field($itemtag) );
519     my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
520     my $default_values = _build_default_values_for_mod_marc($frameworkcode);
521     foreach my $item_field ( keys %$default_values ) {
522         $item->{$item_field} = $default_values->{$item_field}
523           unless exists $item->{$item_field};
524     }
525     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
526
527     ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
528     return $item;
529 }
530
531 =head2 ModItem
532
533   ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
534
535 Change one or more columns in an item record and update
536 the MARC representation of the item.
537
538 The first argument is a hashref mapping from item column
539 names to the new values.  The second and third arguments
540 are the biblionumber and itemnumber, respectively.
541
542 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
543 an arrayref containing subfields present in the original MARC
544 representation of the item (e.g., from the item editor) that are
545 not mapped to C<items> columns directly but should instead
546 be stored in C<items.more_subfields_xml> and included in 
547 the biblio items tag for display and indexing.
548
549 If one of the changed columns is used to calculate
550 the derived value of a column such as C<items.cn_sort>, 
551 this routine will perform the necessary calculation
552 and set the value.
553
554 =cut
555
556 sub ModItem {
557     my $item = shift;
558     my $biblionumber = shift;
559     my $itemnumber = shift;
560
561     # if $biblionumber is undefined, get it from the current item
562     unless (defined $biblionumber) {
563         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
564     }
565
566     my $dbh           = @_ ? shift : C4::Context->dbh;
567     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
568     
569     my $unlinked_item_subfields;  
570     if (@_) {
571         $unlinked_item_subfields = shift;
572         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
573     };
574
575     $item->{'itemnumber'} = $itemnumber or return;
576
577     my @fields = qw( itemlost withdrawn );
578
579     # Only call GetItem if we need to set an "on" date field
580     if ( $item->{itemlost} || $item->{withdrawn} ) {
581         my $pre_mod_item = GetItem( $item->{'itemnumber'} );
582         for my $field (@fields) {
583             if (    defined( $item->{$field} )
584                 and not $pre_mod_item->{$field}
585                 and $item->{$field} )
586             {
587                 $item->{ $field . '_on' } =
588                   DateTime::Format::MySQL->format_datetime( dt_from_string() );
589             }
590         }
591     }
592
593     # If the field is defined but empty, we are removing and,
594     # and thus need to clear out the 'on' field as well
595     for my $field (@fields) {
596         if ( defined( $item->{$field} ) && !$item->{$field} ) {
597             $item->{ $field . '_on' } = undef;
598         }
599     }
600
601
602     _set_derived_columns_for_mod($item);
603     _do_column_fixes_for_mod($item);
604     # FIXME add checks
605     # duplicate barcode
606     # attempt to change itemnumber
607     # attempt to change biblionumber (if we want
608     # an API to relink an item to a different bib,
609     # it should be a separate function)
610
611     # update items table
612     _koha_modify_item($item);
613
614     # request that bib be reindexed so that searching on current
615     # item status is possible
616     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
617
618     logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
619 }
620
621 =head2 ModItemTransfer
622
623   ModItemTransfer($itenumber, $frombranch, $tobranch);
624
625 Marks an item as being transferred from one branch
626 to another.
627
628 =cut
629
630 sub ModItemTransfer {
631     my ( $itemnumber, $frombranch, $tobranch ) = @_;
632
633     my $dbh = C4::Context->dbh;
634
635     # Remove the 'shelving cart' location status if it is being used.
636     CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
637
638     #new entry in branchtransfers....
639     my $sth = $dbh->prepare(
640         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
641         VALUES (?, ?, NOW(), ?)");
642     $sth->execute($itemnumber, $frombranch, $tobranch);
643
644     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
645     ModDateLastSeen($itemnumber);
646     return;
647 }
648
649 =head2 ModDateLastSeen
650
651   ModDateLastSeen($itemnum);
652
653 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
654 C<$itemnum> is the item number
655
656 =cut
657
658 sub ModDateLastSeen {
659     my ($itemnumber) = @_;
660     
661     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
662     ModItem({ itemlost => 0, datelastseen => $today }, undef, $itemnumber);
663 }
664
665 =head2 DelItem
666
667   DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
668
669 Exported function (core API) for deleting an item record in Koha.
670
671 =cut
672
673 sub DelItem {
674     my ( $params ) = @_;
675
676     my $itemnumber   = $params->{itemnumber};
677     my $biblionumber = $params->{biblionumber};
678
679     unless ($biblionumber) {
680         $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
681     }
682
683     # If there is no biblionumber for the given itemnumber, there is nothing to delete
684     return 0 unless $biblionumber;
685
686     # FIXME check the item has no current issues
687     my $deleted = _koha_delete_item( $itemnumber );
688
689     # get the MARC record
690     my $record = GetMarcBiblio($biblionumber);
691     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
692
693     #search item field code
694     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
695     return $deleted;
696 }
697
698 =head2 CheckItemPreSave
699
700     my $item_ref = TransformMarcToKoha($marc, 'items');
701     # do stuff
702     my %errors = CheckItemPreSave($item_ref);
703     if (exists $errors{'duplicate_barcode'}) {
704         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
705     } elsif (exists $errors{'invalid_homebranch'}) {
706         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
707     } elsif (exists $errors{'invalid_holdingbranch'}) {
708         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
709     } else {
710         print "item is OK";
711     }
712
713 Given a hashref containing item fields, determine if it can be
714 inserted or updated in the database.  Specifically, checks for
715 database integrity issues, and returns a hash containing any
716 of the following keys, if applicable.
717
718 =over 2
719
720 =item duplicate_barcode
721
722 Barcode, if it duplicates one already found in the database.
723
724 =item invalid_homebranch
725
726 Home branch, if not defined in branches table.
727
728 =item invalid_holdingbranch
729
730 Holding branch, if not defined in branches table.
731
732 =back
733
734 This function does NOT implement any policy-related checks,
735 e.g., whether current operator is allowed to save an
736 item that has a given branch code.
737
738 =cut
739
740 sub CheckItemPreSave {
741     my $item_ref = shift;
742
743     my %errors = ();
744
745     # check for duplicate barcode
746     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
747         my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
748         if ($existing_itemnumber) {
749             if (!exists $item_ref->{'itemnumber'}                       # new item
750                 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
751                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
752             }
753         }
754     }
755
756     # check for valid home branch
757     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
758         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
759         unless (defined $home_library) {
760             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
761         }
762     }
763
764     # check for valid holding branch
765     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
766         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
767         unless (defined $holding_library) {
768             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
769         }
770     }
771
772     return %errors;
773
774 }
775
776 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
777
778 The following functions provide various ways of 
779 getting an item record, a set of item records, or
780 lists of authorized values for certain item fields.
781
782 Some of the functions in this group are candidates
783 for refactoring -- for example, some of the code
784 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
785 has copy-and-paste work.
786
787 =cut
788
789 =head2 GetItemStatus
790
791   $itemstatushash = GetItemStatus($fwkcode);
792
793 Returns a list of valid values for the
794 C<items.notforloan> field.
795
796 NOTE: does B<not> return an individual item's
797 status.
798
799 Can be MARC dependent.
800 fwkcode is optional.
801 But basically could be can be loan or not
802 Create a status selector with the following code
803
804 =head3 in PERL SCRIPT
805
806  my $itemstatushash = getitemstatus;
807  my @itemstatusloop;
808  foreach my $thisstatus (keys %$itemstatushash) {
809      my %row =(value => $thisstatus,
810                  statusname => $itemstatushash->{$thisstatus}->{'statusname'},
811              );
812      push @itemstatusloop, \%row;
813  }
814  $template->param(statusloop=>\@itemstatusloop);
815
816 =head3 in TEMPLATE
817
818 <select name="statusloop" id="statusloop">
819     <option value="">Default</option>
820     [% FOREACH statusloo IN statusloop %]
821         [% IF ( statusloo.selected ) %]
822             <option value="[% statusloo.value %]" selected="selected">[% statusloo.statusname %]</option>
823         [% ELSE %]
824             <option value="[% statusloo.value %]">[% statusloo.statusname %]</option>
825         [% END %]
826     [% END %]
827 </select>
828
829 =cut
830
831 sub GetItemStatus {
832
833     # returns a reference to a hash of references to status...
834     my ($fwk) = @_;
835     my %itemstatus;
836     my $dbh = C4::Context->dbh;
837     my $sth;
838     $fwk = '' unless ($fwk);
839     my ( $tag, $subfield ) =
840       GetMarcFromKohaField( "items.notforloan", $fwk );
841     if ( $tag and $subfield ) {
842         my $sth =
843           $dbh->prepare(
844             "SELECT authorised_value
845             FROM marc_subfield_structure
846             WHERE tagfield=?
847                 AND tagsubfield=?
848                 AND frameworkcode=?
849             "
850           );
851         $sth->execute( $tag, $subfield, $fwk );
852         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
853             my $authvalsth =
854               $dbh->prepare(
855                 "SELECT authorised_value,lib
856                 FROM authorised_values 
857                 WHERE category=? 
858                 ORDER BY lib
859                 "
860               );
861             $authvalsth->execute($authorisedvaluecat);
862             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
863                 $itemstatus{$authorisedvalue} = $lib;
864             }
865             return \%itemstatus;
866             exit 1;
867         }
868         else {
869
870             #No authvalue list
871             # build default
872         }
873     }
874
875     #No authvalue list
876     #build default
877     $itemstatus{"1"} = "Not For Loan";
878     return \%itemstatus;
879 }
880
881 =head2 GetItemLocation
882
883   $itemlochash = GetItemLocation($fwk);
884
885 Returns a list of valid values for the
886 C<items.location> field.
887
888 NOTE: does B<not> return an individual item's
889 location.
890
891 where fwk stands for an optional framework code.
892 Create a location selector with the following code
893
894 =head3 in PERL SCRIPT
895
896   my $itemlochash = getitemlocation;
897   my @itemlocloop;
898   foreach my $thisloc (keys %$itemlochash) {
899       my $selected = 1 if $thisbranch eq $branch;
900       my %row =(locval => $thisloc,
901                   selected => $selected,
902                   locname => $itemlochash->{$thisloc},
903                );
904       push @itemlocloop, \%row;
905   }
906   $template->param(itemlocationloop => \@itemlocloop);
907
908 =head3 in TEMPLATE
909
910   <select name="location">
911       <option value="">Default</option>
912   <!-- TMPL_LOOP name="itemlocationloop" -->
913       <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
914   <!-- /TMPL_LOOP -->
915   </select>
916
917 =cut
918
919 sub GetItemLocation {
920
921     # returns a reference to a hash of references to location...
922     my ($fwk) = @_;
923     my %itemlocation;
924     my $dbh = C4::Context->dbh;
925     my $sth;
926     $fwk = '' unless ($fwk);
927     my ( $tag, $subfield ) =
928       GetMarcFromKohaField( "items.location", $fwk );
929     if ( $tag and $subfield ) {
930         my $sth =
931           $dbh->prepare(
932             "SELECT authorised_value
933             FROM marc_subfield_structure 
934             WHERE tagfield=? 
935                 AND tagsubfield=? 
936                 AND frameworkcode=?"
937           );
938         $sth->execute( $tag, $subfield, $fwk );
939         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
940             my $authvalsth =
941               $dbh->prepare(
942                 "SELECT authorised_value,lib
943                 FROM authorised_values
944                 WHERE category=?
945                 ORDER BY lib"
946               );
947             $authvalsth->execute($authorisedvaluecat);
948             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
949                 $itemlocation{$authorisedvalue} = $lib;
950             }
951             return \%itemlocation;
952             exit 1;
953         }
954         else {
955
956             #No authvalue list
957             # build default
958         }
959     }
960
961     #No authvalue list
962     #build default
963     $itemlocation{"1"} = "Not For Loan";
964     return \%itemlocation;
965 }
966
967 =head2 GetLostItems
968
969   $items = GetLostItems( $where );
970
971 This function gets a list of lost items.
972
973 =over 2
974
975 =item input:
976
977 C<$where> is a hashref. it containts a field of the items table as key
978 and the value to match as value. For example:
979
980 { barcode    => 'abc123',
981   homebranch => 'CPL',    }
982
983 =item return:
984
985 C<$items> is a reference to an array full of hashrefs with columns
986 from the "items" table as keys.
987
988 =item usage in the perl script:
989
990   my $where = { barcode => '0001548' };
991   my $items = GetLostItems( $where );
992   $template->param( itemsloop => $items );
993
994 =back
995
996 =cut
997
998 sub GetLostItems {
999     # Getting input args.
1000     my $where   = shift;
1001     my $dbh     = C4::Context->dbh;
1002
1003     my $query   = "
1004         SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
1005                itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
1006         FROM   items
1007             LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
1008             LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
1009             LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
1010         WHERE
1011                 authorised_values.category = 'LOST'
1012                 AND itemlost IS NOT NULL
1013                 AND itemlost <> 0
1014     ";
1015     my @query_parameters;
1016     foreach my $key (keys %$where) {
1017         $query .= " AND $key LIKE ?";
1018         push @query_parameters, "%$where->{$key}%";
1019     }
1020
1021     my $sth = $dbh->prepare($query);
1022     $sth->execute( @query_parameters );
1023     my $items = [];
1024     while ( my $row = $sth->fetchrow_hashref ){
1025         push @$items, $row;
1026     }
1027     return $items;
1028 }
1029
1030 =head2 GetItemsForInventory
1031
1032 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
1033   minlocation  => $minlocation,
1034   maxlocation  => $maxlocation,
1035   location     => $location,
1036   itemtype     => $itemtype,
1037   ignoreissued => $ignoreissued,
1038   datelastseen => $datelastseen,
1039   branchcode   => $branchcode,
1040   branch       => $branch,
1041   offset       => $offset,
1042   size         => $size,
1043   statushash   => $statushash,
1044   interface    => $interface,
1045 } );
1046
1047 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
1048
1049 The sub returns a reference to a list of hashes, each containing
1050 itemnumber, author, title, barcode, item callnumber, and date last
1051 seen. It is ordered by callnumber then title.
1052
1053 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
1054 the datelastseen can be used to specify that you want to see items not seen since a past date only.
1055 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
1056 $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.
1057
1058 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
1059
1060 =cut
1061
1062 sub GetItemsForInventory {
1063     my ( $parameters ) = @_;
1064     my $minlocation  = $parameters->{'minlocation'}  // '';
1065     my $maxlocation  = $parameters->{'maxlocation'}  // '';
1066     my $location     = $parameters->{'location'}     // '';
1067     my $itemtype     = $parameters->{'itemtype'}     // '';
1068     my $ignoreissued = $parameters->{'ignoreissued'} // '';
1069     my $datelastseen = $parameters->{'datelastseen'} // '';
1070     my $branchcode   = $parameters->{'branchcode'}   // '';
1071     my $branch       = $parameters->{'branch'}       // '';
1072     my $offset       = $parameters->{'offset'}       // '';
1073     my $size         = $parameters->{'size'}         // '';
1074     my $statushash   = $parameters->{'statushash'}   // '';
1075     my $interface    = $parameters->{'interface'}    // '';
1076
1077     my $dbh = C4::Context->dbh;
1078     my ( @bind_params, @where_strings );
1079
1080     my $select_columns = q{
1081         SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
1082     };
1083     my $select_count = q{SELECT COUNT(*)};
1084     my $query = q{
1085         FROM items
1086         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
1087         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
1088     };
1089     if ($statushash){
1090         for my $authvfield (keys %$statushash){
1091             if ( scalar @{$statushash->{$authvfield}} > 0 ){
1092                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
1093                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
1094             }
1095         }
1096     }
1097
1098     if ($minlocation) {
1099         push @where_strings, 'itemcallnumber >= ?';
1100         push @bind_params, $minlocation;
1101     }
1102
1103     if ($maxlocation) {
1104         push @where_strings, 'itemcallnumber <= ?';
1105         push @bind_params, $maxlocation;
1106     }
1107
1108     if ($datelastseen) {
1109         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
1110         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
1111         push @bind_params, $datelastseen;
1112     }
1113
1114     if ( $location ) {
1115         push @where_strings, 'items.location = ?';
1116         push @bind_params, $location;
1117     }
1118
1119     if ( $branchcode ) {
1120         if($branch eq "homebranch"){
1121         push @where_strings, 'items.homebranch = ?';
1122         }else{
1123             push @where_strings, 'items.holdingbranch = ?';
1124         }
1125         push @bind_params, $branchcode;
1126     }
1127
1128     if ( $itemtype ) {
1129         push @where_strings, 'biblioitems.itemtype = ?';
1130         push @bind_params, $itemtype;
1131     }
1132
1133     if ( $ignoreissued) {
1134         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1135         push @where_strings, 'issues.date_due IS NULL';
1136     }
1137
1138     if ( @where_strings ) {
1139         $query .= 'WHERE ';
1140         $query .= join ' AND ', @where_strings;
1141     }
1142     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1143     my $count_query = $select_count . $query;
1144     $query .= " LIMIT $offset, $size" if ($offset and $size);
1145     $query = $select_columns . $query;
1146     my $sth = $dbh->prepare($query);
1147     $sth->execute( @bind_params );
1148
1149     my @results = ();
1150     my $tmpresults = $sth->fetchall_arrayref({});
1151     $sth = $dbh->prepare( $count_query );
1152     $sth->execute( @bind_params );
1153     my ($iTotalRecords) = $sth->fetchrow_array();
1154
1155     my $avmapping = C4::Koha::GetKohaAuthorisedValuesMapping( {
1156                       interface => $interface
1157                     } );
1158     foreach my $row (@$tmpresults) {
1159
1160         # Auth values
1161         foreach (keys %$row) {
1162             if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
1163                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
1164             }
1165         }
1166         push @results, $row;
1167     }
1168
1169     return (\@results, $iTotalRecords);
1170 }
1171
1172 =head2 GetItemsCount
1173
1174   $count = &GetItemsCount( $biblionumber);
1175
1176 This function return count of item with $biblionumber
1177
1178 =cut
1179
1180 sub GetItemsCount {
1181     my ( $biblionumber ) = @_;
1182     my $dbh = C4::Context->dbh;
1183     my $query = "SELECT count(*)
1184           FROM  items 
1185           WHERE biblionumber=?";
1186     my $sth = $dbh->prepare($query);
1187     $sth->execute($biblionumber);
1188     my $count = $sth->fetchrow;  
1189     return ($count);
1190 }
1191
1192 =head2 GetItemInfosOf
1193
1194   GetItemInfosOf(@itemnumbers);
1195
1196 =cut
1197
1198 sub GetItemInfosOf {
1199     my @itemnumbers = @_;
1200
1201     my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
1202
1203     my $query = "
1204         SELECT *
1205         FROM items
1206         WHERE itemnumber IN ($itemnumber_values)
1207     ";
1208     return get_infos_of( $query, 'itemnumber' );
1209 }
1210
1211 =head2 GetItemsByBiblioitemnumber
1212
1213   GetItemsByBiblioitemnumber($biblioitemnumber);
1214
1215 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1216 Called by C<C4::XISBN>
1217
1218 =cut
1219
1220 sub GetItemsByBiblioitemnumber {
1221     my ( $bibitem ) = @_;
1222     my $dbh = C4::Context->dbh;
1223     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1224     # Get all items attached to a biblioitem
1225     my $i = 0;
1226     my @results; 
1227     $sth->execute($bibitem) || die $sth->errstr;
1228     while ( my $data = $sth->fetchrow_hashref ) {  
1229         # Foreach item, get circulation information
1230         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1231                                    WHERE itemnumber = ?
1232                                    AND issues.borrowernumber = borrowers.borrowernumber"
1233         );
1234         $sth2->execute( $data->{'itemnumber'} );
1235         if ( my $data2 = $sth2->fetchrow_hashref ) {
1236             # if item is out, set the due date and who it is out too
1237             $data->{'date_due'}   = $data2->{'date_due'};
1238             $data->{'cardnumber'} = $data2->{'cardnumber'};
1239             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1240         }
1241         else {
1242             # set date_due to blank, so in the template we check itemlost, and withdrawn
1243             $data->{'date_due'} = '';                                                                                                         
1244         }    # else         
1245         # Find the last 3 people who borrowed this item.                  
1246         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1247                       AND old_issues.borrowernumber = borrowers.borrowernumber
1248                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1249         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1250         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1251         my $i2 = 0;
1252         while ( my $data2 = $sth2->fetchrow_hashref ) {
1253             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1254             $data->{"card$i2"}      = $data2->{'cardnumber'};
1255             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1256             $i2++;
1257         }
1258         push(@results,$data);
1259     } 
1260     return (\@results); 
1261 }
1262
1263 =head2 GetItemsInfo
1264
1265   @results = GetItemsInfo($biblionumber);
1266
1267 Returns information about items with the given biblionumber.
1268
1269 C<GetItemsInfo> returns a list of references-to-hash. Each element
1270 contains a number of keys. Most of them are attributes from the
1271 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1272 Koha database. Other keys include:
1273
1274 =over 2
1275
1276 =item C<$data-E<gt>{branchname}>
1277
1278 The name (not the code) of the branch to which the book belongs.
1279
1280 =item C<$data-E<gt>{datelastseen}>
1281
1282 This is simply C<items.datelastseen>, except that while the date is
1283 stored in YYYY-MM-DD format in the database, here it is converted to
1284 DD/MM/YYYY format. A NULL date is returned as C<//>.
1285
1286 =item C<$data-E<gt>{datedue}>
1287
1288 =item C<$data-E<gt>{class}>
1289
1290 This is the concatenation of C<biblioitems.classification>, the book's
1291 Dewey code, and C<biblioitems.subclass>.
1292
1293 =item C<$data-E<gt>{ocount}>
1294
1295 I think this is the number of copies of the book available.
1296
1297 =item C<$data-E<gt>{order}>
1298
1299 If this is set, it is set to C<One Order>.
1300
1301 =back
1302
1303 =cut
1304
1305 sub GetItemsInfo {
1306     my ( $biblionumber ) = @_;
1307     my $dbh   = C4::Context->dbh;
1308     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1309     require C4::Languages;
1310     my $language = C4::Languages::getlanguage();
1311     my $query = "
1312     SELECT items.*,
1313            biblio.*,
1314            biblioitems.volume,
1315            biblioitems.number,
1316            biblioitems.itemtype,
1317            biblioitems.isbn,
1318            biblioitems.issn,
1319            biblioitems.publicationyear,
1320            biblioitems.publishercode,
1321            biblioitems.volumedate,
1322            biblioitems.volumedesc,
1323            biblioitems.lccn,
1324            biblioitems.url,
1325            items.notforloan as itemnotforloan,
1326            issues.borrowernumber,
1327            issues.date_due as datedue,
1328            issues.onsite_checkout,
1329            borrowers.cardnumber,
1330            borrowers.surname,
1331            borrowers.firstname,
1332            borrowers.branchcode as bcode,
1333            serial.serialseq,
1334            serial.publisheddate,
1335            itemtypes.description,
1336            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1337            itemtypes.notforloan as notforloan_per_itemtype,
1338            holding.branchurl,
1339            holding.branchcode,
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, { unsafe => 1 });
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
2863     # it would be perhaps beneficial (?) to call GetMarcStructure with 'unsafe' parameter
2864     # for performance reasons, but $tagslib may be passed to $plugin->build(), and there
2865     # is no way to ensure that this structure is not getting corrupted somewhere in there
2866     my $tagslib = &GetMarcStructure( 1, $frameworkcode );
2867
2868     # return nothing if we don't have found an existing framework.
2869     return q{} unless $tagslib;
2870     my $itemrecord;
2871     if ($itemnum) {
2872         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2873     }
2874     my @loop_data;
2875
2876     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2877     my $query = qq{
2878         SELECT authorised_value,lib FROM authorised_values
2879     };
2880     $query .= qq{
2881         LEFT JOIN authorised_values_branches ON ( id = av_id )
2882     } if $branch_limit;
2883     $query .= qq{
2884         WHERE category = ?
2885     };
2886     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2887     $query .= qq{ ORDER BY lib};
2888     my $authorised_values_sth = $dbh->prepare( $query );
2889     foreach my $tag ( sort keys %{$tagslib} ) {
2890         my $previous_tag = '';
2891         if ( $tag ne '' ) {
2892
2893             # loop through each subfield
2894             my $cntsubf;
2895             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2896                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2897                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2898                 my %subfield_data;
2899                 $subfield_data{tag}           = $tag;
2900                 $subfield_data{subfield}      = $subfield;
2901                 $subfield_data{countsubfield} = $cntsubf++;
2902                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2903                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2904
2905                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2906                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2907                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2908                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2909                 $subfield_data{hidden}     = "display:none"
2910                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2911                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2912                 my ( $x, $defaultvalue );
2913                 if ($itemrecord) {
2914                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2915                 }
2916                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2917                 if ( !defined $defaultvalue ) {
2918                     $defaultvalue = q||;
2919                 } else {
2920                     $defaultvalue =~ s/"/&quot;/g;
2921                 }
2922
2923                 # search for itemcallnumber if applicable
2924                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2925                     && C4::Context->preference('itemcallnumber') ) {
2926                     my $CNtag      = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2927                     my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2928                     if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2929                         $defaultvalue = $field->subfield($CNsubfield);
2930                     }
2931                 }
2932                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2933                     && $defaultvalues
2934                     && $defaultvalues->{'callnumber'} ) {
2935                     if( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ){
2936                         # if the item record exists, only use default value if the item has no callnumber
2937                         $defaultvalue = $defaultvalues->{callnumber};
2938                     } elsif ( !$itemrecord and $defaultvalues ) {
2939                         # if the item record *doesn't* exists, always use the default value
2940                         $defaultvalue = $defaultvalues->{callnumber};
2941                     }
2942                 }
2943                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2944                     && $defaultvalues
2945                     && $defaultvalues->{'branchcode'} ) {
2946                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2947                         $defaultvalue = $defaultvalues->{branchcode};
2948                     }
2949                 }
2950                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2951                     && $defaultvalues
2952                     && $defaultvalues->{'location'} ) {
2953
2954                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2955                         # if the item record exists, only use default value if the item has no locationr
2956                         $defaultvalue = $defaultvalues->{location};
2957                     } elsif ( !$itemrecord and $defaultvalues ) {
2958                         # if the item record *doesn't* exists, always use the default value
2959                         $defaultvalue = $defaultvalues->{location};
2960                     }
2961                 }
2962                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2963                     my @authorised_values;
2964                     my %authorised_lib;
2965
2966                     # builds list, depending on authorised value...
2967                     #---- branch
2968                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2969                         if (   ( C4::Context->preference("IndependentBranches") )
2970                             && !C4::Context->IsSuperLibrarian() ) {
2971                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2972                             $sth->execute( C4::Context->userenv->{branch} );
2973                             push @authorised_values, ""
2974                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2975                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2976                                 push @authorised_values, $branchcode;
2977                                 $authorised_lib{$branchcode} = $branchname;
2978                             }
2979                         } else {
2980                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2981                             $sth->execute;
2982                             push @authorised_values, ""
2983                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2984                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2985                                 push @authorised_values, $branchcode;
2986                                 $authorised_lib{$branchcode} = $branchname;
2987                             }
2988                         }
2989
2990                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2991                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2992                             $defaultvalue = $defaultvalues->{branchcode};
2993                         }
2994
2995                         #----- itemtypes
2996                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2997                         my $itemtypes = GetItemTypes( style => 'array' );
2998                         push @authorised_values, ""
2999                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3000                         for my $itemtype ( @$itemtypes ) {
3001                             push @authorised_values, $itemtype->{itemtype};
3002                             $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
3003                         }
3004                         #---- class_sources
3005                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
3006                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3007
3008                         my $class_sources = GetClassSources();
3009                         my $default_source = C4::Context->preference("DefaultClassificationSource");
3010
3011                         foreach my $class_source (sort keys %$class_sources) {
3012                             next unless $class_sources->{$class_source}->{'used'} or
3013                                         ($class_source eq $default_source);
3014                             push @authorised_values, $class_source;
3015                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
3016                         }
3017
3018                         $defaultvalue = $default_source;
3019
3020                         #---- "true" authorised value
3021                     } else {
3022                         $authorised_values_sth->execute(
3023                             $tagslib->{$tag}->{$subfield}->{authorised_value},
3024                             $branch_limit ? $branch_limit : ()
3025                         );
3026                         push @authorised_values, ""
3027                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3028                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
3029                             push @authorised_values, $value;
3030                             $authorised_lib{$value} = $lib;
3031                         }
3032                     }
3033                     $subfield_data{marc_value} = {
3034                         type    => 'select',
3035                         values  => \@authorised_values,
3036                         default => "$defaultvalue",
3037                         labels  => \%authorised_lib,
3038                     };
3039                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
3040                 # it is a plugin
3041                     require Koha::FrameworkPlugin;
3042                     my $plugin = Koha::FrameworkPlugin->new({
3043                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
3044                         item_style => 1,
3045                     });
3046                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
3047                     $plugin->build( $pars );
3048                     if( !$plugin->errstr ) {
3049                         #TODO Move html to template; see report 12176/13397
3050                         my $tab= $plugin->noclick? '-1': '';
3051                         my $class= $plugin->noclick? ' disabled': '';
3052                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
3053                         $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;
3054                     } else {
3055                         warn $plugin->errstr;
3056                         $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
3057                     }
3058                 }
3059                 elsif ( $tag eq '' ) {       # it's an hidden field
3060                     $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" />);
3061                 }
3062                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
3063                     $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" />);
3064                 }
3065                 elsif ( length($defaultvalue) > 100
3066                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
3067                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
3068                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
3069                                   500 <= $tag && $tag < 600                     )
3070                           ) {
3071                     # oversize field (textarea)
3072                     $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");
3073                 } else {
3074                     $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
3075                 }
3076                 push( @loop_data, \%subfield_data );
3077             }
3078         }
3079     }
3080     my $itemnumber;
3081     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
3082         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
3083     }
3084     return {
3085         'itemtagfield'    => $itemtagfield,
3086         'itemtagsubfield' => $itemtagsubfield,
3087         'itemnumber'      => $itemnumber,
3088         'iteminformation' => \@loop_data
3089     };
3090 }
3091
3092 sub ToggleNewStatus {
3093     my ( $params ) = @_;
3094     my @rules = @{ $params->{rules} };
3095     my $report_only = $params->{report_only};
3096
3097     my $dbh = C4::Context->dbh;
3098     my @errors;
3099     my @item_columns = map { "items.$_" } Koha::Items->columns;
3100     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
3101     my $report;
3102     for my $rule ( @rules ) {
3103         my $age = $rule->{age};
3104         my $conditions = $rule->{conditions};
3105         my $substitutions = $rule->{substitutions};
3106         my @params;
3107
3108         my $query = q|
3109             SELECT items.biblionumber, items.itemnumber
3110             FROM items
3111             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
3112             WHERE 1
3113         |;
3114         for my $condition ( @$conditions ) {
3115             if (
3116                  grep {/^$condition->{field}$/} @item_columns
3117               or grep {/^$condition->{field}$/} @biblioitem_columns
3118             ) {
3119                 if ( $condition->{value} =~ /\|/ ) {
3120                     my @values = split /\|/, $condition->{value};
3121                     $query .= qq| AND $condition->{field} IN (|
3122                         . join( ',', ('?') x scalar @values )
3123                         . q|)|;
3124                     push @params, @values;
3125                 } else {
3126                     $query .= qq| AND $condition->{field} = ?|;
3127                     push @params, $condition->{value};
3128                 }
3129             }
3130         }
3131         if ( defined $age ) {
3132             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
3133             push @params, $age;
3134         }
3135         my $sth = $dbh->prepare($query);
3136         $sth->execute( @params );
3137         while ( my $values = $sth->fetchrow_hashref ) {
3138             my $biblionumber = $values->{biblionumber};
3139             my $itemnumber = $values->{itemnumber};
3140             my $item = C4::Items::GetItem( $itemnumber );
3141             for my $substitution ( @$substitutions ) {
3142                 next unless $substitution->{field};
3143                 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
3144                     unless $report_only;
3145                 push @{ $report->{$itemnumber} }, $substitution;
3146             }
3147         }
3148     }
3149
3150     return $report;
3151 }
3152
3153
3154 1;