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

(-)a/C4/Reserves.pm (-24 / +35 lines)
Lines 417-452 sub GetReservesFromItemnumber { Link Here
417
417
418
=head2 GetReservesFromBorrowernumber
418
=head2 GetReservesFromBorrowernumber
419
419
420
    $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
420
    $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber[,$status][,$biblionumber][,$itemnumber]);
421
422
TODO :: Descritpion
423
421
424
=cut
422
=cut
425
423
426
sub GetReservesFromBorrowernumber {
424
sub GetReservesFromBorrowernumber {
427
    my ( $borrowernumber, $status ) = @_;
425
    my ( $borrowernumber, $found, $biblionumber, $itemnumber ) = @_;
428
    my $dbh   = C4::Context->dbh;
426
429
    my $sth;
427
    my @params;
430
    if ($status) {
428
431
        $sth = $dbh->prepare("
429
    my $sql = "SELECT * FROM reserves WHERE borrowernumber = ?";
432
            SELECT *
430
    push( @params, $borrowernumber );
433
            FROM   reserves
431
434
            WHERE  borrowernumber=?
432
    if ( $biblionumber ) {
435
                AND found =?
433
        $sql .= " AND biblionumber = ? ";
436
            ORDER BY reservedate
434
        push( @params, $biblionumber );
437
        ");
435
    }
438
        $sth->execute($borrowernumber,$status);
436
439
    } else {
437
    if ( $itemnumber ) {
440
        $sth = $dbh->prepare("
438
        $sql .= " AND itemnumber = ? ";
441
            SELECT *
439
        push( @params, $itemnumber );
442
            FROM   reserves
443
            WHERE  borrowernumber=?
444
            ORDER BY reservedate
445
        ");
446
        $sth->execute($borrowernumber);
447
    }
440
    }
441
442
    if ( $found ) {
443
        $sql .= " AND found = ? ";
444
        push( @params, $found );
445
    }
446
447
    $sql .= " ORDER BY reservedate ";
448
449
    my $dbh = C4::Context->dbh;
450
    my $sth = $dbh->prepare( $sql );
451
452
    $sth->execute( @params );
453
448
    my $data = $sth->fetchall_arrayref({});
454
    my $data = $sth->fetchall_arrayref({});
449
    return @$data;
455
456
    if ( scalar @$data ) {
457
        return @$data;
458
    } else {
459
        return ();
460
    }
450
}
461
}
451
#-------------------------------------------------------------------------------------
462
#-------------------------------------------------------------------------------------
452
=head2 CanBookBeReserved
463
=head2 CanBookBeReserved
(-)a/installer/data/mysql/sysprefs.sql (+1 lines)
Lines 198-203 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
198
('MarcFieldsToOrder','',NULL,'Set the mapping values for a new order line created from a MARC record in a staged file. In a YAML format.','textarea'),
198
('MarcFieldsToOrder','',NULL,'Set the mapping values for a new order line created from a MARC record in a staged file. In a YAML format.','textarea'),
199
('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'),
199
('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'),
200
('MaxFine',NULL,'','Maximum fine a patron can have for all late returns at one moment. Single item caps are specified in the circulation rules matrix.','Integer'),
200
('MaxFine',NULL,'','Maximum fine a patron can have for all late returns at one moment. Single item caps are specified in the circulation rules matrix.','Integer'),
201
('MaxHoldsPerRecord', '1', 'The maximum number of holds allowed per bibliographic record per borrower', 'Integer'),
201
('MaxItemsForBatch','1000',NULL,'Max number of items record to process in a batch (modification or deletion)','Integer'),
202
('MaxItemsForBatch','1000',NULL,'Max number of items record to process in a batch (modification or deletion)','Integer'),
202
('maxItemsInSearchResults','20',NULL,'Specify the maximum number of items to display for each result on a page of results','free'),
203
('maxItemsInSearchResults','20',NULL,'Specify the maximum number of items to display for each result on a page of results','free'),
203
('maxoutstanding','5','','maximum amount withstanding to be able make holds','Integer'),
204
('maxoutstanding','5','','maximum amount withstanding to be able make holds','Integer'),
(-)a/installer/data/mysql/updatedatabase.pl (+9 lines)
Lines 9669-9674 if ( CheckVersion($DBversion) ) { Link Here
9669
    SetVersion ($DBversion);
9669
    SetVersion ($DBversion);
9670
}
9670
}
9671
9671
9672
$DBversion = "3.19.00.XXX";
9673
if ( CheckVersion($DBversion) ) {
9674
    $dbh->do(q|
9675
        INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES ('MaxHoldsPerRecord', '1', 'The maximum number of holds allowed per bibliographic record per borrower', NULL, 'Integer')
9676
    |);
9677
    print "Upgrade to $DBversion done (Bug 7710 - multiple holds per title)\n";
9678
    SetVersion ($DBversion);
9679
}
9680
9672
=head1 FUNCTIONS
9681
=head1 FUNCTIONS
9673
9682
9674
=head2 TableExists($table)
9683
=head2 TableExists($table)
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-1 / +6 lines)
Lines 515-521 Circulation: Link Here
515
            - Patrons can only have
515
            - Patrons can only have
516
            - pref: maxreserves
516
            - pref: maxreserves
517
              class: integer
517
              class: integer
518
            - holds at once.
518
            - total holds at a time.
519
        -
520
            - Patrons can only place
521
            - pref: MaxHoldsPerRecord
522
              class: integer
523
            - holds on a single record at a time.
519
        -
524
        -
520
            - pref: emailLibrarianWhenHoldIsPlaced
525
            - pref: emailLibrarianWhenHoldIsPlaced
521
              choices:
526
              choices:
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-2 / +2 lines)
Lines 62-68 function check() { Link Here
62
    }
62
    }
63
63
64
    if (alreadyreserved > "0"){
64
    if (alreadyreserved > "0"){
65
		msg += (_("- This patron had already placed a hold on this item") + "\n" + _("Please cancel the previous hold first") + "\n");
65
         msg += (_("This patron has already placed the maximum number of holds for this record.") + "\n\n" + _("Please cancel a previous hold first.") + "\n");
66
    }
66
    }
67
67
68
	if (msg == "") return(true);
68
	if (msg == "") return(true);
Lines 260-266 function checkMultiHold() { Link Here
260
          <li><strong>Age restricted</strong></li>
260
          <li><strong>Age restricted</strong></li>
261
        [% END %]
261
        [% END %]
262
        [% IF ( alreadyreserved ) %]
262
        [% IF ( alreadyreserved ) %]
263
          <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>already has a hold</strong> on this item </li>
263
          <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>has already placed the maximum allowed number of holds</strong> on this record<    /li>
264
        [% END %]
264
        [% END %]
265
        [% IF ( none_available ) %]
265
        [% IF ( none_available ) %]
266
          <li> <strong>No items are available</strong> to be placed on hold</li>
266
          <li> <strong>No items are available</strong> to be placed on hold</li>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-reserve.tt (+2 lines)
Lines 154-159 Link Here
154
                                                [% ELSE %]
154
                                                [% ELSE %]
155
                                                    [% IF ( bibitemloo.already_patron_possession ) %]
155
                                                    [% IF ( bibitemloo.already_patron_possession ) %]
156
                                                        <div class="alert">This title cannot be requested because it's already in your possession.</div>
156
                                                        <div class="alert">This title cannot be requested because it's already in your possession.</div>
157
                                                    [% ELSIF bibitemloo.max_holds %]
158
                                                        <div class="bibmessage">This title cannot be requested. You have already placed the maximum number of requests allowed for this title.</div>
157
                                                    [% ELSE %]
159
                                                    [% ELSE %]
158
                                                        <div class="alert">This title cannot be requested.</div>
160
                                                        <div class="alert">This title cannot be requested.</div>
159
                                                    [% END %]
161
                                                    [% END %]
(-)a/opac/opac-reserve.pl (-6 / +16 lines)
Lines 40-45 use Date::Calc qw/Today Date_to_Days/; Link Here
40
# use Data::Dumper;
40
# use Data::Dumper;
41
41
42
my $maxreserves = C4::Context->preference("maxreserves");
42
my $maxreserves = C4::Context->preference("maxreserves");
43
my $max_holds_per_record = C4::Context->preference('MaxHoldsPerRecord');
43
44
44
my $query = new CGI;
45
my $query = new CGI;
45
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
46
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
Lines 348-359 unless ( $noreserves ) { Link Here
348
foreach my $res (@reserves) {
349
foreach my $res (@reserves) {
349
    foreach my $biblionumber (@biblionumbers) {
350
    foreach my $biblionumber (@biblionumbers) {
350
        if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) {
351
        if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) {
351
#            $template->param( message => 1 );
352
            $biblioDataHash{$biblionumber}->{holds_count}++;
352
#            $noreserves = 1;
353
#            $template->param( already_reserved => 1 );
354
            $biblioDataHash{$biblionumber}->{already_reserved} = 1;
355
        }
353
        }
356
    }
354
    }
355
357
}
356
}
358
357
359
unless ($noreserves) {
358
unless ($noreserves) {
Lines 397-402 foreach my $biblioNum (@biblionumbers) { Link Here
397
    $biblioLoopIter{reservecount} = $biblioData->{reservecount};
396
    $biblioLoopIter{reservecount} = $biblioData->{reservecount};
398
    $biblioLoopIter{already_reserved} = $biblioData->{already_reserved};
397
    $biblioLoopIter{already_reserved} = $biblioData->{already_reserved};
399
    $biblioLoopIter{mandatorynotes}=0; #FIXME: For future use
398
    $biblioLoopIter{mandatorynotes}=0; #FIXME: For future use
399
    $biblioLoopIter{holds_count} = $biblioData->{holds_count};
400
400
401
    if (!$itemLevelTypes && $biblioData->{itemtype}) {
401
    if (!$itemLevelTypes && $biblioData->{itemtype}) {
402
        $biblioLoopIter{description} = $itemTypes->{$biblioData->{itemtype}}{description};
402
        $biblioLoopIter{description} = $itemTypes->{$biblioData->{itemtype}}{description};
Lines 525-538 foreach my $biblioNum (@biblionumbers) { Link Here
525
            $policy_holdallowed = 0;
525
            $policy_holdallowed = 0;
526
        }
526
        }
527
527
528
        if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum) eq 'OK' and ($itemLoopIter->{already_reserved} ne 1)) {
528
        if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum) and ($itemLoopIter->{holds_count} < $max_holds_per_record )) {
529
            $itemLoopIter->{available} = 1;
529
            $itemLoopIter->{available} = 1;
530
            $numCopiesAvailable++;
530
            $numCopiesAvailable++;
531
        }
531
        }
532
532
533
        $itemLoopIter->{imageurl} = getitemtypeimagelocation( 'opac', $itemTypes->{ $itemInfo->{itype} }{imageurl} );
533
        $itemLoopIter->{imageurl} = getitemtypeimagelocation( 'opac', $itemTypes->{ $itemInfo->{itype} }{imageurl} );
534
534
535
    # Show serial enumeration when needed
535
        # Don't allow multiple item level holds on the same item if borrowers can place multiple holds on one bib
536
        $itemLoopIter->{available} = 0 if ( GetReservesFromBorrowernumber( $borrowernumber, undef, undef, $itemNum ) );
537
538
        # Show serial enumeration when needed
536
        if ($itemLoopIter->{enumchron}) {
539
        if ($itemLoopIter->{enumchron}) {
537
            $itemdata_enumchron = 1;
540
            $itemdata_enumchron = 1;
538
        }
541
        }
Lines 549-554 foreach my $biblioNum (@biblionumbers) { Link Here
549
    if ($biblioLoopIter{already_reserved}) {
552
    if ($biblioLoopIter{already_reserved}) {
550
        $biblioLoopIter{holdable} = undef;
553
        $biblioLoopIter{holdable} = undef;
551
    }
554
    }
555
556
    if ($biblioLoopIter{holds_count} >= $max_holds_per_record) {
557
        $biblioLoopIter{holdable} = undef;
558
        $anyholdable = undef;
559
        $biblioLoopIter{max_holds} = 1;
560
    }
561
552
    my $canReserve = CanBookBeReserved($borrowernumber,$biblioNum);
562
    my $canReserve = CanBookBeReserved($borrowernumber,$biblioNum);
553
    unless ($canReserve eq 'OK') {
563
    unless ($canReserve eq 'OK') {
554
        $biblioLoopIter{holdable} = undef;
564
        $biblioLoopIter{holdable} = undef;
(-)a/reserve/request.pl (-6 / +14 lines)
Lines 174-179 $template->param( messageborrower => $messageborrower ); Link Here
174
174
175
# FIXME launch another time GetMember perhaps until
175
# FIXME launch another time GetMember perhaps until
176
my $borrowerinfo = GetMember( borrowernumber => $borrowernumber_hold );
176
my $borrowerinfo = GetMember( borrowernumber => $borrowernumber_hold );
177
my $max_holds_per_record = C4::Context->preference('MaxHoldsPerRecord');
177
178
178
my @biblionumbers = ();
179
my @biblionumbers = ();
179
my $biblionumbers = $input->param('biblionumbers');
180
my $biblionumbers = $input->param('biblionumbers');
Lines 229-243 foreach my $biblionumber (@biblionumbers) { Link Here
229
        }
230
        }
230
    }
231
    }
231
232
232
    if ( $holds_count ) {
233
    if ( $holds_count > $max_holds_per_record ) {
233
            $alreadyreserved = 1;
234
        $alreadyreserved             = 1;
234
            $biblioloopiter{warn} = 1;
235
        $biblioloopiter{warn}        = 1;
235
            $biblioloopiter{alreadyres} = 1;
236
        $biblioloopiter{alreadyres}  = 1;
237
        $biblioloopiter{maxreserves} = $max_holds_per_record;
236
    }
238
    }
237
239
238
    $template->param(
240
    $template->param(
239
        alreadyreserved => $alreadyreserved,
241
        alreadyreserved   => $alreadyreserved,
240
        alreadypossession => $alreadypossession,
242
        alreadypossession => $alreadypossession,
243
        maxreserves       => $maxreserves,
241
    );
244
    );
242
245
243
    # FIXME think @optionloop, is maybe obsolete, or  must be switchable by a systeme preference fixed rank or not
246
    # FIXME think @optionloop, is maybe obsolete, or  must be switchable by a systeme preference fixed rank or not
Lines 446-451 foreach my $biblionumber (@biblionumbers) { Link Here
446
                $itemdata_enumchron = 1;
449
                $itemdata_enumchron = 1;
447
            }
450
            }
448
451
452
            # Don't allow multiple item level holds on the same item if borrowers can place multiple holds on one bib
453
            if ( GetReservesFromBorrowernumber( $borrowerinfo->{borrowernumber}, undef, undef, $itemnumber) ) {
454
                $item->{available} = 0;
455
                $item->{override} = 0;
456
            }
457
449
            push @{ $biblioitem->{itemloop} }, $item;
458
            push @{ $biblioitem->{itemloop} }, $item;
450
        }
459
        }
451
460
452
- 

Return to bug 7710