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

(-)a/C4/Circulation.pm (-25 / +71 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 1063-1078 sub CanBookBeIssued { Link Here
1063
    }
1064
    }
1064
1065
1065
    ## check for high holds decreasing loan period
1066
    ## check for high holds decreasing loan period
1066
    my $decrease_loan = C4::Context->preference('decreaseLoanHighHolds');
1067
    if ( C4::Context->preference('decreaseLoanHighHolds') ) {
1067
    if ( $decrease_loan && $decrease_loan == 1 ) {
1068
        my $check = checkHighHolds( $item, $borrower );
1068
        my ( $reserved, $num, $duration, $returndate ) =
1069
          checkHighHolds( $item, $borrower );
1070
1069
1071
        if ( $num >= C4::Context->preference('decreaseLoanHighHoldsValue') ) {
1070
        if ( $check->{exceeded} ) {
1072
            $needsconfirmation{HIGHHOLDS} = {
1071
            $needsconfirmation{HIGHHOLDS} = {
1073
                num_holds  => $num,
1072
                num_holds  => $check->{outstanding},
1074
                duration   => $duration,
1073
                duration   => $check->{duration},
1075
                returndate => output_pref($returndate),
1074
                returndate => output_pref( $check->{due_date} ),
1076
            };
1075
            };
1077
        }
1076
        }
1078
    }
1077
    }
Lines 1167-1179 sub checkHighHolds { Link Here
1167
    my ( $item, $borrower ) = @_;
1166
    my ( $item, $borrower ) = @_;
1168
    my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
1167
    my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
1169
    my $branch = _GetCircControlBranch( $item, $borrower );
1168
    my $branch = _GetCircControlBranch( $item, $borrower );
1170
    my $dbh    = C4::Context->dbh;
1169
1171
    my $sth    = $dbh->prepare(
1170
    my $return_data = {
1172
'select count(borrowernumber) as num_holds from reserves where biblionumber=?'
1171
        exceeded    => 0,
1173
    );
1172
        outstanding => 0,
1174
    $sth->execute( $item->{'biblionumber'} );
1173
        duration    => 0,
1175
    my ($holds) = $sth->fetchrow_array;
1174
        due_date    => undef,
1176
    if ($holds) {
1175
    };
1176
1177
    my $holds = Koha::Holds->search( { biblionumber => $item->{'biblionumber'} } );
1178
1179
    if ( $holds->count() ) {
1180
        $return_data->{outstanding} = $holds->count();
1181
1182
        my $decreaseLoanHighHoldsControl        = C4::Context->preference('decreaseLoanHighHoldsControl');
1183
        my $decreaseLoanHighHoldsValue          = C4::Context->preference('decreaseLoanHighHoldsValue');
1184
        my $decreaseLoanHighHoldsIgnoreStatuses = C4::Context->preference('decreaseLoanHighHoldsIgnoreStatuses');
1185
1186
        my @decreaseLoanHighHoldsIgnoreStatuses = split( /,/, $decreaseLoanHighHoldsIgnoreStatuses );
1187
1188
        if ( $decreaseLoanHighHoldsControl eq 'static' ) {
1189
1190
            # static means just more than a given number of holds on the record
1191
1192
            # If the number of holds is less than the threshold, we can stop here
1193
            if ( $holds->count() < $decreaseLoanHighHoldsValue ) {
1194
                return $return_data;
1195
            }
1196
        }
1197
        elsif ( $decreaseLoanHighHoldsControl eq 'dynamic' ) {
1198
1199
            # dynamic means X more than the number of holdable items on the record
1200
1201
            # let's get the items
1202
            my @items = $holds->next()->biblio()->items();
1203
1204
            # Remove any items with status defined to be ignored even if the would not make item unholdable
1205
            foreach my $status (@decreaseLoanHighHoldsIgnoreStatuses) {
1206
                @items = grep { !$_->$status } @items;
1207
            }
1208
1209
            # Remove any items that are not holdable for this patron
1210
            @items = grep { CanItemBeReserved( $borrower->{borrowernumber}, $_->itemnumber ) eq 'OK' } @items;
1211
1212
            my $items_count = scalar @items;
1213
1214
            my $threshold = $items_count + $decreaseLoanHighHoldsValue;
1215
1216
            # If the number of holds is less than the count of items we have
1217
            # plus the number of holds allowed above that count, we can stop here
1218
            if ( $holds->count() <= $threshold ) {
1219
                return $return_data;
1220
            }
1221
        }
1222
1177
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1223
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1178
1224
1179
        my $calendar = Koha::Calendar->new( branchcode => $branch );
1225
        my $calendar = Koha::Calendar->new( branchcode => $branch );
Lines 1182-1202 sub checkHighHolds { Link Here
1182
          ( C4::Context->preference('item-level_itypes') )
1228
          ( C4::Context->preference('item-level_itypes') )
1183
          ? $biblio->{'itype'}
1229
          ? $biblio->{'itype'}
1184
          : $biblio->{'itemtype'};
1230
          : $biblio->{'itemtype'};
1185
        my $orig_due =
1186
          C4::Circulation::CalcDateDue( $issuedate, $itype, $branch,
1187
            $borrower );
1188
1231
1189
        my $reduced_datedue =
1232
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branch, $borrower );
1190
          $calendar->addDate( $issuedate,
1233
1191
            C4::Context->preference('decreaseLoanHighHoldsDuration') );
1234
        my $decreaseLoanHighHoldsDuration = C4::Context->preference('decreaseLoanHighHoldsDuration');
1235
1236
        my $reduced_datedue = $calendar->addDate( $issuedate, $decreaseLoanHighHoldsDuration );
1192
1237
1193
        if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
1238
        if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
1194
            return ( 1, $holds,
1239
            $return_data->{exceeded} = 1;
1195
                C4::Context->preference('decreaseLoanHighHoldsDuration'),
1240
            $return_data->{duration} = $decreaseLoanHighHoldsDuration;
1196
                $reduced_datedue );
1241
            $return_data->{due_date} = $reduced_datedue;
1197
        }
1242
        }
1198
    }
1243
    }
1199
    return ( 0, 0, 0, undef );
1244
1245
    return $return_data;
1200
}
1246
}
1201
1247
1202
=head2 AddIssue
1248
=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 99-106 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
99
('dateformat','us','metric|us|iso','Define global date format (us mm/dd/yyyy, metric dd/mm/yyy, ISO yyyy-mm-dd)','Choice'),
99
('dateformat','us','metric|us|iso','Define global date format (us mm/dd/yyyy, metric dd/mm/yyy, ISO yyyy-mm-dd)','Choice'),
100
('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
('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'),
101
('decreaseLoanHighHolds',NULL,'','Decreases the loan period for items with number of holds above the threshold specified in decreaseLoanHighHoldsValue','YesNo'),
101
('decreaseLoanHighHolds',NULL,'','Decreases the loan period for items with number of holds above the threshold specified in decreaseLoanHighHoldsValue','YesNo'),
102
('decreaseLoanHighHoldsControl', 'static', 'static|dynamic', "Chooses between static and dynamic high holds checking", 'Choice'),
102
('decreaseLoanHighHoldsDuration',NULL,'','Specifies a number of days that a loan is reduced to when used in conjunction with decreaseLoanHighHolds','Integer'),
103
('decreaseLoanHighHoldsDuration',NULL,'','Specifies a number of days that a loan is reduced to when used in conjunction 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
('decreaseLoanHighHoldsValue',NULL,'','Specifies a threshold for the minimum number of holds needed to trigger a reduction in loan duration (used with decreaseLoanHighHolds)','Integer'),
105
('decreaseLoanHighHoldsIgnoreStatuses', '', 'damaged|itemlost|notforloan|withdrawn', "Ignore items with these statuses for dynamic high holds checking", 'Choice'),
104
('DefaultClassificationSource','ddc',NULL,'Default classification scheme used by the collection. E.g., Dewey, LCC, etc.','ClassSources'),
106
('DefaultClassificationSource','ddc',NULL,'Default classification scheme used by the collection. E.g., Dewey, LCC, etc.','ClassSources'),
105
('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'),
107
('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
('DefaultLongOverdueChargeValue', NULL, NULL, "Charge a lost item to the borrower's account when the LOST value of the item changes to n.", 'integer'),
108
('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 565-571 Circulation: Link Here
565
            - days for items with more than
565
            - days for items with more than
566
            - pref: decreaseLoanHighHoldsValue
566
            - pref: decreaseLoanHighHoldsValue
567
              class: integer
567
              class: integer
568
            - holds.
568
            - holds
569
            - pref: decreaseLoanHighHoldsControl
570
              choices:
571
                  static: "on the record"
572
                  dynamic: "over the number of holdable items on the record"
573
            - . Ignore items with the following statuses when counting items
574
            - pref: decreaseLoanHighHoldsIgnoreStatuses
575
              multiple:
576
                damaged: Damaged
577
                itemlost: Lost
578
                withdrawn: Withdrawn
579
                notforloan: Not for loan
569
        -
580
        -
570
            - pref: AllowHoldsOnPatronsPossessions
581
            - pref: AllowHoldsOnPatronsPossessions
571
              choices:
582
              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', reservesallowed => '99' } )
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