Bug 27680: Add support for param[] syntax
[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 => 14;
23
24 use Test::MockModule;
25 use Test::Exception;
26 use Time::Fake;
27
28 use C4::Circulation;
29 use C4::Context;
30 use Koha::Item;
31 use Koha::Item::Transfer::Limits;
32 use Koha::Items;
33 use Koha::Database;
34 use Koha::DateUtils qw( dt_from_string );
35
36 use t::lib::TestBuilder;
37 use t::lib::Mocks;
38 use t::lib::Dates;
39
40 my $schema = Koha::Database->new->schema;
41 $schema->storage->txn_begin;
42
43 my $dbh     = C4::Context->dbh;
44
45 my $builder     = t::lib::TestBuilder->new;
46 my $library     = $builder->build( { source => 'Branch' } );
47 my $nb_of_items = Koha::Items->search->count;
48 my $biblio      = $builder->build_sample_biblio();
49 my $new_item_1   = $builder->build_sample_item({
50     biblionumber => $biblio->biblionumber,
51     homebranch       => $library->{branchcode},
52     holdingbranch    => $library->{branchcode},
53 });
54 my $new_item_2   = $builder->build_sample_item({
55     biblionumber => $biblio->biblionumber,
56     homebranch       => $library->{branchcode},
57     holdingbranch    => $library->{branchcode},
58 });
59
60
61 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
62
63 like( $new_item_1->itemnumber, qr|^\d+$|, 'Adding a new item should have set the itemnumber' );
64 is( Koha::Items->search->count, $nb_of_items + 2, 'The 2 items should have been added' );
65
66 my $retrieved_item_1 = Koha::Items->find( $new_item_1->itemnumber );
67 is( $retrieved_item_1->barcode, $new_item_1->barcode, 'Find a item by id should return the correct item' );
68
69 subtest 'store' => sub {
70     plan tests => 7;
71
72     my $biblio = $builder->build_sample_biblio;
73     my $today  = dt_from_string->set( hour => 0, minute => 0, second => 0 );
74     my $item   = Koha::Item->new(
75         {
76             homebranch    => $library->{branchcode},
77             holdingbranch => $library->{branchcode},
78             biblionumber  => $biblio->biblionumber,
79             location      => 'my_loc',
80         }
81     )->store->get_from_storage;
82
83     is( t::lib::Dates::compare( $item->replacementpricedate, $today ),
84         0, 'replacementpricedate must have been set to today if not given' );
85     is( t::lib::Dates::compare( $item->datelastseen, $today ),
86         0, 'datelastseen must have been set to today if not given' );
87     is(
88         $item->itype,
89         $biblio->biblioitem->itemtype,
90         'items.itype must have been set to biblioitem.itemtype is not given'
91     );
92     is( $item->permanent_location, $item->location,
93         'permanent_location must have been set to location if not given' );
94     $item->delete;
95
96     subtest '*_on updates' => sub {
97         plan tests => 9;
98
99         # Once the '_on' value is set (triggered by the related field turning from false to true)
100         # it should not be re-set for any changes outside of the related field being 'unset'.
101
102         my @fields = qw( itemlost withdrawn damaged );
103         my $today = dt_from_string();
104         my $yesterday = $today->clone()->subtract( days => 1 );
105
106         for my $field ( @fields ) {
107             my $item = $builder->build_sample_item(
108                 {
109                     itemlost     => 0,
110                     itemlost_on  => undef,
111                     withdrawn    => 0,
112                     withdrawn_on => undef,
113                     damaged      => 0,
114                     damaged_on   => undef
115                 }
116             );
117             my $field_on = $field . '_on';
118
119             # Set field for the first time
120             Time::Fake->offset( $yesterday->epoch );
121             $item->$field(1)->store;
122             $item->get_from_storage;
123             is( t::lib::Dates::compare( $item->$field_on, $yesterday ),
124                 0, $field_on . " was set upon first truthy setting" );
125
126             # Update the field to a new 'true' value
127             Time::Fake->offset( $today->epoch );
128             $item->$field(2)->store;
129             $item->get_from_storage;
130             is( t::lib::Dates::compare( $item->$field_on, $yesterday ),
131                 0, $field_on . " was not updated upon second truthy setting" );
132
133             # Update the field to a new 'false' value
134             $item->$field(0)->store;
135             $item->get_from_storage;
136             is($item->$field_on, undef, $field_on . " was unset upon untruthy setting");
137
138             Time::Fake->reset;
139         }
140     };
141
142     subtest '_lost_found_trigger' => sub {
143         plan tests => 10;
144
145         t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee', 1 );
146         t::lib::Mocks::mock_preference( 'WhenLostForgiveFine',          0 );
147
148         my $processfee_amount  = 20;
149         my $replacement_amount = 99.00;
150         my $item_type          = $builder->build_object(
151             {
152                 class => 'Koha::ItemTypes',
153                 value => {
154                     notforloan         => undef,
155                     rentalcharge       => 0,
156                     defaultreplacecost => undef,
157                     processfee         => $processfee_amount,
158                     rentalcharge_daily => 0,
159                 }
160             }
161         );
162         my $library = $builder->build_object( { class => 'Koha::Libraries' } );
163
164         $biblio = $builder->build_sample_biblio( { author => 'Hall, Daria' } );
165
166         subtest 'Full write-off tests' => sub {
167
168             plan tests => 12;
169
170             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
171             my $manager =
172               $builder->build_object( { class => "Koha::Patrons" } );
173             t::lib::Mocks::mock_userenv(
174                 { patron => $manager, branchcode => $manager->branchcode } );
175
176             my $item = $builder->build_sample_item(
177                 {
178                     biblionumber     => $biblio->biblionumber,
179                     library          => $library->branchcode,
180                     replacementprice => $replacement_amount,
181                     itype            => $item_type->itemtype,
182                 }
183             );
184
185             C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
186
187             # Simulate item marked as lost
188             $item->itemlost(3)->store;
189             C4::Circulation::LostItem( $item->itemnumber, 1 );
190
191             my $processing_fee_lines = Koha::Account::Lines->search(
192                 {
193                     borrowernumber  => $patron->id,
194                     itemnumber      => $item->itemnumber,
195                     debit_type_code => 'PROCESSING'
196                 }
197             );
198             is( $processing_fee_lines->count,
199                 1, 'Only one processing fee produced' );
200             my $processing_fee_line = $processing_fee_lines->next;
201             is( $processing_fee_line->amount + 0,
202                 $processfee_amount,
203                 'The right PROCESSING amount is generated' );
204             is( $processing_fee_line->amountoutstanding + 0,
205                 $processfee_amount,
206                 'The right PROCESSING amountoutstanding is generated' );
207
208             my $lost_fee_lines = Koha::Account::Lines->search(
209                 {
210                     borrowernumber  => $patron->id,
211                     itemnumber      => $item->itemnumber,
212                     debit_type_code => 'LOST'
213                 }
214             );
215             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
216             my $lost_fee_line = $lost_fee_lines->next;
217             is( $lost_fee_line->amount + 0,
218                 $replacement_amount, 'The right LOST amount is generated' );
219             is( $lost_fee_line->amountoutstanding + 0,
220                 $replacement_amount,
221                 'The right LOST amountoutstanding is generated' );
222             is( $lost_fee_line->status, undef, 'The LOST status was not set' );
223
224             my $account = $patron->account;
225             my $debts   = $account->outstanding_debits;
226
227             # Write off the debt
228             my $credit = $account->add_credit(
229                 {
230                     amount    => $account->balance,
231                     type      => 'WRITEOFF',
232                     interface => 'test',
233                 }
234             );
235             $credit->apply(
236                 { debits => [ $debts->as_list ], offset_type => 'Writeoff' } );
237
238             # Simulate item marked as found
239             $item->itemlost(0)->store;
240             is( $item->{_refunded}, undef, 'No LOST_FOUND account line added' );
241
242             $lost_fee_line->discard_changes;    # reload from DB
243             is( $lost_fee_line->amountoutstanding + 0,
244                 0, 'Lost fee has no outstanding amount' );
245             is( $lost_fee_line->debit_type_code,
246                 'LOST', 'Lost fee now still has account type of LOST' );
247             is( $lost_fee_line->status, 'FOUND',
248                 "Lost fee now has account status of FOUND - No Refund" );
249
250             is( $patron->account->balance,
251                 -0, 'The patron balance is 0, everything was written off' );
252         };
253
254         subtest 'Full payment tests' => sub {
255
256             plan tests => 14;
257
258             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
259
260             my $item = $builder->build_sample_item(
261                 {
262                     biblionumber     => $biblio->biblionumber,
263                     library          => $library->branchcode,
264                     replacementprice => $replacement_amount,
265                     itype            => $item_type->itemtype
266                 }
267             );
268
269             my $issue =
270               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
271
272             # Simulate item marked as lost
273             $item->itemlost(1)->store;
274             C4::Circulation::LostItem( $item->itemnumber, 1 );
275
276             my $processing_fee_lines = Koha::Account::Lines->search(
277                 {
278                     borrowernumber  => $patron->id,
279                     itemnumber      => $item->itemnumber,
280                     debit_type_code => 'PROCESSING'
281                 }
282             );
283             is( $processing_fee_lines->count,
284                 1, 'Only one processing fee produced' );
285             my $processing_fee_line = $processing_fee_lines->next;
286             is( $processing_fee_line->amount + 0,
287                 $processfee_amount,
288                 'The right PROCESSING amount is generated' );
289             is( $processing_fee_line->amountoutstanding + 0,
290                 $processfee_amount,
291                 'The right PROCESSING amountoutstanding is generated' );
292
293             my $lost_fee_lines = Koha::Account::Lines->search(
294                 {
295                     borrowernumber  => $patron->id,
296                     itemnumber      => $item->itemnumber,
297                     debit_type_code => 'LOST'
298                 }
299             );
300             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
301             my $lost_fee_line = $lost_fee_lines->next;
302             is( $lost_fee_line->amount + 0,
303                 $replacement_amount, 'The right LOST amount is generated' );
304             is( $lost_fee_line->amountoutstanding + 0,
305                 $replacement_amount,
306                 'The right LOST amountountstanding is generated' );
307
308             my $account = $patron->account;
309             my $debts   = $account->outstanding_debits;
310
311             # Pay off the debt
312             my $credit = $account->add_credit(
313                 {
314                     amount    => $account->balance,
315                     type      => 'PAYMENT',
316                     interface => 'test',
317                 }
318             );
319             $credit->apply(
320                 { debits => [ $debts->as_list ], offset_type => 'Payment' } );
321
322             # Simulate item marked as found
323             $item->itemlost(0)->store;
324             is( $item->{_refunded}, 1, 'Refund triggered' );
325
326             my $credit_return = Koha::Account::Lines->search(
327                 {
328                     itemnumber       => $item->itemnumber,
329                     credit_type_code => 'LOST_FOUND'
330                 },
331                 { rows => 1 }
332             )->single;
333
334             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
335             is( $credit_return->amount + 0,
336                 -99.00,
337                 'The account line of type LOST_FOUND has an amount of -99' );
338             is(
339                 $credit_return->amountoutstanding + 0,
340                 -99.00,
341 'The account line of type LOST_FOUND has an amountoutstanding of -99'
342             );
343
344             $lost_fee_line->discard_changes;
345             is( $lost_fee_line->amountoutstanding + 0,
346                 0, 'Lost fee has no outstanding amount' );
347             is( $lost_fee_line->debit_type_code,
348                 'LOST', 'Lost fee now still has account type of LOST' );
349             is( $lost_fee_line->status, 'FOUND',
350                 "Lost fee now has account status of FOUND" );
351
352             is( $patron->account->balance, -99,
353 'The patron balance is -99, a credit that equals the lost fee payment'
354             );
355         };
356
357         subtest 'Test without payment or write off' => sub {
358
359             plan tests => 14;
360
361             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
362
363             my $item = $builder->build_sample_item(
364                 {
365                     biblionumber     => $biblio->biblionumber,
366                     library          => $library->branchcode,
367                     replacementprice => 23.00,
368                     replacementprice => $replacement_amount,
369                     itype            => $item_type->itemtype
370                 }
371             );
372
373             my $issue =
374               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
375
376             # Simulate item marked as lost
377             $item->itemlost(3)->store;
378             C4::Circulation::LostItem( $item->itemnumber, 1 );
379
380             my $processing_fee_lines = Koha::Account::Lines->search(
381                 {
382                     borrowernumber  => $patron->id,
383                     itemnumber      => $item->itemnumber,
384                     debit_type_code => 'PROCESSING'
385                 }
386             );
387             is( $processing_fee_lines->count,
388                 1, 'Only one processing fee produced' );
389             my $processing_fee_line = $processing_fee_lines->next;
390             is( $processing_fee_line->amount + 0,
391                 $processfee_amount,
392                 'The right PROCESSING amount is generated' );
393             is( $processing_fee_line->amountoutstanding + 0,
394                 $processfee_amount,
395                 'The right PROCESSING amountoutstanding is generated' );
396
397             my $lost_fee_lines = Koha::Account::Lines->search(
398                 {
399                     borrowernumber  => $patron->id,
400                     itemnumber      => $item->itemnumber,
401                     debit_type_code => 'LOST'
402                 }
403             );
404             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
405             my $lost_fee_line = $lost_fee_lines->next;
406             is( $lost_fee_line->amount + 0,
407                 $replacement_amount, 'The right LOST amount is generated' );
408             is( $lost_fee_line->amountoutstanding + 0,
409                 $replacement_amount,
410                 'The right LOST amountountstanding is generated' );
411
412             # Simulate item marked as found
413             $item->itemlost(0)->store;
414             is( $item->{_refunded}, 1, 'Refund triggered' );
415
416             my $credit_return = Koha::Account::Lines->search(
417                 {
418                     itemnumber       => $item->itemnumber,
419                     credit_type_code => 'LOST_FOUND'
420                 },
421                 { rows => 1 }
422             )->single;
423
424             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
425             is( $credit_return->amount + 0,
426                 -99.00,
427                 'The account line of type LOST_FOUND has an amount of -99' );
428             is(
429                 $credit_return->amountoutstanding + 0,
430                 0,
431 'The account line of type LOST_FOUND has an amountoutstanding of 0'
432             );
433
434             $lost_fee_line->discard_changes;
435             is( $lost_fee_line->amountoutstanding + 0,
436                 0, 'Lost fee has no outstanding amount' );
437             is( $lost_fee_line->debit_type_code,
438                 'LOST', 'Lost fee now still has account type of LOST' );
439             is( $lost_fee_line->status, 'FOUND',
440                 "Lost fee now has account status of FOUND" );
441
442             is( $patron->account->balance,
443                 20, 'The patron balance is 20, still owes the processing fee' );
444         };
445
446         subtest
447           'Test with partial payement and write off, and remaining debt' =>
448           sub {
449
450             plan tests => 17;
451
452             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
453             my $item = $builder->build_sample_item(
454                 {
455                     biblionumber     => $biblio->biblionumber,
456                     library          => $library->branchcode,
457                     replacementprice => $replacement_amount,
458                     itype            => $item_type->itemtype
459                 }
460             );
461
462             my $issue =
463               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
464
465             # Simulate item marked as lost
466             $item->itemlost(1)->store;
467             C4::Circulation::LostItem( $item->itemnumber, 1 );
468
469             my $processing_fee_lines = Koha::Account::Lines->search(
470                 {
471                     borrowernumber  => $patron->id,
472                     itemnumber      => $item->itemnumber,
473                     debit_type_code => 'PROCESSING'
474                 }
475             );
476             is( $processing_fee_lines->count,
477                 1, 'Only one processing fee produced' );
478             my $processing_fee_line = $processing_fee_lines->next;
479             is( $processing_fee_line->amount + 0,
480                 $processfee_amount,
481                 'The right PROCESSING amount is generated' );
482             is( $processing_fee_line->amountoutstanding + 0,
483                 $processfee_amount,
484                 'The right PROCESSING amountoutstanding is generated' );
485
486             my $lost_fee_lines = Koha::Account::Lines->search(
487                 {
488                     borrowernumber  => $patron->id,
489                     itemnumber      => $item->itemnumber,
490                     debit_type_code => 'LOST'
491                 }
492             );
493             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
494             my $lost_fee_line = $lost_fee_lines->next;
495             is( $lost_fee_line->amount + 0,
496                 $replacement_amount, 'The right LOST amount is generated' );
497             is( $lost_fee_line->amountoutstanding + 0,
498                 $replacement_amount,
499                 'The right LOST amountountstanding is generated' );
500
501             my $account = $patron->account;
502             is(
503                 $account->balance,
504                 $processfee_amount + $replacement_amount,
505                 'Balance is PROCESSING + L'
506             );
507
508             # Partially pay fee
509             my $payment_amount = 27;
510             my $payment        = $account->add_credit(
511                 {
512                     amount    => $payment_amount,
513                     type      => 'PAYMENT',
514                     interface => 'test',
515                 }
516             );
517
518             $payment->apply(
519                 { debits => [$lost_fee_line], offset_type => 'Payment' } );
520
521             # Partially write off fee
522             my $write_off_amount = 25;
523             my $write_off        = $account->add_credit(
524                 {
525                     amount    => $write_off_amount,
526                     type      => 'WRITEOFF',
527                     interface => 'test',
528                 }
529             );
530             $write_off->apply(
531                 { debits => [$lost_fee_line], offset_type => 'Writeoff' } );
532
533             is(
534                 $account->balance,
535                 $processfee_amount +
536                   $replacement_amount -
537                   $payment_amount -
538                   $write_off_amount,
539                 'Payment and write off applied'
540             );
541
542             # Store the amountoutstanding value
543             $lost_fee_line->discard_changes;
544             my $outstanding = $lost_fee_line->amountoutstanding;
545
546             # Simulate item marked as found
547             $item->itemlost(0)->store;
548             is( $item->{_refunded}, 1, 'Refund triggered' );
549
550             my $credit_return = Koha::Account::Lines->search(
551                 {
552                     itemnumber       => $item->itemnumber,
553                     credit_type_code => 'LOST_FOUND'
554                 },
555                 { rows => 1 }
556             )->single;
557
558             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
559
560             is(
561                 $account->balance,
562                 $processfee_amount - $payment_amount,
563                 'Balance is PROCESSING - PAYMENT (LOST_FOUND)'
564             );
565
566             $lost_fee_line->discard_changes;
567             is( $lost_fee_line->amountoutstanding + 0,
568                 0, 'Lost fee has no outstanding amount' );
569             is( $lost_fee_line->debit_type_code,
570                 'LOST', 'Lost fee now still has account type of LOST' );
571             is( $lost_fee_line->status, 'FOUND',
572                 "Lost fee now has account status of FOUND" );
573
574             is(
575                 $credit_return->amount + 0,
576                 ( $payment_amount + $outstanding ) * -1,
577 'The account line of type LOST_FOUND has an amount equal to the payment + outstanding'
578             );
579             is(
580                 $credit_return->amountoutstanding + 0,
581                 $payment_amount * -1,
582 'The account line of type LOST_FOUND has an amountoutstanding equal to the payment'
583             );
584
585             is(
586                 $account->balance,
587                 $processfee_amount - $payment_amount,
588 'The patron balance is the difference between the PROCESSING and the credit'
589             );
590           };
591
592         subtest 'Partial payment, existing debits and AccountAutoReconcile' =>
593           sub {
594
595             plan tests => 10;
596
597             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
598             my $barcode            = 'KD123456793';
599             my $replacement_amount = 100;
600             my $processfee_amount  = 20;
601
602             my $item_type = $builder->build_object(
603                 {
604                     class => 'Koha::ItemTypes',
605                     value => {
606                         notforloan         => undef,
607                         rentalcharge       => 0,
608                         defaultreplacecost => undef,
609                         processfee         => 0,
610                         rentalcharge_daily => 0,
611                     }
612                 }
613             );
614             my $item = Koha::Item->new(
615                 {
616                     biblionumber     => $biblio->biblionumber,
617                     homebranch       => $library->branchcode,
618                     holdingbranch    => $library->branchcode,
619                     barcode          => $barcode,
620                     replacementprice => $replacement_amount,
621                     itype            => $item_type->itemtype
622                 },
623             )->store;
624
625             my $issue =
626               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
627
628             # Simulate item marked as lost
629             $item->itemlost(1)->store;
630             C4::Circulation::LostItem( $item->itemnumber, 1 );
631
632             my $lost_fee_lines = Koha::Account::Lines->search(
633                 {
634                     borrowernumber  => $patron->id,
635                     itemnumber      => $item->itemnumber,
636                     debit_type_code => 'LOST'
637                 }
638             );
639             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
640             my $lost_fee_line = $lost_fee_lines->next;
641             is( $lost_fee_line->amount + 0,
642                 $replacement_amount, 'The right LOST amount is generated' );
643             is( $lost_fee_line->amountoutstanding + 0,
644                 $replacement_amount,
645                 'The right LOST amountountstanding is generated' );
646
647             my $account = $patron->account;
648             is( $account->balance, $replacement_amount, 'Balance is L' );
649
650             # Partially pay fee
651             my $payment_amount = 27;
652             my $payment        = $account->add_credit(
653                 {
654                     amount    => $payment_amount,
655                     type      => 'PAYMENT',
656                     interface => 'test',
657                 }
658             );
659             $payment->apply(
660                 { debits => [$lost_fee_line], offset_type => 'Payment' } );
661
662             is(
663                 $account->balance,
664                 $replacement_amount - $payment_amount,
665                 'Payment applied'
666             );
667
668             my $manual_debit_amount = 80;
669             $account->add_debit(
670                 {
671                     amount    => $manual_debit_amount,
672                     type      => 'OVERDUE',
673                     interface => 'test'
674                 }
675             );
676
677             is(
678                 $account->balance,
679                 $manual_debit_amount + $replacement_amount - $payment_amount,
680                 'Manual debit applied'
681             );
682
683             t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 1 );
684
685             # Simulate item marked as found
686             $item->itemlost(0)->store;
687             is( $item->{_refunded}, 1, 'Refund triggered' );
688
689             my $credit_return = Koha::Account::Lines->search(
690                 {
691                     itemnumber       => $item->itemnumber,
692                     credit_type_code => 'LOST_FOUND'
693                 },
694                 { rows => 1 }
695             )->single;
696
697             ok( $credit_return, 'An account line of type LOST_FOUND is added' );
698
699             is(
700                 $account->balance,
701                 $manual_debit_amount - $payment_amount,
702                 'Balance is PROCESSING - payment (LOST_FOUND)'
703             );
704
705             my $manual_debit = Koha::Account::Lines->search(
706                 {
707                     borrowernumber  => $patron->id,
708                     debit_type_code => 'OVERDUE',
709                     status          => 'UNRETURNED'
710                 }
711             )->next;
712             is(
713                 $manual_debit->amountoutstanding + 0,
714                 $manual_debit_amount - $payment_amount,
715                 'reconcile_balance was called'
716             );
717           };
718
719         subtest 'Patron deleted' => sub {
720             plan tests => 1;
721
722             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
723             my $barcode            = 'KD123456794';
724             my $replacement_amount = 100;
725             my $processfee_amount  = 20;
726
727             my $item_type = $builder->build_object(
728                 {
729                     class => 'Koha::ItemTypes',
730                     value => {
731                         notforloan         => undef,
732                         rentalcharge       => 0,
733                         defaultreplacecost => undef,
734                         processfee         => 0,
735                         rentalcharge_daily => 0,
736                     }
737                 }
738             );
739             my $item = Koha::Item->new(
740                 {
741                     biblionumber     => $biblio->biblionumber,
742                     homebranch       => $library->branchcode,
743                     holdingbranch    => $library->branchcode,
744                     barcode          => $barcode,
745                     replacementprice => $replacement_amount,
746                     itype            => $item_type->itemtype
747                 },
748             )->store;
749
750             my $issue =
751               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
752
753             # Simulate item marked as lost
754             $item->itemlost(1)->store;
755             C4::Circulation::LostItem( $item->itemnumber, 1 );
756
757             $issue->delete();
758             $patron->delete();
759
760             # Simulate item marked as found
761             $item->itemlost(0)->store;
762             is( $item->{_refunded}, undef, 'No refund triggered' );
763
764         };
765
766         subtest 'restore fine | no overdue' => sub {
767
768             plan tests => 8;
769
770             my $manager =
771               $builder->build_object( { class => "Koha::Patrons" } );
772             t::lib::Mocks::mock_userenv(
773                 { patron => $manager, branchcode => $manager->branchcode } );
774
775             # Set lostreturn_policy to 'restore' for tests
776             my $specific_rule_restore = $builder->build(
777                 {
778                     source => 'CirculationRule',
779                     value  => {
780                         branchcode   => $manager->branchcode,
781                         categorycode => undef,
782                         itemtype     => undef,
783                         rule_name    => 'lostreturn',
784                         rule_value   => 'restore'
785                     }
786                 }
787             );
788
789             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
790
791             my $item = $builder->build_sample_item(
792                 {
793                     biblionumber     => $biblio->biblionumber,
794                     library          => $library->branchcode,
795                     replacementprice => $replacement_amount,
796                     itype            => $item_type->itemtype
797                 }
798             );
799
800             my $issue =
801               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
802
803             # Simulate item marked as lost
804             $item->itemlost(1)->store;
805             C4::Circulation::LostItem( $item->itemnumber, 1 );
806
807             my $processing_fee_lines = Koha::Account::Lines->search(
808                 {
809                     borrowernumber  => $patron->id,
810                     itemnumber      => $item->itemnumber,
811                     debit_type_code => 'PROCESSING'
812                 }
813             );
814             is( $processing_fee_lines->count,
815                 1, 'Only one processing fee produced' );
816             my $processing_fee_line = $processing_fee_lines->next;
817             is( $processing_fee_line->amount + 0,
818                 $processfee_amount,
819                 'The right PROCESSING amount is generated' );
820             is( $processing_fee_line->amountoutstanding + 0,
821                 $processfee_amount,
822                 'The right PROCESSING amountoutstanding is generated' );
823
824             my $lost_fee_lines = Koha::Account::Lines->search(
825                 {
826                     borrowernumber  => $patron->id,
827                     itemnumber      => $item->itemnumber,
828                     debit_type_code => 'LOST'
829                 }
830             );
831             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
832             my $lost_fee_line = $lost_fee_lines->next;
833             is( $lost_fee_line->amount + 0,
834                 $replacement_amount, 'The right LOST amount is generated' );
835             is( $lost_fee_line->amountoutstanding + 0,
836                 $replacement_amount,
837                 'The right LOST amountountstanding is generated' );
838
839             my $account = $patron->account;
840             my $debts   = $account->outstanding_debits;
841
842             # Pay off the debt
843             my $credit = $account->add_credit(
844                 {
845                     amount    => $account->balance,
846                     type      => 'PAYMENT',
847                     interface => 'test',
848                 }
849             );
850             $credit->apply(
851                 { debits => [ $debts->as_list ], offset_type => 'Payment' } );
852
853             # Simulate item marked as found
854             $item->itemlost(0)->store;
855             is( $item->{_refunded}, 1, 'Refund triggered' );
856             is( $item->{_restored}, undef, 'Restore not triggered when there is no overdue fine found' );
857         };
858
859         subtest 'restore fine | unforgiven overdue' => sub {
860
861             plan tests => 10;
862
863             # Set lostreturn_policy to 'restore' for tests
864             my $manager =
865               $builder->build_object( { class => "Koha::Patrons" } );
866             t::lib::Mocks::mock_userenv(
867                 { patron => $manager, branchcode => $manager->branchcode } );
868             my $specific_rule_restore = $builder->build(
869                 {
870                     source => 'CirculationRule',
871                     value  => {
872                         branchcode   => $manager->branchcode,
873                         categorycode => undef,
874                         itemtype     => undef,
875                         rule_name    => 'lostreturn',
876                         rule_value   => 'restore'
877                     }
878                 }
879             );
880
881             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
882
883             my $item = $builder->build_sample_item(
884                 {
885                     biblionumber     => $biblio->biblionumber,
886                     library          => $library->branchcode,
887                     replacementprice => $replacement_amount,
888                     itype            => $item_type->itemtype
889                 }
890             );
891
892             my $issue =
893               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
894
895             # Simulate item marked as lost
896             $item->itemlost(1)->store;
897             C4::Circulation::LostItem( $item->itemnumber, 1 );
898
899             my $processing_fee_lines = Koha::Account::Lines->search(
900                 {
901                     borrowernumber  => $patron->id,
902                     itemnumber      => $item->itemnumber,
903                     debit_type_code => 'PROCESSING'
904                 }
905             );
906             is( $processing_fee_lines->count,
907                 1, 'Only one processing fee produced' );
908             my $processing_fee_line = $processing_fee_lines->next;
909             is( $processing_fee_line->amount + 0,
910                 $processfee_amount,
911                 'The right PROCESSING amount is generated' );
912             is( $processing_fee_line->amountoutstanding + 0,
913                 $processfee_amount,
914                 'The right PROCESSING amountoutstanding is generated' );
915
916             my $lost_fee_lines = Koha::Account::Lines->search(
917                 {
918                     borrowernumber  => $patron->id,
919                     itemnumber      => $item->itemnumber,
920                     debit_type_code => 'LOST'
921                 }
922             );
923             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
924             my $lost_fee_line = $lost_fee_lines->next;
925             is( $lost_fee_line->amount + 0,
926                 $replacement_amount, 'The right LOST amount is generated' );
927             is( $lost_fee_line->amountoutstanding + 0,
928                 $replacement_amount,
929                 'The right LOST amountountstanding is generated' );
930
931             my $account = $patron->account;
932             my $debts   = $account->outstanding_debits;
933
934             # Pay off the debt
935             my $credit = $account->add_credit(
936                 {
937                     amount    => $account->balance,
938                     type      => 'PAYMENT',
939                     interface => 'test',
940                 }
941             );
942             $credit->apply(
943                 { debits => [ $debts->as_list ], offset_type => 'Payment' } );
944
945             # Fine not forgiven
946             my $overdue = $account->add_debit(
947                 {
948                     amount     => 30.00,
949                     user_id    => $manager->borrowernumber,
950                     library_id => $library->branchcode,
951                     interface  => 'test',
952                     item_id    => $item->itemnumber,
953                     type       => 'OVERDUE',
954                 }
955             )->store();
956             $overdue->status('LOST')->store();
957             $overdue->discard_changes;
958             is( $overdue->status, 'LOST',
959                 'Overdue status set to LOST' );
960
961             # Simulate item marked as found
962             $item->itemlost(0)->store;
963             is( $item->{_refunded}, 1, 'Refund triggered' );
964             is( $item->{_restored}, undef, 'Restore not triggered when overdue was not forgiven' );
965             $overdue->discard_changes;
966             is( $overdue->status, 'FOUND',
967                 'Overdue status updated to FOUND' );
968         };
969
970         subtest 'restore fine | forgiven overdue' => sub {
971
972             plan tests => 12;
973
974             # Set lostreturn_policy to 'restore' for tests
975             my $manager =
976               $builder->build_object( { class => "Koha::Patrons" } );
977             t::lib::Mocks::mock_userenv(
978                 { patron => $manager, branchcode => $manager->branchcode } );
979             my $specific_rule_restore = $builder->build(
980                 {
981                     source => 'CirculationRule',
982                     value  => {
983                         branchcode   => $manager->branchcode,
984                         categorycode => undef,
985                         itemtype     => undef,
986                         rule_name    => 'lostreturn',
987                         rule_value   => 'restore'
988                     }
989                 }
990             );
991
992             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
993
994             my $item = $builder->build_sample_item(
995                 {
996                     biblionumber     => $biblio->biblionumber,
997                     library          => $library->branchcode,
998                     replacementprice => $replacement_amount,
999                     itype            => $item_type->itemtype
1000                 }
1001             );
1002
1003             my $issue =
1004               C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
1005
1006             # Simulate item marked as lost
1007             $item->itemlost(1)->store;
1008             C4::Circulation::LostItem( $item->itemnumber, 1 );
1009
1010             my $processing_fee_lines = Koha::Account::Lines->search(
1011                 {
1012                     borrowernumber  => $patron->id,
1013                     itemnumber      => $item->itemnumber,
1014                     debit_type_code => 'PROCESSING'
1015                 }
1016             );
1017             is( $processing_fee_lines->count,
1018                 1, 'Only one processing fee produced' );
1019             my $processing_fee_line = $processing_fee_lines->next;
1020             is( $processing_fee_line->amount + 0,
1021                 $processfee_amount,
1022                 'The right PROCESSING amount is generated' );
1023             is( $processing_fee_line->amountoutstanding + 0,
1024                 $processfee_amount,
1025                 'The right PROCESSING amountoutstanding is generated' );
1026
1027             my $lost_fee_lines = Koha::Account::Lines->search(
1028                 {
1029                     borrowernumber  => $patron->id,
1030                     itemnumber      => $item->itemnumber,
1031                     debit_type_code => 'LOST'
1032                 }
1033             );
1034             is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' );
1035             my $lost_fee_line = $lost_fee_lines->next;
1036             is( $lost_fee_line->amount + 0,
1037                 $replacement_amount, 'The right LOST amount is generated' );
1038             is( $lost_fee_line->amountoutstanding + 0,
1039                 $replacement_amount,
1040                 'The right LOST amountountstanding is generated' );
1041
1042             my $account = $patron->account;
1043             my $debts   = $account->outstanding_debits;
1044
1045             # Pay off the debt
1046             my $credit = $account->add_credit(
1047                 {
1048                     amount    => $account->balance,
1049                     type      => 'PAYMENT',
1050                     interface => 'test',
1051                 }
1052             );
1053             $credit->apply(
1054                 { debits => [ $debts->as_list ], offset_type => 'Payment' } );
1055
1056             # Add overdue
1057             my $overdue = $account->add_debit(
1058                 {
1059                     amount     => 30.00,
1060                     user_id    => $manager->borrowernumber,
1061                     library_id => $library->branchcode,
1062                     interface  => 'test',
1063                     item_id    => $item->itemnumber,
1064                     type       => 'OVERDUE',
1065                 }
1066             )->store();
1067             $overdue->status('LOST')->store();
1068             is( $overdue->status, 'LOST',
1069                 'Overdue status set to LOST' );
1070
1071             t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 0 );
1072
1073             # Forgive fine
1074             $credit = $account->add_credit(
1075                 {
1076                     amount     => 30.00,
1077                     user_id    => $manager->borrowernumber,
1078                     library_id => $library->branchcode,
1079                     interface  => 'test',
1080                     type       => 'FORGIVEN',
1081                     item_id    => $item->itemnumber
1082                 }
1083             );
1084             $credit->apply(
1085                 { debits => [$overdue], offset_type => 'Forgiven' } );
1086
1087             # Simulate item marked as found
1088             $item->itemlost(0)->store;
1089             is( $item->{_refunded}, 1, 'Refund triggered' );
1090             is( $item->{_restored}, 1, 'Restore triggered when overdue was forgiven' );
1091             $overdue->discard_changes;
1092             is( $overdue->status, 'FOUND', 'Overdue status updated to FOUND' );
1093             is( $overdue->amountoutstanding, $overdue->amount, 'Overdue outstanding has been restored' );
1094             $credit->discard_changes;
1095             is( $credit->status, 'VOID', 'Overdue Forgival has been marked as VOID');
1096         };
1097
1098         subtest 'Continue when userenv is not set' => sub {
1099             plan tests => 1;
1100
1101             my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1102             my $barcode            = 'KD123456795';
1103             my $replacement_amount = 100;
1104             my $processfee_amount  = 20;
1105
1106             my $item_type = $builder->build_object(
1107                 {
1108                     class => 'Koha::ItemTypes',
1109                     value => {
1110                         notforloan         => undef,
1111                         rentalcharge       => 0,
1112                         defaultreplacecost => undef,
1113                         processfee         => 0,
1114                         rentalcharge_daily => 0,
1115                     }
1116                 }
1117             );
1118             my $item = $builder->build_sample_item(
1119                 {
1120                     biblionumber     => $biblio->biblionumber,
1121                     homebranch       => $library->branchcode,
1122                     holdingbranch    => $library->branchcode,
1123                     barcode          => $barcode,
1124                     replacementprice => $replacement_amount,
1125                     itype            => $item_type->itemtype
1126                 }
1127             );
1128
1129             my $issue =
1130               C4::Circulation::AddIssue( $patron->unblessed, $barcode );
1131
1132             # Simulate item marked as lost
1133             $item->itemlost(1)->store;
1134             C4::Circulation::LostItem( $item->itemnumber, 1 );
1135
1136             # Unset the userenv
1137             C4::Context->_new_userenv(undef);
1138
1139             # Simluate item marked as found
1140             $item->itemlost(0)->store;
1141             is( $item->{_refunded}, 1, 'No refund triggered' );
1142
1143         };
1144     };
1145
1146     subtest 'log_action' => sub {
1147         plan tests => 2;
1148         t::lib::Mocks::mock_preference( 'CataloguingLog', 1 );
1149
1150         my $item = Koha::Item->new(
1151             {
1152                 homebranch    => $library->{branchcode},
1153                 holdingbranch => $library->{branchcode},
1154                 biblionumber  => $biblio->biblionumber,
1155                 location      => 'my_loc',
1156             }
1157         )->store;
1158         is(
1159             Koha::ActionLogs->search(
1160                 {
1161                     module => 'CATALOGUING',
1162                     action => 'ADD',
1163                     object => $item->itemnumber,
1164                     info   => 'item'
1165                 }
1166             )->count,
1167             1,
1168             "Item creation logged"
1169         );
1170
1171         $item->location('another_loc')->store;
1172         is(
1173             Koha::ActionLogs->search(
1174                 {
1175                     module => 'CATALOGUING',
1176                     action => 'MODIFY',
1177                     object => $item->itemnumber
1178                 }
1179             )->count,
1180             1,
1181             "Item modification logged"
1182         );
1183     };
1184 };
1185
1186 subtest 'get_transfer' => sub {
1187     plan tests => 3;
1188
1189     my $transfer = $new_item_1->get_transfer();
1190     is( $transfer, undef, 'Koha::Item->get_transfer should return undef if the item is not in transit' );
1191
1192     my $library_to = $builder->build( { source => 'Branch' } );
1193
1194     C4::Circulation::transferbook({
1195         from_branch => $new_item_1->holdingbranch,
1196         to_branch => $library_to->{branchcode},
1197         barcode => $new_item_1->barcode,
1198     });
1199
1200     $transfer = $new_item_1->get_transfer();
1201     is( ref($transfer), 'Koha::Item::Transfer', 'Koha::Item->get_transfer should return a Koha::Item::Transfers object' );
1202
1203     is( $transfer->itemnumber, $new_item_1->itemnumber, 'Koha::Item->get_transfer should return a valid Koha::Item::Transfers object' );
1204 };
1205
1206 subtest 'holds' => sub {
1207     plan tests => 5;
1208
1209     my $biblio = $builder->build_sample_biblio();
1210     my $item   = $builder->build_sample_item({
1211         biblionumber => $biblio->biblionumber,
1212     });
1213     is($item->holds->count, 0, "Nothing returned if no holds");
1214     my $hold1 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'T' }});
1215     my $hold2 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'W' }});
1216     my $hold3 = $builder->build({ source => 'Reserve', value => { itemnumber=>$item->itemnumber, found => 'W' }});
1217
1218     is($item->holds()->count,3,"Three holds found");
1219     is($item->holds({found => 'W'})->count,2,"Two waiting holds found");
1220     is_deeply($item->holds({found => 'T'})->next->unblessed,$hold1,"Found transit holds matches the hold");
1221     is($item->holds({found => undef})->count, 0,"Nothing returned if no matching holds");
1222 };
1223
1224 subtest 'biblio' => sub {
1225     plan tests => 2;
1226
1227     my $biblio = $retrieved_item_1->biblio;
1228     is( ref( $biblio ), 'Koha::Biblio', 'Koha::Item->biblio should return a Koha::Biblio' );
1229     is( $biblio->biblionumber, $retrieved_item_1->biblionumber, 'Koha::Item->biblio should return the correct biblio' );
1230 };
1231
1232 subtest 'biblioitem' => sub {
1233     plan tests => 2;
1234
1235     my $biblioitem = $retrieved_item_1->biblioitem;
1236     is( ref( $biblioitem ), 'Koha::Biblioitem', 'Koha::Item->biblioitem should return a Koha::Biblioitem' );
1237     is( $biblioitem->biblionumber, $retrieved_item_1->biblionumber, 'Koha::Item->biblioitem should return the correct biblioitem' );
1238 };
1239
1240 # Restore userenv
1241 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
1242 subtest 'checkout' => sub {
1243     plan tests => 5;
1244     my $item = Koha::Items->find( $new_item_1->itemnumber );
1245     # No checkout yet
1246     my $checkout = $item->checkout;
1247     is( $checkout, undef, 'Koha::Item->checkout should return undef if there is no current checkout on this item' );
1248
1249     # Add a checkout
1250     my $patron = $builder->build({ source => 'Borrower' });
1251     C4::Circulation::AddIssue( $patron, $item->barcode );
1252     $checkout = $retrieved_item_1->checkout;
1253     is( ref( $checkout ), 'Koha::Checkout', 'Koha::Item->checkout should return a Koha::Checkout' );
1254     is( $checkout->itemnumber, $item->itemnumber, 'Koha::Item->checkout should return the correct checkout' );
1255     is( $checkout->borrowernumber, $patron->{borrowernumber}, 'Koha::Item->checkout should return the correct checkout' );
1256
1257     # Do the return
1258     C4::Circulation::AddReturn( $item->barcode );
1259
1260     # There is no more checkout on this item, making sure it will not return old checkouts
1261     $checkout = $item->checkout;
1262     is( $checkout, undef, 'Koha::Item->checkout should return undef if there is no *current* checkout on this item' );
1263 };
1264
1265 subtest 'can_be_transferred' => sub {
1266     plan tests => 5;
1267
1268     t::lib::Mocks::mock_preference('UseBranchTransferLimits', 1);
1269     t::lib::Mocks::mock_preference('BranchTransferLimitsType', 'itemtype');
1270
1271     my $biblio   = $builder->build_sample_biblio();
1272     my $library1 = $builder->build_object( { class => 'Koha::Libraries' } );
1273     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
1274     my $item  = $builder->build_sample_item({
1275         biblionumber     => $biblio->biblionumber,
1276         homebranch       => $library1->branchcode,
1277         holdingbranch    => $library1->branchcode,
1278     });
1279
1280     is(Koha::Item::Transfer::Limits->search({
1281         fromBranch => $library1->branchcode,
1282         toBranch => $library2->branchcode,
1283     })->count, 0, 'There are no transfer limits between libraries.');
1284     ok($item->can_be_transferred({ to => $library2 }),
1285        'Item can be transferred between libraries.');
1286
1287     my $limit = Koha::Item::Transfer::Limit->new({
1288         fromBranch => $library1->branchcode,
1289         toBranch => $library2->branchcode,
1290         itemtype => $item->effective_itemtype,
1291     })->store;
1292     is(Koha::Item::Transfer::Limits->search({
1293         fromBranch => $library1->branchcode,
1294         toBranch => $library2->branchcode,
1295     })->count, 1, 'Given we have added a transfer limit,');
1296     is($item->can_be_transferred({ to => $library2 }), 0,
1297        'Item can no longer be transferred between libraries.');
1298     is($item->can_be_transferred({ to => $library2, from => $library1 }), 0,
1299        'We get the same result also if we pass the from-library parameter.');
1300 };
1301
1302 subtest 'filter_by_for_loan' => sub {
1303     plan tests => 3;
1304
1305     my $biblio = $builder->build_sample_biblio;
1306     is( $biblio->items->filter_by_for_loan->count, 0, 'no item yet' );
1307     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, notforloan => 1 } );
1308     is( $biblio->items->filter_by_for_loan->count, 0, 'no item for loan' );
1309     $builder->build_sample_item( { biblionumber => $biblio->biblionumber, notforloan => 0 } );
1310     is( $biblio->items->filter_by_for_loan->count, 1, '1 item for loan' );
1311
1312     $biblio->delete;
1313 };
1314
1315 # Reset nb_of_items prior to testing delete
1316 $nb_of_items = Koha::Items->search->count;
1317
1318 # Test delete
1319 $retrieved_item_1->delete;
1320 is( Koha::Items->search->count, $nb_of_items - 1, 'Delete should have deleted the item' );
1321
1322 $schema->storage->txn_rollback;
1323
1324 subtest 'filter_by_visible_in_opac() tests' => sub {
1325
1326     plan tests => 11;
1327
1328     $schema->storage->txn_begin;
1329
1330     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1331     my $mocked_category = Test::MockModule->new('Koha::Patron::Category');
1332     my $exception = 1;
1333     $mocked_category->mock( 'override_hidden_items', sub {
1334         return $exception;
1335     });
1336
1337     # have a fresh biblio
1338     my $biblio = $builder->build_sample_biblio;
1339     # have two itemtypes
1340     my $itype_1 = $builder->build_object({ class => 'Koha::ItemTypes' });
1341     my $itype_2 = $builder->build_object({ class => 'Koha::ItemTypes' });
1342     # have 5 items on that biblio
1343     my $item_1 = $builder->build_sample_item(
1344         {
1345             biblionumber => $biblio->biblionumber,
1346             itemlost     => -1,
1347             itype        => $itype_1->itemtype,
1348             withdrawn    => 1,
1349             copynumber   => undef
1350         }
1351     );
1352     my $item_2 = $builder->build_sample_item(
1353         {
1354             biblionumber => $biblio->biblionumber,
1355             itemlost     => 0,
1356             itype        => $itype_2->itemtype,
1357             withdrawn    => 2,
1358             copynumber   => undef
1359         }
1360     );
1361     my $item_3 = $builder->build_sample_item(
1362         {
1363             biblionumber => $biblio->biblionumber,
1364             itemlost     => 1,
1365             itype        => $itype_1->itemtype,
1366             withdrawn    => 3,
1367             copynumber   => undef
1368         }
1369     );
1370     my $item_4 = $builder->build_sample_item(
1371         {
1372             biblionumber => $biblio->biblionumber,
1373             itemlost     => 0,
1374             itype        => $itype_2->itemtype,
1375             withdrawn    => 4,
1376             copynumber   => undef
1377         }
1378     );
1379     my $item_5 = $builder->build_sample_item(
1380         {
1381             biblionumber => $biblio->biblionumber,
1382             itemlost     => 0,
1383             itype        => $itype_1->itemtype,
1384             withdrawn    => 5,
1385             copynumber   => undef
1386         }
1387     );
1388     my $item_6 = $builder->build_sample_item(
1389         {
1390             biblionumber => $biblio->biblionumber,
1391             itemlost     => 2,
1392             itype        => $itype_1->itemtype,
1393             withdrawn    => 5,
1394             copynumber   => undef
1395         }
1396     );
1397
1398     my $rules = undef;
1399
1400     my $mocked_context = Test::MockModule->new('C4::Context');
1401     $mocked_context->mock( 'yaml_preference', sub {
1402         return $rules;
1403     });
1404
1405     t::lib::Mocks::mock_preference( 'hidelostitems', 0 );
1406     is( $biblio->items->filter_by_visible_in_opac->count,
1407         6, 'No rules passed, hidelostitems unset' );
1408
1409     is( $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1410         6, 'No rules passed, hidelostitems unset, patron exception changes nothing' );
1411
1412     $rules = { copynumber => [ 2 ] };
1413
1414     t::lib::Mocks::mock_preference( 'hidelostitems', 1 );
1415     is(
1416         $biblio->items->filter_by_visible_in_opac->count,
1417         3,
1418         'No rules passed, hidelostitems set'
1419     );
1420
1421     is(
1422         $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1423         3,
1424         'No rules passed, hidelostitems set, patron exception changes nothing'
1425     );
1426
1427     $rules = { withdrawn => [ 1, 2 ], copynumber => [ 2 ] };
1428     is(
1429         $biblio->items->filter_by_visible_in_opac->count,
1430         2,
1431         'Rules on withdrawn, hidelostitems set'
1432     );
1433
1434     is(
1435         $biblio->items->filter_by_visible_in_opac({ patron => $patron })->count,
1436         3,
1437         'hidelostitems set, rules on withdrawn but patron override passed'
1438     );
1439
1440     $rules = { itype => [ $itype_1->itemtype ], copynumber => [ 2 ] };
1441     is(
1442         $biblio->items->filter_by_visible_in_opac->count,
1443         2,
1444         'Rules on itype, hidelostitems set'
1445     );
1446
1447     $rules = { withdrawn => [ 1, 2 ], itype => [ $itype_1->itemtype ], copynumber => [ 2 ] };
1448     is(
1449         $biblio->items->filter_by_visible_in_opac->count,
1450         1,
1451         'Rules on itype and withdrawn, hidelostitems set'
1452     );
1453     is(
1454         $biblio->items->filter_by_visible_in_opac
1455           ->next->itemnumber,
1456         $item_4->itemnumber,
1457         'The right item is returned'
1458     );
1459
1460     $rules = { withdrawn => [ 1, 2 ], itype => [ $itype_2->itemtype ], copynumber => [ 2 ] };
1461     is(
1462         $biblio->items->filter_by_visible_in_opac->count,
1463         1,
1464         'Rules on itype and withdrawn, hidelostitems set'
1465     );
1466     is(
1467         $biblio->items->filter_by_visible_in_opac
1468           ->next->itemnumber,
1469         $item_5->itemnumber,
1470         'The right item is returned'
1471     );
1472
1473     $schema->storage->txn_rollback;
1474 };
1475
1476 subtest 'filter_out_lost() tests' => sub {
1477
1478     plan tests => 2;
1479
1480     $schema->storage->txn_begin;
1481
1482     # have a fresh biblio
1483     my $biblio = $builder->build_sample_biblio;
1484     # have 3 items on that biblio
1485     my $item_1 = $builder->build_sample_item(
1486         {
1487             biblionumber => $biblio->biblionumber,
1488             itemlost     => -1,
1489         }
1490     );
1491     my $item_2 = $builder->build_sample_item(
1492         {
1493             biblionumber => $biblio->biblionumber,
1494             itemlost     => 0,
1495         }
1496     );
1497     my $item_3 = $builder->build_sample_item(
1498         {
1499             biblionumber => $biblio->biblionumber,
1500             itemlost     => 1,
1501         }
1502     );
1503
1504     is( $biblio->items->filter_out_lost->next->itemnumber, $item_2->itemnumber, 'Right item returned' );
1505     is( $biblio->items->filter_out_lost->count, 1, 'Only one item is not lost' );
1506
1507     $schema->storage->txn_rollback;
1508 };
1509