editorial changes to C4/Items.pm
[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            warn $data->{itemnumber};
1142         my $datedue = '';
1143         $isth->execute( $data->{'itemnumber'} );
1144         if ( my $idata = $isth->fetchrow_hashref ) {
1145             $data->{borrowernumber} = $idata->{borrowernumber};
1146             $data->{cardnumber}     = $idata->{cardnumber};
1147             $data->{surname}     = $idata->{surname};
1148             $data->{firstname}     = $idata->{firstname};
1149             $datedue                = $idata->{'date_due'};
1150         if (C4::Context->preference("IndependantBranches")){
1151         my $userenv = C4::Context->userenv;
1152         if ( ($userenv) && ( $userenv->{flags} != 1 ) ) { 
1153             $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
1154         }
1155         }
1156         }
1157                 if ( $data->{'serial'}) {       
1158                         $ssth->execute($data->{'itemnumber'}) ;
1159                         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
1160                         warn $data->{'serialseq'} , $data->{'publisheddate'};
1161                         $serial = 1;
1162         }
1163                 if ( $datedue eq '' ) {
1164             my ( $restype, $reserves ) =
1165               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
1166             if ($restype) {
1167                 $count_reserves = $restype;
1168             }
1169         }
1170         $isth->finish;
1171         $ssth->finish;
1172         #get branch information.....
1173         my $bsth = $dbh->prepare(
1174             "SELECT * FROM branches WHERE branchcode = ?
1175         "
1176         );
1177         $bsth->execute( $data->{'holdingbranch'} );
1178         if ( my $bdata = $bsth->fetchrow_hashref ) {
1179             $data->{'branchname'} = $bdata->{'branchname'};
1180         }
1181         $data->{'datedue'}        = $datedue;
1182         $data->{'count_reserves'} = $count_reserves;
1183
1184         # get notforloan complete status if applicable
1185         my $sthnflstatus = $dbh->prepare(
1186             'SELECT authorised_value
1187             FROM   marc_subfield_structure
1188             WHERE  kohafield="items.notforloan"
1189         '
1190         );
1191
1192         $sthnflstatus->execute;
1193         my ($authorised_valuecode) = $sthnflstatus->fetchrow;
1194         if ($authorised_valuecode) {
1195             $sthnflstatus = $dbh->prepare(
1196                 "SELECT lib FROM authorised_values
1197                  WHERE  category=?
1198                  AND authorised_value=?"
1199             );
1200             $sthnflstatus->execute( $authorised_valuecode,
1201                 $data->{itemnotforloan} );
1202             my ($lib) = $sthnflstatus->fetchrow;
1203             $data->{notforloanvalue} = $lib;
1204         }
1205
1206         # my stack procedures
1207         my $stackstatus = $dbh->prepare(
1208             'SELECT authorised_value
1209              FROM   marc_subfield_structure
1210              WHERE  kohafield="items.stack"
1211         '
1212         );
1213         $stackstatus->execute;
1214
1215         ($authorised_valuecode) = $stackstatus->fetchrow;
1216         if ($authorised_valuecode) {
1217             $stackstatus = $dbh->prepare(
1218                 "SELECT lib
1219                  FROM   authorised_values
1220                  WHERE  category=?
1221                  AND    authorised_value=?
1222             "
1223             );
1224             $stackstatus->execute( $authorised_valuecode, $data->{stack} );
1225             my ($lib) = $stackstatus->fetchrow;
1226             $data->{stack} = $lib;
1227         }
1228         # Find the last 3 people who borrowed this item.
1229         my $sth2 = $dbh->prepare("SELECT * FROM issues,borrowers
1230                                     WHERE itemnumber = ?
1231                                     AND issues.borrowernumber = borrowers.borrowernumber
1232                                     AND returndate IS NOT NULL LIMIT 3");
1233         $sth2->execute($data->{'itemnumber'});
1234         my $ii = 0;
1235         while (my $data2 = $sth2->fetchrow_hashref()) {
1236             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1237             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1238             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1239             $ii++;
1240         }
1241
1242         $results[$i] = $data;
1243         $i++;
1244     }
1245     $sth->finish;
1246         if($serial) {
1247                 return( sort { $b->{'publisheddate'} cmp $a->{'publisheddate'} } @results );
1248         } else {
1249         return (@results);
1250         }
1251 }
1252
1253 =head2 get_itemnumbers_of
1254
1255 =over 4
1256
1257 my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1258
1259 =back
1260
1261 Given a list of biblionumbers, return the list of corresponding itemnumbers
1262 for each biblionumber.
1263
1264 Return a reference on a hash where keys are biblionumbers and values are
1265 references on array of itemnumbers.
1266
1267 =cut
1268
1269 sub get_itemnumbers_of {
1270     my @biblionumbers = @_;
1271
1272     my $dbh = C4::Context->dbh;
1273
1274     my $query = '
1275         SELECT itemnumber,
1276             biblionumber
1277         FROM items
1278         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1279     ';
1280     my $sth = $dbh->prepare($query);
1281     $sth->execute(@biblionumbers);
1282
1283     my %itemnumbers_of;
1284
1285     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1286         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1287     }
1288
1289     return \%itemnumbers_of;
1290 }
1291
1292 =head2 GetItemnumberFromBarcode
1293
1294 =over 4
1295
1296 $result = GetItemnumberFromBarcode($barcode);
1297
1298 =back
1299
1300 =cut
1301
1302 sub GetItemnumberFromBarcode {
1303     my ($barcode) = @_;
1304     my $dbh = C4::Context->dbh;
1305
1306     my $rq =
1307       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1308     $rq->execute($barcode);
1309     my ($result) = $rq->fetchrow;
1310     return ($result);
1311 }
1312
1313 =head1 LIMITED USE FUNCTIONS
1314
1315 The following functions, while part of the public API,
1316 are not exported.  This is generally because they are
1317 meant to be used by only one script for a specific
1318 purpose, and should not be used in any other context
1319 without careful thought.
1320
1321 =cut
1322
1323 =head2 GetMarcItem
1324
1325 =over 4
1326
1327 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1328
1329 =back
1330
1331 Returns MARC::Record of the item passed in parameter.
1332 This function is meant for use only in C<cataloguing/additem.pl>,
1333 where it is needed to support that script's MARC-like
1334 editor.
1335
1336 =cut
1337
1338 sub GetMarcItem {
1339     my ( $biblionumber, $itemnumber ) = @_;
1340
1341     # GetMarcItem has been revised so that it does the following:
1342     #  1. Gets the item information from the items table.
1343     #  2. Converts it to a MARC field for storage in the bib record.
1344     #
1345     # The previous behavior was:
1346     #  1. Get the bib record.
1347     #  2. Return the MARC tag corresponding to the item record.
1348     #
1349     # The difference is that one treats the items row as authoritative,
1350     # while the other treats the MARC representation as authoritative
1351     # under certain circumstances.
1352
1353     my $itemrecord = GetItem($itemnumber);
1354
1355     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1356     # Also, don't emit a subfield if the underlying field is blank.
1357     my $mungeditem = { map {  $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  } keys %{ $itemrecord } };
1358     my $itemmarc = TransformKohaToMarc($mungeditem);
1359     
1360     my $marc = GetMarcBiblio($biblionumber);
1361     my $frameworkcode=GetFrameworkCode($biblionumber);
1362     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
1363     foreach my $field ($marc->field($itemtag)){
1364         if ($field->subfield($itemsubfield)==$itemnumber){
1365             my $tmpmarc=MARC::Record->new();
1366             $tmpmarc->append_fields($field);
1367             _add_unlinked_marc_fields($itemmarc,$tmpmarc,$frameworkcode);
1368         }    
1369     }  
1370     return $itemmarc;
1371
1372 }
1373
1374 =head1 PRIVATE FUNCTIONS AND VARIABLES
1375
1376 The following functions are not meant to be called
1377 directly, but are documented in order to explain
1378 the inner workings of C<C4::Items>.
1379
1380 =cut
1381
1382 =head2 %derived_columns
1383
1384 This hash keeps track of item columns that
1385 are strictly derived from other columns in
1386 the item record and are not meant to be set
1387 independently.
1388
1389 Each key in the hash should be the name of a
1390 column (as named by TransformMarcToKoha).  Each
1391 value should be hashref whose keys are the
1392 columns on which the derived column depends.  The
1393 hashref should also contain a 'BUILDER' key
1394 that is a reference to a sub that calculates
1395 the derived value.
1396
1397 =cut
1398
1399 my %derived_columns = (
1400     'items.cn_sort' => {
1401         'itemcallnumber' => 1,
1402         'items.cn_source' => 1,
1403         'BUILDER' => \&_calc_items_cn_sort,
1404     }
1405 );
1406
1407 =head2 _set_derived_columns_for_add 
1408
1409 =over 4
1410
1411 _set_derived_column_for_add($item);
1412
1413 =back
1414
1415 Given an item hash representing a new item to be added,
1416 calculate any derived columns.  Currently the only
1417 such column is C<items.cn_sort>.
1418
1419 =cut
1420
1421 sub _set_derived_columns_for_add {
1422     my $item = shift;
1423
1424     foreach my $column (keys %derived_columns) {
1425         my $builder = $derived_columns{$column}->{'BUILDER'};
1426         my $source_values = {};
1427         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1428             next if $source_column eq 'BUILDER';
1429             $source_values->{$source_column} = $item->{$source_column};
1430         }
1431         $builder->($item, $source_values);
1432     }
1433 }
1434
1435 =head2 _set_derived_columns_for_mod 
1436
1437 =over 4
1438
1439 _set_derived_column_for_mod($item);
1440
1441 =back
1442
1443 Given an item hash representing a new item to be modified.
1444 calculate any derived columns.  Currently the only
1445 such column is C<items.cn_sort>.
1446
1447 This routine differs from C<_set_derived_columns_for_add>
1448 in that it needs to handle partial item records.  In other
1449 words, the caller of C<ModItem> may have supplied only one
1450 or two columns to be changed, so this function needs to
1451 determine whether any of the columns to be changed affect
1452 any of the derived columns.  Also, if a derived column
1453 depends on more than one column, but the caller is not
1454 changing all of then, this routine retrieves the unchanged
1455 values from the database in order to ensure a correct
1456 calculation.
1457
1458 =cut
1459
1460 sub _set_derived_columns_for_mod {
1461     my $item = shift;
1462
1463     foreach my $column (keys %derived_columns) {
1464         my $builder = $derived_columns{$column}->{'BUILDER'};
1465         my $source_values = {};
1466         my %missing_sources = ();
1467         my $must_recalc = 0;
1468         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1469             next if $source_column eq 'BUILDER';
1470             if (exists $item->{$source_column}) {
1471                 $must_recalc = 1;
1472                 $source_values->{$source_column} = $item->{$source_column};
1473             } else {
1474                 $missing_sources{$source_column} = 1;
1475             }
1476         }
1477         if ($must_recalc) {
1478             foreach my $source_column (keys %missing_sources) {
1479                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1480             }
1481             $builder->($item, $source_values);
1482         }
1483     }
1484 }
1485
1486 =head2 _do_column_fixes_for_mod
1487
1488 =over 4
1489
1490 _do_column_fixes_for_mod($item);
1491
1492 =back
1493
1494 Given an item hashref containing one or more
1495 columns to modify, fix up certain values.
1496 Specifically, set to 0 any passed value
1497 of C<notforloan>, C<damaged>, C<itemlost>, or
1498 C<wthdrawn> that is either undefined or
1499 contains the empty string.
1500
1501 =cut
1502
1503 sub _do_column_fixes_for_mod {
1504     my $item = shift;
1505
1506     if (exists $item->{'notforloan'} and
1507         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1508         $item->{'notforloan'} = 0;
1509     }
1510     if (exists $item->{'damaged'} and
1511         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1512         $item->{'damaged'} = 0;
1513     }
1514     if (exists $item->{'itemlost'} and
1515         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1516         $item->{'itemlost'} = 0;
1517     }
1518     if (exists $item->{'wthdrawn'} and
1519         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
1520         $item->{'wthdrawn'} = 0;
1521     }
1522 }
1523
1524 =head2 _get_single_item_column
1525
1526 =over 4
1527
1528 _get_single_item_column($column, $itemnumber);
1529
1530 =back
1531
1532 Retrieves the value of a single column from an C<items>
1533 row specified by C<$itemnumber>.
1534
1535 =cut
1536
1537 sub _get_single_item_column {
1538     my $column = shift;
1539     my $itemnumber = shift;
1540     
1541     my $dbh = C4::Context->dbh;
1542     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1543     $sth->execute($itemnumber);
1544     my ($value) = $sth->fetchrow();
1545     return $value; 
1546 }
1547
1548 =head2 _calc_items_cn_sort
1549
1550 =over 4
1551
1552 _calc_items_cn_sort($item, $source_values);
1553
1554 =back
1555
1556 Helper routine to calculate C<items.cn_sort>.
1557
1558 =cut
1559
1560 sub _calc_items_cn_sort {
1561     my $item = shift;
1562     my $source_values = shift;
1563
1564     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1565 }
1566
1567 =head2 _set_defaults_for_add 
1568
1569 =over 4
1570
1571 _set_defaults_for_add($item_hash);
1572
1573 =back
1574
1575 Given an item hash representing an item to be added, set
1576 correct default values for columns whose default value
1577 is not handled by the DBMS.  This includes the following
1578 columns:
1579
1580 =over 2
1581
1582 =item * 
1583
1584 C<items.dateaccessioned>
1585
1586 =item *
1587
1588 C<items.notforloan>
1589
1590 =item *
1591
1592 C<items.damaged>
1593
1594 =item *
1595
1596 C<items.itemlost>
1597
1598 =item *
1599
1600 C<items.wthdrawn>
1601
1602 =back
1603
1604 =cut
1605
1606 sub _set_defaults_for_add {
1607     my $item = shift;
1608
1609     # if dateaccessioned is provided, use it. Otherwise, set to NOW()
1610     if (!(exists $item->{'dateaccessioned'}) || 
1611          ($item->{'dateaccessioned'} eq '')) {
1612         # FIXME add check for invalid date
1613         my $today = C4::Dates->new();    
1614         $item->{'dateaccessioned'} =  $today->output("iso"); #TODO: check time issues
1615     }
1616
1617     # various item status fields cannot be null
1618     $item->{'notforloan'} = 0 unless exists $item->{'notforloan'} and defined $item->{'notforloan'} and $item->{'notforloan'} ne '';
1619     $item->{'damaged'}    = 0 unless exists $item->{'damaged'}    and defined $item->{'damaged'}    and $item->{'damaged'} ne '';
1620     $item->{'itemlost'}   = 0 unless exists $item->{'itemlost'}   and defined $item->{'itemlost'}   and $item->{'itemlost'} ne '';
1621     $item->{'wthdrawn'}   = 0 unless exists $item->{'wthdrawn'}   and defined $item->{'wthdrawn'}   and $item->{'wthdrawn'} ne '';
1622 }
1623
1624 =head2 _koha_new_item
1625
1626 =over 4
1627
1628 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1629
1630 =back
1631
1632 Perform the actual insert into the C<items> table.
1633
1634 =cut
1635
1636 sub _koha_new_item {
1637     my ( $item, $barcode ) = @_;
1638     my $dbh=C4::Context->dbh;  
1639     my $error;
1640     my $query =
1641            "INSERT INTO items SET
1642             biblionumber        = ?,
1643             biblioitemnumber    = ?,
1644             barcode             = ?,
1645             dateaccessioned     = ?,
1646             booksellerid        = ?,
1647             homebranch          = ?,
1648             price               = ?,
1649             replacementprice    = ?,
1650             replacementpricedate = NOW(),
1651             datelastborrowed    = ?,
1652             datelastseen        = NOW(),
1653             stack               = ?,
1654             notforloan          = ?,
1655             damaged             = ?,
1656             itemlost            = ?,
1657             wthdrawn            = ?,
1658             itemcallnumber      = ?,
1659             restricted          = ?,
1660             itemnotes           = ?,
1661             holdingbranch       = ?,
1662             paidfor             = ?,
1663             location            = ?,
1664             onloan              = ?,
1665             issues              = ?,
1666             renewals            = ?,
1667             reserves            = ?,
1668             cn_source           = ?,
1669             cn_sort             = ?,
1670             ccode               = ?,
1671             itype               = ?,
1672             materials           = ?,
1673                         uri                 = ?
1674           ";
1675     my $sth = $dbh->prepare($query);
1676    $sth->execute(
1677             $item->{'biblionumber'},
1678             $item->{'biblioitemnumber'},
1679             $barcode,
1680             $item->{'dateaccessioned'},
1681             $item->{'booksellerid'},
1682             $item->{'homebranch'},
1683             $item->{'price'},
1684             $item->{'replacementprice'},
1685             $item->{datelastborrowed},
1686             $item->{stack},
1687             $item->{'notforloan'},
1688             $item->{'damaged'},
1689             $item->{'itemlost'},
1690             $item->{'wthdrawn'},
1691             $item->{'itemcallnumber'},
1692             $item->{'restricted'},
1693             $item->{'itemnotes'},
1694             $item->{'holdingbranch'},
1695             $item->{'paidfor'},
1696             $item->{'location'},
1697             $item->{'onloan'},
1698             $item->{'issues'},
1699             $item->{'renewals'},
1700             $item->{'reserves'},
1701             $item->{'items.cn_source'},
1702             $item->{'items.cn_sort'},
1703             $item->{'ccode'},
1704             $item->{'itype'},
1705             $item->{'materials'},
1706             $item->{'uri'},
1707     );
1708     my $itemnumber = $dbh->{'mysql_insertid'};
1709     if ( defined $sth->errstr ) {
1710         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1711     }
1712     $sth->finish();
1713     return ( $itemnumber, $error );
1714 }
1715
1716 =head2 _koha_modify_item
1717
1718 =over 4
1719
1720 my ($itemnumber,$error) =_koha_modify_item( $item );
1721
1722 =back
1723
1724 Perform the actual update of the C<items> row.  Note that this
1725 routine accepts a hashref specifying the columns to update.
1726
1727 =cut
1728
1729 sub _koha_modify_item {
1730     my ( $item ) = @_;
1731     my $dbh=C4::Context->dbh;  
1732     my $error;
1733
1734     my $query = "UPDATE items SET ";
1735     my @bind;
1736     for my $key ( keys %$item ) {
1737         $query.="$key=?,";
1738         push @bind, $item->{$key};
1739     }
1740     $query =~ s/,$//;
1741     $query .= " WHERE itemnumber=?";
1742     push @bind, $item->{'itemnumber'};
1743     my $sth = C4::Context->dbh->prepare($query);
1744     $sth->execute(@bind);
1745     if ( C4::Context->dbh->errstr ) {
1746         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
1747         warn $error;
1748     }
1749     $sth->finish();
1750     return ($item->{'itemnumber'},$error);
1751 }
1752
1753 =head2 _koha_delete_item
1754
1755 =over 4
1756
1757 _koha_delete_item( $dbh, $itemnum );
1758
1759 =back
1760
1761 Internal function to delete an item record from the koha tables
1762
1763 =cut
1764
1765 sub _koha_delete_item {
1766     my ( $dbh, $itemnum ) = @_;
1767
1768     # save the deleted item to deleteditems table
1769     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1770     $sth->execute($itemnum);
1771     my $data = $sth->fetchrow_hashref();
1772     $sth->finish();
1773     my $query = "INSERT INTO deleteditems SET ";
1774     my @bind  = ();
1775     foreach my $key ( keys %$data ) {
1776         $query .= "$key = ?,";
1777         push( @bind, $data->{$key} );
1778     }
1779     $query =~ s/\,$//;
1780     $sth = $dbh->prepare($query);
1781     $sth->execute(@bind);
1782     $sth->finish();
1783
1784     # delete from items table
1785     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1786     $sth->execute($itemnum);
1787     $sth->finish();
1788     return undef;
1789 }
1790
1791 =head2 _marc_from_item_hash
1792
1793 =over 4
1794
1795 my $item_marc = _marc_from_item_hash($item, $frameworkcode);
1796
1797 =back
1798
1799 Given an item hash representing a complete item record,
1800 create a C<MARC::Record> object containing an embedded
1801 tag representing that item.
1802
1803 =cut
1804
1805 sub _marc_from_item_hash {
1806     my $item = shift;
1807     my $frameworkcode = shift;
1808    
1809     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1810     # Also, don't emit a subfield if the underlying field is blank.
1811     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1812                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1813                                 : ()  } keys %{ $item } }; 
1814
1815     my $item_marc = MARC::Record->new();
1816     foreach my $item_field (keys %{ $mungeditem }) {
1817         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
1818         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1819         if (my $field = $item_marc->field($tag)) {
1820             $field->add_subfields($subfield => $mungeditem->{$item_field});
1821         } else {
1822             $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field});
1823         }
1824     }
1825
1826     return $item_marc;
1827 }
1828
1829 =head2 _add_item_field_to_biblio
1830
1831 =over 4
1832
1833 _add_item_field_to_biblio($item_marc, $biblionumber, $frameworkcode);
1834
1835 =back
1836
1837 Adds the fields from a MARC record containing the
1838 representation of a Koha item record to the MARC
1839 biblio record.  The input C<$item_marc> record
1840 is expect to contain just one field, the embedded
1841 item information field.
1842
1843 =cut
1844
1845 sub _add_item_field_to_biblio {
1846     my ($item_marc, $biblionumber, $frameworkcode) = @_;
1847
1848     my $biblio_marc = GetMarcBiblio($biblionumber);
1849
1850     foreach my $field ($item_marc->fields()) {
1851         $biblio_marc->append_fields($field);
1852     }
1853
1854     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
1855 }
1856
1857 =head2 _replace_item_field_in_biblio
1858
1859 =over
1860
1861 &_replace_item_field_in_biblio($item_marc, $biblionumber, $itemnumber, $frameworkcode)
1862
1863 =back
1864
1865 Given a MARC::Record C<$item_marc> containing one tag with the MARC 
1866 representation of the item, examine the biblio MARC
1867 for the corresponding tag for that item and 
1868 replace it with the tag from C<$item_marc>.
1869
1870 =cut
1871
1872 sub _replace_item_field_in_biblio {
1873     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
1874     my $dbh = C4::Context->dbh;
1875     
1876     # get complete MARC record & replace the item field by the new one
1877     my $completeRecord = GetMarcBiblio($biblionumber);
1878     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
1879     my $itemField = $ItemRecord->field($itemtag);
1880     my @items = $completeRecord->field($itemtag);
1881     my $found = 0;
1882     foreach (@items) {
1883         if ($_->subfield($itemsubfield) eq $itemnumber) {
1884             $_->replace_with($itemField);
1885             $found = 1;
1886         }
1887     }
1888   
1889     unless ($found) { 
1890         # If we haven't found the matching field,
1891         # just add it.  However, this means that
1892         # there is likely a bug.
1893         $completeRecord->append_fields($itemField);
1894     }
1895
1896     # save the record
1897     ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
1898 }
1899
1900 =head2 _repack_item_errors
1901
1902 Add an error message hash generated by C<CheckItemPreSave>
1903 to a list of errors.
1904
1905 =cut
1906
1907 sub _repack_item_errors {
1908     my $item_sequence_num = shift;
1909     my $item_ref = shift;
1910     my $error_ref = shift;
1911
1912     my @repacked_errors = ();
1913
1914     foreach my $error_code (sort keys %{ $error_ref }) {
1915         my $repacked_error = {};
1916         $repacked_error->{'item_sequence'} = $item_sequence_num;
1917         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1918         $repacked_error->{'error_code'} = $error_code;
1919         $repacked_error->{'error_information'} = $error_ref->{$error_code};
1920         push @repacked_errors, $repacked_error;
1921     } 
1922
1923     return @repacked_errors;
1924 }
1925
1926 =head2 _add_unlinked_marc_fields
1927
1928 Adds marc fields to new_item_marc 
1929 from $original_item_marc fields which 
1930 1) are not linked to items table 
1931 2) but still are used in framework 
1932 3) and are provided a value
1933
1934 =cut
1935 sub _add_unlinked_marc_fields{
1936     my $new_item_marc = shift;
1937     my $original_item_marc = shift;
1938     my $frameworkcode = shift;
1939     my $tmp_item_marc =$new_item_marc->clone;  
1940     my $marcstructure = GetMarcStructure(1,$frameworkcode);
1941     foreach my $field ($original_item_marc->fields()){
1942             my $tag = $field->tag();
1943         if ($new_item_marc->fields($tag)){
1944             # It is assumed  that item marc records only have ***one*** tag and that this tag is mandatory.
1945             # So new_item_marc MUST have $field->tag    
1946             foreach my $subfield ($field->subfields()) {
1947                 if (!$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'} && !$tmp_item_marc->subfield($tag,$subfield->[0])) {
1948                                 #$new_item_marc->field($tag)->add_subfields($subfield->[0]=>$subfield->[1]);
1949
1950                             my $ret= eval {$new_item_marc->field($tag)->add_subfields($subfield->[0] => $subfield->[1])};
1951                             if ($@ or !$ret) {
1952                         warn $subfield->[0]."=>".$subfield->[1]."\n".$new_item_marc->as_formatted."\n".$original_item_marc;
1953                     }
1954                 }
1955             }
1956         }
1957     }
1958     return $new_item_marc;      
1959 }
1960
1961 1;