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

(-)a/C4/Reserves.pm (+23 lines)
Lines 458-463 sub CanItemBeReserved { Link Here
458
        return 'tooManyReserves';
458
        return 'tooManyReserves';
459
    }
459
    }
460
460
461
    # Now we need to check hold limits by patron category
462
    my $schema = Koha::Database->new()->schema();
463
    my $rule = $schema->resultset('BranchBorrowerCircRule')->find(
464
        {
465
            branchcode   => $borrower->{branchcode},
466
            categorycode => $borrower->{categorycode},
467
        }
468
    );
469
    $rule ||= $schema->resultset('DefaultBorrowerCircRule')->find(
470
        {
471
            categorycode => $borrower->{categorycode}
472
        }
473
    );
474
    if ( $rule && defined $rule->max_holds ) {
475
        my $total_holds_count = Koha::Holds->search(
476
            {
477
                borrowernumber => $borrower->{borrowernumber}
478
            }
479
        )->count();
480
481
        return 'tooManyReserves' if $total_holds_count >= $rule->max_holds;
482
    }
483
461
    my $circ_control_branch =
484
    my $circ_control_branch =
462
      C4::Circulation::_GetCircControlBranch( $item->unblessed(), $borrower );
485
      C4::Circulation::_GetCircControlBranch( $item->unblessed(), $borrower );
463
    my $branchitemrule =
486
    my $branchitemrule =
(-)a/admin/smart-rules.pl (-14 / +21 lines)
Lines 255-264 elsif ($op eq "add-branch-cat") { Link Here
255
    my $categorycode  = $input->param('categorycode');
255
    my $categorycode  = $input->param('categorycode');
256
    my $maxissueqty   = $input->param('maxissueqty');
256
    my $maxissueqty   = $input->param('maxissueqty');
257
    my $maxonsiteissueqty = $input->param('maxonsiteissueqty');
257
    my $maxonsiteissueqty = $input->param('maxonsiteissueqty');
258
    my $max_holds = $input->param('max_holds');
258
    $maxissueqty =~ s/\s//g;
259
    $maxissueqty =~ s/\s//g;
259
    $maxissueqty = undef if $maxissueqty !~ /^\d+/;
260
    $maxissueqty = undef if $maxissueqty !~ /^\d+/;
260
    $maxonsiteissueqty =~ s/\s//g;
261
    $maxonsiteissueqty =~ s/\s//g;
261
    $maxonsiteissueqty = undef if $maxonsiteissueqty !~ /^\d+/;
262
    $maxonsiteissueqty = undef if $maxonsiteissueqty !~ /^\d+/;
263
    $max_holds =~ s/\s//g;
264
    $max_holds = undef if $max_holds !~ /^\d+/;
262
265
263
    if ($branch eq "*") {
266
    if ($branch eq "*") {
264
        if ($categorycode eq "*") {
267
        if ($categorycode eq "*") {
Lines 266-286 elsif ($op eq "add-branch-cat") { Link Here
266
                                            FROM default_circ_rules");
269
                                            FROM default_circ_rules");
267
            my $sth_insert = $dbh->prepare(q|
270
            my $sth_insert = $dbh->prepare(q|
268
                INSERT INTO default_circ_rules
271
                INSERT INTO default_circ_rules
269
                    (maxissueqty, maxonsiteissueqty)
272
                    (maxissueqty, maxonsiteissueqty, max_holds)
270
                    VALUES (?, ?)
273
                    VALUES (?, ?, ?)
271
            |);
274
            |);
272
            my $sth_update = $dbh->prepare(q|
275
            my $sth_update = $dbh->prepare(q|
273
                UPDATE default_circ_rules
276
                UPDATE default_circ_rules
274
                SET maxissueqty = ?,
277
                SET maxissueqty = ?,
275
                    maxonsiteissueqty = ?
278
                    maxonsiteissueqty = ?,
279
                    max_holds = ?
276
            |);
280
            |);
277
281
278
            $sth_search->execute();
282
            $sth_search->execute();
279
            my $res = $sth_search->fetchrow_hashref();
283
            my $res = $sth_search->fetchrow_hashref();
280
            if ($res->{total}) {
284
            if ($res->{total}) {
281
                $sth_update->execute($maxissueqty, $maxonsiteissueqty);
285
                $sth_update->execute( $maxissueqty, $maxonsiteissueqty, $max_holds );
282
            } else {
286
            } else {
283
                $sth_insert->execute($maxissueqty, $maxonsiteissueqty);
287
                $sth_insert->execute( $maxissueqty, $maxonsiteissueqty, $max_holds );
284
            }
288
            }
285
        } else {
289
        } else {
286
            my $sth_search = $dbh->prepare("SELECT count(*) AS total
290
            my $sth_search = $dbh->prepare("SELECT count(*) AS total
Lines 288-308 elsif ($op eq "add-branch-cat") { Link Here
288
                                            WHERE categorycode = ?");
292
                                            WHERE categorycode = ?");
289
            my $sth_insert = $dbh->prepare(q|
293
            my $sth_insert = $dbh->prepare(q|
290
                INSERT INTO default_borrower_circ_rules
294
                INSERT INTO default_borrower_circ_rules
291
                    (categorycode, maxissueqty, maxonsiteissueqty)
295
                    (categorycode, maxissueqty, maxonsiteissueqty, max_holds)
292
                    VALUES (?, ?, ?)
296
                    VALUES (?, ?, ?, ?)
293
            |);
297
            |);
294
            my $sth_update = $dbh->prepare(q|
298
            my $sth_update = $dbh->prepare(q|
295
                UPDATE default_borrower_circ_rules
299
                UPDATE default_borrower_circ_rules
296
                SET maxissueqty = ?,
300
                SET maxissueqty = ?,
297
                    maxonsiteissueqty = ?
301
                    maxonsiteissueqty = ?,
302
                    max_holds = ?
298
                WHERE categorycode = ?
303
                WHERE categorycode = ?
299
            |);
304
            |);
300
            $sth_search->execute($branch);
305
            $sth_search->execute($branch);
301
            my $res = $sth_search->fetchrow_hashref();
306
            my $res = $sth_search->fetchrow_hashref();
302
            if ($res->{total}) {
307
            if ($res->{total}) {
303
                $sth_update->execute($maxissueqty, $maxonsiteissueqty, $categorycode);
308
                $sth_update->execute( $maxissueqty, $maxonsiteissueqty, $categorycode, $max_holds );
304
            } else {
309
            } else {
305
                $sth_insert->execute($categorycode, $maxissueqty, $maxonsiteissueqty);
310
                $sth_insert->execute( $categorycode, $maxissueqty, $maxonsiteissueqty, $max_holds );
306
            }
311
            }
307
        }
312
        }
308
    } elsif ($categorycode eq "*") {
313
    } elsif ($categorycode eq "*") {
Lines 334-346 elsif ($op eq "add-branch-cat") { Link Here
334
                                        AND   categorycode = ?");
339
                                        AND   categorycode = ?");
335
        my $sth_insert = $dbh->prepare(q|
340
        my $sth_insert = $dbh->prepare(q|
336
            INSERT INTO branch_borrower_circ_rules
341
            INSERT INTO branch_borrower_circ_rules
337
            (branchcode, categorycode, maxissueqty, maxonsiteissueqty)
342
            (branchcode, categorycode, maxissueqty, maxonsiteissueqty, max_holds)
338
            VALUES (?, ?, ?, ?)
343
            VALUES (?, ?, ?, ?, ?)
339
        |);
344
        |);
340
        my $sth_update = $dbh->prepare(q|
345
        my $sth_update = $dbh->prepare(q|
341
            UPDATE branch_borrower_circ_rules
346
            UPDATE branch_borrower_circ_rules
342
            SET maxissueqty = ?,
347
            SET maxissueqty = ?,
343
                maxonsiteissueqty = ?
348
                maxonsiteissueqty = ?
349
                max_holds = ?
344
            WHERE branchcode = ?
350
            WHERE branchcode = ?
345
            AND categorycode = ?
351
            AND categorycode = ?
346
        |);
352
        |);
Lines 348-356 elsif ($op eq "add-branch-cat") { Link Here
348
        $sth_search->execute($branch, $categorycode);
354
        $sth_search->execute($branch, $categorycode);
349
        my $res = $sth_search->fetchrow_hashref();
355
        my $res = $sth_search->fetchrow_hashref();
350
        if ($res->{total}) {
356
        if ($res->{total}) {
351
            $sth_update->execute($maxissueqty, $maxonsiteissueqty, $branch, $categorycode);
357
            $sth_update->execute($maxissueqty, $maxonsiteissueqty, $max_holds, $branch, $categorycode);
352
        } else {
358
        } else {
353
            $sth_insert->execute($branch, $categorycode, $maxissueqty, $maxonsiteissueqty);
359
            $sth_insert->execute($branch, $categorycode, $maxissueqty, $maxonsiteissueqty, $max_holds);
354
        }
360
        }
355
    }
361
    }
356
}
362
}
Lines 546-551 my @sorted_branch_cat_rules = sort { $a->{'humancategorycode'} cmp $b->{'humanca Link Here
546
foreach my $entry (@sorted_branch_cat_rules, @sorted_row_loop) {
552
foreach my $entry (@sorted_branch_cat_rules, @sorted_row_loop) {
547
    $entry->{unlimited_maxissueqty} = 1 unless defined($entry->{maxissueqty});
553
    $entry->{unlimited_maxissueqty} = 1 unless defined($entry->{maxissueqty});
548
    $entry->{unlimited_maxonsiteissueqty} = 1 unless defined($entry->{maxonsiteissueqty});
554
    $entry->{unlimited_maxonsiteissueqty} = 1 unless defined($entry->{maxonsiteissueqty});
555
    $entry->{unlimited_max_holds} = 1 unless defined($entry->{max_holds});
549
}
556
}
550
557
551
@sorted_row_loop = sort by_category_and_itemtype @row_loop;
558
@sorted_row_loop = sort by_category_and_itemtype @row_loop;
(-)a/installer/data/mysql/atomicupdate/max_holds.perl (+18 lines)
Line 0 Link Here
1
$DBversion = 'XXX';  # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
4
    if( !column_exists( 'branch_borrower_circ_rules', 'max_holds' ) ) {
5
        $dbh->do(q{
6
            ALTER TABLE branch_borrower_circ_rules ADD COLUMN max_holds INT(4) NULL DEFAULT NULL AFTER maxonsiteissueqty
7
        });
8
    }
9
10
    if( !column_exists( 'default_borrower_circ_rules', 'max_holds' ) ) {
11
        $dbh->do(q{
12
            ALTER TABLE default_borrower_circ_rules ADD COLUMN max_holds INT(4) NULL DEFAULT NULL AFTER maxonsiteissueqty
13
        });
14
    }
15
16
    SetVersion( $DBversion );
17
    print "Upgrade to $DBversion done (Bug 15524 - Set limit on maximum possible holds per patron by category)\n";
18
}
(-)a/installer/data/mysql/kohastructure.sql (+2 lines)
Lines 377-382 CREATE TABLE `branch_borrower_circ_rules` ( -- includes default circulation rule Link Here
377
  `categorycode` VARCHAR(10) NOT NULL, -- the patron category this rule applies to (categories.categorycode)
377
  `categorycode` VARCHAR(10) NOT NULL, -- the patron category this rule applies to (categories.categorycode)
378
  `maxissueqty` int(4) default NULL, -- the maximum number of checkouts this patron category can have at this branch
378
  `maxissueqty` int(4) default NULL, -- the maximum number of checkouts this patron category can have at this branch
379
  `maxonsiteissueqty` int(4) default NULL, -- the maximum number of on-site checkouts this patron category can have at this branch
379
  `maxonsiteissueqty` int(4) default NULL, -- the maximum number of on-site checkouts this patron category can have at this branch
380
  max_holds INT(4) NULL DEFAULT NULL, -- the maximum number of holds a patron may have at a time
380
  PRIMARY KEY (`categorycode`, `branchcode`),
381
  PRIMARY KEY (`categorycode`, `branchcode`),
381
  CONSTRAINT `branch_borrower_circ_rules_ibfk_1` FOREIGN KEY (`categorycode`) REFERENCES `categories` (`categorycode`)
382
  CONSTRAINT `branch_borrower_circ_rules_ibfk_1` FOREIGN KEY (`categorycode`) REFERENCES `categories` (`categorycode`)
382
    ON DELETE CASCADE ON UPDATE CASCADE,
383
    ON DELETE CASCADE ON UPDATE CASCADE,
Lines 393-398 CREATE TABLE `default_borrower_circ_rules` ( -- default checkout rules found und Link Here
393
  `categorycode` VARCHAR(10) NOT NULL, -- patron category this rul
394
  `categorycode` VARCHAR(10) NOT NULL, -- patron category this rul
394
  `maxissueqty` int(4) default NULL,
395
  `maxissueqty` int(4) default NULL,
395
  `maxonsiteissueqty` int(4) default NULL,
396
  `maxonsiteissueqty` int(4) default NULL,
397
  max_holds INT(4) NULL DEFAULT NULL, -- the maximum number of holds a patron may have at a time
396
  PRIMARY KEY (`categorycode`),
398
  PRIMARY KEY (`categorycode`),
397
  CONSTRAINT `borrower_borrower_circ_rules_ibfk_1` FOREIGN KEY (`categorycode`) REFERENCES `categories` (`categorycode`)
399
  CONSTRAINT `borrower_borrower_circ_rules_ibfk_1` FOREIGN KEY (`categorycode`) REFERENCES `categories` (`categorycode`)
398
    ON DELETE CASCADE ON UPDATE CASCADE
400
    ON DELETE CASCADE ON UPDATE CASCADE
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt (+8 lines)
Lines 555-560 $(document).ready(function() { Link Here
555
                    <th>Patron category</th>
555
                    <th>Patron category</th>
556
                    <th>Total current checkouts allowed</th>
556
                    <th>Total current checkouts allowed</th>
557
                    <th>Total current on-site checkouts allowed</th>
557
                    <th>Total current on-site checkouts allowed</th>
558
                    <th>Maximum total holds allowed (count)</th>
558
                    <th>&nbsp;</th>
559
                    <th>&nbsp;</th>
559
                </tr>
560
                </tr>
560
                [% FOREACH branch_cat_rule_loo IN branch_cat_rule_loop %]
561
                [% FOREACH branch_cat_rule_loo IN branch_cat_rule_loop %]
Lines 581-586 $(document).ready(function() { Link Here
581
                                [% branch_cat_rule_loo.maxonsiteissueqty %]
582
                                [% branch_cat_rule_loo.maxonsiteissueqty %]
582
                            [% END %]
583
                            [% END %]
583
                        </td>
584
                        </td>
585
                        <td>[% IF ( branch_cat_rule_loo.unlimited_max_holds ) %]
586
                                Unlimited
587
                            [% ELSE %]
588
                                [% branch_cat_rule_loo.max_holds %]
589
                            [% END %]
590
                        </td>
584
591
585
                        <td class="actions">
592
                        <td class="actions">
586
                            <a class="btn btn-default btn-xs delete" href="/cgi-bin/koha/admin/smart-rules.pl?op=delete-branch-cat&amp;categorycode=[% branch_cat_rule_loo.categorycode %]&amp;branch=[% current_branch %]"><i class="fa fa-trash"></i> Delete</a>
593
                            <a class="btn btn-default btn-xs delete" href="/cgi-bin/koha/admin/smart-rules.pl?op=delete-branch-cat&amp;categorycode=[% branch_cat_rule_loo.categorycode %]&amp;branch=[% current_branch %]"><i class="fa fa-trash"></i> Delete</a>
Lines 597-602 $(document).ready(function() { Link Here
597
                    </td>
604
                    </td>
598
                    <td><input name="maxissueqty" size="3" /></td>
605
                    <td><input name="maxissueqty" size="3" /></td>
599
                    <td><input name="maxonsiteissueqty" size="3" /></td>
606
                    <td><input name="maxonsiteissueqty" size="3" /></td>
607
                    <td><input name="max_holds" size="3" /></td>
600
                    <td class="actions"><button type="submit" class="btn btn-default btn-xs"><i class="fa fa-plus"></i> Add</td>
608
                    <td class="actions"><button type="submit" class="btn btn-default btn-xs"><i class="fa fa-plus"></i> Add</td>
601
                </tr>
609
                </tr>
602
            </table>
610
            </table>
(-)a/t/db_dependent/Holds.t (-2 / +68 lines)
Lines 7-13 use t::lib::TestBuilder; Link Here
7
7
8
use C4::Context;
8
use C4::Context;
9
9
10
use Test::More tests => 58;
10
use Test::More tests => 59;
11
use MARC::Record;
11
use MARC::Record;
12
use C4::Biblio;
12
use C4::Biblio;
13
use C4::Items;
13
use C4::Items;
Lines 459-464 my $res_id = AddReserve( $branch_1, $borrowernumbers[0], $bibnum, '', 1, ); Link Here
459
is( CanItemBeReserved( $borrowernumbers[0], $itemnumber ),
459
is( CanItemBeReserved( $borrowernumbers[0], $itemnumber ),
460
    'tooManyReserves', 'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed' );
460
    'tooManyReserves', 'Patron cannot reserve item with hold limit of 1, 1 bib level hold placed' );
461
461
462
subtest 'Test max_holds per library/patron category' => sub {
463
    plan tests => 6;
464
465
    $dbh->do('DELETE FROM reserves');
466
    $dbh->do('DELETE FROM issuingrules');
467
468
    ( $bibnum, $title, $bibitemnum ) = create_helper_biblio('TEST');
469
    ( $item_bibnum, $item_bibitemnum, $itemnumber ) =
470
      AddItem( { homebranch => $branch_1, holdingbranch => $branch_1 },
471
        $bibnum );
472
    $dbh->do(
473
        q{
474
            INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed, holds_per_record)
475
            VALUES (?, ?, ?, ?, ?)
476
        },
477
        {},
478
        '*', '*', 'TEST', 99, 99
479
    );
480
    AddReserve( $branch_1, $borrowernumbers[0], $bibnum, '', 1, );
481
    AddReserve( $branch_1, $borrowernumbers[0], $bibnum, '', 1, );
482
    AddReserve( $branch_1, $borrowernumbers[0], $bibnum, '', 1, );
483
484
    my $count =
485
      Koha::Holds->search( { borrowernumber => $borrowernumbers[0] } )->count();
486
    is( $count, 3, 'Patron now has 3 holds' );
487
488
    my $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
489
    is( $ret, 'OK', 'Patron can place hold with no borrower circ rules' );
490
491
    my $rule_all = $schema->resultset('DefaultBorrowerCircRule')->new(
492
        {
493
            categorycode => $category->{categorycode},
494
            max_holds    => 3,
495
        }
496
    )->insert();
497
498
    my $rule_branch = $schema->resultset('BranchBorrowerCircRule')->new(
499
        {
500
            branchcode   => $branch_1,
501
            categorycode => $category->{categorycode},
502
            max_holds    => 5,
503
        }
504
    )->insert();
505
506
    $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
507
    is( $ret, 'OK', 'Patron can place hold with branch/category rule of 5, category rule of 3' );
508
509
    $rule_branch->delete();
510
511
    $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
512
    is( $ret, 'tooManyReserves', 'Patron cannot place hold with only a category rule of 3' );
513
514
    $rule_all->delete();
515
    $rule_branch->max_holds(3);
516
    $rule_branch->insert();
517
518
    $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
519
    is( $ret, 'tooManyReserves', 'Patron cannot place hold with only a branch/category rule of 3' );
520
521
    $rule_branch->max_holds(5);
522
    $rule_branch->update();
523
    $rule_branch->max_holds(5);
524
    $rule_branch->insert();
525
526
    $ret = CanItemBeReserved( $borrowernumbers[0], $itemnumber );
527
    is( $ret, 'OK', 'Patron can place hold with branch/category rule of 5, category rule of 5' );
528
};
462
529
463
# Helper method to set up a Biblio.
530
# Helper method to set up a Biblio.
464
sub create_helper_biblio {
531
sub create_helper_biblio {
465
- 

Return to bug 15524