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