Bug 8002 follow-up for the follow-up, DBRev number
[koha_fer] / C4 / Circulation.pm
index 49bd7b2..e64f07d 100644 (file)
@@ -36,17 +36,28 @@ use C4::Message;
 use C4::Debug;
 use C4::Branch; # GetBranches
 use C4::Log; # logaction
 use C4::Debug;
 use C4::Branch; # GetBranches
 use C4::Log; # logaction
+use C4::Koha qw(GetAuthorisedValueByCode);
+use C4::Overdues qw(CalcFine UpdateFine);
+use Algorithm::CheckDigits;
 
 use Data::Dumper;
 use Koha::DateUtils;
 use Koha::Calendar;
 use Carp;
 
 use Data::Dumper;
 use Koha::DateUtils;
 use Koha::Calendar;
 use Carp;
-
+use Date::Calc qw(
+  Today
+  Today_and_Now
+  Add_Delta_YM
+  Add_Delta_DHMS
+  Date_to_Days
+  Day_of_Week
+  Add_Delta_Days
+);
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 
 BEGIN {
        require Exporter;
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 
 BEGIN {
        require Exporter;
-       $VERSION = 3.02;        # for version checking
+    $VERSION = 3.07.00.049;    # for version checking
        @ISA    = qw(Exporter);
 
        # FIXME subs that should probably be elsewhere
        @ISA    = qw(Exporter);
 
        # FIXME subs that should probably be elsewhere
@@ -168,6 +179,14 @@ sub barcodedecode {
                                $barcode =~ s/^(\D+)[0]*(\d+)$/$branch-$1-$2/i;
                        }
                }
                                $barcode =~ s/^(\D+)[0]*(\d+)$/$branch-$1-$2/i;
                        }
                }
+    } elsif ($filter eq 'EAN13') {
+        my $ean = CheckDigits('ean');
+        if ( $ean->is_valid($barcode) ) {
+            #$barcode = sprintf('%013d',$barcode); # this doesn't work on 32-bit systems
+            $barcode = '0' x ( 13 - length($barcode) ) . $barcode;
+        } else {
+            warn "# [$barcode] not valid EAN-13/UPC-A\n";
+        }
        }
     return $barcode;    # return barcode, modified or not
 }
        }
     return $barcode;    # return barcode, modified or not
 }
@@ -667,6 +686,8 @@ sub CanBookBeIssued {
     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 ( $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 %alerts;               # filled with messages that shouldn't stop issuing, but the librarian should be aware of.
+
     my $item = GetItem(GetItemnumberFromBarcode( $barcode ));
     my $issue = GetItemIssue($item->{itemnumber});
        my $biblioitem = GetBiblioItemData($item->{biblioitemnumber});
     my $item = GetItem(GetItemnumberFromBarcode( $barcode ));
     my $issue = GetItemIssue($item->{itemnumber});
        my $biblioitem = GetBiblioItemData($item->{biblioitemnumber});
@@ -698,7 +719,7 @@ sub CanBookBeIssued {
     }
     if ($duedate) {
         my $today = $now->clone();
     }
     if ($duedate) {
         my $today = $now->clone();
-        $today->truncate( to => 'minutes');
+        $today->truncate( to => 'minute');
         if (DateTime->compare($duedate,$today) == -1 ) { # duedate cannot be before now
             $needsconfirmation{INVALID_DATE} = output_pref($duedate);
         }
         if (DateTime->compare($duedate,$today) == -1 ) { # duedate cannot be before now
             $needsconfirmation{INVALID_DATE} = output_pref($duedate);
         }
@@ -711,7 +732,7 @@ sub CanBookBeIssued {
     #
     if ( $borrower->{'category_type'} eq 'X' && (  $item->{barcode}  )) { 
        # stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1  .
     #
     if ( $borrower->{'category_type'} eq 'X' && (  $item->{barcode}  )) { 
        # stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1  .
-        &UpdateStats(C4::Context->userenv->{'branch'},'localuse','','',$item->{'itemnumber'},$item->{'itemtype'},$borrower->{'borrowernumber'});
+        &UpdateStats(C4::Context->userenv->{'branch'},'localuse','','',$item->{'itemnumber'},$item->{'itemtype'},$borrower->{'borrowernumber'}, undef, $item->{'ccode'});
         ModDateLastSeen( $item->{'itemnumber'} );
         return( { STATS => 1 }, {});
     }
         ModDateLastSeen( $item->{'itemnumber'} );
         return( { STATS => 1 }, {});
     }
@@ -735,8 +756,8 @@ sub CanBookBeIssued {
                 day   => $d,
                 time_zone => C4::Context->tz,
             );
                 day   => $d,
                 time_zone => C4::Context->tz,
             );
-            $expiry_dt->truncate( to => 'days');
-            my $today = $now->clone()->truncate(to => 'days');
+            $expiry_dt->truncate( to => 'day');
+            my $today = $now->clone()->truncate(to => 'day');
             if (DateTime->compare($today, $expiry_dt) == 1) {
                 $issuingimpossible{EXPIRED} = 1;
             }
             if (DateTime->compare($today, $expiry_dt) == 1) {
                 $issuingimpossible{EXPIRED} = 1;
             }
@@ -848,6 +869,11 @@ sub CanBookBeIssued {
     {
         $issuingimpossible{RESTRICTED} = 1;
     }
     {
         $issuingimpossible{RESTRICTED} = 1;
     }
+    if ( $item->{'itemlost'} && C4::Context->preference("IssueLostItem") ne 'nothing' ) {
+        my $code = GetAuthorisedValueByCode( 'LOST', $item->{'itemlost'} );
+        $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") ) {
         my $userenv = C4::Context->userenv;
         if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
     if ( C4::Context->preference("IndependantBranches") ) {
         my $userenv = C4::Context->userenv;
         if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
@@ -923,7 +949,63 @@ sub CanBookBeIssued {
             }
         }
     }
             }
         }
     }
-    return ( \%issuingimpossible, \%needsconfirmation );
+    #
+    # CHECK AGE RESTRICTION
+    #
+
+    # 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;
+            }
+        }
+        # 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";
+                    }
+                    else {
+                        $issuingimpossible{AGE_RESTRICTION} = "$bibvalues";
+                    }
+                }
+            }
+        }
+    }
+    return ( \%issuingimpossible, \%needsconfirmation, \%alerts );
 }
 
 =head2 AddIssue
 }
 
 =head2 AddIssue
@@ -996,12 +1078,12 @@ sub AddIssue {
                # check if we just renew the issue.
                #
                if ($actualissue->{borrowernumber} eq $borrower->{'borrowernumber'}) {
                # 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 {
                        );
                }
                else {
@@ -1016,7 +1098,6 @@ sub AddIssue {
                        }
 
             MoveReserve( $item->{'itemnumber'}, $borrower->{'borrowernumber'}, $cancelreserve );
                        }
 
             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) {
                        # Starting process for transfer job (checking transfert and validate it if we have one)
             my ($datesent) = GetTransfers($item->{'itemnumber'});
             if ($datesent) {
@@ -1044,7 +1125,7 @@ sub AddIssue {
             $datedue = CalcDateDue( $issuedate, $itype, $branch, $borrower );
 
         }
             $datedue = CalcDateDue( $issuedate, $itype, $branch, $borrower );
 
         }
-        $datedue->truncate( to => 'minutes');
+        $datedue->truncate( to => 'minute');
         $sth->execute(
             $borrower->{'borrowernumber'},      # borrowernumber
             $item->{'itemnumber'},              # itemnumber
         $sth->execute(
             $borrower->{'borrowernumber'},      # borrowernumber
             $item->{'itemnumber'},              # itemnumber
@@ -1056,6 +1137,9 @@ sub AddIssue {
           CartToShelf( $item->{'itemnumber'} );
         }
         $item->{'issues'}++;
           CartToShelf( $item->{'itemnumber'} );
         }
         $item->{'issues'}++;
+        if ( C4::Context->preference('UpdateTotalIssuesOnCirc') ) {
+            UpdateTotalIssues($item->{'biblionumber'}, 1);
+        }
 
         ## If item was lost, it has now been found, reverse any list item charges if neccessary.
         if ( $item->{'itemlost'} ) {
 
         ## If item was lost, it has now been found, reverse any list item charges if neccessary.
         if ( $item->{'itemlost'} ) {
@@ -1088,7 +1172,7 @@ sub AddIssue {
             C4::Context->userenv->{'branch'},
             'issue', $charge,
             ($sipmode ? "SIP-$sipmode" : ''), $item->{'itemnumber'},
             C4::Context->userenv->{'branch'},
             'issue', $charge,
             ($sipmode ? "SIP-$sipmode" : ''), $item->{'itemnumber'},
-            $item->{'itype'}, $borrower->{'borrowernumber'}
+            $item->{'itype'}, $borrower->{'borrowernumber'}, undef, $item->{'ccode'}
         );
 
         # Send a checkout slip.
         );
 
         # Send a checkout slip.
@@ -1192,53 +1276,16 @@ Get the Hard Due Date and it's comparison for an itemtype, a borrower type and a
 
 sub GetHardDueDate {
     my ( $borrowertype, $itemtype, $branchcode ) = @_;
 
 sub GetHardDueDate {
     my ( $borrowertype, $itemtype, $branchcode ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth =
-      $dbh->prepare(
-"select hardduedate, hardduedatecompare from issuingrules where categorycode=? and itemtype=? and branchcode=?"
-      );
-    $sth->execute( $borrowertype, $itemtype, $branchcode );
-    my $results = $sth->fetchrow_hashref;
-    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
-      if defined($results) && $results->{hardduedate};
-
-    $sth->execute( $borrowertype, "*", $branchcode );
-    $results = $sth->fetchrow_hashref;
-    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
-      if defined($results) && $results->{hardduedate};
 
 
-    $sth->execute( "*", $itemtype, $branchcode );
-    $results = $sth->fetchrow_hashref;
-    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
-      if defined($results) && $results->{hardduedate};
-
-    $sth->execute( "*", "*", $branchcode );
-    $results = $sth->fetchrow_hashref;
-    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
-      if defined($results) && $results->{hardduedate};
+    my $rule = GetIssuingRule( $borrowertype, $itemtype, $branchcode );
 
 
-    $sth->execute( $borrowertype, $itemtype, "*" );
-    $results = $sth->fetchrow_hashref;
-    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
-      if defined($results) && $results->{hardduedate};
-
-    $sth->execute( $borrowertype, "*", "*" );
-    $results = $sth->fetchrow_hashref;
-    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
-      if defined($results) && $results->{hardduedate};
-
-    $sth->execute( "*", $itemtype, "*" );
-    $results = $sth->fetchrow_hashref;
-    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
-      if defined($results) && $results->{hardduedate};
-
-    $sth->execute( "*", "*", "*" );
-    $results = $sth->fetchrow_hashref;
-    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
-      if defined($results) && $results->{hardduedate};
-
-    # if no rule is set => return undefined
-    return (undef, undef);
+    if ( defined( $rule ) ) {
+        if ( $rule->{hardduedate} ) {
+            return (dt_from_string($rule->{hardduedate}, 'iso'),$rule->{hardduedatecompare});
+        } else {
+            return (undef, undef);
+        }
+    }
 }
 
 =head2 GetIssuingRule
 }
 
 =head2 GetIssuingRule
@@ -1386,13 +1433,17 @@ sub GetBranchBorrowerCircRule {
 Retrieves circulation rule attributes that apply to the given
 branch and item type, regardless of patron category.
 
 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.
 
 
 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
 This searches branchitemrules in the following order:
 
   * Same branchcode and itemtype
@@ -1400,7 +1451,7 @@ This searches branchitemrules in the following order:
   * branchcode '*', same itemtype
   * branchcode and itemtype '*'
 
   * branchcode '*', same itemtype
   * branchcode and itemtype '*'
 
-Neither C<$branchcode> nor C<$categorycode> should be '*'.
+Neither C<$branchcode> nor C<$itemtype> should be '*'.
 
 =cut
 
 
 =cut
 
@@ -1410,33 +1461,37 @@ sub GetBranchItemRule {
     my $result = {};
 
     my @attempts = (
     my $result = {};
 
     my @attempts = (
-        ['SELECT holdallowed
+        ['SELECT holdallowed, returnbranch
             FROM branch_item_rules
             WHERE branchcode = ?
               AND itemtype = ?', $branchcode, $itemtype],
             FROM branch_item_rules
             WHERE branchcode = ?
               AND itemtype = ?', $branchcode, $itemtype],
-        ['SELECT holdallowed
+        ['SELECT holdallowed, returnbranch
             FROM default_branch_circ_rules
             WHERE branchcode = ?', $branchcode],
             FROM default_branch_circ_rules
             WHERE branchcode = ?', $branchcode],
-        ['SELECT holdallowed
+        ['SELECT holdallowed, returnbranch
             FROM default_branch_item_rules
             WHERE itemtype = ?', $itemtype],
             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};
             FROM default_circ_rules'],
     );
 
     foreach my $attempt (@attempts) {
         my ($query, @bind_params) = @{$attempt};
+        my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params )
+          or next;
 
         # 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
 
         # 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
     }
     
     # 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
 }
 
 =head2 AddReturn
@@ -1515,6 +1570,7 @@ patron who last borrowed the book.
 
 sub AddReturn {
     my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
 
 sub AddReturn {
     my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
+
     if ($branch and not GetBranchDetail($branch)) {
         warn "AddReturn error: branch '$branch' not found.  Reverting to " . C4::Context->userenv->{'branch'};
         undef $branch;
     if ($branch and not GetBranchDetail($branch)) {
         warn "AddReturn error: branch '$branch' not found.  Reverting to " . C4::Context->userenv->{'branch'};
         undef $branch;
@@ -1553,9 +1609,10 @@ sub AddReturn {
     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 $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
 
 
     my $borrowernumber = $borrower->{'borrowernumber'} || undef;    # we don't know if we had a borrower or not
 
@@ -1566,8 +1623,8 @@ sub AddReturn {
         $branches->{$hbr}->{PE} and $messages->{'IsPermanent'} = $hbr;
     }
 
         $branches->{$hbr}->{PE} and $messages->{'IsPermanent'} = $hbr;
     }
 
-    # if indy branches and returning to different branch, refuse the return
-    if ($hbr ne $branch && C4::Context->preference("IndependantBranches")){
+    # if indy branches and returning to different branch, refuse the return unless canreservefromotherbranches is turned on
+    if ($hbr ne $branch && C4::Context->preference("IndependantBranches") && !(C4::Context->preference("canreservefromotherbranches"))){
         $messages->{'Wrongbranch'} = {
             Wrongbranch => $branch,
             Rightbranch => $hbr,
         $messages->{'Wrongbranch'} = {
             Wrongbranch => $branch,
             Rightbranch => $hbr,
@@ -1587,7 +1644,9 @@ sub AddReturn {
     }
 
     # case of a return of document (deal with issues and holdingbranch)
     }
 
     # 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};
         $borrower or warn "AddReturn without current borrower";
                my $circControlBranch;
         if ($dropbox) {
         $borrower or warn "AddReturn without current borrower";
                my $circControlBranch;
         if ($dropbox) {
@@ -1596,9 +1655,21 @@ 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;
         }
 
         if ($borrowernumber) {
         }
 
         if ($borrowernumber) {
+        if($issue->{'overdue'}){
+                my ( $amount, $type, $unitcounttotal ) = C4::Overdues::CalcFine( $item, $borrower->{categorycode},$branch, $datedue, $today );
+                $type ||= q{};
+        if ( $amount > 0 && ( C4::Context->preference('finesMode') eq 'production' )) {
+          C4::Overdues::UpdateFine(
+              $issue->{itemnumber},
+              $issue->{borrowernumber},
+                      $amount, $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, '', $borrower->{'privacy'});
             $messages->{'WasReturned'} = 1;    # FIXME is the "= 1" right?  This could be the borrower hash.
         }
@@ -1625,12 +1696,15 @@ sub AddReturn {
             );
             $sth->execute( $item->{'itemnumber'} );
             # if we have a reservation with valid transfer, we can set it's status to 'W'
             );
             $sth->execute( $item->{'itemnumber'} );
             # if we have a reservation with valid transfer, we can set it's status to 'W'
+            ShelfToCart( $item->{'itemnumber'} ) if ( C4::Context->preference("ReturnToShelvingCart") );
             C4::Reserves::ModReserveStatus($item->{'itemnumber'}, 'W');
         } else {
             $messages->{'WrongTransfer'}     = $tobranch;
             $messages->{'WrongTransferItem'} = $item->{'itemnumber'};
         }
         $validTransfert = 1;
             C4::Reserves::ModReserveStatus($item->{'itemnumber'}, 'W');
         } else {
             $messages->{'WrongTransfer'}     = $tobranch;
             $messages->{'WrongTransferItem'} = $item->{'itemnumber'};
         }
         $validTransfert = 1;
+    } else {
+        ShelfToCart( $item->{'itemnumber'} ) if ( C4::Context->preference("ReturnToShelvingCart") );
     }
 
     # fix up the accounts.....
     }
 
     # fix up the accounts.....
@@ -1644,9 +1718,12 @@ sub AddReturn {
         my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox);
         defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!";  # zero is OK, check defined
         
         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);
+        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);
+        }
     }
 
     # find reserves.....
     }
 
     # find reserves.....
@@ -1663,7 +1740,7 @@ sub AddReturn {
         $branch, $stat_type, '0', '',
         $item->{'itemnumber'},
         $biblio->{'itemtype'},
         $branch, $stat_type, '0', '',
         $item->{'itemnumber'},
         $biblio->{'itemtype'},
-        $borrowernumber
+        $borrowernumber, undef, $item->{'ccode'}
     );
 
     # Send a check-in slip. # NOTE: borrower may be undef.  probably shouldn't try to send messages then.
     );
 
     # Send a check-in slip. # NOTE: borrower may be undef.  probably shouldn't try to send messages then.
@@ -1731,14 +1808,15 @@ routine in C<C4::Accounts>.
 
 sub MarkIssueReturned {
     my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy ) = @_;
 
 sub MarkIssueReturned {
     my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy ) = @_;
+
     my $dbh   = C4::Context->dbh;
     my $query = 'UPDATE issues SET returndate=';
     my @bind;
     if ($dropbox_branch) {
     my $dbh   = C4::Context->dbh;
     my $query = 'UPDATE issues SET returndate=';
     my @bind;
     if ($dropbox_branch) {
-        my $calendar = Koha->new( branchcode => $dropbox_branch );
+        my $calendar = Koha::Calendar->new( branchcode => $dropbox_branch );
         my $dropboxdate = $calendar->addDate( DateTime->now( time_zone => C4::Context->tz), -1 );
         $query .= ' ? ';
         my $dropboxdate = $calendar->addDate( DateTime->now( time_zone => C4::Context->tz), -1 );
         $query .= ' ? ';
-        push @bind, $dropboxdate->strftimei('%Y-%m-%d %H:%M');
+        push @bind, $dropboxdate->strftime('%Y-%m-%d %H:%M');
     } elsif ($returndate) {
         $query .= ' ? ';
         push @bind, $returndate;
     } elsif ($returndate) {
         $query .= ' ? ';
         push @bind, $returndate;
@@ -1770,59 +1848,70 @@ sub MarkIssueReturned {
     $sth_del->execute($borrowernumber, $itemnumber);
 }
 
     $sth_del->execute($borrowernumber, $itemnumber);
 }
 
-=head2 _FixFineDaysOnReturn
+=head2 _debar_user_on_return
 
 
-    &_FixFineDaysOnReturn($borrower, $item, $datedue);
+    _debar_user_on_return($borrower, $item, $datedue, today);
 
 C<$borrower> borrower hashref
 
 C<$item> item hashref
 
 
 C<$borrower> borrower hashref
 
 C<$item> item hashref
 
-C<$datedue> date due
+C<$datedue> date due DateTime object
 
 
-Internal function, called only by AddReturn that calculate and update the user fine days, and debars him
+C<$today> DateTime object representing the return time
 
 
-=cut
+Internal function, called only by AddReturn that calculates and updates
+ the user fine days, and debars him if necessary.
 
 
-sub _FixFineDaysOnReturn {
-    my ( $borrower, $item, $datedue ) = @_;
+Should only be called for overdue returns
 
 
-    if ($datedue) {
-        $datedue = C4::Dates->new( $datedue, "iso" );
-    } else {
-        return;
-    }
+=cut
+
+sub _debar_user_on_return {
+    my ( $borrower, $item, $dt_due, $dt_today ) = @_;
 
     my $branchcode = _GetCircControlBranch( $item, $borrower );
 
     my $branchcode = _GetCircControlBranch( $item, $borrower );
-    my $calendar = C4::Calendar->new( branchcode => $branchcode );
-    my $today = C4::Dates->new();
+    my $calendar = Koha::Calendar->new( branchcode => $branchcode );
 
 
-    my $deltadays = $calendar->daysBetween( $datedue, C4::Dates->new() );
+    # $deltadays is a DateTime::Duration object
+    my $deltadays = $calendar->days_between( $dt_due, $dt_today );
 
     my $circcontrol = C4::Context::preference('CircControl');
 
     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 = $issuingrule->{firstremind};
-
-    if ( $deltadays - $grace > 0 ) {
-        my @newdate = Add_Delta_Days( Today(), $deltadays * $finedays );
-        my $isonewdate = join( '-', @newdate );
-        my ( $deby, $debm, $debd ) = split( /-/, $borrower->{debarred} );
-        if ( check_date( $deby, $debm, $debd ) ) {
-            my @olddate = split( /-/, $borrower->{debarred} );
-
-            if ( Delta_Days( @olddate, @newdate ) > 0 ) {
-                C4::Members::DebarMember( $borrower->{borrowernumber}, $isonewdate );
-                return $isonewdate;
+    my $issuingrule =
+      GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
+    my $finedays = $issuingrule->{finedays};
+    my $unit     = $issuingrule->{lengthunit};
+
+    if ($finedays) {
+
+        # finedays is in days, so hourly loans must multiply by 24
+        # thus 1 hour late equals 1 day suspension * finedays rate
+        $finedays = $finedays * 24 if ( $unit eq 'hours' );
+
+        # grace period is measured in the same units as the loan
+        my $grace =
+          DateTime::Duration->new( $unit => $issuingrule->{firstremind} );
+        if ( $deltadays->subtract($grace)->is_positive() ) {
+
+            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;
+                }
+
             }
             }
-        } else {
-            C4::Members::DebarMember( $borrower->{borrowernumber}, $isonewdate );
-            return $isonewdate;
+            C4::Members::DebarMember( $borrower->{borrowernumber},
+                $new_debar_dt->ymd() );
+            return $new_debar_dt->ymd();
         }
     }
         }
     }
+    return;
 }
 
 =head2 _FixOverduesOnReturn
 }
 
 =head2 _FixOverduesOnReturn
@@ -2045,10 +2134,10 @@ sub GetItemIssue {
     my $data = $sth->fetchrow_hashref;
     return unless $data;
     $data->{issuedate} = dt_from_string($data->{issuedate}, 'sql');
     my $data = $sth->fetchrow_hashref;
     return unless $data;
     $data->{issuedate} = dt_from_string($data->{issuedate}, 'sql');
-    $data->{issuedate}->truncate(to => 'minutes');
+    $data->{issuedate}->truncate(to => 'minute');
     $data->{date_due} = dt_from_string($data->{date_due}, 'sql');
     $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->{date_due}->truncate(to => 'minute');
+    my $dt = DateTime->now( time_zone => C4::Context->tz)->truncate( to => 'minute');
     $data->{'overdue'} = DateTime->compare($data->{'date_due'}, $dt ) == -1 ? 1 : 0;
     return $data;
 }
     $data->{'overdue'} = DateTime->compare($data->{'date_due'}, $dt ) == -1 ? 1 : 0;
     return $data;
 }
@@ -2093,7 +2182,7 @@ sub GetItemIssues {
     my ( $itemnumber, $history ) = @_;
     
     my $today = DateTime->now( time_zome => C4::Context->tz);  # get today date
     my ( $itemnumber, $history ) = @_;
     
     my $today = DateTime->now( time_zome => C4::Context->tz);  # get today date
-    $today->truncate( to => 'minutes' );
+    $today->truncate( to => 'minute' );
     my $sql = "SELECT * FROM issues
               JOIN borrowers USING (borrowernumber)
               JOIN items     USING (itemnumber)
     my $sql = "SELECT * FROM issues
               JOIN borrowers USING (borrowernumber)
               JOIN items     USING (itemnumber)
@@ -2115,7 +2204,7 @@ sub GetItemIssues {
     my $results = $sth->fetchall_arrayref({});
     foreach (@$results) {
         my $date_due = dt_from_string($_->{date_due},'sql');
     my $results = $sth->fetchall_arrayref({});
     foreach (@$results) {
         my $date_due = dt_from_string($_->{date_due},'sql');
-        $date_due->truncate( to => 'minutes' );
+        $date_due->truncate( to => 'minute' );
 
         $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0;
     }
 
         $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0;
     }
@@ -2383,7 +2472,7 @@ sub AddRenewal {
             'Rent', $charge, $itemnumber );
     }
     # Log the renewal
             'Rent', $charge, $itemnumber );
     }
     # Log the renewal
-    UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
+    UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber, undef, $item->{'ccode'});
        return $datedue;
 }
 
        return $datedue;
 }
 
@@ -2708,11 +2797,14 @@ sub SendCirculationAlert {
         borrowernumber => $borrower->{borrowernumber},
         message_name   => $message_name{$type},
     });
         borrowernumber => $borrower->{borrowernumber},
         message_name   => $message_name{$type},
     });
+    my $issues_table = ( $type eq 'CHECKOUT' ) ? 'issues' : 'old_issues';
     my $letter =  C4::Letters::GetPreparedLetter (
         module => 'circulation',
         letter_code => $type,
         branchcode => $branch,
         tables => {
     my $letter =  C4::Letters::GetPreparedLetter (
         module => 'circulation',
         letter_code => $type,
         branchcode => $branch,
         tables => {
+            $issues_table => $item->{itemnumber},
+            'items'       => $item->{itemnumber},
             'biblio'      => $item->{biblionumber},
             'biblioitems' => $item->{biblionumber},
             'borrowers'   => $borrower,
             'biblio'      => $item->{biblionumber},
             'biblioitems' => $item->{biblionumber},
             'borrowers'   => $borrower,
@@ -2720,7 +2812,7 @@ sub SendCirculationAlert {
         }
     ) or return;
 
         }
     ) or return;
 
-    my @transports = @{ $borrower_preferences->{transports} };
+    my @transports = keys %{ $borrower_preferences->{transports} };
     # warn "no transports" unless @transports;
     for (@transports) {
         # warn "transport: $_";
     # warn "no transports" unless @transports;
     for (@transports) {
         # warn "transport: $_";
@@ -3046,10 +3138,12 @@ sub ReturnLostItem{
 
     MarkIssueReturned( $borrowernumber, $itemnum );
     my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber );
 
     MarkIssueReturned( $borrowernumber, $itemnum );
     my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber );
+    my $item = C4::Items::GetItem( $itemnum );
+    my $old_note = ($item->{'paidfor'} && ($item->{'paidfor'} ne q{})) ? $item->{'paidfor'}.' / ' : q{};
     my @datearr = localtime(time);
     my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
     my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
     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);
+    ModItem({ paidfor =>  $old_note."Paid for by $bor $date" }, undef, $itemnum);
 }
 
 
 }
 
 
@@ -3068,12 +3162,13 @@ sub LostItem{
 
     # if a borrower lost the item, add a replacement cost to the their record
     if ( my $borrowernumber = $issues->{borrowernumber} ){
 
     # 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 );
 
         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 ";
 
         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;
+        MarkIssueReturned($borrowernumber,$itemnumber,undef,undef,$borrower->{'privacy'}) if $mark_returned;
     }
 }
 
     }
 }