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