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

(-)a/C4/Reserves.pm (-24 / +27 lines)
Lines 1130-1168 sub ModReserve { Link Here
1130
    Koha::Exceptions::ObjectNotFound->throw( 'No hold with id ' . $reserve_id ) unless $hold;
1130
    Koha::Exceptions::ObjectNotFound->throw( 'No hold with id ' . $reserve_id ) unless $hold;
1131
1131
1132
    if ( $rank eq "del" ) {
1132
    if ( $rank eq "del" ) {
1133
        $hold->cancel({ cancellation_reason => $cancellation_reason });
1133
        $hold->cancel( { cancellation_reason => $cancellation_reason } );
1134
    }
1134
    } elsif ( $rank eq "recall" ) {
1135
    elsif ( $rank eq "recall" ) {
1136
        if ( !$biblionumber ) {
1135
        if ( !$biblionumber ) {
1137
            $biblionumber = $hold->biblionumber;
1136
            $biblionumber = $hold->biblionumber;
1138
        }
1137
        }
1139
        my $biblio = Koha::Biblios->find( $biblionumber );
1138
        my $biblio = Koha::Biblios->find($biblionumber);
1140
        if ( !$borrowernumber ) {
1139
        if ( !$borrowernumber ) {
1141
            $borrowernumber = $hold->borrowernumber;
1140
            $borrowernumber = $hold->borrowernumber;
1142
        }
1141
        }
1143
        my $patron = Koha::Patrons->find( $borrowernumber );
1142
        my $patron = Koha::Patrons->find($borrowernumber);
1144
        if ( $hold->item_level_hold ) {
1143
        if ( $hold->item_level_hold ) {
1145
            if ( $hold->item->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) {
1144
            if ( $hold->item->can_be_recalled( { patron => $patron, hold_convert => 1 } ) ) {
1146
                my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({
1145
                my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall(
1147
                    patron => $patron,
1146
                    {
1148
                    biblio => $biblio,
1147
                        patron         => $patron,
1149
                    branchcode => $branchcode,
1148
                        biblio         => $biblio,
1150
                    expirationdate => $date,
1149
                        branchcode     => $branchcode,
1151
                    interface => 'intranet',
1150
                        expirationdate => $date,
1152
                    item => $hold->item,
1151
                        interface      => 'intranet',
1153
                });
1152
                        item           => $hold->item,
1154
                $hold->cancel({ cancellation_reason => 'RECALLED' });
1153
                    }
1154
                );
1155
                $hold->cancel( { cancellation_reason => 'RECALLED' } );
1155
            }
1156
            }
1156
        } else {
1157
        } else {
1157
            if ( $biblio->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) {
1158
            if ( $biblio->can_be_recalled( { patron => $patron, hold_convert => 1 } ) ) {
1158
                my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({
1159
                my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall(
1159
                    patron => $patron,
1160
                    {
1160
                    biblio => $biblio,
1161
                        patron         => $patron,
1161
                    branchcode => $branchcode,
1162
                        biblio         => $biblio,
1162
                    expirationdate => $date,
1163
                        branchcode     => $branchcode,
1163
                    interface => 'intranet',
1164
                        expirationdate => $date,
1164
                });
1165
                        interface      => 'intranet',
1165
                $hold->cancel({ cancellation_reason => 'RECALLED' });
1166
                    }
1167
                );
1168
                $hold->cancel( { cancellation_reason => 'RECALLED' } );
1166
            }
1169
            }
1167
        }
1170
        }
1168
    }
1171
    }
(-)a/installer/data/mysql/atomicupdate/bug_31391_-_add_HOLD_CANCELLATION-RECALLED_authorised_value.pl (-4 / +6 lines)
Lines 1-13 Link Here
1
use Modern::Perl;
1
use Modern::Perl;
2
2
3
return {
3
return {
4
    bug_number => "31391",
4
    bug_number  => "31391",
5
    description => "Staff-side recalls",
5
    description => "Staff-side recalls",
6
    up => sub {
6
    up          => sub {
7
        my ($args) = @_;
7
        my ($args) = @_;
8
        my ($dbh, $out) = @$args{qw(dbh out)};
8
        my ( $dbh, $out ) = @$args{qw(dbh out)};
9
9
10
        $dbh->do(q{ INSERT IGNORE INTO authorised_values (category, authorised_value, lib) VALUES ('HOLD_CANCELLATION','RECALLED','Hold was converted to a recall') });
10
        $dbh->do(
11
            q{ INSERT IGNORE INTO authorised_values (category, authorised_value, lib) VALUES ('HOLD_CANCELLATION','RECALLED','Hold was converted to a recall') }
12
        );
11
13
12
        say $out "Added RECALLED value to HOLD_CANCELLATION authorised value category";
14
        say $out "Added RECALLED value to HOLD_CANCELLATION authorised value category";
13
    },
15
    },
(-)a/installer/data/mysql/atomicupdate/bug_31391_-_add_RecallsInterface_syspref.pl (-4 / +6 lines)
Lines 1-13 Link Here
1
use Modern::Perl;
1
use Modern::Perl;
2
2
3
return {
3
return {
4
    bug_number => "31391",
4
    bug_number  => "31391",
5
    description => "Staff-side recalls",
5
    description => "Staff-side recalls",
6
    up => sub {
6
    up          => sub {
7
        my ($args) = @_;
7
        my ($args) = @_;
8
        my ($dbh, $out) = @$args{qw(dbh out)};
8
        my ( $dbh, $out ) = @$args{qw(dbh out)};
9
9
10
        $dbh->do(q{INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('RecallsInterface','opac','opac|staff|both','The interface that recalls can be placed through','Choice') });
10
        $dbh->do(
11
            q{INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('RecallsInterface','opac','opac|staff|both','The interface that recalls can be placed through','Choice') }
12
        );
11
13
12
        say $out "Added system preference 'RecallsInterface'";
14
        say $out "Added system preference 'RecallsInterface'";
13
    },
15
    },
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/recalls/request.tt (-1 / +1 lines)
Lines 59-65 Link Here
59
59
60
        [% IF patron_holds_count %]
60
        [% IF patron_holds_count %]
61
            <div class="dialog message">
61
            <div class="dialog message">
62
                Patron has placed one or more record-level holds. <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio.biblionumber | html %]">Convert a hold to a recall</a>.
62
                Patron has placed one or more record-level holds. <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio.biblionumber | uri %]">Convert a hold to a recall</a>.
63
            </div>
63
            </div>
64
        [% END %]
64
        [% END %]
65
65
(-)a/recalls/request.pl (-59 / +78 lines)
Lines 35-106 my ($template, $loggedinuser, $cookie)= get_template_and_user( Link Here
35
    }
35
    }
36
);
36
);
37
37
38
my $op = $input->param('op') || 'list';
38
my $op             = $input->param('op') || 'list';
39
my @recall_ids = $input->multi_param('recall_ids');
39
my @recall_ids     = $input->multi_param('recall_ids');
40
my $biblionumber = $input->param('biblionumber');
40
my $biblionumber   = $input->param('biblionumber');
41
my $biblio = Koha::Biblios->find( $biblionumber );
41
my $biblio         = Koha::Biblios->find($biblionumber);
42
my $borrowernumber = $input->param('borrowernumber');
42
my $borrowernumber = $input->param('borrowernumber');
43
my $error = $input->param('error');
43
my $error          = $input->param('error');
44
44
45
if ( $op eq 'cancel_multiple_recalls' ) {
45
if ( $op eq 'cancel_multiple_recalls' ) {
46
    foreach my $id ( @recall_ids ) {
46
    foreach my $id (@recall_ids) {
47
        Koha::Recalls->find( $id )->set_cancelled;
47
        Koha::Recalls->find($id)->set_cancelled;
48
    }
48
    }
49
    print $input->redirect('/cgi-bin/koha/recalls/request.pl?biblionumber='.$biblionumber);
49
    print $input->redirect( '/cgi-bin/koha/recalls/request.pl?biblionumber=' . $biblionumber );
50
}
50
}
51
elsif ( $op eq 'request' ) {
51
elsif ( $op eq 'request' ) {
52
52
53
    if ( C4::Context->preference('UseRecalls') and C4::Context->preference('RecallsInterface') ne 'opac' and $borrowernumber ) {
53
    if (    C4::Context->preference('UseRecalls')
54
        my $patron = Koha::Patrons->find( $borrowernumber );
54
        and C4::Context->preference('RecallsInterface') ne 'opac'
55
        and $borrowernumber )
56
    {
57
        my $patron = Koha::Patrons->find($borrowernumber);
55
58
56
        unless ( $biblio->can_be_recalled({ patron => $patron }) ) { $error = 'unavailable'; }
59
        unless ( $biblio->can_be_recalled( { patron => $patron } ) ) { $error = 'unavailable'; }
57
        my $items = Koha::Items->search({ biblionumber => $biblionumber })->as_list;
60
        my $items = Koha::Items->search( { biblionumber => $biblionumber } )->as_list;
58
61
59
        # check if already recalled
62
        # check if already recalled
60
        my $recalled = $biblio->recalls->filter_by_current->search({ patron_id => $borrowernumber })->count;
63
        my $recalled = $biblio->recalls->filter_by_current->search( { patron_id => $borrowernumber } )->count;
61
        if ( defined $recalled and $recalled > 0 ) {
64
        if ( defined $recalled and $recalled > 0 ) {
62
            my $recalls_per_record = Koha::CirculationRules->get_effective_rule({
65
            my $recalls_per_record = Koha::CirculationRules->get_effective_rule(
63
                categorycode => $patron->categorycode,
66
                {
64
                branchcode => undef,
67
                    categorycode => $patron->categorycode,
65
                itemtype => undef,
68
                    branchcode   => undef,
66
                rule_name => 'recalls_per_record'
69
                    itemtype     => undef,
67
            });
70
                    rule_name    => 'recalls_per_record'
68
            if ( defined $recalls_per_record and $recalls_per_record->rule_value and $recalled >= $recalls_per_record->rule_value ){
71
                }
72
            );
73
            if (    defined $recalls_per_record
74
                and $recalls_per_record->rule_value
75
                and $recalled >= $recalls_per_record->rule_value )
76
            {
69
                $error = 'duplicate';
77
                $error = 'duplicate';
70
            }
78
            }
71
        }
79
        }
72
80
73
        if ( !defined $error ){
81
        if ( !defined $error ) {
74
            my $pickuploc = $input->param('pickup');
82
            my $pickuploc  = $input->param('pickup');
75
            my $expdate = $input->param('expirationdate');
83
            my $expdate    = $input->param('expirationdate');
76
            my $level = $input->param('type');
84
            my $level      = $input->param('type');
77
            my $itemnumber = $input->param('itemnumber');
85
            my $itemnumber = $input->param('itemnumber');
78
86
79
            my ( $recall, $due_interval, $due_date );
87
            my ( $recall, $due_interval, $due_date );
80
88
81
            if ( !defined $level and defined $itemnumber ) {
89
            if ( !defined $level and defined $itemnumber ) {
82
                my $item = Koha::Items->find( $itemnumber );
90
                my $item = Koha::Items->find($itemnumber);
83
                if ( $item->can_be_recalled({ patron => $patron }) ) {
91
                if ( $item->can_be_recalled( { patron => $patron } ) ) {
84
                    ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({
92
                    ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall(
85
                        patron => $patron,
93
                        {
86
                        biblio => $biblio,
94
                            patron         => $patron,
87
                        branchcode => $pickuploc,
95
                            biblio         => $biblio,
88
                        item => $item,
96
                            branchcode     => $pickuploc,
89
                        expirationdate => $expdate,
97
                            item           => $item,
90
                        interface => 'staff',
98
                            expirationdate => $expdate,
91
                    });
99
                            interface      => 'staff',
100
                        }
101
                    );
92
                } else {
102
                } else {
93
                    $error = 'cannot';
103
                    $error = 'cannot';
94
                }
104
                }
95
            } else {
105
            } else {
96
                if ( $biblio->can_be_recalled({ patron => $patron }) ) {
106
                if ( $biblio->can_be_recalled( { patron => $patron } ) ) {
97
                    ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({
107
                    ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall(
98
                        patron => $patron,
108
                        {
99
                        biblio => $biblio,
109
                            patron         => $patron,
100
                        branchcode => $pickuploc,
110
                            biblio         => $biblio,
101
                        expirationdate => $expdate,
111
                            branchcode     => $pickuploc,
102
                        interface => 'staff',
112
                            expirationdate => $expdate,
103
                    });
113
                            interface      => 'staff',
114
                        }
115
                    );
104
                } else {
116
                } else {
105
                    $error = 'cannot';
117
                    $error = 'cannot';
106
                }
118
                }
Lines 108-113 elsif ( $op eq 'request' ) { Link Here
108
            if ( !defined $recall ) {
120
            if ( !defined $recall ) {
109
                $error = 'failed';
121
                $error = 'failed';
110
            } else {
122
            } else {
123
111
                # successful recall, go back to Recalls tab
124
                # successful recall, go back to Recalls tab
112
                print $input->redirect("/cgi-bin/koha/recalls/request.pl?biblionumber=$biblionumber");
125
                print $input->redirect("/cgi-bin/koha/recalls/request.pl?biblionumber=$biblionumber");
113
            }
126
            }
Lines 115-152 elsif ( $op eq 'request' ) { Link Here
115
    }
128
    }
116
}
129
}
117
130
118
if ( $borrowernumber ) {
131
if ($borrowernumber) {
119
    my $patron = Koha::Patrons->find( $borrowernumber );
132
    my $patron = Koha::Patrons->find($borrowernumber);
120
133
121
    if ( C4::Context->preference('UseRecalls') and C4::Context->preference('RecallsInterface') ne 'opac' and $patron and $patron->borrowernumber ) {
134
    if (    C4::Context->preference('UseRecalls')
122
        if ( !$biblio->can_be_recalled({ patron => $patron }) ) {
135
        and C4::Context->preference('RecallsInterface') ne 'opac'
136
        and $patron
137
        and $patron->borrowernumber )
138
    {
139
        if ( !$biblio->can_be_recalled( { patron => $patron } ) ) {
123
            $error = 1;
140
            $error = 1;
124
        }
141
        }
125
        my $patron_holds_count = Koha::Holds->search({ borrowernumber => $borrowernumber, biblionumber => $biblio->biblionumber, item_level_hold => 0 })->count;
142
        my $patron_holds_count = Koha::Holds->search(
143
            { borrowernumber => $borrowernumber, biblionumber => $biblio->biblionumber, item_level_hold => 0 } )->count;
126
144
127
        $template->param(
145
        $template->param(
128
            patron => $patron,
146
            patron             => $patron,
129
            patron_holds_count => $patron_holds_count,
147
            patron_holds_count => $patron_holds_count,
130
        );
148
        );
131
    }
149
    }
132
}
150
}
133
151
134
my $recalls = Koha::Recalls->search({ biblio_id => $biblionumber, completed => 0 });
152
my $recalls            = Koha::Recalls->search( { biblio_id => $biblionumber, completed => 0 } );
135
my $branches = Koha::Libraries->search;
153
my $branches           = Koha::Libraries->search;
136
my $single_branch_mode = $branches->count == 1;
154
my $single_branch_mode = $branches->count == 1;
137
my $items = Koha::Items->search({ biblionumber => $biblionumber });
155
my $items              = Koha::Items->search( { biblionumber => $biblionumber } );
138
156
139
$template->param(
157
$template->param(
140
    recalls     => $recalls,
158
    recalls            => $recalls,
141
    recallsview => 1,
159
    recallsview        => 1,
142
    biblio      => $biblio,
160
    biblio             => $biblio,
143
    checkboxes  => 1,
161
    checkboxes         => 1,
144
    branches    => $branches,
162
    branches           => $branches,
145
    items       => $items,
163
    items              => $items,
146
    error       => $error,
164
    error              => $error,
147
    single_branch_mode => $single_branch_mode,
165
    single_branch_mode => $single_branch_mode,
148
    C4::Search::enabled_staff_search_views,
166
    C4::Search::enabled_staff_search_views,
149
    attribute_type_codes => ( C4::Context->preference('ExtendedPatronAttributes')
167
    attribute_type_codes => (
168
        C4::Context->preference('ExtendedPatronAttributes')
150
        ? [ Koha::Patron::Attribute::Types->search( { staff_searchable => 1 } )->get_column('code') ]
169
        ? [ Koha::Patron::Attribute::Types->search( { staff_searchable => 1 } )->get_column('code') ]
151
        : []
170
        : []
152
    ),
171
    ),
(-)a/reserve/modrequest.pl (-8 / +8 lines)
Lines 83-89 else { Link Here
83
83
84
        if ( $rank[$i] eq 'recall' and !$recallerror ) {
84
        if ( $rank[$i] eq 'recall' and !$recallerror ) {
85
            my $hold = Koha::Holds->find( $reserve_id[$i] );
85
            my $hold = Koha::Holds->find( $reserve_id[$i] );
86
            if ( !$hold->biblio->can_be_recalled({ patron => $hold->patron }) ) {
86
            if ( !$hold->biblio->can_be_recalled( { patron => $hold->patron } ) ) {
87
                $recallerror = 1;
87
                $recallerror = 1;
88
            }
88
            }
89
        }
89
        }
Lines 127-137 if ( $from eq 'borrower'){ Link Here
127
} elsif ( $from eq 'circ'){
127
} elsif ( $from eq 'circ'){
128
    print $query->redirect("/cgi-bin/koha/circ/circulation.pl?borrowernumber=$borrower[0]");
128
    print $query->redirect("/cgi-bin/koha/circ/circulation.pl?borrowernumber=$borrower[0]");
129
} else {
129
} else {
130
     my $url = URI->new("/cgi-bin/koha/reserve/request.pl");
130
    my $url = URI->new("/cgi-bin/koha/reserve/request.pl");
131
     if ( $recallerror ) {
131
    if ($recallerror) {
132
         $url->query_form( biblionumber => [@biblionumber], recallerror => 1 );
132
        $url->query_form( biblionumber => [@biblionumber], recallerror => 1 );
133
     } else {
133
    } else {
134
         $url->query_form( biblionumber => [@biblionumber] );
134
        $url->query_form( biblionumber => [@biblionumber] );
135
     }
135
    }
136
     print $query->redirect($url);
136
    print $query->redirect($url);
137
}
137
}
(-)a/reserve/request.pl (-1 / +1 lines)
Lines 710-716 $template->param( pickup => $pickup || C4::Context->userenv->{branch} ); Link Here
710
$template->param(borrowernumber => $borrowernumber_hold);
710
$template->param(borrowernumber => $borrowernumber_hold);
711
711
712
my $recallerror = $input->param('recallerror');
712
my $recallerror = $input->param('recallerror');
713
if ( $recallerror ) {
713
if ($recallerror) {
714
    $template->param( recallerror => 1 );
714
    $template->param( recallerror => 1 );
715
}
715
}
716
716
(-)a/t/db_dependent/Holds.t (-31 / +35 lines)
Lines 7-13 use t::lib::TestBuilder; Link Here
7
7
8
use C4::Context;
8
use C4::Context;
9
9
10
use Test::More tests => 74;
10
use Test::More tests => 75;
11
use Test::Exception;
11
use Test::Exception;
12
12
13
use MARC::Record;
13
use MARC::Record;
Lines 1893-1903 subtest 'ModReserve to convert a hold to a recall' => sub { Link Here
1893
1893
1894
    $schema->storage->txn_begin;
1894
    $schema->storage->txn_begin;
1895
1895
1896
    my $category = $builder->build({ source => 'Category' });
1896
    my $category = $builder->build( { source => 'Category' } );
1897
    my $branch = $builder->build({ source => 'Branch' })->{ branchcode };
1897
    my $branch   = $builder->build( { source => 'Branch' } )->{branchcode};
1898
    my $biblio = $builder->build_sample_biblio( { itemtype => 'DUMMY' } );
1898
    my $biblio = $builder->build_sample_biblio( { itemtype => 'DUMMY' } );
1899
    my $item = $builder->build_sample_item(
1899
    my $itype  = $builder->build( { source => 'Itemtype' } );
1900
        { library => $branch, biblionumber => $biblio->biblionumber } );
1900
    my $item =
1901
        $builder->build_sample_item(
1902
        { library => $branch, biblionumber => $biblio->biblionumber, itype => $itype->{itemtype} } );
1901
1903
1902
    my $patron = Koha::Patron->new(
1904
    my $patron = Koha::Patron->new(
1903
        {
1905
        {
Lines 1920-1963 subtest 'ModReserve to convert a hold to a recall' => sub { Link Here
1920
        }
1922
        }
1921
    )->store;
1923
    )->store;
1922
1924
1923
    t::lib::Mocks::mock_preference("UseRecalls", 1);
1925
    t::lib::Mocks::mock_preference( "UseRecalls", 1 );
1924
    t::lib::Mocks::mock_userenv( { branchcode => $branch } );
1926
    t::lib::Mocks::mock_userenv( { branchcode => $branch } );
1925
1927
1926
    C4::Circulation::AddIssue( $patron2->unblessed, $item->barcode );
1928
    C4::Circulation::AddIssue( $patron2, $item->barcode );
1927
1929
1928
    Koha::CirculationRules->set_rules({
1930
    Koha::CirculationRules->set_rules(
1929
        branchcode => undef,
1931
        {
1930
        categorycode => undef,
1932
            branchcode   => undef,
1931
        itemtype => undef,
1933
            categorycode => undef,
1932
        rules => {
1934
            itemtype     => undef,
1933
            recalls_allowed => 5,
1935
            rules        => {
1934
            recalls_per_record => 1,
1936
                recalls_allowed    => 5,
1935
            on_shelf_recalls => 'any',
1937
                recalls_per_record => 1,
1936
        },
1938
                on_shelf_recalls   => 'any',
1937
    });
1939
            },
1940
        }
1941
    );
1938
1942
1939
    my $notice = Koha::Notice::Template->new({
1943
    my $notice = Koha::Notice::Template->new(
1940
        name                   => 'Hold cancellation',
1944
        {
1941
        module                 => 'reserves',
1945
            name                   => 'Hold cancellation',
1942
        code                   => 'HOLD_CANCELLATION',
1946
            module                 => 'reserves',
1943
        title                  => 'Hold cancelled',
1947
            code                   => 'HOLD_CANCELLATION',
1944
        content                => 'Your hold was cancelled.',
1948
            title                  => 'Hold cancelled',
1945
        message_transport_type => 'email',
1949
            content                => 'Your hold was cancelled.',
1946
        branchcode             => q{},
1950
            message_transport_type => 'email',
1947
    })->store();
1951
            branchcode             => q{},
1952
        }
1953
    )->store();
1948
1954
1949
    my $reserve_id = AddReserve(
1955
    my $reserve_id = AddReserve(
1950
        {
1956
        {
1951
            branchcode     => $branch,
1957
            branchcode     => $branch,
1952
            borrowernumber => $borrowernumber,
1958
            borrowernumber => $borrowernumber,
1953
            biblionumber   => $biblio->biblionumber,
1959
            biblionumber   => $biblio->biblionumber,
1954
            priority       =>
1960
            priority       => C4::Reserves::CalculatePriority( $biblio->biblionumber ),
1955
              C4::Reserves::CalculatePriority( $biblio->biblionumber ),
1961
            itemnumber     => $item->id,
1956
            itemnumber => $item->id,
1957
        }
1962
        }
1958
    );
1963
    );
1959
1964
1960
    my $hold = Koha::Holds->find($reserve_id);
1965
    my $hold           = Koha::Holds->find($reserve_id);
1961
    my $before_recalls = Koha::Recalls->search->filter_by_current->count;
1966
    my $before_recalls = Koha::Recalls->search->filter_by_current->count;
1962
1967
1963
    ModReserve(
1968
    ModReserve(
1964
- 

Return to bug 31391