use strict;
-require Exporter;
+#use warnings; # soon!
use C4::Context;
use C4::Stats;
use C4::Reserves;
use C4::Dates;
use C4::Calendar;
use C4::Accounts;
+use C4::ItemCirculationAlertPreference;
+use C4::Message;
use Date::Calc qw(
Today
Today_and_Now
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
BEGIN {
- # set the version for version checking
- $VERSION = 3.01;
+ require Exporter;
+ $VERSION = 3.02; # for version checking
@ISA = qw(Exporter);
# FIXME subs that should probably be elsewhere
&CanBookBeIssued
&CanBookBeRenewed
&AddIssue
- &ForceIssue
&AddRenewal
- &ForceRenewal
&GetRenewCount
&GetItemIssue
&GetOpenIssue
&GetIssuingCharges
&GetIssuingRule
&GetBranchBorrowerCircRule
+ &GetBranchItemRule
&GetBiblioIssues
&AnonymiseIssueHistory
);
# subs to deal with returns
push @EXPORT, qw(
&AddReturn
- &ForceReturn
&MarkIssueReturned
);
&GetTransfersFromTo
&updateWrongTransfer
&DeleteTransfer
+ &IsBranchTransferAllowed
+ &CreateBranchTransferLimit
+ &DeleteBranchTransferLimits
);
}
=head2 barcodedecode
-=head3 $str = &barcodedecode($barcode);
+=head3 $str = &barcodedecode($barcode, [$filter]);
=over 4
For proper functioning of this filter, calling the function on the
correct barcode string (items.barcode) should return an unaltered barcode.
+The optional $filter argument is to allow for testing or explicit
+behavior that ignores the System Pref. Valid values are the same as the
+System Pref options.
+
=back
=cut
# FIXME -- these plugins should be moved out of Circulation.pm
#
sub barcodedecode {
- my ($barcode) = @_;
- my $filter = C4::Context->preference('itemBarcodeInputFilter');
- if($filter eq 'whitespace') {
+ my ($barcode, $filter) = @_;
+ $filter = C4::Context->preference('itemBarcodeInputFilter') unless $filter;
+ $filter or return $barcode; # ensure filter is defined, else return untouched barcode
+ if ($filter eq 'whitespace') {
$barcode =~ s/\s//g;
- return $barcode;
- } elsif($filter eq 'cuecat') {
+ } elsif ($filter eq 'cuecat') {
chomp($barcode);
my @fields = split( /\./, $barcode );
my @results = map( decode($_), @fields[ 1 .. $#fields ] );
- if ( $#results == 2 ) {
- return $results[2];
- }
- else {
- return $barcode;
- }
- } elsif($filter eq 'T-prefix') {
- if ( $barcode =~ /^[Tt]/) {
- if (substr($barcode,1,1) eq '0') {
- return $barcode;
- } else {
- $barcode = substr($barcode,2) + 0 ;
- }
+ ($#results == 2) and return $results[2];
+ } elsif ($filter eq 'T-prefix') {
+ if ($barcode =~ /^[Tt](\d)/) {
+ (defined($1) and $1 eq '0') and return $barcode;
+ $barcode = substr($barcode, 2) + 0; # FIXME: probably should be substr($barcode, 1)
}
- return sprintf( "T%07d",$barcode);
+ return sprintf("T%07d", $barcode);
+ # FIXME: $barcode could be "T1", causing warning: substr outside of string
+ # Why drop the nonzero digit after the T?
+ # Why pass non-digits (or empty string) to "T%07d"?
}
+ return $barcode; # return barcode, modified or not
}
=head2 decode
=item Decodes a segment of a string emitted by a CueCat barcode scanner and
returns it.
+FIXME: Should be replaced with Barcode::Cuecat from CPAN
+or Javascript based decoding on the client side.
+
=back
=cut
my $l = ( $#s + 1 ) % 4;
if ($l) {
if ( $l == 1 ) {
- warn "Error!";
+ # warn "Error: Cuecat decode parsing failed!";
return;
}
$l = 4 - $l;
my $hbr = $biblio->{'homebranch'};
my $fbr = $biblio->{'holdingbranch'};
+ # if using Branch Transfer Limits
+ if ( C4::Context->preference("UseBranchTransferLimits") == 1 ) {
+ if ( C4::Context->preference("item-level_itypes") && C4::Context->preference("BranchTransferLimitsType") eq 'itemtype' ) {
+ if ( ! IsBranchTransferAllowed( $tbr, $fbr, $biblio->{'itype'} ) ) {
+ $messages->{'NotAllowed'} = $tbr . "::" . $biblio->{'itype'};
+ $dotransfer = 0;
+ }
+ } elsif ( ! IsBranchTransferAllowed( $tbr, $fbr, $biblio->{ C4::Context->preference("BranchTransferLimitsType") } ) ) {
+ $messages->{'NotAllowed'} = $tbr . "::" . $biblio->{ C4::Context->preference("BranchTransferLimitsType") };
+ $dotransfer = 0;
+ }
+ }
+
# if is permanent...
if ( $hbr && $branches->{$hbr}->{'PE'} ) {
$messages->{'IsPermanent'} = $hbr;
+ $dotransfer = 0;
}
# can't transfer book if is already there....
$item->{'itemtype'}=$item->{'itype'};
my $dbh = C4::Context->dbh;
+ # MANDATORY CHECKS - unless item exists, nothing else matters
+ unless ( $item->{barcode} ) {
+ $issuingimpossible{UNKNOWN_BARCODE} = 1;
+ }
+ return ( \%issuingimpossible, \%needsconfirmation ) if %issuingimpossible;
+
#
# DUE DATE is OK ? -- should already have checked.
#
- #$issuingimpossible{INVALID_DATE} = 1 unless ($duedate);
+ unless ( $duedate ) {
+ my $issuedate = strftime( "%Y-%m-%d", localtime );
+ my $branch = (C4::Context->preference('CircControl') eq 'PickupLibrary') ? C4::Context->userenv->{'branch'} :
+ (C4::Context->preference('CircControl') eq 'PatronLibrary') ? $borrower->{'branchcode'} :
+ $item->{'homebranch'}; # fallback to item's homebranch
+ my $itype = ( C4::Context->preference('item-level_itypes') ) ? $item->{'itype'} : $biblioitem->{'itemtype'};
+ my $loanlength = GetLoanLength( $borrower->{'categorycode'}, $itype, $branch );
+ $duedate = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $loanlength, $branch, $borrower );
+
+ # Offline circ calls AddIssue directly, doesn't run through here
+ # So issuingimpossible should be ok.
+ }
+ $issuingimpossible{INVALID_DATE} = $duedate->output('syspref') unless ( $duedate && $duedate->output('iso') ge C4::Dates->today('iso') );
#
# BORROWER STATUS
if ( $amount > $amountlimit && !$inprocess ) {
$issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
}
- elsif ( $amount <= $amountlimit && !$inprocess ) {
+ elsif ( $amount > 0 && $amount <= $amountlimit && !$inprocess ) {
$needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
}
}
# JB34 CHECKS IF BORROWERS DONT HAVE ISSUE TOO MANY BOOKS
#
my $toomany = TooMany( $borrower, $item->{biblionumber}, $item );
- $needsconfirmation{TOO_MANY} = $toomany if $toomany;
+ # if TooMany return / 0, then the user has no permission to check out this book
+ if ($toomany =~ /\/ 0/) {
+ $needsconfirmation{PATRON_CANT} = 1;
+ } else {
+ $needsconfirmation{TOO_MANY} = $toomany if $toomany;
+ }
#
# ITEM CHECKING
#
- unless ( $item->{barcode} ) {
- $issuingimpossible{UNKNOWN_BARCODE} = 1;
- }
if ( $item->{'notforloan'}
&& $item->{'notforloan'} > 0 )
{
- $issuingimpossible{NOT_FOR_LOAN} = 1;
+ if(!C4::Context->preference("AllowNotForLoanOverride")){
+ $issuingimpossible{NOT_FOR_LOAN} = 1;
+ }else{
+ $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
+ }
+ }
+ elsif ( !$item->{'notforloan'} ){
+ # we have to check itemtypes.notforloan also
+ if (C4::Context->preference('item-level_itypes')){
+ # this should probably be a subroutine
+ my $sth = $dbh->prepare("SELECT notforloan FROM itemtypes WHERE itemtype = ?");
+ $sth->execute($item->{'itemtype'});
+ my $notforloan=$sth->fetchrow_hashref();
+ $sth->finish();
+ if ($notforloan->{'notforloan'}) {
+ if (!C4::Context->preference("AllowNotForLoanOverride")) {
+ $issuingimpossible{NOT_FOR_LOAN} = 1;
+ } else {
+ $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
+ }
+ }
+ }
+ elsif ($biblioitem->{'notforloan'} == 1){
+ if (!C4::Context->preference("AllowNotForLoanOverride")) {
+ $issuingimpossible{NOT_FOR_LOAN} = 1;
+ } else {
+ $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
+ }
+ }
}
- elsif ( !$item->{'notforloan'} ){
- # we have to check itemtypes.notforloan also
- if (C4::Context->preference('item-level_itypes')){
- # this should probably be a subroutine
- my $sth = $dbh->prepare("SELECT notforloan FROM itemtypes WHERE itemtype = ?");
- $sth->execute($item->{'itemtype'});
- my $notforloan=$sth->fetchrow_hashref();
- $sth->finish();
- if ($notforloan->{'notforloan'} == 1){
- $issuingimpossible{NOT_FOR_LOAN} = 1;
- }
- }
- elsif ($biblioitem->{'notforloan'} == 1){
- $issuingimpossible{NOT_FOR_LOAN} = 1;
- }
- }
if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} == 1 )
{
$issuingimpossible{WTHDRAWN} = 1;
}
if ( C4::Context->preference("IndependantBranches") ) {
my $userenv = C4::Context->userenv;
- if ( ($userenv) && ( $userenv->{flags} != 1 ) ) {
+ if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
$issuingimpossible{NOTSAMEBRANCH} = 1
if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} );
}
elsif ($issue->{borrowernumber}) {
# issued to someone else
- my $currborinfo = GetMemberDetails( $issue->{borrowernumber} );
+ my $currborinfo = C4::Members::GetMemberDetails( $issue->{borrowernumber} );
# warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
$needsconfirmation{ISSUED_TO_ANOTHER} =
my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
if ($restype) {
my $resbor = $res->{'borrowernumber'};
- my ( $resborrower ) = GetMemberDetails( $resbor, 0 );
+ my ( $resborrower ) = C4::Members::GetMemberDetails( $resbor, 0 );
my $branches = GetBranches();
my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'};
if ( $resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting" )
"$res->{'reservedate'} : $resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'})";
}
}
- if ( C4::Context->preference("LibraryName") eq "Horowhenua Library Trust" ) {
- if ( $borrower->{'categorycode'} eq 'W' ) {
- my %emptyhash;
- return ( \%emptyhash, \%needsconfirmation );
- }
- }
return ( \%issuingimpossible, \%needsconfirmation );
}
Issue a book. Does no check, they are done in CanBookBeIssued. If we reach this sub, it means the user confirmed if needed.
-&AddIssue($borrower,$barcode,$date)
+&AddIssue($borrower, $barcode, [$datedue], [$cancelreserve], [$issuedate])
=over 4
-=item C<$borrower> hash with borrower informations (from GetMemberDetails)
+=item C<$borrower> is a hash with borrower informations (from GetMemberDetails).
-=item C<$barcode> is the bar code of the book being issued.
+=item C<$barcode> is the barcode of the item being issued.
-=item C<$date> contains the max date of return. calculated if empty.
+=item C<$datedue> is a C4::Dates object for the max date of return, i.e. the date due (optional).
+Calculated if empty.
-=item C<$cancelreserve>
+=item C<$cancelreserve> is 1 to override and cancel any pending reserves for the item (optional).
-=item C<$issuedate> the date to issue the item in iso format (YYYY-MM-DD). Defaults to today.
+=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 C4::Dates object, unfortunately.
AddIssue does the following things :
-- step 01: check that there is a borrowernumber & a barcode provided
-- check for RENEWAL (book issued & being issued to the same patron)
- - renewal YES = Calculate Charge & renew
- - renewal NO =
- * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
- * RESERVE PLACED ?
- - fill reserve if reserve to this patron
- - cancel reserve or not, otherwise
- * TRANSFERT PENDING ?
- - complete the transfert
- * ISSUE THE BOOK
+
+ - step 01: check that there is a borrowernumber & a barcode provided
+ - check for RENEWAL (book issued & being issued to the same patron)
+ - renewal YES = Calculate Charge & renew
+ - renewal NO =
+ * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
+ * RESERVE PLACED ?
+ - fill reserve if reserve to this patron
+ - cancel reserve or not, otherwise
+ * TRANSFERT PENDING ?
+ - complete the transfert
+ * ISSUE THE BOOK
=back
=cut
sub AddIssue {
- my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate ) = @_;
+ my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate, $sipmode) = @_;
my $dbh = C4::Context->dbh;
my $barcodecheck=CheckValidBarcode($barcode);
# $issuedate defaults to today.
if ( ! defined $issuedate ) {
$issuedate = strftime( "%Y-%m-%d", localtime );
+ # TODO: for hourly circ, this will need to be a C4::Dates object
+ # and all calls to AddIssue including issuedate will need to pass a Dates object.
}
if ($borrower and $barcode and $barcodecheck ne '0'){
# find which item we issue
my $item = GetItem('', $barcode) or return undef; # if we don't get an Item, abort.
- my $branch;
- # Get which branchcode we need
- if (C4::Context->preference('CircControl') eq 'PickupLibrary'){
- $branch = C4::Context->userenv->{'branch'};
- }
- elsif (C4::Context->preference('CircControl') eq 'PatronLibrary'){
- $branch = $borrower->{'branchcode'};
- }
- else {
- # items home library
- $branch = $item->{'homebranch'};
- }
+ my $branch = (C4::Context->preference('CircControl') eq 'PickupLibrary') ? C4::Context->userenv->{'branch'} :
+ (C4::Context->preference('CircControl') eq 'PatronLibrary') ? $borrower->{'branchcode'} :
+ $item->{'homebranch'}; # fallback to item's homebranch
# get actual issuing if there is one
my $actualissue = GetItemIssue( $item->{itemnumber});
#
# check if we just renew the issue.
#
- if ( $actualissue->{borrowernumber} eq $borrower->{'borrowernumber'} ) {
- AddRenewal(
+ if ($actualissue->{borrowernumber} eq $borrower->{'borrowernumber'}) {
+ $datedue = AddRenewal(
$borrower->{'borrowernumber'},
$item->{'itemnumber'},
$branch,
$datedue,
- $issuedate,
+ $issuedate, # here interpreted as the renewal date
);
-
}
else {
# it's NOT a renewal
if ($restype) {
my $resbor = $res->{'borrowernumber'};
if ( $resbor eq $borrower->{'borrowernumber'} ) {
-
# The item is reserved by the current patron
ModReserveFill($res);
}
elsif ( $restype eq "Waiting" ) {
-
# warn "Waiting";
# The item is on reserve and waiting, but has been
# reserved by some other patron.
}
elsif ( $restype eq "Reserved" ) {
-
# warn "Reserved";
# The item is reserved by someone else.
if ($cancelreserve) { # cancel reserves on this item
- CancelReserve( 0, $res->{'itemnumber'},
- $res->{'borrowernumber'} );
+ CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
}
}
if ($cancelreserve) {
- CancelReserve( $res->{'biblionumber'}, 0,
- $res->{'borrowernumber'} );
+ CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'});
}
else {
# set waiting reserve to first in reserve queue as book isn't waiting now
# Starting process for transfer job (checking transfert and validate it if we have one)
my ($datesent) = GetTransfers($item->{'itemnumber'});
if ($datesent) {
- # updating line of branchtranfert to finish it, and changing the to branch value, implement a comment for lisibility of this case (maybe for stats ....)
- my $sth =
+ # updating line of branchtranfert to finish it, and changing the to branch value, implement a comment for visibility of this case (maybe for stats ....)
+ my $sth =
$dbh->prepare(
"UPDATE branchtransfers
SET datearrived = now(),
comments = 'Forced branchtransfer'
WHERE itemnumber= ? AND datearrived IS NULL"
);
- $sth->execute(C4::Context->userenv->{'branch'},$item->{'itemnumber'});
- $sth->finish;
+ $sth->execute(C4::Context->userenv->{'branch'},$item->{'itemnumber'});
}
# Record in the database the fact that the book was issued.
(borrowernumber, itemnumber,issuedate, date_due, branchcode)
VALUES (?,?,?,?,?)"
);
- my $dateduef;
- if ($datedue) {
- $dateduef = $datedue;
- } else {
+ unless ($datedue) {
my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
my $loanlength = GetLoanLength( $borrower->{'categorycode'}, $itype, $branch );
- $dateduef = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $loanlength, $branch );
+ $datedue = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $loanlength, $branch, $borrower );
- # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
- if ( C4::Context->preference('ReturnBeforeExpiry') && $dateduef->output('iso') gt $borrower->{dateexpiry} ) {
- $dateduef = C4::Dates->new( $borrower->{dateexpiry}, 'iso' );
- }
}
- $sth->execute(
- $borrower->{'borrowernumber'}, # borrowernumber
- $item->{'itemnumber'}, # itemnumber
- $issuedate, # issuedate
- $dateduef->output('iso'), # date_due
- C4::Context->userenv->{'branch'} # branchcode
- );
+ $sth->execute(
+ $borrower->{'borrowernumber'}, # borrowernumber
+ $item->{'itemnumber'}, # itemnumber
+ $issuedate, # issuedate
+ $datedue->output('iso'), # date_due
+ C4::Context->userenv->{'branch'} # branchcode
+ );
$sth->finish;
$item->{'issues'}++;
ModItem({ issues => $item->{'issues'},
holdingbranch => C4::Context->userenv->{'branch'},
itemlost => 0,
datelastborrowed => C4::Dates->new()->output('iso'),
- onloan => $dateduef->output('iso'),
+ onloan => $datedue->output('iso'),
}, $item->{'biblionumber'}, $item->{'itemnumber'});
ModDateLastSeen( $item->{'itemnumber'} );
-
+
# If it costs to borrow this book, charge it to the patron's account.
my ( $charge, $itemtype ) = GetIssuingCharges(
$item->{'itemnumber'},
# Record the fact that this book was issued.
&UpdateStats(
C4::Context->userenv->{'branch'},
- 'issue', $charge,
- '', $item->{'itemnumber'},
+ 'issue', $charge,
+ ($sipmode ? "SIP-$sipmode" : ''), $item->{'itemnumber'},
$item->{'itype'}, $borrower->{'borrowernumber'}
);
+
+ # Send a checkout slip.
+ my $circulation_alert = 'C4::ItemCirculationAlertPreference';
+ my %conditions = (
+ branchcode => $branch,
+ categorycode => $borrower->{categorycode},
+ item_type => $item->{itype},
+ notification => 'CHECKOUT',
+ );
+ if ($circulation_alert->is_enabled_for(\%conditions)) {
+ SendCirculationAlert({
+ type => 'CHECKOUT',
+ item => $item,
+ borrower => $borrower,
+ branch => $branch,
+ });
+ }
}
-
+
logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'biblionumber'})
if C4::Context->preference("IssueLog");
- return ($datedue);
}
+ return ($datedue); # not necessarily the same as when it came in!
}
-=head2 ForceIssue
-
-ForceIssue()
-
-Issues an item to a member, ignoring any problems that would normally dissallow the issue.
-
-=cut
-
-sub ForceIssue {
- my ( $borrowernumber, $itemnumber, $date_due, $branchcode, $date ) = @_;
-warn "ForceIssue( $borrowernumber, $itemnumber, $date_due, $branchcode, $date );";
- my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare( "INSERT INTO `issues` ( `borrowernumber`, `itemnumber`, `date_due`, `branchcode`, `issuingbranch`, `returndate`, `lastreneweddate`, `return`, `renewals`, `timestamp`, `issuedate` )
- VALUES ( ?, ?, ?, ?, ?, NULL, NULL, NULL, NULL, NOW(), ? )" );
- $sth->execute( $borrowernumber, $itemnumber, $date_due, $branchcode, $branchcode, $date );
- $sth->finish();
-
- my $item = GetBiblioFromItemNumber( $itemnumber );
-
- UpdateStats( $branchcode, 'issue', undef, undef, $itemnumber, $item->{ 'itemtype' }, $borrowernumber );
-}
-
-
=head2 GetLoanLength
Get loan length for an itemtype, a borrower type and a branch
};
}
+=head2 GetBranchItemRule
+
+=over 4
+
+my $branch_item_rule = GetBranchItemRule($branchcode, $itemtype);
+
+=back
+
+Retrieves circulation rule attributes that apply to the given
+branch and item type, regardless of patron category.
+
+The return value is a hashref containing the following key:
+
+holdallowed => Hold policy for this branch and itemtype. Possible values:
+ 0: No holds allowed.
+ 1: Holds allowed only by patrons that have the same homebranch as the item.
+ 2: Holds allowed from any patron.
+
+This searches branchitemrules in the following order:
+
+ * Same branchcode and itemtype
+ * Same branchcode, itemtype '*'
+ * branchcode '*', same itemtype
+ * branchcode and itemtype '*'
+
+Neither C<$branchcode> nor C<$categorycode> should be '*'.
+
+=cut
+
+sub GetBranchItemRule {
+ my ( $branchcode, $itemtype ) = @_;
+ my $dbh = C4::Context->dbh();
+ my $result = {};
+
+ my @attempts = (
+ ['SELECT holdallowed
+ FROM branch_item_rules
+ WHERE branchcode = ?
+ AND itemtype = ?', $branchcode, $itemtype],
+ ['SELECT holdallowed
+ FROM default_branch_circ_rules
+ WHERE branchcode = ?', $branchcode],
+ ['SELECT holdallowed
+ FROM default_branch_item_rules
+ WHERE itemtype = ?', $itemtype],
+ ['SELECT holdallowed
+ FROM default_circ_rules'],
+ );
+
+ foreach my $attempt (@attempts) {
+ my ($query, @bind_params) = @{$attempt};
+
+ # Since branch/category and branch/itemtype use the same per-branch
+ # defaults tables, we have to check that the key we want is set, not
+ # just that a row was returned
+ return $result if ( defined( $result->{'holdallowed'} = $dbh->selectrow_array( $query, {}, @bind_params ) ) );
+ }
+
+ # built-in default circulation rule
+ return {
+ holdallowed => 2,
+ };
+}
+
=head2 AddReturn
($doreturn, $messages, $iteminformation, $borrower) =
my $curr_iteminfo = GetItem($iteminformation->{'itemnumber'});
$iteminformation->{'homebranch'} = $curr_iteminfo->{'homebranch'};
$iteminformation->{'holdingbranch'} = $curr_iteminfo->{'holdingbranch'};
+ $iteminformation->{'itemlost'} = $curr_iteminfo->{'itemlost'};
$doreturn = 0;
}
}
MarkIssueReturned($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'},$circControlBranch);
$messages->{'WasReturned'} = 1; # FIXME is the "= 1" right?
- }
-
- # continue to deal with returns cases, but not only if we have an issue
+
- # the holdingbranch is updated if the document is returned in an other location .
- if ( $iteminformation->{'holdingbranch'} ne C4::Context->userenv->{'branch'} ) {
- UpdateHoldingbranch(C4::Context->userenv->{'branch'},$iteminformation->{'itemnumber'});
- # reload iteminformation holdingbranch with the userenv value
- $iteminformation->{'holdingbranch'} = C4::Context->userenv->{'branch'};
+ # continue to deal with returns cases, but not only if we have an issue
+
+ # the holdingbranch is updated if the document is returned in an other location .
+ if ( $iteminformation->{'holdingbranch'} ne C4::Context->userenv->{'branch'} ) {
+ UpdateHoldingbranch(C4::Context->userenv->{'branch'},$iteminformation->{'itemnumber'});
+ # reload iteminformation holdingbranch with the userenv value
+ $iteminformation->{'holdingbranch'} = C4::Context->userenv->{'branch'};
+ }
+ ModDateLastSeen( $iteminformation->{'itemnumber'} );
+ ModItem({ onloan => undef }, $biblio->{'biblionumber'}, $iteminformation->{'itemnumber'});
+
+ if ($iteminformation->{borrowernumber}){
+ ($borrower) = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
+ }
}
- ModDateLastSeen( $iteminformation->{'itemnumber'} );
- ModItem({ onloan => undef }, $biblio->{'biblionumber'}, $iteminformation->{'itemnumber'});
-
- if ($iteminformation->{borrowernumber}){
- ($borrower) = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
- }
# fix up the accounts.....
if ( $iteminformation->{'itemlost'} ) {
$messages->{'WasLost'} = 1;
$biblio->{'itemtype'},
$borrower->{'borrowernumber'}
);
+
+ # Send a check-in slip.
+ my $circulation_alert = 'C4::ItemCirculationAlertPreference';
+ my %conditions = (
+ branchcode => $branch,
+ categorycode => $borrower->{categorycode},
+ item_type => $iteminformation->{itype},
+ notification => 'CHECKIN',
+ );
+ if ($doreturn && $circulation_alert->is_enabled_for(\%conditions)) {
+ SendCirculationAlert({
+ type => 'CHECKIN',
+ item => $iteminformation,
+ borrower => $borrower,
+ branch => $branch,
+ });
+ }
logaction("CIRCULATION", "RETURN", $iteminformation->{borrowernumber}, $iteminformation->{'biblionumber'})
if C4::Context->preference("ReturnLog");
#adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
#we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
- if ( ($iteminformation->{'holdingbranch'} ne $iteminformation->{'homebranch'}) and not $messages->{'WrongTransfer'} and ($validTransfert ne 1) and ($reserveDone ne 1) ){
+ if ($doreturn and ($branch ne $iteminformation->{'homebranch'}) and not $messages->{'WrongTransfer'} and ($validTransfert ne 1) and ($reserveDone ne 1) ){
if (C4::Context->preference("AutomaticItemReturn") == 1) {
ModItemTransfer($iteminformation->{'itemnumber'}, C4::Context->userenv->{'branch'}, $iteminformation->{'homebranch'});
$messages->{'WasTransfered'} = 1;
+ } elsif ( C4::Context->preference("UseBranchTransferLimits") == 1
+ && ! IsBranchTransferAllowed( $branch, $iteminformation->{'homebranch'}, $iteminformation->{ C4::Context->preference("BranchTransferLimitsType") } )
+ ) {
+ ModItemTransfer($iteminformation->{'itemnumber'}, C4::Context->userenv->{'branch'}, $iteminformation->{'homebranch'});
+ $messages->{'WasTransfered'} = 1;
}
else {
$messages->{'NeedsTransfer'} = 1;
return ( $doreturn, $messages, $iteminformation, $borrower );
}
-=head2 ForceReturn
-
-ForceReturn( $barcode, $date, $branchcode );
-
-Returns an item is if it were returned on C<$date>.
-
-This function is non-interactive and does not check for reserves.
-
-C<$barcode> is the barcode of the item being returned.
-
-C<$date> is the date of the actual return, in the format YYYY-MM-DD.
-
-C<$branchcode> is the branchcode for the library the item was returned to.
-
-=cut
-
-sub ForceReturn {
- my ( $barcode, $date, $branchcode ) = @_;
- my $dbh = C4::Context->dbh;
-
- my $item = GetBiblioFromItemNumber( undef, $barcode );
-
- ## FIXME: Is there a way to get the borrower of an item through the Koha API?
- my $sth=$dbh->prepare( "SELECT borrowernumber FROM issues WHERE itemnumber = ? AND returndate IS NULL");
- $sth->execute( $item->{'itemnumber'} );
- my ( $borrowernumber ) = $sth->fetchrow;
- $sth->finish();
-
- ## Move the issue from issues to old_issues
- $sth = $dbh->prepare( "INSERT INTO old_issues ( SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL )" );
- $sth->execute( $item->{'itemnumber'} );
- $sth->finish();
- ## Delete the row in issues
- $sth = $dbh->prepare( "DELETE FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
- $sth->execute( $item->{'itemnumber'} );
- $sth->finish();
- ## Now set the returndate
- $sth = $dbh->prepare( 'UPDATE old_issues SET returndate = ? WHERE itemnumber = ? AND returndate IS NULL' );
- $sth->execute( $date, $item->{'itemnumber'} );
- $sth->finish();
-
- UpdateStats( $branchcode, 'return', my $amount, my $other, $item->{ 'itemnumber' }, $item->{ 'itemtype' }, $borrowernumber );
-}
-
-
=head2 MarkIssueReturned
=over 4
LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
LEFT JOIN items ON issues.itemnumber = items.itemnumber
LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
- LEFT JOIN biblio ON biblio.biblionumber = items.biblioitemnumber
+ LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
WHERE biblio.biblionumber = ?
UNION ALL
SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
- LEFT JOIN biblio ON biblio.biblionumber = items.biblioitemnumber
+ LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
WHERE biblio.biblionumber = ?
ORDER BY timestamp
";
=head2 CanBookBeRenewed
-($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber);
+($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber[, $override_limit]);
Find out whether a borrowed item may be renewed.
C<$itemnumber> is the number of the item to renew.
+C<$override_limit>, if supplied with a true value, causes
+the limit on the number of times that the loan can be renewed
+(as controlled by the item type) to be ignored.
+
C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
item must currently be on loan to the specified borrower; renewals
must be allowed for the item's type; and the borrower must not have
sub CanBookBeRenewed {
# check renewal status
- my ( $borrowernumber, $itemnumber ) = @_;
+ my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
my $dbh = C4::Context->dbh;
my $renews = 1;
my $renewokay = 0;
if ( my $data2 = $sth2->fetchrow_hashref ) {
$renews = $data2->{'renewalsallowed'};
}
- if ( $renews && $renews > $data1->{'renewals'} ) {
+ if ( ( $renews && $renews > $data1->{'renewals'} ) || $override_limit ) {
$renewokay = 1;
}
else {
=head2 AddRenewal
-&AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$issuedate]);
+&AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$lastreneweddate]);
Renews a loan.
C<$datedue> can be a C4::Dates object used to set the due date.
-C<$issuedate> can be a iso formatted date to use for the issuedate.
+C<$lastreneweddate> is an optional ISO-formatted date used to set issues.lastreneweddate. If
+this parameter is not supplied, lastreneweddate is set to the current date.
If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
from the book's item type.
my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
my $branch = (@_) ? shift : $item->{homebranch}; # opac-renew doesn't send branch
my $datedue = shift;
- my $issuedate = shift;
- # If the due date wasn't specified, calculate it by adding the
- # book's loan length to today's date.
- unless ($datedue && $datedue->output('iso')) {
-
- my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef;
- my $loanlength = GetLoanLength(
- $borrower->{'categorycode'},
- (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ,
- $item->{homebranch} # item's homebranch determines loanlength OR do we want the branch specified by the AddRenewal argument?
- );
- #FIXME -- use circControl?
- $datedue = CalcDateDue(C4::Dates->new(),$loanlength,$branch); # this branch is the transactional branch.
- # The question of whether to use item's homebranch calendar is open.
+ my $lastreneweddate = shift;
+ # $lastreneweddate defaults to today.
+ unless (defined $lastreneweddate) {
+ $lastreneweddate = strftime( "%Y-%m-%d", localtime );
}
my $dbh = C4::Context->dbh;
my $issuedata = $sth->fetchrow_hashref;
$sth->finish;
+ # If the due date wasn't specified, calculate it by adding the
+ # book's loan length to today's date or the current due date
+ # based on the value of the RenewalPeriodBase syspref.
+ unless ($datedue && $datedue->output('iso')) {
+
+ my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef;
+ my $loanlength = GetLoanLength(
+ $borrower->{'categorycode'},
+ (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ,
+ $item->{homebranch} # item's homebranch determines loanlength OR do we want the branch specified by the AddRenewal argument?
+ );
+
+ $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
+ C4::Dates->new($issuedata->{date_due}, 'iso') :
+ C4::Dates->new();
+ #FIXME -- use circControl?
+ $datedue = CalcDateDue($datedue,$loanlength,$branch,$borrower); # this branch is the transactional branch.
+ # The question of whether to use item's homebranch calendar is open.
+ }
+
# Update the issues record to have the new due date, and a new count
# of how many times it has been renewed.
my $renews = $issuedata->{'renewals'} + 1;
- $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = CURRENT_DATE, issuedate = ?
+ $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ?
WHERE borrowernumber=?
AND itemnumber=?"
);
- $sth->execute( $datedue->output('iso'), $renews, $issuedate, $borrowernumber, $itemnumber );
+ $sth->execute( $datedue->output('iso'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
$sth->finish;
# Update the renewal count on the item, and tell zebra to reindex
$renews = $biblio->{'renewals'} + 1;
- ModItem({ renewals => $renews }, $biblio->{'biblionumber'}, $itemnumber);
+ ModItem({ renewals => $renews, onloan => $datedue->output('iso') }, $biblio->{'biblionumber'}, $itemnumber);
# Charge a new rental fee, if applicable?
my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
}
# Log the renewal
UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
+ return $datedue;
}
-
-=head2 ForceRenewal
-
-ForRenewal( $itemnumber, $date, $date_due );
-
-Renews an item for the given date. This function should only be used to update renewals that have occurred in the past.
-
-C<$itemnumber> is the itemnumber of the item being renewed.
-
-C<$date> is the date the renewal took place, in the format YYYY-MM-DD
-
-C<$date_due> is the date the item is now due to be returned, in the format YYYY-MM-DD
-
-=cut
-
-sub ForceRenewal {
- my ( $itemnumber, $date, $date_due ) = @_;
- my $dbh = C4::Context->dbh;
-
- my $sth = $dbh->prepare("SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL");
- $sth->execute( $itemnumber );
- my $issue = $sth->fetchrow_hashref();
- $sth->finish();
-
-
- $sth = $dbh->prepare('UPDATE issues SET renewals = ?, lastreneweddate = ?, date_due = ? WHERE itemnumber = ? AND returndate IS NULL');
- $sth->execute( $issue->{'renewals'} + 1, $date, $date_due, $itemnumber );
- $sth->finish();
-
- my $item = GetBiblioFromItemNumber( $itemnumber );
- UpdateStats( $issue->{'branchcode'}, 'renew', undef, undef, $itemnumber, $item->{ 'itemtype' }, $issue->{'borrowernumber'} );
-}
-
-
sub GetRenewCount {
# check renewal status
my ($bornum,$itemno)=@_;
return @row;
}
-
=head2 GetTransfersFromTo
@results = GetTransfersFromTo($frombranch,$tobranch);
return $rows_affected;
}
+=head2 SendCirculationAlert
+
+Send out a C<check-in> or C<checkout> alert using the messaging system.
+
+B<Parameters>:
+
+=over 4
+
+=item type
+
+Valid values for this parameter are: C<CHECKIN> and C<CHECKOUT>.
+
+=item item
+
+Hashref of information about the item being checked in or out.
+
+=item borrower
+
+Hashref of information about the borrower of the item.
+
+=item branch
+
+The branchcode from where the checkout or check-in took place.
+
+=back
+
+B<Example>:
+
+ SendCirculationAlert({
+ type => 'CHECKOUT',
+ item => $item,
+ borrower => $borrower,
+ branch => $branch,
+ });
+
+=cut
+
+sub SendCirculationAlert {
+ my ($opts) = @_;
+ my ($type, $item, $borrower, $branch) =
+ ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch});
+ my %message_name = (
+ CHECKIN => 'Item Check-in',
+ CHECKOUT => 'Item Checkout',
+ );
+ my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
+ borrowernumber => $borrower->{borrowernumber},
+ message_name => $message_name{$type},
+ });
+ my $letter = C4::Letters::getletter('circulation', $type);
+ C4::Letters::parseletter($letter, 'biblio', $item->{biblionumber});
+ C4::Letters::parseletter($letter, 'biblioitems', $item->{biblionumber});
+ C4::Letters::parseletter($letter, 'borrowers', $borrower->{borrowernumber});
+ C4::Letters::parseletter($letter, 'branches', $branch);
+ my @transports = @{ $borrower_preferences->{transports} };
+ # warn "no transports" unless @transports;
+ for (@transports) {
+ # warn "transport: $_";
+ my $message = C4::Message->find_last_message($borrower, $type, $_);
+ if (!$message) {
+ #warn "create new message";
+ C4::Message->enqueue($letter, $borrower, $_);
+ } else {
+ #warn "append to old message";
+ $message->append($letter);
+ $message->update;
+ }
+ }
+ $letter;
+}
+
=head2 updateWrongTransfer
$items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
=cut
sub CalcDateDue {
- my ($startdate,$loanlength,$branch) = @_;
+ my ($startdate,$loanlength,$branch,$borrower) = @_;
+ my $datedue;
+
if(C4::Context->preference('useDaysMode') eq 'Days') { # ignoring calendar
- my $datedue = time + ($loanlength) * 86400;
+ my $timedue = time + ($loanlength) * 86400;
#FIXME - assumes now even though we take a startdate
- my @datearr = localtime($datedue);
- return C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
+ my @datearr = localtime($timedue);
+ $datedue = C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
} else {
my $calendar = C4::Calendar->new( branchcode => $branch );
- my $datedue = $calendar->addDate($startdate, $loanlength);
- return $datedue;
+ $datedue = $calendar->addDate($startdate, $loanlength);
}
+
+ # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
+ if ( C4::Context->preference('ReturnBeforeExpiry') && $datedue->output('iso') gt $borrower->{dateexpiry} ) {
+ $datedue = C4::Dates->new( $borrower->{dateexpiry}, 'iso' );
+ }
+
+ # if ceilingDueDate ON the datedue can't be after the ceiling date
+ if ( C4::Context->preference('ceilingDueDate')
+ && ( C4::Context->preference('ceilingDueDate') =~ C4::Dates->regexp('syspref') )
+ && $datedue->output gt C4::Context->preference('ceilingDueDate') ) {
+ $datedue = C4::Dates->new( C4::Context->preference('ceilingDueDate') );
+ }
+
+ return $datedue;
}
=head2 CheckValidDatedue
return $exist;
}
-1;
+=head2 IsBranchTransferAllowed
+
+$allowed = IsBranchTransferAllowed( $toBranch, $fromBranch, $code );
+
+Code is either an itemtype or collection doe depending on the pref BranchTransferLimitsType
+
+=cut
+
+sub IsBranchTransferAllowed {
+ my ( $toBranch, $fromBranch, $code ) = @_;
+
+ if ( $toBranch eq $fromBranch ) { return 1; } ## Short circuit for speed.
+
+ my $limitType = C4::Context->preference("BranchTransferLimitsType");
+ my $dbh = C4::Context->dbh;
+
+ my $sth = $dbh->prepare("SELECT * FROM branch_transfer_limits WHERE toBranch = ? AND fromBranch = ? AND $limitType = ?");
+ $sth->execute( $toBranch, $fromBranch, $code );
+ my $limit = $sth->fetchrow_hashref();
+
+ ## If a row is found, then that combination is not allowed, if no matching row is found, then the combination *is allowed*
+ if ( $limit->{'limitId'} ) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+=head2 CreateBranchTransferLimit
+
+CreateBranchTransferLimit( $toBranch, $fromBranch, $code );
+
+$code is either itemtype or collection code depending on what the pref BranchTransferLimitsType is set to.
+
+=cut
+
+sub CreateBranchTransferLimit {
+ my ( $toBranch, $fromBranch, $code ) = @_;
+
+ my $limitType = C4::Context->preference("BranchTransferLimitsType");
+
+ my $dbh = C4::Context->dbh;
+
+ my $sth = $dbh->prepare("INSERT INTO branch_transfer_limits ( $limitType, toBranch, fromBranch ) VALUES ( ?, ?, ? )");
+ $sth->execute( $code, $toBranch, $fromBranch );
+}
+
+=head2 DeleteBranchTransferLimits
+
+DeleteBranchTransferLimits();
+
+=cut
+
+sub DeleteBranchTransferLimits {
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare("TRUNCATE TABLE branch_transfer_limits");
+ $sth->execute();
+}
+
+
+ 1;
__END__