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
);
}
=cut
sub TransportCostMatrix {
+ my ( $params ) = @_;
+
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};
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;
}
=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 = ();
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);
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};
}
}
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')) {
$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 = ?
$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
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;
my $local_hold_match;
foreach my $item (@$available_items) {
- next
- if ( !$item->{holdallowed} )
- || ( $item->{holdallowed} == 1
- && $item->{homebranch} ne $request->{borrowerbranch} )
- || $item->{_object}->exclude_from_local_holds_priority;
+ next if $item->{_object}->exclude_from_local_holds_priority;
+
+ next unless _checkHoldPolicy($item, $request);
next if $request->{itemnumber} && $request->{itemnumber} != $item->{itemnumber};
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} } } )
)
# 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} };
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};
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}
$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} );
}
# 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
}
}
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}
}
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')
# 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<biblio_id>, 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<requests>: the pending holds count for the biblio.
+
+=item I<available_items> the count of items that are available to fill holds for the biblio.
+
+=item I<mapped_items> the total items that got mapped.
+
+=back
+
+=head4 Optional parameters
+
+=over
+
+=item I<branches_to_use> a list of branchcodes to be used to restrict which items can be used.
+
+=item I<transport_cost_matrix> is the output of C<TransportCostMatrix>.
+
+=item I<delete> 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;