use YAML::XS;
use Encode;
-use Koha::DateUtils qw( dt_from_string output_pref );
use C4::Context;
use C4::Stats qw( UpdateStats );
use C4::Reserves qw( CheckReserves CanItemBeReserved MoveReserve ModReserve ModReserveMinusPriority RevertWaitingStatus IsItemOnHoldAndFound IsAvailableForItemLevelRequest ItemsAnyAvailableAndNotRestricted );
use Koha::AuthorisedValues;
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
use Koha::Biblioitems;
-use Koha::DateUtils qw( dt_from_string output_pref );
+use Koha::DateUtils qw( dt_from_string );
use Koha::Calendar;
use Koha::Checkouts;
use Koha::Illrequests;
GetBranchBorrowerCircRule
GetBranchItemRule
GetBiblioIssues
- GetOpenIssue
GetUpcomingDueIssues
CheckIfIssuedToPatron
IsItemIssued
transferbook
TooMany
- GetTransfers
GetTransfersFromTo
updateWrongTransfer
CalcDateDue
DeleteOfflineOperation
ProcessOfflineOperation
ProcessOfflinePayment
+ ProcessOfflineIssue
);
push @EXPORT_OK, '_GetCircControlBranch'; # This is wrong!
}
} elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
$checkouts = $patron->checkouts; # if branch is the patron's home branch, then count all loans by patron
} else {
+ my $branch_type = C4::Context->preference('HomeOrHoldingBranch') || 'homebranch';
$checkouts = $patron->checkouts->search(
- { 'item.homebranch' => $maxissueqty_rule->branchcode } );
+ { "item.$branch_type" => $maxissueqty_rule->branchcode } );
}
} else {
$checkouts = $patron->checkouts; # if rule is not branch specific then count all loans by patron
} elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
$checkouts = $patron->checkouts; # if branch is the patron's home branch, then count all loans by patron
} else {
+ my $branch_type = C4::Context->preference('HomeOrHoldingBranch') || 'homebranch';
$checkouts = $patron->checkouts->search(
- { 'item.homebranch' => $branch},
- { prefetch => 'item' } );
+ { "item.$branch_type" => $branch},
+ { prefetch => 'item' } );
}
my $checkout_count = $checkouts->count;
my $now = dt_from_string();
$duedate ||= CalcDateDue( $now, $effective_itemtype, $circ_library->branchcode, $patron_unblessed );
if (DateTime->compare($duedate,$now) == -1 ) { # duedate cannot be before now
- $needsconfirmation{INVALID_DATE} = output_pref($duedate);
+ $needsconfirmation{INVALID_DATE} = $duedate;
}
my $fees = Koha::Charges::Fees->new(
#
if ( $patron->category->category_type eq 'X' && ( $item_object->barcode )) {
# stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1 .
- C4::Stats::UpdateStats({
- branch => C4::Context->userenv->{'branch'},
- type => 'localuse',
- itemnumber => $item_object->itemnumber,
- itemtype => $effective_itemtype,
- borrowernumber => $patron->borrowernumber,
- ccode => $item_object->ccode}
- );
+ C4::Stats::UpdateStats(
+ {
+ branch => C4::Context->userenv->{'branch'},
+ type => 'localuse',
+ itemnumber => $item_object->itemnumber,
+ itemtype => $effective_itemtype,
+ borrowernumber => $patron->borrowernumber,
+ ccode => $item_object->ccode,
+ categorycode => $patron->categorycode,
+ }
+ );
ModDateLastSeen( $item_object->itemnumber ); # FIXME Move to Koha::Item
return( { STATS => 1 }, {});
}
my $check = checkHighHolds( $item_object, $patron );
if ( $check->{exceeded} ) {
+ my $highholds = {
+ num_holds => $check->{outstanding},
+ duration => $check->{duration},
+ returndate => $check->{due_date},
+ };
if ($override_high_holds) {
- $alerts{HIGHHOLDS} = {
- num_holds => $check->{outstanding},
- duration => $check->{duration},
- returndate => output_pref( { dt => dt_from_string($check->{due_date}), dateformat => 'iso', timeformat => '24hr' }),
- };
+ $alerts{HIGHHOLDS} = $highholds;
}
else {
- $needsconfirmation{HIGHHOLDS} = {
- num_holds => $check->{outstanding},
- duration => $check->{duration},
- returndate => output_pref( { dt => dt_from_string($check->{due_date}), dateformat => 'iso', timeformat => '24hr' }),
- };
+ $needsconfirmation{HIGHHOLDS} = $highholds;
}
}
}
=item C<$cancelreserve> is 1 to override and cancel any pending reserves for the item (optional).
-=item C<$issuedate> is the date to issue the item in iso (YYYY-MM-DD) format (optional).
-Defaults to today. Unlike C<$datedue>, NOT a DateTime object, unfortunately.
+=item C<$issuedate> is a DateTime object for the date to issue the item (optional).
+Defaults to today.
AddIssue does the following things :
my $issue_attributes = {
borrowernumber => $borrower->{'borrowernumber'},
- issuedate => $issuedate->strftime('%Y-%m-%d %H:%M:%S'),
- date_due => $datedue->strftime('%Y-%m-%d %H:%M:%S'),
+ issuedate => $issuedate,
+ date_due => $datedue,
branchcode => C4::Context->userenv->{'branch'},
onsite_checkout => $onsite_checkout,
auto_renew => $auto_renew ? 1 : 0,
location => $item_object->location,
borrowernumber => $borrower->{'borrowernumber'},
ccode => $item_object->ccode,
+ categorycode => $borrower->{'categorycode'}
}
);
from_any_library: Holds allowed from any patron.
from_local_hold_group: Holds allowed from libraries in hold group
-returnbranch => branch to which to return item. Possible values:
- noreturn: do not return, let item remain where checked in (floating collections)
- homebranch: return to item's home branch
- holdingbranch: return to issuer branch
-
This searches branchitemrules in the following order:
* Same branchcode and itemtype
my $rules = Koha::CirculationRules->get_effective_rules({
branchcode => $branchcode,
itemtype => $itemtype,
- rules => ['holdallowed', 'hold_fulfillment_policy', 'returnbranch']
+ rules => ['holdallowed', 'hold_fulfillment_policy']
});
# built-in default circulation rule
$rules->{holdallowed} //= 'from_any_library';
$rules->{hold_fulfillment_policy} //= 'any';
- $rules->{returnbranch} //= 'homebranch';
return $rules;
}
}
# full item data, but no borrowernumber or checkout info (no issue)
- my $hbr = GetBranchItemRule($item->homebranch, $itemtype)->{'returnbranch'} || "homebranch";
+ my $hbr = Koha::CirculationRules->get_return_branch_policy($item);
# get the proper branch to which to return the item
my $returnbranch = $hbr ne 'noreturn' ? $item->$hbr : $branch;
# if $hbr was "noreturn" or any other non-item table value, then it should 'float' (i.e. stay at this branch)
foreach my $key ( keys %$rules ) {
if ( $item->notforloan eq $key ) {
$messages->{'NotForLoanStatusUpdated'} = { from => $item->notforloan, to => $rules->{$key} };
- $item->notforloan($rules->{$key})->store({ log_action => 0, skip_record_index => 1, skip_holds_queue => 1 });
+ $item->notforloan($rules->{$key})->store({ log_action => 0, skip_record_index => 1, skip_holds_queue => 1 }) unless $rules->{$key} eq 'ONLYMESSAGE';
last;
}
}
for my $message (@object_messages) {
$messages->{'LostItemFeeRefunded'} = 1
if $message->message eq 'lost_refunded';
+ $messages->{'ProcessingFeeRefunded'} = 1
+ if $message->message eq 'processing_refunded';
$messages->{'LostItemFeeRestored'} = 1
if $message->message eq 'lost_restored';
}
# Record the fact that this book was returned.
+ my $categorycode = $patron_unblessed ? $patron_unblessed->{categorycode} : undef;
C4::Stats::UpdateStats({
branch => $branch,
type => $stat_type,
location => $item->location,
borrowernumber => $borrowernumber,
ccode => $item->ccode,
+ categorycode => $categorycode,
});
# Send a check-in slip. # NOTE: borrower may be undef. Do not try to send messages then.
return $branch;
}
-=head2 GetOpenIssue
-
- $issue = GetOpenIssue( $itemnumber );
-
-Returns the row from the issues table if the item is currently issued, undef if the item is not currently issued
-
-C<$itemnumber> is the item's itemnumber
-
-Returns a hashref
-
-=cut
-
-sub GetOpenIssue {
- my ( $itemnumber ) = @_;
- return unless $itemnumber;
- my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare( "SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
- $sth->execute( $itemnumber );
- return $sth->fetchrow_hashref();
-
-}
-
=head2 GetUpcomingDueIssues
my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
my $item = Koha::Items->find($itemnumber) or return ( 0, 'no_item' );
my $issue = $item->checkout or return ( 0, 'no_checkout' );
return ( 0, 'onsite_checkout' ) if $issue->onsite_checkout;
- return ( 0, 'item_denied_renewal') if _item_denied_renewal({ item => $item });
+ return ( 0, 'item_denied_renewal') if $item->is_denied_renewal;
my $patron = $issue->patron or return;
return ( 0, "too_unseen" )
if C4::Context->preference('UnseenRenewals') &&
- $issuing_rule->{unseen_renewals_allowed} &&
+ looks_like_number($issuing_rule->{unseen_renewals_allowed}) &&
$issuing_rule->{unseen_renewals_allowed} <= $issue->unseen_renewals;
my $overduesblockrenewing = C4::Context->preference('OverduesBlockRenewing');
}
}
- # Note: possible_reserves will contain all title level holds on this bib and item level
- # holds on the checked out item
- my ( $resfound, $resrec, $possible_reserves ) = C4::Reserves::CheckReserves($itemnumber);
-
- # If next hold is non priority, then check if any hold with priority (non_priority = 0) exists for the same biblionumber.
- if ( $resfound && $resrec->{non_priority} ) {
- $resfound = Koha::Holds->search(
- { biblionumber => $resrec->{biblionumber}, non_priority => 0 } )
- ->count > 0;
- }
-
-
-
- # This item can fill one or more unfilled reserve, can those unfilled reserves
- # all be filled by other available items?
- if ( $resfound
- && C4::Context->preference('AllowRenewalIfOtherItemsAvailable') )
- {
- my $item_holds = Koha::Holds->search( { itemnumber => $itemnumber, found => undef } )->count();
- if ($item_holds) {
- # There is an item level hold on this item, no other item can fill the hold
- $resfound = 1;
- }
- else {
+ my $fillable_holds = Koha::Holds->search(
+ {
+ biblionumber => $item->biblionumber,
+ non_priority => 0,
+ found => undef,
+ reservedate => { '<=' => \'NOW()' },
+ suspend => 0,
+ },
+ { prefetch => 'patron' }
+ );
+ if ( $fillable_holds->count ) {
+ if ( C4::Context->preference('AllowRenewalIfOtherItemsAvailable') ) {
+ my @possible_holds = $fillable_holds->as_list;
# Get all other items that could possibly fill reserves
# FIXME We could join reserves (or more tables) here to eliminate some checks later
- my $items = Koha::Items->search({
- biblionumber => $resrec->{biblionumber},
+ my @other_items = Koha::Items->search({
+ biblionumber => $item->biblionumber,
onloan => undef,
notforloan => 0,
- -not => { itemnumber => $itemnumber }
- });
- my $item_count = $items->count();
-
- # Get all other reserves that could have been filled by this item
- my @borrowernumbers = map { $_->{borrowernumber} } @$possible_reserves;
- # Note: fetching the patrons in this manner means that a patron with 2 holds will
- # not block renewal if one reserve can be satisfied i.e. each patron is checked once
- my $patrons = Koha::Patrons->search({
- borrowernumber => { -in => \@borrowernumbers }
- });
- my $patron_count = $patrons->count();
+ -not => { itemnumber => $itemnumber } })->as_list;
- return ( 0, "on_reserve" ) if ($patron_count > $item_count);
- # We cannot possibly fill all reserves if we don't have enough items
+ return ( 0, "on_reserve" ) if @possible_holds && (scalar @other_items < scalar @possible_holds);
- # If we can fill each hold that has been found with the available items on the record
- # then the patron can renew. If we cannot, they cannot renew.
- # FIXME This code does not check whether the item we are renewing can fill
- # any of the existing reserves.
- my $reservable = 0;
my %matched_items;
- my $seen = 0;
- PATRON: while ( my $patron = $patrons->next ) {
- # If there is a reserve that cannot be filled we are done
- return ( 0, "on_reserve" ) if ( $seen > $reservable );
- my $items_any_available = ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron });
- while ( my $other_item = $items->next ) {
- next if defined $matched_items{$other_item->itemnumber};
- next if IsItemOnHoldAndFound( $other_item->itemnumber );
- next unless IsAvailableForItemLevelRequest($other_item, $patron, undef, $items_any_available);
- next unless CanItemBeReserved($patron,$other_item,undef,{ignore_hold_counts=>1})->{status} eq 'OK';
- # NOTE: At checkin we call 'CheckReserves' which checks hold 'policy'
- # CanItemBeReserved checks 'rules' and 'policies' which means
- # items will fill holds at checkin that are rejected here
- $reservable++;
- if ($reservable >= $patron_count) {
- $resfound = 0;
- last PATRON;
- }
- $matched_items{$other_item->itemnumber} = 1;
- last;
+ foreach my $possible_hold (@possible_holds) {
+ my $fillable = 0;
+ my $patron_with_reserve = Koha::Patrons->find($possible_hold->borrowernumber);
+ my $items_any_available = ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron_with_reserve });
+
+ # FIXME: We are not checking whether the item we are renewing can fill the hold
+
+ foreach my $other_item (@other_items) {
+ next if defined $matched_items{$other_item->itemnumber};
+ next if IsItemOnHoldAndFound( $other_item->itemnumber );
+ next unless IsAvailableForItemLevelRequest($other_item, $patron_with_reserve, undef, $items_any_available);
+ next unless CanItemBeReserved($patron_with_reserve,$other_item,undef,{ignore_hold_counts=>1})->{status} eq 'OK';
+ # NOTE: At checkin we call 'CheckReserves' which checks hold 'policy'
+ # CanItemBeReserved checks 'rules' and 'policies' which means
+ # items will fill holds at checkin that are rejected here
+ $fillable = 1;
+ $matched_items{$other_item->itemnumber} = 1;
+ last;
}
- $items->reset;
- $seen++;
+ return ( 0, "on_reserve" ) unless $fillable;
}
+
+ } else {
+ my ($status, $matched_reserve, $possible_reserves) = CheckReserves($itemnumber);
+ return ( 0, "on_reserve" ) if $status;
}
}
- return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found
return ( 0, $auto_renew, { soonest_renew_date => $soonest } ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
- $soonest = GetSoonestRenewDate($borrowernumber, $itemnumber);
+ $soonest = GetSoonestRenewDate($issue);
if ( $soonest > dt_from_string() ){
return (0, "too_soon", { soonest_renew_date => $soonest } ) unless $override_limit;
}
$borrowernumber ||= $issue->borrowernumber;
if ( defined $datedue && ref $datedue ne 'DateTime' ) {
- carp 'Invalid date passed to AddRenewal.';
- return;
+ $datedue = dt_from_string($datedue, 'sql');
}
my $patron = Koha::Patrons->find( $borrowernumber ) or return; # FIXME Should do more than just return
rule_name => 'unseen_renewals_allowed'
}
);
- if (!$seen && $rule && $rule->rule_value) {
+ if (!$seen && $rule && looks_like_number($rule->rule_value)) {
$unseen_renewals++;
} else {
# If the renewal is seen, unseen should revert to 0
location => $item_object->location,
borrowernumber => $borrowernumber,
ccode => $item_object->ccode,
+ categorycode => $patron->categorycode,
}
);
=head2 GetSoonestRenewDate
- $NoRenewalBeforeThisDate = &GetSoonestRenewDate($borrowernumber, $itemnumber);
+ $NoRenewalBeforeThisDate = &GetSoonestRenewDate($checkout);
Find out the soonest possible renew date of a borrowed item.
-C<$borrowernumber> is the borrower number of the patron who currently
-has the item on loan.
-
-C<$itemnumber> is the number of the item to renew.
+C<$checkout> is the checkout object to renew.
C<$GetSoonestRenewDate> returns the DateTime of the soonest possible
renew date, based on the value "No renewal before" of the applicable
issuing rule. Returns the current date if the item can already be
-renewed, and returns undefined if the borrower, loan, or item
+renewed, and returns undefined if the patron, item, or checkout
cannot be found.
=cut
sub GetSoonestRenewDate {
- my ( $borrowernumber, $itemnumber ) = @_;
-
- my $dbh = C4::Context->dbh;
+ my ( $checkout ) = @_;
- my $item = Koha::Items->find($itemnumber) or return;
- my $itemissue = $item->checkout or return;
-
- $borrowernumber ||= $itemissue->borrowernumber;
- my $patron = Koha::Patrons->find( $borrowernumber )
- or return;
+ my $item = $checkout->item or return;
+ my $patron = $checkout->patron or return;
my $branchcode = _GetCircControlBranch( $item->unblessed, $patron->unblessed );
my $issuing_rule = Koha::CirculationRules->get_effective_rules(
and $issuing_rule->{norenewalbefore} ne "" )
{
my $soonestrenewal =
- dt_from_string( $itemissue->date_due )->subtract(
+ dt_from_string( $checkout->date_due )->subtract(
$issuing_rule->{lengthunit} => $issuing_rule->{norenewalbefore} );
if ( C4::Context->preference('NoRenewalBeforePrecision') eq 'date'
$soonestrenewal->truncate( to => 'day' );
}
return $soonestrenewal if $now < $soonestrenewal;
- } elsif ( $itemissue->auto_renew && $patron->autorenew_checkouts ) {
+ } elsif ( $checkout->auto_renew && $patron->autorenew_checkouts ) {
# Checkouts with auto-renewing fall back to due date
- my $soonestrenewal = dt_from_string( $itemissue->date_due );
+ my $soonestrenewal = dt_from_string( $checkout->date_due );
if ( C4::Context->preference('NoRenewalBeforePrecision') eq 'date'
and $issuing_rule->{lengthunit} eq 'days' )
{
);
}
-=head2 GetTransfers
-
- GetTransfers($itemnumber);
-
-=cut
-
-sub GetTransfers {
- my ($itemnumber) = @_;
-
- my $dbh = C4::Context->dbh;
-
- my $query = '
- SELECT datesent,
- frombranch,
- tobranch,
- branchtransfer_id,
- daterequested,
- reason
- FROM branchtransfers
- WHERE itemnumber = ?
- AND datearrived IS NULL
- AND datecancelled IS NULL
- ';
- my $sth = $dbh->prepare($query);
- $sth->execute($itemnumber);
- my @row = $sth->fetchrow_array();
- return @row;
-}
-
=head2 GetTransfersFromTo
@results = GetTransfersFromTo($frombranch,$tobranch);
if ( $item ) {
my $itemnumber = $item->itemnumber;
- my $issue = GetOpenIssue( $itemnumber );
+ my $issue = $item->checkout;
if ( $issue ) {
my $leave_item_lost = C4::Context->preference("BlockReturnOfLostItems") ? 1 : 0;
ModDateLastSeen( $itemnumber, $leave_item_lost );
MarkIssueReturned(
- $issue->{borrowernumber},
+ $issue->borrowernumber,
$itemnumber,
$operation->{timestamp},
);
return "Barcode not found.";
}
my $itemnumber = $item->itemnumber;
- my $issue = GetOpenIssue( $itemnumber );
+ my $issue = $item->checkout;
- if ( $issue and ( $issue->{borrowernumber} ne $patron->borrowernumber ) ) { # Item already issued to another patron mark it returned
+ if ( $issue and ( $issue->borrowernumber ne $patron->borrowernumber ) ) { # Item already issued to another patron mark it returned
MarkIssueReturned(
- $issue->{borrowernumber},
+ $issue->borrowernumber,
$itemnumber,
$operation->{timestamp},
);
# we only need to calculate and change the fines if we want to do that on return
# Should be on for hourly loans
my $control = C4::Context->preference('CircControl');
+ my $branch_type = C4::Context->preference('HomeOrHoldingBranch') || 'homebranch';
my $control_branchcode =
- ( $control eq 'ItemHomeLibrary' ) ? $item->{homebranch}
+ ( $control eq 'ItemHomeLibrary' ) ? $item->{$branch_type}
: ( $control eq 'PatronLibrary' ) ? $borrower->{branchcode}
: $issue->branchcode;
itemnumber => $issue->itemnumber,
borrowernumber => $issue->borrowernumber,
amount => $amount,
- due => output_pref($datedue),
+ due => $datedue,
});
}
elsif ($return_date) {
itemnumber => $issue->itemnumber,
borrowernumber => $issue->borrowernumber,
amount => 0,
- due => output_pref($datedue),
+ due => $datedue,
});
}
}
}
}
- my $soonest = GetSoonestRenewDate($patron->id, $item->id);
+ my $soonest = GetSoonestRenewDate($issue);
if ( $soonest > dt_from_string() )
{
return ( "auto_too_soon", $soonest );
return "ok";
}
-sub _item_denied_renewal {
- my ($params) = @_;
-
- my $item = $params->{item};
- return unless $item;
-
- my $denyingrules = Koha::Config::SysPrefs->find('ItemsDeniedRenewal')->get_yaml_pref_hash();
- return unless $denyingrules;
- foreach my $field (keys %$denyingrules) {
- my $val = $item->$field;
- if( !defined $val) {
- if ( any { !defined $_ } @{$denyingrules->{$field}} ){
- return 1;
- }
- } elsif (any { defined($_) && $val eq $_ } @{$denyingrules->{$field}}) {
- # If the results matches the values in the syspref
- # We return true if match found
- return 1;
- }
- }
- return 0;
-}
1;