X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FHoldsQueue.pm;h=8173efaae81f0eabf68ef5a86f54da8505f7a716;hb=eded6edacc5e3bf8dd0be21ed05842c3b78aadce;hp=ce4594b882996b5947a096f9fc82c4feb23f0a96;hpb=d46657f8af980524a4351e40abe0b901d1af3e6a;p=koha-ffzg.git diff --git a/C4/HoldsQueue.pm b/C4/HoldsQueue.pm old mode 100755 new mode 100644 index ce4594b882..8173efaae8 --- a/C4/HoldsQueue.pm +++ b/C4/HoldsQueue.pm @@ -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 . # FIXME: expand perldoc, explain intended logic @@ -23,29 +23,28 @@ use strict; use warnings; use C4::Context; -use C4::Search; -use C4::Items; -use C4::Branch; -use C4::Circulation; -use C4::Members; -use C4::Biblio; -use C4::Dates qw/format_date/; - -use List::Util qw(shuffle); -use List::MoreUtils qw(any); -use Data::Dumper; - -use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); +use C4::Circulation qw( GetBranchItemRule ); +use Koha::DateUtils qw( dt_from_string ); +use Koha::Items; +use Koha::Patrons; +use Koha::Libraries; + +use List::Util qw( shuffle ); +use List::MoreUtils qw( any ); + +our (@ISA, @EXPORT_OK); BEGIN { - $VERSION = 3.03; require Exporter; @ISA = qw(Exporter); @EXPORT_OK = qw( - &CreateQueue - &GetHoldsQueueItems - - &TransportCostMatrix - &UpdateTransportCostMatrix + CreateQueue + GetHoldsQueueItems + + TransportCostMatrix + UpdateTransportCostMatrix + GetPendingHoldRequestsForBib + load_branches_to_pull_from + update_queue_for_biblio ); } @@ -61,17 +60,24 @@ Returns Transport Cost Matrix as a hashref => dbh; my $transport_costs = $dbh->selectall_arrayref("SELECT * FROM transport_cost",{ Slice => {} }); + my $today = dt_from_string(); 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 + }; } + return \%transport_cost_matrix; } @@ -90,7 +96,7 @@ sub UpdateTransportCostMatrix { my $sth = $dbh->prepare("INSERT INTO transport_cost (frombranch, tobranch, cost, disable_transfer) VALUES (?, ?, ?, ?)"); - $dbh->do("TRUNCATE TABLE transport_cost"); + $dbh->do("DELETE FROM transport_cost"); foreach (@$records) { my $cost = $_->{cost}; my $from = $_->{frombranch}; @@ -99,7 +105,7 @@ sub UpdateTransportCostMatrix { $cost ||= 0; } elsif ( !defined ($cost) || ($cost !~ m/(0|[1-9][0-9]*)(\.[0-9]*)?/o) ) { - warn "Invalid $from -> $to cost $cost - must be a number >= 0, disablig"; + warn "Invalid $from -> $to cost $cost - must be a number >= 0, disabling"; $cost = 0; $_->{disable_transfer} = 1; } @@ -109,40 +115,50 @@ sub UpdateTransportCostMatrix { =head2 GetHoldsQueueItems - GetHoldsQueueItems($branch); + GetHoldsQueueItems({ branchlimit => $branch, itemtypeslimit => $itype, ccodeslimit => $ccode, locationslimit => $location ); Returns hold queue for a holding branch. If branch is omitted, then whole queue is returned =cut sub GetHoldsQueueItems { - my ($branchlimit) = @_; + my $params = shift; my $dbh = C4::Context->dbh; my @bind_params = (); - 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 + 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, biblio.subtitle, biblio.medium, + biblio.part_number, biblio.part_name, + biblioitems.publicationyear, biblioitems.pages, biblioitems.size, + biblioitems.isbn, biblioitems.editionstatement, items.copynumber FROM tmp_holdsqueue JOIN biblio USING (biblionumber) LEFT JOIN biblioitems USING (biblionumber) LEFT JOIN items USING ( itemnumber) + WHERE 1=1 /; - if ($branchlimit) { - $query .=" WHERE tmp_holdsqueue.holdingbranch = ?"; - push @bind_params, $branchlimit; + if ($params->{branchlimit}) { + $query .="AND tmp_holdsqueue.holdingbranch = ? "; + push @bind_params, $params->{branchlimit}; + } + if( $params->{itemtypeslimit} ) { + $query .=" AND items.itype = ? "; + push @bind_params, $params->{itemtypeslimit}; + } + if( $params->{ccodeslimit} ) { + $query .=" AND items.ccode = ? "; + push @bind_params, $params->{ccodeslimit}; + } + if( $params->{locationslimit} ) { + $query .=" AND items.location = ? "; + push @bind_params, $params->{locationslimit}; } $query .= " ORDER BY ccode, location, cn_sort, author, title, pickbranch, reservedate"; my $sth = $dbh->prepare($query); $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->{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}; @@ -183,34 +199,25 @@ sub CreateQueue { undef $transport_cost_matrix; } } - unless ($transport_cost_matrix) { - $branches_to_use = load_branches_to_pull_from(); - } + + $branches_to_use = load_branches_to_pull_from($use_transport_cost_matrix); my $bibs_with_pending_requests = GetBibsWithPendingHoldRequests(); foreach my $biblionumber (@$bibs_with_pending_requests) { + $total_bibs++; - my $hold_requests = GetPendingHoldRequestsForBib($biblionumber); - my $available_items = GetItemsAvailableToFillHoldRequestsForBib($biblionumber, $branches_to_use); - $total_requests += scalar(@$hold_requests); - $total_available_items += scalar(@$available_items); - my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix); - $item_map or next; - my $item_map_size = scalar(keys %$item_map) - or next; + my $result = update_queue_for_biblio( + { biblio_id => $biblionumber, + branches_to_use => $branches_to_use, + transport_cost_matrix => $transport_cost_matrix, + } + ); - $num_items_mapped += $item_map_size; - CreatePicklistFromItemMap($item_map); - AddToHoldTargetMap($item_map); - if (($item_map_size < scalar(@$hold_requests )) and - ($item_map_size < scalar(@$available_items))) { - # DOUBLE CHECK, but this is probably OK - unfilled item-level requests - # FIXME - #warn "unfilled requests for $biblionumber"; - #warn Dumper($hold_requests), Dumper($available_items), Dumper($item_map); - } + $total_requests += $result->{requests}; + $total_available_items += $result->{available_items}; + $num_items_mapped += $result->{mapped_items}; } } @@ -267,8 +274,8 @@ sub GetPendingHoldRequestsForBib { my $dbh = C4::Context->dbh; - my $request_query = "SELECT biblionumber, borrowernumber, itemnumber, priority, reserves.branchcode, - reservedate, reservenotes, borrowers.branchcode AS borrowerbranch + my $request_query = "SELECT biblionumber, borrowernumber, itemnumber, priority, reserve_id, reserves.branchcode, + reservedate, reservenotes, borrowers.branchcode AS borrowerbranch, itemtype, item_level_hold FROM reserves JOIN borrowers USING (borrowernumber) WHERE biblionumber = ? @@ -299,6 +306,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 @@ -306,7 +314,7 @@ sub GetItemsAvailableToFillHoldRequestsForBib { my ($biblionumber, $branches_to_use) = @_; my $dbh = C4::Context->dbh; - my $items_query = "SELECT itemnumber, homebranch, holdingbranch, itemtypes.itemtype AS itype + my $items_query = "SELECT items.itemnumber, homebranch, holdingbranch, itemtypes.itemtype AS itype FROM items "; if (C4::Context->preference('item-level_itypes')) { @@ -315,14 +323,17 @@ sub GetItemsAvailableToFillHoldRequestsForBib { $items_query .= "JOIN biblioitems USING (biblioitemnumber) LEFT JOIN itemtypes USING (itemtype) "; } - $items_query .= "WHERE items.notforloan = 0 + $items_query .= " LEFT JOIN branchtransfers ON (items.itemnumber = branchtransfers.itemnumber)"; + $items_query .= " WHERE items.notforloan = 0 AND holdingbranch IS NOT NULL AND itemlost = 0 - AND wthdrawn = 0"; + AND withdrawn = 0"; + $items_query .= " AND branchtransfers.datearrived IS NULL + AND branchtransfers.datecancelled IS NULL"; $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) - AND itemnumber NOT IN ( + AND items.itemnumber NOT IN ( SELECT itemnumber FROM reserves WHERE biblionumber = ? @@ -330,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) { @@ -342,11 +351,57 @@ sub GetItemsAvailableToFillHoldRequestsForBib { $sth->execute(@params); my $itm = $sth->fetchall_arrayref({}); - my @items = grep { ! scalar GetTransfers($_->{itemnumber}) } @$itm; return [ grep { - my $rule = GetBranchItemRule($_->{homebranch}, $_->{itype}); - $_->{holdallowed} = $rule->{holdallowed} != 0 - } @items ]; + my $rule = C4::Circulation::GetBranchItemRule($_->{homebranch}, $_->{itype}); + $_->{holdallowed} = $rule->{holdallowed}; + $_->{hold_fulfillment_policy} = $rule->{hold_fulfillment_policy}; + } @{$itm} ]; +} + +=head2 _checkHoldPolicy + + _checkHoldPolicy($item, $request) + + check if item agrees with hold policies + +=cut + +sub _checkHoldPolicy { + my ( $item, $request ) = @_; + + return 0 unless $item->{holdallowed} ne 'not_allowed'; + + return 0 + if $item->{holdallowed} eq 'from_home_library' + && $item->{homebranch} ne $request->{borrowerbranch}; + + return 0 + if $item->{'holdallowed'} eq 'from_local_hold_group' + && !Koha::Libraries->find( $item->{homebranch} ) + ->validate_hold_sibling( { branchcode => $request->{borrowerbranch} } ); + + my $hold_fulfillment_policy = $item->{hold_fulfillment_policy}; + + return 0 + if $hold_fulfillment_policy eq 'holdgroup' + && !Koha::Libraries->find( $item->{homebranch} ) + ->validate_hold_sibling( { branchcode => $request->{branchcode} } ); + + return 0 + if $hold_fulfillment_policy eq 'homebranch' + && $request->{branchcode} ne $item->{$hold_fulfillment_policy}; + + return 0 + if $hold_fulfillment_policy eq 'holdingbranch' + && $request->{branchcode} ne $item->{$hold_fulfillment_policy}; + + return 0 + if $hold_fulfillment_policy eq 'patrongroup' + && !Koha::Libraries->find( $request->{borrowerbranch} ) + ->validate_hold_sibling( { branchcode => $request->{branchcode} } ); + + return 1; + } =head2 MapItemsToHoldRequests @@ -367,6 +422,10 @@ sub MapItemsToHoldRequests { grep { defined($_->{itemnumber}) } @$hold_requests; + map { $_->{_object} = Koha::Items->find( $_->{itemnumber} ) } @$available_items; + my $libraries = {}; + map { $libraries->{$_->id} = $_ } Koha::Libraries->search->as_list; + # group available items by itemnumber my %items_by_itemnumber = map { $_->{itemnumber} => $_ } @$available_items; @@ -378,24 +437,98 @@ sub MapItemsToHoldRequests { # figure out which item-level requests can be filled my $num_items_remaining = scalar(@$available_items); + + # Look for Local Holds Priority matches first + if ( C4::Context->preference('LocalHoldsPriority') ) { + my $LocalHoldsPriorityPatronControl = + C4::Context->preference('LocalHoldsPriorityPatronControl'); + my $LocalHoldsPriorityItemControl = + C4::Context->preference('LocalHoldsPriorityItemControl'); + + foreach my $request (@$hold_requests) { + last if $num_items_remaining == 0; + my $patron = Koha::Patrons->find($request->{borrowernumber}); + next if $patron->category->exclude_from_local_holds_priority; + + my $local_hold_match; + foreach my $item (@$available_items) { + next if $item->{_object}->exclude_from_local_holds_priority; + + next unless _checkHoldPolicy($item, $request); + + next if $request->{itemnumber} && $request->{itemnumber} != $item->{itemnumber}; + + next unless $item->{_object}->can_be_transferred( { to => $libraries->{ $request->{branchcode} } } ); + + my $local_holds_priority_item_branchcode = + $item->{$LocalHoldsPriorityItemControl}; + + my $local_holds_priority_patron_branchcode = + ( $LocalHoldsPriorityPatronControl eq 'PickupLibrary' ) + ? $request->{branchcode} + : ( $LocalHoldsPriorityPatronControl eq 'HomeLibrary' ) + ? $request->{borrowerbranch} + : undef; + + $local_hold_match = + $local_holds_priority_item_branchcode eq + $local_holds_priority_patron_branchcode; + + if ($local_hold_match) { + if ( exists $items_by_itemnumber{ $item->{itemnumber} } + and not exists $allocated_items{ $item->{itemnumber} } + and not $request->{allocated}) + { + $item_map{ $item->{itemnumber} } = { + borrowernumber => $request->{borrowernumber}, + biblionumber => $request->{biblionumber}, + holdingbranch => $item->{holdingbranch}, + pickup_branch => $request->{branchcode} + || $request->{borrowerbranch}, + reserve_id => $request->{reserve_id}, + item_level => $request->{item_level_hold}, + reservedate => $request->{reservedate}, + reservenotes => $request->{reservenotes}, + }; + $allocated_items{ $item->{itemnumber} }++; + $request->{allocated} = 1; + $num_items_remaining--; + } + } + } + } + } + foreach my $request (@$hold_requests) { last if $num_items_remaining == 0; + next if $request->{allocated}; # is this an item-level request? if (defined($request->{itemnumber})) { # fill it if possible; if not skip it - if (exists $items_by_itemnumber{$request->{itemnumber}} and - not exists $allocated_items{$request->{itemnumber}}) { - $item_map{$request->{itemnumber}} = { + if ( + exists $items_by_itemnumber{ $request->{itemnumber} } + and not exists $allocated_items{ $request->{itemnumber} } + and _checkHoldPolicy($items_by_itemnumber{ $request->{itemnumber} }, $request) # Don't fill item level holds that contravene the hold pickup policy at this time + and ( !$request->{itemtype} # If hold itemtype is set, item's itemtype must match + || $items_by_itemnumber{ $request->{itemnumber} }->{itype} eq $request->{itemtype} ) + + and $items_by_itemnumber{ $request->{itemnumber} }->{_object}->can_be_transferred( { to => $libraries->{ $request->{branchcode} } } ) + + ) + { + + $item_map{ $request->{itemnumber} } = { borrowernumber => $request->{borrowernumber}, - biblionumber => $request->{biblionumber}, - holdingbranch => $items_by_itemnumber{$request->{itemnumber}}->{holdingbranch}, - pickup_branch => $request->{branchcode} || $request->{borrowerbranch}, - item_level => 1, - reservedate => $request->{reservedate}, - reservenotes => $request->{reservenotes}, + biblionumber => $request->{biblionumber}, + holdingbranch => $items_by_itemnumber{ $request->{itemnumber} }->{holdingbranch}, + pickup_branch => $request->{branchcode} || $request->{borrowerbranch}, + reserve_id => $request->{reserve_id}, + item_level => $request->{item_level_hold}, + reservedate => $request->{reservedate}, + reservenotes => $request->{reservenotes}, }; - $allocated_items{$request->{itemnumber}}++; + $allocated_items{ $request->{itemnumber} }++; $num_items_remaining--; } } else { @@ -407,7 +540,7 @@ sub MapItemsToHoldRequests { # group available items by branch my %items_by_branch = (); foreach my $item (@$available_items) { - next unless $item->{holdallowed}; + next unless $item->{holdallowed} ne 'not_allowed'; push @{ $items_by_branch{ $item->{holdingbranch} } }, $item unless exists $allocated_items{ $item->{itemnumber} }; @@ -419,6 +552,7 @@ sub MapItemsToHoldRequests { my $pull_branches; foreach my $request (@$hold_requests) { last if $num_items_remaining == 0; + next if $request->{allocated}; next if defined($request->{itemnumber}); # already handled these # look for local match first @@ -428,13 +562,20 @@ sub MapItemsToHoldRequests { 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} ) { + next unless $items_by_itemnumber{ $item->{itemnumber} }->{_object}->can_be_transferred( { to => $libraries->{ $request->{branchcode} } } ); + + if ( + $request->{borrowerbranch} eq $item->{homebranch} + && _checkHoldPolicy($item, $request) # Don't fill item level holds that contravene the hold pickup policy at this time + && ( !$request->{itemtype} # If hold itemtype is set, item's itemtype must match + || ( $request->{itemnumber} && ( $items_by_itemnumber{ $request->{itemnumber} }->{itype} eq $request->{itemtype} ) ) ) + ) + { $itemnumber = $item->{itemnumber}; last; } } $holdingbranch = $pickup_branch; - $itemnumber ||= $holding_branch_items->[0]->{itemnumber}; } elsif ($transport_cost_matrix) { $pull_branches = [keys %items_by_branch]; @@ -444,14 +585,21 @@ sub MapItemsToHoldRequests { my $holding_branch_items = $items_by_branch{$holdingbranch}; foreach my $item (@$holding_branch_items) { next if $request->{borrowerbranch} ne $item->{homebranch}; + next unless $items_by_itemnumber{ $item->{itemnumber} }->{_object}->can_be_transferred( { to => $libraries->{ $request->{branchcode} } } ); + + # Don't fill item level holds that contravene the hold pickup policy at this time + next unless _checkHoldPolicy($item, $request); + + # If hold itemtype is set, item's itemtype must match + next unless ( !$request->{itemtype} + || $item->{itype} eq $request->{itemtype} ); $itemnumber = $item->{itemnumber}; last; } - $itemnumber ||= $holding_branch_items->[0]->{itemnumber}; } else { - warn "No transport costs for $pickup_branch"; + next; } } @@ -462,6 +610,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} @@ -470,14 +620,62 @@ sub MapItemsToHoldRequests { $holdingbranch ||= $branch; foreach my $item (@$holding_branch_items) { next if $pickup_branch ne $item->{homebranch}; + next unless _checkHoldPolicy($item, $request); + next unless $items_by_itemnumber{ $item->{itemnumber} }->{_object}->can_be_transferred( { to => $libraries->{ $request->{branchcode} } } ); + + # Don't fill item level holds that contravene the hold pickup policy at this time + next unless $item->{hold_fulfillment_policy} eq 'any' + || $request->{branchcode} eq $item->{ $item->{hold_fulfillment_policy} }; + + # If hold itemtype is set, item's itemtype must match + next unless ( !$request->{itemtype} + || $item->{itype} eq $request->{itemtype} ); $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 || !$holdingbranch) { + foreach my $current_item ( @{ $items_by_branch{$holdingbranch} } ) { + next unless _checkHoldPolicy($current_item, $request); # Don't fill item level holds that contravene the hold pickup policy at this time + + # If hold itemtype is set, item's itemtype must match + next unless ( !$request->{itemtype} + || $current_item->{itype} eq $request->{itemtype} ); + + next unless $items_by_itemnumber{ $current_item->{itemnumber} }->{_object}->can_be_transferred( { to => $libraries->{ $request->{branchcode} } } ); + + $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) { + # Don't fill item level holds that contravene the hold pickup policy at this time + next unless _checkHoldPolicy($item, $request); + + # If hold itemtype is set, item's itemtype must match + next unless ( !$request->{itemtype} + || $item->{itype} eq $request->{itemtype} ); + + next unless $items_by_itemnumber{ $item->{itemnumber} }->{_object}->can_be_transferred( { to => $libraries->{ $request->{branchcode} } } ); + + $itemnumber = $item->{itemnumber}; + $holdingbranch = $branch; + last PULL_BRANCHES2; + } + } + } } if ($itemnumber) { @@ -491,7 +689,8 @@ sub MapItemsToHoldRequests { biblionumber => $request->{biblionumber}, holdingbranch => $holdingbranch, pickup_branch => $pickup_branch, - item_level => 0, + reserve_id => $request->{reserve_id}, + item_level => $request->{item_level_hold}, reservedate => $request->{reservedate}, reservenotes => $request->{reservenotes}, }; @@ -527,18 +726,18 @@ sub CreatePicklistFromItemMap { my $reservenotes = $mapped_item->{reservenotes}; my $item_level = $mapped_item->{item_level}; - my $item = GetItem($itemnumber); - my $barcode = $item->{barcode}; - my $itemcallnumber = $item->{itemcallnumber}; + my $item = Koha::Items->find($itemnumber); + my $barcode = $item->barcode; + my $itemcallnumber = $item->itemcallnumber; - my $borrower = GetMember('borrowernumber'=>$borrowernumber); - my $cardnumber = $borrower->{'cardnumber'}; - my $surname = $borrower->{'surname'}; - my $firstname = $borrower->{'firstname'}; - my $phone = $borrower->{'phone'}; + my $patron = Koha::Patrons->find( $borrowernumber ); + my $cardnumber = $patron->cardnumber; + my $surname = $patron->surname; + my $firstname = $patron->firstname; + my $phone = $patron->phone; - my $bib = GetBiblioData($biblionumber); - my $title = $bib->{title}; + my $biblio = Koha::Biblios->find( $biblionumber ); + my $title = $biblio->title; $sth_load->execute($biblionumber, $itemnumber, $barcode, $surname, $firstname, $phone, $borrowernumber, $cardnumber, $reservedate, $title, $itemcallnumber, @@ -556,15 +755,15 @@ sub AddToHoldTargetMap { my $dbh = C4::Context->dbh; my $insert_sql = q( - INSERT INTO hold_fill_targets (borrowernumber, biblionumber, itemnumber, source_branchcode, item_level_request) - VALUES (?, ?, ?, ?, ?) + INSERT INTO hold_fill_targets (borrowernumber, biblionumber, itemnumber, source_branchcode, item_level_request, reserve_id) + VALUES (?, ?, ?, ?, ?, ?) ); my $sth_insert = $dbh->prepare($insert_sql); foreach my $itemnumber (keys %$item_map) { my $mapped_item = $item_map->{$itemnumber}; $sth_insert->execute($mapped_item->{borrowernumber}, $mapped_item->{biblionumber}, $itemnumber, - $mapped_item->{holdingbranch}, $mapped_item->{item_level}); + $mapped_item->{holdingbranch}, $mapped_item->{item_level}, $mapped_item->{reserve_id}); } } @@ -578,12 +777,31 @@ sub _trim { } sub load_branches_to_pull_from { - my $static_branch_list = C4::Context->preference("StaticHoldsQueueWeight") - or return; + my $use_transport_cost_matrix = shift; + + my @branches_to_use; + + unless ( $use_transport_cost_matrix ) { + 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); - my @branches_to_use = map _trim($_), split /,/, $static_branch_list; + @branches_to_use = shuffle(@branches_to_use) + if C4::Context->preference("RandomizeHoldsQueueWeight"); - @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; } @@ -595,7 +813,7 @@ sub least_cost_branch { # 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; + 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 @@ -632,5 +850,90 @@ sub least_cost_branch { # return $branch[0] if @branch == 1; } +=head3 update_queue_for_biblio + + my $result = update_queue_for_biblio( + { + biblio_id => $biblio_id, + [ branches_to_use => $branches_to_use, + transport_cost_matrix => $transport_cost_matrix, + delete => $delete, ] + } + ); + +Given a I, this method calculates and sets the holds queue entries +for the biblio's holds, and the hold fill targets (items). + +=head4 Return value + +It return a hashref containing: + +=over + +=item I: the pending holds count for the biblio. + +=item I the count of items that are available to fill holds for the biblio. + +=item I the total items that got mapped. + +=back + +=head4 Optional parameters + +=over + +=item I a list of branchcodes to be used to restrict which items can be used. + +=item I is the output of C. + +=item I tells the method to delete prior entries on the related tables for the biblio_id. + +=back + +Note: All the optional parameters will be calculated in the method if omitted. They +are allowed to be passed to avoid calculating them many times inside loops. + +=cut + +sub update_queue_for_biblio { + my ($args) = @_; + + my $biblio_id = $args->{biblio_id}; + + my $branches_to_use = $args->{branches_to_use} // load_branches_to_pull_from( C4::Context->preference('UseTransportCostMatrix') ); + my $transport_cost_matrix; + + if ( !exists $args->{transport_cost_matrix} + && C4::Context->preference('UseTransportCostMatrix') ) { + $transport_cost_matrix = TransportCostMatrix(); + } else { + $transport_cost_matrix = $args->{transport_cost_matrix}; + } + + if ( $args->{delete} ) { + my $dbh = C4::Context->dbh; + + $dbh->do("DELETE FROM tmp_holdsqueue WHERE biblionumber=$biblio_id"); + $dbh->do("DELETE FROM hold_fill_targets WHERE biblionumber=$biblio_id"); + } + + my $hold_requests = GetPendingHoldRequestsForBib($biblio_id); + my $available_items = GetItemsAvailableToFillHoldRequestsForBib( $biblio_id, $branches_to_use ); + + my $result = { + requests => scalar( @{$hold_requests} ), + available_items => scalar( @{$available_items} ), + }; + + my $item_map = MapItemsToHoldRequests( $hold_requests, $available_items, $branches_to_use, $transport_cost_matrix ); + $result->{mapped_items} = scalar( keys %{$item_map} ); + + if ($item_map) { + CreatePicklistFromItemMap($item_map); + AddToHoldTargetMap($item_map); + } + + return $result; +} 1;