Bug 2505 - Add commented use warnings where missing in the serials/ directory
[koha.git] / C4 / Items.pm
index b637e82..cd6f7d0 100644 (file)
@@ -13,12 +13,13 @@ package C4::Items;
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along with
-# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 use strict;
 
+use Carp;
 use C4::Context;
 use C4::Koha;
 use C4::Biblio;
@@ -29,6 +30,7 @@ use C4::Log;
 use C4::Branch;
 require C4::Reserves;
 use C4::Charset;
+use C4::Acquisition;
 
 use vars qw($VERSION @ISA @EXPORT);
 
@@ -45,6 +47,7 @@ BEGIN {
         AddItem
         AddItemBatchFromMarc
         ModItemFromMarc
+               Item2Marc
         ModItem
         ModDateLastSeen
         ModItemTransfer
@@ -62,6 +65,11 @@ BEGIN {
         GetItemsInfo
         get_itemnumbers_of
         GetItemnumberFromBarcode
+
+               DelItemCheck
+               MoveItemFromBiblio 
+               GetLatestAcquisitions
+        CartToShelf
     );
 }
 
@@ -153,6 +161,34 @@ sub GetItem {
     return $data;
 }    # sub GetItem
 
+=head2 CartToShelf
+
+=over 4
+
+CartToShelf($itemnumber);
+
+=back
+
+Set the current shelving location of the item record
+to its stored permanent shelving location.  This is
+primarily used to indicate when an item whose current
+location is a special processing ('PROC') or shelving cart
+('CART') location is back in the stacks.
+
+=cut
+
+sub CartToShelf {
+    my ( $itemnumber ) = @_;
+
+    unless ( $itemnumber ) {
+        croak "FAILED CartToShelf() - no itemnumber supplied";
+    }
+
+    my $item = GetItem($itemnumber);
+    $item->{location} = $item->{permanent_location};
+    ModItem($item, undef, $itemnumber);
+}
+
 =head2 AddItemFromMarc
 
 =over 4
@@ -173,8 +209,12 @@ sub AddItemFromMarc {
 
     # parse item hash from MARC
     my $frameworkcode = GetFrameworkCode( $biblionumber );
-    my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
-    my $unlinked_item_subfields = _get_unlinked_item_subfields($source_item_marc, $frameworkcode);
+       my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
+       
+       my $localitemmarc=MARC::Record->new;
+       $localitemmarc->append_fields($source_item_marc->field($itemtag));
+    my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode ,'items');
+    my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
     return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
 }
 
@@ -412,11 +452,15 @@ sub ModItemFromMarc {
 
     my $dbh = C4::Context->dbh;
     my $frameworkcode = GetFrameworkCode( $biblionumber );
-    my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode );
+       my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
+       
+       my $localitemmarc=MARC::Record->new;
+       $localitemmarc->append_fields($item_marc->field($itemtag));
+    my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode, 'items');
     foreach my $item_field (keys %default_values_for_mod_from_marc) {
         $item->{$item_field} = $default_values_for_mod_from_marc{$item_field} unless exists $item->{$item_field};
     }
-    my $unlinked_item_subfields = _get_unlinked_item_subfields($item_marc, $frameworkcode);
+    my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
    
     return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
 }
@@ -769,7 +813,6 @@ sub GetItemStatus {
             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
                 $itemstatus{$authorisedvalue} = $lib;
             }
-            $authvalsth->finish;
             return \%itemstatus;
             exit 1;
         }
@@ -778,7 +821,6 @@ sub GetItemStatus {
             #No authvalue list
             # build default
         }
-        $sth->finish;
     }
 
     #No authvalue list
@@ -869,7 +911,6 @@ sub GetItemLocation {
             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
                 $itemlocation{$authorisedvalue} = $lib;
             }
-            $authvalsth->finish;
             return \%itemlocation;
             exit 1;
         }
@@ -878,7 +919,6 @@ sub GetItemLocation {
             #No authvalue list
             # build default
         }
-        $sth->finish;
     }
 
     #No authvalue list
@@ -933,23 +973,24 @@ sub GetLostItems {
 
     my $query   = "
         SELECT *
-        FROM   items, biblio, authorised_values
+        FROM   items
+            LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
+            LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
+            LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
         WHERE
-                       items.biblionumber = biblio.biblionumber
-                       AND items.itemlost = authorised_values.authorised_value
-                       AND authorised_values.category = 'LOST'
+               authorised_values.category = 'LOST'
                AND itemlost IS NOT NULL
                AND itemlost <> 0
-          
     ";
     my @query_parameters;
     foreach my $key (keys %$where) {
         $query .= " AND $key LIKE ?";
         push @query_parameters, "%$where->{$key}%";
     }
-    if ( defined $orderby ) {
-        $query .= ' ORDER BY ?';
-        push @query_parameters, $orderby;
+    my @ordervalues = qw/title author homebranch itype barcode price replacementprice lib datelastseen location/;
+    
+    if ( defined $orderby && grep($orderby, @ordervalues)) {
+        $query .= ' ORDER BY '.$orderby;
     }
 
     my $sth = $dbh->prepare($query);
@@ -965,7 +1006,7 @@ sub GetLostItems {
 
 =over 4
 
-$itemlist = GetItemsForInventory($minlocation, $maxlocation, $location, $itemtype $datelastseen, $branch, $offset, $size);
+$itemlist = GetItemsForInventory($minlocation, $maxlocation, $location, $itemtype $datelastseen, $branch, $offset, $size, $statushash);
 
 =back
 
@@ -978,11 +1019,12 @@ seen. It is ordered by callnumber then title.
 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
 the datelastseen can be used to specify that you want to see items not seen since a past date only.
 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
+$statushash requires a hashref that has the authorized values fieldname (intems.notforloan, etc...) as keys, and an arrayref of statuscodes we are searching for as values.
 
 =cut
 
 sub GetItemsForInventory {
-    my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branch, $offset, $size ) = @_;
+    my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branch, $offset, $size, $statushash ) = @_;
     my $dbh = C4::Context->dbh;
     my ( @bind_params, @where_strings );
 
@@ -992,6 +1034,14 @@ FROM items
   LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
   LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
 END_SQL
+    if ($statushash){
+        for my $authvfield (keys %$statushash){
+            if ( scalar @{$statushash->{$authvfield}} > 0 ){
+                my $joinedvals = join ',', @{$statushash->{$authvfield}};
+                push @where_strings, "$authvfield in (" . $joinedvals . ")";
+            }
+        }
+    }
 
     if ($minlocation) {
         push @where_strings, 'itemcallnumber >= ?';
@@ -1033,7 +1083,7 @@ END_SQL
         $query .= 'WHERE ';
         $query .= join ' AND ', @where_strings;
     }
-    $query .= ' ORDER BY itemcallnumber, title';
+    $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
     my $sth = $dbh->prepare($query);
     $sth->execute( @bind_params );
 
@@ -1070,7 +1120,6 @@ sub GetItemsCount {
     my $sth = $dbh->prepare($query);
     $sth->execute($biblionumber);
     my $count = $sth->fetchrow;  
-    $sth->finish;
     return ($count);
 }
 
@@ -1133,7 +1182,6 @@ sub GetItemsByBiblioitemnumber {
             # set date_due to blank, so in the template we check itemlost, and wthdrawn 
             $data->{'date_due'} = '';                                                                                                         
         }    # else         
-        $sth2->finish;
         # Find the last 3 people who borrowed this item.                  
         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
                       AND old_issues.borrowernumber = borrowers.borrowernumber
@@ -1147,10 +1195,8 @@ sub GetItemsByBiblioitemnumber {
             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
             $i2++;
         }
-        $sth2->finish;
         push(@results,$data);
     } 
-    $sth->finish;
     return (\@results); 
 }
 
@@ -1223,18 +1269,20 @@ sub GetItemsInfo {
            biblioitems.lccn,
            biblioitems.url,
            items.notforloan as itemnotforloan,
-           itemtypes.description
+           itemtypes.description,
+           branchurl
      FROM items
+     LEFT JOIN branches ON items.homebranch = branches.branchcode
      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
-    $query .= " WHERE items.biblionumber = ? ORDER BY items.dateaccessioned desc" ;
+    $query .= " WHERE items.biblionumber = ? ORDER BY branches.branchname,items.dateaccessioned desc" ;
     my $sth = $dbh->prepare($query);
     $sth->execute($biblionumber);
     my $i = 0;
     my @results;
-    my ( $date_due, $count_reserves, $serial );
+    my $serial;
 
     my $isth    = $dbh->prepare(
         "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
@@ -1244,6 +1292,7 @@ sub GetItemsInfo {
        my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? "); 
        while ( my $data = $sth->fetchrow_hashref ) {
         my $datedue = '';
+        my $count_reserves;
         $isth->execute( $data->{'itemnumber'} );
         if ( my $idata = $isth->fetchrow_hashref ) {
             $data->{borrowernumber} = $idata->{borrowernumber};
@@ -1253,7 +1302,7 @@ sub GetItemsInfo {
             $datedue                = $idata->{'date_due'};
         if (C4::Context->preference("IndependantBranches")){
         my $userenv = C4::Context->userenv;
-        if ( ($userenv) && ( $userenv->{flags} != 1 ) ) { 
+        if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) { 
             $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
         }
         }
@@ -1266,12 +1315,11 @@ sub GetItemsInfo {
                if ( $datedue eq '' ) {
             my ( $restype, $reserves ) =
               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
-            if ($restype) {
-                $count_reserves = $restype;
-            }
+# Previous conditional check with if ($restype) is not needed because a true
+# result for one item will result in subsequent items defaulting to this true
+# value.
+            $count_reserves = $restype;
         }
-        $isth->finish;
-        $ssth->finish;
         #get branch information.....
         my $bsth = $dbh->prepare(
             "SELECT * FROM branches WHERE branchcode = ?
@@ -1354,6 +1402,60 @@ sub GetItemsInfo {
        }
 }
 
+=head2 GetLastAcquisitions
+
+=over 4
+
+my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 'itemtypes' => ('BK','BD')}, 10);
+
+=back
+
+=cut
+
+sub  GetLastAcquisitions {
+       my ($data,$max) = @_;
+
+       my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
+       
+       my $number_of_branches = @{$data->{branches}};
+       my $number_of_itemtypes   = @{$data->{itemtypes}};
+       
+       
+       my @where = ('WHERE 1 '); 
+       $number_of_branches and push @where
+          , 'AND holdingbranch IN (' 
+          , join(',', ('?') x $number_of_branches )
+          , ')'
+        ;
+       
+       $number_of_itemtypes and push @where
+          , "AND $itemtype IN (" 
+          , join(',', ('?') x $number_of_itemtypes )
+          , ')'
+        ;
+
+       my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
+                                FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
+                                   RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
+                                   @where
+                                   GROUP BY biblio.biblionumber 
+                                   ORDER BY dateaccessioned DESC LIMIT $max";
+
+       my $dbh = C4::Context->dbh;
+       my $sth = $dbh->prepare($query);
+    
+    $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
+       
+       my @results;
+       while( my $row = $sth->fetchrow_hashref){
+               push @results, {date => $row->{dateaccessioned} 
+                                               , biblionumber => $row->{biblionumber}
+                                               , title => $row->{title}};
+       }
+       
+       return @results;
+}
+
 =head2 get_itemnumbers_of
 
 =over 4
@@ -1471,7 +1573,7 @@ sub get_item_authorised_values {
   authorised values for a biblio.
 
   parameters: listref of authorised values, such as comes from
-    get_item_ahtorised_values or
+    get_item_authorised_values or
     from C4::Biblio::get_biblio_authorised_values
 
   returns: listref of hashrefs for each image. Each hashref looks like
@@ -1558,23 +1660,28 @@ sub GetMarcItem {
 
     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
     # Also, don't emit a subfield if the underlying field is blank.
+
+    
+    return Item2Marc($itemrecord,$biblionumber);
+
+}
+sub Item2Marc {
+       my ($itemrecord,$biblionumber)=@_;
     my $mungeditem = { 
         map {  
             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
         } keys %{ $itemrecord } 
     };
     my $itemmarc = TransformKohaToMarc($mungeditem);
+    my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
 
     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
-        my @fields = $itemmarc->fields();
-        if ($#fields > -1) {
-            $fields[0]->add_subfields(@$unlinked_item_subfields);
+               foreach my $field ($itemmarc->field($itemtag)){
+            $field->add_subfields(@$unlinked_item_subfields);
         }
     }
-    
-    return $itemmarc;
-
+       return $itemmarc;
 }
 
 =head1 PRIVATE FUNCTIONS AND VARIABLES
@@ -1725,6 +1832,9 @@ sub _do_column_fixes_for_mod {
         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
         $item->{'wthdrawn'} = 0;
     }
+    if (exists $item->{'location'} && !exists $item->{'permanent_location'}) {
+        $item->{'permanent_location'} = $item->{'location'};
+    }
 }
 
 =head2 _get_single_item_column
@@ -1909,10 +2019,124 @@ sub _koha_new_item {
     if ( defined $sth->errstr ) {
         $error.="ERROR in _koha_new_item $query".$sth->errstr;
     }
-    $sth->finish();
     return ( $itemnumber, $error );
 }
 
+=head2 MoveItemFromBiblio
+
+=over 4
+
+MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
+
+=back
+
+Moves an item from a biblio to another
+
+Returns undef if the move failed or the biblionumber of the destination record otherwise
+=cut
+sub MoveItemFromBiblio {
+    my ($itemnumber, $frombiblio, $tobiblio) = @_;
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = ?");
+    $sth->execute( $tobiblio );
+    my ( $tobiblioitem ) = $sth->fetchrow();
+    $sth = $dbh->prepare("UPDATE items SET biblioitemnumber = ?, biblionumber = ? WHERE itemnumber = ? AND biblionumber = ?");
+    my $return = $sth->execute($tobiblioitem, $tobiblio, $itemnumber, $frombiblio);
+    if ($return == 1) {
+
+       # Getting framework
+       my $frameworkcode = GetFrameworkCode($frombiblio);
+
+       # Getting marc field for itemnumber
+       my ($itemtag, $itemsubfield) = GetMarcFromKohaField('items.itemnumber', $frameworkcode);
+
+       # Getting the record we want to move the item from
+       my $record = GetMarcBiblio($frombiblio);
+
+       # The item we want to move
+       my $item;
+
+       # For each item
+       foreach my $fielditem ($record->field($itemtag)){
+               # If it is the item we want to move
+               if ($fielditem->subfield($itemsubfield) == $itemnumber) {
+                   # We save it
+                   $item = $fielditem;
+                   # Then delete it from the record
+                   $record->delete_field($fielditem) 
+               }
+       }
+
+       # If we found an item (should always true, except in case of database-marcxml inconsistency)
+       if ($item) {
+
+           # Checking if the item we want to move is in an order 
+           my $order = GetOrderFromItemnumber($itemnumber);
+           if ($order) {
+               # Replacing the biblionumber within the order if necessary
+               $order->{'biblionumber'} = $tobiblio;
+               ModOrder($order);
+           }
+
+           # Saving the modification
+           ModBiblioMarc($record, $frombiblio, $frameworkcode);
+
+           # Getting the record we want to move the item to
+           $record = GetMarcBiblio($tobiblio);
+
+           # Inserting the previously saved item
+           $record->insert_fields_ordered($item);      
+
+           # Saving the modification
+           ModBiblioMarc($record, $tobiblio, $frameworkcode);
+
+       } else {
+           return undef;
+       }
+    } else {
+       return undef;
+    }
+}
+
+=head2 DelItemCheck
+
+=over 4
+
+DelItemCheck($dbh, $biblionumber, $itemnumber);
+
+=back
+
+Exported function (core API) for deleting an item record in Koha if there no current issue.
+
+=cut
+
+sub DelItemCheck {
+    my ( $dbh, $biblionumber, $itemnumber ) = @_;
+    my $error;
+
+    # check that there is no issue on this item before deletion.
+    my $sth=$dbh->prepare("select * from issues i where i.itemnumber=?");
+    $sth->execute($itemnumber);
+
+    my $onloan=$sth->fetchrow;
+
+    if ($onloan){
+        $error = "book_on_loan" 
+    }else{
+        # check it doesnt have a waiting reserve
+        $sth=$dbh->prepare("SELECT * FROM reserves WHERE found = 'W' AND itemnumber = ?");
+        $sth->execute($itemnumber);
+        my $reserve=$sth->fetchrow;
+        if ($reserve){
+            $error = "book_reserved";
+        }else{
+            DelItem($dbh, $biblionumber, $itemnumber);
+            return 1;
+        }
+    }
+    return $error;
+}
+
 =head2 _koha_modify_item
 
 =over 4
@@ -1946,7 +2170,6 @@ sub _koha_modify_item {
         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
         warn $error;
     }
-    $sth->finish();
     return ($item->{'itemnumber'},$error);
 }
 
@@ -1969,7 +2192,6 @@ sub _koha_delete_item {
     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
     $sth->execute($itemnum);
     my $data = $sth->fetchrow_hashref();
-    $sth->finish();
     my $query = "INSERT INTO deleteditems SET ";
     my @bind  = ();
     foreach my $key ( keys %$data ) {
@@ -1979,12 +2201,10 @@ sub _koha_delete_item {
     $query =~ s/\,$//;
     $sth = $dbh->prepare($query);
     $sth->execute(@bind);
-    $sth->finish();
 
     # delete from items table
     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
     $sth->execute($itemnum);
-    $sth->finish();
     return undef;
 }