Merge remote-tracking branch 'kc/master' into merged_5549
authorChris Cormack <chrisc@catalyst.net.nz>
Wed, 21 Mar 2012 20:36:55 +0000 (09:36 +1300)
committerChris Cormack <chrisc@catalyst.net.nz>
Wed, 21 Mar 2012 20:36:55 +0000 (09:36 +1300)
Fixed conflicts

Conflicts:
catalogue/moredetail.pl
installer/data/mysql/updatedatabase.pl
koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt

1  2 
C4/Circulation.pm
admin/smart-rules.pl
catalogue/moredetail.pl
circ/overdue.pl
installer/data/mysql/kohastructure.sql
installer/data/mysql/updatedatabase.pl
koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt
koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt
opac/opac-reserve.pl
opac/opac-user.pl

diff --combined C4/Circulation.pm
@@@ -21,7 -21,6 +21,7 @@@ package C4::Circulation
  
  use strict;
  #use warnings; FIXME - Bug 2505
 +use DateTime;
  use C4::Context;
  use C4::Stats;
  use C4::Reserves;
@@@ -29,18 -28,28 +29,18 @@@ use C4::Biblio
  use C4::Items;
  use C4::Members;
  use C4::Dates;
 -use C4::Calendar;
 +use C4::Dates qw(format_date);
  use C4::Accounts;
  use C4::ItemCirculationAlertPreference;
 -use C4::Dates qw(format_date);
  use C4::Message;
  use C4::Debug;
 -use Date::Calc qw(
 -  Today
 -  Today_and_Now
 -  Add_Delta_YM
 -  Add_Delta_DHMS
 -  Date_to_Days
 -  Day_of_Week
 -  Add_Delta_Days      
 -  check_date
 -  Delta_Days
 -);
 -use POSIX qw(strftime);
  use C4::Branch; # GetBranches
  use C4::Log; # logaction
  
  use Data::Dumper;
 +use Koha::DateUtils;
 +use Koha::Calendar;
 +use Carp;
  
  use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  
@@@ -433,7 -442,7 +433,7 @@@ sub TooMany 
      my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower);
      if (defined($branch_borrower_circ_rule->{maxissueqty})) {
          my @bind_params = ();
 -        my $branch_count_query = "SELECT COUNT(*) FROM issues 
 +        my $branch_count_query = "SELECT COUNT(*) FROM issues
                                    JOIN items USING (itemnumber)
                                    WHERE borrowernumber = ? ";
          push @bind_params, $borrower->{borrowernumber};
@@@ -682,28 -691,21 +682,28 @@@ sub CanBookBeIssued 
      #
      # DUE DATE is OK ? -- should already have checked.
      #
 +    if ($duedate && ref $duedate ne 'DateTime') {
 +        $duedate = dt_from_string($duedate);
 +    }
 +    my $now = DateTime->now( time_zone => C4::Context->tz() );
      unless ( $duedate ) {
 -        my $issuedate = strftime( "%Y-%m-%d", localtime );
 +        my $issuedate = $now->clone();
  
          my $branch = _GetCircControlBranch($item,$borrower);
          my $itype = ( C4::Context->preference('item-level_itypes') ) ? $item->{'itype'} : $biblioitem->{'itemtype'};
 -        $duedate = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower );
 +        $duedate = CalcDateDue( $issuedate, $itype, $branch, $borrower );
  
          # Offline circ calls AddIssue directly, doesn't run through here
          #  So issuingimpossible should be ok.
      }
      if ($duedate) {
 -        $needsconfirmation{INVALID_DATE} = $duedate->output('syspref')
 -          unless $duedate->output('iso') ge C4::Dates->today('iso');
 +        my $today = $now->clone();
 +        $today->truncate( to => 'minutes');
 +        if (DateTime->compare($duedate,$today) == -1 ) { # duedate cannot be before now
 +            $needsconfirmation{INVALID_DATE} = output_pref($duedate);
 +        }
      } else {
 -        $issuingimpossible{INVALID_DATE} = $duedate->output('syspref');
 +            $issuingimpossible{INVALID_DATE} = output_pref($duedate);
      }
  
      #
      if ( $borrower->{flags}->{'DBARRED'} ) {
          $issuingimpossible{DEBARRED} = 1;
      }
 -    if ( $borrower->{'dateexpiry'} eq '0000-00-00') {
 +    if ( !defined $borrower->{dateexpiry} || $borrower->{'dateexpiry'} eq '0000-00-00') {
          $issuingimpossible{EXPIRED} = 1;
      } else {
 -        my @expirydate=  split /-/,$borrower->{'dateexpiry'};
 -        if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 ||
 -            Date_to_Days(Today) > Date_to_Days( @expirydate )) {
 -            $issuingimpossible{EXPIRED} = 1;                                   
 +        my ($y, $m, $d) =  split /-/,$borrower->{'dateexpiry'};
 +        if ($y && $m && $d) { # are we really writing oinvalid dates to borrs
 +            my $expiry_dt = DateTime->new(
 +                year => $y,
 +                month => $m,
 +                day   => $d,
 +                time_zone => C4::Context->tz,
 +            );
 +            $expiry_dt->truncate( to => 'days');
 +            my $today = $now->clone()->truncate(to => 'days');
 +            if (DateTime->compare($today, $expiry_dt) == 1) {
 +                $issuingimpossible{EXPIRED} = 1;
 +            }
 +        } else {
 +            carp("Invalid expity date in borr");
 +            $issuingimpossible{EXPIRED} = 1;
          }
      }
      #
  
      # 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 ($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){
@@@ -968,20 -958,13 +968,20 @@@ sub AddIssue 
      my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate, $sipmode) = @_;
      my $dbh = C4::Context->dbh;
        my $barcodecheck=CheckValidBarcode($barcode);
 +    if ($datedue && ref $datedue ne 'DateTime') {
 +        $datedue = dt_from_string($datedue);
 +    }
      # $issuedate defaults to today.
      if ( ! defined $issuedate ) {
 -        $issuedate = strftime( "%Y-%m-%d", localtime );
 -        # TODO: for hourly circ, this will need to be a C4::Dates object
 -        # and all calls to AddIssue including issuedate will need to pass a Dates object.
 +        $issuedate = DateTime->now(time_zone => C4::Context->tz());
      }
 -      if ($borrower and $barcode and $barcodecheck ne '0'){
 +    else {
 +        if ( ref $issuedate ne 'DateTime') {
 +            $issuedate = dt_from_string($issuedate);
 +
 +        }
 +    }
 +      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);
                # 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 {
                        }
  
              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) {
          # Record in the database the fact that the book was issued.
          my $sth =
            $dbh->prepare(
 -                "INSERT INTO issues 
 +                "INSERT INTO issues
                      (borrowernumber, itemnumber,issuedate, date_due, branchcode)
                  VALUES (?,?,?,?,?)"
            );
          unless ($datedue) {
              my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
 -            $datedue = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower );
 +            $datedue = CalcDateDue( $issuedate, $itype, $branch, $borrower );
  
          }
 +        $datedue->truncate( to => 'minutes');
          $sth->execute(
              $borrower->{'borrowernumber'},      # borrowernumber
              $item->{'itemnumber'},              # itemnumber
 -            $issuedate,                         # issuedate
 -            $datedue->output('iso'),            # date_due
 +            $issuedate->strftime('%Y-%m-%d %H:%M:00'), # issuedate
 +            $datedue->strftime('%Y-%m-%d %H:%M:00'),   # date_due
              C4::Context->userenv->{'branch'}    # branchcode
          );
 -        $sth->finish;
          if ( C4::Context->preference('ReturnToShelvingCart') ) { ## ReturnToShelvingCart is on, anything issued should be taken off the cart.
            CartToShelf( $item->{'itemnumber'} );
          }
          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'} );
  
@@@ -1127,57 -1111,53 +1127,57 @@@ sub GetLoanLength 
      my $dbh = C4::Context->dbh;
      my $sth =
        $dbh->prepare(
 -"select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null"
 +'select issuelength, lengthunit from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null'
        );
  # warn "in get loan lenght $borrowertype $itemtype $branchcode ";
  # try to find issuelength & return the 1st available.
  # check with borrowertype, itemtype and branchcode, then without one of those parameters
      $sth->execute( $borrowertype, $itemtype, $branchcode );
      my $loanlength = $sth->fetchrow_hashref;
 -    return $loanlength->{issuelength}
 -      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
 +    return $loanlength
 +      if defined($loanlength) && $loanlength->{issuelength};
  
 -    $sth->execute( $borrowertype, "*", $branchcode );
 +    $sth->execute( $borrowertype, '*', $branchcode );
      $loanlength = $sth->fetchrow_hashref;
 -    return $loanlength->{issuelength}
 -      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
 +    return $loanlength
 +      if defined($loanlength) && $loanlength->{issuelength};
  
 -    $sth->execute( "*", $itemtype, $branchcode );
 +    $sth->execute( '*', $itemtype, $branchcode );
      $loanlength = $sth->fetchrow_hashref;
 -    return $loanlength->{issuelength}
 -      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
 +    return $loanlength
 +      if defined($loanlength) && $loanlength->{issuelength};
  
 -    $sth->execute( "*", "*", $branchcode );
 +    $sth->execute( '*', '*', $branchcode );
      $loanlength = $sth->fetchrow_hashref;
 -    return $loanlength->{issuelength}
 -      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
 +    return $loanlength
 +      if defined($loanlength) && $loanlength->{issuelength};
  
 -    $sth->execute( $borrowertype, $itemtype, "*" );
 +    $sth->execute( $borrowertype, $itemtype, '*' );
      $loanlength = $sth->fetchrow_hashref;
 -    return $loanlength->{issuelength}
 -      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
 +    return $loanlength
 +      if defined($loanlength) && $loanlength->{issuelength};
  
 -    $sth->execute( $borrowertype, "*", "*" );
 +    $sth->execute( $borrowertype, '*', '*' );
      $loanlength = $sth->fetchrow_hashref;
 -    return $loanlength->{issuelength}
 -      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
 +    return $loanlength
 +      if defined($loanlength) && $loanlength->{issuelength};
  
 -    $sth->execute( "*", $itemtype, "*" );
 +    $sth->execute( '*', $itemtype, '*' );
      $loanlength = $sth->fetchrow_hashref;
 -    return $loanlength->{issuelength}
 -      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
 +    return $loanlength
 +      if defined($loanlength) && $loanlength->{issuelength};
  
 -    $sth->execute( "*", "*", "*" );
 +    $sth->execute( '*', '*', '*' );
      $loanlength = $sth->fetchrow_hashref;
 -    return $loanlength->{issuelength}
 -      if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
 +    return $loanlength
 +      if defined($loanlength) && $loanlength->{issuelength};
  
      # if no rule is set => 21 days (hardcoded)
 -    return 21;
 +    return {
 +        issuelength => 21,
 +        lengthunit => 'days',
 +    };
 +
  }
  
  
@@@ -1198,43 -1178,43 +1198,43 @@@ sub GetHardDueDate 
        );
      $sth->execute( $borrowertype, $itemtype, $branchcode );
      my $results = $sth->fetchrow_hashref;
 -    return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 -      if defined($results) && $results->{hardduedate} ne 'NULL';
 +    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 +      if defined($results) && $results->{hardduedate};
  
      $sth->execute( $borrowertype, "*", $branchcode );
      $results = $sth->fetchrow_hashref;
 -    return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 -      if defined($results) && $results->{hardduedate} ne 'NULL';
 +    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 +      if defined($results) && $results->{hardduedate};
  
      $sth->execute( "*", $itemtype, $branchcode );
      $results = $sth->fetchrow_hashref;
 -    return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 -      if defined($results) && $results->{hardduedate} ne 'NULL';
 +    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 +      if defined($results) && $results->{hardduedate};
  
      $sth->execute( "*", "*", $branchcode );
      $results = $sth->fetchrow_hashref;
 -    return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 -      if defined($results) && $results->{hardduedate} ne 'NULL';
 +    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 +      if defined($results) && $results->{hardduedate};
  
      $sth->execute( $borrowertype, $itemtype, "*" );
      $results = $sth->fetchrow_hashref;
 -    return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 -      if defined($results) && $results->{hardduedate} ne 'NULL';
 +    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 +      if defined($results) && $results->{hardduedate};
  
      $sth->execute( $borrowertype, "*", "*" );
      $results = $sth->fetchrow_hashref;
 -    return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 -      if defined($results) && $results->{hardduedate} ne 'NULL';
 +    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 +      if defined($results) && $results->{hardduedate};
  
      $sth->execute( "*", $itemtype, "*" );
      $results = $sth->fetchrow_hashref;
 -    return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 -      if defined($results) && $results->{hardduedate} ne 'NULL';
 +    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 +      if defined($results) && $results->{hardduedate};
  
      $sth->execute( "*", "*", "*" );
      $results = $sth->fetchrow_hashref;
 -    return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 -      if defined($results) && $results->{hardduedate} ne 'NULL';
 +    return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
 +      if defined($results) && $results->{hardduedate};
  
      # if no rule is set => return undefined
      return (undef, undef);
@@@ -1385,13 -1365,17 +1385,17 @@@ sub GetBranchBorrowerCircRule 
  Retrieves circulation rule attributes that apply to the given
  branch and item type, regardless of patron category.
  
- The return value is a hashref containing the following key:
+ The return value is a hashref containing the following keys:
  
  holdallowed => Hold policy for this branch and itemtype. Possible values:
    0: No holds allowed.
    1: Holds allowed only by patrons that have the same homebranch as the item.
    2: Holds allowed from any patron.
  
+ returnbranch => branch to which to return item.  Possible values:
+   noreturn: do not return, let item remain where checked in (floating collections)
+   homebranch: return to item's home branch
  This searches branchitemrules in the following order:
  
    * Same branchcode and itemtype
    * branchcode '*', same itemtype
    * branchcode and itemtype '*'
  
- Neither C<$branchcode> nor C<$categorycode> should be '*'.
+ Neither C<$branchcode> nor C<$itemtype> should be '*'.
  
  =cut
  
@@@ -1409,33 -1393,36 +1413,36 @@@ sub GetBranchItemRule 
      my $result = {};
  
      my @attempts = (
-         ['SELECT holdallowed
+         ['SELECT holdallowed, returnbranch
              FROM branch_item_rules
              WHERE branchcode = ?
                AND itemtype = ?', $branchcode, $itemtype],
-         ['SELECT holdallowed
+         ['SELECT holdallowed, returnbranch
              FROM default_branch_circ_rules
              WHERE branchcode = ?', $branchcode],
-         ['SELECT holdallowed
+         ['SELECT holdallowed, returnbranch
              FROM default_branch_item_rules
              WHERE itemtype = ?', $itemtype],
-         ['SELECT holdallowed
+         ['SELECT holdallowed, returnbranch
              FROM default_circ_rules'],
      );
  
      foreach my $attempt (@attempts) {
          my ($query, @bind_params) = @{$attempt};
+         my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params );
  
          # Since branch/category and branch/itemtype use the same per-branch
          # defaults tables, we have to check that the key we want is set, not
          # just that a row was returned
-         return $result if ( defined( $result->{'holdallowed'} = $dbh->selectrow_array( $query, {}, @bind_params ) ) );
+         $result->{'holdallowed'}  = $search_result->{'holdallowed'}  unless ( defined $result->{'holdallowed'} );
+         $result->{'returnbranch'} = $search_result->{'returnbranch'} unless ( defined $result->{'returnbranch'} );
      }
      
      # built-in default circulation rule
-     return {
-         holdallowed => 2,
-     };
+     $result->{'holdallowed'} = 2 unless ( defined $result->{'holdallowed'} );
+     $result->{'returnbranch'} = 'homebranch' unless ( defined $result->{'returnbranch'} );
+     return $result;
  }
  
  =head2 AddReturn
@@@ -1552,9 -1539,10 +1559,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 $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
  
              # define circControlBranch only if dropbox mode is set
              # don't allow dropbox mode to create an invalid entry in issues (issuedate > today)
              # FIXME: check issuedate > returndate, factoring in holidays
 -            $circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
 +            #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
 +            $circControlBranch = _GetCircControlBranch($item,$borrower);
          }
  
          if ($borrowernumber) {
@@@ -1731,27 -1718,27 +1739,27 @@@ routine in C<C4::Accounts>
  sub MarkIssueReturned {
      my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy ) = @_;
      my $dbh   = C4::Context->dbh;
 -    my $query = "UPDATE issues SET returndate=";
 +    my $query = 'UPDATE issues SET returndate=';
      my @bind;
      if ($dropbox_branch) {
 -        my $calendar = C4::Calendar->new( branchcode => $dropbox_branch );
 -        my $dropboxdate = $calendar->addDate( C4::Dates->new(), -1 );
 -        $query .= " ? ";
 -        push @bind, $dropboxdate->output('iso');
 +        my $calendar = Koha::Calendar->new( branchcode => $dropbox_branch );
 +        my $dropboxdate = $calendar->addDate( DateTime->now( time_zone => C4::Context->tz), -1 );
 +        $query .= ' ? ';
 +        push @bind, $dropboxdate->strftime('%Y-%m-%d %H:%M');
      } elsif ($returndate) {
 -        $query .= " ? ";
 +        $query .= ' ? ';
          push @bind, $returndate;
      } else {
 -        $query .= " now() ";
 +        $query .= ' now() ';
      }
 -    $query .= " WHERE  borrowernumber = ?  AND itemnumber = ?";
 +    $query .= ' WHERE  borrowernumber = ?  AND itemnumber = ?';
      push @bind, $borrowernumber, $itemnumber;
      # FIXME transaction
      my $sth_upd  = $dbh->prepare($query);
      $sth_upd->execute(@bind);
 -    my $sth_copy = $dbh->prepare("INSERT INTO old_issues SELECT * FROM issues 
 +    my $sth_copy = $dbh->prepare('INSERT INTO old_issues SELECT * FROM issues
                                    WHERE borrowernumber = ?
 -                                  AND itemnumber = ?");
 +                                  AND itemnumber = ?');
      $sth_copy->execute($borrowernumber, $itemnumber);
      # anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber
      if ( $privacy == 2) {
@@@ -1785,16 -1772,18 +1793,16 @@@ Internal function, called only by AddRe
  
  sub _FixFineDaysOnReturn {
      my ( $borrower, $item, $datedue ) = @_;
 -
 -    if ($datedue) {
 -        $datedue = C4::Dates->new( $datedue, "iso" );
 -    } else {
 -        return;
 -    }
 +    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 = 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 $issuingrule = GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
  
      # 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 $grace = DateTime::Duration->new( days => $issuingrule->{firstremind} );
 +
 +    if ( ( $deltadays - $grace )->is_positive ) { # you can't compare DateTime::Durations with logical operators
 +        my $new_debar_dt = $dt_today->clone()->add_duration( $deltadays * $finedays );
 +        my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
 +        # check to see if the current debar date is a valid date
 +        if ( $borrower->{debarred} && $borrower_debar_dt ) {
 +        # if so, is it before the new date?  update only if true
 +            if ( DateTime->compare( $borrower_debar_dt, $new_debar_dt ) == -1 ) {
 +                C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() );
 +                return $new_debar_dt->ymd();
              }
 +        # if the borrower's debar date is not set or valid, debar them
          } else {
 -            C4::Members::DebarMember( $borrower->{borrowernumber}, $isonewdate );
 -            return $isonewdate;
 +            C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() );
 +            return $new_debar_dt->ymd();
          }
      }
  }
@@@ -2035,19 -2024,14 +2043,19 @@@ sub GetItemIssue 
      return unless $itemnumber;
      my $sth = C4::Context->dbh->prepare(
          "SELECT *
 -        FROM issues 
 +        FROM issues
          LEFT JOIN items ON issues.itemnumber=items.itemnumber
          WHERE issues.itemnumber=?");
      $sth->execute($itemnumber);
      my $data = $sth->fetchrow_hashref;
      return unless $data;
 -    $data->{'overdue'} = ($data->{'date_due'} lt C4::Dates->today('iso')) ? 1 : 0;
 -    return ($data);
 +    $data->{issuedate} = dt_from_string($data->{issuedate}, 'sql');
 +    $data->{issuedate}->truncate(to => 'minutes');
 +    $data->{date_due} = dt_from_string($data->{date_due}, 'sql');
 +    $data->{date_due}->truncate(to => 'minutes');
 +    my $dt = DateTime->now( time_zone => C4::Context->tz)->truncate( to => 'minutes');
 +    $data->{'overdue'} = DateTime->compare($data->{'date_due'}, $dt ) == -1 ? 1 : 0;
 +    return $data;
  }
  
  =head2 GetOpenIssue
@@@ -2089,15 -2073,14 +2097,15 @@@ Returns reference to an array of hashe
  sub GetItemIssues {
      my ( $itemnumber, $history ) = @_;
      
 -    my $today = C4::Dates->today('iso');  # get today date
 -    my $sql = "SELECT * FROM issues 
 +    my $today = DateTime->now( time_zome => C4::Context->tz);  # get today date
 +    $today->truncate( to => 'minutes' );
 +    my $sql = "SELECT * FROM issues
                JOIN borrowers USING (borrowernumber)
                JOIN items     USING (itemnumber)
                WHERE issues.itemnumber = ? ";
      if ($history) {
          $sql .= "UNION ALL
 -                 SELECT * FROM old_issues 
 +                 SELECT * FROM old_issues
                   LEFT JOIN borrowers USING (borrowernumber)
                   JOIN items USING (itemnumber)
                   WHERE old_issues.itemnumber = ? ";
      }
      my $results = $sth->fetchall_arrayref({});
      foreach (@$results) {
 -        $_->{'overdue'} = ($_->{'date_due'} lt $today) ? 1 : 0;
 +        my $date_due = dt_from_string($_->{date_due},'sql');
 +        $date_due->truncate( to => 'minutes' );
 +
 +        $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0;
      }
      return $results;
  }
@@@ -2244,7 -2224,7 +2252,7 @@@ sub CanBookBeRenewed 
                     SELECT 
                      borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
                     FROM  issuingrules, 
 -                   issues 
 +                   issues
                     LEFT JOIN items USING (itemnumber) 
                     LEFT JOIN borrowers USING (borrowernumber) 
                     LEFT JOIN biblioitems USING (biblioitemnumber)
@@@ -2315,7 -2295,7 +2323,7 @@@ sub AddRenewal 
      my $itemnumber      = shift or return undef;
      my $branch          = shift;
      my $datedue         = shift;
 -    my $lastreneweddate = shift || C4::Dates->new()->output('iso');
 +    my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd();
      my $item   = GetItem($itemnumber) or return undef;
      my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
  
      $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
          my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
  
          $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
 -                                        C4::Dates->new($issuedata->{date_due}, 'iso') :
 -                                        C4::Dates->new();
 +                                        $issuedata->{date_due} :
 +                                        DateTime->now( time_zone => C4::Context->tz());
          $datedue =  CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower);
      }
  
                              WHERE borrowernumber=? 
                              AND itemnumber=?"
      );
 -    $sth->execute( $datedue->output('iso'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
 +
 +    $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
      $sth->finish;
  
      # Update the renewal count on the item, and tell zebra to reindex
      $renews = $biblio->{'renewals'} + 1;
 -    ModItem({ renewals => $renews, onloan => $datedue->output('iso') }, $biblio->{'biblionumber'}, $itemnumber);
 +    ModItem({ renewals => $renews, onloan => $datedue->strftime('%Y-%m-%d %H:%M')}, $biblio->{'biblionumber'}, $itemnumber);
  
      # Charge a new rental fee, if applicable?
      my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
          $sth->execute( $borrowernumber, $accountno, $charge, $manager_id,
              "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
              'Rent', $charge, $itemnumber );
 -        $sth->finish;
      }
      # Log the renewal
      UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
@@@ -2787,89 -2767,88 +2795,89 @@@ C<$borrower> = Borrower objec
  
  =cut
  
 -sub CalcDateDue { 
 -      my ($startdate,$itemtype,$branch,$borrower) = @_;
 -      my $datedue;
 -        my $loanlength = GetLoanLength($borrower->{'categorycode'},$itemtype, $branch);
 -
 -      # if globalDueDate ON the datedue is set to that date
 -      if ( C4::Context->preference('globalDueDate')
 -             && ( C4::Context->preference('globalDueDate') =~ C4::Dates->regexp('syspref') ) ) {
 -            $datedue = C4::Dates->new( C4::Context->preference('globalDueDate') );
 -      } else {
 -      # otherwise, calculate the datedue as normal
 -              if(C4::Context->preference('useDaysMode') eq 'Days') {  # ignoring calendar
 -                      my $timedue = time + ($loanlength) * 86400;
 -              #FIXME - assumes now even though we take a startdate 
 -                      my @datearr  = localtime($timedue);
 -                      $datedue = C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
 -              } else {
 -                      my $calendar = C4::Calendar->new(  branchcode => $branch );
 -                      $datedue = $calendar->addDate($startdate, $loanlength);
 -              }
 -      }
 +sub CalcDateDue {
 +    my ( $startdate, $itemtype, $branch, $borrower ) = @_;
  
 -      # if Hard Due Dates are used, retreive them and apply as necessary
 -        my ($hardduedate, $hardduedatecompare) = GetHardDueDate($borrower->{'categorycode'},$itemtype, $branch);
 -      if ( $hardduedate && $hardduedate->output('iso') && $hardduedate->output('iso') ne '0000-00-00') {
 -            # if the calculated due date is after the 'before' Hard Due Date (ceiling), override
 -            if ( $datedue->output( 'iso' ) gt $hardduedate->output( 'iso' ) && $hardduedatecompare == -1) {
 -                $datedue = $hardduedate;
 -            # if the calculated date is before the 'after' Hard Due Date (floor), override
 -            } elsif ( $datedue->output( 'iso' ) lt $hardduedate->output( 'iso' ) && $hardduedatecompare == 1) {
 -                $datedue = $hardduedate;               
 -            # if the hard due date is set to 'exactly', overrride
 -            } elsif ( $hardduedatecompare == 0) {
 -                $datedue = $hardduedate;
 -            }
 -            # in all other cases, keep the date due as it is
 -      }
 +    # 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' );
 -      }
 -
 -      return $datedue;
 -}
 +    my $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;
  }
  
  
diff --combined admin/smart-rules.pl
@@@ -100,9 -100,9 +100,9 @@@ elsif ($op eq 'delete-branch-item') 
  }
  # save the values entered
  elsif ($op eq 'add') {
 -    my $sth_search = $dbh->prepare("SELECT COUNT(*) AS total FROM issuingrules WHERE branchcode=? AND categorycode=? AND itemtype=?");
 -    my $sth_insert = $dbh->prepare("INSERT INTO issuingrules (branchcode, categorycode, itemtype, maxissueqty, renewalsallowed, reservesallowed, issuelength, hardduedate, hardduedatecompare, fine, finedays, firstremind, chargeperiod,rentaldiscount) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
 -    my $sth_update=$dbh->prepare("UPDATE issuingrules SET fine=?, finedays=?, firstremind=?, chargeperiod=?, maxissueqty=?, renewalsallowed=?, reservesallowed=?, issuelength=?, hardduedate=?, hardduedatecompare=?, rentaldiscount=?  WHERE branchcode=? AND categorycode=? AND itemtype=?");
 +    my $sth_search = $dbh->prepare('SELECT COUNT(*) AS total FROM issuingrules WHERE branchcode=? AND categorycode=? AND itemtype=?');
 +    my $sth_insert = $dbh->prepare('INSERT INTO issuingrules (branchcode, categorycode, itemtype, maxissueqty, renewalsallowed, reservesallowed, issuelength, lengthunit, hardduedate, hardduedatecompare, fine, finedays, firstremind, chargeperiod,rentaldiscount) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
 +    my $sth_update=$dbh->prepare("UPDATE issuingrules SET fine=?, finedays=?, firstremind=?, chargeperiod=?, maxissueqty=?, renewalsallowed=?, reservesallowed=?, issuelength=?, lengthunit = ?, hardduedate=?, hardduedatecompare=?, rentaldiscount=?  WHERE branchcode=? AND categorycode=? AND itemtype=?");
      
      my $br = $branch; # branch
      my $bor  = $input->param('categorycode'); # borrower category
      $maxissueqty =~ s/\s//g;
      $maxissueqty = undef if $maxissueqty !~ /^\d+/;
      my $issuelength  = $input->param('issuelength');
 +    my $lengthunit  = $input->param('lengthunit');
      my $hardduedate = $input->param('hardduedate');
      $hardduedate = format_date_in_iso($hardduedate);
      my $hardduedatecompare = $input->param('hardduedatecompare');
      $sth_search->execute($br,$bor,$cat);
      my $res = $sth_search->fetchrow_hashref();
      if ($res->{total}) {
 -        $sth_update->execute($fine, $finedays,$firstremind, $chargeperiod, $maxissueqty, $renewalsallowed,$reservesallowed, $issuelength,$hardduedate,$hardduedatecompare,$rentaldiscount, $br,$bor,$cat);
 +        $sth_update->execute($fine, $finedays,$firstremind, $chargeperiod, $maxissueqty, $renewalsallowed,$reservesallowed, $issuelength,$lengthunit, $hardduedate,$hardduedatecompare,$rentaldiscount, $br,$bor,$cat);
      } else {
 -        $sth_insert->execute($br,$bor,$cat,$maxissueqty,$renewalsallowed,$reservesallowed,$issuelength,$hardduedate,$hardduedatecompare,$fine,$finedays,$firstremind,$chargeperiod,$rentaldiscount);
 +        $sth_insert->execute($br,$bor,$cat,$maxissueqty,$renewalsallowed,$reservesallowed,$issuelength,$lengthunit,$hardduedate,$hardduedatecompare,$fine,$finedays,$firstremind,$chargeperiod,$rentaldiscount);
      }
  } 
  elsif ($op eq "set-branch-defaults") {
      my $categorycode  = $input->param('categorycode');
      my $maxissueqty   = $input->param('maxissueqty');
      my $holdallowed   = $input->param('holdallowed');
+     my $returnbranch  = $input->param('returnbranch');
      $maxissueqty =~ s/\s//g;
      $maxissueqty = undef if $maxissueqty !~ /^\d+/;
      $holdallowed =~ s/\s//g;
          my $sth_search = $dbh->prepare("SELECT count(*) AS total
                                          FROM default_circ_rules");
          my $sth_insert = $dbh->prepare("INSERT INTO default_circ_rules
-                                         (maxissueqty, holdallowed)
-                                         VALUES (?, ?)");
+                                         (maxissueqty, holdallowed, returnbranch)
+                                         VALUES (?, ?, ?)");
          my $sth_update = $dbh->prepare("UPDATE default_circ_rules
-                                         SET maxissueqty = ?, holdallowed = ?");
+                                         SET maxissueqty = ?, holdallowed = ?, returnbranch = ?");
  
          $sth_search->execute();
          my $res = $sth_search->fetchrow_hashref();
          if ($res->{total}) {
-             $sth_update->execute($maxissueqty, $holdallowed);
+             $sth_update->execute($maxissueqty, $holdallowed, $returnbranch);
          } else {
-             $sth_insert->execute($maxissueqty, $holdallowed);
+             $sth_insert->execute($maxissueqty, $holdallowed, $returnbranch);
          }
      } else {
          my $sth_search = $dbh->prepare("SELECT count(*) AS total
                                          FROM default_branch_circ_rules
                                          WHERE branchcode = ?");
          my $sth_insert = $dbh->prepare("INSERT INTO default_branch_circ_rules
-                                         (branchcode, maxissueqty, holdallowed)
-                                         VALUES (?, ?, ?)");
+                                         (branchcode, maxissueqty, holdallowed, returnbranch)
+                                         VALUES (?, ?, ?, ?)");
          my $sth_update = $dbh->prepare("UPDATE default_branch_circ_rules
-                                         SET maxissueqty = ?, holdallowed = ?
+                                         SET maxissueqty = ?, holdallowed = ?, returnbranch = ?
                                          WHERE branchcode = ?");
          $sth_search->execute($branch);
          my $res = $sth_search->fetchrow_hashref();
          if ($res->{total}) {
-             $sth_update->execute($maxissueqty, $holdallowed, $branch);
+             $sth_update->execute($maxissueqty, $holdallowed, $returnbranch, $branch);
          } else {
-             $sth_insert->execute($branch, $maxissueqty, $holdallowed);
+             $sth_insert->execute($branch, $maxissueqty, $holdallowed, $returnbranch);
          }
      }
  }
@@@ -259,6 -259,7 +260,7 @@@ elsif ($op eq "add-branch-cat") 
  elsif ($op eq "add-branch-item") {
      my $itemtype  = $input->param('itemtype');
      my $holdallowed   = $input->param('holdallowed');
+     my $returnbranch  = $input->param('returnbranch');
      $holdallowed =~ s/\s//g;
      $holdallowed = undef if $holdallowed !~ /^\d+/;
  
              my $sth_search = $dbh->prepare("SELECT count(*) AS total
                                              FROM default_circ_rules");
              my $sth_insert = $dbh->prepare("INSERT INTO default_circ_rules
-                                             (holdallowed)
-                                             VALUES (?)");
+                                             (holdallowed, returnbranch)
+                                             VALUES (?, ?)");
              my $sth_update = $dbh->prepare("UPDATE default_circ_rules
-                                             SET holdallowed = ?");
+                                             SET holdallowed = ?, returnbranch = ?");
  
              $sth_search->execute();
              my $res = $sth_search->fetchrow_hashref();
              if ($res->{total}) {
-                 $sth_update->execute($holdallowed);
+                 $sth_update->execute($holdallowed, $returnbranch);
              } else {
-                 $sth_insert->execute($holdallowed);
+                 $sth_insert->execute($holdallowed, $returnbranch);
              }
          } else {
              my $sth_search = $dbh->prepare("SELECT count(*) AS total
                                              FROM default_branch_item_rules
                                              WHERE itemtype = ?");
              my $sth_insert = $dbh->prepare("INSERT INTO default_branch_item_rules
-                                             (itemtype, holdallowed)
-                                             VALUES (?, ?)");
+                                             (itemtype, holdallowed, returnbranch)
+                                             VALUES (?, ?, ?)");
              my $sth_update = $dbh->prepare("UPDATE default_branch_item_rules
-                                             SET holdallowed = ?
+                                             SET holdallowed = ?, returnbranch = ?
                                              WHERE itemtype = ?");
              $sth_search->execute($itemtype);
              my $res = $sth_search->fetchrow_hashref();
              if ($res->{total}) {
-                 $sth_update->execute($holdallowed, $itemtype);
+                 $sth_update->execute($holdallowed, $returnbranch, $itemtype);
              } else {
-                 $sth_insert->execute($itemtype, $holdallowed);
+                 $sth_insert->execute($itemtype, $holdallowed, $returnbranch);
              }
          }
      } elsif ($itemtype eq "*") {
                                          FROM default_branch_circ_rules
                                          WHERE branchcode = ?");
          my $sth_insert = $dbh->prepare("INSERT INTO default_branch_circ_rules
-                                         (branchcode, holdallowed)
-                                         VALUES (?, ?)");
+                                         (branchcode, holdallowed, returnbranch)
+                                         VALUES (?, ?, ?)");
          my $sth_update = $dbh->prepare("UPDATE default_branch_circ_rules
-                                         SET holdallowed = ?
+                                         SET holdallowed = ?, returnbranch = ?
                                          WHERE branchcode = ?");
          $sth_search->execute($branch);
          my $res = $sth_search->fetchrow_hashref();
          if ($res->{total}) {
-             $sth_update->execute($holdallowed, $branch);
+             $sth_update->execute($holdallowed, $returnbranch, $branch);
          } else {
-             $sth_insert->execute($branch, $holdallowed);
+             $sth_insert->execute($branch, $holdallowed, $returnbranch);
          }
      } else {
          my $sth_search = $dbh->prepare("SELECT count(*) AS total
                                          WHERE branchcode = ?
                                          AND   itemtype = ?");
          my $sth_insert = $dbh->prepare("INSERT INTO branch_item_rules
-                                         (branchcode, itemtype, holdallowed)
-                                         VALUES (?, ?, ?)");
+                                         (branchcode, itemtype, holdallowed, returnbranch)
+                                         VALUES (?, ?, ?, ?)");
          my $sth_update = $dbh->prepare("UPDATE branch_item_rules
-                                         SET holdallowed = ?
+                                         SET holdallowed = ?, returnbranch = ?
                                          WHERE branchcode = ?
                                          AND itemtype = ?");
  
          $sth_search->execute($branch, $itemtype);
          my $res = $sth_search->fetchrow_hashref();
          if ($res->{total}) {
-             $sth_update->execute($holdallowed, $branch, $itemtype);
+             $sth_update->execute($holdallowed, $returnbranch, $branch, $itemtype);
          } else {
-             $sth_insert->execute($branch, $itemtype, $holdallowed);
+             $sth_insert->execute($branch, $itemtype, $holdallowed, $returnbranch);
          }
      }
  }
@@@ -485,6 -486,7 +487,7 @@@ if ($defaults) 
      $template->param(default_holdallowed_same => 1) if($defaults->{holdallowed} == 1);
      $template->param(default_holdallowed_any => 1) if($defaults->{holdallowed} == 2);
      $template->param(default_maxissueqty => $defaults->{maxissueqty});
+     $template->param(default_returnbranch => $defaults->{returnbranch});
  }
  
  $template->param(default_rules => ($defaults ? 1 : 0));
diff --combined catalogue/moredetail.pl
@@@ -27,15 -27,14 +27,15 @@@ use C4::Biblio
  use C4::Items;
  use C4::Branch;
  use C4::Acquisition;
- use C4::Output;
+ use C4::Bookseller qw(GetBookSellerFromId);
+ use C4::Output;             # contains gettemplate
  use C4::Auth;
  use C4::Serials;
- use C4::Dates qw/format_date/;
  use C4::Circulation;  # to use itemissues
  use C4::Members; # to use GetMember
  use C4::Search;               # enabled_staff_search_views
  use C4::Members qw/GetHideLostItemsPreference/;
 +use Koha::DateUtils;
  
  my $query=new CGI;
  
@@@ -120,7 -119,7 +120,7 @@@ my $ccodes= GetKohaAuthorisedValues('it
  my $itemtypes = GetItemTypes;
  
  $data->{'itemtypename'} = $itemtypes->{$data->{'itemtype'}}->{'description'};
+ $data->{'rentalcharge'} = sprintf( "%.2f", $data->{'rentalcharge'} );
  foreach ( keys %{$data} ) {
      $template->param( "$_" => defined $data->{$_} ? $data->{$_} : '' );
  }
@@@ -132,10 -131,8 +132,8 @@@ foreach my $item (@items)
      $item->{'collection'}              = $ccodes->{ $item->{ccode} } if ($ccodes);
      $item->{'itype'}                   = $itemtypes->{ $item->{'itype'} }->{'description'};
      $item->{'replacementprice'}        = sprintf( "%.2f", $item->{'replacementprice'} );
-     $item->{$_}                        = format_date( $item->{$_} ) foreach qw/datelastborrowed dateaccessioned datelastseen lastreneweddate/;
      $item->{'copyvol'}                 = $item->{'copynumber'};
  
      # item has a host number if its biblio number does not match the current bib
      if ($item->{biblionumber} ne $biblionumber){
          $item->{hostbiblionumber} = $item->{biblionumber};
      $item->{'ordernumber'}             = $order->{'ordernumber'};
      $item->{'basketno'}                = $order->{'basketno'};
      $item->{'booksellerinvoicenumber'} = $order->{'booksellerinvoicenumber'};
+     $item->{'orderdate'}               = $order->{'entrydate'};
+     if ($item->{'basketno'}){
+           my $basket = GetBasket($item->{'basketno'});
+           my $bookseller = GetBookSellerFromId($basket->{'booksellerid'});
+           $item->{'vendor'} = $bookseller->{'name'};
+     }
      $item->{'datereceived'}            = $order->{'datereceived'};
  
      if ($item->{notforloantext} or $item->{itemlost} or $item->{damaged} or $item->{wthdrawn}) {
      }
      $item->{'homebranchname'} = GetBranchName($item->{'homebranch'});
      $item->{'holdingbranchname'} = GetBranchName($item->{'holdingbranch'});
-     if ($item->{datedue}) {
-         $item->{datedue} = format_sqldatetime($item->{datedue});
+     if ($item->{'datedue'}) {
          $item->{'issue'}= 1;
      } else {
          $item->{'issue'}= 0;
diff --combined circ/overdue.pl
@@@ -28,9 -28,8 +28,9 @@@ use C4::Auth
  use C4::Branch;
  use C4::Debug;
  use C4::Dates qw/format_date format_date_in_iso/;
 -use Date::Calc qw/Today/;
  use Text::CSV_XS;
 +use Koha::DateUtils;
 +use DateTime;
  
  my $input = new CGI;
  my $order           = $input->param('order') || '';
@@@ -43,14 -42,6 +43,14 @@@ my $branchfilter    = $input->param('br
  my $op              = $input->param('op') || '';
  my $dateduefrom = format_date_in_iso($input->param( 'dateduefrom' )) || '';
  my $datedueto   = format_date_in_iso($input->param( 'datedueto' )) || '';
 +# FIXME This is a kludge to include times
 +if ($datedueto) {
 +    $datedueto .= ' 23:59';
 +}
 +if ($dateduefrom) {
 +    $dateduefrom .= ' 00:00';
 +}
 +# kludge end
  my $isfiltered      = $op =~ /apply/i && $op =~ /filter/i;
  my $noreport        = C4::Context->preference('FilterBeforeOverdueReport') && ! $isfiltered && $op ne "csv";
  
@@@ -238,9 -229,7 +238,9 @@@ if ($noreport) 
      #  FIX 2: ensure there are indexes for columns participating in the WHERE clauses, where feasible/reasonable
  
  
 -    my $todaysdate = sprintf("%-04.4d-%-02.2d-%02.2d", Today());
 +    my $today_dt = DateTime->now(time_zone => C4::Context->tz);
 +    $today_dt->truncate(to => 'minutes');
 +    my $todaysdate = $today_dt->strftime('%Y-%m-%d %H:%M');
  
      $bornamefilter =~s/\*/\%/g;
      $bornamefilter =~s/\?/\_/g;
          biblio.biblionumber,
          borrowers.branchcode,
          items.itemcallnumber,
-         items.replacementprice
+         items.replacementprice,
+         items.enumchron
        FROM issues
      LEFT JOIN borrowers   ON (issues.borrowernumber=borrowers.borrowernumber )
      LEFT JOIN items       ON (issues.itemnumber=items.itemnumber)
              my @displayvalues = map { $_->[1] } @{ $pattrs->{$pattr_filter->{code}} };   # grab second value from each subarray
              push @patron_attr_value_loop, { value => join(', ', sort { lc $a cmp lc $b } @displayvalues) };
          }
 +        my $dt = dt_from_string($data->{date_due}, 'sql');
  
          push @overduedata, {
 -            duedate                => format_date($data->{date_due}),
 +            duedate                => output_pref($dt),
              borrowernumber         => $data->{borrowernumber},
              barcode                => $data->{barcode},
              itemnum                => $data->{itemnumber},
              branchcode             => $data->{branchcode},
              itemcallnumber         => $data->{itemcallnumber},
              replacementprice       => $data->{replacementprice},
+             enumchron              => $data->{enumchron},
              patron_attr_value_loop => \@patron_attr_value_loop,
          };
      }
  
      $template->param(
          csv_param_string        => $csv_param_string,
 -        todaysdate              => format_date($todaysdate),
 +        todaysdate              => output_pref($today_dt),
          overdueloop             => \@overduedata,
          nnoverdue               => scalar(@overduedata),
          noverdue_is_plural      => scalar(@overduedata) != 1,
@@@ -317,6 -317,7 +317,7 @@@ CREATE TABLE `branch_item_rules` 
    `branchcode` varchar(10) NOT NULL,
    `itemtype` varchar(10) NOT NULL,
    `holdallowed` tinyint(1) default NULL,
+   `returnbranch` varchar(15) default NULL,
    PRIMARY KEY  (`itemtype`,`branchcode`),
    KEY `branch_item_rules_ibfk_2` (`branchcode`),
    CONSTRAINT `branch_item_rules_ibfk_1` FOREIGN KEY (`itemtype`) REFERENCES `itemtypes` (`itemtype`)
@@@ -497,6 -498,7 +498,7 @@@ CREATE TABLE `default_branch_circ_rules
    `branchcode` VARCHAR(10) NOT NULL,
    `maxissueqty` int(4) default NULL,
    `holdallowed` tinyint(1) default NULL,
+   `returnbranch` varchar(15) default NULL,
    PRIMARY KEY (`branchcode`),
    CONSTRAINT `default_branch_circ_rules_ibfk_1` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`)
      ON DELETE CASCADE ON UPDATE CASCADE
@@@ -509,6 -511,7 +511,7 @@@ DROP TABLE IF EXISTS `default_branch_it
  CREATE TABLE `default_branch_item_rules` (
    `itemtype` varchar(10) NOT NULL,
    `holdallowed` tinyint(1) default NULL,
+   `returnbranch` varchar(15) default NULL,
    PRIMARY KEY  (`itemtype`),
    CONSTRAINT `default_branch_item_rules_ibfk_1` FOREIGN KEY (`itemtype`) REFERENCES `itemtypes` (`itemtype`)
      ON DELETE CASCADE ON UPDATE CASCADE
@@@ -523,6 -526,7 +526,7 @@@ CREATE TABLE `default_circ_rules` 
      `singleton` enum('singleton') NOT NULL default 'singleton',
      `maxissueqty` int(4) default NULL,
      `holdallowed` int(1) default NULL,
+     `returnbranch` varchar(15) default NULL,
      PRIMARY KEY (`singleton`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  
@@@ -949,15 -953,15 +953,15 @@@ DROP TABLE IF EXISTS `issues`
  CREATE TABLE `issues` ( -- information related to check outs or issues
    `borrowernumber` int(11), -- foreign key, linking this to the borrowers table for the patron this item was checked out to
    `itemnumber` int(11), -- foreign key, linking this to the items table for the item that was checked out
 -  `date_due` date default NULL, -- date the item is due (yyyy-mm-dd)
 +  `date_due` datetime default NULL, -- datetime the item is due (yyyy-mm-dd hh:mm::ss)
    `branchcode` varchar(10) default NULL, -- foreign key, linking to the branches table for the location the item was checked out
    `issuingbranch` varchar(18) default NULL,
 -  `returndate` date default NULL, -- date the item was returned, will be NULL until moved to old_issues
 -  `lastreneweddate` date default NULL, -- date the item was last renewed
 +  `returndate` datetime default NULL, -- date the item was returned, will be NULL until moved to old_issues
 +  `lastreneweddate` datetime default NULL, -- date the item was last renewed
    `return` varchar(4) default NULL,
    `renewals` tinyint(4) default NULL, -- lists the number of times the item was renewed
    `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, -- the date and time this record was last touched
 -  `issuedate` date default NULL, -- date the item was checked out or issued
 +  `issuedate` datetime default NULL, -- date the item was checked out or issued
    KEY `issuesborridx` (`borrowernumber`),
    KEY `bordate` (`borrowernumber`,`timestamp`),
    CONSTRAINT `issues_ibfk_1` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE RESTRICT ON UPDATE CASCADE,
@@@ -1356,6 -1360,55 +1360,55 @@@ CREATE TABLE `nozebra` 
    ENGINE=InnoDB DEFAULT CHARSET=utf8;
  
  --
+ -- Table structure for table `oai_sets`
+ --
+ DROP TABLE IF EXISTS `oai_sets`;
+ CREATE TABLE `oai_sets` (
+   `id` int(11) NOT NULL auto_increment,
+   `spec` varchar(80) NOT NULL UNIQUE,
+   `name` varchar(80) NOT NULL,
+   PRIMARY KEY (`id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ --
+ -- Table structure for table `oai_sets_descriptions`
+ --
+ DROP TABLE IF EXISTS `oai_sets_descriptions`;
+ CREATE TABLE `oai_sets_descriptions` (
+   `set_id` int(11) NOT NULL,
+   `description` varchar(255) NOT NULL,
+   CONSTRAINT `oai_sets_descriptions_ibfk_1` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ --
+ -- Table structure for table `oai_sets_mappings`
+ --
+ DROP TABLE IF EXISTS `oai_sets_mappings`;
+ CREATE TABLE `oai_sets_mappings` (
+   `set_id` int(11) NOT NULL,
+   `marcfield` char(3) NOT NULL,
+   `marcsubfield` char(1) NOT NULL,
+   `marcvalue` varchar(80) NOT NULL,
+   CONSTRAINT `oai_sets_mappings_ibfk_1` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ --
+ -- Table structure for table `oai_sets_biblios`
+ --
+ DROP TABLE IF EXISTS `oai_sets_biblios`;
+ CREATE TABLE `oai_sets_biblios` (
+   `biblionumber` int(11) NOT NULL,
+   `set_id` int(11) NOT NULL,
+   PRIMARY KEY (`biblionumber`, `set_id`),
+   CONSTRAINT `oai_sets_biblios_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE,
+   CONSTRAINT `oai_sets_biblios_ibfk_2` FOREIGN KEY (`set_id`) REFERENCES `oai_sets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ --
  -- Table structure for table `old_issues`
  --
  
@@@ -1363,15 -1416,15 +1416,15 @@@ DROP TABLE IF EXISTS `old_issues`
  CREATE TABLE `old_issues` ( -- lists items that were checked out and have been returned
    `borrowernumber` int(11) default NULL, -- foreign key, linking this to the borrowers table for the patron this item was checked out to
    `itemnumber` int(11) default NULL, -- foreign key, linking this to the items table for the item that was checked out
 -  `date_due` date default NULL, -- date the item is due (yyyy-mm-dd)
 +  `date_due` datetime default NULL, -- date the item is due (yyyy-mm-dd)
    `branchcode` varchar(10) default NULL, -- foreign key, linking to the branches table for the location the item was checked out
    `issuingbranch` varchar(18) default NULL,
 -  `returndate` date default NULL, -- date the item was returned
 -  `lastreneweddate` date default NULL, -- date the item was last renewed
 +  `returndate` datetime default NULL, -- date the item was returned
 +  `lastreneweddate` datetime default NULL, -- date the item was last renewed
    `return` varchar(4) default NULL,
    `renewals` tinyint(4) default NULL, -- lists the number of times the item was renewed
    `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, -- the date and time this record was last touched
 -  `issuedate` date default NULL, -- date the item was checked out or issued
 +  `issuedate` datetime default NULL, -- date the item was checked out or issued
    KEY `old_issuesborridx` (`borrowernumber`),
    KEY `old_issuesitemidx` (`itemnumber`),
    KEY `old_bordate` (`borrowernumber`,`timestamp`),
@@@ -2002,11 -2055,15 +2055,15 @@@ DROP TABLE IF EXISTS `virtualshelves`
  CREATE TABLE `virtualshelves` ( -- information about lists (or virtual shelves) 
    `shelfnumber` int(11) NOT NULL auto_increment, -- unique identifier assigned by Koha
    `shelfname` varchar(255) default NULL, -- name of the list
-   `owner` varchar(80) default NULL, -- foriegn key linking to the borrowers table (using borrowernumber) for the creator of this list
-   `category` varchar(1) default NULL, -- type of list (public [2], private [1] or open [3])
+   `owner` int default NULL, -- foreign key linking to the borrowers table (using borrowernumber) for the creator of this list (changed from varchar(80) to int)
+   `category` varchar(1) default NULL, -- type of list (private [1], public [2])
    `sortfield` varchar(16) default NULL, -- the field this list is sorted on
    `lastmodified` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, -- date and time the list was last modified
-   PRIMARY KEY  (`shelfnumber`)
+   `allow_add` tinyint(1) default 0, -- permission for adding entries to list
+   `allow_delete_own` tinyint(1) default 1, -- permission for deleting entries frm list that you added yourself
+   `allow_delete_other` tinyint(1) default 0, -- permission for deleting entries from list that another person added
+   PRIMARY KEY  (`shelfnumber`),
+   CONSTRAINT `virtualshelves_ibfk_1` FOREIGN KEY (`owner`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE SET NULL -- no cascaded delete, please see HandleDelBorrower in VirtualShelves.pm
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  
  --
@@@ -2019,10 -2076,27 +2076,27 @@@ CREATE TABLE `virtualshelfcontents` ( -
    `biblionumber` int(11) NOT NULL default 0, -- foreign key linking to the biblio table, defines the bib record that has been added to the list
    `flags` int(11) default NULL,
    `dateadded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- date and time this bib record was added to the list
+   `borrowernumber` int, -- borrower number that created this list entry (only the first one is saved: no need for use in/as key)
    KEY `shelfnumber` (`shelfnumber`),
    KEY `biblionumber` (`biblionumber`),
    CONSTRAINT `virtualshelfcontents_ibfk_1` FOREIGN KEY (`shelfnumber`) REFERENCES `virtualshelves` (`shelfnumber`) ON DELETE CASCADE ON UPDATE CASCADE,
-   CONSTRAINT `shelfcontents_ibfk_2` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE
+   CONSTRAINT `shelfcontents_ibfk_2` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE,
+   CONSTRAINT `shelfcontents_ibfk_3` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE SET NULL -- no cascaded delete, please see HandleDelBorrower in VirtualShelves.pm
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ --
+ -- Table structure for table `virtualshelfshares`
+ --
+ DROP TABLE IF EXISTS `virtualshelfshares`;
+ CREATE TABLE `virtualshelfshares` ( -- shared private lists
+   `id` int AUTO_INCREMENT PRIMARY KEY,  -- unique key
+   `shelfnumber` int NOT NULL,  -- foreign key for virtualshelves
+   `borrowernumber` int,  -- borrower that accepted access to this list
+   `invitekey` varchar(10), -- temporary string used in accepting the invitation to access thist list; not-empty means that the invitation has not been accepted yet
+   `sharedate` datetime,  -- date of invitation or acceptance of invitation
+   CONSTRAINT `virtualshelfshares_ibfk_1` FOREIGN KEY (`shelfnumber`) REFERENCES `virtualshelves` (`shelfnumber`) ON DELETE CASCADE ON UPDATE CASCADE,
+   CONSTRAINT `virtualshelfshares_ibfk_2` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE SET NULL -- no cascaded delete, please see HandleDelBorrower in VirtualShelves.pm
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  
  --
@@@ -4932,22 -4932,68 +4932,81 @@@ if ( C4::Context->preference("Version"
      SetVersion($DBversion);
  }
  
 -
+ $DBversion = "3.07.00.029";
+ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
+     my $installer = C4::Installer->new();
+     my $full_path = C4::Context->config('intranetdir') . "/installer/data/$installer->{dbms}/atomicupdate/oai_sets.sql";
+     my $error     = $installer->load_sql($full_path);
+     warn $error if $error;
+     print "Upgrade to $DBversion done (Atomic update for OAI-PMH sets management)\n";
+     SetVersion($DBversion);
+ }
+ $DBversion = "3.07.00.030";
+ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
+     $dbh->do("ALTER TABLE default_circ_rules ADD
+             COLUMN `returnbranch` varchar(15) default NULL AFTER `holdallowed`");
+     $dbh->do("ALTER TABLE branch_item_rules ADD
+             COLUMN `returnbranch` varchar(15) default NULL AFTER `holdallowed`");
+     $dbh->do("ALTER TABLE default_branch_circ_rules ADD
+             COLUMN `returnbranch` varchar(15) default NULL AFTER `holdallowed`");
+     $dbh->do("ALTER TABLE default_branch_item_rules ADD
+             COLUMN `returnbranch` varchar(15) default NULL AFTER `holdallowed`");
+     # set the default rule to the current value of HomeOrHoldingBranchReturn (default to 'homebranch' if need be)
+     my $homeorholdingbranchreturn = C4::Context->prefernce('HomeOrHoldingBranchReturn') || 'homebranch';
+     $dbh->do("UPDATE default_circ_rules SET returnbranch = '$homeorholdingbranchreturn'");
+     print "Upgrade to $DBversion done (Atomic update for OAI-PMH sets management)\n";
+     SetVersion($DBversion);
+ }
+ $DBversion = "3.07.00.031";
+ if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
+     $dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('UseICU', '1', 'Tell Koha if ICU indexing is in use for Zebra or not.','1','YesNo')");
+     print "Upgrade to $DBversion done (Add syspref to tell Koha if ICU indexing is in use for Zebra or not.)\n";
+     SetVersion ($DBversion);
+ }
+ $DBversion = "3.07.99.032";
+ if ( C4::Context->preference("Version") lt TransformToNum($DBversion) ) {
+     $dbh->do("ALTER TABLE virtualshelves MODIFY COLUMN owner int"); #should have been int already (fk to borrowers)
+     $dbh->do("UPDATE virtualshelves vi LEFT JOIN borrowers bo ON bo.borrowernumber=vi.owner SET vi.owner=NULL where bo.borrowernumber IS NULL"); #before adding the constraint on borrowernumber, we need to get rid of deleted owners
+     $dbh->do("DELETE FROM virtualshelves WHERE owner IS NULL and category=1"); #delete private lists without owner (cascades to shelfcontents)
+     $dbh->do("ALTER TABLE virtualshelves ADD COLUMN allow_add tinyint(1) DEFAULT 0, ADD COLUMN allow_delete_own tinyint(1) DEFAULT 1, ADD COLUMN allow_delete_other tinyint(1) DEFAULT 0, ADD CONSTRAINT `virtualshelves_ibfk_1` FOREIGN KEY (`owner`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE SET NULL");
+     $dbh->do("UPDATE virtualshelves SET allow_add=0, allow_delete_own=1, allow_delete_other=0 WHERE category=1");
+     $dbh->do("UPDATE virtualshelves SET allow_add=0, allow_delete_own=1, allow_delete_other=0 WHERE category=2");
+     $dbh->do("UPDATE virtualshelves SET allow_add=1, allow_delete_own=1, allow_delete_other=1 WHERE category=3");
+     $dbh->do("UPDATE virtualshelves SET category=2 WHERE category=3");
+     $dbh->do("ALTER TABLE virtualshelfcontents ADD COLUMN borrowernumber int, ADD CONSTRAINT `shelfcontents_ibfk_3` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE SET NULL");
+     $dbh->do("UPDATE virtualshelfcontents co LEFT JOIN virtualshelves sh USING (shelfnumber) SET co.borrowernumber=sh.owner");
+     $dbh->do("CREATE TABLE virtualshelfshares
+     (id int AUTO_INCREMENT PRIMARY KEY, shelfnumber int NOT NULL,
+     borrowernumber int, invitekey varchar(10), sharedate datetime,
+     CONSTRAINT `virtualshelfshares_ibfk_1` FOREIGN KEY (`shelfnumber`) REFERENCES `virtualshelves` (`shelfnumber`) ON DELETE CASCADE ON UPDATE CASCADE,
+         CONSTRAINT `virtualshelfshares_ibfk_2` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE SET NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8");
+     $dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacAllowPublicListCreation',1,'If set, allows opac users to create public lists',NULL,'YesNo');");
+     $dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacAllowSharingPrivateLists',0,'If set, allows opac users to share private lists with other patrons',NULL,'YesNo');");
+     print "Upgrade to $DBversion done (BZ7310: Improving list permissions)\n";
+     SetVersion($DBversion);
+ }
 +$DBversion = "XXX";
 +if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
 +    $dbh->do("ALTER TABLE issues CHANGE date_due date_due datetime");
 +    $dbh->do("ALTER TABLE issues CHANGE returndate returndate datetime");
 +    $dbh->do("ALTER TABLE issues CHANGE lastreneweddate lastreneweddate datetime");
 +    $dbh->do("ALTER TABLE issues CHANGE issuedate issuedate datetime");
 +    $dbh->do("ALTER TABLE old_issues CHANGE date_due date_due datetime");
 +    $dbh->do("ALTER TABLE old_issues CHANGE returndate returndate datetime");
 +    $dbh->do("ALTER TABLE old_issues CHANGE lastreneweddate lastreneweddate datetime");
 +    $dbh->do("ALTER TABLE old_issues CHANGE issuedate issuedate datetime");
 +    print "Upgrade to $DBversion done (Setting up issues tables for hourly loans)\n";
 +    SetVersion($DBversion);
 +}
 +
  =head1 FUNCTIONS
  
  =head2 DropAllForeignKeys($table)
@@@ -317,7 -317,7 +317,7 @@@ OPAC
                choices:
                    yes: "Don't allow"
                    no: Allow
-             - patrons to select their branch on the OPAC.
+             - patrons to select their library on the OPAC.
          -
              - pref: SearchMyLibraryFirst
                choices:
              - Use 
              - pref: OpacRenewalBranch
                choices:
-                   itemhomebranch: "the item's home branch"
-                   patronhomebranch: "the patron's home branch"
-                   checkoutbranch: "the branch the item was checked out from"
+                   itemhomebranch: "the item's home library"
+                   patronhomebranch: "the patron's home library"
+                   checkoutbranch: "the library the item was checked out from"
                    null: "NULL"
                    opacrenew: "'OPACRenew'"
              - as branchcode to store in the statistics table.
              - Only allow patrons to renew their own books on the OPAC if they have less than
              - pref: OPACFineNoRenewals
                class: currency
 -            - '[% local_currency %] in fines (set a large value to always allow renewal).'
 +            - '[% local_currency %] in fines (leave blank to disable).'
          -
              - pref: OPACViewOthersSuggestions
                choices:
                choices:
                    no: "Don't allow"
                    yes: Allow
-             - patrons to select branch when making a purchase suggestion
+             - patrons to select library when making a purchase suggestion
          -
              - pref: OpacHiddenItems
                type: textarea
                class: code
-             - Allows to define custom rules for hiding specific items at opac. See docs/opac/OpacHiddenItems.txt for more informations.
+             - Allows to define custom rules for hiding specific items at opac. See docs/opac/OpacHiddenItems.txt for more informations
+         -
+             - pref: OpacAllowPublicListCreation
+               default: 1
+               choices:
+                   no: "Don't allow"
+                   yes: Allow
+             - opac users to create public lists
+         -
+             - pref: OpacAllowSharingPrivateLists
+               default: 0
+               choices:
+                   no: "Don't allow"
+                   yes: Allow
+             - opac users to share private lists with other patrons. This feature is not active yet but will be released soon
  
      Privacy:
          -
                choices:
                    yes: Use
                    no: "Don't use"
-             - "the item home branch when finding items for the shelf browser."      
+             - "the item home library when finding items for the shelf browser."
          -
              - pref: ShelfBrowserUsesCcode
                default: 0
@@@ -69,19 -69,18 +69,19 @@@ for="tobranch"><strong>Clone these rule
              <input type="hidden" name="op" value="add" />
              <table>
              <tr>
-                 <th>Patron Category</th>
-                 <th>Item Type</th>
-                 <th>Current Checkouts Allowed</th>
-                 <th>Loan Period</th>
+                 <th>Patron category</th>
+                 <th>Item type</th>
+                 <th>Current checkouts allowed</th>
 -                <th>Loan period (day)</th>
++                <th>Loan period</th>
 +                <th>Unit</th>
-                 <th>Hard Due Date</th>
-                 <th>Fine Amount</th>
-                 <th>Fine Charging Interval</th>
-                 <th>Fine Grace period (day)</th>
-                 <th>Suspension in Days (day)</th>
-                 <th>Renewals Allowed (count)</th>
-                 <th>Holds Allowed (count)</th>
-                       <th>Rental Discount (%)</th>
+                 <th>Hard due date</th>
+                 <th>Fine amount</th>
+                 <th>Fine charging interval</th>
+                 <th>Fine grace period (day)</th>
+                 <th>Suspension in days (day)</th>
+                 <th>Renewals allowed (count)</th>
+                 <th>Holds allowed (count)</th>
 -                      <th>Rental discount (%)</th>
++              <th>Rental discount (%)</th>
                                <th>&nbsp;</th>
              </tr>
                                [% FOREACH rule IN rules %]
                                                                [% END %]
                                                        </td>
                                                        <td>[% rule.issuelength %]</td>
 +                                                      <td>
 +                                                          [% rule.lengthunit %]
 +                                                      </td>
                                                          <td>[% IF ( rule.hardduedate ) %]
                                                                 [% IF ( rule.hardduedatebefore ) %]before [% rule.hardduedate %]</td>
                                                                 [% ELSE %][% IF ( rule.hardduedateexact ) %]on [% rule.hardduedate %]</td>
                      </td>
                      <td><input name="maxissueqty" size="3" /></td>
                      <td><input name="issuelength" size="3" /> </td>
 +                    <td>
 +                    <select name="lengthunit">
 +                      <option value="days" selected>Days</option>
 +                      <option value="hours">Hours</option>
 +                    </select>
 +                  </td>
                      <td><select name="hardduedatecompare">
                             <option value="-1">Before</option>
                             <option value="0">Exactly on</option>
          </form>
      </div>
      <div id="defaults-for-this-library" class="container">
-     <h3>Default checkout and hold policy for [% IF ( humanbranch ) %][% humanbranch %][% ELSE %]all libraries[% END %]</h3>
-         <p>You can set a default maximum number of checkouts and hold policy that will be used if none is defined below for a particular item type or category.</p>
+     <h3>Default checkout, hold and return policy for [% IF ( humanbranch ) %][% humanbranch %][% ELSE %]all libraries[% END %]</h3>
+         <p>You can set a default maximum number of checkouts, hold policy and return policy that will be used if none is defined below for a particular item type or category.</p>
          <form method="post" action="/cgi-bin/koha/admin/smart-rules.pl">
              <input type="hidden" name="op" value="set-branch-defaults" />
              <input type="hidden" name="branch" value="[% current_branch %]"/>
              <table>
                  <tr>
                      <th>&nbsp;</th>
-                     <th>Total Current Checkouts Allowed</th>
-                     <th>Hold Policy</th>
+                     <th>Total current checkouts allowed</th>
+                     <th>Hold policy</th>
+                     <th>Return policy</th>
                      <th>&nbsp;</th>
                      <th>&nbsp;</th>
                  </tr>
                              [% ELSE %]
                              <option value="2">
                              [% END %]
-                                 From Any Library
+                                 From any library
                              </option>
                              [% IF ( default_holdallowed_same ) %]
                              <option value="1" selected="selected">
                              [% ELSE %]
                              <option value="1">
                              [% END %]
-                                 From Home Library
+                                 From home library
                              </option>
                              [% IF ( default_holdallowed_none ) %]
                              <option value="0" selected="selected">
                              [% ELSE %]
                              <option value="0">
                              [% END %]
-                                 No Holds Allowed
+                                 No holds allowed
+                             </option>
+                         </select>
+                     </td>
+                     <td>
+                         <select name="returnbranch">
+                             [% IF ( default_returnbranch == 'homebranch' ) %]
+                             <option value="homebranch" selected="selected">
+                             [% ELSE %]
+                             <option value="homebranch">
+                             [% END %]
+                                 Item returns home
+                             </option>
+                             [% IF ( default_returnbranch == 'holdingbranch' ) %]
+                             <option value="holdingbranch" selected="selected">
+                             [% ELSE %]
+                             <option value="holdingbranch">
+                             [% END %]
+                                 Item returns to issuing branch
+                             </option>
+                             [% IF ( default_returnbranch == 'noreturn' ) %]
+                             <option value="noreturn" selected="selected">
+                             [% ELSE %]
+                             <option value="noreturn">
+                             [% END %]
+                                 Item floats
                              </option>
                          </select>
                      </td>
              <input type="hidden" name="branch" value="[% current_branch %]"/>
              <table>
                  <tr>
-                     <th>Patron Category</th>
-                     <th>Total Current Checkouts Allowed</th>
+                     <th>Patron category</th>
+                     <th>Total current checkouts allowed</th>
                      <th>&nbsp;</th>
                  </tr>
                  [% FOREACH branch_cat_rule_loo IN branch_cat_rule_loop %]
              The various policies have the following effects:
          </p>
          <ul>
-             <li><strong>From Any Library:</strong> Patrons from any library may put this item on hold. <cite>(default if none is defined)</cite></li>
-             <li><strong>From Home Library:</strong> Only patrons from the item's home library may put this book on hold.</li>
-             <li><strong>No Holds Allowed:</strong> No patron may put this book on hold.</li>
+             <li><strong>From any library:</strong> Patrons from any library may put this item on hold. <cite>(default if none is defined)</cite></li>
+             <li><strong>From home library:</strong> Only patrons from the item's home library may put this book on hold.</li>
+             <li><strong>No holds allowed:</strong> No patron may put this book on hold.</li>
          </ul>
          <p>
              Note that if the system preference
              <input type="hidden" name="branch" value="[% current_branch %]"/>
              <table>
                  <tr>
-                     <th>Item Type</th>
-                     <th>Hold Policy</th>
+                     <th>Item type</th>
+                     <th>Hold policy</th>
+                     <th>Return policy</th>
                      <th>&nbsp;</th>
                  </tr>
                  [% FOREACH branch_item_rule_loo IN branch_item_rule_loop %]
                              [% END %]
                          </td>
                          <td>[% IF ( branch_item_rule_loo.holdallowed_any ) %]
-                                 From Any Library
+                                 From any library
                              [% ELSIF ( branch_item_rule_loo.holdallowed_same ) %]
-                                 From Home Library
+                                 From home library
                              [% ELSE %]
-                                 No Holds Allowed
+                                 No holds allowed
+                             [% END %]
+                         </td>
+                         <td>[% IF ( branch_item_rule_loo.returnbranch == 'homebranch' ) %]
+                                 Item returns home
+                             [% ELSIF ( branch_item_rule_loo.returnbranch == 'holdingbranch' ) %]
+                                 Item returns to issuing branch
+                             [% ELSIF ( branch_item_rule_loo.returnbranch == 'noreturn' ) %]
+                                 Item floats
+                             [% ELSE %]
+                                 Error - unknown option
                              [% END %]
                          </td>
                          <td>
                      </td>
                      <td>
                          <select name="holdallowed">
-                             <option value="2">From Any Library</option>
-                             <option value="1">From Home Library</option>
-                             <option value="0">No Holds Allowed</option>
+                             <option value="2">From any library</option>
+                             <option value="1">From home library</option>
+                             <option value="0">No holds allowed</option>
+                         </select>
+                     </td>
+                     <td>
+                         <select name="returnbranch">
+                             <option value="homebranch">Item returns home</option>
+                             <option value="holdingbranch">Item returns to issuing branch</option>
+                             <option value="noreturn">Item floats</option>
                          </select>
                      </td>
                      <td><input type="submit" value="Add" class="submit" /></td>
@@@ -1,4 -1,3 +1,4 @@@
 +[% USE KohaDates %]
  [% INCLUDE 'doc-head-open.inc' %]
  <title>Koha &rsaquo; Circulation &rsaquo; Check In [% title |html %]</title>
  [% INCLUDE 'doc-head-close.inc' %]
@@@ -196,7 -195,7 +196,7 @@@ function Dopop(link) 
      [% END %]
  
      [% IF ( transfer ) %]
-     <!-- transfer: item with no reservation, must be returned to its homebranch -->
+     <!-- transfer: item with no reservation, must be returned to its home library -->
        <div id="return1" class="dialog message">
              <h3>Please return <a href="/cgi-bin/koha/catalogue/detail.pl?type=intra&amp;biblionumber=[% itembiblionumber %]">[% title or "item" |html %]</a> to [% homebranchname %]<br/>( <a href="#" onclick="Dopop('transfer-slip.pl?transferitem=[% itemnumber %]&amp;branchcode=[% homebranch %]&amp;op=slip'); return true;">Print Slip</a> )</h3>
          </div>
                          <p class="problem">Item is withdrawn.</p>
                      [% END %]
                      [% IF ( errmsgloo.debarred ) %]
 -                        <p class="problem"><a href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=[% errmsgloo.debarborrowernumber %]">[% errmsgloo.debarname %]([% errmsgloo.debarcardnumber %])</a> is now debarred until [% errmsgloo.debarred %] </p>
 +                        <p class="problem"><a href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=[% errmsgloo.debarborrowernumber %]">[% errmsgloo.debarname %]([% errmsgloo.debarcardnumber %])</a> is now debarred until [% errmsgloo.debarred | $KohaDates %] </p>
                      [% END %]
              [% END %]
  [% IF ( soundon ) %]
diff --combined opac/opac-reserve.pl
@@@ -31,7 -31,6 +31,7 @@@ use C4::Members
  use C4::Branch; # GetBranches
  use C4::Overdues;
  use C4::Debug;
 +use Koha::DateUtils;
  # use Data::Dumper;
  
  my $MAXIMUM_NUMBER_OF_RESERVES = C4::Context->preference("maxreserves");
@@@ -199,7 -198,7 +199,7 @@@ if ( $query->param('place_reserve') ) 
          my $itemNum   = shift(@selectedItems);
          my $branch    = shift(@selectedItems); # i.e., branch code, not name
  
-         my $singleBranchMode = $template->param('singleBranchMode');
+         my $singleBranchMode = C4::Context->preference("singleBranchMode");
          if ($singleBranchMode || ! $OPACChooseBranch) { # single branch mode or disabled user choosing
              $branch = $borr->{'branchcode'};
          }
@@@ -398,7 -397,7 +398,7 @@@ foreach my $biblioNum (@biblionumbers) 
          # change the background color.
          my $issues= GetItemIssue($itemNum);
          if ( $issues->{'date_due'} ) {
 -            $itemLoopIter->{dateDue} = format_date($issues->{'date_due'});
 +            $itemLoopIter->{dateDue} = format_sqlduedatetime($issues->{date_due});
              $itemLoopIter->{backgroundcolor} = 'onloan';
          }
  
diff --combined opac/opac-user.pl
@@@ -36,7 -36,6 +36,7 @@@ use C4::Items
  use C4::Dates qw/format_date/;
  use C4::Letters;
  use C4::Branch; # GetBranches
 +use Koha::DateUtils;
  
  use constant ATTRIBUTE_SHOW_BARCODE => 'SHOW_BCODE';
  
@@@ -152,9 -151,9 +152,9 @@@ my $overdues_count = 0
  my @overdues;
  my @issuedat;
  my $itemtypes = GetItemTypes();
 -my ($issues) = GetPendingIssues($borrowernumber);
 +my $issues = GetPendingIssues($borrowernumber);
  if ($issues){
 -      foreach my $issue ( sort { $b->{'date_due'} cmp $a->{'date_due'} } @$issues ) {
 +      foreach my $issue ( sort { $b->{date_due}->datetime() cmp $a->{date_due}->datetime() } @{$issues} ) {
                # check for reserves
                my ( $restype, $res, undef ) = CheckReserves( $issue->{'itemnumber'} );
                if ( $restype ) {
                        $issue->{'imageurl'}    = getitemtypeimagelocation( 'opac', $itemtypes->{$itemtype}->{'imageurl'} );
                        $issue->{'description'} = $itemtypes->{$itemtype}->{'description'};
                }
 -              $issue->{date_due} = format_date($issue->{date_due});
 +              $issue->{date_due} = output_pref_due($issue->{date_due});
                push @issuedat, $issue;
                $count++;
                
@@@ -363,7 -362,6 +363,6 @@@ if ( $borr->{'opacnote'} ) 
  $template->param(
      bor_messages_loop => GetMessages( $borrowernumber, 'B', 'NONE' ),
      waiting_count      => $wcount,
-     textmessaging      => $borr->{textmessaging},
      patronupdate => $patronupdate,
      OpacRenewalAllowed => C4::Context->preference("OpacRenewalAllowed"),
      userview => 1,