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

(-)a/C4/Circulation.pm (-10 / +47 lines)
Lines 1148-1153 sub CanBookBeIssued { Link Here
1148
                }
1148
                }
1149
            }
1149
            }
1150
        }
1150
        }
1151
1152
        my $now = dt_from_string();
1153
        my $preventCheckoutOnSameReservePeriod =
1154
            C4::Context->preference("PreventCheckoutOnSameReservePeriod");
1155
        my $reserves_on_same_period =
1156
            ReservesOnSamePeriod($item_object->biblionumber, $item_object->itemnumber, $now->ymd, $duedate->ymd);
1157
        if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
1158
            my $reserve = $reserves_on_same_period->[0];
1159
            my $patron = Koha::Patrons->find( $reserve->{borrowernumber} );
1160
            my $branchname = Koha::Libraries->find($reserve->{branchcode})->branchname;
1161
1162
            $needsconfirmation{RESERVED} = 1;
1163
            $needsconfirmation{resfirstname} = $patron->firstname;
1164
            $needsconfirmation{ressurname} = $patron->surname;
1165
            $needsconfirmation{rescardnumber} = $patron->cardnumber;
1166
            $needsconfirmation{resborrowernumber} = $patron->borrowernumber;
1167
            $needsconfirmation{resbranchname} = $branchname;
1168
            $needsconfirmation{resreservedate} = $reserve->{reservedate};
1169
            $needsconfirmation{reserve_id} = $reserve->{reserve_id};
1170
        }
1171
1151
    }
1172
    }
1152
1173
1153
    ## CHECK AGE RESTRICTION
1174
    ## CHECK AGE RESTRICTION
Lines 1402-1416 AddIssue does the following things : Link Here
1402
1423
1403
  - step 01: check that there is a borrowernumber & a barcode provided
1424
  - step 01: check that there is a borrowernumber & a barcode provided
1404
  - check for RENEWAL (book issued & being issued to the same patron)
1425
  - check for RENEWAL (book issued & being issued to the same patron)
1405
      - renewal YES = Calculate Charge & renew
1426
  - renewal YES = Calculate Charge & renew renewal NO = * BOOK
1406
      - renewal NO  =
1427
  - ACTUALLY ISSUED ? do a return if book is actually issued (but to
1407
          * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
1428
  - someone else) * RESERVE PLACED ?  fill reserve if reserve to this
1408
          * RESERVE PLACED ?
1429
  - patron cancel reserve or not, otherwise * TRANSFERT PENDING ?
1409
              - fill reserve if reserve to this patron
1430
  - complete the transfert * ISSUE THE BOOK
1410
              - cancel reserve or not, otherwise
1411
          * TRANSFERT PENDING ?
1412
              - complete the transfert
1413
          * ISSUE THE BOOK
1414
1431
1415
=back
1432
=back
1416
1433
Lines 2739-2745 already renewed the loan. $error will contain the reason the renewal can not pro Link Here
2739
=cut
2756
=cut
2740
2757
2741
sub CanBookBeRenewed {
2758
sub CanBookBeRenewed {
2742
    my ( $borrowernumber, $itemnumber, $override_limit, $cron ) = @_;
2759
    my ( $borrowernumber, $itemnumber, $override_limit, $cron, $date_due ) = @_;
2743
2760
2744
    my $dbh    = C4::Context->dbh;
2761
    my $dbh    = C4::Context->dbh;
2745
    my $renews = 1;
2762
    my $renews = 1;
Lines 2924-2929 sub CanBookBeRenewed { Link Here
2924
            }
2941
            }
2925
        }
2942
        }
2926
    }
2943
    }
2944
2945
    unless ($date_due) {
2946
        my $itemtype = $item->effective_itemtype;
2947
        my $patron_unblessed = $patron->unblessed;
2948
        $date_due = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2949
                                        dt_from_string( $issue->date_due, 'sql' ) :
2950
                                        DateTime->now( time_zone => C4::Context->tz());
2951
        $date_due =  CalcDateDue($date_due, $itemtype, _GetCircControlBranch($item->unblessed(), $patron_unblessed), $patron_unblessed, 'is a renewal');
2952
    }
2953
2954
    my $now = dt_from_string();
2955
    my $biblionumber = $item->biblionumber;
2956
    my $preventCheckoutOnSameReservePeriod =
2957
        C4::Context->preference("PreventCheckoutOnSameReservePeriod");
2958
    my $reserves_on_same_period =
2959
        ReservesOnSamePeriod($biblionumber, $itemnumber, $now->ymd, $date_due->ymd);
2960
    if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
2961
        $resfound = 1;
2962
    }
2963
2927
    if( $cron ) { #The cron wants to return 'too_soon' over 'on_reserve'
2964
    if( $cron ) { #The cron wants to return 'too_soon' over 'on_reserve'
2928
        return ( 0, $auto_renew  ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
2965
        return ( 0, $auto_renew  ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
2929
        return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2966
        return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
(-)a/C4/Reserves.pm (-1 / +50 lines)
Lines 36-42 use Koha::Biblios; Link Here
36
use Koha::Calendar;
36
use Koha::Calendar;
37
use Koha::CirculationRules;
37
use Koha::CirculationRules;
38
use Koha::Database;
38
use Koha::Database;
39
use Koha::DateUtils qw( dt_from_string output_pref );
39
use Koha::DateUtils qw( dt_from_string output_pref date_ranges_overlap );
40
use Koha::Hold;
40
use Koha::Hold;
41
use Koha::Holds;
41
use Koha::Holds;
42
use Koha::ItemTypes;
42
use Koha::ItemTypes;
Lines 145-150 BEGIN { Link Here
145
      MergeHolds
145
      MergeHolds
146
146
147
      RevertWaitingStatus
147
      RevertWaitingStatus
148
149
      ReservesOnSamePeriod
148
    );
150
    );
149
}
151
}
150
152
Lines 2300-2305 sub GetMaxPatronHoldsForRecord { Link Here
2300
    return $max;
2302
    return $max;
2301
}
2303
}
2302
2304
2305
=head2 ReservesOnSamePeriod
2306
2307
    my $reserve = ReservesOnSamePeriod( $biblionumber, $itemnumber, $resdate, $expdate);
2308
2309
    Return the reserve that match the period ($resdate => $expdate),
2310
    undef if no reserve match.
2311
2312
=cut
2313
2314
sub ReservesOnSamePeriod {
2315
    my ($biblionumber, $itemnumber, $resdate, $expdate) = @_;
2316
2317
    unless ($resdate && $expdate) {
2318
        return;
2319
    }
2320
2321
    my @reserves = Koha::Holds->search({ biblionumber => $biblionumber });
2322
2323
    $resdate = output_pref({ str => $resdate, dateonly => 1, dateformat => 'iso' });
2324
    $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' });
2325
2326
    my @reserves_overlaps;
2327
    foreach my $reserve ( @reserves ) {
2328
2329
        unless ($reserve->reservedate && $reserve->expirationdate) {
2330
            next;
2331
        }
2332
2333
        if (date_ranges_overlap($resdate, $expdate,
2334
                                $reserve->reservedate,
2335
                                $reserve->expirationdate)) {
2336
2337
            # If reserve is item level and the requested periods overlap.
2338
            if ($itemnumber && $reserve->itemnumber == $itemnumber ) {
2339
                return [$reserve->unblessed];
2340
            }
2341
            push @reserves_overlaps, $reserve->unblessed;
2342
        }
2343
    }
2344
2345
    if ( @reserves_overlaps >= Koha::Items->search({ biblionumber => $biblionumber })->count() ) {
2346
        return \@reserves_overlaps;
2347
    }
2348
2349
    return;
2350
}
2351
2303
=head1 AUTHOR
2352
=head1 AUTHOR
2304
2353
2305
Koha Development Team <http://koha-community.org/>
2354
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 338-341 sub format_sqldatetime { Link Here
338
    return q{};
338
    return q{};
339
}
339
}
340
340
341
=head2 date_ranges_overlap
342
343
    $bool = date_ranges_overlap($start1, $end1, $start2, $end2);
344
345
    Tells if first range ($start1 => $end1) overlaps
346
    the second one ($start2 => $end2)
347
348
=cut
349
350
sub date_ranges_overlap {
351
    my ($start1, $end1, $start2, $end2) = @_;
352
353
    $start1 = dt_from_string( $start1, 'iso' );
354
    $end1 = dt_from_string( $end1, 'iso' );
355
    $start2 = dt_from_string( $start2, 'iso' );
356
    $end2 = dt_from_string( $end2, 'iso' );
357
358
    if (
359
        # Start of range 2 is in the range 1.
360
        (
361
            DateTime->compare($start2, $start1) >= 0
362
            && DateTime->compare($start2, $end1) <= 0
363
        )
364
        ||
365
        # End of range 2 is in the range 1.
366
        (
367
            DateTime->compare($end2, $start1) >= 0
368
            && DateTime->compare($end2, $end1) <= 0
369
        )
370
        ||
371
        # Range 2 start before and end after range 1.
372
        (
373
            DateTime->compare($start2, $start1) < 0
374
            && DateTime->compare($end2, $end1) > 0
375
        )
376
    ) {
377
        return 1;
378
    }
379
380
    return;
381
}
382
341
1;
383
1;
(-)a/circ/circulation.pl (+4 lines)
Lines 406-411 if (@$barcodes) { Link Here
406
        }
406
        }
407
        unless($confirm_required) {
407
        unless($confirm_required) {
408
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
408
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
409
            if ( $cancelreserve eq 'cancel' ) {
410
                CancelReserve({ reserve_id => $query->param('reserveid') });
411
            }
412
            $cancelreserve = $cancelreserve eq 'revert' ? 'revert' : undef;
409
            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, } );
413
            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, } );
410
            $template_params->{issue} = $issue;
414
            $template_params->{issue} = $issue;
411
            $session->clear('auto_renew');
415
            $session->clear('auto_renew');
(-)a/installer/data/mysql/atomicupdate/bug_15261-add_preventchechoutonsamereserveperiod_syspref.pl (+14 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
return {
4
    bug_number => "15261",
5
    description => "Add new system preference PreventCheckoutOnSameReservePeriod",
6
    up => sub {
7
        my ($args) = @_;
8
        my ($dbh, $out) = @$args{qw(dbh out)};
9
        # Do you stuffs here
10
        $dbh->do(q{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')});
11
        # Print useful stuff here
12
        say $out "Update is going well so far";
13
    },
14
}
(-)a/installer/data/mysql/atomicupdate/bug_15261-add_preventreservesonsameperiod_syspref.pl (+14 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
return {
4
    bug_number => "15261",
5
    description => "Add new system preference PreventReservesOnSamePeriod",
6
    up => sub {
7
        my ($args) = @_;
8
        my ($dbh, $out) = @$args{qw(dbh out)};
9
        # Do you stuffs here
10
        $dbh->do(q{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')});
11
        # Print useful stuff here
12
        say $out "Update is going well so far";
13
    },
14
}
(-)a/installer/data/mysql/mandatory/sysprefs.sql (+2 lines)
Lines 533-538 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
533
('PrefillGuaranteeField', 'phone,email,streetnumber,address,city,state,zipcode,country', NULL, 'Prefill these fields in guarantee member entry form from guarantor patron record', 'Multiple'),
533
('PrefillGuaranteeField', 'phone,email,streetnumber,address,city,state,zipcode,country', NULL, 'Prefill these fields in guarantee member entry form from guarantor patron record', 'Multiple'),
534
('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'),
534
('PrefillItem','0','','When a new item is added, should it be prefilled with last created item values?','YesNo'),
535
('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'),
535
('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'),
536
('PreventCheckoutOnSameReservePeriod','0','','Prevent to checkout a document if a reserve on same period exists','YesNo'),
537
('PreventReservesOnSamePeriod','0','','Prevent to hold a document if a reserve on same period exists','YesNo'),
536
('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'),
538
('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'),
537
('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'),
539
('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'),
538
('PrivacyPolicyURL','',NULL,'This URL is used in messages about GDPR consents.', 'Free'),
540
('PrivacyPolicyURL','',NULL,'This URL is used in messages about GDPR consents.', 'Free'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (+12 lines)
Lines 565-570 Circulation: Link Here
565
            - to fill holds.
565
            - to fill holds.
566
            - "(list of not for loan values separated with a pipe '|')"
566
            - "(list of not for loan values separated with a pipe '|')"
567
        -
567
        -
568
            - pref: PreventCheckoutOnSameReservePeriod
569
              choices:
570
                  yes: Do
571
                  no: "Don't"
572
            - If yes, checkouts periods can't overlap with a reserve period.
573
        -
568
            - pref: HoldsAutoFill
574
            - pref: HoldsAutoFill
569
              choices:
575
              choices:
570
                  1: Do
576
                  1: Do
Lines 915-920 Circulation: Link Here
915
                  months: months
921
                  months: months
916
                  years: years
922
                  years: years
917
            - from reserve date.
923
            - from reserve date.
924
        -
925
            - pref: PreventReservesOnSamePeriod
926
              choices:
927
                  yes: Do
928
                  no: "Don't"
929
            - If yes, Reserves periods for the same document can't overlap.
918
    Interlibrary loans:
930
    Interlibrary loans:
919
        -
931
        -
920
            - pref: ILLModule
932
            - pref: ILLModule
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt (+2 lines)
Lines 250-255 Link Here
250
                                    [% IF ( RESERVED ) %]
250
                                    [% IF ( RESERVED ) %]
251
                                        <p>
251
                                        <p>
252
                                            <input type="checkbox" id="cancelreserve" name="cancelreserve" value="cancel" />
252
                                            <input type="checkbox" id="cancelreserve" name="cancelreserve" value="cancel" />
253
                                            <input type="hidden" name="reserveid" value="[% resreserveid | html %]" />
253
                                            <label for="cancelreserve">Cancel hold</label>
254
                                            <label for="cancelreserve">Cancel hold</label>
254
                                        </p>
255
                                        </p>
255
                                    [% END %]
256
                                    [% END %]
Lines 258-263 Link Here
258
                                        <p>
259
                                        <p>
259
                                            <label for="cancelreserve">Cancel hold</label>
260
                                            <label for="cancelreserve">Cancel hold</label>
260
                                            <input type="radio" value="cancel" name="cancelreserve" id="cancelreserve" /><br />
261
                                            <input type="radio" value="cancel" name="cancelreserve" id="cancelreserve" /><br />
262
                                            <input type="hidden" name="reserveid" value="[% resreserveid | html %]" />
261
                                            <label for="revertreserve">Revert waiting status</label>
263
                                            <label for="revertreserve">Revert waiting status</label>
262
                                            <input type="radio" value="revert" name="cancelreserve" id="revertreserve" checked="checked"/>
264
                                            <input type="radio" value="revert" name="cancelreserve" id="revertreserve" checked="checked"/>
263
                                        </p>
265
                                        </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 (-1 / +10 lines)
Lines 24-30 use CGI qw ( -utf8 ); Link Here
24
use C4::Auth qw( get_template_and_user );
24
use C4::Auth qw( get_template_and_user );
25
use C4::Koha qw( getitemtypeimagelocation getitemtypeimagesrc );
25
use C4::Koha qw( getitemtypeimagelocation getitemtypeimagesrc );
26
use C4::Circulation qw( GetBranchItemRule GetTransfers );
26
use C4::Circulation qw( GetBranchItemRule GetTransfers );
27
use C4::Reserves qw( CanItemBeReserved CanBookBeReserved AddReserve GetReservesControlBranch IsAvailableForItemLevelRequest );
27
use C4::Reserves qw( CanItemBeReserved CanBookBeReserved AddReserve GetReservesControlBranch IsAvailableForItemLevelRequest ReservesOnSamePeriod );
28
use C4::Biblio qw( GetBiblioData GetFrameworkCode GetMarcBiblio );
28
use C4::Biblio qw( GetBiblioData GetFrameworkCode GetMarcBiblio );
29
use C4::Items qw( GetHostItemsInfo GetItemsInfo );
29
use C4::Items qw( GetHostItemsInfo GetItemsInfo );
30
use C4::Output qw( output_html_with_http_headers );
30
use C4::Output qw( output_html_with_http_headers );
Lines 285-290 if ( $query->param('place_reserve') ) { Link Here
285
            # Inserts a null into the 'itemnumber' field of 'reserves' table.
285
            # Inserts a null into the 'itemnumber' field of 'reserves' table.
286
            $itemNum = undef;
286
            $itemNum = undef;
287
        }
287
        }
288
289
        if ($canreserve) {
290
            if (C4::Context->preference("PreventReservesOnSamePeriod") &&
291
                ReservesOnSamePeriod($biblioNum, $itemNum, $startdate, $expiration_date)) {
292
                $canreserve = 0;
293
                $failed_holds++;
294
            }
295
        }
296
288
        my $notes = $query->param('notes_'.$biblioNum)||'';
297
        my $notes = $query->param('notes_'.$biblioNum)||'';
289
298
290
        if (   $maxreserves
299
        if (   $maxreserves
(-)a/reserve/placerequest.pl (-4 / +47 lines)
Lines 23-41 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::Reserves qw( CanItemBeReserved AddReserve CanBookBeReserved );
29
use C4::Reserves qw( CanItemBeReserved AddReserve CanBookBeReserved ReservesOnSamePeriod);
28
use C4::Auth qw( checkauth );
30
use C4::Auth qw/get_template_and_user/;
31
use C4::Output qw( output_html_with_http_headers );
29
32
30
use Koha::Items;
33
use Koha::Items;
31
use Koha::Patrons;
34
use Koha::Patrons;
32
35
33
my $input = CGI->new();
36
my $input = CGI->new();
34
37
35
checkauth($input, 0, { reserveforothers => 'place_holds' }, 'intranet');
38
my ($template, $loggedinuser, $cookie) = get_template_and_user({
39
    template_name   => 'reserve/placerequest.tt',
40
    query           => $input,
41
    type            => 'intranet',
42
    authnotrequired => 0,
43
    flagsrequired   => { reserveforothers => 'place_holds' },
44
});
36
45
37
my @bibitems       = $input->multi_param('biblioitem');
46
my @bibitems       = $input->multi_param('biblioitem');
38
my @reqbib         = $input->multi_param('reqbib');
39
my $biblionumber   = $input->param('biblionumber');
47
my $biblionumber   = $input->param('biblionumber');
40
my $borrowernumber = $input->param('borrowernumber');
48
my $borrowernumber = $input->param('borrowernumber');
41
my $notes          = $input->param('notes');
49
my $notes          = $input->param('notes');
Lines 48-53 my $checkitem = $input->param('checkitem'); Link Here
48
my $expirationdate = $input->param('expiration_date');
56
my $expirationdate = $input->param('expiration_date');
49
my $itemtype       = $input->param('itemtype') || undef;
57
my $itemtype       = $input->param('itemtype') || undef;
50
my $non_priority   = $input->param('non_priority');
58
my $non_priority   = $input->param('non_priority');
59
my $confirm        = $input->param('confirm');
60
my @confirm_biblionumbers = $input->multi_param('confirm_biblionumbers');
51
61
52
my $borrower = Koha::Patrons->find( $borrowernumber );
62
my $borrower = Koha::Patrons->find( $borrowernumber );
53
$borrower = $borrower->unblessed if $borrower;
63
$borrower = $borrower->unblessed if $borrower;
Lines 71-78 foreach my $bibnum (@biblionumbers) { Link Here
71
my $found;
81
my $found;
72
82
73
if ( $type eq 'str8' && $borrower ) {
83
if ( $type eq 'str8' && $borrower ) {
84
    if (C4::Context->preference('PreventReservesOnSamePeriod') && !$confirm) {
85
        my %overlap_reserves;
86
        foreach my $biblionumber ( keys %bibinfos ) {
87
            my $overlapping_reserves = ReservesOnSamePeriod($biblionumber, $checkitem, $startdate, $expirationdate);
88
            if ($overlapping_reserves && 0 < scalar @$overlapping_reserves) {
89
                $overlap_reserves{$biblionumber} = {
90
                    title => $multi_hold ? $bibinfos{$biblionumber}->{title} : $title,
91
                    checkitem => $checkitem,
92
                    rank => $multi_hold ? $bibinfos{$biblionumber}->{rank} : $rank[0],
93
                };
94
            }
95
        }
96
97
        if (scalar keys %overlap_reserves) {
98
            $template->param(
99
                borrowernumber => $borrowernumber,
100
                biblionumbers => $biblionumbers,
101
                biblionumber => $biblionumber,
102
                overlap_reserves => \%overlap_reserves,
103
                reserve_date => $startdate,
104
                expiration_date => $expirationdate,
105
                notes => $notes,
106
                rank_request => \@rank,
107
                pickup => $branch,
108
                multi_hold => $multi_hold,
109
            );
110
111
            output_html_with_http_headers $input, $cookie, $template->output;
112
            exit;
113
        }
114
    }
74
115
75
    foreach my $biblionumber ( keys %bibinfos ) {
116
    foreach my $biblionumber ( keys %bibinfos ) {
117
        next if ($confirm && none { $_ eq $biblionumber } @confirm_biblionumbers);
118
76
        my $count = @bibitems;
119
        my $count = @bibitems;
77
        @bibitems = sort @bibitems;
120
        @bibitems = sort @bibitems;
78
        my $i2 = 1;
121
        my $i2 = 1;
(-)a/svc/renew (-1 / +1 lines)
Lines 59-65 $data->{borrowernumber} = $borrowernumber; Link Here
59
$data->{branchcode} = $branchcode;
59
$data->{branchcode} = $branchcode;
60
60
61
( $data->{renew_okay}, $data->{error} ) =
61
( $data->{renew_okay}, $data->{error} ) =
62
  CanBookBeRenewed( $borrowernumber, $itemnumber, $override_limit );
62
  CanBookBeRenewed( $borrowernumber, $itemnumber, $override_limit, $date_due );
63
63
64
# If we're allowing reserved items to be renewed...
64
# If we're allowing reserved items to be renewed...
65
if ( $data->{error} && $data->{error} eq 'on_reserve' && C4::Context->preference('AllowRenewalOnHoldOverride')) {
65
if ( $data->{error} && $data->{error} eq 'on_reserve' && C4::Context->preference('AllowRenewalOnHoldOverride')) {
(-)a/t/db_dependent/Circulation.t (+60 lines)
Lines 521-526 subtest "CanBookBeRenewed tests" => sub { Link Here
521
    my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
521
    my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
522
    is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
522
    is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
523
523
524
    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
524
    my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
525
    my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
525
    is( $renewokay, 1, 'Can renew, no holds for this title or item');
526
    is( $renewokay, 1, 'Can renew, no holds for this title or item');
526
527
Lines 1735-1740 subtest "AllowRenewalIfOtherItemsAvailable tests" => sub { Link Here
1735
}
1736
}
1736
1737
1737
{
1738
{
1739
    # Don't allow renewing on reserve period with PreventCheckoutOnSameReservePeriod enabled
1740
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 1 );
1741
1742
    my $start_issue_dt = DateTime->now();
1743
    $start_issue_dt->subtract( days => 15);
1744
    my $due_date = $start_issue_dt->clone;
1745
    $due_date->add( days => 17 );
1746
1747
    my $biblio = $builder->build({
1748
        source => 'Biblio',
1749
    });
1750
    my $biblionumber = $biblio->{biblionumber};
1751
1752
    my $item = $builder->build({
1753
        source => 'Item',
1754
        value => {
1755
            biblionumber => $biblionumber
1756
        }
1757
    });
1758
    my $itemnumber = $item->{itemnumber};
1759
1760
    my $issue = $builder->build({
1761
        source => 'Issue',
1762
        value => {
1763
            itemnumber => $itemnumber,
1764
            biblionumber => $biblionumber,
1765
            issuedate => $start_issue_dt->ymd,
1766
            date_due => $due_date->ymd,
1767
            onsite_checkout => 0
1768
        }
1769
    });
1770
1771
    my $reservedate = $due_date->clone;
1772
    $reservedate->add( days => 5 );
1773
    my $expirationdate = $reservedate->clone;
1774
    $expirationdate->add( days => 15 );
1775
    $builder->build({
1776
        source => 'Reserve',
1777
        value => {
1778
            biblionumber => $biblionumber,
1779
            itemnumber => $itemnumber,
1780
            reservedate => $reservedate->ymd,
1781
            expirationdate => $expirationdate->ymd
1782
        }
1783
    });
1784
1785
    my $requested_date_due = $due_date->clone;
1786
    $requested_date_due->add( days => 4 );
1787
    my ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1788
    is( $renewed, 1, 'CanBookBeRenewed should allow to renew if no reserve date overlap' );
1789
1790
    $requested_date_due->add( days => 2 );
1791
    ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1792
    is( $renewed, 0, 'CanBookBeRenewed should not allow to renew if reserve date overlap' );
1793
1794
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 0 );
1795
}
1796
1797
{
1738
    my $library = $builder->build({ source => 'Branch' });
1798
    my $library = $builder->build({ source => 'Branch' });
1739
1799
1740
    my $biblio = $builder->build_sample_biblio();
1800
    my $biblio = $builder->build_sample_biblio();
(-)a/t/db_dependent/Circulation/CanBookBeIssued.t (+106 lines)
Line 0 Link Here
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 1;
21
use C4::Members;
22
use C4::Reserves;
23
use C4::Circulation;
24
use Koha::DateUtils;
25
26
use t::lib::TestBuilder;
27
28
my $schema  = Koha::Database->new->schema;
29
$schema->storage->txn_begin;
30
31
my $builder = t::lib::TestBuilder->new();
32
33
subtest 'Tests for CanBookBeIssued with overlap reserves' => sub {
34
    plan tests => 6;
35
36
    my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
37
    my $branch = $builder->build({ source => 'Branch' });
38
    my $branchcode = $branch->{branchcode};
39
40
    my $borrower = $builder->build_object({
41
        class => 'Koha::Patrons',
42
        value => {
43
            branchcode   => $branchcode,
44
            categorycode => $categorycode,
45
        }
46
    });
47
    my $borrowernumber = $borrower->borrowernumber;
48
49
    my $biblio = $builder->build({source => 'Biblio'});
50
    my $biblioitem = $builder->build({
51
        source => 'Biblioitem',
52
        value => {
53
            biblionumber => $biblio->{biblionumber},
54
        },
55
    });
56
    my $item = $builder->build({
57
        source => 'Item',
58
        value => {
59
            biblionumber => $biblio->{biblionumber},
60
            biblioitemnumber => $biblioitem->{biblioitemnumber},
61
            withdrawn => 0,
62
            itemlost => 0,
63
            notforloan => 0,
64
        },
65
    });
66
67
68
    my $startdate = dt_from_string();
69
    $startdate->add_duration(DateTime::Duration->new(days => 4));
70
    my $expdate = $startdate->clone();
71
    $expdate->add_duration(DateTime::Duration->new(days => 10));
72
73
    my $reserveid = AddReserve($branchcode, $borrowernumber,
74
        $item->{biblionumber}, undef,  1, $startdate->ymd(), $expdate->ymd,
75
        undef, undef, undef, undef);
76
77
    my $non_overlap_duedate = dt_from_string();
78
    $non_overlap_duedate->add_duration(DateTime::Duration->new(days => 2));
79
    my ($error, $question, $alerts ) =
80
        CanBookBeIssued($borrower, $item->{barcode}, $non_overlap_duedate, 1, 0);
81
82
    is_deeply($error, {}, "");
83
    is_deeply($question, {}, "");
84
    is_deeply($alerts, {}, "");
85
86
    my $overlap_duedate = dt_from_string();
87
    $overlap_duedate->add_duration(DateTime::Duration->new(days => 5));
88
    ($error, $question, $alerts ) =
89
        CanBookBeIssued($borrower, $item->{barcode}, $overlap_duedate, 1, 0);
90
91
    is_deeply($error, {}, "");
92
    my $expected = {
93
        RESERVED => 1,
94
        resfirstname => $borrower->firstname,
95
        ressurname => $borrower->surname,
96
        rescardnumber => $borrower->cardnumber,
97
        resborrowernumber => $borrower->borrowernumber,
98
        resbranchname => $branch->{branchname},
99
        resreservedate => $startdate->ymd,
100
        resreserveid => $reserveid,
101
    };
102
    is_deeply($question, $expected, "");
103
    is_deeply($alerts, {}, "");
104
};
105
106
$schema->storage->txn_rollback;
(-)a/t/db_dependent/Reserves/ReserveDate.t (-1 / +108 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 6;
21
use Test::Warn;
22
23
use MARC::Record;
24
use DateTime::Duration;
25
26
use C4::Biblio;
27
use C4::Items;
28
use C4::Members;
29
use C4::Circulation;
30
use Koha::Holds;
31
use t::lib::TestBuilder;
32
33
use Koha::DateUtils;
34
35
36
use_ok('C4::Reserves');
37
38
my $dbh = C4::Context->dbh;
39
40
# Start transaction
41
$dbh->{AutoCommit} = 0;
42
$dbh->{RaiseError} = 1;
43
44
my $builder = t::lib::TestBuilder->new();
45
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
46
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
47
48
my $borrower = $builder->build({
49
    source => 'Borrower',
50
    value => {
51
        branchcode   => $branchcode,
52
        categorycode => $categorycode,
53
    }
54
});
55
56
my $borrower2 = $builder->build({
57
    source => 'Borrower',
58
    value => {
59
        branchcode   => $branchcode,
60
        categorycode => $categorycode,
61
    }
62
});
63
64
my $borrowernumber = $borrower->{borrowernumber};
65
my $borrowernumber2 = $borrower2->{borrowernumber};
66
67
# Create a helper biblio
68
my $biblio = MARC::Record->new();
69
my $title = 'Alone in the Dark';
70
my $author = 'Karen Rose';
71
if( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
72
    $biblio->append_fields(
73
        MARC::Field->new('600', '', '1', a => $author),
74
        MARC::Field->new('200', '', '', a => $title),
75
    );
76
}
77
else {
78
    $biblio->append_fields(
79
        MARC::Field->new('100', '', '', a => $author),
80
        MARC::Field->new('245', '', '', a => $title),
81
    );
82
}
83
my ($bibnum, $bibitemnum);
84
($bibnum, $title, $bibitemnum) = AddBiblio($biblio, '');
85
86
my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $branchcode, holdingbranch => $branchcode, barcode => '333' } , $bibnum);
87
88
C4::Context->set_preference('AllowHoldDateInFuture', 1);
89
90
AddReserve($branchcode, $borrowernumber, $bibnum,
91
           $bibitemnum,  1, '2015-11-01', '2015-11-20', undef,
92
           undef, undef, undef);
93
94
is(ReservesOnSamePeriod($bibnum, undef, '2015-11-25', '2015-11-30'), undef, "Period doesn't overlaps");
95
96
ok(ReservesOnSamePeriod($bibnum, undef, '2015-11-02', '2015-11-10'), "Period overlaps");
97
98
my ($item_bibnum2, $item_bibitemnum2, $itemnumber2) = AddItem({ homebranch => $branchcode, holdingbranch => $branchcode, barcode => '444' } , $bibnum);
99
is(ReservesOnSamePeriod($bibnum, undef, '2015-11-02', '2015-11-10'), undef, "Period overlaps but there is 2 items");
100
101
AddReserve($branchcode, $borrowernumber2, $bibnum,
102
           $bibitemnum,  1, '2016-02-01', '2016-02-10', undef,
103
           undef, $itemnumber, undef);
104
is(ReservesOnSamePeriod($bibnum, $itemnumber, '02/12/2015', '10/12/2015'), undef, "Period on item does not overlap (with metric date format)");
105
106
my $reserve = ReservesOnSamePeriod($bibnum, $itemnumber, '2016-01-31', '2016-02-05');
107
is($reserve->[0]->{itemnumber}, $itemnumber, 'Period on item overlaps');
108
$dbh->rollback;

Return to bug 15261