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