@@ -, +, @@
closed
---
C4/Calendar.pm | 62 +++-
C4/Circulation.pm | 37 ++-
C4/Overdues.pm | 31 +-
Koha/Calendar.pm | 84 +++--
installer/data/mysql/updatedatabase.pl | 34 ++
.../prog/en/modules/tools/holidays.tt | 143 ++++++++-
misc/cronjobs/fines.pl | 17 +-
t/Calendar1.t | 333 ++++++++++++++++++++
tools/holidays.pl | 66 +++-
9 files changed, 755 insertions(+), 52 deletions(-)
create mode 100755 t/Calendar1.t
--- a/C4/Calendar.pm
+++ a/C4/Calendar.pm
@@ -17,13 +17,19 @@ package C4::Calendar;
use strict;
use warnings;
-use vars qw($VERSION @EXPORT);
+use vars qw($VERSION @ISA @EXPORT_OK @EXPORT);
use Carp;
use Date::Calc qw( Date_to_Days Today);
use C4::Context;
+BEGIN {
+ $VERSION = 3.07.00.049;
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(setOpeningHours getOpeningHours);
+}
+
use constant ISO_DATE_FORMAT => "%04d-%02d-%02d";
=head1 NAME
@@ -722,6 +728,60 @@ sub daysBetween {
return($count);
}
+sub setOpeningHours {
+ my ($branchcode, $weekcode, $openhour, $closehour) = @_;
+
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare("SELECT * FROM openinghours WHERE branchcode = ? AND weekcode = ?");
+ $sth->execute($branchcode, $weekcode);
+
+ if ( not $sth->fetchrow_hashref ) {
+ my $sth2 = $dbh->prepare('INSERT INTO openinghours (branchcode, weekcode, openhour, closehour) VALUES(?,?,?,?)');
+
+ $sth2->execute( $branchcode, $weekcode, $openhour, $closehour );
+ }
+ else {
+ my $sth3 = $dbh->prepare('UPDATE openinghours set openhour = ?, closehour = ? WHERE branchcode = ? AND weekcode = ? ');
+
+ $sth3->execute( $openhour, $closehour, $branchcode, $weekcode );
+ }
+}
+
+sub getOpeningHours {
+ my ($branchcode) = @_;
+ my $dbh = C4::Context->dbh;
+ my $query = "SELECT * FROM openinghours ";
+ $query .= "WHERE branchcode = ?" if(defined $branchcode);
+ my $sth = $dbh->prepare($query);
+
+ my %results;
+
+ if($branchcode){
+ $sth->execute($branchcode);
+ while ( my $hours = $sth->fetchrow_hashref ) {
+ $results{ $hours->{'weekcode'} } = $hours;
+ }
+ }
+ else{
+ $sth->execute();
+ my @tmp;
+ while ( my $row = $sth->fetchrow_hashref ) {
+ #add a few precalculated fields
+ my $t1 = DateTime::Format::DateParse->parse_datetime( $row->{openhour});
+ my $t2 = DateTime::Format::DateParse->parse_datetime( $row->{closehour});
+ $row->{hashopen} = {hour => $t1->hour(), minute => $t1->minute(), second => $t1->second()};
+ $row->{hashclose} = {hour => $t2->hour(), minute => $t2->minute(), second => $t2->second() };
+ $row->{totalminutes} = $t1->delta_ms($t2)->delta_minutes();
+
+ #build the hash
+ $results{ $row->{'branchcode'} } ||= {};
+ $results{ $row->{'branchcode'} }->{$row->{'weekcode'}} = $row;
+ }
+ }
+
+ return \%results;
+}
+
1;
__END__
--- a/C4/Circulation.pm
+++ a/C4/Circulation.pm
@@ -33,6 +33,7 @@ use C4::Dates qw(format_date);
use C4::Accounts;
use C4::ItemCirculationAlertPreference;
use C4::Message;
+use C4::Calendar qw(getOpeningHours);
use C4::Debug;
use C4::Branch; # GetBranches
use C4::Log; # logaction
@@ -91,6 +92,7 @@ BEGIN {
&AnonymiseIssueHistory
&CheckIfIssuedToPatron
&IsItemIssued
+ &CalcDateDue
);
# subs to deal with returns
@@ -120,6 +122,7 @@ BEGIN {
&DeleteOfflineOperation
&ProcessOfflineOperation
);
+
}
=head1 NAME
@@ -1729,7 +1732,7 @@ sub AddReturn {
return (0, { BadBarcode => $barcode }); # no barcode means no item or borrower. bail out.
}
my $issue = GetItemIssue($itemnumber);
-# warn Dumper($iteminformation);
+
if ($issue and $issue->{borrowernumber}) {
$borrower = C4::Members::GetMemberDetails($issue->{borrowernumber})
or die "Data inconsistency: barcode $barcode (itemnumber:$itemnumber) claims to be issued to non-existant borrowernumber '$issue->{borrowernumber}'\n"
@@ -3228,6 +3231,24 @@ C<$borrower> = Borrower object
C<$isrenewal> = Boolean: is true if we want to calculate the date due for a renewal. Else is false.
=cut
+sub CapDateDue{
+ my ( $calcDueDate, $loanlength, $branch) = @_;
+
+ # for hourly loans, we will limit the due date to close time
+ return $calcDueDate unless ($loanlength->{lengthunit} eq 'hours');
+
+# my $now = DateTime->now( time_zone => C4::Context->tz() );
+ my $branchHours = getOpeningHours();
+
+ my $close = $calcDueDate->clone()->truncate( to => 'days');
+ $close->set($branchHours->{$branch}->{$calcDueDate->local_day_of_week()-1}->{hashclose});
+
+ if (DateTime->compare($calcDueDate, $close) > 0){ # we want the document back at close time
+ return $close;
+ }
+
+ return $calcDueDate;
+}
sub CalcDateDue {
my ( $startdate, $itemtype, $branch, $borrower, $isrenewal ) = @_;
@@ -3255,13 +3276,17 @@ sub CalcDateDue {
->truncate( to => 'minute' );
}
-
+ my $calendar = Koha::Calendar->new( branchcode => $branch );
# calculate the datedue as normal
if ( C4::Context->preference('useDaysMode') eq 'Days' )
{ # ignoring calendar
if ( $loanlength->{lengthunit} eq 'hours' ) {
- $datedue->add( hours => $loanlength->{$length_key} );
- } else { # days
+ if($loanlength->{issuelength} == 24){
+ $datedue = $calendar->addDate( $datedue, 1);
+ } else {
+ $datedue->add( hours => $loanlength->{issuelength} );
+ }
+ }else { # days
$datedue->add( days => $loanlength->{$length_key} );
$datedue->set_hour(23);
$datedue->set_minute(59);
@@ -3274,7 +3299,6 @@ sub CalcDateDue {
else { # days
$dur = DateTime::Duration->new( days => $loanlength->{$length_key});
}
- my $calendar = Koha::Calendar->new( branchcode => $branch );
$datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} );
if ($loanlength->{lengthunit} eq 'days') {
$datedue->set_hour(23);
@@ -3311,7 +3335,7 @@ sub CalcDateDue {
}
}
- return $datedue;
+ return CapDateDue($datedue, $loanlength, $branch);
}
@@ -3761,4 +3785,3 @@ __END__
Koha Development Team
=cut
-
--- a/C4/Overdues.pm
+++ a/C4/Overdues.pm
@@ -41,6 +41,7 @@ BEGIN {
# subs to rename (and maybe merge some...)
push @EXPORT, qw(
&CalcFine
+ &_get_chargeable_units
&Getoverdues
&checkoverdues
&NumberNotifyId
@@ -110,8 +111,9 @@ sub Getoverdues {
if ( C4::Context->preference('item-level_itypes') ) {
$statement = "
SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode
- FROM issues
+ FROM issues
LEFT JOIN items USING (itemnumber)
+join issuingrules on issuingrules.itemtype = items.itype
WHERE date_due < NOW()
";
} else {
@@ -135,6 +137,11 @@ LEFT JOIN biblioitems USING (biblioitemnumber)
$statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
push @bind_parameters, $params->{'maximumdays'};
}
+
+ if($params->{'shortrun'}){
+ $statement .= " and issuingrules.lengthunit in ('hours') " ;
+ }
+
$statement .= 'ORDER BY borrowernumber';
my $sth = $dbh->prepare( $statement );
$sth->execute( @bind_parameters );
@@ -240,7 +247,7 @@ or "Final Notice". But CalcFine never defined any value.
=cut
sub CalcFine {
- my ( $item, $bortype, $branchcode, $due_dt, $end_date ) = @_;
+ my ( $item, $bortype, $branchcode, $due_dt, $end_date, $openingHours ) = @_;
my $start_date = $due_dt->clone();
# get issuingrules (fines part will be used)
my $itemtype = $item->{itemtype} || $item->{itype};
@@ -248,7 +255,7 @@ sub CalcFine {
my $fine_unit = $data->{lengthunit};
$fine_unit ||= 'days';
- my $chargeable_units = _get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
+ my $chargeable_units = _get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode, $openingHours);
my $units_minus_grace = $chargeable_units - $data->{firstremind};
my $amount = 0;
if ($data->{'chargeperiod'} && ($units_minus_grace > 0) ) {
@@ -282,25 +289,29 @@ C<$branchcode> is the branch whose calendar to use for finding holidays.
=cut
sub _get_chargeable_units {
- my ($unit, $dt1, $dt2, $branchcode) = @_;
+ my ($unit, $dt1, $dt2, $branchcode, $openingHours) = @_;
my $charge_units = 0;
my $charge_duration;
if ($unit eq 'hours') {
if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
- my $calendar = Koha::Calendar->new( branchcode => $branchcode );
- $charge_duration = $calendar->hours_between( $dt1, $dt2 );
+ if (!defined $openingHours->{$branchcode}{'Calendar'}){
+ $openingHours->{$branchcode}{'Calendar'} = Koha::Calendar->new( branchcode => $branchcode );
+ }
+ $charge_duration = $openingHours->{$branchcode}{'Calendar'}->hours_between( $dt1, $dt2, $openingHours->{$branchcode} );
} else {
$charge_duration = $dt2->delta_ms( $dt1 );
}
- if($charge_duration->in_units('hours') == 0 && $charge_duration->in_units('seconds') > 0){
+ if($charge_duration->in_units($unit) == 0 && $charge_duration->in_units('seconds') > 0){
return 1;
}
- return $charge_duration->in_units('hours');
+ return $charge_duration->in_units($unit);
}
else { # days
if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
- my $calendar = Koha::Calendar->new( branchcode => $branchcode );
- $charge_duration = $calendar->days_between( $dt1, $dt2 );
+ if (!defined $openingHours->{$branchcode}{'Calendar'}){
+ $openingHours->{$branchcode}{'Calendar'} = Koha::Calendar->new( branchcode => $branchcode );
+ }
+ $charge_duration = $openingHours->{$branchcode}{'Calendar'}->days_between( $dt1, $dt2, $openingHours->{$branchcode} );
} else {
$charge_duration = $dt2->delta_days( $dt1 );
}
--- a/Koha/Calendar.pm
+++ a/Koha/Calendar.pm
@@ -271,7 +271,7 @@ sub days_between {
my $self = shift;
my $start_dt = shift;
my $end_dt = shift;
-
+ my $branchHours = shift;
if ( $start_dt->compare($end_dt) > 0 ) {
# swap dates
my $int_dt = $end_dt;
@@ -279,47 +279,79 @@ sub days_between {
$start_dt = $int_dt;
}
-
- # start and end should not be closed days
my $days = $start_dt->delta_days($end_dt)->delta_days;
- for (my $dt = $start_dt->clone();
- $dt <= $end_dt;
- $dt->add(days => 1)
- ) {
- if ($self->is_holiday($dt)) {
- $days--;
+ # start and end should not be closed days
+ while ($start_dt <= $end_dt) {
+ if (defined $branchHours->{holidays}{$start_dt->ymd()}) {
+ $days-- if $branchHours->{holidays}{$start_dt->ymd()};
+ } else {
+ $branchHours->{holidays}{$start_dt->ymd()} = $self->is_holiday($start_dt);
+ $days-- if $branchHours->{holidays}{$start_dt->ymd()};
}
+ $start_dt->add(days => 1);
}
return DateTime::Duration->new( days => $days );
}
sub hours_between {
- my ($self, $start_date, $end_date) = @_;
+ my ($self, $start_date, $end_date, $branchHours) = @_;
+
my $start_dt = $start_date->clone();
my $end_dt = $end_date->clone();
- my $duration = $end_dt->delta_ms($start_dt);
+
$start_dt->truncate( to => 'day' );
$end_dt->truncate( to => 'day' );
- # NB this is a kludge in that it assumes all days are 24 hours
- # However for hourly loans the logic should be expanded to
- # take into account open/close times then it would be a duration
- # of library open hours
- my $skipped_days = 0;
- for (my $dt = $start_dt->clone();
- $dt <= $end_dt;
- $dt->add(days => 1)
- ) {
- if ($self->is_holiday($dt)) {
- ++$skipped_days;
+
+ if (defined $branchHours->{0}){ # We were given the branch's opening hours.
+ my $totalMinutes = 0;
+ for (my $dt = $start_dt->clone(); $dt <= $end_dt; $dt->add(days => 1)) {
+ # hash to keep track of holidays
+ if (defined $branchHours->{holidays}{$dt->ymd()}) {
+ next if $branchHours->{holidays}{$dt->ymd()};
+ } else {
+ $branchHours->{holidays}{$dt->ymd()} = $self->is_holiday($dt);
+ next if ($self->is_holiday($dt));
+ }
+
+ if(DateTime->compare($dt,$start_dt) == 0){ # from location till close time
+ #create a timestamp for today's close time using the current day object with the branchHours's hours.
+ my $close = $dt->clone()->set($branchHours->{$dt->local_day_of_week()-1}->{hashclose});
+
+ if(DateTime->compare($end_date, $close) < 0){ # we are still the same day, before closing time
+ $totalMinutes += $start_date->delta_ms($end_date)->delta_minutes();
+ } elsif(DateTime->compare($start_date, $close) < 0) { # we started (issue) before closing hour
+ $totalMinutes += $start_date->delta_ms($close)->delta_minutes();
+ }
+ }
+ elsif(DateTime->compare($dt, $end_dt) == 0){ # from open time till now
+ my $open = $dt->clone()->set($branchHours->{$dt->local_day_of_week()-1}->{hashopen});
+ if(DateTime->compare($end_date, $open) > 0) { # we end (return issue) after opening hour
+ $totalMinutes += $end_date->delta_ms($open)->delta_minutes();
+ }
+ }
+ else{
+ $totalMinutes += $branchHours->{$dt->local_day_of_week()- 1}->{totalminutes};
+ }
}
+
+ return DateTime::Duration->new( minutes => $totalMinutes);
}
- if ($skipped_days) {
- $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
+ else{
+ my $duration = $end_dt->delta_ms($start_dt);
+ my $skipped_days = 0;
+ while ($start_dt <= $end_dt){
+ if ($self->is_holiday($start_dt)) {
+ ++$skipped_days;
+ }
+ $start_dt->add(days => 1);
+ }
+ if ($skipped_days) {
+ $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
+ }
+ return $duration;
}
- return $duration;
-
}
sub set_daysmode {
--- a/installer/data/mysql/updatedatabase.pl
+++ a/installer/data/mysql/updatedatabase.pl
@@ -8560,6 +8560,40 @@ if ( CheckVersion($DBversion) ) {
SetVersion($DBversion);
}
+$DBversion ="XXX";
+if( CheckVersion($DBversion) ){
+ $dbh->do(q{
+ CREATE TABLE IF NOT EXISTS openinghours (
+ branchcode varchar(10) NOT NULL,
+ weekcode int(2) NOT NULL,
+ openhour time NOT NULL,
+ closehour time NOT NULL,
+ PRIMARY KEY (branchcode,weekcode)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ });
+ $dbh->do(q{
+ ALTER TABLE openinghours
+ ADD CONSTRAINT openinghours_ibfk_1
+ FOREIGN KEY (branchcode) REFERENCES
+ branches(branchcode);
+ });
+
+ my $sth = $dbh->prepare("SELECT branchcode FROM branches");
+ $sth->execute;
+ my @branches;
+ my $weekcode;
+ while (@branches = $sth->fetchrow_array()){
+ for ($weekcode = 0; $weekcode <= 6; $weekcode++) {
+ my $count = $dbh->selectrow_array('SELECT count(*) FROM openinghours WHERE branchcode=? and weekcode=?',undef, $branches[0], $weekcode);
+ if ($count==0){
+ $dbh->do('INSERT INTO openinghours (branchcode, weekcode, openhour, closehour) VALUES (?, ?, ? ,?)', undef, $branches[0], $weekcode, '09:00:00', '17:00:00') or die $dbh->errstr;
+ }
+ }
+ }
+ print "Calendar opening hours set.\n";
+ SetVersion($DBversion);
+}
+
=head1 FUNCTIONS
=head2 TableExists($table)
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/holidays.tt
+++ a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/holidays.tt
@@ -6,6 +6,10 @@
[% INCLUDE 'datatables.inc' %]