Bug 27600: 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(
328         $result,
329         {
330             messages => {
331                 'NotIssued'       => $item->barcode,
332                 'WasTransfered'   => $library->branchcode,
333                 'TransferTrigger' => 'ReturnToHome'
334             }
335         },
336         "Messages show not issued and transferred"
337     );
338     is( $ci_transaction->item->destination_loc,$library->branchcode,"Item destination correctly set");
339
340     subtest 'Checkin an in transit item' => sub {
341
342         plan tests => 5;
343
344         my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
345         my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
346
347         my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {branchcode => $library_1->branchcode, }});
348         my $sip_patron = C4::SIP::ILS::Patron->new( $patron->cardnumber );
349         my $item = $builder->build_sample_item({ library => $library_1->branchcode });
350         my $sip_item   = C4::SIP::ILS::Item->new( $item->barcode );
351
352         t::lib::Mocks::mock_userenv(
353             { branchcode => $library_1->branchcode, flags => 1 } );
354
355         my $reserve = AddReserve(
356             {
357                 branchcode     => $library_1->branchcode,
358                 borrowernumber => $patron->borrowernumber,
359                 biblionumber   => $item->biblionumber,
360             }
361         );
362
363         ModReserveAffect( $item->itemnumber, $patron->borrowernumber ); # Mark waiting
364
365         my $ci_transaction = C4::SIP::ILS::Transaction::Checkin->new();
366         is( $ci_transaction->patron($sip_patron),
367             $sip_patron, "Patron assigned to transaction" );
368         is( $ci_transaction->item($sip_item),
369             $sip_item, "Item assigned to transaction" );
370
371         my $checkin = $ci_transaction->do_checkin($library_2->branchcode, C4::SIP::Sip::timestamp);
372
373         my $hold = Koha::Holds->find($reserve);
374         is( $hold->found, 'T', );
375         is( $hold->itemnumber, $item->itemnumber, );
376         is( Koha::Checkouts->search({itemnumber => $item->itemnumber})->count, 0, );
377     };
378 };
379
380 subtest do_checkout_with_holds => sub {
381     plan tests => 7;
382
383     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
384     my $patron = $builder->build_object(
385         {
386             class => 'Koha::Patrons',
387             value => {
388                 branchcode => $library->branchcode,
389             }
390         }
391     );
392     my $patron2 = $builder->build_object(
393         {
394             class => 'Koha::Patrons',
395             value => {
396                 branchcode => $library->branchcode,
397             }
398         }
399     );
400
401     t::lib::Mocks::mock_userenv(
402         { branchcode => $library->branchcode, flags => 1 } );
403
404     my $item = $builder->build_sample_item(
405         {
406             library => $library->branchcode,
407         }
408     );
409
410     my $reserve = AddReserve(
411         {
412                 branchcode     => $library->branchcode,
413                 borrowernumber => $patron2->borrowernumber,
414                 biblionumber   => $item->biblionumber,
415         }
416     );
417
418     my $sip_patron  = C4::SIP::ILS::Patron->new( $patron->cardnumber );
419     my $sip_item    = C4::SIP::ILS::Item->new( $item->barcode );
420     my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new();
421     is( $co_transaction->patron($sip_patron),
422         $sip_patron, "Patron assigned to transaction" );
423     is( $co_transaction->item($sip_item),
424         $sip_item, "Item assigned to transaction" );
425
426     # Test attached holds
427     ModReserveAffect( $item->itemnumber, $patron->borrowernumber, 0, $reserve ); # Mark waiting (W)
428     my $hold = Koha::Holds->find($reserve);
429     $co_transaction->do_checkout();
430     is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (W)');
431
432     $hold->set_transfer;
433     $co_transaction->do_checkout();
434     is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (T)');
435
436     $hold->set_processing;
437     $co_transaction->do_checkout();
438     is( $patron->checkouts->count, 0, 'Checkout was not done due to attached hold (P)');
439
440     # Test non-attached holds
441     C4::Reserves::RevertWaitingStatus({ itemnumber => $hold->itemnumber });
442     t::lib::Mocks::mock_preference('AllowItemsOnHoldCheckoutSIP', '0');
443     $co_transaction->do_checkout();
444     is( $patron->checkouts->count, 0, 'Checkout refused due to hold and AllowItemsOnHoldCheckoutSIP');
445
446     t::lib::Mocks::mock_preference('AllowItemsOnHoldCheckoutSIP', '1');
447     $co_transaction->do_checkout();
448     is( $patron->checkouts->count, 1, 'Checkout allowed due to hold and AllowItemsOnHoldCheckoutSIP');
449 };
450
451 subtest checkin_lost => sub {
452     plan tests => 2;
453
454     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
455
456     t::lib::Mocks::mock_userenv(
457         { branchcode => $library->branchcode, flags => 1 } );
458
459     my $item = $builder->build_sample_item(
460         {
461             library     => $library->branchcode,
462         }
463     );
464
465     $item->itemlost(1)->itemlost_on(dt_from_string)->store();
466
467     my $instituation = {
468         id             => $library->id,
469         implementation => "ILS",
470         policy         => {
471             checkin  => "true",
472             renewal  => "true",
473             checkout => "true",
474             timeout  => 100,
475             retries  => 5,
476         }
477     };
478     my $ils = C4::SIP::ILS->new( $instituation );
479
480     t::lib::Mocks::mock_preference('BlockReturnOfLostItems', '1');
481     my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
482     is( $circ->{screen_msg}, 'Item lost, return not allowed', "Got correct screen message" );
483
484     t::lib::Mocks::mock_preference('BlockReturnOfLostItems', '0');
485     $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
486     is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" );
487 };
488
489 subtest checkin_withdrawn => sub {
490     plan tests => 2;
491
492     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
493
494     t::lib::Mocks::mock_userenv(
495         { branchcode => $library->branchcode, flags => 1 } );
496
497     my $item = $builder->build_sample_item(
498         {
499             library     => $library->branchcode,
500         }
501     );
502
503     $item->withdrawn(1)->withdrawn_on(dt_from_string)->store();
504
505     my $instituation = {
506         id             => $library->id,
507         implementation => "ILS",
508         policy         => {
509             checkin  => "true",
510             renewal  => "true",
511             checkout => "true",
512             timeout  => 100,
513             retries  => 5,
514         }
515     };
516     my $ils = C4::SIP::ILS->new( $instituation );
517
518     t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', '1');
519     my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
520     is( $circ->{screen_msg}, 'Item withdrawn, return not allowed', "Got correct screen message" );
521
522     t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', '0');
523     $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp, undef, $library->branchcode );
524     is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" );
525 };
526
527 subtest item_circulation_status => sub {
528     plan tests => 4;
529
530     my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
531     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
532
533     my $patron = $builder->build_object(
534         {
535             class => 'Koha::Patrons',
536             value => {
537                 branchcode => $library->branchcode,
538             }
539         }
540     );
541
542     t::lib::Mocks::mock_userenv(
543         { branchcode => $library->branchcode, flags => 1 } );
544
545     my $item = $builder->build_sample_item(
546         {
547             library => $library->branchcode,
548         }
549     );
550
551     my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
552     my $status = $sip_item->sip_circulation_status;
553     is( $status, '03', "Item circulation status is available");
554
555     my $transfer = Koha::Item::Transfer->new({
556         itemnumber => $item->id,
557         datesent   => '2020-01-01',
558         frombranch => $library->branchcode,
559         tobranch   => $library2->branchcode,
560     })->store();
561
562     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
563     $status = $sip_item->sip_circulation_status;
564     is( $status, '10', "Item circulation status is in transit" );
565
566     $transfer->delete;
567
568     my $claim = Koha::Checkouts::ReturnClaim->new({
569         itemnumber     => $item->id,
570         borrowernumber => $patron->id,
571         created_by     => $patron->id,
572     })->store();
573
574     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
575     $status = $sip_item->sip_circulation_status;
576     is( $status, '11', "Item circulation status is claimed returned" );
577
578     $claim->delete;
579
580     $item->itemlost(1)->store();
581     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
582     $status = $sip_item->sip_circulation_status;
583     is( $status, '12', "Item circulation status is lost" );
584 };
585 $schema->storage->txn_rollback;