Bug 25348: Add support for circulation status 12 ( lost )
[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 => 11;
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 => 8;
276
277     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
278     my $patron = $builder->build_object(
279         {
280             class => 'Koha::Patrons',
281             value => {
282                 branchcode => $library->branchcode,
283             }
284         }
285     );
286
287     t::lib::Mocks::mock_userenv(
288         { branchcode => $library->branchcode, flags => 1 } );
289
290     my $item = $builder->build_sample_item(
291         {
292             library => $library->branchcode,
293         }
294     );
295
296
297     # Checkout
298     my $sip_patron  = C4::SIP::ILS::Patron->new( $patron->cardnumber );
299     my $sip_item    = C4::SIP::ILS::Item->new( $item->barcode );
300     my $co_transaction = C4::SIP::ILS::Transaction::Checkout->new();
301     is( $co_transaction->patron($sip_patron),
302         $sip_patron, "Patron assigned to transaction" );
303     is( $co_transaction->item($sip_item),
304         $sip_item, "Item assigned to transaction" );
305     my $checkout = $co_transaction->do_checkout();
306     is( $patron->checkouts->count, 1, 'Checkout should have been done successfully');
307
308     # Checkin
309     my $ci_transaction = C4::SIP::ILS::Transaction::Checkin->new();
310     is( $ci_transaction->patron($sip_patron),
311         $sip_patron, "Patron assigned to transaction" );
312     is( $ci_transaction->item($sip_item),
313         $sip_item, "Item assigned to transaction" );
314
315     my $checkin = $ci_transaction->do_checkin($library->branchcode, C4::SIP::Sip::timestamp);
316     is( $patron->checkouts->count, 0, 'Checkin should have been done successfully');
317
318     # Test checkin without return date
319     $co_transaction->do_checkout;
320     is( $patron->checkouts->count, 1, 'Checkout should have been done successfully');
321     $ci_transaction->do_checkin($library->branchcode, undef);
322     is( $patron->checkouts->count, 0, 'Checkin should have been done successfully');
323 };
324
325 subtest checkin_lost => sub {
326     plan tests => 2;
327
328     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
329
330     t::lib::Mocks::mock_userenv(
331         { branchcode => $library->branchcode, flags => 1 } );
332
333     my $item = $builder->build_sample_item(
334         {
335             library     => $library->branchcode,
336         }
337     );
338
339     $item->itemlost(1)->itemlost_on(dt_from_string)->store();
340
341     my $instituation = {
342         id             => $library->id,
343         implementation => "ILS",
344         policy         => {
345             checkin  => "true",
346             renewal  => "true",
347             checkout => "true",
348             timeout  => 100,
349             retries  => 5,
350         }
351     };
352     my $ils = C4::SIP::ILS->new( $instituation );
353
354     t::lib::Mocks::mock_preference('BlockReturnOfLostItems', '1');
355     my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp );
356     is( $circ->{screen_msg}, 'Item lost, return not allowed', "Got correct screen message" );
357
358     t::lib::Mocks::mock_preference('BlockReturnOfLostItems', '0');
359     $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp );
360     is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" );
361 };
362
363 subtest checkin_withdrawn => sub {
364     plan tests => 2;
365
366     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
367
368     t::lib::Mocks::mock_userenv(
369         { branchcode => $library->branchcode, flags => 1 } );
370
371     my $item = $builder->build_sample_item(
372         {
373             library     => $library->branchcode,
374         }
375     );
376
377     $item->withdrawn(1)->withdrawn_on(dt_from_string)->store();
378
379     my $instituation = {
380         id             => $library->id,
381         implementation => "ILS",
382         policy         => {
383             checkin  => "true",
384             renewal  => "true",
385             checkout => "true",
386             timeout  => 100,
387             retries  => 5,
388         }
389     };
390     my $ils = C4::SIP::ILS->new( $instituation );
391
392     t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', '1');
393     my $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp );
394     is( $circ->{screen_msg}, 'Item withdrawn, return not allowed', "Got correct screen message" );
395
396     t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', '0');
397     $circ = $ils->checkin( $item->barcode, C4::SIP::Sip::timestamp );
398     is( $circ->{screen_msg}, 'Item not checked out', "Got 'Item not checked out' screen message" );
399 };
400
401 subtest item_circulation_status => sub {
402     plan tests => 4;
403
404     my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
405     my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
406
407     my $patron = $builder->build_object(
408         {
409             class => 'Koha::Patrons',
410             value => {
411                 branchcode => $library->branchcode,
412             }
413         }
414     );
415
416     t::lib::Mocks::mock_userenv(
417         { branchcode => $library->branchcode, flags => 1 } );
418
419     my $item = $builder->build_sample_item(
420         {
421             library => $library->branchcode,
422         }
423     );
424
425     my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
426     my $status = $sip_item->sip_circulation_status;
427     is( $status, '03', "Item circulation status is available");
428
429     my $transfer = Koha::Item::Transfer->new({
430         itemnumber => $item->id,
431         datesent   => '2020-01-01',
432         frombranch => $library->branchcode,
433         tobranch   => $library2->branchcode,
434     })->store();
435
436     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
437     $status = $sip_item->sip_circulation_status;
438     is( $status, '10', "Item circulation status is in transit" );
439
440     $transfer->delete;
441
442     my $claim = Koha::Checkouts::ReturnClaim->new({
443         itemnumber     => $item->id,
444         borrowernumber => $patron->id,
445         created_by     => $patron->id,
446     })->store();
447
448     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
449     $status = $sip_item->sip_circulation_status;
450     is( $status, '11', "Item circulation status is claimed returned" );
451
452     $claim->delete;
453
454     $item->itemlost(1)->store();
455     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
456     $status = $sip_item->sip_circulation_status;
457     is( $status, '12', "Item circulation status is lost" );
458 };
459 $schema->storage->txn_rollback;