View | Details | Raw Unified | Return to bug 5911
Collapse All | Expand All

(-)a/C4/Circulation.pm (-1 / +2 lines)
Lines 1413-1419 sub GetBranchItemRule { Link Here
1413
1413
1414
    foreach my $attempt (@attempts) {
1414
    foreach my $attempt (@attempts) {
1415
        my ($query, @bind_params) = @{$attempt};
1415
        my ($query, @bind_params) = @{$attempt};
1416
        my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params );
1416
        my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params )
1417
          or next;
1417
1418
1418
        # Since branch/category and branch/itemtype use the same per-branch
1419
        # Since branch/category and branch/itemtype use the same per-branch
1419
        # defaults tables, we have to check that the key we want is set, not
1420
        # defaults tables, we have to check that the key we want is set, not
(-)a/C4/HoldsQueue.pm (+622 lines)
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 ($_->{disable_transfer}) {
98
            $cost ||= 0;
99
        }
100
        elsif ( !defined ($cost) || ($cost !~ m/(0|[1-9][0-9]*)(\.[0-9]*)?/o) ) {
101
            warn  "Invalid $from -> $to cost $cost - must be a number >= 0, disablig";
102
            $cost = 0;
103
            $_->{disable_transfer} = 1;
104
        }
105
        $sth->execute( $from, $to, $cost, $_->{disable_transfer} ? 1 : 0 );
106
    }
107
}
108
109
=head2 GetHoldsQueueItems
110
111
  GetHoldsQueueItems($branch);
112
113
Returns hold queue for a holding branch. If branch is omitted, then whole queue is returned
114
115
=cut
116
117
sub GetHoldsQueueItems {
118
    my ($branchlimit) = @_;
119
120
    my @bind_params = ();
121
    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
122
                  FROM tmp_holdsqueue
123
                       JOIN biblio      USING (biblionumber)
124
                  LEFT JOIN biblioitems USING (biblionumber)
125
                  LEFT JOIN items       USING (  itemnumber)
126
                /;
127
    if ($branchlimit) {
128
        $query .=" WHERE tmp_holdsqueue.holdingbranch = ?";
129
        push @bind_params, $branchlimit;
130
    }
131
    $query .= " ORDER BY ccode, location, cn_sort, author, title, pickbranch, reservedate";
132
    my $sth = $dbh->prepare($query);
133
    $sth->execute(@bind_params);
134
    my $items = [];
135
    while ( my $row = $sth->fetchrow_hashref ){
136
        $row->{reservedate} = format_date($row->{reservedate});
137
        my $record = GetMarcBiblio($row->{biblionumber});
138
        if ($record){
139
            $row->{subtitle} = GetRecordValue('subtitle',$record,'')->[0]->{subfield};
140
            $row->{parts} = GetRecordValue('parts',$record,'')->[0]->{subfield};
141
            $row->{numbers} = GetRecordValue('numbers',$record,'')->[0]->{subfield};
142
        }
143
        push @$items, $row;
144
    }
145
    return $items;
146
}
147
148
=head2 CreateQueue
149
150
  CreateQueue();
151
152
Top level function that turns reserves into tmp_holdsqueue and hold_fill_targets.
153
154
=cut
155
156
sub CreateQueue {
157
158
    $dbh->do("DELETE FROM tmp_holdsqueue");  # clear the old table for new info
159
    $dbh->do("DELETE FROM hold_fill_targets");
160
161
    my $total_bibs            = 0;
162
    my $total_requests        = 0;
163
    my $total_available_items = 0;
164
    my $num_items_mapped      = 0;
165
166
    my $branches_to_use;
167
    my $transport_cost_matrix;
168
    my $use_transport_cost_matrix = C4::Context->preference("UseTransportCostMatrix");
169
    if ($use_transport_cost_matrix) {
170
        $transport_cost_matrix = TransportCostMatrix();
171
        unless (keys %$transport_cost_matrix) {
172
            warn "UseTransportCostMatrix set to yes, but matrix not populated";
173
            undef $transport_cost_matrix;
174
        }
175
    }
176
    unless ($transport_cost_matrix) {
177
        $branches_to_use = load_branches_to_pull_from();
178
    }
179
180
    my $bibs_with_pending_requests = GetBibsWithPendingHoldRequests();
181
182
    foreach my $biblionumber (@$bibs_with_pending_requests) {
183
        $total_bibs++;
184
        my $hold_requests   = GetPendingHoldRequestsForBib($biblionumber);
185
        my $available_items = GetItemsAvailableToFillHoldRequestsForBib($biblionumber, $branches_to_use);
186
        $total_requests        += scalar(@$hold_requests);
187
        $total_available_items += scalar(@$available_items);
188
189
        my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix);
190
        $item_map  or next;
191
        my $item_map_size = scalar(keys %$item_map)
192
          or next;
193
194
        $num_items_mapped += $item_map_size;
195
        CreatePicklistFromItemMap($item_map);
196
        AddToHoldTargetMap($item_map);
197
        if (($item_map_size < scalar(@$hold_requests  )) and
198
            ($item_map_size < scalar(@$available_items))) {
199
            # DOUBLE CHECK, but this is probably OK - unfilled item-level requests
200
            # FIXME
201
            #warn "unfilled requests for $biblionumber";
202
            #warn Dumper($hold_requests), Dumper($available_items), Dumper($item_map);
203
        }
204
    }
205
}
206
207
=head2 GetBibsWithPendingHoldRequests
208
209
  my $biblionumber_aref = GetBibsWithPendingHoldRequests();
210
211
Return an arrayref of the biblionumbers of all bibs
212
that have one or more unfilled hold requests.
213
214
=cut
215
216
sub GetBibsWithPendingHoldRequests {
217
    my $dbh = C4::Context->dbh;
218
219
    my $bib_query = "SELECT DISTINCT biblionumber
220
                     FROM reserves
221
                     WHERE found IS NULL
222
                     AND priority > 0
223
                     AND reservedate <= CURRENT_DATE()";
224
    my $sth = $dbh->prepare($bib_query);
225
226
    $sth->execute();
227
    my $biblionumbers = $sth->fetchall_arrayref();
228
229
    return [ map { $_->[0] } @$biblionumbers ];
230
}
231
232
=head2 GetPendingHoldRequestsForBib
233
234
  my $requests = GetPendingHoldRequestsForBib($biblionumber);
235
236
Returns an arrayref of hashrefs to pending, unfilled hold requests
237
on the bib identified by $biblionumber.  The following keys
238
are present in each hashref:
239
240
    biblionumber
241
    borrowernumber
242
    itemnumber
243
    priority
244
    branchcode
245
    reservedate
246
    reservenotes
247
    borrowerbranch
248
249
The arrayref is sorted in order of increasing priority.
250
251
=cut
252
253
sub GetPendingHoldRequestsForBib {
254
    my $biblionumber = shift;
255
256
    my $dbh = C4::Context->dbh;
257
258
    my $request_query = "SELECT biblionumber, borrowernumber, itemnumber, priority, reserves.branchcode, 
259
                                reservedate, reservenotes, borrowers.branchcode AS borrowerbranch
260
                         FROM reserves
261
                         JOIN borrowers USING (borrowernumber)
262
                         WHERE biblionumber = ?
263
                         AND found IS NULL
264
                         AND priority > 0
265
                         AND reservedate <= CURRENT_DATE()
266
                         ORDER BY priority";
267
    my $sth = $dbh->prepare($request_query);
268
    $sth->execute($biblionumber);
269
270
    my $requests = $sth->fetchall_arrayref({});
271
    return $requests;
272
273
}
274
275
=head2 GetItemsAvailableToFillHoldRequestsForBib
276
277
  my $available_items = GetItemsAvailableToFillHoldRequestsForBib($biblionumber, $branches_ar);
278
279
Returns an arrayref of items available to fill hold requests
280
for the bib identified by C<$biblionumber>.  An item is available
281
to fill a hold request if and only if:
282
283
    * it is not on loan
284
    * it is not withdrawn
285
    * it is not marked notforloan
286
    * it is not currently in transit
287
    * it is not lost
288
    * it is not sitting on the hold shelf
289
290
=cut
291
292
sub GetItemsAvailableToFillHoldRequestsForBib {
293
    my ($biblionumber, $branches_to_use) = @_;
294
295
    my $dbh = C4::Context->dbh;
296
    my $items_query = "SELECT itemnumber, homebranch, holdingbranch, itemtypes.itemtype AS itype
297
                       FROM items ";
298
299
    if (C4::Context->preference('item-level_itypes')) {
300
        $items_query .=   "LEFT JOIN itemtypes ON (itemtypes.itemtype = items.itype) ";
301
    } else {
302
        $items_query .=   "JOIN biblioitems USING (biblioitemnumber)
303
                           LEFT JOIN itemtypes USING (itemtype) ";
304
    }
305
    $items_query .=   "WHERE items.notforloan = 0
306
                       AND holdingbranch IS NOT NULL
307
                       AND itemlost = 0
308
                       AND wthdrawn = 0";
309
    $items_query .= "  AND damaged = 0" unless C4::Context->preference('AllowHoldsOnDamagedItems');
310
    $items_query .= "  AND items.onloan IS NULL
311
                       AND (itemtypes.notforloan IS NULL OR itemtypes.notforloan = 0)
312
                       AND itemnumber NOT IN (
313
                           SELECT itemnumber
314
                           FROM reserves
315
                           WHERE biblionumber = ?
316
                           AND itemnumber IS NOT NULL
317
                           AND (found IS NOT NULL OR priority = 0)
318
                        )
319
                       AND items.biblionumber = ?";
320
    $items_query .=  " AND damaged = 0 "
321
      unless C4::Context->preference('AllowHoldsOnDamagedItems');
322
323
    my @params = ($biblionumber, $biblionumber);
324
    if ($branches_to_use && @$branches_to_use) {
325
        $items_query .= " AND holdingbranch IN (" . join (",", map { "?" } @$branches_to_use) . ")";
326
        push @params, @$branches_to_use;
327
    }
328
    my $sth = $dbh->prepare($items_query);
329
    $sth->execute(@params);
330
331
    my $itm = $sth->fetchall_arrayref({});
332
    my @items = grep { ! scalar GetTransfers($_->{itemnumber}) } @$itm;
333
    return [ grep { 
334
        my $rule = GetBranchItemRule($_->{homebranch}, $_->{itype});
335
        $_->{holdallowed} = $rule->{holdallowed} != 0
336
    } @items ];
337
}
338
339
=head2 MapItemsToHoldRequests
340
341
  MapItemsToHoldRequests($hold_requests, $available_items, $branches, $transport_cost_matrix)
342
343
=cut
344
345
sub MapItemsToHoldRequests {
346
    my ($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix) = @_;
347
348
    # handle trival cases
349
    return unless scalar(@$hold_requests) > 0;
350
    return unless scalar(@$available_items) > 0;
351
352
    my $automatic_return = C4::Context->preference("AutomaticItemReturn");
353
354
    # identify item-level requests
355
    my %specific_items_requested = map { $_->{itemnumber} => 1 } 
356
                                   grep { defined($_->{itemnumber}) }
357
                                   @$hold_requests;
358
359
    # group available items by itemnumber
360
    my %items_by_itemnumber = map { $_->{itemnumber} => $_ } @$available_items;
361
362
    # items already allocated
363
    my %allocated_items = ();
364
365
    # map of items to hold requests
366
    my %item_map = ();
367
 
368
    # figure out which item-level requests can be filled    
369
    my $num_items_remaining = scalar(@$available_items);
370
    foreach my $request (@$hold_requests) {
371
        last if $num_items_remaining == 0;
372
373
        # is this an item-level request?
374
        if (defined($request->{itemnumber})) {
375
            # fill it if possible; if not skip it
376
            if (exists $items_by_itemnumber{$request->{itemnumber}} and
377
                not exists $allocated_items{$request->{itemnumber}}) {
378
                $item_map{$request->{itemnumber}} = { 
379
                    borrowernumber => $request->{borrowernumber},
380
                    biblionumber => $request->{biblionumber},
381
                    holdingbranch =>  $items_by_itemnumber{$request->{itemnumber}}->{holdingbranch},
382
                    pickup_branch => $request->{branchcode} || $request->{borrowerbranch},
383
                    item_level => 1,
384
                    reservedate => $request->{reservedate},
385
                    reservenotes => $request->{reservenotes},
386
                };
387
                $allocated_items{$request->{itemnumber}}++;
388
                $num_items_remaining--;
389
            }
390
        } else {
391
            # it's title-level request that will take up one item
392
            $num_items_remaining--;
393
        }
394
    }
395
396
    # group available items by branch
397
    my %items_by_branch = ();
398
    foreach my $item (@$available_items) {
399
        next unless $item->{holdallowed};
400
401
        push @{ $items_by_branch{  $automatic_return ? $item->{homebranch}
402
                                                     : $item->{holdingbranch} } }, $item
403
          unless exists $allocated_items{ $item->{itemnumber} };
404
    }
405
    return unless keys %items_by_branch;
406
407
    # now handle the title-level requests
408
    $num_items_remaining = scalar(@$available_items) - scalar(keys %allocated_items); 
409
    my $pull_branches;
410
    foreach my $request (@$hold_requests) {
411
        last if $num_items_remaining == 0;
412
        next if defined($request->{itemnumber}); # already handled these
413
414
        # look for local match first
415
        my $pickup_branch = $request->{branchcode} || $request->{borrowerbranch};
416
        my ($itemnumber, $holdingbranch);
417
418
        my $holding_branch_items = $automatic_return ? undef : $items_by_branch{$pickup_branch};
419
        if ( $holding_branch_items ) {
420
            foreach my $item (@$holding_branch_items) {
421
                if ( $request->{borrowerbranch} eq $item->{homebranch} ) {
422
                    $itemnumber = $item->{itemnumber};
423
                    last;
424
                }
425
            }
426
            $holdingbranch = $pickup_branch;
427
            $itemnumber ||= $holding_branch_items->[0]->{itemnumber};
428
        }
429
        elsif ($transport_cost_matrix) {
430
            $pull_branches = [keys %items_by_branch];
431
            $holdingbranch = least_cost_branch( $pickup_branch, $pull_branches, $transport_cost_matrix );
432
            if ( $holdingbranch ) {
433
434
                my $holding_branch_items = $items_by_branch{$holdingbranch};
435
                foreach my $item (@$holding_branch_items) {
436
                    next if $request->{borrowerbranch} ne $item->{homebranch};
437
438
                    $itemnumber = $item->{itemnumber};
439
                    last;
440
                }
441
                $itemnumber ||= $holding_branch_items->[0]->{itemnumber};
442
            }
443
            else {
444
                warn "No transport costs for $pickup_branch";
445
            }
446
        }
447
448
        unless ($itemnumber) {
449
            # not found yet, fall back to basics
450
            if ($branches_to_use) {
451
                $pull_branches = $branches_to_use;
452
            } else {
453
                $pull_branches = [keys %items_by_branch];
454
            }
455
            PULL_BRANCHES:
456
            foreach my $branch (@$pull_branches) {
457
                my $holding_branch_items = $items_by_branch{$branch}
458
                  or next;
459
460
                $holdingbranch ||= $branch;
461
                foreach my $item (@$holding_branch_items) {
462
                    next if $pickup_branch ne $item->{homebranch};
463
464
                    $itemnumber = $item->{itemnumber};
465
                    $holdingbranch = $branch;
466
                    last PULL_BRANCHES;
467
                }
468
            }
469
            $itemnumber ||= $items_by_branch{$holdingbranch}->[0]->{itemnumber}
470
              if $holdingbranch;
471
        }
472
473
        if ($itemnumber) {
474
            my $holding_branch_items = $items_by_branch{$holdingbranch}
475
              or die "Have $itemnumber, $holdingbranch, but no items!";
476
            @$holding_branch_items = grep { $_->{itemnumber} != $itemnumber } @$holding_branch_items;
477
            delete $items_by_branch{$holdingbranch} unless @$holding_branch_items;
478
479
            $item_map{$itemnumber} = {
480
                borrowernumber => $request->{borrowernumber},
481
                biblionumber => $request->{biblionumber},
482
                holdingbranch => $holdingbranch,
483
                pickup_branch => $pickup_branch,
484
                item_level => 0,
485
                reservedate => $request->{reservedate},
486
                reservenotes => $request->{reservenotes},
487
            };
488
            $num_items_remaining--; 
489
        }
490
    }
491
    return \%item_map;
492
}
493
494
=head2 CreatePickListFromItemMap 
495
496
=cut
497
498
sub CreatePicklistFromItemMap {
499
    my $item_map = shift;
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 _trim {
564
    return $_[0] unless $_[0];
565
    $_[0] =~ s/^\s+//;
566
    $_[0] =~ s/\s+$//;
567
    $_[0];
568
}
569
570
sub load_branches_to_pull_from {
571
    my $static_branch_list = C4::Context->preference("StaticHoldsQueueWeight")
572
      or return;
573
574
    my @branches_to_use = map _trim($_), split /,/, $static_branch_list;
575
576
    @branches_to_use = shuffle(@branches_to_use) if  C4::Context->preference("RandomizeHoldsQueueWeight");
577
578
    return \@branches_to_use;
579
}
580
581
sub least_cost_branch {
582
583
    #$from - arrayref
584
    my ($to, $from, $transport_cost_matrix) = @_;
585
586
# Nothing really spectacular: supply to branch, a list of potential from branches
587
# and find the minimum from - to value from the transport_cost_matrix
588
    return $from->[0] if @$from == 1;
589
590
    my ($least_cost, @branch);
591
    foreach (@$from) {
592
        my $cell = $transport_cost_matrix->{$to}{$_};
593
        next if $cell->{disable_transfer};
594
595
        my $cost = $cell->{cost};
596
        next unless defined $cost; # XXX should this be reported?
597
598
        unless (defined $least_cost) {
599
            $least_cost = $cost;
600
            push @branch, $_;
601
            next;
602
        }
603
604
        next if $cost > $least_cost;
605
606
        if ($cost == $least_cost) {
607
            push @branch, $_;
608
            next;
609
        }
610
611
        @branch = ($_);
612
        $least_cost = $cost;
613
    }
614
615
    return $branch[0];
616
617
    # XXX return a random @branch with minimum cost instead of the first one;
618
    # return $branch[0] if @branch == 1;
619
}
620
621
622
1;
(-)a/admin/systempreferences.pl (+1 lines)
Lines 203-208 $tabsysprefs{DisplayClearScreenButton} = "Circulation"; Link Here
203
$tabsysprefs{AllowAllMessageDeletion}        = "Circulation";
203
$tabsysprefs{AllowAllMessageDeletion}        = "Circulation";
204
$tabsysprefs{OverdueNoticeBcc}               = "Circulation";
204
$tabsysprefs{OverdueNoticeBcc}               = "Circulation";
205
$tabsysprefs{OverduesBlockCirc}              = "Circulation";
205
$tabsysprefs{OverduesBlockCirc}              = "Circulation";
206
$tabsysprefs{UseTransportCostMatrix}         = "Circulation";
206
207
207
208
208
# Staff Client
209
# Staff Client
(-)a/admin/transport-cost-matrix.pl (+125 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
# Copyright 2000-2002 Katipo Communications
3
# copyright 2010 BibLibre
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
use strict;
21
use warnings;
22
use CGI;
23
use C4::Context;
24
use C4::Output;
25
use C4::Auth;
26
use C4::Koha;
27
use C4::Debug;
28
use C4::Branch; # GetBranches
29
use C4::HoldsQueue qw(TransportCostMatrix UpdateTransportCostMatrix);
30
31
use Data::Dumper;
32
33
my $input = new CGI;
34
35
my ($template, $loggedinuser, $cookie)
36
    = get_template_and_user({template_name => "admin/transport-cost-matrix.tmpl",
37
                            query => $input,
38
                            type => "intranet",
39
                            authnotrequired => 0,
40
                            flagsrequired => {parameters => 1},
41
                            debug => 1,
42
                            });
43
my $use_transport_cost_matrix = C4::Context->preference("UseTransportCostMatrix");
44
45
my $update = $input->param('op') eq 'set-cost-matrix';
46
47
my ($cost_matrix, $have_matrix);
48
unless ($update) {
49
    $cost_matrix = TransportCostMatrix();
50
    $have_matrix = keys %$cost_matrix if $cost_matrix;
51
}
52
53
my $branches = GetBranches();
54
my @branchloop = map { code => $_,
55
                       name => $branches->{$_}->{'branchname'} },
56
                 sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} }
57
                 keys %$branches;
58
my (@branchfromloop, @cost, @errors);
59
foreach my $branchfrom ( @branchloop ) {
60
    my $fromcode = $branchfrom->{code};
61
62
    my %from_row = ( code => $fromcode, name => $branchfrom->{name} );
63
    foreach my $branchto ( @branchloop ) {
64
        my $tocode = $branchto->{code};
65
66
        my %from_to_input_def = ( code => $tocode, name => $branchto->{name} );
67
        push @{ $from_row{branchtoloop} }, \%from_to_input_def;
68
69
        if ($fromcode eq $tocode) {
70
            $from_to_input_def{skip} = 1;
71
            next;
72
        }
73
74
        (my $from_to = "${fromcode}_${tocode}") =~ s/\W//go;
75
         $from_to_input_def{id} = $from_to;
76
        my $input_name   = "cost_$from_to";
77
        my $disable_name = "disable_$from_to";
78
79
        if ($update) {
80
            my $value = $from_to_input_def{value} = $input->param($input_name);
81
            if ( $input->param($disable_name) ) {
82
                $from_to_input_def{disabled} = 1;
83
            }
84
            else {
85
                push @errors, "Invalid value for $from_row{name} -> $from_to_input_def{name}"
86
                  unless $value =~ /\d/o && $value >= 0.0;
87
            }
88
        }
89
        else {
90
            if ($have_matrix) {
91
                if ( my $cell = $cost_matrix->{$tocode}{$fromcode} ) {
92
                    $from_to_input_def{value} = $cell->{cost};
93
                    $from_to_input_def{disabled} = 1 if $cell->{disable_transfer};
94
                }
95
            } else {
96
                $from_to_input_def{disabled} = 1;
97
            }
98
        }
99
    }
100
    
101
#              die Dumper(\%from_row);
102
    push @branchfromloop, \%from_row;
103
}
104
105
if ($update && !@errors) {
106
    my @update_recs = map {
107
        my $from = $_->{code};
108
        map { frombranch => $from, tobranch => $_->{code}, cost => $_->{value}, disable_transfer => $_->{disabled} || 0 },
109
            grep { $_->{code} ne $from }
110
            @{ $_->{branchtoloop} };
111
    } @branchfromloop;
112
113
    UpdateTransportCostMatrix(\@update_recs);
114
}
115
   
116
$template->param(
117
    branchloop => \@branchloop,
118
    branchfromloop => \@branchfromloop,
119
    WARNING_transport_cost_matrix_off => !$use_transport_cost_matrix,
120
    errors => \@errors,
121
);
122
output_html_with_http_headers $input, $cookie, $template->output;
123
124
exit 0;
125
(-)a/circ/view_holdsqueue.pl (-32 / +2 lines)
Lines 31-37 use C4::Biblio; Link Here
31
use C4::Items;
31
use C4::Items;
32
use C4::Koha;   # GetItemTypes
32
use C4::Koha;   # GetItemTypes
33
use C4::Branch; # GetBranches
33
use C4::Branch; # GetBranches
34
use C4::Dates qw/format_date/;
34
use C4::HoldsQueue qw(GetHoldsQueueItems);
35
35
36
my $query = new CGI;
36
my $query = new CGI;
37
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
37
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
Lines 51-56 my $branchlimit = $params->{'branchlimit'}; Link Here
51
my $itemtypeslimit = $params->{'itemtypeslimit'};
51
my $itemtypeslimit = $params->{'itemtypeslimit'};
52
52
53
if ( $run_report ) {
53
if ( $run_report ) {
54
    # XXX GetHoldsQueueItems() does not support $itemtypeslimit!
54
    my $items = GetHoldsQueueItems($branchlimit, $itemtypeslimit);
55
    my $items = GetHoldsQueueItems($branchlimit, $itemtypeslimit);
55
    $template->param(
56
    $template->param(
56
        branch     => $branchlimit,
57
        branch     => $branchlimit,
Lines 76-111 $template->param( Link Here
76
   itemtypeloop => \@itemtypesloop,
77
   itemtypeloop => \@itemtypesloop,
77
);
78
);
78
79
79
sub GetHoldsQueueItems {
80
	my ($branchlimit,$itemtypelimit) = @_;
81
	my $dbh = C4::Context->dbh;
82
83
    my @bind_params = ();
84
	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
85
                  FROM tmp_holdsqueue
86
                       JOIN biblio      USING (biblionumber)
87
				  LEFT JOIN biblioitems USING (biblionumber)
88
                  LEFT JOIN items       USING (  itemnumber)
89
                /;
90
    if ($branchlimit) {
91
	    $query .=" WHERE tmp_holdsqueue.holdingbranch = ?";
92
        push @bind_params, $branchlimit;
93
    }
94
    $query .= " ORDER BY ccode, location, cn_sort, author, title, pickbranch, reservedate";
95
	my $sth = $dbh->prepare($query);
96
	$sth->execute(@bind_params);
97
	my $items = [];
98
    while ( my $row = $sth->fetchrow_hashref ){
99
	$row->{reservedate} = format_date($row->{reservedate});
100
	my $record = GetMarcBiblio($row->{biblionumber});
101
    if ($record){
102
        $row->{subtitle} = GetRecordValue('subtitle',$record,'')->[0]->{subfield};
103
	    $row->{parts} = GetRecordValue('parts',$record,'')->[0]->{subfield};
104
	    $row->{numbers} = GetRecordValue('numbers',$record,'')->[0]->{subfield};
105
	}
106
        push @$items, $row;
107
    }
108
    return $items;
109
}
110
# writing the template
80
# writing the template
111
output_html_with_http_headers $query, $cookie, $template->output;
81
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/installer/data/mysql/kohastructure.sql (+16 lines)
Lines 2808-2813 CREATE TABLE `fieldmapping` ( -- koha to keyword mapping Link Here
2808
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2808
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2809
2809
2810
--
2810
--
2811
-- Table structure for table `transport_cost`
2812
--
2813
2814
DROP TABLE IF EXISTS transport_cost;
2815
CREATE TABLE transport_cost (
2816
      frombranch varchar(10) NOT NULL,
2817
      tobranch varchar(10) NOT NULL,
2818
      cost decimal(6,2) NOT NULL,
2819
      disable_transfer tinyint(1) NOT NULL DEFAULT 0,
2820
      CHECK ( frombranch <> tobranch ), -- a dud check, mysql does not support that
2821
      PRIMARY KEY (frombranch, tobranch),
2822
      CONSTRAINT transport_cost_ibfk_1 FOREIGN KEY (frombranch) REFERENCES branches (branchcode) ON DELETE CASCADE ON UPDATE CASCADE,
2823
      CONSTRAINT transport_cost_ibfk_2 FOREIGN KEY (tobranch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE
2824
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2825
2826
--
2811
-- Table structure for table `biblioimages`
2827
-- Table structure for table `biblioimages`
2812
--
2828
--
2813
2829
(-)a/installer/data/mysql/sysprefs.sql (+1 lines)
Lines 317-322 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES Link Here
317
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('OpacHiddenItems','','This syspref allows to define custom rules for hiding specific items at opac. See docs/opac/OpacHiddenItems.txt for more informations.','','Textarea');
317
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('OpacHiddenItems','','This syspref allows to define custom rules for hiding specific items at opac. See docs/opac/OpacHiddenItems.txt for more informations.','','Textarea');
318
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('numSearchRSSResults',50,'Specify the maximum number of results to display on a RSS page of results',NULL,'Integer');
318
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('numSearchRSSResults',50,'Specify the maximum number of results to display on a RSS page of results',NULL,'Integer');
319
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('OpacRenewalBranch','checkoutbranch','Choose how the branch for an OPAC renewal is recorded in statistics','itemhomebranch|patronhomebranch|checkoutbranch|null','Choice');
319
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('OpacRenewalBranch','checkoutbranch','Choose how the branch for an OPAC renewal is recorded in statistics','itemhomebranch|patronhomebranch|checkoutbranch|null','Choice');
320
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('UseTransportCostMatrix',0,"Use Transport Cost Matrix when filling holds",'','YesNo');
320
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice');
321
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice');
321
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn           | a|a     d', NULL, NULL, 'Textarea');
322
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn           | a|a     d', NULL, NULL, 'Textarea');
322
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo');
323
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo');
(-)a/installer/data/mysql/updatedatabase.pl (+22 lines)
Lines 5684-5689 if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { Link Here
5684
    SetVersion($DBversion);
5684
    SetVersion($DBversion);
5685
}
5685
}
5686
5686
5687
5688
5689
5690
$DBversion = "3.09.00.XXX";
5691
if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
5692
    $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('UseTransportCostMatrix',0,'Use Transport Cost Matrix when filling holds','','YesNo')");
5693
5694
 $dbh->do("CREATE TABLE `transport_cost` (
5695
              `frombranch` varchar(10) NOT NULL,
5696
              `tobranch` varchar(10) NOT NULL,
5697
              `cost` decimal(6,2) NOT NULL,
5698
              `disable_transfer` tinyint(1) NOT NULL DEFAULT 0,
5699
              CHECK ( `frombranch` <> `tobranch` ), -- a dud check, mysql does not support that
5700
              PRIMARY KEY (`frombranch`, `tobranch`),
5701
              CONSTRAINT `transport_cost_ibfk_1` FOREIGN KEY (`frombranch`) REFERENCES `branches` (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE,
5702
              CONSTRAINT `transport_cost_ibfk_2` FOREIGN KEY (`tobranch`) REFERENCES `branches` (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE
5703
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8");
5704
5705
    print "Upgrade to $DBversion done (creating `transport_cost` table; adding UseTransportCostMatrix systempref, in circulation)\n";
5706
    SetVersion ($DBversion);
5707
}
5708
5687
=head1 FUNCTIONS
5709
=head1 FUNCTIONS
5688
5710
5689
=head2 TableExists($table)
5711
=head2 TableExists($table)
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+2 lines)
Lines 50-55 Link Here
50
    <dd>Define extended attributes (identifiers and statistical categories) for patron records</dd>
50
    <dd>Define extended attributes (identifiers and statistical categories) for patron records</dd>
51
    <dt><a href="/cgi-bin/koha/admin/branch_transfer_limits.pl">Library transfer limits</a></dt>
51
    <dt><a href="/cgi-bin/koha/admin/branch_transfer_limits.pl">Library transfer limits</a></dt>
52
	<dd>Limit the ability to transfer items between libraries based on the library sending, the library receiving, and the item type involved. These rules only go into effect if the preference UseBranchTransferLimits is set to ON.</dd>
52
	<dd>Limit the ability to transfer items between libraries based on the library sending, the library receiving, and the item type involved. These rules only go into effect if the preference UseBranchTransferLimits is set to ON.</dd>
53
    <dt><a href="/cgi-bin/koha/admin/transport-cost-matrix.pl">Transport Cost Matrix</a></dt>
54
	<dd>Define transport costs between branches</dd>
53
    <dt><a href="/cgi-bin/koha/admin/item_circulation_alerts.pl">Item circulation alerts</a></dt>
55
    <dt><a href="/cgi-bin/koha/admin/item_circulation_alerts.pl">Item circulation alerts</a></dt>
54
	<dd>Define rules for check-in and checkout notifications for combinations of libraries, patron categories, and item types</dd>
56
	<dd>Define rules for check-in and checkout notifications for combinations of libraries, patron categories, and item types</dd>
55
    <dt><a href="/cgi-bin/koha/admin/cities.pl">Cities and towns</a></dt>
57
    <dt><a href="/cgi-bin/koha/admin/cities.pl">Cities and towns</a></dt>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (+6 lines)
Lines 171-176 Circulation: Link Here
171
                  itemtype: item type
171
                  itemtype: item type
172
            - .
172
            - .
173
        -
173
        -
174
            - pref: UseTransportCostMatrix
175
              choices:
176
                  yes: Use
177
                  no: "Don't use"
178
            - Transport Cost Matrix for calculating optimal holds filling between branches.
179
        -
174
            - Use the checkout and fines rules of
180
            - Use the checkout and fines rules of
175
            - pref: CircControl
181
            - pref: CircControl
176
              type: choice
182
              type: choice
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/transport-cost-matrix.tt (+131 lines)
Line 0 Link Here
1
[% INCLUDE 'doc-head-open.inc' %]
2
<title>Koha &rsaquo; Administration &rsaquo; Transport Cost Matrix</title>
3
[% INCLUDE 'doc-head-close.inc' %]
4
5
<script type="text/javascript">
6
//<![CDATA[
7
function check_transport_cost(e) {
8
    var val = e.value;
9
    if (val && val != '' && !isNaN(parseFloat(val)) && val >= 0.0) {
10
        return;
11
    }
12
    alert("Cost must be expressed as a decimal number >= 0");
13
}
14
function disable_transport_cost_chg(e, cost_id) {
15
    disable_transport_cost(cost_id, e.checked);
16
}
17
function disable_transport_cost(cost_id, disable) {
18
    if (disable) {
19
        $('#celldiv_'+cost_id).find('input[type=text]').attr("disabled","disabled").addClass('disabled-transfer');
20
    } else {
21
        $('#celldiv_'+cost_id).find('input:disabled').removeAttr("disabled").removeClass('disabled-transfer');
22
    }
23
}
24
function enable_cost_input(cost_id) {
25
    var cell = $('#celldiv_'+cost_id);
26
    var cost = $(cell).text();
27
    var disabled = $(cell).hasClass('disabled-transfer');
28
    $(cell).removeClass('disabled-transfer');
29
30
    $('#celldiv_'+cost_id).html(
31
        '<input type="text" name="cost_'+cost_id+'" onblur="check_transport_cost(this);" size="4" value="'+$.trim(cost)+'" />'+
32
        '<br/>Disable <input name="disable_'+cost_id+'" value="1" onchange="disable_transport_cost_chg(this, \''+cost_id+'\');" type="checkbox" '+(disabled ? 'checked' : '')+' />'
33
    );
34
    disable_transport_cost(cost_id, disabled);
35
}
36
37
function form_submit (f) {
38
    $(f).find('input:disabled').removeAttr("disabled");
39
    return true;
40
}
41
42
$(document).ready(function() {
43
    show_transport_cost_matrix([% IF UseTransportCostMatrix %]true[% ELSE %]false[% END %]);
44
});
45
//]]>
46
</script>
47
<style type="text/css">
48
.disabled-transfer {
49
    background-color: red;
50
}
51
.errors {
52
    color: red;
53
}
54
</style>
55
56
</head>
57
<body>
58
[% INCLUDE 'header.inc' %]
59
[% INCLUDE 'cat-search.inc' %]
60
61
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> &rsaquo; Transport Cost Matrix</div>
62
63
<div id="doc3" class="yui-t1">
64
65
<div id="bd">
66
    <div id="yui-main">
67
    <div class="yui-b">
68
    <h1 class="parameters">
69
            Defining transport costs between libraries
70
    </h1>
71
[% IF ( WARNING_transport_cost_matrix_off ) %]
72
<div class="dialog message">Because the "UseTransportCostMatrix" system preference is currently not enabled, Transport Cost Matrix is not being used.  Go <a href="/cgi-bin/koha/admin/preferences.pl?op=search&amp;searchfield=UseTransportCostMatrix">here</a> if you wish to enable this feature.</div>
73
[% END %]
74
75
    <div class="container">
76
        <form method="post" action="?" onSubmit="return form_submit(this);">
77
            <input type="hidden" name="op" value="set-cost-matrix" />
78
            <div id="transport-cost-matrix">
79
                <div class="help">
80
                    <p>Costs are decimal values 0 to some arbitrarymax value (1 or 100), 0 being minimum (no) cost.</p>
81
                    <p>Red cells signify no transfer allowed</p>
82
                    <p>Click on the cell to edit</p>
83
                </div>
84
                <ul class="errors" %]>
85
                [% FOR e IN errors %]
86
                    <li>[% e %]</li>
87
                [% END %]
88
                </ul>
89
                <table>
90
                    <tr>
91
                        <th>From \ To</th>
92
                [% FOR b IN branchloop %]
93
                        <th>[% b.name %]</th>
94
                [% END %]
95
                    <tr>
96
                [% FOR bf IN branchfromloop %]
97
                    <tr>
98
                        <th>[% bf.name %]</th>
99
                    [% FOR bt IN bf.branchtoloop %]
100
                        <td>
101
                        [% IF bt.skip %]
102
                            &nbsp;
103
                        [% ELSE %]
104
                            [% IF bt.disabled %]
105
                            <div id="celldiv_[% bt.id %]" class="disabled-transfer">
106
                            [% ELSE %]
107
                            <div id="celldiv_[% bt.id %]">
108
                            [% END %]
109
                            <div onclick="enable_cost_input('[% bt.id %]');">[% bt.disabled ? '&nbsp;' : bt.value %]</div>
110
                            <input type="hidden" name="cost_[% bt.id %]" value="[% bt.value %]" />
111
                            [% IF bt.disabled %]
112
                            <input type="hidden" name="disable_[% bt.id %]" value="1" />
113
                            [% END %]
114
                            </div>
115
                        [% END %]
116
                        </td>
117
                    [% END %]
118
                    </tr>
119
                [% END %]
120
                </table>
121
            </div>
122
            <input type="submit" value="Save" class="submit" />
123
        </form>
124
    </div>
125
    </div>
126
    </div>
127
<div class="yui-b">
128
[% INCLUDE 'admin-menu.inc' %]
129
</div>
130
</div>
131
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/misc/cronjobs/holds/build_holds_queue.pl (-383 / +2 lines)
Lines 5-11 Link Here
5
#-----------------------------------
5
#-----------------------------------
6
# FIXME: add command-line options for verbosity and summary
6
# FIXME: add command-line options for verbosity and summary
7
# FIXME: expand perldoc, explain intended logic
7
# FIXME: expand perldoc, explain intended logic
8
# FIXME: refactor all subroutines into C4 for testability
9
8
10
use strict;
9
use strict;
11
use warnings;
10
use warnings;
Lines 16-402 BEGIN { Link Here
16
    eval { require "$FindBin::Bin/../kohalib.pl" };
15
    eval { require "$FindBin::Bin/../kohalib.pl" };
17
}
16
}
18
17
19
use C4::Context;
18
use C4::HoldsQueue qw(CreateQueue);
20
use C4::Search;
21
use C4::Items;
22
use C4::Branch;
23
use C4::Circulation;
24
use C4::Members;
25
use C4::Biblio;
26
19
27
use List::Util qw(shuffle);
20
CreateQueue();
28
21
29
my $bibs_with_pending_requests = GetBibsWithPendingHoldRequests();
30
31
my $dbh   = C4::Context->dbh;
32
$dbh->do("DELETE FROM tmp_holdsqueue");  # clear the old table for new info
33
$dbh->do("DELETE FROM hold_fill_targets");
34
35
my $total_bibs            = 0;
36
my $total_requests        = 0;
37
my $total_available_items = 0;
38
my $num_items_mapped      = 0;
39
40
my @branches_to_use = _get_branches_to_pull_from();
41
42
foreach my $biblionumber (@$bibs_with_pending_requests) {
43
    $total_bibs++;
44
    my $hold_requests   = GetPendingHoldRequestsForBib($biblionumber);
45
    my $available_items = GetItemsAvailableToFillHoldRequestsForBib($biblionumber, @branches_to_use);
46
    $total_requests        += scalar(@$hold_requests);
47
    $total_available_items += scalar(@$available_items);
48
    my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, @branches_to_use);
49
50
    (defined($item_map)) or next;
51
52
    my $item_map_size = scalar(keys %$item_map);
53
    $num_items_mapped += $item_map_size;
54
    CreatePicklistFromItemMap($item_map);
55
    AddToHoldTargetMap($item_map);
56
    if (($item_map_size < scalar(@$hold_requests  )) and
57
        ($item_map_size < scalar(@$available_items))) {
58
        # DOUBLE CHECK, but this is probably OK - unfilled item-level requests
59
        # FIXME
60
        #warn "unfilled requests for $biblionumber";
61
        #warn Dumper($hold_requests), Dumper($available_items), Dumper($item_map);
62
    }
63
}
64
65
exit 0;
66
67
=head1 FUNCTIONS
68
69
=head2 GetBibsWithPendingHoldRequests
70
71
  my $biblionumber_aref = GetBibsWithPendingHoldRequests();
72
73
Return an arrayref of the biblionumbers of all bibs
74
that have one or more unfilled hold requests.
75
76
=cut
77
78
sub GetBibsWithPendingHoldRequests {
79
    my $dbh = C4::Context->dbh;
80
81
    my $bib_query = "SELECT DISTINCT biblionumber
82
                     FROM reserves
83
                     WHERE found IS NULL
84
                     AND priority > 0
85
                     AND reservedate <= CURRENT_DATE()
86
                     AND suspend = 0
87
                     ";
88
    my $sth = $dbh->prepare($bib_query);
89
90
    $sth->execute();
91
    my $biblionumbers = $sth->fetchall_arrayref();
92
93
    return [ map { $_->[0] } @$biblionumbers ];
94
}
95
96
=head2 GetPendingHoldRequestsForBib
97
98
  my $requests = GetPendingHoldRequestsForBib($biblionumber);
99
100
Returns an arrayref of hashrefs to pending, unfilled hold requests
101
on the bib identified by $biblionumber.  The following keys
102
are present in each hashref:
103
104
    biblionumber
105
    borrowernumber
106
    itemnumber
107
    priority
108
    branchcode
109
    reservedate
110
    reservenotes
111
    borrowerbranch
112
113
The arrayref is sorted in order of increasing priority.
114
115
=cut
116
117
sub GetPendingHoldRequestsForBib {
118
    my $biblionumber = shift;
119
120
    my $dbh = C4::Context->dbh;
121
122
    my $request_query = "SELECT biblionumber, borrowernumber, itemnumber, priority, reserves.branchcode, 
123
                                reservedate, reservenotes, borrowers.branchcode AS borrowerbranch
124
                         FROM reserves
125
                         JOIN borrowers USING (borrowernumber)
126
                         WHERE biblionumber = ?
127
                         AND found IS NULL
128
                         AND priority > 0
129
                         AND reservedate <= CURRENT_DATE()
130
                         AND suspend = 0
131
                         ORDER BY priority";
132
    my $sth = $dbh->prepare($request_query);
133
    $sth->execute($biblionumber);
134
135
    my $requests = $sth->fetchall_arrayref({});
136
    return $requests;
137
138
}
139
140
=head2 GetItemsAvailableToFillHoldRequestsForBib
141
142
  my $available_items = GetItemsAvailableToFillHoldRequestsForBib($biblionumber);
143
144
Returns an arrayref of items available to fill hold requests
145
for the bib identified by C<$biblionumber>.  An item is available
146
to fill a hold request if and only if:
147
148
    * it is not on loan
149
    * it is not withdrawn
150
    * it is not marked notforloan
151
    * it is not currently in transit
152
    * it is not lost
153
    * it is not sitting on the hold shelf
154
155
=cut
156
157
sub GetItemsAvailableToFillHoldRequestsForBib {
158
    my $biblionumber = shift;
159
    my @branches_to_use = @_;
160
161
    my $dbh = C4::Context->dbh;
162
    my $items_query = "SELECT itemnumber, homebranch, holdingbranch, itemtypes.itemtype AS itype
163
                       FROM items ";
164
165
    if (C4::Context->preference('item-level_itypes')) {
166
        $items_query .=   "LEFT JOIN itemtypes ON (itemtypes.itemtype = items.itype) ";
167
    } else {
168
        $items_query .=   "JOIN biblioitems USING (biblioitemnumber)
169
                           LEFT JOIN itemtypes USING (itemtype) ";
170
    }
171
    $items_query .=   "WHERE items.notforloan = 0
172
                       AND holdingbranch IS NOT NULL
173
                       AND itemlost = 0
174
                       AND wthdrawn = 0";
175
    $items_query .=   " AND damaged = 0 " unless C4::Context->preference('AllowHoldsOnDamagedItems');
176
    $items_query .=   " AND items.onloan IS NULL
177
                       AND (itemtypes.notforloan IS NULL OR itemtypes.notforloan = 0)
178
                       AND itemnumber NOT IN (
179
                           SELECT itemnumber
180
                           FROM reserves
181
                           WHERE biblionumber = ?
182
                           AND itemnumber IS NOT NULL
183
                           AND (found IS NOT NULL OR priority = 0)
184
                        )
185
                       AND items.biblionumber = ?";
186
    my @params = ($biblionumber, $biblionumber);
187
    if ($#branches_to_use > -1) {
188
        $items_query .= " AND holdingbranch IN (" . join (",", map { "?" } @branches_to_use) . ")";
189
        push @params, @branches_to_use;
190
    }
191
    my $sth = $dbh->prepare($items_query);
192
    $sth->execute(@params);
193
194
    my $items = $sth->fetchall_arrayref({});
195
    $items = [ grep { my @transfers = GetTransfers($_->{itemnumber}); $#transfers == -1; } @$items ]; 
196
    map { my $rule = GetBranchItemRule($_->{homebranch}, $_->{itype}); $_->{holdallowed} = $rule->{holdallowed}; $rule->{holdallowed} != 0 } @$items;
197
    return [ grep { $_->{holdallowed} != 0 } @$items ];
198
}
199
200
=head2 MapItemsToHoldRequests
201
202
  MapItemsToHoldRequests($hold_requests, $available_items);
203
204
=cut
205
206
sub MapItemsToHoldRequests {
207
    my $hold_requests = shift;
208
    my $available_items = shift;
209
    my @branches_to_use = @_;
210
211
    # handle trival cases
212
    return unless scalar(@$hold_requests) > 0;
213
    return unless scalar(@$available_items) > 0;
214
215
    # identify item-level requests
216
    my %specific_items_requested = map { $_->{itemnumber} => 1 } 
217
                                   grep { defined($_->{itemnumber}) }
218
                                   @$hold_requests;
219
220
    # group available items by itemnumber
221
    my %items_by_itemnumber = map { $_->{itemnumber} => $_ } @$available_items;
222
223
    # items already allocated
224
    my %allocated_items = ();
225
226
    # map of items to hold requests
227
    my %item_map = ();
228
 
229
    # figure out which item-level requests can be filled    
230
    my $num_items_remaining = scalar(@$available_items);
231
    foreach my $request (@$hold_requests) {
232
        last if $num_items_remaining == 0;
233
234
        # is this an item-level request?
235
        if (defined($request->{itemnumber})) {
236
            # fill it if possible; if not skip it
237
            if (exists $items_by_itemnumber{$request->{itemnumber}} and
238
                not exists $allocated_items{$request->{itemnumber}}) {
239
                $item_map{$request->{itemnumber}} = { 
240
                    borrowernumber => $request->{borrowernumber},
241
                    biblionumber => $request->{biblionumber},
242
                    holdingbranch =>  $items_by_itemnumber{$request->{itemnumber}}->{holdingbranch},
243
                    pickup_branch => $request->{branchcode},
244
                    item_level => 1,
245
                    reservedate => $request->{reservedate},
246
                    reservenotes => $request->{reservenotes},
247
                };
248
                $allocated_items{$request->{itemnumber}}++;
249
                $num_items_remaining--;
250
            }
251
        } else {
252
            # it's title-level request that will take up one item
253
            $num_items_remaining--;
254
        }
255
    }
256
257
    # group available items by branch
258
    my %items_by_branch = ();
259
    foreach my $item (@$available_items) {
260
        push @{ $items_by_branch{ $item->{holdingbranch} } }, $item unless exists $allocated_items{ $item->{itemnumber} };
261
    }
262
263
    # now handle the title-level requests
264
    $num_items_remaining = scalar(@$available_items) - scalar(keys %allocated_items); 
265
    foreach my $request (@$hold_requests) {
266
        last if $num_items_remaining <= 0;
267
        next if defined($request->{itemnumber}); # already handled these
268
269
        # look for local match first
270
        my $pickup_branch = $request->{branchcode};
271
        if (exists $items_by_branch{$pickup_branch} and 
272
            not ($items_by_branch{$pickup_branch}->[0]->{holdallowed} == 1 and 
273
                 $request->{borrowerbranch} ne $items_by_branch{$pickup_branch}->[0]->{homebranch}) 
274
           ) {
275
            my $item = pop @{ $items_by_branch{$pickup_branch} };
276
            delete $items_by_branch{$pickup_branch} if scalar(@{ $items_by_branch{$pickup_branch} }) == 0;
277
            $item_map{$item->{itemnumber}} = { 
278
                                                borrowernumber => $request->{borrowernumber},
279
                                                biblionumber => $request->{biblionumber},
280
                                                holdingbranch => $pickup_branch,
281
                                                pickup_branch => $pickup_branch,
282
                                                item_level => 0,
283
                                                reservedate => $request->{reservedate},
284
                                                reservenotes => $request->{reservenotes},
285
                                             };
286
            $num_items_remaining--;
287
        } else {
288
            my @pull_branches = ();
289
            if ($#branches_to_use > -1) {
290
                @pull_branches = @branches_to_use;
291
            } else {
292
                @pull_branches = sort keys %items_by_branch;
293
            }
294
            foreach my $branch (@pull_branches) {
295
                next unless exists $items_by_branch{$branch} and
296
                            not ($items_by_branch{$branch}->[0]->{holdallowed} == 1 and 
297
                                $request->{borrowerbranch} ne $items_by_branch{$branch}->[0]->{homebranch});
298
                my $item = pop @{ $items_by_branch{$branch} };
299
                delete $items_by_branch{$branch} if scalar(@{ $items_by_branch{$branch} }) == 0;
300
                $item_map{$item->{itemnumber}} = { 
301
                                                    borrowernumber => $request->{borrowernumber},
302
                                                    biblionumber => $request->{biblionumber},
303
                                                    holdingbranch => $branch,
304
                                                    pickup_branch => $pickup_branch,
305
                                                    item_level => 0,
306
                                                    reservedate => $request->{reservedate},
307
                                                    reservenotes => $request->{reservenotes},
308
                                                 };
309
                $num_items_remaining--; 
310
                last;
311
            }
312
        }
313
    }
314
    return \%item_map;
315
}
316
317
=head2 CreatePickListFromItemMap 
318
319
=cut
320
321
sub CreatePicklistFromItemMap {
322
    my $item_map = shift;
323
324
    my $dbh = C4::Context->dbh;
325
326
    my $sth_load=$dbh->prepare("
327
        INSERT INTO tmp_holdsqueue (biblionumber,itemnumber,barcode,surname,firstname,phone,borrowernumber,
328
                                    cardnumber,reservedate,title, itemcallnumber,
329
                                    holdingbranch,pickbranch,notes, item_level_request)
330
        VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
331
    ");
332
333
    foreach my $itemnumber  (sort keys %$item_map) {
334
        my $mapped_item = $item_map->{$itemnumber};
335
        my $biblionumber = $mapped_item->{biblionumber}; 
336
        my $borrowernumber = $mapped_item->{borrowernumber}; 
337
        my $pickbranch = $mapped_item->{pickup_branch};
338
        my $holdingbranch = $mapped_item->{holdingbranch};
339
        my $reservedate = $mapped_item->{reservedate};
340
        my $reservenotes = $mapped_item->{reservenotes};
341
        my $item_level = $mapped_item->{item_level};
342
343
        my $item = GetItem($itemnumber);
344
        my $barcode = $item->{barcode};
345
        my $itemcallnumber = $item->{itemcallnumber};
346
347
        my $borrower = GetMember('borrowernumber'=>$borrowernumber);
348
        my $cardnumber = $borrower->{'cardnumber'};
349
        my $surname = $borrower->{'surname'};
350
        my $firstname = $borrower->{'firstname'};
351
        my $phone = $borrower->{'phone'};
352
   
353
        my $bib = GetBiblioData($biblionumber);
354
        my $title = $bib->{title}; 
355
356
        $sth_load->execute($biblionumber, $itemnumber, $barcode, $surname, $firstname, $phone, $borrowernumber,
357
                           $cardnumber, $reservedate, $title, $itemcallnumber,
358
                           $holdingbranch, $pickbranch, $reservenotes, $item_level);
359
    }
360
}
361
362
=head2 AddToHoldTargetMap
363
364
=cut
365
366
sub AddToHoldTargetMap {
367
    my $item_map = shift;
368
369
    my $dbh = C4::Context->dbh;
370
371
    my $insert_sql = q(
372
        INSERT INTO hold_fill_targets (borrowernumber, biblionumber, itemnumber, source_branchcode, item_level_request)
373
                               VALUES (?, ?, ?, ?, ?)
374
    );
375
    my $sth_insert = $dbh->prepare($insert_sql);
376
377
    foreach my $itemnumber (keys %$item_map) {
378
        my $mapped_item = $item_map->{$itemnumber};
379
        $sth_insert->execute($mapped_item->{borrowernumber}, $mapped_item->{biblionumber}, $itemnumber,
380
                             $mapped_item->{holdingbranch}, $mapped_item->{item_level});
381
    }
382
}
383
384
=head2 _get_branches_to_pull_from
385
386
Query system preferences to get ordered list of
387
branches to use to fill hold requests.
388
389
=cut
390
391
sub _get_branches_to_pull_from {
392
    my @branches_to_use = ();
393
  
394
    my $static_branch_list = C4::Context->preference("StaticHoldsQueueWeight");
395
    if ($static_branch_list) {
396
        @branches_to_use = map { s/^\s+//; s/\s+$//; $_; } split /,/, $static_branch_list;
397
    }
398
399
    @branches_to_use = shuffle(@branches_to_use) if  C4::Context->preference("RandomizeHoldsQueueWeight");
400
401
    return @branches_to_use;
402
}
(-)a/t/db_dependent/HoldsQueue.t (-1 / +175 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Test C4::HoldsQueue::CreateQueue() for both transport cost matrix
4
# and StaticHoldsQueueWeight array (no RandomizeHoldsQueueWeight, no point)
5
# Wraps tests in transaction that's rolled back, so no data is destroyed
6
# MySQL WARNING: This makes sense only if your tables are InnoDB, otherwise
7
# transactions are not supported and mess is left behind
8
9
use strict;
10
use warnings;
11
use C4::Context;
12
13
use Data::Dumper;
14
15
use Test::More tests => 18;
16
17
BEGIN {
18
	use FindBin;
19
	use lib $FindBin::Bin;
20
	use_ok('C4::Reserves');
21
	use_ok('C4::HoldsQueue');
22
}
23
24
my $TITLE = "Test Holds Queue XXX";
25
# Pick a plausible borrower. Easier than creating one.
26
my $BORROWER_QRY = <<EOQ;
27
select *
28
from borrowers
29
where borrowernumber = (select max(borrowernumber) from issues)
30
EOQ
31
my $dbh = C4::Context->dbh;
32
my $borrower = $dbh->selectrow_hashref($BORROWER_QRY);
33
my $borrowernumber = $borrower->{borrowernumber};
34
# Set special (for this test) branches
35
my $borrower_branchcode = $borrower->{branchcode};
36
my @other_branches = grep { $_ ne $borrower_branchcode } @{ $dbh->selectcol_arrayref("SELECT branchcode FROM branches") };
37
my $least_cost_branch_code = pop @other_branches
38
  or BAIL_OUT("No point testing only one branch...");
39
my $itemtype = $dbh->selectrow_array("SELECT min(itemtype) FROM itemtypes WHERE notforloan = 0")
40
  or BAIL_OUT("No adequate itemtype");
41
42
# Start transaction
43
$dbh->{AutoCommit} = 0;
44
$dbh->{RaiseError} = 1;
45
46
#Set up the stage
47
# Sysprefs and cost matrix
48
$dbh->do("UPDATE systempreferences SET value = ? WHERE variable = 'StaticHoldsQueueWeight'", undef,
49
         join( ',', @other_branches, $borrower_branchcode, $least_cost_branch_code));
50
$dbh->do("UPDATE systempreferences SET value = '0' WHERE variable = 'RandomizeHoldsQueueWeight'");
51
52
$dbh->do("DELETE FROM transport_cost");
53
my $transport_cost_insert_sth = $dbh->prepare("insert into transport_cost (frombranch, tobranch, cost) values (?, ?, ?)");
54
# Favour $least_cost_branch_code
55
$transport_cost_insert_sth->execute($borrower_branchcode, $least_cost_branch_code, 0.2);
56
$transport_cost_insert_sth->execute($least_cost_branch_code, $borrower_branchcode, 0.2);
57
my @b = @other_branches;
58
while ( my $b1 = shift @b ) {
59
    foreach my $b2 ($borrower_branchcode, $least_cost_branch_code, @b) {
60
        $transport_cost_insert_sth->execute($b1, $b2, 0.5);
61
        $transport_cost_insert_sth->execute($b2, $b1, 0.5);
62
    }
63
}
64
65
66
# Loanable items - all possible combinations of homebranch and holdingbranch
67
$dbh->do("INSERT INTO biblio (frameworkcode, author, title, datecreated)
68
          VALUES             ('SER', 'Koha test', '$TITLE', '2011-02-01')");
69
my $biblionumber = $dbh->selectrow_array("SELECT biblionumber FROM biblio WHERE title = '$TITLE'")
70
  or BAIL_OUT("Cannot find newly created biblio record");
71
$dbh->do("INSERT INTO biblioitems (biblionumber, marcxml, itemtype)
72
          VALUES                  ($biblionumber, '', '$itemtype')");
73
my $biblioitemnumber = $dbh->selectrow_array("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = $biblionumber")
74
  or BAIL_OUT("Cannot find newly created biblioitems record");
75
76
my $items_insert_sth = $dbh->prepare("INSERT INTO items (biblionumber, biblioitemnumber, barcode, homebranch, holdingbranch, notforloan, damaged, itemlost, wthdrawn, onloan, itype)
77
                                      VALUES            ($biblionumber, $biblioitemnumber, ?, ?, ?, 0, 0, 0, 0, NULL, '$itemtype')"); # CURRENT_DATE - 3)");
78
my $first_barcode = int(rand(1000000000000)); # XXX
79
my $barcode = $first_barcode;
80
foreach ( $borrower_branchcode, $least_cost_branch_code, @other_branches ) {
81
    $items_insert_sth->execute($barcode++, $borrower_branchcode, $_);
82
    $items_insert_sth->execute($barcode++, $_, $_);
83
    $items_insert_sth->execute($barcode++, $_, $borrower_branchcode);
84
}
85
86
# Remove existing reserves, makes debugging easier
87
$dbh->do("DELETE FROM reserves");
88
my $constraint = undef;
89
my $bibitems = undef;
90
my $priority = 1;
91
# Make a reserve
92
AddReserve ( $borrower_branchcode, $borrowernumber, $biblionumber, $constraint, $bibitems,  $priority );
93
#                           $resdate, $expdate, $notes, $title, $checkitem, $found
94
$dbh->do("UPDATE reserves SET reservedate = reservedate - 1");
95
96
# Tests
97
my $use_cost_matrix_sth = $dbh->prepare("UPDATE systempreferences SET value = ? WHERE variable = 'UseTransportCostMatrix'");
98
my $test_sth = $dbh->prepare("SELECT * FROM hold_fill_targets
99
                              JOIN tmp_holdsqueue USING (borrowernumber, biblionumber, itemnumber)
100
                              JOIN items USING (itemnumber)
101
                              WHERE borrowernumber = $borrowernumber");
102
103
# We have a book available homed in borrower branch, no point fiddling with AutomaticItemReturn
104
test_queue ('take from homebranch',  0, $borrower_branchcode, $borrower_branchcode);
105
test_queue ('take from homebranch',  1, $borrower_branchcode, $borrower_branchcode);
106
107
$dbh->do("DELETE FROM tmp_holdsqueue");
108
$dbh->do("DELETE FROM hold_fill_targets");
109
$dbh->do("DELETE FROM issues WHERE itemnumber IN (SELECT itemnumber FROM items WHERE homebranch = '$borrower_branchcode' AND holdingbranch = '$borrower_branchcode')");
110
$dbh->do("DELETE FROM items WHERE homebranch = '$borrower_branchcode' AND holdingbranch = '$borrower_branchcode'");
111
# test_queue will flush
112
$dbh->do("UPDATE systempreferences SET value = 1 WHERE variable = 'AutomaticItemReturn'");
113
# Not sure how to make this test more difficult - holding branch does not matter
114
test_queue ('take from holdingbranch AutomaticItemReturn on', 0, $borrower_branchcode, undef);
115
test_queue ('take from holdingbranch AutomaticItemReturn on', 1, $borrower_branchcode, $least_cost_branch_code);
116
117
$dbh->do("DELETE FROM tmp_holdsqueue");
118
$dbh->do("DELETE FROM hold_fill_targets");
119
$dbh->do("DELETE FROM issues WHERE itemnumber IN (SELECT itemnumber FROM items WHERE homebranch = '$borrower_branchcode')");
120
$dbh->do("DELETE FROM items WHERE homebranch = '$borrower_branchcode'");
121
$dbh->do("UPDATE systempreferences SET value = 0 WHERE variable = 'AutomaticItemReturn'");
122
# We have a book available held in borrower branch
123
test_queue ('take from holdingbranch', 0, $borrower_branchcode, $borrower_branchcode);
124
test_queue ('take from holdingbranch', 1, $borrower_branchcode, $borrower_branchcode);
125
126
$dbh->do("DELETE FROM tmp_holdsqueue");
127
$dbh->do("DELETE FROM hold_fill_targets");
128
$dbh->do("DELETE FROM issues WHERE itemnumber IN (SELECT itemnumber FROM items WHERE holdingbranch = '$borrower_branchcode')");
129
$dbh->do("DELETE FROM items WHERE holdingbranch = '$borrower_branchcode'");
130
# No book available in borrower branch, pick according to the rules
131
# Frst branch from StaticHoldsQueueWeight
132
test_queue ('take from lowest cost branch', 0, $borrower_branchcode, $other_branches[0]);
133
test_queue ('take from lowest cost branch', 1, $borrower_branchcode, $least_cost_branch_code);
134
my $queue = C4::HoldsQueue::GetHoldsQueueItems($least_cost_branch_code) || [];
135
my $queue_item = $queue->[0];
136
ok( $queue_item
137
 && $queue_item->{pickbranch} eq $borrower_branchcode
138
 && $queue_item->{holdingbranch} eq $least_cost_branch_code, "GetHoldsQueueItems" )
139
  or diag( "Expected item for pick $borrower_branchcode, hold $least_cost_branch_code, got ".Dumper($queue_item) );
140
141
# XXX All this tests are for borrower branch pick-up.
142
# Maybe needs expanding to homebranch or holdingbranch pick-up.
143
144
# Cleanup
145
$dbh->rollback;
146
147
exit;
148
149
sub test_queue {
150
    my ($test_name, $use_cost_matrix, $pick_branch, $hold_branch) = @_;
151
152
    $test_name = "$test_name (".($use_cost_matrix ? "" : "don't ")."use cost matrix)";
153
154
    $use_cost_matrix_sth->execute($use_cost_matrix);
155
    C4::Context->clear_syspref_cache();
156
    C4::HoldsQueue::CreateQueue();
157
158
    my $results = $dbh->selectall_arrayref($test_sth, { Slice => {} }); # should be only one
159
    my $r = $results->[0];
160
161
    my $ok = is( $r->{pickbranch}, $pick_branch, "$test_name pick up branch");
162
    $ok &&=  is( $r->{holdingbranch}, $hold_branch, "$test_name holding branch")
163
      if $hold_branch;
164
165
    diag( "Wrong pick-up/hold for first target (pick_branch, hold_branch, reserves, hold_fill_targets, tmp_holdsqueue): "
166
        . Dumper ($pick_branch, $hold_branch, map dump_records($_), qw(reserves hold_fill_targets tmp_holdsqueue)) )
167
      unless $ok;
168
}
169
170
sub dump_records {
171
    my ($tablename) = @_;
172
    return $dbh->selectall_arrayref("SELECT * from $tablename where borrowernumber = ?", { Slice => {} }, $borrowernumber);
173
}
174
175

Return to bug 5911