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

(-)a/C4/Reserves.pm (-24 / +35 lines)
Lines 397-432 sub GetReservesFromItemnumber { Link Here
397
397
398
=head2 GetReservesFromBorrowernumber
398
=head2 GetReservesFromBorrowernumber
399
399
400
    $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
400
    $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber[,$status][,$biblionumber][,$itemnumber]);
401
402
TODO :: Descritpion
403
401
404
=cut
402
=cut
405
403
406
sub GetReservesFromBorrowernumber {
404
sub GetReservesFromBorrowernumber {
407
    my ( $borrowernumber, $status ) = @_;
405
    my ( $borrowernumber, $found, $biblionumber, $itemnumber ) = @_;
408
    my $dbh   = C4::Context->dbh;
406
409
    my $sth;
407
    my @params;
410
    if ($status) {
408
411
        $sth = $dbh->prepare("
409
    my $sql = "SELECT * FROM reserves WHERE borrowernumber = ?";
412
            SELECT *
410
    push( @params, $borrowernumber );
413
            FROM   reserves
411
414
            WHERE  borrowernumber=?
412
    if ( $biblionumber ) {
415
                AND found =?
413
        $sql .= " AND biblionumber = ? ";
416
            ORDER BY reservedate
414
        push( @params, $biblionumber );
417
        ");
415
    }
418
        $sth->execute($borrowernumber,$status);
416
419
    } else {
417
    if ( $itemnumber ) {
420
        $sth = $dbh->prepare("
418
        $sql .= " AND itemnumber = ? ";
421
            SELECT *
419
        push( @params, $itemnumber );
422
            FROM   reserves
423
            WHERE  borrowernumber=?
424
            ORDER BY reservedate
425
        ");
426
        $sth->execute($borrowernumber);
427
    }
420
    }
421
422
    if ( $found ) {
423
        $sql .= " AND found = ? ";
424
        push( @params, $found );
425
    }
426
427
    $sql .= " ORDER BY reservedate ";
428
429
    my $dbh = C4::Context->dbh;
430
    my $sth = $dbh->prepare( $sql );
431
432
    $sth->execute( @params );
433
428
    my $data = $sth->fetchall_arrayref({});
434
    my $data = $sth->fetchall_arrayref({});
429
    return @$data;
435
436
    if ( scalar @$data ) {
437
        return @$data;
438
    } else {
439
        return ();
440
    }
430
}
441
}
431
#-------------------------------------------------------------------------------------
442
#-------------------------------------------------------------------------------------
432
=head2 CanBookBeReserved
443
=head2 CanBookBeReserved
(-)a/installer/data/mysql/sysprefs.sql (-1 / +1 lines)
Lines 179-184 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
179
('MARCAuthorityControlField008','|| aca||aabn           | a|a     d',NULL,'Define the contents of MARC21 authority control field 008 position 06-39','Textarea'),
179
('MARCAuthorityControlField008','|| aca||aabn           | a|a     d',NULL,'Define the contents of MARC21 authority control field 008 position 06-39','Textarea'),
180
('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'),
180
('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'),
181
('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'),
181
('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'),
182
('MaxHoldsPerRecord', '1', 'The maximum number of holds allowed per bibliographic record per borrower', 'Integer'),
182
('MaxItemsForBatch','1000',NULL,'Max number of items record to process in a batch (modification or deletion)','Integer'),
183
('MaxItemsForBatch','1000',NULL,'Max number of items record to process in a batch (modification or deletion)','Integer'),
183
('maxItemsInSearchResults','20',NULL,'Specify the maximum number of items to display for each result on a page of results','free'),
184
('maxItemsInSearchResults','20',NULL,'Specify the maximum number of items to display for each result on a page of results','free'),
184
('maxoutstanding','5','','maximum amount withstanding to be able make holds','Integer'),
185
('maxoutstanding','5','','maximum amount withstanding to be able make holds','Integer'),
Lines 428-434 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
428
('XISBN','0','','Use with FRBRizeEditions. If ON, Koha will use the OCLC xISBN web service in the Editions tab on the detail pages. See: http://www.worldcat.org/affiliate/webservices/xisbn/app.jsp','YesNo'),
429
('XISBN','0','','Use with FRBRizeEditions. If ON, Koha will use the OCLC xISBN web service in the Editions tab on the detail pages. See: http://www.worldcat.org/affiliate/webservices/xisbn/app.jsp','YesNo'),
429
('XISBNDailyLimit','999','','The xISBN Web service is free for non-commercial use when usage does not exceed 1000 requests per day','Integer'),
430
('XISBNDailyLimit','999','','The xISBN Web service is free for non-commercial use when usage does not exceed 1000 requests per day','Integer'),
430
('XSLTDetailsDisplay','default','','Enable XSL stylesheet control over details page display on intranet','Free'),
431
('XSLTDetailsDisplay','default','','Enable XSL stylesheet control over details page display on intranet','Free'),
431
('XSLTResultsDisplay','default','','Enable XSL stylesheet control over results page display on intranet','Free'),
432
('yuipath','local','local|http://yui.yahooapis.com/2.5.1/build','Insert the path to YUI libraries, choose local if you use koha offline','Choice'),
432
('yuipath','local','local|http://yui.yahooapis.com/2.5.1/build','Insert the path to YUI libraries, choose local if you use koha offline','Choice'),
433
('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'),
433
('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'),
434
('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo')
434
('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo')
(-)a/installer/data/mysql/updatedatabase.pl (+7 lines)
Lines 8202-8207 if ( CheckVersion($DBversion) ) { Link Here
8202
    SetVersion($DBversion);
8202
    SetVersion($DBversion);
8203
}
8203
}
8204
8204
8205
$DBversion = "3.15.00.XXX";
8206
if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
8207
    $dbh->do("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')");
8208
    print "Upgrade to $DBversion done (Bug 7710 - multiple holds per title)\n";
8209
    SetVersion ($DBversion);
8210
}
8211
8205
=head1 FUNCTIONS
8212
=head1 FUNCTIONS
8206
8213
8207
=head2 TableExists($table)
8214
=head2 TableExists($table)
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-1 / +6 lines)
Lines 447-453 Circulation: Link Here
447
            - Patrons can only have
447
            - Patrons can only have
448
            - pref: maxreserves
448
            - pref: maxreserves
449
              class: integer
449
              class: integer
450
            - holds at once.
450
            - total holds at a time.
451
        -
452
            - Patrons can only place
453
            - pref: MaxHoldsPerRecord
454
              class: integer
455
            - holds on a single record at a time.
451
        -
456
        -
452
            - pref: emailLibrarianWhenHoldIsPlaced
457
            - pref: emailLibrarianWhenHoldIsPlaced
453
              choices:
458
              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 247-253 function checkMultiHold() { Link Here
247
          <li><strong>Too many holds: </strong> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %] </a> has too many holds.</li>
247
          <li><strong>Too many holds: </strong> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %] </a> has too many holds.</li>
248
        [% END %]
248
        [% END %]
249
        [% IF ( alreadyreserved ) %]
249
        [% IF ( alreadyreserved ) %]
250
          <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>already has a hold</strong> on this item </li>
250
          <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>
251
        [% END %]
251
        [% END %]
252
        [% IF ( none_available ) %]
252
        [% IF ( none_available ) %]
253
          <li> <strong>No items are available</strong> to be placed on hold</li>
253
          <li> <strong>No items are available</strong> to be placed on hold</li>
(-)a/koha-tmpl/opac-tmpl/prog/en/modules/opac-reserve.tt (+2 lines)
Lines 330-335 Link Here
330
                                [% ELSE %]
330
                                [% ELSE %]
331
                                    [% IF ( bibitemloo.already_patron_possession ) %]
331
                                    [% IF ( bibitemloo.already_patron_possession ) %]
332
                                        <div class="bibmessage">This title cannot be requested because it's already in your possession.</div>
332
                                        <div class="bibmessage">This title cannot be requested because it's already in your possession.</div>
333
                                    [% ELSIF bibitemloo.max_holds %]
334
                                        <div class="bibmessage">This title cannot be requested. You have already placed the maximum number of requests allowed for this title.</div>
333
                                    [% ELSE %]
335
                                    [% ELSE %]
334
                                        <div class="bibmessage">This title cannot be requested.</div>
336
                                        <div class="bibmessage">This title cannot be requested.</div>
335
                                    [% END %]
337
                                    [% END %]
(-)a/opac/opac-reserve.pl (-11 / +19 lines)
Lines 37-43 use C4::Debug; Link Here
37
use Koha::DateUtils;
37
use Koha::DateUtils;
38
# use Data::Dumper;
38
# use Data::Dumper;
39
39
40
my $MAXIMUM_NUMBER_OF_RESERVES = C4::Context->preference("maxreserves");
40
my $maxreserves = C4::Context->preference("maxreserves");
41
my $max_holds_per_record = C4::Context->preference('MaxHoldsPerRecord');
41
42
42
my $query = new CGI;
43
my $query = new CGI;
43
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
44
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
Lines 171-177 foreach my $biblioNumber (@biblionumbers) { Link Here
171
#
172
#
172
if ( $query->param('place_reserve') ) {
173
if ( $query->param('place_reserve') ) {
173
    my $reserve_cnt = 0;
174
    my $reserve_cnt = 0;
174
    if ($MAXIMUM_NUMBER_OF_RESERVES) {
175
    if ($maxreserves) {
175
        $reserve_cnt = GetReservesFromBorrowernumber( $borrowernumber );
176
        $reserve_cnt = GetReservesFromBorrowernumber( $borrowernumber );
176
    }
177
    }
177
178
Lines 255-262 if ( $query->param('place_reserve') ) { Link Here
255
        }
256
        }
256
        my $notes = $query->param('notes_'.$biblioNum)||'';
257
        my $notes = $query->param('notes_'.$biblioNum)||'';
257
258
258
        if (   $MAXIMUM_NUMBER_OF_RESERVES
259
        if (   $maxreserves
259
            && $reserve_cnt >= $MAXIMUM_NUMBER_OF_RESERVES )
260
            && $reserve_cnt >= $maxreserves )
260
        {
261
        {
261
            $canreserve = 0;
262
            $canreserve = 0;
262
        }
263
        }
Lines 317-323 if ( $borr->{'debarred'} ) { Link Here
317
318
318
my @reserves = GetReservesFromBorrowernumber( $borrowernumber );
319
my @reserves = GetReservesFromBorrowernumber( $borrowernumber );
319
$template->param( RESERVES => \@reserves );
320
$template->param( RESERVES => \@reserves );
320
if ( $MAXIMUM_NUMBER_OF_RESERVES && (scalar(@reserves) >= $MAXIMUM_NUMBER_OF_RESERVES) ) {
321
if ( $maxreserves && (scalar(@reserves) >= $maxreserves) ) {
321
    $template->param( message => 1 );
322
    $template->param( message => 1 );
322
    $noreserves = 1;
323
    $noreserves = 1;
323
    $template->param( too_many_reserves => scalar(@reserves));
324
    $template->param( too_many_reserves => scalar(@reserves));
Lines 325-336 if ( $MAXIMUM_NUMBER_OF_RESERVES && (scalar(@reserves) >= $MAXIMUM_NUMBER_OF_RES Link Here
325
foreach my $res (@reserves) {
326
foreach my $res (@reserves) {
326
    foreach my $biblionumber (@biblionumbers) {
327
    foreach my $biblionumber (@biblionumbers) {
327
        if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) {
328
        if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) {
328
#            $template->param( message => 1 );
329
            $biblioDataHash{$biblionumber}->{holds_count}++;
329
#            $noreserves = 1;
330
#            $template->param( already_reserved => 1 );
331
            $biblioDataHash{$biblionumber}->{already_reserved} = 1;
332
        }
330
        }
333
    }
331
    }
332
334
}
333
}
335
334
336
unless ($noreserves) {
335
unless ($noreserves) {
Lines 374-379 foreach my $biblioNum (@biblionumbers) { Link Here
374
    $biblioLoopIter{reservecount} = $biblioData->{reservecount};
373
    $biblioLoopIter{reservecount} = $biblioData->{reservecount};
375
    $biblioLoopIter{already_reserved} = $biblioData->{already_reserved};
374
    $biblioLoopIter{already_reserved} = $biblioData->{already_reserved};
376
    $biblioLoopIter{mandatorynotes}=0; #FIXME: For future use
375
    $biblioLoopIter{mandatorynotes}=0; #FIXME: For future use
376
    $biblioLoopIter{holds_count} = $biblioData->{holds_count};
377
377
378
    if (!$itemLevelTypes && $biblioData->{itemtype}) {
378
    if (!$itemLevelTypes && $biblioData->{itemtype}) {
379
        $biblioLoopIter{description} = $itemTypes->{$biblioData->{itemtype}}{description};
379
        $biblioLoopIter{description} = $itemTypes->{$biblioData->{itemtype}}{description};
Lines 502-515 foreach my $biblioNum (@biblionumbers) { Link Here
502
            $policy_holdallowed = 0;
502
            $policy_holdallowed = 0;
503
        }
503
        }
504
504
505
        if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum) and ($itemLoopIter->{already_reserved} ne 1)) {
505
        if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum) and ($itemLoopIter->{holds_count} < $max_holds_per_record )) {
506
            $itemLoopIter->{available} = 1;
506
            $itemLoopIter->{available} = 1;
507
            $numCopiesAvailable++;
507
            $numCopiesAvailable++;
508
        }
508
        }
509
509
510
        $itemLoopIter->{imageurl} = getitemtypeimagelocation( 'opac', $itemTypes->{ $itemInfo->{itype} }{imageurl} );
510
        $itemLoopIter->{imageurl} = getitemtypeimagelocation( 'opac', $itemTypes->{ $itemInfo->{itype} }{imageurl} );
511
511
512
    # Show serial enumeration when needed
512
        # Don't allow multiple item level holds on the same item if borrowers can place multiple holds on one bib
513
        $itemLoopIter->{available} = 0 if ( GetReservesFromBorrowernumber( $borrowernumber, undef, undef, $itemNum ) );
514
515
        # Show serial enumeration when needed
513
        if ($itemLoopIter->{enumchron}) {
516
        if ($itemLoopIter->{enumchron}) {
514
            $itemdata_enumchron = 1;
517
            $itemdata_enumchron = 1;
515
        }
518
        }
Lines 526-531 foreach my $biblioNum (@biblionumbers) { Link Here
526
    if ($biblioLoopIter{already_reserved}) {
529
    if ($biblioLoopIter{already_reserved}) {
527
        $biblioLoopIter{holdable} = undef;
530
        $biblioLoopIter{holdable} = undef;
528
    }
531
    }
532
    if ($biblioLoopIter{holds_count} >= $max_holds_per_record) {
533
        $biblioLoopIter{holdable} = undef;
534
        $anyholdable = undef;
535
        $biblioLoopIter{max_holds} = 1;
536
    }
529
    if(not CanBookBeReserved($borrowernumber,$biblioNum)){
537
    if(not CanBookBeReserved($borrowernumber,$biblioNum)){
530
        $biblioLoopIter{holdable} = undef;
538
        $biblioLoopIter{holdable} = undef;
531
    }
539
    }
(-)a/reserve/request.pl (-6 / +14 lines)
Lines 169-174 $template->param( messageborrower => $messageborrower ); Link Here
169
169
170
# FIXME launch another time GetMember perhaps until
170
# FIXME launch another time GetMember perhaps until
171
my $borrowerinfo = GetMember( borrowernumber => $borrowernumber_hold );
171
my $borrowerinfo = GetMember( borrowernumber => $borrowernumber_hold );
172
my $max_holds_per_record = C4::Context->preference('MaxHoldsPerRecord');
172
173
173
my @biblionumbers = ();
174
my @biblionumbers = ();
174
my $biblionumbers = $input->param('biblionumbers');
175
my $biblionumbers = $input->param('biblionumbers');
Lines 212-226 foreach my $biblionumber (@biblionumbers) { Link Here
212
        }
213
        }
213
    }
214
    }
214
215
215
    if ( $holds_count ) {
216
    if ( $holds_count > $max_holds_per_record ) {
216
            $alreadyreserved = 1;
217
        $alreadyreserved             = 1;
217
            $biblioloopiter{warn} = 1;
218
        $biblioloopiter{warn}        = 1;
218
            $biblioloopiter{alreadyres} = 1;
219
        $biblioloopiter{alreadyres}  = 1;
220
        $biblioloopiter{maxreserves} = $max_holds_per_record;
219
    }
221
    }
220
222
221
    $template->param(
223
    $template->param(
222
        alreadyreserved => $alreadyreserved,
224
        alreadyreserved   => $alreadyreserved,
223
        alreadypossession => $alreadypossession,
225
        alreadypossession => $alreadypossession,
226
        maxreserves       => $maxreserves,
224
    );
227
    );
225
228
226
    # FIXME think @optionloop, is maybe obsolete, or  must be switchable by a systeme preference fixed rank or not
229
    # FIXME think @optionloop, is maybe obsolete, or  must be switchable by a systeme preference fixed rank or not
Lines 430-435 foreach my $biblionumber (@biblionumbers) { Link Here
430
                $itemdata_enumchron = 1;
433
                $itemdata_enumchron = 1;
431
            }
434
            }
432
435
436
            # Don't allow multiple item level holds on the same item if borrowers can place multiple holds on one bib
437
            if ( GetReservesFromBorrowernumber( $borrowerinfo->{borrowernumber}, undef, undef, $itemnumber) ) {
438
                $item->{available} = 0;
439
                $item->{override} = 0;
440
            }
441
433
            push @{ $biblioitem->{itemloop} }, $item;
442
            push @{ $biblioitem->{itemloop} }, $item;
434
        }
443
        }
435
444
436
- 

Return to bug 7710