Bug 33653: Never consider received orders as late
[koha.git] / t / db_dependent / Koha / Acquisition / Order.t
1 #!/usr/bin/perl
2
3 # Copyright 2017 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 => 12;
23 use Test::Exception;
24
25 use t::lib::TestBuilder;
26 use t::lib::Mocks;
27
28 use C4::Circulation qw( AddIssue AddReturn );
29
30 use Koha::Biblios;
31 use Koha::Database;
32 use Koha::DateUtils qw(dt_from_string);
33 use Koha::Items;
34
35 my $schema  = Koha::Database->schema;
36 my $builder = t::lib::TestBuilder->new;
37
38 subtest 'basket() tests' => sub {
39
40     plan tests => 2;
41
42     $schema->storage->txn_begin;
43
44     my $basket = $builder->build_object(
45         {
46             class => 'Koha::Acquisition::Baskets'
47         }
48     );
49     my $order = $builder->build_object(
50         {
51             class => 'Koha::Acquisition::Orders',
52             value => { basketno => $basket->basketno }
53         }
54     );
55
56     my $retrieved_basket = $order->basket;
57     is( ref($retrieved_basket), 'Koha::Acquisition::Basket',
58         'Type is correct for ->basket' );
59     is_deeply( $retrieved_basket->unblessed,
60         $basket->unblessed, "Correct basket found and updated" );
61
62     $schema->storage->txn_rollback;
63 };
64
65 subtest 'biblio() tests' => sub {
66
67     plan tests => 5;
68
69     $schema->storage->txn_begin;
70
71     my $order = $builder->build_object(
72         {
73             class => 'Koha::Acquisition::Orders',
74             value => { biblionumber => undef }
75         }
76     );
77
78     is( $order->biblio, undef, 'If no linked biblio, undef is returned' );
79
80     # Add and link a biblio to the order
81     my $biblio = $builder->build_sample_biblio();
82     $order->set({ biblionumber => $biblio->biblionumber })->store->discard_changes;
83
84     my $THE_biblio = $order->biblio;
85     is( ref($THE_biblio), 'Koha::Biblio', 'Returns a Koha::Biblio object' );
86     is( $THE_biblio->biblionumber, $biblio->biblionumber, 'It is not cheating about the object' );
87
88     $order->biblio->delete;
89     $order = Koha::Acquisition::Orders->find($order->ordernumber);
90     ok( $order, 'The order is not deleted if the biblio is deleted' );
91     is( $order->biblio, undef, 'order.biblio is correctly set to NULL when the biblio is deleted' );
92
93     $schema->storage->txn_rollback;
94 };
95
96 subtest 'store' => sub {
97     plan tests => 1;
98
99     $schema->storage->txn_begin;
100     my $o = $builder->build_object(
101         {
102             class => 'Koha::Acquisition::Orders'
103         }
104     );
105
106     subtest 'entrydate' => sub {
107         plan tests => 2;
108
109         my $order;
110
111         t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
112         $order = Koha::Acquisition::Order->new(
113             {
114                 basketno     => $o->basketno,
115                 biblionumber => $o->biblionumber,
116                 budget_id    => $o->budget_id,
117                 quantity     => 1,
118             }
119         )->store;
120         $order->discard_changes;
121         like( $order->entrydate, qr|^\d{4}-\d{2}-\d{2}$| );
122
123         t::lib::Mocks::mock_preference( 'TimeFormat', '24hr' );
124         $order = Koha::Acquisition::Order->new(
125             {
126                 basketno     => $o->basketno,
127                 biblionumber => $o->biblionumber,
128                 budget_id    => $o->budget_id,
129                 quantity     => 1,
130             }
131         )->store;
132         $order->discard_changes;
133         like( $order->entrydate, qr|^\d{4}-\d{2}-\d{2}$| );
134     };
135     $schema->storage->txn_rollback;
136 };
137
138 subtest 'fund' => sub {
139     plan tests => 1;
140
141     $schema->storage->txn_begin;
142     my $o = $builder->build_object(
143         {
144             class => 'Koha::Acquisition::Orders',
145         }
146     );
147
148     my $order = Koha::Acquisition::Orders->find( $o->ordernumber );
149     is( ref( $order->fund ),
150         'Koha::Acquisition::Fund',
151         '->fund should return a Koha::Acquisition::Fund object' );
152     $schema->storage->txn_rollback;
153 };
154
155 subtest 'invoice' => sub {
156     plan tests => 2;
157
158     $schema->storage->txn_begin;
159     my $o = $builder->build_object(
160         {
161             class => 'Koha::Acquisition::Orders',
162             value => { cancellationreason => 'XXXXXXXX', invoiceid => undef }, # not received yet
163         }
164     );
165
166     my $order = Koha::Acquisition::Orders->find( $o->ordernumber );
167     is( $order->invoice, undef,
168         '->invoice should return undef if no invoice defined yet');
169
170     my $invoice = $builder->build_object(
171         {
172             class => 'Koha::Acquisition::Invoices',
173         },
174     );
175
176     $o->invoiceid( $invoice->invoiceid )->store;
177     $order = Koha::Acquisition::Orders->find( $o->ordernumber );
178     is( ref( $order->invoice ), 'Koha::Acquisition::Invoice',
179         '->invoice should return a Koha::Acquisition::Invoice object if an invoice is defined');
180
181     $schema->storage->txn_rollback;
182 };
183
184 subtest 'subscription' => sub {
185     plan tests => 2;
186
187     $schema->storage->txn_begin;
188     my $o = $builder->build_object(
189         {
190             class => 'Koha::Acquisition::Orders',
191             value => { subscriptionid => undef }, # not linked to a subscription
192         }
193     );
194
195     my $order = Koha::Acquisition::Orders->find( $o->ordernumber );
196     is( $order->subscription, undef,
197         '->subscription should return undef if not created from a subscription');
198
199     $o = $builder->build_object(
200         {
201             class => 'Koha::Acquisition::Orders',
202             # Will be linked to a subscription by TestBuilder
203         }
204     );
205
206     $order = Koha::Acquisition::Orders->find( $o->ordernumber );
207     is( ref( $order->subscription ), 'Koha::Subscription',
208         '->subscription should return a Koha::Subscription object if created from a subscription');
209
210     $schema->storage->txn_rollback;
211 };
212
213 subtest 'duplicate_to | add_item' => sub {
214     plan tests => 3;
215
216     $schema->storage->txn_begin;
217
218     my $item = $builder->build_sample_item;
219     my $order_no_sub = $builder->build_object(
220         {
221             class => 'Koha::Acquisition::Orders',
222             value =>
223               {
224                   biblionumber => $item->biblionumber,
225                   subscriptionid => undef, # not linked to a subscription
226               }
227         }
228     );
229     $order_no_sub->basket->create_items(undef)->store; # use syspref
230     $order_no_sub->add_item( $item->itemnumber );
231
232     $item = $builder->build_sample_item;
233     my $order_from_sub = $builder->build_object(
234         {
235             class => 'Koha::Acquisition::Orders',
236             value =>
237               {
238                   biblionumber => $item->biblionumber,
239                   # Will be linked to a subscription by TestBuilder
240               }
241         }
242     );
243     $order_from_sub->basket->create_items(undef)->store; # use syspref
244     $order_from_sub->add_item( $item->itemnumber );
245
246     my $basket_to = $builder->build_object(
247          { class => 'Koha::Acquisition::Baskets' });
248
249     subtest 'Create item on receiving' => sub {
250         plan tests => 2;
251
252         t::lib::Mocks::mock_preference('AcqCreateItem', 'receiving');
253
254         my $duplicated_order = $order_no_sub->duplicate_to($basket_to);
255         is( $duplicated_order->items->count, 0,
256             'Items should not be copied if the original order did not create items on ordering'
257         );
258
259         $duplicated_order = $order_from_sub->duplicate_to($basket_to);
260         is( $duplicated_order->items->count, 0,
261             'Items should not be copied if the original order is created from a subscription'
262         );
263     };
264
265     subtest 'Create item on ordering' => sub {
266         plan tests => 2;
267
268         t::lib::Mocks::mock_preference('AcqCreateItem', 'ordering');
269
270         my $duplicated_order = $order_no_sub->duplicate_to($basket_to);
271         is( $duplicated_order->items->count, 1,
272             'Items should be copied if items are created on ordering'
273         );
274
275         $duplicated_order = $order_from_sub->duplicate_to($basket_to);
276         is( $duplicated_order->items->count, 0,
277             'Items should never be copied if the original order is created from a subscription'
278         );
279     };
280
281     subtest 'Regression tests' => sub {
282         plan tests => 1;
283
284         my $duplicated_order = $order_no_sub->duplicate_to($basket_to);
285         is($duplicated_order->invoiceid, undef, "invoiceid should be set to null for a new duplicated order");
286     };
287
288     $schema->storage->txn_rollback;
289 };
290
291 subtest 'current_item_level_holds() tests' => sub {
292
293     plan tests => 5;
294
295     $schema->storage->txn_begin;
296
297     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
298     my $biblio = $builder->build_sample_biblio();
299     my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
300     my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
301     my $item_3 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
302
303     C4::Reserves::AddReserve(
304         {
305             branchcode       => $patron->branchcode,
306             borrowernumber   => $patron->borrowernumber,
307             biblionumber     => $biblio->biblionumber,
308             reservation_date => dt_from_string->add( days => -2 ),
309             itemnumber       => $item_1->itemnumber,
310         }
311     );
312     C4::Reserves::AddReserve(
313         {
314             branchcode       => $patron->branchcode,
315             borrowernumber   => $patron->borrowernumber,
316             biblionumber     => $biblio->biblionumber,
317             reservation_date => dt_from_string->add( days => -2 ),
318             itemnumber       => $item_2->itemnumber,
319         }
320     );
321     # Add a hold in the future
322     C4::Reserves::AddReserve(
323         {
324             branchcode       => $patron->branchcode,
325             borrowernumber   => $patron->borrowernumber,
326             biblionumber     => $biblio->biblionumber,
327             reservation_date => dt_from_string->add( days => 2 ),
328             itemnumber       => $item_3->itemnumber,
329         }
330     );
331
332     # Add an order with no biblionumber
333     my $order = $builder->build_object(
334         {
335             class => 'Koha::Acquisition::Orders',
336             value => {
337                 biblionumber => undef
338             }
339         }
340     );
341
342     my $holds = $order->current_item_level_holds;
343
344     is( ref($holds), 'Koha::Holds', 'Koha::Holds iterator returned if no linked biblio' );
345     is( $holds->count, 0, 'Count is 0 if no linked biblio' );
346
347     $order->set({ biblionumber => $biblio->biblionumber })->store->discard_changes;
348
349     $holds = $order->current_item_level_holds;
350
351     is( ref($holds), 'Koha::Holds', 'Koha::Holds iterator returned if no linked items' );
352     is( $holds->count, 0, 'Count is 0 if no linked items' );
353
354     $order->add_item( $item_2->itemnumber );
355     $order->add_item( $item_3->itemnumber );
356
357     $holds = $order->current_item_level_holds;
358     is( $holds->count, 1, 'Only current (not future) holds are returned');
359
360     $schema->storage->txn_rollback;
361 };
362
363 subtest 'claim*' => sub {
364     plan tests => 6;
365
366     $schema->storage->txn_begin;
367     my $order = $builder->build_object(
368         {
369             class => 'Koha::Acquisition::Orders',
370         }
371     );
372
373     my $now = dt_from_string;
374     is( $order->claims->count, 0, 'No claim yet, ->claims should return an empty set');
375     is( $order->claims_count, 0, 'No claim yet, ->claims_count should return 0');
376     is( $order->claimed_date, undef, 'No claim yet, ->claimed_date should return undef');
377
378     my $claim_1 = $order->claim;
379     my $claim_2 = $order->claim;
380
381     $claim_1->claimed_on($now->clone->subtract(days => 1))->store;
382     $claim_2->claimed_on($now)->store;
383
384     is( $order->claims->count, 2, '->claims should return the correct number of claims');
385     is( $order->claims_count, 2, '->claims_count should return the correct number of claims');
386     is( dt_from_string($order->claimed_date), $now, '->claimed_date should return the date of the last claim');
387
388     $schema->storage->txn_rollback;
389 };
390
391 subtest 'filter_by_late' => sub {
392     plan tests => 17;
393
394     $schema->storage->txn_begin;
395     my $now        = dt_from_string;
396     my $bookseller = $builder->build_object(
397         {
398             class => 'Koha::Acquisition::Booksellers',
399             value => { deliverytime => 2 }
400         }
401     );
402     my $basket_1 = $builder->build_object(
403         {
404             class => 'Koha::Acquisition::Baskets',
405             value => {
406                 booksellerid => $bookseller->id,
407                 closedate    => undef,
408             }
409         }
410     );
411     my $order_1 = $builder->build_object(
412         {
413             class => 'Koha::Acquisition::Orders',
414             value => {
415                 basketno                => $basket_1->basketno,
416                 datereceived            => undef,
417                 datecancellationprinted => undef,
418                 estimated_delivery_date => undef,
419                 orderstatus             => 'ordered',
420             }
421         }
422     );
423     my $basket_2 = $builder->build_object(    # expected tomorrow
424         {
425             class => 'Koha::Acquisition::Baskets',
426             value => {
427                 booksellerid => $bookseller->id,
428                 closedate    => $now->clone->subtract( days => 1 ),
429             }
430         }
431     );
432     my $order_2 = $builder->build_object(
433         {
434             class => 'Koha::Acquisition::Orders',
435             value => {
436                 basketno                => $basket_2->basketno,
437                 datereceived            => undef,
438                 datecancellationprinted => undef,
439                 estimated_delivery_date => undef,
440                 orderstatus             => 'ordered',
441             }
442         }
443     );
444     my $basket_3 = $builder->build_object(    # expected yesterday (1 day)
445         {
446             class => 'Koha::Acquisition::Baskets',
447             value => {
448                 booksellerid => $bookseller->id,
449                 closedate    => $now->clone->subtract( days => 3 ),
450             }
451         }
452     );
453     my $order_3 = $builder->build_object(
454         {
455             class => 'Koha::Acquisition::Orders',
456             value => {
457                 basketno                => $basket_3->basketno,
458                 datereceived            => undef,
459                 datecancellationprinted => undef,
460                 estimated_delivery_date => undef,
461                 orderstatus             => 'ordered',
462             }
463         }
464     );
465     my $basket_4 = $builder->build_object(    # expected 3 days ago
466         {
467             class => 'Koha::Acquisition::Baskets',
468             value => {
469                 booksellerid => $bookseller->id,
470                 closedate    => $now->clone->subtract( days => 5 ),
471             }
472         }
473     );
474     my $order_4 = $builder->build_object(
475         {
476             class => 'Koha::Acquisition::Orders',
477             value => {
478                 basketno                => $basket_4->basketno,
479                 datereceived            => undef,
480                 datecancellationprinted => undef,
481                 estimated_delivery_date => undef,
482                 orderstatus             => 'ordered',
483             }
484         }
485     );
486     my $order_42 = $builder->build_object(
487         {
488             class => 'Koha::Acquisition::Orders',
489             value => {
490                 basketno                => $basket_4->basketno,
491                 datereceived            => undef,
492                 datecancellationprinted => undef,
493                 estimated_delivery_date => undef,
494                 orderstatus             => 'complete',
495             }
496         }
497     );
498
499     my $orders = Koha::Acquisition::Orders->search(
500         {
501             ordernumber => {
502                 -in => [
503                     $order_1->ordernumber, $order_2->ordernumber,
504                     $order_3->ordernumber, $order_4->ordernumber,
505                 ]
506             }
507         }
508     );
509
510     my $late_orders = $orders->filter_by_lates;
511     is( $late_orders->count, 3 );
512
513     $late_orders = $orders->filter_by_lates( { delay => 0 } );
514     is( $late_orders->count, 3 );
515
516     $late_orders = $orders->filter_by_lates( { delay => 1 } );
517     is( $late_orders->count, 3 );
518
519     $late_orders = $orders->filter_by_lates( { delay => 3 } );
520     is( $late_orders->count, 2 );
521
522     $late_orders = $orders->filter_by_lates( { delay => 4 } );
523     is( $late_orders->count, 1 );
524
525     $late_orders = $orders->filter_by_lates( { delay => 5 } );
526     is( $late_orders->count, 1 );
527
528     $late_orders = $orders->filter_by_lates( { delay => 6 } );
529     is( $late_orders->count, 0 );
530
531     $late_orders = $orders->filter_by_lates(
532         { estimated_from => $now->clone->subtract( days => 6 ) } );
533     is( $late_orders->count,             2 );
534     is( $late_orders->next->ordernumber, $order_3->ordernumber );
535
536     $late_orders = $orders->filter_by_lates(
537         { estimated_from => $now->clone->subtract( days => 5 ) } );
538     is( $late_orders->count,             2 );
539     is( $late_orders->next->ordernumber, $order_3->ordernumber );
540
541     $late_orders = $orders->filter_by_lates(
542         { estimated_from => $now->clone->subtract( days => 4 ) } );
543     is( $late_orders->count,             2 );
544     is( $late_orders->next->ordernumber, $order_3->ordernumber );
545
546     $late_orders = $orders->filter_by_lates(
547         { estimated_from => $now->clone->subtract( days => 3 ) } );
548     is( $late_orders->count, 2 );
549
550     $late_orders = $orders->filter_by_lates(
551         { estimated_from => $now->clone->subtract( days => 1 ) } );
552     is( $late_orders->count, 1 );
553
554     $late_orders = $orders->filter_by_lates(
555         {
556             estimated_from => $now->clone->subtract( days => 4 ),
557             estimated_to   => $now->clone->subtract( days => 3 )
558         }
559     );
560     is( $late_orders->count, 1 );
561
562     my $basket_5 = $builder->build_object(    # closed today
563         {
564             class => 'Koha::Acquisition::Baskets',
565             value => {
566                 booksellerid => $bookseller->id,
567                 closedate    => $now,
568             }
569         }
570     );
571     my $order_5 = $builder->build_object(
572         {
573             class => 'Koha::Acquisition::Orders',
574             value => {
575                 basketno                => $basket_4->basketno,
576                 datereceived            => undef,
577                 datecancellationprinted => undef,
578                 estimated_delivery_date => $now->clone->subtract( days => 2 ),
579             }
580         }
581     );
582     $late_orders = $orders->filter_by_lates(
583         {
584             estimated_from => $now->clone->subtract( days => 3 ),
585             estimated_to   => $now->clone->subtract( days => 2 )
586         }
587     );
588     is( $late_orders->count, 1 );
589
590     $schema->storage->txn_rollback;
591 };
592
593 subtest 'filter_by_current & filter_by_cancelled' => sub {
594     plan tests => 2;
595
596     $schema->storage->txn_begin;
597     my $now        = dt_from_string;
598     my $order_1 = $builder->build_object(
599         {
600             class => 'Koha::Acquisition::Orders',
601             value => {
602                 datecancellationprinted => undef,
603             }
604         }
605     );
606     my $order_2 = $builder->build_object(
607         {
608             class => 'Koha::Acquisition::Orders',
609             value => {
610                 datecancellationprinted => undef,
611             }
612         }
613     );
614     my $order_3 = $builder->build_object(
615         {
616             class => 'Koha::Acquisition::Orders',
617             value => {
618                 datecancellationprinted => dt_from_string,
619             }
620         }
621     );
622
623     my $orders = Koha::Acquisition::Orders->search(
624         {
625             ordernumber => {
626                 -in => [
627                     $order_1->ordernumber, $order_2->ordernumber,
628                     $order_3->ordernumber,
629                 ]
630             }
631         }
632     );
633
634     is( $orders->filter_by_current->count, 2);
635     is( $orders->filter_by_cancelled->count, 1);
636
637
638     $schema->storage->txn_rollback;
639 };
640
641 subtest 'cancel() tests' => sub {
642
643     plan tests => 54;
644
645     $schema->storage->txn_begin;
646
647     my $reason = 'Some reason';
648
649     # Scenario:
650     # * order with one item attached
651     # * the item is on loan
652     # * delete_biblio is passed
653     # => order is not cancelled
654     # => item in order is not removed
655     # => biblio in order is not removed
656     # => message about not being able to delete
657
658     my $item      = $builder->build_sample_item;
659     my $biblio_id = $item->biblionumber;
660     my $order     = $builder->build_object(
661         {
662             class => 'Koha::Acquisition::Orders',
663             value => {
664                 orderstatus             => 'new',
665                 biblionumber            => $item->biblionumber,
666                 datecancellationprinted => undef,
667                 cancellationreason      => undef,
668             }
669         }
670     );
671     $order->add_item( $item->id );
672
673     my $patron = $builder->build_object(
674         {
675             class => 'Koha::Patrons',
676             value => { branchcode => $item->homebranch, flags => 1 }
677         }
678     );
679     t::lib::Mocks::mock_userenv({ patron => $patron });
680
681     # Add a checkout so deleting the item fails because od 'book_on_loan'
682     C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
683
684     my $result = $order->cancel({ reason => $reason });
685     # refresh the order object
686     $order->discard_changes;
687
688     is( $result, $order, 'self is returned' );
689     is( $order->orderstatus, 'cancelled', 'Order is not marked as cancelled' );
690     isnt( $order->datecancellationprinted, undef, 'datecancellationprinted is not undef' );
691     is( $order->cancellationreason, $reason, 'cancellationreason is set' );
692     is( ref(Koha::Items->find($item->id)), 'Koha::Item', 'The item is present' );
693     is( ref(Koha::Biblios->find($biblio_id)), 'Koha::Biblio', 'The biblio is present' );
694     my @messages = @{ $order->object_messages };
695     is( $messages[0]->message, 'error_delitem', 'An error message is attached to the order' );
696
697     # Scenario:
698     # * order with one item attached
699     # * the item is no longer on loan
700     # * delete_biblio not passed
701     # => order is cancelled
702     # => item in order is removed
703     # => biblio remains untouched
704
705     C4::Circulation::AddReturn( $item->barcode );
706
707     $order = Koha::Acquisition::Orders->find($order->ordernumber);
708     $order->cancel({ reason => $reason })
709           ->discard_changes;
710
711     is( $order->orderstatus, 'cancelled', 'Order is marked as cancelled' );
712     isnt( $order->datecancellationprinted, undef, 'datecancellationprinted is set' );
713     is( $order->cancellationreason, $reason, 'cancellationreason is undef' );
714     is( Koha::Items->find($item->id), undef, 'The item is no longer present' );
715     is( ref(Koha::Biblios->find($biblio_id)), 'Koha::Biblio', 'The biblio is present' );
716     @messages = @{ $order->object_messages };
717     is( scalar @messages, 0, 'No messages' );
718
719     # Scenario:
720     # * order with one item attached
721     # * biblio has another item
722     # => order is cancelled
723     # => item in order is removed
724     # => the extra item remains untouched
725     # => biblio remains untouched
726
727     my $item_1 = $builder->build_sample_item;
728     $biblio_id = $item_1->biblionumber;
729     my $item_2 = $builder->build_sample_item({ biblionumber => $biblio_id });
730     $order     = $builder->build_object(
731         {
732             class => 'Koha::Acquisition::Orders',
733             value => {
734                 orderstatus             => 'new',
735                 biblionumber            => $biblio_id,
736                 datecancellationprinted => undef,
737                 cancellationreason      => undef,
738             }
739         }
740     );
741     $order->add_item( $item_1->id );
742
743     $order->cancel({ reason => $reason, delete_biblio => 1 })
744           ->discard_changes;
745
746     is( $order->orderstatus, 'cancelled', 'Order is marked as cancelled' );
747     isnt( $order->datecancellationprinted, undef, 'datecancellationprinted is set' );
748     is( $order->cancellationreason, $reason, 'cancellationreason is undef' );
749     is( Koha::Items->find($item_1->id), undef, 'The item is no longer present' );
750     is( ref(Koha::Items->find($item_2->id)), 'Koha::Item', 'The item is still present' );
751     is( ref(Koha::Biblios->find($biblio_id)), 'Koha::Biblio', 'The biblio is still present' );
752     @messages = @{ $order->object_messages };
753     is( $messages[0]->message, 'error_delbiblio_items', 'Cannot delete biblio and it gets notified' );
754
755     # Scenario:
756     # * order with one item attached
757     # * there's another order pointing to the biblio
758     # => order is cancelled
759     # => item in order is removed
760     # => biblio remains untouched
761     # => biblio delete error notified
762
763     $item      = $builder->build_sample_item;
764     $biblio_id = $item->biblionumber;
765     $order     = $builder->build_object(
766         {
767             class => 'Koha::Acquisition::Orders',
768             value => {
769                 orderstatus             => 'new',
770                 biblionumber            => $biblio_id,
771                 datecancellationprinted => undef,
772                 cancellationreason      => undef,
773             }
774         }
775     );
776     $order->add_item( $item->id );
777
778     # Add another order
779     $builder->build_object(
780         {
781             class => 'Koha::Acquisition::Orders',
782             value => {
783                 orderstatus             => 'new',
784                 biblionumber            => $biblio_id,
785                 datecancellationprinted => undef,
786                 cancellationreason      => undef,
787             }
788         }
789     );
790
791     $order->cancel({ reason => $reason, delete_biblio => 1 })
792           ->discard_changes;
793
794     is( $order->orderstatus, 'cancelled', 'Order is marked as cancelled' );
795     isnt( $order->datecancellationprinted, undef, 'datecancellationprinted is set' );
796     is( $order->cancellationreason, $reason, 'cancellationreason is undef' );
797     is( Koha::Items->find($item->id), undef, 'The item is no longer present' );
798     is( ref(Koha::Biblios->find($biblio_id)), 'Koha::Biblio', 'The biblio is still present' );
799     @messages = @{ $order->object_messages };
800     is( $messages[0]->message, 'error_delbiblio_active_orders', 'Cannot delete biblio and it gets notified' );
801
802     # Scenario:
803     # * order with one item attached
804     # * there's a subscription on the biblio
805     # => order is cancelled
806     # => item in order is removed
807     # => biblio remains untouched
808     # => biblio delete error notified
809
810     $item      = $builder->build_sample_item;
811     $biblio_id = $item->biblionumber;
812     $order     = $builder->build_object(
813         {
814             class => 'Koha::Acquisition::Orders',
815             value => {
816                 orderstatus             => 'new',
817                 biblionumber            => $biblio_id,
818                 datecancellationprinted => undef,
819                 cancellationreason      => undef,
820             }
821         }
822     );
823     $order->add_item( $item->id );
824
825     # Add a subscription
826     $builder->build_object(
827         {
828             class => 'Koha::Subscriptions',
829             value => {
830                 biblionumber => $biblio_id,
831             }
832         }
833     );
834
835     $order->cancel({ reason => $reason, delete_biblio => 1 })
836           ->discard_changes;
837
838     is( $order->orderstatus, 'cancelled', 'Order is marked as cancelled' );
839     isnt( $order->datecancellationprinted, undef, 'datecancellationprinted is set' );
840     is( $order->cancellationreason, $reason, 'cancellationreason is undef' );
841     is( Koha::Items->find($item->id), undef, 'The item is no longer present' );
842     is( ref(Koha::Biblios->find($biblio_id)), 'Koha::Biblio', 'The biblio is still present' );
843     @messages = @{ $order->object_messages };
844     is( $messages[0]->message, 'error_delbiblio_subscriptions', 'Cannot delete biblio and it gets notified' );
845
846     # Scenario:
847     # * order with one item attached
848     # * delete_biblio is passed
849     # => order is cancelled
850     # => item in order is removed
851     # => biblio in order is removed
852
853     $item      = $builder->build_sample_item;
854     $biblio_id = $item->biblionumber;
855     $order     = $builder->build_object(
856         {
857             class => 'Koha::Acquisition::Orders',
858             value => {
859                 orderstatus             => 'new',
860                 biblionumber            => $item->biblionumber,
861                 datecancellationprinted => undef,
862                 cancellationreason      => undef,
863             }
864         }
865     );
866     $order->add_item( $item->id );
867
868     $order->cancel({ reason => $reason, delete_biblio => 1 })
869           ->discard_changes;
870
871     is( $order->orderstatus, 'cancelled', 'Order is not marked as cancelled' );
872     isnt( $order->datecancellationprinted, undef, 'datecancellationprinted is not undef' );
873     is( $order->cancellationreason, $reason, 'cancellationreason is set' );
874     is( Koha::Items->find($item->id), undef, 'The item is not present' );
875     is( Koha::Biblios->find($biblio_id), undef, 'The biblio is not present' );
876     @messages = @{ $order->object_messages };
877     is( scalar @messages, 0, 'No errors' );
878
879     # Scenario:
880     # * order with two items attached
881     # * one of the items is on loan
882     # => order is cancelled
883     # => item on loan is kept
884     # => the other item is removed
885     # => biblio remains untouched
886     # => biblio delete error notified
887     # => item delete error notified
888
889     $item_1    = $builder->build_sample_item;
890     $item_2    = $builder->build_sample_item({ biblionumber => $item_1->biblionumber });
891     my $item_3 = $builder->build_sample_item({ biblionumber => $item_1->biblionumber });
892     $biblio_id = $item_1->biblionumber;
893     $order     = $builder->build_object(
894         {
895             class => 'Koha::Acquisition::Orders',
896             value => {
897                 orderstatus             => 'new',
898                 biblionumber            => $biblio_id,
899                 datecancellationprinted => undef,
900                 cancellationreason      => undef,
901             }
902         }
903     );
904     $order->add_item( $item_1->id );
905     $order->add_item( $item_2->id );
906     $order->add_item( $item_3->id );
907
908     # Add a checkout so deleting the item fails because od 'book_on_loan'
909     C4::Circulation::AddIssue( $patron->unblessed, $item_2->barcode );
910     C4::Reserves::AddReserve(
911         {
912             branchcode     => $item_3->holdingbranch,
913             borrowernumber => $patron->borrowernumber,
914             biblionumber   => $biblio_id,
915             itemnumber     => $item_3->id,
916             found          => 'W',
917         }
918     );
919
920     $order->cancel({ reason => $reason, delete_biblio => 1 })
921           ->discard_changes;
922
923     is( $order->orderstatus, 'cancelled', 'Order is marked as cancelled' );
924     isnt( $order->datecancellationprinted, undef, 'datecancellationprinted is set' );
925     is( $order->cancellationreason, $reason, 'cancellationreason is undef' );
926     is( Koha::Items->find($item_1->id), undef, 'The item is no longer present' );
927     is( ref(Koha::Items->find($item_2->id)), 'Koha::Item', 'The on loan item is still present' );
928     is( ref(Koha::Biblios->find($biblio_id)), 'Koha::Biblio', 'The biblio is still present' );
929     @messages = @{ $order->object_messages };
930     is( $messages[0]->message, 'error_delitem', 'Cannot delete on loan item' );
931     is( $messages[0]->payload->{item}->id, $item_2->id, 'Cannot delete on loan item' );
932     is( $messages[0]->payload->{reason}, 'book_on_loan', 'Item on loan notified' );
933     is( $messages[1]->message, 'error_delitem', 'Cannot delete reserved and found item' );
934     is( $messages[1]->payload->{item}->id, $item_3->id, 'Cannot delete reserved and found item' );
935     is( $messages[1]->payload->{reason}, 'book_reserved', 'Item reserved notified' );
936     is( $messages[2]->message, 'error_delbiblio_items', 'Cannot delete on loan item' );
937     is( $messages[2]->payload->{biblio}->id, $biblio_id, 'The right biblio is attached' );
938
939     # Call ->store with biblionumber NULL (as ->cancel does)
940     $item_1 = $builder->build_sample_item;
941     $biblio_id = $item_1->biblionumber;
942     $order= $builder->build_object({
943         class => 'Koha::Acquisition::Orders',
944         value => {
945             orderstatus             => 'new',
946             biblionumber            => $biblio_id,
947             datecancellationprinted => undef,
948             cancellationreason      => undef,
949         }
950     });
951     my $columns = {
952         biblionumber            => undef,
953         cancellationreason      => $reason,
954         datecancellationprinted => \'NOW()',
955         orderstatus             => 'cancelled',
956     };
957     lives_ok { $order->set($columns)->store; } 'No croak on missing biblionumber when cancelling an order';
958     throws_ok { $order->orderstatus('new')->store; } qr/Cannot insert order: Mandatory parameter biblionumber is missing/, 'Expected croak';
959
960     $schema->storage->txn_rollback;
961 };