|
Line 0
Link Here
|
|
|
1 |
package C4::HoldsQueue; |
| 2 |
|
| 3 |
# Copyright 2011 Catalyst IT |
| 4 |
# |
| 5 |
# This file is part of Koha. |
| 6 |
# |
| 7 |
# Koha is free software; you can redistribute it and/or modify it under the |
| 8 |
# terms of the GNU General Public License as published by the Free Software |
| 9 |
# Foundation; either version 2 of the License, or (at your option) any later |
| 10 |
# version. |
| 11 |
# |
| 12 |
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY |
| 13 |
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
| 14 |
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 15 |
# |
| 16 |
# You should have received a copy of the GNU General Public License along |
| 17 |
# with Koha; if not, write to the Free Software Foundation, Inc., |
| 18 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 19 |
|
| 20 |
# FIXME: expand perldoc, explain intended logic |
| 21 |
|
| 22 |
use strict; |
| 23 |
use warnings; |
| 24 |
|
| 25 |
use C4::Context; |
| 26 |
use C4::Search; |
| 27 |
use C4::Items; |
| 28 |
use C4::Branch; |
| 29 |
use C4::Circulation; |
| 30 |
use C4::Members; |
| 31 |
use C4::Biblio; |
| 32 |
use C4::Dates qw/format_date/; |
| 33 |
|
| 34 |
use List::Util qw(shuffle); |
| 35 |
use Data::Dumper; |
| 36 |
|
| 37 |
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); |
| 38 |
BEGIN { |
| 39 |
$VERSION = 3.03; |
| 40 |
require Exporter; |
| 41 |
@ISA = qw(Exporter); |
| 42 |
@EXPORT_OK = qw( |
| 43 |
&CreateQueue |
| 44 |
&GetHoldsQueueItems |
| 45 |
|
| 46 |
&TransportCostMatrix |
| 47 |
&UpdateTransportCostMatrix |
| 48 |
); |
| 49 |
} |
| 50 |
|
| 51 |
# XXX This is not safe in a persistant environment |
| 52 |
my $dbh = C4::Context->dbh; |
| 53 |
|
| 54 |
=head1 FUNCTIONS |
| 55 |
|
| 56 |
=head2 TransportCostMatrix |
| 57 |
|
| 58 |
TransportCostMatrix(); |
| 59 |
|
| 60 |
Returns Transport Cost Matrix as a hashref <to branch code> => <from branch code> => cost |
| 61 |
|
| 62 |
=cut |
| 63 |
|
| 64 |
sub TransportCostMatrix { |
| 65 |
my $transport_costs = $dbh->selectall_arrayref("SELECT * FROM transport_cost",{ Slice => {} }); |
| 66 |
|
| 67 |
my %transport_cost_matrix; |
| 68 |
foreach (@$transport_costs) { |
| 69 |
my $from = $_->{frombranch}; |
| 70 |
my $to = $_->{tobranch}; |
| 71 |
my $cost = $_->{cost}; |
| 72 |
my $disabled = $_->{disable_transfer}; |
| 73 |
$transport_cost_matrix{$to}{$from} = { cost => $cost, disable_transfer => $disabled }; |
| 74 |
} |
| 75 |
return \%transport_cost_matrix; |
| 76 |
} |
| 77 |
|
| 78 |
=head2 UpdateTransportCostMatrix |
| 79 |
|
| 80 |
UpdateTransportCostMatrix($records); |
| 81 |
|
| 82 |
Updates full Transport Cost Matrix table. $records is an arrayref of records. |
| 83 |
Records: { frombranch => <code>, tobranch => <code>, cost => <figure>, disable_transfer => <0,1> } |
| 84 |
|
| 85 |
=cut |
| 86 |
|
| 87 |
sub UpdateTransportCostMatrix { |
| 88 |
my ($records) = @_; |
| 89 |
|
| 90 |
my $sth = $dbh->prepare("INSERT INTO transport_cost (frombranch, tobranch, cost, disable_transfer) VALUES (?, ?, ?, ?)"); |
| 91 |
|
| 92 |
$dbh->do("TRUNCATE TABLE transport_cost"); |
| 93 |
foreach (@$records) { |
| 94 |
my $cost = $_->{cost}; |
| 95 |
my $from = $_->{frombranch}; |
| 96 |
my $to = $_->{tobranch}; |
| 97 |
if ( !defined ($cost) || ($cost !~ m/(0|[1-9][0-9]*)(\.[0-9]*)?/o) ) { |
| 98 |
warn "Invalid $from -> $to cost $cost - nust be a number in 0 to 1 range, disablig"; |
| 99 |
$_->{disable_transfer} = 1; |
| 100 |
} |
| 101 |
$sth->execute( $from, $to, $cost, $_->{disable_transfer} ? 1 : 0 ); |
| 102 |
} |
| 103 |
} |
| 104 |
|
| 105 |
=head2 GetHoldsQueueItems |
| 106 |
|
| 107 |
GetHoldsQueueItems($branch); |
| 108 |
|
| 109 |
Returns hold queue for a holding branch. If branch is omitted, then whole queue is returned |
| 110 |
|
| 111 |
=cut |
| 112 |
|
| 113 |
sub GetHoldsQueueItems { |
| 114 |
my ($branchlimit) = @_; |
| 115 |
|
| 116 |
my @bind_params = (); |
| 117 |
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 |
| 118 |
FROM tmp_holdsqueue |
| 119 |
JOIN biblio USING (biblionumber) |
| 120 |
LEFT JOIN biblioitems USING (biblionumber) |
| 121 |
LEFT JOIN items USING ( itemnumber) |
| 122 |
/; |
| 123 |
if ($branchlimit) { |
| 124 |
$query .=" WHERE tmp_holdsqueue.holdingbranch = ?"; |
| 125 |
push @bind_params, $branchlimit; |
| 126 |
} |
| 127 |
$query .= " ORDER BY ccode, location, cn_sort, author, title, pickbranch, reservedate"; |
| 128 |
my $sth = $dbh->prepare($query); |
| 129 |
$sth->execute(@bind_params); |
| 130 |
my $items = []; |
| 131 |
while ( my $row = $sth->fetchrow_hashref ){ |
| 132 |
$row->{reservedate} = format_date($row->{reservedate}); |
| 133 |
my $record = GetMarcBiblio($row->{biblionumber}); |
| 134 |
if ($record){ |
| 135 |
$row->{subtitle} = GetRecordValue('subtitle',$record,'')->[0]->{subfield}; |
| 136 |
$row->{parts} = GetRecordValue('parts',$record,'')->[0]->{subfield}; |
| 137 |
$row->{numbers} = GetRecordValue('numbers',$record,'')->[0]->{subfield}; |
| 138 |
} |
| 139 |
push @$items, $row; |
| 140 |
} |
| 141 |
return $items; |
| 142 |
} |
| 143 |
|
| 144 |
=head2 CreateQueue |
| 145 |
|
| 146 |
CreateQueue(); |
| 147 |
|
| 148 |
Top level function that turns reserves into tmp_holdsqueue and hold_fill_targets. |
| 149 |
|
| 150 |
=cut |
| 151 |
|
| 152 |
sub CreateQueue { |
| 153 |
|
| 154 |
$dbh->do("DELETE FROM tmp_holdsqueue"); # clear the old table for new info |
| 155 |
$dbh->do("DELETE FROM hold_fill_targets"); |
| 156 |
|
| 157 |
my $total_bibs = 0; |
| 158 |
my $total_requests = 0; |
| 159 |
my $total_available_items = 0; |
| 160 |
my $num_items_mapped = 0; |
| 161 |
|
| 162 |
my $branches_to_use; |
| 163 |
my $transport_cost_matrix; |
| 164 |
my $use_transport_cost_matrix = C4::Context->preference("UseTransportCostMatrix"); |
| 165 |
if ($use_transport_cost_matrix) { |
| 166 |
debug( "Using cost matrix" ); |
| 167 |
$transport_cost_matrix = TransportCostMatrix(); |
| 168 |
unless (keys %$transport_cost_matrix) { |
| 169 |
warn "UseTransportCostMatrix set to yes, but matrix not populated"; |
| 170 |
undef $transport_cost_matrix; |
| 171 |
} |
| 172 |
} |
| 173 |
unless ($transport_cost_matrix) { |
| 174 |
debug( "Not using cost matrix" ); |
| 175 |
$branches_to_use = load_branches_to_pull_from(); |
| 176 |
} |
| 177 |
|
| 178 |
my $bibs_with_pending_requests = GetBibsWithPendingHoldRequests(); |
| 179 |
debug( "bibs_with_pending_requests: ".Dumper($bibs_with_pending_requests) ); |
| 180 |
|
| 181 |
foreach my $biblionumber (@$bibs_with_pending_requests) { |
| 182 |
$total_bibs++; |
| 183 |
my $hold_requests = GetPendingHoldRequestsForBib($biblionumber); |
| 184 |
my $available_items = GetItemsAvailableToFillHoldRequestsForBib($biblionumber, $branches_to_use); |
| 185 |
$total_requests += scalar(@$hold_requests); |
| 186 |
$total_available_items += scalar(@$available_items); |
| 187 |
|
| 188 |
my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix); |
| 189 |
$item_map or next; |
| 190 |
my $item_map_size = scalar(keys %$item_map) |
| 191 |
or next;; |
| 192 |
|
| 193 |
$num_items_mapped += $item_map_size; |
| 194 |
CreatePicklistFromItemMap($item_map); |
| 195 |
AddToHoldTargetMap($item_map); |
| 196 |
if (($item_map_size < scalar(@$hold_requests )) and |
| 197 |
($item_map_size < scalar(@$available_items))) { |
| 198 |
# DOUBLE CHECK, but this is probably OK - unfilled item-level requests |
| 199 |
# FIXME |
| 200 |
#warn "unfilled requests for $biblionumber"; |
| 201 |
#warn Dumper($hold_requests), Dumper($available_items), Dumper($item_map); |
| 202 |
} |
| 203 |
} |
| 204 |
} |
| 205 |
|
| 206 |
=head2 GetBibsWithPendingHoldRequests |
| 207 |
|
| 208 |
my $biblionumber_aref = GetBibsWithPendingHoldRequests(); |
| 209 |
|
| 210 |
Return an arrayref of the biblionumbers of all bibs |
| 211 |
that have one or more unfilled hold requests. |
| 212 |
|
| 213 |
=cut |
| 214 |
|
| 215 |
sub GetBibsWithPendingHoldRequests { |
| 216 |
my $dbh = C4::Context->dbh; |
| 217 |
|
| 218 |
my $bib_query = "SELECT DISTINCT biblionumber |
| 219 |
FROM reserves |
| 220 |
WHERE found IS NULL |
| 221 |
AND priority > 0 |
| 222 |
AND reservedate <= CURRENT_DATE()"; |
| 223 |
my $sth = $dbh->prepare($bib_query); |
| 224 |
|
| 225 |
$sth->execute(); |
| 226 |
my $biblionumbers = $sth->fetchall_arrayref(); |
| 227 |
|
| 228 |
return [ map { $_->[0] } @$biblionumbers ]; |
| 229 |
} |
| 230 |
|
| 231 |
=head2 GetPendingHoldRequestsForBib |
| 232 |
|
| 233 |
my $requests = GetPendingHoldRequestsForBib($biblionumber); |
| 234 |
|
| 235 |
Returns an arrayref of hashrefs to pending, unfilled hold requests |
| 236 |
on the bib identified by $biblionumber. The following keys |
| 237 |
are present in each hashref: |
| 238 |
|
| 239 |
biblionumber |
| 240 |
borrowernumber |
| 241 |
itemnumber |
| 242 |
priority |
| 243 |
branchcode |
| 244 |
reservedate |
| 245 |
reservenotes |
| 246 |
borrowerbranch |
| 247 |
|
| 248 |
The arrayref is sorted in order of increasing priority. |
| 249 |
|
| 250 |
=cut |
| 251 |
|
| 252 |
sub GetPendingHoldRequestsForBib { |
| 253 |
my $biblionumber = shift; |
| 254 |
|
| 255 |
my $dbh = C4::Context->dbh; |
| 256 |
|
| 257 |
my $request_query = "SELECT biblionumber, borrowernumber, itemnumber, priority, reserves.branchcode, |
| 258 |
reservedate, reservenotes, borrowers.branchcode AS borrowerbranch |
| 259 |
FROM reserves |
| 260 |
JOIN borrowers USING (borrowernumber) |
| 261 |
WHERE biblionumber = ? |
| 262 |
AND found IS NULL |
| 263 |
AND priority > 0 |
| 264 |
AND reservedate <= CURRENT_DATE() |
| 265 |
ORDER BY priority"; |
| 266 |
my $sth = $dbh->prepare($request_query); |
| 267 |
$sth->execute($biblionumber); |
| 268 |
|
| 269 |
my $requests = $sth->fetchall_arrayref({}); |
| 270 |
return $requests; |
| 271 |
|
| 272 |
} |
| 273 |
|
| 274 |
=head2 GetItemsAvailableToFillHoldRequestsForBib |
| 275 |
|
| 276 |
my $available_items = GetItemsAvailableToFillHoldRequestsForBib($biblionumber, $branches_ar); |
| 277 |
|
| 278 |
Returns an arrayref of items available to fill hold requests |
| 279 |
for the bib identified by C<$biblionumber>. An item is available |
| 280 |
to fill a hold request if and only if: |
| 281 |
|
| 282 |
* it is not on loan |
| 283 |
* it is not withdrawn |
| 284 |
* it is not marked notforloan |
| 285 |
* it is not currently in transit |
| 286 |
* it is not lost |
| 287 |
* it is not sitting on the hold shelf |
| 288 |
|
| 289 |
=cut |
| 290 |
|
| 291 |
sub GetItemsAvailableToFillHoldRequestsForBib { |
| 292 |
my ($biblionumber, $branches_to_use) = @_; |
| 293 |
|
| 294 |
my $dbh = C4::Context->dbh; |
| 295 |
my $items_query = "SELECT itemnumber, homebranch, holdingbranch, itemtypes.itemtype AS itype |
| 296 |
FROM items "; |
| 297 |
|
| 298 |
if (C4::Context->preference('item-level_itypes')) { |
| 299 |
$items_query .= "LEFT JOIN itemtypes ON (itemtypes.itemtype = items.itype) "; |
| 300 |
} else { |
| 301 |
$items_query .= "JOIN biblioitems USING (biblioitemnumber) |
| 302 |
LEFT JOIN itemtypes USING (itemtype) "; |
| 303 |
} |
| 304 |
$items_query .= "WHERE items.notforloan = 0 |
| 305 |
AND holdingbranch IS NOT NULL |
| 306 |
AND itemlost = 0 |
| 307 |
AND wthdrawn = 0 |
| 308 |
AND items.onloan IS NULL |
| 309 |
AND (itemtypes.notforloan IS NULL OR itemtypes.notforloan = 0) |
| 310 |
AND itemnumber NOT IN ( |
| 311 |
SELECT itemnumber |
| 312 |
FROM reserves |
| 313 |
WHERE biblionumber = ? |
| 314 |
AND itemnumber IS NOT NULL |
| 315 |
AND (found IS NOT NULL OR priority = 0) |
| 316 |
) |
| 317 |
AND items.biblionumber = ?"; |
| 318 |
$items_query .= " AND damaged = 0 " |
| 319 |
unless C4::Context->preference('AllowHoldsOnDamagedItems'); |
| 320 |
|
| 321 |
my @params = ($biblionumber, $biblionumber); |
| 322 |
if ($branches_to_use && @$branches_to_use) { |
| 323 |
$items_query .= " AND holdingbranch IN (" . join (",", map { "?" } @$branches_to_use) . ")"; |
| 324 |
push @params, @$branches_to_use; |
| 325 |
} |
| 326 |
my $sth = $dbh->prepare($items_query); |
| 327 |
$sth->execute(@params); |
| 328 |
|
| 329 |
my $itm = $sth->fetchall_arrayref({}); |
| 330 |
my @items = grep { ! scalar GetTransfers($_->{itemnumber}) } @$itm; |
| 331 |
return [ grep { |
| 332 |
my $rule = GetBranchItemRule($_->{homebranch}, $_->{itype}); |
| 333 |
$_->{holdallowed} = $rule->{holdallowed} != 0 |
| 334 |
} @items ]; |
| 335 |
} |
| 336 |
|
| 337 |
=head2 MapItemsToHoldRequests |
| 338 |
|
| 339 |
MapItemsToHoldRequests($hold_requests, $available_items, $branches, $transport_cost_matrix) |
| 340 |
|
| 341 |
=cut |
| 342 |
|
| 343 |
sub MapItemsToHoldRequests { |
| 344 |
my ($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix) = @_; |
| 345 |
|
| 346 |
# handle trival cases |
| 347 |
return unless scalar(@$hold_requests) > 0; |
| 348 |
return unless scalar(@$available_items) > 0; |
| 349 |
|
| 350 |
debug( "MapItemsToHoldRequests() for ", Dumper($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix) ); |
| 351 |
my $automatic_return = C4::Context->preference("AutomaticItemReturn"); |
| 352 |
|
| 353 |
# identify item-level requests |
| 354 |
my %specific_items_requested = map { $_->{itemnumber} => 1 } |
| 355 |
grep { defined($_->{itemnumber}) } |
| 356 |
@$hold_requests; |
| 357 |
|
| 358 |
# group available items by itemnumber |
| 359 |
my %items_by_itemnumber = map { $_->{itemnumber} => $_ } @$available_items; |
| 360 |
|
| 361 |
# items already allocated |
| 362 |
my %allocated_items = (); |
| 363 |
|
| 364 |
# map of items to hold requests |
| 365 |
my %item_map = (); |
| 366 |
|
| 367 |
# figure out which item-level requests can be filled |
| 368 |
my $num_items_remaining = scalar(@$available_items); |
| 369 |
foreach my $request (@$hold_requests) { |
| 370 |
last if $num_items_remaining == 0; |
| 371 |
|
| 372 |
# is this an item-level request? |
| 373 |
if (defined($request->{itemnumber})) { |
| 374 |
# fill it if possible; if not skip it |
| 375 |
if (exists $items_by_itemnumber{$request->{itemnumber}} and |
| 376 |
not exists $allocated_items{$request->{itemnumber}}) { |
| 377 |
$item_map{$request->{itemnumber}} = { |
| 378 |
borrowernumber => $request->{borrowernumber}, |
| 379 |
biblionumber => $request->{biblionumber}, |
| 380 |
holdingbranch => $items_by_itemnumber{$request->{itemnumber}}->{holdingbranch}, |
| 381 |
pickup_branch => $request->{branchcode} || $request->{borrowerbranch}, |
| 382 |
item_level => 1, |
| 383 |
reservedate => $request->{reservedate}, |
| 384 |
reservenotes => $request->{reservenotes}, |
| 385 |
}; |
| 386 |
$allocated_items{$request->{itemnumber}}++; |
| 387 |
$num_items_remaining--; |
| 388 |
} |
| 389 |
} else { |
| 390 |
# it's title-level request that will take up one item |
| 391 |
$num_items_remaining--; |
| 392 |
} |
| 393 |
} |
| 394 |
|
| 395 |
# group available items by branch |
| 396 |
my %items_by_branch = (); |
| 397 |
foreach my $item (@$available_items) { |
| 398 |
next unless $item->{holdallowed}; |
| 399 |
|
| 400 |
push @{ $items_by_branch{ $automatic_return ? $item->{homebranch} |
| 401 |
: $item->{holdingbranch} } }, $item |
| 402 |
unless exists $allocated_items{ $item->{itemnumber} }; |
| 403 |
} |
| 404 |
return unless keys %items_by_branch; |
| 405 |
|
| 406 |
# now handle the title-level requests |
| 407 |
$num_items_remaining = scalar(@$available_items) - scalar(keys %allocated_items); |
| 408 |
my $pull_branches; |
| 409 |
foreach my $request (@$hold_requests) { |
| 410 |
last if $num_items_remaining == 0; |
| 411 |
next if defined($request->{itemnumber}); # already handled these |
| 412 |
|
| 413 |
# look for local match first |
| 414 |
my $pickup_branch = $request->{branchcode} || $request->{borrowerbranch}; |
| 415 |
my ($itemnumber, $holdingbranch); |
| 416 |
|
| 417 |
my $holding_branch_items = $automatic_return ? undef : $items_by_branch{$pickup_branch}; |
| 418 |
if ( $holding_branch_items ) { |
| 419 |
foreach my $item (@$holding_branch_items) { |
| 420 |
if ( $request->{borrowerbranch} eq $item->{homebranch} ) { |
| 421 |
$itemnumber = $item->{itemnumber}; |
| 422 |
last; |
| 423 |
} |
| 424 |
} |
| 425 |
$holdingbranch = $pickup_branch; |
| 426 |
$itemnumber ||= $holding_branch_items->[0]->{itemnumber}; |
| 427 |
} |
| 428 |
elsif ($transport_cost_matrix) { |
| 429 |
$pull_branches = [keys %items_by_branch]; |
| 430 |
$holdingbranch = least_cost_branch( $pickup_branch, $pull_branches, $transport_cost_matrix ); |
| 431 |
if ( $holdingbranch ) { |
| 432 |
|
| 433 |
my $holding_branch_items = $items_by_branch{$holdingbranch}; |
| 434 |
foreach my $item (@$holding_branch_items) { |
| 435 |
next if $request->{borrowerbranch} ne $item->{homebranch}; |
| 436 |
|
| 437 |
$itemnumber = $item->{itemnumber}; |
| 438 |
last; |
| 439 |
} |
| 440 |
$itemnumber ||= $holding_branch_items->[0]->{itemnumber}; |
| 441 |
} |
| 442 |
else { |
| 443 |
warn "No transport costs for $pickup_branch"; |
| 444 |
} |
| 445 |
} |
| 446 |
|
| 447 |
unless ($itemnumber) { |
| 448 |
# not found yet, fall back to basics |
| 449 |
if ($branches_to_use) { |
| 450 |
$pull_branches = $branches_to_use; |
| 451 |
} else { |
| 452 |
$pull_branches = [keys %items_by_branch]; |
| 453 |
} |
| 454 |
PULL_BRANCHES: |
| 455 |
foreach my $branch (@$pull_branches) { |
| 456 |
my $holding_branch_items = $items_by_branch{$branch} |
| 457 |
or next; |
| 458 |
|
| 459 |
$holdingbranch ||= $branch; |
| 460 |
foreach my $item (@$holding_branch_items) { |
| 461 |
next if $pickup_branch ne $item->{homebranch}; |
| 462 |
|
| 463 |
$itemnumber = $item->{itemnumber}; |
| 464 |
$holdingbranch = $branch; |
| 465 |
last PULL_BRANCHES; |
| 466 |
} |
| 467 |
} |
| 468 |
$itemnumber ||= $items_by_branch{$holdingbranch}->[0]->{itemnumber} |
| 469 |
if $holdingbranch; |
| 470 |
} |
| 471 |
|
| 472 |
if ($itemnumber) { |
| 473 |
my $holding_branch_items = $items_by_branch{$holdingbranch} |
| 474 |
or die "Have $itemnumber, $holdingbranch, but no items!"; |
| 475 |
@$holding_branch_items = grep { $_->{itemnumber} != $itemnumber } @$holding_branch_items; |
| 476 |
delete $items_by_branch{$holdingbranch} unless @$holding_branch_items; |
| 477 |
|
| 478 |
$item_map{$itemnumber} = { |
| 479 |
borrowernumber => $request->{borrowernumber}, |
| 480 |
biblionumber => $request->{biblionumber}, |
| 481 |
holdingbranch => $holdingbranch, |
| 482 |
pickup_branch => $pickup_branch, |
| 483 |
item_level => 0, |
| 484 |
reservedate => $request->{reservedate}, |
| 485 |
reservenotes => $request->{reservenotes}, |
| 486 |
}; |
| 487 |
$num_items_remaining--; |
| 488 |
} |
| 489 |
} |
| 490 |
return \%item_map; |
| 491 |
} |
| 492 |
|
| 493 |
=head2 CreatePickListFromItemMap |
| 494 |
|
| 495 |
=cut |
| 496 |
|
| 497 |
sub CreatePicklistFromItemMap { |
| 498 |
my $item_map = shift; |
| 499 |
debug ("CreatePicklistFromItemMap for ", Dumper($item_map) ); |
| 500 |
|
| 501 |
my $dbh = C4::Context->dbh; |
| 502 |
|
| 503 |
my $sth_load=$dbh->prepare(" |
| 504 |
INSERT INTO tmp_holdsqueue (biblionumber,itemnumber,barcode,surname,firstname,phone,borrowernumber, |
| 505 |
cardnumber,reservedate,title, itemcallnumber, |
| 506 |
holdingbranch,pickbranch,notes, item_level_request) |
| 507 |
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |
| 508 |
"); |
| 509 |
|
| 510 |
foreach my $itemnumber (sort keys %$item_map) { |
| 511 |
my $mapped_item = $item_map->{$itemnumber}; |
| 512 |
my $biblionumber = $mapped_item->{biblionumber}; |
| 513 |
my $borrowernumber = $mapped_item->{borrowernumber}; |
| 514 |
my $pickbranch = $mapped_item->{pickup_branch}; |
| 515 |
my $holdingbranch = $mapped_item->{holdingbranch}; |
| 516 |
my $reservedate = $mapped_item->{reservedate}; |
| 517 |
my $reservenotes = $mapped_item->{reservenotes}; |
| 518 |
my $item_level = $mapped_item->{item_level}; |
| 519 |
|
| 520 |
my $item = GetItem($itemnumber); |
| 521 |
my $barcode = $item->{barcode}; |
| 522 |
my $itemcallnumber = $item->{itemcallnumber}; |
| 523 |
|
| 524 |
my $borrower = GetMember('borrowernumber'=>$borrowernumber); |
| 525 |
my $cardnumber = $borrower->{'cardnumber'}; |
| 526 |
my $surname = $borrower->{'surname'}; |
| 527 |
my $firstname = $borrower->{'firstname'}; |
| 528 |
my $phone = $borrower->{'phone'}; |
| 529 |
|
| 530 |
my $bib = GetBiblioData($biblionumber); |
| 531 |
my $title = $bib->{title}; |
| 532 |
|
| 533 |
$sth_load->execute($biblionumber, $itemnumber, $barcode, $surname, $firstname, $phone, $borrowernumber, |
| 534 |
$cardnumber, $reservedate, $title, $itemcallnumber, |
| 535 |
$holdingbranch, $pickbranch, $reservenotes, $item_level); |
| 536 |
} |
| 537 |
} |
| 538 |
|
| 539 |
=head2 AddToHoldTargetMap |
| 540 |
|
| 541 |
=cut |
| 542 |
|
| 543 |
sub AddToHoldTargetMap { |
| 544 |
my $item_map = shift; |
| 545 |
|
| 546 |
my $dbh = C4::Context->dbh; |
| 547 |
|
| 548 |
my $insert_sql = q( |
| 549 |
INSERT INTO hold_fill_targets (borrowernumber, biblionumber, itemnumber, source_branchcode, item_level_request) |
| 550 |
VALUES (?, ?, ?, ?, ?) |
| 551 |
); |
| 552 |
my $sth_insert = $dbh->prepare($insert_sql); |
| 553 |
|
| 554 |
foreach my $itemnumber (keys %$item_map) { |
| 555 |
my $mapped_item = $item_map->{$itemnumber}; |
| 556 |
$sth_insert->execute($mapped_item->{borrowernumber}, $mapped_item->{biblionumber}, $itemnumber, |
| 557 |
$mapped_item->{holdingbranch}, $mapped_item->{item_level}); |
| 558 |
} |
| 559 |
} |
| 560 |
|
| 561 |
# Helper functions, not part of any interface |
| 562 |
|
| 563 |
sub debug { |
| 564 |
# warn @_; |
| 565 |
} |
| 566 |
|
| 567 |
sub load_branches_to_pull_from { |
| 568 |
my $static_branch_list = C4::Context->preference("StaticHoldsQueueWeight") |
| 569 |
or return; |
| 570 |
|
| 571 |
my @branches_to_use = map { s/^\s+//; s/\s+$//; $_; } split /,/, $static_branch_list; |
| 572 |
|
| 573 |
@branches_to_use = shuffle(@branches_to_use) if C4::Context->preference("RandomizeHoldsQueueWeight"); |
| 574 |
|
| 575 |
return \@branches_to_use; |
| 576 |
} |
| 577 |
|
| 578 |
sub least_cost_branch { |
| 579 |
|
| 580 |
#$from - arrayref |
| 581 |
my ($to, $from, $transport_cost_matrix) = @_; |
| 582 |
|
| 583 |
# Nothing really spectacular: supply to branch, a list of potential from branches |
| 584 |
# and find the minimum from - to value from the transport_cost_matrix |
| 585 |
return $from->[0] if @$from == 1; |
| 586 |
|
| 587 |
my ($least_cost, @branch); |
| 588 |
foreach (@$from) { |
| 589 |
my $cell = $transport_cost_matrix->{$to}{$_}; |
| 590 |
next if $cell->{disable_transfer}; |
| 591 |
|
| 592 |
my $cost = $cell->{cost}; |
| 593 |
|
| 594 |
unless (defined $least_cost) { |
| 595 |
$least_cost = $cost; |
| 596 |
push @branch, $_; |
| 597 |
next; |
| 598 |
} |
| 599 |
|
| 600 |
next if $cost > $least_cost; |
| 601 |
|
| 602 |
if ($cost == $least_cost) { |
| 603 |
push @branch, $_; |
| 604 |
next; |
| 605 |
} |
| 606 |
|
| 607 |
@branch = ($_); |
| 608 |
$least_cost = $cost; |
| 609 |
} |
| 610 |
|
| 611 |
return $branch[0]; |
| 612 |
|
| 613 |
# XXX return a random @branch with minimum cost instead of the first one; |
| 614 |
# return $branch[0] if @branch == 1; |
| 615 |
} |
| 616 |
|
| 617 |
|
| 618 |
1; |