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