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