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