From 9d04a50897f2bd88466f87c01c495202ff1dbfca Mon Sep 17 00:00:00 2001 From: Laura_Escamilla Date: Tue, 14 Oct 2025 21:08:12 +0000 Subject: [PATCH] Bug 35612: Recorded branch context in accountlines.branchcode for OVERDUE, LOST, and PROCESSING fees --- C4/Circulation.pm | 55 +++++++++++++++++++++++++++--------- C4/Overdues.pm | 43 +++++++++++++++++++++++++++- misc/cronjobs/longoverdue.pl | 21 ++++++++++++-- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 0a8df34263..f3e55ae152 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -34,8 +34,8 @@ use C4::Items qw( ModItemTransfer ModDateLastSeen CartToShelf ); use C4::Accounts; use C4::ItemCirculationAlertPreference; use C4::Message; -use C4::Log qw( logaction ); # logaction -use C4::Overdues; +use C4::Log qw( logaction ); # logaction +use C4::Overdues qw( branch_for_fee_context ); use C4::RotatingCollections qw(GetCollectionItemBranches); use Algorithm::CheckDigits qw( CheckDigits ); @@ -4446,20 +4446,47 @@ sub LostItem { or warn "_FixOverduesOnReturn($borrowernumber, $itemnumber...) failed!"; # zero is OK, check defined if ( C4::Context->preference('WhenLostChargeReplacementFee') ) { - C4::Accounts::chargelostitem( - $borrowernumber, - $itemnumber, - $issues->{'replacementprice'}, - sprintf( - "%s %s %s", - $issues->{'title'} || q{}, - $issues->{'barcode'} || q{}, - $issues->{'itemcallnumber'} || q{}, - ), + my $desc = sprintf( + "%s %s %s", + $issues->{'title'} || q{}, + $issues->{'barcode'} || q{}, + $issues->{'itemcallnumber'} || q{}, ); - #FIXME : Should probably have a way to distinguish this from an item that really was returned. - #warn " $issues->{'borrowernumber'} / $itemnumber "; + # interface: cron if called from longoverdue, otherwise current interface + my $interface = + ( $mark_lost_from && $mark_lost_from eq 'cronjob' ) + ? 'cron' + : C4::Context->interface; + my $library_id = $params && ref($params) eq 'HASH' ? $params->{library_id} : undef; + + if ( !defined $library_id ) { + my $item_obj = Koha::Items->find($itemnumber); + my $issue_obj = Koha::Checkouts->search( + { + itemnumber => $itemnumber, + borrowernumber => $borrowernumber, + } + )->next; + $library_id = branch_for_fee_context( + fee_type => 'LOST', + patron => $patron, + item => $item_obj, + issue => $issue_obj, + ); + } + + Koha::Account->new( { patron_id => $borrowernumber } )->add_debit( + { + amount => $issues->{'replacementprice'} + 0, + type => 'LOST', + description => $desc, + interface => $interface, + library_id => $library_id, + item_id => $itemnumber, + ( $issues->{issue_id} ? ( issue_id => $issues->{issue_id} ) : () ), + } + ); } MarkIssueReturned( $borrowernumber, $itemnumber, undef, $patron->privacy, $params ) if $mark_returned; diff --git a/C4/Overdues.pm b/C4/Overdues.pm index 99df5aeb76..62e12e1e6c 100644 --- a/C4/Overdues.pm +++ b/C4/Overdues.pm @@ -30,7 +30,9 @@ use C4::Accounts; use C4::Context; use Koha::Account::Lines; use Koha::Account::Offsets; +use Koha::Checkouts; use Koha::DateUtils qw( output_pref ); +use Koha::Items; use Koha::Libraries; use Koha::Recalls; use Koha::Logger; @@ -55,6 +57,7 @@ BEGIN { GetOverdueMessageTransportTypes parse_overdues_letter GetIssuesIteminfo + branch_for_fee_context ); } @@ -327,6 +330,32 @@ sub CalcFine { return ( $amount, $units_minus_grace, $chargeable_units ); } +# Resolve which branch’s rules control a fee, honoring CircControl/LostChargesControl and HomeOrHoldingBranch. + +sub branch_for_fee_context { + my (%args) = @_; + + my $fee_type = $args{fee_type} // 'OVERDUE'; + + my $control_pref = + $fee_type eq 'LOST' || $fee_type eq 'PROCESSING' + ? C4::Context->preference('LostChargesControl') + : C4::Context->preference('CircControl'); + my $hoh_pref = C4::Context->preference('HomeOrHoldingBranch') // 'home'; + my $use_holding = ( $hoh_pref =~ /holding/i ) ? 1 : 0; + + my $patron_branch = eval { $args{patron}->branchcode } // $args{patron}->{branchcode}; + my $issuing_branch = eval { $args{issue}->branchcode } // $args{issue}->{branchcode}; + my $item_home = eval { $args{item}->homebranch } // $args{item}->{homebranch}; + my $item_holding = eval { $args{item}->holdingbranch } // $args{item}->{holdingbranch}; + my $item_branch = $use_holding ? ( $item_holding // $item_home ) : $item_home; + + return + ( $control_pref && $control_pref eq 'PatronLibrary' ) ? $patron_branch + : ( $control_pref && $control_pref eq 'PickupLibrary' ) ? ( $issuing_branch // $patron_branch // $item_branch ) + : $item_branch; +} + =head2 get_chargeable_units get_chargeable_units($unit, $start_date_ $end_date, $branchcode); @@ -651,6 +680,18 @@ sub UpdateFine { my $desc = $letter ? $letter->{content} : sprintf( "Item %s - due %s", $itemnum, output_pref($due) ); my $account = Koha::Account->new( { patron_id => $borrowernumber } ); + + # Resolve the branch that controlled this OVERDUE fee + my $patron_obj = Koha::Patrons->find($borrowernumber); + my $item_obj = Koha::Items->find($itemnum); + my $issue_obj = Koha::Checkouts->find($issue_id); # issuing branch if CircControl=PickupLibrary + my $rule_branch = branch_for_fee_context( + fee_type => 'OVERDUE', + patron => $patron_obj, + item => $item_obj, + issue => $issue_obj, + ); + $accountline = $account->add_debit( { amount => $amount, @@ -658,7 +699,7 @@ sub UpdateFine { note => undef, user_id => undef, interface => C4::Context->interface, - library_id => undef, #FIXME: Should we grab the checkout or circ-control branch here perhaps? + library_id => $rule_branch, # record the branch used to calculate the fee type => 'OVERDUE', item_id => $itemnum, issue_id => $issue_id, diff --git a/misc/cronjobs/longoverdue.pl b/misc/cronjobs/longoverdue.pl index b65467f75b..48e57355b2 100755 --- a/misc/cronjobs/longoverdue.pl +++ b/misc/cronjobs/longoverdue.pl @@ -31,9 +31,11 @@ use warnings; use Getopt::Long qw( GetOptions ); use Pod::Usage qw( pod2usage ); +use Koha::Checkouts; use C4::Circulation qw( LostItem MarkIssueReturned ); use C4::Context; -use C4::Log qw( cronlogaction ); +use C4::Log qw( cronlogaction ); +use C4::Overdues qw( branch_for_fee_context ); use Koha::ItemTypes; use Koha::Patron::Categories; use Koha::Patrons; @@ -522,7 +524,22 @@ foreach my $startrange ( sort keys %$lost ) { if ($confirm) { Koha::Items->find( $row->{itemnumber} )->itemlost($lostvalue)->store; if ( $charge && $charge eq $lostvalue ) { - LostItem( $row->{'itemnumber'}, 'cronjob', $mark_returned ); + my $patron = Koha::Patrons->find( $row->{borrowernumber} ); + my $item = Koha::Items->find( $row->{itemnumber} ); + my $issue = Koha::Checkouts->search( + { + itemnumber => $row->{itemnumber}, + borrowernumber => $row->{borrowernumber}, + } + )->next; # issuing branch if available + my $rule_branch = branch_for_fee_context( + fee_type => 'LOST', + patron => $patron, + item => $item, + issue => $issue, # ok if undef + ); + LostItem( $row->{itemnumber}, 'cronjob', $mark_returned, { library_id => $rule_branch } ); + } elsif ($mark_returned) { $patron ||= Koha::Patrons->find( $row->{borrowernumber} ); MarkIssueReturned( $row->{borrowernumber}, $row->{itemnumber}, undef, $patron->privacy ); -- 2.39.5