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::Debug;
-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;
use Koha::Checkouts::ReturnClaims;
use Koha::SearchEngine::Indexer;
use Koha::Exceptions::Checkout;
-use Carp;
-use List::MoreUtils qw( uniq any );
+use Koha::Plugins;
+use Koha::Recalls;
+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
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;
The item was eligible to be transferred. Barring problems communicating with the database, the transfer should indeed have succeeded. The value should be ignored.
+=item C<RecallPlacedAtHoldingBranch>
+
+A recall for this item was found, and the transfer has already been completed as the item's branch matches the recall's pickup branch.
+
+=item C<RecallFound>
+
+A recall for this item was found, and the item needs to be transferred to the recall's pickup branch.
+
=back
=back
$dotransfer = 0 unless $ignoreRs;
}
+ # find recall
+ if ( C4::Context->preference('UseRecalls') ) {
+ my $recall = Koha::Recalls->find({ itemnumber => $itemnumber, status => 'in_transit' });
+ if ( defined $recall ) {
+ # do a transfer if the recall branch is different to the item holding branch
+ if ( $recall->branchcode eq $fbr ) {
+ $dotransfer = 0;
+ $messages->{'RecallPlacedAtHoldingBranch'} = 1;
+ } else {
+ $dotransfer = 1;
+ $messages->{'RecallFound'} = $recall;
+ }
+ }
+ }
+
#actually do the transfer....
if ($dotransfer) {
ModItemTransfer( $itemnumber, $fbr, $tbr, $trigger );
$checkouts = $patron->checkouts; # if branch is the patron's home branch, then count all loans by patron
} else {
$checkouts = $patron->checkouts->search(
- { 'item.homebranch' => $maxissueqty_rule->branchcode },
- { prefetch => 'item' } );
+ { 'item.homebranch' => $maxissueqty_rule->branchcode } );
}
} else {
$checkouts = $patron->checkouts; # if rule is not branch specific then count all loans by patron
}
+ $checkouts = $checkouts->search(undef, { prefetch => 'item' });
+
my $sum_checkouts;
my $rule_itemtype = $maxissueqty_rule->itemtype;
- while ( my $c = $checkouts->next ) {
- my $itemtype = $c->item->effective_itemtype;
- my @types;
- unless ( $rule_itemtype ) {
- # matching rule has the default item type, so count only
- # those existing loans that don't fall under a more
- # specific rule
- @types = Koha::CirculationRules->search(
- {
- branchcode => $maxissueqty_rule->branchcode,
- categorycode => [ $maxissueqty_rule->categorycode, $cat_borrower ],
- itemtype => { '!=' => undef },
- rule_name => 'maxissueqty'
- }
- )->get_column('itemtype');
- next if grep {$_ eq $itemtype} @types;
- } else {
- my @types;
- if ( $parent_maxissueqty_rule ) {
+ my @types;
+ unless ( $rule_itemtype ) {
+ # matching rule has the default item type, so count only
+ # those existing loans that don't fall under a more
+ # specific rule
+ @types = Koha::CirculationRules->search(
+ {
+ branchcode => $maxissueqty_rule->branchcode,
+ categorycode => [ $maxissueqty_rule->categorycode, $cat_borrower ],
+ itemtype => { '!=' => undef },
+ rule_name => 'maxissueqty'
+ }
+ )->get_column('itemtype');
+ } else {
+ if ( $parent_maxissueqty_rule ) {
# if we have a parent item type then we count loans of the
# specific item type or its siblings or parent
- my $children = Koha::ItemTypes->search({ parent_type => $parent_type });
- @types = $children->get_column('itemtype');
- push @types, $parent_type;
- } elsif ( $child_types ) {
+ my $children = Koha::ItemTypes->search({ parent_type => $parent_type });
+ @types = $children->get_column('itemtype');
+ push @types, $parent_type;
+ } elsif ( $child_types ) {
# If we are a parent type, we need to count all child types and our own type
- @types = $child_types->get_column('itemtype');
- push @types, $type; # And don't forget to count our own types
- } else { push @types, $type; } # Otherwise only count the specific itemtype
+ @types = $child_types->get_column('itemtype');
+ push @types, $type; # And don't forget to count our own types
+ } else {
+ # Otherwise only count the specific itemtype
+ push @types, $type;
+ }
+ }
+
+ while ( my $c = $checkouts->next ) {
+ my $itemtype = $c->item->effective_itemtype;
+ unless ( $rule_itemtype ) {
+ next if grep {$_ eq $itemtype} @types;
+ } else {
next unless grep {$_ eq $itemtype} @types;
}
+
$sum_checkouts->{total}++;
$sum_checkouts->{onsite_checkouts}++ if $c->onsite_checkout;
$sum_checkouts->{itemtype}->{$itemtype}++;
if the borrower borrows to much things
+=head3 RECALLED
+
+recalled by someone else
+
=cut
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,
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;
my $no_issues_charge_guarantors = C4::Context->preference("NoIssuesChargeGuarantorsWithGuarantees");
$no_issues_charge_guarantors = undef unless looks_like_number( $no_issues_charge_guarantors );
if ( defined $no_issues_charge_guarantors ) {
- my $guarantors_non_issues_charges += $patron->relationships_debt({ include_guarantors => 1, only_this_guarantor => 0, include_this_patron => 1 });
+ my $guarantors_non_issues_charges = $patron->relationships_debt({ include_guarantors => 1, only_this_guarantor => 0, include_this_patron => 1 });
if ( $guarantors_non_issues_charges > $no_issues_charge_guarantors && !$inprocess && !$allowfineoverride) {
$issuingimpossible{DEBT_GUARANTORS} = $guarantors_non_issues_charges;
}
}
- unless ( $ignore_reserves ) {
+ my $recall;
+ # CHECK IF ITEM HAS BEEN RECALLED BY ANOTHER PATRON
+ # Only bother doing this if UseRecalls is enabled and the item is recallable
+ # Don't look at recalls that are in transit
+ if ( C4::Context->preference('UseRecalls') and $item_object->can_be_waiting_recall ) {
+ my @recalls = $biblio->recalls({},{ order_by => { -asc => 'recalldate' } })->filter_by_current->as_list;
+
+ foreach my $r ( @recalls ) {
+ if ( $r->itemnumber and
+ $r->itemnumber == $item_object->itemnumber and
+ $r->borrowernumber == $patron->borrowernumber and
+ ( $r->waiting or $r->requested ) ) {
+ $messages{RECALLED} = $r->recall_id;
+ $recall = $r;
+ # this item is recalled by or already waiting for this borrower and the recall can be fulfilled
+ last;
+ }
+ elsif ( $r->itemnumber and
+ $r->itemnumber == $item_object->itemnumber and
+ $r->in_transit ) {
+ # recalled item is in transit
+ $issuingimpossible{RECALLED_INTRANSIT} = $r->branchcode;
+ }
+ elsif ( $r->item_level_recall and
+ $r->itemnumber == $item_object->itemnumber and
+ $r->borrowernumber != $patron->borrowernumber and
+ !$r->in_transit ) {
+ # this specific item has been recalled by a different patron
+ $needsconfirmation{RECALLED} = $r;
+ $recall = $r;
+ last;
+ }
+ elsif ( !$r->item_level_recall and
+ $r->borrowernumber != $patron->borrowernumber and
+ !$r->in_transit ) {
+ # a different patron has placed a biblio-level recall and this item is eligible to fill it
+ $needsconfirmation{RECALLED} = $r;
+ $recall = $r;
+ last;
+ }
+ }
+ }
+
+ unless ( $ignore_reserves and defined $recall ) {
# See if the item is on reserve.
my ( $restype, $res ) = C4::Reserves::CheckReserves( $item_object->itemnumber );
if ($restype) {
## 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) {
=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,
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();
}
# 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;
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',
}
* RESERVE PLACED ?
- fill reserve if reserve to this patron
- cancel reserve or not, otherwise
+ * RECALL PLACED ?
+ - fill recall if recall to this patron
+ - cancel recall or not
+ - revert recall's waiting status or not
* TRANSFERT PENDING ?
- complete the transfert
* ISSUE THE BOOK
my $onsite_checkout = $params && $params->{onsite_checkout} ? 1 : 0;
my $switch_onsite_checkout = $params && $params->{switch_onsite_checkout};
my $auto_renew = $params && $params->{auto_renew};
+ my $cancel_recall = $params && $params->{cancel_recall};
+ my $recall_id = $params && $params->{recall_id};
my $dbh = C4::Context->dbh;
my $barcodecheck = CheckValidBarcode($barcode);
$item_object->discard_changes;
}
+ if ( C4::Context->preference('UseRecalls') ) {
+ Koha::Recalls->move_recall(
+ {
+ action => $cancel_recall,
+ recall_id => $recall_id,
+ item => $item_object,
+ borrowernumber => $borrower->{borrowernumber},
+ }
+ );
+ }
+
C4::Reserves::MoveReserve( $item_object->itemnumber, $borrower->{'borrowernumber'}, $cancelreserve );
# Starting process for transfer job (checking transfert and validate it if we have one)
)->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.
}
# Record the fact that this book was issued.
- &UpdateStats(
+ C4::Stats::UpdateStats(
{
branch => C4::Context->userenv->{'branch'},
type => ( $onsite_checkout ? 'onsite_checkout' : 'issue' ),
my ( $branchcode, $itemtype ) = @_;
# Search for rules!
- my $holdallowed_rule = Koha::CirculationRules->get_effective_rule(
- {
- branchcode => $branchcode,
- itemtype => $itemtype,
- rule_name => 'holdallowed',
- }
- );
- my $hold_fulfillment_policy_rule = Koha::CirculationRules->get_effective_rule(
- {
- branchcode => $branchcode,
- itemtype => $itemtype,
- rule_name => 'hold_fulfillment_policy',
- }
- );
- my $returnbranch_rule = Koha::CirculationRules->get_effective_rule(
- {
- branchcode => $branchcode,
- itemtype => $itemtype,
- rule_name => 'returnbranch',
- }
- );
+ my $rules = Koha::CirculationRules->get_effective_rules({
+ branchcode => $branchcode,
+ itemtype => $itemtype,
+ rules => ['holdallowed', 'hold_fulfillment_policy', 'returnbranch']
+ });
# built-in default circulation rule
- my $rules;
- $rules->{holdallowed} = defined $holdallowed_rule
- ? $holdallowed_rule->rule_value
- : 'from_any_library';
- $rules->{hold_fulfillment_policy} = defined $hold_fulfillment_policy_rule
- ? $hold_fulfillment_policy_rule->rule_value
- : 'any';
- $rules->{returnbranch} = defined $returnbranch_rule
- ? $returnbranch_rule->rule_value
- : 'homebranch';
+ $rules->{holdallowed} //= 'from_any_library';
+ $rules->{hold_fulfillment_policy} //= 'any';
+ $rules->{returnbranch} //= 'homebranch';
return $rules;
}
If AutomaticItemReturn is disabled, return branch is given as value of NeedsTransfer.
+=item C<RecallFound>
+
+This item can fill a recall. The recall object is returned. If the recall pickup branch differs from
+the branch this item is being returned at, C<RecallNeedsTransfer> is also returned which contains this
+branchcode.
+
+=item C<TransferredRecall>
+
+This item has been transferred to this branch to fill a recall. The recall object is returned.
+
=back
C<$iteminformation> is a reference-to-hash, giving information about the
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});
}
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;
+ }
}
}
}
if ( $transfer->tobranch eq $branch ) {
$transfer->receive;
$messages->{'TransferArrived'} = $transfer->frombranch;
+ # validTransfer=1 allows us returning the item back if the reserve is cancelled
+ $validTransfer = 1 if $transfer->reason eq 'Reserve';
}
else {
$messages->{'WrongTransfer'} = $transfer->tobranch;
if ( $transfer->tobranch eq $branch ) {
$transfer->receive;
$messages->{'TransferArrived'} = $transfer->frombranch;
+ # validTransfer=1 allows us returning the item back if the reserve is cancelled
+ $validTransfer = 1 if $transfer->reason eq 'Reserve';
}
else {
$messages->{'WasTransfered'} = $transfer->tobranch;
}
}
+ # find recalls...
+ if ( C4::Context->preference('UseRecalls') ) {
+ # check if this item is recallable first, which includes checking if UseRecalls syspref is enabled
+ my $recall = undef;
+ $recall = $item->check_recalls if $item->can_be_waiting_recall;
+ if ( defined $recall ) {
+ $messages->{RecallFound} = $recall;
+ if ( $recall->branchcode ne $branch ) {
+ $messages->{RecallNeedsTransfer} = $branch;
+ }
+ }
+ }
+
# find reserves.....
# launch the Checkreserves routine to find any holds
my ($resfound, $resrec);
}
# Record the fact that this book was returned.
- UpdateStats({
+ C4::Stats::UpdateStats({
branch => $branch,
type => $stat_type,
itemnumber => $itemnumber,
item => $item->unblessed,
borrower => $patron->unblessed,
branch => $branch,
+ issue => $issue
});
}
$request->status('RET') if $request;
}
+ if ( C4::Context->preference('UseRecalls') ) {
+ # all recalls that have triggered a transfer will have an allocated itemnumber
+ my $transfer_recall = Koha::Recalls->find({ itemnumber => $item->itemnumber, status => 'in_transit' });
+ if ( $transfer_recall and $transfer_recall->branchcode eq $branch ) {
+ $messages->{TransferredRecall} = $transfer_recall;
+ }
+ }
+
# Transfer to returnbranch if Automatic transfer set or append message NeedsTransfer
if ( $validTransfer && !C4::RotatingCollections::isItemInAnyCollection( $item->itemnumber )
&& ( $doreturn or $messages->{'NotIssued'} )
and !$resfound
and ( $branch ne $returnbranch )
and not $messages->{'WrongTransfer'}
- and not $messages->{'WasTransfered'} )
+ and not $messages->{'WasTransfered'}
+ and not $messages->{TransferredRecall}
+ and not $messages->{RecallNeedsTransfer} )
{
my $BranchTransferLimitsType = C4::Context->preference("BranchTransferLimitsType") eq 'itemtype' ? 'effective_itemtype' : 'ccode';
if (C4::Context->preference("AutomaticItemReturn" ) or
(C4::Context->preference("UseBranchTransferLimits") and
! IsBranchTransferAllowed($branch, $returnbranch, $item->$BranchTransferLimitsType )
)) {
- $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s, %s)", $item->itemnumber,$branch, $returnbranch, $transfer_trigger;
- $debug and warn "item: " . Dumper($item->unblessed);
ModItemTransfer($item->itemnumber, $branch, $returnbranch, $transfer_trigger, { skip_record_index => 1 });
$messages->{'WasTransfered'} = $returnbranch;
$messages->{'TransferTrigger'} = $transfer_trigger;
my $issue_id = $issue->issue_id;
- my $anonymouspatron;
- if ( $privacy && $privacy == 2 ) {
- # The default of 0 will not work due to foreign key constraints
- # The anonymisation will fail if AnonymousPatron is not a valid entry
- # We need to check if the anonymous patron exist, Koha will fail loudly if it does not
- # Note that a warning should appear on the about page (System information tab).
- $anonymouspatron = C4::Context->preference('AnonymousPatron');
- die "Fatal error: the patron ($borrowernumber) has requested their circulation history be anonymized on check-in, but the AnonymousPatron system preference is empty or not set correctly."
- unless Koha::Patrons->find( $anonymouspatron );
- }
-
my $schema = Koha::Database->schema;
# FIXME Improve the return value and handle it from callers
# anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber
if ( $privacy && $privacy == 2) {
- $old_checkout->borrowernumber($anonymouspatron)->store;
+ $old_checkout->anonymize;
}
# And finally delete the issue
}
);
- $credit->apply({ debits => [ $accountline ], offset_type => 'Forgiven' });
+ $credit->apply({ debits => [ $accountline ] });
if (C4::Context->preference("FinesLog")) {
&logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
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' );
branchcode => $branchcode,
rules => [
'renewalsallowed',
- 'no_auto_renewal_after',
- 'no_auto_renewal_after_hard_limit',
'lengthunit',
- 'norenewalbefore',
'unseen_renewals_allowed'
]
}
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";
- }
- }
+ $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';
+ }
- # 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";
+ if ( C4::Context->preference('UseRecalls') ) {
+ my $recall = undef;
+ $recall = $item->check_recalls if $item->can_be_waiting_recall;
+ if ( defined $recall ) {
+ if ( $recall->item_level_recall ) {
+ # item-level recall. check if this item is the recalled item, otherwise renewal will be allowed
+ return ( 0, 'recalled' ) if ( $recall->itemnumber == $item->itemnumber );
} else {
- $auto_renew = "auto_too_soon";
+ # biblio-level recall, so only disallow renewal if the biblio-level recall has been fulfilled by a different item
+ return ( 0, 'recalled' ) unless ( $recall->waiting );
}
}
}
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;
}
}
}
- 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
}
# Add the renewal to stats
- UpdateStats(
+ C4::Stats::UpdateStats(
{
branch => $item_object->renewal_branchcode({branch => $branch}),
type => 'renew',
);
my $now = dt_from_string;
- return $now unless $issuing_rule;
if ( defined $issuing_rule->{norenewalbefore}
and $issuing_rule->{norenewalbefore} ne "" )
$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;
}
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
}
}
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',
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} };
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;
}
}
+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) = @_;