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

(-)a/C4/Circulation.pm (-25 / +69 lines)
Lines 50-55 use Koha::DateUtils; Link Here
50
use Koha::Calendar;
50
use Koha::Calendar;
51
use Koha::Borrower::Debarments;
51
use Koha::Borrower::Debarments;
52
use Koha::Database;
52
use Koha::Database;
53
use Koha::Holds;
53
use Carp;
54
use Carp;
54
use List::MoreUtils qw( uniq );
55
use List::MoreUtils qw( uniq );
55
use Date::Calc qw(
56
use Date::Calc qw(
Lines 1009-1024 sub CanBookBeIssued { Link Here
1009
    }
1010
    }
1010
1011
1011
    ## check for high holds decreasing loan period
1012
    ## check for high holds decreasing loan period
1012
    my $decrease_loan = C4::Context->preference('decreaseLoanHighHolds');
1013
    if ( C4::Context->preference('decreaseLoanHighHolds') ) {
1013
    if ( $decrease_loan && $decrease_loan == 1 ) {
1014
        my $check = checkHighHolds( $item, $borrower );
1014
        my ( $reserved, $num, $duration, $returndate ) =
1015
          checkHighHolds( $item, $borrower );
1016
1015
1017
        if ( $num >= C4::Context->preference('decreaseLoanHighHoldsValue') ) {
1016
        if ( $check->{exceeded} ) {
1018
            $needsconfirmation{HIGHHOLDS} = {
1017
            $needsconfirmation{HIGHHOLDS} = {
1019
                num_holds  => $num,
1018
                num_holds  => $check->{outstanding},
1020
                duration   => $duration,
1019
                duration   => $check->{duration},
1021
                returndate => output_pref($returndate),
1020
                returndate => output_pref( $check->{due_date} ),
1022
            };
1021
            };
1023
        }
1022
        }
1024
    }
1023
    }
Lines 1113-1125 sub checkHighHolds { Link Here
1113
    my ( $item, $borrower ) = @_;
1112
    my ( $item, $borrower ) = @_;
1114
    my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
1113
    my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
1115
    my $branch = _GetCircControlBranch( $item, $borrower );
1114
    my $branch = _GetCircControlBranch( $item, $borrower );
1116
    my $dbh    = C4::Context->dbh;
1115
1117
    my $sth    = $dbh->prepare(
1116
    my $return_data = {
1118
'select count(borrowernumber) as num_holds from reserves where biblionumber=?'
1117
        exceeded    => 0,
1119
    );
1118
        outstanding => 0,
1120
    $sth->execute( $item->{'biblionumber'} );
1119
        duration    => 0,
1121
    my ($holds) = $sth->fetchrow_array;
1120
        due_date    => undef,
1122
    if ($holds) {
1121
    };
1122
1123
    my $holds = Koha::Holds->search( { biblionumber => $item->{'biblionumber'} } );
1124
1125
    if ( $holds->count() ) {
1126
        $return_data->{outstanding} = $holds->count();
1127
1128
        my $decreaseLoanHighHoldsControl        = C4::Context->preference('decreaseLoanHighHoldsControl');
1129
        my $decreaseLoanHighHoldsValue          = C4::Context->preference('decreaseLoanHighHoldsValue');
1130
        my $decreaseLoanHighHoldsIgnoreStatuses = C4::Context->preference('decreaseLoanHighHoldsIgnoreStatuses');
1131
1132
        my @decreaseLoanHighHoldsIgnoreStatuses = split( /,/, $decreaseLoanHighHoldsIgnoreStatuses );
1133
1134
        if ( $decreaseLoanHighHoldsControl eq 'static' ) {
1135
1136
            # static means just more than a given number of holds on the record
1137
1138
            # If the number of holds is less than the threshold, we can stop here
1139
            if ( $holds->count() < $decreaseLoanHighHoldsValue ) {
1140
                return $return_data;
1141
            }
1142
        }
1143
        elsif ( $decreaseLoanHighHoldsControl eq 'dynamic' ) {
1144
1145
            # dynamic means X more than the number of holdable items on the record
1146
1147
            # let's get the items
1148
            my @items = $holds->next()->biblio()->items();
1149
1150
            # Remove any items with status defined to be ignored even if the would not make item unholdable
1151
            foreach my $status (@decreaseLoanHighHoldsIgnoreStatuses) {
1152
                @items = grep { !$_->$status } @items;
1153
            }
1154
1155
            # Remove any items that are not holdable for this patron
1156
            @items = grep { CanItemBeReserved( $borrower->{borrowernumber}, $_->itemnumber ) ne 'OK' } @items;
1157
1158
            my $items_count = scalar @items;
1159
1160
            # If the number of holds is less than the count of items we have
1161
            # plus the number of holds allowed above that count, we can stop here
1162
            if ( $holds->count() < $items_count + $decreaseLoanHighHoldsValue ) {
1163
                return $return_data;
1164
            }
1165
        }
1166
1123
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1167
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1124
1168
1125
        my $calendar = Koha::Calendar->new( branchcode => $branch );
1169
        my $calendar = Koha::Calendar->new( branchcode => $branch );
Lines 1128-1148 sub checkHighHolds { Link Here
1128
          ( C4::Context->preference('item-level_itypes') )
1172
          ( C4::Context->preference('item-level_itypes') )
1129
          ? $biblio->{'itype'}
1173
          ? $biblio->{'itype'}
1130
          : $biblio->{'itemtype'};
1174
          : $biblio->{'itemtype'};
1131
        my $orig_due =
1132
          C4::Circulation::CalcDateDue( $issuedate, $itype, $branch,
1133
            $borrower );
1134
1175
1135
        my $reduced_datedue =
1176
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branch, $borrower );
1136
          $calendar->addDate( $issuedate,
1177
1137
            C4::Context->preference('decreaseLoanHighHoldsDuration') );
1178
        my $decreaseLoanHighHoldsDuration = C4::Context->preference('decreaseLoanHighHoldsDuration');
1179
1180
        my $reduced_datedue = $calendar->addDate( $issuedate, $decreaseLoanHighHoldsDuration );
1138
1181
1139
        if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
1182
        if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
1140
            return ( 1, $holds,
1183
            $return_data->{exceeded} = 1;
1141
                C4::Context->preference('decreaseLoanHighHoldsDuration'),
1184
            $return_data->{duration} = $decreaseLoanHighHoldsDuration;
1142
                $reduced_datedue );
1185
            $return_data->{due_date} = $reduced_datedue;
1143
        }
1186
        }
1144
    }
1187
    }
1145
    return ( 0, 0, 0, undef );
1188
1189
    return $return_data;
1146
}
1190
}
1147
1191
1148
=head2 AddIssue
1192
=head2 AddIssue
(-)a/Koha/Biblio.pm (+15 lines)
Lines 35-40 Koha::Biblio - Koha Biblio Object class Link Here
35
35
36
=cut
36
=cut
37
37
38
=head3 items
39
40
Returns the related Koha::Items object for this biblio in scalar context,
41
or list of Koha::Item objects in list context.
42
43
=cut
44
45
sub items {
46
    my ($self) = @_;
47
48
    $self->{_items} ||= Koha::Items->search( { biblionumber => $self->biblionumber() } );
49
50
    return wantarray ? $self->{_items}->as_list : $self->{_items};
51
}
52
38
=head3 type
53
=head3 type
39
54
40
=cut
55
=cut
(-)a/installer/data/mysql/atomicupdate/bug_14694.sql (+3 lines)
Line 0 Link Here
1
INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
2
('decreaseLoanHighHoldsControl', 'static', 'static|dynamic', "Chooses between static and dynamic high holds checking", 'Choice'),
3
('decreaseLoanHighHoldsIgnoreStatuses', '', 'damaged|itemlost|notforloan|withdrawn', "Ignore items with these statuses for dynamic high holds checking", 'Choice');
(-)a/installer/data/mysql/sysprefs.sql (+2 lines)
Lines 98-105 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
98
('dateformat','us','metric|us|iso','Define global date format (us mm/dd/yyyy, metric dd/mm/yyy, ISO yyyy-mm-dd)','Choice'),
98
('dateformat','us','metric|us|iso','Define global date format (us mm/dd/yyyy, metric dd/mm/yyy, ISO yyyy-mm-dd)','Choice'),
99
('DebugLevel','2','0|1|2','Define the level of debugging information sent to the browser when errors are encountered (set to 0 in production). 0=none, 1=some, 2=most','Choice'),
99
('DebugLevel','2','0|1|2','Define the level of debugging information sent to the browser when errors are encountered (set to 0 in production). 0=none, 1=some, 2=most','Choice'),
100
('decreaseLoanHighHolds',NULL,'','Decreases the loan period for items with number of holds above the threshold specified in decreaseLoanHighHoldsValue','YesNo'),
100
('decreaseLoanHighHolds',NULL,'','Decreases the loan period for items with number of holds above the threshold specified in decreaseLoanHighHoldsValue','YesNo'),
101
('decreaseLoanHighHoldsControl', 'static', 'static|dynamic', "Chooses between static and dynamic high holds checking", 'Choice'),
101
('decreaseLoanHighHoldsDuration',NULL,'','Specifies a number of days that a loan is reduced to when used in conjunction with decreaseLoanHighHolds','Integer'),
102
('decreaseLoanHighHoldsDuration',NULL,'','Specifies a number of days that a loan is reduced to when used in conjunction with decreaseLoanHighHolds','Integer'),
102
('decreaseLoanHighHoldsValue',NULL,'','Specifies a threshold for the minimum number of holds needed to trigger a reduction in loan duration (used with decreaseLoanHighHolds)','Integer'),
103
('decreaseLoanHighHoldsValue',NULL,'','Specifies a threshold for the minimum number of holds needed to trigger a reduction in loan duration (used with decreaseLoanHighHolds)','Integer'),
104
('decreaseLoanHighHoldsIgnoreStatuses', '', 'damaged|itemlost|notforloan|withdrawn', "Ignore items with these statuses for dynamic high holds checking", 'Choice'),
103
('DefaultClassificationSource','ddc',NULL,'Default classification scheme used by the collection. E.g., Dewey, LCC, etc.','ClassSources'),
105
('DefaultClassificationSource','ddc',NULL,'Default classification scheme used by the collection. E.g., Dewey, LCC, etc.','ClassSources'),
104
('DefaultLanguageField008','','','Fill in the default language for field 008 Range 35-37 of MARC21 records (e.g. eng, nor, ger, see <a href=\"http://www.loc.gov/marc/languages/language_code.html\">MARC Code List for Languages</a>)','Free'),
106
('DefaultLanguageField008','','','Fill in the default language for field 008 Range 35-37 of MARC21 records (e.g. eng, nor, ger, see <a href=\"http://www.loc.gov/marc/languages/language_code.html\">MARC Code List for Languages</a>)','Free'),
105
('DefaultLongOverdueChargeValue', NULL, NULL, "Charge a lost item to the borrower's account when the LOST value of the item changes to n.", 'integer'),
107
('DefaultLongOverdueChargeValue', NULL, NULL, "Charge a lost item to the borrower's account when the LOST value of the item changes to n.", 'integer'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-1 / +12 lines)
Lines 556-562 Circulation: Link Here
556
            - days for items with more than
556
            - days for items with more than
557
            - pref: decreaseLoanHighHoldsValue
557
            - pref: decreaseLoanHighHoldsValue
558
              class: integer
558
              class: integer
559
            - holds.
559
            - holds
560
            - pref: decreaseLoanHighHoldsControl
561
              choices:
562
                  static: "on the record"
563
                  dynamic: "over the number of holdable items on the record"
564
            - . Ignore items with the following statuses when counting items
565
            - pref: decreaseLoanHighHoldsIgnoreStatuses
566
              multiple:
567
                damaged: Damaged
568
                itemlost: Lost
569
                withdrawn: Withdrawn
570
                notforloan: Not for loan
560
        -
571
        -
561
            - pref: AllowHoldsOnPatronsPossessions
572
            - pref: AllowHoldsOnPatronsPossessions
562
              choices:
573
              choices:
(-)a/t/db_dependent/DecreaseLoanHighHolds.t (-1 / +151 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 DateTime;
21
22
use C4::Circulation;
23
use Koha::Database;
24
use Koha::Borrower;
25
use Koha::Biblio;
26
use Koha::Item;
27
use Koha::Holds;
28
use Koha::Hold;
29
30
use Test::More tests => 12;
31
32
my $dbh    = C4::Context->dbh;
33
my $schema = Koha::Database->new()->schema();
34
35
# Start transaction
36
$dbh->{RaiseError} = 1;
37
$schema->storage->txn_begin();
38
39
$dbh->do('DELETE FROM issues');
40
$dbh->do('DELETE FROM issuingrules');
41
$dbh->do('DELETE FROM borrowers');
42
$dbh->do('DELETE FROM items');
43
44
# Set userenv
45
C4::Context->_new_userenv('xxx');
46
C4::Context->set_userenv( 0, 0, 0, 'firstname', 'surname', 'MPL', 'Midway Public Library', '', '', '' );
47
is( C4::Context->userenv->{branch}, 'MPL', 'userenv set' );
48
49
my @patrons;
50
for my $i ( 1 .. 20 ) {
51
    my $patron = Koha::Borrower->new(
52
        { cardnumber => $i, firstname => 'Kyle', surname => 'Hall', categorycode => 'S', branchcode => 'MPL' } )
53
      ->store();
54
    push( @patrons, $patron );
55
}
56
57
my $biblio = Koha::Biblio->new()->store();
58
my $biblioitem =
59
  $schema->resultset('Biblioitem')->new( { biblionumber => $biblio->biblionumber } )->insert();
60
61
my @items;
62
for my $i ( 1 .. 10 ) {
63
    my $item = Koha::Item->new( { biblionumber => $biblio->id(), biblioitemnumber => $biblioitem->id(), } )->store();
64
    push( @items, $item );
65
}
66
67
for my $i ( 0 .. 4 ) {
68
    my $patron = $patrons[$i];
69
    my $hold   = Koha::Hold->new(
70
        {
71
            borrowernumber => $patron->id,
72
            biblionumber   => $biblio->id,
73
            branchcode     => 'MPL',
74
        }
75
    )->store();
76
}
77
78
$schema->resultset('Issuingrule')
79
  ->new( { branchcode => '*', categorycode => '*', itemtype => '*', issuelength => '14', lengthunit => 'days' } )
80
  ->insert();
81
82
my $item   = pop(@items);
83
my $patron = pop(@patrons);
84
85
C4::Context->set_preference( 'decreaseLoanHighHolds',               1 );
86
C4::Context->set_preference( 'decreaseLoanHighHoldsDuration',       1 );
87
C4::Context->set_preference( 'decreaseLoanHighHoldsValue',          1 );
88
C4::Context->set_preference( 'decreaseLoanHighHoldsControl',        'static' );
89
C4::Context->set_preference( 'decreaseLoanHighHoldsIgnoreStatuses', 'damaged,itemlost,notforloan,withdrawn' );
90
91
my $item_hr = { itemnumber => $item->id, biblionumber => $biblio->id, homebranch => 'MPL', holdingbranch => 'MPL' };
92
my $patron_hr = { borrower => $patron->id, branchcode => 'MPL' };
93
94
my $data = C4::Circulation::checkHighHolds( $item_hr, $patron_hr );
95
is( $data->{exceeded},        1,          "Should exceed threshold" );
96
is( $data->{outstanding},     5,          "Should have 5 outstanding holds" );
97
is( $data->{duration},        1,          "Should have duration of 1" );
98
is( ref( $data->{due_date} ), 'DateTime', "due_date should be a DateTime object" );
99
100
C4::Context->set_preference( 'decreaseLoanHighHoldsControl', 'dynamic' );
101
$data = C4::Circulation::checkHighHolds( $item_hr, $patron_hr );
102
is( $data->{exceeded}, 0, "Should not exceed threshold" );
103
104
for my $i ( 5 .. 10 ) {
105
    my $patron = $patrons[$i];
106
    my $hold   = Koha::Hold->new(
107
        {
108
            borrowernumber => $patron->id,
109
            biblionumber   => $biblio->id,
110
            branchcode     => 'MPL',
111
        }
112
    )->store();
113
}
114
115
$data = C4::Circulation::checkHighHolds( $item_hr, $patron_hr );
116
is( $data->{exceeded}, 1, "Should exceed threshold" );
117
118
C4::Context->set_preference( 'decreaseLoanHighHoldsValue', 2 );
119
$data = C4::Circulation::checkHighHolds( $item_hr, $patron_hr );
120
is( $data->{exceeded}, 0, "Should not exceed threshold" );
121
122
my $unholdable = pop(@items);
123
$unholdable->damaged(-1);
124
$unholdable->store();
125
126
$data = C4::Circulation::checkHighHolds( $item_hr, $patron_hr );
127
is( $data->{exceeded}, 1, "Should exceed threshold" );
128
129
$unholdable->damaged(0);
130
$unholdable->itemlost(-1);
131
$unholdable->store();
132
133
$data = C4::Circulation::checkHighHolds( $item_hr, $patron_hr );
134
is( $data->{exceeded}, 1, "Should exceed threshold" );
135
136
$unholdable->itemlost(0);
137
$unholdable->notforloan(-1);
138
$unholdable->store();
139
140
$data = C4::Circulation::checkHighHolds( $item_hr, $patron_hr );
141
is( $data->{exceeded}, 1, "Should exceed threshold" );
142
143
$unholdable->notforloan(0);
144
$unholdable->withdrawn(-1);
145
$unholdable->store();
146
147
$data = C4::Circulation::checkHighHolds( $item_hr, $patron_hr );
148
is( $data->{exceeded}, 1, "Should exceed threshold" );
149
150
$schema->storage->txn_rollback();
151
1;

Return to bug 14694