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 188-193 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
188
('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'),
188
('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'),
189
('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'),
189
('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'),
190
('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'),
190
('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'),
191
('MaxHoldsPerRecord', '1', 'The maximum number of holds allowed per bibliographic record per borrower', 'Integer'),
191
('MaxItemsForBatch','1000',NULL,'Max number of items record to process in a batch (modification or deletion)','Integer'),
192
('MaxItemsForBatch','1000',NULL,'Max number of items record to process in a batch (modification or deletion)','Integer'),
192
('maxItemsInSearchResults','20',NULL,'Specify the maximum number of items to display for each result on a page of results','free'),
193
('maxItemsInSearchResults','20',NULL,'Specify the maximum number of items to display for each result on a page of results','free'),
193
('maxoutstanding','5','','maximum amount withstanding to be able make holds','Integer'),
194
('maxoutstanding','5','','maximum amount withstanding to be able make holds','Integer'),
Lines 442-448 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
442
('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'),
443
('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'),
443
('XISBNDailyLimit','999','','The xISBN Web service is free for non-commercial use when usage does not exceed 1000 requests per day','Integer'),
444
('XISBNDailyLimit','999','','The xISBN Web service is free for non-commercial use when usage does not exceed 1000 requests per day','Integer'),
444
('XSLTDetailsDisplay','default','','Enable XSL stylesheet control over details page display on intranet','Free'),
445
('XSLTDetailsDisplay','default','','Enable XSL stylesheet control over details page display on intranet','Free'),
445
('XSLTResultsDisplay','default','','Enable XSL stylesheet control over results page display on intranet','Free'),
446
('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'),
446
('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'),
447
('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'),
447
('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'),
448
('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo')
448
('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo')
(-)a/installer/data/mysql/updatedatabase.pl (+7 lines)
Lines 8589-8594 if ( CheckVersion($DBversion) ) { Link Here
8589
    SetVersion($DBversion);
8589
    SetVersion($DBversion);
8590
}
8590
}
8591
8591
8592
$DBversion = "3.17.00.XXX";
8593
if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
8594
    $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')");
8595
    print "Upgrade to $DBversion done (Bug 7710 - multiple holds per title)\n";
8596
    SetVersion ($DBversion);
8597
}
8598
8592
=head1 FUNCTIONS
8599
=head1 FUNCTIONS
8593
8600
8594
=head2 TableExists($table)
8601
=head2 TableExists($table)
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-1 / +6 lines)
Lines 454-460 Circulation: Link Here
454
            - Patrons can only have
454
            - Patrons can only have
455
            - pref: maxreserves
455
            - pref: maxreserves
456
              class: integer
456
              class: integer
457
            - holds at once.
457
            - total holds at a time.
458
        -
459
            - Patrons can only place
460
            - pref: MaxHoldsPerRecord
461
              class: integer
462
            - holds on a single record at a time.
458
        -
463
        -
459
            - pref: emailLibrarianWhenHoldIsPlaced
464
            - pref: emailLibrarianWhenHoldIsPlaced
460
              choices:
465
              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 358-363 function select_first_available(id){ Link Here
358
                                [% ELSE %]
358
                                [% ELSE %]
359
                                    [% IF ( bibitemloo.already_patron_possession ) %]
359
                                    [% IF ( bibitemloo.already_patron_possession ) %]
360
                                        <div class="bibmessage">This title cannot be requested because it's already in your possession.</div>
360
                                        <div class="bibmessage">This title cannot be requested because it's already in your possession.</div>
361
                                    [% ELSIF bibitemloo.max_holds %]
362
                                        <div class="bibmessage">This title cannot be requested. You have already placed the maximum number of requests allowed for this title.</div>
361
                                    [% ELSE %]
363
                                    [% ELSE %]
362
                                        <div class="bibmessage">This title cannot be requested.</div>
364
                                        <div class="bibmessage">This title cannot be requested.</div>
363
                                    [% END %]
365
                                    [% END %]
(-)a/opac/opac-reserve.pl (-11 / +19 lines)
Lines 38-44 use Koha::DateUtils; Link Here
38
use Date::Calc qw/Today Date_to_Days/;
38
use Date::Calc qw/Today Date_to_Days/;
39
# use Data::Dumper;
39
# use Data::Dumper;
40
40
41
my $MAXIMUM_NUMBER_OF_RESERVES = C4::Context->preference("maxreserves");
41
my $maxreserves = C4::Context->preference("maxreserves");
42
my $max_holds_per_record = C4::Context->preference('MaxHoldsPerRecord');
42
43
43
my $query = new CGI;
44
my $query = new CGI;
44
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
45
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
Lines 183-189 foreach my $biblioNumber (@biblionumbers) { Link Here
183
#
184
#
184
if ( $query->param('place_reserve') ) {
185
if ( $query->param('place_reserve') ) {
185
    my $reserve_cnt = 0;
186
    my $reserve_cnt = 0;
186
    if ($MAXIMUM_NUMBER_OF_RESERVES) {
187
    if ($maxreserves) {
187
        $reserve_cnt = GetReservesFromBorrowernumber( $borrowernumber );
188
        $reserve_cnt = GetReservesFromBorrowernumber( $borrowernumber );
188
    }
189
    }
189
190
Lines 267-274 if ( $query->param('place_reserve') ) { Link Here
267
        }
268
        }
268
        my $notes = $query->param('notes_'.$biblioNum)||'';
269
        my $notes = $query->param('notes_'.$biblioNum)||'';
269
270
270
        if (   $MAXIMUM_NUMBER_OF_RESERVES
271
        if (   $maxreserves
271
            && $reserve_cnt >= $MAXIMUM_NUMBER_OF_RESERVES )
272
            && $reserve_cnt >= $maxreserves )
272
        {
273
        {
273
            $canreserve = 0;
274
            $canreserve = 0;
274
        }
275
        }
Lines 329-335 if ( $borr->{'debarred'} ) { Link Here
329
330
330
my @reserves = GetReservesFromBorrowernumber( $borrowernumber );
331
my @reserves = GetReservesFromBorrowernumber( $borrowernumber );
331
$template->param( RESERVES => \@reserves );
332
$template->param( RESERVES => \@reserves );
332
if ( $MAXIMUM_NUMBER_OF_RESERVES && (scalar(@reserves) >= $MAXIMUM_NUMBER_OF_RESERVES) ) {
333
if ( $maxreserves && (scalar(@reserves) >= $maxreserves) ) {
333
    $template->param( message => 1 );
334
    $template->param( message => 1 );
334
    $noreserves = 1;
335
    $noreserves = 1;
335
    $template->param( too_many_reserves => scalar(@reserves));
336
    $template->param( too_many_reserves => scalar(@reserves));
Lines 337-348 if ( $MAXIMUM_NUMBER_OF_RESERVES && (scalar(@reserves) >= $MAXIMUM_NUMBER_OF_RES Link Here
337
foreach my $res (@reserves) {
338
foreach my $res (@reserves) {
338
    foreach my $biblionumber (@biblionumbers) {
339
    foreach my $biblionumber (@biblionumbers) {
339
        if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) {
340
        if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) {
340
#            $template->param( message => 1 );
341
            $biblioDataHash{$biblionumber}->{holds_count}++;
341
#            $noreserves = 1;
342
#            $template->param( already_reserved => 1 );
343
            $biblioDataHash{$biblionumber}->{already_reserved} = 1;
344
        }
342
        }
345
    }
343
    }
344
346
}
345
}
347
346
348
unless ($noreserves) {
347
unless ($noreserves) {
Lines 386-391 foreach my $biblioNum (@biblionumbers) { Link Here
386
    $biblioLoopIter{reservecount} = $biblioData->{reservecount};
385
    $biblioLoopIter{reservecount} = $biblioData->{reservecount};
387
    $biblioLoopIter{already_reserved} = $biblioData->{already_reserved};
386
    $biblioLoopIter{already_reserved} = $biblioData->{already_reserved};
388
    $biblioLoopIter{mandatorynotes}=0; #FIXME: For future use
387
    $biblioLoopIter{mandatorynotes}=0; #FIXME: For future use
388
    $biblioLoopIter{holds_count} = $biblioData->{holds_count};
389
389
390
    if (!$itemLevelTypes && $biblioData->{itemtype}) {
390
    if (!$itemLevelTypes && $biblioData->{itemtype}) {
391
        $biblioLoopIter{description} = $itemTypes->{$biblioData->{itemtype}}{description};
391
        $biblioLoopIter{description} = $itemTypes->{$biblioData->{itemtype}}{description};
Lines 514-527 foreach my $biblioNum (@biblionumbers) { Link Here
514
            $policy_holdallowed = 0;
514
            $policy_holdallowed = 0;
515
        }
515
        }
516
516
517
        if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum) and ($itemLoopIter->{already_reserved} ne 1)) {
517
        if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum) and ($itemLoopIter->{holds_count} < $max_holds_per_record )) {
518
            $itemLoopIter->{available} = 1;
518
            $itemLoopIter->{available} = 1;
519
            $numCopiesAvailable++;
519
            $numCopiesAvailable++;
520
        }
520
        }
521
521
522
        $itemLoopIter->{imageurl} = getitemtypeimagelocation( 'opac', $itemTypes->{ $itemInfo->{itype} }{imageurl} );
522
        $itemLoopIter->{imageurl} = getitemtypeimagelocation( 'opac', $itemTypes->{ $itemInfo->{itype} }{imageurl} );
523
523
524
    # Show serial enumeration when needed
524
        # Don't allow multiple item level holds on the same item if borrowers can place multiple holds on one bib
525
        $itemLoopIter->{available} = 0 if ( GetReservesFromBorrowernumber( $borrowernumber, undef, undef, $itemNum ) );
526
527
        # Show serial enumeration when needed
525
        if ($itemLoopIter->{enumchron}) {
528
        if ($itemLoopIter->{enumchron}) {
526
            $itemdata_enumchron = 1;
529
            $itemdata_enumchron = 1;
527
        }
530
        }
Lines 538-543 foreach my $biblioNum (@biblionumbers) { Link Here
538
    if ($biblioLoopIter{already_reserved}) {
541
    if ($biblioLoopIter{already_reserved}) {
539
        $biblioLoopIter{holdable} = undef;
542
        $biblioLoopIter{holdable} = undef;
540
    }
543
    }
544
    if ($biblioLoopIter{holds_count} >= $max_holds_per_record) {
545
        $biblioLoopIter{holdable} = undef;
546
        $anyholdable = undef;
547
        $biblioLoopIter{max_holds} = 1;
548
    }
541
    if(not CanBookBeReserved($borrowernumber,$biblioNum)){
549
    if(not CanBookBeReserved($borrowernumber,$biblioNum)){
542
        $biblioLoopIter{holdable} = undef;
550
        $biblioLoopIter{holdable} = undef;
543
    }
551
    }
(-)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