Bug 12243: ensure that records with invalid ISBNs do not cause searches to fail
[koha_fer] / C4 / Circulation.pm
index b5a5172..639956b 100644 (file)
@@ -47,6 +47,7 @@ use Algorithm::CheckDigits;
 use Data::Dumper;
 use Koha::DateUtils;
 use Koha::Calendar;
 use Data::Dumper;
 use Koha::DateUtils;
 use Koha::Calendar;
+use Koha::Borrower::Debarments;
 use Carp;
 use Date::Calc qw(
   Today
 use Carp;
 use Date::Calc qw(
   Today
@@ -78,6 +79,7 @@ BEGIN {
                &AddIssue
                &AddRenewal
                &GetRenewCount
                &AddIssue
                &AddRenewal
                &GetRenewCount
+        &GetSoonestRenewDate
                &GetItemIssue
                &GetItemIssues
                &GetIssuingCharges
                &GetItemIssue
                &GetItemIssues
                &GetIssuingCharges
@@ -88,6 +90,7 @@ BEGIN {
                &GetOpenIssue
                &AnonymiseIssueHistory
         &CheckIfIssuedToPatron
                &GetOpenIssue
                &AnonymiseIssueHistory
         &CheckIfIssuedToPatron
+        &IsItemIssued
        );
 
        # subs to deal with returns
        );
 
        # subs to deal with returns
@@ -564,7 +567,7 @@ sub itemissues {
             $data->{'borrower'} = $data2->{'borrowernumber'};
         }
         else {
             $data->{'borrower'} = $data2->{'borrowernumber'};
         }
         else {
-            $data->{'date_due'} = ($data->{'wthdrawn'} eq '1') ? 'Cancelled' : 'Available';
+            $data->{'date_due'} = ($data->{'withdrawn'} eq '1') ? 'Cancelled' : 'Available';
         }
 
 
         }
 
 
@@ -826,9 +829,15 @@ sub CanBookBeIssued {
         $needsconfirmation{PATRON_CANT} = 1;
     } else {
         if($max_loans_allowed){
         $needsconfirmation{PATRON_CANT} = 1;
     } else {
         if($max_loans_allowed){
-            $needsconfirmation{TOO_MANY} = 1;
-            $needsconfirmation{current_loan_count} = $current_loan_count;
-            $needsconfirmation{max_loans_allowed} = $max_loans_allowed;
+            if ( C4::Context->preference("AllowTooManyOverride") ) {
+                $needsconfirmation{TOO_MANY} = 1;
+                $needsconfirmation{current_loan_count} = $current_loan_count;
+                $needsconfirmation{max_loans_allowed} = $max_loans_allowed;
+            } else {
+                $issuingimpossible{TOO_MANY} = 1;
+                $issuingimpossible{current_loan_count} = $current_loan_count;
+                $issuingimpossible{max_loans_allowed} = $max_loans_allowed;
+            }
         }
     }
 
         }
     }
 
@@ -852,7 +861,6 @@ sub CanBookBeIssued {
             my $sth = $dbh->prepare("SELECT notforloan FROM itemtypes WHERE itemtype = ?");
             $sth->execute($item->{'itemtype'});
             my $notforloan=$sth->fetchrow_hashref();
             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;
             if ($notforloan->{'notforloan'}) {
                 if (!C4::Context->preference("AllowNotForLoanOverride")) {
                     $issuingimpossible{NOT_FOR_LOAN} = 1;
@@ -873,7 +881,7 @@ sub CanBookBeIssued {
             }
         }
     }
             }
         }
     }
-    if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} > 0 )
+    if ( $item->{'withdrawn'} && $item->{'withdrawn'} > 0 )
     {
         $issuingimpossible{WTHDRAWN} = 1;
     }
     {
         $issuingimpossible{WTHDRAWN} = 1;
     }
@@ -887,11 +895,13 @@ sub CanBookBeIssued {
         $needsconfirmation{ITEM_LOST} = $code if ( C4::Context->preference("IssueLostItem") eq 'confirm' );
         $alerts{ITEM_LOST} = $code if ( C4::Context->preference("IssueLostItem") eq 'alert' );
     }
         $needsconfirmation{ITEM_LOST} = $code if ( C4::Context->preference("IssueLostItem") eq 'confirm' );
         $alerts{ITEM_LOST} = $code if ( C4::Context->preference("IssueLostItem") eq 'alert' );
     }
-    if ( C4::Context->preference("IndependantBranches") ) {
+    if ( C4::Context->preference("IndependentBranches") ) {
         my $userenv = C4::Context->userenv;
         my $userenv = C4::Context->userenv;
-        if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
-            $issuingimpossible{ITEMNOTSAMEBRANCH} = 1
-              if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} );
+        unless ( C4::Context->IsSuperLibrarian() ) {
+            if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} ){
+                $issuingimpossible{ITEMNOTSAMEBRANCH} = 1;
+                $issuingimpossible{'itemhomebranch'} = $item->{C4::Context->preference("HomeOrHoldingBranch")};
+            }
             $needsconfirmation{BORRNOTSAMEBRANCH} = GetBranchName( $borrower->{'branchcode'} )
               if ( $borrower->{'branchcode'} ne $userenv->{branch} );
         }
             $needsconfirmation{BORRNOTSAMEBRANCH} = GetBranchName( $borrower->{'branchcode'} )
               if ( $borrower->{'branchcode'} ne $userenv->{branch} );
         }
@@ -962,64 +972,35 @@ sub CanBookBeIssued {
             }
         }
     }
             }
         }
     }
-    #
-    # CHECK AGE RESTRICTION
-    #
 
 
+    ## CHECK AGE RESTRICTION
     # get $marker from preferences. Could be something like "FSK|PEGI|Alter|Age:"
     # get $marker from preferences. Could be something like "FSK|PEGI|Alter|Age:"
-    my $markers = C4::Context->preference('AgeRestrictionMarker' );
-    my $bibvalues = $biblioitem->{'agerestriction'};
-    if (($markers)&&($bibvalues))
-    {
-        # Split $bibvalues to something like FSK 16 or PEGI 6
-        my @values = split ' ', $bibvalues;
-
-        # Search first occurence of one of the markers
-        my @markers = split /\|/, $markers;
-        my $index = 0;
-        my $take = -1;
-        for my $value (@values) {
-            $index ++;
-            for my $marker (@markers) {
-                $marker =~ s/^\s+//; #remove leading spaces
-                $marker =~ s/\s+$//; #remove trailing spaces
-                if (uc($marker) eq uc($value)) {
-                    $take = $index;
-                    last;
-                }
-            }
-            if ($take > -1) {
-                last;
+    my $markers         = C4::Context->preference('AgeRestrictionMarker');
+    my $bibvalues       = $biblioitem->{'agerestriction'};
+    my $restriction_age = GetAgeRestriction( $bibvalues );
+
+    if ( $restriction_age > 0 ) {
+        if ( $borrower->{'dateofbirth'} ) {
+            my @alloweddate = split /-/, $borrower->{'dateofbirth'};
+            $alloweddate[0] += $restriction_age;
+
+            #Prevent runime eror on leap year (invalid date)
+            if ( ( $alloweddate[1] == 2 ) && ( $alloweddate[2] == 29 ) ) {
+                $alloweddate[2] = 28;
             }
             }
-        }
-        # Index points to the next value
-        my $restrictionyear = 0;
-        if (($take <= $#values) && ($take >= 0)){
-            $restrictionyear += $values[$take];
-        }
 
 
-        if ($restrictionyear > 0) {
-            if ( $borrower->{'dateofbirth'}  ) {
-                my @alloweddate =  split /-/,$borrower->{'dateofbirth'} ;
-                $alloweddate[0] += $restrictionyear;
-                #Prevent runime eror on leap year (invalid date)
-                if (($alloweddate[1] == 2) && ($alloweddate[2] == 29)) {
-                    $alloweddate[2] = 28;
+            if ( Date_to_Days(Today) < Date_to_Days(@alloweddate) - 1 ) {
+                if ( C4::Context->preference('AgeRestrictionOverride') ) {
+                    $needsconfirmation{AGE_RESTRICTION} = "$bibvalues";
                 }
                 }
-
-                if ( Date_to_Days(Today) <  Date_to_Days(@alloweddate) -1  ) {
-                    if (C4::Context->preference('AgeRestrictionOverride' )) {
-                        $needsconfirmation{AGE_RESTRICTION} = "$bibvalues";
-                    }
-                    else {
-                        $issuingimpossible{AGE_RESTRICTION} = "$bibvalues";
-                    }
+                else {
+                    $issuingimpossible{AGE_RESTRICTION} = "$bibvalues";
                 }
             }
         }
     }
 
                 }
             }
         }
     }
 
-## check for high holds decreasing loan period
+    ## check for high holds decreasing loan period
     my $decrease_loan = C4::Context->preference('decreaseLoanHighHolds');
     if ( $decrease_loan && $decrease_loan == 1 ) {
         my ( $reserved, $num, $duration, $returndate ) =
     my $decrease_loan = C4::Context->preference('decreaseLoanHighHolds');
     if ( $decrease_loan && $decrease_loan == 1 ) {
         my ( $reserved, $num, $duration, $returndate ) =
@@ -1034,6 +1015,32 @@ sub CanBookBeIssued {
         }
     }
 
         }
     }
 
+    if (
+        !C4::Context->preference('AllowMultipleIssuesOnABiblio') &&
+        # don't do the multiple loans per bib check if we've
+        # already determined that we've got a loan on the same item
+        !$issuingimpossible{NO_MORE_RENEWALS} &&
+        !$needsconfirmation{RENEW_ISSUE}
+    ) {
+        # Check if borrower has already issued an item from the same biblio
+        # Only if it's not a subscription
+        my $biblionumber = $item->{biblionumber};
+        require C4::Serials;
+        my $is_a_subscription = C4::Serials::CountSubscriptionFromBiblionumber($biblionumber);
+        unless ($is_a_subscription) {
+            my $issues = GetIssues( {
+                borrowernumber => $borrower->{borrowernumber},
+                biblionumber   => $biblionumber,
+            } );
+            my @issues = $issues ? @$issues : ();
+            # if we get here, we don't already have a loan on this item,
+            # so if there are any loans on this bib, ask for confirmation
+            if (scalar @issues > 0) {
+                $needsconfirmation{BIBLIO_ALREADY_ISSUED} = 1;
+            }
+        }
+    }
+
     return ( \%issuingimpossible, \%needsconfirmation, \%alerts );
 }
 
     return ( \%issuingimpossible, \%needsconfirmation, \%alerts );
 }
 
@@ -1334,15 +1341,20 @@ Get loan length for an itemtype, a borrower type and a branch
 sub GetLoanLength {
     my ( $borrowertype, $itemtype, $branchcode ) = @_;
     my $dbh = C4::Context->dbh;
 sub GetLoanLength {
     my ( $borrowertype, $itemtype, $branchcode ) = @_;
     my $dbh = C4::Context->dbh;
-    my $sth =
-      $dbh->prepare(
-'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
+    my $sth = $dbh->prepare(qq{
+        SELECT issuelength, lengthunit, renewalperiod
+        FROM issuingrules
+        WHERE   categorycode=?
+            AND itemtype=?
+            AND branchcode=?
+            AND issuelength IS NOT NULL
+    });
+
+    # 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;
     $sth->execute( $borrowertype, $itemtype, $branchcode );
     my $loanlength = $sth->fetchrow_hashref;
+
     return $loanlength
       if defined($loanlength) && $loanlength->{issuelength};
 
     return $loanlength
       if defined($loanlength) && $loanlength->{issuelength};
 
@@ -1384,6 +1396,7 @@ sub GetLoanLength {
     # if no rule is set => 21 days (hardcoded)
     return {
         issuelength => 21,
     # if no rule is set => 21 days (hardcoded)
     return {
         issuelength => 21,
+        renewalperiod => 21,
         lengthunit => 'days',
     };
 
         lengthunit => 'days',
     };
 
@@ -1418,7 +1431,7 @@ sub GetHardDueDate {
 
 FIXME - This is a copy-paste of GetLoanLength
 as a stop-gap.  Do not wish to change API for GetLoanLength 
 
 FIXME - This is a copy-paste of GetLoanLength
 as a stop-gap.  Do not wish to change API for GetLoanLength 
-this close to release, however, Overdues::GetIssuingRules is broken.
+this close to release.
 
 Get the issuing rule for an itemtype, a borrower type and a branch
 Returns a hashref from the issuingrules table.
 
 Get the issuing rule for an itemtype, a borrower type and a branch
 Returns a hashref from the issuingrules table.
@@ -1621,7 +1634,7 @@ sub GetBranchItemRule {
 =head2 AddReturn
 
   ($doreturn, $messages, $iteminformation, $borrower) =
 =head2 AddReturn
 
   ($doreturn, $messages, $iteminformation, $borrower) =
-      &AddReturn($barcode, $branch, $exemptfine, $dropbox);
+      &AddReturn( $barcode, $branch [,$exemptfine] [,$dropbox] [,$returndate] );
 
 Returns a book.
 
 
 Returns a book.
 
@@ -1632,13 +1645,16 @@ Returns a book.
 =item C<$branch> is the code of the branch where the book is being returned.
 
 =item C<$exemptfine> indicates that overdue charges for the item will be
 =item C<$branch> is the code of the branch where the book is being returned.
 
 =item C<$exemptfine> indicates that overdue charges for the item will be
-removed.
+removed. Optional.
 
 =item C<$dropbox> indicates that the check-in date is assumed to be
 yesterday, or the last non-holiday as defined in C4::Calendar .  If
 overdue charges are applied and C<$dropbox> is true, the last charge
 will be removed.  This assumes that the fines accrual script has run
 
 =item C<$dropbox> indicates that the check-in date is assumed to be
 yesterday, or the last non-holiday as defined in C4::Calendar .  If
 overdue charges are applied and C<$dropbox> is true, the last charge
 will be removed.  This assumes that the fines accrual script has run
-for _today_.
+for _today_. Optional.
+
+=item C<$return_date> allows the default return date to be overridden
+by the given return date. Optional.
 
 =back
 
 
 =back
 
@@ -1665,7 +1681,7 @@ The book's home branch is a permanent collection. If you have borrowed
 this book, you are not allowed to return it. The value is the code for
 the book's home branch.
 
 this book, you are not allowed to return it. The value is the code for
 the book's home branch.
 
-=item C<wthdrawn>
+=item C<withdrawn>
 
 This book has been withdrawn/cancelled. The value should be ignored.
 
 
 This book has been withdrawn/cancelled. The value should be ignored.
 
@@ -1693,7 +1709,7 @@ patron who last borrowed the book.
 =cut
 
 sub AddReturn {
 =cut
 
 sub AddReturn {
-    my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
+    my ( $barcode, $branch, $exemptfine, $dropbox, $return_date ) = @_;
 
     if ($branch and not GetBranchDetail($branch)) {
         warn "AddReturn error: branch '$branch' not found.  Reverting to " . C4::Context->userenv->{'branch'};
 
     if ($branch and not GetBranchDetail($branch)) {
         warn "AddReturn error: branch '$branch' not found.  Reverting to " . C4::Context->userenv->{'branch'};
@@ -1758,15 +1774,16 @@ sub AddReturn {
         return ( $doreturn, $messages, $issue, $borrower );
     }
 
         return ( $doreturn, $messages, $issue, $borrower );
     }
 
-    if ( $item->{'wthdrawn'} ) { # book has been cancelled
-        $messages->{'wthdrawn'} = 1;
+    if ( $item->{'withdrawn'} ) { # book has been cancelled
+        $messages->{'withdrawn'} = 1;
         $doreturn = 0 if C4::Context->preference("BlockReturnOfWithdrawnItems");
     }
 
     # case of a return of document (deal with issues and holdingbranch)
     my $today = DateTime->now( time_zone => C4::Context->tz() );
         $doreturn = 0 if C4::Context->preference("BlockReturnOfWithdrawnItems");
     }
 
     # case of a return of document (deal with issues and holdingbranch)
     my $today = DateTime->now( time_zone => C4::Context->tz() );
+
     if ($doreturn) {
     if ($doreturn) {
-    my $datedue = $issue->{date_due};
+        my $datedue = $issue->{date_due};
         $borrower or warn "AddReturn without current borrower";
                my $circControlBranch;
         if ($dropbox) {
         $borrower or warn "AddReturn without current borrower";
                my $circControlBranch;
         if ($dropbox) {
@@ -1775,23 +1792,52 @@ sub AddReturn {
             # FIXME: check issuedate > returndate, factoring in holidays
             #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
             $circControlBranch = _GetCircControlBranch($item,$borrower);
             # FIXME: check issuedate > returndate, factoring in holidays
             #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
             $circControlBranch = _GetCircControlBranch($item,$borrower);
-        $issue->{'overdue'} = DateTime->compare($issue->{'date_due'}, $today ) == -1 ? 1 : 0;
+            $issue->{'overdue'} = DateTime->compare($issue->{'date_due'}, $today ) == -1 ? 1 : 0;
         }
 
         if ($borrowernumber) {
         }
 
         if ($borrowernumber) {
-        if($issue->{'overdue'}){
-                my ( $amount, $type, $unitcounttotal ) = C4::Overdues::CalcFine( $item, $borrower->{categorycode},$branch, $datedue, $today );
+            if ( ( C4::Context->preference('CalculateFinesOnReturn') && $issue->{'overdue'} ) || $return_date ) {
+                # we only need to calculate and change the fines if we want to do that on return
+                # Should be on for hourly loans
+                my $control = C4::Context->preference('CircControl');
+                my $control_branchcode =
+                    ( $control eq 'ItemHomeLibrary' ) ? $item->{homebranch}
+                  : ( $control eq 'PatronLibrary' )   ? $borrower->{branchcode}
+                  :                                     $issue->{branchcode};
+
+                my $date_returned =
+                  $return_date ? dt_from_string($return_date) : $today;
+
+                my ( $amount, $type, $unitcounttotal ) =
+                  C4::Overdues::CalcFine( $item, $borrower->{categorycode},
+                    $control_branchcode, $datedue, $date_returned );
+
                 $type ||= q{};
                 $type ||= q{};
-        if ( $amount > 0 && ( C4::Context->preference('finesMode') eq 'production' )) {
-          C4::Overdues::UpdateFine(
-              $issue->{itemnumber},
-              $issue->{borrowernumber},
-                      $amount, $type, output_pref($datedue)
-              );
-        }
+
+                if ( C4::Context->preference('finesMode') eq 'production' ) {
+                    if ( $amount > 0 ) {
+                        C4::Overdues::UpdateFine( $issue->{itemnumber},
+                            $issue->{borrowernumber},
+                            $amount, $type, output_pref($datedue) );
+                    }
+                    elsif ($return_date) {
+
+                       # Backdated returns may have fines that shouldn't exist,
+                       # so in this case, we need to drop those fines to 0
+
+                        C4::Overdues::UpdateFine( $issue->{itemnumber},
+                            $issue->{borrowernumber},
+                            0, $type, output_pref($datedue) );
+                    }
+                }
             }
             }
-            MarkIssueReturned($borrowernumber, $item->{'itemnumber'}, $circControlBranch, '', $borrower->{'privacy'});
-            $messages->{'WasReturned'} = 1;    # FIXME is the "= 1" right?  This could be the borrower hash.
+
+            MarkIssueReturned( $borrowernumber, $item->{'itemnumber'},
+                $circControlBranch, $return_date, $borrower->{'privacy'} );
+
+            # FIXME is the "= 1" right?  This could be the borrower hash.
+            $messages->{'WasReturned'} = 1;
+
         }
 
         ModItem({ onloan => undef }, $issue->{'biblionumber'}, $item->{'itemnumber'});
         }
 
         ModItem({ onloan => undef }, $issue->{'biblionumber'}, $item->{'itemnumber'});
@@ -1843,17 +1889,29 @@ sub AddReturn {
         defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!";  # zero is OK, check defined
         
         if ( $issue->{overdue} && $issue->{date_due} ) {
         defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!";  # zero is OK, check defined
         
         if ( $issue->{overdue} && $issue->{date_due} ) {
-# fix fine days
-            my $debardate =
-              _debar_user_on_return( $borrower, $item, $issue->{date_due}, $today );
-            $messages->{Debarred} = $debardate if ($debardate);
+        # fix fine days
+            my ($debardate,$reminder) = _debar_user_on_return( $borrower, $item, $issue->{date_due}, $today );
+            if ($reminder){
+                $messages->{'PrevDebarred'} = $debardate;
+            } else {
+                $messages->{'Debarred'} = $debardate if $debardate;
+            }
+        # there's no overdue on the item but borrower had been previously debarred
+        } elsif ( $issue->{date_due} and $borrower->{'debarred'} ) {
+             my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
+             $borrower_debar_dt->truncate(to => 'day');
+             my $today_dt = $today->clone()->truncate(to => 'day');
+             if ( DateTime->compare( $borrower_debar_dt, $today_dt ) != -1 ) {
+                 $messages->{'PrevDebarred'} = $borrower->{'debarred'};
+             }
         }
     }
 
     # find reserves.....
     # if we don't have a reserve with the status W, we launch the Checkreserves routine
     my ($resfound, $resrec);
         }
     }
 
     # find reserves.....
     # if we don't have a reserve with the status W, we launch the Checkreserves routine
     my ($resfound, $resrec);
-    ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} ) unless ( $item->{'wthdrawn'} );
+    my $lookahead= C4::Context->preference('ConfirmFutureHolds'); #number of days to look for future holds
+    ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'}, undef, $lookahead ) unless ( $item->{'withdrawn'} );
     if ($resfound) {
           $resrec->{'ResFound'} = $resfound;
         $messages->{'ResFound'} = $resrec;
     if ($resfound) {
           $resrec->{'ResFound'} = $resfound;
         $messages->{'ResFound'} = $resrec;
@@ -1888,6 +1946,16 @@ sub AddReturn {
     logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'itemnumber'})
         if C4::Context->preference("ReturnLog");
     
     logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'itemnumber'})
         if C4::Context->preference("ReturnLog");
     
+    # Remove any OVERDUES related debarment if the borrower has no overdues
+    if ( $borrowernumber
+      && $borrower->{'debarred'}
+      && C4::Context->preference('AutoRemoveOverduesRestrictions')
+      && !HasOverdues( $borrowernumber )
+      && @{ GetDebarments({ borrowernumber => $borrowernumber, type => 'OVERDUES' }) }
+    ) {
+        DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' });
+    }
+
     # FIXME: make this comment intelligible.
     #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 .
     # FIXME: make this comment intelligible.
     #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 .
@@ -1961,6 +2029,7 @@ sub MarkIssueReturned {
     if ( $privacy == 2) {
         # The default of 0 does not work due to foreign key constraints
         # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
     if ( $privacy == 2) {
         # The default of 0 does not work due to foreign key constraints
         # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
+        # FIXME the above is unacceptable - bug 9942 relates
         my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
         my $sth_ano = $dbh->prepare("UPDATE old_issues SET borrowernumber=?
                                   WHERE borrowernumber = ?
         my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
         my $sth_ano = $dbh->prepare("UPDATE old_issues SET borrowernumber=?
                                   WHERE borrowernumber = ?
@@ -2020,19 +2089,16 @@ sub _debar_user_on_return {
 
             my $new_debar_dt =
               $dt_today->clone()->add_duration( $deltadays * $finedays );
 
             my $new_debar_dt =
               $dt_today->clone()->add_duration( $deltadays * $finedays );
-            if ( $borrower->{debarred} ) {
-                my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
-
-                # Update patron only if new date > old
-                if ( DateTime->compare( $borrower_debar_dt, $new_debar_dt ) !=
-                    -1 )
-                {
-                    return;
-                }
 
 
+            Koha::Borrower::Debarments::AddUniqueDebarment({
+                borrowernumber => $borrower->{borrowernumber},
+                expiration     => $new_debar_dt->ymd(),
+                type           => 'SUSPENSION',
+            });
+            # if borrower was already debarred but does not get an extra debarment
+            if ( $borrower->{debarred} eq Koha::Borrower::Debarments::IsDebarred($borrower->{borrowernumber}) ) {
+                    return ($borrower->{debarred},1);
             }
             }
-            C4::Members::DebarMember( $borrower->{borrowernumber},
-                $new_debar_dt->ymd() );
             return $new_debar_dt->ymd();
         }
     }
             return $new_debar_dt->ymd();
         }
     }
@@ -2165,14 +2231,13 @@ sub _FixAccountForLostAndReturned {
             # FIXME: move prepares outside while loop!
             my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
                     WHERE (accountlines_id = ?)");
             # FIXME: move prepares outside while loop!
             my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
                     WHERE (accountlines_id = ?)");
-            $usth->execute($newamtos,'$thisacct');    # FIXME: '$thisacct' is a string literal!
+            $usth->execute($newamtos,$thisacct);
             $usth = $dbh->prepare("INSERT INTO accountoffsets
                 (borrowernumber, accountno, offsetaccount,  offsetamount)
                 VALUES
                 (?,?,?,?)");
             $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
         }
             $usth = $dbh->prepare("INSERT INTO accountoffsets
                 (borrowernumber, accountno, offsetaccount,  offsetamount)
                 VALUES
                 (?,?,?,?)");
             $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
         }
-        $msth->finish;  # $msth might actually have data left
     }
     $amountleft *= -1 if ($amountleft > 0);
     my $desc = "Item Returned " . $item_id;
     }
     $amountleft *= -1 if ($amountleft > 0);
     my $desc = "Item Returned " . $item_id;
@@ -2249,7 +2314,7 @@ sub GetItemIssue {
     my ($itemnumber) = @_;
     return unless $itemnumber;
     my $sth = C4::Context->dbh->prepare(
     my ($itemnumber) = @_;
     return unless $itemnumber;
     my $sth = C4::Context->dbh->prepare(
-        "SELECT *
+        "SELECT items.*, issues.*
         FROM issues
         LEFT JOIN items ON issues.itemnumber=items.itemnumber
         WHERE issues.itemnumber=?");
         FROM issues
         LEFT JOIN items ON issues.itemnumber=items.itemnumber
         WHERE issues.itemnumber=?");
@@ -2279,12 +2344,79 @@ Returns a hashref
 
 sub GetOpenIssue {
   my ( $itemnumber ) = @_;
 
 sub GetOpenIssue {
   my ( $itemnumber ) = @_;
-
+  return unless $itemnumber;
   my $dbh = C4::Context->dbh;  
   my $sth = $dbh->prepare( "SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
   $sth->execute( $itemnumber );
   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();
-  return $issue;
+  return $sth->fetchrow_hashref();
+
+}
+
+=head2 GetIssues
+
+    $issues = GetIssues({});    # return all issues!
+    $issues = GetIssues({ borrowernumber => $borrowernumber, biblionumber => $biblionumber });
+
+Returns all pending issues that match given criteria.
+Returns a arrayref or undef if an error occurs.
+
+Allowed criteria are:
+
+=over 2
+
+=item * borrowernumber
+
+=item * biblionumber
+
+=item * itemnumber
+
+=back
+
+=cut
+
+sub GetIssues {
+    my ($criteria) = @_;
+
+    # Build filters
+    my @filters;
+    my @allowed = qw(borrowernumber biblionumber itemnumber);
+    foreach (@allowed) {
+        if (defined $criteria->{$_}) {
+            push @filters, {
+                field => $_,
+                value => $criteria->{$_},
+            };
+        }
+    }
+
+    # Do we need to join other tables ?
+    my %join;
+    if (defined $criteria->{biblionumber}) {
+        $join{items} = 1;
+    }
+
+    # Build SQL query
+    my $where = '';
+    if (@filters) {
+        $where = "WHERE " . join(' AND ', map { "$_->{field} = ?" } @filters);
+    }
+    my $query = q{
+        SELECT issues.*
+        FROM issues
+    };
+    if (defined $join{items}) {
+        $query .= q{
+            LEFT JOIN items ON (issues.itemnumber = items.itemnumber)
+        };
+    }
+    $query .= $where;
+
+    # Execute SQL query
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare($query);
+    my $rv = $sth->execute(map { $_->{value} } @filters);
+
+    return $rv ? $sth->fetchall_arrayref({}) : undef;
 }
 
 =head2 GetItemIssues
 }
 
 =head2 GetItemIssues
@@ -2395,8 +2527,8 @@ SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-
 FROM issues 
 LEFT JOIN items USING (itemnumber)
 LEFT OUTER JOIN branches USING (branchcode)
 FROM issues 
 LEFT JOIN items USING (itemnumber)
 LEFT OUTER JOIN branches USING (branchcode)
-WhERE returndate is NULL
-AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
+WHERE returndate is NULL
+HAVING days_until_due >= 0 AND days_until_due <= ?
 END_SQL
 
     my @bind_parameters = ( $params->{'days_in_advance'} );
 END_SQL
 
     my @bind_parameters = ( $params->{'days_in_advance'} );
@@ -2404,7 +2536,6 @@ END_SQL
     my $sth = $dbh->prepare( $statement );
     $sth->execute( @bind_parameters );
     my $upcoming_dues = $sth->fetchall_arrayref({});
     my $sth = $dbh->prepare( $statement );
     $sth->execute( @bind_parameters );
     my $upcoming_dues = $sth->fetchall_arrayref({});
-    $sth->finish;
 
     return $upcoming_dues;
 }
 
     return $upcoming_dues;
 }
@@ -2415,8 +2546,6 @@ END_SQL
 
 Find out whether a borrowed item may be renewed.
 
 
 Find out whether a borrowed item may be renewed.
 
-C<$dbh> is a DBI handle to the Koha database.
-
 C<$borrowernumber> is the borrower number of the patron who currently
 has the item on loan.
 
 C<$borrowernumber> is the borrower number of the patron who currently
 has the item on loan.
 
@@ -2426,7 +2555,7 @@ 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.
 
 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
+C<$CanBookBeRenewed> returns a true value if 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
 already renewed the loan. $error will contain the reason the renewal can not proceed
 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
 already renewed the loan. $error will contain the reason the renewal can not proceed
@@ -2434,69 +2563,56 @@ already renewed the loan. $error will contain the reason the renewal can not pro
 =cut
 
 sub CanBookBeRenewed {
 =cut
 
 sub CanBookBeRenewed {
-
-    # check renewal status
     my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
     my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
+
     my $dbh       = C4::Context->dbh;
     my $renews    = 1;
     my $dbh       = C4::Context->dbh;
     my $renews    = 1;
-    my $renewokay = 0;
-       my $error;
+    my $renewokay = 1;
+    my $error;
 
 
-    # Look in the issues table for this item, lent to this borrower,
-    # and not yet returned.
+    my $item      = GetItem($itemnumber)      or return ( 0, 'no_item' );
+    my $itemissue = GetItemIssue($itemnumber) or return ( 0, 'no_checkout' );
 
 
-    # Look in the issues table for this item, lent to this borrower,
-    # and not yet returned.
-    my %branch = (
-            'ItemHomeLibrary' => 'items.homebranch',
-            'PickupLibrary'   => 'items.holdingbranch',
-            'PatronLibrary'   => 'borrowers.branchcode'
-            );
-    my $controlbranch = $branch{C4::Context->preference('CircControl')};
-    my $itype         = C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype';
-    
-    my $sthcount = $dbh->prepare("
-                   SELECT 
-                    borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
-                   FROM  issuingrules, 
-                   issues
-                   LEFT JOIN items USING (itemnumber) 
-                   LEFT JOIN borrowers USING (borrowernumber) 
-                   LEFT JOIN biblioitems USING (biblioitemnumber)
-                   
-                   WHERE
-                    (issuingrules.categorycode = borrowers.categorycode OR issuingrules.categorycode = '*')
-                   AND
-                    (issuingrules.itemtype = $itype OR issuingrules.itemtype = '*')
-                   AND
-                    (issuingrules.branchcode = $controlbranch OR issuingrules.branchcode = '*') 
-                   AND 
-                    borrowernumber = ? 
-                   AND
-                    itemnumber = ?
-                   ORDER BY
-                    issuingrules.categorycode desc,
-                    issuingrules.itemtype desc,
-                    issuingrules.branchcode desc
-                   LIMIT 1;
-                  ");
-
-    $sthcount->execute( $borrowernumber, $itemnumber );
-    if ( my $data1 = $sthcount->fetchrow_hashref ) {
-        if ( ( $data1->{renewalsallowed} && $data1->{renewalsallowed} > $data1->{renewals} ) || $override_limit ) {
-            $renewokay = 1;
-        }
-        else {
-            $error = "too_many";
-        }
+    $borrowernumber ||= $itemissue->{borrowernumber};
+    my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber )
+      or return;
+
+    my $branchcode  = _GetCircControlBranch($item, $borrower);
 
 
-        my $resstatus = C4::Reserves::GetReserveStatus($itemnumber);
-        if ( $resstatus eq "Waiting" or $resstatus eq "Reserved" ) {
+    my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{itype}, $branchcode);
+
+    if ( $issuingrule->{norenewalbefore} ) {
+
+        # Get current time and add norenewalbefore. If this is smaller than date_due, it's too soon for renewal.
+        if (
+            DateTime->now( time_zone => C4::Context->tz() )->add(
+                $issuingrule->{lengthunit} => $issuingrule->{norenewalbefore}
+            ) < $itemissue->{date_due}
+        )
+        {
             $renewokay = 0;
             $renewokay = 0;
-            $error = "on_reserve";
+            $error     = "too_soon";
         }
     }
         }
     }
-    return ($renewokay,$error);
+
+    if ( $issuingrule->{renewalsallowed} <= $itemissue->{renewals} ) {
+        $renewokay = 0;
+        $error = "too_many";
+    }
+
+    if ( $override_limit ) {
+        $renewokay = 1;
+        $error     = undef;
+    }
+
+    my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves( $itemnumber );
+
+    if ( $resfound ) { # '' when no hold was found
+        $renewokay = 0;
+        $error = "on_reserve";
+    }
+
+    return ( $renewokay, $error );
 }
 
 =head2 AddRenewal
 }
 
 =head2 AddRenewal
@@ -2524,28 +2640,32 @@ from the book's item type.
 =cut
 
 sub AddRenewal {
 =cut
 
 sub AddRenewal {
-    my $borrowernumber  = shift or return;
+    my $borrowernumber  = shift;
     my $itemnumber      = shift or return;
     my $branch          = shift;
     my $datedue         = shift;
     my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd();
     my $itemnumber      = shift or return;
     my $branch          = shift;
     my $datedue         = shift;
     my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd();
+
     my $item   = GetItem($itemnumber) or return;
     my $biblio = GetBiblioFromItemNumber($itemnumber) or return;
 
     my $dbh = C4::Context->dbh;
     my $item   = GetItem($itemnumber) or return;
     my $biblio = GetBiblioFromItemNumber($itemnumber) or return;
 
     my $dbh = C4::Context->dbh;
+
     # Find the issues record for this book
     my $sth =
     # Find the issues record for this book
     my $sth =
-      $dbh->prepare("SELECT * FROM issues
-                        WHERE borrowernumber=? 
-                        AND itemnumber=?"
-      );
-    $sth->execute( $borrowernumber, $itemnumber );
+      $dbh->prepare("SELECT * FROM issues WHERE itemnumber = ?");
+    $sth->execute( $itemnumber );
     my $issuedata = $sth->fetchrow_hashref;
     my $issuedata = $sth->fetchrow_hashref;
-    $sth->finish;
-    if(defined $datedue && ref $datedue ne 'DateTime' ) {
+
+    return unless ( $issuedata );
+
+    $borrowernumber ||= $issuedata->{borrowernumber};
+
+    if ( defined $datedue && ref $datedue ne 'DateTime' ) {
         carp 'Invalid date passed to AddRenewal.';
         return;
     }
         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.
     # 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.
@@ -2555,9 +2675,9 @@ sub AddRenewal {
         my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
 
         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
         my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
 
         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
-                                        $issuedata->{date_due} :
+                                        dt_from_string( $issuedata->{date_due} ) :
                                         DateTime->now( time_zone => C4::Context->tz());
                                         DateTime->now( time_zone => C4::Context->tz());
-        $datedue =  CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower);
+        $datedue =  CalcDateDue($datedue, $itemtype, $issuedata->{'branchcode'}, $borrower, 'is a renewal');
     }
 
     # Update the issues record to have the new due date, and a new count
     }
 
     # Update the issues record to have the new due date, and a new count
@@ -2569,7 +2689,6 @@ sub AddRenewal {
     );
 
     $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $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;
 
     # Update the renewal count on the item, and tell zebra to reindex
     $renews = $biblio->{'renewals'} + 1;
@@ -2613,6 +2732,16 @@ sub AddRenewal {
        }
     }
 
        }
     }
 
+    # Remove any OVERDUES related debarment if the borrower has no overdues
+    my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber );
+    if ( $borrowernumber
+      && $borrower->{'debarred'}
+      && !HasOverdues( $borrowernumber )
+      && @{ GetDebarments({ borrowernumber => $borrowernumber, type => 'OVERDUES' }) }
+    ) {
+        DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' });
+    }
+
     # Log the renewal
     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber, undef, $item->{'ccode'});
        return $datedue;
     # Log the renewal
     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber, undef, $item->{'ccode'});
        return $datedue;
@@ -2641,7 +2770,6 @@ sub GetRenewCount {
     $sth->execute( $bornum, $itemno );
     my $data = $sth->fetchrow_hashref;
     $renewcount = $data->{'renewals'} if $data->{'renewals'};
     $sth->execute( $bornum, $itemno );
     my $data = $sth->fetchrow_hashref;
     $renewcount = $data->{'renewals'} if $data->{'renewals'};
-    $sth->finish;
     # $item and $borrower should be calculated
     my $branchcode = _GetCircControlBranch($item, $borrower);
     
     # $item and $borrower should be calculated
     my $branchcode = _GetCircControlBranch($item, $borrower);
     
@@ -2653,6 +2781,54 @@ sub GetRenewCount {
     return ( $renewcount, $renewsallowed, $renewsleft );
 }
 
     return ( $renewcount, $renewsallowed, $renewsleft );
 }
 
+=head2 GetSoonestRenewDate
+
+  $NoRenewalBeforeThisDate = &GetSoonestRenewDate($borrowernumber, $itemnumber);
+
+Find out the soonest possible renew date of a borrowed item.
+
+C<$borrowernumber> is the borrower number of the patron who currently
+has the item on loan.
+
+C<$itemnumber> is the number of the item to renew.
+
+C<$GetSoonestRenewDate> returns the DateTime of the soonest possible
+renew date, based on the value "No renewal before" of the applicable
+issuing rule. Returns the current date if the item can already be
+renewed, and returns undefined if the borrower, loan, or item
+cannot be found.
+
+=cut
+
+sub GetSoonestRenewDate {
+    my ( $borrowernumber, $itemnumber ) = @_;
+
+    my $dbh = C4::Context->dbh;
+
+    my $item      = GetItem($itemnumber)      or return;
+    my $itemissue = GetItemIssue($itemnumber) or return;
+
+    $borrowernumber ||= $itemissue->{borrowernumber};
+    my $borrower = C4::Members::GetMemberDetails($borrowernumber)
+      or return;
+
+    my $branchcode = _GetCircControlBranch( $item, $borrower );
+    my $issuingrule =
+      GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
+
+    my $now = DateTime->now( time_zone => C4::Context->tz() );
+
+    if ( $issuingrule->{norenewalbefore} ) {
+        my $soonestrenewal =
+          $itemissue->{date_due}->subtract(
+            $issuingrule->{lengthunit} => $issuingrule->{norenewalbefore} );
+
+        $soonestrenewal = $now > $soonestrenewal ? $now : $soonestrenewal;
+        return $soonestrenewal;
+    }
+    return $now;
+}
+
 =head2 GetIssuingCharges
 
   ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
 =head2 GetIssuingCharges
 
   ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
@@ -2710,7 +2886,6 @@ sub GetIssuingCharges {
         }
     }
 
         }
     }
 
-    $sth->finish; # we havent _explicitly_ fetched all rows
     return ( $charge, $item_type );
 }
 
     return ( $charge, $item_type );
 }
 
@@ -2772,7 +2947,6 @@ sub AddIssuingCharge {
     ";
     my $sth = $dbh->prepare($query);
     $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge, $manager_id );
     ";
     my $sth = $dbh->prepare($query);
     $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge, $manager_id );
-    $sth->finish;
 }
 
 =head2 GetTransfers
 }
 
 =head2 GetTransfers
@@ -2797,7 +2971,6 @@ sub GetTransfers {
     my $sth = $dbh->prepare($query);
     $sth->execute($itemnumber);
     my @row = $sth->fetchrow_array();
     my $sth = $dbh->prepare($query);
     $sth->execute($itemnumber);
     my @row = $sth->fetchrow_array();
-    $sth->finish;
     return @row;
 }
 
     return @row;
 }
 
@@ -2827,7 +3000,6 @@ sub GetTransfersFromTo {
     while ( my $data = $sth->fetchrow_hashref ) {
         push @gettransfers, $data;
     }
     while ( my $data = $sth->fetchrow_hashref ) {
         push @gettransfers, $data;
     }
-    $sth->finish;
     return (@gettransfers);
 }
 
     return (@gettransfers);
 }
 
@@ -2839,19 +3011,19 @@ sub GetTransfersFromTo {
 
 sub DeleteTransfer {
     my ($itemnumber) = @_;
 
 sub DeleteTransfer {
     my ($itemnumber) = @_;
+    return unless $itemnumber;
     my $dbh          = C4::Context->dbh;
     my $sth          = $dbh->prepare(
         "DELETE FROM branchtransfers
          WHERE itemnumber=?
          AND datearrived IS NULL "
     );
     my $dbh          = C4::Context->dbh;
     my $sth          = $dbh->prepare(
         "DELETE FROM branchtransfers
          WHERE itemnumber=?
          AND datearrived IS NULL "
     );
-    $sth->execute($itemnumber);
-    $sth->finish;
+    return $sth->execute($itemnumber);
 }
 
 =head2 AnonymiseIssueHistory
 
 }
 
 =head2 AnonymiseIssueHistory
 
-  $rows = AnonymiseIssueHistory($date,$borrowernumber)
+  ($rows,$err_history_not_deleted) = AnonymiseIssueHistory($date,$borrowernumber)
 
 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
 
 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
@@ -2859,7 +3031,7 @@ if C<$borrowernumber> is not set, it will delete the issue history for all borro
 If c<$borrowernumber> is set, it will delete issue history for only that borrower, regardless of their opac privacy
 setting (force delete).
 
 If c<$borrowernumber> is set, it will delete issue history for only that borrower, regardless of their opac privacy
 setting (force delete).
 
-return the number of affected rows.
+return the number of affected rows and a value that evaluates to true if an error occurred deleting the history.
 
 =cut
 
 
 =cut
 
@@ -2886,8 +3058,9 @@ sub AnonymiseIssueHistory {
     }
     my $sth = $dbh->prepare($query);
     $sth->execute(@bind_params);
     }
     my $sth = $dbh->prepare($query);
     $sth->execute(@bind_params);
+    my $anonymisation_err = $dbh->err;
     my $rows_affected = $sth->rows;  ### doublecheck row count return function
     my $rows_affected = $sth->rows;  ### doublecheck row count return function
-    return $rows_affected;
+    return ($rows_affected, $anonymisation_err);
 }
 
 =head2 SendCirculationAlert
 }
 
 =head2 SendCirculationAlert
@@ -2990,7 +3163,6 @@ sub updateWrongTransfer {
                        "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
                );
                $sth->execute($FromLibrary,$itemNumber);
                        "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
                );
                $sth->execute($FromLibrary,$itemNumber);
-               $sth->finish;
 
 # second step create a new line of branchtransfer to the right location .
        ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
 
 # second step create a new line of branchtransfer to the right location .
        ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
@@ -3022,62 +3194,60 @@ C<$startdate>   = C4::Dates object representing start date of loan period (assum
 C<$itemtype>  = itemtype code of item in question
 C<$branch>  = location whose calendar to use
 C<$borrower> = Borrower object
 C<$itemtype>  = itemtype code of item in question
 C<$branch>  = location whose calendar to use
 C<$borrower> = Borrower object
+C<$isrenewal> = Boolean: is true if we want to calculate the date due for a renewal. Else is false.
 
 =cut
 
 sub CalcDateDue {
 
 =cut
 
 sub CalcDateDue {
-    my ( $startdate, $itemtype, $branch, $borrower ) = @_;
+    my ( $startdate, $itemtype, $branch, $borrower, $isrenewal ) = @_;
+
+    $isrenewal ||= 0;
 
     # loanlength now a href
     my $loanlength =
 
     # loanlength now a href
     my $loanlength =
-      GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
+            GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
 
 
-    my $datedue;
+    my $length_key = ( $isrenewal and defined $loanlength->{renewalperiod} )
+            ? qq{renewalperiod}
+            : qq{issuelength};
 
 
-    # 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')
-        );
+    my $datedue;
+    if ( $startdate ) {
+        if (ref $startdate ne 'DateTime' ) {
+            $datedue = dt_from_string($datedue);
+        } else {
+            $datedue = $startdate->clone;
+        }
     } else {
     } else {
+        $datedue =
+          DateTime->now( time_zone => C4::Context->tz() )
+          ->truncate( to => 'minute' );
+    }
 
 
-        # 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} );
-            } else {    # days
-                $dt->add( days => $loanlength->{issuelength} );
-                $dt->set_hour(23);
-                $dt->set_minute(59);
-            }
-            # break
-            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);
-            }
+    # calculate the datedue as normal
+    if ( C4::Context->preference('useDaysMode') eq 'Days' )
+    {    # ignoring calendar
+        if ( $loanlength->{lengthunit} eq 'hours' ) {
+            $datedue->add( hours => $loanlength->{$length_key} );
+        } else {    # days
+            $datedue->add( days => $loanlength->{$length_key} );
+            $datedue->set_hour(23);
+            $datedue->set_minute(59);
+        }
+    } else {
+        my $dur;
+        if ($loanlength->{lengthunit} eq 'hours') {
+            $dur = DateTime::Duration->new( hours => $loanlength->{$length_key});
+        }
+        else { # days
+            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
+        }
+        my $calendar = Koha::Calendar->new( branchcode => $branch );
+        $datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} );
+        if ($loanlength->{lengthunit} eq 'days') {
+            $datedue->set_hour(23);
+            $datedue->set_minute(59);
         }
     }
 
         }
     }
 
@@ -3098,6 +3268,7 @@ sub CalcDateDue {
         }
 
         # in all other cases, keep the date due as it is
         }
 
         # in all other cases, keep the date due as it is
+
     }
 
     # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
     }
 
     # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
@@ -3135,7 +3306,6 @@ my $query = qq|SELECT count(*)
 my $sth = $dbh->prepare($query);
 $sth->execute($branchcode,$week_day);
 my $result=$sth->fetchrow;
 my $sth = $dbh->prepare($query);
 $sth->execute($branchcode,$week_day);
 my $result=$sth->fetchrow;
-$sth->finish;
 return $result;
 }
 
 return $result;
 }
 
@@ -3167,7 +3337,6 @@ my $query=qq|SELECT count(*)
 my $sth = $dbh->prepare($query);
 $sth->execute($years,$month,$day,$branchcode);
 my $countspecial=$sth->fetchrow ;
 my $sth = $dbh->prepare($query);
 $sth->execute($years,$month,$day,$branchcode);
 my $countspecial=$sth->fetchrow ;
-$sth->finish;
 return $countspecial;
 }
 
 return $countspecial;
 }
 
@@ -3196,7 +3365,6 @@ my $query=qq|SELECT count(*)
 my $sth = $dbh->prepare($query);
 $sth->execute($month,$day,$branchcode);
 my $countspecial=$sth->fetchrow ;
 my $sth = $dbh->prepare($query);
 $sth->execute($month,$day,$branchcode);
 my $countspecial=$sth->fetchrow ;
-$sth->finish;
 return $countspecial;
 }
 
 return $countspecial;
 }
 
@@ -3212,7 +3380,6 @@ my $query=qq|SELECT count(*)
 my $sth = $dbh->prepare($query);
 $sth->execute($barcode);
 my $exist=$sth->fetchrow ;
 my $sth = $dbh->prepare($query);
 $sth->execute($barcode);
 my $exist=$sth->fetchrow ;
-$sth->finish;
 return $exist;
 }
 
 return $exist;
 }
 
@@ -3254,28 +3421,31 @@ $code is either itemtype or collection code depending on what the pref BranchTra
 
 sub CreateBranchTransferLimit {
    my ( $toBranch, $fromBranch, $code ) = @_;
 
 sub CreateBranchTransferLimit {
    my ( $toBranch, $fromBranch, $code ) = @_;
-
+   return unless defined($toBranch) && defined($fromBranch);
    my $limitType = C4::Context->preference("BranchTransferLimitsType");
    
    my $dbh = C4::Context->dbh;
    
    my $sth = $dbh->prepare("INSERT INTO branch_transfer_limits ( $limitType, toBranch, fromBranch ) VALUES ( ?, ?, ? )");
    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 );
+   return $sth->execute( $code, $toBranch, $fromBranch );
 }
 
 =head2 DeleteBranchTransferLimits
 
 }
 
 =head2 DeleteBranchTransferLimits
 
-DeleteBranchTransferLimits($frombranch);
+    my $result = DeleteBranchTransferLimits($frombranch);
 
 
-Deletes all the branch transfer limits for one branch
+Deletes all the library transfer limits for one library.  Returns the
+number of limits deleted, 0e0 if no limits were deleted, or undef if
+no arguments are supplied.
 
 =cut
 
 sub DeleteBranchTransferLimits {
     my $branch = shift;
 
 =cut
 
 sub DeleteBranchTransferLimits {
     my $branch = shift;
+    return unless defined $branch;
     my $dbh    = C4::Context->dbh;
     my $sth    = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
     my $dbh    = C4::Context->dbh;
     my $sth    = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
-    $sth->execute($branch);
+    return $sth->execute($branch);
 }
 
 sub ReturnLostItem{
 }
 
 sub ReturnLostItem{
@@ -3293,7 +3463,7 @@ sub ReturnLostItem{
 
 
 sub LostItem{
 
 
 sub LostItem{
-    my ($itemnumber, $mark_returned, $charge_fee) = @_;
+    my ($itemnumber, $mark_returned) = @_;
 
     my $dbh = C4::Context->dbh();
     my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title 
 
     my $dbh = C4::Context->dbh();
     my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title 
@@ -3303,16 +3473,21 @@ sub LostItem{
                            WHERE issues.itemnumber=?");
     $sth->execute($itemnumber);
     my $issues=$sth->fetchrow_hashref();
                            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 a borrower lost the item, add a replacement cost to the their record
     if ( my $borrowernumber = $issues->{borrowernumber} ){
         my $borrower = C4::Members::GetMemberDetails( $borrowernumber );
 
     if ( my $borrowernumber = $issues->{borrowernumber} ){
         my $borrower = C4::Members::GetMemberDetails( $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 ";
+        if (C4::Context->preference('WhenLostForgiveFine')){
+            my $fix = _FixOverduesOnReturn($borrowernumber, $itemnumber, 1, 0); # 1, 0 = exemptfine, no-dropbox
+            defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $itemnumber...) failed!";  # zero is OK, check defined
+        }
+        if (C4::Context->preference('WhenLostChargeReplacementFee')){
+            C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}");
+            #FIXME : Should probably have a way to distinguish this from an item that really was returned.
+            #warn " $issues->{'borrowernumber'}  /  $itemnumber ";
+        }
+
         MarkIssueReturned($borrowernumber,$itemnumber,undef,undef,$borrower->{'privacy'}) if $mark_returned;
     }
 }
         MarkIssueReturned($borrowernumber,$itemnumber,undef,undef,$borrower->{'privacy'}) if $mark_returned;
     }
 }
@@ -3322,23 +3497,23 @@ sub GetOfflineOperations {
     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({});
     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 {
     return $results;
 }
 
 sub GetOfflineOperation {
+    my $operationid = shift;
+    return unless $operationid;
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE operationid=?");
     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;
+    $sth->execute( $operationid );
+    return $sth->fetchrow_hashref;
 }
 
 sub AddOfflineOperation {
 }
 
 sub AddOfflineOperation {
+    my ( $userid, $branchcode, $timestamp, $action, $barcode, $cardnumber, $amount ) = @_;
     my $dbh = C4::Context->dbh;
     my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber) VALUES(?,?,?,?,?,?)");
-    $sth->execute( @_ );
+    my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber, amount) VALUES(?,?,?,?,?,?,?)");
+    $sth->execute( $userid, $branchcode, $timestamp, $action, $barcode, $cardnumber, $amount );
     return "Added.";
 }
 
     return "Added.";
 }
 
@@ -3357,6 +3532,8 @@ sub ProcessOfflineOperation {
         $report = ProcessOfflineReturn( $operation );
     } elsif ( $operation->{action} eq 'issue' ) {
         $report = ProcessOfflineIssue( $operation );
         $report = ProcessOfflineReturn( $operation );
     } elsif ( $operation->{action} eq 'issue' ) {
         $report = ProcessOfflineIssue( $operation );
+    } elsif ( $operation->{action} eq 'payment' ) {
+        $report = ProcessOfflinePayment( $operation );
     }
 
     DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid};
     }
 
     DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid};
@@ -3426,6 +3603,16 @@ sub ProcessOfflineIssue {
     }
 }
 
     }
 }
 
+sub ProcessOfflinePayment {
+    my $operation = shift;
+
+    my $borrower = C4::Members::GetMemberDetails( undef, $operation->{cardnumber} ); # Get borrower from operation cardnumber
+    my $amount = $operation->{amount};
+
+    recordpayment( $borrower->{borrowernumber}, $amount );
+
+    return "Success."
+}
 
 
 =head2 TransferSlip
 
 
 =head2 TransferSlip
@@ -3476,6 +3663,63 @@ sub CheckIfIssuedToPatron {
     return;
 }
 
     return;
 }
 
+=head2 IsItemIssued
+
+  IsItemIssued( $itemnumber )
+
+  Return 1 if the item is on loan, otherwise return 0
+
+=cut
+
+sub IsItemIssued {
+    my $itemnumber = shift;
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare(q{
+        SELECT COUNT(*)
+        FROM issues
+        WHERE itemnumber = ?
+    });
+    $sth->execute($itemnumber);
+    return $sth->fetchrow;
+}
+
+sub GetAgeRestriction {
+    my ($record_restrictions) = @_;
+    my $markers = C4::Context->preference('AgeRestrictionMarker');
+
+    # Split $record_restrictions to something like FSK 16 or PEGI 6
+    my @values = split ' ', uc($record_restrictions);
+    return unless @values;
+
+    # Search first occurence of one of the markers
+    my @markers = split /\|/, uc($markers);
+    return unless @markers;
+
+    my $index            = 0;
+    my $restriction_year = 0;
+    for my $value (@values) {
+        $index++;
+        for my $marker (@markers) {
+            $marker =~ s/^\s+//;    #remove leading spaces
+            $marker =~ s/\s+$//;    #remove trailing spaces
+            if ( $marker eq $value ) {
+                if ( $index <= $#values ) {
+                    $restriction_year += $values[$index];
+                }
+                last;
+            }
+            elsif ( $value =~ /^\Q$marker\E(\d+)$/ ) {
+
+                # Perhaps it is something like "K16" (as in Finland)
+                $restriction_year += $1;
+                last;
+            }
+        }
+        last if ( $restriction_year > 0 );
+    }
+
+    return $restriction_year;
+}
 
 1;
 
 
 1;