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

(-)a/C4/Reserves.pm (-47 / +68 lines)
Lines 285-291 sub CanBookBeReserved{ Link Here
285
    my $canReserve = { status => '' };
285
    my $canReserve = { status => '' };
286
    foreach my $itemnumber (@itemnumbers) {
286
    foreach my $itemnumber (@itemnumbers) {
287
        $canReserve = CanItemBeReserved( $borrowernumber, $itemnumber, $pickup_branchcode );
287
        $canReserve = CanItemBeReserved( $borrowernumber, $itemnumber, $pickup_branchcode );
288
        return { status => 'OK' } if $canReserve->{status} eq 'OK';
288
        return $canReserve if $canReserve->{status} eq 'OK';
289
    }
289
    }
290
    return $canReserve;
290
    return $canReserve;
291
}
291
}
Lines 323-353 sub CanItemBeReserved { Link Here
323
    my $patron = Koha::Patrons->find( $borrowernumber );
323
    my $patron = Koha::Patrons->find( $borrowernumber );
324
    my $borrower = $patron->unblessed;
324
    my $borrower = $patron->unblessed;
325
325
326
    # If an item is damaged and we don't allow holds on damaged items, we can stop right here
327
    return { status =>'damaged' }
328
      if ( $item->damaged
329
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
330
331
    # Check for the age restriction
332
    my ( $ageRestriction, $daysToAgeRestriction ) =
333
      C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction, $borrower );
334
    return { status => 'ageRestricted' } if $daysToAgeRestriction && $daysToAgeRestriction > 0;
335
336
    # Check that the patron doesn't have an item level hold on this item already
337
    return { status =>'itemAlreadyOnHold' }
338
      if Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->count();
339
340
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
326
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
341
327
342
    my $querycount = q{
343
        SELECT count(*) AS count
344
          FROM reserves
345
     LEFT JOIN items USING (itemnumber)
346
     LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
347
     LEFT JOIN borrowers USING (borrowernumber)
348
         WHERE borrowernumber = ?
349
    };
350
351
    my $branchcode  = "";
328
    my $branchcode  = "";
352
    my $branchfield = "reserves.branchcode";
329
    my $branchfield = "reserves.branchcode";
353
330
Lines 361-376 sub CanItemBeReserved { Link Here
361
    }
338
    }
362
339
363
    # we retrieve rights
340
    # we retrieve rights
364
    if ( my $rights = GetHoldRule( $borrower->{'categorycode'}, $item->effective_itemtype, $branchcode ) ) {
341
    my $issuingrule;
365
        $ruleitemtype     = $rights->{itemtype};
342
    if ( $issuingrule = GetHoldRule( $borrower->{'categorycode'}, $item->effective_itemtype, $branchcode ) ) {
366
        $allowedreserves  = $rights->{reservesallowed};
343
        $ruleitemtype     = $issuingrule->{itemtype};
367
        $holds_per_record = $rights->{holds_per_record};
344
        $allowedreserves  = $issuingrule->{reservesallowed};
368
        $holds_per_day    = $rights->{holds_per_day};
345
        $holds_per_record = $issuingrule->{holds_per_record};
346
        $holds_per_day    = $issuingrule->{holds_per_day};
369
    }
347
    }
370
    else {
348
    else {
371
        $ruleitemtype = '*';
349
        $ruleitemtype = '*';
372
    }
350
    }
373
351
352
    my $circulation_rule = Koha::CirculationRules->get_effective_rule(
353
        {
354
            categorycode => $borrower->{categorycode},
355
            branchcode   => $branchcode,
356
            rule_name    => 'max_holds',
357
        }
358
    );
359
360
    # If an item is damaged and we don't allow holds on damaged items, we can stop right here
361
    return { status =>'damaged', issuingrule => $issuingrule, circulation_rule => $circulation_rule }
362
      if ( $item->damaged
363
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
364
365
    # Check for the age restriction
366
    my ( $ageRestriction, $daysToAgeRestriction ) =
367
      C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction, $borrower );
368
    return { status => 'ageRestricted', issuingrule => $issuingrule, circulation_rule => $circulation_rule } if $daysToAgeRestriction && $daysToAgeRestriction > 0;
369
370
    # Check that the patron doesn't have an item level hold on this item already
371
    return { status =>'itemAlreadyOnHold', issuingrule => $issuingrule, circulation_rule => $circulation_rule }
372
      if Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->count();
373
374
    my $querycount = q{
375
        SELECT count(*) AS count
376
          FROM reserves
377
     LEFT JOIN items USING (itemnumber)
378
     LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
379
     LEFT JOIN borrowers USING (borrowernumber)
380
         WHERE borrowernumber = ?
381
    };
382
383
    $item = Koha::Items->find( $itemnumber );
374
    my $holds = Koha::Holds->search(
384
    my $holds = Koha::Holds->search(
375
        {
385
        {
376
            borrowernumber => $borrowernumber,
386
            borrowernumber => $borrowernumber,
Lines 379-385 sub CanItemBeReserved { Link Here
379
        }
389
        }
380
    );
390
    );
381
    if ( $holds->count() >= $holds_per_record ) {
391
    if ( $holds->count() >= $holds_per_record ) {
382
        return { status => "tooManyHoldsForThisRecord", limit => $holds_per_record };
392
        $issuingrule->{exceeded} = 'holds_per_record';
393
        return { status => "tooManyHoldsForThisRecord",
394
                 limit => $holds_per_record,
395
                 issuingrule => $issuingrule,
396
                 circulation_rule => $circulation_rule };
383
    }
397
    }
384
398
385
    my $today_holds = Koha::Holds->search({
399
    my $today_holds = Koha::Holds->search({
Lines 391-397 sub CanItemBeReserved { Link Here
391
          (   ( $holds_per_day > 0 && $today_holds->count() >= $holds_per_day )
405
          (   ( $holds_per_day > 0 && $today_holds->count() >= $holds_per_day )
392
           or ( $holds_per_day == 0 ) )
406
           or ( $holds_per_day == 0 ) )
393
        )  {
407
        )  {
394
        return { status => 'tooManyReservesToday', limit => $holds_per_day };
408
        $issuingrule->{exceeded} = 'holds_per_day';
409
        return { status => 'tooManyReservesToday',
410
                 limit => $holds_per_day,
411
                 issuingrule => $issuingrule,
412
                 circulation_rule => $circulation_rule };
395
    }
413
    }
396
414
397
    # we retrieve count
415
    # we retrieve count
Lines 422-446 sub CanItemBeReserved { Link Here
422
440
423
    # we check if it's ok or not
441
    # we check if it's ok or not
424
    if ( $reservecount >= $allowedreserves ) {
442
    if ( $reservecount >= $allowedreserves ) {
425
        return { status => 'tooManyReserves', limit => $allowedreserves };
443
        $issuingrule->{exceeded} = 'allowedreserves';
444
        return { status => 'tooManyReserves',
445
                 limit => $allowedreserves,
446
                 issuingrule => $issuingrule,
447
                 circulation_rule => $circulation_rule };
426
    }
448
    }
427
449
428
    # Now we need to check hold limits by patron category
450
    # Now we need to check hold limits by patron category
429
    my $rule = Koha::CirculationRules->get_effective_rule(
451
    if ( $circulation_rule && defined( $circulation_rule->rule_value ) && $circulation_rule->rule_value ne '' ) {
430
        {
431
            categorycode => $borrower->{categorycode},
432
            branchcode   => $branchcode,
433
            rule_name    => 'max_holds',
434
        }
435
    );
436
    if ( $rule && defined( $rule->rule_value ) && $rule->rule_value ne '' ) {
437
        my $total_holds_count = Koha::Holds->search(
452
        my $total_holds_count = Koha::Holds->search(
438
            {
453
            {
439
                borrowernumber => $borrower->{borrowernumber}
454
                borrowernumber => $borrower->{borrowernumber}
440
            }
455
            }
441
        )->count();
456
        )->count();
442
457
443
        return { status => 'tooManyReserves', limit => $rule->rule_value} if $total_holds_count >= $rule->rule_value;
458
        return {
459
            status => 'tooManyReserves',
460
            limit => $circulation_rule->rule_value,
461
            issuingrule => $issuingrule,
462
            circulation_rule => $circulation_rule,
463
            circulation_rule_exceeded => 1
464
        } if $total_holds_count >= $circulation_rule->rule_value;
444
    }
465
    }
445
466
446
    my $reserves_control_branch =
467
    my $reserves_control_branch =
Lines 449-461 sub CanItemBeReserved { Link Here
449
      C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->itype ); # FIXME Should not be item->effective_itemtype?
470
      C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->itype ); # FIXME Should not be item->effective_itemtype?
450
471
451
    if ( $branchitemrule->{holdallowed} == 0 ) {
472
    if ( $branchitemrule->{holdallowed} == 0 ) {
452
        return { status => 'notReservable' };
473
        return { status => 'notReservable', issuingrule => $issuingrule, circulation_rule => $circulation_rule };
453
    }
474
    }
454
475
455
    if (   $branchitemrule->{holdallowed} == 1
476
    if (   $branchitemrule->{holdallowed} == 1
456
        && $borrower->{branchcode} ne $item->homebranch )
477
        && $borrower->{branchcode} ne $item->homebranch )
457
    {
478
    {
458
        return { status => 'cannotReserveFromOtherBranches' };
479
        return { status => 'cannotReserveFromOtherBranches', issuingrule => $issuingrule, circulation_rule => $circulation_rule };
459
    }
480
    }
460
481
461
    # If reservecount is ok, we check item branch if IndependentBranches is ON
482
    # If reservecount is ok, we check item branch if IndependentBranches is ON
Lines 464-470 sub CanItemBeReserved { Link Here
464
        and !C4::Context->preference('canreservefromotherbranches') )
485
        and !C4::Context->preference('canreservefromotherbranches') )
465
    {
486
    {
466
        if ( $item->homebranch ne $borrower->{branchcode} ) {
487
        if ( $item->homebranch ne $borrower->{branchcode} ) {
467
            return { status => 'cannotReserveFromOtherBranches' };
488
            return { status => 'cannotReserveFromOtherBranches', issuingrule => $issuingrule, circulation_rule => $circulation_rule };
468
        }
489
        }
469
    }
490
    }
470
491
Lines 474-490 sub CanItemBeReserved { Link Here
474
        });
495
        });
475
496
476
        unless ($destination) {
497
        unless ($destination) {
477
            return { status => 'libraryNotFound' };
498
            return { status => 'libraryNotFound', issuingrule => $issuingrule, circulation_rule => $circulation_rule };
478
        }
499
        }
479
        unless ($destination->pickup_location) {
500
        unless ($destination->pickup_location) {
480
            return { status => 'libraryNotPickupLocation' };
501
            return { status => 'libraryNotPickupLocation', issuingrule => $issuingrule, circulation_rule => $circulation_rule };
481
        }
502
        }
482
        unless ($item->can_be_transferred({ to => $destination })) {
503
        unless ($item->can_be_transferred({ to => $destination })) {
483
            return { status => 'cannotBeTransferred' };
504
            return { status => 'cannotBeTransferred' };
484
        }
505
        }
485
    }
506
    }
486
507
487
    return { status => 'OK' };
508
    return { status => 'OK', issuingrule => $issuingrule, circulation_rule => $circulation_rule };
488
}
509
}
489
510
490
=head2 CanReserveBeCanceledFromOpac
511
=head2 CanReserveBeCanceledFromOpac
(-)a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss (+4 lines)
Lines 4334-4336 div#makechart ol li { Link Here
4334
        border-bottom-right-radius: 5px;
4334
        border-bottom-right-radius: 5px;
4335
    }
4335
    }
4336
}
4336
}
4337
4338
span.exceeded {
4339
    color: red;
4340
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-5 / +143 lines)
Lines 38-43 Link Here
38
38
39
<div class="main container-fluid">
39
<div class="main container-fluid">
40
    <div class="row">
40
    <div class="row">
41
42
        <div class="modal fade" id="TODOmyModal" tabindex="-1" role="dialog" aria-labelledby="TODOmyModalLabel">
43
            <div class="modal-dialog" role="document">
44
                <div class="modal-content">
45
                    <div class="modal-header">
46
                        <h4 class="modal-title" id="myModalLabel">Holding rules</h4>
47
                    </div>
48
                    <div class="modal-body">
49
                        <div>
50
                            <h4>Overall info</h4>
51
                                <ul>
52
                                    <li>
53
                                        Patron has <strong>[% reserves_count | html %]</strong> hold(s) and this request would raise the number to <strong>[% new_reserves_count + reserves_count | html %]</strong>.
54
                                    </li>
55
                                    <li>
56
                                        <span [% IF reserves_count >= Koha.Preference('maxreserves') %] class="text-danger" [% END %]>
57
                                            <i>maxreserves</i> preference: <strong>[% Koha.Preference('maxreserves') | html %]</strong>
58
                                        </span>
59
                                    </li>
60
                                    <li>
61
                                        <i>ReservesControlBranch</i> preference: <strong>[% Koha.Preference('ReservesControlBranch') | html %]</strong> <!-- TODO make translatable -->
62
                                    </li
63
                                </ul>
64
                        </div>
65
66
                        <h4>Per record info</h4>
67
                        <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
68
69
                            [% FOREACH element IN whyicantreserve %]
70
                                <div class="panel panel-default">
71
                                    <div class="panel-heading" role="tab" id="heading-[% element.biblionumber | html %]">
72
                                        <h4 class="panel-title">
73
                                            <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse-[% element.biblionumber | html %]" aria-expanded="true" aria-controls="collapse-[% element.biblionumber | html %]">
74
                                                [% element.title | html %]
75
                                            </a>
76
                                        </h4>
77
                                    </div>
78
                                    <div id="collapse-[% element.biblionumber | html %]" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="heading-[% element.biblionumber | html %]">
79
                                        <div class="panel-body">
80
81
                                            <div id="elements-to-reserve-details">
82
                                                <h5>Matched circulation rule:</h5>
83
                                                <table>
84
                                                    <tr>
85
                                                        <th>Library</th>
86
                                                        <th>Category</th>
87
                                                        <th>Item type</th>
88
                                                        <th>Holds</th>
89
                                                        <th>Holds per record</th>
90
                                                        <th>Holds per day</th>
91
                                                    </tr>
92
                                                    <tr>
93
                                                        <td>
94
                                                            [% IF element.issuingrule.branchcode == '*' %]
95
                                                                <i>All</i>
96
                                                            [% ELSE %]
97
                                                                [% Branches.GetName(element.issuingrule.branchcode) | html %]
98
                                                                ([% element.issuingrule.branchcode | html %])
99
                                                            [% END %]
100
                                                        </td>
101
                                                        <td>
102
                                                            [% IF element.issuingrule.categorycode == '*' %]
103
                                                                <i>All</i>
104
                                                            [% ELSE %]
105
                                                                [% Categories.GetName(element.issuingrule.categorycode) | html %]
106
                                                            [% END %]
107
                                                        </td>
108
                                                        <td>
109
                                                            [% IF element.issuingrule.itemtype == '*' %]
110
                                                                <i>All</i>
111
                                                            [% ELSE %]
112
                                                                [% ItemTypes.GetDescription(element.issuingrule.itemtype) | html %]
113
                                                            [% END %]
114
                                                        </td>
115
                                                        <td>
116
                                                            [% element.issuingrule.reservesallowed | html %]
117
                                                        </td>
118
                                                        <td>
119
                                                            [% element.issuingrule.holds_per_record | html %]
120
                                                        </td>
121
                                                        <td>
122
                                                            [% element.issuingrule.holds_per_day || 'unlimited' | html %]
123
                                                        </td>
124
                                                    </tr>
125
                                                </table>
126
127
                                                <h5>Matched hold policy:</h5>
128
129
                                                <table>
130
                                                    <tr>
131
                                                        <th>Library</th>
132
                                                        <th>Category</th>
133
                                                        <th>Holds</th>
134
                                                    </tr>
135
                                                    <tr>
136
                                                        [% IF element.circulation_rule %]
137
                                                            <td>
138
                                                                [% IF element.circulation_rule.branchcode %]
139
                                                                    [% Branches.GetName(element.circulation_rule.branchcode) | html %]
140
                                                                    ([% element.circulation_rule.branchcode | html %])
141
                                                                [% ELSE %]
142
                                                                    <i>All</i>
143
                                                                [% END %]
144
                                                            </td>
145
                                                            <td>
146
                                                                [% Categories.GetName(element.circulation_rule.categorycode) | html %]
147
                                                            </td>
148
                                                            <td>
149
                                                                [% element.circulation_rule.rule_value || 'unlimited' | html %]
150
                                                            </td>
151
                                                        [% ELSE %]
152
                                                            <td colspan="3">No rule matched</td>
153
                                                        [% END %]
154
                                                    </tr>
155
                                                </table>
156
157
                                            </div><!-- END elements-to-reserve-details -->
158
                                        </div>
159
                                    </div>
160
                                </div>
161
                            [% END %]
162
                        </div>
163
                    </div>
164
                    <div class="modal-footer">
165
                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
166
                    </div>
167
                </div>
168
            </div>
169
        </div> <!-- end of Modal -->
170
41
        [% IF ( multi_hold ) # No sidebar menu when placing multiple holds %]
171
        [% IF ( multi_hold ) # No sidebar menu when placing multiple holds %]
42
            <div class="col-md-10 col-md-offset-1">
172
            <div class="col-md-10 col-md-offset-1">
43
        [% ELSE %]
173
        [% ELSE %]
Lines 67-77 Link Here
67
                </div>
197
                </div>
68
            [% END %]
198
            [% END %]
69
199
70
            [% UNLESS ( multi_hold ) %]
200
            <h1>
71
                <h1>Place a hold on [% INCLUDE 'biblio-default-view.inc' %][% INCLUDE 'biblio-title.inc' %]</a></h1>
201
                [% UNLESS ( multi_hold ) %]
72
            [% ELSE %]
202
                    Place a hold on [% INCLUDE 'biblio-default-view.inc' %][% INCLUDE 'biblio-title.inc' %]</a>
73
                <h1>Confirm holds</h1>
203
                [% ELSE %]
74
            [% END %]
204
                    Confirm holds
205
                [% END %]
206
207
                [% IF whyicantreserve %]
208
                    <button type="button" class="btn btn-info btn-sm" data-toggle="modal" data-target="#TODOmyModal">
209
                        Show holding rules
210
                    </button>
211
                [% END %]
212
            </h1>
75
213
76
            [% UNLESS club OR patron OR patron.borrowernumber OR noitems %]
214
            [% UNLESS club OR patron OR patron.borrowernumber OR noitems %]
77
                [% IF ( messageborrower ) %]
215
                [% IF ( messageborrower ) %]
(-)a/reserve/request.pl (-3 / +13 lines)
Lines 182-189 if ($borrowernumber_hold && !$action) { Link Here
182
    # FIXME At this time we have a simple count of reservs, but, later, we could improve the infos "title" ...
182
    # FIXME At this time we have a simple count of reservs, but, later, we could improve the infos "title" ...
183
183
184
    my $reserves_count = $patron->holds->count;
184
    my $reserves_count = $patron->holds->count;
185
    $template->param(reserves_count => $reserves_count);
185
186
186
    my $new_reserves_count = scalar( @biblionumbers );
187
    my $new_reserves_count = scalar( @biblionumbers );
188
    $template->param(new_reserves_count   => $new_reserves_count);
187
189
188
    my $maxreserves = C4::Context->preference('maxreserves');
190
    my $maxreserves = C4::Context->preference('maxreserves');
189
    if ( $maxreserves
191
    if ( $maxreserves
Lines 197-204 if ($borrowernumber_hold && !$action) { Link Here
197
        $exceeded_maxreserves = 1;
199
        $exceeded_maxreserves = 1;
198
        $template->param(
200
        $template->param(
199
            new_reserves_allowed => $new_reserves_allowed,
201
            new_reserves_allowed => $new_reserves_allowed,
200
            new_reserves_count   => $new_reserves_count,
201
            reserves_count       => $reserves_count,
202
            maxreserves          => $maxreserves,
202
            maxreserves          => $maxreserves,
203
        );
203
        );
204
    }
204
    }
Lines 288-293 if ($patron) { Link Here
288
my $itemdata_enumchron = 0;
288
my $itemdata_enumchron = 0;
289
my $itemdata_ccode = 0;
289
my $itemdata_ccode = 0;
290
my @biblioloop = ();
290
my @biblioloop = ();
291
my @whyicantreserve;
291
foreach my $biblionumber (@biblionumbers) {
292
foreach my $biblionumber (@biblionumbers) {
292
    next unless $biblionumber =~ m|^\d+$|;
293
    next unless $biblionumber =~ m|^\d+$|;
293
294
Lines 299-304 foreach my $biblionumber (@biblionumbers) { Link Here
299
    if ( $patron ) {
300
    if ( $patron ) {
300
        { # CanBookBeReserved
301
        { # CanBookBeReserved
301
            my $canReserve = CanBookBeReserved( $patron->borrowernumber, $biblionumber, $pickup );
302
            my $canReserve = CanBookBeReserved( $patron->borrowernumber, $biblionumber, $pickup );
303
304
            push @whyicantreserve, {
305
                biblionumber                => $biblionumber,
306
                title                       => $biblio->title,
307
                issuingrule                 => $canReserve->{issuingrule},
308
                circulation_rule            => $canReserve->{circulation_rule},
309
                circulation_rule_exceeded   => $canReserve->{circulation_rule_exceeded},
310
            };
311
302
            if ( $canReserve->{status} eq 'OK' ) {
312
            if ( $canReserve->{status} eq 'OK' ) {
303
313
304
                #All is OK and we can continue
314
                #All is OK and we can continue
Lines 718-723 foreach my $biblionumber (@biblionumbers) { Link Here
718
    push @biblioloop, \%biblioloopiter;
728
    push @biblioloop, \%biblioloopiter;
719
}
729
}
720
730
731
$template->param( whyicantreserve => \@whyicantreserve );
721
$template->param( biblioloop => \@biblioloop );
732
$template->param( biblioloop => \@biblioloop );
722
$template->param( biblionumbers => $biblionumbers );
733
$template->param( biblionumbers => $biblionumbers );
723
$template->param( exceeded_maxreserves => $exceeded_maxreserves );
734
$template->param( exceeded_maxreserves => $exceeded_maxreserves );
724
- 

Return to bug 23732