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

(-)a/C4/Circulation.pm (-1 / +44 lines)
Lines 1035-1040 sub CanBookBeIssued { Link Here
1035
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1035
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1036
                    $needsconfirmation{'resbranchcode'} = $res->{branchcode};
1036
                    $needsconfirmation{'resbranchcode'} = $res->{branchcode};
1037
                    $needsconfirmation{'reswaitingdate'} = $res->{'waitingdate'};
1037
                    $needsconfirmation{'reswaitingdate'} = $res->{'waitingdate'};
1038
                    $needsconfirmation{resreserveid} = $res->{reserve_id};
1038
                }
1039
                }
1039
                elsif ( $restype eq "Reserved" ) {
1040
                elsif ( $restype eq "Reserved" ) {
1040
                    # The item is on reserve for someone else.
1041
                    # The item is on reserve for someone else.
Lines 1045-1053 sub CanBookBeIssued { Link Here
1045
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1046
                    $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber;
1046
                    $needsconfirmation{'resbranchcode'} = $patron->branchcode;
1047
                    $needsconfirmation{'resbranchcode'} = $patron->branchcode;
1047
                    $needsconfirmation{'resreservedate'} = $res->{reservedate};
1048
                    $needsconfirmation{'resreservedate'} = $res->{reservedate};
1049
                    $needsconfirmation{resreserveid} = $res->{reserve_id};
1048
                }
1050
                }
1049
            }
1051
            }
1050
        }
1052
        }
1053
1054
        my $now = dt_from_string();
1055
        my $preventCheckoutOnSameReservePeriod =
1056
            C4::Context->preference("PreventCheckoutOnSameReservePeriod");
1057
        my $reserves_on_same_period =
1058
            ReservesOnSamePeriod($item_object->biblionumber, $item_object->itemnumber, $now->ymd, $duedate->ymd);
1059
        if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
1060
            my $reserve = $reserves_on_same_period->[0];
1061
            my $patron = Koha::Patrons->find( $reserve->{borrowernumber} );
1062
            my $branchname = Koha::Libraries->find($reserve->{branchcode})->branchname;
1063
1064
            $needsconfirmation{RESERVED} = 1;
1065
            $needsconfirmation{resfirstname} = $patron->firstname;
1066
            $needsconfirmation{ressurname} = $patron->surname;
1067
            $needsconfirmation{rescardnumber} = $patron->cardnumber;
1068
            $needsconfirmation{resborrowernumber} = $patron->borrowernumber;
1069
            $needsconfirmation{resbranchname} = $branchname;
1070
            $needsconfirmation{resreservedate} = $reserve->{reservedate};
1071
            $needsconfirmation{resreserveid} = $reserve->{reserve_id};
1072
        }
1073
1051
    }
1074
    }
1052
1075
1053
    ## CHECK AGE RESTRICTION
1076
    ## CHECK AGE RESTRICTION
Lines 2667-2673 already renewed the loan. $error will contain the reason the renewal can not pro Link Here
2667
=cut
2690
=cut
2668
2691
2669
sub CanBookBeRenewed {
2692
sub CanBookBeRenewed {
2670
    my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
2693
    my ( $borrowernumber, $itemnumber, $override_limit, $date_due ) = @_;
2671
2694
2672
    my $dbh    = C4::Context->dbh;
2695
    my $dbh    = C4::Context->dbh;
2673
    my $renews = 1;
2696
    my $renews = 1;
Lines 2744-2749 sub CanBookBeRenewed { Link Here
2744
            }
2767
            }
2745
        }
2768
        }
2746
    }
2769
    }
2770
2771
    unless ($date_due) {
2772
        my $itemtype = $item->effective_itemtype;
2773
        my $patron_unblessed = $patron->unblessed;
2774
        $date_due = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2775
                                        dt_from_string( $issue->date_due, 'sql' ) :
2776
                                        DateTime->now( time_zone => C4::Context->tz());
2777
        $date_due =  CalcDateDue($date_due, $itemtype, _GetCircControlBranch($item->unblessed(), $patron_unblessed), $patron_unblessed, 'is a renewal');
2778
    }
2779
2780
    my $now = dt_from_string();
2781
    my $biblionumber = $item->biblionumber;
2782
    my $preventCheckoutOnSameReservePeriod =
2783
        C4::Context->preference("PreventCheckoutOnSameReservePeriod");
2784
    my $reserves_on_same_period =
2785
        ReservesOnSamePeriod($biblionumber, $itemnumber, $now->ymd, $date_due->ymd);
2786
    if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
2787
        $resfound = 1;
2788
    }
2789
2747
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2790
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2748
2791
2749
    return ( 1, undef ) if $override_limit;
2792
    return ( 1, undef ) if $override_limit;
(-)a/C4/Reserves.pm (+48 lines)
Lines 132-137 BEGIN { Link Here
132
        &SuspendAll
132
        &SuspendAll
133
133
134
        &GetReservesControlBranch
134
        &GetReservesControlBranch
135
        &ReservesOnSamePeriod
135
136
136
        IsItemOnHoldAndFound
137
        IsItemOnHoldAndFound
137
138
Lines 2226-2231 sub GetHoldRule { Link Here
2226
    return $sth->fetchrow_hashref();
2227
    return $sth->fetchrow_hashref();
2227
}
2228
}
2228
2229
2230
=head2 ReservesOnSamePeriod
2231
2232
    my $reserve = ReservesOnSamePeriod( $biblionumber, $itemnumber, $resdate, $expdate);
2233
2234
    Return the reserve that match the period ($resdate => $expdate),
2235
    undef if no reserve match.
2236
2237
=cut
2238
2239
sub ReservesOnSamePeriod {
2240
    my ($biblionumber, $itemnumber, $resdate, $expdate) = @_;
2241
2242
    unless ($resdate && $expdate) {
2243
        return;
2244
    }
2245
2246
    my @reserves = Koha::Holds->search({ biblionumber => $biblionumber });
2247
2248
    $resdate = output_pref({ str => $resdate, dateonly => 1, dateformat => 'iso' });
2249
    $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' });
2250
2251
    my @reserves_overlaps;
2252
    foreach my $reserve ( @reserves ) {
2253
2254
        unless ($reserve->reservedate && $reserve->expirationdate) {
2255
            next;
2256
        }
2257
2258
        if (date_ranges_overlap($resdate, $expdate,
2259
                                $reserve->reservedate,
2260
                                $reserve->expirationdate)) {
2261
2262
            # If reserve is item level and the requested periods overlap.
2263
            if ($itemnumber && $reserve->itemnumber == $itemnumber ) {
2264
                return [$reserve->unblessed];
2265
            }
2266
            push @reserves_overlaps, $reserve->unblessed;
2267
        }
2268
    }
2269
2270
    if ( @reserves_overlaps >= Koha::Items->search({ biblionumber => $biblionumber })->count() ) {
2271
        return \@reserves_overlaps;
2272
    }
2273
2274
    return;
2275
}
2276
2229
=head1 AUTHOR
2277
=head1 AUTHOR
2230
2278
2231
Koha Development Team <http://koha-community.org/>
2279
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 433-438 if (@$barcodes) { Link Here
433
        }
433
        }
434
        unless($confirm_required) {
434
        unless($confirm_required) {
435
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
435
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
436
            if ( $cancelreserve eq 'cancel' ) {
437
                CancelReserve({ reserve_id => $query->param('reserveid') });
438
            }
439
            $cancelreserve = $cancelreserve eq 'revert' ? 'revert' : undef;
436
            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, } );
440
            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
            $template_params->{issue} = $issue;
441
            $template_params->{issue} = $issue;
438
            $session->clear('auto_renew');
442
            $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
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 1271-1276 subtest "AllowRenewalIfOtherItemsAvailable tests" => sub { Link Here
1271
    is( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
1272
    is( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
1272
}
1273
}
1273
1274
1275
{
1276
    # Don't allow renewing on reserve period with PreventCheckoutOnSameReservePeriod enabled
1277
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 1 );
1278
1279
    my $start_issue_dt = DateTime->now();
1280
    $start_issue_dt->subtract( days => 15);
1281
    my $due_date = $start_issue_dt->clone;
1282
    $due_date->add( days => 17 );
1283
1284
    my $biblio = $builder->build({
1285
        source => 'Biblio',
1286
    });
1287
    my $biblionumber = $biblio->{biblionumber};
1288
1289
    my $item = $builder->build({
1290
        source => 'Item',
1291
        value => {
1292
            biblionumber => $biblionumber
1293
        }
1294
    });
1295
    my $itemnumber = $item->{itemnumber};
1296
1297
    my $issue = $builder->build({
1298
        source => 'Issue',
1299
        value => {
1300
            itemnumber => $itemnumber,
1301
            biblionumber => $biblionumber,
1302
            issuedate => $start_issue_dt->ymd,
1303
            date_due => $due_date->ymd,
1304
            onsite_checkout => 0
1305
        }
1306
    });
1307
1308
    my $reservedate = $due_date->clone;
1309
    $reservedate->add( days => 5 );
1310
    my $expirationdate = $reservedate->clone;
1311
    $expirationdate->add( days => 15 );
1312
    $builder->build({
1313
        source => 'Reserve',
1314
        value => {
1315
            biblionumber => $biblionumber,
1316
            itemnumber => $itemnumber,
1317
            reservedate => $reservedate->ymd,
1318
            expirationdate => $expirationdate->ymd
1319
        }
1320
    });
1321
1322
    my $requested_date_due = $due_date->clone;
1323
    $requested_date_due->add( days => 4 );
1324
    my ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1325
    is( $renewed, 1, 'CanBookBeRenewed should allow to renew if no reserve date overlap' );
1326
1327
    $requested_date_due->add( days => 2 );
1328
    ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1329
    is( $renewed, 0, 'CanBookBeRenewed should not allow to renew if reserve date overlap' );
1330
1331
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 0 );
1332
}
1333
1274
{
1334
{
1275
    my $library = $builder->build({ source => 'Branch' });
1335
    my $library = $builder->build({ source => 'Branch' });
1276
1336
(-)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