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