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