Bug 12721: (followup) Replace mysqlism by DBIx::Class
[koha.git] / C4 / HoldsQueue.pm
index 608732f..4fda2c6 100755 (executable)
@@ -4,18 +4,18 @@ package C4::HoldsQueue;
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY 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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 # FIXME: expand perldoc, explain intended logic
 
@@ -29,14 +29,14 @@ use C4::Branch;
 use C4::Circulation;
 use C4::Members;
 use C4::Biblio;
-use C4::Dates qw/format_date/;
+use Koha::DateUtils;
 
 use List::Util qw(shuffle);
+use List::MoreUtils qw(any);
 use Data::Dumper;
 
-use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 BEGIN {
-    $VERSION = 3.03;
     require Exporter;
     @ISA = qw(Exporter);
     @EXPORT_OK = qw(
@@ -48,8 +48,6 @@ BEGIN {
      );
 }
 
-# XXX This is not safe in a persistant environment
-my $dbh   = C4::Context->dbh;
 
 =head1 FUNCTIONS
 
@@ -62,15 +60,28 @@ Returns Transport Cost Matrix as a hashref <to branch code> => <from branch code
 =cut
 
 sub TransportCostMatrix {
+    my $dbh   = C4::Context->dbh;
     my $transport_costs = $dbh->selectall_arrayref("SELECT * FROM transport_cost",{ Slice => {} });
 
+    my $today = dt_from_string();
+    my $calendars;
     my %transport_cost_matrix;
     foreach (@$transport_costs) {
-        my $from = $_->{frombranch};
-        my $to = $_->{tobranch};
-        my $cost = $_->{cost};
+        my $from     = $_->{frombranch};
+        my $to       = $_->{tobranch};
+        my $cost     = $_->{cost};
         my $disabled = $_->{disable_transfer};
-        $transport_cost_matrix{$to}{$from} = { cost => $cost, disable_transfer => $disabled };
+        $transport_cost_matrix{$to}{$from} = {
+            cost             => $cost,
+            disable_transfer => $disabled
+        };
+
+        if ( C4::Context->preference("HoldsQueueSkipClosed") ) {
+            $calendars->{$from} ||= Koha::Calendar->new( branchcode => $from );
+            $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
+              $calendars->{$from}->is_holiday( $today );
+        }
+
     }
     return \%transport_cost_matrix;
 }
@@ -86,6 +97,7 @@ Records: { frombranch => <code>, tobranch => <code>, cost => <figure>, disable_t
 
 sub UpdateTransportCostMatrix {
     my ($records) = @_;
+    my $dbh   = C4::Context->dbh;
 
     my $sth = $dbh->prepare("INSERT INTO transport_cost (frombranch, tobranch, cost, disable_transfer) VALUES (?, ?, ?, ?)");
 
@@ -116,9 +128,10 @@ Returns hold queue for a holding branch. If branch is omitted, then whole queue
 
 sub GetHoldsQueueItems {
     my ($branchlimit) = @_;
+    my $dbh   = C4::Context->dbh;
 
     my @bind_params = ();
-    my $query = q/SELECT tmp_holdsqueue.*, biblio.author, items.ccode, items.location, items.enumchron, items.cn_sort, biblioitems.publishercode,biblio.copyrightdate,biblioitems.publicationyear,biblioitems.pages,biblioitems.size,biblioitems.publicationyear,biblioitems.isbn,items.copynumber
+    my $query = q/SELECT tmp_holdsqueue.*, biblio.author, items.ccode, items.itype, biblioitems.itemtype, items.location, items.enumchron, items.cn_sort, biblioitems.publishercode,biblio.copyrightdate,biblioitems.publicationyear,biblioitems.pages,biblioitems.size,biblioitems.publicationyear,biblioitems.isbn,items.copynumber
                   FROM tmp_holdsqueue
                        JOIN biblio      USING (biblionumber)
                   LEFT JOIN biblioitems USING (biblionumber)
@@ -133,13 +146,19 @@ sub GetHoldsQueueItems {
     $sth->execute(@bind_params);
     my $items = [];
     while ( my $row = $sth->fetchrow_hashref ){
-        $row->{reservedate} = format_date($row->{reservedate});
         my $record = GetMarcBiblio($row->{biblionumber});
         if ($record){
-            $row->{subtitle} = GetRecordValue('subtitle',$record,'')->[0]->{subfield};
+            $row->{subtitle} = [ map { $_->{subfield} } @{ GetRecordValue( 'subtitle', $record, '' ) } ];
             $row->{parts} = GetRecordValue('parts',$record,'')->[0]->{subfield};
             $row->{numbers} = GetRecordValue('numbers',$record,'')->[0]->{subfield};
         }
+
+        # return the bib-level or item-level itype per syspref
+        if (!C4::Context->preference('item-level_itypes')) {
+            $row->{itype} = $row->{itemtype};
+        }
+        delete $row->{itemtype};
+
         push @$items, $row;
     }
     return $items;
@@ -154,6 +173,7 @@ Top level function that turns reserves into tmp_holdsqueue and hold_fill_targets
 =cut
 
 sub CreateQueue {
+    my $dbh   = C4::Context->dbh;
 
     $dbh->do("DELETE FROM tmp_holdsqueue");  # clear the old table for new info
     $dbh->do("DELETE FROM hold_fill_targets");
@@ -220,7 +240,9 @@ sub GetBibsWithPendingHoldRequests {
                      FROM reserves
                      WHERE found IS NULL
                      AND priority > 0
-                     AND reservedate <= CURRENT_DATE()";
+                     AND reservedate <= CURRENT_DATE()
+                     AND suspend = 0
+                     ";
     my $sth = $dbh->prepare($bib_query);
 
     $sth->execute();
@@ -263,6 +285,7 @@ sub GetPendingHoldRequestsForBib {
                          AND found IS NULL
                          AND priority > 0
                          AND reservedate <= CURRENT_DATE()
+                         AND suspend = 0
                          ORDER BY priority";
     my $sth = $dbh->prepare($request_query);
     $sth->execute($biblionumber);
@@ -286,6 +309,7 @@ to fill a hold request if and only if:
     * it is not currently in transit
     * it is not lost
     * it is not sitting on the hold shelf
+    * it is not damaged (unless AllowHoldsOnDamagedItems is on)
 
 =cut
 
@@ -305,7 +329,7 @@ sub GetItemsAvailableToFillHoldRequestsForBib {
     $items_query .=   "WHERE items.notforloan = 0
                        AND holdingbranch IS NOT NULL
                        AND itemlost = 0
-                       AND wthdrawn = 0";
+                       AND withdrawn = 0";
     $items_query .= "  AND damaged = 0" unless C4::Context->preference('AllowHoldsOnDamagedItems');
     $items_query .= "  AND items.onloan IS NULL
                        AND (itemtypes.notforloan IS NULL OR itemtypes.notforloan = 0)
@@ -317,8 +341,6 @@ sub GetItemsAvailableToFillHoldRequestsForBib {
                            AND (found IS NOT NULL OR priority = 0)
                         )
                        AND items.biblionumber = ?";
-    $items_query .=  " AND damaged = 0 "
-      unless C4::Context->preference('AllowHoldsOnDamagedItems');
 
     my @params = ($biblionumber, $biblionumber);
     if ($branches_to_use && @$branches_to_use) {
@@ -332,7 +354,7 @@ sub GetItemsAvailableToFillHoldRequestsForBib {
     my @items = grep { ! scalar GetTransfers($_->{itemnumber}) } @$itm;
     return [ grep {
         my $rule = GetBranchItemRule($_->{homebranch}, $_->{itype});
-        $_->{holdallowed} = $rule->{holdallowed} != 0
+        $_->{holdallowed} = $rule->{holdallowed};
     } @items ];
 }
 
@@ -349,8 +371,6 @@ sub MapItemsToHoldRequests {
     return unless scalar(@$hold_requests) > 0;
     return unless scalar(@$available_items) > 0;
 
-    my $automatic_return = C4::Context->preference("AutomaticItemReturn");
-
     # identify item-level requests
     my %specific_items_requested = map { $_->{itemnumber} => 1 }
                                    grep { defined($_->{itemnumber}) }
@@ -398,11 +418,10 @@ sub MapItemsToHoldRequests {
     foreach my $item (@$available_items) {
         next unless $item->{holdallowed};
 
-        push @{ $items_by_branch{  $automatic_return ? $item->{homebranch}
-                                                     : $item->{holdingbranch} } }, $item
+        push @{ $items_by_branch{ $item->{holdingbranch} } }, $item
           unless exists $allocated_items{ $item->{itemnumber} };
     }
-    return unless keys %items_by_branch;
+    return \%item_map unless keys %items_by_branch;
 
     # now handle the title-level requests
     $num_items_remaining = scalar(@$available_items) - scalar(keys %allocated_items);
@@ -415,7 +434,7 @@ sub MapItemsToHoldRequests {
         my $pickup_branch = $request->{branchcode} || $request->{borrowerbranch};
         my ($itemnumber, $holdingbranch);
 
-        my $holding_branch_items = $automatic_return ? undef : $items_by_branch{$pickup_branch};
+        my $holding_branch_items = $items_by_branch{$pickup_branch};
         if ( $holding_branch_items ) {
             foreach my $item (@$holding_branch_items) {
                 if ( $request->{borrowerbranch} eq $item->{homebranch} ) {
@@ -424,7 +443,6 @@ sub MapItemsToHoldRequests {
                 }
             }
             $holdingbranch = $pickup_branch;
-            $itemnumber ||= $holding_branch_items->[0]->{itemnumber};
         }
         elsif ($transport_cost_matrix) {
             $pull_branches = [keys %items_by_branch];
@@ -438,10 +456,9 @@ sub MapItemsToHoldRequests {
                     $itemnumber = $item->{itemnumber};
                     last;
                 }
-                $itemnumber ||= $holding_branch_items->[0]->{itemnumber};
             }
             else {
-                warn "No transport costs for $pickup_branch";
+                next;
             }
         }
 
@@ -452,6 +469,8 @@ sub MapItemsToHoldRequests {
             } else {
                 $pull_branches = [keys %items_by_branch];
             }
+
+            # Try picking items where the home and pickup branch match first
             PULL_BRANCHES:
             foreach my $branch (@$pull_branches) {
                 my $holding_branch_items = $items_by_branch{$branch}
@@ -460,14 +479,40 @@ sub MapItemsToHoldRequests {
                 $holdingbranch ||= $branch;
                 foreach my $item (@$holding_branch_items) {
                     next if $pickup_branch ne $item->{homebranch};
+                    next if ( $item->{holdallowed} == 1 && $item->{homebranch} ne $request->{borrowerbranch} );
 
                     $itemnumber = $item->{itemnumber};
                     $holdingbranch = $branch;
                     last PULL_BRANCHES;
                 }
             }
-            $itemnumber ||= $items_by_branch{$holdingbranch}->[0]->{itemnumber}
-              if $holdingbranch;
+
+            # Now try items from the least cost branch based on the transport cost matrix or StaticHoldsQueueWeight
+            unless ( $itemnumber ) {
+                foreach my $current_item ( @{ $items_by_branch{$holdingbranch} } ) {
+                    if ( $holdingbranch && ( $current_item->{holdallowed} == 2 || $request->{borrowerbranch} eq $current_item->{homebranch} ) ) {
+                        $itemnumber = $current_item->{itemnumber};
+                        last; # quit this loop as soon as we have a suitable item
+                    }
+                }
+            }
+
+            # Now try for items for any item that can fill this hold
+            unless ( $itemnumber ) {
+                PULL_BRANCHES2:
+                foreach my $branch (@$pull_branches) {
+                    my $holding_branch_items = $items_by_branch{$branch}
+                      or next;
+
+                    foreach my $item (@$holding_branch_items) {
+                        next if ( $item->{holdallowed} == 1 && $item->{homebranch} ne $request->{borrowerbranch} );
+
+                        $itemnumber = $item->{itemnumber};
+                        $holdingbranch = $branch;
+                        last PULL_BRANCHES2;
+                    }
+                }
+            }
         }
 
         if ($itemnumber) {
@@ -568,12 +613,27 @@ sub _trim {
 }
 
 sub load_branches_to_pull_from {
-    my $static_branch_list = C4::Context->preference("StaticHoldsQueueWeight")
-      or return;
-
-    my @branches_to_use = map _trim($_), split /,/, $static_branch_list;
-
-    @branches_to_use = shuffle(@branches_to_use) if  C4::Context->preference("RandomizeHoldsQueueWeight");
+    my @branches_to_use;
+
+    my $static_branch_list = C4::Context->preference("StaticHoldsQueueWeight");
+    @branches_to_use = map { _trim($_) } split( /,/, $static_branch_list )
+      if $static_branch_list;
+
+    @branches_to_use =
+      Koha::Database->new()->schema()->resultset('Branch')
+      ->get_column('branchcode')->all()
+      unless (@branches_to_use);
+
+    @branches_to_use = shuffle(@branches_to_use)
+      if C4::Context->preference("RandomizeHoldsQueueWeight");
+
+    my $today = dt_from_string();
+    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
+        @branches_to_use = grep {
+            !Koha::Calendar->new( branchcode => $_ )
+              ->is_holiday( $today )
+        } @branches_to_use;
+    }
 
     return \@branches_to_use;
 }
@@ -583,9 +643,13 @@ sub least_cost_branch {
     #$from - arrayref
     my ($to, $from, $transport_cost_matrix) = @_;
 
-# Nothing really spectacular: supply to branch, a list of potential from branches
-# and find the minimum from - to value from the transport_cost_matrix
-    return $from->[0] if @$from == 1;
+    # Nothing really spectacular: supply to branch, a list of potential from branches
+    # and find the minimum from - to value from the transport_cost_matrix
+    return $from->[0] if ( @$from == 1 && $transport_cost_matrix->{$to}{$from->[0]}->{disable_transfer} != 1 );
+
+    # If the pickup library is in the list of libraries to pull from,
+    # return that library right away, it is obviously the least costly
+    return ($to) if any { $_ eq $to } @$from;
 
     my ($least_cost, @branch);
     foreach (@$from) {