View | Details | Raw Unified | Return to bug 25408
Collapse All | Expand All

(-)a/C4/Reserves.pm (-464 / +526 lines)
Lines 20-30 package C4::Reserves; Link Here
20
# You should have received a copy of the GNU General Public License
20
# You should have received a copy of the GNU General Public License
21
# along with Koha; if not, see <http://www.gnu.org/licenses>.
21
# along with Koha; if not, see <http://www.gnu.org/licenses>.
22
22
23
24
use Modern::Perl;
23
use Modern::Perl;
25
24
26
use C4::Accounts;
25
use C4::Accounts;
27
use C4::Biblio qw( GetMarcFromKohaField );
26
use C4::Biblio      qw( GetMarcFromKohaField );
28
use C4::Circulation qw( CheckIfIssuedToPatron GetAgeRestriction GetBranchItemRule );
27
use C4::Circulation qw( CheckIfIssuedToPatron GetAgeRestriction GetBranchItemRule );
29
use C4::Context;
28
use C4::Context;
30
use C4::Items qw( CartToShelf get_hostitemnumbers_of );
29
use C4::Items qw( CartToShelf get_hostitemnumbers_of );
Lines 97-149 This modules provides somes functions to deal with reservations. Link Here
97
96
98
=cut
97
=cut
99
98
100
our (@ISA, @EXPORT_OK);
99
our ( @ISA, @EXPORT_OK );
100
101
BEGIN {
101
BEGIN {
102
    require Exporter;
102
    require Exporter;
103
    @ISA = qw(Exporter);
103
    @ISA       = qw(Exporter);
104
    @EXPORT_OK = qw(
104
    @EXPORT_OK = qw(
105
      AddReserve
105
        AddReserve
106
106
107
      GetReserveStatus
107
        GetReserveStatus
108
108
109
      GetOtherReserves
109
        GetOtherReserves
110
      ChargeReserveFee
110
        ChargeReserveFee
111
      GetReserveFee
111
        GetReserveFee
112
112
113
      ModReserveAffect
113
        ModReserveAffect
114
      ModReserve
114
        ModReserve
115
      ModReserveStatus
115
        ModReserveStatus
116
      ModReserveCancelAll
116
        ModReserveCancelAll
117
      ModReserveMinusPriority
117
        ModReserveMinusPriority
118
      MoveReserve
118
        MoveReserve
119
119
120
      CheckReserves
120
        CheckReserves
121
      CanBookBeReserved
121
        CanBookBeReserved
122
      CanItemBeReserved
122
        CanItemBeReserved
123
      CanReserveBeCanceledFromOpac
123
        CanReserveBeCanceledFromOpac
124
      CancelExpiredReserves
124
        CancelExpiredReserves
125
125
126
      AutoUnsuspendReserves
126
        AutoUnsuspendReserves
127
127
128
      IsAvailableForItemLevelRequest
128
        IsAvailableForItemLevelRequest
129
      ItemsAnyAvailableAndNotRestricted
129
        ItemsAnyAvailableAndNotRestricted
130
130
131
      AlterPriority
131
        AlterPriority
132
      ToggleLowestPriority
132
        ToggleLowestPriority
133
133
134
      ReserveSlip
134
        ReserveSlip
135
      ToggleSuspend
135
        ToggleSuspend
136
      SuspendAll
136
        SuspendAll
137
137
138
      CalculatePriority
138
        CalculatePriority
139
139
140
      IsItemOnHoldAndFound
140
        IsItemOnHoldAndFound
141
141
142
      GetMaxPatronHoldsForRecord
142
        GetMaxPatronHoldsForRecord
143
143
144
      MergeHolds
144
        MergeHolds
145
145
146
      RevertWaitingStatus
146
        RevertWaitingStatus
147
    );
147
    );
148
}
148
}
149
149
Lines 185-211 The following tables are available within the HOLDPLACED_PATRON message: Link Here
185
=cut
185
=cut
186
186
187
sub AddReserve {
187
sub AddReserve {
188
    my ($params)       = @_;
188
    my ($params)               = @_;
189
    my $branch         = $params->{branchcode};
189
    my $branch                 = $params->{branchcode};
190
    my $borrowernumber = $params->{borrowernumber};
190
    my $borrowernumber         = $params->{borrowernumber};
191
    my $biblionumber   = $params->{biblionumber};
191
    my $biblionumber           = $params->{biblionumber};
192
    my $priority       = $params->{priority};
192
    my $priority               = $params->{priority};
193
    my $resdate        = $params->{reservation_date};
193
    my $resdate                = $params->{reservation_date};
194
    my $patron_expiration_date = $params->{expiration_date};
194
    my $patron_expiration_date = $params->{expiration_date};
195
    my $notes          = $params->{notes};
195
    my $notes                  = $params->{notes};
196
    my $title          = $params->{title};
196
    my $title                  = $params->{title};
197
    my $checkitem      = $params->{itemnumber};
197
    my $checkitem              = $params->{itemnumber};
198
    my $found          = $params->{found};
198
    my $found                  = $params->{found};
199
    my $itemtype       = $params->{itemtype};
199
    my $itemtype               = $params->{itemtype};
200
    my $non_priority   = $params->{non_priority};
200
    my $non_priority           = $params->{non_priority};
201
    my $item_group_id  = $params->{item_group_id};
201
    my $item_group_id          = $params->{item_group_id};
202
202
203
    $resdate ||= dt_from_string;
203
    $resdate ||= dt_from_string;
204
204
205
    # if we have an item selectionned, and the pickup branch is the same as the holdingbranch
205
    # if we have an item selectionned, and the pickup branch is the same as the holdingbranch
206
    # of the document, we force the value $priority and $found .
206
    # of the document, we force the value $priority and $found .
207
    if ( $checkitem and not C4::Context->preference('ReservesNeedReturns') ) {
207
    if ( $checkitem and not C4::Context->preference('ReservesNeedReturns') ) {
208
        my $item = Koha::Items->find( $checkitem ); # FIXME Prevent bad calls
208
        my $item = Koha::Items->find($checkitem);    # FIXME Prevent bad calls
209
209
210
        if (
210
        if (
211
            # If item is already checked out, it cannot be set waiting
211
            # If item is already checked out, it cannot be set waiting
Lines 221-235 sub AddReserve { Link Here
221
            && ( $item->damaged && C4::Context->preference('AllowHoldsOnDamagedItems') || !$item->damaged )
221
            && ( $item->damaged && C4::Context->preference('AllowHoldsOnDamagedItems') || !$item->damaged )
222
222
223
            # Lastly, if this already has holds, we shouldn't make it waiting for the new hold
223
            # Lastly, if this already has holds, we shouldn't make it waiting for the new hold
224
            && !$item->current_holds->count )
224
            && !$item->current_holds->count
225
            )
225
        {
226
        {
226
            $priority = 0;
227
            $priority = 0;
227
            $found = 'W';
228
            $found    = 'W';
228
        }
229
        }
229
    }
230
    }
230
    if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
231
    if ( C4::Context->preference('AllowHoldDateInFuture') ) {
231
    # Make room in reserves for this if passed a priority
232
232
    $priority = _ShiftPriority( $biblionumber, $priority );
233
        # Make room in reserves for this if passed a priority
234
        $priority = _ShiftPriority( $biblionumber, $priority );
233
    }
235
    }
234
236
235
    my $waitingdate;
237
    my $waitingdate;
Lines 245-264 sub AddReserve { Link Here
245
    # updates take place here
247
    # updates take place here
246
    my $hold = Koha::Hold->new(
248
    my $hold = Koha::Hold->new(
247
        {
249
        {
248
            borrowernumber => $borrowernumber,
250
            borrowernumber         => $borrowernumber,
249
            biblionumber   => $biblionumber,
251
            biblionumber           => $biblionumber,
250
            item_group_id  => $item_group_id,
252
            item_group_id          => $item_group_id,
251
            reservedate    => $resdate,
253
            reservedate            => $resdate,
252
            branchcode     => $branch,
254
            branchcode             => $branch,
253
            priority       => $priority,
255
            priority               => $priority,
254
            reservenotes   => $notes,
256
            reservenotes           => $notes,
255
            itemnumber     => $checkitem,
257
            itemnumber             => $checkitem,
256
            found          => $found,
258
            found                  => $found,
257
            waitingdate    => $waitingdate,
259
            waitingdate            => $waitingdate,
258
            patron_expiration_date => $patron_expiration_date,
260
            patron_expiration_date => $patron_expiration_date,
259
            itemtype       => $itemtype,
261
            itemtype               => $itemtype,
260
            item_level_hold => $checkitem ? 1 : 0,
262
            item_level_hold        => $checkitem    ? 1 : 0,
261
            non_priority   => $non_priority ? 1 : 0,
263
            non_priority           => $non_priority ? 1 : 0,
262
        }
264
        }
263
    )->store();
265
    )->store();
264
    $hold->set_waiting() if $found && $found eq 'W';
266
    $hold->set_waiting() if $found && $found eq 'W';
Lines 277-302 sub AddReserve { Link Here
277
        ChargeReserveFee( $borrowernumber, $reserve_fee, $title );
279
        ChargeReserveFee( $borrowernumber, $reserve_fee, $title );
278
    }
280
    }
279
281
280
    _FixPriority({ biblionumber => $biblionumber});
282
    _FixPriority( { biblionumber => $biblionumber } );
281
283
282
    # Send e-mail to librarian if syspref is active
284
    # Send e-mail to librarian if syspref is active
283
    if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){
285
    if ( C4::Context->preference("emailLibrarianWhenHoldIsPlaced") ) {
284
        my $patron = $hold->patron;
286
        my $patron  = $hold->patron;
285
        my $library = $patron->library;
287
        my $library = $patron->library;
286
        if ( my $letter =  C4::Letters::GetPreparedLetter (
288
        if (
287
            module => 'reserves',
289
            my $letter = C4::Letters::GetPreparedLetter(
288
            letter_code => 'HOLDPLACED',
290
                module      => 'reserves',
289
            branchcode => $branch,
291
                letter_code => 'HOLDPLACED',
290
            lang => $patron->lang,
292
                branchcode  => $branch,
291
            tables => {
293
                lang        => $patron->lang,
292
                'branches'    => $library->unblessed,
294
                tables      => {
293
                'borrowers'   => $patron->unblessed,
295
                    'branches'    => $library->unblessed,
294
                'biblio'      => $biblionumber,
296
                    'borrowers'   => $patron->unblessed,
295
                'biblioitems' => $biblionumber,
297
                    'biblio'      => $biblionumber,
296
                'items'       => $checkitem,
298
                    'biblioitems' => $biblionumber,
297
                'reserves'    => $hold->unblessed,
299
                    'items'       => $checkitem,
298
            },
300
                    'reserves'    => $hold->unblessed,
299
        ) ) {
301
                },
302
            )
303
            )
304
        {
300
305
301
            my $branch_email_address = $library->inbound_email_address;
306
            my $branch_email_address = $library->inbound_email_address;
302
307
Lines 338-344 sub AddReserve { Link Here
338
        }
343
        }
339
    }
344
    }
340
345
341
    Koha::Plugins->call('after_hold_create', $hold);
346
    Koha::Plugins->call( 'after_hold_create', $hold );
342
    Koha::Plugins->call(
347
    Koha::Plugins->call(
343
        'after_hold_action',
348
        'after_hold_action',
344
        {
349
        {
Lines 347-357 sub AddReserve { Link Here
347
        }
352
        }
348
    );
353
    );
349
354
350
    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
355
    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue( { biblio_ids => [$biblionumber] } )
351
        {
356
        if C4::Context->preference('RealTimeHoldsQueue');
352
            biblio_ids => [ $biblionumber ]
353
        }
354
    ) if C4::Context->preference('RealTimeHoldsQueue');
355
357
356
    return $reserve_id;
358
    return $reserve_id;
357
}
359
}
Lines 367-379 See CanItemBeReserved() for possible return values. Link Here
367
369
368
=cut
370
=cut
369
371
370
sub CanBookBeReserved{
372
sub CanBookBeReserved {
371
    my ($borrowernumber, $biblionumber, $pickup_branchcode, $params) = @_;
373
    my ( $borrowernumber, $biblionumber, $pickup_branchcode, $params ) = @_;
372
374
373
    # Check that patron have not checked out this biblio (if AllowHoldsOnPatronsPossessions set)
375
    # Check that patron have not checked out this biblio (if AllowHoldsOnPatronsPossessions set)
374
    if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions')
376
    if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions')
375
        && C4::Circulation::CheckIfIssuedToPatron( $borrowernumber, $biblionumber ) ) {
377
        && C4::Circulation::CheckIfIssuedToPatron( $borrowernumber, $biblionumber ) )
376
        return { status =>'alreadypossession' };
378
    {
379
        return { status => 'alreadypossession' };
377
    }
380
    }
378
381
379
    if ( $params->{itemtype} ) {
382
    if ( $params->{itemtype} ) {
Lines 398-435 sub CanBookBeReserved{ Link Here
398
                    { 'item.itype'  => $params->{itemtype} }
401
                    { 'item.itype'  => $params->{itemtype} }
399
                ]
402
                ]
400
            },
403
            },
401
            {
404
            { join => ['item'] }
402
                join => ['item']
403
            }
404
        )->count;
405
        )->count;
405
406
406
        return { status => '' }
407
        return { status => '' }
407
          if defined $reservesallowed and $reservesallowed < $count + 1;
408
            if defined $reservesallowed and $reservesallowed < $count + 1;
408
    }
409
    }
409
410
410
    my $items;
411
    my $items;
412
411
    #get items linked via host records
413
    #get items linked via host records
412
    my @hostitemnumbers = get_hostitemnumbers_of($biblionumber);
414
    my @hostitemnumbers = get_hostitemnumbers_of($biblionumber);
413
    if (@hostitemnumbers){
415
    if (@hostitemnumbers) {
414
        $items = Koha::Items->search({
416
        $items = Koha::Items->search(
415
            -or => [
417
            {
416
                biblionumber => $biblionumber,
418
                -or => [
417
                itemnumber => { -in => @hostitemnumbers }
419
                    biblionumber => $biblionumber,
418
            ]
420
                    itemnumber   => { -in => @hostitemnumbers }
419
        });
421
                ]
422
            }
423
        );
420
    } else {
424
    } else {
421
        $items = Koha::Items->search({ biblionumber => $biblionumber});
425
        $items = Koha::Items->search( { biblionumber => $biblionumber } );
422
    }
426
    }
423
427
424
    my $canReserve = { status => '' };
428
    my $canReserve = { status => '' };
425
    my $patron = Koha::Patrons->find( $borrowernumber );
429
    my $patron     = Koha::Patrons->find($borrowernumber);
426
    while ( my $item = $items->next ) {
430
    while ( my $item = $items->next ) {
427
        $canReserve = CanItemBeReserved( $patron, $item, $pickup_branchcode, $params );
431
        $canReserve = CanItemBeReserved( $patron, $item, $pickup_branchcode, $params );
428
        if ( C4::Context->interface eq 'opac' ) {
432
        if ( C4::Context->interface eq 'opac' ) {
429
            my $opacitemholds = Koha::CirculationRules->get_opacitemholds_policy({ item => $item,
433
            my $opacitemholds = Koha::CirculationRules->get_opacitemholds_policy(
430
                                                                                   patron => $patron }) // 'Y';
434
                {
435
                    item   => $item,
436
                    patron => $patron
437
                }
438
            ) // 'Y';
431
            return { status => 'recordHoldNotAllowed' } if $opacitemholds eq 'F';
439
            return { status => 'recordHoldNotAllowed' } if $opacitemholds eq 'F';
432
            return { status => 'OK' } if $canReserve->{status} eq 'recordHoldsOnly';
440
            return { status => 'OK' }                   if $canReserve->{status} eq 'recordHoldsOnly';
433
        }
441
        }
434
        return { status => 'OK' } if $canReserve->{status} eq 'OK';
442
        return { status => 'OK' } if $canReserve->{status} eq 'OK';
435
    }
443
    }
Lines 461-468 sub CanBookBeReserved{ Link Here
461
=cut
469
=cut
462
470
463
our $CanItemBeReserved_cache_key;
471
our $CanItemBeReserved_cache_key;
472
464
sub _cache {
473
sub _cache {
465
    my ( $return )  = @_;
474
    my ($return) = @_;
466
    my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
475
    my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
467
    $memory_cache->set_in_cache( $CanItemBeReserved_cache_key, $return );
476
    $memory_cache->set_in_cache( $CanItemBeReserved_cache_key, $return );
468
    return $return;
477
    return $return;
Lines 472-486 sub CanItemBeReserved { Link Here
472
    my ( $patron, $item, $pickup_branchcode, $params ) = @_;
481
    my ( $patron, $item, $pickup_branchcode, $params ) = @_;
473
482
474
    my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
483
    my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
475
    $CanItemBeReserved_cache_key = sprintf "Hold_CanItemBeReserved:%s:%s:%s", $patron->id, $item->itemnumber, $pickup_branchcode || "";
484
    $CanItemBeReserved_cache_key = sprintf "Hold_CanItemBeReserved:%s:%s:%s", $patron->id, $item->itemnumber,
485
        $pickup_branchcode || "";
476
    if ( $params->{get_from_cache} ) {
486
    if ( $params->{get_from_cache} ) {
477
        my $cached = $memory_cache->get_from_cache($CanItemBeReserved_cache_key);
487
        my $cached = $memory_cache->get_from_cache($CanItemBeReserved_cache_key);
478
        return $cached if $cached;
488
        return $cached if $cached;
479
    }
489
    }
480
490
481
    my $dbh = C4::Context->dbh;
491
    my $dbh = C4::Context->dbh;
482
    my $ruleitemtype;    # itemtype of the matching issuing rule
492
    my $ruleitemtype;           # itemtype of the matching issuing rule
483
    my $allowedreserves  = 0; # Total number of holds allowed across all records, default to none
493
    my $allowedreserves = 0;    # Total number of holds allowed across all records, default to none
484
494
485
    # We check item branch if IndependentBranches is ON
495
    # We check item branch if IndependentBranches is ON
486
    # and canreservefromotherbranches is OFF
496
    # and canreservefromotherbranches is OFF
Lines 497-527 sub CanItemBeReserved { Link Here
497
    my $borrower = $patron->unblessed;
507
    my $borrower = $patron->unblessed;
498
508
499
    # If an item is damaged and we don't allow holds on damaged items, we can stop right here
509
    # If an item is damaged and we don't allow holds on damaged items, we can stop right here
500
    return _cache { status =>'damaged' }
510
    return _cache { status => 'damaged' }
501
      if ( $item->damaged
511
        if ( $item->damaged
502
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
512
        && !C4::Context->preference('AllowHoldsOnDamagedItems') );
503
513
504
    if( GetMarcFromKohaField('biblioitems.agerestriction') ){
514
    if ( GetMarcFromKohaField('biblioitems.agerestriction') ) {
505
        my $biblio = $item->biblio;
515
        my $biblio = $item->biblio;
516
506
        # Check for the age restriction
517
        # Check for the age restriction
507
        my ( $ageRestriction, $daysToAgeRestriction ) =
518
        my ( $ageRestriction, $daysToAgeRestriction ) =
508
          C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction, $borrower );
519
            C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction, $borrower );
509
        return _cache { status => 'ageRestricted' } if $daysToAgeRestriction && $daysToAgeRestriction > 0;
520
        return _cache { status => 'ageRestricted' } if $daysToAgeRestriction && $daysToAgeRestriction > 0;
510
    }
521
    }
511
522
512
    # Check that the patron doesn't have an item level hold on this item already
523
    # Check that the patron doesn't have an item level hold on this item already
513
    return _cache { status =>'itemAlreadyOnHold' }
524
    return _cache { status => 'itemAlreadyOnHold' }
514
      if ( !$params->{ignore_hold_counts} && Koha::Holds->search( { borrowernumber => $patron->borrowernumber, itemnumber => $item->itemnumber } )->count() );
525
        if ( !$params->{ignore_hold_counts}
526
        && Koha::Holds->search( { borrowernumber => $patron->borrowernumber, itemnumber => $item->itemnumber } )
527
        ->count() );
515
528
516
    # Check that patron have not checked out this biblio (if AllowHoldsOnPatronsPossessions set)
529
    # Check that patron have not checked out this biblio (if AllowHoldsOnPatronsPossessions set)
517
    if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions')
530
    if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions')
518
        && C4::Circulation::CheckIfIssuedToPatron( $patron->borrowernumber, $item->biblionumber ) ) {
531
        && C4::Circulation::CheckIfIssuedToPatron( $patron->borrowernumber, $item->biblionumber ) )
519
        return _cache { status =>'alreadypossession' };
532
    {
533
        return _cache { status => 'alreadypossession' };
520
    }
534
    }
521
535
522
    # check if a recall exists on this item from this borrower
536
    # check if a recall exists on this item from this borrower
523
    return _cache { status => 'recall' }
537
    return _cache { status => 'recall' }
524
      if $patron->recalls->filter_by_current->search({ item_id => $item->itemnumber })->count;
538
        if $patron->recalls->filter_by_current->search( { item_id => $item->itemnumber } )->count;
525
539
526
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
540
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
527
541
Lines 529-569 sub CanItemBeReserved { Link Here
529
    my $branchfield = "reserves.branchcode";
543
    my $branchfield = "reserves.branchcode";
530
544
531
    if ( $controlbranch eq "ItemHomeLibrary" ) {
545
    if ( $controlbranch eq "ItemHomeLibrary" ) {
532
        $branchfield = "items.homebranch";
546
        $branchfield             = "items.homebranch";
533
        $reserves_control_branch  = $item->homebranch;
547
        $reserves_control_branch = $item->homebranch;
534
    }
548
    } elsif ( $controlbranch eq "PatronLibrary" ) {
535
    elsif ( $controlbranch eq "PatronLibrary" ) {
549
        $branchfield             = "borrowers.branchcode";
536
        $branchfield = "borrowers.branchcode";
550
        $reserves_control_branch = $borrower->{branchcode};
537
        $reserves_control_branch  = $borrower->{branchcode};
538
    }
551
    }
539
552
540
    # we retrieve rights
553
    # we retrieve rights
541
    if (
554
    if (
542
        my $reservesallowed = Koha::CirculationRules->get_effective_rule({
555
        my $reservesallowed = Koha::CirculationRules->get_effective_rule(
556
            {
543
                itemtype     => $item->effective_itemtype,
557
                itemtype     => $item->effective_itemtype,
544
                categorycode => $borrower->{categorycode},
558
                categorycode => $borrower->{categorycode},
545
                branchcode   => $reserves_control_branch,
559
                branchcode   => $reserves_control_branch,
546
                rule_name    => 'reservesallowed',
560
                rule_name    => 'reservesallowed',
547
        })
561
            }
548
    ) {
562
        )
549
        $ruleitemtype     = $reservesallowed->itemtype;
563
        )
550
        $allowedreserves  = $reservesallowed->rule_value // 0; #undefined is 0, blank is unlimited
564
    {
551
    }
565
        $ruleitemtype    = $reservesallowed->itemtype;
552
    else {
566
        $allowedreserves = $reservesallowed->rule_value // 0;    #undefined is 0, blank is unlimited
567
    } else {
553
        $ruleitemtype = undef;
568
        $ruleitemtype = undef;
554
    }
569
    }
555
570
556
    my $rights = Koha::CirculationRules->get_effective_rules({
571
    my $rights = Koha::CirculationRules->get_effective_rules(
557
        categorycode => $borrower->{'categorycode'},
572
        {
558
        itemtype     => $item->effective_itemtype,
573
            categorycode => $borrower->{'categorycode'},
559
        branchcode   => $reserves_control_branch,
574
            itemtype     => $item->effective_itemtype,
560
        rules        => ['holds_per_record','holds_per_day','opacitemholds']
575
            branchcode   => $reserves_control_branch,
561
    });
576
            rules        => [ 'holds_per_record', 'holds_per_day', 'opacitemholds' ]
577
        }
578
    );
562
    my $holds_per_record = $rights->{holds_per_record} // 1;
579
    my $holds_per_record = $rights->{holds_per_record} // 1;
563
    my $holds_per_day    = $rights->{holds_per_day};
580
    my $holds_per_day    = $rights->{holds_per_day};
564
    my $opacitemholds    = $rights->{opacitemholds} // 'Y';
581
    my $opacitemholds    = $rights->{opacitemholds} // 'Y';
565
582
566
    if (   defined $holds_per_record && $holds_per_record ne '' ){
583
    if ( defined $holds_per_record && $holds_per_record ne '' ) {
567
        if ( $holds_per_record == 0 ) {
584
        if ( $holds_per_record == 0 ) {
568
            return _cache { status => "noReservesAllowed" };
585
            return _cache { status => "noReservesAllowed" };
569
        }
586
        }
Lines 573-597 sub CanItemBeReserved { Link Here
573
                biblionumber   => $item->biblionumber,
590
                biblionumber   => $item->biblionumber,
574
            };
591
            };
575
            my $holds = Koha::Holds->search($search_params);
592
            my $holds = Koha::Holds->search($search_params);
576
            return _cache { status => "tooManyHoldsForThisRecord", limit => $holds_per_record } if $holds->count() >= $holds_per_record;
593
            return _cache { status => "tooManyHoldsForThisRecord", limit => $holds_per_record }
594
                if $holds->count() >= $holds_per_record;
577
        }
595
        }
578
    }
596
    }
579
597
580
    if (!$params->{ignore_hold_counts} && defined $holds_per_day && $holds_per_day ne '')
598
    if ( !$params->{ignore_hold_counts} && defined $holds_per_day && $holds_per_day ne '' ) {
581
    {
599
        my $today_holds = Koha::Holds->search(
582
        my $today_holds = Koha::Holds->search({
600
            {
583
            borrowernumber => $patron->borrowernumber,
601
                borrowernumber => $patron->borrowernumber,
584
            reservedate    => dt_from_string->date
602
                reservedate    => dt_from_string->date
585
        });
603
            }
586
        return _cache { status => 'tooManyReservesToday', limit => $holds_per_day } if $today_holds->count() >= $holds_per_day;
604
        );
605
        return _cache { status => 'tooManyReservesToday', limit => $holds_per_day }
606
            if $today_holds->count() >= $holds_per_day;
587
    }
607
    }
588
608
589
    # we check if it's ok or not
609
    # we check if it's ok or not
590
    if ( defined $allowedreserves && $allowedreserves ne '' ){
610
    if ( defined $allowedreserves && $allowedreserves ne '' ) {
591
        if( $allowedreserves == 0 ){
611
        if ( $allowedreserves == 0 ) {
592
            return _cache { status => 'noReservesAllowed' };
612
            return _cache { status => 'noReservesAllowed' };
593
        }
613
        }
594
        if ( !$params->{ignore_hold_counts} ) {
614
        if ( !$params->{ignore_hold_counts} ) {
615
595
            # we retrieve count
616
            # we retrieve count
596
            my $querycount = q{
617
            my $querycount = q{
597
                SELECT count(*) AS count
618
                SELECT count(*) AS count
Lines 611-618 sub CanItemBeReserved { Link Here
611
                        AND ( COALESCE( items.itype, biblioitems.itemtype ) = ?
632
                        AND ( COALESCE( items.itype, biblioitems.itemtype ) = ?
612
                           OR reserves.itemtype = ? )
633
                           OR reserves.itemtype = ? )
613
                    };
634
                    };
614
                }
635
                } else {
615
                else {
616
                    $querycount .= q{
636
                    $querycount .= q{
617
                        AND ( biblioitems.itemtype = ?
637
                        AND ( biblioitems.itemtype = ?
618
                           OR reserves.itemtype = ? )
638
                           OR reserves.itemtype = ? )
Lines 624-631 sub CanItemBeReserved { Link Here
624
644
625
            if ( defined $ruleitemtype ) {
645
            if ( defined $ruleitemtype ) {
626
                $sthcount->execute( $patron->borrowernumber, $reserves_control_branch, $ruleitemtype, $ruleitemtype );
646
                $sthcount->execute( $patron->borrowernumber, $reserves_control_branch, $ruleitemtype, $ruleitemtype );
627
            }
647
            } else {
628
            else {
629
                $sthcount->execute( $patron->borrowernumber, $reserves_control_branch );
648
                $sthcount->execute( $patron->borrowernumber, $reserves_control_branch );
630
            }
649
            }
631
650
Lines 634-640 sub CanItemBeReserved { Link Here
634
                $reservecount = $rowcount->{count};
653
                $reservecount = $rowcount->{count};
635
            }
654
            }
636
655
637
            return _cache { status => 'tooManyReserves', limit => $allowedreserves } if $reservecount >= $allowedreserves;
656
            return _cache { status => 'tooManyReserves', limit => $allowedreserves }
657
                if $reservecount >= $allowedreserves;
638
        }
658
        }
639
    }
659
    }
640
660
Lines 646-663 sub CanItemBeReserved { Link Here
646
            rule_name    => 'max_holds',
666
            rule_name    => 'max_holds',
647
        }
667
        }
648
    );
668
    );
649
    if (!$params->{ignore_hold_counts} && $rule && defined( $rule->rule_value ) && $rule->rule_value ne '' ) {
669
    if ( !$params->{ignore_hold_counts} && $rule && defined( $rule->rule_value ) && $rule->rule_value ne '' ) {
650
        my $total_holds_count = Koha::Holds->search(
670
        my $total_holds_count = Koha::Holds->search( { borrowernumber => $patron->borrowernumber } )->count();
651
            {
652
                borrowernumber => $patron->borrowernumber
653
            }
654
        )->count();
655
671
656
        return _cache { status => 'tooManyReserves', limit => $rule->rule_value} if $total_holds_count >= $rule->rule_value;
672
        return _cache { status => 'tooManyReserves', limit => $rule->rule_value }
673
            if $total_holds_count >= $rule->rule_value;
657
    }
674
    }
658
675
659
    my $branchitemrule =
676
    my $branchitemrule = C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->effective_itemtype );
660
      C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->effective_itemtype );
661
677
662
    if ( $branchitemrule->{holdallowed} eq 'not_allowed' ) {
678
    if ( $branchitemrule->{holdallowed} eq 'not_allowed' ) {
663
        return _cache { status => 'notReservable' };
679
        return _cache { status => 'notReservable' };
Lines 669-704 sub CanItemBeReserved { Link Here
669
        return _cache { status => 'cannotReserveFromOtherBranches' };
685
        return _cache { status => 'cannotReserveFromOtherBranches' };
670
    }
686
    }
671
687
672
    my $item_library = Koha::Libraries->find( {branchcode => $item->homebranch} );
688
    my $item_library = Koha::Libraries->find( { branchcode => $item->homebranch } );
673
    if ( $branchitemrule->{holdallowed} eq 'from_local_hold_group') {
689
    if ( $branchitemrule->{holdallowed} eq 'from_local_hold_group' ) {
674
        if($patron->branchcode ne $item->homebranch && !$item_library->validate_hold_sibling( {branchcode => $patron->branchcode} )) {
690
        if ( $patron->branchcode ne $item->homebranch
691
            && !$item_library->validate_hold_sibling( { branchcode => $patron->branchcode } ) )
692
        {
675
            return _cache { status => 'branchNotInHoldGroup' };
693
            return _cache { status => 'branchNotInHoldGroup' };
676
        }
694
        }
677
    }
695
    }
678
696
679
    if ($pickup_branchcode) {
697
    if ($pickup_branchcode) {
680
        my $destination = Koha::Libraries->find({
698
        my $destination = Koha::Libraries->find(
681
            branchcode => $pickup_branchcode,
699
            {
682
        });
700
                branchcode => $pickup_branchcode,
701
            }
702
        );
683
703
684
        unless ($destination) {
704
        unless ($destination) {
685
            return _cache { status => 'libraryNotFound' };
705
            return _cache { status => 'libraryNotFound' };
686
        }
706
        }
687
        unless ($destination->pickup_location) {
707
        unless ( $destination->pickup_location ) {
688
            return _cache { status => 'libraryNotPickupLocation' };
708
            return _cache { status => 'libraryNotPickupLocation' };
689
        }
709
        }
690
        unless ($item->can_be_transferred({ to => $destination })) {
710
        unless ( $item->can_be_transferred( { to => $destination } ) ) {
691
            return _cache { status => 'cannotBeTransferred' };
711
            return _cache { status => 'cannotBeTransferred' };
692
        }
712
        }
693
        if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup' && !$item_library->validate_hold_sibling( {branchcode => $pickup_branchcode} )) {
713
        if ( $branchitemrule->{hold_fulfillment_policy} eq 'holdgroup'
714
            && !$item_library->validate_hold_sibling( { branchcode => $pickup_branchcode } ) )
715
        {
694
            return _cache { status => 'pickupNotInHoldGroup' };
716
            return _cache { status => 'pickupNotInHoldGroup' };
695
        }
717
        }
696
        if ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup' && !Koha::Libraries->find({branchcode => $borrower->{branchcode}})->validate_hold_sibling({branchcode => $pickup_branchcode})) {
718
        if ( $branchitemrule->{hold_fulfillment_policy} eq 'patrongroup'
719
            && !Koha::Libraries->find( { branchcode => $borrower->{branchcode} } )
720
            ->validate_hold_sibling( { branchcode => $pickup_branchcode } ) )
721
        {
697
            return _cache { status => 'pickupNotInHoldGroup' };
722
            return _cache { status => 'pickupNotInHoldGroup' };
698
        }
723
        }
699
    }
724
    }
700
725
701
    if ( $opacitemholds eq "N" && C4::Context->interface eq 'opac') {
726
    if ( $opacitemholds eq "N" && C4::Context->interface eq 'opac' ) {
702
        return _cache { status => "recordHoldsOnly" };
727
        return _cache { status => "recordHoldsOnly" };
703
    }
728
    }
704
729
Lines 716-722 sub CanItemBeReserved { Link Here
716
=cut
741
=cut
717
742
718
sub CanReserveBeCanceledFromOpac {
743
sub CanReserveBeCanceledFromOpac {
719
    my ($reserve_id, $borrowernumber) = @_;
744
    my ( $reserve_id, $borrowernumber ) = @_;
720
745
721
    return unless $reserve_id and $borrowernumber;
746
    return unless $reserve_id and $borrowernumber;
722
    my $reserve = Koha::Holds->find($reserve_id) or return;
747
    my $reserve = Koha::Holds->find($reserve_id) or return;
Lines 742-747 sub GetOtherReserves { Link Here
742
    if ($checkreserves) {
767
    if ($checkreserves) {
743
        if ( $item->holdingbranch ne $checkreserves->{'branchcode'} ) {
768
        if ( $item->holdingbranch ne $checkreserves->{'branchcode'} ) {
744
            $messages->{'transfert'} = $checkreserves->{'branchcode'};
769
            $messages->{'transfert'} = $checkreserves->{'branchcode'};
770
745
            #minus priorities of others reservs
771
            #minus priorities of others reservs
746
            ModReserveMinusPriority(
772
            ModReserveMinusPriority(
747
                $itemnumber,
773
                $itemnumber,
Lines 754-771 sub GetOtherReserves { Link Here
754
                $item->holdingbranch,
780
                $item->holdingbranch,
755
                $checkreserves->{'branchcode'},
781
                $checkreserves->{'branchcode'},
756
                'Reserve'
782
                'Reserve'
757
              ),
783
                ),
758
              ;
784
                ;
759
        }
785
        }
760
786
761
     #step 2b : case of a reservation on the same branch, set the waiting status
787
        #step 2b : case of a reservation on the same branch, set the waiting status
762
        else {
788
        else {
763
            $messages->{'waiting'} = 1;
789
            $messages->{'waiting'} = 1;
764
            ModReserveMinusPriority(
790
            ModReserveMinusPriority(
765
                $itemnumber,
791
                $itemnumber,
766
                $checkreserves->{'reserve_id'},
792
                $checkreserves->{'reserve_id'},
767
            );
793
            );
768
            ModReserveStatus($itemnumber,'W');
794
            ModReserveStatus( $itemnumber, 'W' );
769
        }
795
        }
770
796
771
        $nextreservinfo = $checkreserves;
797
        $nextreservinfo = $checkreserves;
Lines 823-843 SELECT COUNT(*) FROM reserves WHERE biblionumber=? AND borrowernumber<>? Link Here
823
    };
849
    };
824
850
825
    my $dbh = C4::Context->dbh;
851
    my $dbh = C4::Context->dbh;
826
    my ( $fee ) = $dbh->selectrow_array( $borquery, undef, ($borrowernumber) );
852
    my ($fee) = $dbh->selectrow_array( $borquery, undef, ($borrowernumber) );
827
    $fee += 0;
853
    $fee += 0;
828
    my $hold_fee_mode = C4::Context->preference('HoldFeeMode') || 'not_always';
854
    my $hold_fee_mode = C4::Context->preference('HoldFeeMode') || 'not_always';
829
    if( $fee and $fee > 0 and $hold_fee_mode eq 'not_always' ) {
855
    if ( $fee and $fee > 0 and $hold_fee_mode eq 'not_always' ) {
856
830
        # This is a reconstruction of the old code:
857
        # This is a reconstruction of the old code:
831
        # Compare number of items with items issued, and optionally check holds
858
        # Compare number of items with items issued, and optionally check holds
832
        # If not all items are issued and there are no holds: charge no fee
859
        # If not all items are issued and there are no holds: charge no fee
833
        # NOTE: Lost, damaged, not-for-loan, etc. are just ignored here
860
        # NOTE: Lost, damaged, not-for-loan, etc. are just ignored here
834
        my ( $notissued, $reserved );
861
        my ( $notissued, $reserved );
835
        ( $notissued ) = $dbh->selectrow_array( $issue_qry, undef,
862
        ($notissued) = $dbh->selectrow_array(
836
            ( $biblionumber ) );
863
            $issue_qry, undef,
837
        if( $notissued == 0 ) {
864
            ($biblionumber)
865
        );
866
        if ( $notissued == 0 ) {
867
838
            # all items are issued
868
            # all items are issued
839
            ( $reserved ) = $dbh->selectrow_array( $holds_qry, undef,
869
            ($reserved) = $dbh->selectrow_array(
840
                ( $biblionumber, $borrowernumber ) );
870
                $holds_qry, undef,
871
                ( $biblionumber, $borrowernumber )
872
            );
841
            $fee = 0 if $reserved == 0;
873
            $fee = 0 if $reserved == 0;
842
        } else {
874
        } else {
843
            $fee = 0;
875
            $fee = 0;
Lines 864-885 sub GetReserveStatus { Link Here
864
896
865
    my $dbh = C4::Context->dbh;
897
    my $dbh = C4::Context->dbh;
866
898
867
    my ($sth, $found, $priority);
899
    my ( $sth, $found, $priority );
868
    if ( $itemnumber ) {
900
    if ($itemnumber) {
869
        $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1");
901
        $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1");
870
        $sth->execute($itemnumber);
902
        $sth->execute($itemnumber);
871
        ($found, $priority) = $sth->fetchrow_array;
903
        ( $found, $priority ) = $sth->fetchrow_array;
872
    }
904
    }
873
905
874
    if(defined $found) {
906
    if ( defined $found ) {
875
        return 'Waiting'  if $found eq 'W' and $priority == 0;
907
        return 'Waiting'    if $found eq 'W' and $priority == 0;
876
        return 'Processing'  if $found eq 'P';
908
        return 'Processing' if $found eq 'P';
877
        return 'Finished' if $found eq 'F';
909
        return 'Finished'   if $found eq 'F';
878
    }
910
    }
879
911
880
    return 'Reserved' if defined $priority && $priority > 0;
912
    return 'Reserved' if defined $priority && $priority > 0;
881
913
882
    return ''; # empty string here will remove need for checking undef, or less log lines
914
    return '';    # empty string here will remove need for checking undef, or less log lines
883
}
915
}
884
916
885
=head2 CheckReserves
917
=head2 CheckReserves
Lines 912-920 table in the Koha database. Link Here
912
944
913
sub CheckReserves {
945
sub CheckReserves {
914
    my ( $item, $lookahead_days, $ignore_borrowers ) = @_;
946
    my ( $item, $lookahead_days, $ignore_borrowers ) = @_;
947
915
    # note: we get the itemnumber because we might have started w/ just the barcode.  Now we know for sure we have it.
948
    # note: we get the itemnumber because we might have started w/ just the barcode.  Now we know for sure we have it.
916
949
917
    return unless $item; # bail if we got nothing.
950
    return unless $item;    # bail if we got nothing.
918
951
919
    return if ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
952
    return if ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
920
953
Lines 927-942 sub CheckReserves { Link Here
927
    my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan;
960
    my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan;
928
    if ( !$dont_trap ) {
961
    if ( !$dont_trap ) {
929
        my $item_type = $item->effective_itemtype;
962
        my $item_type = $item->effective_itemtype;
930
        if ( $item_type ) {
963
        if ($item_type) {
931
            return if Koha::ItemTypes->find( $item_type )->notforloan;
964
            return if Koha::ItemTypes->find($item_type)->notforloan;
932
        }
965
        }
933
    }
966
    } else {
934
    else {
935
        return;
967
        return;
936
    }
968
    }
937
969
938
    # Find this item in the reserves
970
    # Find this item in the reserves
939
    my @reserves = _Findgroupreserve( $item->biblionumber, $item->itemnumber, $lookahead_days, $ignore_borrowers);
971
    my @reserves = _Findgroupreserve( $item->biblionumber, $item->itemnumber, $lookahead_days, $ignore_borrowers );
940
972
941
    # $priority and $highest are used to find the most important item
973
    # $priority and $highest are used to find the most important item
942
    # in the list returned by &_Findgroupreserve. (The lower $priority,
974
    # in the list returned by &_Findgroupreserve. (The lower $priority,
Lines 944-962 sub CheckReserves { Link Here
944
    # $highest is the most important item we've seen so far.
976
    # $highest is the most important item we've seen so far.
945
    my $highest;
977
    my $highest;
946
978
947
    if (scalar @reserves) {
979
    if ( scalar @reserves ) {
948
        my $LocalHoldsPriority = C4::Context->preference('LocalHoldsPriority');
980
        my $LocalHoldsPriority              = C4::Context->preference('LocalHoldsPriority');
949
        my $LocalHoldsPriorityPatronControl = C4::Context->preference('LocalHoldsPriorityPatronControl');
981
        my $LocalHoldsPriorityPatronControl = C4::Context->preference('LocalHoldsPriorityPatronControl');
950
        my $LocalHoldsPriorityItemControl = C4::Context->preference('LocalHoldsPriorityItemControl');
982
        my $LocalHoldsPriorityItemControl   = C4::Context->preference('LocalHoldsPriorityItemControl');
951
        my $priority = 10000000;
983
        my $priority                        = 10000000;
952
984
953
        foreach my $res (@reserves) {
985
        foreach my $res (@reserves) {
954
            if ($res->{'found'} && $res->{'found'} eq 'W') {
986
            if ( $res->{'found'} && $res->{'found'} eq 'W' ) {
955
                return ( "Waiting", $res, \@reserves ); # Found it, it is waiting
987
                return ( "Waiting", $res, \@reserves );    # Found it, it is waiting
956
            } elsif ($res->{'found'} && $res->{'found'} eq 'P') {
988
            } elsif ( $res->{'found'} && $res->{'found'} eq 'P' ) {
957
                return ( "Processing", $res, \@reserves ); # Found determinated hold, e. g. the transferred one
989
                return ( "Processing", $res, \@reserves );    # Found determinated hold, e. g. the transferred one
958
            } elsif ($res->{'found'} && $res->{'found'} eq 'T') {
990
            } elsif ( $res->{'found'} && $res->{'found'} eq 'T' ) {
959
                return ( "Transferred", $res, \@reserves ); # Found determinated hold, e. g. the transferred one
991
                return ( "Transferred", $res, \@reserves );    # Found determinated hold, e. g. the transferred one
960
            } else {
992
            } else {
961
                my $patron;
993
                my $patron;
962
                my $local_hold_match;
994
                my $local_hold_match;
Lines 964-999 sub CheckReserves { Link Here
964
                if ($LocalHoldsPriority) {
996
                if ($LocalHoldsPriority) {
965
                    $patron = Koha::Patrons->find( $res->{borrowernumber} );
997
                    $patron = Koha::Patrons->find( $res->{borrowernumber} );
966
998
967
                    unless ($item->exclude_from_local_holds_priority || $patron->category->exclude_from_local_holds_priority) {
999
                    unless ( $item->exclude_from_local_holds_priority
968
                        my $local_holds_priority_item_branchcode =
1000
                        || $patron->category->exclude_from_local_holds_priority )
969
                            $item->$LocalHoldsPriorityItemControl;
1001
                    {
1002
                        my $local_holds_priority_item_branchcode = $item->$LocalHoldsPriorityItemControl;
970
                        my $local_holds_priority_patron_branchcode =
1003
                        my $local_holds_priority_patron_branchcode =
971
                            ( $LocalHoldsPriorityPatronControl eq 'PickupLibrary' )
1004
                              ( $LocalHoldsPriorityPatronControl eq 'PickupLibrary' ) ? $res->{branchcode}
972
                            ? $res->{branchcode}
1005
                            : ( $LocalHoldsPriorityPatronControl eq 'HomeLibrary' )   ? $patron->branchcode
973
                            : ( $LocalHoldsPriorityPatronControl eq 'HomeLibrary' )
1006
                            :                                                           undef;
974
                            ? $patron->branchcode
975
                            : undef;
976
                        $local_hold_match =
1007
                        $local_hold_match =
977
                            $local_holds_priority_item_branchcode eq
1008
                            $local_holds_priority_item_branchcode eq $local_holds_priority_patron_branchcode;
978
                            $local_holds_priority_patron_branchcode;
979
                    }
1009
                    }
980
                }
1010
                }
981
1011
982
                # See if this item is more important than what we've got so far
1012
                # See if this item is more important than what we've got so far
983
                if ( ( $res->{'priority'} && $res->{'priority'} < $priority ) || $local_hold_match ) {
1013
                if ( ( $res->{'priority'} && $res->{'priority'} < $priority ) || $local_hold_match ) {
984
                    next if $res->{item_group_id} && ( !$item->item_group || $item->item_group->id != $res->{item_group_id} );
1014
                    next
1015
                        if $res->{item_group_id}
1016
                        && ( !$item->item_group || $item->item_group->id != $res->{item_group_id} );
985
                    next if $res->{itemtype} && $res->{itemtype} ne $item->effective_itemtype;
1017
                    next if $res->{itemtype} && $res->{itemtype} ne $item->effective_itemtype;
986
                    $patron //= Koha::Patrons->find( $res->{borrowernumber} );
1018
                    $patron //= Koha::Patrons->find( $res->{borrowernumber} );
987
                    my $branch = Koha::Policy::Holds->holds_control_library( $item, $patron );
1019
                    my $branch         = Koha::Policy::Holds->holds_control_library( $item, $patron );
988
                    my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$item->effective_itemtype);
1020
                    my $branchitemrule = C4::Circulation::GetBranchItemRule( $branch, $item->effective_itemtype );
989
                    next if ($branchitemrule->{'holdallowed'} eq 'not_allowed');
1021
                    next if ( $branchitemrule->{'holdallowed'} eq 'not_allowed' );
990
                    next if (($branchitemrule->{'holdallowed'} eq 'from_home_library') && ($item->homebranch ne $patron->branchcode));
1022
                    next
991
                    my $library = Koha::Libraries->find({branchcode=>$item->homebranch});
1023
                        if ( ( $branchitemrule->{'holdallowed'} eq 'from_home_library' )
992
                    next if (($branchitemrule->{'holdallowed'} eq 'from_local_hold_group') && (!$library->validate_hold_sibling({branchcode => $patron->branchcode}) ));
1024
                        && ( $item->homebranch ne $patron->branchcode ) );
1025
                    my $library = Koha::Libraries->find( { branchcode => $item->homebranch } );
1026
                    next
1027
                        if ( ( $branchitemrule->{'holdallowed'} eq 'from_local_hold_group' )
1028
                        && ( !$library->validate_hold_sibling( { branchcode => $patron->branchcode } ) ) );
993
                    my $hold_fulfillment_policy = $branchitemrule->{hold_fulfillment_policy};
1029
                    my $hold_fulfillment_policy = $branchitemrule->{hold_fulfillment_policy};
994
                    next if ( ($hold_fulfillment_policy eq 'holdgroup') && (!$library->validate_hold_sibling({branchcode => $res->{branchcode}})) );
1030
                    next
995
                    next if ( ($hold_fulfillment_policy eq 'homebranch') && ($res->{branchcode} ne $item->$hold_fulfillment_policy) );
1031
                        if ( ( $hold_fulfillment_policy eq 'holdgroup' )
996
                    next if ( ($hold_fulfillment_policy eq 'holdingbranch') && ($res->{branchcode} ne $item->$hold_fulfillment_policy) );
1032
                        && ( !$library->validate_hold_sibling( { branchcode => $res->{branchcode} } ) ) );
1033
                    next
1034
                        if ( ( $hold_fulfillment_policy eq 'homebranch' )
1035
                        && ( $res->{branchcode} ne $item->$hold_fulfillment_policy ) );
1036
                    next
1037
                        if ( ( $hold_fulfillment_policy eq 'holdingbranch' )
1038
                        && ( $res->{branchcode} ne $item->$hold_fulfillment_policy ) );
997
                    next unless $item->can_be_transferred( { to => Koha::Libraries->find( $res->{branchcode} ) } );
1039
                    next unless $item->can_be_transferred( { to => Koha::Libraries->find( $res->{branchcode} ) } );
998
                    $priority = $res->{'priority'};
1040
                    $priority = $res->{'priority'};
999
                    $highest  = $res;
1041
                    $highest  = $res;
Lines 1010-1016 sub CheckReserves { Link Here
1010
        return ( "Reserved", $highest, \@reserves );
1052
        return ( "Reserved", $highest, \@reserves );
1011
    }
1053
    }
1012
1054
1013
    return ( '' );
1055
    return ('');
1014
}
1056
}
1015
1057
1016
=head2 CancelExpiredReserves
1058
=head2 CancelExpiredReserves
Lines 1023-1057 Cancels all reserves with an expiration date from before today. Link Here
1023
1065
1024
sub CancelExpiredReserves {
1066
sub CancelExpiredReserves {
1025
    my $cancellation_reason = shift;
1067
    my $cancellation_reason = shift;
1026
    my $today = dt_from_string();
1068
    my $today               = dt_from_string();
1027
    my $cancel_on_holidays = C4::Context->preference('ExpireReservesOnHolidays');
1069
    my $cancel_on_holidays  = C4::Context->preference('ExpireReservesOnHolidays');
1028
    my $expireWaiting = C4::Context->preference('ExpireReservesMaxPickUpDelay');
1070
    my $expireWaiting       = C4::Context->preference('ExpireReservesMaxPickUpDelay');
1029
1071
1030
    my $dtf = Koha::Database->new->schema->storage->datetime_parser;
1072
    my $dtf    = Koha::Database->new->schema->storage->datetime_parser;
1031
    my $params = {
1073
    my $params = {
1032
        -or => [
1074
        -or => [
1033
            { expirationdate => { '<', $dtf->format_date($today) } },
1075
            { expirationdate         => { '<', $dtf->format_date($today) } },
1034
            { patron_expiration_date => { '<' => $dtf->format_date($today) } }
1076
            { patron_expiration_date => { '<' => $dtf->format_date($today) } }
1035
        ]
1077
        ]
1036
    };
1078
    };
1037
1079
1038
    $params->{found} = [ { '!=', 'W' }, undef ]  unless $expireWaiting;
1080
    $params->{found} = [ { '!=', 'W' }, undef ] unless $expireWaiting;
1039
1081
1040
    # FIXME To move to Koha::Holds->search_expired (?)
1082
    # FIXME To move to Koha::Holds->search_expired (?)
1041
    my $holds = Koha::Holds->search( $params );
1083
    my $holds = Koha::Holds->search($params);
1042
1084
1043
    while ( my $hold = $holds->next ) {
1085
    while ( my $hold = $holds->next ) {
1044
        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
1086
        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
1045
1087
1046
        next if !$cancel_on_holidays && $calendar->is_holiday( $today );
1088
        next if !$cancel_on_holidays && $calendar->is_holiday($today);
1047
1089
1048
        my $cancel_params = {};
1090
        my $cancel_params = {};
1049
        $cancel_params->{cancellation_reason} = $cancellation_reason if defined($cancellation_reason);
1091
        $cancel_params->{cancellation_reason} = $cancellation_reason if defined($cancellation_reason);
1050
        if ( defined($hold->found) && $hold->found eq 'W' ) {
1092
        if ( defined( $hold->found ) && $hold->found eq 'W' ) {
1051
            $cancel_params->{charge_cancel_fee} = 1;
1093
            $cancel_params->{charge_cancel_fee} = 1;
1052
        }
1094
        }
1053
        $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
1095
        $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
1054
        $hold->cancel( $cancel_params );
1096
        $hold->cancel($cancel_params);
1055
    }
1097
    }
1056
}
1098
}
1057
1099
Lines 1111-1137 itemnumber and supplying itemnumber. Link Here
1111
=cut
1153
=cut
1112
1154
1113
sub ModReserve {
1155
sub ModReserve {
1114
    my ( $params ) = @_;
1156
    my ($params) = @_;
1115
1157
1116
    my $rank = $params->{'rank'};
1158
    my $rank                = $params->{'rank'};
1117
    my $reserve_id = $params->{'reserve_id'};
1159
    my $reserve_id          = $params->{'reserve_id'};
1118
    my $branchcode = $params->{'branchcode'};
1160
    my $branchcode          = $params->{'branchcode'};
1119
    my $itemnumber = $params->{'itemnumber'};
1161
    my $itemnumber          = $params->{'itemnumber'};
1120
    my $suspend_until = $params->{'suspend_until'};
1162
    my $suspend_until       = $params->{'suspend_until'};
1121
    my $borrowernumber = $params->{'borrowernumber'};
1163
    my $borrowernumber      = $params->{'borrowernumber'};
1122
    my $biblionumber = $params->{'biblionumber'};
1164
    my $biblionumber        = $params->{'biblionumber'};
1123
    my $cancellation_reason = $params->{'cancellation_reason'};
1165
    my $cancellation_reason = $params->{'cancellation_reason'};
1124
    my $date = $params->{expirationdate};
1166
    my $date                = $params->{expirationdate};
1125
1167
1126
    return if defined $rank && $rank eq "n";
1168
    return if defined $rank && $rank eq "n";
1127
1169
1128
    return unless ( $reserve_id || ( $borrowernumber && ( $biblionumber || $itemnumber ) ) );
1170
    return unless ( $reserve_id || ( $borrowernumber && ( $biblionumber || $itemnumber ) ) );
1129
1171
1130
    my $hold;
1172
    my $hold;
1131
    unless ( $reserve_id ) {
1173
    unless ($reserve_id) {
1132
        my $holds = Koha::Holds->search({ biblionumber => $biblionumber, borrowernumber => $borrowernumber, itemnumber => $itemnumber });
1174
        my $holds = Koha::Holds->search(
1133
        return unless $holds->count; # FIXME Should raise an exception
1175
            { biblionumber => $biblionumber, borrowernumber => $borrowernumber, itemnumber => $itemnumber } );
1134
        $hold = $holds->next;
1176
        return unless $holds->count;    # FIXME Should raise an exception
1177
        $hold       = $holds->next;
1135
        $reserve_id = $hold->reserve_id;
1178
        $reserve_id = $hold->reserve_id;
1136
    }
1179
    }
1137
1180
Lines 1141-1156 sub ModReserve { Link Here
1141
    Koha::Exceptions::ObjectNotFound->throw( 'No hold with id ' . $reserve_id ) unless $hold;
1184
    Koha::Exceptions::ObjectNotFound->throw( 'No hold with id ' . $reserve_id ) unless $hold;
1142
1185
1143
    if ( $rank eq "del" ) {
1186
    if ( $rank eq "del" ) {
1144
        $hold->cancel({ cancellation_reason => $cancellation_reason });
1187
        $hold->cancel( { cancellation_reason => $cancellation_reason } );
1145
    }
1188
    } elsif ( $hold->found && $hold->priority eq '0' && $date ) {
1146
    elsif ($hold->found && $hold->priority eq '0' && $date) {
1147
        logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
1189
        logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
1148
            if C4::Context->preference('HoldsLog');
1190
            if C4::Context->preference('HoldsLog');
1149
1191
1150
        # The only column that can be updated for a found hold is the expiration date
1192
        # The only column that can be updated for a found hold is the expiration date
1151
        $hold->expirationdate($date)->store();
1193
        $hold->expirationdate($date)->store();
1152
    }
1194
    } elsif ( $rank =~ /^\d+/ and $rank > 0 ) {
1153
    elsif ($rank =~ /^\d+/ and $rank > 0) {
1154
        logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
1195
        logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
1155
            if C4::Context->preference('HoldsLog');
1196
            if C4::Context->preference('HoldsLog');
1156
1197
Lines 1161-1186 sub ModReserve { Link Here
1161
            found       => undef,
1202
            found       => undef,
1162
            waitingdate => undef
1203
            waitingdate => undef
1163
        };
1204
        };
1164
        if (exists $params->{reservedate}) {
1205
        if ( exists $params->{reservedate} ) {
1165
            $properties->{reservedate} = $params->{reservedate} || undef;
1206
            $properties->{reservedate} = $params->{reservedate} || undef;
1166
        }
1207
        }
1167
        if (exists $params->{expirationdate}) {
1208
        if ( exists $params->{expirationdate} ) {
1168
            $properties->{expirationdate} = $params->{expirationdate} || undef;
1209
            $properties->{expirationdate} = $params->{expirationdate} || undef;
1169
        }
1210
        }
1170
1211
1171
        $hold->set($properties)->store();
1212
        $hold->set($properties)->store();
1172
1213
1173
        if ( defined( $suspend_until ) ) {
1214
        if ( defined($suspend_until) ) {
1174
            if ( $suspend_until ) {
1215
            if ($suspend_until) {
1175
                $hold->suspend_hold( $suspend_until );
1216
                $hold->suspend_hold($suspend_until);
1176
            } else {
1217
            } else {
1218
1177
                # If the hold is suspended leave the hold suspended, but convert it to an indefinite hold.
1219
                # If the hold is suspended leave the hold suspended, but convert it to an indefinite hold.
1178
                # If the hold is not suspended, this does nothing.
1220
                # If the hold is not suspended, this does nothing.
1179
                $hold->set( { suspend_until => undef } )->store();
1221
                $hold->set( { suspend_until => undef } )->store();
1180
            }
1222
            }
1181
        }
1223
        }
1182
1224
1183
        _FixPriority({ reserve_id => $reserve_id, rank =>$rank });
1225
        _FixPriority( { reserve_id => $reserve_id, rank => $rank } );
1184
    }
1226
    }
1185
}
1227
}
1186
1228
Lines 1199-1216 $newstatus is the new status. Link Here
1199
sub ModReserveStatus {
1241
sub ModReserveStatus {
1200
1242
1201
    #first : check if we have a reservation for this item .
1243
    #first : check if we have a reservation for this item .
1202
    my ($itemnumber, $newstatus) = @_;
1244
    my ( $itemnumber, $newstatus ) = @_;
1203
    my $dbh = C4::Context->dbh;
1245
    my $dbh = C4::Context->dbh;
1204
1246
1205
    my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1247
    my $query =
1248
        "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1206
    my $sth_set = $dbh->prepare($query);
1249
    my $sth_set = $dbh->prepare($query);
1207
    $sth_set->execute( $newstatus, $itemnumber );
1250
    $sth_set->execute( $newstatus, $itemnumber );
1208
1251
1209
    my $item = Koha::Items->find($itemnumber);
1252
    my $item = Koha::Items->find($itemnumber);
1210
    if ( $item->location && $item->location eq 'CART'
1253
    if (   $item->location
1254
        && $item->location eq 'CART'
1211
        && ( !$item->permanent_location || $item->permanent_location ne 'CART' )
1255
        && ( !$item->permanent_location || $item->permanent_location ne 'CART' )
1212
        && $newstatus ) {
1256
        && $newstatus )
1213
      CartToShelf( $itemnumber );
1257
    {
1258
        CartToShelf($itemnumber);
1214
    }
1259
    }
1215
}
1260
}
1216
1261
Lines 1245-1254 sub ModReserveAffect { Link Here
1245
    # waiting in order to not send duplicate hold filled notifications
1290
    # waiting in order to not send duplicate hold filled notifications
1246
1291
1247
    my $hold;
1292
    my $hold;
1293
1248
    # Find hold by id if we have it
1294
    # Find hold by id if we have it
1249
    $hold = Koha::Holds->find( $reserve_id ) if $reserve_id;
1295
    $hold = Koha::Holds->find($reserve_id) if $reserve_id;
1296
1250
    # Find item level hold for this item if there is one
1297
    # Find item level hold for this item if there is one
1251
    $hold ||= Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->next();
1298
    $hold ||= Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->next();
1299
1252
    # Find record level hold if there is no item level hold
1300
    # Find record level hold if there is no item level hold
1253
    $hold ||= Koha::Holds->search( { borrowernumber => $borrowernumber, biblionumber => $biblionumber } )->next();
1301
    $hold ||= Koha::Holds->search( { borrowernumber => $borrowernumber, biblionumber => $biblionumber } )->next();
1254
1302
Lines 1260-1287 sub ModReserveAffect { Link Here
1260
1308
1261
    if ($transferToDo) {
1309
    if ($transferToDo) {
1262
        $hold->set_transfer();
1310
        $hold->set_transfer();
1263
    } elsif (C4::Context->preference('HoldsNeedProcessingSIP')
1311
    } elsif ( C4::Context->preference('HoldsNeedProcessingSIP')
1264
             && C4::Context->interface eq 'sip'
1312
        && C4::Context->interface eq 'sip'
1265
             && !$already_on_shelf) {
1313
        && !$already_on_shelf )
1314
    {
1266
        $hold->set_processing();
1315
        $hold->set_processing();
1267
    } else {
1316
    } else {
1268
        $hold->set_waiting($desk_id);
1317
        $hold->set_waiting($desk_id);
1269
        _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
1318
        _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
1319
1270
        # Complete transfer if one exists
1320
        # Complete transfer if one exists
1271
        my $transfer = $hold->item->get_transfer;
1321
        my $transfer = $hold->item->get_transfer;
1272
        $transfer->receive if $transfer;
1322
        $transfer->receive if $transfer;
1273
    }
1323
    }
1274
1324
1275
    _koha_notify_hold_changed( $hold ) if $notify_library;
1325
    _koha_notify_hold_changed($hold) if $notify_library;
1276
1326
1277
    _FixPriority( { biblionumber => $biblionumber } );
1327
    _FixPriority( { biblionumber => $biblionumber } );
1278
    my $item = Koha::Items->find($itemnumber);
1328
    my $item = Koha::Items->find($itemnumber);
1279
    if ( $item->location && $item->location eq 'CART'
1329
    if (   $item->location
1280
        && ( !$item->permanent_location || $item->permanent_location ne 'CART' ) ) {
1330
        && $item->location eq 'CART'
1281
      CartToShelf( $itemnumber );
1331
        && ( !$item->permanent_location || $item->permanent_location ne 'CART' ) )
1332
    {
1333
        CartToShelf($itemnumber);
1282
    }
1334
    }
1283
1335
1284
    my $std = $dbh->prepare(q{
1336
    my $std = $dbh->prepare(
1337
        q{
1285
        DELETE  q, t
1338
        DELETE  q, t
1286
        FROM    tmp_holdsqueue q
1339
        FROM    tmp_holdsqueue q
1287
        INNER JOIN hold_fill_targets t
1340
        INNER JOIN hold_fill_targets t
Lines 1291-1298 sub ModReserveAffect { Link Here
1291
            AND q.item_level_request = t.item_level_request
1344
            AND q.item_level_request = t.item_level_request
1292
            AND q.holdingbranch = t.source_branchcode
1345
            AND q.holdingbranch = t.source_branchcode
1293
        WHERE t.reserve_id = ?
1346
        WHERE t.reserve_id = ?
1294
    });
1347
    }
1295
    $std->execute($hold->reserve_id);
1348
    );
1349
    $std->execute( $hold->reserve_id );
1296
1350
1297
    logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
1351
    logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
1298
        if C4::Context->preference('HoldsLog');
1352
        if C4::Context->preference('HoldsLog');
Lines 1314-1322 sub ModReserveCancelAll { Link Here
1314
    my ( $itemnumber, $borrowernumber, $cancellation_reason ) = @_;
1368
    my ( $itemnumber, $borrowernumber, $cancellation_reason ) = @_;
1315
1369
1316
    #step 1 : cancel the reservation
1370
    #step 1 : cancel the reservation
1317
    my $holds = Koha::Holds->search({ itemnumber => $itemnumber, borrowernumber => $borrowernumber });
1371
    my $holds = Koha::Holds->search( { itemnumber => $itemnumber, borrowernumber => $borrowernumber } );
1318
    return unless $holds->count;
1372
    return unless $holds->count;
1319
    $holds->next->cancel({ cancellation_reason => $cancellation_reason });
1373
    $holds->next->cancel( { cancellation_reason => $cancellation_reason } );
1320
1374
1321
    #step 2 launch the subroutine of the others reserves
1375
    #step 2 launch the subroutine of the others reserves
1322
    ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1376
    ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
Lines 1344-1351 sub ModReserveMinusPriority { Link Here
1344
    ";
1398
    ";
1345
    my $sth_upd = $dbh->prepare($query);
1399
    my $sth_upd = $dbh->prepare($query);
1346
    $sth_upd->execute( $itemnumber, $reserve_id );
1400
    $sth_upd->execute( $itemnumber, $reserve_id );
1401
1347
    # second step update all others reserves
1402
    # second step update all others reserves
1348
    _FixPriority({ reserve_id => $reserve_id, rank => '0' });
1403
    _FixPriority( { reserve_id => $reserve_id, rank => '0' } );
1349
}
1404
}
1350
1405
1351
=head2 IsAvailableForItemLevelRequest
1406
=head2 IsAvailableForItemLevelRequest
Lines 1378-1403 checks with CanItemBeReserved or CanBookBeReserved. Link Here
1378
=cut
1433
=cut
1379
1434
1380
sub IsAvailableForItemLevelRequest {
1435
sub IsAvailableForItemLevelRequest {
1381
    my $item                = shift;
1436
    my $item              = shift;
1382
    my $patron              = shift;
1437
    my $patron            = shift;
1383
    my $pickup_branchcode   = shift;
1438
    my $pickup_branchcode = shift;
1384
1439
1385
    my $dbh = C4::Context->dbh;
1440
    my $dbh = C4::Context->dbh;
1441
1386
    # must check the notforloan setting of the itemtype
1442
    # must check the notforloan setting of the itemtype
1387
    # FIXME - a lot of places in the code do this
1443
    # FIXME - a lot of places in the code do this
1388
    #         or something similar - need to be
1444
    #         or something similar - need to be
1389
    #         consolidated
1445
    #         consolidated
1390
    my $itemtype = $item->effective_itemtype;
1446
    my $itemtype = $item->effective_itemtype;
1391
    return 0
1447
    return 0
1392
      unless defined $itemtype;
1448
        unless defined $itemtype;
1393
    my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
1449
    my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
1394
1450
1395
    return 0 if
1451
    return 0
1396
        $notforloan_per_itemtype ||
1452
        if $notforloan_per_itemtype
1397
        $item->itemlost        ||
1453
        || $item->itemlost
1398
        $item->notforloan > 0  || # item with negative or zero notforloan value is holdable
1454
        || $item->notforloan > 0
1399
        $item->withdrawn        ||
1455
        ||    # item with negative or zero notforloan value is holdable
1400
        ($item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems'));
1456
        $item->withdrawn
1457
        || ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
1401
1458
1402
    if ($pickup_branchcode) {
1459
    if ($pickup_branchcode) {
1403
        my $destination = Koha::Libraries->find($pickup_branchcode);
1460
        my $destination = Koha::Libraries->find($pickup_branchcode);
Lines 1405-1414 sub IsAvailableForItemLevelRequest { Link Here
1405
        return 0 unless $destination->pickup_location;
1462
        return 0 unless $destination->pickup_location;
1406
        return 0 unless $item->can_be_transferred( { to => $destination } );
1463
        return 0 unless $item->can_be_transferred( { to => $destination } );
1407
        my $reserves_control_branch = Koha::Policy::Holds->holds_control_library( $item, $patron );
1464
        my $reserves_control_branch = Koha::Policy::Holds->holds_control_library( $item, $patron );
1408
        my $branchitemrule =
1465
        my $branchitemrule          = C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->itype );
1409
            C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->itype );
1466
        my $home_library            = Koha::Libraries->find( { branchcode => $item->homebranch } );
1410
        my $home_library = Koha::Libraries->find( {branchcode => $item->homebranch} );
1467
        return 0
1411
        return 0 unless $branchitemrule->{hold_fulfillment_policy} ne 'holdgroup' || $home_library->validate_hold_sibling( {branchcode => $pickup_branchcode} );
1468
            unless $branchitemrule->{hold_fulfillment_policy} ne 'holdgroup'
1469
            || $home_library->validate_hold_sibling( { branchcode => $pickup_branchcode } );
1412
    }
1470
    }
1413
1471
1414
    my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
1472
    my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
Lines 1454-1475 sub ItemsAnyAvailableAndNotRestricted { Link Here
1454
1512
1455
    foreach my $i (@items) {
1513
    foreach my $i (@items) {
1456
        my $reserves_control_branch = Koha::Policy::Holds->holds_control_library( $i, $param->{patron} );
1514
        my $reserves_control_branch = Koha::Policy::Holds->holds_control_library( $i, $param->{patron} );
1457
        my $branchitemrule =
1515
        my $branchitemrule          = C4::Circulation::GetBranchItemRule( $reserves_control_branch, $i->itype );
1458
            C4::Circulation::GetBranchItemRule( $reserves_control_branch, $i->itype );
1516
        my $item_library            = Koha::Libraries->find( { branchcode => $i->homebranch } );
1459
        my $item_library = Koha::Libraries->find( { branchcode => $i->homebranch } );
1460
1517
1461
        # we can return (end the loop) when first one found:
1518
        # we can return (end the loop) when first one found:
1462
        return 1
1519
        return 1
1463
            unless $i->itemlost
1520
            unless $i->itemlost
1464
            || $i->notforloan # items with non-zero notforloan cannot be checked out
1521
            || $i->notforloan    # items with non-zero notforloan cannot be checked out
1465
            || $i->withdrawn
1522
            || $i->withdrawn
1466
            || $i->onloan
1523
            || $i->onloan
1467
            || IsItemOnHoldAndFound( $i->id )
1524
            || IsItemOnHoldAndFound( $i->id )
1468
            || ( $i->damaged
1525
            || ( $i->damaged
1469
                 && ! C4::Context->preference('AllowHoldsOnDamagedItems') )
1526
            && !C4::Context->preference('AllowHoldsOnDamagedItems') )
1470
            || Koha::ItemTypes->find( $i->effective_itemtype() )->notforloan
1527
            || Koha::ItemTypes->find( $i->effective_itemtype() )->notforloan
1471
            || $branchitemrule->{holdallowed} eq 'from_home_library' && $param->{patron}->branchcode ne $i->homebranch
1528
            || $branchitemrule->{holdallowed} eq 'from_home_library' && $param->{patron}->branchcode ne $i->homebranch
1472
            || $branchitemrule->{holdallowed} eq 'from_local_hold_group' && ! $item_library->validate_hold_sibling( { branchcode => $param->{patron}->branchcode } )
1529
            || $branchitemrule->{holdallowed} eq 'from_local_hold_group'
1530
            && !$item_library->validate_hold_sibling( { branchcode => $param->{patron}->branchcode } )
1473
            || CanItemBeReserved( $param->{patron}, $i )->{status} ne 'OK';
1531
            || CanItemBeReserved( $param->{patron}, $i )->{status} ne 'OK';
1474
    }
1532
    }
1475
1533
Lines 1488-1518 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was Link Here
1488
sub AlterPriority {
1546
sub AlterPriority {
1489
    my ( $where, $reserve_id, $prev_priority, $next_priority, $first_priority, $last_priority ) = @_;
1547
    my ( $where, $reserve_id, $prev_priority, $next_priority, $first_priority, $last_priority ) = @_;
1490
1548
1491
    my $hold = Koha::Holds->find( $reserve_id );
1549
    my $hold = Koha::Holds->find($reserve_id);
1492
    return unless $hold;
1550
    return unless $hold;
1493
1551
1494
    if ( $hold->cancellationdate ) {
1552
    if ( $hold->cancellationdate ) {
1495
        warn "I cannot alter the priority for reserve_id $reserve_id, the reserve has been cancelled (" . $hold->cancellationdate . ')';
1553
        warn "I cannot alter the priority for reserve_id $reserve_id, the reserve has been cancelled ("
1554
            . $hold->cancellationdate . ')';
1496
        return;
1555
        return;
1497
    }
1556
    }
1498
1557
1499
    if ( $where eq 'up' ) {
1558
    if ( $where eq 'up' ) {
1500
      return unless $prev_priority;
1559
        return unless $prev_priority;
1501
      _FixPriority({ reserve_id => $reserve_id, rank => $prev_priority })
1560
        _FixPriority( { reserve_id => $reserve_id, rank => $prev_priority } );
1502
    } elsif ( $where eq 'down' ) {
1561
    } elsif ( $where eq 'down' ) {
1503
      return unless $next_priority;
1562
        return unless $next_priority;
1504
      _FixPriority({ reserve_id => $reserve_id, rank => $next_priority })
1563
        _FixPriority( { reserve_id => $reserve_id, rank => $next_priority } );
1505
    } elsif ( $where eq 'top' ) {
1564
    } elsif ( $where eq 'top' ) {
1506
      _FixPriority({ reserve_id => $reserve_id, rank => $first_priority })
1565
        _FixPriority( { reserve_id => $reserve_id, rank => $first_priority } );
1507
    } elsif ( $where eq 'bottom' ) {
1566
    } elsif ( $where eq 'bottom' ) {
1508
      _FixPriority({ reserve_id => $reserve_id, rank => $last_priority });
1567
        _FixPriority( { reserve_id => $reserve_id, rank => $last_priority } );
1509
    }
1568
    }
1510
1569
1511
    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
1570
    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue( { biblio_ids => [ $hold->biblionumber ] } )
1512
        {
1571
        if C4::Context->preference('RealTimeHoldsQueue');
1513
            biblio_ids => [ $hold->biblionumber ]
1572
1514
        }
1515
    ) if C4::Context->preference('RealTimeHoldsQueue');
1516
    # FIXME Should return the new priority
1573
    # FIXME Should return the new priority
1517
}
1574
}
1518
1575
Lines 1525-1538 This function sets the lowestPriority field to true if is false, and false if it Link Here
1525
=cut
1582
=cut
1526
1583
1527
sub ToggleLowestPriority {
1584
sub ToggleLowestPriority {
1528
    my ( $reserve_id ) = @_;
1585
    my ($reserve_id) = @_;
1529
1586
1530
    my $dbh = C4::Context->dbh;
1587
    my $dbh = C4::Context->dbh;
1531
1588
1532
    my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1589
    my $sth = $dbh->prepare("UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1533
    $sth->execute( $reserve_id );
1590
    $sth->execute($reserve_id);
1534
1591
1535
    _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1592
    _FixPriority( { reserve_id => $reserve_id, rank => '999999' } );
1536
}
1593
}
1537
1594
1538
=head2 ToggleSuspend
1595
=head2 ToggleSuspend
Lines 1548-1559 be cleared when it is unsuspended. Link Here
1548
sub ToggleSuspend {
1605
sub ToggleSuspend {
1549
    my ( $reserve_id, $suspend_until ) = @_;
1606
    my ( $reserve_id, $suspend_until ) = @_;
1550
1607
1551
    my $hold = Koha::Holds->find( $reserve_id );
1608
    my $hold = Koha::Holds->find($reserve_id);
1552
1609
1553
    if ( $hold->is_suspended ) {
1610
    if ( $hold->is_suspended ) {
1554
        $hold->resume()
1611
        $hold->resume();
1555
    } else {
1612
    } else {
1556
        $hold->suspend_hold( $suspend_until );
1613
        $hold->suspend_hold($suspend_until);
1557
    }
1614
    }
1558
}
1615
}
1559
1616
Lines 1579-1604 sub SuspendAll { Link Here
1579
    my $borrowernumber = $params{'borrowernumber'} || undef;
1636
    my $borrowernumber = $params{'borrowernumber'} || undef;
1580
    my $biblionumber   = $params{'biblionumber'}   || undef;
1637
    my $biblionumber   = $params{'biblionumber'}   || undef;
1581
    my $suspend_until  = $params{'suspend_until'}  || undef;
1638
    my $suspend_until  = $params{'suspend_until'}  || undef;
1582
    my $suspend = defined( $params{'suspend'} ) ? $params{'suspend'} : 1;
1639
    my $suspend        = defined( $params{'suspend'} ) ? $params{'suspend'} : 1;
1583
1640
1584
    return unless ( $borrowernumber || $biblionumber );
1641
    return unless ( $borrowernumber || $biblionumber );
1585
1642
1586
    my $params;
1643
    my $params;
1587
    $params->{found}          = undef;
1644
    $params->{found}          = undef;
1588
    $params->{borrowernumber} = $borrowernumber if $borrowernumber;
1645
    $params->{borrowernumber} = $borrowernumber if $borrowernumber;
1589
    $params->{biblionumber}   = $biblionumber if $biblionumber;
1646
    $params->{biblionumber}   = $biblionumber   if $biblionumber;
1590
1647
1591
    my @holds = Koha::Holds->search($params)->as_list;
1648
    my @holds = Koha::Holds->search($params)->as_list;
1592
1649
1593
    if ($suspend) {
1650
    if ($suspend) {
1594
        map { $_->suspend_hold($suspend_until) } @holds;
1651
        map { $_->suspend_hold($suspend_until) } @holds;
1595
    }
1652
    } else {
1596
    else {
1597
        map { $_->resume() } @holds;
1653
        map { $_->resume() } @holds;
1598
    }
1654
    }
1599
}
1655
}
1600
1656
1601
1602
=head2 _FixPriority
1657
=head2 _FixPriority
1603
1658
1604
  _FixPriority({
1659
  _FixPriority({
Lines 1635-1666 when _FixPriority calls itself. Link Here
1635
=cut
1690
=cut
1636
1691
1637
sub _FixPriority {
1692
sub _FixPriority {
1638
    my ( $params ) = @_;
1693
    my ($params)            = @_;
1639
    my $reserve_id = $params->{reserve_id};
1694
    my $reserve_id          = $params->{reserve_id};
1640
    my $rank = $params->{rank} // '';
1695
    my $rank                = $params->{rank} // '';
1641
    my $ignoreSetLowestRank = $params->{ignoreSetLowestRank};
1696
    my $ignoreSetLowestRank = $params->{ignoreSetLowestRank};
1642
    my $biblionumber = $params->{biblionumber};
1697
    my $biblionumber        = $params->{biblionumber};
1643
1698
1644
    my $dbh = C4::Context->dbh;
1699
    my $dbh = C4::Context->dbh;
1645
1700
1646
    my $hold;
1701
    my $hold;
1647
    if ( $reserve_id ) {
1702
    if ($reserve_id) {
1648
        $hold = Koha::Holds->find( $reserve_id );
1703
        $hold = Koha::Holds->find($reserve_id);
1649
        if (!defined $hold){
1704
        if ( !defined $hold ) {
1705
1650
            # may have already been checked out and hold fulfilled
1706
            # may have already been checked out and hold fulfilled
1651
            $hold = Koha::Old::Holds->find( $reserve_id );
1707
            $hold = Koha::Old::Holds->find($reserve_id);
1652
        }
1708
        }
1653
        return unless $hold;
1709
        return unless $hold;
1654
    }
1710
    }
1655
1711
1656
    unless ( $biblionumber ) { # FIXME This is a very weird API
1712
    unless ($biblionumber) {    # FIXME This is a very weird API
1657
        $biblionumber = $hold->biblionumber;
1713
        $biblionumber = $hold->biblionumber;
1658
    }
1714
    }
1659
1715
1660
    if ( $rank eq "del" ) { # FIXME will crash if called without $hold
1716
    if ( $rank eq "del" ) {     # FIXME will crash if called without $hold
1661
        $hold->cancel;
1717
        $hold->cancel;
1662
    }
1718
    } elsif ( $reserve_id && ( $rank eq "W" || $rank eq "0" ) ) {
1663
    elsif ( $reserve_id && ( $rank eq "W" || $rank eq "0" ) ) {
1664
1719
1665
        # make sure priority for waiting or in-transit items is 0
1720
        # make sure priority for waiting or in-transit items is 0
1666
        my $query = "
1721
        my $query = "
Lines 1670-1676 sub _FixPriority { Link Here
1670
            AND found IN ('W', 'T', 'P')
1725
            AND found IN ('W', 'T', 'P')
1671
        ";
1726
        ";
1672
        my $sth = $dbh->prepare($query);
1727
        my $sth = $dbh->prepare($query);
1673
        $sth->execute( $reserve_id );
1728
        $sth->execute($reserve_id);
1674
    }
1729
    }
1675
    my @priority;
1730
    my @priority;
1676
1731
Lines 1683-1691 sub _FixPriority { Link Here
1683
        ORDER BY priority ASC
1738
        ORDER BY priority ASC
1684
    ";
1739
    ";
1685
    my $sth = $dbh->prepare($query);
1740
    my $sth = $dbh->prepare($query);
1686
    $sth->execute( $biblionumber );
1741
    $sth->execute($biblionumber);
1687
    while ( my $line = $sth->fetchrow_hashref ) {
1742
    while ( my $line = $sth->fetchrow_hashref ) {
1688
        push( @priority,     $line );
1743
        push( @priority, $line );
1689
    }
1744
    }
1690
1745
1691
    # FIXME This whole sub must be rewritten, especially to highlight what is done when reserve_id is not given
1746
    # FIXME This whole sub must be rewritten, especially to highlight what is done when reserve_id is not given
Lines 1701-1707 sub _FixPriority { Link Here
1701
1756
1702
    # if index exists in array then move it to new position
1757
    # if index exists in array then move it to new position
1703
    if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1758
    if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1704
        my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
1759
        my $new_rank    = $rank - 1;                      # $new_rank is what you want the new index to be in the array
1705
        my $moving_item = splice( @priority, $key, 1 );
1760
        my $moving_item = splice( @priority, $key, 1 );
1706
        $new_rank = scalar @priority if $new_rank > scalar @priority;
1761
        $new_rank = scalar @priority if $new_rank > scalar @priority;
1707
        splice( @priority, $new_rank, 0, $moving_item );
1762
        splice( @priority, $new_rank, 0, $moving_item );
Lines 1721-1736 sub _FixPriority { Link Here
1721
        );
1776
        );
1722
    }
1777
    }
1723
1778
1724
    unless ( $ignoreSetLowestRank ) {
1779
    unless ($ignoreSetLowestRank) {
1725
        $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 AND biblionumber = ? ORDER BY priority" );
1780
        $sth = $dbh->prepare(
1781
            "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 AND biblionumber = ? ORDER BY priority");
1726
        $sth->execute($biblionumber);
1782
        $sth->execute($biblionumber);
1727
      while ( my $res = $sth->fetchrow_hashref() ) {
1783
        while ( my $res = $sth->fetchrow_hashref() ) {
1728
        _FixPriority({
1784
            _FixPriority(
1729
            reserve_id => $res->{'reserve_id'},
1785
                {
1730
            rank => '999999',
1786
                    reserve_id          => $res->{'reserve_id'},
1731
            ignoreSetLowestRank => 1
1787
                    rank                => '999999',
1732
        });
1788
                    ignoreSetLowestRank => 1
1733
      }
1789
                }
1790
            );
1791
        }
1734
    }
1792
    }
1735
}
1793
}
1736
1794
Lines 1757-1764 All return values will respect any borrowernumbers passed as arrayref in $ignore Link Here
1757
=cut
1815
=cut
1758
1816
1759
sub _Findgroupreserve {
1817
sub _Findgroupreserve {
1760
    my ( $biblionumber, $itemnumber, $lookahead, $ignore_borrowers) = @_;
1818
    my ( $biblionumber, $itemnumber, $lookahead, $ignore_borrowers ) = @_;
1761
    my $dbh   = C4::Context->dbh;
1819
    my $dbh = C4::Context->dbh;
1762
1820
1763
    # check for targeted match form the holds queue
1821
    # check for targeted match form the holds queue
1764
    my $hold_target_query = qq{
1822
    my $hold_target_query = qq{
Lines 1788-1798 sub _Findgroupreserve { Link Here
1788
        ORDER BY priority
1846
        ORDER BY priority
1789
    };
1847
    };
1790
    my $sth = $dbh->prepare($hold_target_query);
1848
    my $sth = $dbh->prepare($hold_target_query);
1791
    $sth->execute($itemnumber, $lookahead||0);
1849
    $sth->execute( $itemnumber, $lookahead || 0 );
1792
    my @results;
1850
    my @results;
1793
    if ( my $data = $sth->fetchrow_hashref ) {
1851
    if ( my $data = $sth->fetchrow_hashref ) {
1794
        push( @results, $data )
1852
        push( @results, $data )
1795
          unless any{ $data->{borrowernumber} eq $_ } @$ignore_borrowers ;
1853
            unless any { $data->{borrowernumber} eq $_ } @$ignore_borrowers;
1796
    }
1854
    }
1797
    return @results if @results;
1855
    return @results if @results;
1798
1856
Lines 1820-1830 sub _Findgroupreserve { Link Here
1820
          ORDER BY priority
1878
          ORDER BY priority
1821
    };
1879
    };
1822
    $sth = $dbh->prepare($query);
1880
    $sth = $dbh->prepare($query);
1823
    $sth->execute( $biblionumber, $itemnumber, $lookahead||0);
1881
    $sth->execute( $biblionumber, $itemnumber, $lookahead || 0 );
1824
    @results = ();
1882
    @results = ();
1825
    while ( my $data = $sth->fetchrow_hashref ) {
1883
    while ( my $data = $sth->fetchrow_hashref ) {
1826
        push( @results, $data )
1884
        push( @results, $data )
1827
          unless any{ $data->{borrowernumber} eq $_ } @$ignore_borrowers ;
1885
            unless any { $data->{borrowernumber} eq $_ } @$ignore_borrowers;
1828
    }
1886
    }
1829
    return @results;
1887
    return @results;
1830
}
1888
}
Lines 1860-1897 The following tables are availalbe witin the notice: Link Here
1860
sub _koha_notify_reserve {
1918
sub _koha_notify_reserve {
1861
    my $reserve_id = shift;
1919
    my $reserve_id = shift;
1862
1920
1863
    my $hold = Koha::Holds->find($reserve_id);
1921
    my $hold           = Koha::Holds->find($reserve_id);
1864
    my $borrowernumber = $hold->borrowernumber;
1922
    my $borrowernumber = $hold->borrowernumber;
1865
1923
1866
    my $patron = Koha::Patrons->find( $borrowernumber );
1924
    my $patron = Koha::Patrons->find($borrowernumber);
1867
1925
1868
    # Try to get the borrower's email address
1926
    # Try to get the borrower's email address
1869
    my $to_address = $patron->notice_email_address;
1927
    my $to_address = $patron->notice_email_address;
1870
1928
1871
    my $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( {
1929
    my $messagingprefs = C4::Members::Messaging::GetMessagingPreferences(
1930
        {
1872
            borrowernumber => $borrowernumber,
1931
            borrowernumber => $borrowernumber,
1873
            message_name => 'Hold_Filled'
1932
            message_name   => 'Hold_Filled'
1874
    } );
1933
        }
1934
    );
1875
1935
1876
    my $library = Koha::Libraries->find( $hold->branchcode );
1936
    my $library            = Koha::Libraries->find( $hold->branchcode );
1877
    my $from_email_address = $library->from_email_address;
1937
    my $from_email_address = $library->from_email_address;
1878
1938
1879
    my %letter_params = (
1939
    my %letter_params = (
1880
        module => 'reserves',
1940
        module     => 'reserves',
1881
        branchcode => $hold->branchcode,
1941
        branchcode => $hold->branchcode,
1882
        lang => $patron->lang,
1942
        lang       => $patron->lang,
1883
        tables => {
1943
        tables     => {
1884
            'branches'       => $library->unblessed,
1944
            'branches'    => $library->unblessed,
1885
            'borrowers'      => $patron->unblessed,
1945
            'borrowers'   => $patron->unblessed,
1886
            'biblio'         => $hold->biblionumber,
1946
            'biblio'      => $hold->biblionumber,
1887
            'biblioitems'    => $hold->biblionumber,
1947
            'biblioitems' => $hold->biblionumber,
1888
            'reserves'       => $hold->unblessed,
1948
            'reserves'    => $hold->unblessed,
1889
            'items'          => $hold->itemnumber,
1949
            'items'       => $hold->itemnumber,
1890
        },
1950
        },
1891
    );
1951
    );
1892
1952
1893
    my $notification_sent = 0; #Keeping track if a Hold_filled message is sent. If no message can be sent, then default to a print message.
1953
    my $notification_sent =
1894
    my $do_not_lock = ( exists $ENV{_} && $ENV{_} =~ m|prove| ) || $ENV{KOHA_TESTING};
1954
        0;  #Keeping track if a Hold_filled message is sent. If no message can be sent, then default to a print message.
1955
    my $do_not_lock       = ( exists $ENV{_} && $ENV{_} =~ m|prove| ) || $ENV{KOHA_TESTING};
1895
    my $send_notification = sub {
1956
    my $send_notification = sub {
1896
        my ( $mtt, $letter_code, $wants_digest ) = (@_);
1957
        my ( $mtt, $letter_code, $wants_digest ) = (@_);
1897
        return unless defined $letter_code;
1958
        return unless defined $letter_code;
Lines 1928-1946 sub _koha_notify_reserve { Link Here
1928
    };
1989
    };
1929
1990
1930
    while ( my ( $mtt, $letter_code ) = each %{ $messagingprefs->{transports} } ) {
1991
    while ( my ( $mtt, $letter_code ) = each %{ $messagingprefs->{transports} } ) {
1931
        next if (
1992
        next
1932
               ( $mtt eq 'email' and not $to_address ) # No email address
1993
            if (
1933
            or ( $mtt eq 'sms'   and not $patron->smsalertnumber ) # No SMS number
1994
            ( $mtt eq 'email'  and not $to_address )                # No email address
1934
            or ( $mtt eq 'itiva' and C4::Context->preference('TalkingTechItivaPhoneNotification') ) # Notice is handled by TalkingTech_itiva_outbound.pl
1995
            or ( $mtt eq 'sms' and not $patron->smsalertnumber )    # No SMS number
1935
            or ( $mtt eq 'phone' and not $patron->phone ) # No phone number to call
1996
            or ( $mtt eq 'itiva'
1936
        );
1997
                and C4::Context->preference('TalkingTechItivaPhoneNotification')
1998
            )                                                       # Notice is handled by TalkingTech_itiva_outbound.pl
1999
            or ( $mtt eq 'phone' and not $patron->phone )           # No phone number to call
2000
            );
1937
2001
1938
        &$send_notification($mtt, $letter_code, $messagingprefs->{wants_digest});
2002
        &$send_notification( $mtt, $letter_code, $messagingprefs->{wants_digest} );
1939
        $notification_sent++;
2003
        $notification_sent++;
1940
    }
2004
    }
2005
1941
    #Making sure that a print notification is sent if no other transport types can be utilized.
2006
    #Making sure that a print notification is sent if no other transport types can be utilized.
1942
    if (! $notification_sent) {
2007
    if ( !$notification_sent ) {
1943
        &$send_notification('print', 'HOLD');
2008
        &$send_notification( 'print', 'HOLD' );
1944
    }
2009
    }
1945
2010
1946
}
2011
}
Lines 1954-1967 sub _koha_notify_reserve { Link Here
1954
sub _koha_notify_hold_changed {
2019
sub _koha_notify_hold_changed {
1955
    my $hold = shift;
2020
    my $hold = shift;
1956
2021
1957
    my $patron = $hold->patron;
2022
    my $patron  = $hold->patron;
1958
    my $library = $hold->branch;
2023
    my $library = $hold->branch;
1959
2024
1960
    my $letter = C4::Letters::GetPreparedLetter(
2025
    my $letter = C4::Letters::GetPreparedLetter(
1961
        module      => 'reserves',
2026
        module      => 'reserves',
1962
        letter_code => 'HOLD_CHANGED',
2027
        letter_code => 'HOLD_CHANGED',
1963
        branchcode  => $hold->branchcode,
2028
        branchcode  => $hold->branchcode,
1964
        substitute  => { today => output_pref( dt_from_string ) },
2029
        substitute  => { today => output_pref(dt_from_string) },
1965
        tables      => {
2030
        tables      => {
1966
            'branches'    => $library->unblessed,
2031
            'branches'    => $library->unblessed,
1967
            'borrowers'   => $patron->unblessed,
2032
            'borrowers'   => $patron->unblessed,
Lines 1974-1982 sub _koha_notify_hold_changed { Link Here
1974
2039
1975
    return unless $letter;
2040
    return unless $letter;
1976
2041
1977
    my $email =
2042
    my $email = C4::Context->preference('ExpireReservesAutoFillEmail')
1978
         C4::Context->preference('ExpireReservesAutoFillEmail')
2043
        || $library->inbound_email_address;
1979
      || $library->inbound_email_address;
1980
2044
1981
    C4::Letters::EnqueueLetter(
2045
    C4::Letters::EnqueueLetter(
1982
        {
2046
        {
Lines 2010-2022 the sub accounts for that too. Link Here
2010
sub _ShiftPriority {
2074
sub _ShiftPriority {
2011
    my ( $biblio, $new_priority ) = @_;
2075
    my ( $biblio, $new_priority ) = @_;
2012
2076
2013
    my $dbh = C4::Context->dbh;
2077
    my $dbh   = C4::Context->dbh;
2014
    my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND priority > ? ORDER BY priority ASC LIMIT 1";
2078
    my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND priority > ? ORDER BY priority ASC LIMIT 1";
2015
    my $sth = $dbh->prepare( $query );
2079
    my $sth   = $dbh->prepare($query);
2016
    $sth->execute( $biblio, $new_priority );
2080
    $sth->execute( $biblio, $new_priority );
2017
    my $min_priority = $sth->fetchrow;
2081
    my $min_priority = $sth->fetchrow;
2082
2018
    # if no such matches are found, $new_priority remains as original value
2083
    # if no such matches are found, $new_priority remains as original value
2019
    $new_priority = $min_priority if ( $min_priority );
2084
    $new_priority = $min_priority if ($min_priority);
2020
2085
2021
    # Shift the priority up by one; works in conjunction with the next SQL statement
2086
    # Shift the priority up by one; works in conjunction with the next SQL statement
2022
    $query = "UPDATE reserves
2087
    $query = "UPDATE reserves
Lines 2025-2041 sub _ShiftPriority { Link Here
2025
              AND borrowernumber = ?
2090
              AND borrowernumber = ?
2026
              AND reservedate = ?
2091
              AND reservedate = ?
2027
              AND found IS NULL";
2092
              AND found IS NULL";
2028
    my $sth_update = $dbh->prepare( $query );
2093
    my $sth_update = $dbh->prepare($query);
2029
2094
2030
    # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
2095
    # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
2031
    $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
2096
    $query =
2032
    $sth = $dbh->prepare( $query );
2097
        "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
2098
    $sth = $dbh->prepare($query);
2033
    $sth->execute( $new_priority, $biblio );
2099
    $sth->execute( $new_priority, $biblio );
2034
    while ( my $row = $sth->fetchrow_hashref ) {
2100
    while ( my $row = $sth->fetchrow_hashref ) {
2035
        $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
2101
        $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
2036
    }
2102
    }
2037
2103
2038
    return $new_priority;  # so the caller knows what priority they wind up receiving
2104
    return $new_priority;    # so the caller knows what priority they wind up receiving
2039
}
2105
}
2040
2106
2041
=head2 MoveReserve
2107
=head2 MoveReserve
Lines 2052-2089 sub MoveReserve { Link Here
2052
2118
2053
    $cancelreserve //= 0;
2119
    $cancelreserve //= 0;
2054
2120
2055
    my $lookahead = C4::Context->preference('ConfirmFutureHolds'); #number of days to look for future holds
2121
    my $lookahead = C4::Context->preference('ConfirmFutureHolds');    #number of days to look for future holds
2056
    my $item = Koha::Items->find($itemnumber);
2122
    my $item      = Koha::Items->find($itemnumber);
2057
    my ( $restype, $res, undef ) = CheckReserves( $item, $lookahead );
2123
    my ( $restype, $res, undef ) = CheckReserves( $item, $lookahead );
2058
    return unless $res;
2124
    return unless $res;
2059
2125
2060
    my $biblionumber = $res->{biblionumber};
2126
    my $biblionumber = $res->{biblionumber};
2061
2127
2062
    if ($res->{borrowernumber} == $borrowernumber) {
2128
    if ( $res->{borrowernumber} == $borrowernumber ) {
2063
        my $hold = Koha::Holds->find( $res->{reserve_id} );
2129
        my $hold = Koha::Holds->find( $res->{reserve_id} );
2064
        $hold->fill({ item_id => $itemnumber });
2130
        $hold->fill( { item_id => $itemnumber } );
2065
    }
2131
    } else {
2066
    else {
2132
2067
        # warn "Reserved";
2133
        # warn "Reserved";
2068
        # The item is reserved by someone else.
2134
        # The item is reserved by someone else.
2069
        # Find this item in the reserves
2135
        # Find this item in the reserves
2070
2136
2071
        my $borr_res  = Koha::Holds->search({
2137
        my $borr_res = Koha::Holds->search(
2072
            borrowernumber => $borrowernumber,
2138
            {
2073
            biblionumber   => $biblionumber,
2139
                borrowernumber => $borrowernumber,
2074
        },{
2140
                biblionumber   => $biblionumber,
2075
            order_by       => 'priority'
2141
            },
2076
        })->next();
2142
            { order_by => 'priority' }
2143
        )->next();
2144
2145
        if ($borr_res) {
2077
2146
2078
        if ( $borr_res ) {
2079
            # The item is reserved by the current patron
2147
            # The item is reserved by the current patron
2080
            $borr_res->fill({ item_id => $itemnumber });
2148
            $borr_res->fill( { item_id => $itemnumber } );
2081
        }
2149
        }
2082
2150
2083
        if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1
2151
        if ( $cancelreserve eq 'revert' ) {    ## Revert waiting reserve to priority 1
2084
            RevertWaitingStatus({ itemnumber => $itemnumber });
2152
            RevertWaitingStatus( { itemnumber => $itemnumber } );
2085
        }
2153
        } elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) {    # cancel reserves on this item
2086
        elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item
2087
            my $hold = Koha::Holds->find( $res->{reserve_id} );
2154
            my $hold = Koha::Holds->find( $res->{reserve_id} );
2088
            $hold->cancel;
2155
            $hold->cancel;
2089
        }
2156
        }
Lines 2100-2127 This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by Link Here
2100
2167
2101
sub MergeHolds {
2168
sub MergeHolds {
2102
    my ( $dbh, $to_biblio, $from_biblio ) = @_;
2169
    my ( $dbh, $to_biblio, $from_biblio ) = @_;
2103
    my $sth = $dbh->prepare(
2170
    my $sth = $dbh->prepare("SELECT count(*) as reserve_count FROM reserves WHERE biblionumber = ?");
2104
        "SELECT count(*) as reserve_count FROM reserves WHERE biblionumber = ?"
2105
    );
2106
    $sth->execute($from_biblio);
2171
    $sth->execute($from_biblio);
2107
    if ( my $data = $sth->fetchrow_hashref() ) {
2172
    if ( my $data = $sth->fetchrow_hashref() ) {
2108
2173
2109
        # holds exist on old record, if not we don't need to do anything
2174
        # holds exist on old record, if not we don't need to do anything
2110
        $sth = $dbh->prepare(
2175
        $sth = $dbh->prepare("UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
2111
            "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
2112
        $sth->execute( $to_biblio, $from_biblio );
2176
        $sth->execute( $to_biblio, $from_biblio );
2113
2177
2114
        # Reorder by date
2178
        # Reorder by date
2115
        # don't reorder those already waiting
2179
        # don't reorder those already waiting
2116
2180
2117
        $sth = $dbh->prepare(
2181
        $sth = $dbh->prepare(
2118
"SELECT * FROM reserves WHERE biblionumber = ? AND (found NOT IN ('W', 'T', 'P') OR found is NULL) ORDER BY reservedate ASC"
2182
            "SELECT * FROM reserves WHERE biblionumber = ? AND (found NOT IN ('W', 'T', 'P') OR found is NULL) ORDER BY reservedate ASC"
2119
        );
2183
        );
2120
        my $upd_sth = $dbh->prepare(
2184
        my $upd_sth = $dbh->prepare(
2121
"UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
2185
            "UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
2122
        AND reservedate = ? AND (itemnumber = ? or itemnumber is NULL) "
2186
        AND reservedate = ? AND (itemnumber = ? or itemnumber is NULL) "
2123
        );
2187
        );
2124
        $sth->execute( $to_biblio );
2188
        $sth->execute($to_biblio);
2125
        my $priority = 1;
2189
        my $priority = 1;
2126
        while ( my $reserve = $sth->fetchrow_hashref() ) {
2190
        while ( my $reserve = $sth->fetchrow_hashref() ) {
2127
            $upd_sth->execute(
2191
            $upd_sth->execute(
Lines 2149-2158 sub MergeHolds { Link Here
2149
=cut
2213
=cut
2150
2214
2151
sub RevertWaitingStatus {
2215
sub RevertWaitingStatus {
2152
    my ( $params ) = @_;
2216
    my ($params) = @_;
2153
    my $itemnumber = $params->{'itemnumber'};
2217
    my $itemnumber = $params->{'itemnumber'};
2154
2218
2155
    return unless ( $itemnumber );
2219
    return unless ($itemnumber);
2156
2220
2157
    my $dbh = C4::Context->dbh;
2221
    my $dbh = C4::Context->dbh;
2158
2222
Lines 2160-2182 sub RevertWaitingStatus { Link Here
2160
    my $hold = Koha::Holds->search(
2224
    my $hold = Koha::Holds->search(
2161
        {
2225
        {
2162
            itemnumber => $itemnumber,
2226
            itemnumber => $itemnumber,
2163
            found => { not => undef },
2227
            found      => { not => undef },
2164
        }
2228
        }
2165
    )->next;
2229
    )->next;
2166
2230
2167
    ## Increment the priority of all other non-waiting
2231
    ## Increment the priority of all other non-waiting
2168
    ## reserves for this bib record
2232
    ## reserves for this bib record
2169
    my $holds = Koha::Holds->search({ biblionumber => $hold->biblionumber, priority => { '>' => 0 } })
2233
    my $holds = Koha::Holds->search( { biblionumber => $hold->biblionumber, priority => { '>' => 0 } } )
2170
                           ->update({ priority => \'priority + 1' }, { no_triggers => 1 });
2234
        ->update( { priority => \'priority + 1' }, { no_triggers => 1 } );
2171
2235
2172
    ## Fix up the currently waiting reserve
2236
    ## Fix up the currently waiting reserve
2173
    $hold->set(
2237
    $hold->set(
2174
        {
2238
        {
2175
            priority    => 1,
2239
            priority       => 1,
2176
            found       => undef,
2240
            found          => undef,
2177
            waitingdate => undef,
2241
            waitingdate    => undef,
2178
            expirationdate => $hold->patron_expiration_date,
2242
            expirationdate => $hold->patron_expiration_date,
2179
            itemnumber  => $hold->item_level_hold ? $hold->itemnumber : undef,
2243
            itemnumber     => $hold->item_level_hold ? $hold->itemnumber : undef,
2180
        }
2244
        }
2181
    )->store();
2245
    )->store();
2182
2246
Lines 2185-2196 sub RevertWaitingStatus { Link Here
2185
2249
2186
    _FixPriority( { biblionumber => $hold->biblionumber } );
2250
    _FixPriority( { biblionumber => $hold->biblionumber } );
2187
2251
2188
    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
2252
    Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue( { biblio_ids => [ $hold->biblionumber ] } )
2189
        {
2253
        if C4::Context->preference('RealTimeHoldsQueue');
2190
            biblio_ids => [ $hold->biblionumber ]
2191
        }
2192
    ) if C4::Context->preference('RealTimeHoldsQueue');
2193
2194
2254
2195
    return $hold;
2255
    return $hold;
2196
}
2256
}
Lines 2222-2244 available within the slip: Link Here
2222
=cut
2282
=cut
2223
2283
2224
sub ReserveSlip {
2284
sub ReserveSlip {
2225
    my ($args) = @_;
2285
    my ($args)     = @_;
2226
    my $branchcode     = $args->{branchcode};
2286
    my $branchcode = $args->{branchcode};
2227
    my $reserve_id = $args->{reserve_id};
2287
    my $reserve_id = $args->{reserve_id};
2228
    my $itemnumber = $args->{itemnumber};
2288
    my $itemnumber = $args->{itemnumber};
2229
2289
2230
    my $hold = Koha::Holds->find($reserve_id);
2290
    my $hold = Koha::Holds->find($reserve_id);
2231
    return unless $hold;
2291
    return unless $hold;
2232
2292
2233
    my $patron = $hold->borrower;
2293
    my $patron  = $hold->borrower;
2234
    my $reserve = $hold->unblessed;
2294
    my $reserve = $hold->unblessed;
2235
2295
2236
    return  C4::Letters::GetPreparedLetter (
2296
    return C4::Letters::GetPreparedLetter(
2237
        module => 'circulation',
2297
        module      => 'circulation',
2238
        letter_code => 'HOLD_SLIP',
2298
        letter_code => 'HOLD_SLIP',
2239
        branchcode => $branchcode,
2299
        branchcode  => $branchcode,
2240
        lang => $patron->lang,
2300
        lang        => $patron->lang,
2241
        tables => {
2301
        tables      => {
2242
            'reserves'    => $reserve,
2302
            'reserves'    => $reserve,
2243
            'branches'    => $reserve->{branchcode},
2303
            'branches'    => $reserve->{branchcode},
2244
            'borrowers'   => $reserve->{borrowernumber},
2304
            'borrowers'   => $reserve->{borrowernumber},
Lines 2277-2297 sub CalculatePriority { Link Here
2277
        AND   priority > 0
2337
        AND   priority > 0
2278
        AND   (found IS NULL OR found = '')
2338
        AND   (found IS NULL OR found = '')
2279
    };
2339
    };
2340
2280
    #skip found==W or found==T or found==P (waiting, transit or processing holds)
2341
    #skip found==W or found==T or found==P (waiting, transit or processing holds)
2281
    if( $resdate ) {
2342
    if ($resdate) {
2282
        $sql.= ' AND ( reservedate <= ? )';
2343
        $sql .= ' AND ( reservedate <= ? )';
2283
    }
2344
    } else {
2284
    else {
2345
        $sql .= ' AND ( reservedate < NOW() )';
2285
        $sql.= ' AND ( reservedate < NOW() )';
2286
    }
2346
    }
2287
    my $dbh = C4::Context->dbh();
2347
    my $dbh = C4::Context->dbh();
2288
    my @row = $dbh->selectrow_array(
2348
    my @row = $dbh->selectrow_array(
2289
        $sql,
2349
        $sql,
2290
        undef,
2350
        undef,
2291
        $resdate ? ($biblionumber, $resdate) : ($biblionumber)
2351
        $resdate ? ( $biblionumber, $resdate ) : ($biblionumber)
2292
    );
2352
    );
2293
2353
2294
    return @row ? $row[0]+1 : 1;
2354
    return @row ? $row[0] + 1 : 1;
2295
}
2355
}
2296
2356
2297
=head2 IsItemOnHoldAndFound
2357
=head2 IsItemOnHoldAndFound
Lines 2334-2340 sub GetMaxPatronHoldsForRecord { Link Here
2334
    my ( $borrowernumber, $biblionumber ) = @_;
2394
    my ( $borrowernumber, $biblionumber ) = @_;
2335
2395
2336
    my $patron = Koha::Patrons->find($borrowernumber);
2396
    my $patron = Koha::Patrons->find($borrowernumber);
2337
    my @items = Koha::Items->search( { biblionumber => $biblionumber } )->as_list;
2397
    my @items  = Koha::Items->search( { biblionumber => $biblionumber } )->as_list;
2338
2398
2339
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
2399
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
2340
2400
Lines 2348-2359 sub GetMaxPatronHoldsForRecord { Link Here
2348
2408
2349
        $branchcode = $item->homebranch if ( $controlbranch eq "ItemHomeLibrary" );
2409
        $branchcode = $item->homebranch if ( $controlbranch eq "ItemHomeLibrary" );
2350
2410
2351
        my $rule = Koha::CirculationRules->get_effective_rule({
2411
        my $rule = Koha::CirculationRules->get_effective_rule(
2352
            categorycode => $categorycode,
2412
            {
2353
            itemtype     => $itemtype,
2413
                categorycode => $categorycode,
2354
            branchcode   => $branchcode,
2414
                itemtype     => $itemtype,
2355
            rule_name    => 'holds_per_record'
2415
                branchcode   => $branchcode,
2356
        });
2416
                rule_name    => 'holds_per_record'
2417
            }
2418
        );
2357
        my $holds_per_record = $rule ? $rule->rule_value : 0;
2419
        my $holds_per_record = $rule ? $rule->rule_value : 0;
2358
        $max = $holds_per_record if $holds_per_record > $max;
2420
        $max = $holds_per_record if $holds_per_record > $max;
2359
    }
2421
    }
(-)a/opac/opac-reserve.pl (-143 / +150 lines)
Lines 1-6 Link Here
1
#!/usr/bin/perl
1
#!/usr/bin/perl
2
2
3
4
# Copyright Katipo Communications 2002
3
# Copyright Katipo Communications 2002
5
# Copyright Koha Development team 2012
4
# Copyright Koha Development team 2012
6
#
5
#
Lines 21-33 Link Here
21
20
22
use Modern::Perl;
21
use Modern::Perl;
23
22
24
use CGI qw ( -utf8 );
23
use CGI             qw ( -utf8 );
25
use C4::Auth qw( get_template_and_user );
24
use C4::Auth        qw( get_template_and_user );
26
use C4::Koha qw( getitemtypeimagelocation getitemtypeimagesrc );
25
use C4::Koha        qw( getitemtypeimagelocation getitemtypeimagesrc );
27
use C4::Circulation qw( GetBranchItemRule );
26
use C4::Circulation qw( GetBranchItemRule );
28
use C4::Reserves qw( CanItemBeReserved CanBookBeReserved AddReserve IsAvailableForItemLevelRequest GetReserveFee );
27
use C4::Reserves    qw( CanItemBeReserved CanBookBeReserved AddReserve IsAvailableForItemLevelRequest GetReserveFee );
29
use C4::Biblio qw( GetBiblioData GetFrameworkCode );
28
use C4::Biblio      qw( GetBiblioData GetFrameworkCode );
30
use C4::Output qw( output_html_with_http_headers );
29
use C4::Output      qw( output_html_with_http_headers );
31
use C4::Context;
30
use C4::Context;
32
use C4::Members;
31
use C4::Members;
33
use C4::Overdues;
32
use C4::Overdues;
Lines 48-73 my $query = CGI->new; Link Here
48
my $op    = $query->param('op') // q{};
47
my $op    = $query->param('op') // q{};
49
48
50
# if OPACHoldRequests (for placing holds) is disabled, leave immediately
49
# if OPACHoldRequests (for placing holds) is disabled, leave immediately
51
if ( ! C4::Context->preference('OPACHoldRequests') ) {
50
if ( !C4::Context->preference('OPACHoldRequests') ) {
52
    print $query->redirect("/cgi-bin/koha/errors/404.pl");
51
    print $query->redirect("/cgi-bin/koha/errors/404.pl");
53
    exit;
52
    exit;
54
}
53
}
55
54
56
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
55
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
57
    {
56
    {
58
        template_name   => "opac-reserve.tt",
57
        template_name => "opac-reserve.tt",
59
        query           => $query,
58
        query         => $query,
60
        type            => "opac",
59
        type          => "opac",
61
    }
60
    }
62
);
61
);
63
62
64
my $patron = Koha::Patrons->find( $borrowernumber, { prefetch => ['categorycode'] } );
63
my $patron   = Koha::Patrons->find( $borrowernumber, { prefetch => ['categorycode'] } );
65
my $category = $patron->category;
64
my $category = $patron->category;
66
65
67
my $can_place_hold_if_available_at_pickup = C4::Context->preference('OPACHoldsIfAvailableAtPickup');
66
my $can_place_hold_if_available_at_pickup = C4::Context->preference('OPACHoldsIfAvailableAtPickup');
68
unless ( $can_place_hold_if_available_at_pickup ) {
67
unless ($can_place_hold_if_available_at_pickup) {
69
    my @patron_categories = split ',', C4::Context->preference('OPACHoldsIfAvailableAtPickupExceptions');
68
    my @patron_categories = split ',', C4::Context->preference('OPACHoldsIfAvailableAtPickupExceptions');
70
    if ( @patron_categories ) {
69
    if (@patron_categories) {
71
        my $categorycode = $patron->categorycode;
70
        my $categorycode = $patron->categorycode;
72
        $can_place_hold_if_available_at_pickup = grep { $_ eq $categorycode } @patron_categories;
71
        $can_place_hold_if_available_at_pickup = grep { $_ eq $categorycode } @patron_categories;
73
    }
72
    }
Lines 78-89 my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search_with_l Link Here
78
# There are two ways of calling this script, with a single biblio num
77
# There are two ways of calling this script, with a single biblio num
79
# or multiple biblio nums.
78
# or multiple biblio nums.
80
my $biblionumbers = $query->param('biblionumbers');
79
my $biblionumbers = $query->param('biblionumbers');
81
my $reserveMode = $query->param('reserve_mode');
80
my $reserveMode   = $query->param('reserve_mode');
82
if ($reserveMode && ($reserveMode eq 'single')) {
81
if ( $reserveMode && ( $reserveMode eq 'single' ) ) {
83
    my $bib = $query->param('single_bib');
82
    my $bib = $query->param('single_bib');
84
    $biblionumbers = "$bib/";
83
    $biblionumbers = "$bib/";
85
}
84
}
86
if (! $biblionumbers) {
85
if ( !$biblionumbers ) {
87
    $biblionumbers = $query->param('biblionumber');
86
    $biblionumbers = $query->param('biblionumber');
88
}
87
}
89
88
Lines 100-107 $template->param( biblionumbers => $biblionumbers ); Link Here
100
# Each biblio number is suffixed with '/', e.g. "1/2/3/"
99
# Each biblio number is suffixed with '/', e.g. "1/2/3/"
101
my @biblionumbers = split /\//, $biblionumbers;
100
my @biblionumbers = split /\//, $biblionumbers;
102
if ( $#biblionumbers < 0 && $op ne 'cud-place_reserve' ) {
101
if ( $#biblionumbers < 0 && $op ne 'cud-place_reserve' ) {
102
103
    # TODO: New message?
103
    # TODO: New message?
104
    $template->param(message=>1, no_biblionumber=>1);
104
    $template->param( message => 1, no_biblionumber => 1 );
105
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
105
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
106
    exit;
106
    exit;
107
}
107
}
Lines 111-135 if ( $#biblionumbers < 0 && $op ne 'cud-place_reserve' ) { Link Here
111
# Here we check that the borrower can actually make reserves Stage 1.
111
# Here we check that the borrower can actually make reserves Stage 1.
112
#
112
#
113
#
113
#
114
my $noreserves     = 0;
114
my $noreserves = 0;
115
if ( $category->effective_BlockExpiredPatronOpacActions ) {
115
if ( $category->effective_BlockExpiredPatronOpacActions ) {
116
    if ( $patron->is_expired ) {
116
    if ( $patron->is_expired ) {
117
117
        # cannot reserve, their card has expired and the rules set mean this is not allowed
118
        # cannot reserve, their card has expired and the rules set mean this is not allowed
118
        $noreserves = 1;
119
        $noreserves = 1;
119
        $template->param( message => 1, expired_patron => 1 );
120
        $template->param( message => 1, expired_patron => 1 );
120
    }
121
    }
121
}
122
}
122
123
123
my $maxoutstanding = C4::Context->preference("maxoutstanding");
124
my $maxoutstanding    = C4::Context->preference("maxoutstanding");
124
my $amountoutstanding = $patron->account->balance;
125
my $amountoutstanding = $patron->account->balance;
125
if ( $amountoutstanding && ($amountoutstanding > $maxoutstanding) ) {
126
if ( $amountoutstanding && ( $amountoutstanding > $maxoutstanding ) ) {
126
    my $amount = sprintf "%.02f", $amountoutstanding;
127
    my $amount = sprintf "%.02f", $amountoutstanding;
127
    $template->param( message => 1 );
128
    $template->param( message => 1 );
128
    $noreserves = 1;
129
    $noreserves = 1;
129
    $template->param( too_much_oweing => $amount );
130
    $template->param( too_much_oweing => $amount );
130
}
131
}
131
132
132
if ( $patron->gonenoaddress && ($patron->gonenoaddress == 1) ) {
133
if ( $patron->gonenoaddress && ( $patron->gonenoaddress == 1 ) ) {
133
    $noreserves = 1;
134
    $noreserves = 1;
134
    $template->param(
135
    $template->param(
135
        message => 1,
136
        message => 1,
Lines 137-143 if ( $patron->gonenoaddress && ($patron->gonenoaddress == 1) ) { Link Here
137
    );
138
    );
138
}
139
}
139
140
140
if ( $patron->lost && ($patron->lost == 1) ) {
141
if ( $patron->lost && ( $patron->lost == 1 ) ) {
141
    $noreserves = 1;
142
    $noreserves = 1;
142
    $template->param(
143
    $template->param(
143
        message => 1,
144
        message => 1,
Lines 155-161 if ( $patron->is_debarred ) { Link Here
155
    );
156
    );
156
}
157
}
157
158
158
my $holds = $patron->holds;
159
my $holds          = $patron->holds;
159
my $reserves_count = $holds->count;
160
my $reserves_count = $holds->count;
160
$template->param( RESERVES => $holds->unblessed );
161
$template->param( RESERVES => $holds->unblessed );
161
if ( $maxreserves && ( $reserves_count >= $maxreserves ) ) {
162
if ( $maxreserves && ( $reserves_count >= $maxreserves ) ) {
Lines 164-177 if ( $maxreserves && ( $reserves_count >= $maxreserves ) ) { Link Here
164
    $template->param( too_many_reserves => $holds->count );
165
    $template->param( too_many_reserves => $holds->count );
165
}
166
}
166
167
167
if( $noreserves ){
168
if ($noreserves) {
168
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
169
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
169
    exit;
170
    exit;
170
}
171
}
171
172
172
173
# pass the pickup branch along....
173
# pass the pickup branch along....
174
my $branch = $query->param('branch') || $patron->branchcode || C4::Context->userenv->{branch} || '' ;
174
my $branch = $query->param('branch') || $patron->branchcode || C4::Context->userenv->{branch} || '';
175
$template->param( branch => $branch );
175
$template->param( branch => $branch );
176
176
177
#
177
#
Lines 189-200 if ( $op eq 'cud-place_reserve' ) { Link Here
189
    # List is composed of alternating biblio/item/branch
189
    # List is composed of alternating biblio/item/branch
190
    my $selectedItems = $query->param('selecteditems');
190
    my $selectedItems = $query->param('selecteditems');
191
191
192
    if ($query->param('reserve_mode') eq 'single') {
192
    if ( $query->param('reserve_mode') eq 'single' ) {
193
193
        # This indicates non-JavaScript mode, so there was
194
        # This indicates non-JavaScript mode, so there was
194
        # only a single biblio number selected.
195
        # only a single biblio number selected.
195
        my $bib = $query->param('single_bib');
196
        my $bib  = $query->param('single_bib');
196
        my $item = $query->param("checkitem_$bib");
197
        my $item = $query->param("checkitem_$bib");
197
        if ($item eq 'any') {
198
        if ( $item eq 'any' ) {
198
            $item = '';
199
            $item = '';
199
        }
200
        }
200
        my $branch = $query->param('branch');
201
        my $branch = $query->param('branch');
Lines 207-214 if ( $op eq 'cud-place_reserve' ) { Link Here
207
    # Make sure there is a biblionum/itemnum/branch triplet for each item.
208
    # Make sure there is a biblionum/itemnum/branch triplet for each item.
208
    # The itemnum can be 'any', meaning next available.
209
    # The itemnum can be 'any', meaning next available.
209
    my $selectionCount = @selectedItems;
210
    my $selectionCount = @selectedItems;
210
    if (($selectionCount == 0) || (($selectionCount % 3) != 0)) {
211
    if ( ( $selectionCount == 0 ) || ( ( $selectionCount % 3 ) != 0 ) ) {
211
        $template->param(message=>1, bad_data=>1);
212
        $template->param( message => 1, bad_data => 1 );
212
        output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
213
        output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
213
        exit;
214
        exit;
214
    }
215
    }
Lines 222-243 if ( $op eq 'cud-place_reserve' ) { Link Here
222
        my $canreserve = 0;
223
        my $canreserve = 0;
223
224
224
        my $singleBranchMode = Koha::Libraries->search->count == 1;
225
        my $singleBranchMode = Koha::Libraries->search->count == 1;
225
        if ( $singleBranchMode || ! C4::Context->preference("OPACAllowUserToChooseBranch") )
226
        if ( $singleBranchMode || !C4::Context->preference("OPACAllowUserToChooseBranch") )
226
        {    # single branch mode or disabled user choosing
227
        {                                         # single branch mode or disabled user choosing
227
            $branch = $patron->branchcode;
228
            $branch = $patron->branchcode;
228
        }
229
        }
229
230
230
        # FIXME We shouldn't need to fetch the item here
231
        # FIXME We shouldn't need to fetch the item here
231
        my $item = $itemNum ? Koha::Items->find( $itemNum ) : undef;
232
        my $item = $itemNum ? Koha::Items->find($itemNum) : undef;
233
232
        # When choosing a specific item, the default pickup library should be dictated by the default hold policy
234
        # When choosing a specific item, the default pickup library should be dictated by the default hold policy
233
        if ( ! C4::Context->preference("OPACAllowUserToChooseBranch") && $item ) {
235
        if ( !C4::Context->preference("OPACAllowUserToChooseBranch") && $item ) {
234
            my $type                    = $item->effective_itemtype;
236
            my $type                    = $item->effective_itemtype;
235
            my $reserves_control_branch = Koha::Policy::Holds->holds_control_library( $item, $patron );
237
            my $reserves_control_branch = Koha::Policy::Holds->holds_control_library( $item, $patron );
236
            my $rule                    = GetBranchItemRule( $reserves_control_branch, $type );
238
            my $rule                    = GetBranchItemRule( $reserves_control_branch, $type );
237
239
238
            if ( $rule->{hold_fulfillment_policy} eq 'any' || $rule->{hold_fulfillment_policy} eq 'patrongroup' ) {
240
            if ( $rule->{hold_fulfillment_policy} eq 'any' || $rule->{hold_fulfillment_policy} eq 'patrongroup' ) {
239
                $branch = $patron->branchcode;
241
                $branch = $patron->branchcode;
240
            } elsif ( $rule->{hold_fulfillment_policy} eq 'holdgroup' ){
242
            } elsif ( $rule->{hold_fulfillment_policy} eq 'holdgroup' ) {
241
                $branch = $item->homebranch;
243
                $branch = $item->homebranch;
242
            } else {
244
            } else {
243
                my $policy = $rule->{hold_fulfillment_policy};
245
                my $policy = $rule->{hold_fulfillment_policy};
Lines 246-252 if ( $op eq 'cud-place_reserve' ) { Link Here
246
        }
248
        }
247
249
248
        # if we have an item, we are placing the hold on the item's bib, in case of analytics
250
        # if we have an item, we are placing the hold on the item's bib, in case of analytics
249
        if ( $item ) {
251
        if ($item) {
250
            $biblioNum = $item->biblionumber;
252
            $biblioNum = $item->biblionumber;
251
        }
253
        }
252
254
Lines 264-282 if ( $op eq 'cud-place_reserve' ) { Link Here
264
        $itemtype = undef if $itemNum;
266
        $itemtype = undef if $itemNum;
265
267
266
        my $biblio = Koha::Biblios->find($biblioNum);
268
        my $biblio = Koha::Biblios->find($biblioNum);
267
        my $rank = $biblio->holds->search( { found => [ { "!=" => "W" }, undef ] } )->count + 1;
269
        my $rank   = $biblio->holds->search( { found => [ { "!=" => "W" }, undef ] } )->count + 1;
268
        if ( $item ) {
270
        if ($item) {
269
            my $status = CanItemBeReserved( $patron, $item, $branch, { get_from_cache => 1 } )->{status};
271
            my $status = CanItemBeReserved( $patron, $item, $branch, { get_from_cache => 1 } )->{status};
270
            if( $status eq 'OK' ){
272
            if ( $status eq 'OK' ) {
271
                $canreserve = 1;
273
                $canreserve = 1;
272
            } else {
274
            } else {
273
                push @failed_holds, $status;
275
                push @failed_holds, $status;
274
            }
276
            }
275
277
276
        }
278
        } else {
277
        else {
278
            my $status = CanBookBeReserved( $borrowernumber, $biblioNum, $branch, { itemtype => $itemtype } )->{status};
279
            my $status = CanBookBeReserved( $borrowernumber, $biblioNum, $branch, { itemtype => $itemtype } )->{status};
279
            if( $status eq 'OK'){
280
            if ( $status eq 'OK' ) {
280
                $canreserve = 1;
281
                $canreserve = 1;
281
            } else {
282
            } else {
282
                push @failed_holds, $status;
283
                push @failed_holds, $status;
Lines 285-291 if ( $op eq 'cud-place_reserve' ) { Link Here
285
            # Inserts a null into the 'itemnumber' field of 'reserves' table.
286
            # Inserts a null into the 'itemnumber' field of 'reserves' table.
286
            $itemNum = undef;
287
            $itemNum = undef;
287
        }
288
        }
288
        my $notes = $query->param('notes_'.$biblioNum)||'';
289
        my $notes         = $query->param( 'notes_' . $biblioNum )    || '';
289
        my $item_group_id = $query->param("item_group_id_$biblioNum") || undef;
290
        my $item_group_id = $query->param("item_group_id_$biblioNum") || undef;
290
291
291
        if (   $maxreserves
292
        if (   $maxreserves
Lines 295-304 if ( $op eq 'cud-place_reserve' ) { Link Here
295
            $canreserve = 0;
296
            $canreserve = 0;
296
        }
297
        }
297
298
298
        unless ( $can_place_hold_if_available_at_pickup ) {
299
        unless ($can_place_hold_if_available_at_pickup) {
299
            my $items_in_this_library = Koha::Items->search({ biblionumber => $biblioNum, holdingbranch => $branch });
300
            my $items_in_this_library = Koha::Items->search( { biblionumber => $biblioNum, holdingbranch => $branch } );
300
            my $nb_of_items_issued = $items_in_this_library->search({ 'issue.itemnumber' => { not => undef }}, { join => 'issue' })->count;
301
            my $nb_of_items_issued =
301
            my $nb_of_items_unavailable = $items_in_this_library->search({ -or => { lost => { '!=' => 0 }, damaged => { '!=' => 0 }, } });
302
                $items_in_this_library->search( { 'issue.itemnumber' => { not => undef } }, { join => 'issue' } )
303
                ->count;
304
            my $nb_of_items_unavailable =
305
                $items_in_this_library->search( { -or => { lost => { '!=' => 0 }, damaged => { '!=' => 0 }, } } );
302
            if ( $items_in_this_library->count > $nb_of_items_issued + $nb_of_items_unavailable ) {
306
            if ( $items_in_this_library->count > $nb_of_items_issued + $nb_of_items_unavailable ) {
303
                $canreserve = 0;
307
                $canreserve = 0;
304
                push @failed_holds, 'items_available';
308
                push @failed_holds, 'items_available';
Lines 323-329 if ( $op eq 'cud-place_reserve' ) { Link Here
323
                    item_group_id    => $item_group_id,
327
                    item_group_id    => $item_group_id,
324
                }
328
                }
325
            );
329
            );
326
            if( $reserve_id ){
330
            if ($reserve_id) {
327
                ++$reserve_cnt;
331
                ++$reserve_cnt;
328
            } else {
332
            } else {
329
                push @failed_holds, 'not_placed';
333
                push @failed_holds, 'not_placed';
Lines 331-337 if ( $op eq 'cud-place_reserve' ) { Link Here
331
        }
335
        }
332
    }
336
    }
333
337
334
    print $query->redirect("/cgi-bin/koha/opac-user.pl?" . ( @failed_holds ? "failed_holds=" . join('|',@failed_holds) : q|| ) . "&opac-user-holds=1");
338
    print $query->redirect( "/cgi-bin/koha/opac-user.pl?"
339
            . ( @failed_holds ? "failed_holds=" . join( '|', @failed_holds ) : q|| )
340
            . "&opac-user-holds=1" );
335
    exit;
341
    exit;
336
}
342
}
337
343
Lines 341-385 if ( $op eq 'cud-place_reserve' ) { Link Here
341
#
347
#
342
#
348
#
343
349
344
my %biblioDataHash; # Hash of biblionumber to biblio/biblioitems record.
350
my %biblioDataHash;    # Hash of biblionumber to biblio/biblioitems record.
345
foreach my $biblioNumber (@biblionumbers) {
351
foreach my $biblioNumber (@biblionumbers) {
346
352
347
    my $biblioData = GetBiblioData($biblioNumber);
353
    my $biblioData = GetBiblioData($biblioNumber);
348
    $biblioDataHash{$biblioNumber} = $biblioData;
354
    $biblioDataHash{$biblioNumber} = $biblioData;
349
355
350
    my $biblio = Koha::Biblios->find( $biblioNumber );
356
    my $biblio = Koha::Biblios->find($biblioNumber);
351
    next unless $biblio;
357
    next unless $biblio;
352
358
353
    my $items = Koha::Items->search_ordered(
359
    my $items = Koha::Items->search_ordered(
354
        [
360
        [
355
            'me.biblionumber' => $biblioNumber,
361
            'me.biblionumber' => $biblioNumber,
356
            'me.itemnumber' => {
362
            'me.itemnumber'   => { -in => [ $biblio->host_items->get_column('itemnumber') ] }
357
                -in => [
358
                    $biblio->host_items->get_column('itemnumber')
359
                ]
360
            }
361
        ],
363
        ],
362
        { prefetch => [ 'issue', 'homebranch', 'holdingbranch' ] }
364
        { prefetch => [ 'issue', 'homebranch', 'holdingbranch' ] }
363
    )->filter_by_visible_in_opac({ patron => $patron });
365
    )->filter_by_visible_in_opac( { patron => $patron } );
364
366
365
    $biblioData->{items} = [$items->as_list]; # FIXME Potentially a lot in memory here!
367
    $biblioData->{items} = [ $items->as_list ];    # FIXME Potentially a lot in memory here!
366
368
367
    # Compute the priority rank.
369
    # Compute the priority rank.
368
    $biblioData->{object} = $biblio;
370
    $biblioData->{object} = $biblio;
369
    my $reservecount = $biblio->holds->search({ found => [ {"!=" => "W"},undef] })->count;
371
    my $reservecount = $biblio->holds->search( { found => [ { "!=" => "W" }, undef ] } )->count;
370
    $biblioData->{reservecount} = $reservecount;
372
    $biblioData->{reservecount} = $reservecount;
371
    $biblioData->{rank} = $reservecount + 1;
373
    $biblioData->{rank}         = $reservecount + 1;
372
}
374
}
373
375
374
376
my $requested_reserves_count = scalar(@biblionumbers);
375
my $requested_reserves_count = scalar( @biblionumbers );
376
if ( $maxreserves && ( $reserves_count + $requested_reserves_count > $maxreserves ) ) {
377
if ( $maxreserves && ( $reserves_count + $requested_reserves_count > $maxreserves ) ) {
377
    $template->param( new_reserves_allowed => $maxreserves - $reserves_count );
378
    $template->param( new_reserves_allowed => $maxreserves - $reserves_count );
378
}
379
}
379
380
380
$template->param( select_item_types => 1 );
381
$template->param( select_item_types => 1 );
381
382
382
383
#
383
#
384
#
384
#
385
# Build the template parameters that will show the info
385
# Build the template parameters that will show the info
Lines 387-400 $template->param( select_item_types => 1 ); Link Here
387
#
387
#
388
#
388
#
389
389
390
my $biblioLoop = [];
390
my $biblioLoop         = [];
391
my $numBibsAvailable = 0;
391
my $numBibsAvailable   = 0;
392
my $itemdata_enumchron = 0;
392
my $itemdata_enumchron = 0;
393
my $itemdata_ccode = 0;
393
my $itemdata_ccode     = 0;
394
my $anyholdable = 0;
394
my $anyholdable        = 0;
395
my $itemLevelTypes = C4::Context->preference('item-level_itypes');
395
my $itemLevelTypes     = C4::Context->preference('item-level_itypes');
396
my $pickup_locations = Koha::Libraries->search({ pickup_location => 1 });
396
my $pickup_locations   = Koha::Libraries->search( { pickup_location => 1 } );
397
$template->param('item_level_itypes' => $itemLevelTypes);
397
$template->param( 'item_level_itypes' => $itemLevelTypes );
398
398
399
my $patron_unblessed = $patron->unblessed;
399
my $patron_unblessed = $patron->unblessed;
400
foreach my $biblioNum (@biblionumbers) {
400
foreach my $biblioNum (@biblionumbers) {
Lines 404-459 foreach my $biblioNum (@biblionumbers) { Link Here
404
404
405
    # Get relevant biblio data.
405
    # Get relevant biblio data.
406
    my $biblioData = $biblioDataHash{$biblioNum};
406
    my $biblioData = $biblioDataHash{$biblioNum};
407
    if (! $biblioData) {
407
    if ( !$biblioData ) {
408
        $template->param(message=>1, bad_biblionumber=>$biblioNum);
408
        $template->param( message => 1, bad_biblionumber => $biblioNum );
409
        output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
409
        output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
410
        exit;
410
        exit;
411
    }
411
    }
412
412
413
    my @not_available_at = ();
413
    my @not_available_at = ();
414
    my $biblio = $biblioData->{object};
414
    my $biblio           = $biblioData->{object};
415
    foreach my $library ( $pickup_locations->as_list ) {
415
    foreach my $library ( $pickup_locations->as_list ) {
416
        push( @not_available_at, $library->branchcode ) unless $biblio->can_be_transferred({ to => $library });
416
        push( @not_available_at, $library->branchcode ) unless $biblio->can_be_transferred( { to => $library } );
417
    }
417
    }
418
418
419
    my $frameworkcode = GetFrameworkCode( $biblioData->{biblionumber} );
419
    my $frameworkcode = GetFrameworkCode( $biblioData->{biblionumber} );
420
    $biblioLoopIter{biblionumber} = $biblioData->{biblionumber};
420
    $biblioLoopIter{biblionumber}     = $biblioData->{biblionumber};
421
    $biblioLoopIter{title} = $biblioData->{title};
421
    $biblioLoopIter{title}            = $biblioData->{title};
422
    $biblioLoopIter{subtitle} = $biblioData->{'subtitle'};
422
    $biblioLoopIter{subtitle}         = $biblioData->{'subtitle'};
423
    $biblioLoopIter{medium} = $biblioData->{medium};
423
    $biblioLoopIter{medium}           = $biblioData->{medium};
424
    $biblioLoopIter{part_number} = $biblioData->{part_number};
424
    $biblioLoopIter{part_number}      = $biblioData->{part_number};
425
    $biblioLoopIter{part_name} = $biblioData->{part_name};
425
    $biblioLoopIter{part_name}        = $biblioData->{part_name};
426
    $biblioLoopIter{author} = $biblioData->{author};
426
    $biblioLoopIter{author}           = $biblioData->{author};
427
    $biblioLoopIter{rank} = $biblioData->{rank};
427
    $biblioLoopIter{rank}             = $biblioData->{rank};
428
    $biblioLoopIter{reservecount} = $biblioData->{reservecount};
428
    $biblioLoopIter{reservecount}     = $biblioData->{reservecount};
429
    $biblioLoopIter{already_reserved} = $biblioData->{already_reserved};
429
    $biblioLoopIter{already_reserved} = $biblioData->{already_reserved};
430
    $biblioLoopIter{object} = $biblio;
430
    $biblioLoopIter{object}           = $biblio;
431
431
432
    if (!$itemLevelTypes && $biblioData->{itemtype}) {
432
    if ( !$itemLevelTypes && $biblioData->{itemtype} ) {
433
        $biblioLoopIter{translated_description} = $itemtypes->{$biblioData->{itemtype}}{translated_description};
433
        $biblioLoopIter{translated_description} = $itemtypes->{ $biblioData->{itemtype} }{translated_description};
434
        $biblioLoopIter{imageurl} = getitemtypeimagesrc() . "/". $itemtypes->{$biblioData->{itemtype}}{imageurl};
434
        $biblioLoopIter{imageurl} = getitemtypeimagesrc() . "/" . $itemtypes->{ $biblioData->{itemtype} }{imageurl};
435
    }
435
    }
436
436
437
438
439
    $biblioLoopIter{itemLoop} = [];
437
    $biblioLoopIter{itemLoop} = [];
440
    my $numCopiesAvailable = 0;
438
    my $numCopiesAvailable     = 0;
441
    my $numCopiesOPACAvailable = 0;
439
    my $numCopiesOPACAvailable = 0;
440
442
    # iterating through all items first to check if any of them available
441
    # iterating through all items first to check if any of them available
443
    # to pass this value further inside down to IsAvailableForItemLevelRequest to
442
    # to pass this value further inside down to IsAvailableForItemLevelRequest to
444
    # it's complicated logic to analyse.
443
    # it's complicated logic to analyse.
445
    # (before this loop was inside that sub loop so it was O(n^2) )
444
    # (before this loop was inside that sub loop so it was O(n^2) )
446
    foreach my $item (@{$biblioData->{items}}) {
445
    foreach my $item ( @{ $biblioData->{items} } ) {
447
446
448
        my $item_info = $item->unblessed;
447
        my $item_info = $item->unblessed;
449
        $item_info->{holding_branch} = $item->holding_branch;
448
        $item_info->{holding_branch} = $item->holding_branch;
450
        $item_info->{home_branch}    = $item->home_branch;
449
        $item_info->{home_branch}    = $item->home_branch;
451
        if ($itemLevelTypes) {
450
        if ($itemLevelTypes) {
452
            my $itemtype = $item->itemtype;
451
            my $itemtype = $item->itemtype;
453
            $item_info->{'imageurl'} = getitemtypeimagelocation( 'opac',
452
            $item_info->{'imageurl'} = getitemtypeimagelocation(
454
                $itemtypes->{ $itemtype->itemtype }->{'imageurl'} );
453
                'opac',
454
                $itemtypes->{ $itemtype->itemtype }->{'imageurl'}
455
            );
455
            $item_info->{'translated_description'} =
456
            $item_info->{'translated_description'} =
456
              $itemtypes->{ $itemtype->itemtype }->{translated_description};
457
                $itemtypes->{ $itemtype->itemtype }->{translated_description};
457
        }
458
        }
458
459
459
        # checking for holds
460
        # checking for holds
Lines 469-476 foreach my $biblioNum (@biblionumbers) { Link Here
469
        if ( $transfer && $transfer->in_transit ) {
470
        if ( $transfer && $transfer->in_transit ) {
470
            $item_info->{transfertwhen} = $transfer->datesent;
471
            $item_info->{transfertwhen} = $transfer->datesent;
471
            $item_info->{transfertfrom} = $transfer->frombranch;
472
            $item_info->{transfertfrom} = $transfer->frombranch;
472
            $item_info->{transfertto} = $transfer->tobranch;
473
            $item_info->{transfertto}   = $transfer->tobranch;
473
            $item_info->{nocancel} = 1;
474
            $item_info->{nocancel}      = 1;
474
        }
475
        }
475
476
476
        # if the items belongs to a host record, show link to host record
477
        # if the items belongs to a host record, show link to host record
Lines 483-496 foreach my $biblioNum (@biblionumbers) { Link Here
483
484
484
        my $item_status = CanItemBeReserved( $patron, $item, undef, { get_from_cache => 1 } )->{status};
485
        my $item_status = CanItemBeReserved( $patron, $item, undef, { get_from_cache => 1 } )->{status};
485
486
486
        if ($item_status eq 'recordHoldsOnly') {
487
        if ( $item_status eq 'recordHoldsOnly' ) {
487
            $biblioLoopIter{force_hold} = 1;
488
            $biblioLoopIter{force_hold}        = 1;
488
            $biblioLoopIter{itemholdable} = 0;
489
            $biblioLoopIter{itemholdable}      = 0;
489
            $biblioLoopIter{forced_hold_level} = 'record';
490
            $biblioLoopIter{forced_hold_level} = 'record';
490
        }
491
        }
491
492
492
        my $policy_holdallowed = IsAvailableForItemLevelRequest($item, $patron, undef) &&
493
        my $policy_holdallowed = IsAvailableForItemLevelRequest( $item, $patron, undef )
493
            ($item_status eq 'OK' or $item_status eq 'recordHoldsOnly');
494
            && ( $item_status eq 'OK' or $item_status eq 'recordHoldsOnly' );
494
495
495
        if ($policy_holdallowed) {
496
        if ($policy_holdallowed) {
496
            $numCopiesAvailable++;
497
            $numCopiesAvailable++;
Lines 498-506 foreach my $biblioNum (@biblionumbers) { Link Here
498
                $numCopiesOPACAvailable++;
499
                $numCopiesOPACAvailable++;
499
                $item_info->{available} = 1;
500
                $item_info->{available} = 1;
500
            }
501
            }
501
            unless ( $can_place_hold_if_available_at_pickup ) {
502
            unless ($can_place_hold_if_available_at_pickup) {
502
                my $items_in_this_library = Koha::Items->search({ biblionumber => $item->biblionumber, holdingbranch => $item->holdingbranch, notforloan => 0 });
503
                my $items_in_this_library = Koha::Items->search(
503
                my $nb_of_items_issued = $items_in_this_library->search({ 'issue.itemnumber' => { not => undef }}, { join => 'issue' })->count;
504
                    { biblionumber => $item->biblionumber, holdingbranch => $item->holdingbranch, notforloan => 0 } );
505
                my $nb_of_items_issued =
506
                    $items_in_this_library->search( { 'issue.itemnumber' => { not => undef } }, { join => 'issue' } )
507
                    ->count;
504
                if ( $items_in_this_library->count > $nb_of_items_issued ) {
508
                if ( $items_in_this_library->count > $nb_of_items_issued ) {
505
                    push @not_available_at, $item->holdingbranch;
509
                    push @not_available_at, $item->holdingbranch;
506
                }
510
                }
Lines 508-546 foreach my $biblioNum (@biblionumbers) { Link Here
508
        }
512
        }
509
513
510
        # Show serial enumeration when needed
514
        # Show serial enumeration when needed
511
        if ($item_info->{enumchron}) {
515
        if ( $item_info->{enumchron} ) {
512
            $itemdata_enumchron = 1;
516
            $itemdata_enumchron = 1;
513
        }
517
        }
518
514
        # Show collection when needed
519
        # Show collection when needed
515
        if ($item_info->{ccode}) {
520
        if ( $item_info->{ccode} ) {
516
            $itemdata_ccode = 1;
521
            $itemdata_ccode = 1;
517
        }
522
        }
518
523
519
        push @{$biblioLoopIter{itemLoop}}, $item_info;
524
        push @{ $biblioLoopIter{itemLoop} }, $item_info;
520
    }
525
    }
521
    $template->param(
526
    $template->param(
522
        itemdata_enumchron => $itemdata_enumchron,
527
        itemdata_enumchron => $itemdata_enumchron,
523
        itemdata_ccode     => $itemdata_ccode,
528
        itemdata_ccode     => $itemdata_ccode,
524
    );
529
    );
525
530
526
    if ($numCopiesAvailable > 0) {
531
    if ( $numCopiesAvailable > 0 ) {
527
        $numBibsAvailable++;
532
        $numBibsAvailable++;
528
        $biblioLoopIter{bib_available} = 1;
533
        $biblioLoopIter{bib_available} = 1;
529
        $biblioLoopIter{holdable} = 1;
534
        $biblioLoopIter{holdable}      = 1;
530
        $biblioLoopIter{itemholdable} = 1 if $numCopiesOPACAvailable;
535
        $biblioLoopIter{itemholdable}  = 1 if $numCopiesOPACAvailable;
531
    }
536
    }
532
    if ($biblioLoopIter{already_reserved}) {
537
    if ( $biblioLoopIter{already_reserved} ) {
533
        $biblioLoopIter{holdable} = undef;
538
        $biblioLoopIter{holdable}     = undef;
534
        $biblioLoopIter{itemholdable} = undef;
539
        $biblioLoopIter{itemholdable} = undef;
535
    }
540
    }
536
    if ( $biblioLoopIter{holdable} ) {
541
    if ( $biblioLoopIter{holdable} ) {
537
        @not_available_at = uniq @not_available_at;
542
        @not_available_at = uniq @not_available_at;
538
        $biblioLoopIter{not_available_at} = \@not_available_at ;
543
        $biblioLoopIter{not_available_at} = \@not_available_at;
539
    }
544
    }
540
545
541
    unless ( $can_place_hold_if_available_at_pickup ) {
546
    unless ($can_place_hold_if_available_at_pickup) {
542
        @not_available_at = uniq @not_available_at;
547
        @not_available_at = uniq @not_available_at;
543
        $biblioLoopIter{not_available_at} = \@not_available_at ;
548
        $biblioLoopIter{not_available_at} = \@not_available_at;
549
544
        # The record is not holdable is not available at any of the libraries
550
        # The record is not holdable is not available at any of the libraries
545
        if ( Koha::Libraries->search->count == @not_available_at ) {
551
        if ( Koha::Libraries->search->count == @not_available_at ) {
546
            $biblioLoopIter{holdable} = 0;
552
            $biblioLoopIter{holdable} = 0;
Lines 548-575 foreach my $biblioNum (@biblionumbers) { Link Here
548
    }
554
    }
549
555
550
    my $record_status = CanBookBeReserved( $borrowernumber, $biblioNum )->{status};
556
    my $record_status = CanBookBeReserved( $borrowernumber, $biblioNum )->{status};
551
    if ($record_status eq 'recordHoldNotAllowed') {
557
    if ( $record_status eq 'recordHoldNotAllowed' ) {
552
        $biblioLoopIter{force_hold} = 1;
558
        $biblioLoopIter{force_hold}        = 1;
553
        $biblioLoopIter{itemholdable} = 1;
559
        $biblioLoopIter{itemholdable}      = 1;
554
        $biblioLoopIter{forced_hold_level} = 'item';
560
        $biblioLoopIter{forced_hold_level} = 'item';
555
        $biblioLoopIter{holdable} &&= 1;
561
        $biblioLoopIter{holdable} &&= 1;
556
    }
562
    } else {
557
    else {
558
        $biblioLoopIter{holdable} &&= $record_status eq 'OK';
563
        $biblioLoopIter{holdable} &&= $record_status eq 'OK';
559
    }
564
    }
560
565
561
    if ( $biblioLoopIter{holdable} and C4::Context->preference('AllowHoldItemTypeSelection') ) {
566
    if ( $biblioLoopIter{holdable} and C4::Context->preference('AllowHoldItemTypeSelection') ) {
567
562
        # build the allowed item types loop
568
        # build the allowed item types loop
563
        my $rs = $biblio->items->search_ordered(
569
        my $rs = $biblio->items->search_ordered(
564
            undef,
570
            undef,
565
            {   select => [ { distinct => 'itype' } ],
571
            {
572
                select => [ { distinct => 'itype' } ],
566
                as     => 'item_type'
573
                as     => 'item_type'
567
            }
574
            }
568
        );
575
        );
569
576
570
        my @item_types =
577
        my @item_types =
571
          grep { CanBookBeReserved( $borrowernumber, $biblioNum, $branch, { itemtype => $_ } )->{status} eq 'OK' }
578
            grep { CanBookBeReserved( $borrowernumber, $biblioNum, $branch, { itemtype => $_ } )->{status} eq 'OK' }
572
          $rs->get_column('item_type');
579
            $rs->get_column('item_type');
573
580
574
        $biblioLoopIter{allowed_item_types} = \@item_types;
581
        $biblioLoopIter{allowed_item_types} = \@item_types;
575
    }
582
    }
Lines 591-600 foreach my $biblioNum (@biblionumbers) { Link Here
591
        }
598
        }
592
    )->forced_hold_level();
599
    )->forced_hold_level();
593
    if ($forced_hold_level) {
600
    if ($forced_hold_level) {
594
        $biblioLoopIter{force_hold}   = 1 if $forced_hold_level eq 'item';
601
        $biblioLoopIter{force_hold}        = 1 if $forced_hold_level eq 'item';
595
        $biblioLoopIter{force_hold}   = 0 if $forced_hold_level eq 'item_group';
602
        $biblioLoopIter{force_hold}        = 0 if $forced_hold_level eq 'item_group';
596
        $biblioLoopIter{itemholdable} = 0 if $forced_hold_level eq 'record';
603
        $biblioLoopIter{itemholdable}      = 0 if $forced_hold_level eq 'record';
597
        $biblioLoopIter{itemholdable} = 0 if $forced_hold_level eq 'item_group';
604
        $biblioLoopIter{itemholdable}      = 0 if $forced_hold_level eq 'item_group';
598
        $biblioLoopIter{forced_hold_level} = $forced_hold_level;
605
        $biblioLoopIter{forced_hold_level} = $forced_hold_level;
599
    }
606
    }
600
607
Lines 606-640 foreach my $biblioNum (@biblionumbers) { Link Here
606
    $anyholdable = 1 if $biblioLoopIter{holdable};
613
    $anyholdable = 1 if $biblioLoopIter{holdable};
607
}
614
}
608
615
609
unless ($pickup_locations->count) {
616
unless ( $pickup_locations->count ) {
610
    $numBibsAvailable = 0;
617
    $numBibsAvailable = 0;
611
    $anyholdable = 0;
618
    $anyholdable      = 0;
612
    $template->param(
619
    $template->param(
613
        message => 1,
620
        message             => 1,
614
        no_pickup_locations => 1
621
        no_pickup_locations => 1
615
    );
622
    );
616
}
623
}
617
624
618
if ( $numBibsAvailable == 0 || $anyholdable == 0) {
625
if ( $numBibsAvailable == 0 || $anyholdable == 0 ) {
619
    $template->param( none_available => 1 );
626
    $template->param( none_available => 1 );
620
}
627
}
621
628
622
if (scalar @biblionumbers > 1) {
629
if ( scalar @biblionumbers > 1 ) {
623
    $template->param( multi_hold => 1);
630
    $template->param( multi_hold => 1 );
624
}
631
}
625
632
626
my $show_notes=C4::Context->preference('OpacHoldNotes');
633
my $show_notes = C4::Context->preference('OpacHoldNotes');
627
$template->param(OpacHoldNotes=>$show_notes);
634
$template->param( OpacHoldNotes => $show_notes );
628
635
629
# display infos
636
# display infos
630
$template->param(bibitemloop => $biblioLoop);
637
$template->param( bibitemloop => $biblioLoop );
638
631
# can set reserve date in future
639
# can set reserve date in future
632
if (
640
if (   C4::Context->preference('AllowHoldDateInFuture')
633
    C4::Context->preference( 'AllowHoldDateInFuture' ) &&
641
    && C4::Context->preference('OPACAllowHoldDateInFuture') )
634
    C4::Context->preference( 'OPACAllowHoldDateInFuture' )
642
{
635
    ) {
636
    $template->param(
643
    $template->param(
637
            reserve_in_future         => 1,
644
        reserve_in_future => 1,
638
    );
645
    );
639
}
646
}
640
647
(-)a/t/db_dependent/Reserves.t (-553 / +720 lines)
Lines 32-38 use C4::Items; Link Here
32
use C4::Biblio qw( GetMarcFromKohaField ModBiblio );
32
use C4::Biblio qw( GetMarcFromKohaField ModBiblio );
33
use C4::HoldsQueue;
33
use C4::HoldsQueue;
34
use C4::Members;
34
use C4::Members;
35
use C4::Reserves qw( AddReserve AlterPriority CheckReserves ModReserve ModReserveAffect ReserveSlip CalculatePriority CanReserveBeCanceledFromOpac CanBookBeReserved IsAvailableForItemLevelRequest MoveReserve ChargeReserveFee RevertWaitingStatus CanItemBeReserved MergeHolds );
35
use C4::Reserves
36
    qw( AddReserve AlterPriority CheckReserves ModReserve ModReserveAffect ReserveSlip CalculatePriority CanReserveBeCanceledFromOpac CanBookBeReserved IsAvailableForItemLevelRequest MoveReserve ChargeReserveFee RevertWaitingStatus CanItemBeReserved MergeHolds );
36
use Koha::ActionLogs;
37
use Koha::ActionLogs;
37
use Koha::Biblios;
38
use Koha::Biblios;
38
use Koha::Caches;
39
use Koha::Caches;
Lines 51-57 BEGIN { Link Here
51
52
52
# Start transaction
53
# Start transaction
53
my $database = Koha::Database->new();
54
my $database = Koha::Database->new();
54
my $schema = $database->schema();
55
my $schema   = $database->schema();
55
$schema->storage->txn_begin();
56
$schema->storage->txn_begin();
56
my $dbh = C4::Context->dbh;
57
my $dbh = C4::Context->dbh;
57
$dbh->do('DELETE FROM circulation_rules');
58
$dbh->do('DELETE FROM circulation_rules');
Lines 60-70 my $builder = t::lib::TestBuilder->new; Link Here
60
61
61
my $frameworkcode = q//;
62
my $frameworkcode = q//;
62
63
63
64
t::lib::Mocks::mock_preference( 'ReservesNeedReturns', 1 );
64
t::lib::Mocks::mock_preference('ReservesNeedReturns', 1);
65
65
66
# Somewhat arbitrary field chosen for age restriction unit tests. Must be added to db before the framework is cached
66
# Somewhat arbitrary field chosen for age restriction unit tests. Must be added to db before the framework is cached
67
$dbh->do("update marc_subfield_structure set kohafield='biblioitems.agerestriction' where tagfield='521' and tagsubfield='a' and frameworkcode=?", undef, $frameworkcode);
67
$dbh->do(
68
    "update marc_subfield_structure set kohafield='biblioitems.agerestriction' where tagfield='521' and tagsubfield='a' and frameworkcode=?",
69
    undef, $frameworkcode
70
);
68
my $cache = Koha::Caches->get_instance;
71
my $cache = Koha::Caches->get_instance;
69
$cache->clear_from_cache("MarcStructure-0-$frameworkcode");
72
$cache->clear_from_cache("MarcStructure-0-$frameworkcode");
70
$cache->clear_from_cache("MarcStructure-1-$frameworkcode");
73
$cache->clear_from_cache("MarcStructure-1-$frameworkcode");
Lines 72-112 $cache->clear_from_cache("MarcSubfieldStructure-$frameworkcode"); Link Here
72
75
73
## Setup Test
76
## Setup Test
74
# Add branches
77
# Add branches
75
my $branch_1 = $builder->build({ source => 'Branch' })->{ branchcode };
78
my $branch_1 = $builder->build( { source => 'Branch' } )->{branchcode};
76
my $branch_2 = $builder->build({ source => 'Branch' })->{ branchcode };
79
my $branch_2 = $builder->build( { source => 'Branch' } )->{branchcode};
77
my $branch_3 = $builder->build({ source => 'Branch' })->{ branchcode };
80
my $branch_3 = $builder->build( { source => 'Branch' } )->{branchcode};
81
78
# Add categories
82
# Add categories
79
my $category_1 = $builder->build({ source => 'Category' })->{ categorycode };
83
my $category_1 = $builder->build( { source => 'Category' } )->{categorycode};
80
my $category_2 = $builder->build({ source => 'Category' })->{ categorycode };
84
my $category_2 = $builder->build( { source => 'Category' } )->{categorycode};
85
81
# Add an item type
86
# Add an item type
82
my $itemtype = $builder->build(
87
my $itemtype = $builder->build( { source => 'Itemtype', value => { notforloan => undef } } )->{itemtype};
83
    { source => 'Itemtype', value => { notforloan => undef } } )->{itemtype};
84
88
85
t::lib::Mocks::mock_userenv({ branchcode => $branch_1 });
89
t::lib::Mocks::mock_userenv( { branchcode => $branch_1 } );
86
90
87
my $bibnum = $builder->build_sample_biblio({frameworkcode => $frameworkcode})->biblionumber;
91
my $bibnum = $builder->build_sample_biblio( { frameworkcode => $frameworkcode } )->biblionumber;
88
92
89
# Create a helper item instance for testing
93
# Create a helper item instance for testing
90
my $item = $builder->build_sample_item({ biblionumber => $bibnum, library => $branch_1, itype => $itemtype });
94
my $item = $builder->build_sample_item( { biblionumber => $bibnum, library => $branch_1, itype => $itemtype } );
91
95
92
my $biblio_with_no_item = $builder->build_sample_biblio;
96
my $biblio_with_no_item = $builder->build_sample_biblio;
93
97
94
# Modify item; setting barcode.
98
# Modify item; setting barcode.
95
my $testbarcode = '97531';
99
my $testbarcode = '97531';
96
$item->barcode($testbarcode)->store; # FIXME We should not hardcode a barcode! Also, what's the purpose of this?
100
$item->barcode($testbarcode)->store;    # FIXME We should not hardcode a barcode! Also, what's the purpose of this?
97
98
101
99
# Create a borrower
102
# Create a borrower
100
my %data = (
103
my %data = (
101
    firstname =>  'my firstname',
104
    firstname    => 'my firstname',
102
    surname => 'my surname',
105
    surname      => 'my surname',
103
    categorycode => $category_1,
106
    categorycode => $category_1,
104
    branchcode => $branch_1,
107
    branchcode   => $branch_1,
105
);
108
);
106
Koha::Patron::Categories->find($category_1)->set({ enrolmentfee => 0})->store;
109
Koha::Patron::Categories->find($category_1)->set( { enrolmentfee => 0 } )->store;
107
my $borrowernumber = Koha::Patron->new(\%data)->store->borrowernumber;
110
my $borrowernumber = Koha::Patron->new( \%data )->store->borrowernumber;
108
my $patron = Koha::Patrons->find( $borrowernumber );
111
my $patron         = Koha::Patrons->find($borrowernumber);
109
my $borrower = $patron->unblessed;
112
my $borrower       = $patron->unblessed;
110
my $biblionumber   = $bibnum;
113
my $biblionumber   = $bibnum;
111
114
112
my $branchcode = Koha::Libraries->search->next->branchcode;
115
my $branchcode = Koha::Libraries->search->next->branchcode;
Lines 120-160 AddReserve( Link Here
120
    }
123
    }
121
);
124
);
122
125
123
my ($status, $reserve, $all_reserves) = CheckReserves( $item );
126
my ( $status, $reserve, $all_reserves ) = CheckReserves($item);
124
127
125
is($status, "Reserved", "CheckReserves Test 1");
128
is( $status, "Reserved", "CheckReserves Test 1" );
126
129
127
ok(exists($reserve->{reserve_id}), 'CheckReserves() include reserve_id in its response');
130
ok( exists( $reserve->{reserve_id} ), 'CheckReserves() include reserve_id in its response' );
128
131
129
($status, $reserve, $all_reserves) = CheckReserves( $item );
132
( $status, $reserve, $all_reserves ) = CheckReserves($item);
130
is($status, "Reserved", "CheckReserves Test 2");
133
is( $status, "Reserved", "CheckReserves Test 2" );
131
134
132
###
135
###
133
### Regression test for bug 10272
136
### Regression test for bug 10272
134
###
137
###
135
my %requesters = ();
138
my %requesters = ();
136
$requesters{$branch_1} = Koha::Patron->new({
139
$requesters{$branch_1} = Koha::Patron->new(
137
    branchcode   => $branch_1,
140
    {
138
    categorycode => $category_2,
139
    surname      => "borrower from $branch_1",
140
})->store->borrowernumber;
141
for my $i ( 2 .. 5 ) {
142
    $requesters{"CPL$i"} = Koha::Patron->new({
143
        branchcode   => $branch_1,
141
        branchcode   => $branch_1,
144
        categorycode => $category_2,
142
        categorycode => $category_2,
145
        surname      => "borrower $i from $branch_1",
143
        surname      => "borrower from $branch_1",
146
    })->store->borrowernumber;
144
    }
145
)->store->borrowernumber;
146
for my $i ( 2 .. 5 ) {
147
    $requesters{"CPL$i"} = Koha::Patron->new(
148
        {
149
            branchcode   => $branch_1,
150
            categorycode => $category_2,
151
            surname      => "borrower $i from $branch_1",
152
        }
153
    )->store->borrowernumber;
147
}
154
}
148
$requesters{$branch_2} = Koha::Patron->new({
155
$requesters{$branch_2} = Koha::Patron->new(
149
    branchcode   => $branch_2,
156
    {
150
    categorycode => $category_2,
157
        branchcode   => $branch_2,
151
    surname      => "borrower from $branch_2",
158
        categorycode => $category_2,
152
})->store->borrowernumber;
159
        surname      => "borrower from $branch_2",
153
$requesters{$branch_3} = Koha::Patron->new({
160
    }
154
    branchcode   => $branch_3,
161
)->store->borrowernumber;
155
    categorycode => $category_2,
162
$requesters{$branch_3} = Koha::Patron->new(
156
    surname      => "borrower from $branch_3",
163
    {
157
})->store->borrowernumber;
164
        branchcode   => $branch_3,
165
        categorycode => $category_2,
166
        surname      => "borrower from $branch_3",
167
    }
168
)->store->borrowernumber;
158
169
159
# Configure rules so that $branch_1 allows only $branch_1 patrons
170
# Configure rules so that $branch_1 allows only $branch_1 patrons
160
# to request its items, while $branch_2 will allow its items
171
# to request its items, while $branch_2 will allow its items
Lines 167-173 Koha::CirculationRules->set_rules( Link Here
167
        categorycode => undef,
178
        categorycode => undef,
168
        itemtype     => undef,
179
        itemtype     => undef,
169
        rules        => {
180
        rules        => {
170
            reservesallowed => 25,
181
            reservesallowed  => 25,
171
            holds_per_record => 1,
182
            holds_per_record => 1,
172
        }
183
        }
173
    }
184
    }
Lines 176-184 Koha::CirculationRules->set_rules( Link Here
176
# CPL allows only its own patrons to request its items
187
# CPL allows only its own patrons to request its items
177
Koha::CirculationRules->set_rules(
188
Koha::CirculationRules->set_rules(
178
    {
189
    {
179
        branchcode   => $branch_1,
190
        branchcode => $branch_1,
180
        itemtype     => undef,
191
        itemtype   => undef,
181
        rules        => {
192
        rules      => {
182
            holdallowed  => 'from_home_library',
193
            holdallowed  => 'from_home_library',
183
            returnbranch => 'homebranch',
194
            returnbranch => 'homebranch',
184
        }
195
        }
Lines 188-205 Koha::CirculationRules->set_rules( Link Here
188
# ... while FPL allows anybody to request its items
199
# ... while FPL allows anybody to request its items
189
Koha::CirculationRules->set_rules(
200
Koha::CirculationRules->set_rules(
190
    {
201
    {
191
        branchcode   => $branch_2,
202
        branchcode => $branch_2,
192
        itemtype     => undef,
203
        itemtype   => undef,
193
        rules        => {
204
        rules      => {
194
            holdallowed  => 'from_any_library',
205
            holdallowed  => 'from_any_library',
195
            returnbranch => 'homebranch',
206
            returnbranch => 'homebranch',
196
        }
207
        }
197
    }
208
    }
198
);
209
);
199
210
200
my $bibnum2 = $builder->build_sample_biblio({frameworkcode => $frameworkcode})->biblionumber;
211
my $bibnum2 = $builder->build_sample_biblio( { frameworkcode => $frameworkcode } )->biblionumber;
201
212
202
my ($itemnum_cpl, $itemnum_fpl);
213
my ( $itemnum_cpl, $itemnum_fpl );
203
$itemnum_cpl = $builder->build_sample_item(
214
$itemnum_cpl = $builder->build_sample_item(
204
    {
215
    {
205
        biblionumber => $bibnum2,
216
        biblionumber => $bibnum2,
Lines 219-225 $itemnum_fpl = $builder->build_sample_item( Link Here
219
230
220
# Ensure that priorities are numbered correcly when a hold is moved to waiting
231
# Ensure that priorities are numbered correcly when a hold is moved to waiting
221
# (bug 11947)
232
# (bug 11947)
222
$dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum2));
233
$dbh->do( "DELETE FROM reserves WHERE biblionumber=?", undef, ($bibnum2) );
223
AddReserve(
234
AddReserve(
224
    {
235
    {
225
        branchcode     => $branch_3,
236
        branchcode     => $branch_3,
Lines 244-264 AddReserve( Link Here
244
        priority       => 3,
255
        priority       => 3,
245
    }
256
    }
246
);
257
);
247
ModReserveAffect($itemnum_cpl, $requesters{$branch_3}, 0);
258
ModReserveAffect( $itemnum_cpl, $requesters{$branch_3}, 0 );
248
259
249
# Now it should have different priorities.
260
# Now it should have different priorities.
250
my $biblio = Koha::Biblios->find( $bibnum2 );
261
my $biblio = Koha::Biblios->find($bibnum2);
251
my $holds = $biblio->holds({}, { order_by => 'reserve_id' });;
262
my $holds  = $biblio->holds( {}, { order_by => 'reserve_id' } );
252
is($holds->next->priority, 0, 'Item is correctly waiting');
263
is( $holds->next->priority, 0, 'Item is correctly waiting' );
253
is($holds->next->priority, 1, 'Item is correctly priority 1');
264
is( $holds->next->priority, 1, 'Item is correctly priority 1' );
254
is($holds->next->priority, 2, 'Item is correctly priority 2');
265
is( $holds->next->priority, 2, 'Item is correctly priority 2' );
255
266
256
my @reserves = Koha::Holds->search({ borrowernumber => $requesters{$branch_3} })->waiting->as_list;
267
my @reserves = Koha::Holds->search( { borrowernumber => $requesters{$branch_3} } )->waiting->as_list;
257
is( @reserves, 1, 'GetWaiting got only the waiting reserve' );
268
is( @reserves,                      1,                      'GetWaiting got only the waiting reserve' );
258
is( $reserves[0]->borrowernumber(), $requesters{$branch_3}, 'GetWaiting got the reserve for the correct borrower' );
269
is( $reserves[0]->borrowernumber(), $requesters{$branch_3}, 'GetWaiting got the reserve for the correct borrower' );
259
270
260
271
$dbh->do( "DELETE FROM reserves WHERE biblionumber=?", undef, ($bibnum2) );
261
$dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum2));
262
AddReserve(
272
AddReserve(
263
    {
273
    {
264
        branchcode     => $branch_3,
274
        branchcode     => $branch_3,
Lines 289-301 AddReserve( Link Here
289
t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
299
t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
290
300
291
my $messages;
301
my $messages;
302
292
# Return the CPL item at FPL.  The hold that should be triggered is
303
# Return the CPL item at FPL.  The hold that should be triggered is
293
# the one placed by the CPL patron, as the other two patron's hold
304
# the one placed by the CPL patron, as the other two patron's hold
294
# requests cannot be filled by that item per policy.
305
# requests cannot be filled by that item per policy.
295
(undef, $messages, undef, undef) = AddReturn('bug10272_CPL', $branch_2);
306
( undef, $messages, undef, undef ) = AddReturn( 'bug10272_CPL', $branch_2 );
296
is( $messages->{ResFound}->{borrowernumber},
307
is(
308
    $messages->{ResFound}->{borrowernumber},
297
    $requesters{$branch_1},
309
    $requesters{$branch_1},
298
    'restrictive library\'s items only fill requests by own patrons (bug 10272)');
310
    'restrictive library\'s items only fill requests by own patrons (bug 10272)'
311
);
299
312
300
# Return the FPL item at FPL.  The hold that should be triggered is
313
# Return the FPL item at FPL.  The hold that should be triggered is
301
# the one placed by the RPL patron, as that patron is first in line
314
# the one placed by the RPL patron, as that patron is first in line
Lines 304-323 is( $messages->{ResFound}->{borrowernumber}, Link Here
304
# Ensure that the preference 'LocalHoldsPriority' is not set (Bug 15244):
317
# Ensure that the preference 'LocalHoldsPriority' is not set (Bug 15244):
305
t::lib::Mocks::mock_preference( 'LocalHoldsPriority', '' );
318
t::lib::Mocks::mock_preference( 'LocalHoldsPriority', '' );
306
319
307
(undef, $messages, undef, undef) = AddReturn('bug10272_FPL', $branch_2);
320
( undef, $messages, undef, undef ) = AddReturn( 'bug10272_FPL', $branch_2 );
308
is( $messages->{ResFound}->{borrowernumber},
321
is(
322
    $messages->{ResFound}->{borrowernumber},
309
    $requesters{$branch_3},
323
    $requesters{$branch_3},
310
    'for generous library, its items fill first hold request in line (bug 10272)');
324
    'for generous library, its items fill first hold request in line (bug 10272)'
325
);
311
326
312
$biblio = Koha::Biblios->find( $biblionumber );
327
$biblio = Koha::Biblios->find($biblionumber);
313
$holds = $biblio->holds;
328
$holds  = $biblio->holds;
314
is($holds->count, 1, "Only one reserves for this biblio");
329
is( $holds->count, 1, "Only one reserves for this biblio" );
315
$holds->next->reserve_id;
330
$holds->next->reserve_id;
316
331
317
# Tests for bug 9761 (ConfirmFutureHolds): new CheckReserves lookahead parameter, and corresponding change in AddReturn
332
# Tests for bug 9761 (ConfirmFutureHolds): new CheckReserves lookahead parameter, and corresponding change in AddReturn
318
# Note that CheckReserve uses its lookahead parameter and does not check ConfirmFutureHolds pref (it should be passed if needed like AddReturn does)
333
# Note that CheckReserve uses its lookahead parameter and does not check ConfirmFutureHolds pref (it should be passed if needed like AddReturn does)
319
# Test 9761a: Add a reserve without date, CheckReserve should return it
334
# Test 9761a: Add a reserve without date, CheckReserve should return it
320
$dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
335
$dbh->do( "DELETE FROM reserves WHERE biblionumber=?", undef, ($bibnum) );
321
AddReserve(
336
AddReserve(
322
    {
337
    {
323
        branchcode     => $branch_1,
338
        branchcode     => $branch_1,
Lines 326-341 AddReserve( Link Here
326
        priority       => 1,
341
        priority       => 1,
327
    }
342
    }
328
);
343
);
329
($status)=CheckReserves( $item );
344
($status) = CheckReserves($item);
330
is( $status, 'Reserved', 'CheckReserves returns reserve without lookahead');
345
is( $status, 'Reserved', 'CheckReserves returns reserve without lookahead' );
331
($status)=CheckReserves( $item, 7 );
346
($status) = CheckReserves( $item, 7 );
332
is( $status, 'Reserved', 'CheckReserves also returns reserve with lookahead');
347
is( $status, 'Reserved', 'CheckReserves also returns reserve with lookahead' );
333
348
334
# Test 9761b: Add a reserve with future date, CheckReserve should not return it
349
# Test 9761b: Add a reserve with future date, CheckReserve should not return it
335
$dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
350
$dbh->do( "DELETE FROM reserves WHERE biblionumber=?", undef, ($bibnum) );
336
t::lib::Mocks::mock_preference('AllowHoldDateInFuture', 1);
351
t::lib::Mocks::mock_preference( 'AllowHoldDateInFuture', 1 );
337
my $resdate= dt_from_string();
352
my $resdate = dt_from_string();
338
$resdate->add_duration(DateTime::Duration->new(days => 4));
353
$resdate->add_duration( DateTime::Duration->new( days => 4 ) );
339
my $reserve_id = AddReserve(
354
my $reserve_id = AddReserve(
340
    {
355
    {
341
        branchcode       => $branch_1,
356
        branchcode       => $branch_1,
Lines 345-375 my $reserve_id = AddReserve( Link Here
345
        reservation_date => $resdate,
360
        reservation_date => $resdate,
346
    }
361
    }
347
);
362
);
348
($status)=CheckReserves( $item );
363
($status) = CheckReserves($item);
349
is( $status, '', 'CheckReserves returns no future reserve without lookahead');
364
is( $status, '', 'CheckReserves returns no future reserve without lookahead' );
350
365
351
# Test 9761c: Add a reserve with future date, CheckReserve should return it if lookahead is high enough
366
# Test 9761c: Add a reserve with future date, CheckReserve should return it if lookahead is high enough
352
($status)=CheckReserves( $item, 3 );
367
($status) = CheckReserves( $item, 3 );
353
is( $status, '', 'CheckReserves returns no future reserve with insufficient lookahead');
368
is( $status, '', 'CheckReserves returns no future reserve with insufficient lookahead' );
354
($status)=CheckReserves( $item, 4 );
369
($status) = CheckReserves( $item, 4 );
355
is( $status, 'Reserved', 'CheckReserves returns future reserve with sufficient lookahead');
370
is( $status, 'Reserved', 'CheckReserves returns future reserve with sufficient lookahead' );
356
371
357
# Test 9761d: Check ResFound message of AddReturn for future hold
372
# Test 9761d: Check ResFound message of AddReturn for future hold
358
# Note that AddReturn is in Circulation.pm, but this test really pertains to reserves; AddReturn uses the ConfirmFutureHolds pref when calling CheckReserves
373
# Note that AddReturn is in Circulation.pm, but this test really pertains to reserves; AddReturn uses the ConfirmFutureHolds pref when calling CheckReserves
359
# In this test we do not need an issued item; it is just a 'checkin'
374
# In this test we do not need an issued item; it is just a 'checkin'
360
t::lib::Mocks::mock_preference('ConfirmFutureHolds', 0);
375
t::lib::Mocks::mock_preference( 'ConfirmFutureHolds', 0 );
361
(my $doreturn, $messages)= AddReturn($testbarcode,$branch_1);
376
( my $doreturn, $messages ) = AddReturn( $testbarcode, $branch_1 );
362
is($messages->{ResFound}//'', '', 'AddReturn does not care about future reserve when ConfirmFutureHolds is off');
377
is( $messages->{ResFound} // '', '', 'AddReturn does not care about future reserve when ConfirmFutureHolds is off' );
363
t::lib::Mocks::mock_preference('ConfirmFutureHolds', 3);
378
t::lib::Mocks::mock_preference( 'ConfirmFutureHolds', 3 );
364
($doreturn, $messages)= AddReturn($testbarcode,$branch_1);
379
( $doreturn, $messages ) = AddReturn( $testbarcode, $branch_1 );
365
is(exists $messages->{ResFound}?1:0, 0, 'AddReturn ignores future reserve beyond ConfirmFutureHolds days');
380
is( exists $messages->{ResFound} ? 1 : 0, 0, 'AddReturn ignores future reserve beyond ConfirmFutureHolds days' );
366
t::lib::Mocks::mock_preference('ConfirmFutureHolds', 7);
381
t::lib::Mocks::mock_preference( 'ConfirmFutureHolds', 7 );
367
($doreturn, $messages)= AddReturn($testbarcode,$branch_1);
382
( $doreturn, $messages ) = AddReturn( $testbarcode, $branch_1 );
368
is(exists $messages->{ResFound}?1:0, 1, 'AddReturn considers future reserve within ConfirmFutureHolds days');
383
is( exists $messages->{ResFound} ? 1 : 0, 1, 'AddReturn considers future reserve within ConfirmFutureHolds days' );
369
384
370
my $now_holder = $builder->build_object({ class => 'Koha::Patrons', value => {
385
my $now_holder = $builder->build_object(
371
    branchcode       => $branch_1,
386
    {
372
}});
387
        class => 'Koha::Patrons',
388
        value => {
389
            branchcode => $branch_1,
390
        }
391
    }
392
);
373
my $now_reserve_id = AddReserve(
393
my $now_reserve_id = AddReserve(
374
    {
394
    {
375
        branchcode       => $branch_1,
395
        branchcode       => $branch_1,
Lines 380-425 my $now_reserve_id = AddReserve( Link Here
380
    }
400
    }
381
);
401
);
382
my $which_highest;
402
my $which_highest;
383
($status,$which_highest)=CheckReserves( $item, 3 );
403
( $status, $which_highest ) = CheckReserves( $item, 3 );
384
is( $which_highest->{reserve_id}, $now_reserve_id, 'CheckReserves returns lower priority current reserve with insufficient lookahead');
404
is(
385
($status, $which_highest)=CheckReserves( $item, 4 );
405
    $which_highest->{reserve_id}, $now_reserve_id,
386
is( $which_highest->{reserve_id}, $reserve_id, 'CheckReserves returns higher priority future reserve with sufficient lookahead');
406
    'CheckReserves returns lower priority current reserve with insufficient lookahead'
387
ModReserve({ reserve_id => $now_reserve_id, rank => 'del', cancellation_reason => 'test reserve' });
407
);
388
408
( $status, $which_highest ) = CheckReserves( $item, 4 );
409
is(
410
    $which_highest->{reserve_id}, $reserve_id,
411
    'CheckReserves returns higher priority future reserve with sufficient lookahead'
412
);
413
ModReserve( { reserve_id => $now_reserve_id, rank => 'del', cancellation_reason => 'test reserve' } );
389
414
390
# End of tests for bug 9761 (ConfirmFutureHolds)
415
# End of tests for bug 9761 (ConfirmFutureHolds)
391
416
392
393
# test marking a hold as captured
417
# test marking a hold as captured
394
my $hold_notice_count = count_hold_print_messages();
418
my $hold_notice_count = count_hold_print_messages();
395
ModReserveAffect($item->itemnumber, $requesters{$branch_1}, 0);
419
ModReserveAffect( $item->itemnumber, $requesters{$branch_1}, 0 );
396
my $new_count = count_hold_print_messages();
420
my $new_count = count_hold_print_messages();
397
is($new_count, $hold_notice_count + 1, 'patron notified when item set to waiting');
421
is( $new_count, $hold_notice_count + 1, 'patron notified when item set to waiting' );
398
422
399
# test that duplicate notices aren't generated
423
# test that duplicate notices aren't generated
400
ModReserveAffect($item->itemnumber, $requesters{$branch_1}, 0);
424
ModReserveAffect( $item->itemnumber, $requesters{$branch_1}, 0 );
401
$new_count = count_hold_print_messages();
425
$new_count = count_hold_print_messages();
402
is($new_count, $hold_notice_count + 1, 'patron not notified a second time (bug 11445)');
426
is( $new_count, $hold_notice_count + 1, 'patron not notified a second time (bug 11445)' );
403
427
404
# avoiding the not_same_branch error
428
# avoiding the not_same_branch error
405
t::lib::Mocks::mock_preference('IndependentBranches', 0);
429
t::lib::Mocks::mock_preference( 'IndependentBranches', 0 );
406
$item = Koha::Items->find($item->itemnumber);
430
$item = Koha::Items->find( $item->itemnumber );
407
is(
431
is(
408
    @{$item->safe_delete->messages}[0]->message,
432
    @{ $item->safe_delete->messages }[0]->message,
409
    'book_reserved',
433
    'book_reserved',
410
    'item that is captured to fill a hold cannot be deleted',
434
    'item that is captured to fill a hold cannot be deleted',
411
);
435
);
412
436
413
my $letter = ReserveSlip( { branchcode => $branch_1, reserve_id => $reserve_id } );
437
my $letter = ReserveSlip( { branchcode => $branch_1, reserve_id => $reserve_id } );
414
ok(defined($letter), 'can successfully generate hold slip (bug 10949)');
438
ok( defined($letter), 'can successfully generate hold slip (bug 10949)' );
415
439
416
# Tests for bug 9788: Does Koha::Item->current_holds return a future wait?
440
# Tests for bug 9788: Does Koha::Item->current_holds return a future wait?
417
# 9788a: current_holds does not return future next available hold
441
# 9788a: current_holds does not return future next available hold
418
$dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
442
$dbh->do( "DELETE FROM reserves WHERE biblionumber=?", undef, ($bibnum) );
419
t::lib::Mocks::mock_preference('ConfirmFutureHolds', 2);
443
t::lib::Mocks::mock_preference( 'ConfirmFutureHolds',    2 );
420
t::lib::Mocks::mock_preference('AllowHoldDateInFuture', 1);
444
t::lib::Mocks::mock_preference( 'AllowHoldDateInFuture', 1 );
421
$resdate= dt_from_string();
445
$resdate = dt_from_string();
422
$resdate->add_duration(DateTime::Duration->new(days => 2));
446
$resdate->add_duration( DateTime::Duration->new( days => 2 ) );
423
AddReserve(
447
AddReserve(
424
    {
448
    {
425
        branchcode       => $branch_1,
449
        branchcode       => $branch_1,
Lines 431-441 AddReserve( Link Here
431
);
455
);
432
456
433
$holds = $item->current_holds;
457
$holds = $item->current_holds;
434
my $dtf = Koha::Database->new->schema->storage->datetime_parser;
458
my $dtf          = Koha::Database->new->schema->storage->datetime_parser;
435
my $future_holds = $holds->search({ reservedate => { '>' => $dtf->format_date( dt_from_string ) } } );
459
my $future_holds = $holds->search( { reservedate => { '>' => $dtf->format_date(dt_from_string) } } );
436
is( $future_holds->count, 0, 'current_holds does not return a future next available hold');
460
is( $future_holds->count, 0, 'current_holds does not return a future next available hold' );
461
437
# 9788b: current_holds does not return future item level hold
462
# 9788b: current_holds does not return future item level hold
438
$dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
463
$dbh->do( "DELETE FROM reserves WHERE biblionumber=?", undef, ($bibnum) );
439
AddReserve(
464
AddReserve(
440
    {
465
    {
441
        branchcode       => $branch_1,
466
        branchcode       => $branch_1,
Lines 445-463 AddReserve( Link Here
445
        reservation_date => $resdate,
470
        reservation_date => $resdate,
446
        itemnumber       => $item->itemnumber,
471
        itemnumber       => $item->itemnumber,
447
    }
472
    }
448
); #item level hold
473
);    #item level hold
449
$future_holds = $holds->search({ reservedate => { '>' => $dtf->format_date( dt_from_string ) } } );
474
$future_holds = $holds->search( { reservedate => { '>' => $dtf->format_date(dt_from_string) } } );
450
is( $future_holds->count, 0, 'current_holds does not return a future item level hold' );
475
is( $future_holds->count, 0, 'current_holds does not return a future item level hold' );
476
451
# 9788c: current_holds returns future wait (confirmed future hold)
477
# 9788c: current_holds returns future wait (confirmed future hold)
452
ModReserveAffect( $item->itemnumber,  $requesters{$branch_1} , 0); #confirm hold
478
ModReserveAffect( $item->itemnumber, $requesters{$branch_1}, 0 );    #confirm hold
453
$future_holds = $holds->search({ reservedate => { '>' => $dtf->format_date( dt_from_string ) } } );
479
$future_holds = $holds->search( { reservedate => { '>' => $dtf->format_date(dt_from_string) } } );
454
is( $future_holds->count, 1, 'current_holds returns a future wait (confirmed future hold)' );
480
is( $future_holds->count, 1, 'current_holds returns a future wait (confirmed future hold)' );
481
455
# End of tests for bug 9788
482
# End of tests for bug 9788
456
483
457
$dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
484
$dbh->do( "DELETE FROM reserves WHERE biblionumber=?", undef, ($bibnum) );
485
458
# Tests for CalculatePriority (bug 8918)
486
# Tests for CalculatePriority (bug 8918)
459
my $p = C4::Reserves::CalculatePriority($bibnum2);
487
my $p = C4::Reserves::CalculatePriority($bibnum2);
460
is($p, 4, 'CalculatePriority should now return priority 4');
488
is( $p, 4, 'CalculatePriority should now return priority 4' );
461
AddReserve(
489
AddReserve(
462
    {
490
    {
463
        branchcode     => $branch_1,
491
        branchcode     => $branch_1,
Lines 467-477 AddReserve( Link Here
467
    }
495
    }
468
);
496
);
469
$p = C4::Reserves::CalculatePriority($bibnum2);
497
$p = C4::Reserves::CalculatePriority($bibnum2);
470
is($p, 5, 'CalculatePriority should now return priority 5');
498
is( $p, 5, 'CalculatePriority should now return priority 5' );
499
471
#some tests on bibnum
500
#some tests on bibnum
472
$dbh->do("DELETE FROM reserves WHERE biblionumber=?",undef,($bibnum));
501
$dbh->do( "DELETE FROM reserves WHERE biblionumber=?", undef, ($bibnum) );
473
$p = C4::Reserves::CalculatePriority($bibnum);
502
$p = C4::Reserves::CalculatePriority($bibnum);
474
is($p, 1, 'CalculatePriority should now return priority 1');
503
is( $p, 1, 'CalculatePriority should now return priority 1' );
504
475
#add a new reserve and confirm it to waiting
505
#add a new reserve and confirm it to waiting
476
AddReserve(
506
AddReserve(
477
    {
507
    {
Lines 483-492 AddReserve( Link Here
483
    }
513
    }
484
);
514
);
485
$p = C4::Reserves::CalculatePriority($bibnum);
515
$p = C4::Reserves::CalculatePriority($bibnum);
486
is($p, 2, 'CalculatePriority should now return priority 2');
516
is( $p, 2, 'CalculatePriority should now return priority 2' );
487
ModReserveAffect( $item->itemnumber,  $requesters{$branch_1} , 0);
517
ModReserveAffect( $item->itemnumber, $requesters{$branch_1}, 0 );
488
$p = C4::Reserves::CalculatePriority($bibnum);
518
$p = C4::Reserves::CalculatePriority($bibnum);
489
is($p, 1, 'CalculatePriority should now return priority 1');
519
is( $p, 1, 'CalculatePriority should now return priority 1' );
520
490
#add another biblio hold, no resdate
521
#add another biblio hold, no resdate
491
AddReserve(
522
AddReserve(
492
    {
523
    {
Lines 497-564 AddReserve( Link Here
497
    }
528
    }
498
);
529
);
499
$p = C4::Reserves::CalculatePriority($bibnum);
530
$p = C4::Reserves::CalculatePriority($bibnum);
500
is($p, 2, 'CalculatePriority should now return priority 2');
531
is( $p, 2, 'CalculatePriority should now return priority 2' );
532
501
#add another future hold
533
#add another future hold
502
t::lib::Mocks::mock_preference('AllowHoldDateInFuture', 1);
534
t::lib::Mocks::mock_preference( 'AllowHoldDateInFuture', 1 );
503
$resdate= dt_from_string();
535
$resdate = dt_from_string();
504
$resdate->add_duration(DateTime::Duration->new(days => 1));
536
$resdate->add_duration( DateTime::Duration->new( days => 1 ) );
505
AddReserve(
537
AddReserve(
506
    {
538
    {
507
        branchcode     => $branch_1,
539
        branchcode       => $branch_1,
508
        borrowernumber => $requesters{'CPL2'},
540
        borrowernumber   => $requesters{'CPL2'},
509
        biblionumber   => $bibnum,
541
        biblionumber     => $bibnum,
510
        priority       => $p,
542
        priority         => $p,
511
        reservation_date => $resdate,
543
        reservation_date => $resdate,
512
    }
544
    }
513
);
545
);
514
$p = C4::Reserves::CalculatePriority($bibnum);
546
$p = C4::Reserves::CalculatePriority($bibnum);
515
is($p, 2, 'CalculatePriority should now still return priority 2');
547
is( $p, 2, 'CalculatePriority should now still return priority 2' );
548
516
#calc priority with future resdate
549
#calc priority with future resdate
517
$p = C4::Reserves::CalculatePriority($bibnum, $resdate);
550
$p = C4::Reserves::CalculatePriority( $bibnum, $resdate );
518
is($p, 3, 'CalculatePriority should now return priority 3');
551
is( $p, 3, 'CalculatePriority should now return priority 3' );
552
519
# End of tests for bug 8918
553
# End of tests for bug 8918
520
554
521
# regression test for bug 12630
555
# regression test for bug 12630
522
# Now there are 2 reserves on $bibnum
556
# Now there are 2 reserves on $bibnum
523
t::lib::Mocks::mock_preference('AllowHoldDateInFuture', 1);
557
t::lib::Mocks::mock_preference( 'AllowHoldDateInFuture', 1 );
524
my $bor_tmp_1 = $builder->build_object({ class => 'Koha::Patrons',value =>{
558
my $bor_tmp_1 = $builder->build_object(
525
    firstname =>  'my firstname tmp 1',
559
    {
526
    surname => 'my surname tmp 1',
560
        class => 'Koha::Patrons',
527
    categorycode => 'S',
561
        value => {
528
    branchcode => 'CPL',
562
            firstname    => 'my firstname tmp 1',
529
}});
563
            surname      => 'my surname tmp 1',
530
my $bor_tmp_2 = $builder->build_object({ class => 'Koha::Patrons',value =>{
564
            categorycode => 'S',
531
    firstname =>  'my firstname tmp 2',
565
            branchcode   => 'CPL',
532
    surname => 'my surname tmp 2',
566
        }
533
    categorycode => 'S',
567
    }
534
    branchcode => 'CPL',
568
);
535
}});
569
my $bor_tmp_2 = $builder->build_object(
570
    {
571
        class => 'Koha::Patrons',
572
        value => {
573
            firstname    => 'my firstname tmp 2',
574
            surname      => 'my surname tmp 2',
575
            categorycode => 'S',
576
            branchcode   => 'CPL',
577
        }
578
    }
579
);
536
my $borrowernumber_tmp_1 = $bor_tmp_1->borrowernumber;
580
my $borrowernumber_tmp_1 = $bor_tmp_1->borrowernumber;
537
my $borrowernumber_tmp_2 = $bor_tmp_2->borrowernumber;
581
my $borrowernumber_tmp_2 = $bor_tmp_2->borrowernumber;
538
my $date_in_future = dt_from_string();
582
my $date_in_future       = dt_from_string();
539
$date_in_future = $date_in_future->add_duration(DateTime::Duration->new(days => 1));
583
$date_in_future = $date_in_future->add_duration( DateTime::Duration->new( days => 1 ) );
540
AddReserve({
584
AddReserve(
541
    branchcode => 'CPL',
585
    {
542
    borrowernumber => $borrowernumber_tmp_1,
586
        branchcode       => 'CPL',
543
    biblionumber => $bibnum,
587
        borrowernumber   => $borrowernumber_tmp_1,
544
    priority => 3,
588
        biblionumber     => $bibnum,
545
    reservation_date => $date_in_future
589
        priority         => 3,
546
});
590
        reservation_date => $date_in_future
547
AddReserve({
591
    }
548
    branchcode => 'CPL',
592
);
549
    borrowernumber => $borrowernumber_tmp_2,
593
AddReserve(
550
    biblionumber => $bibnum,
594
    {
551
    priority => 4,
595
        branchcode       => 'CPL',
552
    reservation_date => $date_in_future
596
        borrowernumber   => $borrowernumber_tmp_2,
553
});
597
        biblionumber     => $bibnum,
554
my @r1 = Koha::Holds->search({ borrowernumber => $borrowernumber_tmp_1 })->as_list;
598
        priority         => 4,
555
my @r2 = Koha::Holds->search({ borrowernumber => $borrowernumber_tmp_2 })->as_list;
599
        reservation_date => $date_in_future
556
is( $r1[0]->priority, 3, 'priority for hold in future should be correct');
600
    }
557
is( $r2[0]->priority, 4, 'priority for hold not in future should be correct');
601
);
602
my @r1 = Koha::Holds->search( { borrowernumber => $borrowernumber_tmp_1 } )->as_list;
603
my @r2 = Koha::Holds->search( { borrowernumber => $borrowernumber_tmp_2 } )->as_list;
604
is( $r1[0]->priority, 3, 'priority for hold in future should be correct' );
605
is( $r2[0]->priority, 4, 'priority for hold not in future should be correct' );
606
558
# end of tests for bug 12630
607
# end of tests for bug 12630
559
608
560
# Tests for cancel reserves by users from OPAC.
609
# Tests for cancel reserves by users from OPAC.
561
$dbh->do('DELETE FROM reserves', undef, ($bibnum));
610
$dbh->do( 'DELETE FROM reserves', undef, ($bibnum) );
562
AddReserve(
611
AddReserve(
563
    {
612
    {
564
        branchcode     => $branch_1,
613
        branchcode     => $branch_1,
Lines 567-575 AddReserve( Link Here
567
        priority       => 1,
616
        priority       => 1,
568
    }
617
    }
569
);
618
);
570
my (undef, $canres, undef) = CheckReserves( $item );
619
my ( undef, $canres, undef ) = CheckReserves($item);
571
620
572
is( CanReserveBeCanceledFromOpac(), undef,
621
is(
622
    CanReserveBeCanceledFromOpac(), undef,
573
    'CanReserveBeCanceledFromOpac should return undef if called without any parameter'
623
    'CanReserveBeCanceledFromOpac should return undef if called without any parameter'
574
);
624
);
575
is(
625
is(
Lines 583-601 is( Link Here
583
    'CanReserveBeCanceledFromOpac should return undef if called without borrowernumber'
633
    'CanReserveBeCanceledFromOpac should return undef if called without borrowernumber'
584
);
634
);
585
635
586
my $cancancel = CanReserveBeCanceledFromOpac($canres->{reserve_id}, $requesters{$branch_1});
636
my $cancancel = CanReserveBeCanceledFromOpac( $canres->{reserve_id}, $requesters{$branch_1} );
587
is($cancancel, 1, 'Can user cancel its own reserve');
637
is( $cancancel, 1, 'Can user cancel its own reserve' );
588
638
589
$cancancel = CanReserveBeCanceledFromOpac($canres->{reserve_id}, $requesters{$branch_2});
639
$cancancel = CanReserveBeCanceledFromOpac( $canres->{reserve_id}, $requesters{$branch_2} );
590
is($cancancel, 0, 'Other user cant cancel reserve');
640
is( $cancancel, 0, 'Other user cant cancel reserve' );
591
641
592
ModReserveAffect($item->itemnumber, $requesters{$branch_1}, 1);
642
ModReserveAffect( $item->itemnumber, $requesters{$branch_1}, 1 );
593
$cancancel = CanReserveBeCanceledFromOpac($canres->{reserve_id}, $requesters{$branch_1});
643
$cancancel = CanReserveBeCanceledFromOpac( $canres->{reserve_id}, $requesters{$branch_1} );
594
is($cancancel, 0, 'Reserve in transfer status cant be canceled');
644
is( $cancancel, 0, 'Reserve in transfer status cant be canceled' );
595
645
596
$dbh->do('DELETE FROM reserves', undef, ($bibnum));
646
$dbh->do( 'DELETE FROM reserves', undef, ($bibnum) );
597
is( CanReserveBeCanceledFromOpac($canres->{resserve_id}, $requesters{$branch_1}), undef,
647
is(
598
    'Cannot cancel a deleted hold' );
648
    CanReserveBeCanceledFromOpac( $canres->{resserve_id}, $requesters{$branch_1} ), undef,
649
    'Cannot cancel a deleted hold'
650
);
599
651
600
AddReserve(
652
AddReserve(
601
    {
653
    {
Lines 605-621 AddReserve( Link Here
605
        priority       => 1,
657
        priority       => 1,
606
    }
658
    }
607
);
659
);
608
(undef, $canres, undef) = CheckReserves( $item );
660
( undef, $canres, undef ) = CheckReserves($item);
609
661
610
ModReserveAffect($item->itemnumber, $requesters{$branch_1}, 0);
662
ModReserveAffect( $item->itemnumber, $requesters{$branch_1}, 0 );
611
$cancancel = CanReserveBeCanceledFromOpac($canres->{reserve_id}, $requesters{$branch_1});
663
$cancancel = CanReserveBeCanceledFromOpac( $canres->{reserve_id}, $requesters{$branch_1} );
612
is($cancancel, 0, 'Reserve in waiting status cant be canceled');
664
is( $cancancel, 0, 'Reserve in waiting status cant be canceled' );
613
665
614
# End of tests for bug 12876
666
# End of tests for bug 12876
615
667
616
       ####
668
####
617
####### Testing Bug 13113 - Prevent juvenile/children from reserving ageRestricted material >>>
669
####### Testing Bug 13113 - Prevent juvenile/children from reserving ageRestricted material >>>
618
       ####
670
####
619
671
620
t::lib::Mocks::mock_preference( 'AgeRestrictionMarker', 'FSK|PEGI|Age|K' );
672
t::lib::Mocks::mock_preference( 'AgeRestrictionMarker', 'FSK|PEGI|Age|K' );
621
673
Lines 624-655 t::lib::Mocks::mock_preference( 'AgeRestrictionMarker', 'FSK|PEGI|Age|K' ); Link Here
624
#Set the ageRestriction for the Biblio
676
#Set the ageRestriction for the Biblio
625
$biblio = Koha::Biblios->find($bibnum);
677
$biblio = Koha::Biblios->find($bibnum);
626
my $record = $biblio->metadata->record;
678
my $record = $biblio->metadata->record;
627
my ( $ageres_tagid, $ageres_subfieldid ) = GetMarcFromKohaField( "biblioitems.agerestriction" );
679
my ( $ageres_tagid, $ageres_subfieldid ) = GetMarcFromKohaField("biblioitems.agerestriction");
628
$record->append_fields(  MARC::Field->new($ageres_tagid, '', '', $ageres_subfieldid => 'PEGI 16')  );
680
$record->append_fields( MARC::Field->new( $ageres_tagid, '', '', $ageres_subfieldid => 'PEGI 16' ) );
629
C4::Biblio::ModBiblio( $record, $bibnum, $frameworkcode );
681
C4::Biblio::ModBiblio( $record, $bibnum, $frameworkcode );
630
682
631
is( C4::Reserves::CanBookBeReserved($borrowernumber, $biblionumber)->{status} , 'OK', "Reserving an ageRestricted Biblio without a borrower dateofbirth succeeds" );
683
is(
684
    C4::Reserves::CanBookBeReserved( $borrowernumber, $biblionumber )->{status}, 'OK',
685
    "Reserving an ageRestricted Biblio without a borrower dateofbirth succeeds"
686
);
632
687
633
#Set the dateofbirth for the Borrower making them "too young".
688
#Set the dateofbirth for the Borrower making them "too young".
634
$borrower->{dateofbirth} = DateTime->now->add( years => -15 );
689
$borrower->{dateofbirth} = DateTime->now->add( years => -15 );
635
Koha::Patrons->find( $borrowernumber )->set({ dateofbirth => $borrower->{dateofbirth} })->store;
690
Koha::Patrons->find($borrowernumber)->set( { dateofbirth => $borrower->{dateofbirth} } )->store;
636
691
637
is( C4::Reserves::CanBookBeReserved($borrowernumber, $biblionumber)->{status} , 'ageRestricted', "Reserving a 'PEGI 16' Biblio by a 15 year old borrower fails");
692
is(
693
    C4::Reserves::CanBookBeReserved( $borrowernumber, $biblionumber )->{status}, 'ageRestricted',
694
    "Reserving a 'PEGI 16' Biblio by a 15 year old borrower fails"
695
);
638
696
639
#Set the dateofbirth for the Borrower making them "too old".
697
#Set the dateofbirth for the Borrower making them "too old".
640
$borrower->{dateofbirth} = DateTime->now->add( years => -30 );
698
$borrower->{dateofbirth} = DateTime->now->add( years => -30 );
641
Koha::Patrons->find( $borrowernumber )->set({ dateofbirth => $borrower->{dateofbirth} })->store;
699
Koha::Patrons->find($borrowernumber)->set( { dateofbirth => $borrower->{dateofbirth} } )->store;
642
700
643
is( C4::Reserves::CanBookBeReserved($borrowernumber, $biblionumber)->{status} , 'OK', "Reserving a 'PEGI 16' Biblio by a 30 year old borrower succeeds");
701
is(
702
    C4::Reserves::CanBookBeReserved( $borrowernumber, $biblionumber )->{status}, 'OK',
703
    "Reserving a 'PEGI 16' Biblio by a 30 year old borrower succeeds"
704
);
644
705
645
is( C4::Reserves::CanBookBeReserved($borrowernumber, $biblio_with_no_item->biblionumber)->{status} , '', "Biblio with no item. Status is empty");
706
is(
646
       ####
707
    C4::Reserves::CanBookBeReserved( $borrowernumber, $biblio_with_no_item->biblionumber )->{status}, '',
708
    "Biblio with no item. Status is empty"
709
);
710
####
647
####### EO Bug 13113 <<<
711
####### EO Bug 13113 <<<
648
       ####
712
####
649
713
650
ok( C4::Reserves::IsAvailableForItemLevelRequest($item, $patron), "Reserving a book on item level" );
714
ok( C4::Reserves::IsAvailableForItemLevelRequest( $item, $patron ), "Reserving a book on item level" );
651
715
652
my $pickup_branch = $builder->build({ source => 'Branch' })->{ branchcode };
716
my $pickup_branch = $builder->build( { source => 'Branch' } )->{branchcode};
653
t::lib::Mocks::mock_preference( 'UseBranchTransferLimits',  '1' );
717
t::lib::Mocks::mock_preference( 'UseBranchTransferLimits',  '1' );
654
t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
718
t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
655
my $limit = Koha::Item::Transfer::Limit->new(
719
my $limit = Koha::Item::Transfer::Limit->new(
Lines 659-675 my $limit = Koha::Item::Transfer::Limit->new( Link Here
659
        itemtype   => $item->effective_itemtype,
723
        itemtype   => $item->effective_itemtype,
660
    }
724
    }
661
)->store();
725
)->store();
662
is( C4::Reserves::IsAvailableForItemLevelRequest($item, $patron, $pickup_branch), 0, "Item level request not available due to transfer limit" );
726
is(
663
t::lib::Mocks::mock_preference( 'UseBranchTransferLimits',  '0' );
727
    C4::Reserves::IsAvailableForItemLevelRequest( $item, $patron, $pickup_branch ), 0,
728
    "Item level request not available due to transfer limit"
729
);
730
t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
664
731
665
my $categorycode = $borrower->{categorycode};
732
my $categorycode  = $borrower->{categorycode};
666
my $holdingbranch = $item->{holdingbranch};
733
my $holdingbranch = $item->{holdingbranch};
667
Koha::CirculationRules->set_rules(
734
Koha::CirculationRules->set_rules(
668
    {
735
    {
669
        categorycode => $categorycode,
736
        categorycode => $categorycode,
670
        itemtype     => $item->effective_itemtype,
737
        itemtype     => $item->effective_itemtype,
671
        branchcode   => $holdingbranch,
738
        branchcode   => $holdingbranch,
672
        rules => {
739
        rules        => {
673
            onshelfholds => 1,
740
            onshelfholds => 1,
674
        }
741
        }
675
    }
742
    }
Lines 677-685 Koha::CirculationRules->set_rules( Link Here
677
744
678
# tests for MoveReserve in relation to ConfirmFutureHolds (BZ 14526)
745
# tests for MoveReserve in relation to ConfirmFutureHolds (BZ 14526)
679
#   hold from A pos 1, today, no fut holds: MoveReserve should fill it
746
#   hold from A pos 1, today, no fut holds: MoveReserve should fill it
680
$dbh->do('DELETE FROM reserves', undef, ($bibnum));
747
$dbh->do( 'DELETE FROM reserves', undef, ($bibnum) );
681
t::lib::Mocks::mock_preference('ConfirmFutureHolds', 0);
748
t::lib::Mocks::mock_preference( 'ConfirmFutureHolds',    0 );
682
t::lib::Mocks::mock_preference('AllowHoldDateInFuture', 1);
749
t::lib::Mocks::mock_preference( 'AllowHoldDateInFuture', 1 );
683
AddReserve(
750
AddReserve(
684
    {
751
    {
685
        branchcode     => $branch_1,
752
        branchcode     => $branch_1,
Lines 689-698 AddReserve( Link Here
689
    }
756
    }
690
);
757
);
691
MoveReserve( $item->itemnumber, $borrowernumber );
758
MoveReserve( $item->itemnumber, $borrowernumber );
692
($status)=CheckReserves( $item );
759
($status) = CheckReserves($item);
693
is( $status, '', 'MoveReserve filled hold');
760
is( $status, '', 'MoveReserve filled hold' );
761
694
#   hold from A waiting, today, no fut holds: MoveReserve should fill it
762
#   hold from A waiting, today, no fut holds: MoveReserve should fill it
695
my $other_item = $builder->build_sample_item({ biblionumber => $biblio->id });
763
my $other_item = $builder->build_sample_item( { biblionumber => $biblio->id } );
696
AddReserve(
764
AddReserve(
697
    {
765
    {
698
        branchcode     => $branch_1,
766
        branchcode     => $branch_1,
Lines 704-770 AddReserve( Link Here
704
    }
772
    }
705
);
773
);
706
MoveReserve( $item->itemnumber, $borrowernumber );
774
MoveReserve( $item->itemnumber, $borrowernumber );
707
($status)=CheckReserves( $item );
775
($status) = CheckReserves($item);
708
is( $status, '', 'MoveReserve filled waiting hold');
776
is( $status, '', 'MoveReserve filled waiting hold' );
777
709
#   hold from A pos 1, tomorrow, no fut holds: not filled
778
#   hold from A pos 1, tomorrow, no fut holds: not filled
710
$resdate= dt_from_string();
779
$resdate = dt_from_string();
711
$resdate->add_duration(DateTime::Duration->new(days => 1));
780
$resdate->add_duration( DateTime::Duration->new( days => 1 ) );
712
AddReserve(
781
AddReserve(
713
    {
782
    {
714
        branchcode     => $branch_1,
783
        branchcode       => $branch_1,
715
        borrowernumber => $borrowernumber,
784
        borrowernumber   => $borrowernumber,
716
        biblionumber   => $bibnum,
785
        biblionumber     => $bibnum,
717
        priority       => 1,
786
        priority         => 1,
718
        reservation_date => $resdate,
787
        reservation_date => $resdate,
719
    }
788
    }
720
);
789
);
721
MoveReserve( $item->itemnumber, $borrowernumber );
790
MoveReserve( $item->itemnumber, $borrowernumber );
722
($status)=CheckReserves( $item, 1 );
791
($status) = CheckReserves( $item, 1 );
723
is( $status, 'Reserved', 'MoveReserve did not fill future hold');
792
is( $status, 'Reserved', 'MoveReserve did not fill future hold' );
724
$dbh->do('DELETE FROM reserves', undef, ($bibnum));
793
$dbh->do( 'DELETE FROM reserves', undef, ($bibnum) );
794
725
#   hold from A pos 1, tomorrow, fut holds=2: MoveReserve should fill it
795
#   hold from A pos 1, tomorrow, fut holds=2: MoveReserve should fill it
726
t::lib::Mocks::mock_preference('ConfirmFutureHolds', 2);
796
t::lib::Mocks::mock_preference( 'ConfirmFutureHolds', 2 );
727
AddReserve(
797
AddReserve(
728
    {
798
    {
729
        branchcode     => $branch_1,
799
        branchcode       => $branch_1,
730
        borrowernumber => $borrowernumber,
800
        borrowernumber   => $borrowernumber,
731
        biblionumber   => $bibnum,
801
        biblionumber     => $bibnum,
732
        priority       => 1,
802
        priority         => 1,
733
        reservation_date => $resdate,
803
        reservation_date => $resdate,
734
    }
804
    }
735
);
805
);
736
MoveReserve( $item->itemnumber, $borrowernumber );
806
MoveReserve( $item->itemnumber, $borrowernumber );
737
($status)=CheckReserves( $item, undef, 2 );
807
($status) = CheckReserves( $item, undef, 2 );
738
is( $status, '', 'MoveReserve filled future hold now');
808
is( $status, '', 'MoveReserve filled future hold now' );
809
739
#   hold from A waiting, tomorrow, fut holds=2: MoveReserve should fill it
810
#   hold from A waiting, tomorrow, fut holds=2: MoveReserve should fill it
740
AddReserve(
811
AddReserve(
741
    {
812
    {
742
        branchcode     => $branch_1,
813
        branchcode       => $branch_1,
743
        borrowernumber => $borrowernumber,
814
        borrowernumber   => $borrowernumber,
744
        biblionumber   => $bibnum,
815
        biblionumber     => $bibnum,
745
        priority       => 1,
816
        priority         => 1,
746
        reservation_date => $resdate,
817
        reservation_date => $resdate,
747
    }
818
    }
748
);
819
);
749
MoveReserve( $item->itemnumber, $borrowernumber );
820
MoveReserve( $item->itemnumber, $borrowernumber );
750
($status)=CheckReserves( $item, undef, 2 );
821
($status) = CheckReserves( $item, undef, 2 );
751
is( $status, '', 'MoveReserve filled future waiting hold now');
822
is( $status, '', 'MoveReserve filled future waiting hold now' );
823
752
#   hold from A pos 1, today+3, fut holds=2: MoveReserve should not fill it
824
#   hold from A pos 1, today+3, fut holds=2: MoveReserve should not fill it
753
$resdate= dt_from_string();
825
$resdate = dt_from_string();
754
$resdate->add_duration(DateTime::Duration->new(days => 3));
826
$resdate->add_duration( DateTime::Duration->new( days => 3 ) );
755
AddReserve(
827
AddReserve(
756
    {
828
    {
757
        branchcode     => $branch_1,
829
        branchcode       => $branch_1,
758
        borrowernumber => $borrowernumber,
830
        borrowernumber   => $borrowernumber,
759
        biblionumber   => $bibnum,
831
        biblionumber     => $bibnum,
760
        priority       => 1,
832
        priority         => 1,
761
        reservation_date => $resdate,
833
        reservation_date => $resdate,
762
    }
834
    }
763
);
835
);
764
MoveReserve( $item->itemnumber, $borrowernumber );
836
MoveReserve( $item->itemnumber, $borrowernumber );
765
($status)=CheckReserves( $item, 3 );
837
($status) = CheckReserves( $item, 3 );
766
is( $status, 'Reserved', 'MoveReserve did not fill future hold of 3 days');
838
is( $status, 'Reserved', 'MoveReserve did not fill future hold of 3 days' );
767
$dbh->do('DELETE FROM reserves', undef, ($bibnum));
839
$dbh->do( 'DELETE FROM reserves', undef, ($bibnum) );
768
840
769
$cache->clear_from_cache("MarcStructure-0-$frameworkcode");
841
$cache->clear_from_cache("MarcStructure-0-$frameworkcode");
770
$cache->clear_from_cache("MarcStructure-1-$frameworkcode");
842
$cache->clear_from_cache("MarcStructure-1-$frameworkcode");
Lines 774-836 subtest '_koha_notify_reserve() tests' => sub { Link Here
774
846
775
    plan tests => 3;
847
    plan tests => 3;
776
848
777
    my $branch = $builder->build_object({
849
    my $branch = $builder->build_object(
778
        class => 'Koha::Libraries',
850
        {
779
        value => {
851
            class => 'Koha::Libraries',
780
            branchemail => 'branch@e.mail',
852
            value => {
781
            branchreplyto => 'branch@reply.to',
853
                branchemail     => 'branch@e.mail',
782
            pickup_location => 1
854
                branchreplyto   => 'branch@reply.to',
855
                pickup_location => 1
856
            }
783
        }
857
        }
784
    });
858
    );
785
    my $item = $builder->build_sample_item({
859
    my $item = $builder->build_sample_item(
786
        homebranch => $branch->branchcode,
860
        {
787
        holdingbranch => $branch->branchcode
861
            homebranch    => $branch->branchcode,
788
    });
862
            holdingbranch => $branch->branchcode
863
        }
864
    );
789
865
790
    my $wants_hold_and_email = {
866
    my $wants_hold_and_email = {
791
        wants_digest => '0',
867
        wants_digest => '0',
792
        transports => {
868
        transports   => {
793
            sms => 'HOLD',
869
            sms   => 'HOLD',
794
            email => 'HOLD',
870
            email => 'HOLD',
795
            },
871
        },
796
        letter_code => 'HOLD'
872
        letter_code => 'HOLD'
797
    };
873
    };
798
874
799
    my $mp = Test::MockModule->new( 'C4::Members::Messaging' );
875
    my $mp = Test::MockModule->new('C4::Members::Messaging');
800
876
801
    $mp->mock("GetMessagingPreferences",$wants_hold_and_email);
877
    $mp->mock( "GetMessagingPreferences", $wants_hold_and_email );
802
878
803
    $dbh->do('DELETE FROM letter');
879
    $dbh->do('DELETE FROM letter');
804
880
805
    my $email_hold_notice = $builder->build({
881
    my $email_hold_notice = $builder->build(
882
        {
806
            source => 'Letter',
883
            source => 'Letter',
807
            value => {
884
            value  => {
808
                message_transport_type => 'email',
885
                message_transport_type => 'email',
809
                branchcode => '',
886
                branchcode             => '',
810
                code => 'HOLD',
887
                code                   => 'HOLD',
811
                module => 'reserves',
888
                module                 => 'reserves',
812
                lang => 'default',
889
                lang                   => 'default',
813
            }
890
            }
814
        });
891
        }
892
    );
815
893
816
    my $sms_hold_notice = $builder->build({
894
    my $sms_hold_notice = $builder->build(
895
        {
817
            source => 'Letter',
896
            source => 'Letter',
818
            value => {
897
            value  => {
819
                message_transport_type => 'sms',
898
                message_transport_type => 'sms',
820
                branchcode => '',
899
                branchcode             => '',
821
                code => 'HOLD',
900
                code                   => 'HOLD',
822
                module => 'reserves',
901
                module                 => 'reserves',
823
                lang=>'default',
902
                lang                   => 'default',
824
            }
903
            }
825
        });
904
        }
905
    );
826
906
827
    my $hold_borrower = $builder->build({
907
    my $hold_borrower = $builder->build(
908
        {
828
            source => 'Borrower',
909
            source => 'Borrower',
829
            value => {
910
            value  => {
830
                smsalertnumber=>'5555555555',
911
                smsalertnumber => '5555555555',
831
                email=>'a@b.com',
912
                email          => 'a@b.com',
832
            }
913
            }
833
        })->{borrowernumber};
914
        }
915
    )->{borrowernumber};
834
916
835
    C4::Reserves::AddReserve(
917
    C4::Reserves::AddReserve(
836
        {
918
        {
Lines 840-881 subtest '_koha_notify_reserve() tests' => sub { Link Here
840
        }
922
        }
841
    );
923
    );
842
924
843
    ModReserveAffect($item->itemnumber, $hold_borrower, 0);
925
    ModReserveAffect( $item->itemnumber, $hold_borrower, 0 );
844
    my $sms_message_address = $schema->resultset('MessageQueue')->search({
926
    my $sms_message_address = $schema->resultset('MessageQueue')->search(
845
            letter_code     => 'HOLD',
927
        {
928
            letter_code            => 'HOLD',
846
            message_transport_type => 'sms',
929
            message_transport_type => 'sms',
847
            borrowernumber => $hold_borrower,
930
            borrowernumber         => $hold_borrower,
848
        })->next()->to_address();
931
        }
849
    is($sms_message_address, undef ,"We should not populate the sms message with the sms number, sending will do so");
932
    )->next()->to_address();
933
    is( $sms_message_address, undef, "We should not populate the sms message with the sms number, sending will do so" );
850
934
851
    my $email = $schema->resultset('MessageQueue')->search({
935
    my $email = $schema->resultset('MessageQueue')->search(
852
            letter_code     => 'HOLD',
936
        {
937
            letter_code            => 'HOLD',
853
            message_transport_type => 'email',
938
            message_transport_type => 'email',
854
            borrowernumber => $hold_borrower,
939
            borrowernumber         => $hold_borrower,
855
        })->next();
940
        }
941
    )->next();
856
    my $email_to_address = $email->to_address();
942
    my $email_to_address = $email->to_address();
857
    is($email_to_address, undef ,"We should not populate the hold message with the email address, sending will do so");
943
    is(
944
        $email_to_address, undef,
945
        "We should not populate the hold message with the email address, sending will do so"
946
    );
858
    my $email_from_address = $email->from_address();
947
    my $email_from_address = $email->from_address();
859
    is($email_from_address,'branch@e.mail',"Library's from address is used for sending");
948
    is( $email_from_address, 'branch@e.mail', "Library's from address is used for sending" );
860
949
861
};
950
};
862
951
863
subtest 'ReservesNeedReturns' => sub {
952
subtest 'ReservesNeedReturns' => sub {
864
    plan tests => 18;
953
    plan tests => 18;
865
954
866
    my $library    = $builder->build_object( { class => 'Koha::Libraries' } );
955
    my $library   = $builder->build_object( { class => 'Koha::Libraries' } );
867
    my $item_info  = {
956
    my $item_info = {
868
        homebranch       => $library->branchcode,
957
        homebranch    => $library->branchcode,
869
        holdingbranch    => $library->branchcode,
958
        holdingbranch => $library->branchcode,
870
    };
959
    };
871
    my $item = $builder->build_sample_item($item_info);
960
    my $item   = $builder->build_sample_item($item_info);
872
    my $patron   = $builder->build_object(
961
    my $patron = $builder->build_object(
873
        {
962
        {
874
            class => 'Koha::Patrons',
963
            class => 'Koha::Patrons',
875
            value => { branchcode => $library->branchcode, }
964
            value => { branchcode => $library->branchcode, }
876
        }
965
        }
877
    );
966
    );
878
    my $patron_2   = $builder->build_object(
967
    my $patron_2 = $builder->build_object(
879
        {
968
        {
880
            class => 'Koha::Patrons',
969
            class => 'Koha::Patrons',
881
            value => { branchcode => $library->branchcode, }
970
            value => { branchcode => $library->branchcode, }
Lines 884-899 subtest 'ReservesNeedReturns' => sub { Link Here
884
973
885
    my $priority = 1;
974
    my $priority = 1;
886
975
887
    t::lib::Mocks::mock_preference('ReservesNeedReturns', 1); # Test with feature disabled
976
    t::lib::Mocks::mock_preference( 'ReservesNeedReturns', 1 );    # Test with feature disabled
888
    my $hold = place_item_hold( $patron, $item, $library, $priority );
977
    my $hold = place_item_hold( $patron, $item, $library, $priority );
889
    is( $hold->priority, $priority, 'If ReservesNeedReturns is 1, priority must not have been set to changed' );
978
    is( $hold->priority, $priority, 'If ReservesNeedReturns is 1, priority must not have been set to changed' );
890
    is( $hold->found, undef, 'If ReservesNeedReturns is 1, found must not have been set waiting' );
979
    is( $hold->found,    undef,     'If ReservesNeedReturns is 1, found must not have been set waiting' );
891
    $hold->delete;
980
    $hold->delete;
892
981
893
    t::lib::Mocks::mock_preference('ReservesNeedReturns', 0); # '0' means 'Automatically mark a hold as found and waiting'
982
    t::lib::Mocks::mock_preference( 'ReservesNeedReturns', 0 )
983
        ;    # '0' means 'Automatically mark a hold as found and waiting'
894
    $hold = place_item_hold( $patron, $item, $library, $priority );
984
    $hold = place_item_hold( $patron, $item, $library, $priority );
895
    is( $hold->priority, 0, 'If ReservesNeedReturns is 0 and no other status, priority must have been set to 0' );
985
    is( $hold->priority, 0,   'If ReservesNeedReturns is 0 and no other status, priority must have been set to 0' );
896
    is( $hold->found, 'W', 'If ReservesNeedReturns is 0 and no other status, found must have been set waiting' );
986
    is( $hold->found,    'W', 'If ReservesNeedReturns is 0 and no other status, found must have been set waiting' );
897
    $hold->delete;
987
    $hold->delete;
898
988
899
    $item->onloan('2010-01-01')->store;
989
    $item->onloan('2010-01-01')->store;
Lines 901-963 subtest 'ReservesNeedReturns' => sub { Link Here
901
    is( $hold->priority, 1, 'If ReservesNeedReturns is 0 but item onloan priority must be set to 1' );
991
    is( $hold->priority, 1, 'If ReservesNeedReturns is 0 but item onloan priority must be set to 1' );
902
    $hold->delete;
992
    $hold->delete;
903
993
904
    t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 0); # '0' means damaged holds not allowed
994
    t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 0 );    # '0' means damaged holds not allowed
905
    $item->onloan(undef)->damaged(1)->store;
995
    $item->onloan(undef)->damaged(1)->store;
906
    $hold = place_item_hold( $patron, $item, $library, $priority );
996
    $hold = place_item_hold( $patron, $item, $library, $priority );
907
    is( $hold->priority, 1, 'If ReservesNeedReturns is 0 but item damaged and not allowed holds on damaged items priority must be set to 1' );
997
    is(
998
        $hold->priority, 1,
999
        'If ReservesNeedReturns is 0 but item damaged and not allowed holds on damaged items priority must be set to 1'
1000
    );
908
    $hold->delete;
1001
    $hold->delete;
909
    t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 1); # '0' means damaged holds not allowed
1002
    t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 1 );    # '0' means damaged holds not allowed
910
    $hold = place_item_hold( $patron, $item, $library, $priority );
1003
    $hold = place_item_hold( $patron, $item, $library, $priority );
911
    is( $hold->priority, 0, 'If ReservesNeedReturns is 0 and damaged holds allowed, priority must have been set to 0' );
1004
    is( $hold->priority, 0, 'If ReservesNeedReturns is 0 and damaged holds allowed, priority must have been set to 0' );
912
    is( $hold->found,  'W', 'If ReservesNeedReturns is 0 and damaged holds allowed, found must have been set waiting' );
1005
    is( $hold->found, 'W',  'If ReservesNeedReturns is 0 and damaged holds allowed, found must have been set waiting' );
913
    $hold->delete;
1006
    $hold->delete;
914
1007
915
    my $hold_1 = place_item_hold( $patron, $item, $library, $priority );
1008
    my $hold_1 = place_item_hold( $patron, $item, $library, $priority );
916
    is( $hold_1->found,  'W', 'First hold on item is set to waiting with ReservesNeedReturns set to 0' );
1009
    is( $hold_1->found,    'W', 'First hold on item is set to waiting with ReservesNeedReturns set to 0' );
917
    is( $hold_1->priority, 0, 'First hold on item is set to waiting with ReservesNeedReturns set to 0' );
1010
    is( $hold_1->priority, 0,   'First hold on item is set to waiting with ReservesNeedReturns set to 0' );
918
    $hold = place_item_hold( $patron_2, $item, $library, $priority );
1011
    $hold = place_item_hold( $patron_2, $item, $library, $priority );
919
    is( $hold->priority, 1, 'If ReservesNeedReturns is 0 but item already on hold priority must be set to 1' );
1012
    is( $hold->priority, 1, 'If ReservesNeedReturns is 0 but item already on hold priority must be set to 1' );
920
    $hold->delete;
1013
    $hold->delete;
921
    $hold_1->delete;
1014
    $hold_1->delete;
922
1015
923
    my $transfer = $builder->build_object({
1016
    my $transfer = $builder->build_object(
924
        class => "Koha::Item::Transfers",
1017
        {
925
        value => {
1018
            class => "Koha::Item::Transfers",
926
          itemnumber  => $item->itemnumber,
1019
            value => {
927
          datearrived => undef,
1020
                itemnumber    => $item->itemnumber,
928
          datecancelled => undef
1021
                datearrived   => undef,
1022
                datecancelled => undef
1023
            }
929
        }
1024
        }
930
    });
1025
    );
931
    $item->damaged(0)->store;
1026
    $item->damaged(0)->store;
932
    $hold = place_item_hold( $patron, $item, $library, $priority );
1027
    $hold = place_item_hold( $patron, $item, $library, $priority );
933
    is( $hold->found, undef, 'If ReservesNeedReturns is 0 but item in transit the hold must not be set to waiting' );
1028
    is( $hold->found,    undef, 'If ReservesNeedReturns is 0 but item in transit the hold must not be set to waiting' );
934
    is( $hold->priority, 1,  'If ReservesNeedReturns is 0 but item in transit the hold must not be set to waiting' );
1029
    is( $hold->priority, 1,     'If ReservesNeedReturns is 0 but item in transit the hold must not be set to waiting' );
935
    $hold->delete;
1030
    $hold->delete;
936
    $transfer->delete;
1031
    $transfer->delete;
937
1032
938
    $hold = place_item_hold( $patron, $item, $library, $priority );
1033
    $hold = place_item_hold( $patron, $item, $library, $priority );
939
    is( $hold->priority, 0, 'If ReservesNeedReturns is 0 and no other status, priority must have been set to 0' );
1034
    is( $hold->priority, 0,   'If ReservesNeedReturns is 0 and no other status, priority must have been set to 0' );
940
    is( $hold->found, 'W', 'If ReservesNeedReturns is 0 and no other status, found must have been set waiting' );
1035
    is( $hold->found,    'W', 'If ReservesNeedReturns is 0 and no other status, found must have been set waiting' );
941
    $hold_1 = place_item_hold( $patron, $item, $library, $priority );
1036
    $hold_1 = place_item_hold( $patron, $item, $library, $priority );
942
    is( $hold_1->priority, 1, 'If ReservesNeedReturns is 0 but item has a hold priority is 1' );
1037
    is( $hold_1->priority, 1, 'If ReservesNeedReturns is 0 but item has a hold priority is 1' );
943
    $hold_1->suspend(1)->store; # We suspend the hold
1038
    $hold_1->suspend(1)->store;    # We suspend the hold
944
    $hold->delete; # Delete the waiting hold
1039
    $hold->delete;                 # Delete the waiting hold
945
    $hold = place_item_hold( $patron, $item, $library, $priority );
1040
    $hold = place_item_hold( $patron, $item, $library, $priority );
946
    is( $hold->priority, 0, 'If ReservesNeedReturns is 0 and other hold(s) suspended, priority must have been set to 0' );
1041
    is(
947
    is( $hold->found, 'W', 'If ReservesNeedReturns is 0 and other  hold(s) suspended, found must have been set waiting' );
1042
        $hold->priority, 0,
948
1043
        'If ReservesNeedReturns is 0 and other hold(s) suspended, priority must have been set to 0'
949
1044
    );
950
1045
    is(
1046
        $hold->found, 'W',
1047
        'If ReservesNeedReturns is 0 and other  hold(s) suspended, found must have been set waiting'
1048
    );
951
1049
952
    t::lib::Mocks::mock_preference('ReservesNeedReturns', 1); # Don't affect other tests
1050
    t::lib::Mocks::mock_preference( 'ReservesNeedReturns', 1 );    # Don't affect other tests
953
};
1051
};
954
1052
955
subtest 'ChargeReserveFee tests' => sub {
1053
subtest 'ChargeReserveFee tests' => sub {
956
1054
957
    plan tests => 8;
1055
    plan tests => 8;
958
1056
959
    my $library = $builder->build_object({ class => 'Koha::Libraries' });
1057
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
960
    my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
1058
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
961
1059
962
    my $fee   = 20;
1060
    my $fee   = 20;
963
    my $title = 'A title';
1061
    my $title = 'A title';
Lines 967-987 subtest 'ChargeReserveFee tests' => sub { Link Here
967
1065
968
    my $line = C4::Reserves::ChargeReserveFee( $patron->id, $fee, $title );
1066
    my $line = C4::Reserves::ChargeReserveFee( $patron->id, $fee, $title );
969
1067
970
    is( ref($line), 'Koha::Account::Line' , 'Returns a Koha::Account::Line object');
1068
    is( ref($line), 'Koha::Account::Line', 'Returns a Koha::Account::Line object' );
971
    ok( $line->is_debit, 'Generates a debit line' );
1069
    ok( $line->is_debit, 'Generates a debit line' );
972
    is( $line->debit_type_code, 'RESERVE' , 'generates RESERVE debit_type');
1070
    is( $line->debit_type_code,   'RESERVE',    'generates RESERVE debit_type' );
973
    is( $line->borrowernumber, $patron->id , 'generated line belongs to the passed patron');
1071
    is( $line->borrowernumber,    $patron->id,  'generated line belongs to the passed patron' );
974
    is( $line->amount, $fee , 'amount set correctly');
1072
    is( $line->amount,            $fee,         'amount set correctly' );
975
    is( $line->amountoutstanding, $fee , 'amountoutstanding set correctly');
1073
    is( $line->amountoutstanding, $fee,         'amountoutstanding set correctly' );
976
    is( $line->description, "$title" , 'description is title of reserved item');
1074
    is( $line->description,       "$title",     'description is title of reserved item' );
977
    is( $line->branchcode, $library->id , "Library id is picked from userenv and stored correctly" );
1075
    is( $line->branchcode,        $library->id, "Library id is picked from userenv and stored correctly" );
978
};
1076
};
979
1077
980
subtest 'reserves.item_level_hold' => sub {
1078
subtest 'reserves.item_level_hold' => sub {
981
    plan tests => 4;
1079
    plan tests => 4;
982
1080
983
    my $item   = $builder->build_sample_item;
1081
    my $item   = $builder->build_sample_item;
984
    my $patron = $builder->build_object({ class => "Koha::Patrons" });
1082
    my $patron = $builder->build_object( { class => "Koha::Patrons" } );
985
    $patron->branchcode( $item->homebranch );
1083
    $patron->branchcode( $item->homebranch );
986
1084
987
    subtest 'item level hold' => sub {
1085
    subtest 'item level hold' => sub {
Lines 1003-1016 subtest 'reserves.item_level_hold' => sub { Link Here
1003
        ModReserveAffect( $item->itemnumber, $patron->id, 1 );
1101
        ModReserveAffect( $item->itemnumber, $patron->id, 1 );
1004
1102
1005
        my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
1103
        my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
1006
        $mock->mock( 'enqueue', sub {
1104
        $mock->mock(
1007
            my ( $self, $args ) = @_;
1105
            'enqueue',
1008
            is_deeply(
1106
            sub {
1009
                $args->{biblio_ids},
1107
                my ( $self, $args ) = @_;
1010
                [ $hold->biblionumber ],
1108
                is_deeply(
1011
                "AlterPriority triggers a holds queue update for the related biblio"
1109
                    $args->{biblio_ids},
1012
            );
1110
                    [ $hold->biblionumber ],
1013
        } );
1111
                    "AlterPriority triggers a holds queue update for the related biblio"
1112
                );
1113
            }
1114
        );
1014
1115
1015
        t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
1116
        t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
1016
        t::lib::Mocks::mock_preference( 'HoldsLog',           1 );
1117
        t::lib::Mocks::mock_preference( 'HoldsLog',           1 );
Lines 1060-1066 subtest 'reserves.item_level_hold' => sub { Link Here
1060
        );
1161
        );
1061
1162
1062
        my $hold = Koha::Holds->find($reserve_id);
1163
        my $hold = Koha::Holds->find($reserve_id);
1063
        is( $hold->item_level_hold, 0, 'item_level_hold should not be set when AddReserve is called without a specific item' );
1164
        is(
1165
            $hold->item_level_hold, 0,
1166
            'item_level_hold should not be set when AddReserve is called without a specific item'
1167
        );
1064
1168
1065
        # Mark it waiting
1169
        # Mark it waiting
1066
        ModReserveAffect( $item->itemnumber, $patron->borrowernumber, 1 );
1170
        ModReserveAffect( $item->itemnumber, $patron->borrowernumber, 1 );
Lines 1087-1107 subtest 'reserves.item_level_hold' => sub { Link Here
1087
                itemtype     => undef,
1191
                itemtype     => undef,
1088
                rules        => {
1192
                rules        => {
1089
                    reservesallowed => 25,
1193
                    reservesallowed => 25,
1090
                    opacitemholds => 'F',
1194
                    opacitemholds   => 'F',
1091
                }
1195
                }
1092
            }
1196
            }
1093
        );
1197
        );
1094
1198
1095
        my $canreserve = C4::Reserves::CanBookBeReserved( $patron->id, $item->biblionumber );
1199
        my $canreserve = C4::Reserves::CanBookBeReserved( $patron->id, $item->biblionumber );
1096
1200
1097
        is( $canreserve->{status},
1201
        is(
1202
            $canreserve->{status},
1098
            'recordHoldNotAllowed',
1203
            'recordHoldNotAllowed',
1099
            'record-level holds should not be possible with opacitemholds set to "Force"' );
1204
            'record-level holds should not be possible with opacitemholds set to "Force"'
1205
        );
1100
1206
1101
        $canreserve = C4::Reserves::CanItemBeReserved( $patron, $item );
1207
        $canreserve = C4::Reserves::CanItemBeReserved( $patron, $item );
1102
1208
1103
        is( $canreserve->{status}, 'OK',
1209
        is(
1104
            'item-level holds should be possible with opacitemholds set to "Force"' );
1210
            $canreserve->{status}, 'OK',
1211
            'item-level holds should be possible with opacitemholds set to "Force"'
1212
        );
1105
1213
1106
        Koha::CirculationRules->set_rules(
1214
        Koha::CirculationRules->set_rules(
1107
            {
1215
            {
Lines 1110-1129 subtest 'reserves.item_level_hold' => sub { Link Here
1110
                itemtype     => undef,
1218
                itemtype     => undef,
1111
                rules        => {
1219
                rules        => {
1112
                    reservesallowed => 25,
1220
                    reservesallowed => 25,
1113
                    opacitemholds => 'N',
1221
                    opacitemholds   => 'N',
1114
                }
1222
                }
1115
            }
1223
            }
1116
            );
1224
        );
1117
1225
1118
        $canreserve = C4::Reserves::CanBookBeReserved( $patron->id, $item->biblionumber );
1226
        $canreserve = C4::Reserves::CanBookBeReserved( $patron->id, $item->biblionumber );
1119
1227
1120
        is( $canreserve->{status}, 'OK',
1228
        is(
1121
            'record-level holds should be possible with opacitemholds set to "No"' );
1229
            $canreserve->{status}, 'OK',
1230
            'record-level holds should be possible with opacitemholds set to "No"'
1231
        );
1122
1232
1123
        $canreserve = C4::Reserves::CanItemBeReserved( $patron, $item );
1233
        $canreserve = C4::Reserves::CanItemBeReserved( $patron, $item );
1124
1234
1125
        is( $canreserve->{status}, 'recordHoldsOnly',
1235
        is(
1126
            'item-level holds should not be possible with opacitemholds set to "No"' );
1236
            $canreserve->{status}, 'recordHoldsOnly',
1237
            'item-level holds should not be possible with opacitemholds set to "No"'
1238
        );
1127
1239
1128
        Koha::CirculationRules->set_rules(
1240
        Koha::CirculationRules->set_rules(
1129
            {
1241
            {
Lines 1132-1151 subtest 'reserves.item_level_hold' => sub { Link Here
1132
                itemtype     => undef,
1244
                itemtype     => undef,
1133
                rules        => {
1245
                rules        => {
1134
                    reservesallowed => 25,
1246
                    reservesallowed => 25,
1135
                    opacitemholds => 'Y',
1247
                    opacitemholds   => 'Y',
1136
                }
1248
                }
1137
            }
1249
            }
1138
            );
1250
        );
1139
1251
1140
        $canreserve = C4::Reserves::CanBookBeReserved( $patron->id, $item->biblionumber );
1252
        $canreserve = C4::Reserves::CanBookBeReserved( $patron->id, $item->biblionumber );
1141
1253
1142
        is( $canreserve->{status}, 'OK',
1254
        is(
1143
            'record-level holds should be possible with opacitemholds set to "Yes"' );
1255
            $canreserve->{status}, 'OK',
1256
            'record-level holds should be possible with opacitemholds set to "Yes"'
1257
        );
1144
1258
1145
        $canreserve = C4::Reserves::CanItemBeReserved( $patron, $item );
1259
        $canreserve = C4::Reserves::CanItemBeReserved( $patron, $item );
1146
1260
1147
        is( $canreserve->{status}, 'OK',
1261
        is(
1148
            'item-level holds should be possible with opacitemholds set to "Yes"' );
1262
            $canreserve->{status}, 'OK',
1263
            'item-level holds should be possible with opacitemholds set to "Yes"'
1264
        );
1149
    };
1265
    };
1150
1266
1151
    subtest 'test opacitemholds rules in staff context' => sub {
1267
    subtest 'test opacitemholds rules in staff context' => sub {
Lines 1159-1188 subtest 'reserves.item_level_hold' => sub { Link Here
1159
                itemtype     => undef,
1275
                itemtype     => undef,
1160
                rules        => {
1276
                rules        => {
1161
                    reservesallowed => 25,
1277
                    reservesallowed => 25,
1162
                    opacitemholds => 'N',
1278
                    opacitemholds   => 'N',
1163
                }
1279
                }
1164
            }
1280
            }
1165
        );
1281
        );
1166
1282
1167
        my $canreserve = C4::Reserves::CanBookBeReserved( $patron->id, $item->biblionumber );
1283
        my $canreserve = C4::Reserves::CanBookBeReserved( $patron->id, $item->biblionumber );
1168
1284
1169
        is( $canreserve->{status}, 'OK',
1285
        is(
1170
            'record-level holds should be possible with opacitemholds set to "No"' );
1286
            $canreserve->{status}, 'OK',
1287
            'record-level holds should be possible with opacitemholds set to "No"'
1288
        );
1171
1289
1172
        $canreserve = C4::Reserves::CanItemBeReserved( $patron, $item );
1290
        $canreserve = C4::Reserves::CanItemBeReserved( $patron, $item );
1173
1291
1174
        is( $canreserve->{status}, 'OK',
1292
        is(
1175
            'item-level holds should still be possible in staff context, even with opacitemholds set to "No"' );
1293
            $canreserve->{status}, 'OK',
1294
            'item-level holds should still be possible in staff context, even with opacitemholds set to "No"'
1295
        );
1176
    };
1296
    };
1177
1297
1178
    Koha::CirculationRules->set_rules(
1298
    Koha::CirculationRules->set_rules(
1179
        {
1299
        {
1180
             branchcode   => undef,
1300
            branchcode   => undef,
1181
             categorycode => undef,
1301
            categorycode => undef,
1182
             itemtype     => undef,
1302
            itemtype     => undef,
1183
             rules        => {
1303
            rules        => {
1184
                 opacitemholds => undef,
1304
                opacitemholds => undef,
1185
             }
1305
            }
1186
        }
1306
        }
1187
    );
1307
    );
1188
};
1308
};
Lines 1193-1203 subtest 'MoveReserve additional test' => sub { Link Here
1193
1313
1194
    # Create the items and patrons we need
1314
    # Create the items and patrons we need
1195
    my $biblio = $builder->build_sample_biblio();
1315
    my $biblio = $builder->build_sample_biblio();
1196
    my $itype = $builder->build_object({ class => "Koha::ItemTypes", value => { notforloan => 0 } });
1316
    my $itype  = $builder->build_object( { class => "Koha::ItemTypes", value => { notforloan => 0 } } );
1197
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber,notforloan => 0, itype => $itype->itemtype });
1317
    my $item_1 = $builder->build_sample_item(
1198
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber, notforloan => 0, itype => $itype->itemtype });
1318
        { biblionumber => $biblio->biblionumber, notforloan => 0, itype => $itype->itemtype } );
1199
    my $patron_1 = $builder->build_object({ class => "Koha::Patrons" });
1319
    my $item_2 = $builder->build_sample_item(
1200
    my $patron_2 = $builder->build_object({ class => "Koha::Patrons" });
1320
        { biblionumber => $biblio->biblionumber, notforloan => 0, itype => $itype->itemtype } );
1321
    my $patron_1 = $builder->build_object( { class => "Koha::Patrons" } );
1322
    my $patron_2 = $builder->build_object( { class => "Koha::Patrons" } );
1201
1323
1202
    # Place a hold on the title for both patrons
1324
    # Place a hold on the title for both patrons
1203
    my $reserve_1 = AddReserve(
1325
    my $reserve_1 = AddReserve(
Lines 1218-1233 subtest 'MoveReserve additional test' => sub { Link Here
1218
            itemnumber     => $item_1->itemnumber,
1340
            itemnumber     => $item_1->itemnumber,
1219
        }
1341
        }
1220
    );
1342
    );
1221
    is($patron_1->holds->next()->reserve_id, $reserve_1, "The 1st patron has a hold");
1343
    is( $patron_1->holds->next()->reserve_id, $reserve_1, "The 1st patron has a hold" );
1222
    is($patron_2->holds->next()->reserve_id, $reserve_2, "The 2nd patron has a hold");
1344
    is( $patron_2->holds->next()->reserve_id, $reserve_2, "The 2nd patron has a hold" );
1223
1345
1224
    # Fake the holds queue
1346
    # Fake the holds queue
1225
    $dbh->do(q{INSERT INTO hold_fill_targets VALUES (?, ?, ?, ?, ?,?)},undef,($patron_1->borrowernumber,$biblio->biblionumber,$item_1->itemnumber,$item_1->homebranch,0,$reserve_1));
1347
    $dbh->do(
1348
        q{INSERT INTO hold_fill_targets VALUES (?, ?, ?, ?, ?,?)}, undef,
1349
        ( $patron_1->borrowernumber, $biblio->biblionumber, $item_1->itemnumber, $item_1->homebranch, 0, $reserve_1 )
1350
    );
1226
1351
1227
    # The 2nd hold should be filed even if the item is preselected for the first hold
1352
    # The 2nd hold should be filed even if the item is preselected for the first hold
1228
    MoveReserve($item_1->itemnumber,$patron_2->borrowernumber);
1353
    MoveReserve( $item_1->itemnumber, $patron_2->borrowernumber );
1229
    is($patron_2->holds->count, 0, "The 2nd patrons no longer has a hold");
1354
    is( $patron_2->holds->count, 0, "The 2nd patrons no longer has a hold" );
1230
    is($patron_2->old_holds->next()->reserve_id, $reserve_2, "The 2nd patrons hold was filled and moved to old holds");
1355
    is(
1356
        $patron_2->old_holds->next()->reserve_id, $reserve_2,
1357
        "The 2nd patrons hold was filled and moved to old holds"
1358
    );
1231
1359
1232
};
1360
};
1233
1361
Lines 1238-1246 subtest 'RevertWaitingStatus' => sub { Link Here
1238
    # Create the items and patrons we need
1366
    # Create the items and patrons we need
1239
    my $biblio  = $builder->build_sample_biblio();
1367
    my $biblio  = $builder->build_sample_biblio();
1240
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
1368
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
1241
    my $itype   = $builder->build_object(
1369
    my $itype   = $builder->build_object( { class => "Koha::ItemTypes", value => { notforloan => 0 } } );
1242
        { class => "Koha::ItemTypes", value => { notforloan => 0 } } );
1370
    my $item_1  = $builder->build_sample_item(
1243
    my $item_1 = $builder->build_sample_item(
1244
        {
1371
        {
1245
            biblionumber => $biblio->biblionumber,
1372
            biblionumber => $biblio->biblionumber,
1246
            itype        => $itype->itemtype,
1373
            itype        => $itype->itemtype,
Lines 1254-1263 subtest 'RevertWaitingStatus' => sub { Link Here
1254
1381
1255
    # Place a hold on the title for both patrons
1382
    # Place a hold on the title for both patrons
1256
    my $priority = 1;
1383
    my $priority = 1;
1257
    my $hold_1 = place_item_hold( $patron_1, $item_1, $library, $priority );
1384
    my $hold_1   = place_item_hold( $patron_1, $item_1, $library, $priority );
1258
    my $hold_2 = place_item_hold( $patron_2, $item_1, $library, $priority );
1385
    my $hold_2   = place_item_hold( $patron_2, $item_1, $library, $priority );
1259
    my $hold_3 = place_item_hold( $patron_3, $item_1, $library, $priority );
1386
    my $hold_3   = place_item_hold( $patron_3, $item_1, $library, $priority );
1260
    my $hold_4 = place_item_hold( $patron_4, $item_1, $library, $priority );
1387
    my $hold_4   = place_item_hold( $patron_4, $item_1, $library, $priority );
1261
1388
1262
    $hold_1->set_waiting;
1389
    $hold_1->set_waiting;
1263
    AddIssue( $patron_3, $item_1->barcode, undef, 'revert' );
1390
    AddIssue( $patron_3, $item_1->barcode, undef, 'revert' );
Lines 1278-1284 subtest 'CheckReserves additional tests' => sub { Link Here
1278
1405
1279
    plan tests => 8;
1406
    plan tests => 8;
1280
1407
1281
    my $item = $builder->build_sample_item;
1408
    my $item     = $builder->build_sample_item;
1282
    my $reserve1 = $builder->build_object(
1409
    my $reserve1 = $builder->build_object(
1283
        {
1410
        {
1284
            class => "Koha::Holds",
1411
            class => "Koha::Holds",
Lines 1340-1379 subtest 'CheckReserves additional tests' => sub { Link Here
1340
        }
1467
        }
1341
    );
1468
    );
1342
1469
1343
    ModReserveAffect( $item->itemnumber, $reserve1->borrowernumber, 1,
1470
    ModReserveAffect(
1344
        $reserve1->reserve_id );
1471
        $item->itemnumber, $reserve1->borrowernumber, 1,
1345
    my ( $status, $matched_reserve, $possible_reserves ) =
1472
        $reserve1->reserve_id
1346
      CheckReserves( $item );
1473
    );
1474
    my ( $status, $matched_reserve, $possible_reserves ) = CheckReserves($item);
1347
1475
1348
    is( $status, 'Transferred', "We found a reserve" );
1476
    is( $status, 'Transferred', "We found a reserve" );
1349
    is( $matched_reserve->{reserve_id},
1477
    is(
1350
        $reserve1->reserve_id, "We got the Transit reserve" );
1478
        $matched_reserve->{reserve_id},
1479
        $reserve1->reserve_id, "We got the Transit reserve"
1480
    );
1351
    is( scalar @$possible_reserves, 2, 'We do get both reserves' );
1481
    is( scalar @$possible_reserves, 2, 'We do get both reserves' );
1352
1482
1353
    my $patron_B = $builder->build_object({ class => "Koha::Patrons" });
1483
    my $patron_B = $builder->build_object( { class => "Koha::Patrons" } );
1354
    my $item_A = $builder->build_sample_item;
1484
    my $item_A   = $builder->build_sample_item;
1355
    my $item_B = $builder->build_sample_item({
1485
    my $item_B   = $builder->build_sample_item(
1356
        homebranch => $patron_B->branchcode,
1486
        {
1357
        biblionumber => $item_A->biblionumber,
1487
            homebranch   => $patron_B->branchcode,
1358
        itype => $item_A->itype
1488
            biblionumber => $item_A->biblionumber,
1359
    });
1489
            itype        => $item_A->itype
1490
        }
1491
    );
1360
    Koha::CirculationRules->set_rules(
1492
    Koha::CirculationRules->set_rules(
1361
        {
1493
        {
1362
            branchcode   => undef,
1494
            branchcode   => undef,
1363
            categorycode => undef,
1495
            categorycode => undef,
1364
            itemtype     => $item_A->itype,
1496
            itemtype     => $item_A->itype,
1365
            rules        => {
1497
            rules        => {
1366
                reservesallowed => 25,
1498
                reservesallowed  => 25,
1367
                holds_per_record => 1,
1499
                holds_per_record => 1,
1368
            }
1500
            }
1369
        }
1501
        }
1370
    );
1502
    );
1371
    Koha::CirculationRules->set_rule({
1503
    Koha::CirculationRules->set_rule(
1372
        branchcode => undef,
1504
        {
1373
        itemtype   => $item_A->itype,
1505
            branchcode => undef,
1374
        rule_name  => 'holdallowed',
1506
            itemtype   => $item_A->itype,
1375
        rule_value => 'from_home_library'
1507
            rule_name  => 'holdallowed',
1376
    });
1508
            rule_value => 'from_home_library'
1509
        }
1510
    );
1377
    my $reserve_id = AddReserve(
1511
    my $reserve_id = AddReserve(
1378
        {
1512
        {
1379
            branchcode     => $patron_B->branchcode,
1513
            branchcode     => $patron_B->branchcode,
Lines 1384-1415 subtest 'CheckReserves additional tests' => sub { Link Here
1384
        }
1518
        }
1385
    );
1519
    );
1386
1520
1387
    ok( $reserve_id, "We can place a record level hold because one item is owned by patron's home library");
1521
    ok( $reserve_id, "We can place a record level hold because one item is owned by patron's home library" );
1388
    t::lib::Mocks::mock_preference('ReservesControlBranch', 'ItemHomeLibrary');
1522
    t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'ItemHomeLibrary' );
1389
    ( $status, $matched_reserve, $possible_reserves ) = CheckReserves( $item_A );
1523
    ( $status, $matched_reserve, $possible_reserves ) = CheckReserves($item_A);
1390
    is( $status, "", "We do not fill the hold with item A because it is not from the patron's homebranch");
1524
    is( $status, "", "We do not fill the hold with item A because it is not from the patron's homebranch" );
1391
    Koha::CirculationRules->set_rule({
1525
    Koha::CirculationRules->set_rule(
1392
        branchcode => $item_A->homebranch,
1526
        {
1393
        itemtype   => $item_A->itype,
1527
            branchcode => $item_A->homebranch,
1394
        rule_name  => 'holdallowed',
1528
            itemtype   => $item_A->itype,
1395
        rule_value => 'from_any_library'
1529
            rule_name  => 'holdallowed',
1396
    });
1530
            rule_value => 'from_any_library'
1397
    ( $status, $matched_reserve, $possible_reserves ) = CheckReserves( $item_A );
1531
        }
1398
    is( $status, "Reserved", "We fill the hold with item A because item's branch rule says allow any");
1532
    );
1399
1533
    ( $status, $matched_reserve, $possible_reserves ) = CheckReserves($item_A);
1534
    is( $status, "Reserved", "We fill the hold with item A because item's branch rule says allow any" );
1400
1535
1401
    # Changing the control branch should change only the rule we get
1536
    # Changing the control branch should change only the rule we get
1402
    t::lib::Mocks::mock_preference('ReservesControlBranch', 'PatronLibrary');
1537
    t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
1403
    ( $status, $matched_reserve, $possible_reserves ) = CheckReserves( $item_A );
1538
    ( $status, $matched_reserve, $possible_reserves ) = CheckReserves($item_A);
1404
    is( $status, "", "We do not fill the hold with item A because it is not from the patron's homebranch");
1539
    is( $status, "", "We do not fill the hold with item A because it is not from the patron's homebranch" );
1405
    Koha::CirculationRules->set_rule({
1540
    Koha::CirculationRules->set_rule(
1406
        branchcode   => $patron_B->branchcode,
1541
        {
1407
        itemtype   => $item_A->itype,
1542
            branchcode => $patron_B->branchcode,
1408
        rule_name  => 'holdallowed',
1543
            itemtype   => $item_A->itype,
1409
        rule_value => 'from_any_library'
1544
            rule_name  => 'holdallowed',
1410
    });
1545
            rule_value => 'from_any_library'
1411
    ( $status, $matched_reserve, $possible_reserves ) = CheckReserves( $item_A );
1546
        }
1412
    is( $status, "Reserved", "We fill the hold with item A because patron's branch rule says allow any");
1547
    );
1548
    ( $status, $matched_reserve, $possible_reserves ) = CheckReserves($item_A);
1549
    is( $status, "Reserved", "We fill the hold with item A because patron's branch rule says allow any" );
1413
1550
1414
};
1551
};
1415
1552
Lines 1419-1464 subtest 'AllowHoldOnPatronPossession test' => sub { Link Here
1419
1556
1420
    # Create the items and patrons we need
1557
    # Create the items and patrons we need
1421
    my $biblio = $builder->build_sample_biblio();
1558
    my $biblio = $builder->build_sample_biblio();
1422
    my $itype = $builder->build_object({ class => "Koha::ItemTypes", value => { notforloan => 0 } });
1559
    my $itype  = $builder->build_object( { class => "Koha::ItemTypes", value => { notforloan => 0 } } );
1423
    my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber,notforloan => 0, itype => $itype->itemtype });
1560
    my $item   = $builder->build_sample_item(
1424
    my $patron = $builder->build_object({ class => "Koha::Patrons",
1561
        { biblionumber => $biblio->biblionumber, notforloan => 0, itype => $itype->itemtype } );
1425
                                          value => { branchcode => $item->homebranch }});
1562
    my $patron = $builder->build_object(
1426
1563
        {
1427
    C4::Circulation::AddIssue($patron,
1564
            class => "Koha::Patrons",
1428
                              $item->barcode);
1565
            value => { branchcode => $item->homebranch }
1429
    t::lib::Mocks::mock_preference('AllowHoldsOnPatronsPossessions', 0);
1566
        }
1430
1567
    );
1431
    is(C4::Reserves::CanBookBeReserved($patron->borrowernumber,
1568
1432
                                       $item->biblionumber)->{status},
1569
    C4::Circulation::AddIssue(
1433
       'alreadypossession',
1570
        $patron,
1434
       'Patron cannot place hold on a book loaned to itself');
1571
        $item->barcode
1435
1572
    );
1436
    is(C4::Reserves::CanItemBeReserved( $patron, $item )->{status},
1573
    t::lib::Mocks::mock_preference( 'AllowHoldsOnPatronsPossessions', 0 );
1437
       'alreadypossession',
1574
1438
       'Patron cannot place hold on an item loaned to itself');
1575
    is(
1439
1576
        C4::Reserves::CanBookBeReserved(
1440
    t::lib::Mocks::mock_preference('AllowHoldsOnPatronsPossessions', 1);
1577
            $patron->borrowernumber,
1441
1578
            $item->biblionumber
1442
    is(C4::Reserves::CanBookBeReserved($patron->borrowernumber,
1579
        )->{status},
1443
                                       $item->biblionumber)->{status},
1580
        'alreadypossession',
1444
       'OK',
1581
        'Patron cannot place hold on a book loaned to itself'
1445
       'Patron can place hold on a book loaned to itself');
1582
    );
1446
1583
1447
    is(C4::Reserves::CanItemBeReserved( $patron, $item )->{status},
1584
    is(
1448
       'OK',
1585
        C4::Reserves::CanItemBeReserved( $patron, $item )->{status},
1449
       'Patron can place hold on an item loaned to itself');
1586
        'alreadypossession',
1587
        'Patron cannot place hold on an item loaned to itself'
1588
    );
1589
1590
    t::lib::Mocks::mock_preference( 'AllowHoldsOnPatronsPossessions', 1 );
1591
1592
    is(
1593
        C4::Reserves::CanBookBeReserved(
1594
            $patron->borrowernumber,
1595
            $item->biblionumber
1596
        )->{status},
1597
        'OK',
1598
        'Patron can place hold on a book loaned to itself'
1599
    );
1600
1601
    is(
1602
        C4::Reserves::CanItemBeReserved( $patron, $item )->{status},
1603
        'OK',
1604
        'Patron can place hold on an item loaned to itself'
1605
    );
1450
};
1606
};
1451
1607
1452
subtest 'MergeHolds' => sub {
1608
subtest 'MergeHolds' => sub {
1453
1609
1454
    plan tests => 1;
1610
    plan tests => 1;
1455
1611
1456
    my $biblio_1  = $builder->build_sample_biblio();
1612
    my $biblio_1 = $builder->build_sample_biblio();
1457
    my $biblio_2  = $builder->build_sample_biblio();
1613
    my $biblio_2 = $builder->build_sample_biblio();
1458
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
1614
    my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
1459
    my $itype   = $builder->build_object(
1615
    my $itype    = $builder->build_object( { class => "Koha::ItemTypes", value => { notforloan => 0 } } );
1460
        { class => "Koha::ItemTypes", value => { notforloan => 0 } } );
1616
    my $item_1   = $builder->build_sample_item(
1461
    my $item_1 = $builder->build_sample_item(
1462
        {
1617
        {
1463
            biblionumber => $biblio_1->biblionumber,
1618
            biblionumber => $biblio_1->biblionumber,
1464
            itype        => $itype->itemtype,
1619
            itype        => $itype->itemtype,
Lines 1472-1478 subtest 'MergeHolds' => sub { Link Here
1472
    place_item_hold( $patron_1, $item_1, $library, $priority );
1627
    place_item_hold( $patron_1, $item_1, $library, $priority );
1473
1628
1474
    # Move and make sure hold is now on $biblio_2
1629
    # Move and make sure hold is now on $biblio_2
1475
    C4::Reserves::MergeHolds($dbh, $biblio_2->biblionumber, $biblio_1->biblionumber);
1630
    C4::Reserves::MergeHolds( $dbh, $biblio_2->biblionumber, $biblio_1->biblionumber );
1476
    is( $biblio_2->holds->count, 1, 'Hold has been transferred' );
1631
    is( $biblio_2->holds->count, 1, 'Hold has been transferred' );
1477
};
1632
};
1478
1633
Lines 1480-1486 subtest 'ModReserveAffect logging' => sub { Link Here
1480
1635
1481
    plan tests => 4;
1636
    plan tests => 4;
1482
1637
1483
    my $item = $builder->build_sample_item;
1638
    my $item   = $builder->build_sample_item;
1484
    my $patron = $builder->build_object(
1639
    my $patron = $builder->build_object(
1485
        {
1640
        {
1486
            class => "Koha::Patrons",
1641
            class => "Koha::Patrons",
Lines 1488-1495 subtest 'ModReserveAffect logging' => sub { Link Here
1488
        }
1643
        }
1489
    );
1644
    );
1490
1645
1491
    t::lib::Mocks::mock_userenv({ patron => $patron });
1646
    t::lib::Mocks::mock_userenv( { patron => $patron } );
1492
    t::lib::Mocks::mock_preference('HoldsLog', 1);
1647
    t::lib::Mocks::mock_preference( 'HoldsLog', 1 );
1493
1648
1494
    my $reserve_id = AddReserve(
1649
    my $reserve_id = AddReserve(
1495
        {
1650
        {
Lines 1501-1507 subtest 'ModReserveAffect logging' => sub { Link Here
1501
        }
1656
        }
1502
    );
1657
    );
1503
1658
1504
    my $hold = Koha::Holds->find($reserve_id);
1659
    my $hold               = Koha::Holds->find($reserve_id);
1505
    my $previous_timestamp = '1970-01-01 12:34:56';
1660
    my $previous_timestamp = '1970-01-01 12:34:56';
1506
    $hold->timestamp($previous_timestamp)->store;
1661
    $hold->timestamp($previous_timestamp)->store;
1507
1662
Lines 1519-1541 subtest 'ModReserveAffect logging' => sub { Link Here
1519
    ok( $hold->is_waiting, 'Hold has been set waiting' );
1674
    ok( $hold->is_waiting, 'Hold has been set waiting' );
1520
    isnt( $hold->timestamp, $previous_timestamp, 'The timestamp has been modified' );
1675
    isnt( $hold->timestamp, $previous_timestamp, 'The timestamp has been modified' );
1521
1676
1522
    my $log = Koha::ActionLogs->search({ module => 'HOLDS', action => 'MODIFY', object => $hold->reserve_id })->next;
1677
    my $log = Koha::ActionLogs->search( { module => 'HOLDS', action => 'MODIFY', object => $hold->reserve_id } )->next;
1523
    my $expected = sprintf q{'timestamp' => '%s'}, $hold->timestamp;
1678
    my $expected = sprintf q{'timestamp' => '%s'}, $hold->timestamp;
1524
    like( $log->info, qr{$expected}, 'Timestamp logged is the current one' );
1679
    like( $log->info, qr{$expected}, 'Timestamp logged is the current one' );
1525
};
1680
};
1526
1681
1527
sub count_hold_print_messages {
1682
sub count_hold_print_messages {
1528
    my $message_count = $dbh->selectall_arrayref(q{
1683
    my $message_count = $dbh->selectall_arrayref(
1684
        q{
1529
        SELECT COUNT(*)
1685
        SELECT COUNT(*)
1530
        FROM message_queue
1686
        FROM message_queue
1531
        WHERE letter_code = 'HOLD' 
1687
        WHERE letter_code = 'HOLD' 
1532
        AND   message_transport_type = 'print'
1688
        AND   message_transport_type = 'print'
1533
    });
1689
    }
1690
    );
1534
    return $message_count->[0]->[0];
1691
    return $message_count->[0]->[0];
1535
}
1692
}
1536
1693
1537
sub place_item_hold {
1694
sub place_item_hold {
1538
    my ($patron,$item,$library,$priority) = @_;
1695
    my ( $patron, $item, $library, $priority ) = @_;
1539
1696
1540
    my $hold_id = C4::Reserves::AddReserve(
1697
    my $hold_id = C4::Reserves::AddReserve(
1541
        {
1698
        {
Lines 1620-1633 subtest 'AddReserve() tests' => sub { Link Here
1620
    my $biblio  = $builder->build_sample_biblio;
1777
    my $biblio  = $builder->build_sample_biblio;
1621
1778
1622
    my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
1779
    my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
1623
    $mock->mock( 'enqueue', sub {
1780
    $mock->mock(
1624
        my ( $self, $args ) = @_;
1781
        'enqueue',
1625
        is_deeply(
1782
        sub {
1626
            $args->{biblio_ids},
1783
            my ( $self, $args ) = @_;
1627
            [ $biblio->id ],
1784
            is_deeply(
1628
            "AddReserve triggers a holds queue update for the related biblio"
1785
                $args->{biblio_ids},
1629
        );
1786
                [ $biblio->id ],
1630
    } );
1787
                "AddReserve triggers a holds queue update for the related biblio"
1788
            );
1789
        }
1790
    );
1631
1791
1632
    t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
1792
    t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
1633
1793
Lines 1661-1671 subtest 'AlterPriorty() tests' => sub { Link Here
1661
1821
1662
    $schema->storage->txn_begin;
1822
    $schema->storage->txn_begin;
1663
1823
1664
    my $library = $builder->build_object({ class => 'Koha::Libraries' });
1824
    my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
1665
    my $patron_1  = $builder->build_object({ class => 'Koha::Patrons' });
1825
    my $patron_1 = $builder->build_object( { class => 'Koha::Patrons' } );
1666
    my $patron_2  = $builder->build_object({ class => 'Koha::Patrons' });
1826
    my $patron_2 = $builder->build_object( { class => 'Koha::Patrons' } );
1667
    my $patron_3  = $builder->build_object({ class => 'Koha::Patrons' });
1827
    my $patron_3 = $builder->build_object( { class => 'Koha::Patrons' } );
1668
    my $biblio  = $builder->build_sample_biblio;
1828
    my $biblio   = $builder->build_sample_biblio;
1669
1829
1670
    my $reserve_id = AddReserve(
1830
    my $reserve_id = AddReserve(
1671
        {
1831
        {
Lines 1690-1703 subtest 'AlterPriorty() tests' => sub { Link Here
1690
    );
1850
    );
1691
1851
1692
    my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
1852
    my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
1693
    $mock->mock( 'enqueue', sub {
1853
    $mock->mock(
1694
        my ( $self, $args ) = @_;
1854
        'enqueue',
1695
        is_deeply(
1855
        sub {
1696
            $args->{biblio_ids},
1856
            my ( $self, $args ) = @_;
1697
            [ $biblio->id ],
1857
            is_deeply(
1698
            "AlterPriority triggers a holds queue update for the related biblio"
1858
                $args->{biblio_ids},
1699
        );
1859
                [ $biblio->id ],
1700
    } );
1860
                "AlterPriority triggers a holds queue update for the related biblio"
1861
            );
1862
        }
1863
    );
1701
1864
1702
    t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
1865
    t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
1703
1866
Lines 1705-1711 subtest 'AlterPriorty() tests' => sub { Link Here
1705
1868
1706
    my $hold = Koha::Holds->find($reserve_id);
1869
    my $hold = Koha::Holds->find($reserve_id);
1707
1870
1708
    is($hold->priority,3,'Successfully altered priority to bottom');
1871
    is( $hold->priority, 3, 'Successfully altered priority to bottom' );
1709
1872
1710
    t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
1873
    t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
1711
1874
Lines 1720-1735 subtest 'CanBookBeReserved() tests' => sub { Link Here
1720
1883
1721
    $schema->storage->txn_begin;
1884
    $schema->storage->txn_begin;
1722
1885
1723
    my $library = $builder->build_object(
1886
    my $library = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1 } } );
1724
        { class => 'Koha::Libraries', value => { pickup_location => 1 } } );
1887
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
1725
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1888
    my $itype   = $builder->build_object( { class => 'Koha::ItemTypes' } );
1726
    my $itype  = $builder->build_object( { class => 'Koha::ItemTypes' } );
1727
1889
1728
    my $biblio = $builder->build_sample_biblio();
1890
    my $biblio = $builder->build_sample_biblio();
1729
    my $item_1 = $builder->build_sample_item(
1891
    my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->id, itype => $itype->id } );
1730
        { biblionumber => $biblio->id, itype => $itype->id } );
1892
    my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->id, itype => $itype->id } );
1731
    my $item_2 = $builder->build_sample_item(
1732
        { biblionumber => $biblio->id, itype => $itype->id } );
1733
1893
1734
    Koha::CirculationRules->delete;
1894
    Koha::CirculationRules->delete;
1735
    Koha::CirculationRules->set_rules(
1895
    Koha::CirculationRules->set_rules(
Lines 1765-1774 subtest 'CanBookBeReserved() tests' => sub { Link Here
1765
1925
1766
    ## Limit on item type is 2, only one hold, success tests
1926
    ## Limit on item type is 2, only one hold, success tests
1767
1927
1768
    my $res = CanBookBeReserved( $patron->id, $biblio->id, $library->id,
1928
    my $res = CanBookBeReserved(
1769
        { itemtype => $itype->id } );
1929
        $patron->id, $biblio->id, $library->id,
1770
    is_deeply( $res, { status => 'OK' },
1930
        { itemtype => $itype->id }
1771
        'Holds on itemtype limit not reached' );
1931
    );
1932
    is_deeply(
1933
        $res, { status => 'OK' },
1934
        'Holds on itemtype limit not reached'
1935
    );
1772
1936
1773
    # Add a second hold, biblio-level and item type-constrained
1937
    # Add a second hold, biblio-level and item type-constrained
1774
    C4::Reserves::AddReserve(
1938
    C4::Reserves::AddReserve(
Lines 1783-1790 subtest 'CanBookBeReserved() tests' => sub { Link Here
1783
1947
1784
    ## Limit on item type is 2, two holds, one of them biblio-level/item type-constrained
1948
    ## Limit on item type is 2, two holds, one of them biblio-level/item type-constrained
1785
1949
1786
    $res = CanBookBeReserved( $patron->id, $biblio->id, $library->id,
1950
    $res = CanBookBeReserved(
1787
        { itemtype => $itype->id } );
1951
        $patron->id, $biblio->id, $library->id,
1952
        { itemtype => $itype->id }
1953
    );
1788
    is_deeply( $res, { status => '' }, 'Holds on itemtype limit reached' );
1954
    is_deeply( $res, { status => '' }, 'Holds on itemtype limit reached' );
1789
1955
1790
    $schema->storage->txn_rollback;
1956
    $schema->storage->txn_rollback;
Lines 1801-1812 subtest 'CanItemBeReserved() tests' => sub { Link Here
1801
    my $itype   = $builder->build_object( { class => 'Koha::ItemTypes' } );
1967
    my $itype   = $builder->build_object( { class => 'Koha::ItemTypes' } );
1802
1968
1803
    my $biblio = $builder->build_sample_biblio();
1969
    my $biblio = $builder->build_sample_biblio();
1804
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->id, itype => $itype->id });
1970
    my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->id, itype => $itype->id } );
1805
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->id, itype => $itype->id });
1971
    my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->id, itype => $itype->id } );
1806
1972
1807
    Koha::CirculationRules->delete;
1973
    Koha::CirculationRules->delete;
1808
    Koha::CirculationRules->set_rules(
1974
    Koha::CirculationRules->set_rules(
1809
        {   branchcode   => undef,
1975
        {
1976
            branchcode   => undef,
1810
            categorycode => undef,
1977
            categorycode => undef,
1811
            itemtype     => undef,
1978
            itemtype     => undef,
1812
            rules        => {
1979
            rules        => {
Lines 1815-1821 subtest 'CanItemBeReserved() tests' => sub { Link Here
1815
        }
1982
        }
1816
    );
1983
    );
1817
    Koha::CirculationRules->set_rules(
1984
    Koha::CirculationRules->set_rules(
1818
        {   branchcode   => undef,
1985
        {
1986
            branchcode   => undef,
1819
            categorycode => undef,
1987
            categorycode => undef,
1820
            itemtype     => $itype->id,
1988
            itemtype     => $itype->id,
1821
            rules        => {
1989
            rules        => {
Lines 1862-1885 subtest 'DefaultHoldExpiration tests' => sub { Link Here
1862
    plan tests => 2;
2030
    plan tests => 2;
1863
    $schema->storage->txn_begin;
2031
    $schema->storage->txn_begin;
1864
2032
1865
    t::lib::Mocks::mock_preference( 'DefaultHoldExpirationdate', 1 );
2033
    t::lib::Mocks::mock_preference( 'DefaultHoldExpirationdate',       1 );
1866
    t::lib::Mocks::mock_preference( 'DefaultHoldExpirationdatePeriod', 365 );
2034
    t::lib::Mocks::mock_preference( 'DefaultHoldExpirationdatePeriod', 365 );
1867
    t::lib::Mocks::mock_preference( 'DefaultHoldExpirationUnitOfTime', 'days;' );
2035
    t::lib::Mocks::mock_preference( 'DefaultHoldExpirationUnitOfTime', 'days;' );
1868
2036
1869
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
2037
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1870
    my $item    = $builder->build_sample_item();
2038
    my $item   = $builder->build_sample_item();
1871
2039
1872
    my $reserve_id = AddReserve({
2040
    my $reserve_id = AddReserve(
1873
        branchcode     => $item->homebranch,
2041
        {
1874
        borrowernumber => $patron->id,
2042
            branchcode     => $item->homebranch,
1875
        biblionumber   => $item->biblionumber,
2043
            borrowernumber => $patron->id,
1876
    });
2044
            biblionumber   => $item->biblionumber,
2045
        }
2046
    );
1877
2047
1878
    my $today = dt_from_string();
2048
    my $today = dt_from_string();
1879
    my $hold = Koha::Holds->find( $reserve_id );
2049
    my $hold  = Koha::Holds->find($reserve_id);
1880
2050
1881
    is( $hold->reservedate, $today->ymd, "Hold created today" );
2051
    is( $hold->reservedate,    $today->ymd,                     "Hold created today" );
1882
    is( $hold->expirationdate, $today->add( days => 365)->ymd, "Reserve date set 1 year from today" );
2052
    is( $hold->expirationdate, $today->add( days => 365 )->ymd, "Reserve date set 1 year from today" );
1883
2053
1884
    $schema->txn_rollback;
2054
    $schema->txn_rollback;
1885
};
2055
};
Lines 1912-1919 subtest '_Findgroupreserves' => sub { Link Here
1912
    C4::HoldsQueue::AddToHoldTargetMap(
2082
    C4::HoldsQueue::AddToHoldTargetMap(
1913
        {
2083
        {
1914
            $item->id => {
2084
            $item->id => {
1915
                borrowernumber => $patron_1->id,        biblionumber => $item->biblionumber,
2085
                borrowernumber => $patron_1->id, biblionumber => $item->biblionumber,
1916
                holdingbranch  => $item->holdingbranch, item_level   => 0, reserve_id => $reserve_id_1
2086
                holdingbranch  => $item->holdingbranch, item_level => 0, reserve_id => $reserve_id_1
1917
            }
2087
            }
1918
        }
2088
        }
1919
    );
2089
    );
Lines 1951-1958 subtest '_Findgroupreserves' => sub { Link Here
1951
    is( scalar @reserves,           1,             "We should still only get the item level hold that is in the map" );
2121
    is( scalar @reserves,           1,             "We should still only get the item level hold that is in the map" );
1952
    is( $reserves[0]->{reserve_id}, $reserve_id_1, "We got the expected reserve which has been updated" );
2122
    is( $reserves[0]->{reserve_id}, $reserve_id_1, "We got the expected reserve which has been updated" );
1953
2123
1954
1955
1956
    $schema->txn_rollback;
2124
    $schema->txn_rollback;
1957
};
2125
};
1958
2126
1959
- 

Return to bug 25408