C4::Items -- removed two warns
[koha.git] / C4 / Items.pm
1 package C4::Items;
2
3 # Copyright 2007 LibLime, Inc.
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA  02111-1307 USA
19
20 use strict;
21
22 use C4::Context;
23 use C4::Koha;
24 use C4::Biblio;
25 use C4::Dates qw/format_date format_date_in_iso/;
26 use MARC::Record;
27 use C4::ClassSource;
28 use C4::Log;
29 use C4::Branch;
30 require C4::Reserves;
31
32 use vars qw($VERSION @ISA @EXPORT);
33
34 BEGIN {
35     $VERSION = 3.01;
36
37         require Exporter;
38     @ISA = qw( Exporter );
39
40     # function exports
41     @EXPORT = qw(
42         GetItem
43         AddItemFromMarc
44         AddItem
45         AddItemBatchFromMarc
46         ModItemFromMarc
47         ModItem
48         ModDateLastSeen
49         ModItemTransfer
50         DelItem
51     
52         CheckItemPreSave
53     
54         GetItemStatus
55         GetItemLocation
56         GetLostItems
57         GetItemsForInventory
58         GetItemsCount
59         GetItemInfosOf
60         GetItemsByBiblioitemnumber
61         GetItemsInfo
62         get_itemnumbers_of
63         GetItemnumberFromBarcode
64     );
65 }
66
67 =head1 NAME
68
69 C4::Items - item management functions
70
71 =head1 DESCRIPTION
72
73 This module contains an API for manipulating item 
74 records in Koha, and is used by cataloguing, circulation,
75 acquisitions, and serials management.
76
77 A Koha item record is stored in two places: the
78 items table and embedded in a MARC tag in the XML
79 version of the associated bib record in C<biblioitems.marcxml>.
80 This is done to allow the item information to be readily
81 indexed (e.g., by Zebra), but means that each item
82 modification transaction must keep the items table
83 and the MARC XML in sync at all times.
84
85 Consequently, all code that creates, modifies, or deletes
86 item records B<must> use an appropriate function from 
87 C<C4::Items>.  If no existing function is suitable, it is
88 better to add one to C<C4::Items> than to use add
89 one-off SQL statements to add or modify items.
90
91 The items table will be considered authoritative.  In other
92 words, if there is ever a discrepancy between the items
93 table and the MARC XML, the items table should be considered
94 accurate.
95
96 =head1 HISTORICAL NOTE
97
98 Most of the functions in C<C4::Items> were originally in
99 the C<C4::Biblio> module.
100
101 =head1 CORE EXPORTED FUNCTIONS
102
103 The following functions are meant for use by users
104 of C<C4::Items>
105
106 =cut
107
108 =head2 GetItem
109
110 =over 4
111
112 $item = GetItem($itemnumber,$barcode,$serial);
113
114 =back
115
116 Return item information, for a given itemnumber or barcode.
117 The return value is a hashref mapping item column
118 names to values.  If C<$serial> is true, include serial publication data.
119
120 =cut
121
122 sub GetItem {
123     my ($itemnumber,$barcode, $serial) = @_;
124     my $dbh = C4::Context->dbh;
125         my $data;
126     if ($itemnumber) {
127         my $sth = $dbh->prepare("
128             SELECT * FROM items 
129             WHERE itemnumber = ?");
130         $sth->execute($itemnumber);
131         $data = $sth->fetchrow_hashref;
132     } else {
133         my $sth = $dbh->prepare("
134             SELECT * FROM items 
135             WHERE barcode = ?"
136             );
137         $sth->execute($barcode);                
138         $data = $sth->fetchrow_hashref;
139     }
140     if ( $serial) {      
141     my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
142         $ssth->execute($data->{'itemnumber'}) ;
143         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
144                 warn $data->{'serialseq'} , $data->{'publisheddate'};
145     }           
146     return $data;
147 }    # sub GetItem
148
149 =head2 AddItemFromMarc
150
151 =over 4
152
153 my ($biblionumber, $biblioitemnumber, $itemnumber) 
154     = AddItemFromMarc($source_item_marc, $biblionumber);
155
156 =back
157
158 Given a MARC::Record object containing an embedded item
159 record and a biblionumber, create a new item record.
160
161 =cut
162
163 sub AddItemFromMarc {
164     my ( $source_item_marc, $biblionumber ) = @_;
165     my $dbh = C4::Context->dbh;
166
167     # parse item hash from MARC
168     my $frameworkcode = GetFrameworkCode( $biblionumber );
169     my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
170     return AddItem($item, $biblionumber, $dbh, $frameworkcode,$source_item_marc);
171 }
172
173 =head2 AddItem
174
175 =over 4
176
177 my ($biblionumber, $biblioitemnumber, $itemnumber) 
178     = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $original_item_marc]);
179
180 =back
181
182 Given a hash containing item column names as keys,
183 create a new Koha item record.
184
185 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
186 do not need to be supplied for general use; they exist
187 simply to allow them to be picked up from AddItemFromMarc.
188
189 The final optional parameter, C<$original_item_marc>, contains
190 a C<MARC::Record> object containing the original MARC item tag
191 that the item record came from, either from the item editor or
192 the source MARC record during a bulk import.  This is used
193 to supply item-level subfields that are not directly mapped to 
194 a column in the C<items> table but should remain available for
195 display and indexing.
196
197 =cut
198
199 sub AddItem {
200     my $item = shift;
201     my $biblionumber = shift;
202
203     my $dbh           = @_ ? shift : C4::Context->dbh;
204     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
205     my $original_item_marc;  
206     if (@_) {
207         $original_item_marc = shift
208     };
209
210     # needs old biblionumber and biblioitemnumber
211     $item->{'biblionumber'} = $biblionumber;
212     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
213     $sth->execute( $item->{'biblionumber'} );
214     ($item->{'biblioitemnumber'}) = $sth->fetchrow;
215
216     _set_defaults_for_add($item);
217     _set_derived_columns_for_add($item);
218     # FIXME - checks here
219           my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
220     $item->{'itemnumber'} = $itemnumber;
221
222     # create MARC tag representing item and add to bib
223     my $new_item_marc = _marc_from_item_hash($item, $frameworkcode);
224     if ($original_item_marc) {
225         _add_unlinked_marc_fields($new_item_marc,$original_item_marc,$frameworkcode);
226     }
227     _add_item_field_to_biblio($new_item_marc, $item->{'biblionumber'}, $frameworkcode );
228    
229     logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item") 
230         if C4::Context->preference("CataloguingLog");
231     
232     return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
233 }
234
235 =head2 AddItemBatchFromMarc
236
237 =over 4
238
239 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, $biblionumber, $biblioitemnumber, $frameworkcode);
240
241 =back
242
243 Efficiently create item records from a MARC biblio record with
244 embedded item fields.  This routine is suitable for batch jobs.
245
246 This API assumes that the bib record has already been
247 saved to the C<biblio> and C<biblioitems> tables.  It does
248 not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
249 are populated, but it will do so via a call to ModBibiloMarc.
250
251 The goal of this API is to have a similar effect to using AddBiblio
252 and AddItems in succession, but without inefficient repeated
253 parsing of the MARC XML bib record.
254
255 This function returns an arrayref of new itemsnumbers and an arrayref of item
256 errors encountered during the processing.  Each entry in the errors
257 list is a hashref containing the following keys:
258
259 =over 2
260
261 =item item_sequence
262
263 Sequence number of original item tag in the MARC record.
264
265 =item item_barcode
266
267 Item barcode, provide to assist in the construction of
268 useful error messages.
269
270 =item error_condition
271
272 Code representing the error condition.  Can be 'duplicate_barcode',
273 'invalid_homebranch', or 'invalid_holdingbranch'.
274
275 =item error_information
276
277 Additional information appropriate to the error condition.
278
279 =back
280
281 =cut
282
283 sub AddItemBatchFromMarc {
284     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
285     my $error;
286     my @itemnumbers = ();
287     my @errors = ();
288     my $dbh = C4::Context->dbh;
289
290     # loop through the item tags and start creating items
291     my @bad_item_fields = ();
292     my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
293     my $item_sequence_num = 0;
294     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
295         $item_sequence_num++;
296         # we take the item field and stick it into a new
297         # MARC record -- this is required so far because (FIXME)
298         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
299         # and there is no TransformMarcFieldToKoha
300         my $temp_item_marc = MARC::Record->new();
301         $temp_item_marc->append_fields($item_field);
302     
303         # add biblionumber and biblioitemnumber
304         my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' );
305         $item->{'biblionumber'} = $biblionumber;
306         $item->{'biblioitemnumber'} = $biblioitemnumber;
307
308         # check for duplicate barcode
309         my %item_errors = CheckItemPreSave($item);
310         if (%item_errors) {
311             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
312             push @bad_item_fields, $item_field;
313             next ITEMFIELD;
314         }
315
316         _set_defaults_for_add($item);
317         _set_derived_columns_for_add($item);
318         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
319         warn $error if $error;
320         push @itemnumbers, $itemnumber; # FIXME not checking error
321         $item->{'itemnumber'} = $itemnumber;
322
323         &logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item")
324         if C4::Context->preference("CataloguingLog"); 
325
326         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode);
327         # FIXME - add _add_unlinked_marc_fields here?
328         $item_field->replace_with($new_item_marc->field($itemtag));
329     }
330
331     # remove any MARC item fields for rejected items
332     foreach my $item_field (@bad_item_fields) {
333         $record->delete_field($item_field);
334     }
335
336     # update the MARC biblio
337     $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
338
339     return (\@itemnumbers, \@errors);
340 }
341
342 =head2 ModItemFromMarc
343
344 =over 4
345
346 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
347
348 =back
349
350 This function updates an item record based on a supplied
351 C<MARC::Record> object containing an embedded item field.
352 This API is meant for the use of C<additem.pl>; for 
353 other purposes, C<ModItem> should be used.
354
355 =cut
356
357 sub ModItemFromMarc {
358     my $item_marc = shift;
359     my $biblionumber = shift;
360     my $itemnumber = shift;
361
362     my $dbh = C4::Context->dbh;
363     my $frameworkcode = GetFrameworkCode( $biblionumber );
364     my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode );
365    
366     return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $item_marc); 
367 }
368
369 =head2 ModItem
370
371 =over 4
372
373 ModItem({ column => $newvalue }, $biblionumber, $itemnumber[, $original_item_marc]);
374
375 =back
376
377 Change one or more columns in an item record and update
378 the MARC representation of the item.
379
380 The first argument is a hashref mapping from item column
381 names to the new values.  The second and third arguments
382 are the biblionumber and itemnumber, respectively.
383
384 The fourth, optional parameter, C<$original_item_marc>, contains
385 a C<MARC::Record> object containing the original MARC item tag
386 that the item record came from if the item is being modified
387 via the the cataloging item editor.  This is used
388 to supply item-level subfields that are not directly mapped to 
389 a column in the C<items> table but should remain available for
390 display and indexing.
391
392 If one of the changed columns is used to calculate
393 the derived value of a column such as C<items.cn_sort>, 
394 this routine will perform the necessary calculation
395 and set the value.
396
397 =cut
398
399 sub ModItem {
400     my $item = shift;
401     my $biblionumber = shift;
402     my $itemnumber = shift;
403
404     # if $biblionumber is undefined, get it from the current item
405     unless (defined $biblionumber) {
406         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
407     }
408
409     my $dbh           = @_ ? shift : C4::Context->dbh;
410     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
411     
412     my $original_item_marc;  
413     if (@_) {
414         $original_item_marc = shift
415     };
416
417     $item->{'itemnumber'} = $itemnumber or return undef;
418     _set_derived_columns_for_mod($item);
419     _do_column_fixes_for_mod($item);
420     # FIXME add checks
421     # duplicate barcode
422     # attempt to change itemnumber
423     # attempt to change biblionumber (if we want
424     # an API to relink an item to a different bib,
425     # it should be a separate function)
426
427     # update items table
428     _koha_modify_item($item);
429
430     # update biblio MARC XML
431     my $whole_item = GetItem($itemnumber) or die "FAILED GetItem($itemnumber)";
432     my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode) 
433         or die "FAILED _marc_from_item_hash($whole_item, $frameworkcode)";
434     
435     # FIXME - handle case where item is being modified but must carry 
436     # additional unlnked subfields    
437     if ($original_item_marc) {
438         _add_unlinked_marc_fields($new_item_marc,$original_item_marc,$frameworkcode);
439     }
440                   
441     _replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
442         (C4::Context->userenv eq '0') and die "userenv is '0', not hashref";         # logaction line would crash anyway
443         ($new_item_marc       eq '0') and die "$new_item_marc is '0', not hashref";  # logaction line would crash anyway
444     logaction(C4::Context->userenv->{'number'},"CATALOGUING","MODIFY",$itemnumber,$new_item_marc->as_formatted)
445         if C4::Context->preference("CataloguingLog");
446 }
447
448 =head2 ModItemTransfer
449
450 =over 4
451
452 ModItemTransfer($itenumber, $frombranch, $tobranch);
453
454 =back
455
456 Marks an item as being transferred from one branch
457 to another.
458
459 =cut
460
461 sub ModItemTransfer {
462     my ( $itemnumber, $frombranch, $tobranch ) = @_;
463
464     my $dbh = C4::Context->dbh;
465
466     #new entry in branchtransfers....
467     my $sth = $dbh->prepare(
468         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
469         VALUES (?, ?, NOW(), ?)");
470     $sth->execute($itemnumber, $frombranch, $tobranch);
471
472     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
473     ModDateLastSeen($itemnumber);
474     return;
475 }
476
477 =head2 ModDateLastSeen
478
479 =over 4
480
481 ModDateLastSeen($itemnum);
482
483 =back
484
485 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
486 C<$itemnum> is the item number
487
488 =cut
489
490 sub ModDateLastSeen {
491     my ($itemnumber) = @_;
492     
493     my $today = C4::Dates->new();    
494     ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
495 }
496
497 =head2 DelItem
498
499 =over 4
500
501 DelItem($biblionumber, $itemnumber);
502
503 =back
504
505 Exported function (core API) for deleting an item record in Koha.
506
507 =cut
508
509 sub DelItem {
510     my ( $dbh, $biblionumber, $itemnumber ) = @_;
511     
512     # FIXME check the item has no current issues
513     
514     _koha_delete_item( $dbh, $itemnumber );
515
516     # get the MARC record
517     my $record = GetMarcBiblio($biblionumber);
518     my $frameworkcode = GetFrameworkCode($biblionumber);
519
520     # backup the record
521     my $copy2deleted = $dbh->prepare("UPDATE deleteditems SET marc=? WHERE itemnumber=?");
522     $copy2deleted->execute( $record->as_usmarc(), $itemnumber );
523
524     #search item field code
525     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
526     my @fields = $record->field($itemtag);
527
528     # delete the item specified
529     foreach my $field (@fields) {
530         if ( $field->subfield($itemsubfield) eq $itemnumber ) {
531             $record->delete_field($field);
532         }
533     }
534     &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
535     &logaction(C4::Context->userenv->{'number'},"CATALOGUING","DELETE",$itemnumber,"item") 
536         if C4::Context->preference("CataloguingLog");
537 }
538
539 =head2 CheckItemPreSave
540
541 =over 4
542
543     my $item_ref = TransformMarcToKoha($marc, 'items');
544     # do stuff
545     my %errors = CheckItemPreSave($item_ref);
546     if (exists $errors{'duplicate_barcode'}) {
547         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
548     } elsif (exists $errors{'invalid_homebranch'}) {
549         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
550     } elsif (exists $errors{'invalid_holdingbranch'}) {
551         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
552     } else {
553         print "item is OK";
554     }
555
556 =back
557
558 Given a hashref containing item fields, determine if it can be
559 inserted or updated in the database.  Specifically, checks for
560 database integrity issues, and returns a hash containing any
561 of the following keys, if applicable.
562
563 =over 2
564
565 =item duplicate_barcode
566
567 Barcode, if it duplicates one already found in the database.
568
569 =item invalid_homebranch
570
571 Home branch, if not defined in branches table.
572
573 =item invalid_holdingbranch
574
575 Holding branch, if not defined in branches table.
576
577 =back
578
579 This function does NOT implement any policy-related checks,
580 e.g., whether current operator is allowed to save an
581 item that has a given branch code.
582
583 =cut
584
585 sub CheckItemPreSave {
586     my $item_ref = shift;
587
588     my %errors = ();
589
590     # check for duplicate barcode
591     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
592         my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
593         if ($existing_itemnumber) {
594             if (!exists $item_ref->{'itemnumber'}                       # new item
595                 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
596                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
597             }
598         }
599     }
600
601     # check for valid home branch
602     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
603         my $branch_name = GetBranchName($item_ref->{'homebranch'});
604         unless (defined $branch_name) {
605             # relies on fact that branches.branchname is a non-NULL column,
606             # so GetBranchName returns undef only if branch does not exist
607             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
608         }
609     }
610
611     # check for valid holding branch
612     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
613         my $branch_name = GetBranchName($item_ref->{'holdingbranch'});
614         unless (defined $branch_name) {
615             # relies on fact that branches.branchname is a non-NULL column,
616             # so GetBranchName returns undef only if branch does not exist
617             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
618         }
619     }
620
621     return %errors;
622
623 }
624
625 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
626
627 The following functions provide various ways of 
628 getting an item record, a set of item records, or
629 lists of authorized values for certain item fields.
630
631 Some of the functions in this group are candidates
632 for refactoring -- for example, some of the code
633 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
634 has copy-and-paste work.
635
636 =cut
637
638 =head2 GetItemStatus
639
640 =over 4
641
642 $itemstatushash = GetItemStatus($fwkcode);
643
644 =back
645
646 Returns a list of valid values for the
647 C<items.notforloan> field.
648
649 NOTE: does B<not> return an individual item's
650 status.
651
652 Can be MARC dependant.
653 fwkcode is optional.
654 But basically could be can be loan or not
655 Create a status selector with the following code
656
657 =head3 in PERL SCRIPT
658
659 =over 4
660
661 my $itemstatushash = getitemstatus;
662 my @itemstatusloop;
663 foreach my $thisstatus (keys %$itemstatushash) {
664     my %row =(value => $thisstatus,
665                 statusname => $itemstatushash->{$thisstatus}->{'statusname'},
666             );
667     push @itemstatusloop, \%row;
668 }
669 $template->param(statusloop=>\@itemstatusloop);
670
671 =back
672
673 =head3 in TEMPLATE
674
675 =over 4
676
677 <select name="statusloop">
678     <option value="">Default</option>
679 <!-- TMPL_LOOP name="statusloop" -->
680     <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="statusname" --></option>
681 <!-- /TMPL_LOOP -->
682 </select>
683
684 =back
685
686 =cut
687
688 sub GetItemStatus {
689
690     # returns a reference to a hash of references to status...
691     my ($fwk) = @_;
692     my %itemstatus;
693     my $dbh = C4::Context->dbh;
694     my $sth;
695     $fwk = '' unless ($fwk);
696     my ( $tag, $subfield ) =
697       GetMarcFromKohaField( "items.notforloan", $fwk );
698     if ( $tag and $subfield ) {
699         my $sth =
700           $dbh->prepare(
701             "SELECT authorised_value
702             FROM marc_subfield_structure
703             WHERE tagfield=?
704                 AND tagsubfield=?
705                 AND frameworkcode=?
706             "
707           );
708         $sth->execute( $tag, $subfield, $fwk );
709         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
710             my $authvalsth =
711               $dbh->prepare(
712                 "SELECT authorised_value,lib
713                 FROM authorised_values 
714                 WHERE category=? 
715                 ORDER BY lib
716                 "
717               );
718             $authvalsth->execute($authorisedvaluecat);
719             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
720                 $itemstatus{$authorisedvalue} = $lib;
721             }
722             $authvalsth->finish;
723             return \%itemstatus;
724             exit 1;
725         }
726         else {
727
728             #No authvalue list
729             # build default
730         }
731         $sth->finish;
732     }
733
734     #No authvalue list
735     #build default
736     $itemstatus{"1"} = "Not For Loan";
737     return \%itemstatus;
738 }
739
740 =head2 GetItemLocation
741
742 =over 4
743
744 $itemlochash = GetItemLocation($fwk);
745
746 =back
747
748 Returns a list of valid values for the
749 C<items.location> field.
750
751 NOTE: does B<not> return an individual item's
752 location.
753
754 where fwk stands for an optional framework code.
755 Create a location selector with the following code
756
757 =head3 in PERL SCRIPT
758
759 =over 4
760
761 my $itemlochash = getitemlocation;
762 my @itemlocloop;
763 foreach my $thisloc (keys %$itemlochash) {
764     my $selected = 1 if $thisbranch eq $branch;
765     my %row =(locval => $thisloc,
766                 selected => $selected,
767                 locname => $itemlochash->{$thisloc},
768             );
769     push @itemlocloop, \%row;
770 }
771 $template->param(itemlocationloop => \@itemlocloop);
772
773 =back
774
775 =head3 in TEMPLATE
776
777 =over 4
778
779 <select name="location">
780     <option value="">Default</option>
781 <!-- TMPL_LOOP name="itemlocationloop" -->
782     <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
783 <!-- /TMPL_LOOP -->
784 </select>
785
786 =back
787
788 =cut
789
790 sub GetItemLocation {
791
792     # returns a reference to a hash of references to location...
793     my ($fwk) = @_;
794     my %itemlocation;
795     my $dbh = C4::Context->dbh;
796     my $sth;
797     $fwk = '' unless ($fwk);
798     my ( $tag, $subfield ) =
799       GetMarcFromKohaField( "items.location", $fwk );
800     if ( $tag and $subfield ) {
801         my $sth =
802           $dbh->prepare(
803             "SELECT authorised_value
804             FROM marc_subfield_structure 
805             WHERE tagfield=? 
806                 AND tagsubfield=? 
807                 AND frameworkcode=?"
808           );
809         $sth->execute( $tag, $subfield, $fwk );
810         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
811             my $authvalsth =
812               $dbh->prepare(
813                 "SELECT authorised_value,lib
814                 FROM authorised_values
815                 WHERE category=?
816                 ORDER BY lib"
817               );
818             $authvalsth->execute($authorisedvaluecat);
819             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
820                 $itemlocation{$authorisedvalue} = $lib;
821             }
822             $authvalsth->finish;
823             return \%itemlocation;
824             exit 1;
825         }
826         else {
827
828             #No authvalue list
829             # build default
830         }
831         $sth->finish;
832     }
833
834     #No authvalue list
835     #build default
836     $itemlocation{"1"} = "Not For Loan";
837     return \%itemlocation;
838 }
839
840 =head2 GetLostItems
841
842 =over 4
843
844 $items = GetLostItems($where,$orderby);
845
846 =back
847
848 This function get the items lost into C<$items>.
849
850 =over 2
851
852 =item input:
853 C<$where> is a hashref. it containts a field of the items table as key
854 and the value to match as value.
855 C<$orderby> is a field of the items table.
856
857 =item return:
858 C<$items> is a reference to an array full of hasref which keys are items' table column.
859
860 =item usage in the perl script:
861
862 my %where;
863 $where{barcode} = 0001548;
864 my $items = GetLostItems( \%where, "homebranch" );
865 $template->param(itemsloop => $items);
866
867 =back
868
869 =cut
870
871 sub GetLostItems {
872     # Getting input args.
873     my $where   = shift;
874     my $orderby = shift;
875     my $dbh     = C4::Context->dbh;
876
877     my $query   = "
878         SELECT *
879         FROM   items
880         WHERE  itemlost IS NOT NULL
881           AND  itemlost <> 0
882     ";
883     foreach my $key (keys %$where) {
884         $query .= " AND " . $key . " LIKE '%" . $where->{$key} . "%'";
885     }
886     $query .= " ORDER BY ".$orderby if defined $orderby;
887
888     my $sth = $dbh->prepare($query);
889     $sth->execute;
890     my @items;
891     while ( my $row = $sth->fetchrow_hashref ){
892         push @items, $row;
893     }
894     return \@items;
895 }
896
897 =head2 GetItemsForInventory
898
899 =over 4
900
901 $itemlist = GetItemsForInventory($minlocation,$maxlocation,$datelastseen,$offset,$size)
902
903 =back
904
905 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
906
907 The sub returns a list of hashes, containing itemnumber, author, title, barcode & item callnumber.
908 It is ordered by callnumber,title.
909
910 The minlocation & maxlocation parameters are used to specify a range of item callnumbers
911 the datelastseen can be used to specify that you want to see items not seen since a past date only.
912 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
913
914 =cut
915
916 sub GetItemsForInventory {
917     my ( $minlocation, $maxlocation,$location, $datelastseen, $branch, $offset, $size ) = @_;
918     my $dbh = C4::Context->dbh;
919     my $sth;
920     if ($datelastseen) {
921         $datelastseen=format_date_in_iso($datelastseen);  
922         my $query =
923                 "SELECT itemnumber,barcode,itemcallnumber,title,author,biblio.biblionumber,datelastseen
924                  FROM items
925                    LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber 
926                  WHERE itemcallnumber>= ?
927                    AND itemcallnumber <=?
928                    AND (datelastseen< ? OR datelastseen IS NULL)";
929         $query.= " AND items.location=".$dbh->quote($location) if $location;
930         $query.= " AND items.homebranch=".$dbh->quote($branch) if $branch;
931         $query .= " ORDER BY itemcallnumber,title";
932         $sth = $dbh->prepare($query);
933         $sth->execute( $minlocation, $maxlocation, $datelastseen );
934     }
935     else {
936         my $query ="
937                 SELECT itemnumber,barcode,itemcallnumber,biblio.biblionumber,title,author,datelastseen
938                 FROM items 
939                   LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber 
940                 WHERE itemcallnumber>= ?
941                   AND itemcallnumber <=?";
942         $query.= " AND items.location=".$dbh->quote($location) if $location;
943         $query.= " AND items.homebranch=".$dbh->quote($branch) if $branch;
944         $query .= " ORDER BY itemcallnumber,title";
945         $sth = $dbh->prepare($query);
946         $sth->execute( $minlocation, $maxlocation );
947     }
948     my @results;
949     while ( my $row = $sth->fetchrow_hashref ) {
950         $offset-- if ($offset);
951         $row->{datelastseen}=format_date($row->{datelastseen});
952         if ( ( !$offset ) && $size ) {
953             push @results, $row;
954             $size--;
955         }
956     }
957     return \@results;
958 }
959
960 =head2 GetItemsCount
961
962 =over 4
963 $count = &GetItemsCount( $biblionumber);
964
965 =back
966
967 This function return count of item with $biblionumber
968
969 =cut
970
971 sub GetItemsCount {
972     my ( $biblionumber ) = @_;
973     my $dbh = C4::Context->dbh;
974     my $query = "SELECT count(*)
975           FROM  items 
976           WHERE biblionumber=?";
977     my $sth = $dbh->prepare($query);
978     $sth->execute($biblionumber);
979     my $count = $sth->fetchrow;  
980     $sth->finish;
981     return ($count);
982 }
983
984 =head2 GetItemInfosOf
985
986 =over 4
987
988 GetItemInfosOf(@itemnumbers);
989
990 =back
991
992 =cut
993
994 sub GetItemInfosOf {
995     my @itemnumbers = @_;
996
997     my $query = '
998         SELECT *
999         FROM items
1000         WHERE itemnumber IN (' . join( ',', @itemnumbers ) . ')
1001     ';
1002     return get_infos_of( $query, 'itemnumber' );
1003 }
1004
1005 =head2 GetItemsByBiblioitemnumber
1006
1007 =over 4
1008
1009 GetItemsByBiblioitemnumber($biblioitemnumber);
1010
1011 =back
1012
1013 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1014 Called by C<C4::XISBN>
1015
1016 =cut
1017
1018 sub GetItemsByBiblioitemnumber {
1019     my ( $bibitem ) = @_;
1020     my $dbh = C4::Context->dbh;
1021     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1022     # Get all items attached to a biblioitem
1023     my $i = 0;
1024     my @results; 
1025     $sth->execute($bibitem) || die $sth->errstr;
1026     while ( my $data = $sth->fetchrow_hashref ) {  
1027         # Foreach item, get circulation information
1028         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1029                                    WHERE itemnumber = ?
1030                                    AND returndate is NULL
1031                                    AND issues.borrowernumber = borrowers.borrowernumber"
1032         );
1033         $sth2->execute( $data->{'itemnumber'} );
1034         if ( my $data2 = $sth2->fetchrow_hashref ) {
1035             # if item is out, set the due date and who it is out too
1036             $data->{'date_due'}   = $data2->{'date_due'};
1037             $data->{'cardnumber'} = $data2->{'cardnumber'};
1038             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1039         }
1040         else {
1041             # set date_due to blank, so in the template we check itemlost, and wthdrawn 
1042             $data->{'date_due'} = '';                                                                                                         
1043         }    # else         
1044         $sth2->finish;
1045         # Find the last 3 people who borrowed this item.                  
1046         my $query2 = "SELECT * FROM issues, borrowers WHERE itemnumber = ?
1047                       AND issues.borrowernumber = borrowers.borrowernumber
1048                       AND returndate is not NULL
1049                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1050         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1051         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1052         my $i2 = 0;
1053         while ( my $data2 = $sth2->fetchrow_hashref ) {
1054             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1055             $data->{"card$i2"}      = $data2->{'cardnumber'};
1056             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1057             $i2++;
1058         }
1059         $sth2->finish;
1060         push(@results,$data);
1061     } 
1062     $sth->finish;
1063     return (\@results); 
1064 }
1065
1066 =head2 GetItemsInfo
1067
1068 =over 4
1069
1070 @results = GetItemsInfo($biblionumber, $type);
1071
1072 =back
1073
1074 Returns information about books with the given biblionumber.
1075
1076 C<$type> may be either C<intra> or anything else. If it is not set to
1077 C<intra>, then the search will exclude lost, very overdue, and
1078 withdrawn items.
1079
1080 C<GetItemsInfo> returns a list of references-to-hash. Each element
1081 contains a number of keys. Most of them are table items from the
1082 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1083 Koha database. Other keys include:
1084
1085 =over 2
1086
1087 =item C<$data-E<gt>{branchname}>
1088
1089 The name (not the code) of the branch to which the book belongs.
1090
1091 =item C<$data-E<gt>{datelastseen}>
1092
1093 This is simply C<items.datelastseen>, except that while the date is
1094 stored in YYYY-MM-DD format in the database, here it is converted to
1095 DD/MM/YYYY format. A NULL date is returned as C<//>.
1096
1097 =item C<$data-E<gt>{datedue}>
1098
1099 =item C<$data-E<gt>{class}>
1100
1101 This is the concatenation of C<biblioitems.classification>, the book's
1102 Dewey code, and C<biblioitems.subclass>.
1103
1104 =item C<$data-E<gt>{ocount}>
1105
1106 I think this is the number of copies of the book available.
1107
1108 =item C<$data-E<gt>{order}>
1109
1110 If this is set, it is set to C<One Order>.
1111
1112 =back
1113
1114 =cut
1115
1116 sub GetItemsInfo {
1117     my ( $biblionumber, $type ) = @_;
1118     my $dbh   = C4::Context->dbh;
1119     my $query = "SELECT *,items.notforloan as itemnotforloan
1120                  FROM items 
1121                  LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1122                  LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
1123     $query .=  (C4::Context->preference('item-level_itypes')) ?
1124                      " LEFT JOIN itemtypes on items.itype = itemtypes.itemtype "
1125                     : " LEFT JOIN itemtypes on biblioitems.itemtype = itemtypes.itemtype ";
1126     $query .= "WHERE items.biblionumber = ? ORDER BY items.dateaccessioned desc" ;
1127     my $sth = $dbh->prepare($query);
1128     $sth->execute($biblionumber);
1129     my $i = 0;
1130     my @results;
1131     my ( $date_due, $count_reserves, $serial );
1132
1133     my $isth    = $dbh->prepare(
1134         "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
1135         FROM   issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
1136         WHERE  itemnumber = ?
1137             AND returndate IS NULL"
1138        );
1139         my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? "); 
1140         while ( my $data = $sth->fetchrow_hashref ) {
1141         my $datedue = '';
1142         $isth->execute( $data->{'itemnumber'} );
1143         if ( my $idata = $isth->fetchrow_hashref ) {
1144             $data->{borrowernumber} = $idata->{borrowernumber};
1145             $data->{cardnumber}     = $idata->{cardnumber};
1146             $data->{surname}     = $idata->{surname};
1147             $data->{firstname}     = $idata->{firstname};
1148             $datedue                = $idata->{'date_due'};
1149         if (C4::Context->preference("IndependantBranches")){
1150         my $userenv = C4::Context->userenv;
1151         if ( ($userenv) && ( $userenv->{flags} != 1 ) ) { 
1152             $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
1153         }
1154         }
1155         }
1156                 if ( $data->{'serial'}) {       
1157                         $ssth->execute($data->{'itemnumber'}) ;
1158                         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
1159                         $serial = 1;
1160         }
1161                 if ( $datedue eq '' ) {
1162             my ( $restype, $reserves ) =
1163               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
1164             if ($restype) {
1165                 $count_reserves = $restype;
1166             }
1167         }
1168         $isth->finish;
1169         $ssth->finish;
1170         #get branch information.....
1171         my $bsth = $dbh->prepare(
1172             "SELECT * FROM branches WHERE branchcode = ?
1173         "
1174         );
1175         $bsth->execute( $data->{'holdingbranch'} );
1176         if ( my $bdata = $bsth->fetchrow_hashref ) {
1177             $data->{'branchname'} = $bdata->{'branchname'};
1178         }
1179         $data->{'datedue'}        = $datedue;
1180         $data->{'count_reserves'} = $count_reserves;
1181
1182         # get notforloan complete status if applicable
1183         my $sthnflstatus = $dbh->prepare(
1184             'SELECT authorised_value
1185             FROM   marc_subfield_structure
1186             WHERE  kohafield="items.notforloan"
1187         '
1188         );
1189
1190         $sthnflstatus->execute;
1191         my ($authorised_valuecode) = $sthnflstatus->fetchrow;
1192         if ($authorised_valuecode) {
1193             $sthnflstatus = $dbh->prepare(
1194                 "SELECT lib FROM authorised_values
1195                  WHERE  category=?
1196                  AND authorised_value=?"
1197             );
1198             $sthnflstatus->execute( $authorised_valuecode,
1199                 $data->{itemnotforloan} );
1200             my ($lib) = $sthnflstatus->fetchrow;
1201             $data->{notforloanvalue} = $lib;
1202         }
1203
1204         # my stack procedures
1205         my $stackstatus = $dbh->prepare(
1206             'SELECT authorised_value
1207              FROM   marc_subfield_structure
1208              WHERE  kohafield="items.stack"
1209         '
1210         );
1211         $stackstatus->execute;
1212
1213         ($authorised_valuecode) = $stackstatus->fetchrow;
1214         if ($authorised_valuecode) {
1215             $stackstatus = $dbh->prepare(
1216                 "SELECT lib
1217                  FROM   authorised_values
1218                  WHERE  category=?
1219                  AND    authorised_value=?
1220             "
1221             );
1222             $stackstatus->execute( $authorised_valuecode, $data->{stack} );
1223             my ($lib) = $stackstatus->fetchrow;
1224             $data->{stack} = $lib;
1225         }
1226         # Find the last 3 people who borrowed this item.
1227         my $sth2 = $dbh->prepare("SELECT * FROM issues,borrowers
1228                                     WHERE itemnumber = ?
1229                                     AND issues.borrowernumber = borrowers.borrowernumber
1230                                     AND returndate IS NOT NULL LIMIT 3");
1231         $sth2->execute($data->{'itemnumber'});
1232         my $ii = 0;
1233         while (my $data2 = $sth2->fetchrow_hashref()) {
1234             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1235             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1236             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1237             $ii++;
1238         }
1239
1240         $results[$i] = $data;
1241         $i++;
1242     }
1243     $sth->finish;
1244         if($serial) {
1245                 return( sort { $b->{'publisheddate'} cmp $a->{'publisheddate'} } @results );
1246         } else {
1247         return (@results);
1248         }
1249 }
1250
1251 =head2 get_itemnumbers_of
1252
1253 =over 4
1254
1255 my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1256
1257 =back
1258
1259 Given a list of biblionumbers, return the list of corresponding itemnumbers
1260 for each biblionumber.
1261
1262 Return a reference on a hash where keys are biblionumbers and values are
1263 references on array of itemnumbers.
1264
1265 =cut
1266
1267 sub get_itemnumbers_of {
1268     my @biblionumbers = @_;
1269
1270     my $dbh = C4::Context->dbh;
1271
1272     my $query = '
1273         SELECT itemnumber,
1274             biblionumber
1275         FROM items
1276         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1277     ';
1278     my $sth = $dbh->prepare($query);
1279     $sth->execute(@biblionumbers);
1280
1281     my %itemnumbers_of;
1282
1283     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1284         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1285     }
1286
1287     return \%itemnumbers_of;
1288 }
1289
1290 =head2 GetItemnumberFromBarcode
1291
1292 =over 4
1293
1294 $result = GetItemnumberFromBarcode($barcode);
1295
1296 =back
1297
1298 =cut
1299
1300 sub GetItemnumberFromBarcode {
1301     my ($barcode) = @_;
1302     my $dbh = C4::Context->dbh;
1303
1304     my $rq =
1305       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1306     $rq->execute($barcode);
1307     my ($result) = $rq->fetchrow;
1308     return ($result);
1309 }
1310
1311 =head1 LIMITED USE FUNCTIONS
1312
1313 The following functions, while part of the public API,
1314 are not exported.  This is generally because they are
1315 meant to be used by only one script for a specific
1316 purpose, and should not be used in any other context
1317 without careful thought.
1318
1319 =cut
1320
1321 =head2 GetMarcItem
1322
1323 =over 4
1324
1325 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1326
1327 =back
1328
1329 Returns MARC::Record of the item passed in parameter.
1330 This function is meant for use only in C<cataloguing/additem.pl>,
1331 where it is needed to support that script's MARC-like
1332 editor.
1333
1334 =cut
1335
1336 sub GetMarcItem {
1337     my ( $biblionumber, $itemnumber ) = @_;
1338
1339     # GetMarcItem has been revised so that it does the following:
1340     #  1. Gets the item information from the items table.
1341     #  2. Converts it to a MARC field for storage in the bib record.
1342     #
1343     # The previous behavior was:
1344     #  1. Get the bib record.
1345     #  2. Return the MARC tag corresponding to the item record.
1346     #
1347     # The difference is that one treats the items row as authoritative,
1348     # while the other treats the MARC representation as authoritative
1349     # under certain circumstances.
1350
1351     my $itemrecord = GetItem($itemnumber);
1352
1353     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1354     # Also, don't emit a subfield if the underlying field is blank.
1355     my $mungeditem = { map {  $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  } keys %{ $itemrecord } };
1356     my $itemmarc = TransformKohaToMarc($mungeditem);
1357     
1358     my $marc = GetMarcBiblio($biblionumber);
1359     my $frameworkcode=GetFrameworkCode($biblionumber);
1360     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
1361     foreach my $field ($marc->field($itemtag)){
1362         if ($field->subfield($itemsubfield)==$itemnumber){
1363             my $tmpmarc=MARC::Record->new();
1364             $tmpmarc->append_fields($field);
1365             _add_unlinked_marc_fields($itemmarc,$tmpmarc,$frameworkcode);
1366         }    
1367     }  
1368     return $itemmarc;
1369
1370 }
1371
1372 =head1 PRIVATE FUNCTIONS AND VARIABLES
1373
1374 The following functions are not meant to be called
1375 directly, but are documented in order to explain
1376 the inner workings of C<C4::Items>.
1377
1378 =cut
1379
1380 =head2 %derived_columns
1381
1382 This hash keeps track of item columns that
1383 are strictly derived from other columns in
1384 the item record and are not meant to be set
1385 independently.
1386
1387 Each key in the hash should be the name of a
1388 column (as named by TransformMarcToKoha).  Each
1389 value should be hashref whose keys are the
1390 columns on which the derived column depends.  The
1391 hashref should also contain a 'BUILDER' key
1392 that is a reference to a sub that calculates
1393 the derived value.
1394
1395 =cut
1396
1397 my %derived_columns = (
1398     'items.cn_sort' => {
1399         'itemcallnumber' => 1,
1400         'items.cn_source' => 1,
1401         'BUILDER' => \&_calc_items_cn_sort,
1402     }
1403 );
1404
1405 =head2 _set_derived_columns_for_add 
1406
1407 =over 4
1408
1409 _set_derived_column_for_add($item);
1410
1411 =back
1412
1413 Given an item hash representing a new item to be added,
1414 calculate any derived columns.  Currently the only
1415 such column is C<items.cn_sort>.
1416
1417 =cut
1418
1419 sub _set_derived_columns_for_add {
1420     my $item = shift;
1421
1422     foreach my $column (keys %derived_columns) {
1423         my $builder = $derived_columns{$column}->{'BUILDER'};
1424         my $source_values = {};
1425         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1426             next if $source_column eq 'BUILDER';
1427             $source_values->{$source_column} = $item->{$source_column};
1428         }
1429         $builder->($item, $source_values);
1430     }
1431 }
1432
1433 =head2 _set_derived_columns_for_mod 
1434
1435 =over 4
1436
1437 _set_derived_column_for_mod($item);
1438
1439 =back
1440
1441 Given an item hash representing a new item to be modified.
1442 calculate any derived columns.  Currently the only
1443 such column is C<items.cn_sort>.
1444
1445 This routine differs from C<_set_derived_columns_for_add>
1446 in that it needs to handle partial item records.  In other
1447 words, the caller of C<ModItem> may have supplied only one
1448 or two columns to be changed, so this function needs to
1449 determine whether any of the columns to be changed affect
1450 any of the derived columns.  Also, if a derived column
1451 depends on more than one column, but the caller is not
1452 changing all of then, this routine retrieves the unchanged
1453 values from the database in order to ensure a correct
1454 calculation.
1455
1456 =cut
1457
1458 sub _set_derived_columns_for_mod {
1459     my $item = shift;
1460
1461     foreach my $column (keys %derived_columns) {
1462         my $builder = $derived_columns{$column}->{'BUILDER'};
1463         my $source_values = {};
1464         my %missing_sources = ();
1465         my $must_recalc = 0;
1466         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1467             next if $source_column eq 'BUILDER';
1468             if (exists $item->{$source_column}) {
1469                 $must_recalc = 1;
1470                 $source_values->{$source_column} = $item->{$source_column};
1471             } else {
1472                 $missing_sources{$source_column} = 1;
1473             }
1474         }
1475         if ($must_recalc) {
1476             foreach my $source_column (keys %missing_sources) {
1477                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1478             }
1479             $builder->($item, $source_values);
1480         }
1481     }
1482 }
1483
1484 =head2 _do_column_fixes_for_mod
1485
1486 =over 4
1487
1488 _do_column_fixes_for_mod($item);
1489
1490 =back
1491
1492 Given an item hashref containing one or more
1493 columns to modify, fix up certain values.
1494 Specifically, set to 0 any passed value
1495 of C<notforloan>, C<damaged>, C<itemlost>, or
1496 C<wthdrawn> that is either undefined or
1497 contains the empty string.
1498
1499 =cut
1500
1501 sub _do_column_fixes_for_mod {
1502     my $item = shift;
1503
1504     if (exists $item->{'notforloan'} and
1505         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1506         $item->{'notforloan'} = 0;
1507     }
1508     if (exists $item->{'damaged'} and
1509         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1510         $item->{'damaged'} = 0;
1511     }
1512     if (exists $item->{'itemlost'} and
1513         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1514         $item->{'itemlost'} = 0;
1515     }
1516     if (exists $item->{'wthdrawn'} and
1517         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
1518         $item->{'wthdrawn'} = 0;
1519     }
1520 }
1521
1522 =head2 _get_single_item_column
1523
1524 =over 4
1525
1526 _get_single_item_column($column, $itemnumber);
1527
1528 =back
1529
1530 Retrieves the value of a single column from an C<items>
1531 row specified by C<$itemnumber>.
1532
1533 =cut
1534
1535 sub _get_single_item_column {
1536     my $column = shift;
1537     my $itemnumber = shift;
1538     
1539     my $dbh = C4::Context->dbh;
1540     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1541     $sth->execute($itemnumber);
1542     my ($value) = $sth->fetchrow();
1543     return $value; 
1544 }
1545
1546 =head2 _calc_items_cn_sort
1547
1548 =over 4
1549
1550 _calc_items_cn_sort($item, $source_values);
1551
1552 =back
1553
1554 Helper routine to calculate C<items.cn_sort>.
1555
1556 =cut
1557
1558 sub _calc_items_cn_sort {
1559     my $item = shift;
1560     my $source_values = shift;
1561
1562     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1563 }
1564
1565 =head2 _set_defaults_for_add 
1566
1567 =over 4
1568
1569 _set_defaults_for_add($item_hash);
1570
1571 =back
1572
1573 Given an item hash representing an item to be added, set
1574 correct default values for columns whose default value
1575 is not handled by the DBMS.  This includes the following
1576 columns:
1577
1578 =over 2
1579
1580 =item * 
1581
1582 C<items.dateaccessioned>
1583
1584 =item *
1585
1586 C<items.notforloan>
1587
1588 =item *
1589
1590 C<items.damaged>
1591
1592 =item *
1593
1594 C<items.itemlost>
1595
1596 =item *
1597
1598 C<items.wthdrawn>
1599
1600 =back
1601
1602 =cut
1603
1604 sub _set_defaults_for_add {
1605     my $item = shift;
1606
1607     # if dateaccessioned is provided, use it. Otherwise, set to NOW()
1608     if (!(exists $item->{'dateaccessioned'}) || 
1609          ($item->{'dateaccessioned'} eq '')) {
1610         # FIXME add check for invalid date
1611         my $today = C4::Dates->new();    
1612         $item->{'dateaccessioned'} =  $today->output("iso"); #TODO: check time issues
1613     }
1614
1615     # various item status fields cannot be null
1616     $item->{'notforloan'} = 0 unless exists $item->{'notforloan'} and defined $item->{'notforloan'} and $item->{'notforloan'} ne '';
1617     $item->{'damaged'}    = 0 unless exists $item->{'damaged'}    and defined $item->{'damaged'}    and $item->{'damaged'} ne '';
1618     $item->{'itemlost'}   = 0 unless exists $item->{'itemlost'}   and defined $item->{'itemlost'}   and $item->{'itemlost'} ne '';
1619     $item->{'wthdrawn'}   = 0 unless exists $item->{'wthdrawn'}   and defined $item->{'wthdrawn'}   and $item->{'wthdrawn'} ne '';
1620 }
1621
1622 =head2 _koha_new_item
1623
1624 =over 4
1625
1626 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1627
1628 =back
1629
1630 Perform the actual insert into the C<items> table.
1631
1632 =cut
1633
1634 sub _koha_new_item {
1635     my ( $item, $barcode ) = @_;
1636     my $dbh=C4::Context->dbh;  
1637     my $error;
1638     my $query =
1639            "INSERT INTO items SET
1640             biblionumber        = ?,
1641             biblioitemnumber    = ?,
1642             barcode             = ?,
1643             dateaccessioned     = ?,
1644             booksellerid        = ?,
1645             homebranch          = ?,
1646             price               = ?,
1647             replacementprice    = ?,
1648             replacementpricedate = NOW(),
1649             datelastborrowed    = ?,
1650             datelastseen        = NOW(),
1651             stack               = ?,
1652             notforloan          = ?,
1653             damaged             = ?,
1654             itemlost            = ?,
1655             wthdrawn            = ?,
1656             itemcallnumber      = ?,
1657             restricted          = ?,
1658             itemnotes           = ?,
1659             holdingbranch       = ?,
1660             paidfor             = ?,
1661             location            = ?,
1662             onloan              = ?,
1663             issues              = ?,
1664             renewals            = ?,
1665             reserves            = ?,
1666             cn_source           = ?,
1667             cn_sort             = ?,
1668             ccode               = ?,
1669             itype               = ?,
1670             materials           = ?,
1671                         uri                 = ?
1672           ";
1673     my $sth = $dbh->prepare($query);
1674    $sth->execute(
1675             $item->{'biblionumber'},
1676             $item->{'biblioitemnumber'},
1677             $barcode,
1678             $item->{'dateaccessioned'},
1679             $item->{'booksellerid'},
1680             $item->{'homebranch'},
1681             $item->{'price'},
1682             $item->{'replacementprice'},
1683             $item->{datelastborrowed},
1684             $item->{stack},
1685             $item->{'notforloan'},
1686             $item->{'damaged'},
1687             $item->{'itemlost'},
1688             $item->{'wthdrawn'},
1689             $item->{'itemcallnumber'},
1690             $item->{'restricted'},
1691             $item->{'itemnotes'},
1692             $item->{'holdingbranch'},
1693             $item->{'paidfor'},
1694             $item->{'location'},
1695             $item->{'onloan'},
1696             $item->{'issues'},
1697             $item->{'renewals'},
1698             $item->{'reserves'},
1699             $item->{'items.cn_source'},
1700             $item->{'items.cn_sort'},
1701             $item->{'ccode'},
1702             $item->{'itype'},
1703             $item->{'materials'},
1704             $item->{'uri'},
1705     );
1706     my $itemnumber = $dbh->{'mysql_insertid'};
1707     if ( defined $sth->errstr ) {
1708         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1709     }
1710     $sth->finish();
1711     return ( $itemnumber, $error );
1712 }
1713
1714 =head2 _koha_modify_item
1715
1716 =over 4
1717
1718 my ($itemnumber,$error) =_koha_modify_item( $item );
1719
1720 =back
1721
1722 Perform the actual update of the C<items> row.  Note that this
1723 routine accepts a hashref specifying the columns to update.
1724
1725 =cut
1726
1727 sub _koha_modify_item {
1728     my ( $item ) = @_;
1729     my $dbh=C4::Context->dbh;  
1730     my $error;
1731
1732     my $query = "UPDATE items SET ";
1733     my @bind;
1734     for my $key ( keys %$item ) {
1735         $query.="$key=?,";
1736         push @bind, $item->{$key};
1737     }
1738     $query =~ s/,$//;
1739     $query .= " WHERE itemnumber=?";
1740     push @bind, $item->{'itemnumber'};
1741     my $sth = C4::Context->dbh->prepare($query);
1742     $sth->execute(@bind);
1743     if ( C4::Context->dbh->errstr ) {
1744         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
1745         warn $error;
1746     }
1747     $sth->finish();
1748     return ($item->{'itemnumber'},$error);
1749 }
1750
1751 =head2 _koha_delete_item
1752
1753 =over 4
1754
1755 _koha_delete_item( $dbh, $itemnum );
1756
1757 =back
1758
1759 Internal function to delete an item record from the koha tables
1760
1761 =cut
1762
1763 sub _koha_delete_item {
1764     my ( $dbh, $itemnum ) = @_;
1765
1766     # save the deleted item to deleteditems table
1767     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1768     $sth->execute($itemnum);
1769     my $data = $sth->fetchrow_hashref();
1770     $sth->finish();
1771     my $query = "INSERT INTO deleteditems SET ";
1772     my @bind  = ();
1773     foreach my $key ( keys %$data ) {
1774         $query .= "$key = ?,";
1775         push( @bind, $data->{$key} );
1776     }
1777     $query =~ s/\,$//;
1778     $sth = $dbh->prepare($query);
1779     $sth->execute(@bind);
1780     $sth->finish();
1781
1782     # delete from items table
1783     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1784     $sth->execute($itemnum);
1785     $sth->finish();
1786     return undef;
1787 }
1788
1789 =head2 _marc_from_item_hash
1790
1791 =over 4
1792
1793 my $item_marc = _marc_from_item_hash($item, $frameworkcode);
1794
1795 =back
1796
1797 Given an item hash representing a complete item record,
1798 create a C<MARC::Record> object containing an embedded
1799 tag representing that item.
1800
1801 =cut
1802
1803 sub _marc_from_item_hash {
1804     my $item = shift;
1805     my $frameworkcode = shift;
1806    
1807     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1808     # Also, don't emit a subfield if the underlying field is blank.
1809     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1810                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1811                                 : ()  } keys %{ $item } }; 
1812
1813     my $item_marc = MARC::Record->new();
1814     foreach my $item_field (keys %{ $mungeditem }) {
1815         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
1816         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1817         if (my $field = $item_marc->field($tag)) {
1818             $field->add_subfields($subfield => $mungeditem->{$item_field});
1819         } else {
1820             $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field});
1821         }
1822     }
1823
1824     return $item_marc;
1825 }
1826
1827 =head2 _add_item_field_to_biblio
1828
1829 =over 4
1830
1831 _add_item_field_to_biblio($item_marc, $biblionumber, $frameworkcode);
1832
1833 =back
1834
1835 Adds the fields from a MARC record containing the
1836 representation of a Koha item record to the MARC
1837 biblio record.  The input C<$item_marc> record
1838 is expect to contain just one field, the embedded
1839 item information field.
1840
1841 =cut
1842
1843 sub _add_item_field_to_biblio {
1844     my ($item_marc, $biblionumber, $frameworkcode) = @_;
1845
1846     my $biblio_marc = GetMarcBiblio($biblionumber);
1847
1848     foreach my $field ($item_marc->fields()) {
1849         $biblio_marc->append_fields($field);
1850     }
1851
1852     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
1853 }
1854
1855 =head2 _replace_item_field_in_biblio
1856
1857 =over
1858
1859 &_replace_item_field_in_biblio($item_marc, $biblionumber, $itemnumber, $frameworkcode)
1860
1861 =back
1862
1863 Given a MARC::Record C<$item_marc> containing one tag with the MARC 
1864 representation of the item, examine the biblio MARC
1865 for the corresponding tag for that item and 
1866 replace it with the tag from C<$item_marc>.
1867
1868 =cut
1869
1870 sub _replace_item_field_in_biblio {
1871     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
1872     my $dbh = C4::Context->dbh;
1873     
1874     # get complete MARC record & replace the item field by the new one
1875     my $completeRecord = GetMarcBiblio($biblionumber);
1876     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
1877     my $itemField = $ItemRecord->field($itemtag);
1878     my @items = $completeRecord->field($itemtag);
1879     my $found = 0;
1880     foreach (@items) {
1881         if ($_->subfield($itemsubfield) eq $itemnumber) {
1882             $_->replace_with($itemField);
1883             $found = 1;
1884         }
1885     }
1886   
1887     unless ($found) { 
1888         # If we haven't found the matching field,
1889         # just add it.  However, this means that
1890         # there is likely a bug.
1891         $completeRecord->append_fields($itemField);
1892     }
1893
1894     # save the record
1895     ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
1896 }
1897
1898 =head2 _repack_item_errors
1899
1900 Add an error message hash generated by C<CheckItemPreSave>
1901 to a list of errors.
1902
1903 =cut
1904
1905 sub _repack_item_errors {
1906     my $item_sequence_num = shift;
1907     my $item_ref = shift;
1908     my $error_ref = shift;
1909
1910     my @repacked_errors = ();
1911
1912     foreach my $error_code (sort keys %{ $error_ref }) {
1913         my $repacked_error = {};
1914         $repacked_error->{'item_sequence'} = $item_sequence_num;
1915         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1916         $repacked_error->{'error_code'} = $error_code;
1917         $repacked_error->{'error_information'} = $error_ref->{$error_code};
1918         push @repacked_errors, $repacked_error;
1919     } 
1920
1921     return @repacked_errors;
1922 }
1923
1924 =head2 _add_unlinked_marc_fields
1925
1926 Adds marc fields to new_item_marc 
1927 from $original_item_marc fields which 
1928 1) are not linked to items table 
1929 2) but still are used in framework 
1930 3) and are provided a value
1931
1932 =cut
1933 sub _add_unlinked_marc_fields{
1934     my $new_item_marc = shift;
1935     my $original_item_marc = shift;
1936     my $frameworkcode = shift;
1937     my $tmp_item_marc =$new_item_marc->clone;  
1938     my $marcstructure = GetMarcStructure(1,$frameworkcode);
1939     foreach my $field ($original_item_marc->fields()){
1940             my $tag = $field->tag();
1941         if ($new_item_marc->fields($tag)){
1942             # It is assumed  that item marc records only have ***one*** tag and that this tag is mandatory.
1943             # So new_item_marc MUST have $field->tag    
1944             foreach my $subfield ($field->subfields()) {
1945                 if (!$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'} && !$tmp_item_marc->subfield($tag,$subfield->[0])) {
1946                                 #$new_item_marc->field($tag)->add_subfields($subfield->[0]=>$subfield->[1]);
1947
1948                             my $ret= eval {$new_item_marc->field($tag)->add_subfields($subfield->[0] => $subfield->[1])};
1949                             if ($@ or !$ret) {
1950                         warn $subfield->[0]."=>".$subfield->[1]."\n".$new_item_marc->as_formatted."\n".$original_item_marc;
1951                     }
1952                 }
1953             }
1954         }
1955     }
1956     return $new_item_marc;      
1957 }
1958
1959 1;