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

(-)a/C4/Reserves.pm (-33 / +104 lines)
Lines 41-46 use Koha::Calendar; Link Here
41
use Koha::Database;
41
use Koha::Database;
42
use Koha::Hold;
42
use Koha::Hold;
43
use Koha::Holds;
43
use Koha::Holds;
44
use Koha::Borrowers;
44
45
45
use List::MoreUtils qw( firstidx any );
46
use List::MoreUtils qw( firstidx any );
46
use Carp;
47
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 158-169 sub AddReserve { Link Here
158
        $title,      $checkitem, $found
161
        $title,      $checkitem, $found
159
    ) = @_;
162
    ) = @_;
160
163
161
    if ( Koha::Holds->search( { borrowernumber => $borrowernumber, biblionumber => $biblionumber } )->count() > 0 ) {
164
    my $dbh = C4::Context->dbh;
162
        carp("AddReserve: borrower $borrowernumber already has a hold for biblionumber $biblionumber");
163
        return;
164
    }
165
166
    my $dbh     = C4::Context->dbh;
167
165
168
    $resdate = output_pref( { str => dt_from_string( $resdate ), dateonly => 1, dateformat => 'iso' })
166
    $resdate = output_pref( { str => dt_from_string( $resdate ), dateonly => 1, dateformat => 'iso' })
169
        or output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
167
        or output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
Lines 441-447 sub CanItemBeReserved { Link Here
441
439
442
    my $dbh = C4::Context->dbh;
440
    my $dbh = C4::Context->dbh;
443
    my $ruleitemtype;    # itemtype of the matching issuing rule
441
    my $ruleitemtype;    # itemtype of the matching issuing rule
444
    my $allowedreserves = 0;
442
    my $allowedreserves  = 0; # Total number of holds allowed across all records
443
    my $holds_per_record = 1; # Total number of holds allowed for this one given record
445
444
446
    # we retrieve borrowers and items informations #
445
    # we retrieve borrowers and items informations #
447
    # item->{itype} will come for biblioitems if necessery
446
    # item->{itype} will come for biblioitems if necessery
Lines 454-479 sub CanItemBeReserved { Link Here
454
      if ( $item->{damaged}
453
      if ( $item->{damaged}
455
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
454
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
456
455
457
    #Check for the age restriction
456
    # Check for the age restriction
458
    my ( $ageRestriction, $daysToAgeRestriction ) =
457
    my ( $ageRestriction, $daysToAgeRestriction ) =
459
      C4::Circulation::GetAgeRestriction( $biblioData->{agerestriction}, $borrower );
458
      C4::Circulation::GetAgeRestriction( $biblioData->{agerestriction}, $borrower );
460
    return 'ageRestricted' if $daysToAgeRestriction && $daysToAgeRestriction > 0;
459
    return 'ageRestricted' if $daysToAgeRestriction && $daysToAgeRestriction > 0;
461
460
462
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
461
    # Check that the patron doesn't have an item level hold on this item already
462
    return 'itemAlreadyOnHold'
463
      if Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->count();
463
464
464
    # we retrieve user rights on this itemtype and branchcode
465
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
465
    my $sth = $dbh->prepare(
466
        q{
467
         SELECT categorycode, itemtype, branchcode, reservesallowed
468
           FROM issuingrules
469
          WHERE (categorycode in (?,'*') )
470
            AND (itemtype IN (?,'*'))
471
            AND (branchcode IN (?,'*'))
472
       ORDER BY categorycode DESC,
473
                itemtype     DESC,
474
                branchcode   DESC
475
        }
476
    );
477
466
478
    my $querycount = q{
467
    my $querycount = q{
479
        SELECT count(*) AS count
468
        SELECT count(*) AS count
Lines 497-511 sub CanItemBeReserved { Link Here
497
    }
486
    }
498
487
499
    # we retrieve rights
488
    # we retrieve rights
500
    $sth->execute( $borrower->{'categorycode'}, $item->{'itype'}, $branchcode );
489
    if ( my $rights = GetHoldRule( $borrower->{'categorycode'}, $item->{'itype'}, $branchcode ) ) {
501
    if ( my $rights = $sth->fetchrow_hashref() ) {
490
        $ruleitemtype     = $rights->{itemtype};
502
        $ruleitemtype    = $rights->{itemtype};
491
        $allowedreserves  = $rights->{reservesallowed};
503
        $allowedreserves = $rights->{reservesallowed};
492
        $holds_per_record = $rights->{holds_per_record};
504
    }
493
    }
505
    else {
494
    else {
506
        $ruleitemtype = '*';
495
        $ruleitemtype = '*';
507
    }
496
    }
508
497
498
    my $item = Koha::Items->find( $itemnumber );
499
    my $holds = Koha::Holds->search(
500
        {
501
            borrowernumber => $borrowernumber,
502
            biblionumber   => $item->biblionumber,
503
            found          => undef, # Found holds don't count against a patron's holds limit
504
        }
505
    );
506
    if ( $holds->count() >= $holds_per_record ) {
507
        return "tooManyHoldsForThisRecord";
508
    }
509
509
    # we retrieve count
510
    # we retrieve count
510
511
511
    $querycount .= "AND $branchfield = ?";
512
    $querycount .= "AND $branchfield = ?";
Lines 734-741 sub GetReservesToBranch { Link Here
734
    my $dbh = C4::Context->dbh;
735
    my $dbh = C4::Context->dbh;
735
    my $sth = $dbh->prepare(
736
    my $sth = $dbh->prepare(
736
        "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
737
        "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
737
         FROM reserves 
738
         FROM reserves
738
         WHERE priority='0' 
739
         WHERE priority='0'
739
           AND branchcode=?"
740
           AND branchcode=?"
740
    );
741
    );
741
    $sth->execute( $frombranch );
742
    $sth->execute( $frombranch );
Lines 760-766 sub GetReservesForBranch { Link Here
760
761
761
    my $query = "
762
    my $query = "
762
        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
763
        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
763
        FROM   reserves 
764
        FROM   reserves
764
        WHERE   priority='0'
765
        WHERE   priority='0'
765
        AND found='W'
766
        AND found='W'
766
    ";
767
    ";
Lines 1377-1383 sub ModReserveMinusPriority { Link Here
1377
    my $dbh   = C4::Context->dbh;
1378
    my $dbh   = C4::Context->dbh;
1378
    my $query = "
1379
    my $query = "
1379
        UPDATE reserves
1380
        UPDATE reserves
1380
        SET    priority = 0 , itemnumber = ? 
1381
        SET    priority = 0 , itemnumber = ?
1381
        WHERE  reserve_id = ?
1382
        WHERE  reserve_id = ?
1382
    ";
1383
    ";
1383
    my $sth_upd = $dbh->prepare($query);
1384
    my $sth_upd = $dbh->prepare($query);
Lines 1592-1598 sub ToggleLowestPriority { Link Here
1592
1593
1593
    my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1594
    my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1594
    $sth->execute( $reserve_id );
1595
    $sth->execute( $reserve_id );
1595
    
1596
1596
    _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1597
    _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1597
}
1598
}
1598
1599
Lines 1802-1808 sub _FixPriority { Link Here
1802
            $priority[$j]->{'reserve_id'}
1803
            $priority[$j]->{'reserve_id'}
1803
        );
1804
        );
1804
    }
1805
    }
1805
    
1806
1806
    $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1807
    $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1807
    $sth->execute();
1808
    $sth->execute();
1808
1809
Lines 2017-2023 sub _koha_notify_reserve { Link Here
2017
    if (! $notification_sent) {
2018
    if (! $notification_sent) {
2018
        &$send_notification('print', 'HOLD');
2019
        &$send_notification('print', 'HOLD');
2019
    }
2020
    }
2020
    
2021
2021
}
2022
}
2022
2023
2023
=head2 _ShiftPriorityByDateAndPriority
2024
=head2 _ShiftPriorityByDateAndPriority
Lines 2435-2440 sub IsItemOnHoldAndFound { Link Here
2435
    return $found;
2436
    return $found;
2436
}
2437
}
2437
2438
2439
=head2 GetMaxPatronHoldsForRecord
2440
2441
my $holds_per_record = ReservesControlBranch( $borrowernumber, $biblionumber );
2442
2443
For multiple holds on a given record for a given patron, the max
2444
number of record level holds that a patron can be placed is the highest
2445
value of the holds_per_record rule for each item if the record for that
2446
patron. This subroutine finds and returns the highest holds_per_record
2447
rule value for a given patron id and record id.
2448
2449
=cut
2450
2451
sub GetMaxPatronHoldsForRecord {
2452
    my ( $borrowernumber, $biblionumber ) = @_;
2453
2454
    my $patron = Koha::Borrowers->find($borrowernumber);
2455
    my @items = Koha::Items->search( { biblionumber => $biblionumber } );
2456
2457
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
2458
2459
    my $categorycode = $patron->categorycode;
2460
    my $branchcode;
2461
    $branchcode = $patron->branchcode if ( $controlbranch eq "PatronLibrary" );
2462
2463
    my $max = 0;
2464
    foreach my $item (@items) {
2465
        my $itemtype = $item->effective_itemtype();
2466
2467
        $branchcode = $item->homebranch if ( $controlbranch eq "ItemHomeLibrary" );
2468
2469
        my $rule = GetHoldRule( $categorycode, $itemtype, $branchcode );
2470
        my $holds_per_record = $rule ? $rule->{holds_per_record} : 0;
2471
        $max = $holds_per_record if $holds_per_record > $max;
2472
    }
2473
2474
    return $max;
2475
}
2476
2477
=head2 GetHoldRule
2478
2479
my $rule = GetHoldRule( $categorycode, $itemtype, $branchcode );
2480
2481
Returns the matching hold related issuingrule fields for a given
2482
patron category, itemtype, and library.
2483
2484
=cut
2485
2486
sub GetHoldRule {
2487
    my ( $categorycode, $itemtype, $branchcode ) = @_;
2488
2489
    my $dbh = C4::Context->dbh;
2490
2491
    my $sth = $dbh->prepare(
2492
        q{
2493
         SELECT categorycode, itemtype, branchcode, reservesallowed, holds_per_record
2494
           FROM issuingrules
2495
          WHERE (categorycode in (?,'*') )
2496
            AND (itemtype IN (?,'*'))
2497
            AND (branchcode IN (?,'*'))
2498
       ORDER BY categorycode DESC,
2499
                itemtype     DESC,
2500
                branchcode   DESC
2501
        }
2502
    );
2503
2504
    $sth->execute( $categorycode, $itemtype, $branchcode );
2505
2506
    return $sth->fetchrow_hashref();
2507
}
2508
2438
=head1 AUTHOR
2509
=head1 AUTHOR
2439
2510
2440
Koha Development Team <http://koha-community.org/>
2511
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 61-109 $(document).ready(function() { Link Here
61
});
61
});
62
62
63
function check() {
63
function check() {
64
	var msg = "";
64
    var msg = "";
65
	var count_reserv = 0;
65
    var count_reserv = 0;
66
	var alreadyreserved = 0;
67
66
68
    // check if we have checkitem form
67
    // check if we have checkitem form
69
    if (document.form.checkitem){
68
    if (document.form.checkitem){
70
        for (i=0;i<document.form.checkitem.length;i++){
69
        for (i=0;i<document.form.checkitem.length;i++){
71
            if (document.form.checkitem[i].checked == true) {
70
            if (document.form.checkitem[i].checked == true) {
72
				count_reserv++ ;
71
                count_reserv++ ;
73
			}
72
            }
74
        }
73
        }
75
        // for only one item, check the checkitem without consider the loop checkitem
74
        // for only one item, check the checkitem without consider the loop checkitem
76
        if (i==0){
75
        if (i==0){
77
		    if (document.form.checkitem.checked == true) {
76
            if (document.form.checkitem.checked == true) {
78
			    count_reserv++;
77
                count_reserv++;
79
		    }
78
            }
80
	    }
79
        }
81
    }
82
83
    if (document.form.request.checked == true){
84
		count_reserv++ ;
85
    }
80
    }
86
81
87
    if (document.form.alreadyreserved && document.form.alreadyreserved.value == "1"){
82
    if (document.form.requestany.checked == true){
88
		 alreadyreserved++ ;
83
        count_reserv++ ;
89
    }
84
    }
90
85
91
    if (count_reserv == "0"){
86
    if (count_reserv == "0"){
92
		msg += (_("- Please select an item to place a hold") + "\n");
87
        msg += (_("- Please select an item to place a hold") + "\n");
93
    }
94
    if (count_reserv >= "2"){
95
		msg += (_("- You may only place a hold on one item at a time") + "\n");
96
    }
88
    }
97
89
98
    if (alreadyreserved > "0"){
90
    if (msg == "") {
99
		msg += (_("- This patron had already placed a hold on this item") + "\n" + _("Please cancel the previous hold first") + "\n");
91
        $('#hold-request-form').preventDoubleFormSubmit();
92
        return(true);
93
    } else {
94
        alert(msg);
95
        return(false);
100
    }
96
    }
101
102
	if (msg == "") return(true);
103
	else	{
104
		alert(msg);
105
		return(false);
106
	}
107
}
97
}
108
98
109
function checkMultiHold() {
99
function checkMultiHold() {
Lines 129-134 function checkMultiHold() { Link Here
129
    $("#multi_hold_bibs").val(biblionumbers);
119
    $("#multi_hold_bibs").val(biblionumbers);
130
    $("#bad_bibs").val(badBibs);
120
    $("#bad_bibs").val(badBibs);
131
121
122
    $('#hold-request-form').preventDoubleFormSubmit();
123
132
    return true;
124
    return true;
133
}
125
}
134
126
Lines 176-182 function checkMultiHold() { Link Here
176
        $("#" + fieldID).val("");
168
        $("#" + fieldID).val("");
177
    });
169
    });
178
170
179
    $('#hold-request-form').preventDoubleFormSubmit();
180
171
181
[% UNLESS ( borrowernumber || borrowers || noitems ) %]
172
[% UNLESS ( borrowernumber || borrowers || noitems ) %]
182
    [% IF ( CircAutocompl ) %]
173
    [% IF ( CircAutocompl ) %]
Lines 307-313 function checkMultiHold() { Link Here
307
        [% IF ( exceeded_maxreserves ) %]
298
        [% IF ( exceeded_maxreserves ) %]
308
          <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>
299
          <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>
309
        [% ELSIF ( alreadypossession ) %]
300
        [% ELSIF ( alreadypossession ) %]
310
          <li> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>is already in possession</strong> of one item</li>
301
          <li> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>is already in possession</strong> of one item</lie
311
        [% ELSIF ( alreadyreserved ) %]
302
        [% ELSIF ( alreadyreserved ) %]
312
          <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>already has a hold</strong> on this item </li>
303
          <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% borrowernumber %]">[% borrowerfirstname %] [% borrowersurname %]</a> <strong>already has a hold</strong> on this item </li>
313
        [% ELSIF ( ageRestricted ) %]
304
        [% ELSIF ( ageRestricted ) %]
Lines 424-434 function checkMultiHold() { Link Here
424
	</li>
415
	</li>
425
416
426
        [% UNLESS ( multi_hold ) %]
417
        [% UNLESS ( multi_hold ) %]
427
          <li> <label for="requestany">Place a hold on the next available item </label>
418
          <li> <label for="requestany">Hold next available item </label>
428
               <input type="checkbox" id="requestany" name="request" checked="checked" value="Any" />
419
               [% IF force_hold_level == 'item' %]
420
                   <input type="checkbox" id="requestany" name="request" disabled="true" />
421
               [% ELSIF force_hold_level == 'record' %]
422
                   <input type="checkbox" id="requestany" checked="checked" value="Any" disabled="true"/>
423
                   <input type="hidden" name="request" value="Any"/>
424
               [% ELSE %]
425
                   <input type="checkbox" id="requestany" name="request" checked="checked" value="Any" />
426
                [% END %]
429
               <input type="hidden" name="biblioitem" value="[% biblioitemnumber %]" />
427
               <input type="hidden" name="biblioitem" value="[% biblioitemnumber %]" />
430
               <input type="hidden" name="alreadyreserved" value="[% alreadyreserved %]" />
428
               <input type="hidden" name="alreadyreserved" value="[% alreadyreserved %]" />
431
          </li>
429
          </li>
430
431
          [% IF max_holds_for_record > 1 %]
432
              [% SET count = 1 %]
433
              <li>
434
                   <label for="holds_to_place_count">Holds to place (count)</label>
435
                   <select name="holds_to_place_count" id="holds_to_place_count">
436
                   [% WHILE count <= max_holds_for_record %]
437
                        <option value="[% count %]">[% count %]</option>
438
                        [% SET count = count + 1 %]
439
                   [% END %]
440
441
                   </select>
442
              </li>
443
            [% ELSE %]
444
                <input type="hidden" name="holds_to_place_count" value="1";
445
            [% END %]
432
        [% END %]
446
        [% END %]
433
447
434
</ol>
448
</ol>
Lines 453-459 function checkMultiHold() { Link Here
453
            [% IF ( bibitemloo.publicationyear ) %]<li><span class="label">Publication year:</span> [% bibitemloo.publicationyear %]</li>[% END %]
467
            [% IF ( bibitemloo.publicationyear ) %]<li><span class="label">Publication year:</span> [% bibitemloo.publicationyear %]</li>[% END %]
454
          </ol>
468
          </ol>
455
469
456
        <h2 style="padding: 0 1em;">Place a hold on a specific item</h2>
470
        <h2 style="padding: 0 1em;">
471
            Place a hold on a specific item
472
            [% IF bibitemloo.force_hold_level == 'item' %]
473
                <span class="error"><i>(Required)</i></span>
474
            [% END %]
475
        </h2>
457
        <table id="requestspecific">
476
        <table id="requestspecific">
458
            <thead>
477
            <thead>
459
                <tr>
478
                <tr>
Lines 473-490 function checkMultiHold() { Link Here
473
                </tr>
492
                </tr>
474
            </thead>
493
            </thead>
475
            <tbody>
494
            <tbody>
495
            [% SET selected = 0 %]
476
            [% FOREACH itemloo IN bibitemloo.itemloop %]
496
            [% FOREACH itemloo IN bibitemloo.itemloop %]
477
            [% UNLESS ( itemloo.hide ) %]
497
            [% UNLESS ( itemloo.hide ) %]
478
                <tr class="[% itemloo.backgroundcolor %]">
498
                <tr class="[% itemloo.backgroundcolor %]">
479
                    <td>
499
                    <td>
480
                [% IF ( itemloo.available ) %]
500
                [% IF itemloo.force_hold_level == 'record' # Patron has placed a record level hold previously for this record %]
501
                    <span class="error">
502
                        <i class="fa fa-times fa-lg" alt="Cannot be put on hold"></i>
503
                        Hold must be record level
504
                    </span>
505
                [% ELSIF ( itemloo.available ) %]
481
                    <input type="radio" name="checkitem" value="[% itemloo.itemnumber %]" />
506
                    <input type="radio" name="checkitem" value="[% itemloo.itemnumber %]" />
482
                [% ELSIF ( itemloo.override ) %]
507
                [% ELSIF ( itemloo.override ) %]
483
                    <input type="radio" name="checkitem" class="needsoverride" value="[% itemloo.itemnumber %]" />
508
                    <input type="radio" name="checkitem" class="needsoverride" value="[% itemloo.itemnumber %]" />
484
                    <img src="[% interface %]/[% theme %]/img/famfamfam/silk/error.png" alt="Requires override of hold policy" />
509
                    <i class="fa fa-exclamation-triangle fa-lg" style="color:gold" alt="Requires override of hold policy"/></i>
485
                [% ELSE %]
510
                [% ELSE %]
486
                    <input disabled="disabled" type="radio" name="checkitem" value="[% itemloo.itemnumber %]" />
511
                    <span class="error">
487
                    <img src="[% interface %]/[% theme %]/img/famfamfam/silk/cross.png" alt="Cannot be put on hold" />
512
                        <i class="fa fa-times fa-lg" alt="Cannot be put on hold"></i>
513
                        [% IF itemloo.not_holdable %]
514
                            [% IF itemloo.not_holdable == 'damaged' %]
515
                                Item damaged
516
                            [% ELSIF itemloo.not_holdable == 'ageRestricted' %]
517
                                Age restricted
518
                            [% ELSIF itemloo.not_holdable == 'tooManyHoldsForThisRecord' %]
519
                                Exceeded max holds per record
520
                            [% ELSIF itemloo.not_holdable == 'tooManyReserves' %]
521
                                Too many holds
522
                            [% ELSIF itemloo.not_holdable == 'notReservable' %]
523
                                Not holdable
524
                            [% ELSIF itemloo.not_holdable == 'cannotReserveFromOtherBranches' %]
525
                                Patron is from different library
526
                            [% ELSIF itemloo.not_holdable == 'itemAlreadyOnHold' %]
527
                                Patron already has hold for this item
528
                            [% ELSE %]
529
                                [% itemloo.not_holdable %]
530
                            [% END %]
531
                        [% END %]
532
                    </span>
488
                [% END %]
533
                [% END %]
489
                    </td>
534
                    </td>
490
                [% IF ( item_level_itypes ) %]
535
                [% IF ( item_level_itypes ) %]
(-)a/opac/opac-reserve.pl (-20 / +24 lines)
Lines 352-368 unless ( $noreserves ) { Link Here
352
    }
352
    }
353
}
353
}
354
354
355
foreach my $res (@reserves) {
356
    foreach my $biblionumber (@biblionumbers) {
357
        if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) {
358
#            $template->param( message => 1 );
359
#            $noreserves = 1;
360
#            $template->param( already_reserved => 1 );
361
            $biblioDataHash{$biblionumber}->{already_reserved} = 1;
362
        }
363
    }
364
}
365
366
unless ($noreserves) {
355
unless ($noreserves) {
367
    $template->param( select_item_types => 1 );
356
    $template->param( select_item_types => 1 );
368
}
357
}
Lines 460-468 foreach my $biblioNum (@biblionumbers) { Link Here
460
449
461
        # the item could be reserved for this borrower vi a host record, flag this
450
        # the item could be reserved for this borrower vi a host record, flag this
462
        $reservedfor //= '';
451
        $reservedfor //= '';
463
        if ($reservedfor eq $borrowernumber){
464
            $itemLoopIter->{already_reserved} = 1;
465
        }
466
452
467
        if ( defined $reservedate ) {
453
        if ( defined $reservedate ) {
468
            $itemLoopIter->{backgroundcolor} = 'reserved';
454
            $itemLoopIter->{backgroundcolor} = 'reserved';
Lines 507-518 foreach my $biblioNum (@biblionumbers) { Link Here
507
            $itemLoopIter->{nocancel} = 1;
493
            $itemLoopIter->{nocancel} = 1;
508
        }
494
        }
509
495
510
	# if the items belongs to a host record, show link to host record
496
        # if the items belongs to a host record, show link to host record
511
	if ($itemInfo->{biblionumber} ne $biblioNum){
497
        if ( $itemInfo->{biblionumber} ne $biblioNum ) {
512
		$biblioLoopIter{hostitemsflag} = 1;
498
            $biblioLoopIter{hostitemsflag}    = 1;
513
		$itemLoopIter->{hostbiblionumber} = $itemInfo->{biblionumber};
499
            $itemLoopIter->{hostbiblionumber} = $itemInfo->{biblionumber};
514
		$itemLoopIter->{hosttitle} = GetBiblioData($itemInfo->{biblionumber})->{title};
500
            $itemLoopIter->{hosttitle}        = GetBiblioData( $itemInfo->{biblionumber} )->{title};
515
	}
501
        }
516
502
517
        # If there is no loan, return and transfer, we show a checkbox.
503
        # If there is no loan, return and transfer, we show a checkbox.
518
        $itemLoopIter->{notforloan} = $itemLoopIter->{notforloan} || 0;
504
        $itemLoopIter->{notforloan} = $itemLoopIter->{notforloan} || 0;
Lines 561-566 foreach my $biblioNum (@biblionumbers) { Link Here
561
547
562
    $biblioLoopIter{holdable} &&= CanBookBeReserved($borrowernumber,$biblioNum) eq 'OK';
548
    $biblioLoopIter{holdable} &&= CanBookBeReserved($borrowernumber,$biblioNum) eq 'OK';
563
549
550
    # For multiple holds per record, if a patron has previously placed a hold,
551
    # the patron can only place more holds of the same type. That is, if the
552
    # patron placed a record level hold, all the holds the patron places must
553
    # be record level. If the patron placed an item level hold, all holds
554
    # the patron places must be item level
555
    my $forced_hold_level = Koha::Holds->search(
556
        {
557
            borrowernumber => $borrowernumber,
558
            biblionumber   => $biblioNum,
559
            found          => undef,
560
        }
561
    )->forced_hold_level();
562
    if ($forced_hold_level) {
563
        $biblioLoopIter{force_hold}   = 1 if $forced_hold_level eq 'item';
564
        $biblioLoopIter{itemholdable} = 0 if $forced_hold_level eq 'record';
565
    }
566
567
564
    push @$biblioLoop, \%biblioLoopIter;
568
    push @$biblioLoop, \%biblioLoopIter;
565
569
566
    $anyholdable = 1 if $biblioLoopIter{holdable};
570
    $anyholdable = 1 if $biblioLoopIter{holdable};
(-)a/reserve/placerequest.pl (-31 / +38 lines)
Lines 54-59 my $expirationdate = $input->param('expiration_date'); Link Here
54
my $multi_hold = $input->param('multi_hold');
54
my $multi_hold = $input->param('multi_hold');
55
my $biblionumbers = $multi_hold ? $input->param('biblionumbers') : ($biblionumber . '/');
55
my $biblionumbers = $multi_hold ? $input->param('biblionumbers') : ($biblionumber . '/');
56
my $bad_bibs = $input->param('bad_bibs');
56
my $bad_bibs = $input->param('bad_bibs');
57
my $holds_to_place_count = $input->param('holds_to_place_count') || 1;
57
58
58
my %bibinfos = ();
59
my %bibinfos = ();
59
my @biblionumbers = split '/', $biblionumbers;
60
my @biblionumbers = split '/', $biblionumbers;
Lines 77-115 if (defined $checkitem && $checkitem ne ''){ Link Here
77
    }
78
    }
78
}
79
}
79
80
80
if ($type eq 'str8' && $borrower){
81
if ( $type eq 'str8' && $borrower ) {
81
82
82
    foreach my $biblionumber (keys %bibinfos) {
83
    foreach my $biblionumber ( keys %bibinfos ) {
83
        my $count=@bibitems;
84
        my $count = @bibitems;
84
        @bibitems=sort @bibitems;
85
        @bibitems = sort @bibitems;
85
        my $i2=1;
86
        my $i2 = 1;
86
        my @realbi;
87
        my @realbi;
87
        $realbi[0]=$bibitems[0];
88
        $realbi[0] = $bibitems[0];
88
        for (my $i=1;$i<$count;$i++) {
89
        for ( my $i = 1 ; $i < $count ; $i++ ) {
89
            my $i3=$i2-1;
90
            my $i3 = $i2 - 1;
90
            if ($realbi[$i3] ne $bibitems[$i]) {
91
            if ( $realbi[$i3] ne $bibitems[$i] ) {
91
                $realbi[$i2]=$bibitems[$i];
92
                $realbi[$i2] = $bibitems[$i];
92
                $i2++;
93
                $i2++;
93
            }
94
            }
94
        }
95
        }
95
        my $const;
96
97
    if (defined $checkitem && $checkitem ne ''){
98
		my $item = GetItem($checkitem);
99
        	if ($item->{'biblionumber'} ne $biblionumber) {
100
                	$biblionumber = $item->{'biblionumber'};
101
        	}
102
	}
103
104
96
97
        if ( defined $checkitem && $checkitem ne '' ) {
98
            my $item = GetItem($checkitem);
99
            if ( $item->{'biblionumber'} ne $biblionumber ) {
100
                $biblionumber = $item->{'biblionumber'};
101
            }
102
        }
105
103
106
        if ($multi_hold) {
104
        if ($multi_hold) {
107
            my $bibinfo = $bibinfos{$biblionumber};
105
            my $bibinfo = $bibinfos{$biblionumber};
108
            AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,[$biblionumber],
106
            AddReserve( $branch, $borrower->{'borrowernumber'},
109
                       $bibinfo->{rank},$startdate,$expirationdate,$notes,$bibinfo->{title},$checkitem,$found);
107
                $biblionumber, [$biblionumber],
110
        } else {
108
                $bibinfo->{rank}, $startdate, $expirationdate, $notes, $bibinfo->{title}, $checkitem,
111
            # place a request on 1st available
109
                $found );
112
            AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,\@realbi,$rank[0],$startdate,$expirationdate,$notes,$title,$checkitem,$found);
110
        }
111
        else {
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 );
116
            }
113
        }
117
        }
114
    }
118
    }
115
119
Lines 118-130 if ($type eq 'str8' && $borrower){ Link Here
118
            $biblionumbers .= $bad_bibs;
122
            $biblionumbers .= $bad_bibs;
119
        }
123
        }
120
        print $input->redirect("request.pl?biblionumbers=$biblionumbers&multi_hold=1");
124
        print $input->redirect("request.pl?biblionumbers=$biblionumbers&multi_hold=1");
121
    } else {
125
    }
126
    else {
122
        print $input->redirect("request.pl?biblionumber=$biblionumber");
127
        print $input->redirect("request.pl?biblionumber=$biblionumber");
123
    }
128
    }
124
} elsif ($borrower eq ''){
129
}
125
	print $input->header();
130
elsif ( $borrower eq '' ) {
126
	print "Invalid borrower number please try again";
131
    print $input->header();
127
# Not sure that Dump() does HTML escaping. Use firebug or something to trace
132
    print "Invalid borrower number please try again";
128
# instead.
133
129
#	print $input->Dump;
134
    # Not sure that Dump() does HTML escaping. Use firebug or something to trace
135
    # instead.
136
    #	print $input->Dump;
130
}
137
}
(-)a/reserve/request.pl (-34 / +44 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 234-271 foreach my $biblionumber (@biblionumbers) { Link Here
234
        $biblioloopiter{$canReserve} = 1;
234
        $biblioloopiter{$canReserve} = 1;
235
    }
235
    }
236
236
237
    my $alreadypossession;
237
    my $force_hold_level;
238
    if (not C4::Context->preference('AllowHoldsOnPatronsPossessions') and CheckIfIssuedToPatron($borrowerinfo->{borrowernumber},$biblionumber)) {
238
    if ( $borrowerinfo->{borrowernumber} ) {
239
        $alreadypossession = 1;
239
        # For multiple holds per record, if a patron has previously placed a hold,
240
        # the patron can only place more holds of the same type. That is, if the
241
        # patron placed a record level hold, all the holds the patron places must
242
        # be record level. If the patron placed an item level hold, all holds
243
        # the patron places must be item level
244
        my $holds = Koha::Holds->search(
245
            {
246
                borrowernumber => $borrowerinfo->{borrowernumber},
247
                biblionumber   => $biblionumber,
248
                found          => undef,
249
            }
250
        );
251
        $force_hold_level = $holds->forced_hold_level();
252
        $biblioloopiter{force_hold_level} = $force_hold_level;
253
        $template->param( force_hold_level => $force_hold_level );
254
255
        # For a librarian to be able to place multiple record holds for a patron for a record,
256
        # we must find out what the maximum number of holds they can place for the patron is
257
        my $max_holds_for_record = GetMaxPatronHoldsForRecord( $borrowerinfo->{borrowernumber}, $biblionumber );
258
        $max_holds_for_record = $max_holds_for_record - $holds->count();
259
        $biblioloopiter{max_holds_for_record} = $max_holds_for_record;
260
        $template->param( max_holds_for_record => $max_holds_for_record );
240
    }
261
    }
241
262
242
    # get existing reserves .....
263
    # Check to see if patron is allowed to place holds on records where the
243
    my $reserves = GetReservesFromBiblionumber({ biblionumber => $biblionumber, all_dates => 1 });
264
    # patron already has an item from that record checked out
244
    my $count = scalar( @$reserves );
265
    my $alreadypossession;
245
    my $totalcount = $count;
266
    if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions')
246
    my $holds_count = 0;
267
        && CheckIfIssuedToPatron( $borrowerinfo->{borrowernumber}, $biblionumber ) )
247
    my $alreadyreserved = 0;
268
    {
248
269
        $template->param( alreadypossession => $alreadypossession, );
249
    foreach my $res (@$reserves) {
250
        if ( defined $res->{found} ) { # found can be 'W' or 'T'
251
            $count--;
252
        }
253
254
        if ( defined $borrowerinfo && defined($borrowerinfo->{borrowernumber}) && ($borrowerinfo->{borrowernumber} eq $res->{borrowernumber}) ) {
255
            $holds_count++;
256
        }
257
    }
270
    }
258
271
259
    if ( $holds_count ) {
260
            $alreadyreserved = 1;
261
            $biblioloopiter{warn} = 1;
262
            $biblioloopiter{alreadyres} = 1;
263
    }
264
272
265
    $template->param(
273
    my $count = Koha::Holds->search( { biblionumber => $biblionumber } )->count();
266
        alreadyreserved => $alreadyreserved,
274
    my $totalcount = $count;
267
        alreadypossession => $alreadypossession,
268
    );
269
275
270
    # FIXME think @optionloop, is maybe obsolete, or  must be switchable by a systeme preference fixed rank or not
276
    # FIXME think @optionloop, is maybe obsolete, or  must be switchable by a systeme preference fixed rank or not
271
    # make priorities options
277
    # make priorities options
Lines 328-333 foreach my $biblionumber (@biblionumbers) { Link Here
328
        my $num_override  = 0;
334
        my $num_override  = 0;
329
        my $hiddencount   = 0;
335
        my $hiddencount   = 0;
330
336
337
        $biblioitem->{force_hold_level} = $force_hold_level;
338
331
        if ( $biblioitem->{biblioitemnumber} ne $biblionumber ) {
339
        if ( $biblioitem->{biblioitemnumber} ne $biblionumber ) {
332
            $biblioitem->{hostitemsflag} = 1;
340
            $biblioitem->{hostitemsflag} = 1;
333
        }
341
        }
Lines 347-352 foreach my $biblionumber (@biblionumbers) { Link Here
347
        foreach my $itemnumber ( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} } )    {
355
        foreach my $itemnumber ( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} } )    {
348
            my $item = $iteminfos_of->{$itemnumber};
356
            my $item = $iteminfos_of->{$itemnumber};
349
357
358
            $item->{force_hold_level} = $force_hold_level;
359
350
            unless (C4::Context->preference('item-level_itypes')) {
360
            unless (C4::Context->preference('item-level_itypes')) {
351
                $item->{itype} = $biblioitem->{itemtype};
361
                $item->{itype} = $biblioitem->{itemtype};
352
            }
362
            }
Lines 444-456 foreach my $biblionumber (@biblionumbers) { Link Here
444
454
445
            $item->{'holdallowed'} = $branchitemrule->{'holdallowed'};
455
            $item->{'holdallowed'} = $branchitemrule->{'holdallowed'};
446
456
457
            my $can_item_be_reserved = CanItemBeReserved( $borrowerinfo->{borrowernumber}, $itemnumber );
458
            $item->{not_holdable} = $can_item_be_reserved unless ( $can_item_be_reserved eq 'OK' );
459
447
            if (
460
            if (
448
                   !$item->{cantreserve}
461
                   !$item->{cantreserve}
449
                && !$exceeded_maxreserves
462
                && !$exceeded_maxreserves
450
                && IsAvailableForItemLevelRequest($item, $borrowerinfo)
463
                && IsAvailableForItemLevelRequest($item, $borrowerinfo)
451
                && CanItemBeReserved(
464
                && $can_item_be_reserved eq 'OK'
452
                    $borrowerinfo->{borrowernumber}, $itemnumber
453
                ) eq 'OK'
454
              )
465
              )
455
            {
466
            {
456
                $item->{available} = 1;
467
                $item->{available} = 1;
Lines 486-492 foreach my $biblionumber (@biblionumbers) { Link Here
486
497
487
    # existingreserves building
498
    # existingreserves building
488
    my @reserveloop;
499
    my @reserveloop;
489
    $reserves = GetReservesFromBiblionumber({ biblionumber => $biblionumber, all_dates => 1 });
500
    my $reserves = GetReservesFromBiblionumber({ biblionumber => $biblionumber, all_dates => 1 });
490
    foreach my $res ( sort {
501
    foreach my $res ( sort {
491
            my $a_found = $a->{found} || '';
502
            my $a_found = $a->{found} || '';
492
            my $b_found = $a->{found} || '';
503
            my $b_found = $a->{found} || '';
493
- 

Return to bug 14695