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

(-)a/C4/Circulation.pm (-1 / +44 lines)
Lines 1029-1034 sub CanBookBeIssued { Link Here
1029
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1029
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1030
                    $needsconfirmation{'resbranchcode'} = $res->{branchcode};
1030
                    $needsconfirmation{'resbranchcode'} = $res->{branchcode};
1031
                    $needsconfirmation{'reswaitingdate'} = $res->{'waitingdate'};
1031
                    $needsconfirmation{'reswaitingdate'} = $res->{'waitingdate'};
1032
                    $needsconfirmation{resreserveid} = $res->{reserve_id};
1032
                }
1033
                }
1033
                elsif ( $restype eq "Reserved" ) {
1034
                elsif ( $restype eq "Reserved" ) {
1034
                    # The item is on reserve for someone else.
1035
                    # The item is on reserve for someone else.
Lines 1039-1047 sub CanBookBeIssued { Link Here
1039
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1040
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1040
                    $needsconfirmation{'resbranchcode'} = $patron->branchcode;
1041
                    $needsconfirmation{'resbranchcode'} = $patron->branchcode;
1041
                    $needsconfirmation{'resreservedate'} = $res->{reservedate};
1042
                    $needsconfirmation{'resreservedate'} = $res->{reservedate};
1043
                    $needsconfirmation{resreserveid} = $res->{reserve_id};
1042
                }
1044
                }
1043
            }
1045
            }
1044
        }
1046
        }
1047
1048
        my $now = dt_from_string();
1049
        my $preventCheckoutOnSameReservePeriod =
1050
            C4::Context->preference("PreventCheckoutOnSameReservePeriod");
1051
        my $reserves_on_same_period =
1052
            ReservesOnSamePeriod($item_object->biblionumber, $item_object->itemnumber, $now->ymd, $duedate->ymd);
1053
        if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
1054
            my $reserve = $reserves_on_same_period->[0];
1055
            my $patron = Koha::Patrons->find( $reserve->{borrowernumber} );
1056
            my $branchname = Koha::Libraries->find($reserve->{branchcode})->branchname;
1057
1058
            $needsconfirmation{RESERVED} = 1;
1059
            $needsconfirmation{resfirstname} = $patron->firstname;
1060
            $needsconfirmation{ressurname} = $patron->surname;
1061
            $needsconfirmation{rescardnumber} = $patron->cardnumber;
1062
            $needsconfirmation{resborrowernumber} = $patron->borrowernumber;
1063
            $needsconfirmation{resbranchname} = $branchname;
1064
            $needsconfirmation{resreservedate} = $reserve->{reservedate};
1065
            $needsconfirmation{resreserveid} = $reserve->{reserve_id};
1066
        }
1067
1045
    }
1068
    }
1046
1069
1047
    ## CHECK AGE RESTRICTION
1070
    ## CHECK AGE RESTRICTION
Lines 2635-2641 already renewed the loan. $error will contain the reason the renewal can not pro Link Here
2635
=cut
2658
=cut
2636
2659
2637
sub CanBookBeRenewed {
2660
sub CanBookBeRenewed {
2638
    my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
2661
    my ( $borrowernumber, $itemnumber, $override_limit, $date_due ) = @_;
2639
2662
2640
    my $dbh    = C4::Context->dbh;
2663
    my $dbh    = C4::Context->dbh;
2641
    my $renews = 1;
2664
    my $renews = 1;
Lines 2712-2717 sub CanBookBeRenewed { Link Here
2712
            }
2735
            }
2713
        }
2736
        }
2714
    }
2737
    }
2738
2739
    unless ($date_due) {
2740
        my $itemtype = $item->effective_itemtype;
2741
        my $patron_unblessed = $patron->unblessed;
2742
        $date_due = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2743
                                        dt_from_string( $issue->date_due, 'sql' ) :
2744
                                        DateTime->now( time_zone => C4::Context->tz());
2745
        $date_due =  CalcDateDue($date_due, $itemtype, _GetCircControlBranch($item->unblessed(), $patron_unblessed), $patron_unblessed, 'is a renewal');
2746
    }
2747
2748
    my $now = dt_from_string();
2749
    my $biblionumber = $item->biblionumber;
2750
    my $preventCheckoutOnSameReservePeriod =
2751
        C4::Context->preference("PreventCheckoutOnSameReservePeriod");
2752
    my $reserves_on_same_period =
2753
        ReservesOnSamePeriod($biblionumber, $itemnumber, $now->ymd, $date_due->ymd);
2754
    if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
2755
        $resfound = 1;
2756
    }
2757
2715
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2758
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2716
2759
2717
    return ( 1, undef ) if $override_limit;
2760
    return ( 1, undef ) if $override_limit;
(-)a/C4/Reserves.pm (+48 lines)
Lines 133-138 BEGIN { Link Here
133
        &SuspendAll
133
        &SuspendAll
134
134
135
        &GetReservesControlBranch
135
        &GetReservesControlBranch
136
        &ReservesOnSamePeriod
136
137
137
        IsItemOnHoldAndFound
138
        IsItemOnHoldAndFound
138
139
Lines 2174-2179 sub GetHoldRule { Link Here
2174
    return $sth->fetchrow_hashref();
2175
    return $sth->fetchrow_hashref();
2175
}
2176
}
2176
2177
2178
=head2 ReservesOnSamePeriod
2179
2180
    my $reserve = ReservesOnSamePeriod( $biblionumber, $itemnumber, $resdate, $expdate);
2181
2182
    Return the reserve that match the period ($resdate => $expdate),
2183
    undef if no reserve match.
2184
2185
=cut
2186
2187
sub ReservesOnSamePeriod {
2188
    my ($biblionumber, $itemnumber, $resdate, $expdate) = @_;
2189
2190
    unless ($resdate && $expdate) {
2191
        return;
2192
    }
2193
2194
    my @reserves = Koha::Holds->search({ biblionumber => $biblionumber });
2195
2196
    $resdate = output_pref({ str => $resdate, dateonly => 1, dateformat => 'iso' });
2197
    $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' });
2198
2199
    my @reserves_overlaps;
2200
    foreach my $reserve ( @reserves ) {
2201
2202
        unless ($reserve->reservedate && $reserve->expirationdate) {
2203
            next;
2204
        }
2205
2206
        if (date_ranges_overlap($resdate, $expdate,
2207
                                $reserve->reservedate,
2208
                                $reserve->expirationdate)) {
2209
2210
            # If reserve is item level and the requested periods overlap.
2211
            if ($itemnumber && $reserve->itemnumber == $itemnumber ) {
2212
                return [$reserve->unblessed];
2213
            }
2214
            push @reserves_overlaps, $reserve->unblessed;
2215
        }
2216
    }
2217
2218
    if ( @reserves_overlaps >= Koha::Items->search({ biblionumber => $biblionumber })->count() ) {
2219
        return \@reserves_overlaps;
2220
    }
2221
2222
    return;
2223
}
2224
2177
=head1 AUTHOR
2225
=head1 AUTHOR
2178
2226
2179
Koha Development Team <http://koha-community.org/>
2227
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 430-435 if (@$barcodes) { Link Here
430
        }
430
        }
431
        unless($confirm_required) {
431
        unless($confirm_required) {
432
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
432
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
433
            if ( $cancelreserve eq 'cancel' ) {
434
                CancelReserve({ reserve_id => $query->param('reserveid') });
435
            }
436
            $cancelreserve = $cancelreserve eq 'revert' ? 'revert' : undef;
433
            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, } );
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, } );
434
            $template_params->{issue} = $issue;
438
            $template_params->{issue} = $issue;
435
            $session->clear('auto_renew');
439
            $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 485-490 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
485
('PayPalUser',  '', NULL ,  'Your PayPal API username ( email address )',  'Free'),
485
('PayPalUser',  '', NULL ,  'Your PayPal API username ( email address )',  'Free'),
486
('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'),
486
('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'),
487
('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'),
487
('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'),
488
('PreventCheckoutOnSameReservePeriod','0','','Prevent to checkout a document if a reserve on same period exists','YesNo'),
489
('PreventReservesOnSamePeriod','0','','Prevent to hold a document if a reserve on same period exists','YesNo'),
488
('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'),
490
('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'),
489
('printcirculationslips','1','','If ON, enable printing circulation receipts','YesNo'),
491
('printcirculationslips','1','','If ON, enable printing circulation receipts','YesNo'),
490
('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'),
492
('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 498-503 Circulation: Link Here
498
            - "<br />ccode: [NEWFIC,NULL,DVD]"
498
            - "<br />ccode: [NEWFIC,NULL,DVD]"
499
            - "<br />itype: [NEWBK,\"\"]"
499
            - "<br />itype: [NEWBK,\"\"]"
500
            - "<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."
500
            - "<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
        -
502
            - pref: PreventCheckoutOnSameReservePeriod
503
              choices:
504
                  yes: Do
505
                  no: "Don't"
506
            - If yes, checkouts periods can't overlap with a reserve period.
501
    Checkin Policy:
507
    Checkin Policy:
502
        -
508
        -
503
            - pref: HoldsAutoFill
509
            - pref: HoldsAutoFill
Lines 801-806 Circulation: Link Here
801
            - pref: UpdateItemWhenLostFromHoldList
807
            - pref: UpdateItemWhenLostFromHoldList
802
              type: textarea
808
              type: textarea
803
              syntax: text/x-yaml
809
              syntax: text/x-yaml
810
        -
811
            - pref: PreventReservesOnSamePeriod
812
              choices:
813
                  yes: Do
814
                  no: "Don't"
815
            - If yes, Reserves periods for the same document can't overlap.
804
    Interlibrary Loans:
816
    Interlibrary Loans:
805
        -
817
        -
806
            - pref: ILLModule
818
            - pref: ILLModule
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt (+2 lines)
Lines 213-218 Link Here
213
                                    [% IF ( RESERVED ) %]
213
                                    [% IF ( RESERVED ) %]
214
                                        <p>
214
                                        <p>
215
                                            <input type="checkbox" id="cancelreserve" name="cancelreserve" value="cancel" />
215
                                            <input type="checkbox" id="cancelreserve" name="cancelreserve" value="cancel" />
216
                                            <input type="hidden" name="reserveid" value="[% resreserveid | html %]" />
216
                                            <label for="cancelreserve">Cancel hold</label>
217
                                            <label for="cancelreserve">Cancel hold</label>
217
                                        </p>
218
                                        </p>
218
                                    [% END %]
219
                                    [% END %]
Lines 221-226 Link Here
221
                                        <p>
222
                                        <p>
222
                                            <label for="cancelreserve">Cancel hold</label>
223
                                            <label for="cancelreserve">Cancel hold</label>
223
                                            <input type="radio" value="cancel" name="cancelreserve" id="cancelreserve" /><br />
224
                                            <input type="radio" value="cancel" name="cancelreserve" id="cancelreserve" /><br />
225
                                            <input type="hidden" name="reserveid" value="[% resreserveid | html %]" />
224
                                            <label for="revertreserve">Revert waiting status</label>
226
                                            <label for="revertreserve">Revert waiting status</label>
225
                                            <input type="radio" value="revert" name="cancelreserve" id="revertreserve" checked="checked"/>
227
                                            <input type="radio" value="revert" name="cancelreserve" id="revertreserve" checked="checked"/>
226
                                        </p>
228
                                        </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 confirm 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 282-287 if ( $query->param('place_reserve') ) { Link Here
282
            # Inserts a null into the 'itemnumber' field of 'reserves' table.
282
            # Inserts a null into the 'itemnumber' field of 'reserves' table.
283
            $itemNum = undef;
283
            $itemNum = undef;
284
        }
284
        }
285
286
        if ($canreserve) {
287
            if (C4::Context->preference("PreventReservesOnSamePeriod") &&
288
                ReservesOnSamePeriod($biblioNum, $itemNum, $startdate, $expiration_date)) {
289
                $canreserve = 0;
290
                $failed_holds++;
291
            }
292
        }
293
285
        my $notes = $query->param('notes_'.$biblioNum)||'';
294
        my $notes = $query->param('notes_'.$biblioNum)||'';
286
295
287
        if (   $maxreserves
296
        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 => 45;
21
use Test::More tests => 47;
22
use Test::MockModule;
22
use Test::MockModule;
23
23
24
use Data::Dumper;
24
use Data::Dumper;
Lines 382-387 subtest "CanBookBeRenewed tests" => sub { Link Here
382
    my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
382
    my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
383
    is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
383
    is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
384
384
385
    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
385
    my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
386
    my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
386
    is( $renewokay, 1, 'Can renew, no holds for this title or item');
387
    is( $renewokay, 1, 'Can renew, no holds for this title or item');
387
388
Lines 1250-1255 subtest "AllowRenewalIfOtherItemsAvailable tests" => sub { Link Here
1250
    is( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
1251
    is( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
1251
}
1252
}
1252
1253
1254
{
1255
    # Don't allow renewing on reserve period with PreventCheckoutOnSameReservePeriod enabled
1256
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 1 );
1257
1258
    my $start_issue_dt = DateTime->now();
1259
    $start_issue_dt->subtract( days => 15);
1260
    my $due_date = $start_issue_dt->clone;
1261
    $due_date->add( days => 17 );
1262
1263
    my $biblio = $builder->build({
1264
        source => 'Biblio',
1265
    });
1266
    my $biblionumber = $biblio->{biblionumber};
1267
1268
    my $item = $builder->build({
1269
        source => 'Item',
1270
        value => {
1271
            biblionumber => $biblionumber
1272
        }
1273
    });
1274
    my $itemnumber = $item->{itemnumber};
1275
1276
    my $issue = $builder->build({
1277
        source => 'Issue',
1278
        value => {
1279
            itemnumber => $itemnumber,
1280
            biblionumber => $biblionumber,
1281
            issuedate => $start_issue_dt->ymd,
1282
            date_due => $due_date->ymd,
1283
            onsite_checkout => 0
1284
        }
1285
    });
1286
1287
    my $reservedate = $due_date->clone;
1288
    $reservedate->add( days => 5 );
1289
    my $expirationdate = $reservedate->clone;
1290
    $expirationdate->add( days => 15 );
1291
    $builder->build({
1292
        source => 'Reserve',
1293
        value => {
1294
            biblionumber => $biblionumber,
1295
            itemnumber => $itemnumber,
1296
            reservedate => $reservedate->ymd,
1297
            expirationdate => $expirationdate->ymd
1298
        }
1299
    });
1300
1301
    my $requested_date_due = $due_date->clone;
1302
    $requested_date_due->add( days => 4 );
1303
    my ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1304
    is( $renewed, 1, 'CanBookBeRenewed should allow to renew if no reserve date overlap' );
1305
1306
    $requested_date_due->add( days => 2 );
1307
    ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1308
    is( $renewed, 0, 'CanBookBeRenewed should not allow to renew if reserve date overlap' );
1309
1310
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 0 );
1311
}
1312
1253
{
1313
{
1254
    my $library = $builder->build({ source => 'Branch' });
1314
    my $library = $builder->build({ source => 'Branch' });
1255
1315
(-)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