X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FCirculation.pm;h=9c43f362b0330c85d8f558f64c954550f6a47586;hb=81d5f9dcdae68037a43c7953be1e91a2fcf92b04;hp=47068f9e58177bcfa23cdbd5afe795d7038b12e4;hpb=18c491542bd6e4dfd580ea3913a394fbdb8acd5a;p=koha.git diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 47068f9e58..9c43f362b0 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -21,34 +21,26 @@ package C4::Circulation; use strict; #use warnings; FIXME - Bug 2505 +use DateTime; use C4::Context; use C4::Stats; use C4::Reserves; -use C4::Koha; use C4::Biblio; use C4::Items; use C4::Members; use C4::Dates; -use C4::Calendar; +use C4::Dates qw(format_date); use C4::Accounts; use C4::ItemCirculationAlertPreference; -use C4::Dates qw(format_date); use C4::Message; use C4::Debug; -use Date::Calc qw( - Today - Today_and_Now - Add_Delta_YM - Add_Delta_DHMS - Date_to_Days - Day_of_Week - Add_Delta_Days -); -use POSIX qw(strftime); use C4::Branch; # GetBranches use C4::Log; # logaction use Data::Dumper; +use Koha::DateUtils; +use Koha::Calendar; +use Carp; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); @@ -59,8 +51,9 @@ BEGIN { # FIXME subs that should probably be elsewhere push @EXPORT, qw( - &FixOverduesOnReturn &barcodedecode + &LostItem + &ReturnLostItem ); # subs to deal with issuing a book @@ -72,7 +65,6 @@ BEGIN { &GetRenewCount &GetItemIssue &GetItemIssues - &GetBorrowerIssues &GetIssuingCharges &GetIssuingRule &GetBranchBorrowerCircRule @@ -98,7 +90,17 @@ BEGIN { &IsBranchTransferAllowed &CreateBranchTransferLimit &DeleteBranchTransferLimits + &TransferSlip ); + + # subs to deal with offline circulation + push @EXPORT, qw( + &GetOfflineOperations + &GetOfflineOperation + &AddOfflineOperation + &DeleteOfflineOperation + &ProcessOfflineOperation + ); } =head1 NAME @@ -319,7 +321,7 @@ sub transferbook { # find reserves..... # That'll save a database query. - my ( $resfound, $resrec ) = + my ( $resfound, $resrec, undef ) = CheckReserves( $itemnumber ); if ( $resfound and not $ignoreRs ) { $resrec->{'ResFound'} = $resfound; @@ -431,7 +433,7 @@ sub TooMany { my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower); if (defined($branch_borrower_circ_rule->{maxissueqty})) { my @bind_params = (); - my $branch_count_query = "SELECT COUNT(*) FROM issues + my $branch_count_query = "SELECT COUNT(*) FROM issues JOIN items USING (itemnumber) WHERE borrowernumber = ? "; push @bind_params, $borrower->{borrowernumber}; @@ -570,7 +572,7 @@ sub itemissues { =head2 CanBookBeIssued ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, - $barcode, $duedatespec, $inprocess ); + $barcode, $duedatespec, $inprocess, $ignore_reserves ); Check if a book can be issued. @@ -578,13 +580,14 @@ C<$issuingimpossible> and C<$needsconfirmation> are some hashref. =over 4 -=item C<$borrower> hash with borrower informations (from GetMemberDetails) +=item C<$borrower> hash with borrower informations (from GetMember or GetMemberDetails) =item C<$barcode> is the bar code of the book being issued. =item C<$duedatespec> is a C4::Dates object. -=item C<$inprocess> +=item C<$inprocess> boolean switch +=item C<$ignore_reserves> boolean switch =back @@ -661,7 +664,7 @@ if the borrower borrows to much things =cut sub CanBookBeIssued { - my ( $borrower, $barcode, $duedate, $inprocess ) = @_; + my ( $borrower, $barcode, $duedate, $inprocess, $ignore_reserves ) = @_; my %needsconfirmation; # filled with problems that needs confirmations my %issuingimpossible; # filled with problems that causes the issue to be IMPOSSIBLE my $item = GetItem(GetItemnumberFromBarcode( $barcode )); @@ -679,21 +682,28 @@ sub CanBookBeIssued { # # DUE DATE is OK ? -- should already have checked. # + if ($duedate && ref $duedate ne 'DateTime') { + $duedate = dt_from_string($duedate); + } + my $now = DateTime->now( time_zone => C4::Context->tz() ); unless ( $duedate ) { - my $issuedate = strftime( "%Y-%m-%d", localtime ); + my $issuedate = $now->clone(); my $branch = _GetCircControlBranch($item,$borrower); my $itype = ( C4::Context->preference('item-level_itypes') ) ? $item->{'itype'} : $biblioitem->{'itemtype'}; - $duedate = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower ); + $duedate = CalcDateDue( $issuedate, $itype, $branch, $borrower ); # Offline circ calls AddIssue directly, doesn't run through here # So issuingimpossible should be ok. } if ($duedate) { - $needsconfirmation{INVALID_DATE} = $duedate->output('syspref') - unless $duedate->output('iso') ge C4::Dates->today('iso'); + my $today = $now->clone(); + $today->truncate( to => 'minutes'); + if (DateTime->compare($duedate,$today) == -1 ) { # duedate cannot be before now + $needsconfirmation{INVALID_DATE} = output_pref($duedate); + } } else { - $issuingimpossible{INVALID_DATE} = $duedate->output('syspref'); + $issuingimpossible{INVALID_DATE} = output_pref($duedate); } # @@ -714,13 +724,25 @@ sub CanBookBeIssued { if ( $borrower->{flags}->{'DBARRED'} ) { $issuingimpossible{DEBARRED} = 1; } - if ( $borrower->{'dateexpiry'} eq '0000-00-00') { + if ( !defined $borrower->{dateexpiry} || $borrower->{'dateexpiry'} eq '0000-00-00') { $issuingimpossible{EXPIRED} = 1; } else { - my @expirydate= split /-/,$borrower->{'dateexpiry'}; - if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 || - Date_to_Days(Today) > Date_to_Days( @expirydate )) { - $issuingimpossible{EXPIRED} = 1; + my ($y, $m, $d) = split /-/,$borrower->{'dateexpiry'}; + if ($y && $m && $d) { # are we really writing oinvalid dates to borrs + my $expiry_dt = DateTime->new( + year => $y, + month => $m, + day => $d, + time_zone => C4::Context->tz, + ); + $expiry_dt->truncate( to => 'days'); + my $today = $now->clone()->truncate(to => 'days'); + if (DateTime->compare($today, $expiry_dt) == 1) { + $issuingimpossible{EXPIRED} = 1; + } + } else { + carp("Invalid expity date in borr"); + $issuingimpossible{EXPIRED} = 1; } } # @@ -729,7 +751,7 @@ sub CanBookBeIssued { # DEBTS my ($amount) = - C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->output('iso') ); + C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->ymd() ); my $amountlimit = C4::Context->preference("noissuescharge"); my $allowfineoverride = C4::Context->preference("AllowFineOverride"); my $allfinesneedoverride = C4::Context->preference("AllFinesNeedOverride"); @@ -771,7 +793,7 @@ sub CanBookBeIssued { # my ($current_loan_count, $max_loans_allowed) = TooMany( $borrower, $item->{biblionumber}, $item ); # if TooMany max_loans_allowed returns 0 the user doesn't have permission to check out this book - if ($max_loans_allowed eq 0) { + if (defined $max_loans_allowed && $max_loans_allowed == 0) { $needsconfirmation{PATRON_CANT} = 1; } else { if($max_loans_allowed){ @@ -817,7 +839,7 @@ sub CanBookBeIssued { } } } - if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} == 1 ) + if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} > 0 ) { $issuingimpossible{WTHDRAWN} = 1; } @@ -858,7 +880,7 @@ sub CanBookBeIssued { elsif ($issue->{borrowernumber}) { # issued to someone else - my $currborinfo = C4::Members::GetMemberDetails( $issue->{borrowernumber} ); + my $currborinfo = C4::Members::GetMember( borrowernumber => $issue->{borrowernumber} ); # warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})"; $needsconfirmation{ISSUED_TO_ANOTHER} = 1; @@ -868,37 +890,40 @@ sub CanBookBeIssued { $needsconfirmation{issued_borrowernumber} = $currborinfo->{'borrowernumber'}; } - # See if the item is on reserve. - my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} ); - if ($restype) { - my $resbor = $res->{'borrowernumber'}; - my ( $resborrower ) = C4::Members::GetMemberDetails( $resbor, 0 ); - my $branches = GetBranches(); - my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'}; - if ( $resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting" ) - { - # The item is on reserve and waiting, but has been - # reserved by some other patron. - $needsconfirmation{RESERVE_WAITING} = 1; - $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'}; - $needsconfirmation{'ressurname'} = $resborrower->{'surname'}; - $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'}; - $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'}; - $needsconfirmation{'resbranchname'} = $branchname; - $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'}); - } - elsif ( $restype eq "Reserved" ) { - # The item is on reserve for someone else. - $needsconfirmation{RESERVED} = 1; - $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'}; - $needsconfirmation{'ressurname'} = $resborrower->{'surname'}; - $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'}; - $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'}; - $needsconfirmation{'resbranchname'} = $branchname; - $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'}); + unless ( $ignore_reserves ) { + # See if the item is on reserve. + my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} ); + if ($restype) { + my $resbor = $res->{'borrowernumber'}; + if ( $resbor ne $borrower->{'borrowernumber'} ) { + my ( $resborrower ) = C4::Members::GetMember( borrowernumber => $resbor ); + my $branchname = GetBranchName( $res->{'branchcode'} ); + if ( $restype eq "Waiting" ) + { + # The item is on reserve and waiting, but has been + # reserved by some other patron. + $needsconfirmation{RESERVE_WAITING} = 1; + $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'}; + $needsconfirmation{'ressurname'} = $resborrower->{'surname'}; + $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'}; + $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'}; + $needsconfirmation{'resbranchname'} = $branchname; + $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'}); + } + elsif ( $restype eq "Reserved" ) { + # The item is on reserve for someone else. + $needsconfirmation{RESERVED} = 1; + $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'}; + $needsconfirmation{'ressurname'} = $resborrower->{'surname'}; + $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'}; + $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'}; + $needsconfirmation{'resbranchname'} = $branchname; + $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'}); + } + } } } - return ( \%issuingimpossible, \%needsconfirmation ); + return ( \%issuingimpossible, \%needsconfirmation ); } =head2 AddIssue @@ -909,7 +934,7 @@ Issue a book. Does no check, they are done in CanBookBeIssued. If we reach this =over 4 -=item C<$borrower> is a hash with borrower informations (from GetMemberDetails). +=item C<$borrower> is a hash with borrower informations (from GetMember or GetMemberDetails). =item C<$barcode> is the barcode of the item being issued. @@ -943,13 +968,20 @@ sub AddIssue { my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate, $sipmode) = @_; my $dbh = C4::Context->dbh; my $barcodecheck=CheckValidBarcode($barcode); + if ($datedue && ref $datedue ne 'DateTime') { + $datedue = dt_from_string($datedue); + } # $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. + $issuedate = DateTime->now(time_zone => C4::Context->tz()); + } + else { + if ( ref $issuedate ne 'DateTime') { + $issuedate = dt_from_string($issuedate); + + } } - if ($borrower and $barcode and $barcodecheck ne '0'){ + if ($borrower and $barcode and $barcodecheck ne '0'){#??? wtf # find which item we issue my $item = GetItem('', $barcode) or return undef; # if we don't get an Item, abort. my $branch = _GetCircControlBranch($item,$borrower); @@ -964,12 +996,12 @@ sub AddIssue { # check if we just renew the issue. # if ($actualissue->{borrowernumber} eq $borrower->{'borrowernumber'}) { - $datedue = AddRenewal( - $borrower->{'borrowernumber'}, - $item->{'itemnumber'}, - $branch, - $datedue, - $issuedate, # here interpreted as the renewal date + $datedue = AddRenewal( + $borrower->{'borrowernumber'}, + $item->{'itemnumber'}, + $branch, + $datedue, + $issuedate, # here interpreted as the renewal date ); } else { @@ -983,40 +1015,7 @@ sub AddIssue { ); } - # See if the item is on reserve. - my ( $restype, $res ) = - C4::Reserves::CheckReserves( $item->{'itemnumber'} ); - 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'}); - } - } - if ($cancelreserve) { - CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'}); - } - else { - # set waiting reserve to first in reserve queue as book isn't waiting now - ModReserve(1, - $res->{'biblionumber'}, - $res->{'borrowernumber'}, - $res->{'branchcode'} - ); - } - } - + MoveReserve( $item->{'itemnumber'}, $borrower->{'borrowernumber'}, $cancelreserve ); # Starting process for transfer job (checking transfert and validate it if we have one) my ($datesent) = GetTransfers($item->{'itemnumber'}); if ($datesent) { @@ -1035,32 +1034,38 @@ sub AddIssue { # Record in the database the fact that the book was issued. my $sth = $dbh->prepare( - "INSERT INTO issues + "INSERT INTO issues (borrowernumber, itemnumber,issuedate, date_due, branchcode) VALUES (?,?,?,?,?)" ); unless ($datedue) { my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'}; - $datedue = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower ); + $datedue = CalcDateDue( $issuedate, $itype, $branch, $borrower ); } + $datedue->truncate( to => 'minutes'); $sth->execute( $borrower->{'borrowernumber'}, # borrowernumber $item->{'itemnumber'}, # itemnumber - $issuedate, # issuedate - $datedue->output('iso'), # date_due + $issuedate->strftime('%Y-%m-%d %H:%M:00'), # issuedate + $datedue->strftime('%Y-%m-%d %H:%M:00'), # date_due C4::Context->userenv->{'branch'} # branchcode ); - $sth->finish; if ( C4::Context->preference('ReturnToShelvingCart') ) { ## ReturnToShelvingCart is on, anything issued should be taken off the cart. CartToShelf( $item->{'itemnumber'} ); } $item->{'issues'}++; + + ## If item was lost, it has now been found, reverse any list item charges if neccessary. + if ( $item->{'itemlost'} ) { + _FixAccountForLostAndReturned( $item->{'itemnumber'}, undef, $item->{'barcode'} ); + } + ModItem({ issues => $item->{'issues'}, holdingbranch => C4::Context->userenv->{'branch'}, itemlost => 0, - datelastborrowed => C4::Dates->new()->output('iso'), - onloan => $datedue->output('iso'), + datelastborrowed => DateTime->now(time_zone => C4::Context->tz())->ymd(), + onloan => $datedue->ymd(), }, $item->{'biblionumber'}, $item->{'itemnumber'}); ModDateLastSeen( $item->{'itemnumber'} ); @@ -1122,53 +1127,57 @@ sub GetLoanLength { my $dbh = C4::Context->dbh; my $sth = $dbh->prepare( -"select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null" +'select issuelength, lengthunit from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null' ); # warn "in get loan lenght $borrowertype $itemtype $branchcode "; # try to find issuelength & return the 1st available. # check with borrowertype, itemtype and branchcode, then without one of those parameters $sth->execute( $borrowertype, $itemtype, $branchcode ); my $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; + return $loanlength + if defined($loanlength) && $loanlength->{issuelength}; - $sth->execute( $borrowertype, "*", $branchcode ); + $sth->execute( $borrowertype, '*', $branchcode ); $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; + return $loanlength + if defined($loanlength) && $loanlength->{issuelength}; - $sth->execute( "*", $itemtype, $branchcode ); + $sth->execute( '*', $itemtype, $branchcode ); $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; + return $loanlength + if defined($loanlength) && $loanlength->{issuelength}; - $sth->execute( "*", "*", $branchcode ); + $sth->execute( '*', '*', $branchcode ); $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; + return $loanlength + if defined($loanlength) && $loanlength->{issuelength}; - $sth->execute( $borrowertype, $itemtype, "*" ); + $sth->execute( $borrowertype, $itemtype, '*' ); $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; + return $loanlength + if defined($loanlength) && $loanlength->{issuelength}; - $sth->execute( $borrowertype, "*", "*" ); + $sth->execute( $borrowertype, '*', '*' ); $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; + return $loanlength + if defined($loanlength) && $loanlength->{issuelength}; - $sth->execute( "*", $itemtype, "*" ); + $sth->execute( '*', $itemtype, '*' ); $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; + return $loanlength + if defined($loanlength) && $loanlength->{issuelength}; - $sth->execute( "*", "*", "*" ); + $sth->execute( '*', '*', '*' ); $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; + return $loanlength + if defined($loanlength) && $loanlength->{issuelength}; # if no rule is set => 21 days (hardcoded) - return 21; + return { + issuelength => 21, + lengthunit => 'days', + }; + } @@ -1189,43 +1198,43 @@ sub GetHardDueDate { ); $sth->execute( $borrowertype, $itemtype, $branchcode ); my $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; + return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) + if defined($results) && $results->{hardduedate}; $sth->execute( $borrowertype, "*", $branchcode ); $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; + return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) + if defined($results) && $results->{hardduedate}; $sth->execute( "*", $itemtype, $branchcode ); $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; + return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) + if defined($results) && $results->{hardduedate}; $sth->execute( "*", "*", $branchcode ); $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; + return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) + if defined($results) && $results->{hardduedate}; $sth->execute( $borrowertype, $itemtype, "*" ); $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; + return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) + if defined($results) && $results->{hardduedate}; $sth->execute( $borrowertype, "*", "*" ); $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; + return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) + if defined($results) && $results->{hardduedate}; $sth->execute( "*", $itemtype, "*" ); $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; + return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) + if defined($results) && $results->{hardduedate}; $sth->execute( "*", "*", "*" ); $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; + return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) + if defined($results) && $results->{hardduedate}; # if no rule is set => return undefined return (undef, undef); @@ -1376,13 +1385,17 @@ sub GetBranchBorrowerCircRule { 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: +The return value is a hashref containing the following keys: 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. +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 + This searches branchitemrules in the following order: * Same branchcode and itemtype @@ -1390,7 +1403,7 @@ This searches branchitemrules in the following order: * branchcode '*', same itemtype * branchcode and itemtype '*' -Neither C<$branchcode> nor C<$categorycode> should be '*'. +Neither C<$branchcode> nor C<$itemtype> should be '*'. =cut @@ -1400,33 +1413,36 @@ sub GetBranchItemRule { my $result = {}; my @attempts = ( - ['SELECT holdallowed + ['SELECT holdallowed, returnbranch FROM branch_item_rules WHERE branchcode = ? AND itemtype = ?', $branchcode, $itemtype], - ['SELECT holdallowed + ['SELECT holdallowed, returnbranch FROM default_branch_circ_rules WHERE branchcode = ?', $branchcode], - ['SELECT holdallowed + ['SELECT holdallowed, returnbranch FROM default_branch_item_rules WHERE itemtype = ?', $itemtype], - ['SELECT holdallowed + ['SELECT holdallowed, returnbranch FROM default_circ_rules'], ); foreach my $attempt (@attempts) { my ($query, @bind_params) = @{$attempt}; + my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params ); # 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 ) ) ); + $result->{'holdallowed'} = $search_result->{'holdallowed'} unless ( defined $result->{'holdallowed'} ); + $result->{'returnbranch'} = $search_result->{'returnbranch'} unless ( defined $result->{'returnbranch'} ); } # built-in default circulation rule - return { - holdallowed => 2, - }; + $result->{'holdallowed'} = 2 unless ( defined $result->{'holdallowed'} ); + $result->{'returnbranch'} = 'homebranch' unless ( defined $result->{'returnbranch'} ); + + return $result; } =head2 AddReturn @@ -1515,7 +1531,8 @@ sub AddReturn { my $biblio; my $doreturn = 1; my $validTransfert = 0; - + my $stat_type = 'return'; + # get information on item my $itemnumber = GetItemnumberFromBarcode( $barcode ); unless ($itemnumber) { @@ -1532,14 +1549,20 @@ sub AddReturn { # even though item is not on loan, it may still be transferred; therefore, get current branch info $doreturn = 0; # No issue, no borrowernumber. ONLY if $doreturn, *might* you have a $borrower later. + # Record this as a local use, instead of a return, if the RecordLocalUseOnReturn is on + if (C4::Context->preference("RecordLocalUseOnReturn")) { + $messages->{'LocalUse'} = 1; + $stat_type = 'localuse'; + } } my $item = GetItem($itemnumber) or die "GetItem($itemnumber) failed"; # full item data, but no borrowernumber or checkout info (no issue) # we know GetItem should work because GetItemnumberFromBarcode worked - my $hbr = C4::Context->preference("HomeOrHoldingBranchReturn") || "homebranch"; - $hbr = $item->{$hbr} || ''; - # item must be from items table -- issues table has branchcode and issuingbranch, not homebranch nor holdingbranch + my $hbr = GetBranchItemRule($item->{'homebranch'}, $item->{'itype'})->{'returnbranch'} || "homebranch"; + # get the proper branch to which to return the item + $hbr = $item->{$hbr} || $branch ; + # if $hbr was "noreturn" or any other non-item table value, then it should 'float' (i.e. stay at this branch) my $borrowernumber = $borrower->{'borrowernumber'} || undef; # we don't know if we had a borrower or not @@ -1578,7 +1601,8 @@ sub AddReturn { # define circControlBranch only if dropbox mode is set # don't allow dropbox mode to create an invalid entry in issues (issuedate > today) # FIXME: check issuedate > returndate, factoring in holidays - $circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );; + #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );; + $circControlBranch = _GetCircControlBranch($item,$borrower); } if ($borrowernumber) { @@ -1626,11 +1650,15 @@ sub AddReturn { if ($borrowernumber) { my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox); defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!"; # zero is OK, check defined + + # fix fine days + my $debardate = _FixFineDaysOnReturn( $borrower, $item, $issue->{date_due} ); + $messages->{'Debarred'} = $debardate if ($debardate); } # find reserves..... # if we don't have a reserve with the status W, we launch the Checkreserves routine - my ($resfound, $resrec) = C4::Reserves::CheckReserves( $item->{'itemnumber'} ); + my ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} ); if ($resfound) { $resrec->{'ResFound'} = $resfound; $messages->{'ResFound'} = $resrec; @@ -1639,7 +1667,7 @@ sub AddReturn { # update stats? # Record the fact that this book was returned. UpdateStats( - $branch, 'return', '0', '', + $branch, $stat_type, '0', '', $item->{'itemnumber'}, $biblio->{'itemtype'}, $borrowernumber @@ -1711,27 +1739,27 @@ routine in C. sub MarkIssueReturned { my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy ) = @_; my $dbh = C4::Context->dbh; - my $query = "UPDATE issues SET returndate="; + my $query = 'UPDATE issues SET returndate='; my @bind; if ($dropbox_branch) { - my $calendar = C4::Calendar->new( branchcode => $dropbox_branch ); - my $dropboxdate = $calendar->addDate( C4::Dates->new(), -1 ); - $query .= " ? "; - push @bind, $dropboxdate->output('iso'); + my $calendar = Koha::Calendar->new( branchcode => $dropbox_branch ); + my $dropboxdate = $calendar->addDate( DateTime->now( time_zone => C4::Context->tz), -1 ); + $query .= ' ? '; + push @bind, $dropboxdate->strftime('%Y-%m-%d %H:%M'); } elsif ($returndate) { - $query .= " ? "; + $query .= ' ? '; push @bind, $returndate; } else { - $query .= " now() "; + $query .= ' now() '; } - $query .= " WHERE borrowernumber = ? AND itemnumber = ?"; + $query .= ' WHERE borrowernumber = ? AND itemnumber = ?'; push @bind, $borrowernumber, $itemnumber; # FIXME transaction my $sth_upd = $dbh->prepare($query); $sth_upd->execute(@bind); - my $sth_copy = $dbh->prepare("INSERT INTO old_issues SELECT * FROM issues + my $sth_copy = $dbh->prepare('INSERT INTO old_issues SELECT * FROM issues WHERE borrowernumber = ? - AND itemnumber = ?"); + AND itemnumber = ?'); $sth_copy->execute($borrowernumber, $itemnumber); # anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber if ( $privacy == 2) { @@ -1749,6 +1777,59 @@ sub MarkIssueReturned { $sth_del->execute($borrowernumber, $itemnumber); } +=head2 _FixFineDaysOnReturn + + &_FixFineDaysOnReturn($borrower, $item, $datedue); + +C<$borrower> borrower hashref + +C<$item> item hashref + +C<$datedue> date due + +Internal function, called only by AddReturn that calculate and update the user fine days, and debars him + +=cut + +sub _FixFineDaysOnReturn { + my ( $borrower, $item, $datedue ) = @_; + return unless ($datedue); + + my $dt_due = dt_from_string( $datedue ); + my $dt_today = DateTime->now( time_zone => C4::Context->tz() ); + + my $branchcode = _GetCircControlBranch( $item, $borrower ); + my $calendar = Koha::Calendar->new( branchcode => $branchcode ); + + # $deltadays is a DateTime::Duration object + my $deltadays = $calendar->days_between( $dt_due, $dt_today ); + + my $circcontrol = C4::Context::preference('CircControl'); + my $issuingrule = GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode ); + my $finedays = $issuingrule->{finedays}; + + # exit if no finedays defined + return unless $finedays; + my $grace = DateTime::Duration->new( days => $issuingrule->{firstremind} ); + + if ( ( $deltadays - $grace )->is_positive ) { # you can't compare DateTime::Durations with logical operators + my $new_debar_dt = $dt_today->clone()->add_duration( $deltadays * $finedays ); + my $borrower_debar_dt = dt_from_string( $borrower->{debarred} ); + # check to see if the current debar date is a valid date + if ( $borrower->{debarred} && $borrower_debar_dt ) { + # if so, is it before the new date? update only if true + if ( DateTime->compare( $borrower_debar_dt, $new_debar_dt ) == -1 ) { + C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() ); + return $new_debar_dt->ymd(); + } + # if the borrower's debar date is not set or valid, debar them + } else { + C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() ); + return $new_debar_dt->ymd(); + } + } +} + =head2 _FixOverduesOnReturn &_FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode); @@ -1832,10 +1913,11 @@ sub _FixAccountForLostAndReturned { my $item_id = @_ ? shift : $itemnumber; # Send the barcode if you want that logged in the description my $dbh = C4::Context->dbh; # check for charge made for lost book - my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE (itemnumber = ?) AND (accounttype='L' OR accounttype='Rep') ORDER BY date DESC"); + my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE itemnumber = ? AND accounttype IN ('L', 'Rep', 'W') ORDER BY date DESC, accountno DESC"); $sth->execute($itemnumber); my $data = $sth->fetchrow_hashref; $data or return; # bail if there is nothing to do + $data->{accounttype} eq 'W' and return; # Written off # writeoff this amount my $offset; @@ -1923,8 +2005,8 @@ sub _GetCircControlBranch { my $circcontrol = C4::Context->preference('CircControl'); my $branch; - if ($circcontrol eq 'PickupLibrary') { - $branch= C4::Context->userenv->{'branch'} if C4::Context->userenv; + if ($circcontrol eq 'PickupLibrary' and (C4::Context->userenv and C4::Context->userenv->{'branch'}) ) { + $branch= C4::Context->userenv->{'branch'}; } elsif ($circcontrol eq 'PatronLibrary') { $branch=$borrower->{branchcode}; } else { @@ -1961,14 +2043,19 @@ sub GetItemIssue { return unless $itemnumber; my $sth = C4::Context->dbh->prepare( "SELECT * - FROM issues + FROM issues LEFT JOIN items ON issues.itemnumber=items.itemnumber WHERE issues.itemnumber=?"); $sth->execute($itemnumber); my $data = $sth->fetchrow_hashref; return unless $data; - $data->{'overdue'} = ($data->{'date_due'} lt C4::Dates->today('iso')) ? 1 : 0; - return ($data); + $data->{issuedate} = dt_from_string($data->{issuedate}, 'sql'); + $data->{issuedate}->truncate(to => 'minutes'); + $data->{date_due} = dt_from_string($data->{date_due}, 'sql'); + $data->{date_due}->truncate(to => 'minutes'); + my $dt = DateTime->now( time_zone => C4::Context->tz)->truncate( to => 'minutes'); + $data->{'overdue'} = DateTime->compare($data->{'date_due'}, $dt ) == -1 ? 1 : 0; + return $data; } =head2 GetOpenIssue @@ -2010,14 +2097,15 @@ Returns reference to an array of hashes sub GetItemIssues { my ( $itemnumber, $history ) = @_; - my $today = C4::Dates->today('iso'); # get today date - my $sql = "SELECT * FROM issues + my $today = DateTime->now( time_zome => C4::Context->tz); # get today date + $today->truncate( to => 'minutes' ); + my $sql = "SELECT * FROM issues JOIN borrowers USING (borrowernumber) JOIN items USING (itemnumber) WHERE issues.itemnumber = ? "; if ($history) { $sql .= "UNION ALL - SELECT * FROM old_issues + SELECT * FROM old_issues LEFT JOIN borrowers USING (borrowernumber) JOIN items USING (itemnumber) WHERE old_issues.itemnumber = ? "; @@ -2031,7 +2119,10 @@ sub GetItemIssues { } my $results = $sth->fetchall_arrayref({}); foreach (@$results) { - $_->{'overdue'} = ($_->{'date_due'} lt $today) ? 1 : 0; + my $date_due = dt_from_string($_->{date_due},'sql'); + $date_due->truncate( to => 'minutes' ); + + $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0; } return $results; } @@ -2161,7 +2252,7 @@ sub CanBookBeRenewed { SELECT borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch FROM issuingrules, - issues + issues LEFT JOIN items USING (itemnumber) LEFT JOIN borrowers USING (borrowernumber) LEFT JOIN biblioitems USING (biblioitemnumber) @@ -2193,7 +2284,7 @@ sub CanBookBeRenewed { $error="too_many"; } - my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber); + my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber); if ($resfound) { $renewokay = 0; $error="on_reserve" @@ -2232,7 +2323,7 @@ sub AddRenewal { my $itemnumber = shift or return undef; my $branch = shift; my $datedue = shift; - my $lastreneweddate = shift || C4::Dates->new()->output('iso'); + my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd(); my $item = GetItem($itemnumber) or return undef; my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef; @@ -2246,21 +2337,21 @@ sub AddRenewal { $sth->execute( $borrowernumber, $itemnumber ); my $issuedata = $sth->fetchrow_hashref; $sth->finish; - if($datedue && ! $datedue->output('iso')){ - warn "Invalid date passed to AddRenewal."; - return undef; + if(defined $datedue && ref $datedue ne 'DateTime' ) { + carp 'Invalid date passed to AddRenewal.'; + return; } # 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) { - my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef; + my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) or return undef; my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'}; $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ? - C4::Dates->new($issuedata->{date_due}, 'iso') : - C4::Dates->new(); + $issuedata->{date_due} : + DateTime->now( time_zone => C4::Context->tz()); $datedue = CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower); } @@ -2271,12 +2362,13 @@ sub AddRenewal { WHERE borrowernumber=? AND itemnumber=?" ); - $sth->execute( $datedue->output('iso'), $renews, $lastreneweddate, $borrowernumber, $itemnumber ); + + $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $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, onloan => $datedue->output('iso') }, $biblio->{'biblionumber'}, $itemnumber); + ModItem({ renewals => $renews, onloan => $datedue->strftime('%Y-%m-%d %H:%M')}, $biblio->{'biblionumber'}, $itemnumber); # Charge a new rental fee, if applicable? my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber ); @@ -2294,7 +2386,6 @@ sub AddRenewal { $sth->execute( $borrowernumber, $accountno, $charge, $manager_id, "Renewal of Rental Item $item->{'title'} $item->{'barcode'}", 'Rent', $charge, $itemnumber ); - $sth->finish; } # Log the renewal UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber); @@ -2309,7 +2400,7 @@ sub GetRenewCount { my $renewsallowed = 0; my $renewsleft = 0; - my $borrower = C4::Members::GetMemberDetails($bornum); + my $borrower = C4::Members::GetMember( borrowernumber => $bornum); my $item = GetItem($itemno); # Look in the issues table for this item, lent to this borrower, @@ -2622,11 +2713,18 @@ sub SendCirculationAlert { 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 $letter = C4::Letters::GetPreparedLetter ( + module => 'circulation', + letter_code => $type, + branchcode => $branch, + tables => { + 'biblio' => $item->{biblionumber}, + 'biblioitems' => $item->{biblionumber}, + 'borrowers' => $borrower, + 'branches' => $branch, + } + ) or return; + my @transports = @{ $borrower_preferences->{transports} }; # warn "no transports" unless @transports; for (@transports) { @@ -2641,7 +2739,8 @@ sub SendCirculationAlert { $message->update; } } - $letter; + + return $letter; } =head2 updateWrongTransfer @@ -2696,88 +2795,89 @@ C<$borrower> = Borrower object =cut -sub CalcDateDue { - my ($startdate,$itemtype,$branch,$borrower) = @_; - my $datedue; - my $loanlength = GetLoanLength($borrower->{'categorycode'},$itemtype, $branch); - - # if globalDueDate ON the datedue is set to that date - if ( C4::Context->preference('globalDueDate') - && ( C4::Context->preference('globalDueDate') =~ C4::Dates->regexp('syspref') ) ) { - $datedue = C4::Dates->new( C4::Context->preference('globalDueDate') ); - } else { - # otherwise, calculate the datedue as normal - if(C4::Context->preference('useDaysMode') eq 'Days') { # ignoring calendar - my $timedue = time + ($loanlength) * 86400; - #FIXME - assumes now even though we take a startdate - 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 ); - $datedue = $calendar->addDate($startdate, $loanlength); - } - } - - # if Hard Due Dates are used, retreive them and apply as necessary - my ($hardduedate, $hardduedatecompare) = GetHardDueDate($borrower->{'categorycode'},$itemtype, $branch); - if ( $hardduedate && $hardduedate->output('iso') && $hardduedate->output('iso') ne '0000-00-00') { - # if the calculated due date is after the 'before' Hard Due Date (ceiling), override - if ( $datedue->output( 'iso' ) gt $hardduedate->output( 'iso' ) && $hardduedatecompare == -1) { - $datedue = $hardduedate; - # if the calculated date is before the 'after' Hard Due Date (floor), override - } elsif ( $datedue->output( 'iso' ) lt $hardduedate->output( 'iso' ) && $hardduedatecompare == 1) { - $datedue = $hardduedate; - # if the hard due date is set to 'exactly', overrride - } elsif ( $hardduedatecompare == 0) { - $datedue = $hardduedate; - } - # in all other cases, keep the date due as it is - } - - # 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' ); - } +sub CalcDateDue { + my ( $startdate, $itemtype, $branch, $borrower ) = @_; - return $datedue; -} + # loanlength now a href + my $loanlength = + GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch ); -=head2 CheckValidDatedue + my $datedue; - $newdatedue = CheckValidDatedue($date_due,$itemnumber,$branchcode); + # if globalDueDate ON the datedue is set to that date + if (C4::Context->preference('globalDueDate') + && ( C4::Context->preference('globalDueDate') =~ + C4::Dates->regexp('syspref') ) + ) { + $datedue = dt_from_string( + C4::Context->preference('globalDueDate'), + C4::Context->preference('dateformat') + ); + } else { -This function does not account for holiday exceptions nor does it handle the 'useDaysMode' syspref . -To be replaced by CalcDateDue() once C4::Calendar use is tested. + # otherwise, calculate the datedue as normal + if ( C4::Context->preference('useDaysMode') eq 'Days' ) + { # ignoring calendar + my $dt = + DateTime->now( time_zone => C4::Context->tz() ) + ->truncate( to => 'minute' ); + if ( $loanlength->{lengthunit} eq 'hours' ) { + $dt->add( hours => $loanlength->{issuelength} ); + return $dt; + } else { # days + $dt->add( days => $loanlength->{issuelength} ); + $dt->set_hour(23); + $dt->set_minute(59); + return $dt; + } + } else { + my $dur; + if ($loanlength->{lengthunit} eq 'hours') { + $dur = DateTime::Duration->new( hours => $loanlength->{issuelength}); + } + else { # days + $dur = DateTime::Duration->new( days => $loanlength->{issuelength}); + } + if (ref $startdate ne 'DateTime' ) { + $startdate = dt_from_string($startdate); + } + my $calendar = Koha::Calendar->new( branchcode => $branch ); + $datedue = $calendar->addDate( $startdate, $dur, $loanlength->{lengthunit} ); + if ($loanlength->{lengthunit} eq 'days') { + $datedue->set_hour(23); + $datedue->set_minute(59); + } + } + } -this function validates the loan length against the holidays calendar, and adjusts the due date as per the 'useDaysMode' syspref. -C<$date_due> = returndate calculate with no day check -C<$itemnumber> = itemnumber -C<$branchcode> = location of issue (affected by 'CircControl' syspref) -C<$loanlength> = loan length prior to adjustment + # if Hard Due Dates are used, retreive them and apply as necessary + my ( $hardduedate, $hardduedatecompare ) = + GetHardDueDate( $borrower->{'categorycode'}, $itemtype, $branch ); + if ($hardduedate) { # hardduedates are currently dates + $hardduedate->truncate( to => 'minute' ); + $hardduedate->set_hour(23); + $hardduedate->set_minute(59); + my $cmp = DateTime->compare( $hardduedate, $datedue ); + +# if the calculated due date is after the 'before' Hard Due Date (ceiling), override +# if the calculated date is before the 'after' Hard Due Date (floor), override +# if the hard due date is set to 'exactly', overrride + if ( $hardduedatecompare == 0 || $hardduedatecompare == $cmp ) { + $datedue = $hardduedate->clone; + } -=cut + # in all other cases, keep the date due as it is + } -sub CheckValidDatedue { -my ($date_due,$itemnumber,$branchcode)=@_; -my @datedue=split('-',$date_due->output('iso')); -my $years=$datedue[0]; -my $month=$datedue[1]; -my $day=$datedue[2]; -# die "Item# $itemnumber ($branchcode) due: " . ${date_due}->output() . "\n(Y,M,D) = ($years,$month,$day)": -my $dow; -for (my $i=0;$i<2;$i++){ - $dow=Day_of_Week($years,$month,$day); - ($dow=0) if ($dow>6); - my $result=CheckRepeatableHolidays($itemnumber,$dow,$branchcode); - my $countspecial=CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode); - my $countspecialrepeatable=CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode); - if (($result ne '0') or ($countspecial ne '0') or ($countspecialrepeatable ne '0') ){ - $i=0; - (($years,$month,$day) = Add_Delta_Days($years,$month,$day, 1))if ($i ne '1'); + # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate + if ( C4::Context->preference('ReturnBeforeExpiry') ) { + my $expiry_dt = dt_from_string( $borrower->{dateexpiry}, 'iso' ); + if ( DateTime->compare( $datedue, $expiry_dt ) == 1 ) { + $datedue = $expiry_dt->clone; } } - my $newdatedue=C4::Dates->new(sprintf("%04d-%02d-%02d",$years,$month,$day),'iso'); -return $newdatedue; + + return $datedue; } @@ -2933,18 +3033,196 @@ sub CreateBranchTransferLimit { =head2 DeleteBranchTransferLimits - DeleteBranchTransferLimits(); +DeleteBranchTransferLimits($frombranch); + +Deletes all the branch transfer limits for one branch =cut sub DeleteBranchTransferLimits { - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("TRUNCATE TABLE branch_transfer_limits"); - $sth->execute(); + my $branch = shift; + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?"); + $sth->execute($branch); +} + +sub ReturnLostItem{ + my ( $borrowernumber, $itemnum ) = @_; + + MarkIssueReturned( $borrowernumber, $itemnum ); + my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber ); + my @datearr = localtime(time); + my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3]; + my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}"; + ModItem({ paidfor => "Paid for by $bor $date" }, undef, $itemnum); +} + + +sub LostItem{ + my ($itemnumber, $mark_returned, $charge_fee) = @_; + + my $dbh = C4::Context->dbh(); + my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title + FROM issues + JOIN items USING (itemnumber) + JOIN biblio USING (biblionumber) + WHERE issues.itemnumber=?"); + $sth->execute($itemnumber); + my $issues=$sth->fetchrow_hashref(); + $sth->finish; + + # if a borrower lost the item, add a replacement cost to the their record + if ( my $borrowernumber = $issues->{borrowernumber} ){ + + C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}") + if $charge_fee; + #FIXME : Should probably have a way to distinguish this from an item that really was returned. + #warn " $issues->{'borrowernumber'} / $itemnumber "; + MarkIssueReturned($borrowernumber,$itemnumber) if $mark_returned; + } +} + +sub GetOfflineOperations { + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE branchcode=? ORDER BY timestamp"); + $sth->execute(C4::Context->userenv->{'branch'}); + my $results = $sth->fetchall_arrayref({}); + $sth->finish; + return $results; +} + +sub GetOfflineOperation { + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE operationid=?"); + $sth->execute( shift ); + my $result = $sth->fetchrow_hashref; + $sth->finish; + return $result; +} + +sub AddOfflineOperation { + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber) VALUES(?,?,?,?,?,?)"); + $sth->execute( @_ ); + return "Added."; +} + +sub DeleteOfflineOperation { + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare("DELETE FROM pending_offline_operations WHERE operationid=?"); + $sth->execute( shift ); + return "Deleted."; +} + +sub ProcessOfflineOperation { + my $operation = shift; + + my $report; + if ( $operation->{action} eq 'return' ) { + $report = ProcessOfflineReturn( $operation ); + } elsif ( $operation->{action} eq 'issue' ) { + $report = ProcessOfflineIssue( $operation ); + } + + DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid}; + + return $report; +} + +sub ProcessOfflineReturn { + my $operation = shift; + + my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} ); + + if ( $itemnumber ) { + my $issue = GetOpenIssue( $itemnumber ); + if ( $issue ) { + MarkIssueReturned( + $issue->{borrowernumber}, + $itemnumber, + undef, + $operation->{timestamp}, + ); + ModItem( + { renewals => 0, onloan => undef }, + $issue->{'biblionumber'}, + $itemnumber + ); + return "Success."; + } else { + return "Item not issued."; + } + } else { + return "Item not found."; + } +} + +sub ProcessOfflineIssue { + my $operation = shift; + + my $borrower = C4::Members::GetMemberDetails( undef, $operation->{cardnumber} ); # Get borrower from operation cardnumber + + if ( $borrower->{borrowernumber} ) { + my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} ); + unless ($itemnumber) { + return "Barcode not found."; + } + my $issue = GetOpenIssue( $itemnumber ); + + if ( $issue and ( $issue->{borrowernumber} ne $borrower->{borrowernumber} ) ) { # Item already issued to another borrower, mark it returned + MarkIssueReturned( + $issue->{borrowernumber}, + $itemnumber, + undef, + $operation->{timestamp}, + ); + } + AddIssue( + $borrower, + $operation->{'barcode'}, + undef, + 1, + $operation->{timestamp}, + undef, + ); + return "Success."; + } else { + return "Borrower not found."; + } +} + + + +=head2 TransferSlip + + TransferSlip($user_branch, $itemnumber, $to_branch) + + Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef + +=cut + +sub TransferSlip { + my ($branch, $itemnumber, $to_branch) = @_; + + my $item = GetItem( $itemnumber ) + or return; + + my $pulldate = C4::Dates->new(); + + return C4::Letters::GetPreparedLetter ( + module => 'circulation', + letter_code => 'TRANSFERSLIP', + branchcode => $branch, + tables => { + 'branches' => $to_branch, + 'biblio' => $item->{biblionumber}, + 'items' => $item, + }, + ); } - 1; +1; __END__