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