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