|
Lines 36-55
use List::MoreUtils qw(any);
Link Here
|
| 36 |
use Data::Dumper; |
36 |
use Data::Dumper; |
| 37 |
|
37 |
|
| 38 |
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); |
38 |
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); |
|
|
39 |
|
| 39 |
BEGIN { |
40 |
BEGIN { |
| 40 |
$VERSION = 3.03; |
41 |
$VERSION = 3.03; |
| 41 |
require Exporter; |
42 |
require Exporter; |
| 42 |
@ISA = qw(Exporter); |
43 |
@ISA = qw(Exporter); |
| 43 |
@EXPORT_OK = qw( |
44 |
@EXPORT_OK = qw( |
| 44 |
&CreateQueue |
45 |
&CreateQueue |
| 45 |
&GetHoldsQueueItems |
46 |
&GetHoldsQueueItems |
| 46 |
|
47 |
|
| 47 |
&TransportCostMatrix |
48 |
&TransportCostMatrix |
| 48 |
&UpdateTransportCostMatrix |
49 |
&UpdateTransportCostMatrix |
| 49 |
); |
50 |
); |
| 50 |
} |
51 |
} |
| 51 |
|
52 |
|
| 52 |
|
|
|
| 53 |
=head1 FUNCTIONS |
53 |
=head1 FUNCTIONS |
| 54 |
|
54 |
|
| 55 |
=head2 TransportCostMatrix |
55 |
=head2 TransportCostMatrix |
|
Lines 61-76
Returns Transport Cost Matrix as a hashref <to branch code> => <from branch code
Link Here
|
| 61 |
=cut |
61 |
=cut |
| 62 |
|
62 |
|
| 63 |
sub TransportCostMatrix { |
63 |
sub TransportCostMatrix { |
| 64 |
my $dbh = C4::Context->dbh; |
64 |
my $dbh = C4::Context->dbh; |
| 65 |
my $transport_costs = $dbh->selectall_arrayref("SELECT * FROM transport_cost",{ Slice => {} }); |
65 |
my $transport_costs = |
|
|
66 |
$dbh->selectall_arrayref( "SELECT * FROM transport_cost", |
| 67 |
{ Slice => {} } ); |
| 66 |
|
68 |
|
| 67 |
my %transport_cost_matrix; |
69 |
my %transport_cost_matrix; |
| 68 |
foreach (@$transport_costs) { |
70 |
foreach (@$transport_costs) { |
| 69 |
my $from = $_->{frombranch}; |
71 |
my $from = $_->{frombranch}; |
| 70 |
my $to = $_->{tobranch}; |
72 |
my $to = $_->{tobranch}; |
| 71 |
my $cost = $_->{cost}; |
73 |
my $cost = $_->{cost}; |
| 72 |
my $disabled = $_->{disable_transfer}; |
74 |
my $disabled = $_->{disable_transfer}; |
| 73 |
$transport_cost_matrix{$to}{$from} = { cost => $cost, disable_transfer => $disabled }; |
75 |
$transport_cost_matrix{$to}{$from} = |
|
|
76 |
{ cost => $cost, disable_transfer => $disabled }; |
| 74 |
} |
77 |
} |
| 75 |
return \%transport_cost_matrix; |
78 |
return \%transport_cost_matrix; |
| 76 |
} |
79 |
} |
|
Lines 86-105
Records: { frombranch => <code>, tobranch => <code>, cost => <figure>, disable_t
Link Here
|
| 86 |
|
89 |
|
| 87 |
sub UpdateTransportCostMatrix { |
90 |
sub UpdateTransportCostMatrix { |
| 88 |
my ($records) = @_; |
91 |
my ($records) = @_; |
| 89 |
my $dbh = C4::Context->dbh; |
92 |
my $dbh = C4::Context->dbh; |
| 90 |
|
93 |
|
| 91 |
my $sth = $dbh->prepare("INSERT INTO transport_cost (frombranch, tobranch, cost, disable_transfer) VALUES (?, ?, ?, ?)"); |
94 |
my $sth = $dbh->prepare( |
|
|
95 |
"INSERT INTO transport_cost (frombranch, tobranch, cost, disable_transfer) VALUES (?, ?, ?, ?)" |
| 96 |
); |
| 92 |
|
97 |
|
| 93 |
$dbh->do("TRUNCATE TABLE transport_cost"); |
98 |
$dbh->do("DELETE FROM transport_cost") |
|
|
99 |
; #Deleting is slower than TRUNCATE, but using TRUNCATE screws up $dbh->{AutoCommit} in unit tests |
| 94 |
foreach (@$records) { |
100 |
foreach (@$records) { |
| 95 |
my $cost = $_->{cost}; |
101 |
my $cost = $_->{cost}; |
| 96 |
my $from = $_->{frombranch}; |
102 |
my $from = $_->{frombranch}; |
| 97 |
my $to = $_->{tobranch}; |
103 |
my $to = $_->{tobranch}; |
| 98 |
if ($_->{disable_transfer}) { |
104 |
if ( $_->{disable_transfer} ) { |
| 99 |
$cost ||= 0; |
105 |
$cost ||= 0; |
| 100 |
} |
106 |
} |
| 101 |
elsif ( !defined ($cost) || ($cost !~ m/(0|[1-9][0-9]*)(\.[0-9]*)?/o) ) { |
107 |
elsif ( !defined($cost) || ( $cost !~ m/(0|[1-9][0-9]*)(\.[0-9]*)?/o ) ) |
| 102 |
warn "Invalid $from -> $to cost $cost - must be a number >= 0, disablig"; |
108 |
{ |
|
|
109 |
warn |
| 110 |
"Invalid $from -> $to cost $cost - must be a number >= 0, disablig"; |
| 103 |
$cost = 0; |
111 |
$cost = 0; |
| 104 |
$_->{disable_transfer} = 1; |
112 |
$_->{disable_transfer} = 1; |
| 105 |
} |
113 |
} |
|
Lines 117-150
Returns hold queue for a holding branch. If branch is omitted, then whole queue
Link Here
|
| 117 |
|
125 |
|
| 118 |
sub GetHoldsQueueItems { |
126 |
sub GetHoldsQueueItems { |
| 119 |
my ($branchlimit) = @_; |
127 |
my ($branchlimit) = @_; |
| 120 |
my $dbh = C4::Context->dbh; |
128 |
my $dbh = C4::Context->dbh; |
| 121 |
|
129 |
|
| 122 |
my @bind_params = (); |
130 |
my @bind_params = (); |
| 123 |
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 |
131 |
my $query = |
|
|
132 |
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 |
| 124 |
FROM tmp_holdsqueue |
133 |
FROM tmp_holdsqueue |
| 125 |
JOIN biblio USING (biblionumber) |
134 |
JOIN biblio USING (biblionumber) |
| 126 |
LEFT JOIN biblioitems USING (biblionumber) |
135 |
LEFT JOIN biblioitems USING (biblionumber) |
| 127 |
LEFT JOIN items USING ( itemnumber) |
136 |
LEFT JOIN items USING ( itemnumber) |
| 128 |
/; |
137 |
/; |
| 129 |
if ($branchlimit) { |
138 |
if ($branchlimit) { |
| 130 |
$query .=" WHERE tmp_holdsqueue.holdingbranch = ?"; |
139 |
$query .= " WHERE tmp_holdsqueue.holdingbranch = ?"; |
| 131 |
push @bind_params, $branchlimit; |
140 |
push @bind_params, $branchlimit; |
| 132 |
} |
141 |
} |
| 133 |
$query .= " ORDER BY ccode, location, cn_sort, author, title, pickbranch, reservedate"; |
142 |
$query .= |
|
|
143 |
" ORDER BY ccode, location, cn_sort, author, title, pickbranch, reservedate"; |
| 134 |
my $sth = $dbh->prepare($query); |
144 |
my $sth = $dbh->prepare($query); |
| 135 |
$sth->execute(@bind_params); |
145 |
$sth->execute(@bind_params); |
| 136 |
my $items = []; |
146 |
my $items = []; |
| 137 |
while ( my $row = $sth->fetchrow_hashref ){ |
147 |
while ( my $row = $sth->fetchrow_hashref ) { |
| 138 |
$row->{reservedate} = format_date($row->{reservedate}); |
148 |
$row->{reservedate} = format_date( $row->{reservedate} ); |
| 139 |
my $record = GetMarcBiblio($row->{biblionumber}); |
149 |
my $record = GetMarcBiblio( $row->{biblionumber} ); |
| 140 |
if ($record){ |
150 |
if ($record) { |
| 141 |
$row->{subtitle} = GetRecordValue('subtitle',$record,'')->[0]->{subfield}; |
151 |
$row->{subtitle} = |
| 142 |
$row->{parts} = GetRecordValue('parts',$record,'')->[0]->{subfield}; |
152 |
GetRecordValue( 'subtitle', $record, '' )->[0]->{subfield}; |
| 143 |
$row->{numbers} = GetRecordValue('numbers',$record,'')->[0]->{subfield}; |
153 |
$row->{parts} = |
|
|
154 |
GetRecordValue( 'parts', $record, '' )->[0]->{subfield}; |
| 155 |
$row->{numbers} = |
| 156 |
GetRecordValue( 'numbers', $record, '' )->[0]->{subfield}; |
| 144 |
} |
157 |
} |
| 145 |
|
158 |
|
| 146 |
# return the bib-level or item-level itype per syspref |
159 |
# return the bib-level or item-level itype per syspref |
| 147 |
if (!C4::Context->preference('item-level_itypes')) { |
160 |
if ( !C4::Context->preference('item-level_itypes') ) { |
| 148 |
$row->{itype} = $row->{itemtype}; |
161 |
$row->{itype} = $row->{itemtype}; |
| 149 |
} |
162 |
} |
| 150 |
delete $row->{itemtype}; |
163 |
delete $row->{itemtype}; |
|
Lines 163-171
Top level function that turns reserves into tmp_holdsqueue and hold_fill_targets
Link Here
|
| 163 |
=cut |
176 |
=cut |
| 164 |
|
177 |
|
| 165 |
sub CreateQueue { |
178 |
sub CreateQueue { |
| 166 |
my $dbh = C4::Context->dbh; |
179 |
my $dbh = C4::Context->dbh; |
| 167 |
|
180 |
|
| 168 |
$dbh->do("DELETE FROM tmp_holdsqueue"); # clear the old table for new info |
181 |
$dbh->do("DELETE FROM tmp_holdsqueue"); # clear the old table for new info |
| 169 |
$dbh->do("DELETE FROM hold_fill_targets"); |
182 |
$dbh->do("DELETE FROM hold_fill_targets"); |
| 170 |
|
183 |
|
| 171 |
my $total_bibs = 0; |
184 |
my $total_bibs = 0; |
|
Lines 175-184
sub CreateQueue {
Link Here
|
| 175 |
|
188 |
|
| 176 |
my $branches_to_use; |
189 |
my $branches_to_use; |
| 177 |
my $transport_cost_matrix; |
190 |
my $transport_cost_matrix; |
| 178 |
my $use_transport_cost_matrix = C4::Context->preference("UseTransportCostMatrix"); |
191 |
my $use_transport_cost_matrix = |
|
|
192 |
C4::Context->preference("UseTransportCostMatrix"); |
| 179 |
if ($use_transport_cost_matrix) { |
193 |
if ($use_transport_cost_matrix) { |
| 180 |
$transport_cost_matrix = TransportCostMatrix(); |
194 |
$transport_cost_matrix = TransportCostMatrix(); |
| 181 |
unless (keys %$transport_cost_matrix) { |
195 |
unless ( keys %$transport_cost_matrix ) { |
| 182 |
warn "UseTransportCostMatrix set to yes, but matrix not populated"; |
196 |
warn "UseTransportCostMatrix set to yes, but matrix not populated"; |
| 183 |
undef $transport_cost_matrix; |
197 |
undef $transport_cost_matrix; |
| 184 |
} |
198 |
} |
|
Lines 191-215
sub CreateQueue {
Link Here
|
| 191 |
|
205 |
|
| 192 |
foreach my $biblionumber (@$bibs_with_pending_requests) { |
206 |
foreach my $biblionumber (@$bibs_with_pending_requests) { |
| 193 |
$total_bibs++; |
207 |
$total_bibs++; |
| 194 |
my $hold_requests = GetPendingHoldRequestsForBib($biblionumber); |
208 |
my $hold_requests = GetPendingHoldRequestsForBib($biblionumber); |
| 195 |
my $available_items = GetItemsAvailableToFillHoldRequestsForBib($biblionumber, $branches_to_use); |
209 |
my $available_items = |
|
|
210 |
GetItemsAvailableToFillHoldRequestsForBib( $biblionumber, |
| 211 |
$branches_to_use ); |
| 196 |
$total_requests += scalar(@$hold_requests); |
212 |
$total_requests += scalar(@$hold_requests); |
| 197 |
$total_available_items += scalar(@$available_items); |
213 |
$total_available_items += scalar(@$available_items); |
| 198 |
|
214 |
|
| 199 |
my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix); |
215 |
my $item_map = MapItemsToHoldRequests( |
| 200 |
$item_map or next; |
216 |
$hold_requests, $available_items, |
| 201 |
my $item_map_size = scalar(keys %$item_map) |
217 |
$branches_to_use, $transport_cost_matrix |
|
|
218 |
); |
| 219 |
$item_map or next; |
| 220 |
my $item_map_size = scalar( keys %$item_map ) |
| 202 |
or next; |
221 |
or next; |
| 203 |
|
222 |
|
| 204 |
$num_items_mapped += $item_map_size; |
223 |
$num_items_mapped += $item_map_size; |
| 205 |
CreatePicklistFromItemMap($item_map); |
224 |
CreatePicklistFromItemMap($item_map); |
| 206 |
AddToHoldTargetMap($item_map); |
225 |
AddToHoldTargetMap($item_map); |
| 207 |
if (($item_map_size < scalar(@$hold_requests )) and |
226 |
if ( ( $item_map_size < scalar(@$hold_requests) ) |
| 208 |
($item_map_size < scalar(@$available_items))) { |
227 |
and ( $item_map_size < scalar(@$available_items) ) ) |
| 209 |
# DOUBLE CHECK, but this is probably OK - unfilled item-level requests |
228 |
{ |
| 210 |
# FIXME |
229 |
# DOUBLE CHECK, but this is probably OK - unfilled item-level requests |
| 211 |
#warn "unfilled requests for $biblionumber"; |
230 |
# FIXME |
| 212 |
#warn Dumper($hold_requests), Dumper($available_items), Dumper($item_map); |
231 |
#warn "unfilled requests for $biblionumber"; |
|
|
232 |
#warn Dumper($hold_requests), Dumper($available_items), Dumper($item_map); |
| 213 |
} |
233 |
} |
| 214 |
} |
234 |
} |
| 215 |
} |
235 |
} |
|
Lines 267-273
sub GetPendingHoldRequestsForBib {
Link Here
|
| 267 |
|
287 |
|
| 268 |
my $dbh = C4::Context->dbh; |
288 |
my $dbh = C4::Context->dbh; |
| 269 |
|
289 |
|
| 270 |
my $request_query = "SELECT biblionumber, borrowernumber, itemnumber, priority, reserves.branchcode, |
290 |
my $request_query = |
|
|
291 |
"SELECT biblionumber, borrowernumber, itemnumber, priority, reserves.branchcode, |
| 271 |
reservedate, reservenotes, borrowers.branchcode AS borrowerbranch |
292 |
reservedate, reservenotes, borrowers.branchcode AS borrowerbranch |
| 272 |
FROM reserves |
293 |
FROM reserves |
| 273 |
JOIN borrowers USING (borrowernumber) |
294 |
JOIN borrowers USING (borrowernumber) |
|
Lines 280-286
sub GetPendingHoldRequestsForBib {
Link Here
|
| 280 |
my $sth = $dbh->prepare($request_query); |
301 |
my $sth = $dbh->prepare($request_query); |
| 281 |
$sth->execute($biblionumber); |
302 |
$sth->execute($biblionumber); |
| 282 |
|
303 |
|
| 283 |
my $requests = $sth->fetchall_arrayref({}); |
304 |
my $requests = $sth->fetchall_arrayref( {} ); |
| 284 |
return $requests; |
305 |
return $requests; |
| 285 |
|
306 |
|
| 286 |
} |
307 |
} |
|
Lines 303-325
to fill a hold request if and only if:
Link Here
|
| 303 |
=cut |
324 |
=cut |
| 304 |
|
325 |
|
| 305 |
sub GetItemsAvailableToFillHoldRequestsForBib { |
326 |
sub GetItemsAvailableToFillHoldRequestsForBib { |
| 306 |
my ($biblionumber, $branches_to_use) = @_; |
327 |
my ( $biblionumber, $branches_to_use ) = @_; |
| 307 |
|
328 |
|
| 308 |
my $dbh = C4::Context->dbh; |
329 |
my $dbh = C4::Context->dbh; |
| 309 |
my $items_query = "SELECT itemnumber, homebranch, holdingbranch, itemtypes.itemtype AS itype |
330 |
my $items_query = |
|
|
331 |
"SELECT itemnumber, homebranch, holdingbranch, ccode, itemtypes.itemtype AS itype |
| 310 |
FROM items "; |
332 |
FROM items "; |
| 311 |
|
333 |
|
| 312 |
if (C4::Context->preference('item-level_itypes')) { |
334 |
if ( C4::Context->preference('item-level_itypes') ) { |
| 313 |
$items_query .= "LEFT JOIN itemtypes ON (itemtypes.itemtype = items.itype) "; |
335 |
$items_query .= |
| 314 |
} else { |
336 |
"LEFT JOIN itemtypes ON (itemtypes.itemtype = items.itype) "; |
| 315 |
$items_query .= "JOIN biblioitems USING (biblioitemnumber) |
337 |
} |
|
|
338 |
else { |
| 339 |
$items_query .= "JOIN biblioitems USING (biblioitemnumber) |
| 316 |
LEFT JOIN itemtypes USING (itemtype) "; |
340 |
LEFT JOIN itemtypes USING (itemtype) "; |
| 317 |
} |
341 |
} |
| 318 |
$items_query .= "WHERE items.notforloan = 0 |
342 |
$items_query .= "WHERE items.notforloan = 0 |
| 319 |
AND holdingbranch IS NOT NULL |
343 |
AND holdingbranch IS NOT NULL |
| 320 |
AND itemlost = 0 |
344 |
AND itemlost = 0 |
| 321 |
AND withdrawn = 0"; |
345 |
AND withdrawn = 0"; |
| 322 |
$items_query .= " AND damaged = 0" unless C4::Context->preference('AllowHoldsOnDamagedItems'); |
346 |
$items_query .= " AND damaged = 0" |
|
|
347 |
unless C4::Context->preference('AllowHoldsOnDamagedItems'); |
| 323 |
$items_query .= " AND items.onloan IS NULL |
348 |
$items_query .= " AND items.onloan IS NULL |
| 324 |
AND (itemtypes.notforloan IS NULL OR itemtypes.notforloan = 0) |
349 |
AND (itemtypes.notforloan IS NULL OR itemtypes.notforloan = 0) |
| 325 |
AND itemnumber NOT IN ( |
350 |
AND itemnumber NOT IN ( |
|
Lines 330-352
sub GetItemsAvailableToFillHoldRequestsForBib {
Link Here
|
| 330 |
AND (found IS NOT NULL OR priority = 0) |
355 |
AND (found IS NOT NULL OR priority = 0) |
| 331 |
) |
356 |
) |
| 332 |
AND items.biblionumber = ?"; |
357 |
AND items.biblionumber = ?"; |
| 333 |
$items_query .= " AND damaged = 0 " |
358 |
$items_query .= " AND damaged = 0 " |
| 334 |
unless C4::Context->preference('AllowHoldsOnDamagedItems'); |
359 |
unless C4::Context->preference('AllowHoldsOnDamagedItems'); |
| 335 |
|
360 |
|
| 336 |
my @params = ($biblionumber, $biblionumber); |
361 |
my @params = ( $biblionumber, $biblionumber ); |
| 337 |
if ($branches_to_use && @$branches_to_use) { |
362 |
if ( $branches_to_use && @$branches_to_use ) { |
| 338 |
$items_query .= " AND holdingbranch IN (" . join (",", map { "?" } @$branches_to_use) . ")"; |
363 |
$items_query .= " AND holdingbranch IN (" |
|
|
364 |
. join( ",", map { "?" } @$branches_to_use ) . ")"; |
| 339 |
push @params, @$branches_to_use; |
365 |
push @params, @$branches_to_use; |
| 340 |
} |
366 |
} |
| 341 |
my $sth = $dbh->prepare($items_query); |
367 |
my $sth = $dbh->prepare($items_query); |
| 342 |
$sth->execute(@params); |
368 |
$sth->execute(@params); |
| 343 |
|
369 |
|
| 344 |
my $itm = $sth->fetchall_arrayref({}); |
370 |
my $itm = $sth->fetchall_arrayref( {} ); |
| 345 |
my @items = grep { ! scalar GetTransfers($_->{itemnumber}) } @$itm; |
371 |
my @items = grep { !scalar GetTransfers( $_->{itemnumber} ) } @$itm; |
| 346 |
return [ grep { |
372 |
return [ |
| 347 |
my $rule = GetBranchItemRule($_->{homebranch}, $_->{itype}); |
373 |
grep { |
| 348 |
$_->{holdallowed} = $rule->{holdallowed}; |
374 |
my $rule = GetBranchItemRule( $_->{homebranch}, $_->{itype} ); |
| 349 |
} @items ]; |
375 |
$_->{holdallowed} = $rule->{holdallowed}; |
|
|
376 |
} @items |
| 377 |
]; |
| 350 |
} |
378 |
} |
| 351 |
|
379 |
|
| 352 |
=head2 MapItemsToHoldRequests |
380 |
=head2 MapItemsToHoldRequests |
|
Lines 356-362
sub GetItemsAvailableToFillHoldRequestsForBib {
Link Here
|
| 356 |
=cut |
384 |
=cut |
| 357 |
|
385 |
|
| 358 |
sub MapItemsToHoldRequests { |
386 |
sub MapItemsToHoldRequests { |
| 359 |
my ($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix) = @_; |
387 |
my ( |
|
|
388 |
$hold_requests, $available_items, |
| 389 |
$branches_to_use, $transport_cost_matrix |
| 390 |
) = @_; |
| 360 |
|
391 |
|
| 361 |
# handle trival cases |
392 |
# handle trival cases |
| 362 |
return unless scalar(@$hold_requests) > 0; |
393 |
return unless scalar(@$hold_requests) > 0; |
|
Lines 364-371
sub MapItemsToHoldRequests {
Link Here
|
| 364 |
|
395 |
|
| 365 |
# identify item-level requests |
396 |
# identify item-level requests |
| 366 |
my %specific_items_requested = map { $_->{itemnumber} => 1 } |
397 |
my %specific_items_requested = map { $_->{itemnumber} => 1 } |
| 367 |
grep { defined($_->{itemnumber}) } |
398 |
grep { defined( $_->{itemnumber} ) } @$hold_requests; |
| 368 |
@$hold_requests; |
|
|
| 369 |
|
399 |
|
| 370 |
# group available items by itemnumber |
400 |
# group available items by itemnumber |
| 371 |
my %items_by_itemnumber = map { $_->{itemnumber} => $_ } @$available_items; |
401 |
my %items_by_itemnumber = map { $_->{itemnumber} => $_ } @$available_items; |
|
Lines 382-404
sub MapItemsToHoldRequests {
Link Here
|
| 382 |
last if $num_items_remaining == 0; |
412 |
last if $num_items_remaining == 0; |
| 383 |
|
413 |
|
| 384 |
# is this an item-level request? |
414 |
# is this an item-level request? |
| 385 |
if (defined($request->{itemnumber})) { |
415 |
if ( defined( $request->{itemnumber} ) ) { |
|
|
416 |
|
| 386 |
# fill it if possible; if not skip it |
417 |
# fill it if possible; if not skip it |
| 387 |
if (exists $items_by_itemnumber{$request->{itemnumber}} and |
418 |
if ( exists $items_by_itemnumber{ $request->{itemnumber} } |
| 388 |
not exists $allocated_items{$request->{itemnumber}}) { |
419 |
and not exists $allocated_items{ $request->{itemnumber} } ) |
| 389 |
$item_map{$request->{itemnumber}} = { |
420 |
{ |
| 390 |
borrowernumber => $request->{borrowernumber}, |
421 |
|
| 391 |
biblionumber => $request->{biblionumber}, |
422 |
#Dereferencing variables for sanitys sake, and because it is faster when repeatedly called. |
| 392 |
holdingbranch => $items_by_itemnumber{$request->{itemnumber}}->{holdingbranch}, |
423 |
my $holdingBranch = |
| 393 |
pickup_branch => $request->{branchcode} || $request->{borrowerbranch}, |
424 |
$items_by_itemnumber{ $request->{itemnumber} } |
| 394 |
item_level => 1, |
425 |
->{holdingbranch}; |
| 395 |
reservedate => $request->{reservedate}, |
426 |
my $pickupBranch = |
| 396 |
reservenotes => $request->{reservenotes}, |
427 |
$request->{branchcode} || $request->{borrowerbranch}; |
| 397 |
}; |
428 |
|
| 398 |
$allocated_items{$request->{itemnumber}}++; |
429 |
my ( $transferOk, $errorMsg ) = |
| 399 |
$num_items_remaining--; |
430 |
C4::Circulation::CanItemBeTransferred( $pickupBranch, |
|
|
431 |
$holdingBranch, |
| 432 |
$items_by_itemnumber{ $request->{itemnumber} }, undef ); |
| 433 |
if ( !$transferOk ) { |
| 434 |
|
| 435 |
#UseBranchTransfer check failed, gosh damn how can this be! It should be blocked in the UI for item level holds! |
| 436 |
warn |
| 437 |
"HoldsQueue.pm:> Branch transfer failed for pickup branch $pickupBranch, holding branch $holdingBranch, itemnumber $request->{itemnumber}\n" |
| 438 |
. "This should be impossible because branch transfer limits for item level holds must be checked when placing holds!"; |
| 439 |
} |
| 440 |
else { |
| 441 |
$item_map{ $request->{itemnumber} } = { |
| 442 |
borrowernumber => $request->{borrowernumber}, |
| 443 |
biblionumber => $request->{biblionumber}, |
| 444 |
holdingbranch => |
| 445 |
$items_by_itemnumber{ $request->{itemnumber} } |
| 446 |
->{holdingbranch}, |
| 447 |
pickup_branch => $request->{branchcode} |
| 448 |
|| $request->{borrowerbranch}, |
| 449 |
item_level => 1, |
| 450 |
reservedate => $request->{reservedate}, |
| 451 |
reservenotes => $request->{reservenotes}, |
| 452 |
}; |
| 453 |
$allocated_items{ $request->{itemnumber} }++; |
| 454 |
$num_items_remaining--; |
| 455 |
} |
| 400 |
} |
456 |
} |
| 401 |
} else { |
457 |
} |
|
|
458 |
else { |
| 402 |
# it's title-level request that will take up one item |
459 |
# it's title-level request that will take up one item |
| 403 |
$num_items_remaining--; |
460 |
$num_items_remaining--; |
| 404 |
} |
461 |
} |
|
Lines 415-432
sub MapItemsToHoldRequests {
Link Here
|
| 415 |
return \%item_map unless keys %items_by_branch; |
472 |
return \%item_map unless keys %items_by_branch; |
| 416 |
|
473 |
|
| 417 |
# now handle the title-level requests |
474 |
# now handle the title-level requests |
| 418 |
$num_items_remaining = scalar(@$available_items) - scalar(keys %allocated_items); |
475 |
$num_items_remaining = |
|
|
476 |
scalar(@$available_items) - scalar( keys %allocated_items ); |
| 419 |
my $pull_branches; |
477 |
my $pull_branches; |
| 420 |
foreach my $request (@$hold_requests) { |
478 |
foreach my $request (@$hold_requests) { |
| 421 |
last if $num_items_remaining == 0; |
479 |
last if $num_items_remaining == 0; |
| 422 |
next if defined($request->{itemnumber}); # already handled these |
480 |
next if defined( $request->{itemnumber} ); # already handled these |
| 423 |
|
481 |
|
| 424 |
# look for local match first |
482 |
# look for local match first |
| 425 |
my $pickup_branch = $request->{branchcode} || $request->{borrowerbranch}; |
483 |
my $pickup_branch = |
| 426 |
my ($itemnumber, $holdingbranch); |
484 |
$request->{branchcode} || $request->{borrowerbranch}; |
|
|
485 |
my ( $itemnumber, $holdingbranch ); |
| 486 |
|
| 487 |
# Used to detect if no Items can be transferred to the pickup location. |
| 488 |
my $branchTransfersAllowedCount = |
| 489 |
1; #Default this to 1. It is decremented when branchTransfers fail. |
| 427 |
|
490 |
|
| 428 |
my $holding_branch_items = $items_by_branch{$pickup_branch}; |
491 |
my $holding_branch_items = $items_by_branch{$pickup_branch}; |
| 429 |
if ( $holding_branch_items ) { |
492 |
if ($holding_branch_items) { |
| 430 |
foreach my $item (@$holding_branch_items) { |
493 |
foreach my $item (@$holding_branch_items) { |
| 431 |
if ( $request->{borrowerbranch} eq $item->{homebranch} ) { |
494 |
if ( $request->{borrowerbranch} eq $item->{homebranch} ) { |
| 432 |
$itemnumber = $item->{itemnumber}; |
495 |
$itemnumber = $item->{itemnumber}; |
|
Lines 437-467
sub MapItemsToHoldRequests {
Link Here
|
| 437 |
$itemnumber ||= $holding_branch_items->[0]->{itemnumber}; |
500 |
$itemnumber ||= $holding_branch_items->[0]->{itemnumber}; |
| 438 |
} |
501 |
} |
| 439 |
elsif ($transport_cost_matrix) { |
502 |
elsif ($transport_cost_matrix) { |
| 440 |
$pull_branches = [keys %items_by_branch]; |
503 |
$pull_branches = [ keys %items_by_branch ]; |
| 441 |
$holdingbranch = least_cost_branch( $pickup_branch, $pull_branches, $transport_cost_matrix ); |
504 |
$holdingbranch = least_cost_branch( $pickup_branch, $pull_branches, |
| 442 |
if ( $holdingbranch ) { |
505 |
$transport_cost_matrix ); |
|
|
506 |
if ($holdingbranch) { |
| 443 |
|
507 |
|
| 444 |
my $holding_branch_items = $items_by_branch{$holdingbranch}; |
508 |
my $holding_branch_items = $items_by_branch{$holdingbranch}; |
|
|
509 |
$branchTransfersAllowedCount = @$holding_branch_items; |
| 510 |
|
| 511 |
# Making sure to put preference to the Item, which is originally from the Borrowers homebranch. |
| 445 |
foreach my $item (@$holding_branch_items) { |
512 |
foreach my $item (@$holding_branch_items) { |
| 446 |
next if $request->{borrowerbranch} ne $item->{homebranch}; |
513 |
next if $request->{borrowerbranch} ne $item->{homebranch}; |
| 447 |
|
514 |
|
| 448 |
$itemnumber = $item->{itemnumber}; |
515 |
$itemnumber = $item->{itemnumber}; |
| 449 |
last; |
516 |
last; |
| 450 |
} |
517 |
} |
|
|
518 |
|
| 519 |
# Checking if the Item can be Transferred to the pickup library |
| 520 |
if ( !$itemnumber |
| 521 |
&& C4::Context->preference("UseBranchTransferLimits") == 1 ) |
| 522 |
{ |
| 523 |
foreach my $item (@$holding_branch_items) { |
| 524 |
my ( $transferOk, $errorMsg ) = |
| 525 |
C4::Circulation::CanItemBeTransferred( $pickup_branch, |
| 526 |
$item->{holdingbranch}, |
| 527 |
$item, undef ); |
| 528 |
if ( !$transferOk ) { |
| 529 |
$branchTransfersAllowedCount--; |
| 530 |
next; |
| 531 |
} |
| 532 |
|
| 533 |
$itemnumber = $item->{itemnumber}; |
| 534 |
last; |
| 535 |
} |
| 536 |
} |
| 537 |
|
| 538 |
# If one is not using UseBranchTransferLimits, then revert to the default setting. |
| 539 |
elsif ( !$itemnumber ) { |
| 540 |
|
| 541 |
# FIXME Choosing the Item based on the first available, even if it could be on hold. |
| 542 |
# Problematic since holds tend to stack on this first item then. |
| 543 |
$itemnumber ||= $holding_branch_items->[0]->{itemnumber}; |
| 544 |
} |
| 451 |
} |
545 |
} |
| 452 |
else { |
546 |
else { |
| 453 |
warn "No transport costs for $pickup_branch"; |
547 |
warn "No transport costs for $pickup_branch"; |
| 454 |
} |
548 |
} |
| 455 |
} |
549 |
} |
| 456 |
|
550 |
|
| 457 |
unless ($itemnumber) { |
551 |
if ( ( !$itemnumber ) && $branchTransfersAllowedCount > 0 ) { |
| 458 |
# not found yet, fall back to basics |
552 |
|
|
|
553 |
# not found yet and branchTransfers are not blocking, fall back to basics |
| 459 |
if ($branches_to_use) { |
554 |
if ($branches_to_use) { |
| 460 |
$pull_branches = $branches_to_use; |
555 |
$pull_branches = $branches_to_use; |
| 461 |
} else { |
|
|
| 462 |
$pull_branches = [keys %items_by_branch]; |
| 463 |
} |
556 |
} |
| 464 |
PULL_BRANCHES: |
557 |
else { |
|
|
558 |
$pull_branches = [ keys %items_by_branch ]; |
| 559 |
} |
| 560 |
PULL_BRANCHES: |
| 465 |
foreach my $branch (@$pull_branches) { |
561 |
foreach my $branch (@$pull_branches) { |
| 466 |
my $holding_branch_items = $items_by_branch{$branch} |
562 |
my $holding_branch_items = $items_by_branch{$branch} |
| 467 |
or next; |
563 |
or next; |
|
Lines 470-486
sub MapItemsToHoldRequests {
Link Here
|
| 470 |
foreach my $item (@$holding_branch_items) { |
566 |
foreach my $item (@$holding_branch_items) { |
| 471 |
next if $pickup_branch ne $item->{homebranch}; |
567 |
next if $pickup_branch ne $item->{homebranch}; |
| 472 |
|
568 |
|
| 473 |
$itemnumber = $item->{itemnumber}; |
569 |
$itemnumber = $item->{itemnumber}; |
| 474 |
$holdingbranch = $branch; |
570 |
$holdingbranch = $branch; |
| 475 |
last PULL_BRANCHES; |
571 |
last PULL_BRANCHES; |
| 476 |
} |
572 |
} |
| 477 |
} |
573 |
} |
| 478 |
|
574 |
|
| 479 |
unless ( $itemnumber ) { |
575 |
unless ($itemnumber) { |
| 480 |
foreach my $current_item ( @{ $items_by_branch{$holdingbranch} } ) { |
576 |
foreach |
| 481 |
if ( $holdingbranch && ( $current_item->{holdallowed} == 2 || $pickup_branch eq $current_item->{homebranch} ) ) { |
577 |
my $current_item ( @{ $items_by_branch{$holdingbranch} } ) |
|
|
578 |
{ |
| 579 |
if ( |
| 580 |
$holdingbranch |
| 581 |
&& ( $current_item->{holdallowed} == 2 |
| 582 |
|| $pickup_branch eq $current_item->{homebranch} ) |
| 583 |
) |
| 584 |
{ |
| 482 |
$itemnumber = $current_item->{itemnumber}; |
585 |
$itemnumber = $current_item->{itemnumber}; |
| 483 |
last; # quit this loop as soon as we have a suitable item |
586 |
last |
|
|
587 |
; # quit this loop as soon as we have a suitable item |
| 484 |
} |
588 |
} |
| 485 |
} |
589 |
} |
| 486 |
} |
590 |
} |
|
Lines 489-505
sub MapItemsToHoldRequests {
Link Here
|
| 489 |
if ($itemnumber) { |
593 |
if ($itemnumber) { |
| 490 |
my $holding_branch_items = $items_by_branch{$holdingbranch} |
594 |
my $holding_branch_items = $items_by_branch{$holdingbranch} |
| 491 |
or die "Have $itemnumber, $holdingbranch, but no items!"; |
595 |
or die "Have $itemnumber, $holdingbranch, but no items!"; |
| 492 |
@$holding_branch_items = grep { $_->{itemnumber} != $itemnumber } @$holding_branch_items; |
596 |
@$holding_branch_items = |
| 493 |
delete $items_by_branch{$holdingbranch} unless @$holding_branch_items; |
597 |
grep { $_->{itemnumber} != $itemnumber } @$holding_branch_items; |
|
|
598 |
delete $items_by_branch{$holdingbranch} |
| 599 |
unless @$holding_branch_items; |
| 494 |
|
600 |
|
| 495 |
$item_map{$itemnumber} = { |
601 |
$item_map{$itemnumber} = { |
| 496 |
borrowernumber => $request->{borrowernumber}, |
602 |
borrowernumber => $request->{borrowernumber}, |
| 497 |
biblionumber => $request->{biblionumber}, |
603 |
biblionumber => $request->{biblionumber}, |
| 498 |
holdingbranch => $holdingbranch, |
604 |
holdingbranch => $holdingbranch, |
| 499 |
pickup_branch => $pickup_branch, |
605 |
pickup_branch => $pickup_branch, |
| 500 |
item_level => 0, |
606 |
item_level => 0, |
| 501 |
reservedate => $request->{reservedate}, |
607 |
reservedate => $request->{reservedate}, |
| 502 |
reservenotes => $request->{reservenotes}, |
608 |
reservenotes => $request->{reservenotes}, |
| 503 |
}; |
609 |
}; |
| 504 |
$num_items_remaining--; |
610 |
$num_items_remaining--; |
| 505 |
} |
611 |
} |
|
Lines 516-554
sub CreatePicklistFromItemMap {
Link Here
|
| 516 |
|
622 |
|
| 517 |
my $dbh = C4::Context->dbh; |
623 |
my $dbh = C4::Context->dbh; |
| 518 |
|
624 |
|
| 519 |
my $sth_load=$dbh->prepare(" |
625 |
my $sth_load = $dbh->prepare( " |
| 520 |
INSERT INTO tmp_holdsqueue (biblionumber,itemnumber,barcode,surname,firstname,phone,borrowernumber, |
626 |
INSERT INTO tmp_holdsqueue (biblionumber,itemnumber,barcode,surname,firstname,phone,borrowernumber, |
| 521 |
cardnumber,reservedate,title, itemcallnumber, |
627 |
cardnumber,reservedate,title, itemcallnumber, |
| 522 |
holdingbranch,pickbranch,notes, item_level_request) |
628 |
holdingbranch,pickbranch,notes, item_level_request) |
| 523 |
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |
629 |
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |
| 524 |
"); |
630 |
" ); |
| 525 |
|
631 |
|
| 526 |
foreach my $itemnumber (sort keys %$item_map) { |
632 |
foreach my $itemnumber ( sort keys %$item_map ) { |
| 527 |
my $mapped_item = $item_map->{$itemnumber}; |
633 |
my $mapped_item = $item_map->{$itemnumber}; |
| 528 |
my $biblionumber = $mapped_item->{biblionumber}; |
634 |
my $biblionumber = $mapped_item->{biblionumber}; |
| 529 |
my $borrowernumber = $mapped_item->{borrowernumber}; |
635 |
my $borrowernumber = $mapped_item->{borrowernumber}; |
| 530 |
my $pickbranch = $mapped_item->{pickup_branch}; |
636 |
my $pickbranch = $mapped_item->{pickup_branch}; |
| 531 |
my $holdingbranch = $mapped_item->{holdingbranch}; |
637 |
my $holdingbranch = $mapped_item->{holdingbranch}; |
| 532 |
my $reservedate = $mapped_item->{reservedate}; |
638 |
my $reservedate = $mapped_item->{reservedate}; |
| 533 |
my $reservenotes = $mapped_item->{reservenotes}; |
639 |
my $reservenotes = $mapped_item->{reservenotes}; |
| 534 |
my $item_level = $mapped_item->{item_level}; |
640 |
my $item_level = $mapped_item->{item_level}; |
| 535 |
|
641 |
|
| 536 |
my $item = GetItem($itemnumber); |
642 |
my $item = GetItem($itemnumber); |
| 537 |
my $barcode = $item->{barcode}; |
643 |
my $barcode = $item->{barcode}; |
| 538 |
my $itemcallnumber = $item->{itemcallnumber}; |
644 |
my $itemcallnumber = $item->{itemcallnumber}; |
| 539 |
|
645 |
|
| 540 |
my $borrower = GetMember('borrowernumber'=>$borrowernumber); |
646 |
my $borrower = GetMember( 'borrowernumber' => $borrowernumber ); |
| 541 |
my $cardnumber = $borrower->{'cardnumber'}; |
647 |
my $cardnumber = $borrower->{'cardnumber'}; |
| 542 |
my $surname = $borrower->{'surname'}; |
648 |
my $surname = $borrower->{'surname'}; |
| 543 |
my $firstname = $borrower->{'firstname'}; |
649 |
my $firstname = $borrower->{'firstname'}; |
| 544 |
my $phone = $borrower->{'phone'}; |
650 |
my $phone = $borrower->{'phone'}; |
| 545 |
|
651 |
|
| 546 |
my $bib = GetBiblioData($biblionumber); |
652 |
my $bib = GetBiblioData($biblionumber); |
| 547 |
my $title = $bib->{title}; |
653 |
my $title = $bib->{title}; |
| 548 |
|
654 |
|
| 549 |
$sth_load->execute($biblionumber, $itemnumber, $barcode, $surname, $firstname, $phone, $borrowernumber, |
655 |
$sth_load->execute( |
| 550 |
$cardnumber, $reservedate, $title, $itemcallnumber, |
656 |
$biblionumber, $itemnumber, $barcode, $surname, |
| 551 |
$holdingbranch, $pickbranch, $reservenotes, $item_level); |
657 |
$firstname, $phone, $borrowernumber, $cardnumber, |
|
|
658 |
$reservedate, $title, $itemcallnumber, $holdingbranch, |
| 659 |
$pickbranch, $reservenotes, $item_level |
| 660 |
); |
| 552 |
} |
661 |
} |
| 553 |
} |
662 |
} |
| 554 |
|
663 |
|
|
Lines 567-576
sub AddToHoldTargetMap {
Link Here
|
| 567 |
); |
676 |
); |
| 568 |
my $sth_insert = $dbh->prepare($insert_sql); |
677 |
my $sth_insert = $dbh->prepare($insert_sql); |
| 569 |
|
678 |
|
| 570 |
foreach my $itemnumber (keys %$item_map) { |
679 |
foreach my $itemnumber ( keys %$item_map ) { |
| 571 |
my $mapped_item = $item_map->{$itemnumber}; |
680 |
my $mapped_item = $item_map->{$itemnumber}; |
| 572 |
$sth_insert->execute($mapped_item->{borrowernumber}, $mapped_item->{biblionumber}, $itemnumber, |
681 |
$sth_insert->execute( |
| 573 |
$mapped_item->{holdingbranch}, $mapped_item->{item_level}); |
682 |
$mapped_item->{borrowernumber}, $mapped_item->{biblionumber}, |
|
|
683 |
$itemnumber, $mapped_item->{holdingbranch}, |
| 684 |
$mapped_item->{item_level} |
| 685 |
); |
| 574 |
} |
686 |
} |
| 575 |
} |
687 |
} |
| 576 |
|
688 |
|
|
Lines 589-595
sub load_branches_to_pull_from {
Link Here
|
| 589 |
|
701 |
|
| 590 |
my @branches_to_use = map _trim($_), split /,/, $static_branch_list; |
702 |
my @branches_to_use = map _trim($_), split /,/, $static_branch_list; |
| 591 |
|
703 |
|
| 592 |
@branches_to_use = shuffle(@branches_to_use) if C4::Context->preference("RandomizeHoldsQueueWeight"); |
704 |
@branches_to_use = shuffle(@branches_to_use) |
|
|
705 |
if C4::Context->preference("RandomizeHoldsQueueWeight"); |
| 593 |
|
706 |
|
| 594 |
return \@branches_to_use; |
707 |
return \@branches_to_use; |
| 595 |
} |
708 |
} |
|
Lines 597-621
sub load_branches_to_pull_from {
Link Here
|
| 597 |
sub least_cost_branch { |
710 |
sub least_cost_branch { |
| 598 |
|
711 |
|
| 599 |
#$from - arrayref |
712 |
#$from - arrayref |
| 600 |
my ($to, $from, $transport_cost_matrix) = @_; |
713 |
my ( $to, $from, $transport_cost_matrix ) = @_; |
| 601 |
|
714 |
|
| 602 |
# Nothing really spectacular: supply to branch, a list of potential from branches |
715 |
# Nothing really spectacular: supply to branch, a list of potential from branches |
| 603 |
# and find the minimum from - to value from the transport_cost_matrix |
716 |
# and find the minimum from - to value from the transport_cost_matrix |
| 604 |
return $from->[0] if @$from == 1; |
717 |
return $from->[0] if @$from == 1; |
| 605 |
|
718 |
|
| 606 |
# If the pickup library is in the list of libraries to pull from, |
719 |
# If the pickup library is in the list of libraries to pull from, |
| 607 |
# return that library right away, it is obviously the least costly |
720 |
# return that library right away, it is obviously the least costly |
| 608 |
return ($to) if any { $_ eq $to } @$from; |
721 |
return ($to) if any { $_ eq $to } @$from; |
| 609 |
|
722 |
|
| 610 |
my ($least_cost, @branch); |
723 |
my ( $least_cost, @branch ); |
| 611 |
foreach (@$from) { |
724 |
foreach (@$from) { |
| 612 |
my $cell = $transport_cost_matrix->{$to}{$_}; |
725 |
my $cell = $transport_cost_matrix->{$to}{$_}; |
| 613 |
next if $cell->{disable_transfer}; |
726 |
next if $cell->{disable_transfer}; |
| 614 |
|
727 |
|
| 615 |
my $cost = $cell->{cost}; |
728 |
my $cost = $cell->{cost}; |
| 616 |
next unless defined $cost; # XXX should this be reported? |
729 |
next unless defined $cost; # XXX should this be reported? |
| 617 |
|
730 |
|
| 618 |
unless (defined $least_cost) { |
731 |
unless ( defined $least_cost ) { |
| 619 |
$least_cost = $cost; |
732 |
$least_cost = $cost; |
| 620 |
push @branch, $_; |
733 |
push @branch, $_; |
| 621 |
next; |
734 |
next; |
|
Lines 623-634
sub least_cost_branch {
Link Here
|
| 623 |
|
736 |
|
| 624 |
next if $cost > $least_cost; |
737 |
next if $cost > $least_cost; |
| 625 |
|
738 |
|
| 626 |
if ($cost == $least_cost) { |
739 |
if ( $cost == $least_cost ) { |
| 627 |
push @branch, $_; |
740 |
push @branch, $_; |
| 628 |
next; |
741 |
next; |
| 629 |
} |
742 |
} |
| 630 |
|
743 |
|
| 631 |
@branch = ($_); |
744 |
@branch = ($_); |
| 632 |
$least_cost = $cost; |
745 |
$least_cost = $cost; |
| 633 |
} |
746 |
} |
| 634 |
|
747 |
|
|
Lines 638-642
sub least_cost_branch {
Link Here
|
| 638 |
# return $branch[0] if @branch == 1; |
751 |
# return $branch[0] if @branch == 1; |
| 639 |
} |
752 |
} |
| 640 |
|
753 |
|
| 641 |
|
|
|
| 642 |
1; |
754 |
1; |