Merge remote-tracking branch 'kc/master' into merged_5549
authorChris Cormack <chrisc@catalyst.net.nz>
Thu, 22 Mar 2012 04:05:16 +0000 (17:05 +1300)
committerChris Cormack <chrisc@catalyst.net.nz>
Thu, 22 Mar 2012 04:05:16 +0000 (17:05 +1300)
45 files changed:
C4/Circulation.pm
C4/Context.pm
C4/ILSDI/Services.pm
C4/Installer/PerlDependencies.pm
C4/Members.pm
C4/Overdues.pm
C4/SIP/ILS/Patron.pm
C4/SIP/ILS/Transaction/Checkout.pm
C4/SIP/ILS/Transaction/Renew.pm
C4/SIP/ILS/Transaction/RenewAll.pm
C4/SIP/Sip.pm
Koha/Calendar.pm [new file with mode: 0644]
Koha/DateUtils.pm [new file with mode: 0644]
admin/smart-rules.pl
catalogue/detail.pl
catalogue/issuehistory.pl
catalogue/moredetail.pl
circ/branchoverdues.pl
circ/circulation.pl
circ/overdue.pl
circ/returns.pl
installer/data/mysql/atomicupdate/hourlyloans.sql [new file with mode: 0644]
installer/data/mysql/atomicupdate/issuedate_times.pl [new file with mode: 0644]
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
koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
members/moremember.pl
members/readingrec.pl
misc/cronjobs/advance_notices.pl
misc/cronjobs/fines.pl
misc/cronjobs/overdue_notices.pl
opac/opac-detail.pl
opac/opac-ics.pl
opac/opac-readingrecord.pl
opac/opac-reserve.pl
opac/opac-user.pl
reserve/renewscript.pl
reserve/request.pl
t/00-testcritic.t
t/DateUtils.t [new file with mode: 0755]
t/Kalendar.t [new file with mode: 0755]
t/db_dependent/rollingloans.t [new file with mode: 0644]

index b6e86e0..9c43f36 100644 (file)
@@ -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;
@@ -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);
 
@@ -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};
@@ -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);
     }
 
     #
@@ -726,13 +724,25 @@ sub CanBookBeIssued {
     if ( $borrower->{flags}->{'DBARRED'} ) {
         $issuingimpossible{DEBARRED} = 1;
     }
-    if ( $borrower->{'dateexpiry'} eq '0000-00-00') {
+    if ( !defined $borrower->{dateexpiry} || $borrower->{'dateexpiry'} eq '0000-00-00') {
         $issuingimpossible{EXPIRED} = 1;
     } else {
-        my @expirydate=  split /-/,$borrower->{'dateexpiry'};
-        if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 ||
-            Date_to_Days(Today) > Date_to_Days( @expirydate )) {
-            $issuingimpossible{EXPIRED} = 1;                                   
+        my ($y, $m, $d) =  split /-/,$borrower->{'dateexpiry'};
+        if ($y && $m && $d) { # are we really writing oinvalid dates to borrs
+            my $expiry_dt = DateTime->new(
+                year => $y,
+                month => $m,
+                day   => $d,
+                time_zone => C4::Context->tz,
+            );
+            $expiry_dt->truncate( to => 'days');
+            my $today = $now->clone()->truncate(to => 'days');
+            if (DateTime->compare($today, $expiry_dt) == 1) {
+                $issuingimpossible{EXPIRED} = 1;
+            }
+        } else {
+            carp("Invalid expity date in borr");
+            $issuingimpossible{EXPIRED} = 1;
         }
     }
     #
@@ -741,7 +751,7 @@ sub CanBookBeIssued {
 
     # DEBTS
     my ($amount) =
-      C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->output('iso') );
+      C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->ymd() );
     my $amountlimit = C4::Context->preference("noissuescharge");
     my $allowfineoverride = C4::Context->preference("AllowFineOverride");
     my $allfinesneedoverride = C4::Context->preference("AllFinesNeedOverride");
@@ -783,7 +793,7 @@ sub CanBookBeIssued {
     #
        my ($current_loan_count, $max_loans_allowed) = TooMany( $borrower, $item->{biblionumber}, $item );
     # if TooMany max_loans_allowed returns 0 the user doesn't have permission to check out this book
-    if ($max_loans_allowed eq 0) {
+    if (defined $max_loans_allowed && $max_loans_allowed == 0) {
         $needsconfirmation{PATRON_CANT} = 1;
     } else {
         if($max_loans_allowed){
@@ -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);
@@ -979,12 +996,12 @@ sub AddIssue {
                # check if we just renew the issue.
                #
                if ($actualissue->{borrowernumber} eq $borrower->{'borrowernumber'}) {
-                       $datedue = AddRenewal(
-                               $borrower->{'borrowernumber'},
-                               $item->{'itemnumber'},
-                               $branch,
-                               $datedue,
-                $issuedate, # here interpreted as the renewal date
+                   $datedue = AddRenewal(
+                       $borrower->{'borrowernumber'},
+                       $item->{'itemnumber'},
+                       $branch,
+                       $datedue,
+                       $issuedate, # here interpreted as the renewal date
                        );
                }
                else {
@@ -999,7 +1016,6 @@ sub AddIssue {
                        }
 
             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) {
@@ -1018,23 +1034,23 @@ sub AddIssue {
         # Record in the database the fact that the book was issued.
         my $sth =
           $dbh->prepare(
-                "INSERT INTO issues 
+                "INSERT INTO issues
                     (borrowernumber, itemnumber,issuedate, date_due, branchcode)
                 VALUES (?,?,?,?,?)"
           );
         unless ($datedue) {
             my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
-            $datedue = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower );
+            $datedue = CalcDateDue( $issuedate, $itype, $branch, $borrower );
 
         }
+        $datedue->truncate( to => 'minutes');
         $sth->execute(
             $borrower->{'borrowernumber'},      # borrowernumber
             $item->{'itemnumber'},              # itemnumber
-            $issuedate,                         # issuedate
-            $datedue->output('iso'),            # date_due
+            $issuedate->strftime('%Y-%m-%d %H:%M:00'), # issuedate
+            $datedue->strftime('%Y-%m-%d %H:%M:00'),   # date_due
             C4::Context->userenv->{'branch'}    # branchcode
         );
-        $sth->finish;
         if ( C4::Context->preference('ReturnToShelvingCart') ) { ## ReturnToShelvingCart is on, anything issued should be taken off the cart.
           CartToShelf( $item->{'itemnumber'} );
         }
@@ -1048,8 +1064,8 @@ sub AddIssue {
         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'} );
 
@@ -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',
+    };
+
 }
 
 
@@ -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);
@@ -1581,7 +1601,8 @@ sub AddReturn {
             # define circControlBranch only if dropbox mode is set
             # don't allow dropbox mode to create an invalid entry in issues (issuedate > today)
             # FIXME: check issuedate > returndate, factoring in holidays
-            $circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
+            #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
+            $circControlBranch = _GetCircControlBranch($item,$borrower);
         }
 
         if ($borrowernumber) {
@@ -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) {
@@ -1772,18 +1793,16 @@ Internal function, called only by AddReturn that calculate and update the user f
 
 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 );
@@ -1791,22 +1810,22 @@ sub _FixFineDaysOnReturn {
 
     # 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();
         }
     }
 }
@@ -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
@@ -2073,14 +2097,15 @@ Returns reference to an array of hashes
 sub GetItemIssues {
     my ( $itemnumber, $history ) = @_;
     
-    my $today = C4::Dates->today('iso');  # get today date
-    my $sql = "SELECT * FROM issues 
+    my $today = DateTime->now( time_zome => C4::Context->tz);  # get today date
+    $today->truncate( to => 'minutes' );
+    my $sql = "SELECT * FROM issues
               JOIN borrowers USING (borrowernumber)
               JOIN items     USING (itemnumber)
               WHERE issues.itemnumber = ? ";
     if ($history) {
         $sql .= "UNION ALL
-                 SELECT * FROM old_issues 
+                 SELECT * FROM old_issues
                  LEFT JOIN borrowers USING (borrowernumber)
                  JOIN items USING (itemnumber)
                  WHERE old_issues.itemnumber = ? ";
@@ -2094,7 +2119,10 @@ sub GetItemIssues {
     }
     my $results = $sth->fetchall_arrayref({});
     foreach (@$results) {
-        $_->{'overdue'} = ($_->{'date_due'} lt $today) ? 1 : 0;
+        my $date_due = dt_from_string($_->{date_due},'sql');
+        $date_due->truncate( to => 'minutes' );
+
+        $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0;
     }
     return $results;
 }
@@ -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)
@@ -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;
 
@@ -2309,9 +2337,9 @@ sub AddRenewal {
     $sth->execute( $borrowernumber, $itemnumber );
     my $issuedata = $sth->fetchrow_hashref;
     $sth->finish;
-    if($datedue && ! $datedue->output('iso')){
-        warn "Invalid date passed to AddRenewal.";
-        return undef;
+    if(defined $datedue && ref $datedue ne 'DateTime' ) {
+        carp 'Invalid date passed to AddRenewal.';
+        return;
     }
     # If the due date wasn't specified, calculate it by adding the
     # book's loan length to today's date or the current due date
@@ -2322,8 +2350,8 @@ sub AddRenewal {
         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);
     }
 
@@ -2334,12 +2362,13 @@ sub AddRenewal {
                             WHERE borrowernumber=? 
                             AND itemnumber=?"
     );
-    $sth->execute( $datedue->output('iso'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
+
+    $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
     $sth->finish;
 
     # Update the renewal count on the item, and tell zebra to reindex
     $renews = $biblio->{'renewals'} + 1;
-    ModItem({ renewals => $renews, onloan => $datedue->output('iso') }, $biblio->{'biblionumber'}, $itemnumber);
+    ModItem({ renewals => $renews, onloan => $datedue->strftime('%Y-%m-%d %H:%M')}, $biblio->{'biblionumber'}, $itemnumber);
 
     # Charge a new rental fee, if applicable?
     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
@@ -2357,7 +2386,6 @@ sub AddRenewal {
         $sth->execute( $borrowernumber, $accountno, $charge, $manager_id,
             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
             'Rent', $charge, $itemnumber );
-        $sth->finish;
     }
     # Log the renewal
     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
@@ -2767,88 +2795,89 @@ C<$borrower> = Borrower object
 
 =cut
 
-sub CalcDateDue { 
-       my ($startdate,$itemtype,$branch,$borrower) = @_;
-       my $datedue;
-        my $loanlength = GetLoanLength($borrower->{'categorycode'},$itemtype, $branch);
+sub CalcDateDue {
+    my ( $startdate, $itemtype, $branch, $borrower ) = @_;
 
-       # 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);
-               }
-       }
+    # loanlength now a href
+    my $loanlength =
+      GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
 
-       # 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
-       }
+    my $datedue;
 
-       # 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;
-}
-
-=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;
 }
 
 
index 0417c45..67d31ee 100644 (file)
@@ -104,6 +104,7 @@ use XML::Simple;
 use C4::Boolean;
 use C4::Debug;
 use POSIX ();
+use DateTime::TimeZone;
 
 =head1 NAME
 
@@ -384,6 +385,7 @@ sub new {
     $self->{"userenv"} = undef;        # User env
     $self->{"activeuser"} = undef;        # current active user
     $self->{"shelves"} = undef;
+    $self->{tz} = undef; # local timezone object
 
     bless $self, $class;
     return $self;
@@ -1112,6 +1114,24 @@ sub get_versions {
 }
 
 
+=head2 tz
+
+  C4::Context->tz
+
+  Returns a DateTime::TimeZone object for the system timezone
+
+=cut
+
+sub tz {
+    my $self = shift;
+    if (!defined $context->{tz}) {
+        $context->{tz} = DateTime::TimeZone->new(name => 'local');
+    }
+    return $context->{tz};
+}
+
+
+
 1;
 __END__
 
index 067afd7..2940c00 100644 (file)
@@ -33,6 +33,7 @@ use C4::ILSDI::Utility;
 use XML::Simple;
 use HTML::Entities;
 use CGI;
+use DateTime;
 
 =head1 NAME
 
@@ -558,7 +559,7 @@ sub RenewLoan {
     # Hashref building
     my $out;
     $out->{'renewals'} = $issue->{'renewals'};
-    $out->{'date_due'} = $issue->{'date_due'};
+    $out->{date_due}   = $issue->{date_due}->strftime('%Y-%m-%d %H:%S');
     $out->{'success'}  = $renewal[0];
     $out->{'error'}    = $renewal[1];
 
index 0a888a8..ae01208 100644 (file)
@@ -197,7 +197,37 @@ our $PERL_DEPS = {
     'DateTime' => {
         'usage'    => 'Core',
         'required' => '1',
-        'min_ver'  => '0.51'
+        'min_ver'  => '0.58'
+    },
+    'DateTime::TimeZone' => {
+        'usage'    => 'Core',
+        'required' => '1',
+        'min_ver'  => '1.26'
+    },
+    'DateTime::Format::DateParse' => {
+        'usage'    => 'Core',
+        'required' => '1',
+        'min_ver'  => '0.04'
+    },
+    'DateTime::Set' => {
+        'usage'    => 'Core',
+        'required' => '1',
+        'min_ver'  => '0.28'
+    },
+    'DateTime::Event::ICal' => {
+        'usage'    => 'Core',
+        'required' => '1',
+        'min_ver'  => '0.08'
+    },
+    'Readonly' => {
+        'usage'    => 'Core',
+        'required' => '1',
+        'min_ver'  => '1.03'
+    },
+    'Readonly::XS' => {
+        'usage'    => 'Core',
+        'required' => '0',
+        'min_ver'  => '1.02'
     },
     'Graphics::Magick' => {
         'usage'    => 'Patron Card Creator Feature',
index 4aa689f..6161ac9 100644 (file)
@@ -35,6 +35,9 @@ use C4::Letters;
 use C4::SQLHelper qw(InsertInTable UpdateInTable SearchInTable);
 use C4::Members::Attributes qw(SearchIdMatchingAttribute);
 use C4::NewsChannels; #get slip news
+use DateTime;
+use DateTime::Format::DateParse;
+use Koha::DateUtils;
 
 our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
 
@@ -639,7 +642,7 @@ sub IsMemberBlocked {
         "SELECT COUNT(*) as latedocs
          FROM issues
          WHERE borrowernumber = ?
-         AND date_due < curdate()"
+         AND date_due < now()"
     );
     $sth->execute($borrowernumber);
     my $latedocs = $sth->fetchrow_hashref->{'latedocs'};
@@ -677,7 +680,7 @@ sub GetMemberIssuesAndFines {
     $sth = $dbh->prepare(
         "SELECT COUNT(*) FROM issues 
          WHERE borrowernumber = ? 
-         AND date_due < curdate()"
+         AND date_due < now()"
     );
     $sth->execute($borrowernumber);
     my $overdue_count = $sth->fetchrow_arrayref->[0];
@@ -1032,9 +1035,15 @@ sub GetPendingIssues {
     my $sth = C4::Context->dbh->prepare($query);
     $sth->execute(@borrowernumbers);
     my $data = $sth->fetchall_arrayref({});
-    my $today = C4::Dates->new->output('iso');
+    my $tz = C4::Context->tz();
+    my $today = DateTime->now( time_zone => $tz);
     foreach (@{$data}) {
-        if ($_->{date_due}  and $_->{date_due} lt $today) {
+        if ($_->{issuedate}) {
+            $_->{issuedate} = dt_from_string($_->{issuedate}, 'sql');
+        }
+        $_->{date_due} or next;
+        $_->{date_due} = DateTime::Format::DateParse->parse_datetime($_->{date_due}, $tz->name());
+        if ( DateTime->compare($_->{date_due}, $today) == -1 ) {
             $_->{overdue} = 1;
         }
     }
index 660e10b..7676a87 100644 (file)
@@ -124,7 +124,7 @@ sub Getoverdues {
    SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode
      FROM issues 
 LEFT JOIN items       USING (itemnumber)
-    WHERE date_due < CURDATE() 
+    WHERE date_due < NOW()
 ";
     } else {
         $statement = "
@@ -132,7 +132,7 @@ LEFT JOIN items       USING (itemnumber)
      FROM issues 
 LEFT JOIN items       USING (itemnumber)
 LEFT JOIN biblioitems USING (biblioitemnumber)
-    WHERE date_due < CURDATE() 
+    WHERE date_due < NOW()
 ";
     }
 
@@ -199,7 +199,7 @@ sub checkoverdues {
          LEFT JOIN biblio      ON items.biblionumber     = biblio.biblionumber
          LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
             WHERE issues.borrowernumber  = ?
-            AND   issues.date_due < CURDATE()"
+            AND   issues.date_due < NOW()"
     );
     # FIXME: SELECT * across 4 tables?  do we really need the marc AND marcxml blobs??
     $sth->execute($borrowernumber);
@@ -209,9 +209,9 @@ sub checkoverdues {
 
 =head2 CalcFine
 
-    ($amount, $chargename, $daycount, $daycounttotal) = &CalcFine($item, 
-                                  $categorycode, $branch, $days_overdue, 
-                                  $description, $start_date, $end_date );
+    ($amount, $chargename,  $daycounttotal) = &CalcFine($item,
+                                  $categorycode, $branch,
+                                  $start_dt, $end_dt );
 
 Calculates the fine for a book.
 
@@ -229,13 +229,8 @@ the book.
 
 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
 
-C<$days_overdue> is the number of days elapsed since the book's due date.
-  NOTE: supplying days_overdue is deprecated.
-
-C<$start_date> & C<$end_date> are C4::Dates objects 
+C<$start_date> & C<$end_date> are DateTime objects
 defining the date range over which to determine the fine.
-Note that if these are defined, we ignore C<$difference> and C<$dues> , 
-but retain these for backwards-comptibility with extant fines scripts.
 
 Fines scripts should just supply the date range over which to calculate the fine.
 
@@ -249,8 +244,6 @@ the categoryitem table, whatever that is.
 C<$daycount> is the number of days between start and end dates, Calendar adjusted (where needed), 
 minus any applicable grace period.
 
-C<$daycounttotal> is C<$daycount> without consideration of grace period.
-
 FIXME - What is chargename supposed to be ?
 
 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
@@ -259,48 +252,39 @@ or "Final Notice".  But CalcFine never defined any value.
 =cut
 
 sub CalcFine {
-    my ( $item, $bortype, $branchcode, $difference ,$dues , $start_date, $end_date  ) = @_;
-       $debug and warn sprintf("CalcFine(%s, %s, %s, %s, %s, %s, %s)",
-                       ($item ? '{item}' : 'UNDEF'), 
-                       ($bortype    || 'UNDEF'), 
-                       ($branchcode || 'UNDEF'), 
-                       ($difference || 'UNDEF'), 
-                       ($dues       || 'UNDEF'), 
-                       ($start_date ? ($start_date->output('iso') || 'Not a C4::Dates object') : 'UNDEF'), 
-                       (  $end_date ? (  $end_date->output('iso') || 'Not a C4::Dates object') : 'UNDEF')
-       );
+    my ( $item, $bortype, $branchcode, $due_dt, $end_date  ) = @_;
+    my $start_date = $due_dt->clone();
     my $dbh = C4::Context->dbh;
     my $amount = 0;
-       my $daystocharge;
-       # get issuingrules (fines part will be used)
-    $debug and warn sprintf("CalcFine calling GetIssuingRule(%s, %s, %s)", $bortype, $item->{'itemtype'}, $branchcode);
-    my $data = C4::Circulation::GetIssuingRule($bortype, $item->{'itemtype'}, $branchcode);
-       if($difference) {
-               # if $difference is supplied, the difference has already been calculated, but we still need to adjust for the calendar.
-       # use copy-pasted functions from calendar module.  (deprecated -- these functions will be removed from C4::Overdues ).
-           my $countspecialday    =    &GetSpecialHolidays($dues,$item->{itemnumber});
-           my $countrepeatableday = &GetRepeatableHolidays($dues,$item->{itemnumber},$difference);    
-           my $countalldayclosed  = $countspecialday + $countrepeatableday;
-           $daystocharge = $difference - $countalldayclosed;
-       } else {
-               # if $difference is not supplied, we have C4::Dates objects giving us the date range, and we use the calendar module.
-               if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
-                       my $calendar = C4::Calendar->new( branchcode => $branchcode );
-                       $daystocharge = $calendar->daysBetween( $start_date, $end_date );
-               } else {
-                       $daystocharge = Date_to_Days(split('-',$end_date->output('iso'))) - Date_to_Days(split('-',$start_date->output('iso')));
-               }
-       }
-       # correct for grace period.
-       my $days_minus_grace = $daystocharge - $data->{'firstremind'};
-    if ($data->{'chargeperiod'} > 0 && $days_minus_grace > 0 ) { 
-        $amount = int($daystocharge / $data->{'chargeperiod'}) * $data->{'fine'};
+    my $charge_duration;
+    # get issuingrules (fines part will be used)
+    my $data = C4::Circulation::GetIssuingRule($bortype, $item->{itemtype}, $branchcode);
+    if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
+        my $calendar = Koha::Calendar->new( branchcode => $branchcode );
+        $charge_duration = $calendar->days_between( $start_date, $end_date );
+    } else {
+        $charge_duration = $end_date - $start_date;
+    }
+    # correct for grace period.
+    my $fine_unit = $data->{lengthunit};
+    $fine_unit ||= 'days';
+    my $chargeable_units;
+    if ($fine_unit eq 'hours') {
+        $chargeable_units = $charge_duration->hours(); # TODO closed times???
+    }
+    else {
+        $chargeable_units = $charge_duration->days;
+    }
+    my $days_minus_grace = $chargeable_units - $data->{firstremind};
+    if ($data->{'chargeperiod'}  && $days_minus_grace  ) {
+        $amount = int($chargeable_units / $data->{'chargeperiod'}) * $data->{'fine'};# TODO fine calc should be in cents
     } else {
         # a zero (or null)  chargeperiod means no charge.
     }
-       $amount = C4::Context->preference('maxFine') if(C4::Context->preference('maxFine') && ( $amount > C4::Context->preference('maxFine')));
-       $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $data->{'chargename'}, $days_minus_grace, $daystocharge);
-    return ($amount, $data->{'chargename'}, $days_minus_grace, $daystocharge);
+    if(C4::Context->preference('maxFine') && ( $amount > C4::Context->preference('maxFine'))) {
+        $amount = C4::Context->preference('maxFine');
+    }
+    return ($amount, $data->{chargename}, $days_minus_grace);
     # FIXME: chargename is NEVER populated anywhere.
 }
 
@@ -1218,7 +1202,7 @@ sub GetOverduesForBranch {
     WHERE (accountlines.amountoutstanding  != '0.000000')
       AND (accountlines.accounttype         = 'FU'      )
       AND (issues.branchcode =  ?   )
-      AND (issues.date_due  < CURDATE())
+      AND (issues.date_due  < NOW())
     ";
     my @getoverdues;
     my $i = 0;
index 95981fb..99fd6e7 100644 (file)
@@ -17,7 +17,6 @@ use Data::Dumper;
 
 use C4::Debug;
 use C4::Context;
-# use C4::Dates;
 use C4::Koha;
 use C4::Members;
 use C4::Reserves;
index 617a4eb..d483e16 100644 (file)
@@ -122,11 +122,14 @@ sub do_checkout {
        $debug and warn "do_checkout: calling AddIssue(\$borrower,$barcode, undef, 0)\n"
                # . "w/ \$borrower: " . Dumper($borrower)
                . "w/ C4::Context->userenv: " . Dumper(C4::Context->userenv);
-       my $c4due  = AddIssue($borrower, $barcode, undef, 0);
-       my $due  = $c4due->output('iso') || undef;
-       $debug and warn "Item due: $due";
-       $self->{'due'} = $due;
-       $self->{item}->due_date($due);
+       my $due_dt  = AddIssue($borrower, $barcode, undef, 0);
+    if ($due_dt) {
+        $self->{due} = $due_dt->clone();
+    } else {
+        $self->{due} = undef;
+    }
+
+    #$self->{item}->due_date($due);
        $self->ok(1);
        return $self;
 }
index 73acaa3..d7f949b 100644 (file)
@@ -37,8 +37,7 @@ sub do_renew_for ($$) {
        my $borrower = shift;
        my ($renewokay,$renewerror) = CanBookBeRenewed($borrower->{borrowernumber},$self->{item}->{itemnumber});
        if ($renewokay){
-               my $datedue = AddIssue( $borrower, $self->{item}->id, undef, 0 );
-               $self->{due} = $datedue;
+               $self->{due} = AddIssue( $borrower, $self->{item}->id, undef, 0 );
                $self->renewal_ok(1);
        } else {
                $self->screen_msg(($self->screen_msg || '') . " " . $renewerror);
index 2e49bf7..10fb27d 100644 (file)
@@ -53,8 +53,8 @@ sub do_renew_all {
                $self->{item} = $item;
                $self->do_renew_for($borrower);
                if ($self->ok) {
-                       $item->{due_date} = $self->{due};
-            push @{$self->{renewed}  }, $item_id;
+                   $item->{due_date} = $self->{due}->clone();
+                   push @{$self->renewed  }, $item_id;
                } else {
             push @{$self->{unrenewed}}, $item_id;
                }
index 337cc0e..4e3f299 100644 (file)
@@ -50,7 +50,9 @@ our $last_response = '';
 
 sub timestamp {
     my $time = $_[0] || time();
-    if ($time=~m/^(\d{4})\-(\d{2})\-(\d{2})/) {
+    if ( ref $time eq 'DateTime') {
+        return $time->strftime(SIP_DATETIME);
+    } elsif ($time=~m/^(\d{4})\-(\d{2})\-(\d{2})/) {
         # passing a db returned date as is + bogus time
         return sprintf( '%04d%02d%02d    235900', $1, $2, $3);
     }
diff --git a/Koha/Calendar.pm b/Koha/Calendar.pm
new file mode 100644 (file)
index 0000000..1e7299c
--- /dev/null
@@ -0,0 +1,300 @@
+package Koha::Calendar;
+use strict;
+use warnings;
+use 5.010;
+
+use DateTime;
+use DateTime::Set;
+use DateTime::Duration;
+use C4::Context;
+use Carp;
+use Readonly;
+
+sub new {
+    my ( $classname, %options ) = @_;
+    my $self = {};
+    bless $self, $classname;
+    for my $o_name ( keys %options ) {
+        my $o = lc $o_name;
+        $self->{$o} = $options{$o_name};
+    }
+    if ( exists $options{TEST_MODE} ) {
+        $self->_mockinit();
+        return $self;
+    }
+    if ( !defined $self->{branchcode} ) {
+        croak 'No branchcode argument passed to Koha::Calendar->new';
+    }
+    $self->_init();
+    return $self;
+}
+
+sub _init {
+    my $self       = shift;
+    my $branch     = $self->{branchcode};
+    my $dbh        = C4::Context->dbh();
+    my $repeat_sth = $dbh->prepare(
+'SELECT * from repeatable_holidays WHERE branchcode = ? AND ISNULL(weekday) = ?'
+    );
+    $repeat_sth->execute( $branch, 0 );
+    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];
+    Readonly::Scalar my $sunday => 7;
+    while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
+        $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1;
+    }
+    $repeat_sth->execute( $branch, 1 );
+    $self->{day_month_closed_days} = {};
+    while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
+        $self->{day_month_closed_days}->{ $tuple->{day} }->{ $tuple->{month} } =
+          1;
+    }
+    my $special = $dbh->prepare(
+'SELECT day, month, year, title, description FROM special_holidays WHERE ( branchcode = ? ) AND (isexception = ?)'
+    );
+    $special->execute( $branch, 1 );
+    my $dates = [];
+    while ( my ( $day, $month, $year, $title, $description ) =
+        $special->fetchrow ) {
+        push @{$dates},
+          DateTime->new(
+            day       => $day,
+            month     => $month,
+            year      => $year,
+            time_zone => C4::Context->tz()
+          )->truncate( to => 'day' );
+    }
+    $self->{exception_holidays} =
+      DateTime::Set->from_datetimes( dates => $dates );
+    $special->execute( $branch, 1 );
+    $dates = [];
+    while ( my ( $day, $month, $year, $title, $description ) =
+        $special->fetchrow ) {
+        push @{$dates},
+          DateTime->new(
+            day       => $day,
+            month     => $month,
+            year      => $year,
+            time_zone => C4::Context->tz()
+          )->truncate( to => 'day' );
+    }
+    $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
+    $self->{days_mode} = C4::Context->preference('useDaysMode');
+    return;
+}
+
+sub addDate {
+    my ( $self, $startdate, $add_duration, $unit ) = @_;
+    my $base_date = $startdate->clone();
+    if ( ref $add_duration ne 'DateTime::Duration' ) {
+        $add_duration = DateTime::Duration->new( days => $add_duration );
+    }
+    $unit ||= q{};    # default days ?
+    my $days_mode = $self->{days_mode};
+    Readonly::Scalar my $return_by_hour => 10;
+    my $day_dur = DateTime::Duration->new( days => 1 );
+    if ( $add_duration->is_negative() ) {
+        $day_dur->inverse();
+    }
+    if ( $days_mode eq 'Datedue' ) {
+
+        my $dt = $base_date + $add_duration;
+        while ( $self->is_holiday($dt) ) {
+
+            # TODOP if hours set to 10 am
+            $dt->add_duration($day_dur);
+            if ( $unit eq 'hours' ) {
+                $dt->set_hour($return_by_hour);    # Staffs specific
+            }
+        }
+        return $dt;
+    } elsif ( $days_mode eq 'Calendar' ) {
+        if ( $unit eq 'hours' ) {
+            $base_date->add_duration($add_duration);
+            while ( $self->is_holiday($base_date) ) {
+                $base_date->add_duration($day_dur);
+
+            }
+
+        } else {
+            my $days = abs $add_duration->in_units('days');
+            while ($days) {
+                $base_date->add_duration($day_dur);
+                if ( $self->is_holiday($base_date) ) {
+                    next;
+                } else {
+                    --$days;
+                }
+            }
+        }
+        if ( $unit eq 'hours' ) {
+            my $dt = $base_date->clone()->subtract( days => 1 );
+            if ( $self->is_holiday($dt) ) {
+                $base_date->set_hour($return_by_hour);    # Staffs specific
+            }
+        }
+        return $base_date;
+    } else {    # Days
+        return $base_date + $add_duration;
+    }
+}
+
+sub is_holiday {
+    my ( $self, $dt ) = @_;
+    my $dow = $dt->day_of_week;
+    if ( $dow == 7 ) {
+        $dow = 0;
+    }
+    if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
+        return 1;
+    }
+    $dt->truncate( to => 'days' );
+    my $day   = $dt->day;
+    my $month = $dt->month;
+    if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
+        return 1;
+    }
+    if ( $self->{exception_holidays}->contains($dt) ) {
+        return 1;
+    }
+    if ( $self->{single_holidays}->contains($dt) ) {
+        return 1;
+    }
+
+    # damn have to go to work after all
+    return 0;
+}
+
+sub days_between {
+    my $self     = shift;
+    my $start_dt = shift;
+    my $end_dt   = shift;
+    $start_dt->truncate( to => 'hours' );
+    $end_dt->truncate( to => 'hours' );
+
+    # start and end should not be closed days
+    my $duration = $end_dt - $start_dt;
+    $start_dt->truncate( to => 'days' );
+    $end_dt->truncate( to => 'days' );
+    while ( DateTime->compare( $start_dt, $end_dt ) == -1 ) {
+        $start_dt->add( days => 1 );
+        if ( $self->is_holiday($start_dt) ) {
+            $duration->subtract( days => 1 );
+        }
+    }
+    return $duration;
+
+}
+
+sub _mockinit {
+    my $self = shift;
+    $self->{weekly_closed_days} = [ 1, 0, 0, 0, 0, 0, 0 ];    # Sunday only
+    $self->{day_month_closed_days} = { 6 => { 16 => 1, } };
+    my $dates = [];
+    $self->{exception_holidays} =
+      DateTime::Set->from_datetimes( dates => $dates );
+    my $special = DateTime->new(
+        year      => 2011,
+        month     => 6,
+        day       => 1,
+        time_zone => 'Europe/London',
+    );
+    push @{$dates}, $special;
+    $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
+    $self->{days_mode} = 'Calendar';
+    return;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Koha::Calendar - Object containing a branches calendar
+
+=head1 VERSION
+
+This documentation refers to Koha::Calendar version 0.0.1
+
+=head1 SYNOPSIS
+
+  use Koha::Calendat
+
+  my $c = Koha::Calender->new( branchcode => 'MAIN' );
+  my $dt = DateTime->now();
+
+  # are we open
+  $open = $c->is_holiday($dt);
+  # when will item be due if loan period = $dur (a DateTime::Duration object)
+  $duedate = $c->addDate($dt,$dur,'days');
+
+
+=head1 DESCRIPTION
+
+  Implements those features of C4::Calendar needed for Staffs Rolling Loans
+
+=head1 METHODS
+
+=head2 new : Create a calendar object
+
+my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );
+
+The option branchcode is required
+
+
+=head2 addDate
+
+    my $dt = $calendar->addDate($date, $dur, $unit)
+
+C<$date> is a DateTime object representing the starting date of the interval.
+
+C<$offset> is a DateTime::Duration to add to it
+
+C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
+
+Currently unit is only used to invoke Staffs return Monday at 10 am rule this
+parameter will be removed when issuingrules properly cope with that
+
+
+=head2 is_holiday
+
+$yesno = $calendar->is_holiday($dt);
+
+passed at DateTime object returns 1 if it is a closed day
+0 if not according to the calendar
+
+=head2 days_between
+
+$duration = $calendar->days_between($start_dt, $end_dt);
+
+Passed two dates returns a DateTime::Duration object measuring the length between them
+ignoring closed days
+
+=head1 DIAGNOSTICS
+
+Will croak if not passed a branchcode in new
+
+=head1 BUGS AND LIMITATIONS
+
+This only contains a limited subset of the functionality in C4::Calendar
+Only enough to support Staffs Rolling loans
+
+=head1 AUTHOR
+
+Colin Campbell colin.campbell@ptfs-europe.com
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
diff --git a/Koha/DateUtils.pm b/Koha/DateUtils.pm
new file mode 100644 (file)
index 0000000..e2e92bd
--- /dev/null
@@ -0,0 +1,187 @@
+package Koha::DateUtils;
+
+# Copyright (c) 2011 PTFS-Europe Ltd.
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA  02111-1307 USA
+
+use strict;
+use warnings;
+use 5.010;
+use DateTime;
+use DateTime::Format::DateParse;
+use C4::Context;
+
+use base 'Exporter';
+use version; our $VERSION = qv('1.0.0');
+
+our @EXPORT = (
+    qw( dt_from_string output_pref format_sqldatetime output_pref_due format_sqlduedatetime)
+);
+
+=head1 DateUtils
+
+Koha::DateUtils - Transitional wrappers to ease use of DateTime
+
+=head1 DESCRIPTION
+
+Koha has historically only used dates not datetimes and been content to
+handle these as strings. It also has confused formatting with actual dates
+this is a temporary module for wrappers to hide the complexity of switch to DateTime
+
+=cut
+
+=head2 dt_ftom_string
+
+$dt = dt_from_string($date_string, [$format, $timezone ]);
+
+Passed a date string returns a DateTime object format and timezone default
+to the system preferences. If the date string is empty DateTime->now is returned
+
+=cut
+
+sub dt_from_string {
+    my ( $date_string, $date_format, $tz ) = @_;
+    if ( !$tz ) {
+        $tz = C4::Context->tz;
+    }
+    if ( !$date_format ) {
+        $date_format = C4::Context->preference('dateformat');
+    }
+    if ($date_string) {
+        if ( ref($date_string) eq 'DateTime' ) {    # already a dt return it
+            return $date_string;
+        }
+
+        if ( $date_format eq 'metric' ) {
+            $date_string =~ s#-#/#g;
+            $date_string =~ s/^00/01/;    # system allows the 0th of the month
+            $date_string =~ s#^(\d{1,2})/(\d{1,2})#$2/$1#;
+        } else {
+            if ( $date_format eq 'iso' ) {
+                $date_string =~ s/-00/-01/;
+                if ( $date_string =~ m/^0000-0/ ) {
+                    return;               # invalid date in db
+                }
+            } elsif ( $date_format eq 'us' ) {
+                $date_string =~ s#-#/#g;
+                $date_string =~ s[/00/][/01/];
+            } elsif ( $date_format eq 'sql' ) {
+                $date_string =~
+s/(\d{4})(\d{2})(\d{2})\s+(\d{2})(\d{2})(\d{2})/$1-$2-$3T$4:$5:$6/;
+                $date_string =~ s/00T/01T/;
+            }
+        }
+        return DateTime::Format::DateParse->parse_datetime( $date_string,
+            $tz->name() );
+    }
+    return DateTime->now( time_zone => $tz );
+
+}
+
+=head2 output_pref
+
+$date_string = output_pref($dt, [$format] );
+
+Returns a string containing the time & date formatted as per the C4::Context setting
+
+A second parameter allows overriding of the syspref value. This is for testing only
+In usage use the DateTime objects own methods for non standard formatting
+
+=cut
+
+sub output_pref {
+    my $dt         = shift;
+    my $force_pref = shift;    # if testing we want to override Context
+    my $pref =
+      defined $force_pref ? $force_pref : C4::Context->preference('dateformat');
+    given ($pref) {
+        when (/^iso/) {
+            return $dt->strftime('%Y-%m-%d %H:%M');
+        }
+        when (/^metric/) {
+            return $dt->strftime('%d/%m/%Y %H:%M');
+        }
+        when (/^us/) {
+            return $dt->strftime('%m/%d/%Y %H:%M');
+        }
+        default {
+            return $dt->strftime('%Y-%m-%d %H:%M');
+        }
+
+    }
+    return;
+}
+
+=head2 output_pref_due
+
+$date_string = output_pref_due($dt, [$format] );
+
+Returns a string containing the time & date formatted as per the C4::Context setting
+
+A second parameter allows overriding of the syspref value. This is for testing only
+In usage use the DateTime objects own methods for non standard formatting
+
+This is effectivelyt a wrapper around output_pref for due dates
+the time portion is stripped if it is '23:59'
+
+=cut
+
+sub output_pref_due {
+    my $disp_str = output_pref(@_);
+    $disp_str =~ s/ 23:59//;
+    return $disp_str;
+}
+
+=head2 format_sqldatetime
+
+$string = format_sqldatetime( $string_as_returned_from_db );
+
+a convenience routine for calling dt_from_string and formatting the result
+with output_pref as it is a frequent activity in scripts
+
+=cut
+
+sub format_sqldatetime {
+    my $str        = shift;
+    my $force_pref = shift;    # if testing we want to override Context
+    if ( defined $str && $str =~ m/^\d{4}-\d{2}-\d{2}/ ) {
+        my $dt = dt_from_string( $str, 'sql' );
+        $dt->truncate( to => 'minutes' );
+        return output_pref( $dt, $force_pref );
+    }
+    return q{};
+}
+
+=head2 format_sqlduedatetime
+
+$string = format_sqldatetime( $string_as_returned_from_db );
+
+a convenience routine for calling dt_from_string and formatting the result
+with output_pref_due as it is a frequent activity in scripts
+
+=cut
+
+sub format_sqlduedatetime {
+    my $str        = shift;
+    my $force_pref = shift;    # if testing we want to override Context
+    if ( defined $str && $str =~ m/^\d{4}-\d{2}-\d{2}/ ) {
+        my $dt = dt_from_string( $str, 'sql' );
+        $dt->truncate( to => 'minutes' );
+        return output_pref_due( $dt, $force_pref );
+    }
+    return q{};
+}
+
+1;
index dcfcfc5..0b8012f 100755 (executable)
@@ -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
@@ -117,6 +117,7 @@ elsif ($op eq 'add') {
     $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');
@@ -126,9 +127,9 @@ elsif ($op eq 'add') {
     $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") {
index db38551..da62e07 100755 (executable)
@@ -39,6 +39,7 @@ use C4::Tags qw(get_tags);
 use C4::VirtualShelves;
 use C4::XSLT;
 use C4::Images;
+use Koha::DateUtils;
 
 # use Smart::Comments;
 
@@ -192,9 +193,10 @@ foreach my $item (@items) {
     $item->{imageurl} = defined $item->{itype} ? getitemtypeimagelocation('intranet', $itemtypes->{ $item->{itype} }{imageurl})
                                                : '';
 
-       foreach (qw(datedue datelastseen onloan)) {
+       foreach (qw(datelastseen onloan)) {
                $item->{$_} = format_date($item->{$_});
-       }
+    }
+    $item->{datedue} = format_sqldatetime($item->{datedue});
     # item damaged, lost, withdrawn loops
     $item->{itemlostloop} = GetAuthorisedValues($authvalcode_items_itemlost, $item->{itemlost}) if $authvalcode_items_itemlost;
     if ($item->{damaged}) {
index 3fb936e..288182c 100755 (executable)
@@ -25,8 +25,8 @@ use C4::Output;
 
 use C4::Circulation;    # GetBiblioIssues
 use C4::Biblio;    # GetBiblio GetBiblioFromItemNumber
-use C4::Dates qw/format_date/;
 use C4::Search;                # enabled_staff_search_views
+use Koha::DateUtils;
 
 my $query = new CGI;
 my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
@@ -67,11 +67,11 @@ if ($itemnumber){
                %{$biblio[0]},
        );
 } 
-foreach (@$issues){
-       $_->{date_due}   = format_date($_->{date_due});
-       $_->{issuedate}  = format_date($_->{issuedate});
-       $_->{returndate} = format_date($_->{returndate});
-       $_->{lastreneweddate} = format_date($_->{lastreneweddate});
+foreach (@{$issues}){
+       $_->{date_due}   = format_sqldatetime($_->{date_due});
+       $_->{issuedate}  = format_sqldatetime($_->{issuedate});
+       $_->{returndate} = format_sqldatetime($_->{returndate});
+       $_->{lastreneweddate} = format_sqldatetime($_->{lastreneweddate});
 }
 $template->param(
     total        => scalar @$issues,
index c9d202c..9d5465b 100755 (executable)
@@ -35,6 +35,7 @@ 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;
 
index 5c1dcaf..dc89d60 100755 (executable)
@@ -22,12 +22,12 @@ use C4::Context;
 use CGI;
 use C4::Output;
 use C4::Auth;
-use C4::Dates qw/format_date/;
 use C4::Overdues;    # AddNotifyLine
 use C4::Biblio;
 use C4::Koha;
 use C4::Debug;
 use C4::Branch;
+use Data::Dumper;
 
 =head1 branchoverdues.pl
 
@@ -97,7 +97,6 @@ elsif ( $input->param('action') eq 'remove' ) {
 
 my @overduesloop;
 my @getoverdues = GetOverduesForBranch( $default, $location );
-use Data::Dumper;
 $debug and warn "HERE : $default / $location" . Dumper(@getoverdues);
 # search for location authorised value
 my ($tag,$subfield) = GetMarcFromKohaField('items.location','');
@@ -114,7 +113,8 @@ foreach my $num (@getoverdues) {
     if ($record){
         $overdueforbranch{'subtitle'} = GetRecordValue('subtitle',$record,'')->[0]->{subfield};
     }
-    $overdueforbranch{'date_due'}          = format_date( $num->{'date_due'} );
+    my $dt = dt_from_string($num->{date_due}, 'sql');
+    $overdueforbranch{'date_due'}          = output_pref($dt);
     $overdueforbranch{'title'}             = $num->{'title'};
     $overdueforbranch{'description'}       = $num->{'description'};
     $overdueforbranch{'barcode'}           = $num->{'barcode'};
@@ -151,7 +151,6 @@ foreach my $num (@getoverdues) {
 # initiate the templates for the overdueloop
 $template->param(
     overduesloop => \@overduesloop,
-    show_date    => format_date(C4::Dates->today('iso')),
     location     => $location,
 );
 
index 6928966..082b3d4 100755 (executable)
@@ -4,6 +4,7 @@
 
 # Copyright 2000-2002 Katipo Communications
 # copyright 2010 BibLibre
+# Copyright 2011 PTFS-Europe Ltd.
 #
 # This file is part of Koha.
 #
@@ -21,7 +22,7 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 use strict;
-#use warnings; FIXME - Bug 2505
+use warnings;
 use CGI;
 use C4::Output;
 use C4::Auth qw/:DEFAULT get_session/;
@@ -36,6 +37,7 @@ use C4::Reserves;
 use C4::Context;
 use CGI::Session;
 use C4::Members::Attributes qw(GetBorrowerAttributes);
+use Koha::DateUtils;
 
 use Date::Calc qw(
   Today
@@ -144,14 +146,9 @@ my $duedatespec_allow = C4::Context->preference('SpecifyDueDate');
 if($duedatespec_allow){
     if ($duedatespec) {
         if ($duedatespec =~ C4::Dates->regexp('syspref')) {
-            my $tempdate = C4::Dates->new($duedatespec);
-#           if ($tempdate and $tempdate->output('iso') gt C4::Dates->new()->output('iso')) {
-#               # i.e., it has to be later than today/now
-                $datedue = $tempdate;
-#           } else {
-#               $invalidduedate = 1;
-#               $template->param(IMPOSSIBLE=>1, INVALID_DATE=>$duedatespec);
-#           }
+                $datedue = dt_from_string($duedatespec);
+                $datedue->set_hour(23);
+                $datedue->set_minute(59);
         } else {
             $invalidduedate = 1;
             $template->param(IMPOSSIBLE=>1, INVALID_DATE=>$duedatespec);
@@ -451,9 +448,10 @@ sub build_issue_data {
         $totalprice += $it->{'replacementprice'};
         $it->{'itemtype'} = $itemtypeinfo->{'description'};
         $it->{'itemtype_image'} = $itemtypeinfo->{'imageurl'};
-        $it->{'dd'} = format_date($it->{'date_due'});
-        $it->{'displaydate'} = format_date($it->{'issuedate'});
-        $it->{'od'} = ( $it->{'date_due'} lt $todaysdate ) ? 1 : 0 ;
+        $it->{'dd'} = output_pref($it->{'date_due'});
+        $it->{'displaydate'} = output_pref($it->{'issuedate'});
+        #$it->{'od'} = ( $it->{'date_due'} lt $todaysdate ) ? 1 : 0 ;
+        $it->{'od'} = $it->{'overdue'};
         ($it->{'author'} eq '') and $it->{'author'} = ' ';
         $it->{'renew_failed'} = $renew_failed{$it->{'itemnumber'}};
 
index c25a1d6..a4cfa7a 100755 (executable)
@@ -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') || '';
@@ -42,6 +43,14 @@ my $branchfilter    = $input->param('branch') || '';
 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";
 
@@ -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;
@@ -318,9 +329,10 @@ if ($noreport) {
             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},
@@ -389,7 +401,7 @@ if ($noreport) {
 
     $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,
index e142117..83817a8 100755 (executable)
@@ -4,6 +4,7 @@
 #           2006 SAN-OP
 #           2007-2010 BibLibre, Paul POULAIN
 #           2010 Catalyst IT
+#           2011 PTFS-Europe Ltd.
 #
 # This file is part of Koha.
 #
@@ -30,13 +31,11 @@ use strict;
 #use warnings; FIXME - Bug 2505
 
 use CGI;
+use DateTime;
 use C4::Context;
 use C4::Auth qw/:DEFAULT get_session/;
 use C4::Output;
 use C4::Circulation;
-use C4::Dates qw/format_date/;
-use Date::Calc qw/Add_Delta_Days/;
-use C4::Calendar;
 use C4::Print;
 use C4::Reserves;
 use C4::Biblio;
@@ -45,6 +44,8 @@ use C4::Members;
 use C4::Branch; # GetBranches GetBranchName
 use C4::Koha;   # FIXME : is it still useful ?
 use C4::RotatingCollections;
+use Koha::DateUtils;
+use Koha::Calendar;
 
 my $query = new CGI;
 
@@ -175,10 +176,9 @@ my $dropboxmode = $query->param('dropboxmode');
 my $dotransfer  = $query->param('dotransfer');
 my $canceltransfer = $query->param('canceltransfer');
 my $dest = $query->param('dest');
-my $calendar    = C4::Calendar->new( branchcode => $userenv_branch );
+my $calendar    = Koha::Calendar->new( branchcode => $userenv_branch );
 #dropbox: get last open day (today - 1)
-my $today       = C4::Dates->new();
-my $today_iso   = $today->output('iso');
+my $today       = DateTime->now( time_zone => C4::Context->tz());
 my $dropboxdate = $calendar->addDate($today, -1);
 if ($dotransfer){
 # An item has been returned to a branch other than the homebranch, and the librarian has chosen to initiate a transfer
@@ -249,13 +249,14 @@ if ($barcode) {
     );
 
     if ($returned) {
-        my $duedate = $issueinformation->{'date_due'};
+        my $time_now = DateTime->now( time_zone => C4::Context->tz )->truncate( to => 'minutes');
+        my $duedate = $issueinformation->{date_due}->strftime('%Y-%m-%d %H:%M');
         $returneditems{0}      = $barcode;
         $riborrowernumber{0}   = $borrower->{'borrowernumber'};
         $riduedate{0}          = $duedate;
         $input{borrowernumber} = $borrower->{'borrowernumber'};
         $input{duedate}        = $duedate;
-        $input{return_overdue} = 1 if ($duedate and $duedate lt $today->output('iso'));
+        $input{return_overdue} = 1 if (DateTime->compare($issueinformation->{date_due}, $time_now) == -1);
         push( @inputloop, \%input );
 
         if ( C4::Context->preference("FineNotifyAtCheckin") ) {
@@ -463,7 +464,7 @@ foreach my $code ( keys %$messages ) {
     elsif ( $code eq 'Wrongbranch' ) {
     }
     elsif ( $code eq 'Debarred' ) {
-        $err{debarred}            = format_date( $messages->{'Debarred'} );
+        $err{debarred}            = $messages->{'Debarred'};
         $err{debarcardnumber}     = $borrower->{cardnumber};
         $err{debarborrowernumber} = $borrower->{borrowernumber};
         $err{debarname}           = "$borrower->{firstname} $borrower->{surname}";
@@ -519,7 +520,7 @@ if ($borrower) {
             {
                 my $biblio = GetBiblioFromItemNumber( $item->{'itemnumber'});
                 push @itemloop, {
-                    duedate   => format_date($item->{'date_due'}),
+                    duedate   => format_sqldatetime($item->{date_due}),
                     biblionum => $biblio->{'biblionumber'},
                     barcode   => $biblio->{'barcode'},
                     title     => $biblio->{'title'},
@@ -545,7 +546,6 @@ if ($borrower) {
         riborfirstname   => $borrower->{'firstname'}
     );
 }
-
 #set up so only the last 8 returned items display (make for faster loading pages)
 my $returned_counter = ( C4::Context->preference('numReturnedItemsToShow') ) ? C4::Context->preference('numReturnedItemsToShow') : 8;
 my $count = 0;
@@ -555,15 +555,16 @@ foreach ( sort { $a <=> $b } keys %returneditems ) {
     my %ri;
     if ( $count++ < $returned_counter ) {
         my $bar_code = $returneditems{$_};
-        my $duedate = $riduedate{$_};
-        if ($duedate) {
-            my @tempdate = split( /-/, $duedate );
-            $ri{year}  = $tempdate[0];
-            $ri{month} = $tempdate[1];
-            $ri{day}   = $tempdate[2];
-            $ri{duedate} = format_date($duedate);
+        if ($riduedate{$_}) {
+            my $duedate = dt_from_string( $riduedate{$_}, 'sql');
+            $ri{year}  = $duedate->year();
+            $ri{month} = $duedate->month();
+            $ri{day}   = $duedate->day();
+            $ri{hour}   = $duedate->hour();
+            $ri{minute}   = $duedate->minute();
+            $ri{duedate} = output_pref($duedate);
             my ($b)      = GetMemberDetails( $riborrowernumber{$_}, 0 );
-            $ri{return_overdue} = 1 if ($duedate lt $today->output('iso'));
+            $ri{return_overdue} = 1 if (DateTime->compare($duedate, DateTime->now()) == -1 );
             $ri{borrowernumber} = $b->{'borrowernumber'};
             $ri{borcnum}        = $b->{'cardnumber'};
             $ri{borfirstname}   = $b->{'firstname'};
@@ -600,7 +601,6 @@ foreach ( sort { $a <=> $b } keys %returneditems ) {
     }
     push @riloop, \%ri;
 }
-
 $template->param(
     riloop         => \@riloop,
     genbrname      => $branches->{$userenv_branch}->{'branchname'},
@@ -610,7 +610,7 @@ $template->param(
     errmsgloop     => \@errmsgloop,
     exemptfine     => $exemptfine,
     dropboxmode    => $dropboxmode,
-    dropboxdate    => $dropboxdate->output(),
+    dropboxdate    => output_pref($dropboxdate),
     overduecharges => $overduecharges,
     soundon        => C4::Context->preference("SoundOn"),
 );
diff --git a/installer/data/mysql/atomicupdate/hourlyloans.sql b/installer/data/mysql/atomicupdate/hourlyloans.sql
new file mode 100644 (file)
index 0000000..664142d
--- /dev/null
@@ -0,0 +1 @@
+alter table issuingrules add column lengthunit varchar(10) default 'days' after issuelength;
diff --git a/installer/data/mysql/atomicupdate/issuedate_times.pl b/installer/data/mysql/atomicupdate/issuedate_times.pl
new file mode 100644 (file)
index 0000000..864109a
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use C4::Context;
+
+my $dbh = C4::Context->dbh;
+
+$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");
+$dbh->do(q{update issues set date_due = addtime(date_due, '0 23:0:0') where hour(date_due) = 0});
index f5d8a24..a26faa9 100644 (file)
@@ -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,
@@ -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`),
index 32099d8..98f660f 100755 (executable)
@@ -4932,7 +4932,6 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
     SetVersion($DBversion);
 }
 
-
 $DBversion = "3.07.00.029";
 if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
     my $installer = C4::Installer->new();
@@ -4994,6 +4993,20 @@ if ( C4::Context->preference("Version") lt TransformToNum($DBversion) ) {
     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)
index fd5124b..ae6b620 100644 (file)
@@ -362,7 +362,7 @@ OPAC:
             - 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:
index 5d5be3e..db78296 100644 (file)
@@ -72,7 +72,8 @@ for="tobranch"><strong>Clone these rules to:</strong></label> <input type="hidde
                 <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>
@@ -80,7 +81,7 @@ for="tobranch"><strong>Clone these rules to:</strong></label> <input type="hidde
                 <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 %]
@@ -108,6 +109,9 @@ for="tobranch"><strong>Clone these rules to:</strong></label> <input type="hidde
                                                                [% 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>
@@ -146,6 +150,12 @@ for="tobranch"><strong>Clone these rules to:</strong></label> <input type="hidde
                     </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>
index 65ce0f6..033d3cf 100644 (file)
@@ -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' %]
@@ -331,7 +332,7 @@ function Dopop(link) {
                         <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 ) %]
index bd19584..42d0ec0 100644 (file)
@@ -396,8 +396,8 @@ function validate1(date) {
 
 <div id="finesholdsissues" class="toptabs">
        <ul>
-               <li><a href="/cgi-bin/koha/members/moremember.pl#checkedout">[% issuecount %] Checkout(s)</a></li>
-    [% IF ( relissuecount ) %]
+               <li><a href="/cgi-bin/koha/members/moremember.pl#checkedout">[% issueloop.size %] Checkout(s)</a></li>
+    [% IF relissueloop.size %]
         <li><a href="/cgi-bin/koha/members/moremember.pl#relissues">Relatives' Checkouts</a></li>
     [% END %]
                <li><a href="/cgi-bin/koha/members/moremember.pl#finesandcharges">Fines &amp; Charges</a></li>
@@ -528,7 +528,7 @@ function validate1(date) {
 </div>
 
 
-[% IF ( relissuecount ) %]
+[% IF relissueloop %]
 <div id="relissues">
  <table id="relissuest">
     <thead>
index 5a988f9..c2f0d53 100755 (executable)
@@ -56,6 +56,8 @@ use C4::Members::Attributes qw(GetBorrowerAttributes);
 
 #use Smart::Comments;
 #use Data::Dumper;
+use DateTime;
+use Koha::DateUtils;
 
 use vars qw($debug);
 
@@ -65,7 +67,7 @@ BEGIN {
 
 my $dbh = C4::Context->dbh;
 
-my $input = new CGI;
+my $input = CGI->new;
 $debug or $debug = $input->param('debug') || 0;
 my $print = $input->param('print');
 my $override_limit = $input->param("override_limit") || 0;
@@ -247,86 +249,15 @@ my $relissue    = [];
 if ( @borrowernumbers ) {
     $relissue    = GetPendingIssues(@borrowernumbers);
 }
-my $issuecount     = @{$issue};
-my $relissuecount  = @{$relissue};
 my $roaddetails = &GetRoadTypeDetails( $data->{'streettype'} );
-my $today       = POSIX::strftime("%Y-%m-%d", localtime);      # iso format
-my @issuedata;
+my $today       = DateTime->now( time_zone => C4::Context->tz);
+$today->truncate(to => 'days');
 my @borrowers_with_issues;
 my $overdues_exist = 0;
 my $totalprice = 0;
 
-my @issuedata = build_issue_data($issue, $issuecount);
-my @relissuedata = build_issue_data($relissue, $relissuecount);
-
-sub build_issue_data {
-    my $issue = shift;
-    my $issuecount = shift;
-
-    my $localissue;
-
-    for ( my $i = 0 ; $i < $issuecount ; $i++ ) {
-        my $datedue = $issue->[$i]{'date_due'};
-        my $issuedate = $issue->[$i]{'issuedate'};
-        $issue->[$i]{'date_due'}  = C4::Dates->new($issue->[$i]{'date_due'}, 'iso')->output('syspref');
-        $issue->[$i]{'issuedate'} = C4::Dates->new($issue->[$i]{'issuedate'},'iso')->output('syspref');
-        my $biblionumber = $issue->[$i]{'biblionumber'};
-        $issue->[$i]{'issuingbranchname'} = GetBranchName($issue->[$i]{'branchcode'});
-        my %row = %{ $issue->[$i] };
-        $totalprice += $issue->[$i]{'replacementprice'};
-        $row{'replacementprice'} = $issue->[$i]{'replacementprice'};
-        # item lost, damaged loops
-        if ($row{'itemlost'}) {
-            my $fw = GetFrameworkCode($issue->[$i]{'biblionumber'});
-            my $category = GetAuthValCode('items.itemlost',$fw);
-            my $lostdbh = C4::Context->dbh;
-            my $sth = $lostdbh->prepare("select lib from authorised_values where category=? and authorised_value =? ");
-            $sth->execute($category, $row{'itemlost'});
-            my $loststat = $sth->fetchrow;
-            if ($loststat) {
-               $row{'itemlost'} = $loststat;
-            }
-        }
-        if ($row{'damaged'}) {
-            my $fw = GetFrameworkCode($issue->[$i]{'biblionumber'});
-            my $category = GetAuthValCode('items.damaged',$fw);
-            my $damageddbh = C4::Context->dbh;
-            my $sth = $damageddbh->prepare("select lib from authorised_values where category=? and authorised_value =? ");
-            $sth->execute($category, $row{'damaged'});
-            my $damagedstat = $sth->fetchrow;
-            if ($damagedstat) {
-               $row{'itemdamaged'} = $damagedstat;
-            }
-        }
-        # end lost, damaged
-        if ( $datedue lt $today ) {
-            $overdues_exist = 1;
-            $row{'red'} = 1;
-        }
-         if ( $issuedate eq $today ) {
-            $row{'today'} = 1; 
-         }
-
-        #find the charge for an item
-        my ( $charge, $itemtype ) =
-          GetIssuingCharges( $issue->[$i]{'itemnumber'}, $issue->[$i]{'borrowernumber'} );
-
-        my $itemtypeinfo = getitemtypeinfo($itemtype);
-        $row{'itemtype_description'} = $itemtypeinfo->{description};
-        $row{'itemtype_image'}       = $itemtypeinfo->{imageurl};
-
-        $row{'charge'} = sprintf( "%.2f", $charge );
-
-        my ( $renewokay,$renewerror ) = CanBookBeRenewed( $issue->[$i]{'borrowernumber'}, $issue->[$i]{'itemnumber'}, $override_limit );
-        $row{'norenew'} = !$renewokay;
-        $row{'can_confirm'} = ( !$renewokay && $renewerror ne 'on_reserve' );
-        $row{"norenew_reason_$renewerror"} = 1 if $renewerror;
-        $row{'renew_failed'}  = $renew_failed{ $issue->[$i]{'itemnumber'} };
-        $row{'return_failed'} = $return_failed{$issue->[$i]{'barcode'}};   
-        push( @$localissue, \%row );
-    }
-    return $localissue;
-}
+my @issuedata = build_issue_data($issue);
+my @relissuedata = build_issue_data($relissue);
 
 
 ### ###############################################################################
@@ -468,8 +399,6 @@ $template->param(
     totaldue_raw    => $total,
     issueloop       => @issuedata,
     relissueloop    => @relissuedata,
-       issuecount      => $issuecount,
-    relissuecount   => $relissuecount,
     overdues_exist  => $overdues_exist,
     error           => $error,
     $error          => 1,
@@ -484,3 +413,89 @@ $template->param(
 );
 
 output_html_with_http_headers $input, $cookie, $template->output;
+
+sub build_issue_data {
+    my $issues = shift;
+
+    my $localissue;
+
+    foreach my $issue ( @{$issues} ) {
+
+        # Getting borrower details
+        my $memberdetails = GetMemberDetails( $issue->{borrowernumber} );
+        $issue->{borrowername} =
+          $memberdetails->{firstname} . ' ' . $memberdetails->{surname};
+        $issue->{cardnumber} = $memberdetails->{cardnumber};
+        my $issuedate;
+        if ($issue->{issuedate} ) {
+           $issuedate = $issue->{issuedate}->clone();
+        }
+
+        $issue->{date_due}  = output_pref( $issue->{date_due} );
+        $issue->{issuedate} = output_pref( $issue->{issuedate} ) if defined $issue->{issuedate};
+        my $biblionumber = $issue->{biblionumber};
+        my %row          = %{$issue};
+        $totalprice += $issue->{replacementprice};
+
+        # item lost, damaged loops
+        if ( $row{'itemlost'} ) {
+            my $fw       = GetFrameworkCode( $issue->{biblionumber} );
+            my $category = GetAuthValCode( 'items.itemlost', $fw );
+            my $lostdbh  = C4::Context->dbh;
+            my $sth      = $lostdbh->prepare(
+"select lib from authorised_values where category=? and authorised_value =? "
+            );
+            $sth->execute( $category, $row{'itemlost'} );
+            my $loststat = $sth->fetchrow;
+            if ($loststat) {
+                $row{'itemlost'} = $loststat;
+            }
+        }
+        if ( $row{'damaged'} ) {
+            my $fw         = GetFrameworkCode( $issue->{biblionumber} );
+            my $category   = GetAuthValCode( 'items.damaged', $fw );
+            my $damageddbh = C4::Context->dbh;
+            my $sth        = $damageddbh->prepare(
+"select lib from authorised_values where category=? and authorised_value =? "
+            );
+            $sth->execute( $category, $row{'damaged'} );
+            my $damagedstat = $sth->fetchrow;
+            if ($damagedstat) {
+                $row{'itemdamaged'} = $damagedstat;
+            }
+        }
+
+        # end lost, damaged
+        if ( $issue->{overdue} ) {
+            $overdues_exist = 1;
+            $row{red} = 1;
+        }
+        if ($issuedate) {
+            $issuedate->truncate( to => 'days' );
+            if ( DateTime->compare( $issuedate, $today ) == 0 ) {
+                $row{today} = 1;
+            }
+        }
+
+        #find the charge for an item
+        my ( $charge, $itemtype ) =
+          GetIssuingCharges( $issue->{itemnumber}, $borrowernumber );
+
+        my $itemtypeinfo = getitemtypeinfo($itemtype);
+        $row{'itemtype_description'} = $itemtypeinfo->{description};
+        $row{'itemtype_image'}       = $itemtypeinfo->{imageurl};
+
+        $row{'charge'} = sprintf( "%.2f", $charge );
+
+        my ( $renewokay, $renewerror ) =
+          CanBookBeRenewed( $borrowernumber, $issue->{itemnumber},
+            $override_limit );
+        $row{'norenew'} = !$renewokay;
+        $row{'can_confirm'} = ( !$renewokay && $renewerror ne 'on_reserve' );
+        $row{"norenew_reason_$renewerror"} = 1 if $renewerror;
+        $row{renew_failed}  = $renew_failed{ $issue->{itemnumber} };
+        $row{return_failed} = $return_failed{ $issue->{barcode} };
+        push( @{$localissue}, \%row );
+    }
+    return $localissue;
+}
index d4ecf65..2645eda 100755 (executable)
@@ -30,6 +30,7 @@ use C4::Output;
 use C4::Members;
 use C4::Branch;
 use List::MoreUtils qw/any uniq/;
+use Koha::DateUtils;
 
 use C4::Dates qw/format_date/;
 use C4::Members::Attributes qw(GetBorrowerAttributes);
@@ -75,9 +76,9 @@ foreach my $issue (@{$issues}){
        $line{title}           = $issue->{'title'};
        $line{author}          = $issue->{'author'};
        $line{classification}  = $issue->{'classification'} || $issue->{'itemcallnumber'};
-       $line{date_due}        = format_date($issue->{'date_due'});
-       $line{returndate}      = format_date($issue->{'returndate'});
-       $line{issuedate}       = format_date($issue->{'issuedate'});
+       $line{date_due}        = format_sqldatetime($issue->{date_due});
+       $line{returndate}      = format_sqldatetime($issue->{returndate});
+       $line{issuedate}       = format_sqldatetime($issue->{issuedate});
        $line{issuingbranch}   = GetBranchName($issue->{'branchcode'});
        $line{renewals}        = $issue->{'renewals'};
        $line{barcode}         = $issue->{'barcode'};
index 09c4017..de00846 100755 (executable)
@@ -54,7 +54,7 @@ use C4::Letters;
 use C4::Members;
 use C4::Members::Messaging;
 use C4::Overdues;
-use C4::Dates qw/format_date/;
+use Koha::DateUtils;
 
 
 # These are defaults for command line options.
@@ -347,6 +347,12 @@ sub parse_letter {
     );
 }
 
+sub format_date {
+    my $date_string = shift;
+    my $dt=dt_from_string($date_string);
+    return output_pref($dt);
+}
+
 1;
 
 __END__
index 73cdf9f..5124f3c 100755 (executable)
@@ -9,6 +9,7 @@
 #  This script is meant to be run nightly out of cron.
 
 # Copyright 2000-2002 Katipo Communications
+# Copyright 2011 PTFS-Europe Ltd
 #
 # This file is part of Koha.
 #
 # with Koha; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-
-# FIXME: use FinesMode as described or change syspref description
 use strict;
-#use warnings; FIXME - Bug 2505
-
-BEGIN {
-    # find Koha's Perl modules
-    # test carefully before changing this
-    use FindBin;
-    eval { require "$FindBin::Bin/kohalib.pl" };
-}
-
-use Date::Calc qw/Date_to_Days/;
+use warnings;
+use 5.010;
 
 use C4::Context;
-use C4::Circulation;
 use C4::Overdues;
-use C4::Calendar qw();  # don't need any exports from Calendar
-use C4::Biblio;
-use C4::Debug;  # supplying $debug and $cgi_debug
 use Getopt::Long;
+use Carp;
+use File::Spec;
+
+use Koha::Calendar;
+use Koha::DateUtils;
 
-my $help = 0;
-my $verbose = 0;
+my $help;
+my $verbose;
 my $output_dir;
 
-GetOptions( 'h|help'    => \$help,
-            'v|verbose' => \$verbose,
-            'o|out:s'   => \$output_dir,
-       );
+GetOptions(
+    'h|help'    => \$help,
+    'v|verbose' => \$verbose,
+    'o|out:s'   => \$output_dir,
+);
 my $usage = << 'ENDUSAGE';
 
 This script calculates and charges overdue fines
@@ -70,102 +63,111 @@ This script has the following parameters :
 
 ENDUSAGE
 
-die $usage if $help;
-
-use vars qw(@borrower_fields @item_fields @other_fields);
-use vars qw($fldir $libname $control $mode $delim $dbname $today $today_iso $today_days);
-use vars qw($filename);
-
-CHECK {
-    @borrower_fields = qw(cardnumber categorycode surname firstname email phone address citystate);
-        @item_fields = qw(itemnumber barcode date_due);
-       @other_fields = qw(type days_overdue fine);
-    $libname = C4::Context->preference('LibraryName');
-    $control = C4::Context->preference('CircControl');
-    $mode    = C4::Context->preference('finesMode');
-    $dbname  = C4::Context->config('database');
-    $delim   = "\t"; # ?  C4::Context->preference('delimiter') || "\t";
-
+if ($help) {
+    print $usage;
+    exit;
 }
 
-INIT {
-    $debug and print "Each line will contain the following fields:\n",
-        "From borrowers : ", join(', ', @borrower_fields), "\n",
-        "From items : ", join(', ', @item_fields), "\n",
-        "Per overdue: ", join(', ', @other_fields), "\n",
-        "Delimiter: '$delim'\n";
-}
+my @borrower_fields =
+  qw(cardnumber categorycode surname firstname email phone address citystate);
+my @item_fields  = qw(itemnumber barcode date_due);
+my @other_fields = qw(type days_overdue fine);
+my $libname      = C4::Context->preference('LibraryName');
+my $control      = C4::Context->preference('CircControl');
+my $mode         = C4::Context->preference('finesMode');
+my $delim = "\t";    # ?  C4::Context->preference('delimiter') || "\t";
+
+my %is_holiday;
+my $today = DateTime->now( time_zone => C4::Context->tz() );
+my $filename = get_filename($output_dir);
+
+open my $fh, '>>', $filename or croak "Cannot write file $filename: $!";
+print {$fh} join $delim, ( @borrower_fields, @item_fields, @other_fields );
+print {$fh} "\n";
+
+my $counted  = 0;
+my $overdues = Getoverdues();
+for my $overdue ( @{$overdues} ) {
+    if ( !defined $overdue->{borrowernumber} ) {
+        carp
+"ERROR in Getoverdues : issues.borrowernumber IS NULL.  Repair 'issues' table now!  Skipping record.\n";
+        next;
+    }
+    my $borrower = BorType( $overdue->{borrowernumber} );
+    my $branchcode =
+        ( $control eq 'ItemHomeLibrary' ) ? $overdue->{homebranch}
+      : ( $control eq 'PatronLibrary' )   ? $borrower->{branchcode}
+      :                                     $overdue->{branchcode};
+
+# In final case, CircControl must be PickupLibrary. (branchcode comes from issues table here).
+    if ( !exists $is_holiday{$branchcode} ) {
+        $is_holiday{$branchcode} = set_holiday( $branchcode, $today );
+    }
 
-my $data = Getoverdues();
-my $overdueItemsCounted = 0;
-my %calendars = ();
-$today = C4::Dates->new();
-$today_iso = $today->output('iso');
-$today_days = Date_to_Days(split(/-/,$today_iso));
-if($output_dir){
-    $fldir = $output_dir if( -d $output_dir );
-} else {
-    $fldir = $ENV{TMPDIR} || "/tmp";
-}
-if (!-d $fldir) {
-    warn "Could not write to $fldir ... does not exist!";
-}
-$filename = $dbname;
-$filename =~ s/\W//;
-$filename = $fldir . '/'. $filename . '_' .  $today_iso . ".log";
-print "writing to $filename\n" if $verbose;
-open (FILE, ">$filename") or die "Cannot write file $filename: $!";
-print FILE join $delim, (@borrower_fields, @item_fields, @other_fields);
-print FILE "\n";
-
-for (my $i=0; $i<scalar(@$data); $i++) {
-    my $datedue = C4::Dates->new($data->[$i]->{'date_due'},'iso');
-    my $datedue_days = Date_to_Days(split(/-/,$datedue->output('iso')));
-    my $due_str = $datedue->output();
-    unless (defined $data->[$i]->{'borrowernumber'}) {
-        print STDERR "ERROR in Getoverdues line $i: issues.borrowernumber IS NULL.  Repair 'issues' table now!  Skipping record.\n";
-        next;   # Note: this doesn't solve everything.  After NULL borrowernumber, multiple issues w/ real borrowernumbers can pile up.
+    my $datedue = dt_from_string( $overdue->{date_due} );
+    if ( DateTime->compare( $datedue, $today ) == 1 ) {
+        next;    # not overdue
     }
-    my $borrower = BorType($data->[$i]->{'borrowernumber'});
-    my $branchcode = ($control eq 'ItemHomeLibrary') ? $data->[$i]->{homebranch} :
-                     ($control eq 'PatronLibrary'  ) ?   $borrower->{branchcode} :
-                                                       $data->[$i]->{branchcode} ;
-    # In final case, CircControl must be PickupLibrary. (branchcode comes from issues table here).
-    my $calendar;
-    unless (defined ($calendars{$branchcode})) {
-        $calendars{$branchcode} = C4::Calendar->new(branchcode => $branchcode);
+    ++$counted;
+
+    my ( $amount, $type, $daycounttotal ) =
+      CalcFine( $overdue, $borrower->{categorycode},
+        $branchcode, $datedue, $today );
+
+    $type ||= q{};
+
+    # Don't update the fine if today is a holiday.
+    # This ensures that dropbox mode will remove the correct amount of fine.
+    if ( $mode eq 'production' && !$is_holiday{$branchcode} ) {
+        if ( $amount > 0 ) {
+            UpdateFine(
+                $overdue->{itemnumber},
+                $overdue->{borrowernumber},
+                $amount, $type, output_pref($datedue)
+            );
+        }
     }
-    $calendar = $calendars{$branchcode};
-    my $isHoliday = $calendar->isHoliday(split '/', $today->output('metric'));
-      
-    ($datedue_days <= $today_days) or next; # or it's not overdue, right?
-
-    $overdueItemsCounted++;
-    my ($amount,$type,$daycounttotal,$daycount)=
-               CalcFine($data->[$i], $borrower->{'categorycode'}, $branchcode,undef,undef, $datedue, $today);
-        # FIXME: $type NEVER gets populated by anything.
-    (defined $type) or $type = '';
-       # Don't update the fine if today is a holiday.  
-       # This ensures that dropbox mode will remove the correct amount of fine.
-       if ($mode eq 'production' and  ! $isHoliday) {
-               UpdateFine($data->[$i]->{'itemnumber'},$data->[$i]->{'borrowernumber'},$amount,$type,$due_str) if( $amount > 0 ) ;
-       }
-    my @cells = ();
-    push @cells, map {$borrower->{$_}} @borrower_fields;
-    push @cells, map {$data->[$i]->{$_}} @item_fields;
+    my @cells;
+    push @cells,
+      map { defined $borrower->{$_} ? $borrower->{$_} : q{} } @borrower_fields;
+    push @cells, map { $overdue->{$_} } @item_fields;
     push @cells, $type, $daycounttotal, $amount;
-    print FILE join($delim, @cells), "\n";
+    say {$fh} join $delim, @cells;
 }
+close $fh;
 
-my $numOverdueItems = scalar(@$data);
 if ($verbose) {
-   print <<EOM;
-Fines assessment -- $today_iso -- Saved to $filename
+    my $overdue_items = @{$overdues};
+    print <<'EOM';
+Fines assessment -- $today->ymd() -- Saved to $filename
 Number of Overdue Items:
-     counted $overdueItemsCounted
-    reported $numOverdueItems
+     counted $overdue_items
+    reported $counted
 
 EOM
 }
 
-close FILE;
+sub set_holiday {
+    my ( $branch, $dt ) = @_;
+
+    my $calendar = Koha::Calendar->new( branchcode => $branch );
+    return $calendar->is_holiday($dt);
+}
+
+sub get_filename {
+    my $directory = shift;
+    if ( !$directory ) {
+        $directory = File::Spec->tmpdir();
+    }
+    if ( !-d $directory ) {
+        carp "Could not write to $directory ... does not exist!";
+    }
+    my $name = C4::Context->config('database');
+    $name =~ s/\W//;
+    $name .= join q{}, q{_}, $today->ymd(), '.log';
+    $name = File::Spec->catfile( $directory, $name );
+    if ($verbose) {
+        say "writing to $name";
+    }
+    return $name;
+}
index af3790d..cb9ffa2 100755 (executable)
@@ -323,7 +323,7 @@ if (@branchcodes) {
 # these are the fields that will be substituted into <<item.content>>
 my @item_content_fields = split( /,/, $itemscontent );
 
-binmode STDOUT, ':encoding(UTF-8)';
+binmode( STDOUT, ':encoding(UTF-8)' );
 
 
 our $csv;       # the Text::CSV_XS object
@@ -350,8 +350,8 @@ if ( defined $htmlfilename ) {
   if ( $htmlfilename eq '' ) {
     $html_fh = *STDOUT;
   } else {
-    my $today = C4::Dates->new();
-    open $html_fh, ">",File::Spec->catdir ($htmlfilename,"notices-".$today->output('iso').".html");
+    my $today = DateTime->now(time_zone => C4::Context->tz );
+    open $html_fh, ">",File::Spec->catdir ($htmlfilename,"notices-".$today->ymd().".html");
   }
   
   print $html_fh "<html>\n";
index 27684d4..b45250d 100755 (executable)
@@ -47,6 +47,7 @@ use MARC::Record;
 use MARC::Field;
 use List::MoreUtils qw/any none/;
 use C4::Images;
+use Koha::DateUtils;
 
 BEGIN {
        if (C4::Context->preference('BakerTaylorEnabled')) {
@@ -494,7 +495,7 @@ for my $itm (@items) {
         # I can't actually find any case in which this is defined. --amoore 2008-12-09
         $itm->{ $itm->{'publictype'} } = 1;
     }
-    $itm->{datedue}      = format_date($itm->{datedue});
+    $itm->{datedue}      = format_sqlduedatetime($itm->{datedue});
     $itm->{datelastseen} = format_date($itm->{datelastseen});
 
     # get collection code description, too
index 337a939..d9d90a6 100755 (executable)
@@ -28,6 +28,8 @@ use Data::ICal::Entry::Event;
 use DateTime;
 use DateTime::Format::ICal;
 use Date::Calc qw (Parse_Date);
+use DateTime;
+use DateTime::Event::ICal;
 
 use C4::Auth;
 use C4::Koha;
@@ -54,7 +56,7 @@ my ( $borr ) =  GetMemberDetails( $borrowernumber );
 my $calendar = Data::ICal->new();
 
 # get issued items ....
-my ($issues) = GetPendingIssues($borrowernumber);
+my $issues = GetPendingIssues($borrowernumber);
 
 foreach my $issue ( @$issues ) {
     my $vevent = Data::ICal::Entry::Event->new();
index 00a91db..6872660 100755 (executable)
@@ -25,8 +25,8 @@ use C4::Auth;
 use C4::Koha;
 use C4::Biblio;
 use C4::Circulation;
-use C4::Dates qw/format_date/;
 use C4::Members;
+use Koha::DateUtils;
 
 use C4::Output;
 
@@ -89,8 +89,8 @@ foreach my $issue (@{$issues} ) {
     $line{title}           = $issue->{'title'};
     $line{author}          = $issue->{'author'};
     $line{itemcallnumber}  = $issue->{'itemcallnumber'};
-    $line{date_due}        = format_date( $issue->{'date_due'} );
-    $line{returndate}      = format_date( $issue->{'returndate'} );
+    $line{date_due}        = format_sqlduedatetime( $issue->{date_due} );
+    $line{returndate}      = format_sqldatetime( $issue->{returndate} );
     $line{volumeddesc}     = $issue->{'volumeddesc'};
     $issue->{'itemtype'}   = C4::Context->preference('item-level_itypes') ? $issue->{'itype'} : $issue->{'itemtype'};
     if($issue->{'itemtype'}) {
index b720842..4e4d440 100755 (executable)
@@ -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");
@@ -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';
         }
 
index 5b18cae..655f812 100755 (executable)
@@ -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';
 
@@ -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 ) {
@@ -200,7 +201,7 @@ if ($issues){
                        $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++;
                
index f4c7fb4..ad03d88 100755 (executable)
@@ -27,7 +27,7 @@ use CGI;
 use C4::Circulation;
 use C4::Auth;
 use URI::Escape;
-use C4::Dates qw/format_date_in_iso/;
+use Koha::DateUtils;
 my $input = new CGI;
 
 #Set Up User_env
@@ -66,7 +66,7 @@ if ($input->param('return_all')) {
 my $branch=$input->param('branch');
 my $datedue;
 if ($input->param('newduedate')){
-    $datedue=C4::Dates->new($input->param('newduedate'));
+    $datedue = dt_from_string($input->param('newduedate'));
 }
 
 # warn "barcodes : @barcodes";
index 24557f4..b96447f 100755 (executable)
@@ -42,6 +42,7 @@ use C4::Circulation;
 use C4::Dates qw/format_date/;
 use C4::Members;
 use C4::Search;                # enabled_staff_search_views
+use Koha::DateUtils;
 
 my $dbh = C4::Context->dbh;
 my $sth;
@@ -367,7 +368,7 @@ foreach my $biblionumber (@biblionumbers) {
             # change the background color
             my $issues= GetItemIssue($itemnumber);
             if ( $issues->{'date_due'} ) {
-                $item->{date_due} = format_date($issues->{'date_due'});
+                $item->{date_due} = format_sqldatetime($issues->{date_due});
                 $item->{backgroundcolor} = 'onloan';
             }
 
index 6018502..c28a2dc 100755 (executable)
@@ -14,10 +14,10 @@ use English qw(-no_match_vars);
 
 my @all_koha_dirs = qw( acqui admin authorities basket C4 catalogue cataloguing circ debian errors
 labels members misc offline_circ opac patroncards reports reserve reviews rotating_collections
-serials sms suggestion t tags test tools virtualshelves);
+serials sms suggestion t tags test tools virtualshelves Koha);
 
 my @dirs = qw( acqui admin authorities basket catalogue cataloguing circ debian errors labels
-    offline_circ reserve reviews rotating_collections serials sms virtualshelves );
+    offline_circ reserve reviews rotating_collections serials sms virtualshelves Koha);
 
 if ( not $ENV{TEST_QA} ) {
     my $msg = 'Author test. Set $ENV{TEST_QA} to a true value to run';
diff --git a/t/DateUtils.t b/t/DateUtils.t
new file mode 100755 (executable)
index 0000000..dbf38a6
--- /dev/null
@@ -0,0 +1,97 @@
+use strict;
+use warnings;
+use 5.010;
+use DateTime;
+use DateTime::TimeZone;
+
+use C4::Context;
+use Test::More tests => 25;
+
+BEGIN { use_ok('Koha::DateUtils'); }
+
+my $tz = C4::Context->tz;
+
+isa_ok( $tz, 'DateTime::TimeZone', 'Context returns timezone object' );
+
+my $testdate_iso = '2011-06-16';                   # Bloomsday 2011
+my $dt = dt_from_string( $testdate_iso, 'iso' );
+
+isa_ok( $dt, 'DateTime', 'dt_from_string returns a DateTime object' );
+
+cmp_ok( $dt->ymd(), 'eq', $testdate_iso, 'Returned object matches input' );
+
+$dt->set_hour(12);
+$dt->set_minute(0);
+
+my $date_string = output_pref( $dt, 'iso' );
+cmp_ok $date_string, 'eq', '2011-06-16 12:00', 'iso output';
+
+$date_string = output_pref( $dt, 'us' );
+cmp_ok $date_string, 'eq', '06/16/2011 12:00', 'us output';
+
+# metric should return the French Revolutionary Calendar Really
+$date_string = output_pref( $dt, 'metric' );
+cmp_ok $date_string, 'eq', '16/06/2011 12:00', 'metric output';
+
+$date_string = output_pref_due( $dt, 'metric' );
+cmp_ok $date_string, 'eq', '16/06/2011 12:00',
+  'output_pref_due preserves non midnight HH:SS';
+
+$dt->set_hour(23);
+$dt->set_minute(59);
+$date_string = output_pref_due( $dt, 'metric' );
+cmp_ok $date_string, 'eq', '16/06/2011',
+  'output_pref_due truncates HH:SS at midnight';
+
+my $dear_dirty_dublin = DateTime::TimeZone->new( name => 'Europe/Dublin' );
+my $new_dt = dt_from_string( '16/06/2011', 'metric', $dear_dirty_dublin );
+isa_ok( $new_dt, 'DateTime', 'Create DateTime with different timezone' );
+cmp_ok( $new_dt->ymd(), 'eq', $testdate_iso,
+    'Returned Dublin object matches input' );
+
+$new_dt = dt_from_string( '2011-06-16 12:00', 'sql' );
+isa_ok( $new_dt, 'DateTime', 'Create DateTime from (mysql) sql' );
+cmp_ok( $new_dt->ymd(), 'eq', $testdate_iso, 'sql returns correct date' );
+
+$new_dt = dt_from_string( $dt, 'iso' );
+isa_ok( $new_dt, 'DateTime', 'Passed a DateTime dt_from_string returns it' );
+
+# C4::Dates allowed 00th of the month
+
+my $ymd = '2012-01-01';
+my $dt0 = dt_from_string( '00/01/2012', 'metric' );
+isa_ok( $dt0, 'DateTime',
+    'dt_from_string returns a DateTime object passed a zero metric day' );
+cmp_ok( $dt0->ymd(), 'eq', $ymd, 'Returned object corrects metric day 0' );
+
+$dt0 = dt_from_string( '01/00/2012', 'us' );
+isa_ok( $dt0, 'DateTime',
+    'dt_from_string returns a DateTime object passed a zero us day' );
+cmp_ok( $dt0->ymd(), 'eq', $ymd, 'Returned object corrects us day 0' );
+
+$dt0 = dt_from_string( '2012-01-00', 'iso' );
+isa_ok( $dt0, 'DateTime',
+    'dt_from_string returns a DateTime object passed a zero iso day' );
+cmp_ok( $dt0->ymd(), 'eq', $ymd, 'Returned object corrects iso day 0' );
+
+# Return undef if passed mysql 0 dates
+$dt0 = dt_from_string( '0000-00-00', 'iso' );
+is( $dt0, undef, "undefined returned for 0 iso date" );
+
+my $formatted = format_sqldatetime( '2011-06-16 12:00:07', 'metric' );
+cmp_ok( $formatted, 'eq', '16/06/2011 12:00', 'format_sqldatetime conversion' );
+
+$formatted = format_sqldatetime( undef, 'metric' );
+cmp_ok( $formatted, 'eq', q{},
+    'format_sqldatetime formats undef as empty string' );
+
+$formatted = format_sqlduedatetime( '2011-06-16 12:00:07', 'metric' );
+cmp_ok(
+    $formatted, 'eq',
+    '16/06/2011 12:00',
+    'format_sqlduedatetime conversion for hourly loans'
+);
+
+$formatted = format_sqlduedatetime( '2011-06-16 23:59:07', 'metric' );
+cmp_ok( $formatted, 'eq', '16/06/2011',
+    'format_sqlduedatetime conversion for daily loans' );
diff --git a/t/Kalendar.t b/t/Kalendar.t
new file mode 100755 (executable)
index 0000000..f955231
--- /dev/null
@@ -0,0 +1,62 @@
+use strict;
+use warnings;
+use 5.010;
+use DateTime;
+use DateTime::TimeZone;
+
+use C4::Context;
+use Test::More tests => 9;
+
+BEGIN { use_ok('Koha::Calendar'); }
+
+my $cal = Koha::Calendar->new( TEST_MODE => 1 );
+
+isa_ok( $cal, 'Koha::Calendar', 'Calendar class returned' );
+
+my $saturday = DateTime->new(
+    year      => 2011,
+    month     => 6,
+    day       => 25,
+    time_zone => 'Europe/London',
+);
+my $sunday = DateTime->new(
+    year      => 2011,
+    month     => 6,
+    day       => 26,
+    time_zone => 'Europe/London',
+);
+my $monday = DateTime->new(
+    year      => 2011,
+    month     => 6,
+    day       => 27,
+    time_zone => 'Europe/London',
+);
+my $bloomsday = DateTime->new(
+    year      => 2011,
+    month     => 6,
+    day       => 16,
+    time_zone => 'Europe/London',
+);    # should be a holiday
+my $special = DateTime->new(
+    year      => 2011,
+    month     => 6,
+    day       => 1,
+    time_zone => 'Europe/London',
+);    # should be a holiday
+my $notspecial = DateTime->new(
+    year      => 2011,
+    month     => 6,
+    day       => 2,
+    time_zone => 'Europe/London',
+);    # should NOT be a holiday
+is( $cal->is_holiday($sunday), 1, 'Sunday is a closed day' );   # wee free test;
+is( $cal->is_holiday($monday),     0, 'Monday is not a closed day' );    # alas
+is( $cal->is_holiday($bloomsday),  1, 'month/day closed day test' );
+is( $cal->is_holiday($special),    1, 'special closed day test' );
+is( $cal->is_holiday($notspecial), 0, 'open day test' );
+
+my $dt = $cal->addDate( $saturday, 1, 'days' );
+is( $dt->day_of_week, 1, 'addDate skips closed Sunday' );
+
+$dt = $cal->addDate( $bloomsday, -1 );
+cmp_ok( $dt->ymd(), 'cmp', '2011-06-15', 'Negative call to addDate' );
diff --git a/t/db_dependent/rollingloans.t b/t/db_dependent/rollingloans.t
new file mode 100644 (file)
index 0000000..af9e454
--- /dev/null
@@ -0,0 +1,45 @@
+
+use strict;
+use warnings;
+use 5.010;
+use C4::Context;
+use C4::Circulation;
+use C4::Members;
+
+use Test::More tests => 8;
+C4::Context->_new_userenv(1234567);
+C4::Context->set_userenv(91, 'CLIstaff', '23529001223661', 'CPL',
+                         'CPL', 'CPL', '', 'cc@cscnet.co.uk');
+
+
+my $test_patron = '23529001223651';
+my $test_item_fic = '502326000402';
+my $test_item_24 = '502326000404';
+my $test_item_48 = '502326000403';
+
+for my $item_barcode ( $test_item_fic, $test_item_24, $test_item_48) {
+    my $duedate = try_issue($test_patron, $item_barcode);
+    isa_ok($duedate, 'DateTime');
+    if ($item_barcode eq $test_item_fic) {
+        is($duedate->hour(), 23, "daily loan hours = 23");
+        is($duedate->minute(), 59, "daily loan mins = 59");
+    }
+    my $ret_ok = try_return($item_barcode);
+    is($ret_ok, 1, 'Return succeeded');
+}
+
+
+sub try_issue {
+    my ($cardnumber, $item ) = @_;
+    my $issuedate = '2011-05-16';
+    my $borrower = GetMemberDetails(0, $cardnumber);
+    my ($issuingimpossible,$needsconfirmation) = CanBookBeIssued( $borrower, $item );
+       my $due_date = AddIssue($borrower, $item, undef, 0, $issuedate);
+    return $due_date;
+}
+
+sub try_return {
+    my $barcode = shift;
+    my ($ret, $messages, $iteminformation, $borrower) = AddReturn($barcode);
+    return $ret;
+}