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