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 2641-2647 already renewed the loan. $error will contain the reason the renewal can not pro Link Here
2641
=cut
2664
=cut
2642
2665
2643
sub CanBookBeRenewed {
2666
sub CanBookBeRenewed {
2644
    my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
2667
    my ( $borrowernumber, $itemnumber, $override_limit, $date_due ) = @_;
2645
2668
2646
    my $dbh    = C4::Context->dbh;
2669
    my $dbh    = C4::Context->dbh;
2647
    my $renews = 1;
2670
    my $renews = 1;
Lines 2718-2723 sub CanBookBeRenewed { Link Here
2718
            }
2741
            }
2719
        }
2742
        }
2720
    }
2743
    }
2744
2745
    unless ($date_due) {
2746
        my $itemtype = $item->effective_itemtype;
2747
        my $patron_unblessed = $patron->unblessed;
2748
        $date_due = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2749
                                        dt_from_string( $issue->date_due, 'sql' ) :
2750
                                        DateTime->now( time_zone => C4::Context->tz());
2751
        $date_due =  CalcDateDue($date_due, $itemtype, _GetCircControlBranch($item->unblessed(), $patron_unblessed), $patron_unblessed, 'is a renewal');
2752
    }
2753
2754
    my $now = dt_from_string();
2755
    my $biblionumber = $item->biblionumber;
2756
    my $preventCheckoutOnSameReservePeriod =
2757
        C4::Context->preference("PreventCheckoutOnSameReservePeriod");
2758
    my $reserves_on_same_period =
2759
        ReservesOnSamePeriod($biblionumber, $itemnumber, $now->ymd, $date_due->ymd);
2760
    if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
2761
        $resfound = 1;
2762
    }
2763
2721
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2764
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2722
2765
2723
    return ( 1, undef ) if $override_limit;
2766
    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 2180-2185 sub GetHoldRule { Link Here
2180
    return $sth->fetchrow_hashref();
2181
    return $sth->fetchrow_hashref();
2181
}
2182
}
2182
2183
2184
=head2 ReservesOnSamePeriod
2185
2186
    my $reserve = ReservesOnSamePeriod( $biblionumber, $itemnumber, $resdate, $expdate);
2187
2188
    Return the reserve that match the period ($resdate => $expdate),
2189
    undef if no reserve match.
2190
2191
=cut
2192
2193
sub ReservesOnSamePeriod {
2194
    my ($biblionumber, $itemnumber, $resdate, $expdate) = @_;
2195
2196
    unless ($resdate && $expdate) {
2197
        return;
2198
    }
2199
2200
    my @reserves = Koha::Holds->search({ biblionumber => $biblionumber });
2201
2202
    $resdate = output_pref({ str => $resdate, dateonly => 1, dateformat => 'iso' });
2203
    $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' });
2204
2205
    my @reserves_overlaps;
2206
    foreach my $reserve ( @reserves ) {
2207
2208
        unless ($reserve->reservedate && $reserve->expirationdate) {
2209
            next;
2210
        }
2211
2212
        if (date_ranges_overlap($resdate, $expdate,
2213
                                $reserve->reservedate,
2214
                                $reserve->expirationdate)) {
2215
2216
            # If reserve is item level and the requested periods overlap.
2217
            if ($itemnumber && $reserve->itemnumber == $itemnumber ) {
2218
                return [$reserve->unblessed];
2219
            }
2220
            push @reserves_overlaps, $reserve->unblessed;
2221
        }
2222
    }
2223
2224
    if ( @reserves_overlaps >= Koha::Items->search({ biblionumber => $biblionumber })->count() ) {
2225
        return \@reserves_overlaps;
2226
    }
2227
2228
    return;
2229
}
2230
2183
=head1 AUTHOR
2231
=head1 AUTHOR
2184
2232
2185
Koha Development Team <http://koha-community.org/>
2233
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 491-496 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
491
('PayPalUser',  '', NULL ,  'Your PayPal API username ( email address )',  'Free'),
491
('PayPalUser',  '', NULL ,  'Your PayPal API username ( email address )',  'Free'),
492
('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'),
492
('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'),
493
('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'),
493
('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'),
494
('PreventCheckoutOnSameReservePeriod','0','','Prevent to checkout a document if a reserve on same period exists','YesNo'),
495
('PreventReservesOnSamePeriod','0','','Prevent to hold a document if a reserve on same period exists','YesNo'),
494
('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'),
496
('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'),
495
('printcirculationslips','1','','If ON, enable printing circulation receipts','YesNo'),
497
('printcirculationslips','1','','If ON, enable printing circulation receipts','YesNo'),
496
('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'),
498
('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 496-501 Circulation: Link Here
496
            - "<br />ccode: [NEWFIC,NULL,DVD]"
496
            - "<br />ccode: [NEWFIC,NULL,DVD]"
497
            - "<br />itype: [NEWBK,\"\"]"
497
            - "<br />itype: [NEWBK,\"\"]"
498
            - "<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."
498
            - "<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."
499
        -
500
            - pref: PreventCheckoutOnSameReservePeriod
501
              choices:
502
                  yes: Do
503
                  no: "Don't"
504
            - If yes, checkouts periods can't overlap with a reserve period.
499
    Checkin Policy:
505
    Checkin Policy:
500
        -
506
        -
501
            - pref: HoldsAutoFill
507
            - pref: HoldsAutoFill
Lines 805-810 Circulation: Link Here
805
            - pref: UpdateItemWhenLostFromHoldList
811
            - pref: UpdateItemWhenLostFromHoldList
806
              type: textarea
812
              type: textarea
807
              syntax: text/x-yaml
813
              syntax: text/x-yaml
814
        -
815
            - pref: PreventReservesOnSamePeriod
816
              choices:
817
                  yes: Do
818
                  no: "Don't"
819
            - If yes, Reserves periods for the same document can't overlap.
808
    Interlibrary Loans:
820
    Interlibrary Loans:
809
        -
821
        -
810
            - pref: ILLModule
822
            - 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 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 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