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

(-)a/C4/Circulation.pm (-25 / +71 lines)
Lines 52-57 use Koha::Borrowers; Link Here
52
use Koha::Borrower::Debarments;
52
use Koha::Borrower::Debarments;
53
use Koha::Database;
53
use Koha::Database;
54
use Koha::Libraries;
54
use Koha::Libraries;
55
use Koha::Holds;
55
use Carp;
56
use Carp;
56
use List::MoreUtils qw( uniq );
57
use List::MoreUtils qw( uniq );
57
use Date::Calc qw(
58
use Date::Calc qw(
Lines 1069-1084 sub CanBookBeIssued { Link Here
1069
    }
1070
    }
1070
1071
1071
    ## check for high holds decreasing loan period
1072
    ## check for high holds decreasing loan period
1072
    my $decrease_loan = C4::Context->preference('decreaseLoanHighHolds');
1073
    if ( C4::Context->preference('decreaseLoanHighHolds') ) {
1073
    if ( $decrease_loan && $decrease_loan == 1 ) {
1074
        my $check = checkHighHolds( $item, $borrower );
1074
        my ( $reserved, $num, $duration, $returndate ) =
1075
          checkHighHolds( $item, $borrower );
1076
1075
1077
        if ( $num >= C4::Context->preference('decreaseLoanHighHoldsValue') ) {
1076
        if ( $check->{exceeded} ) {
1078
            $needsconfirmation{HIGHHOLDS} = {
1077
            $needsconfirmation{HIGHHOLDS} = {
1079
                num_holds  => $num,
1078
                num_holds  => $check->{outstanding},
1080
                duration   => $duration,
1079
                duration   => $check->{duration},
1081
                returndate => output_pref($returndate),
1080
                returndate => output_pref( $check->{due_date} ),
1082
            };
1081
            };
1083
        }
1082
        }
1084
    }
1083
    }
Lines 1173-1185 sub checkHighHolds { Link Here
1173
    my ( $item, $borrower ) = @_;
1172
    my ( $item, $borrower ) = @_;
1174
    my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
1173
    my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
1175
    my $branch = _GetCircControlBranch( $item, $borrower );
1174
    my $branch = _GetCircControlBranch( $item, $borrower );
1176
    my $dbh    = C4::Context->dbh;
1175
1177
    my $sth    = $dbh->prepare(
1176
    my $return_data = {
1178
'select count(borrowernumber) as num_holds from reserves where biblionumber=?'
1177
        exceeded    => 0,
1179
    );
1178
        outstanding => 0,
1180
    $sth->execute( $item->{'biblionumber'} );
1179
        duration    => 0,
1181
    my ($holds) = $sth->fetchrow_array;
1180
        due_date    => undef,
1182
    if ($holds) {
1181
    };
1182
1183
    my $holds = Koha::Holds->search( { biblionumber => $item->{'biblionumber'} } );
1184
1185
    if ( $holds->count() ) {
1186
        $return_data->{outstanding} = $holds->count();
1187
1188
        my $decreaseLoanHighHoldsControl        = C4::Context->preference('decreaseLoanHighHoldsControl');
1189
        my $decreaseLoanHighHoldsValue          = C4::Context->preference('decreaseLoanHighHoldsValue');
1190
        my $decreaseLoanHighHoldsIgnoreStatuses = C4::Context->preference('decreaseLoanHighHoldsIgnoreStatuses');
1191
1192
        my @decreaseLoanHighHoldsIgnoreStatuses = split( /,/, $decreaseLoanHighHoldsIgnoreStatuses );
1193
1194
        if ( $decreaseLoanHighHoldsControl eq 'static' ) {
1195
1196
            # static means just more than a given number of holds on the record
1197
1198
            # If the number of holds is less than the threshold, we can stop here
1199
            if ( $holds->count() < $decreaseLoanHighHoldsValue ) {
1200
                return $return_data;
1201
            }
1202
        }
1203
        elsif ( $decreaseLoanHighHoldsControl eq 'dynamic' ) {
1204
1205
            # dynamic means X more than the number of holdable items on the record
1206
1207
            # let's get the items
1208
            my @items = $holds->next()->biblio()->items();
1209
1210
            # Remove any items with status defined to be ignored even if the would not make item unholdable
1211
            foreach my $status (@decreaseLoanHighHoldsIgnoreStatuses) {
1212
                @items = grep { !$_->$status } @items;
1213
            }
1214
1215
            # Remove any items that are not holdable for this patron
1216
            @items = grep { CanItemBeReserved( $borrower->{borrowernumber}, $_->itemnumber ) eq 'OK' } @items;
1217
1218
            my $items_count = scalar @items;
1219
1220
            my $threshold = $items_count + $decreaseLoanHighHoldsValue;
1221
1222
            # If the number of holds is less than the count of items we have
1223
            # plus the number of holds allowed above that count, we can stop here
1224
            if ( $holds->count() <= $threshold ) {
1225
                return $return_data;
1226
            }
1227
        }
1228
1183
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1229
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1184
1230
1185
        my $calendar = Koha::Calendar->new( branchcode => $branch );
1231
        my $calendar = Koha::Calendar->new( branchcode => $branch );
Lines 1188-1208 sub checkHighHolds { Link Here
1188
          ( C4::Context->preference('item-level_itypes') )
1234
          ( C4::Context->preference('item-level_itypes') )
1189
          ? $biblio->{'itype'}
1235
          ? $biblio->{'itype'}
1190
          : $biblio->{'itemtype'};
1236
          : $biblio->{'itemtype'};
1191
        my $orig_due =
1192
          C4::Circulation::CalcDateDue( $issuedate, $itype, $branch,
1193
            $borrower );
1194
1237
1195
        my $reduced_datedue =
1238
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branch, $borrower );
1196
          $calendar->addDate( $issuedate,
1239
1197
            C4::Context->preference('decreaseLoanHighHoldsDuration') );
1240
        my $decreaseLoanHighHoldsDuration = C4::Context->preference('decreaseLoanHighHoldsDuration');
1241
1242
        my $reduced_datedue = $calendar->addDate( $issuedate, $decreaseLoanHighHoldsDuration );
1198
1243
1199
        if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
1244
        if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
1200
            return ( 1, $holds,
1245
            $return_data->{exceeded} = 1;
1201
                C4::Context->preference('decreaseLoanHighHoldsDuration'),
1246
            $return_data->{duration} = $decreaseLoanHighHoldsDuration;
1202
                $reduced_datedue );
1247
            $return_data->{due_date} = $reduced_datedue;
1203
        }
1248
        }
1204
    }
1249
    }
1205
    return ( 0, 0, 0, undef );
1250
1251
    return $return_data;
1206
}
1252
}
1207
1253
1208
=head2 AddIssue
1254
=head2 AddIssue
(-)a/Koha/Biblio.pm (+14 lines)
Lines 53-58 sub subtitles { Link Here
53
    return map { $_->{subfield} } @{ GetRecordValue( 'subtitle', GetMarcBiblio( $self->id ), $self->frameworkcode ) };
53
    return map { $_->{subfield} } @{ GetRecordValue( 'subtitle', GetMarcBiblio( $self->id ), $self->frameworkcode ) };
54
}
54
}
55
55
56
=head3 items
57
58
Returns the related Koha::Items object for this biblio in scalar context,
59
or list of Koha::Item objects in list context.
60
61
=cut
62
63
sub items {
64
    my ($self) = @_;
65
66
    $self->{_items} ||= Koha::Items->search( { biblionumber => $self->biblionumber() } );
67
68
    return wantarray ? $self->{_items}->as_list : $self->{_items};
69
}
56
70
57
=head3 type
71
=head3 type
58
72
(-)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 105-112 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
105
('dateformat','us','metric|us|iso|dmydot','Define global date format (us mm/dd/yyyy, metric dd/mm/yyy, ISO yyyy-mm-dd, dmydot dd.mm.yyyy)','Choice'),
105
('dateformat','us','metric|us|iso|dmydot','Define global date format (us mm/dd/yyyy, metric dd/mm/yyy, ISO yyyy-mm-dd, dmydot dd.mm.yyyy)','Choice'),
106
('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'),
106
('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'),
107
('decreaseLoanHighHolds',NULL,'','Decreases the loan period for items with number of holds above the threshold specified in decreaseLoanHighHoldsValue','YesNo'),
107
('decreaseLoanHighHolds',NULL,'','Decreases the loan period for items with number of holds above the threshold specified in decreaseLoanHighHoldsValue','YesNo'),
108
('decreaseLoanHighHoldsControl', 'static', 'static|dynamic', "Chooses between static and dynamic high holds checking", 'Choice'),
108
('decreaseLoanHighHoldsDuration',NULL,'','Specifies a number of days that a loan is reduced to when used in conjunction with decreaseLoanHighHolds','Integer'),
109
('decreaseLoanHighHoldsDuration',NULL,'','Specifies a number of days that a loan is reduced to when used in conjunction with decreaseLoanHighHolds','Integer'),
109
('decreaseLoanHighHoldsValue',NULL,'','Specifies a threshold for the minimum number of holds needed to trigger a reduction in loan duration (used with decreaseLoanHighHolds)','Integer'),
110
('decreaseLoanHighHoldsValue',NULL,'','Specifies a threshold for the minimum number of holds needed to trigger a reduction in loan duration (used with decreaseLoanHighHolds)','Integer'),
111
('decreaseLoanHighHoldsIgnoreStatuses', '', 'damaged|itemlost|notforloan|withdrawn', "Ignore items with these statuses for dynamic high holds checking", 'Choice'),
110
('DefaultClassificationSource','ddc',NULL,'Default classification scheme used by the collection. E.g., Dewey, LCC, etc.','ClassSources'),
112
('DefaultClassificationSource','ddc',NULL,'Default classification scheme used by the collection. E.g., Dewey, LCC, etc.','ClassSources'),
111
('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'),
113
('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'),
112
('DefaultLongOverdueChargeValue', NULL, NULL, "Charge a lost item to the borrower's account when the LOST value of the item changes to n.", 'integer'),
114
('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 586-592 Circulation: Link Here
586
            - days for items with more than
586
            - days for items with more than
587
            - pref: decreaseLoanHighHoldsValue
587
            - pref: decreaseLoanHighHoldsValue
588
              class: integer
588
              class: integer
589
            - holds.
589
            - holds
590
            - pref: decreaseLoanHighHoldsControl
591
              choices:
592
                  static: "on the record"
593
                  dynamic: "over the number of holdable items on the record"
594
            - . Ignore items with the following statuses when counting items
595
            - pref: decreaseLoanHighHoldsIgnoreStatuses
596
              multiple:
597
                damaged: Damaged
598
                itemlost: Lost
599
                withdrawn: Withdrawn
600
                notforloan: Not for loan
590
        -
601
        -
591
            - pref: AllowHoldsOnPatronsPossessions
602
            - pref: AllowHoldsOnPatronsPossessions
592
              choices:
603
              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