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