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