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

(-)a/Koha/Item.pm (-5 / +35 lines)
Lines 179-185 sub store { Link Here
179
179
180
            # If the field has changed otherwise, we much update
180
            # If the field has changed otherwise, we much update
181
            # the 'on' field
181
            # the 'on' field
182
            elsif (exists $updated_columns{$field}
182
            elsif ( exists $updated_columns{$field}
183
                && $updated_columns{$field}
183
                && $updated_columns{$field}
184
                && !$pre_mod_item->$field )
184
                && !$pre_mod_item->$field )
185
            {
185
            {
Lines 376-382 sub safe_to_delete { Link Here
376
        {
376
        {
377
            itemnumber => undef,
377
            itemnumber => undef,
378
        }
378
        }
379
    )->count;
379
        )->count;
380
380
381
    if ($error) {
381
    if ($error) {
382
        return Koha::Result::Boolean->new(0)->add_message( { message => $error } );
382
        return Koha::Result::Boolean->new(0)->add_message( { message => $error } );
Lines 778-783 sub check_booking { Link Here
778
    return $bookings_count ? 0 : 1;
778
    return $bookings_count ? 0 : 1;
779
}
779
}
780
780
781
=head3 checkin_availability
782
783
    my $availability = $item->checkin_availability(
784
        {
785
            branch => $branchcode,
786
        }
787
    );
788
789
Returns check-in availability information for this item.
790
791
This is a convenience method that calls Koha::Item::Checkin::Availability->check().
792
793
Returns a Koha::Availability::Result object with methods:
794
    available()         # Boolean: true if check-in allowed
795
    needs_confirmation()# Boolean: true if confirmation required
796
    blockers()          # Hashref of blocking conditions
797
    confirmations()     # Hashref of conditions requiring confirmation
798
    warnings()          # Hashref of warning messages
799
    context()           # Hashref with checkout and patron objects
800
801
=cut
802
803
sub checkin_availability {
804
    my ( $self, $params ) = @_;
805
806
    require Koha::Item::Checkin::Availability;
807
    return Koha::Item::Checkin::Availability->check( $self, $params );
808
}
809
781
=head3 request_transfer
810
=head3 request_transfer
782
811
783
  my $transfer = $item->request_transfer(
812
  my $transfer = $item->request_transfer(
Lines 1525-1532 sub columns_to_str { Link Here
1525
              $subfield
1554
              $subfield
1526
            ? $subfield->{authorised_value}
1555
            ? $subfield->{authorised_value}
1527
                ? C4::Biblio::GetAuthorisedValueDesc(
1556
                ? C4::Biblio::GetAuthorisedValueDesc(
1528
                    $itemtagfield,
1557
                $itemtagfield,
1529
                    $subfield->{tagsubfield}, $value, '', $tagslib
1558
                $subfield->{tagsubfield}, $value, '', $tagslib
1530
                )
1559
                )
1531
                : $value
1560
                : $value
1532
            : $value;
1561
            : $value;
Lines 1750-1756 sub _set_found_trigger { Link Here
1750
                            if ($lost_fee_payment) {
1779
                            if ($lost_fee_payment) {
1751
                                my $today = dt_from_string();
1780
                                my $today = dt_from_string();
1752
                                my $payment_age_in_days =
1781
                                my $payment_age_in_days =
1753
                                    dt_from_string( $lost_fee_payment->created_on )->delta_days($today)
1782
                                    dt_from_string( $lost_fee_payment->created_on )
1783
                                    ->delta_days($today)
1754
                                    ->in_units('days');
1784
                                    ->in_units('days');
1755
                                if ( $payment_age_in_days > $no_refund_if_lost_fee_paid_age ) {
1785
                                if ( $payment_age_in_days > $no_refund_if_lost_fee_paid_age ) {
1756
                                    $self->add_message(
1786
                                    $self->add_message(
(-)a/Koha/Item/Checkin/Availability.pm (+173 lines)
Line 0 Link Here
1
package Koha::Item::Checkin::Availability;
2
3
# Copyright 2026 Koha Development Team
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <https://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use C4::Context;
23
use Koha::Availability::Result;
24
25
=head1 NAME
26
27
Koha::Item::Checkin::Availability - Check-in availability validation for items
28
29
=head1 SYNOPSIS
30
31
    my $availability = Koha::Item::Checkin::Availability->check( $item,
32
        {
33
            branch => $branch,
34
        }
35
    );
36
37
    # Or via instance method
38
    my $availability = $item->checkin_availability( { branch => $branch } );
39
40
=head1 DESCRIPTION
41
42
This class provides validation logic for item check-in operations, extracting
43
and centralizing checks from C4::Circulation::AddReturn.
44
45
The result categorizes conditions as:
46
- blockers: Prevent check-in (e.g., BlockReturnOfWithdrawnItems enabled)
47
- confirmations: Noteworthy conditions (e.g., item not checked out)
48
- warnings: Informational (e.g., item is withdrawn but check-in allowed)
49
50
=head1 API
51
52
=head2 Class methods
53
54
=head3 check
55
56
    my $availability = Koha::Item::Checkin::Availability->check( $item,
57
        {
58
            branch => $branch,
59
        }
60
    );
61
62
Validates check-in availability for an item.
63
64
Parameters:
65
    $item   - Koha::Item object (required)
66
    $params - hashref with:
67
        branch => $branchcode (required)
68
69
Returns a Koha::Availability::Result object with:
70
    blockers      => {}       # Conditions that prevent check-in
71
    confirmations => {}       # Conditions requiring confirmation
72
    warnings      => {}       # Informational messages
73
    context       => {
74
        checkout => $checkout # Koha::Checkout object if checked out
75
        patron   => $patron   # Koha::Patron object if item is checked out
76
    }
77
78
=cut
79
80
sub check {
81
    my ( $class, $item, $params ) = @_;
82
83
    my $branch = $params->{branch};
84
85
    my $result = Koha::Availability::Result->new();
86
87
    # Check if item is withdrawn and blocked
88
    if ( $item->withdrawn && C4::Context->preference("BlockReturnOfWithdrawnItems") ) {
89
        $result->add_blocker( BlockedWithdrawn => 1 );
90
        return $result;
91
    }
92
93
    # Check if item is lost and blocked
94
    if ( $item->itemlost && C4::Context->preference("BlockReturnOfLostItems") ) {
95
        $result->add_blocker( BlockedLost => 1 );
96
        return $result;
97
    }
98
99
    # Check branch check-in policy
100
    my ( $returnallowed, $message ) = _can_book_be_returned( $item, $branch );
101
    unless ($returnallowed) {
102
        $result->add_blocker(
103
            Wrongbranch => {
104
                Wrongbranch => $branch,
105
                Rightbranch => $message
106
            }
107
        );
108
        return $result;
109
    }
110
111
    # Check if item is currently checked out
112
    my $checkout = $item->checkout;
113
    if ($checkout) {
114
        $result->set_context( checkout => $checkout );
115
        $result->set_context( patron   => $checkout->patron );
116
    } else {
117
118
        # Item not checked out
119
        $result->add_confirmation( NotIssued => $item->barcode );
120
    }
121
122
    # Add warnings for withdrawn (when not blocked)
123
    if ( $item->withdrawn ) {
124
        $result->add_warning( withdrawn => 1 );
125
    }
126
127
    return $result;
128
}
129
130
=head2 Internal methods
131
132
=head3 _can_book_be_returned
133
134
    my ($allowed, $message) = _can_book_be_returned($item, $branch);
135
136
Internal method to check if an item can be checked in to a specific branch
137
based on the AllowReturnToBranch system preference.
138
139
This is extracted from C4::Circulation::CanBookBeReturned.
140
141
=cut
142
143
sub _can_book_be_returned {
144
    my ( $item, $branch ) = @_;
145
    my $allowreturntobranch = C4::Context->preference("AllowReturnToBranch") || 'anywhere';
146
147
    my $allowed = 1;
148
    my $message;
149
150
    if ( $allowreturntobranch eq 'homebranch' && $branch ne $item->homebranch ) {
151
        $allowed = 0;
152
        $message = $item->homebranch;
153
    } elsif ( $allowreturntobranch eq 'holdingbranch' && $branch ne $item->holdingbranch ) {
154
        $allowed = 0;
155
        $message = $item->holdingbranch;
156
    } elsif ( $allowreturntobranch eq 'homeorholdingbranch'
157
        && $branch ne $item->homebranch
158
        && $branch ne $item->holdingbranch )
159
    {
160
        $allowed = 0;
161
        $message = $item->homebranch;
162
    }
163
164
    return ( $allowed, $message );
165
}
166
167
=head1 AUTHOR
168
169
Koha Development Team <https://koha-community.org/>
170
171
=cut
172
173
1;
(-)a/t/db_dependent/Koha/Item/Checkin/Availability.t (-1 / +236 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright 2026 Koha Development team
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <https://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Test::More tests => 7;
23
use Test::NoWarnings;
24
25
use t::lib::TestBuilder;
26
use t::lib::Mocks;
27
28
use Koha::Database;
29
use Koha::Item::Checkin::Availability;
30
31
my $builder = t::lib::TestBuilder->new;
32
my $schema  = Koha::Database->new->schema;
33
34
subtest 'check() - item exists' => sub {
35
36
    plan tests => 2;
37
38
    $schema->storage->txn_begin;
39
40
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
41
    my $item    = $builder->build_object( { class => 'Koha::Items' } );
42
43
    my $result = $item->checkin_availability( { branch => $library->branchcode } );
44
45
    isa_ok( $result, 'Koha::Availability::Result', 'Returns Result object' );
46
    ok( $result->available, 'Item is available for check-in' );
47
48
    $schema->storage->txn_rollback;
49
};
50
51
subtest 'check() - BlockedWithdrawn blocker' => sub {
52
53
    plan tests => 2;
54
55
    $schema->storage->txn_begin;
56
57
    t::lib::Mocks::mock_preference( 'BlockReturnOfWithdrawnItems', 1 );
58
59
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
60
    my $item    = $builder->build_object(
61
        {
62
            class => 'Koha::Items',
63
            value => { withdrawn => 1 }
64
        }
65
    );
66
67
    my $result = $item->checkin_availability( { branch => $library->branchcode } );
68
69
    is( $result->blockers->{BlockedWithdrawn}, 1, 'BlockedWithdrawn blocker set' );
70
    is( keys %{ $result->confirmations },      0, 'no confirmations when blocked' );
71
72
    $schema->storage->txn_rollback;
73
};
74
75
subtest 'check() - BlockedLost blocker' => sub {
76
77
    plan tests => 2;
78
79
    $schema->storage->txn_begin;
80
81
    t::lib::Mocks::mock_preference( 'BlockReturnOfLostItems', 1 );
82
83
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
84
    my $item    = $builder->build_object(
85
        {
86
            class => 'Koha::Items',
87
            value => { itemlost => 1 }
88
        }
89
    );
90
91
    my $result = $item->checkin_availability(
92
        {
93
            branch => $library->branchcode,
94
        }
95
    );
96
97
    is( $result->blockers->{BlockedLost}, 1, 'BlockedLost blocker set' );
98
99
    is( keys %{ $result->confirmations }, 0, 'no confirmations when blocked' );
100
101
    $schema->storage->txn_rollback;
102
};
103
104
subtest 'check() - Wrongbranch blocker' => sub {
105
106
    plan tests => 8;
107
108
    $schema->storage->txn_begin;
109
110
    my $homebranch    = $builder->build_object( { class => 'Koha::Libraries' } );
111
    my $holdingbranch = $builder->build_object( { class => 'Koha::Libraries' } );
112
    my $wrongbranch   = $builder->build_object( { class => 'Koha::Libraries' } );
113
114
    my $item = $builder->build_object(
115
        {
116
            class => 'Koha::Items',
117
            value => {
118
                homebranch    => $homebranch->branchcode,
119
                holdingbranch => $holdingbranch->branchcode,
120
            }
121
        }
122
    );
123
124
    # Test homebranch restriction
125
    t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
126
    my $result = $item->checkin_availability(
127
        {
128
            branch => $wrongbranch->branchcode,
129
        }
130
    );
131
132
    is( ref( $result->blockers->{Wrongbranch} ),         'HASH',                   'Wrongbranch blocker is hashref' );
133
    is( $result->blockers->{Wrongbranch}->{Wrongbranch}, $wrongbranch->branchcode, 'wrong branch recorded' );
134
    is( $result->blockers->{Wrongbranch}->{Rightbranch}, $homebranch->branchcode,  'right branch recorded' );
135
136
    # Test holdingbranch restriction
137
    t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
138
    $result = $item->checkin_availability(
139
        {
140
            branch => $wrongbranch->branchcode,
141
        }
142
    );
143
144
    is(
145
        $result->blockers->{Wrongbranch}->{Wrongbranch}, $wrongbranch->branchcode,
146
        'wrong branch recorded for holdingbranch'
147
    );
148
    is(
149
        $result->blockers->{Wrongbranch}->{Rightbranch}, $holdingbranch->branchcode,
150
        'holding branch recorded as right branch'
151
    );
152
153
    # Test homeorholdingbranch restriction
154
    t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homeorholdingbranch' );
155
    $result = $item->checkin_availability(
156
        {
157
            branch => $wrongbranch->branchcode,
158
        }
159
    );
160
161
    is(
162
        $result->blockers->{Wrongbranch}->{Wrongbranch}, $wrongbranch->branchcode,
163
        'wrong branch recorded for homeorholdingbranch'
164
    );
165
166
    # Test anywhere - should not block
167
    t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
168
    $result = $item->checkin_availability(
169
        {
170
            branch => $wrongbranch->branchcode,
171
        }
172
    );
173
174
    is( $result->blockers->{Wrongbranch}, undef, 'no Wrongbranch blocker when anywhere allowed' );
175
    is( keys %{ $result->blockers },      0,     'no blockers when anywhere allowed' );
176
177
    $schema->storage->txn_rollback;
178
};
179
180
subtest 'check() - NotIssued confirmation' => sub {
181
182
    plan tests => 4;
183
184
    $schema->storage->txn_begin;
185
186
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
187
    my $item    = $builder->build_object( { class => 'Koha::Items' } );
188
189
    my $result = $item->checkin_availability(
190
        {
191
            branch => $library->branchcode,
192
        }
193
    );
194
195
    is( keys %{ $result->blockers },         0,              'no blockers for not issued item' );
196
    is( $result->confirmations->{NotIssued}, $item->barcode, 'NotIssued confirmation set with barcode' );
197
    is( $result->{issue},                    undef,          'checkout is undef when not checked out' );
198
    is( $result->context->{patron},          undef,          'patron is undef when not checked out' );
199
200
    $schema->storage->txn_rollback;
201
};
202
203
subtest 'check() - checked out item (no confirmations)' => sub {
204
205
    plan tests => 5;
206
207
    $schema->storage->txn_begin;
208
209
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
210
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
211
    my $item    = $builder->build_object( { class => 'Koha::Items' } );
212
    my $issue   = $builder->build_object(
213
        {
214
            class => 'Koha::Checkouts',
215
            value => {
216
                itemnumber     => $item->itemnumber,
217
                borrowernumber => $patron->borrowernumber,
218
            }
219
        }
220
    );
221
222
    my $result = $item->checkin_availability(
223
        {
224
            branch => $library->branchcode,
225
        }
226
    );
227
228
    is( keys %{ $result->blockers },      0, 'no blockers for checked out item' );
229
    is( keys %{ $result->confirmations }, 0, 'no confirmations for checked out item' );
230
231
    is( ref( $result->context->{checkout} ), 'Koha::Checkout', 'checkout object returned' );
232
    is( ref( $result->context->{patron} ),   'Koha::Patron',   'patron object returned' );
233
    is( $result->context->{patron}->id,      $patron->id,      'correct patron returned' );
234
235
    $schema->storage->txn_rollback;
236
};

Return to bug 41728