X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FHoldsQueue.pm;h=7d0d4abc25bb14e14dd4fd58a00ab3d3c72ffd48;hb=7dfa7bcc21dbf3b79e33a957e9afd46dab214494;hp=774e2477383ddc4be616d9c6e44671c3872a071e;hpb=762a03fca1e7b5b0c37a4a0715a1dc2d84b9200a;p=koha-ffzg.git diff --git a/C4/HoldsQueue.pm b/C4/HoldsQueue.pm old mode 100755 new mode 100644 index 774e247738..7d0d4abc25 --- a/C4/HoldsQueue.pm +++ b/C4/HoldsQueue.pm @@ -23,29 +23,28 @@ use strict; use warnings; use C4::Context; -use C4::Search; -use C4::Items; -use C4::Circulation; -use C4::Members; -use C4::Biblio; -use Koha::DateUtils; +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); -use Data::Dumper; +use List::Util qw( shuffle ); +use List::MoreUtils qw( any ); -use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); +our (@ISA, @EXPORT_OK); BEGIN { 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,11 +60,12 @@ 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 $calendars; my %transport_cost_matrix; foreach (@$transport_costs) { my $from = $_->{frombranch}; @@ -76,14 +76,8 @@ sub TransportCostMatrix { 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; } @@ -121,14 +115,14 @@ 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 = (); @@ -137,15 +131,28 @@ sub GetHoldsQueueItems { biblio.copyrightdate, biblio.subtitle, biblio.medium, biblio.part_number, biblio.part_name, biblioitems.publicationyear, biblioitems.pages, biblioitems.size, - biblioitems.isbn, items.copynumber + 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); @@ -192,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}; } } @@ -276,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, itemtype + 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 = ? @@ -316,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')) { @@ -325,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 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 = ? @@ -350,12 +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}); + my $rule = C4::Circulation::GetBranchItemRule($_->{homebranch}, $_->{itype}); $_->{holdallowed} = $rule->{holdallowed}; $_->{hold_fulfillment_policy} = $rule->{hold_fulfillment_policy}; - } @items ]; + } @{$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 @@ -378,7 +424,7 @@ sub MapItemsToHoldRequests { map { $_->{_object} = Koha::Items->find( $_->{itemnumber} ) } @$available_items; my $libraries = {}; - map { $libraries->{$_->id} = $_ } Koha::Libraries->search(); + map { $libraries->{$_->id} = $_ } Koha::Libraries->search->as_list; # group available items by itemnumber my %items_by_itemnumber = map { $_->{itemnumber} => $_ } @$available_items; @@ -400,15 +446,17 @@ sub MapItemsToHoldRequests { C4::Context->preference('LocalHoldsPriorityItemControl'); foreach my $request (@$hold_requests) { - next if (defined($request->{itemnumber})); #skip item level holds in local priority checking 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->{holdallowed} ) - || ( $item->{holdallowed} == 1 - && $item->{homebranch} ne $request->{borrowerbranch} ); + 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} } } ); @@ -437,7 +485,8 @@ sub MapItemsToHoldRequests { holdingbranch => $item->{holdingbranch}, pickup_branch => $request->{branchcode} || $request->{borrowerbranch}, - item_level => 0, + reserve_id => $request->{reserve_id}, + item_level => $request->{item_level_hold}, reservedate => $request->{reservedate}, reservenotes => $request->{reservenotes}, }; @@ -460,12 +509,10 @@ sub MapItemsToHoldRequests { if ( exists $items_by_itemnumber{ $request->{itemnumber} } and not exists $allocated_items{ $request->{itemnumber} } - and ( # Don't fill item level holds that contravene the hold pickup policy at this time - ( $items_by_itemnumber{ $request->{itemnumber} }->{hold_fulfillment_policy} eq 'any' ) - || ( $request->{branchcode} eq $items_by_itemnumber{ $request->{itemnumber} }->{ $items_by_itemnumber{ $request->{itemnumber} }->{hold_fulfillment_policy} } ) + 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} } } ) ) @@ -476,7 +523,8 @@ sub MapItemsToHoldRequests { biblionumber => $request->{biblionumber}, holdingbranch => $items_by_itemnumber{ $request->{itemnumber} }->{holdingbranch}, pickup_branch => $request->{branchcode} || $request->{borrowerbranch}, - item_level => 1, + reserve_id => $request->{reserve_id}, + item_level => $request->{item_level_hold}, reservedate => $request->{reservedate}, reservenotes => $request->{reservenotes}, }; @@ -492,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} }; @@ -518,10 +566,9 @@ sub MapItemsToHoldRequests { if ( $request->{borrowerbranch} eq $item->{homebranch} - && ( ( $item->{hold_fulfillment_policy} eq 'any' ) # Don't fill item level holds that contravene the hold pickup policy at this time - || $request->{branchcode} eq $item->{ $item->{hold_fulfillment_policy} } ) + && _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 - || $items_by_itemnumber{ $request->{itemnumber} }->{itype} eq $request->{itemtype} ) + || ( $request->{itemnumber} && ( $items_by_itemnumber{ $request->{itemnumber} }->{itype} eq $request->{itemtype} ) ) ) ) { $itemnumber = $item->{itemnumber}; @@ -541,8 +588,7 @@ sub MapItemsToHoldRequests { 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} }; + next unless _checkHoldPolicy($item, $request); # If hold itemtype is set, item's itemtype must match next unless ( !$request->{itemtype} @@ -574,13 +620,9 @@ 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} ); + 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} ); @@ -592,23 +634,18 @@ sub MapItemsToHoldRequests { } # Now try items from the least cost branch based on the transport cost matrix or StaticHoldsQueueWeight - unless ( $itemnumber ) { + unless ( $itemnumber || !$holdingbranch) { foreach my $current_item ( @{ $items_by_branch{$holdingbranch} } ) { - if ( $holdingbranch && ( $current_item->{holdallowed} == 2 || $request->{borrowerbranch} eq $current_item->{homebranch} ) ) { + next unless _checkHoldPolicy($current_item, $request); # Don't fill item level holds that contravene the hold pickup policy at this time - # Don't fill item level holds that contravene the hold pickup policy at this time - next unless $current_item->{hold_fulfillment_policy} eq 'any' - || $request->{branchcode} eq $current_item->{ $current_item->{hold_fulfillment_policy} }; - - # If hold itemtype is set, item's itemtype must match - next unless ( !$request->{itemtype} - || $current_item->{itype} eq $request->{itemtype} ); + # 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} } } ); + 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 - } + $itemnumber = $current_item->{itemnumber}; + last; # quit this loop as soon as we have a suitable item } } @@ -620,11 +657,8 @@ sub MapItemsToHoldRequests { or next; foreach my $item (@$holding_branch_items) { - next if ( $item->{holdallowed} == 1 && $item->{homebranch} ne $request->{borrowerbranch} ); - # 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} }; + next unless _checkHoldPolicy($item, $request); # If hold itemtype is set, item's itemtype must match next unless ( !$request->{itemtype} @@ -651,7 +685,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}, }; @@ -716,15 +751,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}); } } @@ -738,11 +773,15 @@ sub _trim { } sub load_branches_to_pull_from { + my $use_transport_cost_matrix = shift; + 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; + 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') @@ -807,5 +846,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;