Bug 34932: Patron.t - Pass borrowernumber of manager to userenv
[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( dt_from_string($item->datelastseen)->ymd, $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 => 16;
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             my $processing_return = Koha::Account::Lines->search(
459                 {
460                     itemnumber       => $item->itemnumber,
461                     credit_type_code => 'PROCESSING_FOUND'
462                 },
463                 { rows => 1 }
464             )->single;
465             ok( $processing_return, 'An account line of type PROCESSING_FOUND is added' );
466             is( $processing_return->amount + 0,
467                 -20.00,
468                 'The account line of type PROCESSING_FOUND has an amount of -20' );
469
470             $lost_fee_line->discard_changes;
471             is( $lost_fee_line->amountoutstanding + 0,
472                 0, 'Lost fee has no outstanding amount' );
473             is( $lost_fee_line->debit_type_code,
474                 'LOST', 'Lost fee now still has account type of LOST' );
475             is( $lost_fee_line->status, 'FOUND',
476                 "Lost fee now has account status of FOUND" );
477
478             is( $patron->account->balance, -119,
479 'The patron balance is -119, a credit that equals the lost fee payment and the processing fee'
480             );
481         };
482
483         subtest 'Test without payment or write off' => sub {
484
485             plan tests => 14;
486
487             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
488
489             my $item = $builder->build_sample_item(
490                 {
491                     biblionumber     => $biblio->biblionumber,
492                     library          => $library->branchcode,
493                     replacementprice => 23.00,
494                     replacementprice => $replacement_amount,
495                     itype            => $item_type->itemtype
496                 }
497             );
498
499             my $issue =
500               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
501
502             # Simulate item marked as lost
503             $item->itemlost(3)->store;
504             C4::Circulation::LostItem( $item->itemnumber, 1 );
505
506             my $processing_fee_lines = Koha::Account::Lines->search(
507                 {
508                     borrowernumber  => $patron->id,
509                     itemnumber      => $item->itemnumber,
510                     debit_type_code => 'PROCESSING'
511                 }
512             );
513             is( $processing_fee_lines->count,
514                 1, 'Only one processing fee produced' );
515             my $processing_fee_line = $processing_fee_lines->next;
516             is( $processing_fee_line->amount + 0,
517                 $processfee_amount,
518                 'The right PROCESSING amount is generated' );
519             is( $processing_fee_line->amountoutstanding + 0,
520                 $processfee_amount,
521                 'The right PROCESSING amountoutstanding is generated' );
522
523             my $lost_fee_lines = Koha::Account::Lines->search(
524                 {
525                     borrowernumber  => $patron->id,
526                     itemnumber      => $item->itemnumber,
527                     debit_type_code => 'LOST'
528                 }
529             );
530             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
531             my $lost_fee_line = $lost_fee_lines->next;
532             is( $lost_fee_line->amount + 0,
533                 $replacement_amount, 'The right LOST amount is generated' );
534             is( $lost_fee_line->amountoutstanding + 0,
535                 $replacement_amount,
536                 'The right LOST amountountstanding is generated' );
537
538             # Set processingreturn_policy to '0' so processing fee is retained
539             # these tests are just for lostreturn
540             my $processingreturn_rule = $builder->build(
541                 {
542                     source => 'CirculationRule',
543                     value  => {
544                         branchcode   => undef,
545                         categorycode => undef,
546                         itemtype     => undef,
547                         rule_name    => 'processingreturn',
548                         rule_value   => '0'
549                     }
550                 }
551             );
552
553             # Simulate item marked as found
554             $item->itemlost(0)->store;
555             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
556
557             my $credit_return = Koha::Account::Lines->search(
558                 {
559                     itemnumber       => $item->itemnumber,
560                     credit_type_code => 'LOST_FOUND'
561                 },
562                 { rows => 1 }
563             )->single;
564
565             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
566             is( $credit_return->amount + 0,
567                 -99.00,
568                 'The account line of type LOST_FOUND has an amount of -99' );
569             is(
570                 $credit_return->amountoutstanding + 0,
571                 0,
572 'The account line of type LOST_FOUND has an amountoutstanding of 0'
573             );
574
575             $lost_fee_line->discard_changes;
576             is( $lost_fee_line->amountoutstanding + 0,
577                 0, 'Lost fee has no outstanding amount' );
578             is( $lost_fee_line->debit_type_code,
579                 'LOST', 'Lost fee now still has account type of LOST' );
580             is( $lost_fee_line->status, 'FOUND',
581                 "Lost fee now has account status of FOUND" );
582
583             is( $patron->account->balance,
584                 20, 'The patron balance is 20, still owes the processing fee' );
585         };
586
587         subtest
588           'Test with partial payment and write off, and remaining debt' =>
589           sub {
590
591             plan tests => 19;
592
593             t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 0 );
594
595             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
596             my $item = $builder->build_sample_item(
597                 {
598                     biblionumber     => $biblio->biblionumber,
599                     library          => $library->branchcode,
600                     replacementprice => $replacement_amount,
601                     itype            => $item_type->itemtype
602                 }
603             );
604
605             my $issue =
606               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
607
608             # Simulate item marked as lost
609             $item->itemlost(1)->store;
610             C4::Circulation::LostItem( $item->itemnumber, 1 );
611
612             my $processing_fee_lines = Koha::Account::Lines->search(
613                 {
614                     borrowernumber  => $patron->id,
615                     itemnumber      => $item->itemnumber,
616                     debit_type_code => 'PROCESSING'
617                 }
618             );
619             is( $processing_fee_lines->count,
620                 1, 'Only one processing fee produced' );
621             my $processing_fee_line = $processing_fee_lines->next;
622             is( $processing_fee_line->amount + 0,
623                 $processfee_amount,
624                 'The right PROCESSING amount is generated' );
625             is( $processing_fee_line->amountoutstanding + 0,
626                 $processfee_amount,
627                 'The right PROCESSING amountoutstanding is generated' );
628
629             my $lost_fee_lines = Koha::Account::Lines->search(
630                 {
631                     borrowernumber  => $patron->id,
632                     itemnumber      => $item->itemnumber,
633                     debit_type_code => 'LOST'
634                 }
635             );
636             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
637             my $lost_fee_line = $lost_fee_lines->next;
638             is( $lost_fee_line->amount + 0,
639                 $replacement_amount, 'The right LOST amount is generated' );
640             is( $lost_fee_line->amountoutstanding + 0,
641                 $replacement_amount,
642                 'The right LOST amountountstanding is generated' );
643
644             my $account = $patron->account;
645             is(
646                 $account->balance,
647                 $processfee_amount + $replacement_amount,
648                 'Balance is PROCESSING + LOST'
649             );
650
651             # Partially pay fee (99 - 27 = 72)
652             my $payment_amount = 24;
653             my $payment        = $account->add_credit(
654                 {
655                     amount    => $payment_amount,
656                     type      => 'PAYMENT',
657                     interface => 'test',
658                 }
659             );
660
661             $payment->apply( { debits => [$lost_fee_line] } );
662
663             # Partially write off fee (72 - 20 = 52)
664             my $write_off_amount = 20;
665             my $write_off        = $account->add_credit(
666                 {
667                     amount    => $write_off_amount,
668                     type      => 'WRITEOFF',
669                     interface => 'test',
670                 }
671             );
672             $write_off->apply( { debits => [$lost_fee_line] } );
673
674
675             my $payment_amount_2 = 3;
676             my $payment_2        = $account->add_credit(
677                 {
678                     amount    => $payment_amount_2,
679                     type      => 'PAYMENT',
680                     interface => 'test',
681                 }
682             );
683
684             $payment_2->apply(
685                 { debits => [$lost_fee_line] } );
686
687             # Partially write off fee (52 - 5 = 47)
688             my $write_off_amount_2 = 5;
689             my $write_off_2        = $account->add_credit(
690                 {
691                     amount    => $write_off_amount_2,
692                     type      => 'WRITEOFF',
693                     interface => 'test',
694                 }
695             );
696
697             $write_off_2->apply(
698                 { debits => [$lost_fee_line] } );
699
700             is(
701                 $account->balance,
702                 $processfee_amount +
703                   $replacement_amount -
704                   $payment_amount -
705                   $write_off_amount -
706                   $payment_amount_2 -
707                   $write_off_amount_2,
708                 'Balance is PROCESSING + LOST - PAYMENT 1 - WRITEOFF - PAYMENT 2 - WRITEOFF 2'
709             );
710
711             # VOID payment_2 and writeoff_2
712             $payment_2->void({ interface => 'test' });
713             $write_off_2->void({ interface => 'test' });
714
715             is(
716                 $account->balance,
717                 $processfee_amount +
718                   $replacement_amount -
719                   $payment_amount -
720                   $write_off_amount,
721                 'Balance is PROCESSING + LOST - PAYMENT 1 - WRITEOFF (PAYMENT 2 and WRITEOFF 2 VOIDED)'
722             );
723
724             # Store the amountoutstanding value
725             $lost_fee_line->discard_changes;
726             my $outstanding = $lost_fee_line->amountoutstanding;
727             is(
728                 $outstanding + 0,
729                 $replacement_amount - $payment_amount - $write_off_amount,
730                 "Lost Fee Outstanding is LOST - PAYMENT 1 - WRITEOFF"
731             );
732
733             # Simulate item marked as found
734             $item->itemlost(0)->store;
735             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
736
737             my $credit_return = Koha::Account::Lines->search(
738                 {
739                     itemnumber       => $item->itemnumber,
740                     credit_type_code => 'LOST_FOUND'
741                 },
742                 { rows => 1 }
743             )->single;
744
745             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
746
747             is(
748                 $account->balance,
749                 $processfee_amount - $payment_amount,
750                 'Balance is PROCESSING - PAYMENT (LOST_FOUND)'
751             );
752
753             $lost_fee_line->discard_changes;
754             is( $lost_fee_line->amountoutstanding + 0,
755                 0, 'Lost fee has no outstanding amount' );
756             is( $lost_fee_line->debit_type_code,
757                 'LOST', 'Lost fee now still has account type of LOST' );
758             is( $lost_fee_line->status, 'FOUND',
759                 "Lost fee now has account status of FOUND" );
760
761             is(
762                 $credit_return->amount + 0,
763                 ( $payment_amount + $outstanding ) * -1,
764 'The account line of type LOST_FOUND has an amount equal to the payment 1 + outstanding'
765             );
766             is(
767                 $credit_return->amountoutstanding + 0,
768                 $payment_amount * -1,
769 'The account line of type LOST_FOUND has an amountoutstanding equal to the payment'
770             );
771
772             is(
773                 $account->balance,
774                 $processfee_amount - $payment_amount,
775 'The patron balance is the difference between the PROCESSING and the credit'
776             );
777           };
778
779         subtest 'Partial payment, existing debits and AccountAutoReconcile' =>
780           sub {
781
782             plan tests => 10;
783
784             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
785             my $barcode            = 'KD123456793';
786             my $replacement_amount = 100;
787             my $processfee_amount  = 20;
788
789             my $item_type = $builder->build_object(
790                 {
791                     class => 'Koha::ItemTypes',
792                     value => {
793                         notforloan         => undef,
794                         rentalcharge       => 0,
795                         defaultreplacecost => undef,
796                         processfee         => 0,
797                         rentalcharge_daily => 0,
798                     }
799                 }
800             );
801             my $item = Koha::Item->new(
802                 {
803                     biblionumber     => $biblio->biblionumber,
804                     homebranch       => $library->branchcode,
805                     holdingbranch    => $library->branchcode,
806                     barcode          => $barcode,
807                     replacementprice => $replacement_amount,
808                     itype            => $item_type->itemtype
809                 },
810             )->store;
811
812             my $issue =
813               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
814
815             # Simulate item marked as lost
816             $item->itemlost(1)->store;
817             C4::Circulation::LostItem( $item->itemnumber, 1 );
818
819             my $lost_fee_lines = Koha::Account::Lines->search(
820                 {
821                     borrowernumber  => $patron->id,
822                     itemnumber      => $item->itemnumber,
823                     debit_type_code => 'LOST'
824                 }
825             );
826             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
827             my $lost_fee_line = $lost_fee_lines->next;
828             is( $lost_fee_line->amount + 0,
829                 $replacement_amount, 'The right LOST amount is generated' );
830             is( $lost_fee_line->amountoutstanding + 0,
831                 $replacement_amount,
832                 'The right LOST amountountstanding is generated' );
833
834             my $account = $patron->account;
835             is( $account->balance, $replacement_amount, 'Balance is L' );
836
837             # Partially pay fee
838             my $payment_amount = 27;
839             my $payment        = $account->add_credit(
840                 {
841                     amount    => $payment_amount,
842                     type      => 'PAYMENT',
843                     interface => 'test',
844                 }
845             );
846             $payment->apply( { debits => [$lost_fee_line] } );
847
848             is(
849                 $account->balance,
850                 $replacement_amount - $payment_amount,
851                 'Payment applied'
852             );
853
854             my $manual_debit_amount = 80;
855             $account->add_debit(
856                 {
857                     amount    => $manual_debit_amount,
858                     type      => 'OVERDUE',
859                     interface => 'test'
860                 }
861             );
862
863             is(
864                 $account->balance,
865                 $manual_debit_amount + $replacement_amount - $payment_amount,
866                 'Manual debit applied'
867             );
868
869             t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 1 );
870
871             # Simulate item marked as found
872             $item->itemlost(0)->store;
873             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
874
875             my $credit_return = Koha::Account::Lines->search(
876                 {
877                     itemnumber       => $item->itemnumber,
878                     credit_type_code => 'LOST_FOUND'
879                 },
880                 { rows => 1 }
881             )->single;
882
883             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
884
885             is(
886                 $account->balance,
887                 $manual_debit_amount - $payment_amount,
888                 'Balance is PROCESSING - payment (LOST_FOUND)'
889             );
890
891             my $manual_debit = Koha::Account::Lines->search(
892                 {
893                     borrowernumber  => $patron->id,
894                     debit_type_code => 'OVERDUE',
895                     status          => 'UNRETURNED'
896                 }
897             )->next;
898             is(
899                 $manual_debit->amountoutstanding + 0,
900                 $manual_debit_amount - $payment_amount,
901                 'reconcile_balance was called'
902             );
903           };
904
905         subtest 'Patron deleted' => sub {
906             plan tests => 1;
907
908             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
909             my $barcode            = 'KD123456794';
910             my $replacement_amount = 100;
911             my $processfee_amount  = 20;
912
913             my $item_type = $builder->build_object(
914                 {
915                     class => 'Koha::ItemTypes',
916                     value => {
917                         notforloan         => undef,
918                         rentalcharge       => 0,
919                         defaultreplacecost => undef,
920                         processfee         => 0,
921                         rentalcharge_daily => 0,
922                     }
923                 }
924             );
925             my $item = Koha::Item->new(
926                 {
927                     biblionumber     => $biblio->biblionumber,
928                     homebranch       => $library->branchcode,
929                     holdingbranch    => $library->branchcode,
930                     barcode          => $barcode,
931                     replacementprice => $replacement_amount,
932                     itype            => $item_type->itemtype
933                 },
934             )->store;
935
936             my $issue =
937               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
938
939             # Simulate item marked as lost
940             $item->itemlost(1)->store;
941             C4::Circulation::LostItem( $item->itemnumber, 1 );
942
943             $issue->delete();
944             $patron->delete();
945
946             # Simulate item marked as found
947             $item->itemlost(0)->store;
948             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 0, 'No refund triggered' );
949
950         };
951
952         subtest 'restore fine | no overdue' => sub {
953
954             plan tests => 8;
955
956             my $manager =
957               $builder->build_object( { class => "Koha::Patrons" } );
958             t::lib::Mocks::mock_userenv(
959                 { patron => $manager, branchcode => $manager->branchcode } );
960
961             # Set lostreturn_policy to 'restore' for tests
962             my $specific_rule_restore = $builder->build(
963                 {
964                     source => 'CirculationRule',
965                     value  => {
966                         branchcode   => $manager->branchcode,
967                         categorycode => undef,
968                         itemtype     => undef,
969                         rule_name    => 'lostreturn',
970                         rule_value   => 'restore'
971                     }
972                 }
973             );
974
975             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
976
977             my $item = $builder->build_sample_item(
978                 {
979                     biblionumber     => $biblio->biblionumber,
980                     library          => $library->branchcode,
981                     replacementprice => $replacement_amount,
982                     itype            => $item_type->itemtype
983                 }
984             );
985
986             my $issue =
987               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
988
989             # Simulate item marked as lost
990             $item->itemlost(1)->store;
991             C4::Circulation::LostItem( $item->itemnumber, 1 );
992
993             my $processing_fee_lines = Koha::Account::Lines->search(
994                 {
995                     borrowernumber  => $patron->id,
996                     itemnumber      => $item->itemnumber,
997                     debit_type_code => 'PROCESSING'
998                 }
999             );
1000             is( $processing_fee_lines->count,
1001                 1, 'Only one processing fee produced' );
1002             my $processing_fee_line = $processing_fee_lines->next;
1003             is( $processing_fee_line->amount + 0,
1004                 $processfee_amount,
1005                 'The right PROCESSING amount is generated' );
1006             is( $processing_fee_line->amountoutstanding + 0,
1007                 $processfee_amount,
1008                 'The right PROCESSING amountoutstanding is generated' );
1009
1010             my $lost_fee_lines = Koha::Account::Lines->search(
1011                 {
1012                     borrowernumber  => $patron->id,
1013                     itemnumber      => $item->itemnumber,
1014                     debit_type_code => 'LOST'
1015                 }
1016             );
1017             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
1018             my $lost_fee_line = $lost_fee_lines->next;
1019             is( $lost_fee_line->amount + 0,
1020                 $replacement_amount, 'The right LOST amount is generated' );
1021             is( $lost_fee_line->amountoutstanding + 0,
1022                 $replacement_amount,
1023                 'The right LOST amountountstanding is generated' );
1024
1025             my $account = $patron->account;
1026             my $debts   = $account->outstanding_debits;
1027
1028             # Pay off the debt
1029             my $credit = $account->add_credit(
1030                 {
1031                     amount    => $account->balance,
1032                     type      => 'PAYMENT',
1033                     interface => 'test',
1034                 }
1035             );
1036             $credit->apply( { debits => [ $debts->as_list ] } );
1037
1038             # Simulate item marked as found
1039             $item->itemlost(0)->store;
1040             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
1041             is( scalar ( grep { $_->message eq 'lost_restored' } @{$item->object_messages} ), 0, 'Restore not triggered when there is no overdue fine found' );
1042         };
1043
1044         subtest 'restore fine | unforgiven overdue' => sub {
1045
1046             plan tests => 10;
1047
1048             # Set lostreturn_policy to 'restore' for tests
1049             my $manager =
1050               $builder->build_object( { class => "Koha::Patrons" } );
1051             t::lib::Mocks::mock_userenv(
1052                 { patron => $manager, branchcode => $manager->branchcode } );
1053             my $specific_rule_restore = $builder->build(
1054                 {
1055                     source => 'CirculationRule',
1056                     value  => {
1057                         branchcode   => $manager->branchcode,
1058                         categorycode => undef,
1059                         itemtype     => undef,
1060                         rule_name    => 'lostreturn',
1061                         rule_value   => 'restore'
1062                     }
1063                 }
1064             );
1065
1066             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1067
1068             my $item = $builder->build_sample_item(
1069                 {
1070                     biblionumber     => $biblio->biblionumber,
1071                     library          => $library->branchcode,
1072                     replacementprice => $replacement_amount,
1073                     itype            => $item_type->itemtype
1074                 }
1075             );
1076
1077             my $issue =
1078               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
1079
1080             # Simulate item marked as lost
1081             $item->itemlost(1)->store;
1082             C4::Circulation::LostItem( $item->itemnumber, 1 );
1083
1084             my $processing_fee_lines = Koha::Account::Lines->search(
1085                 {
1086                     borrowernumber  => $patron->id,
1087                     itemnumber      => $item->itemnumber,
1088                     debit_type_code => 'PROCESSING'
1089                 }
1090             );
1091             is( $processing_fee_lines->count,
1092                 1, 'Only one processing fee produced' );
1093             my $processing_fee_line = $processing_fee_lines->next;
1094             is( $processing_fee_line->amount + 0,
1095                 $processfee_amount,
1096                 'The right PROCESSING amount is generated' );
1097             is( $processing_fee_line->amountoutstanding + 0,
1098                 $processfee_amount,
1099                 'The right PROCESSING amountoutstanding is generated' );
1100
1101             my $lost_fee_lines = Koha::Account::Lines->search(
1102                 {
1103                     borrowernumber  => $patron->id,
1104                     itemnumber      => $item->itemnumber,
1105                     debit_type_code => 'LOST'
1106                 }
1107             );
1108             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
1109             my $lost_fee_line = $lost_fee_lines->next;
1110             is( $lost_fee_line->amount + 0,
1111                 $replacement_amount, 'The right LOST amount is generated' );
1112             is( $lost_fee_line->amountoutstanding + 0,
1113                 $replacement_amount,
1114                 'The right LOST amountountstanding is generated' );
1115
1116             my $account = $patron->account;
1117             my $debts   = $account->outstanding_debits;
1118
1119             # Pay off the debt
1120             my $credit = $account->add_credit(
1121                 {
1122                     amount    => $account->balance,
1123                     type      => 'PAYMENT',
1124                     interface => 'test',
1125                 }
1126             );
1127             $credit->apply( { debits => [ $debts->as_list ] } );
1128
1129             # Fine not forgiven
1130             my $overdue = $account->add_debit(
1131                 {
1132                     amount     => 30.00,
1133                     user_id    => $manager->borrowernumber,
1134                     library_id => $library->branchcode,
1135                     interface  => 'test',
1136                     item_id    => $item->itemnumber,
1137                     type       => 'OVERDUE',
1138                 }
1139             )->store();
1140             $overdue->status('LOST')->store();
1141             $overdue->discard_changes;
1142             is( $overdue->status, 'LOST',
1143                 'Overdue status set to LOST' );
1144
1145             # Simulate item marked as found
1146             $item->itemlost(0)->store;
1147             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
1148             is( scalar ( grep { $_->message eq 'lost_restored' } @{$item->object_messages} ), 0, 'Restore not triggered when overdue was not forgiven' );
1149             $overdue->discard_changes;
1150             is( $overdue->status, 'FOUND',
1151                 'Overdue status updated to FOUND' );
1152         };
1153
1154         subtest 'restore fine | forgiven overdue' => sub {
1155
1156             plan tests => 12;
1157
1158             # Set lostreturn_policy to 'restore' for tests
1159             my $manager =
1160               $builder->build_object( { class => "Koha::Patrons" } );
1161             t::lib::Mocks::mock_userenv(
1162                 { patron => $manager, branchcode => $manager->branchcode } );
1163             my $specific_rule_restore = $builder->build(
1164                 {
1165                     source => 'CirculationRule',
1166                     value  => {
1167                         branchcode   => $manager->branchcode,
1168                         categorycode => undef,
1169                         itemtype     => undef,
1170                         rule_name    => 'lostreturn',
1171                         rule_value   => 'restore'
1172                     }
1173                 }
1174             );
1175
1176             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1177
1178             my $item = $builder->build_sample_item(
1179                 {
1180                     biblionumber     => $biblio->biblionumber,
1181                     library          => $library->branchcode,
1182                     replacementprice => $replacement_amount,
1183                     itype            => $item_type->itemtype
1184                 }
1185             );
1186
1187             my $issue =
1188               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
1189
1190             # Simulate item marked as lost
1191             $item->itemlost(1)->store;
1192             C4::Circulation::LostItem( $item->itemnumber, 1 );
1193
1194             my $processing_fee_lines = Koha::Account::Lines->search(
1195                 {
1196                     borrowernumber  => $patron->id,
1197                     itemnumber      => $item->itemnumber,
1198                     debit_type_code => 'PROCESSING'
1199                 }
1200             );
1201             is( $processing_fee_lines->count,
1202                 1, 'Only one processing fee produced' );
1203             my $processing_fee_line = $processing_fee_lines->next;
1204             is( $processing_fee_line->amount + 0,
1205                 $processfee_amount,
1206                 'The right PROCESSING amount is generated' );
1207             is( $processing_fee_line->amountoutstanding + 0,
1208                 $processfee_amount,
1209                 'The right PROCESSING amountoutstanding is generated' );
1210
1211             my $lost_fee_lines = Koha::Account::Lines->search(
1212                 {
1213                     borrowernumber  => $patron->id,
1214                     itemnumber      => $item->itemnumber,
1215                     debit_type_code => 'LOST'
1216                 }
1217             );
1218             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
1219             my $lost_fee_line = $lost_fee_lines->next;
1220             is( $lost_fee_line->amount + 0,
1221                 $replacement_amount, 'The right LOST amount is generated' );
1222             is( $lost_fee_line->amountoutstanding + 0,
1223                 $replacement_amount,
1224                 'The right LOST amountountstanding is generated' );
1225
1226             my $account = $patron->account;
1227             my $debts   = $account->outstanding_debits;
1228
1229             # Pay off the debt
1230             my $credit = $account->add_credit(
1231                 {
1232                     amount    => $account->balance,
1233                     type      => 'PAYMENT',
1234                     interface => 'test',
1235                 }
1236             );
1237             $credit->apply( { debits => [ $debts->as_list ] } );
1238
1239             # Add overdue
1240             my $overdue = $account->add_debit(
1241                 {
1242                     amount     => 30.00,
1243                     user_id    => $manager->borrowernumber,
1244                     library_id => $library->branchcode,
1245                     interface  => 'test',
1246                     item_id    => $item->itemnumber,
1247                     type       => 'OVERDUE',
1248                 }
1249             )->store();
1250             $overdue->status('LOST')->store();
1251             is( $overdue->status, 'LOST',
1252                 'Overdue status set to LOST' );
1253
1254             t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 0 );
1255
1256             # Forgive fine
1257             $credit = $account->add_credit(
1258                 {
1259                     amount     => 30.00,
1260                     user_id    => $manager->borrowernumber,
1261                     library_id => $library->branchcode,
1262                     interface  => 'test',
1263                     type       => 'FORGIVEN',
1264                     item_id    => $item->itemnumber
1265                 }
1266             );
1267             $credit->apply( { debits => [$overdue] } );
1268
1269             # Simulate item marked as found
1270             $item->itemlost(0)->store;
1271             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
1272             is( scalar ( grep { $_->message eq 'lost_restored' } @{$item->object_messages} ), 1, 'Restore triggered when overdue was forgiven' );
1273             $overdue->discard_changes;
1274             is( $overdue->status, 'FOUND', 'Overdue status updated to FOUND' );
1275             is( $overdue->amountoutstanding, $overdue->amount, 'Overdue outstanding has been restored' );
1276             $credit->discard_changes;
1277             is( $credit->status, 'VOID', 'Overdue Forgival has been marked as VOID');
1278         };
1279
1280         subtest 'Continue when userenv is not set' => sub {
1281             plan tests => 1;
1282
1283             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1284             my $barcode            = 'KD123456795';
1285             my $replacement_amount = 100;
1286             my $processfee_amount  = 20;
1287
1288             my $item_type = $builder->build_object(
1289                 {
1290                     class => 'Koha::ItemTypes',
1291                     value => {
1292                         notforloan         => undef,
1293                         rentalcharge       => 0,
1294                         defaultreplacecost => undef,
1295                         processfee         => 0,
1296                         rentalcharge_daily => 0,
1297                     }
1298                 }
1299             );
1300             my $item = $builder->build_sample_item(
1301                 {
1302                     biblionumber     => $biblio->biblionumber,
1303                     homebranch       => $library->branchcode,
1304                     holdingbranch    => $library->branchcode,
1305                     barcode          => $barcode,
1306                     replacementprice => $replacement_amount,
1307                     itype            => $item_type->itemtype
1308                 }
1309             );
1310
1311             my $issue =
1312               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
1313
1314             # Simulate item marked as lost
1315             $item->itemlost(1)->store;
1316             C4::Circulation::LostItem( $item->itemnumber, 1 );
1317
1318             # Unset the userenv
1319             C4::Context->_new_userenv(undef);
1320
1321             # Simluate item marked as found
1322             $item->itemlost(0)->store;
1323             is( scalar ( grep { $_->message eq 'lost_refunded' } @{$item->object_messages} ), 1, 'Refund triggered' );
1324
1325         };
1326     };
1327
1328     subtest 'log_action' => sub {
1329         plan tests => 2;
1330         t::lib::Mocks::mock_preference( 'CataloguingLog', 1 );
1331
1332         my $item = Koha::Item->new(
1333             {
1334                 homebranch    => $library->{branchcode},
1335                 holdingbranch => $library->{branchcode},
1336                 biblionumber  => $biblio->biblionumber,
1337                 location      => 'my_loc',
1338             }
1339         )->store;
1340         is(
1341             Koha::ActionLogs->search(
1342                 {
1343                     module => 'CATALOGUING',
1344                     action => 'ADD',
1345                     object => $item->itemnumber,
1346                     info   => 'item'
1347                 }
1348             )->count,
1349             1,
1350             "Item creation logged"
1351         );
1352
1353         $item->location('another_loc')->store;
1354         is(
1355             Koha::ActionLogs->search(
1356                 {
1357                     module => 'CATALOGUING',
1358                     action => 'MODIFY',
1359                     object => $item->itemnumber
1360                 }
1361             )->count,
1362             1,
1363             "Item modification logged"
1364         );
1365     };
1366 };
1367
1368 subtest 'get_transfer' => sub {
1369     plan tests => 7;
1370
1371     my $transfer = $new_item_1->get_transfer();
1372     is( $transfer, undef, 'Koha::Item->get_transfer should return undef if the item is not in transit' );
1373
1374     my $library_to = $builder->build( { source => 'Branch' } );
1375
1376     my $transfer_1 = $builder->build_object(
1377         {
1378             class => 'Koha::Item::Transfers',
1379             value => {
1380                 itemnumber    => $new_item_1->itemnumber,
1381                 frombranch    => $new_item_1->holdingbranch,
1382                 tobranch      => $library_to->{branchcode},
1383                 reason        => 'Manual',
1384                 datesent      => undef,
1385                 datearrived   => undef,
1386                 datecancelled => undef,
1387                 daterequested => \'NOW()'
1388             }
1389         }
1390     );
1391
1392     $transfer = $new_item_1->get_transfer();
1393     is( ref($transfer), 'Koha::Item::Transfer', 'Koha::Item->get_transfer should return a Koha::Item::Transfer object' );
1394
1395     my $transfer_2 = $builder->build_object(
1396         {
1397             class => 'Koha::Item::Transfers',
1398             value => {
1399                 itemnumber    => $new_item_1->itemnumber,
1400                 frombranch    => $new_item_1->holdingbranch,
1401                 tobranch      => $library_to->{branchcode},
1402                 reason        => 'Manual',
1403                 datesent      => undef,
1404                 datearrived   => undef,
1405                 datecancelled => undef,
1406                 daterequested => \'NOW()'
1407             }
1408         }
1409     );
1410
1411     $transfer = $new_item_1->get_transfer();
1412     is( $transfer->branchtransfer_id, $transfer_1->branchtransfer_id, 'Koha::Item->get_transfer returns the oldest transfer request');
1413
1414     $transfer_2->datesent(\'NOW()')->store;
1415     $transfer = $new_item_1->get_transfer();
1416     is( $transfer->branchtransfer_id, $transfer_2->branchtransfer_id, 'Koha::Item->get_transfer returns the in_transit transfer');
1417
1418     my $transfer_3 = $builder->build_object(
1419         {
1420             class => 'Koha::Item::Transfers',
1421             value => {
1422                 itemnumber    => $new_item_1->itemnumber,
1423                 frombranch    => $new_item_1->holdingbranch,
1424                 tobranch      => $library_to->{branchcode},
1425                 reason        => 'Manual',
1426                 datesent      => undef,
1427                 datearrived   => undef,
1428                 datecancelled => undef,
1429                 daterequested => \'NOW()'
1430             }
1431         }
1432     );
1433
1434     $transfer_2->datearrived(\'NOW()')->store;
1435     $transfer = $new_item_1->get_transfer();
1436     is( $transfer->branchtransfer_id, $transfer_1->branchtransfer_id, 'Koha::Item->get_transfer returns the next queued transfer');
1437     is( $transfer->itemnumber, $new_item_1->itemnumber, 'Koha::Item->get_transfer returns the right items transfer' );
1438
1439     $transfer_1->datecancelled(\'NOW()')->store;
1440     $transfer = $new_item_1->get_transfer();
1441     is( $transfer->branchtransfer_id, $transfer_3->branchtransfer_id, 'Koha::Item->get_transfer ignores cancelled transfers');
1442 };
1443
1444 subtest 'holds' => sub {
1445     plan tests => 5;
1446
1447     my $biblio = $builder->build_sample_biblio();
1448     my $item   = $builder->build_sample_item({
1449         biblionumber => $biblio->biblionumber,
1450     });
1451     is($item->holds->count, 0, "Nothing returned if no holds");
1452     my $hold1 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'T' }});
1453     my $hold2 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'W' }});
1454     my $hold3 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'W' }});
1455
1456     is($item->holds()->count,3,"Three holds found");
1457     is($item->holds({found => 'W'})->count,2,"Two waiting holds found");
1458     is_deeply($item->holds({found => 'T'})->next->unblessed,$hold1,"Found transit holds matches the hold");
1459     is($item->holds({found => undef})->count, 0,"Nothing returned if no matching holds");
1460 };
1461
1462 subtest 'biblio' => sub {
1463     plan tests => 2;
1464
1465     my $biblio = $retrieved_item_1->biblio;
1466     is( ref( $biblio ), 'Koha::Biblio', 'Koha::Item->biblio should return a Koha::Biblio' );
1467     is( $biblio->biblionumber, $retrieved_item_1->biblionumber, 'Koha::Item->biblio should return the correct biblio' );
1468 };
1469
1470 subtest 'biblioitem' => sub {
1471     plan tests => 2;
1472
1473     my $biblioitem = $retrieved_item_1->biblioitem;
1474     is( ref( $biblioitem ), 'Koha::Biblioitem', 'Koha::Item->biblioitem should return a Koha::Biblioitem' );
1475     is( $biblioitem->biblionumber, $retrieved_item_1->biblionumber, 'Koha::Item->biblioitem should return the correct biblioitem' );
1476 };
1477
1478 # Restore userenv
1479 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
1480 subtest 'checkout' => sub {
1481     plan tests => 5;
1482     my $item = Koha::Items->find( $new_item_1->itemnumber );
1483     # No checkout yet
1484     my $checkout = $item->checkout;
1485     is( $checkout, undef, 'Koha::Item->checkout should return undef if there is no current checkout on this item' );
1486
1487     # Add a checkout
1488     my $patron = $builder->build({ source => 'Borrower' });
1489     C4::Circulation::AddIssue( $patron, $item->barcode );
1490     $checkout = $retrieved_item_1->checkout;
1491     is( ref( $checkout ), 'Koha::Checkout', 'Koha::Item->checkout should return a Koha::Checkout' );
1492     is( $checkout->itemnumber, $item->itemnumber, 'Koha::Item->checkout should return the correct checkout' );
1493     is( $checkout->borrowernumber, $patron->{borrowernumber}, 'Koha::Item->checkout should return the correct checkout' );
1494
1495     # Do the return
1496     C4::Circulation::AddReturn( $item->barcode );
1497
1498     # There is no more checkout on this item, making sure it will not return old checkouts
1499     $checkout = $item->checkout;
1500     is( $checkout, undef, 'Koha::Item->checkout should return undef if there is no *current* checkout on this item' );
1501 };
1502
1503 subtest 'can_be_transferred' => sub {
1504     plan tests => 5;
1505
1506     t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
1507     t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
1508
1509     my $biblio   = $builder->build_sample_biblio();
1510     my $library1 = $builder->build_object( { class => 'Koha::Libraries' } );
1511     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
1512     my $item  = $builder->build_sample_item({
1513         biblionumber     => $biblio->biblionumber,
1514         homebranch       => $library1->branchcode,
1515         holdingbranch    => $library1->branchcode,
1516     });
1517
1518     is(Koha::Item::Transfer::Limits->search({
1519         fromBranch => $library1->branchcode,
1520         toBranch => $library2->branchcode,
1521     })->count, 0, 'There are no transfer limits between libraries.');
1522     ok($item->can_be_transferred({ to => $library2 }),
1523        'Item can be transferred between libraries.');
1524
1525     my $limit = Koha::Item::Transfer::Limit->new({
1526         fromBranch => $library1->branchcode,
1527         toBranch => $library2->branchcode,
1528         itemtype => $item->effective_itemtype,
1529     })->store;
1530     is(Koha::Item::Transfer::Limits->search({
1531         fromBranch => $library1->branchcode,
1532         toBranch => $library2->branchcode,
1533     })->count, 1, 'Given we have added a transfer limit,');
1534     is($item->can_be_transferred({ to => $library2 }), 0,
1535        'Item can no longer be transferred between libraries.');
1536     is($item->can_be_transferred({ to => $library2, from => $library1 }), 0,
1537        'We get the same result also if we pass the from-library parameter.');
1538 };
1539
1540 # Reset nb_of_items prior to testing delete
1541 $nb_of_items = Koha::Items->search->count;
1542
1543 # Test delete
1544 $retrieved_item_1->delete;
1545 is( Koha::Items->search->count, $nb_of_items - 1, 'Delete should have deleted the item' );
1546
1547 $schema->storage->txn_rollback;
1548
1549 subtest 'filter_by_visible_in_opac() tests' => sub {
1550
1551     plan tests => 14;
1552
1553     $schema->storage->txn_begin;
1554
1555     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1556     my $mocked_category = Test::MockModule->new('Koha::Patron::Category');
1557     my $exception = 1;
1558     $mocked_category->mock( 'override_hidden_items', sub {
1559         return $exception;
1560     });
1561
1562     # have a fresh biblio
1563     my $biblio = $builder->build_sample_biblio;
1564     # have two itemtypes
1565     my $itype_1 = $builder->build_object({ class => 'Koha::ItemTypes' });
1566     my $itype_2 = $builder->build_object({ class => 'Koha::ItemTypes' });
1567     # have 5 items on that biblio
1568     my $item_1 = $builder->build_sample_item(
1569         {
1570             biblionumber => $biblio->biblionumber,
1571             itemlost     => -1,
1572             itype        => $itype_1->itemtype,
1573             withdrawn    => 1,
1574             copynumber   => undef
1575         }
1576     );
1577     my $item_2 = $builder->build_sample_item(
1578         {
1579             biblionumber => $biblio->biblionumber,
1580             itemlost     => 0,
1581             itype        => $itype_2->itemtype,
1582             withdrawn    => 2,
1583             copynumber   => undef
1584         }
1585     );
1586     my $item_3 = $builder->build_sample_item(
1587         {
1588             biblionumber => $biblio->biblionumber,
1589             itemlost     => 1,
1590             itype        => $itype_1->itemtype,
1591             withdrawn    => 3,
1592             copynumber   => undef
1593         }
1594     );
1595     my $item_4 = $builder->build_sample_item(
1596         {
1597             biblionumber => $biblio->biblionumber,
1598             itemlost     => 0,
1599             itype        => $itype_2->itemtype,
1600             withdrawn    => 4,
1601             copynumber   => undef
1602         }
1603     );
1604     my $item_5 = $builder->build_sample_item(
1605         {
1606             biblionumber => $biblio->biblionumber,
1607             itemlost     => 0,
1608             itype        => $itype_1->itemtype,
1609             withdrawn    => 5,
1610             copynumber   => undef
1611         }
1612     );
1613     my $item_6 = $builder->build_sample_item(
1614         {
1615             biblionumber => $biblio->biblionumber,
1616             itemlost     => 2,
1617             itype        => $itype_1->itemtype,
1618             withdrawn    => 5,
1619             copynumber   => undef
1620         }
1621     );
1622
1623     my $rules = undef;
1624
1625     my $mocked_context = Test::MockModule->new('C4::Context');
1626     $mocked_context->mock( 'yaml_preference', sub {
1627         return $rules;
1628     });
1629
1630     t::lib::Mocks::mock_preference( 'hidelostitems', 0 );
1631     is( $biblio->items->filter_by_visible_in_opac->count,
1632         6, 'No rules passed, hidelostitems unset' );
1633
1634     is( $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1635         6, 'No rules passed, hidelostitems unset, patron exception changes nothing' );
1636
1637     $rules = { copynumber => [ 2 ] };
1638
1639     t::lib::Mocks::mock_preference( 'hidelostitems', 1 );
1640     is(
1641         $biblio->items->filter_by_visible_in_opac->count,
1642         3,
1643         'No rules passed, hidelostitems set'
1644     );
1645
1646     is(
1647         $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1648         3,
1649         'No rules passed, hidelostitems set, patron exception changes nothing'
1650     );
1651
1652     $rules = { biblionumber => [ $biblio->biblionumber ] };
1653     is(
1654         $biblio->items->filter_by_visible_in_opac->count,
1655         0,
1656         'Biblionumber rule successfully hides all items'
1657     );
1658
1659     my $biblio2 = $builder->build_sample_biblio;
1660     $rules = { biblionumber => [ $biblio2->biblionumber ] };
1661     my $prefetched = $biblio->items->search({},{ prefetch => ['branchtransfers','reserves'] })->filter_by_visible_in_opac;
1662     ok( $prefetched->next, "Can retrieve object when prefetching and hiding on a duplicated column");
1663
1664     $rules = { withdrawn => [ 1, 2 ], copynumber => [ 2 ] };
1665     is(
1666         $biblio->items->filter_by_visible_in_opac->count,
1667         2,
1668         'Rules on withdrawn, hidelostitems set'
1669     );
1670
1671     is(
1672         $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1673         3,
1674         'hidelostitems set, rules on withdrawn but patron override passed'
1675     );
1676
1677     $rules = { itype => [ $itype_1->itemtype ], copynumber => [ 2 ] };
1678     is(
1679         $biblio->items->filter_by_visible_in_opac->count,
1680         2,
1681         'Rules on itype, hidelostitems set'
1682     );
1683
1684     $rules = { withdrawn => [ 1, 2 ], itype => [ $itype_1->itemtype ], copynumber => [ 2 ] };
1685     is(
1686         $biblio->items->filter_by_visible_in_opac->count,
1687         1,
1688         'Rules on itype and withdrawn, hidelostitems set'
1689     );
1690     is(
1691         $biblio->items->filter_by_visible_in_opac
1692           ->next->itemnumber,
1693         $item_4->itemnumber,
1694         'The right item is returned'
1695     );
1696
1697     $rules = { withdrawn => [ 1, 2 ], itype => [ $itype_2->itemtype ], copynumber => [ 2 ] };
1698     is(
1699         $biblio->items->filter_by_visible_in_opac->count,
1700         1,
1701         'Rules on itype and withdrawn, hidelostitems set'
1702     );
1703     is(
1704         $biblio->items->filter_by_visible_in_opac
1705           ->next->itemnumber,
1706         $item_5->itemnumber,
1707         'The right item is returned'
1708     );
1709
1710     # Make sure the warning on the about page will work
1711     $rules = { itemlost => ['AB'] };
1712     my $c = Koha::Items->filter_by_visible_in_opac->count;
1713     my @warnings = C4::Context->dbh->selectrow_array('SHOW WARNINGS');
1714     like( $warnings[2], qr/Truncated incorrect (DOUBLE|DECIMAL) value: 'AB'/);
1715
1716     $schema->storage->txn_rollback;
1717 };
1718
1719 subtest 'filter_out_lost() tests' => sub {
1720
1721     plan tests => 2;
1722
1723     $schema->storage->txn_begin;
1724
1725     # have a fresh biblio
1726     my $biblio = $builder->build_sample_biblio;
1727     # have 3 items on that biblio
1728     my $item_1 = $builder->build_sample_item(
1729         {
1730             biblionumber => $biblio->biblionumber,
1731             itemlost     => -1,
1732         }
1733     );
1734     my $item_2 = $builder->build_sample_item(
1735         {
1736             biblionumber => $biblio->biblionumber,
1737             itemlost     => 0,
1738         }
1739     );
1740     my $item_3 = $builder->build_sample_item(
1741         {
1742             biblionumber => $biblio->biblionumber,
1743             itemlost     => 1,
1744         }
1745     );
1746
1747     is( $biblio->items->filter_out_lost->next->itemnumber, $item_2->itemnumber, 'Right item returned' );
1748     is( $biblio->items->filter_out_lost->count, 1, 'Only one item is not lost' );
1749
1750     $schema->storage->txn_rollback;
1751 };
1752
1753 subtest 'move_to_biblio() tests' => sub {
1754
1755     plan tests => 2;
1756
1757     $schema->storage->txn_begin;
1758
1759     my $biblio1 = $builder->build_sample_biblio;
1760     my $biblio2 = $builder->build_sample_biblio;
1761     my $item1 = $builder->build_sample_item({ biblionumber => $biblio1->biblionumber });
1762     my $item2 = $builder->build_sample_item({ biblionumber => $biblio1->biblionumber });
1763
1764     $biblio1->items->move_to_biblio($biblio2);
1765
1766     $item1->discard_changes;
1767     $item2->discard_changes;
1768
1769     is($item1->biblionumber, $biblio2->biblionumber, "Item 1 moved");
1770     is($item2->biblionumber, $biblio2->biblionumber, "Item 2 moved");
1771
1772     $schema->storage->txn_rollback;
1773
1774 };
1775
1776 subtest 'search_ordered' => sub {
1777
1778     plan tests => 6;
1779
1780     $schema->storage->txn_begin;
1781
1782     my $library_a = $builder->build_object(
1783         { class => 'Koha::Libraries', value => { branchname => 'TEST_A' } } );
1784     my $library_z = $builder->build_object(
1785         { class => 'Koha::Libraries', value => { branchname => 'TEST_Z' } } );
1786     my $biblio = $builder->build_sample_biblio( { serial => 0 } );
1787     my $item1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1788     my $item2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1789     my $item3 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1790
1791     { # Is not a serial
1792
1793         # order_by homebranch.branchname
1794         $item1->discard_changes->update( { homebranch => $library_z->branchcode } );
1795         $item2->discard_changes->update( { homebranch => $library_a->branchcode } );
1796         $item3->discard_changes->update( { homebranch => $library_z->branchcode } );
1797         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1798             [ $item2->itemnumber, $item1->itemnumber, $item3->itemnumber ],
1799             "not a serial - order by homebranch" );
1800
1801         # order_by me.enumchron
1802         $biblio->items->update( { homebranch => $library_a->branchcode } );
1803         $item1->discard_changes->update( { enumchron => 'cc' } );
1804         $item2->discard_changes->update( { enumchron => 'bb' } );
1805         $item3->discard_changes->update( { enumchron => 'aa' } );
1806         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1807             [ $item3->itemnumber, $item2->itemnumber, $item1->itemnumber ],
1808             "not a serial - order by enumchron" );
1809
1810         # order_by LPAD( me.copynumber, 8, '0' )
1811         $biblio->items->update( { enumchron => undef } );
1812         $item1->discard_changes->update( { copynumber => '12345678' } );
1813         $item2->discard_changes->update( { copynumber => '34567890' } );
1814         $item3->discard_changes->update( { copynumber => '23456789' } );
1815         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1816             [ $item1->itemnumber, $item3->itemnumber, $item2->itemnumber ],
1817             "not a serial - order by LPAD( me.copynumber, 8, '0' )" );
1818
1819         # order_by -desc => 'me.dateaccessioned'
1820         $biblio->items->update( { copynumber => undef } );
1821         $item1->discard_changes->update( { dateaccessioned => '2022-08-19' } );
1822         $item2->discard_changes->update( { dateaccessioned => '2022-07-19' } );
1823         $item3->discard_changes->update( { dateaccessioned => '2022-09-19' } );
1824         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1825             [ $item3->itemnumber, $item1->itemnumber, $item2->itemnumber ],
1826             "not a serial - order by date accessioned desc" );
1827     }
1828
1829     {    # Is a serial
1830
1831         my $sub_freq = $builder->build( { source => 'SubscriptionFrequency' } );
1832         my $sub_np =
1833           $builder->build( { source => 'SubscriptionNumberpattern' } );
1834         my $subscription = $builder->build_object(
1835             {
1836                 class => 'Koha::Subscriptions',
1837                 value => {
1838                     biblionumber  => $biblio->biblionumber,
1839                     periodicity   => $sub_freq->{id},
1840                     numberpattern => $sub_np->{id}
1841                 }
1842             }
1843         );
1844         $builder->build_object(
1845             {
1846                 class => 'Koha::Subscription::Histories',
1847                 value => {
1848                     subscriptionid => $subscription->subscriptionid,
1849                     biblionumber   => $biblio->biblionumber
1850                 }
1851             }
1852         );
1853
1854         $biblio->update( { serial => 1 } );
1855         my $serialid1 =
1856           C4::Serials::NewIssue( "serialseq", $subscription->subscriptionid,
1857             $biblio->biblionumber, 1, undef, undef, "publisheddatetext",
1858             "notes", "routingnotes" );
1859         C4::Serials::AddItem2Serial( $serialid1, $item1->itemnumber );
1860         my $serialid2 =
1861           C4::Serials::NewIssue( "serialseq", $subscription->subscriptionid,
1862             $biblio->biblionumber, 1, undef, undef, "publisheddatetext",
1863             "notes", "routingnotes" );
1864         C4::Serials::AddItem2Serial( $serialid2, $item2->itemnumber );
1865         my $serialid3 =
1866           C4::Serials::NewIssue( "serialseq", $subscription->subscriptionid,
1867             $biblio->biblionumber, 1, undef, undef, "publisheddatetext",
1868             "notes", "routingnotes" );
1869         C4::Serials::AddItem2Serial( $serialid3, $item3->itemnumber );
1870         my $serial1 = Koha::Serials->find($serialid1);
1871         my $serial2 = Koha::Serials->find($serialid2);
1872         my $serial3 = Koha::Serials->find($serialid3);
1873
1874         # order_by serial.publisheddate
1875         $serial1->discard_changes->update( { publisheddate => '2022-09-19' } );
1876         $serial2->discard_changes->update( { publisheddate => '2022-07-19' } );
1877         $serial3->discard_changes->update( { publisheddate => '2022-08-19' } );
1878         is_deeply(
1879             [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1880             [ $item2->itemnumber, $item3->itemnumber, $item1->itemnumber ],
1881             "serial - order by publisheddate"
1882         );
1883
1884         # order_by me.enumchron
1885         $serial1->discard_changes->update({ publisheddate => '2022-08-19' });
1886         $serial2->discard_changes->update({ publisheddate => '2022-08-19' });
1887         $serial3->discard_changes->update({ publisheddate => '2022-08-19' });
1888         $item1->discard_changes->update( { enumchron => 'cc' } );
1889         $item2->discard_changes->update( { enumchron => 'bb' } );
1890         $item3->discard_changes->update( { enumchron => 'aa' } );
1891         is_deeply( [ map { $_->itemnumber } $biblio->items->search_ordered->as_list ],
1892             [ $item3->itemnumber, $item2->itemnumber, $item1->itemnumber ],
1893             "serial - order by enumchron" );
1894
1895     }
1896
1897     $schema->storage->txn_rollback;
1898
1899 };
1900
1901 subtest 'filter_by_for_hold' => sub {
1902
1903     plan tests => 13;
1904
1905     $schema->storage->txn_begin;
1906
1907     my $biblio  = $builder->build_sample_biblio;
1908     my $library = $builder->build_object({ class => 'Koha::Libraries' });
1909
1910     t::lib::Mocks::mock_preference('IndependentBranches', 0); # more robust tests
1911
1912     is( $biblio->items->filter_by_for_hold->count, 0, 'no item yet' );
1913     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, notforloan => 1 } );
1914     is( $biblio->items->filter_by_for_hold->count, 0, 'no item for hold' );
1915     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, notforloan => 0 } );
1916     is( $biblio->items->filter_by_for_hold->count, 1, '1 item for hold' );
1917     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, notforloan => -1 } );
1918     is( $biblio->items->filter_by_for_hold->count, 2, '2 items for hold' );
1919
1920     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, itemlost => 0, library => $library->id } );
1921     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, itemlost => 1, library => $library->id } );
1922     is( $biblio->items->filter_by_for_hold->count, 3, '3 items for hold - itemlost' );
1923
1924     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, withdrawn => 0, library => $library->id } );
1925     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, withdrawn => 1, library => $library->id } );
1926     is( $biblio->items->filter_by_for_hold->count, 4, '4 items for hold - withdrawn' );
1927
1928     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, damaged => 0 } );
1929     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, damaged => 1 } );
1930     t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 0);
1931     is( $biblio->items->filter_by_for_hold->count, 5, '5 items for hold - not damaged if not AllowHoldsOnDamagedItems' );
1932     t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 1);
1933     is( $biblio->items->filter_by_for_hold->count, 6, '6 items for hold - damaged if AllowHoldsOnDamagedItems' );
1934
1935     my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes' });
1936     my $not_holdable_itemtype = $itemtype->itemtype;
1937     $builder->build_sample_item(
1938         {
1939             biblionumber => $biblio->biblionumber,
1940             itype        => $not_holdable_itemtype,
1941         }
1942     );
1943     Koha::CirculationRules->set_rule(
1944         {
1945             branchcode   => undef,
1946             itemtype     => $not_holdable_itemtype,
1947             rule_name    => 'holdallowed',
1948             rule_value   => 'not_allowed',
1949         }
1950     );
1951     is( $biblio->items->filter_by_for_hold->count, 6, '6 items for hold - holdallowed=not_allowed' );
1952
1953     # Remove rule, test notforloan on itemtype
1954     Koha::CirculationRules->set_rule(
1955         {
1956             branchcode   => undef,
1957             itemtype     => $not_holdable_itemtype,
1958             rule_name    => 'holdallowed',
1959             rule_value   => undef,
1960         }
1961     );
1962     is( $biblio->items->filter_by_for_hold->count, 7, '7 items for hold - rule deleted' );
1963     $itemtype->notforloan(1)->store;
1964     is( $biblio->items->filter_by_for_hold->count, 6, '6 items for hold - notforloan' );
1965
1966     {
1967         my $mock_context = Test::MockModule->new('C4::Context');
1968         $mock_context->mock( 'only_my_library', 1 );
1969         $mock_context->mock( 'mybranch',        $library->id );
1970         is( $biblio->items->filter_by_for_hold->count, 2, '2 items for hold, filtered by IndependentBranches' );
1971     }
1972
1973     t::lib::Mocks::mock_preference('item-level_itypes', 0);
1974     $biblio->biblioitem->itemtype($not_holdable_itemtype)->store;
1975     is( $biblio->items->filter_by_for_hold->count, 0, '0 item-level_itypes=0' );
1976
1977     t::lib::Mocks::mock_preference('item-level_itypes', 1);
1978
1979     $schema->storage->txn_rollback;
1980 };