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

(-)a/C4/Circulation.pm (-13 / +55 lines)
Lines 1150-1155 sub CanBookBeIssued { Link Here
1150
                }
1150
                }
1151
            }
1151
            }
1152
        }
1152
        }
1153
1154
        my $now = dt_from_string();
1155
        my $preventCheckoutOnSameReservePeriod =
1156
            C4::Context->preference("PreventCheckoutOnSameReservePeriod");
1157
        my $reserves_on_same_period =
1158
            ReservesOnSamePeriod($item_object->biblionumber, $item_object->itemnumber, $now->ymd, $duedate->ymd);
1159
        if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
1160
            my $reserve = $reserves_on_same_period->[0];
1161
            my $patron = Koha::Patrons->find( $reserve->{borrowernumber} );
1162
            my $branchname = Koha::Libraries->find($reserve->{branchcode})->branchname;
1163
1164
            $needsconfirmation{RESERVED} = 1;
1165
            $needsconfirmation{resfirstname} = $patron->firstname;
1166
            $needsconfirmation{ressurname} = $patron->surname;
1167
            $needsconfirmation{rescardnumber} = $patron->cardnumber;
1168
            $needsconfirmation{resborrowernumber} = $patron->borrowernumber;
1169
            $needsconfirmation{resbranchname} = $branchname;
1170
            $needsconfirmation{resreservedate} = $reserve->{reservedate};
1171
            $needsconfirmation{reserve_id} = $reserve->{reserve_id};
1172
        }
1173
1153
    }
1174
    }
1154
1175
1155
    ## CHECK AGE RESTRICTION
1176
    ## CHECK AGE RESTRICTION
Lines 1403-1417 AddIssue does the following things : Link Here
1403
1424
1404
  - step 01: check that there is a borrowernumber & a barcode provided
1425
  - step 01: check that there is a borrowernumber & a barcode provided
1405
  - check for RENEWAL (book issued & being issued to the same patron)
1426
  - check for RENEWAL (book issued & being issued to the same patron)
1406
      - renewal YES = Calculate Charge & renew
1427
  - renewal YES = Calculate Charge & renew renewal NO = * BOOK
1407
      - renewal NO  =
1428
  - ACTUALLY ISSUED ? do a return if book is actually issued (but to
1408
          * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
1429
  - someone else) * RESERVE PLACED ?  fill reserve if reserve to this
1409
          * RESERVE PLACED ?
1430
  - patron cancel reserve or not, otherwise * TRANSFERT PENDING ?
1410
              - fill reserve if reserve to this patron
1431
  - complete the transfert * ISSUE THE BOOK
1411
              - cancel reserve or not, otherwise
1412
          * TRANSFERT PENDING ?
1413
              - complete the transfert
1414
          * ISSUE THE BOOK
1415
1432
1416
=back
1433
=back
1417
1434
Lines 2712-2718 already renewed the loan. $error will contain the reason the renewal can not pro Link Here
2712
=cut
2729
=cut
2713
2730
2714
sub CanBookBeRenewed {
2731
sub CanBookBeRenewed {
2715
    my ( $borrowernumber, $itemnumber, $override_limit, $cron ) = @_;
2732
    my ( $borrowernumber, $itemnumber, $override_limit, $cron, $date_due ) = @_;
2716
2733
2717
    my $auto_renew = "no";
2734
    my $auto_renew = "no";
2718
2735
Lines 2832-2841 sub CanBookBeRenewed { Link Here
2832
        }
2849
        }
2833
    }
2850
    }
2834
2851
2835
    return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2852
    unless ($date_due) {
2836
    return ( 0, $auto_renew  ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
2853
        my $itemtype = $item->effective_itemtype;
2854
        my $patron_unblessed = $patron->unblessed;
2855
        $date_due = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2856
                                        dt_from_string( $issue->date_due, 'sql' ) :
2857
                                        DateTime->now( time_zone => C4::Context->tz());
2858
        $date_due =  CalcDateDue($date_due, $itemtype, _GetCircControlBranch($item->unblessed(), $patron_unblessed), $patron_unblessed, 'is a renewal');
2859
    }
2860
2861
    my $now = dt_from_string();
2862
    my $biblionumber = $item->biblionumber;
2863
    my $preventCheckoutOnSameReservePeriod =
2864
        C4::Context->preference("PreventCheckoutOnSameReservePeriod");
2865
    my $reserves_on_same_period =
2866
        ReservesOnSamePeriod($biblionumber, $itemnumber, $now->ymd, $date_due->ymd);
2867
    if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) {
2868
        $resfound = 1;
2869
    }
2870
2871
    if( $cron ) { #The cron wants to return 'too_soon' over 'on_reserve'
2872
        return ( 0, $auto_renew  ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
2873
        return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2874
    } else { # For other purposes we want 'on_reserve' before 'too_soon'
2875
        return ( 0, "on_reserve" ) if $resfound;    # '' when no hold was found
2876
        return ( 0, $auto_renew  ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
2877
    }
2878
2837
    if ( GetSoonestRenewDate($borrowernumber, $itemnumber) > dt_from_string() ){
2879
    if ( GetSoonestRenewDate($borrowernumber, $itemnumber) > dt_from_string() ){
2838
        return (0, "too_soon") unless $override_limit;
2880
      return (0, "too_soon") unless $override_limit;
2839
    }
2881
    }
2840
2882
2841
    return ( 0, "auto_renew" ) if $auto_renew eq "ok" && !$override_limit; # 0 if auto-renewal should not succeed
2883
    return ( 0, "auto_renew" ) if $auto_renew eq "ok" && !$override_limit; # 0 if auto-renewal should not succeed
(-)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 143-148 BEGIN { Link Here
143
      MergeHolds
143
      MergeHolds
144
144
145
      RevertWaitingStatus
145
      RevertWaitingStatus
146
147
      ReservesOnSamePeriod
146
    );
148
    );
147
}
149
}
148
150
Lines 2262-2267 sub GetMaxPatronHoldsForRecord { Link Here
2262
    return $max;
2264
    return $max;
2263
}
2265
}
2264
2266
2267
=head2 ReservesOnSamePeriod
2268
2269
    my $reserve = ReservesOnSamePeriod( $biblionumber, $itemnumber, $resdate, $expdate);
2270
2271
    Return the reserve that match the period ($resdate => $expdate),
2272
    undef if no reserve match.
2273
2274
=cut
2275
2276
sub ReservesOnSamePeriod {
2277
    my ($biblionumber, $itemnumber, $resdate, $expdate) = @_;
2278
2279
    unless ($resdate && $expdate) {
2280
        return;
2281
    }
2282
2283
    my @reserves = Koha::Holds->search({ biblionumber => $biblionumber });
2284
2285
    $resdate = output_pref({ str => $resdate, dateonly => 1, dateformat => 'iso' });
2286
    $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' });
2287
2288
    my @reserves_overlaps;
2289
    foreach my $reserve ( @reserves ) {
2290
2291
        unless ($reserve->reservedate && $reserve->expirationdate) {
2292
            next;
2293
        }
2294
2295
        if (date_ranges_overlap($resdate, $expdate,
2296
                                $reserve->reservedate,
2297
                                $reserve->expirationdate)) {
2298
2299
            # If reserve is item level and the requested periods overlap.
2300
            if ($itemnumber && $reserve->itemnumber == $itemnumber ) {
2301
                return [$reserve->unblessed];
2302
            }
2303
            push @reserves_overlaps, $reserve->unblessed;
2304
        }
2305
    }
2306
2307
    if ( @reserves_overlaps >= Koha::Items->search({ biblionumber => $biblionumber })->count() ) {
2308
        return \@reserves_overlaps;
2309
    }
2310
2311
    return;
2312
}
2313
2265
=head1 AUTHOR
2314
=head1 AUTHOR
2266
2315
2267
Koha Development Team <http://koha-community.org/>
2316
Koha Development Team <http://koha-community.org/>
(-)a/Koha/DateUtils.pm (+43 lines)
Lines 30-35 BEGIN { Link Here
30
        dt_from_string
30
        dt_from_string
31
        output_pref
31
        output_pref
32
        format_sqldatetime
32
        format_sqldatetime
33
	date_ranges_overlap
33
    );
34
    );
34
}
35
}
35
36
Lines 376-379 sub format_sqldatetime { Link Here
376
    return q{};
377
    return q{};
377
}
378
}
378
379
380
=head2 date_ranges_overlap
381
382
    $bool = date_ranges_overlap($start1, $end1, $start2, $end2);
383
384
    Tells if first range ($start1 => $end1) overlaps
385
    the second one ($start2 => $end2)
386
387
=cut
388
389
sub date_ranges_overlap {
390
    my ($start1, $end1, $start2, $end2) = @_;
391
392
    $start1 = dt_from_string( $start1, 'iso' );
393
    $end1 = dt_from_string( $end1, 'iso' );
394
    $start2 = dt_from_string( $start2, 'iso' );
395
    $end2 = dt_from_string( $end2, 'iso' );
396
397
    if (
398
        # Start of range 2 is in the range 1.
399
        (
400
            DateTime->compare($start2, $start1) >= 0
401
            && DateTime->compare($start2, $end1) <= 0
402
        )
403
        ||
404
        # End of range 2 is in the range 1.
405
        (
406
            DateTime->compare($end2, $start1) >= 0
407
            && DateTime->compare($end2, $end1) <= 0
408
        )
409
        ||
410
        # Range 2 start before and end after range 1.
411
        (
412
            DateTime->compare($start2, $start1) < 0
413
            && DateTime->compare($end2, $end1) > 0
414
        )
415
    ) {
416
        return 1;
417
    }
418
419
    return;
420
}
421
379
1;
422
1;
(-)a/circ/circulation.pl (+4 lines)
Lines 392-397 if (@$barcodes) { Link Here
392
        }
392
        }
393
        unless($confirm_required) {
393
        unless($confirm_required) {
394
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
394
            my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
395
            if ( $cancelreserve eq 'cancel' ) {
396
                CancelReserve({ reserve_id => $query->param('reserveid') });
397
            }
398
            $cancelreserve = $cancelreserve eq 'revert' ? 'revert' : undef;
395
            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, } );
399
            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, } );
396
            $template_params->{issue} = $issue;
400
            $template_params->{issue} = $issue;
397
            $session->clear('auto_renew');
401
            $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 564-569 Circulation: Link Here
564
              class: integer
564
              class: integer
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
        -
568
            - pref: PreventCheckoutOnSameReservePeriod
569
              choices:
570
                  yes: Do
571
                  no: "Don't"
572
            - If yes, checkouts periods can't overlap with a reserve period.
567
        -
573
        -
568
            - pref: HoldsAutoFill
574
            - pref: HoldsAutoFill
569
              choices:
575
              choices:
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 251-256 Link Here
251
                                    [% IF ( RESERVED ) %]
251
                                    [% IF ( RESERVED ) %]
252
                                        <p>
252
                                        <p>
253
                                            <input type="checkbox" id="cancelreserve" name="cancelreserve" value="cancel" />
253
                                            <input type="checkbox" id="cancelreserve" name="cancelreserve" value="cancel" />
254
                                            <input type="hidden" name="reserveid" value="[% resreserveid | html %]" />
254
                                            <label for="cancelreserve">Cancel hold</label>
255
                                            <label for="cancelreserve">Cancel hold</label>
255
                                        </p>
256
                                        </p>
256
                                    [% END %]
257
                                    [% END %]
Lines 259-264 Link Here
259
                                        <p>
260
                                        <p>
260
                                            <label for="cancelreserve">Cancel hold</label>
261
                                            <label for="cancelreserve">Cancel hold</label>
261
                                            <input type="radio" value="cancel" name="cancelreserve" id="cancelreserve" /><br />
262
                                            <input type="radio" value="cancel" name="cancelreserve" id="cancelreserve" /><br />
263
                                            <input type="hidden" name="reserveid" value="[% resreserveid | html %]" />
262
                                            <label for="revertreserve">Revert waiting status</label>
264
                                            <label for="revertreserve">Revert waiting status</label>
263
                                            <input type="radio" value="revert" name="cancelreserve" id="revertreserve" checked="checked"/>
265
                                            <input type="radio" value="revert" name="cancelreserve" id="revertreserve" checked="checked"/>
264
                                        </p>
266
                                        </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 ItemsAnyAvailableAndNotRestricted IsAvailableForItemLevelRequest );
27
use C4::Reserves qw( CanItemBeReserved CanBookBeReserved AddReserve GetReservesControlBranch ItemsAnyAvailableAndNotRestricted 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 (-3 / +62 lines)
Lines 23-42 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 URI;
29
use URI;
28
use C4::Reserves qw( CanItemBeReserved AddReserve CanBookBeReserved );
30
use C4::Reserves qw( CanItemBeReserved AddReserve CanBookBeReserved ReservesOnSamePeriod);
29
use C4::Auth qw( checkauth );
31
use C4::Auth qw( checkauth get_template_and_user);
32
use C4::Output qw( output_html_with_http_headers );
30
33
31
use Koha::Items;
34
use Koha::Items;
32
use Koha::Patrons;
35
use Koha::Patrons;
33
36
34
my $input = CGI->new();
37
my $input = CGI->new();
35
38
36
checkauth($input, 0, { reserveforothers => 'place_holds' }, 'intranet');
39
my ($template, $loggedinuser, $cookie) = get_template_and_user({
40
    template_name   => 'reserve/placerequest.tt',
41
    query           => $input,
42
    type            => 'intranet',
43
    authnotrequired => 0,
44
    flagsrequired   => { reserveforothers => 'place_holds' },
45
});
46
37
47
38
my @reqbib         = $input->multi_param('reqbib');
48
my @reqbib         = $input->multi_param('reqbib');
39
my @biblionumbers   = $input->multi_param('biblionumber');
49
my @biblionumbers   = $input->multi_param('biblionumber');
50
my @bibitems       = $input->multi_param('biblioitem');
40
my $borrowernumber = $input->param('borrowernumber');
51
my $borrowernumber = $input->param('borrowernumber');
41
my $notes          = $input->param('notes');
52
my $notes          = $input->param('notes');
42
my $branch         = $input->param('pickup');
53
my $branch         = $input->param('pickup');
Lines 48-53 my $checkitem = $input->param('checkitem'); Link Here
48
my $expirationdate = $input->param('expiration_date');
59
my $expirationdate = $input->param('expiration_date');
49
my $itemtype       = $input->param('itemtype') || undef;
60
my $itemtype       = $input->param('itemtype') || undef;
50
my $non_priority   = $input->param('non_priority');
61
my $non_priority   = $input->param('non_priority');
62
my $confirm        = $input->param('confirm');
63
my @confirm_biblionumbers = $input->multi_param('confirm_biblionumbers');
51
64
52
my $patron = Koha::Patrons->find( $borrowernumber );
65
my $patron = Koha::Patrons->find( $borrowernumber );
53
66
Lines 68-74 my $found; Link Here
68
81
69
if ( $type eq 'str8' && $patron ) {
82
if ( $type eq 'str8' && $patron ) {
70
83
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
    }
115
71
    foreach my $biblionumber ( keys %bibinfos ) {
116
    foreach my $biblionumber ( keys %bibinfos ) {
117
        next if ($confirm && none { $_ eq $biblionumber } @confirm_biblionumbers);
118
119
        my $count = @bibitems;
120
        @bibitems = sort @bibitems;
121
        my $i2 = 1;
122
        my @realbi;
123
        $realbi[0] = $bibitems[0];
124
        for ( my $i = 1 ; $i < $count ; $i++ ) {
125
            my $i3 = $i2 - 1;
126
            if ( $realbi[$i3] ne $bibitems[$i] ) {
127
                $realbi[$i2] = $bibitems[$i];
128
                $i2++;
129
            }
130
        }
72
131
73
        my $can_override = C4::Context->preference('AllowHoldPolicyOverride');
132
        my $can_override = C4::Context->preference('AllowHoldPolicyOverride');
74
        if ( defined $checkitem && $checkitem ne '' ) {
133
        if ( defined $checkitem && $checkitem ne '' ) {
(-)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 524-529 subtest "CanBookBeRenewed tests" => sub { Link Here
524
    my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
524
    my $borrowing_borrowernumber = Koha::Checkouts->find( { itemnumber => $item_1->itemnumber } )->borrowernumber;
525
    is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
525
    is ($borrowing_borrowernumber, $renewing_borrowernumber, "Item checked out to $renewing_borrower->{firstname} $renewing_borrower->{surname}");
526
526
527
    t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
527
    my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
528
    my ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
528
    is( $renewokay, 1, 'Can renew, no holds for this title or item');
529
    is( $renewokay, 1, 'Can renew, no holds for this title or item');
529
530
Lines 1719-1724 subtest "AllowRenewalIfOtherItemsAvailable tests" => sub { Link Here
1719
    is( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
1720
    is( $error, 'onsite_checkout', 'A correct error code should be returned by CanBookBeRenewed for on-site checkout' );
1720
}
1721
}
1721
1722
1723
{
1724
    # Don't allow renewing on reserve period with PreventCheckoutOnSameReservePeriod enabled
1725
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 1 );
1726
1727
    my $start_issue_dt = DateTime->now();
1728
    $start_issue_dt->subtract( days => 15);
1729
    my $due_date = $start_issue_dt->clone;
1730
    $due_date->add( days => 17 );
1731
1732
    my $biblio = $builder->build({
1733
        source => 'Biblio',
1734
    });
1735
    my $biblionumber = $biblio->{biblionumber};
1736
1737
    my $item = $builder->build({
1738
        source => 'Item',
1739
        value => {
1740
            biblionumber => $biblionumber
1741
        }
1742
    });
1743
    my $itemnumber = $item->{itemnumber};
1744
1745
    my $issue = $builder->build({
1746
        source => 'Issue',
1747
        value => {
1748
            itemnumber => $itemnumber,
1749
            biblionumber => $biblionumber,
1750
            issuedate => $start_issue_dt->ymd,
1751
            date_due => $due_date->ymd,
1752
            onsite_checkout => 0
1753
        }
1754
    });
1755
1756
    my $reservedate = $due_date->clone;
1757
    $reservedate->add( days => 5 );
1758
    my $expirationdate = $reservedate->clone;
1759
    $expirationdate->add( days => 15 );
1760
    $builder->build({
1761
        source => 'Reserve',
1762
        value => {
1763
            biblionumber => $biblionumber,
1764
            itemnumber => $itemnumber,
1765
            reservedate => $reservedate->ymd,
1766
            expirationdate => $expirationdate->ymd
1767
        }
1768
    });
1769
1770
    my $requested_date_due = $due_date->clone;
1771
    $requested_date_due->add( days => 4 );
1772
    my ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1773
    is( $renewed, 1, 'CanBookBeRenewed should allow to renew if no reserve date overlap' );
1774
1775
    $requested_date_due->add( days => 2 );
1776
    ( $renewed, $error ) = CanBookBeRenewed( $issue->{borrowernumber}, $itemnumber, 1, $requested_date_due );
1777
    is( $renewed, 0, 'CanBookBeRenewed should not allow to renew if reserve date overlap' );
1778
1779
    t::lib::Mocks::mock_preference( 'PreventCheckoutOnSameReservePeriod', 0 );
1780
}
1781
1722
{
1782
{
1723
    my $library = $builder->build({ source => 'Branch' });
1783
    my $library = $builder->build({ source => 'Branch' });
1724
1784
(-)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