BugFix - ReturnToShelvingCart
[koha.git] / C4 / Circulation.pm
index 2b4e86c..d17519d 100644 (file)
@@ -1,6 +1,7 @@
 package C4::Circulation;
 
 # Copyright 2000-2002 Katipo Communications
 package C4::Circulation;
 
 # Copyright 2000-2002 Katipo Communications
+# copyright 2010 BibLibre
 #
 # This file is part of Koha.
 #
 #
 # This file is part of Koha.
 #
@@ -20,46 +21,40 @@ package C4::Circulation;
 
 use strict;
 #use warnings; FIXME - Bug 2505
 
 use strict;
 #use warnings; FIXME - Bug 2505
+use DateTime;
 use C4::Context;
 use C4::Stats;
 use C4::Reserves;
 use C4::Context;
 use C4::Stats;
 use C4::Reserves;
-use C4::Koha;
 use C4::Biblio;
 use C4::Items;
 use C4::Members;
 use C4::Dates;
 use C4::Biblio;
 use C4::Items;
 use C4::Members;
 use C4::Dates;
-use C4::Calendar;
+use C4::Dates qw(format_date);
 use C4::Accounts;
 use C4::ItemCirculationAlertPreference;
 use C4::Accounts;
 use C4::ItemCirculationAlertPreference;
-use C4::Dates qw(format_date);
 use C4::Message;
 use C4::Debug;
 use C4::Message;
 use C4::Debug;
-use Date::Calc qw(
-  Today
-  Today_and_Now
-  Add_Delta_YM
-  Add_Delta_DHMS
-  Date_to_Days
-  Day_of_Week
-  Add_Delta_Days       
-);
-use POSIX qw(strftime);
 use C4::Branch; # GetBranches
 use C4::Log; # logaction
 use C4::Branch; # GetBranches
 use C4::Log; # logaction
-
+use C4::Koha qw(GetAuthorisedValueByCode);
+use C4::Overdues qw(CalcFine UpdateFine);
 use Data::Dumper;
 use Data::Dumper;
+use Koha::DateUtils;
+use Koha::Calendar;
+use Carp;
 
 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
        push @EXPORT, qw(
        @ISA    = qw(Exporter);
 
        # FIXME subs that should probably be elsewhere
        push @EXPORT, qw(
-               &FixOverduesOnReturn
                &barcodedecode
                &barcodedecode
+        &LostItem
+        &ReturnLostItem
        );
 
        # subs to deal with issuing a book
        );
 
        # subs to deal with issuing a book
@@ -71,7 +66,6 @@ BEGIN {
                &GetRenewCount
                &GetItemIssue
                &GetItemIssues
                &GetRenewCount
                &GetItemIssue
                &GetItemIssues
-               &GetBorrowerIssues
                &GetIssuingCharges
                &GetIssuingRule
         &GetBranchBorrowerCircRule
                &GetIssuingCharges
                &GetIssuingRule
         &GetBranchBorrowerCircRule
@@ -97,7 +91,17 @@ BEGIN {
                 &IsBranchTransferAllowed
                 &CreateBranchTransferLimit
                 &DeleteBranchTransferLimits
                 &IsBranchTransferAllowed
                 &CreateBranchTransferLimit
                 &DeleteBranchTransferLimits
+        &TransferSlip
        );
        );
+
+    # subs to deal with offline circulation
+    push @EXPORT, qw(
+      &GetOfflineOperations
+      &GetOfflineOperation
+      &AddOfflineOperation
+      &DeleteOfflineOperation
+      &ProcessOfflineOperation
+    );
 }
 
 =head1 NAME
 }
 
 =head1 NAME
@@ -318,7 +322,7 @@ sub transferbook {
 
     # find reserves.....
     # That'll save a database query.
 
     # find reserves.....
     # That'll save a database query.
-    my ( $resfound, $resrec ) =
+    my ( $resfound, $resrec, undef ) =
       CheckReserves( $itemnumber );
     if ( $resfound and not $ignoreRs ) {
         $resrec->{'ResFound'} = $resfound;
       CheckReserves( $itemnumber );
     if ( $resfound and not $ignoreRs ) {
         $resrec->{'ResFound'} = $resfound;
@@ -430,7 +434,7 @@ sub TooMany {
     my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower);
     if (defined($branch_borrower_circ_rule->{maxissueqty})) {
         my @bind_params = ();
     my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower);
     if (defined($branch_borrower_circ_rule->{maxissueqty})) {
         my @bind_params = ();
-        my $branch_count_query = "SELECT COUNT(*) FROM issues 
+        my $branch_count_query = "SELECT COUNT(*) FROM issues
                                   JOIN items USING (itemnumber)
                                   WHERE borrowernumber = ? ";
         push @bind_params, $borrower->{borrowernumber};
                                   JOIN items USING (itemnumber)
                                   WHERE borrowernumber = ? ";
         push @bind_params, $borrower->{borrowernumber};
@@ -569,7 +573,7 @@ sub itemissues {
 =head2 CanBookBeIssued
 
   ( $issuingimpossible, $needsconfirmation ) =  CanBookBeIssued( $borrower, 
 =head2 CanBookBeIssued
 
   ( $issuingimpossible, $needsconfirmation ) =  CanBookBeIssued( $borrower, 
-                                      $barcode, $duedatespec, $inprocess );
+                      $barcode, $duedatespec, $inprocess, $ignore_reserves );
 
 Check if a book can be issued.
 
 
 Check if a book can be issued.
 
@@ -577,13 +581,14 @@ C<$issuingimpossible> and C<$needsconfirmation> are some hashref.
 
 =over 4
 
 
 =over 4
 
-=item C<$borrower> hash with borrower informations (from GetMemberDetails)
+=item C<$borrower> hash with borrower informations (from GetMember or GetMemberDetails)
 
 =item C<$barcode> is the bar code of the book being issued.
 
 =item C<$duedatespec> is a C4::Dates object.
 
 
 =item C<$barcode> is the bar code of the book being issued.
 
 =item C<$duedatespec> is a C4::Dates object.
 
-=item C<$inprocess>
+=item C<$inprocess> boolean switch
+=item C<$ignore_reserves> boolean switch
 
 =back
 
 
 =back
 
@@ -660,9 +665,11 @@ if the borrower borrows to much things
 =cut
 
 sub CanBookBeIssued {
 =cut
 
 sub CanBookBeIssued {
-    my ( $borrower, $barcode, $duedate, $inprocess ) = @_;
+    my ( $borrower, $barcode, $duedate, $inprocess, $ignore_reserves ) = @_;
     my %needsconfirmation;    # filled with problems that needs confirmations
     my %issuingimpossible;    # filled with problems that causes the issue to be IMPOSSIBLE
     my %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});
@@ -678,22 +685,28 @@ sub CanBookBeIssued {
     #
     # DUE DATE is OK ? -- should already have checked.
     #
     #
     # DUE DATE is OK ? -- should already have checked.
     #
+    if ($duedate && ref $duedate ne 'DateTime') {
+        $duedate = dt_from_string($duedate);
+    }
+    my $now = DateTime->now( time_zone => C4::Context->tz() );
     unless ( $duedate ) {
     unless ( $duedate ) {
-        my $issuedate = strftime( "%Y-%m-%d", localtime );
+        my $issuedate = $now->clone();
 
         my $branch = _GetCircControlBranch($item,$borrower);
         my $itype = ( C4::Context->preference('item-level_itypes') ) ? $item->{'itype'} : $biblioitem->{'itemtype'};
 
         my $branch = _GetCircControlBranch($item,$borrower);
         my $itype = ( C4::Context->preference('item-level_itypes') ) ? $item->{'itype'} : $biblioitem->{'itemtype'};
-        my $loanlength = GetLoanLength( $borrower->{'categorycode'}, $itype, $branch );
-        $duedate = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $loanlength, $branch, $borrower );
+        $duedate = CalcDateDue( $issuedate, $itype, $branch, $borrower );
 
         # Offline circ calls AddIssue directly, doesn't run through here
         #  So issuingimpossible should be ok.
     }
     if ($duedate) {
 
         # Offline circ calls AddIssue directly, doesn't run through here
         #  So issuingimpossible should be ok.
     }
     if ($duedate) {
-        $needsconfirmation{INVALID_DATE} = $duedate->output('syspref')
-          unless $duedate->output('iso') ge C4::Dates->today('iso');
+        my $today = $now->clone();
+        $today->truncate( to => 'minute');
+        if (DateTime->compare($duedate,$today) == -1 ) { # duedate cannot be before now
+            $needsconfirmation{INVALID_DATE} = output_pref($duedate);
+        }
     } else {
     } else {
-        $issuingimpossible{INVALID_DATE} = $duedate->output('syspref');
+            $issuingimpossible{INVALID_DATE} = output_pref($duedate);
     }
 
     #
     }
 
     #
@@ -714,13 +727,25 @@ sub CanBookBeIssued {
     if ( $borrower->{flags}->{'DBARRED'} ) {
         $issuingimpossible{DEBARRED} = 1;
     }
     if ( $borrower->{flags}->{'DBARRED'} ) {
         $issuingimpossible{DEBARRED} = 1;
     }
-    if ( $borrower->{'dateexpiry'} eq '0000-00-00') {
+    if ( !defined $borrower->{dateexpiry} || $borrower->{'dateexpiry'} eq '0000-00-00') {
         $issuingimpossible{EXPIRED} = 1;
     } else {
         $issuingimpossible{EXPIRED} = 1;
     } else {
-        my @expirydate=  split /-/,$borrower->{'dateexpiry'};
-        if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 ||
-            Date_to_Days(Today) > Date_to_Days( @expirydate )) {
-            $issuingimpossible{EXPIRED} = 1;                                   
+        my ($y, $m, $d) =  split /-/,$borrower->{'dateexpiry'};
+        if ($y && $m && $d) { # are we really writing oinvalid dates to borrs
+            my $expiry_dt = DateTime->new(
+                year => $y,
+                month => $m,
+                day   => $d,
+                time_zone => C4::Context->tz,
+            );
+            $expiry_dt->truncate( to => 'day');
+            my $today = $now->clone()->truncate(to => 'day');
+            if (DateTime->compare($today, $expiry_dt) == 1) {
+                $issuingimpossible{EXPIRED} = 1;
+            }
+        } else {
+            carp("Invalid expity date in borr");
+            $issuingimpossible{EXPIRED} = 1;
         }
     }
     #
         }
     }
     #
@@ -729,7 +754,7 @@ sub CanBookBeIssued {
 
     # DEBTS
     my ($amount) =
 
     # DEBTS
     my ($amount) =
-      C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->output('iso') );
+      C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->ymd() );
     my $amountlimit = C4::Context->preference("noissuescharge");
     my $allowfineoverride = C4::Context->preference("AllowFineOverride");
     my $allfinesneedoverride = C4::Context->preference("AllFinesNeedOverride");
     my $amountlimit = C4::Context->preference("noissuescharge");
     my $allowfineoverride = C4::Context->preference("AllowFineOverride");
     my $allfinesneedoverride = C4::Context->preference("AllFinesNeedOverride");
@@ -771,7 +796,7 @@ sub CanBookBeIssued {
     #
        my ($current_loan_count, $max_loans_allowed) = TooMany( $borrower, $item->{biblionumber}, $item );
     # if TooMany max_loans_allowed returns 0 the user doesn't have permission to check out this book
     #
        my ($current_loan_count, $max_loans_allowed) = TooMany( $borrower, $item->{biblionumber}, $item );
     # if TooMany max_loans_allowed returns 0 the user doesn't have permission to check out this book
-    if ($max_loans_allowed eq 0) {
+    if (defined $max_loans_allowed && $max_loans_allowed == 0) {
         $needsconfirmation{PATRON_CANT} = 1;
     } else {
         if($max_loans_allowed){
         $needsconfirmation{PATRON_CANT} = 1;
     } else {
         if($max_loans_allowed){
@@ -817,7 +842,7 @@ sub CanBookBeIssued {
             }
         }
     }
             }
         }
     }
-    if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} == 1 )
+    if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} > 0 )
     {
         $issuingimpossible{WTHDRAWN} = 1;
     }
     {
         $issuingimpossible{WTHDRAWN} = 1;
     }
@@ -826,6 +851,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 ) ) {
@@ -858,7 +888,7 @@ sub CanBookBeIssued {
     elsif ($issue->{borrowernumber}) {
 
         # issued to someone else
     elsif ($issue->{borrowernumber}) {
 
         # issued to someone else
-        my $currborinfo =    C4::Members::GetMemberDetails( $issue->{borrowernumber} );
+        my $currborinfo =    C4::Members::GetMember( borrowernumber => $issue->{borrowernumber} );
 
 #        warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
         $needsconfirmation{ISSUED_TO_ANOTHER} = 1;
 
 #        warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
         $needsconfirmation{ISSUED_TO_ANOTHER} = 1;
@@ -868,37 +898,40 @@ sub CanBookBeIssued {
         $needsconfirmation{issued_borrowernumber} = $currborinfo->{'borrowernumber'};
     }
 
         $needsconfirmation{issued_borrowernumber} = $currborinfo->{'borrowernumber'};
     }
 
-    # See if the item is on reserve.
-    my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
-    if ($restype) {
-               my $resbor = $res->{'borrowernumber'};
-               my ( $resborrower ) = C4::Members::GetMemberDetails( $resbor, 0 );
-               my $branches  = GetBranches();
-               my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'};
-        if ( $resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting" )
-        {
-            # The item is on reserve and waiting, but has been
-            # reserved by some other patron.
-            $needsconfirmation{RESERVE_WAITING} = 1;
-            $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
-            $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
-            $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
-            $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
-            $needsconfirmation{'resbranchname'} = $branchname;
-            $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'});
-        }
-        elsif ( $restype eq "Reserved" ) {
-            # The item is on reserve for someone else.
-            $needsconfirmation{RESERVED} = 1;
-            $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
-            $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
-            $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
-            $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
-            $needsconfirmation{'resbranchname'} = $branchname;
-            $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'});
+    unless ( $ignore_reserves ) {
+        # See if the item is on reserve.
+        my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
+        if ($restype) {
+            my $resbor = $res->{'borrowernumber'};
+            if ( $resbor ne $borrower->{'borrowernumber'} ) {
+                my ( $resborrower ) = C4::Members::GetMember( borrowernumber => $resbor );
+                my $branchname = GetBranchName( $res->{'branchcode'} );
+                if ( $restype eq "Waiting" )
+                {
+                    # The item is on reserve and waiting, but has been
+                    # reserved by some other patron.
+                    $needsconfirmation{RESERVE_WAITING} = 1;
+                    $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
+                    $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
+                    $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
+                    $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
+                    $needsconfirmation{'resbranchname'} = $branchname;
+                    $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'});
+                }
+                elsif ( $restype eq "Reserved" ) {
+                    # The item is on reserve for someone else.
+                    $needsconfirmation{RESERVED} = 1;
+                    $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
+                    $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
+                    $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
+                    $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
+                    $needsconfirmation{'resbranchname'} = $branchname;
+                    $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'});
+                }
+            }
         }
     }
         }
     }
-       return ( \%issuingimpossible, \%needsconfirmation );
+    return ( \%issuingimpossible, \%needsconfirmation, \%alerts );
 }
 
 =head2 AddIssue
 }
 
 =head2 AddIssue
@@ -909,7 +942,7 @@ Issue a book. Does no check, they are done in CanBookBeIssued. If we reach this
 
 =over 4
 
 
 =over 4
 
-=item C<$borrower> is a hash with borrower informations (from GetMemberDetails).
+=item C<$borrower> is a hash with borrower informations (from GetMember or GetMemberDetails).
 
 =item C<$barcode> is the barcode of the item being issued.
 
 
 =item C<$barcode> is the barcode of the item being issued.
 
@@ -943,13 +976,20 @@ sub AddIssue {
     my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate, $sipmode) = @_;
     my $dbh = C4::Context->dbh;
        my $barcodecheck=CheckValidBarcode($barcode);
     my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate, $sipmode) = @_;
     my $dbh = C4::Context->dbh;
        my $barcodecheck=CheckValidBarcode($barcode);
+    if ($datedue && ref $datedue ne 'DateTime') {
+        $datedue = dt_from_string($datedue);
+    }
     # $issuedate defaults to today.
     if ( ! defined $issuedate ) {
     # $issuedate defaults to today.
     if ( ! defined $issuedate ) {
-        $issuedate = strftime( "%Y-%m-%d", localtime );
-        # TODO: for hourly circ, this will need to be a C4::Dates object
-        # and all calls to AddIssue including issuedate will need to pass a Dates object.
+        $issuedate = DateTime->now(time_zone => C4::Context->tz());
+    }
+    else {
+        if ( ref $issuedate ne 'DateTime') {
+            $issuedate = dt_from_string($issuedate);
+
+        }
     }
     }
-       if ($borrower and $barcode and $barcodecheck ne '0'){
+       if ($borrower and $barcode and $barcodecheck ne '0'){#??? wtf
                # find which item we issue
                my $item = GetItem('', $barcode) or return undef;       # if we don't get an Item, abort.
                my $branch = _GetCircControlBranch($item,$borrower);
                # find which item we issue
                my $item = GetItem('', $barcode) or return undef;       # if we don't get an Item, abort.
                my $branch = _GetCircControlBranch($item,$borrower);
@@ -964,12 +1004,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 {
@@ -983,40 +1023,7 @@ sub AddIssue {
                                );
                        }
 
                                );
                        }
 
-                       # See if the item is on reserve.
-                       my ( $restype, $res ) =
-                         C4::Reserves::CheckReserves( $item->{'itemnumber'} );
-                       if ($restype) {
-                               my $resbor = $res->{'borrowernumber'};
-                               if ( $resbor eq $borrower->{'borrowernumber'} ) {
-                                       # The item is reserved by the current patron
-                                       ModReserveFill($res);
-                               }
-                               elsif ( $restype eq "Waiting" ) {
-                                       # warn "Waiting";
-                                       # The item is on reserve and waiting, but has been
-                                       # reserved by some other patron.
-                               }
-                               elsif ( $restype eq "Reserved" ) {
-                                       # warn "Reserved";
-                                       # The item is reserved by someone else.
-                                       if ($cancelreserve) { # cancel reserves on this item
-                                               CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
-                                       }
-                               }
-                               if ($cancelreserve) {
-                                       CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'});
-                               }
-                               else {
-                                       # set waiting reserve to first in reserve queue as book isn't waiting now
-                                       ModReserve(1,
-                                               $res->{'biblionumber'},
-                                               $res->{'borrowernumber'},
-                                               $res->{'branchcode'}
-                                       );
-                               }
-                       }
-
+            MoveReserve( $item->{'itemnumber'}, $borrower->{'borrowernumber'}, $cancelreserve );
                        # Starting process for transfer job (checking transfert and validate it if we have one)
             my ($datesent) = GetTransfers($item->{'itemnumber'});
             if ($datesent) {
                        # Starting process for transfer job (checking transfert and validate it if we have one)
             my ($datesent) = GetTransfers($item->{'itemnumber'});
             if ($datesent) {
@@ -1035,33 +1042,41 @@ sub AddIssue {
         # Record in the database the fact that the book was issued.
         my $sth =
           $dbh->prepare(
         # Record in the database the fact that the book was issued.
         my $sth =
           $dbh->prepare(
-                "INSERT INTO issues 
+                "INSERT INTO issues
                     (borrowernumber, itemnumber,issuedate, date_due, branchcode)
                 VALUES (?,?,?,?,?)"
           );
         unless ($datedue) {
             my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
                     (borrowernumber, itemnumber,issuedate, date_due, branchcode)
                 VALUES (?,?,?,?,?)"
           );
         unless ($datedue) {
             my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
-            my $loanlength = GetLoanLength( $borrower->{'categorycode'}, $itype, $branch );
-            $datedue = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $loanlength, $branch, $borrower );
+            $datedue = CalcDateDue( $issuedate, $itype, $branch, $borrower );
 
         }
 
         }
+        $datedue->truncate( to => 'minute');
         $sth->execute(
             $borrower->{'borrowernumber'},      # borrowernumber
             $item->{'itemnumber'},              # itemnumber
         $sth->execute(
             $borrower->{'borrowernumber'},      # borrowernumber
             $item->{'itemnumber'},              # itemnumber
-            $issuedate,                         # issuedate
-            $datedue->output('iso'),            # date_due
+            $issuedate->strftime('%Y-%m-%d %H:%M:00'), # issuedate
+            $datedue->strftime('%Y-%m-%d %H:%M:00'),   # date_due
             C4::Context->userenv->{'branch'}    # branchcode
         );
             C4::Context->userenv->{'branch'}    # branchcode
         );
-        $sth->finish;
         if ( C4::Context->preference('ReturnToShelvingCart') ) { ## ReturnToShelvingCart is on, anything issued should be taken off the cart.
           CartToShelf( $item->{'itemnumber'} );
         }
         $item->{'issues'}++;
         if ( C4::Context->preference('ReturnToShelvingCart') ) { ## ReturnToShelvingCart is on, anything issued should be taken off the cart.
           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'} ) {
+            _FixAccountForLostAndReturned( $item->{'itemnumber'}, undef, $item->{'barcode'} );
+        }
+
         ModItem({ issues           => $item->{'issues'},
                   holdingbranch    => C4::Context->userenv->{'branch'},
                   itemlost         => 0,
         ModItem({ issues           => $item->{'issues'},
                   holdingbranch    => C4::Context->userenv->{'branch'},
                   itemlost         => 0,
-                  datelastborrowed => C4::Dates->new()->output('iso'),
-                  onloan           => $datedue->output('iso'),
+                  datelastborrowed => DateTime->now(time_zone => C4::Context->tz())->ymd(),
+                  onloan           => $datedue->ymd(),
                 }, $item->{'biblionumber'}, $item->{'itemnumber'});
         ModDateLastSeen( $item->{'itemnumber'} );
 
                 }, $item->{'biblionumber'}, $item->{'itemnumber'});
         ModDateLastSeen( $item->{'itemnumber'} );
 
@@ -1123,53 +1138,117 @@ sub GetLoanLength {
     my $dbh = C4::Context->dbh;
     my $sth =
       $dbh->prepare(
     my $dbh = C4::Context->dbh;
     my $sth =
       $dbh->prepare(
-"select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null"
+'select issuelength, lengthunit from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null'
       );
 # warn "in get loan lenght $borrowertype $itemtype $branchcode ";
 # try to find issuelength & return the 1st available.
 # check with borrowertype, itemtype and branchcode, then without one of those parameters
     $sth->execute( $borrowertype, $itemtype, $branchcode );
     my $loanlength = $sth->fetchrow_hashref;
       );
 # warn "in get loan lenght $borrowertype $itemtype $branchcode ";
 # try to find issuelength & return the 1st available.
 # check with borrowertype, itemtype and branchcode, then without one of those parameters
     $sth->execute( $borrowertype, $itemtype, $branchcode );
     my $loanlength = $sth->fetchrow_hashref;
-    return $loanlength->{issuelength}
-      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
+    return $loanlength
+      if defined($loanlength) && $loanlength->{issuelength};
 
 
-    $sth->execute( $borrowertype, "*", $branchcode );
+    $sth->execute( $borrowertype, '*', $branchcode );
     $loanlength = $sth->fetchrow_hashref;
     $loanlength = $sth->fetchrow_hashref;
-    return $loanlength->{issuelength}
-      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
+    return $loanlength
+      if defined($loanlength) && $loanlength->{issuelength};
 
 
-    $sth->execute( "*", $itemtype, $branchcode );
+    $sth->execute( '*', $itemtype, $branchcode );
     $loanlength = $sth->fetchrow_hashref;
     $loanlength = $sth->fetchrow_hashref;
-    return $loanlength->{issuelength}
-      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
+    return $loanlength
+      if defined($loanlength) && $loanlength->{issuelength};
 
 
-    $sth->execute( "*", "*", $branchcode );
+    $sth->execute( '*', '*', $branchcode );
     $loanlength = $sth->fetchrow_hashref;
     $loanlength = $sth->fetchrow_hashref;
-    return $loanlength->{issuelength}
-      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
+    return $loanlength
+      if defined($loanlength) && $loanlength->{issuelength};
 
 
-    $sth->execute( $borrowertype, $itemtype, "*" );
+    $sth->execute( $borrowertype, $itemtype, '*' );
     $loanlength = $sth->fetchrow_hashref;
     $loanlength = $sth->fetchrow_hashref;
-    return $loanlength->{issuelength}
-      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
+    return $loanlength
+      if defined($loanlength) && $loanlength->{issuelength};
 
 
-    $sth->execute( $borrowertype, "*", "*" );
+    $sth->execute( $borrowertype, '*', '*' );
     $loanlength = $sth->fetchrow_hashref;
     $loanlength = $sth->fetchrow_hashref;
-    return $loanlength->{issuelength}
-      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
+    return $loanlength
+      if defined($loanlength) && $loanlength->{issuelength};
 
 
-    $sth->execute( "*", $itemtype, "*" );
+    $sth->execute( '*', $itemtype, '*' );
     $loanlength = $sth->fetchrow_hashref;
     $loanlength = $sth->fetchrow_hashref;
-    return $loanlength->{issuelength}
-      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
+    return $loanlength
+      if defined($loanlength) && $loanlength->{issuelength};
 
 
-    $sth->execute( "*", "*", "*" );
+    $sth->execute( '*', '*', '*' );
     $loanlength = $sth->fetchrow_hashref;
     $loanlength = $sth->fetchrow_hashref;
-    return $loanlength->{issuelength}
-      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
+    return $loanlength
+      if defined($loanlength) && $loanlength->{issuelength};
 
     # if no rule is set => 21 days (hardcoded)
 
     # if no rule is set => 21 days (hardcoded)
-    return 21;
+    return {
+        issuelength => 21,
+        lengthunit => 'days',
+    };
+
+}
+
+
+=head2 GetHardDueDate
+
+  my ($hardduedate,$hardduedatecompare) = &GetHardDueDate($borrowertype,$itemtype,branchcode)
+
+Get the Hard Due Date and it's comparison for an itemtype, a borrower type and a branch
+
+=cut
+
+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};
+
+    $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);
 }
 
 =head2 GetIssuingRule
 }
 
 =head2 GetIssuingRule
@@ -1317,13 +1396,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
@@ -1331,7 +1414,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
 
@@ -1341,33 +1424,36 @@ 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 );
 
         # 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
@@ -1446,6 +1532,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;
@@ -1456,7 +1543,8 @@ sub AddReturn {
     my $biblio;
     my $doreturn       = 1;
     my $validTransfert = 0;
     my $biblio;
     my $doreturn       = 1;
     my $validTransfert = 0;
-    
+    my $stat_type = 'return';    
+
     # get information on item
     my $itemnumber = GetItemnumberFromBarcode( $barcode );
     unless ($itemnumber) {
     # get information on item
     my $itemnumber = GetItemnumberFromBarcode( $barcode );
     unless ($itemnumber) {
@@ -1473,14 +1561,20 @@ sub AddReturn {
         # even though item is not on loan, it may still be transferred;  therefore, get current branch info
         $doreturn = 0;
         # No issue, no borrowernumber.  ONLY if $doreturn, *might* you have a $borrower later.
         # even though item is not on loan, it may still be transferred;  therefore, get current branch info
         $doreturn = 0;
         # No issue, no borrowernumber.  ONLY if $doreturn, *might* you have a $borrower later.
+        # Record this as a local use, instead of a return, if the RecordLocalUseOnReturn is on
+        if (C4::Context->preference("RecordLocalUseOnReturn")) {
+           $messages->{'LocalUse'} = 1;
+           $stat_type = 'localuse';
+        }
     }
 
     my $item = GetItem($itemnumber) or die "GetItem($itemnumber) failed";
         # full item data, but no borrowernumber or checkout info (no issue)
         # we know GetItem should work because GetItemnumberFromBarcode worked
     }
 
     my $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
 
@@ -1491,8 +1585,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,
@@ -1513,16 +1607,32 @@ sub AddReturn {
 
     # case of a return of document (deal with issues and holdingbranch)
     if ($doreturn) {
 
     # case of a return of document (deal with issues and holdingbranch)
     if ($doreturn) {
+    my $today = DateTime->now( time_zone => C4::Context->tz() );
+    my $datedue = $issue->{date_due};
         $borrower or warn "AddReturn without current borrower";
                my $circControlBranch;
         if ($dropbox) {
             # define circControlBranch only if dropbox mode is set
             # don't allow dropbox mode to create an invalid entry in issues (issuedate > today)
             # FIXME: check issuedate > returndate, factoring in holidays
         $borrower or warn "AddReturn without current borrower";
                my $circControlBranch;
         if ($dropbox) {
             # define circControlBranch only if dropbox mode is set
             # don't allow dropbox mode to create an invalid entry in issues (issuedate > today)
             # FIXME: check issuedate > returndate, factoring in holidays
-            $circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
+            #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
+            $circControlBranch = _GetCircControlBranch($item,$borrower);
+        my $datedue = $issue->{date_due};
+        $issue->{'overdue'} = DateTime->compare($issue->{'date_due'}, $today ) == -1 ? 1 : 0;
         }
 
         if ($borrowernumber) {
         }
 
         if ($borrowernumber) {
+        if($issue->{'overdue'}){
+                my ( $amount, $type, $daycounttotal ) = 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.
         }
@@ -1549,12 +1659,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.....
@@ -1567,11 +1680,15 @@ sub AddReturn {
     if ($borrowernumber) {
         my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox);
         defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!";  # zero is OK, check defined
     if ($borrowernumber) {
         my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox);
         defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!";  # zero is OK, check defined
+        
+        # fix fine days
+        my $debardate = _FixFineDaysOnReturn( $borrower, $item, $issue->{date_due} );
+        $messages->{'Debarred'} = $debardate if ($debardate);
     }
 
     # find reserves.....
     # if we don't have a reserve with the status W, we launch the Checkreserves routine
     }
 
     # find reserves.....
     # if we don't have a reserve with the status W, we launch the Checkreserves routine
-    my ($resfound, $resrec) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
+    my ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
     if ($resfound) {
           $resrec->{'ResFound'} = $resfound;
         $messages->{'ResFound'} = $resrec;
     if ($resfound) {
           $resrec->{'ResFound'} = $resfound;
         $messages->{'ResFound'} = $resrec;
@@ -1580,7 +1697,7 @@ sub AddReturn {
     # update stats?
     # Record the fact that this book was returned.
     UpdateStats(
     # update stats?
     # Record the fact that this book was returned.
     UpdateStats(
-        $branch, 'return', '0', '',
+        $branch, $stat_type, '0', '',
         $item->{'itemnumber'},
         $biblio->{'itemtype'},
         $borrowernumber
         $item->{'itemnumber'},
         $biblio->{'itemtype'},
         $borrowernumber
@@ -1610,7 +1727,7 @@ sub AddReturn {
     #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 .
 
     #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
     #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
 
-    if ($doreturn and ($branch ne $hbr) and not $messages->{'WrongTransfer'} and ($validTransfert ne 1) ){
+    if (($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){
         if ( C4::Context->preference("AutomaticItemReturn"    ) or
             (C4::Context->preference("UseBranchTransferLimits") and
              ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} )
         if ( C4::Context->preference("AutomaticItemReturn"    ) or
             (C4::Context->preference("UseBranchTransferLimits") and
              ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} )
@@ -1651,28 +1768,29 @@ 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 $dbh   = C4::Context->dbh;
-    my $query = "UPDATE issues SET returndate=";
+    my $query = 'UPDATE issues SET returndate=';
     my @bind;
     if ($dropbox_branch) {
     my @bind;
     if ($dropbox_branch) {
-        my $calendar = C4::Calendar->new( branchcode => $dropbox_branch );
-        my $dropboxdate = $calendar->addDate( C4::Dates->new(), -1 );
-        $query .= " ? ";
-        push @bind, $dropboxdate->output('iso');
+        my $calendar = Koha::Calendar->new( branchcode => $dropbox_branch );
+        my $dropboxdate = $calendar->addDate( DateTime->now( time_zone => C4::Context->tz), -1 );
+        $query .= ' ? ';
+        push @bind, $dropboxdate->strftime('%Y-%m-%d %H:%M');
     } elsif ($returndate) {
     } elsif ($returndate) {
-        $query .= " ? ";
+        $query .= ' ? ';
         push @bind, $returndate;
     } else {
         push @bind, $returndate;
     } else {
-        $query .= " now() ";
+        $query .= ' now() ';
     }
     }
-    $query .= " WHERE  borrowernumber = ?  AND itemnumber = ?";
+    $query .= ' WHERE  borrowernumber = ?  AND itemnumber = ?';
     push @bind, $borrowernumber, $itemnumber;
     # FIXME transaction
     my $sth_upd  = $dbh->prepare($query);
     $sth_upd->execute(@bind);
     push @bind, $borrowernumber, $itemnumber;
     # FIXME transaction
     my $sth_upd  = $dbh->prepare($query);
     $sth_upd->execute(@bind);
-    my $sth_copy = $dbh->prepare("INSERT INTO old_issues SELECT * FROM issues 
+    my $sth_copy = $dbh->prepare('INSERT INTO old_issues SELECT * FROM issues
                                   WHERE borrowernumber = ?
                                   WHERE borrowernumber = ?
-                                  AND itemnumber = ?");
+                                  AND itemnumber = ?');
     $sth_copy->execute($borrowernumber, $itemnumber);
     # anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber
     if ( $privacy == 2) {
     $sth_copy->execute($borrowernumber, $itemnumber);
     # anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber
     if ( $privacy == 2) {
@@ -1690,6 +1808,65 @@ sub MarkIssueReturned {
     $sth_del->execute($borrowernumber, $itemnumber);
 }
 
     $sth_del->execute($borrowernumber, $itemnumber);
 }
 
+=head2 _FixFineDaysOnReturn
+
+    &_FixFineDaysOnReturn($borrower, $item, $datedue);
+
+C<$borrower> borrower hashref
+
+C<$item> item hashref
+
+C<$datedue> date due
+
+Internal function, called only by AddReturn that calculate and update the user fine days, and debars him
+
+=cut
+
+sub _FixFineDaysOnReturn {
+    my ( $borrower, $item, $datedue ) = @_;
+    return unless ($datedue);
+    
+    my $dt_due =  dt_from_string( $datedue );
+    my $dt_today = DateTime->now( time_zone => C4::Context->tz() );
+
+    my $branchcode = _GetCircControlBranch( $item, $borrower );
+    my $calendar = Koha::Calendar->new( branchcode => $branchcode );
+
+    # $deltadays is a DateTime::Duration object
+    my $deltadays = $calendar->days_between( $dt_due, $dt_today );
+
+    my $circcontrol = C4::Context::preference('CircControl');
+    my $issuingrule = GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
+    my $finedays    = $issuingrule->{finedays};
+    my $unit        = $issuingrule->{lengthunit};
+
+    # exit if no finedays defined
+    return unless $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 - $grace )->is_positive ) { # you can't compare DateTime::Durations with logical operators
+        my $new_debar_dt = $dt_today->clone()->add_duration( $deltadays * $finedays );
+        my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
+        # check to see if the current debar date is a valid date
+        if ( $borrower->{debarred} && $borrower_debar_dt ) {
+        # if so, is it before the new date?  update only if true
+            if ( DateTime->compare( $borrower_debar_dt, $new_debar_dt ) == -1 ) {
+                C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() );
+                return $new_debar_dt->ymd();
+            }
+        # if the borrower's debar date is not set or valid, debar them
+        } else {
+            C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() );
+            return $new_debar_dt->ymd();
+        }
+    }
+}
+
 =head2 _FixOverduesOnReturn
 
    &_FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
 =head2 _FixOverduesOnReturn
 
    &_FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
@@ -1773,10 +1950,11 @@ sub _FixAccountForLostAndReturned {
     my $item_id        = @_ ? shift : $itemnumber;  # Send the barcode if you want that logged in the description
     my $dbh = C4::Context->dbh;
     # check for charge made for lost book
     my $item_id        = @_ ? shift : $itemnumber;  # Send the barcode if you want that logged in the description
     my $dbh = C4::Context->dbh;
     # check for charge made for lost book
-    my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE (itemnumber = ?) AND (accounttype='L' OR accounttype='Rep') ORDER BY date DESC");
+    my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE itemnumber = ? AND accounttype IN ('L', 'Rep', 'W') ORDER BY date DESC, accountno DESC");
     $sth->execute($itemnumber);
     my $data = $sth->fetchrow_hashref;
     $data or return;    # bail if there is nothing to do
     $sth->execute($itemnumber);
     my $data = $sth->fetchrow_hashref;
     $data or return;    # bail if there is nothing to do
+    $data->{accounttype} eq 'W' and return;    # Written off
 
     # writeoff this amount
     my $offset;
 
     # writeoff this amount
     my $offset;
@@ -1864,7 +2042,7 @@ sub _GetCircControlBranch {
     my $circcontrol = C4::Context->preference('CircControl');
     my $branch;
 
     my $circcontrol = C4::Context->preference('CircControl');
     my $branch;
 
-    if ($circcontrol eq 'PickupLibrary') {
+    if ($circcontrol eq 'PickupLibrary' and (C4::Context->userenv and C4::Context->userenv->{'branch'}) ) {
         $branch= C4::Context->userenv->{'branch'};
     } elsif ($circcontrol eq 'PatronLibrary') {
         $branch=$borrower->{branchcode};
         $branch= C4::Context->userenv->{'branch'};
     } elsif ($circcontrol eq 'PatronLibrary') {
         $branch=$borrower->{branchcode};
@@ -1902,14 +2080,19 @@ sub GetItemIssue {
     return unless $itemnumber;
     my $sth = C4::Context->dbh->prepare(
         "SELECT *
     return unless $itemnumber;
     my $sth = C4::Context->dbh->prepare(
         "SELECT *
-        FROM issues 
+        FROM issues
         LEFT JOIN items ON issues.itemnumber=items.itemnumber
         WHERE issues.itemnumber=?");
     $sth->execute($itemnumber);
     my $data = $sth->fetchrow_hashref;
     return unless $data;
         LEFT JOIN items ON issues.itemnumber=items.itemnumber
         WHERE issues.itemnumber=?");
     $sth->execute($itemnumber);
     my $data = $sth->fetchrow_hashref;
     return unless $data;
-    $data->{'overdue'} = ($data->{'date_due'} lt C4::Dates->today('iso')) ? 1 : 0;
-    return ($data);
+    $data->{issuedate} = dt_from_string($data->{issuedate}, 'sql');
+    $data->{issuedate}->truncate(to => 'minute');
+    $data->{date_due} = dt_from_string($data->{date_due}, 'sql');
+    $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;
 }
 
 =head2 GetOpenIssue
 }
 
 =head2 GetOpenIssue
@@ -1951,14 +2134,15 @@ Returns reference to an array of hashes
 sub GetItemIssues {
     my ( $itemnumber, $history ) = @_;
     
 sub GetItemIssues {
     my ( $itemnumber, $history ) = @_;
     
-    my $today = C4::Dates->today('iso');  # get today date
-    my $sql = "SELECT * FROM issues 
+    my $today = DateTime->now( time_zome => C4::Context->tz);  # get today date
+    $today->truncate( to => 'minute' );
+    my $sql = "SELECT * FROM issues
               JOIN borrowers USING (borrowernumber)
               JOIN items     USING (itemnumber)
               WHERE issues.itemnumber = ? ";
     if ($history) {
         $sql .= "UNION ALL
               JOIN borrowers USING (borrowernumber)
               JOIN items     USING (itemnumber)
               WHERE issues.itemnumber = ? ";
     if ($history) {
         $sql .= "UNION ALL
-                 SELECT * FROM old_issues 
+                 SELECT * FROM old_issues
                  LEFT JOIN borrowers USING (borrowernumber)
                  JOIN items USING (itemnumber)
                  WHERE old_issues.itemnumber = ? ";
                  LEFT JOIN borrowers USING (borrowernumber)
                  JOIN items USING (itemnumber)
                  WHERE old_issues.itemnumber = ? ";
@@ -1972,7 +2156,10 @@ sub GetItemIssues {
     }
     my $results = $sth->fetchall_arrayref({});
     foreach (@$results) {
     }
     my $results = $sth->fetchall_arrayref({});
     foreach (@$results) {
-        $_->{'overdue'} = ($_->{'date_due'} lt $today) ? 1 : 0;
+        my $date_due = dt_from_string($_->{date_due},'sql');
+        $date_due->truncate( to => 'minute' );
+
+        $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0;
     }
     return $results;
 }
     }
     return $results;
 }
@@ -2034,9 +2221,10 @@ sub GetUpcomingDueIssues {
     my $dbh = C4::Context->dbh;
 
     my $statement = <<END_SQL;
     my $dbh = C4::Context->dbh;
 
     my $statement = <<END_SQL;
-SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due
+SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due, branches.branchemail
 FROM issues 
 LEFT JOIN items USING (itemnumber)
 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 ) ) < ?
 END_SQL
 WhERE returndate is NULL
 AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
 END_SQL
@@ -2101,7 +2289,7 @@ sub CanBookBeRenewed {
                    SELECT 
                     borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
                    FROM  issuingrules, 
                    SELECT 
                     borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
                    FROM  issuingrules, 
-                   issues 
+                   issues
                    LEFT JOIN items USING (itemnumber) 
                    LEFT JOIN borrowers USING (borrowernumber) 
                    LEFT JOIN biblioitems USING (biblioitemnumber)
                    LEFT JOIN items USING (itemnumber) 
                    LEFT JOIN borrowers USING (borrowernumber) 
                    LEFT JOIN biblioitems USING (biblioitemnumber)
@@ -2133,7 +2321,7 @@ sub CanBookBeRenewed {
                        $error="too_many";
                }
                
                        $error="too_many";
                }
                
-        my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber);
+        my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber);
         if ($resfound) {
             $renewokay = 0;
                        $error="on_reserve"
         if ($resfound) {
             $renewokay = 0;
                        $error="on_reserve"
@@ -2172,7 +2360,7 @@ sub AddRenewal {
     my $itemnumber      = shift or return undef;
     my $branch          = shift;
     my $datedue         = shift;
     my $itemnumber      = shift or return undef;
     my $branch          = shift;
     my $datedue         = shift;
-    my $lastreneweddate = shift || C4::Dates->new()->output('iso');
+    my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd();
     my $item   = GetItem($itemnumber) or return undef;
     my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
 
     my $item   = GetItem($itemnumber) or return undef;
     my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
 
@@ -2186,25 +2374,22 @@ sub AddRenewal {
     $sth->execute( $borrowernumber, $itemnumber );
     my $issuedata = $sth->fetchrow_hashref;
     $sth->finish;
     $sth->execute( $borrowernumber, $itemnumber );
     my $issuedata = $sth->fetchrow_hashref;
     $sth->finish;
-    if($datedue && ! $datedue->output('iso')){
-        warn "Invalid date passed to AddRenewal.";
-        return undef;
+    if(defined $datedue && ref $datedue ne 'DateTime' ) {
+        carp 'Invalid date passed to AddRenewal.';
+        return;
     }
     # If the due date wasn't specified, calculate it by adding the
     # book's loan length to today's date or the current due date
     # based on the value of the RenewalPeriodBase syspref.
     unless ($datedue) {
 
     }
     # If the due date wasn't specified, calculate it by adding the
     # book's loan length to today's date or the current due date
     # based on the value of the RenewalPeriodBase syspref.
     unless ($datedue) {
 
-        my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef;
-        my $loanlength = GetLoanLength(
-                    $borrower->{'categorycode'},
-                    (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ,
-                               $issuedata->{'branchcode'}  );   # that's the circ control branch.
+        my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) or return undef;
+        my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
 
         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
 
         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
-                                        C4::Dates->new($issuedata->{date_due}, 'iso') :
-                                        C4::Dates->new();
-        $datedue =  CalcDateDue($datedue,$loanlength,$issuedata->{'branchcode'},$borrower);
+                                        $issuedata->{date_due} :
+                                        DateTime->now( time_zone => C4::Context->tz());
+        $datedue =  CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower);
     }
 
     # 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
@@ -2214,31 +2399,30 @@ sub AddRenewal {
                             WHERE borrowernumber=? 
                             AND itemnumber=?"
     );
                             WHERE borrowernumber=? 
                             AND itemnumber=?"
     );
-    $sth->execute( $datedue->output('iso'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
+
+    $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
     $sth->finish;
 
     # Update the renewal count on the item, and tell zebra to reindex
     $renews = $biblio->{'renewals'} + 1;
     $sth->finish;
 
     # Update the renewal count on the item, and tell zebra to reindex
     $renews = $biblio->{'renewals'} + 1;
-    ModItem({ renewals => $renews, onloan => $datedue->output('iso') }, $biblio->{'biblionumber'}, $itemnumber);
+    ModItem({ renewals => $renews, onloan => $datedue->strftime('%Y-%m-%d %H:%M')}, $biblio->{'biblionumber'}, $itemnumber);
 
     # Charge a new rental fee, if applicable?
     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
     if ( $charge > 0 ) {
         my $accountno = getnextacctno( $borrowernumber );
         my $item = GetBiblioFromItemNumber($itemnumber);
 
     # Charge a new rental fee, if applicable?
     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
     if ( $charge > 0 ) {
         my $accountno = getnextacctno( $borrowernumber );
         my $item = GetBiblioFromItemNumber($itemnumber);
+        my $manager_id = 0;
+        $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; 
         $sth = $dbh->prepare(
                 "INSERT INTO accountlines
         $sth = $dbh->prepare(
                 "INSERT INTO accountlines
-                    (date,
-                                       borrowernumber, accountno, amount,
-                    description,
-                                       accounttype, amountoutstanding, itemnumber
-                                       )
-                    VALUES (now(),?,?,?,?,?,?,?)"
+                    (date, borrowernumber, accountno, amount, manager_id,
+                    description,accounttype, amountoutstanding, itemnumber)
+                    VALUES (now(),?,?,?,?,?,?,?,?)"
         );
         );
-        $sth->execute( $borrowernumber, $accountno, $charge,
+        $sth->execute( $borrowernumber, $accountno, $charge, $manager_id,
             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
             'Rent', $charge, $itemnumber );
             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
             'Rent', $charge, $itemnumber );
-        $sth->finish;
     }
     # Log the renewal
     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
     }
     # Log the renewal
     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
@@ -2253,7 +2437,7 @@ sub GetRenewCount {
     my $renewsallowed = 0;
     my $renewsleft    = 0;
 
     my $renewsallowed = 0;
     my $renewsleft    = 0;
 
-    my $borrower = C4::Members::GetMemberDetails($bornum);
+    my $borrower = C4::Members::GetMember( borrowernumber => $bornum);
     my $item     = GetItem($itemno); 
 
     # Look in the issues table for this item, lent to this borrower,
     my $item     = GetItem($itemno); 
 
     # Look in the issues table for this item, lent to this borrower,
@@ -2306,39 +2490,78 @@ sub GetIssuingCharges {
     my $item_type;
 
     # Get the book's item type and rental charge (via its biblioitem).
     my $item_type;
 
     # Get the book's item type and rental charge (via its biblioitem).
-    my $qcharge =     "SELECT itemtypes.itemtype,rentalcharge FROM items
-            LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
-       $qcharge .= (C4::Context->preference('item-level_itypes'))
-                ? " LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
-                : " LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
-       
-    $qcharge .=      "WHERE items.itemnumber =?";
-   
-    my $sth1 = $dbh->prepare($qcharge);
-    $sth1->execute($itemnumber);
-    if ( my $data1 = $sth1->fetchrow_hashref ) {
-        $item_type = $data1->{'itemtype'};
-        $charge    = $data1->{'rentalcharge'};
-        my $q2 = "SELECT rentaldiscount FROM borrowers
+    my $charge_query = 'SELECT itemtypes.itemtype,rentalcharge FROM items
+        LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber';
+    $charge_query .= (C4::Context->preference('item-level_itypes'))
+        ? ' LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype'
+        : ' LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype';
+
+    $charge_query .= ' WHERE items.itemnumber =?';
+
+    my $sth = $dbh->prepare($charge_query);
+    $sth->execute($itemnumber);
+    if ( my $item_data = $sth->fetchrow_hashref ) {
+        $item_type = $item_data->{itemtype};
+        $charge    = $item_data->{rentalcharge};
+        my $branch = C4::Branch::mybranch();
+        my $discount_query = q|SELECT rentaldiscount,
+            issuingrules.itemtype, issuingrules.branchcode
+            FROM borrowers
             LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
             WHERE borrowers.borrowernumber = ?
             LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
             WHERE borrowers.borrowernumber = ?
-            AND (issuingrules.itemtype = ? OR issuingrules.itemtype = '*')";
-        my $sth2 = $dbh->prepare($q2);
-        $sth2->execute( $borrowernumber, $item_type );
-        if ( my $data2 = $sth2->fetchrow_hashref ) {
-            my $discount = $data2->{'rentaldiscount'};
-            if ( $discount eq 'NULL' ) {
-                $discount = 0;
-            }
+            AND (issuingrules.itemtype = ? OR issuingrules.itemtype = '*')
+            AND (issuingrules.branchcode = ? OR issuingrules.branchcode = '*')|;
+        my $discount_sth = $dbh->prepare($discount_query);
+        $discount_sth->execute( $borrowernumber, $item_type, $branch );
+        my $discount_rules = $discount_sth->fetchall_arrayref({});
+        if (@{$discount_rules}) {
+            # We may have multiple rules so get the most specific
+            my $discount = _get_discount_from_rule($discount_rules, $branch, $item_type);
             $charge = ( $charge * ( 100 - $discount ) ) / 100;
         }
             $charge = ( $charge * ( 100 - $discount ) ) / 100;
         }
-        $sth2->finish;
     }
 
     }
 
-    $sth1->finish;
+    $sth->finish; # we havent _explicitly_ fetched all rows
     return ( $charge, $item_type );
 }
 
     return ( $charge, $item_type );
 }
 
+# Select most appropriate discount rule from those returned
+sub _get_discount_from_rule {
+    my ($rules_ref, $branch, $itemtype) = @_;
+    my $discount;
+
+    if (@{$rules_ref} == 1) { # only 1 applicable rule use it
+        $discount = $rules_ref->[0]->{rentaldiscount};
+        return (defined $discount) ? $discount : 0;
+    }
+    # could have up to 4 does one match $branch and $itemtype
+    my @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq $itemtype } @{$rules_ref};
+    if (@d) {
+        $discount = $d[0]->{rentaldiscount};
+        return (defined $discount) ? $discount : 0;
+    }
+    # do we have item type + all branches
+    @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq $itemtype } @{$rules_ref};
+    if (@d) {
+        $discount = $d[0]->{rentaldiscount};
+        return (defined $discount) ? $discount : 0;
+    }
+    # do we all item types + this branch
+    @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq q{*} } @{$rules_ref};
+    if (@d) {
+        $discount = $d[0]->{rentaldiscount};
+        return (defined $discount) ? $discount : 0;
+    }
+    # so all and all (surely we wont get here)
+    @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq q{*} } @{$rules_ref};
+    if (@d) {
+        $discount = $d[0]->{rentaldiscount};
+        return (defined $discount) ? $discount : 0;
+    }
+    # none of the above
+    return 0;
+}
+
 =head2 AddIssuingCharge
 
   &AddIssuingCharge( $itemno, $borrowernumber, $charge )
 =head2 AddIssuingCharge
 
   &AddIssuingCharge( $itemno, $borrowernumber, $charge )
@@ -2349,15 +2572,17 @@ sub AddIssuingCharge {
     my ( $itemnumber, $borrowernumber, $charge ) = @_;
     my $dbh = C4::Context->dbh;
     my $nextaccntno = getnextacctno( $borrowernumber );
     my ( $itemnumber, $borrowernumber, $charge ) = @_;
     my $dbh = C4::Context->dbh;
     my $nextaccntno = getnextacctno( $borrowernumber );
+    my $manager_id = 0;
+    $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
     my $query ="
         INSERT INTO accountlines
             (borrowernumber, itemnumber, accountno,
             date, amount, description, accounttype,
     my $query ="
         INSERT INTO accountlines
             (borrowernumber, itemnumber, accountno,
             date, amount, description, accounttype,
-            amountoutstanding)
-        VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?)
+            amountoutstanding, manager_id)
+        VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?,?)
     ";
     my $sth = $dbh->prepare($query);
     ";
     my $sth = $dbh->prepare($query);
-    $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge );
+    $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge, $manager_id );
     $sth->finish;
 }
 
     $sth->finish;
 }
 
@@ -2518,19 +2743,29 @@ sub SendCirculationAlert {
     my ($type, $item, $borrower, $branch) =
         ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch});
     my %message_name = (
     my ($type, $item, $borrower, $branch) =
         ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch});
     my %message_name = (
-        CHECKIN  => 'Item Check-in',
-        CHECKOUT => 'Item Checkout',
+        CHECKIN  => 'Item_Check_in',
+        CHECKOUT => 'Item_Checkout',
     );
     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
         borrowernumber => $borrower->{borrowernumber},
         message_name   => $message_name{$type},
     });
     );
     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
         borrowernumber => $borrower->{borrowernumber},
         message_name   => $message_name{$type},
     });
-    my $letter = C4::Letters::getletter('circulation', $type);
-    C4::Letters::parseletter($letter, 'biblio',      $item->{biblionumber});
-    C4::Letters::parseletter($letter, 'biblioitems', $item->{biblionumber});
-    C4::Letters::parseletter($letter, 'borrowers',   $borrower->{borrowernumber});
-    C4::Letters::parseletter($letter, 'branches',    $branch);
-    my @transports = @{ $borrower_preferences->{transports} };
+    my $issues_table = ( $type eq 'CHECKOUT' ) ? 'issues' : 'old_issues';
+    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,
+            'branches'    => $branch,
+        }
+    ) or return;
+
+    my @transports = keys %{ $borrower_preferences->{transports} };
     # warn "no transports" unless @transports;
     for (@transports) {
         # warn "transport: $_";
     # warn "no transports" unless @transports;
     for (@transports) {
         # warn "transport: $_";
@@ -2544,7 +2779,8 @@ sub SendCirculationAlert {
             $message->update;
         }
     }
             $message->update;
         }
     }
-    $letter;
+
+    return $letter;
 }
 
 =head2 updateWrongTransfer
 }
 
 =head2 updateWrongTransfer
@@ -2588,81 +2824,100 @@ sub UpdateHoldingbranch {
 
 =head2 CalcDateDue
 
 
 =head2 CalcDateDue
 
-$newdatedue = CalcDateDue($startdate,$loanlength,$branchcode);
-this function calculates the due date given the loan length ,
+$newdatedue = CalcDateDue($startdate,$itemtype,$branchcode,$borrower);
+
+this function calculates the due date given the start date and configured circulation rules,
 checking against the holidays calendar as per the 'useDaysMode' syspref.
 C<$startdate>   = C4::Dates object representing start date of loan period (assumed to be today)
 checking against the holidays calendar as per the 'useDaysMode' syspref.
 C<$startdate>   = C4::Dates object representing start date of loan period (assumed to be today)
+C<$itemtype>  = itemtype code of item in question
 C<$branch>  = location whose calendar to use
 C<$branch>  = location whose calendar to use
-C<$loanlength>  = loan length prior to adjustment
+C<$borrower> = Borrower object
+
 =cut
 
 =cut
 
-sub CalcDateDue { 
-       my ($startdate,$loanlength,$branch,$borrower) = @_;
-       my $datedue;
+sub CalcDateDue {
+    my ( $startdate, $itemtype, $branch, $borrower ) = @_;
 
 
-       if(C4::Context->preference('useDaysMode') eq 'Days') {  # ignoring calendar
-               my $timedue = time + ($loanlength) * 86400;
-       #FIXME - assumes now even though we take a startdate 
-               my @datearr  = localtime($timedue);
-               $datedue = C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
-       } else {
-               my $calendar = C4::Calendar->new(  branchcode => $branch );
-               $datedue = $calendar->addDate($startdate, $loanlength);
-       }
+    # loanlength now a href
+    my $loanlength =
+      GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
 
 
-       # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
-       if ( C4::Context->preference('ReturnBeforeExpiry') && $datedue->output('iso') gt $borrower->{dateexpiry} ) {
-           $datedue = C4::Dates->new( $borrower->{dateexpiry}, 'iso' );
-       }
-
-       # if ceilingDueDate ON the datedue can't be after the ceiling date
-       if ( C4::Context->preference('ceilingDueDate')
-             && ( C4::Context->preference('ceilingDueDate') =~ C4::Dates->regexp('syspref') ) ) {
-            my $ceilingDate = C4::Dates->new( C4::Context->preference('ceilingDueDate') );
-            if ( $datedue->output( 'iso' ) gt $ceilingDate->output( 'iso' ) ) {
-                $datedue = $ceilingDate;
-            }
-       }
+    my $datedue;
 
 
-       return $datedue;
-}
-
-=head2 CheckValidDatedue
-
-  $newdatedue = CheckValidDatedue($date_due,$itemnumber,$branchcode);
+    # if globalDueDate ON the datedue is set to that date
+    if (C4::Context->preference('globalDueDate')
+        && ( C4::Context->preference('globalDueDate') =~
+            C4::Dates->regexp('syspref') )
+      ) {
+        $datedue = dt_from_string(
+            C4::Context->preference('globalDueDate'),
+            C4::Context->preference('dateformat')
+        );
+    } else {
 
 
-This function does not account for holiday exceptions nor does it handle the 'useDaysMode' syspref .
-To be replaced by CalcDateDue() once C4::Calendar use is tested.
+        # otherwise, calculate the datedue as normal
+        if ( C4::Context->preference('useDaysMode') eq 'Days' )
+        {    # ignoring calendar
+            my $dt =
+              DateTime->now( time_zone => C4::Context->tz() )
+              ->truncate( to => 'minute' );
+            if ( $loanlength->{lengthunit} eq 'hours' ) {
+                $dt->add( hours => $loanlength->{issuelength} );
+                return $dt;
+            } else {    # days
+                $dt->add( days => $loanlength->{issuelength} );
+                $dt->set_hour(23);
+                $dt->set_minute(59);
+                return $dt;
+            }
+        } else {
+            my $dur;
+            if ($loanlength->{lengthunit} eq 'hours') {
+                $dur = DateTime::Duration->new( hours => $loanlength->{issuelength});
+            }
+            else { # days
+                $dur = DateTime::Duration->new( days => $loanlength->{issuelength});
+            }
+            if (ref $startdate ne 'DateTime' ) {
+                $startdate = dt_from_string($startdate);
+            }
+            my $calendar = Koha::Calendar->new( branchcode => $branch );
+            $datedue = $calendar->addDate( $startdate, $dur, $loanlength->{lengthunit} );
+            if ($loanlength->{lengthunit} eq 'days') {
+                $datedue->set_hour(23);
+                $datedue->set_minute(59);
+            }
+        }
+    }
 
 
-this function validates the loan length against the holidays calendar, and adjusts the due date as per the 'useDaysMode' syspref.
-C<$date_due>   = returndate calculate with no day check
-C<$itemnumber>  = itemnumber
-C<$branchcode>  = location of issue (affected by 'CircControl' syspref)
-C<$loanlength>  = loan length prior to adjustment
+    # if Hard Due Dates are used, retreive them and apply as necessary
+    my ( $hardduedate, $hardduedatecompare ) =
+      GetHardDueDate( $borrower->{'categorycode'}, $itemtype, $branch );
+    if ($hardduedate) {    # hardduedates are currently dates
+        $hardduedate->truncate( to => 'minute' );
+        $hardduedate->set_hour(23);
+        $hardduedate->set_minute(59);
+        my $cmp = DateTime->compare( $hardduedate, $datedue );
+
+# if the calculated due date is after the 'before' Hard Due Date (ceiling), override
+# if the calculated date is before the 'after' Hard Due Date (floor), override
+# if the hard due date is set to 'exactly', overrride
+        if ( $hardduedatecompare == 0 || $hardduedatecompare == $cmp ) {
+            $datedue = $hardduedate->clone;
+        }
 
 
-=cut
+        # in all other cases, keep the date due as it is
+    }
 
 
-sub CheckValidDatedue {
-my ($date_due,$itemnumber,$branchcode)=@_;
-my @datedue=split('-',$date_due->output('iso'));
-my $years=$datedue[0];
-my $month=$datedue[1];
-my $day=$datedue[2];
-# die "Item# $itemnumber ($branchcode) due: " . ${date_due}->output() . "\n(Y,M,D) = ($years,$month,$day)":
-my $dow;
-for (my $i=0;$i<2;$i++){
-    $dow=Day_of_Week($years,$month,$day);
-    ($dow=0) if ($dow>6);
-    my $result=CheckRepeatableHolidays($itemnumber,$dow,$branchcode);
-    my $countspecial=CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
-    my $countspecialrepeatable=CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
-        if (($result ne '0') or ($countspecial ne '0') or ($countspecialrepeatable ne '0') ){
-        $i=0;
-        (($years,$month,$day) = Add_Delta_Days($years,$month,$day, 1))if ($i ne '1');
+    # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
+    if ( C4::Context->preference('ReturnBeforeExpiry') ) {
+        my $expiry_dt = dt_from_string( $borrower->{dateexpiry}, 'iso' );
+        if ( DateTime->compare( $datedue, $expiry_dt ) == 1 ) {
+            $datedue = $expiry_dt->clone;
         }
     }
         }
     }
-    my $newdatedue=C4::Dates->new(sprintf("%04d-%02d-%02d",$years,$month,$day),'iso');
-return $newdatedue;
+
+    return $datedue;
 }
 
 
 }
 
 
@@ -2818,18 +3073,197 @@ sub CreateBranchTransferLimit {
 
 =head2 DeleteBranchTransferLimits
 
 
 =head2 DeleteBranchTransferLimits
 
-  DeleteBranchTransferLimits();
+DeleteBranchTransferLimits($frombranch);
+
+Deletes all the branch transfer limits for one branch
 
 =cut
 
 sub DeleteBranchTransferLimits {
 
 =cut
 
 sub DeleteBranchTransferLimits {
-   my $dbh = C4::Context->dbh;
-   my $sth = $dbh->prepare("TRUNCATE TABLE branch_transfer_limits");
-   $sth->execute();
+    my $branch = shift;
+    my $dbh    = C4::Context->dbh;
+    my $sth    = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
+    $sth->execute($branch);
+}
+
+sub ReturnLostItem{
+    my ( $borrowernumber, $itemnum ) = @_;
+
+    MarkIssueReturned( $borrowernumber, $itemnum );
+    my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber );
+    my @datearr = localtime(time);
+    my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
+    my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
+    ModItem({ paidfor =>  "Paid for by $bor $date" }, undef, $itemnum);
+}
+
+
+sub LostItem{
+    my ($itemnumber, $mark_returned, $charge_fee) = @_;
+
+    my $dbh = C4::Context->dbh();
+    my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title 
+                           FROM issues 
+                           JOIN items USING (itemnumber) 
+                           JOIN biblio USING (biblionumber)
+                           WHERE issues.itemnumber=?");
+    $sth->execute($itemnumber);
+    my $issues=$sth->fetchrow_hashref();
+    $sth->finish;
+
+    # if a borrower lost the item, add a replacement cost to the their record
+    if ( my $borrowernumber = $issues->{borrowernumber} ){
+        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 ";
+        MarkIssueReturned($borrowernumber,$itemnumber,undef,undef,$borrower->{'privacy'}) if $mark_returned;
+    }
+}
+
+sub GetOfflineOperations {
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE branchcode=? ORDER BY timestamp");
+    $sth->execute(C4::Context->userenv->{'branch'});
+    my $results = $sth->fetchall_arrayref({});
+    $sth->finish;
+    return $results;
+}
+
+sub GetOfflineOperation {
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE operationid=?");
+    $sth->execute( shift );
+    my $result = $sth->fetchrow_hashref;
+    $sth->finish;
+    return $result;
+}
+
+sub AddOfflineOperation {
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber) VALUES(?,?,?,?,?,?)");
+    $sth->execute( @_ );
+    return "Added.";
+}
+
+sub DeleteOfflineOperation {
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("DELETE FROM pending_offline_operations WHERE operationid=?");
+    $sth->execute( shift );
+    return "Deleted.";
+}
+
+sub ProcessOfflineOperation {
+    my $operation = shift;
+
+    my $report;
+    if ( $operation->{action} eq 'return' ) {
+        $report = ProcessOfflineReturn( $operation );
+    } elsif ( $operation->{action} eq 'issue' ) {
+        $report = ProcessOfflineIssue( $operation );
+    }
+
+    DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid};
+
+    return $report;
+}
+
+sub ProcessOfflineReturn {
+    my $operation = shift;
+
+    my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
+
+    if ( $itemnumber ) {
+        my $issue = GetOpenIssue( $itemnumber );
+        if ( $issue ) {
+            MarkIssueReturned(
+                $issue->{borrowernumber},
+                $itemnumber,
+                undef,
+                $operation->{timestamp},
+            );
+            ModItem(
+                { renewals => 0, onloan => undef },
+                $issue->{'biblionumber'},
+                $itemnumber
+            );
+            return "Success.";
+        } else {
+            return "Item not issued.";
+        }
+    } else {
+        return "Item not found.";
+    }
+}
+
+sub ProcessOfflineIssue {
+    my $operation = shift;
+
+    my $borrower = C4::Members::GetMemberDetails( undef, $operation->{cardnumber} ); # Get borrower from operation cardnumber
+
+    if ( $borrower->{borrowernumber} ) {
+        my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
+        unless ($itemnumber) {
+            return "Barcode not found.";
+        }
+        my $issue = GetOpenIssue( $itemnumber );
+
+        if ( $issue and ( $issue->{borrowernumber} ne $borrower->{borrowernumber} ) ) { # Item already issued to another borrower, mark it returned
+            MarkIssueReturned(
+                $issue->{borrowernumber},
+                $itemnumber,
+                undef,
+                $operation->{timestamp},
+            );
+        }
+        AddIssue(
+            $borrower,
+            $operation->{'barcode'},
+            undef,
+            1,
+            $operation->{timestamp},
+            undef,
+        );
+        return "Success.";
+    } else {
+        return "Borrower not found.";
+    }
+}
+
+
+
+=head2 TransferSlip
+
+  TransferSlip($user_branch, $itemnumber, $to_branch)
+
+  Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
+
+=cut
+
+sub TransferSlip {
+    my ($branch, $itemnumber, $to_branch) = @_;
+
+    my $item =  GetItem( $itemnumber )
+      or return;
+
+    my $pulldate = C4::Dates->new();
+
+    return C4::Letters::GetPreparedLetter (
+        module => 'circulation',
+        letter_code => 'TRANSFERSLIP',
+        branchcode => $branch,
+        tables => {
+            'branches'    => $to_branch,
+            'biblio'      => $item->{biblionumber},
+            'items'       => $item,
+        },
+    );
 }
 
 
 }
 
 
-  1;
+1;
 
 __END__
 
 
 __END__