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