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