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

(-)a/C4/Circulation.pm (-25 / +71 lines)
Lines 51-56 use Koha::Items; Link Here
51
use Koha::Borrowers;
51
use Koha::Borrowers;
52
use Koha::Borrower::Debarments;
52
use Koha::Borrower::Debarments;
53
use Koha::Database;
53
use Koha::Database;
54
use Koha::Holds;
54
use Carp;
55
use Carp;
55
use List::MoreUtils qw( uniq );
56
use List::MoreUtils qw( uniq );
56
use Date::Calc qw(
57
use Date::Calc qw(
Lines 1065-1080 sub CanBookBeIssued { Link Here
1065
    }
1066
    }
1066
1067
1067
    ## check for high holds decreasing loan period
1068
    ## check for high holds decreasing loan period
1068
    my $decrease_loan = C4::Context->preference('decreaseLoanHighHolds');
1069
    if ( C4::Context->preference('decreaseLoanHighHolds') ) {
1069
    if ( $decrease_loan && $decrease_loan == 1 ) {
1070
        my $check = checkHighHolds( $item, $borrower );
1070
        my ( $reserved, $num, $duration, $returndate ) =
1071
          checkHighHolds( $item, $borrower );
1072
1071
1073
        if ( $num >= C4::Context->preference('decreaseLoanHighHoldsValue') ) {
1072
        if ( $check->{exceeded} ) {
1074
            $needsconfirmation{HIGHHOLDS} = {
1073
            $needsconfirmation{HIGHHOLDS} = {
1075
                num_holds  => $num,
1074
                num_holds  => $check->{outstanding},
1076
                duration   => $duration,
1075
                duration   => $check->{duration},
1077
                returndate => output_pref($returndate),
1076
                returndate => output_pref( $check->{due_date} ),
1078
            };
1077
            };
1079
        }
1078
        }
1080
    }
1079
    }
Lines 1169-1181 sub checkHighHolds { Link Here
1169
    my ( $item, $borrower ) = @_;
1168
    my ( $item, $borrower ) = @_;
1170
    my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
1169
    my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
1171
    my $branch = _GetCircControlBranch( $item, $borrower );
1170
    my $branch = _GetCircControlBranch( $item, $borrower );
1172
    my $dbh    = C4::Context->dbh;
1171
1173
    my $sth    = $dbh->prepare(
1172
    my $return_data = {
1174
'select count(borrowernumber) as num_holds from reserves where biblionumber=?'
1173
        exceeded    => 0,
1175
    );
1174
        outstanding => 0,
1176
    $sth->execute( $item->{'biblionumber'} );
1175
        duration    => 0,
1177
    my ($holds) = $sth->fetchrow_array;
1176
        due_date    => undef,
1178
    if ($holds) {
1177
    };
1178
1179
    my $holds = Koha::Holds->search( { biblionumber => $item->{'biblionumber'} } );
1180
1181
    if ( $holds->count() ) {
1182
        $return_data->{outstanding} = $holds->count();
1183
1184
        my $decreaseLoanHighHoldsControl        = C4::Context->preference('decreaseLoanHighHoldsControl');
1185
        my $decreaseLoanHighHoldsValue          = C4::Context->preference('decreaseLoanHighHoldsValue');
1186
        my $decreaseLoanHighHoldsIgnoreStatuses = C4::Context->preference('decreaseLoanHighHoldsIgnoreStatuses');
1187
1188
        my @decreaseLoanHighHoldsIgnoreStatuses = split( /,/, $decreaseLoanHighHoldsIgnoreStatuses );
1189
1190
        if ( $decreaseLoanHighHoldsControl eq 'static' ) {
1191
1192
            # static means just more than a given number of holds on the record
1193
1194
            # If the number of holds is less than the threshold, we can stop here
1195
            if ( $holds->count() < $decreaseLoanHighHoldsValue ) {
1196
                return $return_data;
1197
            }
1198
        }
1199
        elsif ( $decreaseLoanHighHoldsControl eq 'dynamic' ) {
1200
1201
            # dynamic means X more than the number of holdable items on the record
1202
1203
            # let's get the items
1204
            my @items = $holds->next()->biblio()->items();
1205
1206
            # Remove any items with status defined to be ignored even if the would not make item unholdable
1207
            foreach my $status (@decreaseLoanHighHoldsIgnoreStatuses) {
1208
                @items = grep { !$_->$status } @items;
1209
            }
1210
1211
            # Remove any items that are not holdable for this patron
1212
            @items = grep { CanItemBeReserved( $borrower->{borrowernumber}, $_->itemnumber ) eq 'OK' } @items;
1213
1214
            my $items_count = scalar @items;
1215
1216
            my $threshold = $items_count + $decreaseLoanHighHoldsValue;
1217
1218
            # If the number of holds is less than the count of items we have
1219
            # plus the number of holds allowed above that count, we can stop here
1220
            if ( $holds->count() <= $threshold ) {
1221
                return $return_data;
1222
            }
1223
        }
1224
1179
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1225
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1180
1226
1181
        my $calendar = Koha::Calendar->new( branchcode => $branch );
1227
        my $calendar = Koha::Calendar->new( branchcode => $branch );
Lines 1184-1204 sub checkHighHolds { Link Here
1184
          ( C4::Context->preference('item-level_itypes') )
1230
          ( C4::Context->preference('item-level_itypes') )
1185
          ? $biblio->{'itype'}
1231
          ? $biblio->{'itype'}
1186
          : $biblio->{'itemtype'};
1232
          : $biblio->{'itemtype'};
1187
        my $orig_due =
1188
          C4::Circulation::CalcDateDue( $issuedate, $itype, $branch,
1189
            $borrower );
1190
1233
1191
        my $reduced_datedue =
1234
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branch, $borrower );
1192
          $calendar->addDate( $issuedate,
1235
1193
            C4::Context->preference('decreaseLoanHighHoldsDuration') );
1236
        my $decreaseLoanHighHoldsDuration = C4::Context->preference('decreaseLoanHighHoldsDuration');
1237
1238
        my $reduced_datedue = $calendar->addDate( $issuedate, $decreaseLoanHighHoldsDuration );
1194
1239
1195
        if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
1240
        if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
1196
            return ( 1, $holds,
1241
            $return_data->{exceeded} = 1;
1197
                C4::Context->preference('decreaseLoanHighHoldsDuration'),
1242
            $return_data->{duration} = $decreaseLoanHighHoldsDuration;
1198
                $reduced_datedue );
1243
            $return_data->{due_date} = $reduced_datedue;
1199
        }
1244
        }
1200
    }
1245
    }
1201
    return ( 0, 0, 0, undef );
1246
1247
    return $return_data;
1202
}
1248
}
1203
1249
1204
=head2 AddIssue
1250
=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 573-579 Circulation: Link Here
573
            - days for items with more than
573
            - days for items with more than
574
            - pref: decreaseLoanHighHoldsValue
574
            - pref: decreaseLoanHighHoldsValue
575
              class: integer
575
              class: integer
576
            - holds.
576
            - holds
577
            - pref: decreaseLoanHighHoldsControl
578
              choices:
579
                  static: "on the record"
580
                  dynamic: "over the number of holdable items on the record"
581
            - . Ignore items with the following statuses when counting items
582
            - pref: decreaseLoanHighHoldsIgnoreStatuses
583
              multiple:
584
                damaged: Damaged
585
                itemlost: Lost
586
                withdrawn: Withdrawn
587
                notforloan: Not for loan
577
        -
588
        -
578
            - pref: AllowHoldsOnPatronsPossessions
589
            - pref: AllowHoldsOnPatronsPossessions
579
              choices:
590
              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