X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FCirculation.pm;h=17cab0c74bc0f9e4877b6ec52fafc93e3d533664;hb=5aee0f6a2a06fedcfdabd34c1757a7a1455a6fcd;hp=89e2737f56a384eb2d8ad3a7c1af040a75c3fb45;hpb=3fe837bc74d110b9b3f378ee0565464d8529e827;p=koha-ffzg.git diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 89e2737f56..17cab0c74b 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -24,32 +24,31 @@ use POSIX qw( floor ); use YAML::XS; use Encode; -use Koha::DateUtils; +use Koha::DateUtils qw( dt_from_string output_pref ); use C4::Context; -use C4::Stats; -use C4::Reserves; -use C4::Biblio; -use C4::Items; -use C4::Members; +use C4::Stats qw( UpdateStats ); +use C4::Reserves qw( CheckReserves CanItemBeReserved MoveReserve ModReserve ModReserveMinusPriority RevertWaitingStatus IsItemOnHoldAndFound IsAvailableForItemLevelRequest ); +use C4::Biblio qw( UpdateTotalIssues ); +use C4::Items qw( ModItemTransfer ModDateLastSeen CartToShelf ); use C4::Accounts; use C4::ItemCirculationAlertPreference; use C4::Message; -use C4::Log; # logaction -use C4::Overdues qw(CalcFine UpdateFine get_chargeable_units); +use C4::Log qw( logaction ); # logaction +use C4::Overdues; use C4::RotatingCollections qw(GetCollectionItemBranches); -use Algorithm::CheckDigits; +use Algorithm::CheckDigits qw( CheckDigits ); -use Data::Dumper; +use Data::Dumper qw( Dumper ); use Koha::Account; use Koha::AuthorisedValues; use Koha::Biblioitems; -use Koha::DateUtils; +use Koha::DateUtils qw( dt_from_string output_pref ); use Koha::Calendar; use Koha::Checkouts; use Koha::Illrequests; use Koha::Items; use Koha::Patrons; -use Koha::Patron::Debarments; +use Koha::Patron::Debarments qw( DelUniqueDebarment GetDebarments AddUniqueDebarment ); use Koha::Database; use Koha::Libraries; use Koha::Account::Lines; @@ -62,77 +61,69 @@ use Koha::Config::SysPref; use Koha::Checkouts::ReturnClaims; use Koha::SearchEngine::Indexer; use Koha::Exceptions::Checkout; -use Carp; -use List::MoreUtils qw( uniq any ); +use Koha::Plugins; +use Carp qw( carp ); +use List::MoreUtils qw( any ); use Scalar::Util qw( looks_like_number ); -use Try::Tiny; -use Date::Calc qw( - Today - Today_and_Now - Add_Delta_YM - Add_Delta_DHMS - Date_to_Days - Day_of_Week - Add_Delta_Days -); -use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); - +use Date::Calc qw( Date_to_Days ); +our (@ISA, @EXPORT_OK); BEGIN { - require Exporter; - @ISA = qw(Exporter); - - # FIXME subs that should probably be elsewhere - push @EXPORT, qw( - &barcodedecode - &LostItem - &ReturnLostItem - &GetPendingOnSiteCheckouts - ); - - # subs to deal with issuing a book - push @EXPORT, qw( - &CanBookBeIssued - &CanBookBeRenewed - &AddIssue - &AddRenewal - &GetRenewCount - &GetSoonestRenewDate - &GetLatestAutoRenewDate - &GetIssuingCharges - &GetBranchBorrowerCircRule - &GetBranchItemRule - &GetOpenIssue - &CheckIfIssuedToPatron - &IsItemIssued - GetTopIssues - ); - - # subs to deal with returns - push @EXPORT, qw( - &AddReturn - &MarkIssueReturned - ); - - # subs to deal with transfers - push @EXPORT, qw( - &transferbook - &GetTransfers - &GetTransfersFromTo - &updateWrongTransfer - &IsBranchTransferAllowed - &CreateBranchTransferLimit - &DeleteBranchTransferLimits - &TransferSlip - ); - - # subs to deal with offline circulation - push @EXPORT, qw( - &GetOfflineOperations - &GetOfflineOperation - &AddOfflineOperation - &DeleteOfflineOperation - &ProcessOfflineOperation + + require Exporter; + @ISA = qw(Exporter); + + # FIXME subs that should probably be elsewhere + push @EXPORT_OK, qw( + barcodedecode + LostItem + ReturnLostItem + GetPendingOnSiteCheckouts + + CanBookBeIssued + checkHighHolds + CanBookBeRenewed + AddIssue + GetLoanLength + GetHardDueDate + AddRenewal + GetRenewCount + GetSoonestRenewDate + GetLatestAutoRenewDate + GetIssuingCharges + AddIssuingCharge + GetBranchBorrowerCircRule + GetBranchItemRule + GetBiblioIssues + GetOpenIssue + GetUpcomingDueIssues + CheckIfIssuedToPatron + IsItemIssued + GetAgeRestriction + GetTopIssues + + AddReturn + MarkIssueReturned + + transferbook + TooMany + GetTransfers + GetTransfersFromTo + updateWrongTransfer + CalcDateDue + CheckValidBarcode + IsBranchTransferAllowed + CreateBranchTransferLimit + DeleteBranchTransferLimits + TransferSlip + + GetOfflineOperations + GetOfflineOperation + AddOfflineOperation + DeleteOfflineOperation + ProcessOfflineOperation + ProcessOfflinePayment ); + push @EXPORT_OK, '_GetCircControlBranch'; # This is wrong! } =head1 NAME @@ -175,6 +166,7 @@ sub barcodedecode { my ($barcode, $filter) = @_; my $branch = C4::Context::mybranch(); $filter = C4::Context->preference('itemBarcodeInputFilter') unless $filter; + Koha::Plugins->call('item_barcode_transform', \$barcode ); $filter or return $barcode; # ensure filter is defined, else return untouched barcode if ($filter eq 'whitespace') { $barcode =~ s/\s//g; @@ -800,7 +792,7 @@ sub CanBookBeIssued { # if ( $patron->category->category_type eq 'X' && ( $item_object->barcode )) { # stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1 . - &UpdateStats({ + C4::Stats::UpdateStats({ branch => C4::Context->userenv->{'branch'}, type => 'localuse', itemnumber => $item_object->itemnumber, @@ -845,7 +837,7 @@ sub CanBookBeIssued { my $no_issues_charge_guarantees = C4::Context->preference("NoIssuesChargeGuarantees"); $no_issues_charge_guarantees = undef unless looks_like_number( $no_issues_charge_guarantees ); if ( defined $no_issues_charge_guarantees ) { - my @guarantees = map { $_->guarantee } $patron->guarantee_relationships(); + my @guarantees = map { $_->guarantee } $patron->guarantee_relationships->as_list; my $guarantees_non_issues_charges = 0; foreach my $g ( @guarantees ) { $guarantees_non_issues_charges += $g->account->non_issues_charges; @@ -1174,7 +1166,7 @@ sub CanBookBeIssued { ## check for high holds decreasing loan period if ( C4::Context->preference('decreaseLoanHighHolds') ) { - my $check = checkHighHolds( $item_unblessed, $patron_unblessed ); + my $check = checkHighHolds( $item_object, $patron ); if ( $check->{exceeded} ) { if ($override_high_holds) { @@ -1286,9 +1278,8 @@ sub CanBookBeReturned { =cut sub checkHighHolds { - my ( $item, $borrower ) = @_; - my $branchcode = _GetCircControlBranch( $item, $borrower ); - my $item_object = Koha::Items->find( $item->{itemnumber} ); + my ( $item, $patron ) = @_; + my $branchcode = _GetCircControlBranch( $item->unblessed, $patron->unblessed ); my $return_data = { exceeded => 0, @@ -1297,7 +1288,7 @@ sub checkHighHolds { due_date => undef, }; - my $holds = Koha::Holds->search( { biblionumber => $item->{'biblionumber'} } ); + my $holds = Koha::Holds->search( { biblionumber => $item->biblionumber } ); if ( $holds->count() ) { $return_data->{outstanding} = $holds->count(); @@ -1330,7 +1321,7 @@ sub checkHighHolds { } # Remove any items that are not holdable for this patron - @items = grep { CanItemBeReserved( $borrower->{borrowernumber}, $_->itemnumber, undef, { ignore_found_holds => 1 } )->{status} eq 'OK' } @items; + @items = grep { CanItemBeReserved( $patron , $_, undef, { ignore_found_holds => 1 } )->{status} eq 'OK' } @items; my $items_count = scalar @items; @@ -1345,22 +1336,22 @@ sub checkHighHolds { my $issuedate = dt_from_string(); - my $itype = $item_object->effective_itemtype; + my $itype = $item->effective_itemtype; my $daysmode = Koha::CirculationRules->get_effective_daysmode( { - categorycode => $borrower->{categorycode}, + categorycode => $patron->categorycode, itemtype => $itype, branchcode => $branchcode, } ); my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode ); - my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $borrower ); + my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $patron->unblessed ); my $rule = Koha::CirculationRules->get_effective_rule( { - categorycode => $borrower->{categorycode}, - itemtype => $item_object->effective_itemtype, + categorycode => $patron->categorycode, + itemtype => $item->effective_itemtype, branchcode => $branchcode, rule_name => 'decreaseloanholds', } @@ -1575,6 +1566,7 @@ sub AddIssue { )->store; } $issue->discard_changes; + C4::Auth::track_login_daily( $borrower->{userid} ); if ( $item_object->location && $item_object->location eq 'CART' && ( !$item_object->permanent_location || $item_object->permanent_location ne 'CART' ) ) { ## Item was moved to cart via UpdateItemLocationOnCheckin, anything issued should be taken off the cart. @@ -1640,7 +1632,7 @@ sub AddIssue { } # Record the fact that this book was issued. - &UpdateStats( + C4::Stats::UpdateStats( { branch => C4::Context->userenv->{'branch'}, type => ( $onsite_checkout ? 'onsite_checkout' : 'issue' ), @@ -2023,7 +2015,10 @@ sub AddReturn { if (defined $update_loc_rules->{_ALL_}) { if ($update_loc_rules->{_ALL_} eq '_PERM_') { $update_loc_rules->{_ALL_} = $item->permanent_location; } if ($update_loc_rules->{_ALL_} eq '_BLANK_') { $update_loc_rules->{_ALL_} = ''; } - if ( defined $item->location && $item->location ne $update_loc_rules->{_ALL_}) { + if ( + ( defined $item->location && $item->location ne $update_loc_rules->{_ALL_}) || + (!defined $item->location && $update_loc_rules->{_ALL_} ne "") + ) { $messages->{'ItemLocationUpdated'} = { from => $item->location, to => $update_loc_rules->{_ALL_} }; $item->location($update_loc_rules->{_ALL_})->store({skip_record_index=>1}); } @@ -2133,29 +2128,34 @@ sub AddReturn { if ($item_was_lost) { $messages->{'WasLost'} = 1; unless ( C4::Context->preference("BlockReturnOfLostItems") ) { - $messages->{'LostItemFeeRefunded'} = $updated_item->{_refunded}; - $messages->{'LostItemFeeRestored'} = $updated_item->{_restored}; - - if ( $updated_item->{_charge} ) { - $issue //= Koha::Old::Checkouts->search( - { itemnumber => $item->itemnumber }, - { order_by => { '-desc' => 'returndate' }, rows => 1 } ) - ->single; - unless ( exists( $patron_unblessed->{branchcode} ) ) { - my $patron = $issue->patron; - $patron_unblessed = $patron->unblessed; - } - _CalculateAndUpdateFine( - { - issue => $issue, - item => $item->unblessed, - borrower => $patron_unblessed, - return_date => $return_date + my @object_messages = @{ $updated_item->object_messages }; + for my $message (@object_messages) { + $messages->{'LostItemFeeRefunded'} = 1 + if $message->message eq 'lost_refunded'; + $messages->{'LostItemFeeRestored'} = 1 + if $message->message eq 'lost_restored'; + + if ( $message->message eq 'lost_charge' ) { + $issue //= Koha::Old::Checkouts->search( + { itemnumber => $item->itemnumber }, + { order_by => { '-desc' => 'returndate' }, rows => 1 } + )->single; + unless ( exists( $patron_unblessed->{branchcode} ) ) { + my $patron = $issue->patron; + $patron_unblessed = $patron->unblessed; } - ); - _FixOverduesOnReturn( $patron_unblessed->{borrowernumber}, - $item->itemnumber, undef, 'RETURNED' ); - $messages->{'LostItemFeeCharged'} = 1; + _CalculateAndUpdateFine( + { + issue => $issue, + item => $item->unblessed, + borrower => $patron_unblessed, + return_date => $return_date + } + ); + _FixOverduesOnReturn( $patron_unblessed->{borrowernumber}, + $item->itemnumber, undef, 'RETURNED' ); + $messages->{'LostItemFeeCharged'} = 1; + } } } } @@ -2238,7 +2238,7 @@ sub AddReturn { } # Record the fact that this book was returned. - UpdateStats({ + C4::Stats::UpdateStats({ branch => $branch, type => $stat_type, itemnumber => $itemnumber, @@ -2263,6 +2263,7 @@ sub AddReturn { item => $item->unblessed, borrower => $patron->unblessed, branch => $branch, + issue => $issue }); } @@ -2617,7 +2618,7 @@ sub _FixOverduesOnReturn { } ); - $credit->apply({ debits => [ $accountline ], offset_type => 'Forgiven' }); + $credit->apply({ debits => [ $accountline ] }); if (C4::Context->preference("FinesLog")) { &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item"); @@ -2747,8 +2748,6 @@ already renewed the loan. $error will contain the reason the renewal can not pro sub CanBookBeRenewed { my ( $borrowernumber, $itemnumber, $override_limit, $cron ) = @_; - my $dbh = C4::Context->dbh; - my $renews = 1; my $auto_renew = "no"; my $item = Koha::Items->find($itemnumber) or return ( 0, 'no_item' ); @@ -2768,10 +2767,7 @@ sub CanBookBeRenewed { branchcode => $branchcode, rules => [ 'renewalsallowed', - 'no_auto_renewal_after', - 'no_auto_renewal_after_hard_limit', 'lengthunit', - 'norenewalbefore', 'unseen_renewals_allowed' ] } @@ -2797,79 +2793,18 @@ sub CanBookBeRenewed { return ( 0, 'overdue'); } - if ( $issue->auto_renew && $patron->autorenew_checkouts ) { - - if ( $patron->category->effective_BlockExpiredPatronOpacActions and $patron->is_expired ) { - return ( 0, 'auto_account_expired' ); - } - - if ( defined $issuing_rule->{no_auto_renewal_after} - and $issuing_rule->{no_auto_renewal_after} ne "" ) { - # Get issue_date and add no_auto_renewal_after - # If this is greater than today, it's too late for renewal. - my $maximum_renewal_date = dt_from_string($issue->issuedate, 'sql'); - $maximum_renewal_date->add( - $issuing_rule->{lengthunit} => $issuing_rule->{no_auto_renewal_after} - ); - my $now = dt_from_string; - if ( $now >= $maximum_renewal_date ) { - return ( 0, "auto_too_late" ); - } - } - if ( defined $issuing_rule->{no_auto_renewal_after_hard_limit} - and $issuing_rule->{no_auto_renewal_after_hard_limit} ne "" ) { - # If no_auto_renewal_after_hard_limit is >= today, it's also too late for renewal - if ( dt_from_string >= dt_from_string( $issuing_rule->{no_auto_renewal_after_hard_limit} ) ) { - return ( 0, "auto_too_late" ); - } - } - - if ( C4::Context->preference('OPACFineNoRenewalsBlockAutoRenew') ) { - my $fine_no_renewals = C4::Context->preference("OPACFineNoRenewals"); - my $amountoutstanding = - C4::Context->preference("OPACFineNoRenewalsIncludeCredit") - ? $patron->account->balance - : $patron->account->outstanding_debits->total_outstanding; - if ( $amountoutstanding and $amountoutstanding > $fine_no_renewals ) { - return ( 0, "auto_too_much_oweing" ); - } - } - } - - if ( defined $issuing_rule->{norenewalbefore} - and $issuing_rule->{norenewalbefore} ne "" ) - { - - # Calculate soonest renewal by subtracting 'No renewal before' from due date - my $soonestrenewal = dt_from_string( $issue->date_due, 'sql' )->subtract( - $issuing_rule->{lengthunit} => $issuing_rule->{norenewalbefore} ); - - # Depending on syspref reset the exact time, only check the date - if ( C4::Context->preference('NoRenewalBeforePrecision') eq 'date' - and $issuing_rule->{lengthunit} eq 'days' ) - { - $soonestrenewal->truncate( to => 'day' ); - } - - if ( $soonestrenewal > dt_from_string() ) - { - $auto_renew = ($issue->auto_renew && $patron->autorenew_checkouts) ? "auto_too_soon" : "too_soon"; - } - elsif ( $issue->auto_renew && $patron->autorenew_checkouts ) { - $auto_renew = "ok"; - } - } - - # Fallback for automatic renewals: - # If norenewalbefore is undef, don't renew before due date. - if ( $issue->auto_renew && $auto_renew eq "no" && $patron->autorenew_checkouts ) { - my $now = dt_from_string; - if ( $now >= dt_from_string( $issue->date_due, 'sql' ) ){ - $auto_renew = "ok"; - } else { - $auto_renew = "auto_too_soon"; - } - } + $auto_renew = _CanBookBeAutoRenewed({ + patron => $patron, + item => $item, + branchcode => $branchcode, + issue => $issue + }); + return ( 0, $auto_renew ) if $auto_renew =~ 'auto_too_soon' && $cron; + # cron wants 'too_soon' over 'on_reserve' for performance and to avoid + # extra notices being sent. Cron also implies no override + return ( 0, $auto_renew ) if $auto_renew =~ 'auto_account_expired'; + return ( 0, $auto_renew ) if $auto_renew =~ 'auto_too_late'; + return ( 0, $auto_renew ) if $auto_renew =~ 'auto_too_much_oweing'; } my ( $resfound, $resrec, $possible_reserves ) = C4::Reserves::CheckReserves($itemnumber); @@ -2918,7 +2853,7 @@ sub CanBookBeRenewed { next if IsItemOnHoldAndFound( $item->itemnumber ); while ( my $patron = $patrons->next ) { next unless IsAvailableForItemLevelRequest($item, $patron); - next unless CanItemBeReserved($patron->borrowernumber,$item->itemnumber,undef,{ignore_hold_counts=>1})->{status} eq 'OK'; + next unless CanItemBeReserved($patron,$item,undef,{ignore_hold_counts=>1})->{status} eq 'OK'; push @reservable, $item->itemnumber; if (@reservable >= @borrowernumbers) { $resfound = 0; @@ -2930,12 +2865,11 @@ sub CanBookBeRenewed { } } } - if( $cron ) { #The cron wants to return 'too_soon' over 'on_reserve' - return ( 0, $auto_renew ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok"; - return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found - } else { # For other purposes we want 'on_reserve' before 'too_soon' - return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found - return ( 0, $auto_renew ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok"; + + return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found + return ( 0, $auto_renew ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok"; + if ( GetSoonestRenewDate($borrowernumber, $itemnumber) > dt_from_string() ){ + return (0, "too_soon") unless $override_limit; } return ( 0, "auto_renew" ) if $auto_renew eq "ok" && !$override_limit; # 0 if auto-renewal should not succeed @@ -3126,7 +3060,7 @@ sub AddRenewal { } # Add the renewal to stats - UpdateStats( + C4::Stats::UpdateStats( { branch => $item_object->renewal_branchcode({branch => $branch}), type => 'renew', @@ -3255,7 +3189,6 @@ sub GetSoonestRenewDate { ); my $now = dt_from_string; - return $now unless $issuing_rule; if ( defined $issuing_rule->{norenewalbefore} and $issuing_rule->{norenewalbefore} ne "" ) @@ -3270,6 +3203,15 @@ sub GetSoonestRenewDate { $soonestrenewal->truncate( to => 'day' ); } return $soonestrenewal if $now < $soonestrenewal; + } elsif ( $itemissue->auto_renew && $patron->autorenew_checkouts ) { + # Checkouts with auto-renewing fall back to due date + my $soonestrenewal = dt_from_string( $itemissue->date_due ); + if ( C4::Context->preference('NoRenewalBeforePrecision') eq 'date' + and $issuing_rule->{lengthunit} eq 'days' ) + { + $soonestrenewal->truncate( to => 'day' ); + } + return $soonestrenewal; } return $now; } @@ -3381,19 +3323,19 @@ sub GetIssuingCharges { if ( my $item_data = $sth->fetchrow_hashref ) { $item_type = $item_data->{itemtype}; $charge = $item_data->{rentalcharge}; - # FIXME This should follow CircControl - my $branch = C4::Context::mybranch(); - my $patron = Koha::Patrons->find( $borrowernumber ); - my $discount = Koha::CirculationRules->get_effective_rule({ - categorycode => $patron->categorycode, - branchcode => $branch, - itemtype => $item_type, - rule_name => 'rentaldiscount' - }); - if ($discount) { - $charge = ( $charge * ( 100 - $discount->rule_value ) ) / 100; - } if ($charge) { + # FIXME This should follow CircControl + my $branch = C4::Context::mybranch(); + my $patron = Koha::Patrons->find( $borrowernumber ); + my $discount = Koha::CirculationRules->get_effective_rule({ + categorycode => $patron->categorycode, + branchcode => $branch, + itemtype => $item_type, + rule_name => 'rentaldiscount' + }); + if ($discount) { + $charge = ( $charge * ( 100 - $discount->rule_value ) ) / 100; + } $charge = sprintf '%.2f', $charge; # ensure no fractions of a penny returned } } @@ -3526,8 +3468,8 @@ B: sub SendCirculationAlert { my ($opts) = @_; - my ($type, $item, $borrower, $branch) = - ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch}); + my ($type, $item, $borrower, $branch, $issue) = + ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch}, $opts->{issue}); my %message_name = ( CHECKIN => 'Item_Check_in', CHECKOUT => 'Item_Checkout', @@ -3537,7 +3479,23 @@ sub SendCirculationAlert { borrowernumber => $borrower->{borrowernumber}, message_name => $message_name{$type}, }); - my $issues_table = ( $type eq 'CHECKOUT' || $type eq 'RENEWAL' ) ? 'issues' : 'old_issues'; + + + my $tables = { + items => $item->{itemnumber}, + biblio => $item->{biblionumber}, + biblioitems => $item->{biblionumber}, + borrowers => $borrower, + branches => $branch, + }; + + # TODO: Currently, we need to pass an issue_id as identifier for old_issues, but still an itemnumber for issues. + # See C4::Letters:: _parseletter_sth + if( $type eq 'CHECKIN' ){ + $tables->{old_issues} = $issue->issue_id; + } else { + $tables->{issues} = $item->{itemnumber}; + } my $schema = Koha::Database->new->schema; my @transports = keys %{ $borrower_preferences->{transports} }; @@ -3555,14 +3513,7 @@ sub SendCirculationAlert { branchcode => $branch, message_transport_type => $mtt, lang => $borrower->{lang}, - tables => { - $issues_table => $item->{itemnumber}, - 'items' => $item->{itemnumber}, - 'biblio' => $item->{biblionumber}, - 'biblioitems' => $item->{biblionumber}, - 'borrowers' => $borrower, - 'branches' => $branch, - } + tables => $tables, ) or next; C4::Context->dbh->do(q|LOCK TABLE message_queue READ|) unless $do_not_lock; @@ -4317,6 +4268,84 @@ sub _CalculateAndUpdateFine { } } +sub _CanBookBeAutoRenewed { + my ( $params ) = @_; + my $patron = $params->{patron}; + my $item = $params->{item}; + my $branchcode = $params->{branchcode}; + my $issue = $params->{issue}; + + return "no" unless $issue->auto_renew && $patron->autorenew_checkouts; + + my $issuing_rule = Koha::CirculationRules->get_effective_rules( + { + categorycode => $patron->categorycode, + itemtype => $item->effective_itemtype, + branchcode => $branchcode, + rules => [ + 'no_auto_renewal_after', + 'no_auto_renewal_after_hard_limit', + 'lengthunit', + 'norenewalbefore', + ] + } + ); + + if ( $patron->category->effective_BlockExpiredPatronOpacActions and $patron->is_expired ) { + return 'auto_account_expired'; + } + + if ( defined $issuing_rule->{no_auto_renewal_after} + and $issuing_rule->{no_auto_renewal_after} ne "" ) { + # Get issue_date and add no_auto_renewal_after + # If this is greater than today, it's too late for renewal. + my $maximum_renewal_date = dt_from_string($issue->issuedate, 'sql'); + $maximum_renewal_date->add( + $issuing_rule->{lengthunit} => $issuing_rule->{no_auto_renewal_after} + ); + my $now = dt_from_string; + if ( $now >= $maximum_renewal_date ) { + return "auto_too_late"; + } + } + if ( defined $issuing_rule->{no_auto_renewal_after_hard_limit} + and $issuing_rule->{no_auto_renewal_after_hard_limit} ne "" ) { + # If no_auto_renewal_after_hard_limit is >= today, it's also too late for renewal + if ( dt_from_string >= dt_from_string( $issuing_rule->{no_auto_renewal_after_hard_limit} ) ) { + return "auto_too_late"; + } + } + + if ( C4::Context->preference('OPACFineNoRenewalsBlockAutoRenew') ) { + my $fine_no_renewals = C4::Context->preference("OPACFineNoRenewals"); + my $amountoutstanding = + C4::Context->preference("OPACFineNoRenewalsIncludeCredit") + ? $patron->account->balance + : $patron->account->outstanding_debits->total_outstanding; + if ( $amountoutstanding and $amountoutstanding > $fine_no_renewals ) { + return "auto_too_much_oweing"; + } + } + + if ( defined $issuing_rule->{norenewalbefore} + and $issuing_rule->{norenewalbefore} ne "" ) { + if ( GetSoonestRenewDate($patron->id, $item->id) > dt_from_string()) { + return "auto_too_soon"; + } else { + return "ok"; + } + } + + # Fallback for automatic renewals: + # If norenewalbefore is undef, don't renew before due date. + my $now = dt_from_string; + if ( $now >= dt_from_string( $issue->date_due, 'sql' ) ){ + return "ok"; + } else { + return "auto_too_soon"; + } +} + sub _item_denied_renewal { my ($params) = @_;