added autocomplete="off"
[koha.git] / C4 / Items.pm
index 939e682..04cb417 100644 (file)
@@ -33,6 +33,9 @@ use C4::Branch;
 require C4::Reserves;
 use C4::Charset;
 use C4::Acquisition;
+use List::MoreUtils qw/any/;
+use Data::Dumper; # used as part of logging item record changes, not just for
+                  # debugging; so please don't remove this
 
 use vars qw($VERSION @ISA @EXPORT);
 
@@ -66,14 +69,19 @@ BEGIN {
         GetItemsByBiblioitemnumber
         GetItemsInfo
        GetItemsLocationInfo
+       GetHostItemsInfo
         get_itemnumbers_of
+       get_hostitemnumbers_of
         GetItemnumberFromBarcode
         GetBarcodeFromItemnumber
-
+        GetHiddenItemnumbers
                DelItemCheck
                MoveItemFromBiblio 
                GetLatestAcquisitions
         CartToShelf
+
+       GetAnalyticsCount
+        GetItemHolds
     );
 }
 
@@ -260,9 +268,7 @@ sub AddItem {
        my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
     $item->{'itemnumber'} = $itemnumber;
 
-    # create MARC tag representing item and add to bib
-    my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
-    _add_item_field_to_biblio($new_item_marc, $item->{'biblionumber'}, $frameworkcode );
+    ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver", undef, undef );
    
     logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
     
@@ -368,7 +374,7 @@ sub AddItemBatchFromMarc {
     }
 
     # update the MARC biblio
-    $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
#   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
 
     return (\@itemnumbers, \@errors);
 }
@@ -395,6 +401,8 @@ Note that only columns that can be directly
 changed from the cataloging and serials
 item editors are included in this hash.
 
+Returns item record
+
 =cut
 
 my %default_values_for_mod_from_marc = (
@@ -413,6 +421,7 @@ my %default_values_for_mod_from_marc = (
     itemnotes            => undef, 
     itype                => undef, 
     location             => undef, 
+    permanent_location   => undef,
     materials            => undef, 
     notforloan           => 0,
     paidfor              => undef, 
@@ -443,13 +452,13 @@ sub ModItemFromMarc {
     }
     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
 
-    return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
+    ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
+    return $item;
 }
 
 =head2 ModItem
 
-  ModItem({ column => $newvalue }, $biblionumber, 
-                  $itemnumber[, $original_item_marc]);
+  ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
 
 Change one or more columns in an item record and update
 the MARC representation of the item.
@@ -492,6 +501,9 @@ sub ModItem {
     };
 
     $item->{'itemnumber'} = $itemnumber or return undef;
+
+    $item->{onloan} = undef if $item->{itemlost};
+
     _set_derived_columns_for_mod($item);
     _do_column_fixes_for_mod($item);
     # FIXME add checks
@@ -504,18 +516,11 @@ sub ModItem {
     # update items table
     _koha_modify_item($item);
 
-    # update biblio MARC XML
-    my $whole_item = GetItem($itemnumber) or die "FAILED GetItem($itemnumber)";
+    # request that bib be reindexed so that searching on current
+    # item status is possible
+    ModZebra( $biblionumber, "specialUpdate", "biblioserver", undef, undef );
 
-    unless (defined $unlinked_item_subfields) {
-        $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'});
-    }
-    my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode, $unlinked_item_subfields) 
-        or die "FAILED _marc_from_item_hash($whole_item, $frameworkcode)";
-    
-    _replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
-       ($new_item_marc       eq '0') and die "$new_item_marc is '0', not hashref";  # logaction line would crash anyway
-    logaction("CATALOGUING", "MODIFY", $itemnumber, $new_item_marc->as_formatted) if C4::Context->preference("CataloguingLog");
+    logaction("CATALOGUING", "MODIFY", $itemnumber, Dumper($item)) if C4::Context->preference("CataloguingLog");
 }
 
 =head2 ModItemTransfer
@@ -576,23 +581,14 @@ sub DelItem {
 
     # get the MARC record
     my $record = GetMarcBiblio($biblionumber);
-    my $frameworkcode = GetFrameworkCode($biblionumber);
+    ModZebra( $biblionumber, "specialUpdate", "biblioserver", undef, undef );
 
     # backup the record
     my $copy2deleted = $dbh->prepare("UPDATE deleteditems SET marc=? WHERE itemnumber=?");
     $copy2deleted->execute( $record->as_usmarc(), $itemnumber );
+    # This last update statement makes that the timestamp column in deleteditems is updated too. If you remove these lines, please add a line to update the timestamp separately. See Bugzilla report 7146 and Biblio.pm (DelBiblio).
 
     #search item field code
-    my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
-    my @fields = $record->field($itemtag);
-
-    # delete the item specified
-    foreach my $field (@fields) {
-        if ( $field->subfield($itemsubfield) eq $itemnumber ) {
-            $record->delete_field($field);
-        }
-    }
-    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
 }
 
@@ -1127,16 +1123,12 @@ sub GetItemsByBiblioitemnumber {
 
 =head2 GetItemsInfo
 
-  @results = GetItemsInfo($biblionumber, $type);
-
-Returns information about books with the given biblionumber.
+  @results = GetItemsInfo($biblionumber);
 
-C<$type> may be either C<intra> or anything else. If it is not set to
-C<intra>, then the search will exclude lost, very overdue, and
-withdrawn items.
+Returns information about items with the given biblionumber.
 
 C<GetItemsInfo> returns a list of references-to-hash. Each element
-contains a number of keys. Most of them are table items from the
+contains a number of keys. Most of them are attributes from the
 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
 Koha database. Other keys include:
 
@@ -1172,7 +1164,7 @@ If this is set, it is set to C<One Order>.
 =cut
 
 sub GetItemsInfo {
-    my ( $biblionumber, $type ) = @_;
+    my ( $biblionumber ) = @_;
     my $dbh   = C4::Context->dbh;
     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
     my $query = "
@@ -1194,7 +1186,7 @@ sub GetItemsInfo {
            itemtypes.notforloan as notforloan_per_itemtype,
            branchurl
      FROM items
-     LEFT JOIN branches ON items.homebranch = branches.branchcode
+     LEFT JOIN branches ON items.holdingbranch = branches.branchcode
      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
@@ -1221,6 +1213,7 @@ sub GetItemsInfo {
             $data->{cardnumber}     = $idata->{cardnumber};
             $data->{surname}     = $idata->{surname};
             $data->{firstname}     = $idata->{firstname};
+            $data->{lastreneweddate} = $idata->{lastreneweddate};
             $datedue                = $idata->{'date_due'};
         if (C4::Context->preference("IndependantBranches")){
         my $userenv = C4::Context->userenv;
@@ -1235,7 +1228,7 @@ sub GetItemsInfo {
                        $serial = 1;
         }
                if ( $datedue eq '' ) {
-            my ( $restype, $reserves ) =
+            my ( $restype, $reserves, undef ) =
               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
 # 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
@@ -1413,6 +1406,46 @@ sub GetItemsLocationInfo {
        return @results;
 }
 
+=head2 GetHostItemsInfo
+
+       $hostiteminfo = GetHostItemsInfo($hostfield);
+       Returns the iteminfo for items linked to records via a host field
+
+=cut
+
+sub GetHostItemsInfo {
+       my ($record) = @_;
+       my @returnitemsInfo;
+
+       if (C4::Context->preference('marcflavour') eq 'MARC21' ||
+        C4::Context->preference('marcflavour') eq 'NORMARC'){
+           foreach my $hostfield ( $record->field('773') ) {
+               my $hostbiblionumber = $hostfield->subfield("0");
+               my $linkeditemnumber = $hostfield->subfield("9");
+               my @hostitemInfos = GetItemsInfo($hostbiblionumber);
+               foreach my $hostitemInfo (@hostitemInfos){
+                       if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
+                               push (@returnitemsInfo,$hostitemInfo);
+                               last;
+                       }
+               }
+           }
+       } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
+           foreach my $hostfield ( $record->field('461') ) {
+               my $hostbiblionumber = $hostfield->subfield("0");
+               my $linkeditemnumber = $hostfield->subfield("9");
+               my @hostitemInfos = GetItemsInfo($hostbiblionumber);
+               foreach my $hostitemInfo (@hostitemInfos){
+                       if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
+                               push (@returnitemsInfo,$hostitemInfo);
+                               last;
+                       }
+               }
+           }
+       }
+       return @returnitemsInfo;
+}
+
 
 =head2 GetLastAcquisitions
 
@@ -1500,6 +1533,53 @@ sub get_itemnumbers_of {
     return \%itemnumbers_of;
 }
 
+=head2 get_hostitemnumbers_of
+
+  my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
+
+Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
+
+Return a reference on a hash where key is a biblionumber and values are
+references on array of itemnumbers.
+
+=cut
+
+
+sub get_hostitemnumbers_of {
+       my ($biblionumber) = @_;
+       my $marcrecord = GetMarcBiblio($biblionumber);
+        my (@returnhostitemnumbers,$tag, $biblio_s, $item_s);
+       
+       my $marcflavor = C4::Context->preference('marcflavour');
+       if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
+        $tag='773';
+        $biblio_s='0';
+        $item_s='9';
+    } elsif ($marcflavor eq 'UNIMARC') {
+        $tag='461';
+        $biblio_s='0';
+        $item_s='9';
+    }
+
+    foreach my $hostfield ( $marcrecord->field($tag) ) {
+        my $hostbiblionumber = $hostfield->subfield($biblio_s);
+        my $linkeditemnumber = $hostfield->subfield($item_s);
+        my @itemnumbers;
+        if (my $itemnumbers = get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber})
+        {
+            @itemnumbers = @$itemnumbers;
+        }
+        foreach my $itemnumber (@itemnumbers){
+            if ($itemnumber eq $linkeditemnumber){
+                push (@returnhostitemnumbers,$itemnumber);
+                last;
+            }
+        }
+    }
+    return @returnhostitemnumbers;
+}
+
+
 =head2 GetItemnumberFromBarcode
 
   $result = GetItemnumberFromBarcode($barcode);
@@ -1534,6 +1614,61 @@ sub GetBarcodeFromItemnumber {
     return ($result);
 }
 
+=head2 GetHiddenItemnumbers
+
+=over 4
+
+$result = GetHiddenItemnumbers(@items);
+
+=back
+
+=cut
+
+sub GetHiddenItemnumbers {
+    my (@items) = @_;
+    my @resultitems;
+
+    my $yaml = C4::Context->preference('OpacHiddenItems');
+    $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
+    my $hidingrules;
+    eval {
+        $hidingrules = YAML::Load($yaml);
+    };
+    if ($@) {
+        warn "Unable to parse OpacHiddenItems syspref : $@";
+        return ();
+    }
+    my $dbh = C4::Context->dbh;
+
+    # For each item
+    foreach my $item (@items) {
+
+        # We check each rule
+        foreach my $field (keys %$hidingrules) {
+            my $val;
+            if (exists $item->{$field}) {
+                $val = $item->{$field};
+            }
+            else {
+                my $query = "SELECT $field from items where itemnumber = ?";
+                $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
+            }
+            $val = '' unless defined $val;
+
+            # If the results matches the values in the yaml file
+            if (any { $val eq $_ } @{$hidingrules->{$field}}) {
+
+                # We add the itemnumber to the list
+                push @resultitems, $item->{'itemnumber'};
+
+                # If at least one rule matched for an item, no need to test the others
+                last;
+            }
+        }
+    }
+    return @resultitems;
+}
+
 =head3 get_item_authorised_values
 
 find the types and values for all authorised values assigned to this item.
@@ -1938,9 +2073,9 @@ sub _koha_new_item {
             homebranch          = ?,
             price               = ?,
             replacementprice    = ?,
-            replacementpricedate = NOW(),
+            replacementpricedate = ?,
             datelastborrowed    = ?,
-            datelastseen        = NOW(),
+            datelastseen        = ?,
             stack               = ?,
             notforloan          = ?,
             damaged             = ?,
@@ -1952,6 +2087,7 @@ sub _koha_new_item {
             holdingbranch       = ?,
             paidfor             = ?,
             location            = ?,
+            permanent_location            = ?,
             onloan              = ?,
             issues              = ?,
             renewals            = ?,
@@ -1968,6 +2104,7 @@ sub _koha_new_item {
             stocknumber         = ?
           ";
     my $sth = $dbh->prepare($query);
+    my $today = C4::Dates->today('iso');
    $sth->execute(
             $item->{'biblionumber'},
             $item->{'biblioitemnumber'},
@@ -1977,7 +2114,9 @@ sub _koha_new_item {
             $item->{'homebranch'},
             $item->{'price'},
             $item->{'replacementprice'},
+            $item->{'replacementpricedate'} || $today,
             $item->{datelastborrowed},
+            $item->{datelastseen} || $today,
             $item->{stack},
             $item->{'notforloan'},
             $item->{'damaged'},
@@ -1989,6 +2128,7 @@ sub _koha_new_item {
             $item->{'holdingbranch'},
             $item->{'paidfor'},
             $item->{'location'},
+            $item->{'permanent_location'},
             $item->{'onloan'},
             $item->{'issues'},
             $item->{'renewals'},
@@ -2004,10 +2144,15 @@ sub _koha_new_item {
             $item->{'copynumber'},
             $item->{'stocknumber'},
     );
-    my $itemnumber = $dbh->{'mysql_insertid'};
+
+    my $itemnumber;
     if ( defined $sth->errstr ) {
         $error.="ERROR in _koha_new_item $query".$sth->errstr;
     }
+    else {
+        $itemnumber = $dbh->{'mysql_insertid'};
+    }
+
     return ( $itemnumber, $error );
 }
 
@@ -2030,59 +2175,18 @@ sub MoveItemFromBiblio {
     $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) {
-
+        ModZebra( $tobiblio, "specialUpdate", "biblioserver", undef, undef );
+        ModZebra( $frombiblio, "specialUpdate", "biblioserver", undef, undef );
            # Checking if the item we want to move is in an order 
-           my $order = GetOrderFromItemnumber($itemnumber);
+        my $order = GetOrderFromItemnumber($itemnumber);
            if ($order) {
-               # Replacing the biblionumber within the order if necessary
-               $order->{'biblionumber'} = $tobiblio;
+                   # 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;
+        return $tobiblio;
        }
-    } else {
-       return undef;
-    }
+    return;
 }
 
 =head2 DelItemCheck
@@ -2097,35 +2201,39 @@ sub DelItemCheck {
     my ( $dbh, $biblionumber, $itemnumber ) = @_;
     my $error;
 
+        my $countanalytics=GetAnalyticsCount($itemnumber);
+
+
     # 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 $item = GetItem($itemnumber);
-    my $onloan = $sth->fetchrow;
-    if ($onloan) {
-        $error = "book_on_loan";
+    my $onloan=$sth->fetchrow;
+
+    if ($onloan){
+        $error = "book_on_loan" 
     }
-    elsif (C4::Context->preference("IndependantBranches") and (C4::Context->userenv->{branch} ne $item->{C4::Context->preference("HomeOrHoldingBranch")||'homebranch'})){
+    elsif ( !(C4::Context->userenv->{flags} & 1) and
+            C4::Context->preference("IndependantBranches") and
+           (C4::Context->userenv->{branch} ne
+             $item->{C4::Context->preference("HomeOrHoldingBranch")||'homebranch'}) )
+    {
         $error = "not_same_branch";
-    } 
-    else {
-       if ($onloan){ 
-           $error = "book_on_loan" 
-       }
-       else {
-           # check it doesnt have a waiting reserve
-           $sth=$dbh->prepare("SELECT * FROM reserves WHERE (found = 'W' or found = 'T') AND itemnumber = ?");
-           $sth->execute($itemnumber);
-           my $reserve=$sth->fetchrow;
-           if ($reserve) {
-               $error = "book_reserved";
-           } 
-           else {
-               DelItem($dbh, $biblionumber, $itemnumber);
-               return 1;
-           }
-       }
+    }
+       else{
+        # check it doesnt have a waiting reserve
+        $sth=$dbh->prepare("SELECT * FROM reserves WHERE (found = 'W' or found = 'T') AND itemnumber = ?");
+        $sth->execute($itemnumber);
+        my $reserve=$sth->fetchrow;
+        if ($reserve){
+            $error = "book_reserved";
+        } elsif ($countanalytics > 0){
+               $error = "linked_analytics";
+       } else {
+            DelItem($dbh, $biblionumber, $itemnumber);
+            return 1;
+        }
     }
     return $error;
 }
@@ -2243,68 +2351,6 @@ sub _marc_from_item_hash {
     return $item_marc;
 }
 
-=head2 _add_item_field_to_biblio
-
-  _add_item_field_to_biblio($item_marc, $biblionumber, $frameworkcode);
-
-Adds the fields from a MARC record containing the
-representation of a Koha item record to the MARC
-biblio record.  The input C<$item_marc> record
-is expect to contain just one field, the embedded
-item information field.
-
-=cut
-
-sub _add_item_field_to_biblio {
-    my ($item_marc, $biblionumber, $frameworkcode) = @_;
-
-    my $biblio_marc = GetMarcBiblio($biblionumber);
-    foreach my $field ($item_marc->fields()) {
-        $biblio_marc->append_fields($field);
-    }
-
-    ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
-}
-
-=head2 _replace_item_field_in_biblio
-
-  &_replace_item_field_in_biblio($item_marc, $biblionumber, $itemnumber, $frameworkcode)
-
-Given a MARC::Record C<$item_marc> containing one tag with the MARC 
-representation of the item, examine the biblio MARC
-for the corresponding tag for that item and 
-replace it with the tag from C<$item_marc>.
-
-=cut
-
-sub _replace_item_field_in_biblio {
-    my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
-    my $dbh = C4::Context->dbh;
-    
-    # get complete MARC record & replace the item field by the new one
-    my $completeRecord = GetMarcBiblio($biblionumber);
-    my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
-    my $itemField = $ItemRecord->field($itemtag);
-    my @items = $completeRecord->field($itemtag);
-    my $found = 0;
-    foreach (@items) {
-        if ($_->subfield($itemsubfield) eq $itemnumber) {
-            $_->replace_with($itemField);
-            $found = 1;
-        }
-    }
-  
-    unless ($found) { 
-        # If we haven't found the matching field,
-        # just add it.  However, this means that
-        # there is likely a bug.
-        $completeRecord->append_fields($itemField);
-    }
-
-    # save the record
-    ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
-}
-
 =head2 _repack_item_errors
 
 Add an error message hash generated by C<CheckItemPreSave>
@@ -2404,4 +2450,51 @@ sub  _parse_unlinked_item_subfields_from_xml {
     return $unlinked_subfields;
 }
 
+=head2 GetAnalyticsCount
+
+  $count= &GetAnalyticsCount($itemnumber)
+
+counts Usage of itemnumber in Analytical bibliorecords. 
+
+=cut
+
+sub GetAnalyticsCount {
+    my ($itemnumber) = @_;
+    if (C4::Context->preference('NoZebra')) {
+        # Read the index Koha-Auth-Number for this authid and count the lines
+        my $result = C4::Search::NZanalyse("hi=$itemnumber");
+        my @tab = split /;/,$result;
+        return scalar @tab;
+    } else {
+        ### ZOOM search here
+        my $query;
+        $query= "hi=".$itemnumber;
+                my ($err,$res,$result) = C4::Search::SimpleSearch($query,0,10);
+        return ($result);
+    }
+}
+
+=head2 GetItemHolds
+
+=over 4
+$holds = &GetItemHolds($biblionumber, $itemnumber);
+
+=back
+
+This function return the count of holds with $biblionumber and $itemnumber
+
+=cut
+
+sub GetItemHolds {
+    my ($biblionumber, $itemnumber) = @_;
+    my $holds;
+    my $dbh            = C4::Context->dbh;
+    my $query          = "SELECT count(*)
+        FROM  reserves
+        WHERE biblionumber=? AND itemnumber=?";
+    my $sth = $dbh->prepare($query);
+    $sth->execute($biblionumber, $itemnumber);
+    $holds = $sth->fetchrow;
+    return $holds;
+}
 1;