Bug 31105: Regression tests
[koha.git] / t / db_dependent / Koha / Items.t
1 #!/usr/bin/perl
2
3 # Copyright 2016 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
22 use Test::More tests => 16;
23
24 use Test::MockModule;
25 use Test::Exception;
26 use Time::Fake;
27
28 use C4::Circulation qw( AddIssue LostItem AddReturn );
29 use C4::Context;
30 use C4::Serials qw( NewIssue AddItem2Serial );
31 use Koha::Item;
32 use Koha::Item::Transfer::Limits;
33 use Koha::Items;
34 use Koha::Database;
35 use Koha::DateUtils qw( dt_from_string );
36
37 use t::lib::TestBuilder;
38 use t::lib::Mocks;
39 use t::lib::Dates;
40
41 my $schema = Koha::Database->new->schema;
42 $schema->storage->txn_begin;
43
44 my $dbh     = C4::Context->dbh;
45
46 my $builder     = t::lib::TestBuilder->new;
47 my $library     = $builder->build( { source => 'Branch' } );
48 my $nb_of_items = Koha::Items->search->count;
49 my $biblio      = $builder->build_sample_biblio();
50 my $new_item_1   = $builder->build_sample_item({
51     biblionumber => $biblio->biblionumber,
52     homebranch       => $library->{branchcode},
53     holdingbranch    => $library->{branchcode},
54 });
55 my $new_item_2   = $builder->build_sample_item({
56     biblionumber => $biblio->biblionumber,
57     homebranch       => $library->{branchcode},
58     holdingbranch    => $library->{branchcode},
59 });
60
61
62 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
63
64 like( $new_item_1->itemnumber, qr|^\d+$|, 'Adding a new item should have set the itemnumber' );
65 is( Koha::Items->search->count, $nb_of_items + 2, 'The 2 items should have been added' );
66
67 my $retrieved_item_1 = Koha::Items->find( $new_item_1->itemnumber );
68 is( $retrieved_item_1->barcode, $new_item_1->barcode, 'Find a item by id should return the correct item' );
69
70 subtest 'store' => sub {
71     plan tests => 7;
72
73     my $biblio = $builder->build_sample_biblio;
74     my $today  = dt_from_string->set( hour => 0, minute => 0, second => 0 );
75     my $item   = Koha::Item->new(
76         {
77             homebranch    => $library->{branchcode},
78             holdingbranch => $library->{branchcode},
79             biblionumber  => $biblio->biblionumber,
80             location      => 'my_loc',
81         }
82     )->store->get_from_storage;
83
84     is( t::lib::Dates::compare( $item->replacementpricedate, $today ),
85         0, 'replacementpricedate must have been set to today if not given' );
86     is( t::lib::Dates::compare( $item->datelastseen, $today ),
87         0, 'datelastseen must have been set to today if not given' );
88     is(
89         $item->itype,
90         $biblio->biblioitem->itemtype,
91         'items.itype must have been set to biblioitem.itemtype is not given'
92     );
93     $item->delete;
94
95     subtest 'permanent_location' => sub {
96         plan tests => 2;
97
98         subtest 'location passed to ->store' => sub {
99             plan tests => 7;
100
101             my $location = 'my_loc';
102             my $attributes = {
103                 homebranch    => $library->{branchcode},
104                 holdingbranch => $library->{branchcode},
105                 biblionumber  => $biblio->biblionumber,
106                 location      => $location,
107             };
108
109             {
110                 # NewItemsDefaultLocation not set
111                 t::lib::Mocks::mock_preference( 'NewItemsDefaultLocation', '' );
112
113                 # Not passing permanent_location on creating the item
114                 my $item = Koha::Item->new($attributes)->store->get_from_storage;
115                 is( $item->location, $location,
116                     'location must have been set to location if given' );
117                 is( $item->permanent_location, $item->location,
118                     'permanent_location must have been set to location if not given' );
119                 $item->delete;
120
121                 # Passing permanent_location on creating the item
122                 $item = Koha::Item->new(
123                     { %$attributes, permanent_location => 'perm_loc' } )
124                   ->store->get_from_storage;
125                 is( $item->permanent_location, 'perm_loc',
126                     'permanent_location must have been kept if given' );
127                 $item->delete;
128             }
129
130             {
131                 # NewItemsDefaultLocation set
132                 my $default_location = 'default_location';
133                 t::lib::Mocks::mock_preference( 'NewItemsDefaultLocation', $default_location );
134
135                 # Not passing permanent_location on creating the item
136                 my $item = Koha::Item->new($attributes)->store->get_from_storage;
137                 is( $item->location, $location,
138                     'location must have been kept if given' );
139                 is( $item->permanent_location, $location,
140                     'permanent_location must have been set to the location given' );
141                 $item->delete;
142
143                 # Passing permanent_location on creating the item
144                 $item = Koha::Item->new(
145                     { %$attributes, permanent_location => 'perm_loc' } )
146                   ->store->get_from_storage;
147                 is( $item->location, $location,
148                     'location must have been kept if given' );
149                 is( $item->permanent_location, 'perm_loc',
150                     'permanent_location must have been kept if given' );
151                 $item->delete;
152             }
153         };
154
155         subtest 'location NOT passed to ->store' => sub {
156             plan tests => 7;
157
158             my $attributes = {
159                 homebranch    => $library->{branchcode},
160                 holdingbranch => $library->{branchcode},
161                 biblionumber  => $biblio->biblionumber,
162             };
163
164             {
165                 # NewItemsDefaultLocation not set
166                 t::lib::Mocks::mock_preference( 'NewItemsDefaultLocation', '' );
167
168                 # Not passing permanent_location on creating the item
169                 my $item = Koha::Item->new($attributes)->store->get_from_storage;
170                 is( $item->location, undef,
171                     'location not passed and no default, it is undef' );
172                 is( $item->permanent_location, $item->location,
173                     'permanent_location must have been set to location if not given' );
174                 $item->delete;
175
176                 # Passing permanent_location on creating the item
177                 $item = Koha::Item->new(
178                     { %$attributes, permanent_location => 'perm_loc' } )
179                   ->store->get_from_storage;
180                 is( $item->permanent_location, 'perm_loc',
181                     'permanent_location must have been kept if given' );
182                 $item->delete;
183             }
184
185             {
186                 # NewItemsDefaultLocation set
187                 my $default_location = 'default_location';
188                 t::lib::Mocks::mock_preference( 'NewItemsDefaultLocation', $default_location );
189
190                 # Not passing permanent_location on creating the item
191                 my $item = Koha::Item->new($attributes)->store->get_from_storage;
192                 is( $item->location, $default_location,
193                     'location must have been set to default location if not given' );
194                 is( $item->permanent_location, $default_location,
195                     'permanent_location must have been set to the default location as well' );
196                 $item->delete;
197
198                 # Passing permanent_location on creating the item
199                 $item = Koha::Item->new(
200                     { %$attributes, permanent_location => 'perm_loc' } )
201                   ->store->get_from_storage;
202                 is( $item->location, $default_location,
203                     'location must have been set to default location if not given' );
204                 is( $item->permanent_location, 'perm_loc',
205                     'permanent_location must have been kept if given' );
206                 $item->delete;
207             }
208         };
209
210     };
211
212     subtest '*_on updates' => sub {
213         plan tests => 9;
214
215         # Once the '_on' value is set (triggered by the related field turning from false to true)
216         # it should not be re-set for any changes outside of the related field being 'unset'.
217
218         my @fields = qw( itemlost withdrawn damaged );
219         my $today = dt_from_string();
220         my $yesterday = $today->clone()->subtract( days => 1 );
221
222         for my $field ( @fields ) {
223             my $item = $builder->build_sample_item(
224                 {
225                     itemlost     => 0,
226                     itemlost_on  => undef,
227                     withdrawn    => 0,
228                     withdrawn_on => undef,
229                     damaged      => 0,
230                     damaged_on   => undef
231                 }
232             );
233             my $field_on = $field . '_on';
234
235             # Set field for the first time
236             Time::Fake->offset( $yesterday->epoch );
237             $item->$field(1)->store;
238             $item->get_from_storage;
239             is( t::lib::Dates::compare( $item->$field_on, $yesterday ),
240                 0, $field_on . " was set upon first truthy setting" );
241
242             # Update the field to a new 'true' value
243             Time::Fake->offset( $today->epoch );
244             $item->$field(2)->store;
245             $item->get_from_storage;
246             is( t::lib::Dates::compare( $item->$field_on, $yesterday ),
247                 0, $field_on . " was not updated upon second truthy setting" );
248
249             # Update the field to a new 'false' value
250             $item->$field(0)->store;
251             $item->get_from_storage;
252             is($item->$field_on, undef, $field_on . " was unset upon untruthy setting");
253
254             Time::Fake->reset;
255         }
256     };
257
258     subtest '_lost_found_trigger' => sub {
259         plan tests => 10;
260
261         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee', 1 );
262         t::lib::Mocks::mock_preference( 'WhenLostForgiveFine',          0 );
263
264         my $processfee_amount  = 20;
265         my $replacement_amount = 99.00;
266         my $item_type          = $builder->build_object(
267             {
268                 class => 'Koha::ItemTypes',
269                 value => {
270                     notforloan         => undef,
271                     rentalcharge       => 0,
272                     defaultreplacecost => undef,
273                     processfee         => $processfee_amount,
274                     rentalcharge_daily => 0,
275                 }
276             }
277         );
278         my $library = $builder->build_object( { class => 'Koha::Libraries' } );
279
280         $biblio = $builder->build_sample_biblio( { author => 'Hall, Daria' } );
281
282         subtest 'Full write-off tests' => sub {
283
284             plan tests => 12;
285
286             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
287             my $manager =
288               $builder->build_object( { class => "Koha::Patrons" } );
289             t::lib::Mocks::mock_userenv(
290                 { patron => $manager, branchcode => $manager->branchcode } );
291
292             my $item = $builder->build_sample_item(
293                 {
294                     biblionumber     => $biblio->biblionumber,
295                     library          => $library->branchcode,
296                     replacementprice => $replacement_amount,
297                     itype            => $item_type->itemtype,
298                 }
299             );
300
301             C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
302
303             # Simulate item marked as lost
304             $item->itemlost(3)->store;
305             C4::Circulation::LostItem( $item->itemnumber, 1 );
306
307             my $processing_fee_lines = Koha::Account::Lines->search(
308                 {
309                     borrowernumber  => $patron->id,
310                     itemnumber      => $item->itemnumber,
311                     debit_type_code => 'PROCESSING'
312                 }
313             );
314             is( $processing_fee_lines->count,
315                 1, 'Only one processing fee produced' );
316             my $processing_fee_line = $processing_fee_lines->next;
317             is( $processing_fee_line->amount + 0,
318                 $processfee_amount,
319                 'The right PROCESSING amount is generated' );
320             is( $processing_fee_line->amountoutstanding + 0,
321                 $processfee_amount,
322                 'The right PROCESSING amountoutstanding is generated' );
323
324             my $lost_fee_lines = Koha::Account::Lines->search(
325                 {
326                     borrowernumber  => $patron->id,
327                     itemnumber      => $item->itemnumber,
328                     debit_type_code => 'LOST'
329                 }
330             );
331             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
332             my $lost_fee_line = $lost_fee_lines->next;
333             is( $lost_fee_line->amount + 0,
334                 $replacement_amount, 'The right LOST amount is generated' );
335             is( $lost_fee_line->amountoutstanding + 0,
336                 $replacement_amount,
337                 'The right LOST amountoutstanding is generated' );
338             is( $lost_fee_line->status, undef, 'The LOST status was not set' );
339
340             my $account = $patron->account;
341             my $debts   = $account->outstanding_debits;
342
343             # Write off the debt
344             my $credit = $account->add_credit(
345                 {
346                     amount    => $account->balance,
347                     type      => 'WRITEOFF',
348                     interface => 'test',
349                 }
350             );
351             $credit->apply( { debits => [ $debts->as_list ] } );
352
353             # Simulate item marked as found
354             $item->itemlost(0)->store;
355             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 0, 'No LOST_FOUND account line added' );
356
357             $lost_fee_line->discard_changes;    # reload from DB
358             is( $lost_fee_line->amountoutstanding + 0,
359                 0, 'Lost fee has no outstanding amount' );
360             is( $lost_fee_line->debit_type_code,
361                 'LOST', 'Lost fee now still has account type of LOST' );
362             is( $lost_fee_line->status, 'FOUND',
363                 "Lost fee now has account status of FOUND - No Refund" );
364
365             is( $patron->account->balance,
366                 -0, 'The patron balance is 0, everything was written off' );
367         };
368
369         subtest 'Full payment tests' => sub {
370
371             plan tests => 14;
372
373             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
374
375             my $item = $builder->build_sample_item(
376                 {
377                     biblionumber     => $biblio->biblionumber,
378                     library          => $library->branchcode,
379                     replacementprice => $replacement_amount,
380                     itype            => $item_type->itemtype
381                 }
382             );
383
384             my $issue =
385               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
386
387             # Simulate item marked as lost
388             $item->itemlost(1)->store;
389             C4::Circulation::LostItem( $item->itemnumber, 1 );
390
391             my $processing_fee_lines = Koha::Account::Lines->search(
392                 {
393                     borrowernumber  => $patron->id,
394                     itemnumber      => $item->itemnumber,
395                     debit_type_code => 'PROCESSING'
396                 }
397             );
398             is( $processing_fee_lines->count,
399                 1, 'Only one processing fee produced' );
400             my $processing_fee_line = $processing_fee_lines->next;
401             is( $processing_fee_line->amount + 0,
402                 $processfee_amount,
403                 'The right PROCESSING amount is generated' );
404             is( $processing_fee_line->amountoutstanding + 0,
405                 $processfee_amount,
406                 'The right PROCESSING amountoutstanding is generated' );
407
408             my $lost_fee_lines = Koha::Account::Lines->search(
409                 {
410                     borrowernumber  => $patron->id,
411                     itemnumber      => $item->itemnumber,
412                     debit_type_code => 'LOST'
413                 }
414             );
415             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
416             my $lost_fee_line = $lost_fee_lines->next;
417             is( $lost_fee_line->amount + 0,
418                 $replacement_amount, 'The right LOST amount is generated' );
419             is( $lost_fee_line->amountoutstanding + 0,
420                 $replacement_amount,
421                 'The right LOST amountountstanding is generated' );
422
423             my $account = $patron->account;
424             my $debts   = $account->outstanding_debits;
425
426             # Pay off the debt
427             my $credit = $account->add_credit(
428                 {
429                     amount    => $account->balance,
430                     type      => 'PAYMENT',
431                     interface => 'test',
432                 }
433             );
434             $credit->apply( { debits => [ $debts->as_list ] } );
435
436             # Simulate item marked as found
437             $item->itemlost(0)->store;
438             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
439
440             my $credit_return = Koha::Account::Lines->search(
441                 {
442                     itemnumber       => $item->itemnumber,
443                     credit_type_code => 'LOST_FOUND'
444                 },
445                 { rows => 1 }
446             )->single;
447
448             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
449             is( $credit_return->amount + 0,
450                 -99.00,
451                 'The account line of type LOST_FOUND has an amount of -99' );
452             is(
453                 $credit_return->amountoutstanding + 0,
454                 -99.00,
455 'The account line of type LOST_FOUND has an amountoutstanding of -99'
456             );
457
458             $lost_fee_line->discard_changes;
459             is( $lost_fee_line->amountoutstanding + 0,
460                 0, 'Lost fee has no outstanding amount' );
461             is( $lost_fee_line->debit_type_code,
462                 'LOST', 'Lost fee now still has account type of LOST' );
463             is( $lost_fee_line->status, 'FOUND',
464                 "Lost fee now has account status of FOUND" );
465
466             is( $patron->account->balance, -99,
467 'The patron balance is -99, a credit that equals the lost fee payment'
468             );
469         };
470
471         subtest 'Test without payment or write off' => sub {
472
473             plan tests => 14;
474
475             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
476
477             my $item = $builder->build_sample_item(
478                 {
479                     biblionumber     => $biblio->biblionumber,
480                     library          => $library->branchcode,
481                     replacementprice => 23.00,
482                     replacementprice => $replacement_amount,
483                     itype            => $item_type->itemtype
484                 }
485             );
486
487             my $issue =
488               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
489
490             # Simulate item marked as lost
491             $item->itemlost(3)->store;
492             C4::Circulation::LostItem( $item->itemnumber, 1 );
493
494             my $processing_fee_lines = Koha::Account::Lines->search(
495                 {
496                     borrowernumber  => $patron->id,
497                     itemnumber      => $item->itemnumber,
498                     debit_type_code => 'PROCESSING'
499                 }
500             );
501             is( $processing_fee_lines->count,
502                 1, 'Only one processing fee produced' );
503             my $processing_fee_line = $processing_fee_lines->next;
504             is( $processing_fee_line->amount + 0,
505                 $processfee_amount,
506                 'The right PROCESSING amount is generated' );
507             is( $processing_fee_line->amountoutstanding + 0,
508                 $processfee_amount,
509                 'The right PROCESSING amountoutstanding is generated' );
510
511             my $lost_fee_lines = Koha::Account::Lines->search(
512                 {
513                     borrowernumber  => $patron->id,
514                     itemnumber      => $item->itemnumber,
515                     debit_type_code => 'LOST'
516                 }
517             );
518             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
519             my $lost_fee_line = $lost_fee_lines->next;
520             is( $lost_fee_line->amount + 0,
521                 $replacement_amount, 'The right LOST amount is generated' );
522             is( $lost_fee_line->amountoutstanding + 0,
523                 $replacement_amount,
524                 'The right LOST amountountstanding is generated' );
525
526             # Simulate item marked as found
527             $item->itemlost(0)->store;
528             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
529
530             my $credit_return = Koha::Account::Lines->search(
531                 {
532                     itemnumber       => $item->itemnumber,
533                     credit_type_code => 'LOST_FOUND'
534                 },
535                 { rows => 1 }
536             )->single;
537
538             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
539             is( $credit_return->amount + 0,
540                 -99.00,
541                 'The account line of type LOST_FOUND has an amount of -99' );
542             is(
543                 $credit_return->amountoutstanding + 0,
544                 0,
545 'The account line of type LOST_FOUND has an amountoutstanding of 0'
546             );
547
548             $lost_fee_line->discard_changes;
549             is( $lost_fee_line->amountoutstanding + 0,
550                 0, 'Lost fee has no outstanding amount' );
551             is( $lost_fee_line->debit_type_code,
552                 'LOST', 'Lost fee now still has account type of LOST' );
553             is( $lost_fee_line->status, 'FOUND',
554                 "Lost fee now has account status of FOUND" );
555
556             is( $patron->account->balance,
557                 20, 'The patron balance is 20, still owes the processing fee' );
558         };
559
560         subtest
561           'Test with partial payment and write off, and remaining debt' =>
562           sub {
563
564             plan tests => 19;
565
566             t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 0 );
567
568             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
569             my $item = $builder->build_sample_item(
570                 {
571                     biblionumber     => $biblio->biblionumber,
572                     library          => $library->branchcode,
573                     replacementprice => $replacement_amount,
574                     itype            => $item_type->itemtype
575                 }
576             );
577
578             my $issue =
579               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
580
581             # Simulate item marked as lost
582             $item->itemlost(1)->store;
583             C4::Circulation::LostItem( $item->itemnumber, 1 );
584
585             my $processing_fee_lines = Koha::Account::Lines->search(
586                 {
587                     borrowernumber  => $patron->id,
588                     itemnumber      => $item->itemnumber,
589                     debit_type_code => 'PROCESSING'
590                 }
591             );
592             is( $processing_fee_lines->count,
593                 1, 'Only one processing fee produced' );
594             my $processing_fee_line = $processing_fee_lines->next;
595             is( $processing_fee_line->amount + 0,
596                 $processfee_amount,
597                 'The right PROCESSING amount is generated' );
598             is( $processing_fee_line->amountoutstanding + 0,
599                 $processfee_amount,
600                 'The right PROCESSING amountoutstanding is generated' );
601
602             my $lost_fee_lines = Koha::Account::Lines->search(
603                 {
604                     borrowernumber  => $patron->id,
605                     itemnumber      => $item->itemnumber,
606                     debit_type_code => 'LOST'
607                 }
608             );
609             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
610             my $lost_fee_line = $lost_fee_lines->next;
611             is( $lost_fee_line->amount + 0,
612                 $replacement_amount, 'The right LOST amount is generated' );
613             is( $lost_fee_line->amountoutstanding + 0,
614                 $replacement_amount,
615                 'The right LOST amountountstanding is generated' );
616
617             my $account = $patron->account;
618             is(
619                 $account->balance,
620                 $processfee_amount + $replacement_amount,
621                 'Balance is PROCESSING + LOST'
622             );
623
624             # Partially pay fee (99 - 27 = 72)
625             my $payment_amount = 24;
626             my $payment        = $account->add_credit(
627                 {
628                     amount    => $payment_amount,
629                     type      => 'PAYMENT',
630                     interface => 'test',
631                 }
632             );
633
634             $payment->apply( { debits => [$lost_fee_line] } );
635
636             # Partially write off fee (72 - 20 = 52)
637             my $write_off_amount = 20;
638             my $write_off        = $account->add_credit(
639                 {
640                     amount    => $write_off_amount,
641                     type      => 'WRITEOFF',
642                     interface => 'test',
643                 }
644             );
645             $write_off->apply( { debits => [$lost_fee_line] } );
646
647
648             my $payment_amount_2 = 3;
649             my $payment_2        = $account->add_credit(
650                 {
651                     amount    => $payment_amount_2,
652                     type      => 'PAYMENT',
653                     interface => 'test',
654                 }
655             );
656
657             $payment_2->apply(
658                 { debits => [$lost_fee_line] } );
659
660             # Partially write off fee (52 - 5 = 47)
661             my $write_off_amount_2 = 5;
662             my $write_off_2        = $account->add_credit(
663                 {
664                     amount    => $write_off_amount_2,
665                     type      => 'WRITEOFF',
666                     interface => 'test',
667                 }
668             );
669
670             $write_off_2->apply(
671                 { debits => [$lost_fee_line] } );
672
673             is(
674                 $account->balance,
675                 $processfee_amount +
676                   $replacement_amount -
677                   $payment_amount -
678                   $write_off_amount -
679                   $payment_amount_2 -
680                   $write_off_amount_2,
681                 'Balance is PROCESSING + LOST - PAYMENT 1 - WRITEOFF - PAYMENT 2 - WRITEOFF 2'
682             );
683
684             # VOID payment_2 and writeoff_2
685             $payment_2->void({ interface => 'test' });
686             $write_off_2->void({ interface => 'test' });
687
688             is(
689                 $account->balance,
690                 $processfee_amount +
691                   $replacement_amount -
692                   $payment_amount -
693                   $write_off_amount,
694                 'Balance is PROCESSING + LOST - PAYMENT 1 - WRITEOFF (PAYMENT 2 and WRITEOFF 2 VOIDED)'
695             );
696
697             # Store the amountoutstanding value
698             $lost_fee_line->discard_changes;
699             my $outstanding = $lost_fee_line->amountoutstanding;
700             is(
701                 $outstanding + 0,
702                 $replacement_amount - $payment_amount - $write_off_amount,
703                 "Lost Fee Outstanding is LOST - PAYMENT 1 - WRITEOFF"
704             );
705
706             # Simulate item marked as found
707             $item->itemlost(0)->store;
708             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
709
710             my $credit_return = Koha::Account::Lines->search(
711                 {
712                     itemnumber       => $item->itemnumber,
713                     credit_type_code => 'LOST_FOUND'
714                 },
715                 { rows => 1 }
716             )->single;
717
718             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
719
720             is(
721                 $account->balance,
722                 $processfee_amount - $payment_amount,
723                 'Balance is PROCESSING - PAYMENT (LOST_FOUND)'
724             );
725
726             $lost_fee_line->discard_changes;
727             is( $lost_fee_line->amountoutstanding + 0,
728                 0, 'Lost fee has no outstanding amount' );
729             is( $lost_fee_line->debit_type_code,
730                 'LOST', 'Lost fee now still has account type of LOST' );
731             is( $lost_fee_line->status, 'FOUND',
732                 "Lost fee now has account status of FOUND" );
733
734             is(
735                 $credit_return->amount + 0,
736                 ( $payment_amount + $outstanding ) * -1,
737 'The account line of type LOST_FOUND has an amount equal to the payment 1 + outstanding'
738             );
739             is(
740                 $credit_return->amountoutstanding + 0,
741                 $payment_amount * -1,
742 'The account line of type LOST_FOUND has an amountoutstanding equal to the payment'
743             );
744
745             is(
746                 $account->balance,
747                 $processfee_amount - $payment_amount,
748 'The patron balance is the difference between the PROCESSING and the credit'
749             );
750           };
751
752         subtest 'Partial payment, existing debits and AccountAutoReconcile' =>
753           sub {
754
755             plan tests => 10;
756
757             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
758             my $barcode            = 'KD123456793';
759             my $replacement_amount = 100;
760             my $processfee_amount  = 20;
761
762             my $item_type = $builder->build_object(
763                 {
764                     class => 'Koha::ItemTypes',
765                     value => {
766                         notforloan         => undef,
767                         rentalcharge       => 0,
768                         defaultreplacecost => undef,
769                         processfee         => 0,
770                         rentalcharge_daily => 0,
771                     }
772                 }
773             );
774             my $item = Koha::Item->new(
775                 {
776                     biblionumber     => $biblio->biblionumber,
777                     homebranch       => $library->branchcode,
778                     holdingbranch    => $library->branchcode,
779                     barcode          => $barcode,
780                     replacementprice => $replacement_amount,
781                     itype            => $item_type->itemtype
782                 },
783             )->store;
784
785             my $issue =
786               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
787
788             # Simulate item marked as lost
789             $item->itemlost(1)->store;
790             C4::Circulation::LostItem( $item->itemnumber, 1 );
791
792             my $lost_fee_lines = Koha::Account::Lines->search(
793                 {
794                     borrowernumber  => $patron->id,
795                     itemnumber      => $item->itemnumber,
796                     debit_type_code => 'LOST'
797                 }
798             );
799             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
800             my $lost_fee_line = $lost_fee_lines->next;
801             is( $lost_fee_line->amount + 0,
802                 $replacement_amount, 'The right LOST amount is generated' );
803             is( $lost_fee_line->amountoutstanding + 0,
804                 $replacement_amount,
805                 'The right LOST amountountstanding is generated' );
806
807             my $account = $patron->account;
808             is( $account->balance, $replacement_amount, 'Balance is L' );
809
810             # Partially pay fee
811             my $payment_amount = 27;
812             my $payment        = $account->add_credit(
813                 {
814                     amount    => $payment_amount,
815                     type      => 'PAYMENT',
816                     interface => 'test',
817                 }
818             );
819             $payment->apply( { debits => [$lost_fee_line] } );
820
821             is(
822                 $account->balance,
823                 $replacement_amount - $payment_amount,
824                 'Payment applied'
825             );
826
827             my $manual_debit_amount = 80;
828             $account->add_debit(
829                 {
830                     amount    => $manual_debit_amount,
831                     type      => 'OVERDUE',
832                     interface => 'test'
833                 }
834             );
835
836             is(
837                 $account->balance,
838                 $manual_debit_amount + $replacement_amount - $payment_amount,
839                 'Manual debit applied'
840             );
841
842             t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 1 );
843
844             # Simulate item marked as found
845             $item->itemlost(0)->store;
846             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
847
848             my $credit_return = Koha::Account::Lines->search(
849                 {
850                     itemnumber       => $item->itemnumber,
851                     credit_type_code => 'LOST_FOUND'
852                 },
853                 { rows => 1 }
854             )->single;
855
856             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
857
858             is(
859                 $account->balance,
860                 $manual_debit_amount - $payment_amount,
861                 'Balance is PROCESSING - payment (LOST_FOUND)'
862             );
863
864             my $manual_debit = Koha::Account::Lines->search(
865                 {
866                     borrowernumber  => $patron->id,
867                     debit_type_code => 'OVERDUE',
868                     status          => 'UNRETURNED'
869                 }
870             )->next;
871             is(
872                 $manual_debit->amountoutstanding + 0,
873                 $manual_debit_amount - $payment_amount,
874                 'reconcile_balance was called'
875             );
876           };
877
878         subtest 'Patron deleted' => sub {
879             plan tests => 1;
880
881             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
882             my $barcode            = 'KD123456794';
883             my $replacement_amount = 100;
884             my $processfee_amount  = 20;
885
886             my $item_type = $builder->build_object(
887                 {
888                     class => 'Koha::ItemTypes',
889                     value => {
890                         notforloan         => undef,
891                         rentalcharge       => 0,
892                         defaultreplacecost => undef,
893                         processfee         => 0,
894                         rentalcharge_daily => 0,
895                     }
896                 }
897             );
898             my $item = Koha::Item->new(
899                 {
900                     biblionumber     => $biblio->biblionumber,
901                     homebranch       => $library->branchcode,
902                     holdingbranch    => $library->branchcode,
903                     barcode          => $barcode,
904                     replacementprice => $replacement_amount,
905                     itype            => $item_type->itemtype
906                 },
907             )->store;
908
909             my $issue =
910               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
911
912             # Simulate item marked as lost
913             $item->itemlost(1)->store;
914             C4::Circulation::LostItem( $item->itemnumber, 1 );
915
916             $issue->delete();
917             $patron->delete();
918
919             # Simulate item marked as found
920             $item->itemlost(0)->store;
921             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 0, 'No refund triggered' );
922
923         };
924
925         subtest 'restore fine | no overdue' => sub {
926
927             plan tests => 8;
928
929             my $manager =
930               $builder->build_object( { class => "Koha::Patrons" } );
931             t::lib::Mocks::mock_userenv(
932                 { patron => $manager, branchcode => $manager->branchcode } );
933
934             # Set lostreturn_policy to 'restore' for tests
935             my $specific_rule_restore = $builder->build(
936                 {
937                     source => 'CirculationRule',
938                     value  => {
939                         branchcode   => $manager->branchcode,
940                         categorycode => undef,
941                         itemtype     => undef,
942                         rule_name    => 'lostreturn',
943                         rule_value   => 'restore'
944                     }
945                 }
946             );
947
948             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
949
950             my $item = $builder->build_sample_item(
951                 {
952                     biblionumber     => $biblio->biblionumber,
953                     library          => $library->branchcode,
954                     replacementprice => $replacement_amount,
955                     itype            => $item_type->itemtype
956                 }
957             );
958
959             my $issue =
960               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
961
962             # Simulate item marked as lost
963             $item->itemlost(1)->store;
964             C4::Circulation::LostItem( $item->itemnumber, 1 );
965
966             my $processing_fee_lines = Koha::Account::Lines->search(
967                 {
968                     borrowernumber  => $patron->id,
969                     itemnumber      => $item->itemnumber,
970                     debit_type_code => 'PROCESSING'
971                 }
972             );
973             is( $processing_fee_lines->count,
974                 1, 'Only one processing fee produced' );
975             my $processing_fee_line = $processing_fee_lines->next;
976             is( $processing_fee_line->amount + 0,
977                 $processfee_amount,
978                 'The right PROCESSING amount is generated' );
979             is( $processing_fee_line->amountoutstanding + 0,
980                 $processfee_amount,
981                 'The right PROCESSING amountoutstanding is generated' );
982
983             my $lost_fee_lines = Koha::Account::Lines->search(
984                 {
985                     borrowernumber  => $patron->id,
986                     itemnumber      => $item->itemnumber,
987                     debit_type_code => 'LOST'
988                 }
989             );
990             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
991             my $lost_fee_line = $lost_fee_lines->next;
992             is( $lost_fee_line->amount + 0,
993                 $replacement_amount, 'The right LOST amount is generated' );
994             is( $lost_fee_line->amountoutstanding + 0,
995                 $replacement_amount,
996                 'The right LOST amountountstanding is generated' );
997
998             my $account = $patron->account;
999             my $debts   = $account->outstanding_debits;
1000
1001             # Pay off the debt
1002             my $credit = $account->add_credit(
1003                 {
1004                     amount    => $account->balance,
1005                     type      => 'PAYMENT',
1006                     interface => 'test',
1007                 }
1008             );
1009             $credit->apply( { debits => [ $debts->as_list ] } );
1010
1011             # Simulate item marked as found
1012             $item->itemlost(0)->store;
1013             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
1014             is( scalar ( grep { $_->message eq 'lost_restored' } @{$item->object_messages} ), 0, 'Restore not triggered when there is no overdue fine found' );
1015         };
1016
1017         subtest 'restore fine | unforgiven overdue' => sub {
1018
1019             plan tests => 10;
1020
1021             # Set lostreturn_policy to 'restore' for tests
1022             my $manager =
1023               $builder->build_object( { class => "Koha::Patrons" } );
1024             t::lib::Mocks::mock_userenv(
1025                 { patron => $manager, branchcode => $manager->branchcode } );
1026             my $specific_rule_restore = $builder->build(
1027                 {
1028                     source => 'CirculationRule',
1029                     value  => {
1030                         branchcode   => $manager->branchcode,
1031                         categorycode => undef,
1032                         itemtype     => undef,
1033                         rule_name    => 'lostreturn',
1034                         rule_value   => 'restore'
1035                     }
1036                 }
1037             );
1038
1039             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1040
1041             my $item = $builder->build_sample_item(
1042                 {
1043                     biblionumber     => $biblio->biblionumber,
1044                     library          => $library->branchcode,
1045                     replacementprice => $replacement_amount,
1046                     itype            => $item_type->itemtype
1047                 }
1048             );
1049
1050             my $issue =
1051               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
1052
1053             # Simulate item marked as lost
1054             $item->itemlost(1)->store;
1055             C4::Circulation::LostItem( $item->itemnumber, 1 );
1056
1057             my $processing_fee_lines = Koha::Account::Lines->search(
1058                 {
1059                     borrowernumber  => $patron->id,
1060                     itemnumber      => $item->itemnumber,
1061                     debit_type_code => 'PROCESSING'
1062                 }
1063             );
1064             is( $processing_fee_lines->count,
1065                 1, 'Only one processing fee produced' );
1066             my $processing_fee_line = $processing_fee_lines->next;
1067             is( $processing_fee_line->amount + 0,
1068                 $processfee_amount,
1069                 'The right PROCESSING amount is generated' );
1070             is( $processing_fee_line->amountoutstanding + 0,
1071                 $processfee_amount,
1072                 'The right PROCESSING amountoutstanding is generated' );
1073
1074             my $lost_fee_lines = Koha::Account::Lines->search(
1075                 {
1076                     borrowernumber  => $patron->id,
1077                     itemnumber      => $item->itemnumber,
1078                     debit_type_code => 'LOST'
1079                 }
1080             );
1081             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
1082             my $lost_fee_line = $lost_fee_lines->next;
1083             is( $lost_fee_line->amount + 0,
1084                 $replacement_amount, 'The right LOST amount is generated' );
1085             is( $lost_fee_line->amountoutstanding + 0,
1086                 $replacement_amount,
1087                 'The right LOST amountountstanding is generated' );
1088
1089             my $account = $patron->account;
1090             my $debts   = $account->outstanding_debits;
1091
1092             # Pay off the debt
1093             my $credit = $account->add_credit(
1094                 {
1095                     amount    => $account->balance,
1096                     type      => 'PAYMENT',
1097                     interface => 'test',
1098                 }
1099             );
1100             $credit->apply( { debits => [ $debts->as_list ] } );
1101
1102             # Fine not forgiven
1103             my $overdue = $account->add_debit(
1104                 {
1105                     amount     => 30.00,
1106                     user_id    => $manager->borrowernumber,
1107                     library_id => $library->branchcode,
1108                     interface  => 'test',
1109                     item_id    => $item->itemnumber,
1110                     type       => 'OVERDUE',
1111                 }
1112             )->store();
1113             $overdue->status('LOST')->store();
1114             $overdue->discard_changes;
1115             is( $overdue->status, 'LOST',
1116                 'Overdue status set to LOST' );
1117
1118             # Simulate item marked as found
1119             $item->itemlost(0)->store;
1120             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
1121             is( scalar ( grep { $_->message eq 'lost_restored' } @{$item->object_messages} ), 0, 'Restore not triggered when overdue was not forgiven' );
1122             $overdue->discard_changes;
1123             is( $overdue->status, 'FOUND',
1124                 'Overdue status updated to FOUND' );
1125         };
1126
1127         subtest 'restore fine | forgiven overdue' => sub {
1128
1129             plan tests => 12;
1130
1131             # Set lostreturn_policy to 'restore' for tests
1132             my $manager =
1133               $builder->build_object( { class => "Koha::Patrons" } );
1134             t::lib::Mocks::mock_userenv(
1135                 { patron => $manager, branchcode => $manager->branchcode } );
1136             my $specific_rule_restore = $builder->build(
1137                 {
1138                     source => 'CirculationRule',
1139                     value  => {
1140                         branchcode   => $manager->branchcode,
1141                         categorycode => undef,
1142                         itemtype     => undef,
1143                         rule_name    => 'lostreturn',
1144                         rule_value   => 'restore'
1145                     }
1146                 }
1147             );
1148
1149             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1150
1151             my $item = $builder->build_sample_item(
1152                 {
1153                     biblionumber     => $biblio->biblionumber,
1154                     library          => $library->branchcode,
1155                     replacementprice => $replacement_amount,
1156                     itype            => $item_type->itemtype
1157                 }
1158             );
1159
1160             my $issue =
1161               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
1162
1163             # Simulate item marked as lost
1164             $item->itemlost(1)->store;
1165             C4::Circulation::LostItem( $item->itemnumber, 1 );
1166
1167             my $processing_fee_lines = Koha::Account::Lines->search(
1168                 {
1169                     borrowernumber  => $patron->id,
1170                     itemnumber      => $item->itemnumber,
1171                     debit_type_code => 'PROCESSING'
1172                 }
1173             );
1174             is( $processing_fee_lines->count,
1175                 1, 'Only one processing fee produced' );
1176             my $processing_fee_line = $processing_fee_lines->next;
1177             is( $processing_fee_line->amount + 0,
1178                 $processfee_amount,
1179                 'The right PROCESSING amount is generated' );
1180             is( $processing_fee_line->amountoutstanding + 0,
1181                 $processfee_amount,
1182                 'The right PROCESSING amountoutstanding is generated' );
1183
1184             my $lost_fee_lines = Koha::Account::Lines->search(
1185                 {
1186                     borrowernumber  => $patron->id,
1187                     itemnumber      => $item->itemnumber,
1188                     debit_type_code => 'LOST'
1189                 }
1190             );
1191             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
1192             my $lost_fee_line = $lost_fee_lines->next;
1193             is( $lost_fee_line->amount + 0,
1194                 $replacement_amount, 'The right LOST amount is generated' );
1195             is( $lost_fee_line->amountoutstanding + 0,
1196                 $replacement_amount,
1197                 'The right LOST amountountstanding is generated' );
1198
1199             my $account = $patron->account;
1200             my $debts   = $account->outstanding_debits;
1201
1202             # Pay off the debt
1203             my $credit = $account->add_credit(
1204                 {
1205                     amount    => $account->balance,
1206                     type      => 'PAYMENT',
1207                     interface => 'test',
1208                 }
1209             );
1210             $credit->apply( { debits => [ $debts->as_list ] } );
1211
1212             # Add overdue
1213             my $overdue = $account->add_debit(
1214                 {
1215                     amount     => 30.00,
1216                     user_id    => $manager->borrowernumber,
1217                     library_id => $library->branchcode,
1218                     interface  => 'test',
1219                     item_id    => $item->itemnumber,
1220                     type       => 'OVERDUE',
1221                 }
1222             )->store();
1223             $overdue->status('LOST')->store();
1224             is( $overdue->status, 'LOST',
1225                 'Overdue status set to LOST' );
1226
1227             t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 0 );
1228
1229             # Forgive fine
1230             $credit = $account->add_credit(
1231                 {
1232                     amount     => 30.00,
1233                     user_id    => $manager->borrowernumber,
1234                     library_id => $library->branchcode,
1235                     interface  => 'test',
1236                     type       => 'FORGIVEN',
1237                     item_id    => $item->itemnumber
1238                 }
1239             );
1240             $credit->apply( { debits => [$overdue] } );
1241
1242             # Simulate item marked as found
1243             $item->itemlost(0)->store;
1244             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
1245             is( scalar ( grep { $_->message eq 'lost_restored' } @{$item->object_messages} ), 1, 'Restore triggered when overdue was forgiven' );
1246             $overdue->discard_changes;
1247             is( $overdue->status, 'FOUND', 'Overdue status updated to FOUND' );
1248             is( $overdue->amountoutstanding, $overdue->amount, 'Overdue outstanding has been restored' );
1249             $credit->discard_changes;
1250             is( $credit->status, 'VOID', 'Overdue Forgival has been marked as VOID');
1251         };
1252
1253         subtest 'Continue when userenv is not set' => sub {
1254             plan tests => 1;
1255
1256             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1257             my $barcode            = 'KD123456795';
1258             my $replacement_amount = 100;
1259             my $processfee_amount  = 20;
1260
1261             my $item_type = $builder->build_object(
1262                 {
1263                     class => 'Koha::ItemTypes',
1264                     value => {
1265                         notforloan         => undef,
1266                         rentalcharge       => 0,
1267                         defaultreplacecost => undef,
1268                         processfee         => 0,
1269                         rentalcharge_daily => 0,
1270                     }
1271                 }
1272             );
1273             my $item = $builder->build_sample_item(
1274                 {
1275                     biblionumber     => $biblio->biblionumber,
1276                     homebranch       => $library->branchcode,
1277                     holdingbranch    => $library->branchcode,
1278                     barcode          => $barcode,
1279                     replacementprice => $replacement_amount,
1280                     itype            => $item_type->itemtype
1281                 }
1282             );
1283
1284             my $issue =
1285               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
1286
1287             # Simulate item marked as lost
1288             $item->itemlost(1)->store;
1289             C4::Circulation::LostItem( $item->itemnumber, 1 );
1290
1291             # Unset the userenv
1292             C4::Context->_new_userenv(undef);
1293
1294             # Simluate item marked as found
1295             $item->itemlost(0)->store;
1296             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
1297
1298         };
1299     };
1300
1301     subtest 'log_action' => sub {
1302         plan tests => 2;
1303         t::lib::Mocks::mock_preference( 'CataloguingLog', 1 );
1304
1305         my $item = Koha::Item->new(
1306             {
1307                 homebranch    => $library->{branchcode},
1308                 holdingbranch => $library->{branchcode},
1309                 biblionumber  => $biblio->biblionumber,
1310                 location      => 'my_loc',
1311             }
1312         )->store;
1313         is(
1314             Koha::ActionLogs->search(
1315                 {
1316                     module => 'CATALOGUING',
1317                     action => 'ADD',
1318                     object => $item->itemnumber,
1319                     info   => 'item'
1320                 }
1321             )->count,
1322             1,
1323             "Item creation logged"
1324         );
1325
1326         $item->location('another_loc')->store;
1327         is(
1328             Koha::ActionLogs->search(
1329                 {
1330                     module => 'CATALOGUING',
1331                     action => 'MODIFY',
1332                     object => $item->itemnumber
1333                 }
1334             )->count,
1335             1,
1336             "Item modification logged"
1337         );
1338     };
1339 };
1340
1341 subtest 'get_transfer' => sub {
1342     plan tests => 7;
1343
1344     my $transfer = $new_item_1->get_transfer();
1345     is( $transfer, undef, 'Koha::Item->get_transfer should return undef if the item is not in transit' );
1346
1347     my $library_to = $builder->build( { source => 'Branch' } );
1348
1349     my $transfer_1 = $builder->build_object(
1350         {
1351             class => 'Koha::Item::Transfers',
1352             value => {
1353                 itemnumber    => $new_item_1->itemnumber,
1354                 frombranch    => $new_item_1->holdingbranch,
1355                 tobranch      => $library_to->{branchcode},
1356                 reason        => 'Manual',
1357                 datesent      => undef,
1358                 datearrived   => undef,
1359                 datecancelled => undef,
1360                 daterequested => \'NOW()'
1361             }
1362         }
1363     );
1364
1365     $transfer = $new_item_1->get_transfer();
1366     is( ref($transfer), 'Koha::Item::Transfer', 'Koha::Item->get_transfer should return a Koha::Item::Transfers object' );
1367
1368     my $transfer_2 = $builder->build_object(
1369         {
1370             class => 'Koha::Item::Transfers',
1371             value => {
1372                 itemnumber    => $new_item_1->itemnumber,
1373                 frombranch    => $new_item_1->holdingbranch,
1374                 tobranch      => $library_to->{branchcode},
1375                 reason        => 'Manual',
1376                 datesent      => undef,
1377                 datearrived   => undef,
1378                 datecancelled => undef,
1379                 daterequested => \'NOW()'
1380             }
1381         }
1382     );
1383
1384     $transfer = $new_item_1->get_transfer();
1385     is( $transfer->branchtransfer_id, $transfer_1->branchtransfer_id, 'Koha::Item->get_transfer returns the oldest transfer request');
1386
1387     $transfer_2->datesent(\'NOW()')->store;
1388     $transfer = $new_item_1->get_transfer();
1389     is( $transfer->branchtransfer_id, $transfer_2->branchtransfer_id, 'Koha::Item->get_transfer returns the in_transit transfer');
1390
1391     my $transfer_3 = $builder->build_object(
1392         {
1393             class => 'Koha::Item::Transfers',
1394             value => {
1395                 itemnumber    => $new_item_1->itemnumber,
1396                 frombranch    => $new_item_1->holdingbranch,
1397                 tobranch      => $library_to->{branchcode},
1398                 reason        => 'Manual',
1399                 datesent      => undef,
1400                 datearrived   => undef,
1401                 datecancelled => undef,
1402                 daterequested => \'NOW()'
1403             }
1404         }
1405     );
1406
1407     $transfer_2->datearrived(\'NOW()')->store;
1408     $transfer = $new_item_1->get_transfer();
1409     is( $transfer->branchtransfer_id, $transfer_1->branchtransfer_id, 'Koha::Item->get_transfer returns the next queued transfer');
1410     is( $transfer->itemnumber, $new_item_1->itemnumber, 'Koha::Item->get_transfer returns the right items transfer' );
1411
1412     $transfer_1->datecancelled(\'NOW()')->store;
1413     $transfer = $new_item_1->get_transfer();
1414     is( $transfer->branchtransfer_id, $transfer_3->branchtransfer_id, 'Koha::Item->get_transfer ignores cancelled transfers');
1415 };
1416
1417 subtest 'holds' => sub {
1418     plan tests => 5;
1419
1420     my $biblio = $builder->build_sample_biblio();
1421     my $item   = $builder->build_sample_item({
1422         biblionumber => $biblio->biblionumber,
1423     });
1424     is($item->holds->count, 0, "Nothing returned if no holds");
1425     my $hold1 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'T' }});
1426     my $hold2 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'W' }});
1427     my $hold3 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'W' }});
1428
1429     is($item->holds()->count,3,"Three holds found");
1430     is($item->holds({found => 'W'})->count,2,"Two waiting holds found");
1431     is_deeply($item->holds({found => 'T'})->next->unblessed,$hold1,"Found transit holds matches the hold");
1432     is($item->holds({found => undef})->count, 0,"Nothing returned if no matching holds");
1433 };
1434
1435 subtest 'biblio' => sub {
1436     plan tests => 2;
1437
1438     my $biblio = $retrieved_item_1->biblio;
1439     is( ref( $biblio ), 'Koha::Biblio', 'Koha::Item->biblio should return a Koha::Biblio' );
1440     is( $biblio->biblionumber, $retrieved_item_1->biblionumber, 'Koha::Item->biblio should return the correct biblio' );
1441 };
1442
1443 subtest 'biblioitem' => sub {
1444     plan tests => 2;
1445
1446     my $biblioitem = $retrieved_item_1->biblioitem;
1447     is( ref( $biblioitem ), 'Koha::Biblioitem', 'Koha::Item->biblioitem should return a Koha::Biblioitem' );
1448     is( $biblioitem->biblionumber, $retrieved_item_1->biblionumber, 'Koha::Item->biblioitem should return the correct biblioitem' );
1449 };
1450
1451 # Restore userenv
1452 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
1453 subtest 'checkout' => sub {
1454     plan tests => 5;
1455     my $item = Koha::Items->find( $new_item_1->itemnumber );
1456     # No checkout yet
1457     my $checkout = $item->checkout;
1458     is( $checkout, undef, 'Koha::Item->checkout should return undef if there is no current checkout on this item' );
1459
1460     # Add a checkout
1461     my $patron = $builder->build({ source => 'Borrower' });
1462     C4::Circulation::AddIssue( $patron, $item->barcode );
1463     $checkout = $retrieved_item_1->checkout;
1464     is( ref( $checkout ), 'Koha::Checkout', 'Koha::Item->checkout should return a Koha::Checkout' );
1465     is( $checkout->itemnumber, $item->itemnumber, 'Koha::Item->checkout should return the correct checkout' );
1466     is( $checkout->borrowernumber, $patron->{borrowernumber}, 'Koha::Item->checkout should return the correct checkout' );
1467
1468     # Do the return
1469     C4::Circulation::AddReturn( $item->barcode );
1470
1471     # There is no more checkout on this item, making sure it will not return old checkouts
1472     $checkout = $item->checkout;
1473     is( $checkout, undef, 'Koha::Item->checkout should return undef if there is no *current* checkout on this item' );
1474 };
1475
1476 subtest 'can_be_transferred' => sub {
1477     plan tests => 5;
1478
1479     t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
1480     t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
1481
1482     my $biblio   = $builder->build_sample_biblio();
1483     my $library1 = $builder->build_object( { class => 'Koha::Libraries' } );
1484     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
1485     my $item  = $builder->build_sample_item({
1486         biblionumber     => $biblio->biblionumber,
1487         homebranch       => $library1->branchcode,
1488         holdingbranch    => $library1->branchcode,
1489     });
1490
1491     is(Koha::Item::Transfer::Limits->search({
1492         fromBranch => $library1->branchcode,
1493         toBranch => $library2->branchcode,
1494     })->count, 0, 'There are no transfer limits between libraries.');
1495     ok($item->can_be_transferred({ to => $library2 }),
1496        'Item can be transferred between libraries.');
1497
1498     my $limit = Koha::Item::Transfer::Limit->new({
1499         fromBranch => $library1->branchcode,
1500         toBranch => $library2->branchcode,
1501         itemtype => $item->effective_itemtype,
1502     })->store;
1503     is(Koha::Item::Transfer::Limits->search({
1504         fromBranch => $library1->branchcode,
1505         toBranch => $library2->branchcode,
1506     })->count, 1, 'Given we have added a transfer limit,');
1507     is($item->can_be_transferred({ to => $library2 }), 0,
1508        'Item can no longer be transferred between libraries.');
1509     is($item->can_be_transferred({ to => $library2, from => $library1 }), 0,
1510        'We get the same result also if we pass the from-library parameter.');
1511 };
1512
1513 # Reset nb_of_items prior to testing delete
1514 $nb_of_items = Koha::Items->search->count;
1515
1516 # Test delete
1517 $retrieved_item_1->delete;
1518 is( Koha::Items->search->count, $nb_of_items - 1, 'Delete should have deleted the item' );
1519
1520 $schema->storage->txn_rollback;
1521
1522 subtest 'filter_by_visible_in_opac() tests' => sub {
1523
1524     plan tests => 14;
1525
1526     $schema->storage->txn_begin;
1527
1528     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1529     my $mocked_category = Test::MockModule->new('Koha::Patron::Category');
1530     my $exception = 1;
1531     $mocked_category->mock( 'override_hidden_items', sub {
1532         return $exception;
1533     });
1534
1535     # have a fresh biblio
1536     my $biblio = $builder->build_sample_biblio;
1537     # have two itemtypes
1538     my $itype_1 = $builder->build_object({ class => 'Koha::ItemTypes' });
1539     my $itype_2 = $builder->build_object({ class => 'Koha::ItemTypes' });
1540     # have 5 items on that biblio
1541     my $item_1 = $builder->build_sample_item(
1542         {
1543             biblionumber => $biblio->biblionumber,
1544             itemlost     => -1,
1545             itype        => $itype_1->itemtype,
1546             withdrawn    => 1,
1547             copynumber   => undef
1548         }
1549     );
1550     my $item_2 = $builder->build_sample_item(
1551         {
1552             biblionumber => $biblio->biblionumber,
1553             itemlost     => 0,
1554             itype        => $itype_2->itemtype,
1555             withdrawn    => 2,
1556             copynumber   => undef
1557         }
1558     );
1559     my $item_3 = $builder->build_sample_item(
1560         {
1561             biblionumber => $biblio->biblionumber,
1562             itemlost     => 1,
1563             itype        => $itype_1->itemtype,
1564             withdrawn    => 3,
1565             copynumber   => undef
1566         }
1567     );
1568     my $item_4 = $builder->build_sample_item(
1569         {
1570             biblionumber => $biblio->biblionumber,
1571             itemlost     => 0,
1572             itype        => $itype_2->itemtype,
1573             withdrawn    => 4,
1574             copynumber   => undef
1575         }
1576     );
1577     my $item_5 = $builder->build_sample_item(
1578         {
1579             biblionumber => $biblio->biblionumber,
1580             itemlost     => 0,
1581             itype        => $itype_1->itemtype,
1582             withdrawn    => 5,
1583             copynumber   => undef
1584         }
1585     );
1586     my $item_6 = $builder->build_sample_item(
1587         {
1588             biblionumber => $biblio->biblionumber,
1589             itemlost     => 2,
1590             itype        => $itype_1->itemtype,
1591             withdrawn    => 5,
1592             copynumber   => undef
1593         }
1594     );
1595
1596     my $rules = undef;
1597
1598     my $mocked_context = Test::MockModule->new('C4::Context');
1599     $mocked_context->mock( 'yaml_preference', sub {
1600         return $rules;
1601     });
1602
1603     t::lib::Mocks::mock_preference( 'hidelostitems', 0 );
1604     is( $biblio->items->filter_by_visible_in_opac->count,
1605         6, 'No rules passed, hidelostitems unset' );
1606
1607     is( $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1608         6, 'No rules passed, hidelostitems unset, patron exception changes nothing' );
1609
1610     $rules = { copynumber => [ 2 ] };
1611
1612     t::lib::Mocks::mock_preference( 'hidelostitems', 1 );
1613     is(
1614         $biblio->items->filter_by_visible_in_opac->count,
1615         3,
1616         'No rules passed, hidelostitems set'
1617     );
1618
1619     is(
1620         $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1621         3,
1622         'No rules passed, hidelostitems set, patron exception changes nothing'
1623     );
1624
1625     $rules = { biblionumber => [ $biblio->biblionumber ] };
1626     is(
1627         $biblio->items->filter_by_visible_in_opac->count,
1628         0,
1629         'Biblionumber rule successfully hides all items'
1630     );
1631
1632     my $biblio2 = $builder->build_sample_biblio;
1633     $rules = { biblionumber => [ $biblio2->biblionumber ] };
1634     my $prefetched = $biblio->items->search({},{ prefetch => ['branchtransfers','reserves'] })->filter_by_visible_in_opac;
1635     ok( $prefetched->next, "Can retrieve object when prefetching and hiding on a duplicated column");
1636
1637     $rules = { withdrawn => [ 1, 2 ], copynumber => [ 2 ] };
1638     is(
1639         $biblio->items->filter_by_visible_in_opac->count,
1640         2,
1641         'Rules on withdrawn, hidelostitems set'
1642     );
1643
1644     is(
1645         $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1646         3,
1647         'hidelostitems set, rules on withdrawn but patron override passed'
1648     );
1649
1650     $rules = { itype => [ $itype_1->itemtype ], copynumber => [ 2 ] };
1651     is(
1652         $biblio->items->filter_by_visible_in_opac->count,
1653         2,
1654         'Rules on itype, hidelostitems set'
1655     );
1656
1657     $rules = { withdrawn => [ 1, 2 ], itype => [ $itype_1->itemtype ], copynumber => [ 2 ] };
1658     is(
1659         $biblio->items->filter_by_visible_in_opac->count,
1660         1,
1661         'Rules on itype and withdrawn, hidelostitems set'
1662     );
1663     is(
1664         $biblio->items->filter_by_visible_in_opac
1665           ->next->itemnumber,
1666         $item_4->itemnumber,
1667         'The right item is returned'
1668     );
1669
1670     $rules = { withdrawn => [ 1, 2 ], itype => [ $itype_2->itemtype ], copynumber => [ 2 ] };
1671     is(
1672         $biblio->items->filter_by_visible_in_opac->count,
1673         1,
1674         'Rules on itype and withdrawn, hidelostitems set'
1675     );
1676     is(
1677         $biblio->items->filter_by_visible_in_opac
1678           ->next->itemnumber,
1679         $item_5->itemnumber,
1680         'The right item is returned'
1681     );
1682
1683     # Make sure the warning on the about page will work
1684     $rules = { itemlost => ['AB'] };
1685     my $c = Koha::Items->filter_by_visible_in_opac->count;
1686     my @warnings = C4::Context->dbh->selectrow_array('SHOW WARNINGS');
1687     like( $warnings[2], qr/Truncated incorrect (DOUBLE|DECIMAL) value: 'AB'/);
1688
1689     $schema->storage->txn_rollback;
1690 };
1691
1692 subtest 'filter_out_lost() tests' => sub {
1693
1694     plan tests => 2;
1695
1696     $schema->storage->txn_begin;
1697
1698     # have a fresh biblio
1699     my $biblio = $builder->build_sample_biblio;
1700     # have 3 items on that biblio
1701     my $item_1 = $builder->build_sample_item(
1702         {
1703             biblionumber => $biblio->biblionumber,
1704             itemlost     => -1,
1705         }
1706     );
1707     my $item_2 = $builder->build_sample_item(
1708         {
1709             biblionumber => $biblio->biblionumber,
1710             itemlost     => 0,
1711         }
1712     );
1713     my $item_3 = $builder->build_sample_item(
1714         {
1715             biblionumber => $biblio->biblionumber,
1716             itemlost     => 1,
1717         }
1718     );
1719
1720     is( $biblio->items->filter_out_lost->next->itemnumber, $item_2->itemnumber, 'Right item returned' );
1721     is( $biblio->items->filter_out_lost->count, 1, 'Only one item is not lost' );
1722
1723     $schema->storage->txn_rollback;
1724 };
1725
1726 subtest 'move_to_biblio() tests' => sub {
1727
1728     plan tests => 2;
1729
1730     $schema->storage->txn_begin;
1731
1732     my $biblio1 = $builder->build_sample_biblio;
1733     my $biblio2 = $builder->build_sample_biblio;
1734     my $item1 = $builder->build_sample_item({ biblionumber => $biblio1->biblionumber });
1735     my $item2 = $builder->build_sample_item({ biblionumber => $biblio1->biblionumber });
1736
1737     $biblio1->items->move_to_biblio($biblio2);
1738
1739     $item1->discard_changes;
1740     $item2->discard_changes;
1741
1742     is($item1->biblionumber, $biblio2->biblionumber, "Item 1 moved");
1743     is($item2->biblionumber, $biblio2->biblionumber, "Item 2 moved");
1744
1745     $schema->storage->txn_rollback;
1746
1747 };
1748
1749 subtest 'search_ordered' => sub {
1750
1751     plan tests => 6;
1752
1753     $schema->storage->txn_begin;
1754
1755     my $library_a = $builder->build_object(
1756         { class => 'Koha::Libraries', value => { branchname => 'TEST_A' } } );
1757     my $library_z = $builder->build_object(
1758         { class => 'Koha::Libraries', value => { branchname => 'TEST_Z' } } );
1759     my $biblio = $builder->build_sample_biblio( { serial => 0 } );
1760     my $item1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1761     my $item2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1762     my $item3 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1763
1764     { # Is not a serial
1765
1766         # order_by homebranch.branchname
1767         $item1->discard_changes->update( { homebranch => $library_z->branchcode } );
1768         $item2->discard_changes->update( { homebranch => $library_a->branchcode } );
1769         $item3->discard_changes->update( { homebranch => $library_z->branchcode } );
1770         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1771             [ $item2->itemnumber, $item1->itemnumber, $item3->itemnumber ],
1772             "not a serial - order by homebranch" );
1773
1774         # order_by me.enumchron
1775         $biblio->items->update( { homebranch => $library_a->branchcode } );
1776         $item1->discard_changes->update( { enumchron => 'cc' } );
1777         $item2->discard_changes->update( { enumchron => 'bb' } );
1778         $item3->discard_changes->update( { enumchron => 'aa' } );
1779         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1780             [ $item3->itemnumber, $item2->itemnumber, $item1->itemnumber ],
1781             "not a serial - order by enumchron" );
1782
1783         # order_by LPAD( me.copynumber, 8, '0' )
1784         $biblio->items->update( { enumchron => undef } );
1785         $item1->discard_changes->update( { copynumber => '12345678' } );
1786         $item2->discard_changes->update( { copynumber => '34567890' } );
1787         $item3->discard_changes->update( { copynumber => '23456789' } );
1788         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1789             [ $item1->itemnumber, $item3->itemnumber, $item2->itemnumber ],
1790             "not a serial - order by LPAD( me.copynumber, 8, '0' )" );
1791
1792         # order_by -desc => 'me.dateaccessioned'
1793         $biblio->items->update( { copynumber => undef } );
1794         $item1->discard_changes->update( { dateaccessioned => '2022-08-19' } );
1795         $item2->discard_changes->update( { dateaccessioned => '2022-07-19' } );
1796         $item3->discard_changes->update( { dateaccessioned => '2022-09-19' } );
1797         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1798             [ $item3->itemnumber, $item1->itemnumber, $item2->itemnumber ],
1799             "not a serial - order by date accessioned desc" );
1800     }
1801
1802     {    # Is a serial
1803
1804         my $sub_freq = $builder->build( { source => 'SubscriptionFrequency' } );
1805         my $sub_np =
1806           $builder->build( { source => 'SubscriptionNumberpattern' } );
1807         my $subscription = $builder->build_object(
1808             {
1809                 class => 'Koha::Subscriptions',
1810                 value => {
1811                     biblionumber  => $biblio->biblionumber,
1812                     periodicity   => $sub_freq->{id},
1813                     numberpattern => $sub_np->{id}
1814                 }
1815             }
1816         );
1817         $builder->build_object(
1818             {
1819                 class => 'Koha::Subscription::Histories',
1820                 value => {
1821                     subscriptionid => $subscription->subscriptionid,
1822                     biblionumber   => $biblio->biblionumber
1823                 }
1824             }
1825         );
1826
1827         $biblio->update( { serial => 1 } );
1828         my $serialid1 =
1829           C4::Serials::NewIssue( "serialseq", $subscription->subscriptionid,
1830             $biblio->biblionumber, 1, undef, undef, "publisheddatetext",
1831             "notes", "routingnotes" );
1832         C4::Serials::AddItem2Serial( $serialid1, $item1->itemnumber );
1833         my $serialid2 =
1834           C4::Serials::NewIssue( "serialseq", $subscription->subscriptionid,
1835             $biblio->biblionumber, 1, undef, undef, "publisheddatetext",
1836             "notes", "routingnotes" );
1837         C4::Serials::AddItem2Serial( $serialid2, $item2->itemnumber );
1838         my $serialid3 =
1839           C4::Serials::NewIssue( "serialseq", $subscription->subscriptionid,
1840             $biblio->biblionumber, 1, undef, undef, "publisheddatetext",
1841             "notes", "routingnotes" );
1842         C4::Serials::AddItem2Serial( $serialid3, $item3->itemnumber );
1843         my $serial1 = Koha::Serials->find($serialid1);
1844         my $serial2 = Koha::Serials->find($serialid2);
1845         my $serial3 = Koha::Serials->find($serialid3);
1846
1847         # order_by serial.publisheddate
1848         $serial1->discard_changes->update( { publisheddate => '2022-09-19' } );
1849         $serial2->discard_changes->update( { publisheddate => '2022-07-19' } );
1850         $serial3->discard_changes->update( { publisheddate => '2022-08-19' } );
1851         is_deeply(
1852             [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1853             [ $item2->itemnumber, $item3->itemnumber, $item1->itemnumber ],
1854             "serial - order by publisheddate"
1855         );
1856
1857         # order_by me.enumchron
1858         $serial1->discard_changes->update({ publisheddate => '2022-08-19' });
1859         $serial2->discard_changes->update({ publisheddate => '2022-08-19' });
1860         $serial3->discard_changes->update({ publisheddate => '2022-08-19' });
1861         $item1->discard_changes->update( { enumchron => 'cc' } );
1862         $item2->discard_changes->update( { enumchron => 'bb' } );
1863         $item3->discard_changes->update( { enumchron => 'aa' } );
1864         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1865             [ $item3->itemnumber, $item2->itemnumber, $item1->itemnumber ],
1866             "serial - order by enumchron" );
1867
1868     }
1869
1870     $schema->storage->txn_rollback;
1871
1872 };
1873
1874 subtest 'filter_by_for_hold' => sub {
1875
1876     plan tests => 13;
1877
1878     $schema->storage->txn_begin;
1879
1880     my $biblio  = $builder->build_sample_biblio;
1881     my $library = $builder->build_object({ class => 'Koha::Libraries' });
1882
1883     t::lib::Mocks::mock_preference('IndependentBranches', 0); # more robust tests
1884
1885     is( $biblio->items->filter_by_for_hold->count, 0, 'no item yet' );
1886     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, notforloan => 1 } );
1887     is( $biblio->items->filter_by_for_hold->count, 0, 'no item for hold' );
1888     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, notforloan => 0 } );
1889     is( $biblio->items->filter_by_for_hold->count, 1, '1 item for hold' );
1890     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, notforloan => -1 } );
1891     is( $biblio->items->filter_by_for_hold->count, 2, '2 items for hold' );
1892
1893     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, itemlost => 0, library => $library->id } );
1894     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, itemlost => 1, library => $library->id } );
1895     is( $biblio->items->filter_by_for_hold->count, 3, '3 items for hold - itemlost' );
1896
1897     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, withdrawn => 0, library => $library->id } );
1898     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, withdrawn => 1, library => $library->id } );
1899     is( $biblio->items->filter_by_for_hold->count, 4, '4 items for hold - withdrawn' );
1900
1901     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, damaged => 0 } );
1902     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, damaged => 1 } );
1903     t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 0);
1904     is( $biblio->items->filter_by_for_hold->count, 5, '5 items for hold - not damaged if not AllowHoldsOnDamagedItems' );
1905     t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 1);
1906     is( $biblio->items->filter_by_for_hold->count, 6, '6 items for hold - damaged if AllowHoldsOnDamagedItems' );
1907
1908     my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes' });
1909     my $not_holdable_itemtype = $itemtype->itemtype;
1910     $builder->build_sample_item(
1911         {
1912             biblionumber => $biblio->biblionumber,
1913             itype        => $not_holdable_itemtype,
1914         }
1915     );
1916     Koha::CirculationRules->set_rule(
1917         {
1918             branchcode   => undef,
1919             itemtype     => $not_holdable_itemtype,
1920             rule_name    => 'holdallowed',
1921             rule_value   => 'not_allowed',
1922         }
1923     );
1924     is( $biblio->items->filter_by_for_hold->count, 6, '6 items for hold - holdallowed=not_allowed' );
1925
1926     # Remove rule, test notforloan on itemtype
1927     Koha::CirculationRules->set_rule(
1928         {
1929             branchcode   => undef,
1930             itemtype     => $not_holdable_itemtype,
1931             rule_name    => 'holdallowed',
1932             rule_value   => undef,
1933         }
1934     );
1935     is( $biblio->items->filter_by_for_hold->count, 7, '7 items for hold - rule deleted' );
1936     $itemtype->notforloan(1)->store;
1937     is( $biblio->items->filter_by_for_hold->count, 6, '6 items for hold - notforloan' );
1938
1939     {
1940         my $mock_context = Test::MockModule->new('C4::Context');
1941         $mock_context->mock( 'only_my_library', 1 );
1942         $mock_context->mock( 'mybranch',        $library->id );
1943         is( $biblio->items->filter_by_for_hold->count, 2, '2 items for hold, filtered by IndependentBranches' );
1944     }
1945
1946     t::lib::Mocks::mock_preference('item-level_itypes', 0);
1947     $biblio->biblioitem->itemtype($not_holdable_itemtype)->store;
1948     is( $biblio->items->filter_by_for_hold->count, 0, '0 item-level_itypes=0' );
1949
1950     t::lib::Mocks::mock_preference('item-level_itypes', 1);
1951
1952     $schema->storage->txn_rollback;
1953 };