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

(-)a/C4/Reserves.pm (-33 / +104 lines)
Lines 44-49 use Koha::Holds; Link Here
44
use Koha::Libraries;
44
use Koha::Libraries;
45
use Koha::Items;
45
use Koha::Items;
46
use Koha::ItemTypes;
46
use Koha::ItemTypes;
47
use Koha::Patrons;
47
48
48
use List::MoreUtils qw( firstidx any );
49
use List::MoreUtils qw( firstidx any );
49
use Carp;
50
use Carp;
Lines 143-148 BEGIN { Link Here
143
        &GetReservesControlBranch
144
        &GetReservesControlBranch
144
145
145
        IsItemOnHoldAndFound
146
        IsItemOnHoldAndFound
147
148
        GetMaxPatronHoldsForRecord
146
    );
149
    );
147
    @EXPORT_OK = qw( MergeHolds );
150
    @EXPORT_OK = qw( MergeHolds );
148
}
151
}
Lines 170-181 sub AddReserve { Link Here
170
        $title,    $checkitem,      $found,        $itemtype
173
        $title,    $checkitem,      $found,        $itemtype
171
    ) = @_;
174
    ) = @_;
172
175
173
    if ( Koha::Holds->search( { borrowernumber => $borrowernumber, biblionumber => $biblionumber } )->count() > 0 ) {
176
    my $dbh = C4::Context->dbh;
174
        carp("AddReserve: borrower $borrowernumber already has a hold for biblionumber $biblionumber");
175
        return;
176
    }
177
178
    my $dbh     = C4::Context->dbh;
179
177
180
    $resdate = output_pref( { str => dt_from_string( $resdate ), dateonly => 1, dateformat => 'iso' })
178
    $resdate = output_pref( { str => dt_from_string( $resdate ), dateonly => 1, dateformat => 'iso' })
181
        or output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
179
        or output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
Lines 464-470 sub CanItemBeReserved { Link Here
464
462
465
    my $dbh = C4::Context->dbh;
463
    my $dbh = C4::Context->dbh;
466
    my $ruleitemtype;    # itemtype of the matching issuing rule
464
    my $ruleitemtype;    # itemtype of the matching issuing rule
467
    my $allowedreserves = 0;
465
    my $allowedreserves  = 0; # Total number of holds allowed across all records
466
    my $holds_per_record = 1; # Total number of holds allowed for this one given record
468
467
469
    # we retrieve borrowers and items informations #
468
    # we retrieve borrowers and items informations #
470
    # item->{itype} will come for biblioitems if necessery
469
    # item->{itype} will come for biblioitems if necessery
Lines 477-502 sub CanItemBeReserved { Link Here
477
      if ( $item->{damaged}
476
      if ( $item->{damaged}
478
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
477
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
479
478
480
    #Check for the age restriction
479
    # Check for the age restriction
481
    my ( $ageRestriction, $daysToAgeRestriction ) =
480
    my ( $ageRestriction, $daysToAgeRestriction ) =
482
      C4::Circulation::GetAgeRestriction( $biblioData->{agerestriction}, $borrower );
481
      C4::Circulation::GetAgeRestriction( $biblioData->{agerestriction}, $borrower );
483
    return 'ageRestricted' if $daysToAgeRestriction && $daysToAgeRestriction > 0;
482
    return 'ageRestricted' if $daysToAgeRestriction && $daysToAgeRestriction > 0;
484
483
485
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
484
    # Check that the patron doesn't have an item level hold on this item already
485
    return 'itemAlreadyOnHold'
486
      if Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->count();
486
487
487
    # we retrieve user rights on this itemtype and branchcode
488
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
488
    my $sth = $dbh->prepare(
489
        q{
490
         SELECT categorycode, itemtype, branchcode, reservesallowed
491
           FROM issuingrules
492
          WHERE (categorycode in (?,'*') )
493
            AND (itemtype IN (?,'*'))
494
            AND (branchcode IN (?,'*'))
495
       ORDER BY categorycode DESC,
496
                itemtype     DESC,
497
                branchcode   DESC
498
        }
499
    );
500
489
501
    my $querycount = q{
490
    my $querycount = q{
502
        SELECT count(*) AS count
491
        SELECT count(*) AS count
Lines 520-534 sub CanItemBeReserved { Link Here
520
    }
509
    }
521
510
522
    # we retrieve rights
511
    # we retrieve rights
523
    $sth->execute( $borrower->{'categorycode'}, $item->{'itype'}, $branchcode );
512
    if ( my $rights = GetHoldRule( $borrower->{'categorycode'}, $item->{'itype'}, $branchcode ) ) {
524
    if ( my $rights = $sth->fetchrow_hashref() ) {
513
        $ruleitemtype     = $rights->{itemtype};
525
        $ruleitemtype    = $rights->{itemtype};
514
        $allowedreserves  = $rights->{reservesallowed};
526
        $allowedreserves = $rights->{reservesallowed};
515
        $holds_per_record = $rights->{holds_per_record};
527
    }
516
    }
528
    else {
517
    else {
529
        $ruleitemtype = '*';
518
        $ruleitemtype = '*';
530
    }
519
    }
531
520
521
    my $item = Koha::Items->find( $itemnumber );
522
    my $holds = Koha::Holds->search(
523
        {
524
            borrowernumber => $borrowernumber,
525
            biblionumber   => $item->biblionumber,
526
            found          => undef, # Found holds don't count against a patron's holds limit
527
        }
528
    );
529
    if ( $holds->count() >= $holds_per_record ) {
530
        return "tooManyHoldsForThisRecord";
531
    }
532
532
    # we retrieve count
533
    # we retrieve count
533
534
534
    $querycount .= "AND $branchfield = ?";
535
    $querycount .= "AND $branchfield = ?";
Lines 758-765 sub GetReservesToBranch { Link Here
758
    my $dbh = C4::Context->dbh;
759
    my $dbh = C4::Context->dbh;
759
    my $sth = $dbh->prepare(
760
    my $sth = $dbh->prepare(
760
        "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
761
        "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
761
         FROM reserves 
762
         FROM reserves
762
         WHERE priority='0' 
763
         WHERE priority='0'
763
           AND branchcode=?"
764
           AND branchcode=?"
764
    );
765
    );
765
    $sth->execute( $frombranch );
766
    $sth->execute( $frombranch );
Lines 784-790 sub GetReservesForBranch { Link Here
784
785
785
    my $query = "
786
    my $query = "
786
        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
787
        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
787
        FROM   reserves 
788
        FROM   reserves
788
        WHERE   priority='0'
789
        WHERE   priority='0'
789
        AND found='W'
790
        AND found='W'
790
    ";
791
    ";
Lines 1414-1420 sub ModReserveMinusPriority { Link Here
1414
    my $dbh   = C4::Context->dbh;
1415
    my $dbh   = C4::Context->dbh;
1415
    my $query = "
1416
    my $query = "
1416
        UPDATE reserves
1417
        UPDATE reserves
1417
        SET    priority = 0 , itemnumber = ? 
1418
        SET    priority = 0 , itemnumber = ?
1418
        WHERE  reserve_id = ?
1419
        WHERE  reserve_id = ?
1419
    ";
1420
    ";
1420
    my $sth_upd = $dbh->prepare($query);
1421
    my $sth_upd = $dbh->prepare($query);
Lines 1651-1657 sub ToggleLowestPriority { Link Here
1651
1652
1652
    my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1653
    my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1653
    $sth->execute( $reserve_id );
1654
    $sth->execute( $reserve_id );
1654
    
1655
1655
    _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1656
    _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1656
}
1657
}
1657
1658
Lines 1835-1841 sub _FixPriority { Link Here
1835
            $priority[$j]->{'reserve_id'}
1836
            $priority[$j]->{'reserve_id'}
1836
        );
1837
        );
1837
    }
1838
    }
1838
    
1839
1839
    $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1840
    $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1840
    $sth->execute();
1841
    $sth->execute();
1841
1842
Lines 2073-2079 sub _koha_notify_reserve { Link Here
2073
    if (! $notification_sent) {
2074
    if (! $notification_sent) {
2074
        &$send_notification('print', 'HOLD');
2075
        &$send_notification('print', 'HOLD');
2075
    }
2076
    }
2076
    
2077
2077
}
2078
}
2078
2079
2079
=head2 _ShiftPriorityByDateAndPriority
2080
=head2 _ShiftPriorityByDateAndPriority
Lines 2502-2507 sub IsItemOnHoldAndFound { Link Here
2502
    return $found;
2503
    return $found;
2503
}
2504
}
2504
2505
2506
=head2 GetMaxPatronHoldsForRecord
2507
2508
my $holds_per_record = ReservesControlBranch( $borrowernumber, $biblionumber );
2509
2510
For multiple holds on a given record for a given patron, the max
2511
number of record level holds that a patron can be placed is the highest
2512
value of the holds_per_record rule for each item if the record for that
2513
patron. This subroutine finds and returns the highest holds_per_record
2514
rule value for a given patron id and record id.
2515
2516
=cut
2517
2518
sub GetMaxPatronHoldsForRecord {
2519
    my ( $borrowernumber, $biblionumber ) = @_;
2520
2521
    my $patron = Koha::Patrons->find($borrowernumber);
2522
    my @items = Koha::Items->search( { biblionumber => $biblionumber } );
2523
2524
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
2525
2526
    my $categorycode = $patron->categorycode;
2527
    my $branchcode;
2528
    $branchcode = $patron->branchcode if ( $controlbranch eq "PatronLibrary" );
2529
2530
    my $max = 0;
2531
    foreach my $item (@items) {
2532
        my $itemtype = $item->effective_itemtype();
2533
2534
        $branchcode = $item->homebranch if ( $controlbranch eq "ItemHomeLibrary" );
2535
2536
        my $rule = GetHoldRule( $categorycode, $itemtype, $branchcode );
2537
        my $holds_per_record = $rule ? $rule->{holds_per_record} : 0;
2538
        $max = $holds_per_record if $holds_per_record > $max;
2539
    }
2540
2541
    return $max;
2542
}
2543
2544
=head2 GetHoldRule
2545
2546
my $rule = GetHoldRule( $categorycode, $itemtype, $branchcode );
2547
2548
Returns the matching hold related issuingrule fields for a given
2549
patron category, itemtype, and library.
2550
2551
=cut
2552
2553
sub GetHoldRule {
2554
    my ( $categorycode, $itemtype, $branchcode ) = @_;
2555
2556
    my $dbh = C4::Context->dbh;
2557
2558
    my $sth = $dbh->prepare(
2559
        q{
2560
         SELECT categorycode, itemtype, branchcode, reservesallowed, holds_per_record
2561
           FROM issuingrules
2562
          WHERE (categorycode in (?,'*') )
2563
            AND (itemtype IN (?,'*'))
2564
            AND (branchcode IN (?,'*'))
2565
       ORDER BY categorycode DESC,
2566
                itemtype     DESC,
2567
                branchcode   DESC
2568
        }
2569
    );
2570
2571
    $sth->execute( $categorycode, $itemtype, $branchcode );
2572
2573
    return $sth->fetchrow_hashref();
2574
}
2575
2505
=head1 AUTHOR
2576
=head1 AUTHOR
2506
2577
2507
Koha Development Team <http://koha-community.org/>
2578
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