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