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