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