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

(-)a/Koha/Account.pm (-89 / +13 lines)
Lines 95-104 sub pay { Link Here
95
        && !defined($cash_register) );
95
        && !defined($cash_register) );
96
96
97
    my @fines_paid; # List of account lines paid on with this payment
97
    my @fines_paid; # List of account lines paid on with this payment
98
    # Item numbers that have had a fine paid where the line has a accounttype
98
99
    # of OVERDUE and a status of UNRETURNED. We might want to try and renew
99
    # The outcome of any attempted item renewals as a result of fines being
100
    # these items.
100
    # paid off
101
    my $overdue_unreturned = {};
101
    my $renew_outcomes = [];
102
102
103
    my $balance_remaining = $amount; # Set it now so we can adjust the amount if necessary
103
    my $balance_remaining = $amount; # Set it now so we can adjust the amount if necessary
104
    $balance_remaining ||= 0;
104
    $balance_remaining ||= 0;
Lines 117-126 sub pay { Link Here
117
        $fine->amountoutstanding($new_amountoutstanding)->store();
117
        $fine->amountoutstanding($new_amountoutstanding)->store();
118
        $balance_remaining = $balance_remaining - $amount_to_pay;
118
        $balance_remaining = $balance_remaining - $amount_to_pay;
119
119
120
        # If we need to make a note of the item associated with this line,
120
        # Attempt to renew the item associated with this debit if
121
        # in order that we can potentially renew it, do so.
121
        # appropriate
122
        if (_fine_paid_renewable($new_amountoutstanding, $fine)) {
122
        if ($fine->renewable) {
123
            $overdue_unreturned->{$fine->itemnumber} = $fine;
123
            my $outcome = $fine->renew_item;
124
            push @{$renew_outcomes}, $outcome;
124
        }
125
        }
125
126
126
        # Same logic exists in Koha::Account::Line::apply
127
        # Same logic exists in Koha::Account::Line::apply
Lines 186-193 sub pay { Link Here
186
        # If we need to make a note of the item associated with this line,
187
        # If we need to make a note of the item associated with this line,
187
        # in order that we can potentially renew it, do so.
188
        # in order that we can potentially renew it, do so.
188
        my $amt = $old_amountoutstanding - $amount_to_pay;
189
        my $amt = $old_amountoutstanding - $amount_to_pay;
189
        if (_fine_paid_renewable($amt, $fine)) {
190
        if ($fine->renewable) {
190
            $overdue_unreturned->{$fine->itemnumber} = $fine;
191
            my $outcome = $fine->renew_item;
192
            push @{$renew_outcomes}, $outcome;
191
        }
193
        }
192
194
193
        if (   $fine->amountoutstanding == 0
195
        if (   $fine->amountoutstanding == 0
Lines 270-280 sub pay { Link Here
270
        }
272
        }
271
    );
273
    );
272
274
273
    # If we have overdue unreturned items that have had payments made
274
    # against them, check whether the balance on those items is now zero
275
    # and, if the syspref is set, renew them
276
    my $renew_result = _maybe_renew($overdue_unreturned, $self->{patron_id});
277
278
    if ( C4::Context->preference("FinesLog") ) {
275
    if ( C4::Context->preference("FinesLog") ) {
279
        logaction(
276
        logaction(
280
            "FINES", 'CREATE',
277
            "FINES", 'CREATE',
Lines 322-328 sub pay { Link Here
322
        }
319
        }
323
    }
320
    }
324
321
325
    return { payment_id => $payment->id, renew_result => $renew_result };
322
    return { payment_id => $payment->id, renew_result => $renew_outcomes };
326
}
323
}
327
324
328
=head3 add_credit
325
=head3 add_credit
Lines 724-802 sub reconcile_balance { Link Here
724
    return $self;
721
    return $self;
725
}
722
}
726
723
727
=head3 _fine_paid_renewable
728
729
my $bool = _fine_paid_renewable($amt, $fine);
730
731
Given an outstanding amount and a fine object, determine if this item
732
is potentially renewable as a result of the fine being paid off
733
734
=cut
735
736
sub _fine_paid_renewable {
737
    my ($amt, $fine) = @_;
738
739
    return (
740
        $amt == 0 &&
741
        $fine->accounttype &&
742
        $fine->accounttype eq 'OVERDUE' &&
743
        $fine->status &&
744
        $fine->status eq 'UNRETURNED'
745
    ) ? 1 : 0;
746
}
747
748
=head3 _maybe_renew
749
750
my $result = _maybe_renew($overdue_unreturned, $patron_id, $library_id);
751
752
If we have overdue unreturned items that have had payments made
753
against them, check whether the balance on those items is now zero
754
and, if the syspref is set, renew them
755
756
=cut
757
758
sub _maybe_renew {
759
    my ($items, $patron_id) = @_;
760
761
    my @results = ();
762
763
    if (
764
        C4::Context->preference('RenewAccruingItemWhenPaid') &&
765
        keys %{$items}
766
    ) {
767
        foreach my $itemnumber (keys %{$items}) {
768
            # Only do something if this item has no fines left on it
769
            my $fine = C4::Overdues::GetFine( $itemnumber, $patron_id );
770
            next if $fine && $fine > 0;
771
772
            my ( $can_renew, $error ) =
773
                C4::Circulation::CanBookBeRenewed($patron_id, $itemnumber);
774
            if ( $can_renew ) {
775
                my $due_date = C4::Circulation::AddRenewal(
776
                    $patron_id,
777
                    $itemnumber,
778
                    $items->{$itemnumber}->{branchcode},
779
                    undef,
780
                    undef,
781
                    1
782
                );
783
                push @results, {
784
                    itemnumber => $itemnumber,
785
                    due_date   => $due_date,
786
                    success    => 1
787
                };
788
            } else {
789
                push @results, {
790
                    itemnumber => $itemnumber,
791
                    error      => $error,
792
                    success    => 0
793
                };
794
            }
795
        }
796
    }
797
    return \@results;
798
}
799
800
1;
724
1;
801
725
802
=head2 Name mappings
726
=head2 Name mappings
(-)a/Koha/Account/Line.pm (-22 / +81 lines)
Lines 23-29 use Data::Dumper; Link Here
23
use C4::Log qw(logaction);
23
use C4::Log qw(logaction);
24
use C4::Overdues qw(GetFine);
24
use C4::Overdues qw(GetFine);
25
25
26
use Koha::Account qw( _fine_paid_renewable _maybe_renew );
27
use Koha::Account::Offsets;
26
use Koha::Account::Offsets;
28
use Koha::Database;
27
use Koha::Database;
29
use Koha::Exceptions::Account;
28
use Koha::Exceptions::Account;
Lines 205-215 sub apply { Link Here
205
204
206
    my $schema = Koha::Database->new->schema;
205
    my $schema = Koha::Database->new->schema;
207
206
208
    # Item numbers that have had a fine paid where the line has a accounttype
209
    # of OVERDUE and a status of UNRETURNED. We might want to try and renew
210
    # these items.
211
    my $overdue_unreturned = {};
212
213
    $schema->txn_do( sub {
207
    $schema->txn_do( sub {
214
        for my $debit ( @{$debits} ) {
208
        for my $debit ( @{$debits} ) {
215
209
Lines 242-255 sub apply { Link Here
242
            $self->amountoutstanding( $available_credit * -1 )->store;
236
            $self->amountoutstanding( $available_credit * -1 )->store;
243
            $debit->amountoutstanding( $owed - $amount_to_cancel )->store;
237
            $debit->amountoutstanding( $owed - $amount_to_cancel )->store;
244
238
245
            # If we need to make a note of the item associated with this line,
239
            # Attempt to renew the item associated with this debit if
246
            # in order that we can potentially renew it, do so.
240
            # appropriate
247
            my $renewable = Koha::Account::_fine_paid_renewable(
241
            if ($debit->renewable) {
248
                $debit->amountoutstanding,
242
                $debit->renew_item;
249
                $debit
250
            );
251
            if ($renewable && $debit->itemnumber) {
252
                $overdue_unreturned->{$debit->itemnumber} = $debit;
253
            }
243
            }
254
244
255
            # Same logic exists in Koha::Account::pay
245
            # Same logic exists in Koha::Account::pay
Lines 264-277 sub apply { Link Here
264
        }
254
        }
265
    });
255
    });
266
256
267
    # If we have overdue unreturned items that have had payments made
268
    # against them, check whether the balance on those items is now zero
269
    # and, if the syspref is set, renew them
270
    Koha::Account::_maybe_renew(
271
        $overdue_unreturned,
272
        $self->borrowernumber
273
    );
274
275
    return $available_credit;
257
    return $available_credit;
276
}
258
}
277
259
Lines 423-428 sub is_debit { Link Here
423
    return !$self->is_credit;
405
    return !$self->is_credit;
424
}
406
}
425
407
408
=head3 renewable
409
410
    my $bool = $line->renewable;
411
412
=cut
413
414
sub renewable {
415
    my ($self) = @_;
416
417
    return (
418
        $self->amountoutstanding == 0 &&
419
        $self->accounttype &&
420
        $self->accounttype eq 'OVERDUE' &&
421
        $self->status &&
422
        $self->status eq 'UNRETURNED'
423
    ) ? 1 : 0;
424
}
425
426
=head3 renew_item
427
428
    my $renew_result = $line->renew_item;
429
430
Conditionally attempt to renew an item and return the outcome. This is
431
as a consequence of the fine on an item being fully paid off
432
433
=cut
434
435
sub renew_item {
436
    my ($self) = @_;
437
438
    my $outcome = {};
439
440
    if (
441
        C4::Context->preference('RenewAccruingItemWhenPaid') &&
442
        $self->item &&
443
        $self->patron
444
    ) {
445
        my $itemnumber = $self->item->itemnumber;
446
        my $borrowernumber = $self->patron->borrowernumber;
447
        # Only do something if this item has no fines left on it
448
        my $fine = C4::Overdues::GetFine($itemnumber, $borrowernumber);
449
        if ($fine && $fine > 0) {
450
            return {
451
                itemnumber => $itemnumber,
452
                error      => 'has_fine',
453
                success    => 0
454
            };
455
        }
456
        my ( $can_renew, $error ) = C4::Circulation::CanBookBeRenewed(
457
            $borrowernumber,
458
            $itemnumber
459
        );
460
        if ( $can_renew ) {
461
            my $due_date = C4::Circulation::AddRenewal(
462
                $borrowernumber,
463
                $itemnumber,
464
                $self->{branchcode},
465
                undef,
466
                undef,
467
                1
468
            );
469
            return {
470
                itemnumber => $itemnumber,
471
                due_date   => $due_date,
472
                success    => 1
473
            };
474
        } else {
475
            return {
476
                itemnumber => $itemnumber,
477
                error      => $error,
478
                success    => 0
479
            };
480
        }
481
    }
482
483
}
484
426
=head2 Internal methods
485
=head2 Internal methods
427
486
428
=cut
487
=cut
(-)a/installer/data/mysql/atomicupdate/bug_23051_add_RenewAccruingItemWhenPaid_syspref.perl (-1 / +1 lines)
Lines 1-7 Link Here
1
$DBversion = 'XXX'; # will be replaced by the RM
1
$DBversion = 'XXX'; # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
2
if( CheckVersion( $DBversion ) ) {
3
3
4
    $dbh->do( q| INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type) VALUES ('RenewAccruingItemWhenPaid', '0', 'If enabled, when the fines on an item accruing is paid off, attempt to renew that item', '', 'YesNo'); | );
4
    $dbh->do( q| INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type) VALUES ('RenewAccruingItemWhenPaid', '0', 'If enabled, when the fines on an item accruing is paid off, attempt to renew that item. If the syspref "RenewalPeriodBase" is set to "due date", renewed items may still be overdue', '', 'YesNo'); | );
5
5
6
    SetVersion( $DBversion );
6
    SetVersion( $DBversion );
7
    print "Upgrade to $DBversion done (Bug 23051 - Add RenewAccruingItemWhenPaid syspref)\n";
7
    print "Upgrade to $DBversion done (Bug 23051 - Add RenewAccruingItemWhenPaid syspref)\n";
(-)a/installer/data/mysql/sysprefs.sql (-1 / +1 lines)
Lines 504-510 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
504
('RandomizeHoldsQueueWeight','0',NULL,'if ON, the holds queue in circulation will be randomized, either based on all location codes, or by the location codes specified in StaticHoldsQueueWeight','YesNo'),
504
('RandomizeHoldsQueueWeight','0',NULL,'if ON, the holds queue in circulation will be randomized, either based on all location codes, or by the location codes specified in StaticHoldsQueueWeight','YesNo'),
505
('RecordLocalUseOnReturn','0',NULL,'If ON, statistically record returns of unissued items as local use, instead of return','YesNo'),
505
('RecordLocalUseOnReturn','0',NULL,'If ON, statistically record returns of unissued items as local use, instead of return','YesNo'),
506
('RefundLostOnReturnControl','CheckinLibrary','CheckinLibrary|ItemHomeBranch|ItemHoldingBranch','If a lost item is returned, choose which branch to pick rules for refunding.','Choice'),
506
('RefundLostOnReturnControl','CheckinLibrary','CheckinLibrary|ItemHomeBranch|ItemHoldingBranch','If a lost item is returned, choose which branch to pick rules for refunding.','Choice'),
507
('RenewAccruingItemWhenPaid','0','','If enabled, when the fines on an item accruing is paid off, attempt to renew that item','YesNo'),
507
('RenewAccruingItemWhenPaid','0','','If enabled, when the fines on an item accruing is paid off, attempt to renew that item. If the syspref "RenewalPeriodBase" is set to "due date", renewed items may still be overdue','YesNo'),
508
('RenewalLog','0','','If ON, log information about renewals','YesNo'),
508
('RenewalLog','0','','If ON, log information about renewals','YesNo'),
509
('RenewalPeriodBase','date_due','date_due|now','Set whether the renewal date should be counted from the date_due or from the moment the Patron asks for renewal ','Choice'),
509
('RenewalPeriodBase','date_due','date_due|now','Set whether the renewal date should be counted from the date_due or from the moment the Patron asks for renewal ','Choice'),
510
('RenewalSendNotice','0','',NULL,'YesNo'),
510
('RenewalSendNotice','0','',NULL,'YesNo'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/renew_strings.inc (+2 lines)
Lines 25-30 Link Here
25
    Item is not allowed renewal
25
    Item is not allowed renewal
26
[% CASE 'onsite_checkout' %]
26
[% CASE 'onsite_checkout' %]
27
    Item is an onsite checkout
27
    Item is an onsite checkout
28
[% CASE 'has_fine' %]
29
    Item has an outstanding fine
28
[% CASE %]
30
[% CASE %]
29
    Unknown error
31
    Unknown error
30
[% END %]
32
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-1 / +1 lines)
Lines 490-496 Circulation: Link Here
490
              choices:
490
              choices:
491
                  yes: Renew
491
                  yes: Renew
492
                  no: "Don't renew"
492
                  no: "Don't renew"
493
            - the item automatically.
493
            - the item automatically. If the syspref "RenewalPeriodBase" is set to "due date", renewed items may still be overdue.
494
        -
494
        -
495
            - pref: ItemsDeniedRenewal
495
            - pref: ItemsDeniedRenewal
496
              type: textarea
496
              type: textarea
(-)a/t/db_dependent/Koha/Account/Lines.t (-2 / +76 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 9;
22
use Test::More tests => 10;
23
use Test::Exception;
23
use Test::Exception;
24
use Test::MockModule;
24
use Test::MockModule;
25
25
Lines 415-420 subtest 'Keep account info when related patron, staff or item is deleted' => sub Link Here
415
    $schema->storage->txn_rollback;
415
    $schema->storage->txn_rollback;
416
};
416
};
417
417
418
subtest 'Renewal related tests' => sub {
419
420
    plan tests => 7;
421
422
    $schema->storage->txn_begin;
423
424
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
425
    my $staff = $builder->build_object( { class => 'Koha::Patrons' } );
426
    my $item = $builder->build_object({ class => 'Koha::Items' });
427
    my $issue = $builder->build_object(
428
        {
429
            class => 'Koha::Checkouts',
430
            value => {
431
                itemnumber      => $item->itemnumber,
432
                onsite_checkout => 0,
433
                renewals        => 99,
434
                auto_renew      => 0
435
            }
436
        }
437
    );
438
    my $line = Koha::Account::Line->new(
439
    {
440
        borrowernumber    => $patron->borrowernumber,
441
        manager_id        => $staff->borrowernumber,
442
        itemnumber        => $item->itemnumber,
443
        accounttype       => "OVERDUE",
444
        status            => "UNRETURNED",
445
        amountoutstanding => 0,
446
        interface         => 'commandline',
447
    })->store;
448
449
    is( $line->renewable, 1, "Item is returned as renewable when it meets the conditions" );
450
    $line->amountoutstanding(5);
451
    is( $line->renewable, 0, "Item is returned as unrenewable when it has outstanding fine" );
452
    $line->amountoutstanding(0);
453
    $line->accounttype("VOID");
454
    is( $line->renewable, 0, "Item is returned as unrenewable when it has the wrong account type" );
455
    $line->accounttype("OVERDUE");
456
    $line->status("RETURNED");
457
    is( $line->renewable, 0, "Item is returned as unrenewable when it has the wrong account status" );
458
459
460
    t::lib::Mocks::mock_preference( 'RenewAccruingItemWhenPaid', 0 );
461
    is ($line->renew_item, 0, 'Attempt to renew fails when syspref is not set');
462
    t::lib::Mocks::mock_preference( 'RenewAccruingItemWhenPaid', 1 );
463
    is_deeply(
464
        $line->renew_item,
465
        {
466
            itemnumber => $item->itemnumber,
467
            error      => 'too_many',
468
            success    => 0
469
        },
470
        'Attempt to renew fails when CanBookBeRenewed returns false'
471
    );
472
    $issue->delete;
473
    $issue = $builder->build_object(
474
        {
475
            class => 'Koha::Checkouts',
476
            value => {
477
                itemnumber      => $item->itemnumber,
478
                onsite_checkout => 0,
479
                renewals        => 0,
480
                auto_renew      => 0
481
            }
482
        }
483
    );
484
    my $called = 0;
485
    my $module = new Test::MockModule('C4::Circulation');
486
    $module->mock('AddRenewal', sub { $called = 1; });
487
    $line->renew_item;
488
    is( $called, 1, 'Attempt to renew succeeds when conditions are met' );
489
490
    $schema->storage->txn_rollback;
491
};
492
418
subtest 'adjust() tests' => sub {
493
subtest 'adjust() tests' => sub {
419
494
420
    plan tests => 29;
495
    plan tests => 29;
421
- 

Return to bug 23051