Bug 27907: Add unit test
[koha.git] / t / db_dependent / SIP / Transaction.t
1 #!/usr/bin/perl
2
3 # Tests for SIP::ILS::Transaction
4 # Current state is very rudimentary. Please help to extend it!
5
6 use Modern::Perl;
7 use Test::More tests => 12;
8
9 use Koha::Database;
10 use t::lib::TestBuilder;
11 use t::lib::Mocks;
12 use C4::SIP::ILS;
13 use C4::SIP::ILS::Patron;
14 use C4::SIP::ILS::Transaction::RenewAll;
15 use C4::SIP::ILS::Transaction::Checkout;
16 use C4::SIP::ILS::Transaction::FeePayment;
17 use C4::SIP::ILS::Transaction::Hold;
18 use C4::SIP::ILS::Transaction::Checkout;
19 use C4::SIP::ILS::Transaction::Checkin;
20
21 use C4::Reserves;
22 use Koha::CirculationRules;
23 use Koha::Item::Transfer;
24 use Koha::DateUtils qw( dt_from_string output_pref );
25
26 my $schema = Koha::Database->new->schema;
27 $schema->storage->txn_begin;
28
29 my $builder = t::lib::TestBuilder->new();
30 my $borr1 = $builder->build({ source => 'Borrower' });
31 my $card = $borr1->{cardnumber};
32 my $sip_patron = C4::SIP::ILS::Patron->new( $card );
33
34 # Create transaction RenewAll, assign patron, and run (no items)
35 my $transaction = C4::SIP::ILS::Transaction::RenewAll->new();
36 is( ref $transaction, "C4::SIP::ILS::Transaction::RenewAll", "New transaction created" );
37 is( $transaction->patron( $sip_patron ), $sip_patron, "Patron assigned to transaction" );
38 isnt( $transaction->do_renew_all, undef, "RenewAll on zero items" );
39
40 subtest fill_holds_at_checkout => sub {
41     plan tests => 6;
42
43
44     my $category = $builder->build({ source => 'Category', value => { category_type => 'A' }});
45     my $branch   = $builder->build({ source => 'Branch' });
46     my $borrower = $builder->build({ source => 'Borrower', value =>{
47         branchcode => $branch->{branchcode},
48         categorycode=>$category->{categorycode}
49         }
50     });
51     t::lib::Mocks::mock_userenv({ branchcode => $branch->{branchcode}, flags => 1 });
52
53     my $itype = $builder->build({ source => 'Itemtype', value =>{notforloan=>0} });
54     my $item1 = $builder->build_sample_item({
55         barcode       => 'barcode4test',
56         homebranch    => $branch->{branchcode},
57         holdingbranch => $branch->{branchcode},
58         itype         => $itype->{itemtype},
59         notforloan       => 0,
60     })->unblessed;
61     my $item2 = $builder->build_sample_item({
62         homebranch    => $branch->{branchcode},
63         holdingbranch => $branch->{branchcode},
64         biblionumber  => $item1->{biblionumber},
65         itype         => $itype->{itemtype},
66         notforloan       => 0,
67     })->unblessed;
68
69     Koha::CirculationRules->set_rules(
70         {
71             categorycode => $borrower->{categorycode},
72             branchcode   => $branch->{branchcode},
73             itemtype     => $itype->{itemtype},
74             rules        => {
75                 onshelfholds     => 1,
76                 reservesallowed  => 3,
77                 holds_per_record => 3,
78                 issuelength      => 5,
79                 lengthunit       => 'days',
80             }
81         }
82     );
83
84     my $reserve1 = AddReserve(
85         {
86             branchcode     => $branch->{branchcode},
87             borrowernumber => $borrower->{borrowernumber},
88             biblionumber   => $item1->{biblionumber}
89         }
90     );
91     my $reserve2 = AddReserve(
92         {
93             branchcode     => $branch->{branchcode},
94             borrowernumber => $borrower->{borrowernumber},
95             biblionumber   => $item1->{biblionumber}
96         }
97     );
98
99     my $bib = Koha::Biblios->find( $item1->{biblionumber} );
100     is( $bib->holds->count(), 2, "Bib has 2 holds");
101
102     my $sip_patron = C4::SIP::ILS::Patron->new( $borrower->{cardnumber} );
103     my $sip_item   = C4::SIP::ILS::Item->new( $item1->{barcode} );
104     my $transaction = C4::SIP::ILS::Transaction::Checkout->new();
105     is( ref $transaction, "C4::SIP::ILS::Transaction::Checkout", "New transaction created" );
106     is( $transaction->patron( $sip_patron ), $sip_patron, "Patron assigned to transaction" );
107     is( $transaction->item( $sip_item ), $sip_item, "Item assigned to transaction" );
108     my $checkout = $transaction->do_checkout();
109     use Data::Dumper; # Temporary debug statement
110     is( $bib->holds->count(), 1, "Bib has 1 holds remaining") or diag Dumper $checkout;
111
112     t::lib::Mocks::mock_preference('itemBarcodeInputFilter', 'whitespace');
113     $sip_item   = C4::SIP::ILS::Item->new( ' barcode 4 test ');
114     $transaction = C4::SIP::ILS::Transaction::Checkout->new();
115     is( $sip_item->{barcode}, $item1->{barcode}, "Item assigned to transaction" );
116 };
117
118 subtest "FeePayment->pay tests" => sub {
119
120     plan tests => 5;
121
122     # Create a borrower and add some outstanding debts to their account
123     my $patron = $builder->build( { source => 'Borrower' } );
124     my $account =
125       Koha::Account->new( { patron_id => $patron->{borrowernumber} } );
126     my $debt1 = $account->add_debit(
127         { type => 'ACCOUNT', amount => 100, interface => 'commandline' } );
128     my $debt2 = $account->add_debit(
129         { type => 'ACCOUNT', amount => 200, interface => 'commandline' } );
130
131     # Instantiate a new FeePayment transaction object
132     my $trans = C4::SIP::ILS::Transaction::FeePayment->new();
133     is(
134         ref $trans,
135         "C4::SIP::ILS::Transaction::FeePayment",
136         "New fee transaction created"
137     );
138
139     # Test the 'pay' method
140     # FIXME: pay should not require a borrowernumber
141     # (we should reach out to the transaction which should contain a patron object)
142     my $pay_type = '00';    # 00 - Cash, 01 - VISA, 02 - Creditcard
143     my $ok =
144       $trans->pay( $patron->{borrowernumber}, 100, $pay_type, $debt1->id, 0,
145         0 );
146     ok( $ok, "FeePayment transaction succeeded" );
147     $debt1->discard_changes;
148     is( $debt1->amountoutstanding + 0, 0,
149         "Debt1 was reduced to 0 as expected" );
150     my $offsets = Koha::Account::Offsets->search(
151         { debit_id => $debt1->id, credit_id => { '!=' => undef } } );
152     is( $offsets->count, 1, "FeePayment produced an offset line correctly" );
153     my $credit = $offsets->next->credit;
154     is( $credit->payment_type, 'SIP00', "Payment type was set correctly" );
155 };
156
157 subtest cancel_hold => sub {
158     plan tests => 7;
159
160     my $library = $builder->build_object ({ class => 'Koha::Libraries' });
161     my $patron = $builder->build_object(
162         {
163             class => 'Koha::Patrons',
164             value => {
165                 branchcode => $library->branchcode,
166             }
167         }
168     );
169     t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode, flags => 1 });
170
171     my $item = $builder->build_sample_item({
172         library       => $library->branchcode,
173     });
174
175     Koha::CirculationRules->set_rules(
176         {
177             categorycode => $patron->categorycode,
178             branchcode   => $library->branchcode,
179             itemtype     => $item->effective_itemtype,
180             rules        => {
181                 onshelfholds     => 1,
182                 reservesallowed  => 3,
183                 holds_per_record => 3,
184                 issuelength      => 5,
185                 lengthunit       => 'days',
186             }
187         }
188     );
189
190     my $reserve1 = AddReserve(
191         {
192             branchcode     => $library->branchcode,
193             borrowernumber => $patron->borrowernumber,
194             biblionumber   => $item->biblio->biblionumber,
195             itemnumber     => $item->itemnumber,
196         }
197     );
198     is( $item->biblio->holds->count(), 1, "Hold was placed on bib");
199     is( $item->holds->count(),1,"Hold was placed on specific item");
200
201     my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber );
202     my $sip_item   = C4::SIP::ILS::Item->new( $item->barcode );
203     my $transaction = C4::SIP::ILS::Transaction::Hold->new();
204     is( ref $transaction, "C4::SIP::ILS::Transaction::Hold", "New transaction created" );
205     is( $transaction->patron( $sip_patron ), $sip_patron, "Patron assigned to transaction" );
206     is( $transaction->item( $sip_item ), $sip_item, "Item assigned to transaction" );
207     my $hold = $transaction->drop_hold();
208     is( $item->biblio->holds->count(), 0, "Bib has 0 holds remaining");
209     is( $item->holds->count(), 0,  "Item has 0 holds remaining");
210 };
211
212 subtest do_hold => sub {
213     plan tests => 8;
214
215     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
216     my $patron_1 = $builder->build_object(
217         {
218             class => 'Koha::Patrons',
219             value => {
220                 branchcode => $library->branchcode,
221             }
222         }
223     );
224     my $patron_2 = $builder->build_object(
225         {
226             class => 'Koha::Patrons',
227             value => {
228                 branchcode   => $library->branchcode,
229                 categorycode => $patron_1->categorycode,
230             }
231         }
232     );
233
234     t::lib::Mocks::mock_userenv(
235         { branchcode => $library->branchcode, flags => 1 } );
236
237     my $item = $builder->build_sample_item(
238         {
239             library => $library->branchcode,
240         }
241     );
242
243     my $reserve1 = AddReserve(
244         {
245             branchcode     => $library->branchcode,
246             borrowernumber => $patron_1->borrowernumber,
247             biblionumber   => $item->biblio->biblionumber,
248             itemnumber     => $item->itemnumber,
249         }
250     );
251     is( $item->biblio->holds->count(), 1, "Hold was placed on bib" );
252     is( $item->holds->count(),         1, "Hold was placed on specific item" );
253
254     my $sip_patron  = C4::SIP::ILS::Patron->new( $patron_2->cardnumber );
255     my $sip_item    = C4::SIP::ILS::Item->new( $item->barcode );
256     my $transaction = C4::SIP::ILS::Transaction::Hold->new();
257     is(
258         ref $transaction,
259         "C4::SIP::ILS::Transaction::Hold",
260         "New transaction created"
261     );
262     is( $transaction->patron($sip_patron),
263         $sip_patron, "Patron assigned to transaction" );
264     is( $transaction->item($sip_item),
265         $sip_item, "Item assigned to transaction" );
266     my $hold = $transaction->do_hold();
267     is( $item->biblio->holds->count(), 2, "Bib has 2 holds" );
268
269     my $THE_hold = $patron_2->holds->next;
270     is( $THE_hold->priority, 2, 'Hold placed from SIP should have a correct priority of 2');
271     is( $THE_hold->branchcode, $patron_2->branchcode, 'Hold placed from SIP should have the branchcode set' );
272 };
273
274 subtest do_checkin => sub {
275     plan tests => 12;
276
277     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
278     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
279     my $patron = $builder->build_object(
280         {
281             class => 'Koha::Patrons',
282             value => {
283                 branchcode => $library->branchcode,
284             }
285         }
286     );
287
288     t::lib::Mocks::mock_userenv(
289         { branchcode => $library->branchcode, flags => 1 } );
290
291     my $item = $builder->build_sample_item(
292         {
293             library => $library->branchcode,
294         }
295     );
296
297
298     # Checkout
299     my $sip_patron  = C4::SIP::ILS::Patron->new( $patron->cardnumber );
300     my $sip_item    = C4::SIP::ILS::Item->new( $item->barcode );
301     my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new();
302     is( $co_transaction->patron($sip_patron),
303         $sip_patron, "Patron assigned to transaction" );
304     is( $co_transaction->item($sip_item),
305         $sip_item, "Item assigned to transaction" );
306     my $checkout = $co_transaction->do_checkout();
307     is( $patron->checkouts->count, 1, 'Checkout should have been done successfully');
308
309     # Checkin
310     my $ci_transaction = C4::SIP::ILS::Transaction::Checkin->new();
311     is( $ci_transaction->patron($sip_patron),
312         $sip_patron, "Patron assigned to transaction" );
313     is( $ci_transaction->item($sip_item),
314         $sip_item, "Item assigned to transaction" );
315
316     my $checkin = $ci_transaction->do_checkin($library->branchcode, C4::SIP::Sip::timestamp);
317     is( $patron->checkouts->count, 0, 'Checkin should have been done successfully');
318
319     # Test checkin without return date
320     $co_transaction->do_checkout;
321     is( $patron->checkouts->count, 1, 'Checkout should have been done successfully');
322     $ci_transaction->do_checkin($library->branchcode, undef);
323     is( $patron->checkouts->count, 0, 'Checkin should have been done successfully');
324
325     my $result  = $ci_transaction->do_checkin($library2->branchcode, undef);
326     is($ci_transaction->alert_type,'04',"Checkin of item no issued at another branch succeeds");
327     is_deeply($result,{ messages => { 'NotIssued' => $item->barcode, 'WasTransfered' => 1 } },"Messages show not issued and transferred");
328     is( $ci_transaction->item->destination_loc,$library->branchcode,"Item destination correctly set");
329
330     subtest 'Checkin an in transit item' => sub {
331
332         plan tests => 5;
333
334         my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
335         my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
336
337         my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {branchcode => $library_1->branchcode, }});
338         my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber );
339         my $item = $builder->build_sample_item({ library => $library_1->branchcode });
340         my $sip_item   = C4::SIP::ILS::Item->new( $item->barcode );
341
342         t::lib::Mocks::mock_userenv(
343             { branchcode => $library_1->branchcode, flags => 1 } );
344
345         my $reserve = AddReserve(
346             {
347                 branchcode     => $library_1->branchcode,
348                 borrowernumber => $patron->borrowernumber,
349                 biblionumber   => $item->biblionumber,
350             }
351         );
352
353         ModReserveAffect( $item->itemnumber, $patron->borrowernumber ); # Mark waiting
354
355         my $ci_transaction = C4::SIP::ILS::Transaction::Checkin->new();
356         is( $ci_transaction->patron($sip_patron),
357             $sip_patron, "Patron assigned to transaction" );
358         is( $ci_transaction->item($sip_item),
359             $sip_item, "Item assigned to transaction" );
360
361         my $checkin = $ci_transaction->do_checkin($library_2->branchcode, C4::SIP::Sip::timestamp);
362
363         my $hold = Koha::Holds->find($reserve);
364         is( $hold->found, 'T', );
365         is( $hold->itemnumber, $item->itemnumber, );
366         is( Koha::Checkouts->search({itemnumber => $item->itemnumber})->count, 0, );
367     };
368 };
369
370 subtest do_checkout_with_holds => sub {
371     plan tests => 7;
372
373     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
374     my $patron = $builder->build_object(
375         {
376             class => 'Koha::Patrons',
377             value => {
378                 branchcode => $library->branchcode,
379             }
380         }
381     );
382     my $patron2 = $builder->build_object(
383         {
384             class => 'Koha::Patrons',
385             value => {
386                 branchcode => $library->branchcode,
387             }
388         }
389     );
390
391     t::lib::Mocks::mock_userenv(
392         { branchcode => $library->branchcode, flags => 1 } );
393
394     my $item = $builder->build_sample_item(
395         {
396             library => $library->branchcode,
397         }
398     );
399
400     my $reserve = AddReserve(
401         {
402                 branchcode     => $library->branchcode,
403                 borrowernumber => $patron2->borrowernumber,
404                 biblionumber   => $item->biblionumber,
405         }
406     );
407
408     my $sip_patron  = C4::SIP::ILS::Patron->new( $patron->cardnumber );
409     my $sip_item    = C4::SIP::ILS::Item->new( $item->barcode );
410     my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new();
411     is( $co_transaction->patron($sip_patron),
412         $sip_patron, "Patron assigned to transaction" );
413     is( $co_transaction->item($sip_item),
414         $sip_item, "Item assigned to transaction" );
415
416     # Test attached holds
417     ModReserveAffect( $item->itemnumber, $patron->borrowernumber, 0, $reserve ); # Mark waiting (W)
418     my $hold = Koha::Holds->find($reserve);
419     $co_transaction->do_checkout();
420     is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (W)');
421
422     $hold->set_transfer;
423     $co_transaction->do_checkout();
424     is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (T)');
425
426     $hold->set_processing;
427     $co_transaction->do_checkout();
428     is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (P)');
429
430     # Test non-attached holds
431     C4::Reserves::RevertWaitingStatus({ itemnumber => $hold->itemnumber });
432     t::lib::Mocks::mock_preference('AllowItemsOnHoldCheckoutSIP', '0');
433     $co_transaction->do_checkout();
434     is( $patron->checkouts->count, 0, 'Checkout refused due to hold and AllowItemsOnHoldCheckoutSIP');
435
436     t::lib::Mocks::mock_preference('AllowItemsOnHoldCheckoutSIP', '1');
437     $co_transaction->do_checkout();
438     is( $patron->checkouts->count, 1, 'Checkout allowed due to hold and AllowItemsOnHoldCheckoutSIP');
439 };
440
441 subtest checkin_lost => sub {
442     plan tests => 2;
443
444     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
445
446     t::lib::Mocks::mock_userenv(
447         { branchcode => $library->branchcode, flags => 1 } );
448
449     my $item = $builder->build_sample_item(
450         {
451             library     => $library->branchcode,
452         }
453     );
454
455     $item->itemlost(1)->itemlost_on(dt_from_string)->store();
456
457     my $instituation = {
458         id             => $library->id,
459         implementation => "ILS",
460         policy         => {
461             checkin  => "true",
462             renewal  => "true",
463             checkout => "true",
464             timeout  => 100,
465             retries  => 5,
466         }
467     };
468     my $ils = C4::SIP::ILS->new( $instituation );
469
470     t::lib::Mocks::mock_preference('BlockReturnOfLostItems', '1');
471     my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
472     is( $circ->{screen_msg}, 'Item lost, return not allowed', "Got correct screen message" );
473
474     t::lib::Mocks::mock_preference('BlockReturnOfLostItems', '0');
475     $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
476     is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" );
477 };
478
479 subtest checkin_withdrawn => sub {
480     plan tests => 2;
481
482     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
483
484     t::lib::Mocks::mock_userenv(
485         { branchcode => $library->branchcode, flags => 1 } );
486
487     my $item = $builder->build_sample_item(
488         {
489             library     => $library->branchcode,
490         }
491     );
492
493     $item->withdrawn(1)->withdrawn_on(dt_from_string)->store();
494
495     my $instituation = {
496         id             => $library->id,
497         implementation => "ILS",
498         policy         => {
499             checkin  => "true",
500             renewal  => "true",
501             checkout => "true",
502             timeout  => 100,
503             retries  => 5,
504         }
505     };
506     my $ils = C4::SIP::ILS->new( $instituation );
507
508     t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', '1');
509     my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
510     is( $circ->{screen_msg}, 'Item withdrawn, return not allowed', "Got correct screen message" );
511
512     t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', '0');
513     $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
514     is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" );
515 };
516
517 subtest item_circulation_status => sub {
518     plan tests => 6;
519
520     my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
521     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
522
523     my $patron = $builder->build_object(
524         {
525             class => 'Koha::Patrons',
526             value => {
527                 branchcode => $library->branchcode,
528             }
529         }
530     );
531
532     t::lib::Mocks::mock_userenv(
533         { branchcode => $library->branchcode, flags => 1 } );
534
535     my $item = $builder->build_sample_item(
536         {
537             library => $library->branchcode,
538         }
539     );
540
541     my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
542     my $status = $sip_item->sip_circulation_status;
543     is( $status, '03', "Item circulation status is available");
544
545     my $transfer = Koha::Item::Transfer->new({
546         itemnumber => $item->id,
547         datesent   => '2020-01-01',
548         frombranch => $library->branchcode,
549         tobranch   => $library2->branchcode,
550     })->store();
551
552     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
553     $status = $sip_item->sip_circulation_status;
554     is( $status, '10', "Item circulation status is in transit" );
555
556     $transfer->delete;
557
558     my $claim = Koha::Checkouts::ReturnClaim->new({
559         itemnumber     => $item->id,
560         borrowernumber => $patron->id,
561         created_by     => $patron->id,
562     })->store();
563
564     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
565     $status = $sip_item->sip_circulation_status;
566     is( $status, '11', "Item circulation status is claimed returned" );
567
568     $claim->delete;
569
570     $item->itemlost(1)->store();
571     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
572     $status = $sip_item->sip_circulation_status;
573     is( $status, '12', "Item circulation status is lost" );
574     $item->itemlost(0)->store();
575
576     my $location = $item->location;
577     $item->location("CART")->store();
578     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
579     $status = $sip_item->sip_circulation_status;
580     is( $status, '09', "Item circulation status is waiting to be re-shelved" );
581     $item->location($location)->store();
582
583     my $nfl = $item->notforloan;
584     $item->notforloan(-1)->store();
585     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
586     $status = $sip_item->sip_circulation_status;
587     is( $status, '02', "Item circulation status is on order" );
588     $item->notforloan($nfl)->store();
589 };
590 $schema->storage->txn_rollback;