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

(-)a/C4/Accounts.pm (-24 / +36 lines)
Lines 33-39 use C4::Members; Link Here
33
use Koha::Account;
33
use Koha::Account;
34
use Koha::Account::Lines;
34
use Koha::Account::Lines;
35
use Koha::Account::Offsets;
35
use Koha::Account::Offsets;
36
use Koha::CirculationRules;
37
use Koha::Checkouts;
36
use Koha::Items;
38
use Koha::Items;
39
use Koha::Patrons;
37
40
38
=head1 NAME
41
=head1 NAME
39
42
Lines 65-76 FIXME : if no replacement price, borrower just doesn't get charged? Link Here
65
68
66
sub chargelostitem {
69
sub chargelostitem {
67
    my $dbh = C4::Context->dbh();
70
    my $dbh = C4::Context->dbh();
68
    my ( $borrowernumber, $itemnumber, $replacementprice, $description ) = @_;
71
    my ( $borrowernumber, $itemnumber, $replacementprice, $description, $opts ) = @_;
72
    $opts ||= {};
73
69
    my $patron = Koha::Patrons->find($borrowernumber);
74
    my $patron = Koha::Patrons->find($borrowernumber);
70
    my $item   = Koha::Items->find($itemnumber);
75
    my $item   = Koha::Items->find($itemnumber);
71
    my $itype  = $item->itemtype;
76
    my $itype  = $item->itemtype;
72
    $replacementprice //= 0;
77
    $replacementprice //= 0;
73
    my $defaultreplacecost = $itype->defaultreplacecost;
78
79
    my $defaultreplacecost        = $itype->defaultreplacecost;
80
    my $usedefaultreplacementcost = C4::Context->preference("useDefaultReplacementCost");
81
    my $processingfeenote         = C4::Context->preference("ProcessingFeeNote");
74
82
75
    my $lost_control_pref = C4::Context->preference('LostChargesControl');
83
    my $lost_control_pref = C4::Context->preference('LostChargesControl');
76
    my $lost_control_branch;
84
    my $lost_control_branch;
Lines 85-125 sub chargelostitem { Link Here
85
            : $item->holdingbranch;
93
            : $item->holdingbranch;
86
    }
94
    }
87
95
88
    my $processfee = Koha::CirculationRules->get_effective_rule_value(
96
    my $interface  = $opts->{interface} // C4::Context->interface;
89
        {
97
    my $library_id = defined $opts->{library_id} ? $opts->{library_id} : $lost_control_branch;
90
            rule_name    => "lost_item_processing_fee",
98
91
            categorycode => undef,
99
    my $issue_id = $opts->{issue_id};
92
            itemtype     => $itype->itemtype,
100
    if ( !defined $issue_id ) {
93
            branchcode   => $lost_control_branch
101
        my $checkout = Koha::Checkouts->find( { itemnumber => $itemnumber } );
102
        if ( !$checkout && $item->in_bundle ) {
103
            my $host = $item->bundle_host;
104
            $checkout = $host->checkout;
94
        }
105
        }
95
    ) // 0;
106
        $issue_id = $checkout ? $checkout->issue_id : undef;
96
    my $usedefaultreplacementcost = C4::Context->preference("useDefaultReplacementCost");
107
    }
97
    my $processingfeenote         = C4::Context->preference("ProcessingFeeNote");
98
108
99
    if ( $usedefaultreplacementcost && $replacementprice == 0 && $defaultreplacecost ) {
109
    if ( $usedefaultreplacementcost && $replacementprice == 0 && $defaultreplacecost ) {
100
        $replacementprice = $defaultreplacecost;
110
        $replacementprice = $defaultreplacecost;
101
    }
111
    }
102
    my $checkout = Koha::Checkouts->find( { itemnumber => $itemnumber } );
103
    if ( !$checkout && $item->in_bundle ) {
104
        my $host = $item->bundle_host;
105
        $checkout = $host->checkout;
106
    }
107
    my $issue_id = $checkout ? $checkout->issue_id : undef;
108
112
109
    my $account = Koha::Account->new( { patron_id => $borrowernumber } );
113
    my $account = Koha::Account->new( { patron_id => $borrowernumber } );
110
114
111
    # first make sure the borrower hasn't already been charged for this item (for this issuance)
112
    my $existing_charges = $account->lines->search(
115
    my $existing_charges = $account->lines->search(
113
        {
116
        {
114
            itemnumber      => $itemnumber,
117
            item_id         => $itemnumber,
115
            debit_type_code => 'LOST',
118
            debit_type_code => 'LOST',
116
            issue_id        => $issue_id
119
            issue_id        => $issue_id,
117
        }
120
        }
118
    )->count();
121
    )->count();
119
122
120
    # OK, they haven't
123
    # OK, they haven't
121
    unless ($existing_charges) {
124
    unless ($existing_charges) {
122
125
126
        my $processfee = Koha::CirculationRules->get_effective_rule_value(
127
            {
128
                rule_name    => "lost_item_processing_fee",
129
                categorycode => undef,
130
                itemtype     => $itype->itemtype,
131
                branchcode   => $library_id,
132
            }
133
        ) // 0;
134
123
        #add processing fee
135
        #add processing fee
124
        if ( $processfee && $processfee > 0 ) {
136
        if ( $processfee && $processfee > 0 ) {
125
            my $accountline = $account->add_debit(
137
            my $accountline = $account->add_debit(
Lines 129-138 sub chargelostitem { Link Here
129
                    note        => $processingfeenote,
141
                    note        => $processingfeenote,
130
                    user_id     => C4::Context->userenv ? C4::Context->userenv->{'number'} : undef,
142
                    user_id     => C4::Context->userenv ? C4::Context->userenv->{'number'} : undef,
131
                    interface   => C4::Context->interface,
143
                    interface   => C4::Context->interface,
132
                    library_id  => C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef,
144
                    library_id  => $library_id,
133
                    type        => 'PROCESSING',
145
                    type        => 'PROCESSING',
134
                    item_id     => $itemnumber,
146
                    item_id     => $itemnumber,
135
                    issue_id    => $issue_id,
147
                    ( defined $issue_id ? ( issue_id => $issue_id ) : () ),
136
                }
148
                }
137
            );
149
            );
138
        }
150
        }
Lines 146-155 sub chargelostitem { Link Here
146
                    note        => undef,
158
                    note        => undef,
147
                    user_id     => C4::Context->userenv ? C4::Context->userenv->{'number'} : undef,
159
                    user_id     => C4::Context->userenv ? C4::Context->userenv->{'number'} : undef,
148
                    interface   => C4::Context->interface,
160
                    interface   => C4::Context->interface,
149
                    library_id  => C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef,
161
                    library_id  => $library_id,
150
                    type        => 'LOST',
162
                    type        => 'LOST',
151
                    item_id     => $itemnumber,
163
                    item_id     => $itemnumber,
152
                    issue_id    => $issue_id,
164
                    ( defined $issue_id ? ( issue_id => $issue_id ) : () ),
153
                }
165
                }
154
            );
166
            );
155
        }
167
        }
(-)a/C4/Circulation.pm (-10 / +37 lines)
Lines 4447-4468 sub LostItem { Link Here
4447
            or warn "_FixOverduesOnReturn($borrowernumber, $itemnumber...) failed!";    # zero is OK, check defined
4447
            or warn "_FixOverduesOnReturn($borrowernumber, $itemnumber...) failed!";    # zero is OK, check defined
4448
4448
4449
        if ( C4::Context->preference('WhenLostChargeReplacementFee') ) {
4449
        if ( C4::Context->preference('WhenLostChargeReplacementFee') ) {
4450
            my $desc = sprintf(
4451
                "%s %s %s",
4452
                $issues->{'title'}          || q{},
4453
                $issues->{'barcode'}        || q{},
4454
                $issues->{'itemcallnumber'} || q{},
4455
            );
4456
4457
            my $interface =
4458
                ( $mark_lost_from && $mark_lost_from eq 'cronjob' )
4459
                ? 'cron'
4460
                : C4::Context->interface;
4461
4462
            my $item_obj  = Koha::Items->find($itemnumber);
4463
            my $issue_obj = Koha::Checkouts->search(
4464
                {
4465
                    itemnumber     => $itemnumber,
4466
                    borrowernumber => $borrowernumber,
4467
                }
4468
            )->next;
4469
            my $rule_branch = Koha::Checkout->branch_for_fee_context(
4470
                fee_type => 'LOST',
4471
                patron   => $patron,
4472
                item     => $item_obj,
4473
                issue    => $issue_obj,
4474
            );
4475
            my $issue_id = $issues->{issue_id} // ( $issue_obj ? $issue_obj->issue_id : undef );
4476
4450
            C4::Accounts::chargelostitem(
4477
            C4::Accounts::chargelostitem(
4451
                $borrowernumber,
4478
                $borrowernumber,
4452
                $itemnumber,
4479
                $itemnumber,
4453
                $issues->{'replacementprice'},
4480
                ( ( $issues->{'replacementprice'} // 0 ) + 0 ),
4454
                sprintf(
4481
                $desc,
4455
                    "%s %s %s",
4482
                {
4456
                    $issues->{'title'}          || q{},
4483
                    interface  => $interface,
4457
                    $issues->{'barcode'}        || q{},
4484
                    library_id => $rule_branch,
4458
                    $issues->{'itemcallnumber'} || q{},
4485
                    issue_id   => $issue_id,
4459
                ),
4486
                }
4460
            );
4487
            );
4461
4462
            #FIXME : Should probably have a way to distinguish this from an item that really was returned.
4463
            #warn " $issues->{'borrowernumber'}  /  $itemnumber ";
4464
        }
4488
        }
4465
4489
4490
        #FIXME : Should probably have a way to distinguish this from an item that really was returned.
4491
        #warn " $issues->{'borrowernumber'}  /  $itemnumber ";
4492
4466
        MarkIssueReturned( $borrowernumber, $itemnumber, undef, $patron->privacy, $params ) if $mark_returned;
4493
        MarkIssueReturned( $borrowernumber, $itemnumber, undef, $patron->privacy, $params ) if $mark_returned;
4467
    }
4494
    }
4468
4495
(-)a/C4/Overdues.pm (-2 / +12 lines)
Lines 647-653 sub UpdateFine { Link Here
647
            };
647
            };
648
            my $desc = $letter ? $letter->{content} : sprintf( "Item %s - due %s", $itemnum, output_pref($due) );
648
            my $desc = $letter ? $letter->{content} : sprintf( "Item %s - due %s", $itemnum, output_pref($due) );
649
649
650
            my $account = Koha::Account->new( { patron_id => $borrowernumber } );
650
            my $account   = Koha::Account->new( { patron_id => $borrowernumber } );
651
            my $item_obj  = Koha::Items->find($itemnum);
652
            my $issue_obj = Koha::Checkouts->find($issue_id);
653
654
            my $rule_branch = Koha::Checkout->branch_for_fee_context(
655
                fee_type => 'OVERDUE',
656
                patron   => $patron,
657
                item     => $item_obj,
658
                issue    => $issue_obj,
659
            );
660
651
            $accountline = $account->add_debit(
661
            $accountline = $account->add_debit(
652
                {
662
                {
653
                    amount      => $amount,
663
                    amount      => $amount,
Lines 655-661 sub UpdateFine { Link Here
655
                    note        => undef,
665
                    note        => undef,
656
                    user_id     => undef,
666
                    user_id     => undef,
657
                    interface   => C4::Context->interface,
667
                    interface   => C4::Context->interface,
658
                    library_id  => undef,       #FIXME: Should we grab the checkout or circ-control branch here perhaps?
668
                    library_id  => $rule_branch,
659
                    type        => 'OVERDUE',
669
                    type        => 'OVERDUE',
660
                    item_id     => $itemnum,
670
                    item_id     => $itemnum,
661
                    issue_id    => $issue_id,
671
                    issue_id    => $issue_id,
(-)a/Koha/Checkout.pm (+47 lines)
Lines 24-29 use DateTime; Link Here
24
use Try::Tiny qw( catch try );
24
use Try::Tiny qw( catch try );
25
25
26
use C4::Circulation qw( AddRenewal CanBookBeRenewed LostItem MarkIssueReturned );
26
use C4::Circulation qw( AddRenewal CanBookBeRenewed LostItem MarkIssueReturned );
27
use Koha::Checkout;
27
use Koha::Booking;
28
use Koha::Booking;
28
use Koha::Checkouts::Renewals;
29
use Koha::Checkouts::Renewals;
29
use Koha::Checkouts::ReturnClaims;
30
use Koha::Checkouts::ReturnClaims;
Lines 341-346 sub claim_returned { Link Here
341
    };
342
    };
342
}
343
}
343
344
345
=head2 branch_for_fee_context
346
347
my $branchcode = Koha::Checkout->branch_for_fee_context(
348
        fee_type => 'OVERDUE' | 'LOST' | 'PROCESSING',
349
        );
350
351
Determines which branch’s rules should apply to a fee based on system preferences and available context. For LOST/PROCESSING it honors LostChargesControl; for OVERDUE it honors CircControl. It also respects HomeOrHoldingBranch when choosing the item’s branch. Returns a branchcode (string) or undef if it cannot be determined.
352
353
=cut
354
355
sub branch_for_fee_context {
356
    my ( $class, %args ) = @_;
357
358
    my $fee_type = $args{fee_type} // 'OVERDUE';
359
360
    # Choose the controlling preference depending on fee type
361
    my $control_pref =
362
        ( $fee_type eq 'LOST' || $fee_type eq 'PROCESSING' )
363
        ? C4::Context->preference('LostChargesControl')
364
        : C4::Context->preference('CircControl');
365
366
    # Item branch selection (home vs holding)
367
    my $hoh_pref    = C4::Context->preference('HomeOrHoldingBranch') // 'home';
368
    my $use_holding = ( $hoh_pref =~ /holding/i ) ? 1 : 0;
369
370
    # Support Koha objects OR plain hashes
371
    my $patron_branch  = eval { $args{patron}->branchcode }  // ( $args{patron} && $args{patron}->{branchcode} );
372
    my $issuing_branch = eval { $args{issue}->branchcode }   // ( $args{issue}  && $args{issue}->{branchcode} );
373
    my $item_home      = eval { $args{item}->homebranch }    // ( $args{item}   && $args{item}->{homebranch} );
374
    my $item_holding   = eval { $args{item}->holdingbranch } // ( $args{item}   && $args{item}->{holdingbranch} );
375
    my $item_branch    = $use_holding ? ( $item_holding // $item_home ) : $item_home;
376
377
    # Normalize empty strings to undef so // works as intended
378
    for my $ref ( \$patron_branch, \$issuing_branch, \$item_home, \$item_holding, \$item_branch ) {
379
        $$ref = undef if defined $$ref && $$ref eq '';
380
    }
381
382
    # Resolution order depends on controlling pref
383
    my $resolved =
384
          ( defined $control_pref && $control_pref eq 'PatronLibrary' ) ? $patron_branch
385
        : ( defined $control_pref && $control_pref eq 'PickupLibrary' )
386
        ? ( $issuing_branch // $patron_branch // $item_branch )
387
        : $item_branch;    # ItemLibrary (default)
388
    return $resolved;
389
}
390
344
=head2 Internal methods
391
=head2 Internal methods
345
392
346
=head3 _type
393
=head3 _type
(-)a/misc/cronjobs/longoverdue.pl (-2 / +20 lines)
Lines 34-39 use Pod::Usage qw( pod2usage ); Link Here
34
use C4::Circulation qw( LostItem MarkIssueReturned );
34
use C4::Circulation qw( LostItem MarkIssueReturned );
35
use C4::Context;
35
use C4::Context;
36
use C4::Log qw( cronlogaction );
36
use C4::Log qw( cronlogaction );
37
use Koha::Checkout;
38
use Koha::Checkouts;
39
use Koha::Items;
37
use Koha::ItemTypes;
40
use Koha::ItemTypes;
38
use Koha::Patron::Categories;
41
use Koha::Patron::Categories;
39
use Koha::Patrons;
42
use Koha::Patrons;
Lines 522-528 foreach my $startrange ( sort keys %$lost ) { Link Here
522
            if ($confirm) {
525
            if ($confirm) {
523
                Koha::Items->find( $row->{itemnumber} )->itemlost($lostvalue)->store;
526
                Koha::Items->find( $row->{itemnumber} )->itemlost($lostvalue)->store;
524
                if ( $charge && $charge eq $lostvalue ) {
527
                if ( $charge && $charge eq $lostvalue ) {
525
                    LostItem( $row->{'itemnumber'}, 'cronjob', $mark_returned );
528
                    my $patron = Koha::Patrons->find( $row->{borrowernumber} );
529
                    my $item   = Koha::Items->find( $row->{itemnumber} );
530
                    my $issue  = Koha::Checkouts->search(
531
                        {
532
                            itemnumber     => $row->{itemnumber},
533
                            borrowernumber => $row->{borrowernumber},
534
                        }
535
                    )->next;
536
537
                    my $rule_branch = Koha::Checkout->branch_for_fee_context(
538
                        fee_type => 'LOST',
539
                        patron   => $patron,
540
                        item     => $item,
541
                        issue    => $issue,
542
                    );
543
544
                    LostItem( $row->{itemnumber}, 'cronjob', $mark_returned, { library_id => $rule_branch } );
526
                } elsif ($mark_returned) {
545
                } elsif ($mark_returned) {
527
                    $patron ||= Koha::Patrons->find( $row->{borrowernumber} );
546
                    $patron ||= Koha::Patrons->find( $row->{borrowernumber} );
528
                    MarkIssueReturned( $row->{borrowernumber}, $row->{itemnumber}, undef, $patron->privacy );
547
                    MarkIssueReturned( $row->{borrowernumber}, $row->{itemnumber}, undef, $patron->privacy );
529
- 

Return to bug 35612