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