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

(-)a/C4/Circulation.pm (-1 / +44 lines)
Lines 1036-1041 sub CanBookBeIssued { Link Here
1036
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1036
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1037
                    $needsconfirmation{'resbranchcode'} = $res->{branchcode};
1037
                    $needsconfirmation{'resbranchcode'} = $res->{branchcode};
1038
                    $needsconfirmation{'reswaitingdate'} = $res->{'waitingdate'};
1038
                    $needsconfirmation{'reswaitingdate'} = $res->{'waitingdate'};
1039
                    $needsconfirmation{resreserveid} = $res->{reserve_id};
1039
                }
1040
                }
1040
                elsif ( $restype eq "Reserved" ) {
1041
                elsif ( $restype eq "Reserved" ) {
1041
                    # The item is on reserve for someone else.
1042
                    # The item is on reserve for someone else.
Lines 1046-1054 sub CanBookBeIssued { Link Here
1046
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1047
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1047
                    $needsconfirmation{'resbranchcode'} = $patron->branchcode;
1048
                    $needsconfirmation{'resbranchcode'} = $patron->branchcode;
1048
                    $needsconfirmation{'resreservedate'} = $res->{reservedate};
1049
                    $needsconfirmation{'resreservedate'} = $res->{reservedate};
1050
                    $needsconfirmation{resreserveid} = $res->{reserve_id};
1049
                }
1051
                }
1050
            }
1052
            }
1051
        }
1053
        }
1054
1055
        my $now = dt_from_string();
1056
        my $preventCheckoutOnSameReservePeriod =
1057
            C4::Context->preference("PreventCheckoutOnSameReservePeriod");
1058
        my $reserves_on_same_period =
1059
            ReservesOnSamePeriod($item_object->biblionumber, $item_object->itemnumber, $now->ymd, $duedate->ymd);
1060
        if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
1061
            my $reserve = $reserves_on_same_period->[0];
1062
            my $patron = Koha::Patrons->find( $reserve->{borrowernumber} );
1063
            my $branchname = Koha::Libraries->find($reserve->{branchcode})->branchname;
1064
1065
            $needsconfirmation{RESERVED} = 1;
1066
            $needsconfirmation{resfirstname} = $patron->firstname;
1067
            $needsconfirmation{ressurname} = $patron->surname;
1068
            $needsconfirmation{rescardnumber} = $patron->cardnumber;
1069
            $needsconfirmation{resborrowernumber} = $patron->borrowernumber;
1070
            $needsconfirmation{resbranchname} = $branchname;
1071
            $needsconfirmation{resreservedate} = $reserve->{reservedate};
1072
            $needsconfirmation{resreserveid} = $reserve->{reserve_id};
1073
        }
1074
1052
    }
1075
    }
1053
1076
1054
    ## CHECK AGE RESTRICTION
1077
    ## CHECK AGE RESTRICTION
Lines 2689-2695 already renewed the loan. $error will contain the reason the renewal can not pro Link Here
2689
=cut
2712
=cut
2690
2713
2691
sub CanBookBeRenewed {
2714
sub CanBookBeRenewed {
2692
    my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
2715
    my ( $borrowernumber, $itemnumber, $override_limit, $date_due ) = @_;
2693
2716
2694
    my $dbh    = C4::Context->dbh;
2717
    my $dbh    = C4::Context->dbh;
2695
    my $renews = 1;
2718
    my $renews = 1;
Lines 2766-2771 sub CanBookBeRenewed { Link Here
2766
            }
2789
            }
2767
        }
2790
        }
2768
    }
2791
    }
2792
2793
    unless ($date_due) {
2794
        my $itemtype = $item->effective_itemtype;
2795
        my $patron_unblessed = $patron->unblessed;
2796
        $date_due = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2797
                                        dt_from_string( $issue->date_due, 'sql' ) :
2798
                                        DateTime->now( time_zone => C4::Context->tz());
2799
        $date_due =  CalcDateDue($date_due, $itemtype, _GetCircControlBranch($item->unblessed(), $patron_unblessed), $patron_unblessed, 'is a renewal');
2800
    }
2801
2802
    my $now = dt_from_string();
2803
    my $biblionumber = $item->biblionumber;
2804
    my $preventCheckoutOnSameReservePeriod =
2805
        C4::Context->preference("PreventCheckoutOnSameReservePeriod");
2806
    my $reserves_on_same_period =
2807
        ReservesOnSamePeriod($biblionumber, $itemnumber, $now->ymd, $date_due->ymd);
2808
    if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
2809
        $resfound = 1;
2810
    }
2811
2769
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2812
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2770
2813
2771
    return ( 1, undef ) if $override_limit;
2814
    return ( 1, undef ) if $override_limit;
(-)a/C4/Reserves.pm (+48 lines)
Lines 131-136 BEGIN { Link Here
131
        &SuspendAll
131
        &SuspendAll
132
132
133
        &GetReservesControlBranch
133
        &GetReservesControlBranch
134
        &ReservesOnSamePeriod
134
135
135
        IsItemOnHoldAndFound
136
        IsItemOnHoldAndFound
136
137
Lines 2245-2250 sub GetHoldRule { Link Here
2245
    return $rules;
2246
    return $rules;
2246
}
2247
}
2247
2248
2249
=head2 ReservesOnSamePeriod
2250
2251
    my $reserve = ReservesOnSamePeriod( $biblionumber, $itemnumber, $resdate, $expdate);
2252
2253
    Return the reserve that match the period ($resdate => $expdate),
2254
    undef if no reserve match.
2255
2256
=cut
2257
2258
sub ReservesOnSamePeriod {
2259
    my ($biblionumber, $itemnumber, $resdate, $expdate) = @_;
2260
2261
    unless ($resdate && $expdate) {
2262
        return;
2263
    }
2264
2265
    my @reserves = Koha::Holds->search({ biblionumber => $biblionumber });
2266
2267
    $resdate = output_pref({ str => $resdate, dateonly => 1, dateformat => 'iso' });
2268
    $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' });
2269
2270
    my @reserves_overlaps;
2271
    foreach my $reserve ( @reserves ) {
2272
2273
        unless ($reserve->reservedate && $reserve->expirationdate) {
2274
            next;
2275
        }
2276
2277
        if (date_ranges_overlap($resdate, $expdate,
2278
                                $reserve->reservedate,
2279
                                $reserve->expirationdate)) {
2280
2281
            # If reserve is item level and the requested periods overlap.
2282
            if ($itemnumber && $reserve->itemnumber == $itemnumber ) {
2283
                return [$reserve->unblessed];
2284
            }
2285
            push @reserves_overlaps, $reserve->unblessed;
2286
        }
2287
    }
2288
2289
    if ( @reserves_overlaps >= Koha::Items->search({ biblionumber => $biblionumber })->count() ) {
2290
        return \@reserves_overlaps;
2291
    }
2292
2293
    return;
2294
}
2295
2248
=head1 AUTHOR
2296
=head1 AUTHOR
2249
2297
2250
Koha Development Team <http://koha-community.org/>
2298
Koha Development Team <http://koha-community.org/>
(-)a/Koha/DateUtils.pm (-1 / +43 lines)
Lines 24-30 use Koha::Exceptions; Link Here
24
use base 'Exporter';
24
use base 'Exporter';
25
25
26
our @EXPORT = (
26
our @EXPORT = (
27
    qw( dt_from_string output_pref format_sqldatetime )
27
    qw( dt_from_string output_pref format_sqldatetime date_ranges_overlap )
28
);
28
);
29
29
30
=head1 DateUtils
30
=head1 DateUtils
Lines 322-325 sub format_sqldatetime { Link Here
322
    return q{};
322
    return q{};
323
}
323
}
324
324
325
=head2 date_ranges_overlap
326
327
    $bool = date_ranges_overlap($start1, $end1, $start2, $end2);
328
329
    Tells if first range ($start1 => $end1) overlaps
330
    the second one ($start2 => $end2)
331
332
=cut
333
334
sub date_ranges_overlap {
335
    my ($start1, $end1, $start2, $end2) = @_;
336
337
    $start1 = dt_from_string( $start1, 'iso' );
338
    $end1 = dt_from_string( $end1, 'iso' );
339
    $start2 = dt_from_string( $start2, 'iso' );
340
    $end2 = dt_from_string( $end2, 'iso' );
341
342
    if (
343
        # Start of range 2 is in the range 1.
344
        (
345
            DateTime->compare($start2, $start1) >= 0
346
            && DateTime->compare($start2, $end1) <= 0
347
        )
348
        ||
349
        # End of range 2 is in the range 1.
350
        (
351
            DateTime->compare($end2, $start1) >= 0
352
            && DateTime->compare($end2, $end1) <= 0
353
        )
354
        ||
355
        # Range 2 start before and end after range 1.
356
        (
357
            DateTime->compare($start2, $start1) < 0
358
            && DateTime->compare($end2, $end1) > 0
359
        )
360
    ) {
361
        return 1;
362
    }
363
364
    return;
365
}
366
325
1;
367
1;
(-)a/circ/circulation.pl (+4 lines)
Lines 434-439 if (@$barcodes) { Link Here
434
        }
434
        }
435
        unless($confirm_required) {
435
        unless($confirm_required) {
436
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
436
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
437
            if ( $cancelreserve eq 'cancel' ) {
438
                CancelReserve({ reserve_id => $query->param('reserveid') });
439
            }
440
            $cancelreserve = $cancelreserve eq 'revert' ? 'revert' : undef;
437
            my $issue = AddIssue( $patron->unblessed, $barcode, $datedue, $cancelreserve, undef, undef, { onsite_checkout => $onsite_checkout, auto_renew => $session->param('auto_renew'), switch_onsite_checkout => $switch_onsite_checkout, } );
441
            my $issue = AddIssue( $patron->unblessed, $barcode, $datedue, $cancelreserve, undef, undef, { onsite_checkout => $onsite_checkout, auto_renew => $session->param('auto_renew'), switch_onsite_checkout => $switch_onsite_checkout, } );
438
            $template_params->{issue} = $issue;
442
            $template_params->{issue} = $issue;
439
            $session->clear('auto_renew');
443
            $session->clear('auto_renew');
(-)a/installer/data/mysql/atomicupdate/bug_15261-add_preventchechoutonsamereserveperiod_syspref.sql (+1 lines)
Line 0 Link Here
1
INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES ('PreventCheckoutOnSameReservePeriod', '0', 'Prevent to checkout a document if a reserve on same period exists', NULL, 'YesNo');
(-)a/installer/data/mysql/atomicupdate/bug_15261-add_preventreservesonsameperiod_syspref.sql (+1 lines)
Line 0 Link Here
1
INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES ('PreventReservesOnSamePeriod', '0', 'Prevent to hold a document if a reserve on same period exists', NULL, 'YesNo');
(-)a/installer/data/mysql/sysprefs.sql (+2 lines)
Lines 498-503 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
498
('PayPalUser',  '', NULL ,  'Your PayPal API username ( email address )',  'Free'),
498
('PayPalUser',  '', NULL ,  'Your PayPal API username ( email address )',  'Free'),
499
('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'),
499
('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'),
500
('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'),
500
('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'),
501
('PreventCheckoutOnSameReservePeriod','0','','Prevent to checkout a document if a reserve on same period exists','YesNo'),
502
('PreventReservesOnSamePeriod','0','','Prevent to hold a document if a reserve on same period exists','YesNo'),
501
('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'),
503
('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'),
502
('printcirculationslips','1','','If ON, enable printing circulation receipts','YesNo'),
504
('printcirculationslips','1','','If ON, enable printing circulation receipts','YesNo'),
503
('PrintNoticesMaxLines','0','','If greater than 0, sets the maximum number of lines an overdue notice will print. If the number of items is greater than this number, the notice will end with a warning asking the borrower to check their online account for a full list of overdue items.','Integer'),
505
('PrintNoticesMaxLines','0','','If greater than 0, sets the maximum number of lines an overdue notice will print. If the number of items is greater than this number, the notice will end with a warning asking the borrower to check their online account for a full list of overdue items.','Integer'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (+12 lines)
Lines 499-504 Circulation: Link Here
499
            - "<br />ccode: [NEWFIC,NULL,DVD]"
499
            - "<br />ccode: [NEWFIC,NULL,DVD]"
500
            - "<br />itype: [NEWBK,\"\"]"
500
            - "<br />itype: [NEWBK,\"\"]"
501
            - "<br /> Note: the word 'NULL' can be used to block renewal on undefined fields, while an empty string \"\" will block on an empty (but defined) field."
501
            - "<br /> Note: the word 'NULL' can be used to block renewal on undefined fields, while an empty string \"\" will block on an empty (but defined) field."
502
        -
503
            - pref: PreventCheckoutOnSameReservePeriod
504
              choices:
505
                  yes: Do
506
                  no: "Don't"
507
            - If yes, checkouts periods can't overlap with a reserve period.
502
    Checkin Policy:
508
    Checkin Policy:
503
        -
509
        -
504
            - pref: HoldsAutoFill
510
            - pref: HoldsAutoFill
Lines 808-813 Circulation: Link Here
808
            - pref: UpdateItemWhenLostFromHoldList
814
            - pref: UpdateItemWhenLostFromHoldList
809
              type: textarea
815
              type: textarea
810
              syntax: text/x-yaml
816
              syntax: text/x-yaml
817
        -
818
            - pref: PreventReservesOnSamePeriod
819
              choices:
820
                  yes: Do
821
                  no: "Don't"
822
            - If yes, Reserves periods for the same document can't overlap.
811
    Interlibrary Loans:
823
    Interlibrary Loans:
812
        -
824
        -
813
            - pref: ILLModule
825
            - pref: ILLModule
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt (+2 lines)
Lines 214-219 Link Here
214
                                    [% IF ( RESERVED ) %]
214
                                    [% IF ( RESERVED ) %]
215
                                        <p>
215
                                        <p>
216
                                            <input type="checkbox" id="cancelreserve" name="cancelreserve" value="cancel" />
216
                                            <input type="checkbox" id="cancelreserve" name="cancelreserve" value="cancel" />
217
                                            <input type="hidden" name="reserveid" value="[% resreserveid | html %]" />
217
                                            <label for="cancelreserve">Cancel hold</label>
218
                                            <label for="cancelreserve">Cancel hold</label>
218
                                        </p>
219
                                        </p>
219
                                    [% END %]
220
                                    [% END %]
Lines 222-227 Link Here
222
                                        <p>
223
                                        <p>
223
                                            <label for="cancelreserve">Cancel hold</label>
224
                                            <label for="cancelreserve">Cancel hold</label>
224
                                            <input type="radio" value="cancel" name="cancelreserve" id="cancelreserve" /><br />
225
                                            <input type="radio" value="cancel" name="cancelreserve" id="cancelreserve" /><br />
226
                                            <input type="hidden" name="reserveid" value="[% resreserveid | html %]" />
225
                                            <label for="revertreserve">Revert waiting status</label>
227
                                            <label for="revertreserve">Revert waiting status</label>
226
                                            <input type="radio" value="revert" name="cancelreserve" id="revertreserve" checked="checked"/>
228
                                            <input type="radio" value="revert" name="cancelreserve" id="revertreserve" checked="checked"/>
227
                                        </p>
229
                                        </p>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/placerequest.tt (+88 lines)
Line 0 Link Here
1
[% USE JSON.Escape %]
2
[% USE raw %]
3
[% INCLUDE 'doc-head-open.inc' %]
4
    <title>Koha &rsaquo; Circulation &rsaquo; Holds &rsaquo; Confirm holds</title>
5
[% INCLUDE 'doc-head-close.inc' %]
6
</head>
7
<body id="circ_placerequest" class="catalog">
8
[% INCLUDE 'header.inc' %]
9
[% INCLUDE 'circ-search.inc' %]
10
11
<div id="breadcrumbs">
12
  <a href="/cgi-bin/koha/mainpage.pl">Home</a>
13
  &rsaquo;
14
  <a href="/cgi-bin/koha/catalogue/search.pl">Catalog</a>
15
  &rsaquo;
16
  Confirm holds
17
</div>
18
19
<div id="doc3" class="yui-t2">
20
21
  <div id="bd">
22
    <div id="yui-main">
23
      <div class="yui-b">
24
        <div class="modal fade" tabindex="-1" role="dialog">
25
          <div class="modal-dialog" role="document">
26
            <div class="modal-content">
27
              <div class="modal-header">
28
                <h4 class="modal-title">Confirm holds</h4>
29
              </div>
30
              <div class="modal-body">
31
                <p>
32
                  Some of the reserves you are trying to do overlaps with existing reserves.
33
                  Please tick the box confirming the reservation then click on Confirm if you want to proceed.
34
                </p>
35
36
                <form method="post" id="confirm-holds">
37
                  <input type="hidden" name="borrowernumber" value="[% borrowernumber | html %]">
38
                  [% IF multi_hold %]
39
                    <input type="hidden" name="biblionumbers" value="[% biblionumbers | html %]">
40
                    <input type="hidden" name="multi_hold" value="1">
41
                  [% ELSE %]
42
                    <input type="hidden" name="biblionumber" value="[% biblionumber | html %]">
43
                  [% END %]
44
                  <input type="hidden" name="reserve_date" value="[% reserve_date | html %]">
45
                  <input type="hidden" name="expiration_date" value="[% expiration_date | html %]">
46
                  <input type="hidden" name="pickup" value="[% pickup | html %]">
47
                  <input type="hidden" name="notes" value="[% notes | html %]">
48
                  <input type="hidden" name="confirm" value="1">
49
                  <input type="hidden" name="type" value="str8">
50
51
                  [% FOREACH biblionumber IN overlap_reserves.keys %]
52
                    [% input_id = "confirm_$biblionumber" %]
53
                    <div>
54
                      <input type="hidden" name="rank_[% biblionumber | html %]" value="[% overlap_reserves.$biblionumber.rank | html %]">
55
                      [% IF (overlap_reserves.$biblionumber.checkitem) %]
56
                        <input type="hidden" name="checkitem" value="[% overlap_reserves.$biblionumber.checkitem | html %]">
57
                      [% END %]
58
                      <input type="checkbox" name="confirm_biblionumbers" id="[% input_id | html %]"
59
                             value="[% biblionumber | html %]">
60
                      <label for="[% input_id | html %]">Confirm hold for [% overlap_reserves.$biblionumber.title | html %]</label>
61
                    </div>
62
                  [% END %]
63
                </form>
64
              </div>
65
              <div class="modal-footer">
66
                <button type="button" class="btn btn-default deny" data-dismiss="modal"><i class="fa fa-times"></i> Cancel</button>
67
                <button type="submit" class="btn btn-default approve" form="confirm-holds"><i class="fa fa-check"></i> Confirm</button>
68
              </div>
69
            </div><!-- /.modal-content -->
70
          </div><!-- /.modal-dialog -->
71
        </div><!-- /.modal -->
72
      </div>
73
    </div>
74
75
  </div>
76
  <script>
77
    $(document).ready(function () {
78
      var modal = $('.modal').modal();
79
      modal.on('hide.bs.modal', function (e) {
80
        [% IF multi_hold %]
81
          window.location = '/cgi-bin/koha/reserve/request.pl?biblionumbers=' + [% biblionumbers.json | $raw %] + '&multi_hold=1' + '&borrowernumber=' + [% borrowernumber.json | $raw %];
82
        [% ELSE %]
83
          window.location = '/cgi-bin/koha/reserve/request.pl?biblionumber=' + [% biblionumber.json | $raw %] + '&borrowernumber=' + [% borrowernumber.json | $raw %]
84
        [% END %]
85
      })
86
    });
87
  </script>
88
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/opac/opac-reserve.pl (+9 lines)
Lines 286-291 if ( $query->param('place_reserve') ) { Link Here
286
            # Inserts a null into the 'itemnumber' field of 'reserves' table.
286
            # Inserts a null into the 'itemnumber' field of 'reserves' table.
287
            $itemNum = undef;
287
            $itemNum = undef;
288
        }
288
        }
289
290
        if ($canreserve) {
291
            if (C4::Context->preference("PreventReservesOnSamePeriod") &&
292
                ReservesOnSamePeriod($biblioNum, $itemNum, $startdate, $expiration_date)) {
293
                $canreserve = 0;
294
                $failed_holds++;
295
            }
296
        }
297
289
        my $notes = $query->param('notes_'.$biblioNum)||'';
298
        my $notes = $query->param('notes_'.$biblioNum)||'';
290
299
291
        if (   $maxreserves
300
        if (   $maxreserves
(-)a/reserve/placerequest.pl (-3 / +45 lines)
Lines 23-28 Link Here
23
23
24
use Modern::Perl;
24
use Modern::Perl;
25
25
26
use List::MoreUtils qw( none );
27
26
use CGI qw ( -utf8 );
28
use CGI qw ( -utf8 );
27
use C4::Biblio;
29
use C4::Biblio;
28
use C4::Items;
30
use C4::Items;
Lines 30-46 use C4::Output; Link Here
30
use C4::Reserves;
32
use C4::Reserves;
31
use C4::Circulation;
33
use C4::Circulation;
32
use C4::Members;
34
use C4::Members;
33
use C4::Auth qw/checkauth/;
35
use C4::Auth;
34
36
35
use Koha::Items;
37
use Koha::Items;
36
use Koha::Patrons;
38
use Koha::Patrons;
37
39
38
my $input = CGI->new();
40
my $input = CGI->new();
39
41
40
checkauth($input, 0, { reserveforothers => 'place_holds' }, 'intranet');
42
my ($template, $loggedinuser, $cookie) = get_template_and_user({
43
    template_name   => 'reserve/placerequest.tt',
44
    query           => $input,
45
    type            => 'intranet',
46
    authnotrequired => 0,
47
    flagsrequired   => { reserveforothers => 'place_holds' },
48
});
41
49
42
my @bibitems       = $input->multi_param('biblioitem');
50
my @bibitems       = $input->multi_param('biblioitem');
43
my @reqbib         = $input->multi_param('reqbib');
44
my $biblionumber   = $input->param('biblionumber');
51
my $biblionumber   = $input->param('biblionumber');
45
my $borrowernumber = $input->param('borrowernumber');
52
my $borrowernumber = $input->param('borrowernumber');
46
my $notes          = $input->param('notes');
53
my $notes          = $input->param('notes');
Lines 52-57 my $title = $input->param('title'); Link Here
52
my $checkitem      = $input->param('checkitem');
59
my $checkitem      = $input->param('checkitem');
53
my $expirationdate = $input->param('expiration_date');
60
my $expirationdate = $input->param('expiration_date');
54
my $itemtype       = $input->param('itemtype') || undef;
61
my $itemtype       = $input->param('itemtype') || undef;
62
my $confirm        = $input->param('confirm');
63
my @confirm_biblionumbers = $input->multi_param('confirm_biblionumbers');
55
64
56
my $borrower = Koha::Patrons->find( $borrowernumber );
65
my $borrower = Koha::Patrons->find( $borrowernumber );
57
$borrower = $borrower->unblessed if $borrower;
66
$borrower = $borrower->unblessed if $borrower;
Lines 73-80 foreach my $bibnum (@biblionumbers) { Link Here
73
my $found;
82
my $found;
74
83
75
if ( $type eq 'str8' && $borrower ) {
84
if ( $type eq 'str8' && $borrower ) {
85
    if (C4::Context->preference('PreventReservesOnSamePeriod') && !$confirm) {
86
        my %overlap_reserves;
87
        foreach my $biblionumber ( keys %bibinfos ) {
88
            my $overlapping_reserves = ReservesOnSamePeriod($biblionumber, $checkitem, $startdate, $expirationdate);
89
            if ($overlapping_reserves && 0 < scalar @$overlapping_reserves) {
90
                $overlap_reserves{$biblionumber} = {
91
                    title => $multi_hold ? $bibinfos{$biblionumber}->{title} : $title,
92
                    checkitem => $checkitem,
93
                    rank => $multi_hold ? $bibinfos{$biblionumber}->{rank} : $rank[0],
94
                };
95
            }
96
        }
97
98
        if (scalar keys %overlap_reserves) {
99
            $template->param(
100
                borrowernumber => $borrowernumber,
101
                biblionumbers => $biblionumbers,
102
                biblionumber => $biblionumber,
103
                overlap_reserves => \%overlap_reserves,
104
                reserve_date => $startdate,
105
                expiration_date => $expirationdate,
106
                notes => $notes,
107
                rank_request => \@rank,
108
                pickup => $branch,
109
                multi_hold => $multi_hold,
110
            );
111
112
            output_html_with_http_headers $input, $cookie, $template->output;
113
            exit;
114
        }
115
    }
76
116
77
    foreach my $biblionumber ( keys %bibinfos ) {
117
    foreach my $biblionumber ( keys %bibinfos ) {
118
        next if ($confirm && none { $_ eq $biblionumber } @confirm_biblionumbers);
119
78
        my $count = @bibitems;
120
        my $count = @bibitems;
79
        @bibitems = sort @bibitems;
121
        @bibitems = sort @bibitems;
80
        my $i2 = 1;
122
        my $i2 = 1;
(-)a/svc/renew (-1 / +1 lines)
Lines 57-63 $data->{borrowernumber} = $borrowernumber; Link Here
57
$data->{branchcode} = $branchcode;
57
$data->{branchcode} = $branchcode;
58
58
59
( $data->{renew_okay}, $data->{error} ) =
59
( $data->{renew_okay}, $data->{error} ) =
60
  CanBookBeRenewed( $borrowernumber, $itemnumber, $override_limit );
60
  CanBookBeRenewed( $borrowernumber, $itemnumber, $override_limit, $date_due );
61
61
62
# If we're allowing reserved items to be renewed...
62
# If we're allowing reserved items to be renewed...
63
if ( $data->{error} && $data->{error} eq 'on_reserve' && C4::Context->preference('AllowRenewalOnHoldOverride')) {
63
if ( $data->{error} && $data->{error} eq 'on_reserve' && C4::Context->preference('AllowRenewalOnHoldOverride')) {
(-)a/t/db_dependent/Circulation.t (-1 / +61 lines)
Lines 18-24 Link Here
18
use Modern::Perl;
18
use Modern::Perl;
19
use utf8;
19
use utf8;
20
20
21
use Test::More tests => 46;
21
use Test::More tests => 48;
22
use Test::MockModule;
22
use Test::MockModule;
23
use Test::Deep qw( cmp_deeply );
23
use Test::Deep qw( cmp_deeply );
24
24
Lines 379-384 subtest "CanBookBeRenewed tests" => sub { Link Here
379
    my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
379
    my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
380
    is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
380
    is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
381
381
382
    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
382
    my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
383
    my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
383
    is( $renewokay, 1, 'Can renew, no holds for this title or item');
384
    is( $renewokay, 1, 'Can renew, no holds for this title or item');
384
385
Lines 1477-1482 subtest "AllowRenewalIfOtherItemsAvailable tests" => sub { Link Here
1477
}
1478
}
1478
1479
1479
{
1480
{
1481
    # Don't allow renewing on reserve period with PreventCheckoutOnSameReservePeriod enabled
1482
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 1 );
1483
1484
    my $start_issue_dt = DateTime->now();
1485
    $start_issue_dt->subtract( days => 15);
1486
    my $due_date = $start_issue_dt->clone;
1487
    $due_date->add( days => 17 );
1488
1489
    my $biblio = $builder->build({
1490
        source => 'Biblio',
1491
    });
1492
    my $biblionumber = $biblio->{biblionumber};
1493
1494
    my $item = $builder->build({
1495
        source => 'Item',
1496
        value => {
1497
            biblionumber => $biblionumber
1498
        }
1499
    });
1500
    my $itemnumber = $item->{itemnumber};
1501
1502
    my $issue = $builder->build({
1503
        source => 'Issue',
1504
        value => {
1505
            itemnumber => $itemnumber,
1506
            biblionumber => $biblionumber,
1507
            issuedate => $start_issue_dt->ymd,
1508
            date_due => $due_date->ymd,
1509
            onsite_checkout => 0
1510
        }
1511
    });
1512
1513
    my $reservedate = $due_date->clone;
1514
    $reservedate->add( days => 5 );
1515
    my $expirationdate = $reservedate->clone;
1516
    $expirationdate->add( days => 15 );
1517
    $builder->build({
1518
        source => 'Reserve',
1519
        value => {
1520
            biblionumber => $biblionumber,
1521
            itemnumber => $itemnumber,
1522
            reservedate => $reservedate->ymd,
1523
            expirationdate => $expirationdate->ymd
1524
        }
1525
    });
1526
1527
    my $requested_date_due = $due_date->clone;
1528
    $requested_date_due->add( days => 4 );
1529
    my ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1530
    is( $renewed, 1, 'CanBookBeRenewed should allow to renew if no reserve date overlap' );
1531
1532
    $requested_date_due->add( days => 2 );
1533
    ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1534
    is( $renewed, 0, 'CanBookBeRenewed should not allow to renew if reserve date overlap' );
1535
1536
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 0 );
1537
}
1538
1539
{
1480
    my $library = $builder->build({ source => 'Branch' });
1540
    my $library = $builder->build({ source => 'Branch' });
1481
1541
1482
    my $biblio = $builder->build_sample_biblio();
1542
    my $biblio = $builder->build_sample_biblio();
(-)a/t/db_dependent/Circulation/CanBookBeIssued.t (+106 lines)
Line 0 Link Here
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 1;
21
use C4::Members;
22
use C4::Reserves;
23
use C4::Circulation;
24
use Koha::DateUtils;
25
26
use t::lib::TestBuilder;
27
28
my $schema  = Koha::Database->new->schema;
29
$schema->storage->txn_begin;
30
31
my $builder = t::lib::TestBuilder->new();
32
33
subtest 'Tests for CanBookBeIssued with overlap reserves' => sub {
34
    plan tests => 6;
35
36
    my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
37
    my $branch = $builder->build({ source => 'Branch' });
38
    my $branchcode = $branch->{branchcode};
39
40
    my $borrower = $builder->build_object({
41
        class => 'Koha::Patrons',
42
        value => {
43
            branchcode   => $branchcode,
44
            categorycode => $categorycode,
45
        }
46
    });
47
    my $borrowernumber = $borrower->borrowernumber;
48
49
    my $biblio = $builder->build({source => 'Biblio'});
50
    my $biblioitem = $builder->build({
51
        source => 'Biblioitem',
52
        value => {
53
            biblionumber => $biblio->{biblionumber},
54
        },
55
    });
56
    my $item = $builder->build({
57
        source => 'Item',
58
        value => {
59
            biblionumber => $biblio->{biblionumber},
60
            biblioitemnumber => $biblioitem->{biblioitemnumber},
61
            withdrawn => 0,
62
            itemlost => 0,
63
            notforloan => 0,
64
        },
65
    });
66
67
68
    my $startdate = dt_from_string();
69
    $startdate->add_duration(DateTime::Duration->new(days => 4));
70
    my $expdate = $startdate->clone();
71
    $expdate->add_duration(DateTime::Duration->new(days => 10));
72
73
    my $reserveid = AddReserve($branchcode, $borrowernumber,
74
        $item->{biblionumber}, undef,  1, $startdate->ymd(), $expdate->ymd,
75
        undef, undef, undef, undef);
76
77
    my $non_overlap_duedate = dt_from_string();
78
    $non_overlap_duedate->add_duration(DateTime::Duration->new(days => 2));
79
    my ($error, $question, $alerts ) =
80
        CanBookBeIssued($borrower, $item->{barcode}, $non_overlap_duedate, 1, 0);
81
82
    is_deeply($error, {}, "");
83
    is_deeply($question, {}, "");
84
    is_deeply($alerts, {}, "");
85
86
    my $overlap_duedate = dt_from_string();
87
    $overlap_duedate->add_duration(DateTime::Duration->new(days => 5));
88
    ($error, $question, $alerts ) =
89
        CanBookBeIssued($borrower, $item->{barcode}, $overlap_duedate, 1, 0);
90
91
    is_deeply($error, {}, "");
92
    my $expected = {
93
        RESERVED => 1,
94
        resfirstname => $borrower->firstname,
95
        ressurname => $borrower->surname,
96
        rescardnumber => $borrower->cardnumber,
97
        resborrowernumber => $borrower->borrowernumber,
98
        resbranchname => $branch->{branchname},
99
        resreservedate => $startdate->ymd,
100
        resreserveid => $reserveid,
101
    };
102
    is_deeply($question, $expected, "");
103
    is_deeply($alerts, {}, "");
104
};
105
106
$schema->storage->txn_rollback;
(-)a/t/db_dependent/Reserves/ReserveDate.t (-1 / +108 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 6;
21
use Test::Warn;
22
23
use MARC::Record;
24
use DateTime::Duration;
25
26
use C4::Biblio;
27
use C4::Items;
28
use C4::Members;
29
use C4::Circulation;
30
use Koha::Holds;
31
use t::lib::TestBuilder;
32
33
use Koha::DateUtils;
34
35
36
use_ok('C4::Reserves');
37
38
my $dbh = C4::Context->dbh;
39
40
# Start transaction
41
$dbh->{AutoCommit} = 0;
42
$dbh->{RaiseError} = 1;
43
44
my $builder = t::lib::TestBuilder->new();
45
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
46
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
47
48
my $borrower = $builder->build({
49
    source => 'Borrower',
50
    value => {
51
        branchcode   => $branchcode,
52
        categorycode => $categorycode,
53
    }
54
});
55
56
my $borrower2 = $builder->build({
57
    source => 'Borrower',
58
    value => {
59
        branchcode   => $branchcode,
60
        categorycode => $categorycode,
61
    }
62
});
63
64
my $borrowernumber = $borrower->{borrowernumber};
65
my $borrowernumber2 = $borrower2->{borrowernumber};
66
67
# Create a helper biblio
68
my $biblio = MARC::Record->new();
69
my $title = 'Alone in the Dark';
70
my $author = 'Karen Rose';
71
if( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
72
    $biblio->append_fields(
73
        MARC::Field->new('600', '', '1', a => $author),
74
        MARC::Field->new('200', '', '', a => $title),
75
    );
76
}
77
else {
78
    $biblio->append_fields(
79
        MARC::Field->new('100', '', '', a => $author),
80
        MARC::Field->new('245', '', '', a => $title),
81
    );
82
}
83
my ($bibnum, $bibitemnum);
84
($bibnum, $title, $bibitemnum) = AddBiblio($biblio, '');
85
86
my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $branchcode, holdingbranch => $branchcode, barcode => '333' } , $bibnum);
87
88
C4::Context->set_preference('AllowHoldDateInFuture', 1);
89
90
AddReserve($branchcode, $borrowernumber, $bibnum,
91
           $bibitemnum,  1, '2015-11-01', '2015-11-20', undef,
92
           undef, undef, undef);
93
94
is(ReservesOnSamePeriod($bibnum, undef, '2015-11-25', '2015-11-30'), undef, "Period doesn't overlaps");
95
96
ok(ReservesOnSamePeriod($bibnum, undef, '2015-11-02', '2015-11-10'), "Period overlaps");
97
98
my ($item_bibnum2, $item_bibitemnum2, $itemnumber2) = AddItem({ homebranch => $branchcode, holdingbranch => $branchcode, barcode => '444' } , $bibnum);
99
is(ReservesOnSamePeriod($bibnum, undef, '2015-11-02', '2015-11-10'), undef, "Period overlaps but there is 2 items");
100
101
AddReserve($branchcode, $borrowernumber2, $bibnum,
102
           $bibitemnum,  1, '2016-02-01', '2016-02-10', undef,
103
           undef, $itemnumber, undef);
104
is(ReservesOnSamePeriod($bibnum, $itemnumber, '02/12/2015', '10/12/2015'), undef, "Period on item does not overlap (with metric date format)");
105
106
my $reserve = ReservesOnSamePeriod($bibnum, $itemnumber, '2016-01-31', '2016-02-05');
107
is($reserve->[0]->{itemnumber}, $itemnumber, 'Period on item overlaps');
108
$dbh->rollback;

Return to bug 15261