Bug 7732: Restoring "More lists" in the Add-to combo of OPAC Search Results
[koha.git] / C4 / Circulation.pm
index 8d6f456..b6e86e0 100644 (file)
@@ -99,7 +99,17 @@ BEGIN {
                 &IsBranchTransferAllowed
                 &CreateBranchTransferLimit
                 &DeleteBranchTransferLimits
+        &TransferSlip
        );
+
+    # subs to deal with offline circulation
+    push @EXPORT, qw(
+      &GetOfflineOperations
+      &GetOfflineOperation
+      &AddOfflineOperation
+      &DeleteOfflineOperation
+      &ProcessOfflineOperation
+    );
 }
 
 =head1 NAME
@@ -320,7 +330,7 @@ sub transferbook {
 
     # find reserves.....
     # That'll save a database query.
-    my ( $resfound, $resrec ) =
+    my ( $resfound, $resrec, undef ) =
       CheckReserves( $itemnumber );
     if ( $resfound and not $ignoreRs ) {
         $resrec->{'ResFound'} = $resfound;
@@ -571,7 +581,7 @@ sub itemissues {
 =head2 CanBookBeIssued
 
   ( $issuingimpossible, $needsconfirmation ) =  CanBookBeIssued( $borrower, 
-                                      $barcode, $duedatespec, $inprocess );
+                      $barcode, $duedatespec, $inprocess, $ignore_reserves );
 
 Check if a book can be issued.
 
@@ -585,7 +595,8 @@ C<$issuingimpossible> and C<$needsconfirmation> are some hashref.
 
 =item C<$duedatespec> is a C4::Dates object.
 
-=item C<$inprocess>
+=item C<$inprocess> boolean switch
+=item C<$ignore_reserves> boolean switch
 
 =back
 
@@ -662,7 +673,7 @@ if the borrower borrows to much things
 =cut
 
 sub CanBookBeIssued {
-    my ( $borrower, $barcode, $duedate, $inprocess ) = @_;
+    my ( $borrower, $barcode, $duedate, $inprocess, $ignore_reserves ) = @_;
     my %needsconfirmation;    # filled with problems that needs confirmations
     my %issuingimpossible;    # filled with problems that causes the issue to be IMPOSSIBLE
     my $item = GetItem(GetItemnumberFromBarcode( $barcode ));
@@ -869,37 +880,40 @@ sub CanBookBeIssued {
         $needsconfirmation{issued_borrowernumber} = $currborinfo->{'borrowernumber'};
     }
 
-    # See if the item is on reserve.
-    my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
-    if ($restype) {
-               my $resbor = $res->{'borrowernumber'};
-               my ( $resborrower ) = C4::Members::GetMember( borrowernumber => $resbor );
-               my $branches  = GetBranches();
-               my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'};
-        if ( $resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting" )
-        {
-            # The item is on reserve and waiting, but has been
-            # reserved by some other patron.
-            $needsconfirmation{RESERVE_WAITING} = 1;
-            $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
-            $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
-            $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
-            $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
-            $needsconfirmation{'resbranchname'} = $branchname;
-            $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'});
-        }
-        elsif ( $restype eq "Reserved" ) {
-            # The item is on reserve for someone else.
-            $needsconfirmation{RESERVED} = 1;
-            $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
-            $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
-            $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
-            $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
-            $needsconfirmation{'resbranchname'} = $branchname;
-            $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'});
+    unless ( $ignore_reserves ) {
+        # See if the item is on reserve.
+        my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
+        if ($restype) {
+            my $resbor = $res->{'borrowernumber'};
+            if ( $resbor ne $borrower->{'borrowernumber'} ) {
+                my ( $resborrower ) = C4::Members::GetMember( borrowernumber => $resbor );
+                my $branchname = GetBranchName( $res->{'branchcode'} );
+                if ( $restype eq "Waiting" )
+                {
+                    # The item is on reserve and waiting, but has been
+                    # reserved by some other patron.
+                    $needsconfirmation{RESERVE_WAITING} = 1;
+                    $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
+                    $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
+                    $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
+                    $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
+                    $needsconfirmation{'resbranchname'} = $branchname;
+                    $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'});
+                }
+                elsif ( $restype eq "Reserved" ) {
+                    # The item is on reserve for someone else.
+                    $needsconfirmation{RESERVED} = 1;
+                    $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
+                    $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
+                    $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
+                    $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
+                    $needsconfirmation{'resbranchname'} = $branchname;
+                    $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'});
+                }
+            }
         }
     }
-       return ( \%issuingimpossible, \%needsconfirmation );
+    return ( \%issuingimpossible, \%needsconfirmation );
 }
 
 =head2 AddIssue
@@ -984,39 +998,7 @@ sub AddIssue {
                                );
                        }
 
-                       # See if the item is on reserve.
-                       my ( $restype, $res ) =
-                         C4::Reserves::CheckReserves( $item->{'itemnumber'} );
-                       if ($restype) {
-                               my $resbor = $res->{'borrowernumber'};
-                               if ( $resbor eq $borrower->{'borrowernumber'} ) {
-                                       # The item is reserved by the current patron
-                                       ModReserveFill($res);
-                               }
-                               elsif ( $restype eq "Waiting" ) {
-                                       # warn "Waiting";
-                                       # The item is on reserve and waiting, but has been
-                                       # reserved by some other patron.
-                               }
-                               elsif ( $restype eq "Reserved" ) {
-                                       # warn "Reserved";
-                                       # The item is reserved by someone else.
-                                       if ($cancelreserve) { # cancel reserves on this item
-                                               CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
-                                       }
-                               }
-                               if ($cancelreserve) {
-                                       CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'});
-                               }
-                               else {
-                                       # set waiting reserve to first in reserve queue as book isn't waiting now
-                                       ModReserve(1,
-                                               $res->{'biblionumber'},
-                                               $res->{'borrowernumber'},
-                                               $res->{'branchcode'}
-                                       );
-                               }
-                       }
+            MoveReserve( $item->{'itemnumber'}, $borrower->{'borrowernumber'}, $cancelreserve );
 
                        # Starting process for transfer job (checking transfert and validate it if we have one)
             my ($datesent) = GetTransfers($item->{'itemnumber'});
@@ -1057,6 +1039,12 @@ sub AddIssue {
           CartToShelf( $item->{'itemnumber'} );
         }
         $item->{'issues'}++;
+
+        ## If item was lost, it has now been found, reverse any list item charges if neccessary.
+        if ( $item->{'itemlost'} ) {
+            _FixAccountForLostAndReturned( $item->{'itemnumber'}, undef, $item->{'barcode'} );
+        }
+
         ModItem({ issues           => $item->{'issues'},
                   holdingbranch    => C4::Context->userenv->{'branch'},
                   itemlost         => 0,
@@ -1377,13 +1365,17 @@ sub GetBranchBorrowerCircRule {
 Retrieves circulation rule attributes that apply to the given
 branch and item type, regardless of patron category.
 
-The return value is a hashref containing the following key:
+The return value is a hashref containing the following keys:
 
 holdallowed => Hold policy for this branch and itemtype. Possible values:
   0: No holds allowed.
   1: Holds allowed only by patrons that have the same homebranch as the item.
   2: Holds allowed from any patron.
 
+returnbranch => branch to which to return item.  Possible values:
+  noreturn: do not return, let item remain where checked in (floating collections)
+  homebranch: return to item's home branch
+
 This searches branchitemrules in the following order:
 
   * Same branchcode and itemtype
@@ -1391,7 +1383,7 @@ This searches branchitemrules in the following order:
   * branchcode '*', same itemtype
   * branchcode and itemtype '*'
 
-Neither C<$branchcode> nor C<$categorycode> should be '*'.
+Neither C<$branchcode> nor C<$itemtype> should be '*'.
 
 =cut
 
@@ -1401,33 +1393,36 @@ sub GetBranchItemRule {
     my $result = {};
 
     my @attempts = (
-        ['SELECT holdallowed
+        ['SELECT holdallowed, returnbranch
             FROM branch_item_rules
             WHERE branchcode = ?
               AND itemtype = ?', $branchcode, $itemtype],
-        ['SELECT holdallowed
+        ['SELECT holdallowed, returnbranch
             FROM default_branch_circ_rules
             WHERE branchcode = ?', $branchcode],
-        ['SELECT holdallowed
+        ['SELECT holdallowed, returnbranch
             FROM default_branch_item_rules
             WHERE itemtype = ?', $itemtype],
-        ['SELECT holdallowed
+        ['SELECT holdallowed, returnbranch
             FROM default_circ_rules'],
     );
 
     foreach my $attempt (@attempts) {
         my ($query, @bind_params) = @{$attempt};
+        my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params );
 
         # Since branch/category and branch/itemtype use the same per-branch
         # defaults tables, we have to check that the key we want is set, not
         # just that a row was returned
-        return $result if ( defined( $result->{'holdallowed'} = $dbh->selectrow_array( $query, {}, @bind_params ) ) );
+        $result->{'holdallowed'}  = $search_result->{'holdallowed'}  unless ( defined $result->{'holdallowed'} );
+        $result->{'returnbranch'} = $search_result->{'returnbranch'} unless ( defined $result->{'returnbranch'} );
     }
     
     # built-in default circulation rule
-    return {
-        holdallowed => 2,
-    };
+    $result->{'holdallowed'} = 2 unless ( defined $result->{'holdallowed'} );
+    $result->{'returnbranch'} = 'homebranch' unless ( defined $result->{'returnbranch'} );
+
+    return $result;
 }
 
 =head2 AddReturn
@@ -1544,9 +1539,10 @@ sub AddReturn {
     my $item = GetItem($itemnumber) or die "GetItem($itemnumber) failed";
         # full item data, but no borrowernumber or checkout info (no issue)
         # we know GetItem should work because GetItemnumberFromBarcode worked
-    my $hbr      = C4::Context->preference("HomeOrHoldingBranchReturn") || "homebranch";
-    $hbr = $item->{$hbr} || '';
-        # item must be from items table -- issues table has branchcode and issuingbranch, not homebranch nor holdingbranch
+    my $hbr      = GetBranchItemRule($item->{'homebranch'}, $item->{'itype'})->{'returnbranch'} || "homebranch";
+        # get the proper branch to which to return the item
+    $hbr = $item->{$hbr} || $branch ;
+        # if $hbr was "noreturn" or any other non-item table value, then it should 'float' (i.e. stay at this branch)
 
     my $borrowernumber = $borrower->{'borrowernumber'} || undef;    # we don't know if we had a borrower or not
 
@@ -1641,7 +1637,7 @@ sub AddReturn {
 
     # find reserves.....
     # if we don't have a reserve with the status W, we launch the Checkreserves routine
-    my ($resfound, $resrec) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
+    my ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
     if ($resfound) {
           $resrec->{'ResFound'} = $resfound;
         $messages->{'ResFound'} = $resrec;
@@ -1898,10 +1894,11 @@ sub _FixAccountForLostAndReturned {
     my $item_id        = @_ ? shift : $itemnumber;  # Send the barcode if you want that logged in the description
     my $dbh = C4::Context->dbh;
     # check for charge made for lost book
-    my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE (itemnumber = ?) AND (accounttype='L' OR accounttype='Rep') ORDER BY date DESC");
+    my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE itemnumber = ? AND accounttype IN ('L', 'Rep', 'W') ORDER BY date DESC, accountno DESC");
     $sth->execute($itemnumber);
     my $data = $sth->fetchrow_hashref;
     $data or return;    # bail if there is nothing to do
+    $data->{accounttype} eq 'W' and return;    # Written off
 
     # writeoff this amount
     my $offset;
@@ -1989,8 +1986,8 @@ sub _GetCircControlBranch {
     my $circcontrol = C4::Context->preference('CircControl');
     my $branch;
 
-    if ($circcontrol eq 'PickupLibrary') {
-        $branch= C4::Context->userenv->{'branch'} if C4::Context->userenv;
+    if ($circcontrol eq 'PickupLibrary' and (C4::Context->userenv and C4::Context->userenv->{'branch'}) ) {
+        $branch= C4::Context->userenv->{'branch'};
     } elsif ($circcontrol eq 'PatronLibrary') {
         $branch=$borrower->{branchcode};
     } else {
@@ -2259,7 +2256,7 @@ sub CanBookBeRenewed {
                        $error="too_many";
                }
                
-        my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber);
+        my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber);
         if ($resfound) {
             $renewokay = 0;
                        $error="on_reserve"
@@ -2688,11 +2685,18 @@ sub SendCirculationAlert {
         borrowernumber => $borrower->{borrowernumber},
         message_name   => $message_name{$type},
     });
-    my $letter = C4::Letters::getletter('circulation', $type);
-    C4::Letters::parseletter($letter, 'biblio',      $item->{biblionumber});
-    C4::Letters::parseletter($letter, 'biblioitems', $item->{biblionumber});
-    C4::Letters::parseletter($letter, 'borrowers',   $borrower->{borrowernumber});
-    C4::Letters::parseletter($letter, 'branches',    $branch);
+    my $letter =  C4::Letters::GetPreparedLetter (
+        module => 'circulation',
+        letter_code => $type,
+        branchcode => $branch,
+        tables => {
+            'biblio'      => $item->{biblionumber},
+            'biblioitems' => $item->{biblionumber},
+            'borrowers'   => $borrower,
+            'branches'    => $branch,
+        }
+    ) or return;
+
     my @transports = @{ $borrower_preferences->{transports} };
     # warn "no transports" unless @transports;
     for (@transports) {
@@ -2707,7 +2711,8 @@ sub SendCirculationAlert {
             $message->update;
         }
     }
-    $letter;
+
+    return $letter;
 }
 
 =head2 updateWrongTransfer
@@ -2999,14 +3004,17 @@ sub CreateBranchTransferLimit {
 
 =head2 DeleteBranchTransferLimits
 
-  DeleteBranchTransferLimits();
+DeleteBranchTransferLimits($frombranch);
+
+Deletes all the branch transfer limits for one branch
 
 =cut
 
 sub DeleteBranchTransferLimits {
-   my $dbh = C4::Context->dbh;
-   my $sth = $dbh->prepare("TRUNCATE TABLE branch_transfer_limits");
-   $sth->execute();
+    my $branch = shift;
+    my $dbh    = C4::Context->dbh;
+    my $sth    = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
+    $sth->execute($branch);
 }
 
 sub ReturnLostItem{
@@ -3022,7 +3030,7 @@ sub ReturnLostItem{
 
 
 sub LostItem{
-    my ($itemnumber, $mark_returned) = @_;
+    my ($itemnumber, $mark_returned, $charge_fee) = @_;
 
     my $dbh = C4::Context->dbh();
     my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title 
@@ -3037,13 +3045,153 @@ sub LostItem{
     # if a borrower lost the item, add a replacement cost to the their record
     if ( my $borrowernumber = $issues->{borrowernumber} ){
 
-        C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}");
+        C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}")
+          if $charge_fee;
         #FIXME : Should probably have a way to distinguish this from an item that really was returned.
         #warn " $issues->{'borrowernumber'}  /  $itemnumber ";
         MarkIssueReturned($borrowernumber,$itemnumber) if $mark_returned;
     }
 }
 
+sub GetOfflineOperations {
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE branchcode=? ORDER BY timestamp");
+    $sth->execute(C4::Context->userenv->{'branch'});
+    my $results = $sth->fetchall_arrayref({});
+    $sth->finish;
+    return $results;
+}
+
+sub GetOfflineOperation {
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE operationid=?");
+    $sth->execute( shift );
+    my $result = $sth->fetchrow_hashref;
+    $sth->finish;
+    return $result;
+}
+
+sub AddOfflineOperation {
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber) VALUES(?,?,?,?,?,?)");
+    $sth->execute( @_ );
+    return "Added.";
+}
+
+sub DeleteOfflineOperation {
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("DELETE FROM pending_offline_operations WHERE operationid=?");
+    $sth->execute( shift );
+    return "Deleted.";
+}
+
+sub ProcessOfflineOperation {
+    my $operation = shift;
+
+    my $report;
+    if ( $operation->{action} eq 'return' ) {
+        $report = ProcessOfflineReturn( $operation );
+    } elsif ( $operation->{action} eq 'issue' ) {
+        $report = ProcessOfflineIssue( $operation );
+    }
+
+    DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid};
+
+    return $report;
+}
+
+sub ProcessOfflineReturn {
+    my $operation = shift;
+
+    my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
+
+    if ( $itemnumber ) {
+        my $issue = GetOpenIssue( $itemnumber );
+        if ( $issue ) {
+            MarkIssueReturned(
+                $issue->{borrowernumber},
+                $itemnumber,
+                undef,
+                $operation->{timestamp},
+            );
+            ModItem(
+                { renewals => 0, onloan => undef },
+                $issue->{'biblionumber'},
+                $itemnumber
+            );
+            return "Success.";
+        } else {
+            return "Item not issued.";
+        }
+    } else {
+        return "Item not found.";
+    }
+}
+
+sub ProcessOfflineIssue {
+    my $operation = shift;
+
+    my $borrower = C4::Members::GetMemberDetails( undef, $operation->{cardnumber} ); # Get borrower from operation cardnumber
+
+    if ( $borrower->{borrowernumber} ) {
+        my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
+        unless ($itemnumber) {
+            return "Barcode not found.";
+        }
+        my $issue = GetOpenIssue( $itemnumber );
+
+        if ( $issue and ( $issue->{borrowernumber} ne $borrower->{borrowernumber} ) ) { # Item already issued to another borrower, mark it returned
+            MarkIssueReturned(
+                $issue->{borrowernumber},
+                $itemnumber,
+                undef,
+                $operation->{timestamp},
+            );
+        }
+        AddIssue(
+            $borrower,
+            $operation->{'barcode'},
+            undef,
+            1,
+            $operation->{timestamp},
+            undef,
+        );
+        return "Success.";
+    } else {
+        return "Borrower not found.";
+    }
+}
+
+
+
+=head2 TransferSlip
+
+  TransferSlip($user_branch, $itemnumber, $to_branch)
+
+  Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
+
+=cut
+
+sub TransferSlip {
+    my ($branch, $itemnumber, $to_branch) = @_;
+
+    my $item =  GetItem( $itemnumber )
+      or return;
+
+    my $pulldate = C4::Dates->new();
+
+    return C4::Letters::GetPreparedLetter (
+        module => 'circulation',
+        letter_code => 'TRANSFERSLIP',
+        branchcode => $branch,
+        tables => {
+            'branches'    => $to_branch,
+            'biblio'      => $item->{biblionumber},
+            'items'       => $item,
+        },
+    );
+}
+
 
 1;