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