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