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

(-)a/C4/Reserves.pm (-33 / +104 lines)
Lines 43-48 use Koha::Holds; Link Here
43
use Koha::Libraries;
43
use Koha::Libraries;
44
use Koha::Items;
44
use Koha::Items;
45
use Koha::ItemTypes;
45
use Koha::ItemTypes;
46
use Koha::Patrons;
46
47
47
use List::MoreUtils qw( firstidx any );
48
use List::MoreUtils qw( firstidx any );
48
use Carp;
49
use Carp;
Lines 141-146 BEGIN { Link Here
141
        &GetReservesControlBranch
142
        &GetReservesControlBranch
142
143
143
        IsItemOnHoldAndFound
144
        IsItemOnHoldAndFound
145
146
        GetMaxPatronHoldsForRecord
144
    );
147
    );
145
    @EXPORT_OK = qw( MergeHolds );
148
    @EXPORT_OK = qw( MergeHolds );
146
}
149
}
Lines 168-179 sub AddReserve { Link Here
168
        $title,    $checkitem,      $found,        $itemtype
171
        $title,    $checkitem,      $found,        $itemtype
169
    ) = @_;
172
    ) = @_;
170
173
171
    if ( Koha::Holds->search( { borrowernumber => $borrowernumber, biblionumber => $biblionumber } )->count() > 0 ) {
174
    my $dbh = C4::Context->dbh;
172
        carp("AddReserve: borrower $borrowernumber already has a hold for biblionumber $biblionumber");
173
        return;
174
    }
175
176
    my $dbh     = C4::Context->dbh;
177
175
178
    $resdate = output_pref( { str => dt_from_string( $resdate ), dateonly => 1, dateformat => 'iso' })
176
    $resdate = output_pref( { str => dt_from_string( $resdate ), dateonly => 1, dateformat => 'iso' })
179
        or output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
177
        or output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
Lines 458-464 sub CanItemBeReserved { Link Here
458
456
459
    my $dbh = C4::Context->dbh;
457
    my $dbh = C4::Context->dbh;
460
    my $ruleitemtype;    # itemtype of the matching issuing rule
458
    my $ruleitemtype;    # itemtype of the matching issuing rule
461
    my $allowedreserves = 0;
459
    my $allowedreserves  = 0; # Total number of holds allowed across all records
460
    my $holds_per_record = 1; # Total number of holds allowed for this one given record
462
461
463
    # we retrieve borrowers and items informations #
462
    # we retrieve borrowers and items informations #
464
    # item->{itype} will come for biblioitems if necessery
463
    # item->{itype} will come for biblioitems if necessery
Lines 471-496 sub CanItemBeReserved { Link Here
471
      if ( $item->{damaged}
470
      if ( $item->{damaged}
472
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
471
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
473
472
474
    #Check for the age restriction
473
    # Check for the age restriction
475
    my ( $ageRestriction, $daysToAgeRestriction ) =
474
    my ( $ageRestriction, $daysToAgeRestriction ) =
476
      C4::Circulation::GetAgeRestriction( $biblioData->{agerestriction}, $borrower );
475
      C4::Circulation::GetAgeRestriction( $biblioData->{agerestriction}, $borrower );
477
    return 'ageRestricted' if $daysToAgeRestriction && $daysToAgeRestriction > 0;
476
    return 'ageRestricted' if $daysToAgeRestriction && $daysToAgeRestriction > 0;
478
477
479
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
478
    # Check that the patron doesn't have an item level hold on this item already
479
    return 'itemAlreadyOnHold'
480
      if Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->count();
480
481
481
    # we retrieve user rights on this itemtype and branchcode
482
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
482
    my $sth = $dbh->prepare(
483
        q{
484
         SELECT categorycode, itemtype, branchcode, reservesallowed
485
           FROM issuingrules
486
          WHERE (categorycode in (?,'*') )
487
            AND (itemtype IN (?,'*'))
488
            AND (branchcode IN (?,'*'))
489
       ORDER BY categorycode DESC,
490
                itemtype     DESC,
491
                branchcode   DESC
492
        }
493
    );
494
483
495
    my $querycount = q{
484
    my $querycount = q{
496
        SELECT count(*) AS count
485
        SELECT count(*) AS count
Lines 514-528 sub CanItemBeReserved { Link Here
514
    }
503
    }
515
504
516
    # we retrieve rights
505
    # we retrieve rights
517
    $sth->execute( $borrower->{'categorycode'}, $item->{'itype'}, $branchcode );
506
    if ( my $rights = GetHoldRule( $borrower->{'categorycode'}, $item->{'itype'}, $branchcode ) ) {
518
    if ( my $rights = $sth->fetchrow_hashref() ) {
507
        $ruleitemtype     = $rights->{itemtype};
519
        $ruleitemtype    = $rights->{itemtype};
508
        $allowedreserves  = $rights->{reservesallowed};
520
        $allowedreserves = $rights->{reservesallowed};
509
        $holds_per_record = $rights->{holds_per_record};
521
    }
510
    }
522
    else {
511
    else {
523
        $ruleitemtype = '*';
512
        $ruleitemtype = '*';
524
    }
513
    }
525
514
515
    my $item = Koha::Items->find( $itemnumber );
516
    my $holds = Koha::Holds->search(
517
        {
518
            borrowernumber => $borrowernumber,
519
            biblionumber   => $item->biblionumber,
520
            found          => undef, # Found holds don't count against a patron's holds limit
521
        }
522
    );
523
    if ( $holds->count() >= $holds_per_record ) {
524
        return "tooManyHoldsForThisRecord";
525
    }
526
526
    # we retrieve count
527
    # we retrieve count
527
528
528
    $querycount .= "AND $branchfield = ?";
529
    $querycount .= "AND $branchfield = ?";
Lines 752-759 sub GetReservesToBranch { Link Here
752
    my $dbh = C4::Context->dbh;
753
    my $dbh = C4::Context->dbh;
753
    my $sth = $dbh->prepare(
754
    my $sth = $dbh->prepare(
754
        "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
755
        "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
755
         FROM reserves 
756
         FROM reserves
756
         WHERE priority='0' 
757
         WHERE priority='0'
757
           AND branchcode=?"
758
           AND branchcode=?"
758
    );
759
    );
759
    $sth->execute( $frombranch );
760
    $sth->execute( $frombranch );
Lines 778-784 sub GetReservesForBranch { Link Here
778
779
779
    my $query = "
780
    my $query = "
780
        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
781
        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
781
        FROM   reserves 
782
        FROM   reserves
782
        WHERE   priority='0'
783
        WHERE   priority='0'
783
        AND found='W'
784
        AND found='W'
784
    ";
785
    ";
Lines 1401-1407 sub ModReserveMinusPriority { Link Here
1401
    my $dbh   = C4::Context->dbh;
1402
    my $dbh   = C4::Context->dbh;
1402
    my $query = "
1403
    my $query = "
1403
        UPDATE reserves
1404
        UPDATE reserves
1404
        SET    priority = 0 , itemnumber = ? 
1405
        SET    priority = 0 , itemnumber = ?
1405
        WHERE  reserve_id = ?
1406
        WHERE  reserve_id = ?
1406
    ";
1407
    ";
1407
    my $sth_upd = $dbh->prepare($query);
1408
    my $sth_upd = $dbh->prepare($query);
Lines 1638-1644 sub ToggleLowestPriority { Link Here
1638
1639
1639
    my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1640
    my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1640
    $sth->execute( $reserve_id );
1641
    $sth->execute( $reserve_id );
1641
    
1642
1642
    _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1643
    _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1643
}
1644
}
1644
1645
Lines 1822-1828 sub _FixPriority { Link Here
1822
            $priority[$j]->{'reserve_id'}
1823
            $priority[$j]->{'reserve_id'}
1823
        );
1824
        );
1824
    }
1825
    }
1825
    
1826
1826
    $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1827
    $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1827
    $sth->execute();
1828
    $sth->execute();
1828
1829
Lines 2060-2066 sub _koha_notify_reserve { Link Here
2060
    if (! $notification_sent) {
2061
    if (! $notification_sent) {
2061
        &$send_notification('print', 'HOLD');
2062
        &$send_notification('print', 'HOLD');
2062
    }
2063
    }
2063
    
2064
2064
}
2065
}
2065
2066
2066
=head2 _ShiftPriorityByDateAndPriority
2067
=head2 _ShiftPriorityByDateAndPriority
Lines 2489-2494 sub IsItemOnHoldAndFound { Link Here
2489
    return $found;
2490
    return $found;
2490
}
2491
}
2491
2492
2493
=head2 GetMaxPatronHoldsForRecord
2494
2495
my $holds_per_record = ReservesControlBranch( $borrowernumber, $biblionumber );
2496
2497
For multiple holds on a given record for a given patron, the max
2498
number of record level holds that a patron can be placed is the highest
2499
value of the holds_per_record rule for each item if the record for that
2500
patron. This subroutine finds and returns the highest holds_per_record
2501
rule value for a given patron id and record id.
2502
2503
=cut
2504
2505
sub GetMaxPatronHoldsForRecord {
2506
    my ( $borrowernumber, $biblionumber ) = @_;
2507
2508
    my $patron = Koha::Patrons->find($borrowernumber);
2509
    my @items = Koha::Items->search( { biblionumber => $biblionumber } );
2510
2511
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
2512
2513
    my $categorycode = $patron->categorycode;
2514
    my $branchcode;
2515
    $branchcode = $patron->branchcode if ( $controlbranch eq "PatronLibrary" );
2516
2517
    my $max = 0;
2518
    foreach my $item (@items) {
2519
        my $itemtype = $item->effective_itemtype();
2520
2521
        $branchcode = $item->homebranch if ( $controlbranch eq "ItemHomeLibrary" );
2522
2523
        my $rule = GetHoldRule( $categorycode, $itemtype, $branchcode );
2524
        my $holds_per_record = $rule ? $rule->{holds_per_record} : 0;
2525
        $max = $holds_per_record if $holds_per_record > $max;
2526
    }
2527
2528
    return $max;
2529
}
2530
2531
=head2 GetHoldRule
2532
2533
my $rule = GetHoldRule( $categorycode, $itemtype, $branchcode );
2534
2535
Returns the matching hold related issuingrule fields for a given
2536
patron category, itemtype, and library.
2537
2538
=cut
2539
2540
sub GetHoldRule {
2541
    my ( $categorycode, $itemtype, $branchcode ) = @_;
2542
2543
    my $dbh = C4::Context->dbh;
2544
2545
    my $sth = $dbh->prepare(
2546
        q{
2547
         SELECT categorycode, itemtype, branchcode, reservesallowed, holds_per_record
2548
           FROM issuingrules
2549
          WHERE (categorycode in (?,'*') )
2550
            AND (itemtype IN (?,'*'))
2551
            AND (branchcode IN (?,'*'))
2552
       ORDER BY categorycode DESC,
2553
                itemtype     DESC,
2554
                branchcode   DESC
2555
        }
2556
    );
2557
2558
    $sth->execute( $categorycode, $itemtype, $branchcode );
2559
2560
    return $sth->fetchrow_hashref();
2561
}
2562
2492
=head1 AUTHOR
2563
=head1 AUTHOR
2493
2564
2494
Koha Development Team <http://koha-community.org/>
2565
Koha Development Team <http://koha-community.org/>
(-)a/Koha/Holds.pm (+32 lines)
Lines 49-54 sub waiting { Link Here
49
    return $self->search( { found => 'W' } );
49
    return $self->search( { found => 'W' } );
50
}
50
}
51
51
52
=head3 forced_hold_level
53
54
If a patron has multiple holds for a single record,
55
those holds must be either all record level holds,
56
or they must all be item level holds.
57
58
This method should be used with Hold sets where all
59
Hold objects share the same patron and record.
60
61
This method will return 'item' if the patron has
62
at least one item level hold. It will return 'record'
63
if the patron has holds but none are item level,
64
Finally, if the patron has no holds, it will return
65
undef which indicateds the patron may select either
66
record or item level holds, barring any other rules
67
that would prevent one or the other.
68
=cut
69
70
sub forced_hold_level {
71
    my ($self) = @_;
72
73
    my $force_hold_level;
74
75
    if ( $self->count() ) {
76
        my $has_item_level_holds;
77
        map { $has_item_level_holds ||= $_->itemnumber } $self->as_list();
78
        $force_hold_level = $has_item_level_holds ? 'item' : 'record';
79
    }
80
81
    return $force_hold_level;
82
}
83
52
=head3 type
84
=head3 type
53
85
54
=cut
86
=cut
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-36 / +81 lines)
Lines 49-97 $(document).ready(function() { Link Here
49
});
49
});
50
50
51
function check() {
51
function check() {
52
	var msg = "";
52
    var msg = "";
53
	var count_reserv = 0;
53
    var count_reserv = 0;
54
	var alreadyreserved = 0;
55
54
56
    // check if we have checkitem form
55
    // check if we have checkitem form
57
    if (document.form.checkitem){
56
    if (document.form.checkitem){
58
        for (i=0;i<document.form.checkitem.length;i++){
57
        for (i=0;i<document.form.checkitem.length;i++){
59
            if (document.form.checkitem[i].checked == true) {
58
            if (document.form.checkitem[i].checked == true) {
60
				count_reserv++ ;
59
                count_reserv++ ;
61
			}
60
            }
62
        }
61
        }
63
        // for only one item, check the checkitem without consider the loop checkitem
62
        // for only one item, check the checkitem without consider the loop checkitem
64
        if (i==0){
63
        if (i==0){
65
		    if (document.form.checkitem.checked == true) {
64
            if (document.form.checkitem.checked == true) {
66
			    count_reserv++;
65
                count_reserv++;
67
		    }
66
            }
68
	    }
67
        }
69
    }
70
71
    if (document.form.request.checked == true){
72
		count_reserv++ ;
73
    }
68
    }
74
69
75
    if (document.form.alreadyreserved && document.form.alreadyreserved.value == "1"){
70
    if (document.form.requestany.checked == true){
76
		 alreadyreserved++ ;
71
        count_reserv++ ;
77
    }
72
    }
78
73
79
    if (count_reserv == "0"){
74
    if (count_reserv == "0"){
80
		msg += (_("- Please select an item to place a hold") + "\n");
75
        msg += (_("- Please select an item to place a hold") + "\n");
81
    }
82
    if (count_reserv >= "2"){
83
		msg += (_("- You may only place a hold on one item at a time") + "\n");
84
    }
76
    }
85
77
86
    if (alreadyreserved > "0"){
78
    if (msg == "") {
87
		msg += (_("- This patron had already placed a hold on this item") + "\n" + _("Please cancel the previous hold first") + "\n");
79
        $('#hold-request-form').preventDoubleFormSubmit();
80
        return(true);
81
    } else {
82
        alert(msg);
83
        return(false);
88
    }
84
    }
89
90
	if (msg == "") return(true);
91
	else	{
92
		alert(msg);
93
		return(false);
94
	}
95
}
85
}
96
86
97
function checkMultiHold() {
87
function checkMultiHold() {
Lines 117-122 function checkMultiHold() { Link Here
117
    $("#multi_hold_bibs").val(biblionumbers);
107
    $("#multi_hold_bibs").val(biblionumbers);
118
    $("#bad_bibs").val(badBibs);
108
    $("#bad_bibs").val(badBibs);
119
109
110
    $('#hold-request-form').preventDoubleFormSubmit();
111
120
    return true;
112
    return true;
121
}
113
}
122
114
Lines 164-170 function checkMultiHold() { Link Here
164
        $("#" + fieldID).val("");
156
        $("#" + fieldID).val("");
165
    });
157
    });
166
158
167
    $('#hold-request-form').preventDoubleFormSubmit();
168
159
169
[% UNLESS ( borrowernumber || borrowers || noitems ) %]
160
[% UNLESS ( borrowernumber || borrowers || noitems ) %]
170
    [% IF ( CircAutocompl ) %]
161
    [% IF ( CircAutocompl ) %]
Lines 271-277 function checkMultiHold() { Link Here
271
        [% IF ( exceeded_maxreserves ) %]
262
        [% IF ( exceeded_maxreserves ) %]
272
          <li><strong>Too many holds: </strong> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %] </a> can only place a maximum of [% maxreserves %] total holds.</li>
263
          <li><strong>Too many holds: </strong> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %] </a> can only place a maximum of [% maxreserves %] total holds.</li>
273
        [% ELSIF ( alreadypossession ) %]
264
        [% ELSIF ( alreadypossession ) %]
274
          <li> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>is already in possession</strong> of one item</li>
265
          <li> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>is already in possession</strong> of one item</lie
275
        [% ELSIF ( alreadyreserved ) %]
266
        [% ELSIF ( alreadyreserved ) %]
276
          <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>already has a hold</strong> on this item </li>
267
          <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>already has a hold</strong> on this item </li>
277
        [% ELSIF ( ageRestricted ) %]
268
        [% ELSIF ( ageRestricted ) %]
Lines 402-412 function checkMultiHold() { Link Here
402
	</li>
393
	</li>
403
394
404
        [% UNLESS ( multi_hold ) %]
395
        [% UNLESS ( multi_hold ) %]
405
          <li> <label for="requestany">Place a hold on the next available item </label>
396
          <li> <label for="requestany">Hold next available item </label>
406
               <input type="checkbox" id="requestany" name="request" checked="checked" value="Any" />
397
               [% IF force_hold_level == 'item' %]
398
                   <input type="checkbox" id="requestany" name="request" disabled="true" />
399
               [% ELSIF force_hold_level == 'record' %]
400
                   <input type="checkbox" id="requestany" checked="checked" value="Any" disabled="true"/>
401
                   <input type="hidden" name="request" value="Any"/>
402
               [% ELSE %]
403
                   <input type="checkbox" id="requestany" name="request" checked="checked" value="Any" />
404
                [% END %]
407
               <input type="hidden" name="biblioitem" value="[% biblioitemnumber %]" />
405
               <input type="hidden" name="biblioitem" value="[% biblioitemnumber %]" />
408
               <input type="hidden" name="alreadyreserved" value="[% alreadyreserved %]" />
406
               <input type="hidden" name="alreadyreserved" value="[% alreadyreserved %]" />
409
          </li>
407
          </li>
408
409
          [% IF max_holds_for_record > 1 %]
410
              [% SET count = 1 %]
411
              <li>
412
                   <label for="holds_to_place_count">Holds to place (count)</label>
413
                   <select name="holds_to_place_count" id="holds_to_place_count">
414
                   [% WHILE count <= max_holds_for_record %]
415
                        <option value="[% count %]">[% count %]</option>
416
                        [% SET count = count + 1 %]
417
                   [% END %]
418
419
                   </select>
420
              </li>
421
            [% ELSE %]
422
                <input type="hidden" name="holds_to_place_count" value="1";
423
            [% END %]
410
        [% END %]
424
        [% END %]
411
425
412
</ol>
426
</ol>
Lines 431-437 function checkMultiHold() { Link Here
431
            [% IF ( bibitemloo.publicationyear ) %]<li><span class="label">Publication year:</span> [% bibitemloo.publicationyear %]</li>[% END %]
445
            [% IF ( bibitemloo.publicationyear ) %]<li><span class="label">Publication year:</span> [% bibitemloo.publicationyear %]</li>[% END %]
432
          </ol>
446
          </ol>
433
447
434
        <h2 style="padding: 0 1em;">Place a hold on a specific item</h2>
448
        <h2 style="padding: 0 1em;">
449
            Place a hold on a specific item
450
            [% IF bibitemloo.force_hold_level == 'item' %]
451
                <span class="error"><i>(Required)</i></span>
452
            [% END %]
453
        </h2>
435
        <table id="requestspecific">
454
        <table id="requestspecific">
436
            <thead>
455
            <thead>
437
                <tr>
456
                <tr>
Lines 451-468 function checkMultiHold() { Link Here
451
                </tr>
470
                </tr>
452
            </thead>
471
            </thead>
453
            <tbody>
472
            <tbody>
473
            [% SET selected = 0 %]
454
            [% FOREACH itemloo IN bibitemloo.itemloop %]
474
            [% FOREACH itemloo IN bibitemloo.itemloop %]
455
            [% UNLESS ( itemloo.hide ) %]
475
            [% UNLESS ( itemloo.hide ) %]
456
                <tr class="[% itemloo.backgroundcolor %]">
476
                <tr class="[% itemloo.backgroundcolor %]">
457
                    <td>
477
                    <td>
458
                [% IF ( itemloo.available ) %]
478
                [% IF itemloo.force_hold_level == 'record' # Patron has placed a record level hold previously for this record %]
479
                    <span class="error">
480
                        <i class="fa fa-times fa-lg" alt="Cannot be put on hold"></i>
481
                        Hold must be record level
482
                    </span>
483
                [% ELSIF ( itemloo.available ) %]
459
                    <input type="radio" name="checkitem" value="[% itemloo.itemnumber %]" />
484
                    <input type="radio" name="checkitem" value="[% itemloo.itemnumber %]" />
460
                [% ELSIF ( itemloo.override ) %]
485
                [% ELSIF ( itemloo.override ) %]
461
                    <input type="radio" name="checkitem" class="needsoverride" value="[% itemloo.itemnumber %]" />
486
                    <input type="radio" name="checkitem" class="needsoverride" value="[% itemloo.itemnumber %]" />
462
                    <img src="[% interface %]/[% theme %]/img/famfamfam/silk/error.png" alt="Requires override of hold policy" />
487
                    <i class="fa fa-exclamation-triangle fa-lg" style="color:gold" alt="Requires override of hold policy"/></i>
463
                [% ELSE %]
488
                [% ELSE %]
464
                    <input disabled="disabled" type="radio" name="checkitem" value="[% itemloo.itemnumber %]" />
489
                    <span class="error">
465
                    <img src="[% interface %]/[% theme %]/img/famfamfam/silk/cross.png" alt="Cannot be put on hold" />
490
                        <i class="fa fa-times fa-lg" alt="Cannot be put on hold"></i>
491
                        [% IF itemloo.not_holdable %]
492
                            [% IF itemloo.not_holdable == 'damaged' %]
493
                                Item damaged
494
                            [% ELSIF itemloo.not_holdable == 'ageRestricted' %]
495
                                Age restricted
496
                            [% ELSIF itemloo.not_holdable == 'tooManyHoldsForThisRecord' %]
497
                                Exceeded max holds per record
498
                            [% ELSIF itemloo.not_holdable == 'tooManyReserves' %]
499
                                Too many holds
500
                            [% ELSIF itemloo.not_holdable == 'notReservable' %]
501
                                Not holdable
502
                            [% ELSIF itemloo.not_holdable == 'cannotReserveFromOtherBranches' %]
503
                                Patron is from different library
504
                            [% ELSIF itemloo.not_holdable == 'itemAlreadyOnHold' %]
505
                                Patron already has hold for this item
506
                            [% ELSE %]
507
                                [% itemloo.not_holdable %]
508
                            [% END %]
509
                        [% END %]
510
                    </span>
466
                [% END %]
511
                [% END %]
467
                    </td>
512
                    </td>
468
                [% IF ( item_level_itypes ) %]
513
                [% IF ( item_level_itypes ) %]
(-)a/opac/opac-reserve.pl (-20 / +24 lines)
Lines 359-375 unless ( $noreserves ) { Link Here
359
    }
359
    }
360
}
360
}
361
361
362
foreach my $res (@reserves) {
363
    foreach my $biblionumber (@biblionumbers) {
364
        if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) {
365
#            $template->param( message => 1 );
366
#            $noreserves = 1;
367
#            $template->param( already_reserved => 1 );
368
            $biblioDataHash{$biblionumber}->{already_reserved} = 1;
369
        }
370
    }
371
}
372
373
unless ($noreserves) {
362
unless ($noreserves) {
374
    $template->param( select_item_types => 1 );
363
    $template->param( select_item_types => 1 );
375
}
364
}
Lines 468-476 foreach my $biblioNum (@biblionumbers) { Link Here
468
457
469
        # the item could be reserved for this borrower vi a host record, flag this
458
        # the item could be reserved for this borrower vi a host record, flag this
470
        $reservedfor //= '';
459
        $reservedfor //= '';
471
        if ($reservedfor eq $borrowernumber){
472
            $itemLoopIter->{already_reserved} = 1;
473
        }
474
460
475
        if ( defined $reservedate ) {
461
        if ( defined $reservedate ) {
476
            $itemLoopIter->{backgroundcolor} = 'reserved';
462
            $itemLoopIter->{backgroundcolor} = 'reserved';
Lines 515-526 foreach my $biblioNum (@biblionumbers) { Link Here
515
            $itemLoopIter->{nocancel} = 1;
501
            $itemLoopIter->{nocancel} = 1;
516
        }
502
        }
517
503
518
	# if the items belongs to a host record, show link to host record
504
        # if the items belongs to a host record, show link to host record
519
	if ($itemInfo->{biblionumber} ne $biblioNum){
505
        if ( $itemInfo->{biblionumber} ne $biblioNum ) {
520
		$biblioLoopIter{hostitemsflag} = 1;
506
            $biblioLoopIter{hostitemsflag}    = 1;
521
		$itemLoopIter->{hostbiblionumber} = $itemInfo->{biblionumber};
507
            $itemLoopIter->{hostbiblionumber} = $itemInfo->{biblionumber};
522
		$itemLoopIter->{hosttitle} = GetBiblioData($itemInfo->{biblionumber})->{title};
508
            $itemLoopIter->{hosttitle}        = GetBiblioData( $itemInfo->{biblionumber} )->{title};
523
	}
509
        }
524
510
525
        # If there is no loan, return and transfer, we show a checkbox.
511
        # If there is no loan, return and transfer, we show a checkbox.
526
        $itemLoopIter->{notforloan} = $itemLoopIter->{notforloan} || 0;
512
        $itemLoopIter->{notforloan} = $itemLoopIter->{notforloan} || 0;
Lines 569-574 foreach my $biblioNum (@biblionumbers) { Link Here
569
555
570
    $biblioLoopIter{holdable} &&= CanBookBeReserved($borrowernumber,$biblioNum) eq 'OK';
556
    $biblioLoopIter{holdable} &&= CanBookBeReserved($borrowernumber,$biblioNum) eq 'OK';
571
557
558
    # For multiple holds per record, if a patron has previously placed a hold,
559
    # the patron can only place more holds of the same type. That is, if the
560
    # patron placed a record level hold, all the holds the patron places must
561
    # be record level. If the patron placed an item level hold, all holds
562
    # the patron places must be item level
563
    my $forced_hold_level = Koha::Holds->search(
564
        {
565
            borrowernumber => $borrowernumber,
566
            biblionumber   => $biblioNum,
567
            found          => undef,
568
        }
569
    )->forced_hold_level();
570
    if ($forced_hold_level) {
571
        $biblioLoopIter{force_hold}   = 1 if $forced_hold_level eq 'item';
572
        $biblioLoopIter{itemholdable} = 0 if $forced_hold_level eq 'record';
573
    }
574
575
572
    push @$biblioLoop, \%biblioLoopIter;
576
    push @$biblioLoop, \%biblioLoopIter;
573
577
574
    $anyholdable = 1 if $biblioLoopIter{holdable};
578
    $anyholdable = 1 if $biblioLoopIter{holdable};
(-)a/reserve/placerequest.pl (-26 / +32 lines)
Lines 56-61 my $borrower = GetMember( 'borrowernumber' => $borrowernumber ); Link Here
56
my $multi_hold = $input->param('multi_hold');
56
my $multi_hold = $input->param('multi_hold');
57
my $biblionumbers = $multi_hold ? $input->param('biblionumbers') : ($biblionumber . '/');
57
my $biblionumbers = $multi_hold ? $input->param('biblionumbers') : ($biblionumber . '/');
58
my $bad_bibs = $input->param('bad_bibs');
58
my $bad_bibs = $input->param('bad_bibs');
59
my $holds_to_place_count = $input->param('holds_to_place_count') || 1;
59
60
60
my %bibinfos = ();
61
my %bibinfos = ();
61
my @biblionumbers = split '/', $biblionumbers;
62
my @biblionumbers = split '/', $biblionumbers;
Lines 79-108 if (defined $checkitem && $checkitem ne ''){ Link Here
79
    }
80
    }
80
}
81
}
81
82
82
if ($type eq 'str8' && $borrower){
83
if ( $type eq 'str8' && $borrower ) {
83
84
84
    foreach my $biblionumber (keys %bibinfos) {
85
    foreach my $biblionumber ( keys %bibinfos ) {
85
        my $count=@bibitems;
86
        my $count = @bibitems;
86
        @bibitems=sort @bibitems;
87
        @bibitems = sort @bibitems;
87
        my $i2=1;
88
        my $i2 = 1;
88
        my @realbi;
89
        my @realbi;
89
        $realbi[0]=$bibitems[0];
90
        $realbi[0] = $bibitems[0];
90
        for (my $i=1;$i<$count;$i++) {
91
        for ( my $i = 1 ; $i < $count ; $i++ ) {
91
            my $i3=$i2-1;
92
            my $i3 = $i2 - 1;
92
            if ($realbi[$i3] ne $bibitems[$i]) {
93
            if ( $realbi[$i3] ne $bibitems[$i] ) {
93
                $realbi[$i2]=$bibitems[$i];
94
                $realbi[$i2] = $bibitems[$i];
94
                $i2++;
95
                $i2++;
95
            }
96
            }
96
        }
97
        }
97
98
98
    if (defined $checkitem && $checkitem ne ''){
99
        if ( defined $checkitem && $checkitem ne '' ) {
99
		my $item = GetItem($checkitem);
100
            my $item = GetItem($checkitem);
100
        	if ($item->{'biblionumber'} ne $biblionumber) {
101
            if ( $item->{'biblionumber'} ne $biblionumber ) {
101
                	$biblionumber = $item->{'biblionumber'};
102
                $biblionumber = $item->{'biblionumber'};
102
        	}
103
            }
103
	}
104
        }
104
105
106
105
107
        if ($multi_hold) {
106
        if ($multi_hold) {
108
            my $bibinfo = $bibinfos{$biblionumber};
107
            my $bibinfo = $bibinfos{$biblionumber};
Lines 110-116 if ($type eq 'str8' && $borrower){ Link Here
110
                       $bibinfo->{rank},$startdate,$expirationdate,$notes,$bibinfo->{title},$checkitem,$found);
109
                       $bibinfo->{rank},$startdate,$expirationdate,$notes,$bibinfo->{title},$checkitem,$found);
111
        } else {
110
        } else {
112
            # place a request on 1st available
111
            # place a request on 1st available
113
            AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,\@realbi,$rank[0],$startdate,$expirationdate,$notes,$title,$checkitem,$found, $itemtype);
112
            for ( my $i = 0 ; $i < $holds_to_place_count ; $i++ ) {
113
                AddReserve( $branch, $borrower->{'borrowernumber'},
114
                    $biblionumber, \@realbi, $rank[0], $startdate, $expirationdate, $notes, $title,
115
                    $checkitem, $found, $itemtype );
116
            }
114
        }
117
        }
115
    }
118
    }
116
119
Lines 119-131 if ($type eq 'str8' && $borrower){ Link Here
119
            $biblionumbers .= $bad_bibs;
122
            $biblionumbers .= $bad_bibs;
120
        }
123
        }
121
        print $input->redirect("request.pl?biblionumbers=$biblionumbers&multi_hold=1");
124
        print $input->redirect("request.pl?biblionumbers=$biblionumbers&multi_hold=1");
122
    } else {
125
    }
126
    else {
123
        print $input->redirect("request.pl?biblionumber=$biblionumber");
127
        print $input->redirect("request.pl?biblionumber=$biblionumber");
124
    }
128
    }
125
} elsif ($borrower eq ''){
129
}
126
	print $input->header();
130
elsif ( $borrower eq '' ) {
127
	print "Invalid borrower number please try again";
131
    print $input->header();
128
# Not sure that Dump() does HTML escaping. Use firebug or something to trace
132
    print "Invalid borrower number please try again";
129
# instead.
133
130
#	print $input->Dump;
134
    # Not sure that Dump() does HTML escaping. Use firebug or something to trace
135
    # instead.
136
    #	print $input->Dump;
131
}
137
}
(-)a/reserve/request.pl (-33 / +43 lines)
Lines 26-33 script to place reserves/requests Link Here
26
26
27
=cut
27
=cut
28
28
29
use strict;
29
use Modern::Perl;
30
use warnings;
30
31
use C4::Branch;
31
use C4::Branch;
32
use CGI qw ( -utf8 );
32
use CGI qw ( -utf8 );
33
use List::MoreUtils qw/uniq/;
33
use List::MoreUtils qw/uniq/;
Lines 235-272 foreach my $biblionumber (@biblionumbers) { Link Here
235
        $biblioloopiter{$canReserve} = 1;
235
        $biblioloopiter{$canReserve} = 1;
236
    }
236
    }
237
237
238
    my $alreadypossession;
238
    my $force_hold_level;
239
    if (not C4::Context->preference('AllowHoldsOnPatronsPossessions') and CheckIfIssuedToPatron($borrowerinfo->{borrowernumber},$biblionumber)) {
239
    if ( $borrowerinfo->{borrowernumber} ) {
240
        $alreadypossession = 1;
240
        # For multiple holds per record, if a patron has previously placed a hold,
241
        # the patron can only place more holds of the same type. That is, if the
242
        # patron placed a record level hold, all the holds the patron places must
243
        # be record level. If the patron placed an item level hold, all holds
244
        # the patron places must be item level
245
        my $holds = Koha::Holds->search(
246
            {
247
                borrowernumber => $borrowerinfo->{borrowernumber},
248
                biblionumber   => $biblionumber,
249
                found          => undef,
250
            }
251
        );
252
        $force_hold_level = $holds->forced_hold_level();
253
        $biblioloopiter{force_hold_level} = $force_hold_level;
254
        $template->param( force_hold_level => $force_hold_level );
255
256
        # For a librarian to be able to place multiple record holds for a patron for a record,
257
        # we must find out what the maximum number of holds they can place for the patron is
258
        my $max_holds_for_record = GetMaxPatronHoldsForRecord( $borrowerinfo->{borrowernumber}, $biblionumber );
259
        $max_holds_for_record = $max_holds_for_record - $holds->count();
260
        $biblioloopiter{max_holds_for_record} = $max_holds_for_record;
261
        $template->param( max_holds_for_record => $max_holds_for_record );
241
    }
262
    }
242
263
243
    # get existing reserves .....
264
    # Check to see if patron is allowed to place holds on records where the
244
    my $reserves = GetReservesFromBiblionumber({ biblionumber => $biblionumber, all_dates => 1 });
265
    # patron already has an item from that record checked out
245
    my $count = scalar( @$reserves );
266
    my $alreadypossession;
246
    my $totalcount = $count;
267
    if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions')
247
    my $holds_count = 0;
268
        && CheckIfIssuedToPatron( $borrowerinfo->{borrowernumber}, $biblionumber ) )
248
    my $alreadyreserved = 0;
269
    {
249
270
        $template->param( alreadypossession => $alreadypossession, );
250
    foreach my $res (@$reserves) {
251
        if ( defined $res->{found} ) { # found can be 'W' or 'T'
252
            $count--;
253
        }
254
255
        if ( defined $borrowerinfo && defined($borrowerinfo->{borrowernumber}) && ($borrowerinfo->{borrowernumber} eq $res->{borrowernumber}) ) {
256
            $holds_count++;
257
        }
258
    }
271
    }
259
272
260
    if ( $holds_count ) {
261
            $alreadyreserved = 1;
262
            $biblioloopiter{warn} = 1;
263
            $biblioloopiter{alreadyres} = 1;
264
    }
265
273
266
    $template->param(
274
    my $count = Koha::Holds->search( { biblionumber => $biblionumber } )->count();
267
        alreadyreserved => $alreadyreserved,
275
    my $totalcount = $count;
268
        alreadypossession => $alreadypossession,
269
    );
270
276
271
    # FIXME think @optionloop, is maybe obsolete, or  must be switchable by a systeme preference fixed rank or not
277
    # FIXME think @optionloop, is maybe obsolete, or  must be switchable by a systeme preference fixed rank or not
272
    # make priorities options
278
    # make priorities options
Lines 329-334 foreach my $biblionumber (@biblionumbers) { Link Here
329
        my $num_override  = 0;
335
        my $num_override  = 0;
330
        my $hiddencount   = 0;
336
        my $hiddencount   = 0;
331
337
338
        $biblioitem->{force_hold_level} = $force_hold_level;
339
332
        if ( $biblioitem->{biblioitemnumber} ne $biblionumber ) {
340
        if ( $biblioitem->{biblioitemnumber} ne $biblionumber ) {
333
            $biblioitem->{hostitemsflag} = 1;
341
            $biblioitem->{hostitemsflag} = 1;
334
        }
342
        }
Lines 348-353 foreach my $biblionumber (@biblionumbers) { Link Here
348
        foreach my $itemnumber ( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} } )    {
356
        foreach my $itemnumber ( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} } )    {
349
            my $item = $iteminfos_of->{$itemnumber};
357
            my $item = $iteminfos_of->{$itemnumber};
350
358
359
            $item->{force_hold_level} = $force_hold_level;
360
351
            unless (C4::Context->preference('item-level_itypes')) {
361
            unless (C4::Context->preference('item-level_itypes')) {
352
                $item->{itype} = $biblioitem->{itemtype};
362
                $item->{itype} = $biblioitem->{itemtype};
353
            }
363
            }
Lines 445-457 foreach my $biblionumber (@biblionumbers) { Link Here
445
455
446
            $item->{'holdallowed'} = $branchitemrule->{'holdallowed'};
456
            $item->{'holdallowed'} = $branchitemrule->{'holdallowed'};
447
457
458
            my $can_item_be_reserved = CanItemBeReserved( $borrowerinfo->{borrowernumber}, $itemnumber );
459
            $item->{not_holdable} = $can_item_be_reserved unless ( $can_item_be_reserved eq 'OK' );
460
448
            if (
461
            if (
449
                   !$item->{cantreserve}
462
                   !$item->{cantreserve}
450
                && !$exceeded_maxreserves
463
                && !$exceeded_maxreserves
451
                && IsAvailableForItemLevelRequest($item, $borrowerinfo)
464
                && IsAvailableForItemLevelRequest($item, $borrowerinfo)
452
                && CanItemBeReserved(
465
                && $can_item_be_reserved eq 'OK'
453
                    $borrowerinfo->{borrowernumber}, $itemnumber
454
                ) eq 'OK'
455
              )
466
              )
456
            {
467
            {
457
                $item->{available} = 1;
468
                $item->{available} = 1;
458
- 

Return to bug 14695