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