Bug 35248: Add tests for Koha::Booking->_assign_item_for_booking
[koha.git] / t / db_dependent / Koha / Booking.t
1 #!/usr/bin/perl
2
3 # Copyright 2024 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 <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21 use utf8;
22
23 use Test::More tests => 2;
24
25 use Test::Exception;
26
27 use Koha::DateUtils qw( dt_from_string );
28
29 use t::lib::TestBuilder;
30 use t::lib::Mocks;
31
32 my $schema  = Koha::Database->new->schema;
33 my $builder = t::lib::TestBuilder->new;
34
35 subtest 'Relation accessor tests' => sub {
36     plan tests => 3;
37
38     subtest 'biblio relation tests' => sub {
39         plan tests => 3;
40         $schema->storage->txn_begin;
41
42         my $biblio = $builder->build_sample_biblio;
43         my $booking =
44             $builder->build_object( { class => 'Koha::Bookings', value => { biblio_id => $biblio->biblionumber } } );
45
46         my $THE_biblio = $booking->biblio;
47         is( ref($THE_biblio),          'Koha::Biblio',        "Koha::Booking->biblio returns a Koha::Biblio object" );
48         is( $THE_biblio->biblionumber, $biblio->biblionumber, "Koha::Booking->biblio returns the links biblio object" );
49
50         $THE_biblio->delete;
51         $booking = Koha::Bookings->find( $booking->booking_id );
52         is( $booking, undef, "The booking is deleted when the biblio it's attached to is deleted" );
53
54         $schema->storage->txn_rollback;
55     };
56
57     subtest 'patron relation tests' => sub {
58         plan tests => 3;
59         $schema->storage->txn_begin;
60
61         my $patron = $builder->build_object( { class => "Koha::Patrons" } );
62         my $booking =
63             $builder->build_object( { class => 'Koha::Bookings', value => { patron_id => $patron->borrowernumber } } );
64
65         my $THE_patron = $booking->patron;
66         is( ref($THE_patron), 'Koha::Patron', "Koha::Booking->patron returns a Koha::Patron object" );
67         is(
68             $THE_patron->borrowernumber, $patron->borrowernumber,
69             "Koha::Booking->patron returns the links patron object"
70         );
71
72         $THE_patron->delete;
73         $booking = Koha::Bookings->find( $booking->booking_id );
74         is( $booking, undef, "The booking is deleted when the patron it's attached to is deleted" );
75
76         $schema->storage->txn_rollback;
77     };
78
79     subtest 'item relation tests' => sub {
80         plan tests => 3;
81         $schema->storage->txn_begin;
82
83         my $item = $builder->build_sample_item( { bookable => 1 } );
84         my $booking =
85             $builder->build_object( { class => 'Koha::Bookings', value => { item_id => $item->itemnumber } } );
86
87         my $THE_item = $booking->item;
88         is( ref($THE_item), 'Koha::Item', "Koha::Booking->item returns a Koha::Item object" );
89         is(
90             $THE_item->itemnumber, $item->itemnumber,
91             "Koha::Booking->item returns the links item object"
92         );
93
94         $THE_item->delete;
95         $booking = Koha::Bookings->find( $booking->booking_id );
96         is( $booking, undef, "The booking is deleted when the item it's attached to is deleted" );
97
98         $schema->storage->txn_rollback;
99     };
100 };
101
102 subtest 'store() tests' => sub {
103     plan tests => 13;
104     $schema->storage->txn_begin;
105
106     my $patron  = $builder->build_object( { class => "Koha::Patrons" } );
107     my $biblio  = $builder->build_sample_biblio();
108     my $item_1  = $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
109     my $start_0 = dt_from_string->subtract( days => 2 )->truncate( to => 'day' );
110     my $end_0   = $start_0->clone()->add( days => 6 );
111
112     my $deleted_item = $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
113     $deleted_item->delete;
114
115     my $wrong_item = $builder->build_sample_item();
116
117     my $booking = Koha::Booking->new(
118         {
119             patron_id  => $patron->borrowernumber,
120             biblio_id  => $biblio->biblionumber,
121             item_id    => $deleted_item->itemnumber,
122             start_date => $start_0,
123             end_date   => $end_0
124         }
125     );
126
127     throws_ok { $booking->store() } 'Koha::Exceptions::Object::FKConstraint',
128         'Throws exception if passed a deleted item';
129
130     $booking = Koha::Booking->new(
131         {
132             patron_id  => $patron->borrowernumber,
133             biblio_id  => $biblio->biblionumber,
134             item_id    => $wrong_item->itemnumber,
135             start_date => $start_0,
136             end_date   => $end_0
137         }
138     );
139
140     throws_ok { $booking->store() } 'Koha::Exceptions::Object::FKConstraint',
141         "Throws exception if item passed doesn't match biblio passed";
142
143     $booking = Koha::Booking->new(
144         {
145             patron_id  => $patron->borrowernumber,
146             biblio_id  => $biblio->biblionumber,
147             item_id    => $item_1->itemnumber,
148             start_date => $start_0,
149             end_date   => $end_0
150         }
151     );
152
153     # FIXME: Should this be allowed if an item is passed specifically?
154     throws_ok { $booking->store() } 'Koha::Exceptions::Booking::Clash',
155         'Throws exception when there are no items marked bookable for this biblio';
156
157     $item_1->bookable(1)->store();
158     $booking->store();
159     ok( $booking->in_storage, 'First booking on item 1 stored OK' );
160
161     # Bookings
162     # ✓ Item 1    |----|
163     # ✗ Item 1      |----|
164
165     my $start_1 = dt_from_string->truncate( to => 'day' );
166     my $end_1   = $start_1->clone()->add( days => 6 );
167     $booking = Koha::Booking->new(
168         {
169             patron_id  => $patron->borrowernumber,
170             biblio_id  => $biblio->biblionumber,
171             item_id    => $item_1->itemnumber,
172             start_date => $start_1,
173             end_date   => $end_1
174         }
175     );
176     throws_ok { $booking->store } 'Koha::Exceptions::Booking::Clash',
177         'Throws exception when passed booking start_date falls inside another booking for the item passed';
178
179     # Bookings
180     # ✓ Item 1    |----|
181     # ✗ Item 1  |----|
182     $start_1 = dt_from_string->subtract( days => 4 )->truncate( to => 'day' );
183     $end_1   = $start_1->clone()->add( days => 6 );
184     $booking = Koha::Booking->new(
185         {
186             patron_id  => $patron->borrowernumber,
187             biblio_id  => $biblio->biblionumber,
188             item_id    => $item_1->itemnumber,
189             start_date => $start_1,
190             end_date   => $end_1
191         }
192     );
193     throws_ok { $booking->store } 'Koha::Exceptions::Booking::Clash',
194         'Throws exception when passed booking end_date falls inside another booking for the item passed';
195
196     # Bookings
197     # ✓ Item 1    |----|
198     # ✗ Item 1  |--------|
199     $start_1 = dt_from_string->subtract( days => 4 )->truncate( to => 'day' );
200     $end_1   = $start_1->clone()->add( days => 10 );
201     $booking = Koha::Booking->new(
202         {
203             patron_id  => $patron->borrowernumber,
204             biblio_id  => $biblio->biblionumber,
205             item_id    => $item_1->itemnumber,
206             start_date => $start_1,
207             end_date   => $end_1
208         }
209     );
210     throws_ok { $booking->store } 'Koha::Exceptions::Booking::Clash',
211         'Throws exception when passed booking dates would envelope another booking for the item passed';
212
213     # Bookings
214     # ✓ Item 1    |----|
215     # ✗ Item 1     |--|
216     $start_1 = dt_from_string->truncate( to => 'day' );
217     $end_1   = $start_1->clone()->add( days => 4 );
218     $booking = Koha::Booking->new(
219         {
220             patron_id  => $patron->borrowernumber,
221             biblio_id  => $biblio->biblionumber,
222             item_id    => $item_1->itemnumber,
223             start_date => $start_1,
224             end_date   => $end_1
225         }
226     );
227     throws_ok { $booking->store } 'Koha::Exceptions::Booking::Clash',
228         'Throws exception when passed booking dates would fall wholly inside another booking for the item passed';
229
230     my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, bookable => 1 } );
231     my $item_3 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, bookable => 0 } );
232
233     # Bookings
234     # ✓ Item 1    |----|
235     # ✓ Item 2     |--|
236     $booking = Koha::Booking->new(
237         {
238             patron_id  => $patron->borrowernumber,
239             biblio_id  => $biblio->biblionumber,
240             item_id    => $item_2->itemnumber,
241             start_date => $start_1,
242             end_date   => $end_1
243         }
244     )->store();
245     ok(
246         $booking->in_storage,
247         'First booking on item 2 stored OK, even though it would overlap with a booking on item 1'
248     );
249
250     # Bookings
251     # ✓ Item 1    |----|
252     # ✓ Item 2     |--|
253     # ✘ Any        |--|
254     $booking = Koha::Booking->new(
255         {
256             patron_id  => $patron->borrowernumber,
257             biblio_id  => $biblio->biblionumber,
258             start_date => $start_1,
259             end_date   => $end_1
260         }
261     );
262     throws_ok { $booking->store } 'Koha::Exceptions::Booking::Clash',
263         'Throws exception when passed booking dates would fall wholly inside all existing bookings when no item specified';
264
265     # Bookings
266     # ✓ Item 1    |----|
267     # ✓ Item 2     |--|
268     # ✓ Any             |--|
269     $start_1 = dt_from_string->add( days => 5 )->truncate( to => 'day' );
270     $end_1   = $start_1->clone()->add( days => 4 );
271     $booking = Koha::Booking->new(
272         {
273             patron_id  => $patron->borrowernumber,
274             biblio_id  => $biblio->biblionumber,
275             start_date => $start_1,
276             end_date   => $end_1
277         }
278     )->store();
279     ok( $booking->in_storage, 'Booking stored OK when item not specified and the booking slot is available' );
280     ok( $booking->item_id,    'An item was assigned to the booking' );
281
282     subtest '_assign_item_for_booking() tests' => sub {
283         plan tests => 1;
284         is( $booking->item_id, $item_1->itemnumber, "Item 1 was assigned to the booking" );
285
286         # Bookings
287         # ✓ Item 1    |----|
288         # ✓ Item 2     |--|
289         # ✓ Any (1)         |--|
290     };
291
292     $schema->storage->txn_rollback;
293 };