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