From dcedfec1b8cb2083bf4bd20161cd04ebbd2ae66e Mon Sep 17 00:00:00 2001 From: Charles Farmer Date: Wed, 4 Sep 2019 14:56:01 -0400 Subject: [PATCH] Bug 17015: DiscreteCalendar UI, Back-End and necessary scripts --- C4/Calendar.pm | 736 --------- C4/Circulation.pm | 13 +- C4/HoldsQueue.pm | 5 +- C4/Overdues.pm | 5 +- C4/Reserves.pm | 4 +- Koha/Calendar.pm | 555 ------- Koha/Charges/Fees.pm | 4 +- Koha/DiscreteCalendar.pm | 1332 +++++++++++++++++ Koha/Hold.pm | 6 +- circ/returns.pl | 3 +- .../prog/css/discretecalendar.css | 213 +++ .../prog/en/includes/tools-menu.inc | 2 +- .../en/modules/tools/discrete_calendar.tt | 708 +++++++++ .../prog/en/modules/tools/holidays.tt | 590 -------- .../prog/en/modules/tools/tools-home.tt | 2 +- misc/cronjobs/add_days_discrete_calendar.pl | 165 ++ misc/cronjobs/fines.pl | 4 +- misc/cronjobs/holds/cancel_unfilled_holds.pl | 2 +- misc/cronjobs/overdue_notices.pl | 18 +- misc/cronjobs/staticfines.pl | 4 +- .../thirdparty/TalkingTech_itiva_outbound.pl | 4 +- tools/copy-holidays.pl | 40 - tools/discrete_calendar.pl | 175 +++ tools/exceptionHolidays.pl | 143 -- tools/holidays.pl | 131 -- tools/newHolidays.pl | 144 -- 26 files changed, 2630 insertions(+), 2378 deletions(-) delete mode 100644 C4/Calendar.pm delete mode 100644 Koha/Calendar.pm create mode 100644 Koha/DiscreteCalendar.pm create mode 100644 koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt delete mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/holidays.tt create mode 100755 misc/cronjobs/add_days_discrete_calendar.pl delete mode 100755 tools/copy-holidays.pl create mode 100755 tools/discrete_calendar.pl delete mode 100755 tools/exceptionHolidays.pl delete mode 100755 tools/holidays.pl delete mode 100755 tools/newHolidays.pl diff --git a/C4/Calendar.pm b/C4/Calendar.pm deleted file mode 100644 index 6a5d8a79a4..0000000000 --- a/C4/Calendar.pm +++ /dev/null @@ -1,736 +0,0 @@ -package C4::Calendar; - -# 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 3 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, see . - -use strict; -use warnings; -use vars qw(@EXPORT); - -use Carp; -use Date::Calc qw( Date_to_Days Today); - -use C4::Context; -use Koha::Caches; - -use constant ISO_DATE_FORMAT => "%04d-%02d-%02d"; - -=head1 NAME - -C4::Calendar::Calendar - Koha module dealing with holidays. - -=head1 SYNOPSIS - - use C4::Calendar::Calendar; - -=head1 DESCRIPTION - -This package is used to deal with holidays. Through this package, you can set -all kind of holidays for the library. - -=head1 FUNCTIONS - -=head2 new - - $calendar = C4::Calendar->new(branchcode => $branchcode); - -Each library branch has its own Calendar. -C<$branchcode> specifies which Calendar you want. - -=cut - -sub new { - my $classname = shift @_; - my %options = @_; - my $self = bless({}, $classname); - foreach my $optionName (keys %options) { - $self->{lc($optionName)} = $options{$optionName}; - } - defined($self->{branchcode}) or croak "No branchcode argument to new. Should be C4::Calendar->new(branchcode => \$branchcode)"; - $self->_init($self->{branchcode}); - return $self; -} - -sub _init { - my $self = shift @_; - my $branch = shift; - defined($branch) or die "No branchcode sent to _init"; # must test for defined here and above to allow "" - my $dbh = C4::Context->dbh(); - my $repeatable = $dbh->prepare( 'SELECT * - FROM repeatable_holidays - WHERE ( branchcode = ? ) - AND (ISNULL(weekday) = ?)' ); - $repeatable->execute($branch,0); - my %week_days_holidays; - while (my $row = $repeatable->fetchrow_hashref) { - my $key = $row->{weekday}; - $week_days_holidays{$key}{title} = $row->{title}; - $week_days_holidays{$key}{description} = $row->{description}; - } - $self->{'week_days_holidays'} = \%week_days_holidays; - - $repeatable->execute($branch,1); - my %day_month_holidays; - while (my $row = $repeatable->fetchrow_hashref) { - my $key = $row->{month} . "/" . $row->{day}; - $day_month_holidays{$key}{title} = $row->{title}; - $day_month_holidays{$key}{description} = $row->{description}; - $day_month_holidays{$key}{day} = sprintf("%02d", $row->{day}); - $day_month_holidays{$key}{month} = sprintf("%02d", $row->{month}); - } - $self->{'day_month_holidays'} = \%day_month_holidays; - - my $special = $dbh->prepare( 'SELECT day, month, year, title, description - FROM special_holidays - WHERE ( branchcode = ? ) - AND (isexception = ?)' ); - $special->execute($branch,1); - my %exception_holidays; - while (my ($day, $month, $year, $title, $description) = $special->fetchrow) { - $exception_holidays{"$year/$month/$day"}{title} = $title; - $exception_holidays{"$year/$month/$day"}{description} = $description; - $exception_holidays{"$year/$month/$day"}{date} = - sprintf(ISO_DATE_FORMAT, $year, $month, $day); - } - $self->{'exception_holidays'} = \%exception_holidays; - - $special->execute($branch,0); - my %single_holidays; - while (my ($day, $month, $year, $title, $description) = $special->fetchrow) { - $single_holidays{"$year/$month/$day"}{title} = $title; - $single_holidays{"$year/$month/$day"}{description} = $description; - $single_holidays{"$year/$month/$day"}{date} = - sprintf(ISO_DATE_FORMAT, $year, $month, $day); - } - $self->{'single_holidays'} = \%single_holidays; - return $self; -} - -=head2 get_week_days_holidays - - $week_days_holidays = $calendar->get_week_days_holidays(); - -Returns a hash reference to week days holidays. - -=cut - -sub get_week_days_holidays { - my $self = shift @_; - my $week_days_holidays = $self->{'week_days_holidays'}; - return $week_days_holidays; -} - -=head2 get_day_month_holidays - - $day_month_holidays = $calendar->get_day_month_holidays(); - -Returns a hash reference to day month holidays. - -=cut - -sub get_day_month_holidays { - my $self = shift @_; - my $day_month_holidays = $self->{'day_month_holidays'}; - return $day_month_holidays; -} - -=head2 get_exception_holidays - - $exception_holidays = $calendar->exception_holidays(); - -Returns a hash reference to exception holidays. This kind of days are those -which stands for a holiday, but you wanted to make an exception for this particular -date. - -=cut - -sub get_exception_holidays { - my $self = shift @_; - my $exception_holidays = $self->{'exception_holidays'}; - return $exception_holidays; -} - -=head2 get_single_holidays - - $single_holidays = $calendar->get_single_holidays(); - -Returns a hash reference to single holidays. This kind of holidays are those which -happened just one time. - -=cut - -sub get_single_holidays { - my $self = shift @_; - my $single_holidays = $self->{'single_holidays'}; - return $single_holidays; -} - -=head2 insert_week_day_holiday - - insert_week_day_holiday(weekday => $weekday, - title => $title, - description => $description); - -Inserts a new week day for $self->{branchcode}. - -C<$day> Is the week day to make holiday. - -C<$title> Is the title to store for the holiday formed by $year/$month/$day. - -C<$description> Is the description to store for the holiday formed by $year/$month/$day. - -=cut - -sub insert_week_day_holiday { - my $self = shift @_; - my %options = @_; - - my $weekday = $options{weekday}; - croak "Invalid weekday $weekday" unless $weekday =~ m/^[0-6]$/; - - my $dbh = C4::Context->dbh(); - my $insertHoliday = $dbh->prepare("insert into repeatable_holidays (branchcode,weekday,day,month,title,description) values ( ?,?,NULL,NULL,?,? )"); - $insertHoliday->execute( $self->{branchcode}, $weekday, $options{title}, $options{description}); - $self->{'week_days_holidays'}->{$weekday}{title} = $options{title}; - $self->{'week_days_holidays'}->{$weekday}{description} = $options{description}; - return $self; -} - -=head2 insert_day_month_holiday - - insert_day_month_holiday(day => $day, - month => $month, - title => $title, - description => $description); - -Inserts a new day month holiday for $self->{branchcode}. - -C<$day> Is the day month to make the date to insert. - -C<$month> Is month to make the date to insert. - -C<$title> Is the title to store for the holiday formed by $year/$month/$day. - -C<$description> Is the description to store for the holiday formed by $year/$month/$day. - -=cut - -sub insert_day_month_holiday { - my $self = shift @_; - my %options = @_; - - my $dbh = C4::Context->dbh(); - my $insertHoliday = $dbh->prepare("insert into repeatable_holidays (branchcode,weekday,day,month,title,description) values (?, NULL, ?, ?, ?,? )"); - $insertHoliday->execute( $self->{branchcode}, $options{day},$options{month},$options{title}, $options{description}); - $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title} = $options{title}; - $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description}; - return $self; -} - -=head2 insert_single_holiday - - insert_single_holiday(day => $day, - month => $month, - year => $year, - title => $title, - description => $description); - -Inserts a new single holiday for $self->{branchcode}. - -C<$day> Is the day month to make the date to insert. - -C<$month> Is month to make the date to insert. - -C<$year> Is year to make the date to insert. - -C<$title> Is the title to store for the holiday formed by $year/$month/$day. - -C<$description> Is the description to store for the holiday formed by $year/$month/$day. - -=cut - -sub insert_single_holiday { - my $self = shift @_; - my %options = @_; - @options{qw(year month day)} = ( $options{date} =~ m/(\d+)-(\d+)-(\d+)/o ) - if $options{date} && !$options{day}; - - my $dbh = C4::Context->dbh(); - my $isexception = 0; - my $insertHoliday = $dbh->prepare("insert into special_holidays (branchcode,day,month,year,isexception,title,description) values (?,?,?,?,?,?,?)"); - $insertHoliday->execute( $self->{branchcode}, $options{day},$options{month},$options{year}, $isexception, $options{title}, $options{description}); - $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title}; - $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description}; - - - # changed the 'single_holidays' table, lets force/reset its cache - my $cache = Koha::Caches->get_instance(); - my $key = $self->{branchcode} . "_holidays"; - $cache->clear_from_cache($key); - - return $self; - -} - -=head2 insert_exception_holiday - - insert_exception_holiday(day => $day, - month => $month, - year => $year, - title => $title, - description => $description); - -Inserts a new exception holiday for $self->{branchcode}. - -C<$day> Is the day month to make the date to insert. - -C<$month> Is month to make the date to insert. - -C<$year> Is year to make the date to insert. - -C<$title> Is the title to store for the holiday formed by $year/$month/$day. - -C<$description> Is the description to store for the holiday formed by $year/$month/$day. - -=cut - -sub insert_exception_holiday { - my $self = shift @_; - my %options = @_; - - @options{qw(year month day)} = ( $options{date} =~ m/(\d+)-(\d+)-(\d+)/o ) - if $options{date} && !$options{day}; - - my $dbh = C4::Context->dbh(); - my $isexception = 1; - my $insertException = $dbh->prepare("insert into special_holidays (branchcode,day,month,year,isexception,title,description) values (?,?,?,?,?,?,?)"); - $insertException->execute( $self->{branchcode}, $options{day},$options{month},$options{year}, $isexception, $options{title}, $options{description}); - $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title}; - $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description}; - - # changed the 'single_holidays' table, lets force/reset its cache - my $cache = Koha::Caches->get_instance(); - my $key = $self->{branchcode} . "_holidays"; - $cache->clear_from_cache($key); - - return $self; -} - -=head2 ModWeekdayholiday - - ModWeekdayholiday(weekday =>$weekday, - title => $title, - description => $description) - -Modifies the title and description of a weekday for $self->{branchcode}. - -C<$weekday> Is the title to update for the holiday. - -C<$description> Is the description to update for the holiday. - -=cut - -sub ModWeekdayholiday { - my $self = shift @_; - my %options = @_; - - my $dbh = C4::Context->dbh(); - my $updateHoliday = $dbh->prepare("UPDATE repeatable_holidays SET title = ?, description = ? WHERE branchcode = ? AND weekday = ?"); - $updateHoliday->execute( $options{title},$options{description},$self->{branchcode},$options{weekday}); - $self->{'week_days_holidays'}->{$options{weekday}}{title} = $options{title}; - $self->{'week_days_holidays'}->{$options{weekday}}{description} = $options{description}; - return $self; -} - -=head2 ModDaymonthholiday - - ModDaymonthholiday(day => $day, - month => $month, - title => $title, - description => $description); - -Modifies the title and description for a day/month holiday for $self->{branchcode}. - -C<$day> The day of the month for the update. - -C<$month> The month to be used for the update. - -C<$title> The title to be updated for the holiday. - -C<$description> The description to be update for the holiday. - -=cut - -sub ModDaymonthholiday { - my $self = shift @_; - my %options = @_; - - my $dbh = C4::Context->dbh(); - my $updateHoliday = $dbh->prepare("UPDATE repeatable_holidays SET title = ?, description = ? WHERE month = ? AND day = ? AND branchcode = ?"); - $updateHoliday->execute( $options{title},$options{description},$options{month},$options{day},$self->{branchcode}); - $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title} = $options{title}; - $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description}; - return $self; -} - -=head2 ModSingleholiday - - ModSingleholiday(day => $day, - month => $month, - year => $year, - title => $title, - description => $description); - -Modifies the title and description for a single holiday for $self->{branchcode}. - -C<$day> Is the day of the month to make the update. - -C<$month> Is the month to make the update. - -C<$year> Is the year to make the update. - -C<$title> Is the title to update for the holiday formed by $year/$month/$day. - -C<$description> Is the description to update for the holiday formed by $year/$month/$day. - -=cut - -sub ModSingleholiday { - my $self = shift @_; - my %options = @_; - - my $dbh = C4::Context->dbh(); - my $isexception = 0; - - my $updateHoliday = $dbh->prepare(" -UPDATE special_holidays SET title = ?, description = ? - WHERE day = ? AND month = ? AND year = ? AND branchcode = ? AND isexception = ?"); - $updateHoliday->execute($options{title},$options{description},$options{day},$options{month},$options{year},$self->{branchcode},$isexception); - $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title}; - $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description}; - - # changed the 'single_holidays' table, lets force/reset its cache - my $cache = Koha::Caches->get_instance(); - my $key = $self->{branchcode} . "_holidays"; - $cache->clear_from_cache($key); - - return $self; -} - -=head2 ModExceptionholiday - - ModExceptionholiday(day => $day, - month => $month, - year => $year, - title => $title, - description => $description); - -Modifies the title and description for an exception holiday for $self->{branchcode}. - -C<$day> Is the day of the month for the holiday. - -C<$month> Is the month for the holiday. - -C<$year> Is the year for the holiday. - -C<$title> Is the title to be modified for the holiday formed by $year/$month/$day. - -C<$description> Is the description to be modified for the holiday formed by $year/$month/$day. - -=cut - -sub ModExceptionholiday { - my $self = shift @_; - my %options = @_; - - my $dbh = C4::Context->dbh(); - my $isexception = 1; - my $updateHoliday = $dbh->prepare(" -UPDATE special_holidays SET title = ?, description = ? - WHERE day = ? AND month = ? AND year = ? AND branchcode = ? AND isexception = ?"); - $updateHoliday->execute($options{title},$options{description},$options{day},$options{month},$options{year},$self->{branchcode},$isexception); - $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title}; - $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description}; - - # changed the 'single_holidays' table, lets force/reset its cache - my $cache = Koha::Caches->get_instance(); - my $key = $self->{branchcode} . "_holidays"; - $cache->clear_from_cache($key); - - return $self; -} - -=head2 delete_holiday - - delete_holiday(weekday => $weekday - day => $day, - month => $month, - year => $year); - -Delete a holiday for $self->{branchcode}. - -C<$weekday> Is the week day to delete. - -C<$day> Is the day month to make the date to delete. - -C<$month> Is month to make the date to delete. - -C<$year> Is year to make the date to delete. - -=cut - -sub delete_holiday { - my $self = shift @_; - my %options = @_; - - # Verify what kind of holiday that day is. For example, if it is - # a repeatable holiday, this should check if there are some exception - # for that holiday rule. Otherwise, if it is a regular holiday, it´s - # ok just deleting it. - - my $dbh = C4::Context->dbh(); - my $isSingleHoliday = $dbh->prepare("SELECT id FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)"); - $isSingleHoliday->execute($self->{branchcode}, $options{day}, $options{month}, $options{year}); - if ($isSingleHoliday->rows) { - my $id = $isSingleHoliday->fetchrow; - $isSingleHoliday->finish; # Close the last query - - my $deleteHoliday = $dbh->prepare("DELETE FROM special_holidays WHERE id = ?"); - $deleteHoliday->execute($id); - delete($self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}); - } else { - $isSingleHoliday->finish; # Close the last query - - my $isWeekdayHoliday = $dbh->prepare("SELECT id FROM repeatable_holidays WHERE branchcode = ? AND weekday = ?"); - $isWeekdayHoliday->execute($self->{branchcode}, $options{weekday}); - if ($isWeekdayHoliday->rows) { - my $id = $isWeekdayHoliday->fetchrow; - $isWeekdayHoliday->finish; # Close the last query - - my $updateExceptions = $dbh->prepare("UPDATE special_holidays SET isexception = 0 WHERE (WEEKDAY(CONCAT(special_holidays.year,'-',special_holidays.month,'-',special_holidays.day)) = ?) AND (branchcode = ?)"); - $updateExceptions->execute($options{weekday}, $self->{branchcode}); - $updateExceptions->finish; # Close the last query - - my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE id = ?"); - $deleteHoliday->execute($id); - delete($self->{'week_days_holidays'}->{$options{weekday}}); - } else { - $isWeekdayHoliday->finish; # Close the last query - - my $isDayMonthHoliday = $dbh->prepare("SELECT id FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)"); - $isDayMonthHoliday->execute($self->{branchcode}, $options{day}, $options{month}); - if ($isDayMonthHoliday->rows) { - my $id = $isDayMonthHoliday->fetchrow; - $isDayMonthHoliday->finish; - my $updateExceptions = $dbh->prepare("UPDATE special_holidays SET isexception = 0 WHERE (special_holidays.branchcode = ?) AND (special_holidays.day = ?) and (special_holidays.month = ?)"); - $updateExceptions->execute($self->{branchcode}, $options{day}, $options{month}); - $updateExceptions->finish; # Close the last query - - my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (id = ?)"); - $deleteHoliday->execute($id); - delete($self->{'day_month_holidays'}->{"$options{month}/$options{day}"}); - } - } - } - - # changed the 'single_holidays' table, lets force/reset its cache - my $cache = Koha::Caches->get_instance(); - my $key = $self->{branchcode} . "_holidays"; - $cache->clear_from_cache($key); - - return $self; -} -=head2 delete_holiday_range - - delete_holiday_range(day => $day, - month => $month, - year => $year); - -Delete a holiday range of dates for $self->{branchcode}. - -C<$day> Is the day month to make the date to delete. - -C<$month> Is month to make the date to delete. - -C<$year> Is year to make the date to delete. - -=cut - -sub delete_holiday_range { - my $self = shift; - my %options = @_; - - my $dbh = C4::Context->dbh(); - my $sth = $dbh->prepare("DELETE FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)"); - $sth->execute($self->{branchcode}, $options{day}, $options{month}, $options{year}); - - # changed the 'single_holidays' table, lets force/reset its cache - my $cache = Koha::Caches->get_instance(); - my $key = $self->{branchcode} . "_holidays"; - $cache->clear_from_cache($key); - -} - -=head2 delete_holiday_range_repeatable - - delete_holiday_range_repeatable(day => $day, - month => $month); - -Delete a holiday for $self->{branchcode}. - -C<$day> Is the day month to make the date to delete. - -C<$month> Is month to make the date to delete. - -=cut - -sub delete_holiday_range_repeatable { - my $self = shift; - my %options = @_; - - my $dbh = C4::Context->dbh(); - my $sth = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)"); - $sth->execute($self->{branchcode}, $options{day}, $options{month}); -} - -=head2 delete_exception_holiday_range - - delete_exception_holiday_range(weekday => $weekday - day => $day, - month => $month, - year => $year); - -Delete a holiday for $self->{branchcode}. - -C<$day> Is the day month to make the date to delete. - -C<$month> Is month to make the date to delete. - -C<$year> Is year to make the date to delete. - -=cut - -sub delete_exception_holiday_range { - my $self = shift; - my %options = @_; - - my $dbh = C4::Context->dbh(); - my $sth = $dbh->prepare("DELETE FROM special_holidays WHERE (branchcode = ?) AND (isexception = 1) AND (day = ?) AND (month = ?) AND (year = ?)"); - $sth->execute($self->{branchcode}, $options{day}, $options{month}, $options{year}); - - # changed the 'single_holidays' table, lets force/reset its cache - my $cache = Koha::Caches->get_instance(); - my $key = $self->{branchcode} . "_holidays"; - $cache->clear_from_cache($key); -} - -=head2 isHoliday - - $isHoliday = isHoliday($day, $month $year); - -C<$day> Is the day to check whether if is a holiday or not. - -C<$month> Is the month to check whether if is a holiday or not. - -C<$year> Is the year to check whether if is a holiday or not. - -=cut - -sub isHoliday { - my ($self, $day, $month, $year) = @_; - # FIXME - date strings are stored in non-padded metric format. should change to iso. - $month=$month+0; - $year=$year+0; - $day=$day+0; - my $weekday = &Date::Calc::Day_of_Week($year, $month, $day) % 7; - my $weekDays = $self->get_week_days_holidays(); - my $dayMonths = $self->get_day_month_holidays(); - my $exceptions = $self->get_exception_holidays(); - my $singles = $self->get_single_holidays(); - if (defined($exceptions->{"$year/$month/$day"})) { - return 0; - } else { - if ((exists($weekDays->{$weekday})) || - (exists($dayMonths->{"$month/$day"})) || - (exists($singles->{"$year/$month/$day"}))) { - return 1; - } else { - return 0; - } - } - -} - -=head2 copy_to_branch - - $calendar->copy_to_branch($target_branch) - -=cut - -sub copy_to_branch { - my ($self, $target_branch) = @_; - - croak "No target_branch" unless $target_branch; - - my $target_calendar = C4::Calendar->new(branchcode => $target_branch); - - my ($y, $m, $d) = Today(); - my $today = sprintf ISO_DATE_FORMAT, $y,$m,$d; - - my $wdh = $self->get_week_days_holidays; - my $target_wdh = $target_calendar->get_week_days_holidays; - foreach my $key (keys %$wdh) { - unless (grep { $_ eq $key } keys %$target_wdh) { - $target_calendar->insert_week_day_holiday( weekday => $key, %{ $wdh->{$key} } ) - } - } - - my $dmh = $self->get_day_month_holidays; - my $target_dmh = $target_calendar->get_day_month_holidays; - foreach my $values (values %$dmh) { - unless (grep { $_->{day} eq $values->{day} && $_->{month} eq $values->{month} } values %$target_dmh) { - $target_calendar->insert_day_month_holiday(%{ $values }); - } - } - - my $exception_holidays = $self->get_exception_holidays; - my $target_exceptions = $target_calendar->get_exception_holidays; - foreach my $values ( grep {$_->{date} gt $today} values %{ $exception_holidays }) { - unless ( grep { $_->{date} eq $values->{date} } values %$target_exceptions) { - $target_calendar->insert_exception_holiday(%{ $values }); - } - } - - my $single_holidays = $self->get_single_holidays; - my $target_singles = $target_calendar->get_single_holidays; - foreach my $values ( grep {$_->{date} gt $today} values %{ $single_holidays }) { - unless ( grep { $_->{date} eq $values->{date} } values %$target_singles){ - $target_calendar->insert_single_holiday(%{ $values }); - } - } - - return 1; -} - -1; - -__END__ - -=head1 AUTHOR - -Koha Physics Library UNLP - -=cut - diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 03fe5c78f2..968af5b250 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -45,7 +45,7 @@ use Koha::Account; use Koha::AuthorisedValues; use Koha::Biblioitems; use Koha::DateUtils; -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::Checkouts; use Koha::Illrequests; use Koha::Items; @@ -1355,7 +1355,7 @@ sub checkHighHolds { branchcode => $branchcode, } ); - my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode ); + my $calendar = Koha::DiscreteCalendar->new( branchcode => $branchcode, days_mode => $daysmode ); my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $borrower ); @@ -2509,7 +2509,7 @@ sub _calculate_new_debar_dt { my $new_debar_dt; # Use the calendar or not to calculate the debarment date if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) { - my $calendar = Koha::Calendar->new( + my $calendar = Koha::DiscreteCalendar->new( branchcode => $branchcode, days_mode => 'Calendar' ); @@ -3704,7 +3704,7 @@ sub CalcDateDue { else { # days $dur = DateTime::Duration->new( days => $loanlength->{$length_key}); } - my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode ); + my $calendar = Koha::DiscreteCalendar->new( branchcode => $branch, days_mode => $daysmode ); $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} ); if ($loanlength->{lengthunit} eq 'days') { $datedue->set_hour(23); @@ -3743,14 +3743,13 @@ sub CalcDateDue { } } if ( $daysmode ne 'Days' ) { - my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode ); + my $calendar = Koha::DiscreteCalendar->new( branchcode => $branch, days_mode => $daysmode ); if ( $calendar->is_holiday($datedue) ) { # Don't return on a closed day - $datedue = $calendar->prev_open_days( $datedue, 1 ); + $datedue = $calendar->prev_open_days( $datedue )->set(hour => 23, minute => 59); } } } - return $datedue; } diff --git a/C4/HoldsQueue.pm b/C4/HoldsQueue.pm index b0af40b938..a9ec5a65ff 100644 --- a/C4/HoldsQueue.pm +++ b/C4/HoldsQueue.pm @@ -33,6 +33,7 @@ use Koha::Items; use Koha::Patrons; use Koha::Libraries; +use Koha::DiscreteCalendar; use List::Util qw(shuffle); use List::MoreUtils qw(any); use Data::Dumper; @@ -82,7 +83,7 @@ sub TransportCostMatrix { }; if ( !$ignore_holds_queue_skip_closed && C4::Context->preference("HoldsQueueSkipClosed") ) { - $calendars->{$from} ||= Koha::Calendar->new( branchcode => $from ); + $calendars->{$from} ||= Koha::DiscreteCalendar->new( branchcode => $from ); $transport_cost_matrix{$to}{$from}{disable_transfer} ||= $calendars->{$from}->is_holiday( $today ); } @@ -798,7 +799,7 @@ sub load_branches_to_pull_from { my $today = dt_from_string(); if ( C4::Context->preference('HoldsQueueSkipClosed') ) { @branches_to_use = grep { - !Koha::Calendar->new( branchcode => $_ ) + !Koha::DiscreteCalendar->new({ branchcode => $_ }) ->is_holiday( $today ) } @branches_to_use; } diff --git a/C4/Overdues.pm b/C4/Overdues.pm index d484c67b0f..920890cc54 100644 --- a/C4/Overdues.pm +++ b/C4/Overdues.pm @@ -33,6 +33,7 @@ use C4::Accounts; use C4::Log; # logaction use C4::Debug; use Koha::DateUtils; +use Koha::DiscreteCalendar; use Koha::Account::Lines; use Koha::Account::Offsets; use Koha::Libraries; @@ -304,7 +305,7 @@ sub get_chargeable_units { my $charge_duration; if ($unit eq 'hours') { if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') { - my $calendar = Koha::Calendar->new( branchcode => $branchcode ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode }); $charge_duration = $calendar->hours_between( $date_due, $date_returned ); } else { $charge_duration = $date_returned->delta_ms( $date_due ); @@ -316,7 +317,7 @@ sub get_chargeable_units { } else { # days if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') { - my $calendar = Koha::Calendar->new( branchcode => $branchcode ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode }); $charge_duration = $calendar->days_between( $date_due, $date_returned ); } else { $charge_duration = $date_returned->delta_days( $date_due ); diff --git a/C4/Reserves.pm b/C4/Reserves.pm index 87a425008f..b40a08fd4e 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -34,10 +34,10 @@ use C4::Members::Messaging; use C4::Members; use Koha::Account::Lines; use Koha::Biblios; -use Koha::Calendar; use Koha::CirculationRules; use Koha::Database; use Koha::DateUtils; +use Koha::DiscreteCalendar; use Koha::Hold; use Koha::Holds; use Koha::ItemTypes; @@ -939,7 +939,7 @@ sub CancelExpiredReserves { my $holds = Koha::Holds->search( $params ); while ( my $hold = $holds->next ) { - my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $hold->branchcode }); next if !$cancel_on_holidays && $calendar->is_holiday( $today ); diff --git a/Koha/Calendar.pm b/Koha/Calendar.pm deleted file mode 100644 index 05f9176716..0000000000 --- a/Koha/Calendar.pm +++ /dev/null @@ -1,555 +0,0 @@ -package Koha::Calendar; - -use Modern::Perl; - -use Carp; -use DateTime; -use DateTime::Duration; -use C4::Context; -use Koha::Caches; -use Koha::Exceptions; - -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 ( !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 $weekly_closed_days_sth = $dbh->prepare( -'SELECT weekday FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NOT NULL' - ); - $weekly_closed_days_sth->execute( $branch ); - $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; - while ( my $tuple = $weekly_closed_days_sth->fetchrow_hashref ) { - $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1; - } - my $day_month_closed_days_sth = $dbh->prepare( -'SELECT day, month FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NULL' - ); - $day_month_closed_days_sth->execute( $branch ); - $self->{day_month_closed_days} = {}; - while ( my $tuple = $day_month_closed_days_sth->fetchrow_hashref ) { - $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } = - 1; - } - - $self->{test} = 0; - return; -} - -sub _holidays { - my ($self) = @_; - - my $key = $self->{branchcode} . "_holidays"; - my $cache = Koha::Caches->get_instance(); - my $holidays = $cache->get_from_cache($key); - - # $holidays looks like: - # { - # 20131122 => 1, # single_holiday - # 20131123 => 0, # exception_holiday - # ... - # } - - # Populate the cache if necessary - unless ($holidays) { - my $dbh = C4::Context->dbh; - $holidays = {}; - - # Add holidays for each branch - my $holidays_sth = $dbh->prepare( -'SELECT day, month, year, MAX(isexception) FROM special_holidays WHERE branchcode = ? GROUP BY day, month, year' - ); - $holidays_sth->execute($self->{branchcode}); - - while ( my ( $day, $month, $year, $exception ) = - $holidays_sth->fetchrow ) - { - my $datestring = - sprintf( "%04d", $year ) - . sprintf( "%02d", $month ) - . sprintf( "%02d", $day ); - - $holidays->{$datestring} = $exception ? 0 : 1; - } - $cache->set_in_cache( $key, $holidays, { expiry => 76800 } ); - } - return $holidays // {}; -} - -sub addDuration { - my ( $self, $startdate, $add_duration, $unit ) = @_; - - - Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addDuration: days_mode") - unless exists $self->{days_mode}; - - # Default to days duration (legacy support I guess) - if ( ref $add_duration ne 'DateTime::Duration' ) { - $add_duration = DateTime::Duration->new( days => $add_duration ); - } - - $unit ||= 'days'; # default days ? - my $dt; - if ( $unit eq 'hours' ) { - # Fixed for legacy support. Should be set as a branch parameter - my $return_by_hour = 10; - - $dt = $self->addHours($startdate, $add_duration, $return_by_hour); - } else { - # days - $dt = $self->addDays($startdate, $add_duration); - } - return $dt; -} - -sub addHours { - my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_; - my $base_date = $startdate->clone(); - - $base_date->add_duration($hours_duration); - - # If we are using the calendar behave for now as if Datedue - # was the chosen option (current intended behaviour) - - Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addHours: days_mode") - unless exists $self->{days_mode}; - - if ( $self->{days_mode} ne 'Days' && - $self->is_holiday($base_date) ) { - - if ( $hours_duration->is_negative() ) { - $base_date = $self->prev_open_days($base_date, 1); - } else { - $base_date = $self->next_open_days($base_date, 1); - } - - $base_date->set_hour($return_by_hour); - - } - - return $base_date; -} - -sub addDays { - my ( $self, $startdate, $days_duration ) = @_; - my $base_date = $startdate->clone(); - - Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addDays: days_mode") - unless exists $self->{days_mode}; - - if ( $self->{days_mode} eq 'Calendar' ) { - # use the calendar to skip all days the library is closed - # when adding - my $days = abs $days_duration->in_units('days'); - - if ( $days_duration->is_negative() ) { - while ($days) { - $base_date = $self->prev_open_days($base_date, 1); - --$days; - } - } else { - while ($days) { - $base_date = $self->next_open_days($base_date, 1); - --$days; - } - } - - } else { # Days, Datedue or Dayweek - # use straight days, then use calendar to push - # the date to the next open day as appropriate - # if Datedue or Dayweek - $base_date->add_duration($days_duration); - - if ( $self->{days_mode} eq 'Datedue' || - $self->{days_mode} eq 'Dayweek') { - # Datedue or Dayweek, then use the calendar to push - # the date to the next open day if holiday - if ( $self->is_holiday($base_date) ) { - my $dow = $base_date->day_of_week; - my $days = $days_duration->in_units('days'); - # Is it a period based on weeks - my $push_amt = $days % 7 == 0 ? - $self->get_push_amt($base_date) : 1; - if ( $days_duration->is_negative() ) { - $base_date = $self->prev_open_days($base_date, $push_amt); - } else { - $base_date = $self->next_open_days($base_date, $push_amt); - } - } - } - } - - return $base_date; -} - -sub get_push_amt { - my ( $self, $base_date) = @_; - - Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->get_push_amt: days_mode") - unless exists $self->{days_mode}; - - my $dow = $base_date->day_of_week; - # Representation fix - # DateTime object dow (1-7) where Monday is 1 - # Arrays are 0-based where 0 = Sunday, not 7. - if ( $dow == 7 ) { - $dow = 0; - } - - return ( - # We're using Dayweek useDaysMode option - $self->{days_mode} eq 'Dayweek' && - # It's not a permanently closed day - !$self->{weekly_closed_days}->[$dow] - ) ? 7 : 1; -} - -sub is_holiday { - my ( $self, $dt ) = @_; - - my $localdt = $dt->clone(); - my $day = $localdt->day; - my $month = $localdt->month; - my $ymd = $localdt->ymd(''); - - #Change timezone to "floating" before doing any calculations or comparisons - $localdt->set_time_zone("floating"); - $localdt->truncate( to => 'day' ); - - return $self->_holidays->{$ymd} if defined($self->_holidays->{$ymd}); - - my $dow = $localdt->day_of_week; - # Representation fix - # DateTime object dow (1-7) where Monday is 1 - # Arrays are 0-based where 0 = Sunday, not 7. - if ( $dow == 7 ) { - $dow = 0; - } - - if ( $self->{weekly_closed_days}->[$dow] == 1 ) { - return 1; - } - - if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) { - return 1; - } - - # damn have to go to work after all - return 0; -} - -sub next_open_days { - my ( $self, $dt, $to_add ) = @_; - - Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->next_open_days: days_mode") - unless exists $self->{days_mode}; - - my $base_date = $dt->clone(); - - $base_date->add(days => $to_add); - while ($self->is_holiday($base_date)) { - my $add_next = $self->get_push_amt($base_date); - $base_date->add(days => $add_next); - } - return $base_date; -} - -sub prev_open_days { - my ( $self, $dt, $to_sub ) = @_; - - Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->get_open_days: days_mode") - unless exists $self->{days_mode}; - - my $base_date = $dt->clone(); - - # It feels logical to be passed a positive number, though we're - # subtracting, so do the right thing - $to_sub = $to_sub > 0 ? 0 - $to_sub : $to_sub; - - $base_date->add(days => $to_sub); - - while ($self->is_holiday($base_date)) { - my $sub_next = $self->get_push_amt($base_date); - # Ensure we're subtracting when we need to be - $sub_next = $sub_next > 0 ? 0 - $sub_next : $sub_next; - $base_date->add(days => $sub_next); - } - - return $base_date; -} - -sub days_forward { - my $self = shift; - my $start_dt = shift; - my $num_days = shift; - - Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->days_forward: days_mode") - unless exists $self->{days_mode}; - - return $start_dt unless $num_days > 0; - - my $base_dt = $start_dt->clone(); - - while ($num_days--) { - $base_dt = $self->next_open_days($base_dt, 1); - } - - return $base_dt; -} - -sub days_between { - my $self = shift; - my $start_dt = shift; - my $end_dt = shift; - - # Change time zone for date math and swap if needed - $start_dt = $start_dt->clone->set_time_zone('floating'); - $end_dt = $end_dt->clone->set_time_zone('floating'); - if( $start_dt->compare($end_dt) > 0 ) { - ( $start_dt, $end_dt ) = ( $end_dt, $start_dt ); - } - - # start and end should not be closed days - my $delta_days = $start_dt->delta_days($end_dt)->delta_days; - while( $start_dt->compare($end_dt) < 1 ) { - $delta_days-- if $self->is_holiday($start_dt); - $start_dt->add( days => 1 ); - } - return DateTime::Duration->new( days => $delta_days ); -} - -sub hours_between { - my ($self, $start_date, $end_date) = @_; - my $start_dt = $start_date->clone()->set_time_zone('floating'); - my $end_dt = $end_date->clone()->set_time_zone('floating'); - - 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; - while( $start_dt->compare($end_dt) < 1 ) { - $skipped_days++ if $self->is_holiday($start_dt); - $start_dt->add( days => 1 ); - } - - if ($skipped_days) { - $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days)); - } - - return $duration; -} - -sub set_daysmode { - my ( $self, $mode ) = @_; - - # if not testing this is a no op - if ( $self->{test} ) { - $self->{days_mode} = $mode; - } - - return; -} - -sub clear_weekly_closed_days { - my $self = shift; - $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; # Sunday only - return; -} - -1; -__END__ - -=head1 NAME - -Koha::Calendar - Object containing a branches calendar - -=head1 SYNOPSIS - - use Koha::Calendar - - my $c = Koha::Calendar->new( branchcode => 'MAIN' ); - my $dt = dt_from_string(); - - # are we open - $open = $c->is_holiday($dt); - # when will item be due if loan period = $dur (a DateTime::Duration object) - $duedate = $c->addDuration($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 addDuration - - my $dt = $calendar->addDuration($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 addHours - - my $dt = $calendar->addHours($date, $dur, $return_by_hour ) - -C<$date> is a DateTime object representing the starting date of the interval. - -C<$offset> is a DateTime::Duration to add to it - -C<$return_by_hour> is an integer value representing the opening hour for the branch - -=head2 get_push_amt - - my $amt = $calendar->get_push_amt($date) - -C<$date> is a DateTime object representing a closed return date - -Using the days_mode syspref value and the nature of the closed return -date, return how many days we should jump forward to find another return date - -=head2 addDays - - my $dt = $calendar->addDays($date, $dur) - -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 a 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. Always returns a positive number irrespective of the -relative order of the parameters. - -Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day - -=head2 hours_between - -$duration = $calendar->hours_between($start_dt, $end_dt); - -Passed two dates returns a DateTime::Duration object measuring the length between them -ignoring closed days. Always returns a positive number irrespective of the -relative order of the parameters. - -Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day - -=head2 next_open_days - -$datetime = $calendar->next_open_days($duedate_dt, $to_add) - -Passed a Datetime and number of days, returns another Datetime representing -the next open day after adding the passed number of days. It is intended for -use to calculate the due date when useDaysMode syspref is set to either -'Datedue', 'Calendar' or 'Dayweek'. - -=head2 prev_open_days - -$datetime = $calendar->prev_open_days($duedate_dt, $to_sub) - -Passed a Datetime and a number of days, returns another Datetime -representing the previous open day after subtracting the number of passed -days. It is intended for use to calculate the due date when useDaysMode -syspref is set to either 'Datedue', 'Calendar' or 'Dayweek'. - -=head2 days_forward - -$datetime = $calendar->days_forward($start_dt, $to_add) - -Passed a Datetime and number of days, returns another Datetime representing -the next open day after adding the passed number of days. It is intended for -use to calculate the due date when useDaysMode syspref is set to either -'Datedue', 'Calendar' or 'Dayweek'. - -=head2 set_daysmode - -For testing only allows the calling script to change days mode - -=head2 clear_weekly_closed_days - -In test mode changes the testing set of closed days to a new set with -no closed days. TODO passing an array of closed days to this would -allow testing of more configurations - -=head2 add_holiday - -Passed a datetime object this will add it to the calendar's list of -closed days. This is for testing so that we can alter the Calenfar object's -list of specified dates - -=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 - -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 3 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, see . diff --git a/Koha/Charges/Fees.pm b/Koha/Charges/Fees.pm index 7eff76ba79..c600bdd4c9 100644 --- a/Koha/Charges/Fees.pm +++ b/Koha/Charges/Fees.pm @@ -21,7 +21,7 @@ use Modern::Perl; use Carp qw( carp confess ); -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::DateUtils qw( dt_from_string ); use Koha::Exceptions; @@ -109,7 +109,7 @@ sub accumulate_rentalcharge { return 0 unless $rentalcharge_increment && $rentalcharge_increment > 0; my $duration; - my $calendar = Koha::Calendar->new( branchcode => $self->library->id ); + my $calendar = Koha::DiscreteCalendar->new( branchcode => $self->library->id ); if ( $units eq 'hours' ) { if ( $itemtype->rentalcharge_hourly_calendar ) { diff --git a/Koha/DiscreteCalendar.pm b/Koha/DiscreteCalendar.pm new file mode 100644 index 0000000000..4875e7c189 --- /dev/null +++ b/Koha/DiscreteCalendar.pm @@ -0,0 +1,1332 @@ +package Koha::DiscreteCalendar; + +# 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 3 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, see . + +#####Sets holiday periods for each branch. Datedues will be extended if branch is closed -TG +use Modern::Perl; + +use CGI qw ( -utf8 ); +use Carp; +use DateTime; +use DateTime::Format::Strptime; +use Data::Dumper; + +use C4::Context; +use C4::Output; +use Koha::Database; +use Koha::DateUtils; + +# Global variables to make code more readable +our $HOLIDAYS = { + EXCEPTION => 'E', + REPEATABLE => 'R', + SINGLE => 'S', + NEED_VALIDATION => 'N', + FLOAT => 'F', + WEEKLY => 'W', + NONE => 'none' +}; + +=head1 NAME + +Koha::DiscreteCalendar - Object containing a branches calendar, working with the SQL database + +=head1 SYNOPSIS + + use Koha::DiscreteCalendar + + my $c = Koha::DiscreteCalendar->new({ branchcode => 'MAIN' }); + my $dt = dt_from_string(); + + # are we open + $open = $c->is_holiday($dt); + # when will item be due if loan period = $dur (a DateTime::Duration object) + $duedate = $c->addDuration($dt, $dur, 'days'); + + +=head1 DESCRIPTION + + Implements a new Calendar object, but uses the SQL database to keep track of days and holidays. + This results in a performance gain since the optimization is done by the MySQL database/team. + +=head1 METHODS + +=head2 new : Create a (discrete) calendar object + +my $calendar = Koha::DiscreteCalendar->new({ branchcode => 'MAIN' }); + +The option branchcode is required + +=cut + +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 ( !defined $self->{branchcode} ) { + croak 'No branchcode argument passed to Koha::DiscreteCalendar->new'; + } + if ( ref $self->{branchcode} eq 'Koha::Library' ) { + $self->{branchcode} = $self->{branchcode}->branchcode; + } + $self->_init(); + + return $self; +} + +sub _init { + my $self = shift; + $self->{days_mode} ||= C4::Context->preference('useDaysMode'); + #If the branchcode doesn't exist we use the default calendar. + my $schema = Koha::Database->new->schema; + my $branchcode = $self->{branchcode}; + my $dtf = $schema->storage->datetime_parser; + my $today = $dtf->format_datetime(DateTime->today); + my $rs = $schema->resultset('DiscreteCalendar')->single( + { + branchcode => $branchcode, + date => $today + } + ); + #use default if no calendar is found + if (!$rs) { + $self->{branchcode} = undef; + $self->{no_branch_selected} = 1; + } + +} + +=head2 get_dates_info + + my @dates = $calendar->get_dates_info(); + +Returns an array of hashes representing the dates in this calendar. The hash +contains the fields C<$date>, C<$outputdate>, C<$holiday_type>, C<$open_hour>, +C<$close_hour> and C<$note>. + +=cut + +sub get_dates_info { + my $self = shift; + my $branchcode = $self->{branchcode}; + my @datesInfos =(); + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode + }, + { + select => [ 'date', { DATE => 'date' } ], + as => [qw/ date date /], + columns =>[ qw/ holiday_type open_hour close_hour note/] + }, + ); + + while (my $date = $rs->next()) { + my $outputdate = dt_from_string( $date->date(), 'iso'); + $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } ); + push @datesInfos, { + date => $date->date(), + outputdate => $outputdate, + holiday_type => $date->holiday_type() , + open_hour => $date->open_hour(), + close_hour => $date->close_hour(), + note => $date->note() + }; + } + + return @datesInfos; +} + +=head2 add_new_branch + + Koha::DiscreteCalendar->add_new_branch($copyBranch, $newBranch) + +This method will copy everything from a given branch to a new branch +C<$copyBranch> is the branch to copy from +C<$newBranch> is the branch to be created, and copy into + +=cut + +sub add_new_branch { + my ( $classname, $copyBranch, $newBranch) = @_; + + my $schema = Koha::Database->new->schema; + + my $branch_rs = $schema->resultset('DiscreteCalendar')->search({ + branchcode => $copyBranch + }); + + while (my $row = $branch_rs->next()) { + $schema->resultset('DiscreteCalendar')->create({ + date => $row->date(), + branchcode => $newBranch, + is_opened => $row->is_opened(), + holiday_type => $row->holiday_type(), + open_hour => $row->open_hour(), + close_hour => $row->close_hour(), + }); + } + +} + +=head2 get_date_info + + my $date = $calendar->get_date_info; + +Returns a reference-to-hash representing a DiscreteCalendar date data object. +The hash contains the fields C<$date>, C<$outputdate>, C<$holiday_type>, +C<$open_hour>, C<$close_hour> and C<$note>. + +=cut + +sub get_date_info { + my ($self, $date) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + #String dates for Database usage + my $date_string = $dtf->format_datetime($date); + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + select => [ 'date', { DATE => 'date' } ], + as => [qw/ date date /], + where => \['DATE(?) = date', $date_string ], + columns =>[ qw/ branchcode holiday_type open_hour close_hour note/] + }, + ); + my $dateDTO; + while (my $date = $rs->next()) { + $dateDTO = { + date => $date->date(), + branchcode => $date->branchcode(), + holiday_type => $date->holiday_type() , + open_hour => $date->open_hour(), + close_hour => $date->close_hour(), + note => $date->note() + }; + } + + return $dateDTO; +} + +=head2 get_max_date + + my $maxDate = $calendar->get_max_date(); + +Returns the furthest date available in the databse of current branch. + +=cut + +sub get_max_date { + my $self = shift; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode + }, + { + select => [{ MAX => 'date' } ], + as => [qw/ max /], + } + ); + + return $rs->next()->get_column('max'); +} + +=head2 get_min_date + + my $minDate = $calendar->get_min_date(); + +Returns the oldest date available in the databse of current branch. + +=cut + +sub get_min_date { + my $self = shift; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode + }, + { + select => [{ MIN => 'date' } ], + as => [qw/ min /], + } + ); + + return $rs->next()->get_column('min'); +} + +=head2 get_unique_holidays + + my @unique_holidays = $calendar->get_unique_holidays(); + +Returns an array of all the date objects that are unique holidays. + +=cut + +sub get_unique_holidays { + my $self = shift; + my $exclude_past = shift || 1; + my $branchcode = $self->{branchcode}; + my @unique_holidays; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + holiday_type => $HOLIDAYS->{EXCEPTION} + }, + { + select => [{ DATE => 'date' }, 'note' ], + as => [qw/ date note/], + where => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ), + } + ); + while (my $date = $rs->next()) { + my $outputdate = dt_from_string($date->date(), 'iso'); + $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } ); + push @unique_holidays, { + date => $date->date(), + outputdate => $outputdate, + note => $date->note() + } + } + + return @unique_holidays; +} + +=head2 get_float_holidays + + my @float_holidays = $calendar->get_float_holidays(); + +Returns an array of all the date objects that are float holidays. + +=cut + +sub get_float_holidays { + my $self = shift; + my $exclude_past = shift || 1; + my $branchcode = $self->{branchcode}; + my @float_holidays; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + holiday_type => $HOLIDAYS->{FLOAT} + }, + { + select => [{ DATE => 'date' }, 'note' ], + as => [qw/ date note/], + where => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ), + } + ); + while (my $date = $rs->next()) { + my $outputdate = dt_from_string($date->date(), 'iso'); + $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } ); + push @float_holidays, { + date => $date->date(), + outputdate => $outputdate, + note => $date->note() + } + } + + return @float_holidays; +} + +=head2 get_need_validation_holidays + + my @need_validation_holidays = $calendar->get_need_validation_holidays(); + +Returns an array of all the date objects that are float holidays in need of validation. + +=cut + +sub get_need_validation_holidays { + my $self = shift; + my $exclude_past = shift || 1; + my $branchcode = $self->{branchcode}; + my @need_validation_holidays; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + holiday_type => $HOLIDAYS->{NEED_VALIDATION} + }, + { + select => [{ DATE => 'date' }, 'note' ], + as => [qw/ date note/], + where => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ), + } + ); + while (my $date = $rs->next()) { + my $outputdate = dt_from_string($date->date(), 'iso'); + $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } ); + push @need_validation_holidays, { + date => $date->date(), + outputdate => $outputdate, + note => $date->note() + } + } + + return @need_validation_holidays; +} + +=head2 get_repeatable_holidays + + my @repeatable_holidays = $calendar->get_repeatable_holidays(); + +Returns an array of all the date objects that are repeatable holidays. + +=cut + +sub get_repeatable_holidays { + my $self = shift; + my $exclude_past = shift || 1; + my $branchcode = $self->{branchcode}; + my @repeatable_holidays; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + holiday_type => $HOLIDAYS->{'REPEATABLE'}, + + }, + { + select => \[ 'distinct DAY(date), MONTH(date), note'], + as => [qw/ day month note/], + where => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ), + } + ); + + while (my $date = $rs->next()) { + push @repeatable_holidays, { + day => $date->get_column('day'), + month => $date->get_column('month'), + note => $date->note() + }; + } + + return @repeatable_holidays; +} + +=head2 get_week_days_holidays + + my @week_days_holidays = $calendar->get_week_days_holidays; + +Returns an array of all the date objects that are weekly holidays. + +=cut + +sub get_week_days_holidays { + my $self = shift; + my $exclude_past = shift || 1; + my $branchcode = $self->{branchcode}; + my @week_days; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + holiday_type => $HOLIDAYS->{WEEKLY}, + branchcode => $branchcode, + }, + { + select => [{ DAYOFWEEK => 'date'}, 'note'], + as => [qw/ weekday note /], + distinct => 1, + where => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ), + } + ); + + while (my $date = $rs->next()) { + push @week_days, { + weekday => ($date->get_column('weekday') -1), + note => $date->note() + }; + } + + return @week_days; +} + +=head2 edit_holiday + +Modifies a date or a range of dates + +C<$title> Is the title to be modified for the holiday formed by $year/$month/$day. + +C<$weekday> Is the day of week for the holiday or the value everyday when it's for the whole range. + +C<$holiday_type> Is the type of the holiday : + E : Exception holiday, single day. + F : Floating holiday, different day each year. + N : Needs validation, copied float holiday from the past + R : Repeatable holiday, repeated on same date. + W : Weekly holiday, same day of the week. + +C<$open_hour> Is the opening hour. +C<$close_hour> Is the closing hour. +C<$start_date> Is the start of the range of dates. +C<$end_date> Is the end of the range of dates. +C<$delete_type> Delete all +C<$today> Today based on the local date, using JavaScript. + +=cut + +sub edit_holiday { + my $self = shift; + my ($params) = @_; + + my $title = $params->{title}; + my $weekday = $params->{weekday} || ''; + my $holiday_type = $params->{holiday_type}; + + my $start_date = $params->{start_date}; + my $end_date = $params->{end_date}; + + my $open_hour = $params->{open_hour} || ''; + my $close_hour = $params->{close_hour} || ''; + + my $delete_type = $params->{delete_type} || undef; + my $today = $params->{today} || dt_from_string()->truncate( to => 'day' ); + + my $branchcode = $self->{branchcode}; + + # When override param is set, this function will allow past dates to be set as holidays, + # otherwise it will not. This is meant to only be used for testing. + my $override = $params->{override} || 0; + + my $schema = Koha::Database->new->schema; + $schema->{AutoCommit} = 0; + $schema->storage->txn_begin; + my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T"); + + #String dates for Database usage + my $start_date_string = $dtf->format_datetime($start_date->clone->truncate(to => 'day')); + my $end_date_string = $dtf->format_datetime($end_date->clone->truncate(to => 'day')); + $today = $dtf->format_datetime($today->clone->truncate(to => 'day')); + my %updateValues = ( + is_opened => 0, + note => $title, + holiday_type => $holiday_type, + ); + $updateValues{open_hour} = $open_hour if $open_hour ne ''; + $updateValues{close_hour} = $close_hour if $close_hour ne ''; + + if ($holiday_type eq $HOLIDAYS->{WEEKLY}) { + #Update weekly holidays + if ($start_date_string eq $end_date_string) { + $end_date_string = $self->get_max_date(); + } + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => \[ 'DAYOFWEEK(date) = ? AND date >= DATE(?) AND date <= DATE(?)', $weekday, $start_date_string, $end_date_string], + } + ); + + while (my $date = $rs->next()) { + $date->update(\%updateValues); + } + } elsif ($holiday_type eq $HOLIDAYS->{EXCEPTION} || $holiday_type eq $HOLIDAYS->{FLOAT} || $holiday_type eq $HOLIDAYS->{NEED_VALIDATION}) { + #Update Exception Float and Needs Validation holidays + my $where = { date => { -between => [$start_date_string, $end_date_string]}}; + if ($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday') { + $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]}; + } + $where->{date}{'>='} = $today unless $override; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => $where, + } + ); + while (my $date = $rs->next()) { + $date->update(\%updateValues); + } + + } elsif ($holiday_type eq $HOLIDAYS->{REPEATABLE}) { + #Update repeatable holidays + my $parser = DateTime::Format::Strptime->new( + pattern => '%m-%d', + on_error => 'croak', + ); + #Format the dates to have only month-day ex: 01-04 for January 4th + $start_date = $parser->format_datetime($start_date); + $end_date = $parser->format_datetime($end_date); + my $where = { -and => [ \["(DATE_FORMAT(date, '\%m-\%d') BETWEEN ? AND ?)", $start_date, $end_date] ] }; + push @{$where->{'-and'}}, { 'date' => { '>=' => $today } } unless $override; + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => $where, + } + ); + while (my $date = $rs->next()) { + $date->update(\%updateValues); + } + + } else { + #Update date(s)/Remove holidays + my $where = { date => { -between => [$start_date_string, $end_date_string]}}; + if ($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday') { + $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]}; + } + $where->{date}{'>='} = $today unless $override; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => $where, + } + ); + #If none, the date(s) will be normal days, else, + if ($holiday_type eq 'none') { + $updateValues{holiday_type} =''; + $updateValues{is_opened} =1; + } else { + delete $updateValues{holiday_type}; + delete $updateValues{is_opened}; + } + + while (my $date = $rs->next()) { + if ($delete_type) { + if ($date->holiday_type() eq $HOLIDAYS->{WEEKLY}) { + $self->remove_weekly_holidays($weekday, \%updateValues, $today); + } elsif ($date->holiday_type() eq $HOLIDAYS->{REPEATABLE}) { + $self->remove_repeatable_holidays($start_date, $end_date, \%updateValues, $today); + } + } else { + $date->update(\%updateValues); + } + } + } + $schema->storage->txn_commit; + $schema->{AutoCommit} = 1; +} + +=head2 remove_weekly_holidays + + $calendar->remove_weekly_holidays($weekday, $updateValues, $today); + +Removes a weekly holiday and updates the days' parameters +C<$weekday> is the weekday to un-holiday +C<$updatevalues> is hashref containing the new parameters +C<$today> is today's date + +=cut + +sub remove_weekly_holidays { + my ($self, $weekday, $updateValues, $today) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 0, + holiday_type => $HOLIDAYS->{WEEKLY} + }, + { + where => {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { '>=' => $today}]}, + } + ); + + while (my $date = $rs->next()) { + $date->update($updateValues); + } +} + +=head2 remove_repeatable_holidays + + $calendar->remove_repeatable_holidays($startDate, $endDate, $today); + +Removes a repeatable holiday and updates the days' parameters +C<$startDatey> is the start date of the repeatable holiday +C<$endDate> is the end date of the repeatble holiday +C<$updatevalues> is hashref containing the new parameters +C<$today> is today's date + +=cut + +sub remove_repeatable_holidays { + my ($self, $startDate, $endDate, $updateValues, $today) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $parser = DateTime::Format::Strptime->new( + pattern => '%m-%d', + on_error => 'croak', + ); + #Format the dates to have only month-day ex: 01-04 for January 4th + $startDate = $parser->format_datetime($startDate); + $endDate = $parser->format_datetime($endDate); + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 0, + holiday_type => $HOLIDAYS->{REPEATABLE}, + }, + { + where => { -and => [date => { '>=' => $today}, \["(DATE_FORMAT(date, '\%m-\%d') BETWEEN ? AND ?)", $startDate, $endDate]]}, + } + ); + + while (my $date = $rs->next()) { + $date->update($updateValues); + } +} + +=head2 copy_to_branch + + $calendar->copy_to_branch($branch2); + +Copies the days and holidays from this branch to $branch2, ignoring dates in C<$self> +but not in C<$branch2> + +C<$branch2> the branch to copy into + +=cut + +sub copy_to_branch { + my ($self, $newBranch) =@_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + + my $copyFrom = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode + }, + { + columns => [ qw/ date is_opened note holiday_type open_hour close_hour /] + } + ); + while (my $copyDate = $copyFrom->next()) { + my $copyTo = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $newBranch, + date => $copyDate->date(), + }, + { + columns => [ qw/ date branchcode is_opened note holiday_type open_hour close_hour /] + } + ); + #if the date does not exist in the copyTO branch, than skip it. + if ($copyTo->count ==0) { + next; + } + $copyTo->next()->update({ + is_opened => $copyDate->is_opened(), + holiday_type => $copyDate->holiday_type(), + note => $copyDate->note(), + open_hour => $copyDate->open_hour(), + close_hour => $copyDate->close_hour() + }); + } +} + +=head2 is_opened + + $calendar->is_opened($date) + +Returns whether the library is open on C<$date> + +=cut + +sub is_opened { + my($self, $date) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + $date= $dtf->format_datetime($date); + #if the date is not found + my $is_opened = -1; + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => \['date = DATE(?)', $date] + } + ); + $is_opened = $rs->next()->is_opened() if $rs->count() != 0; + + return $is_opened; +} + +=head2 is_holiday + + $calendar->is_holiday($date) + +Returns whether C<$date> is a holiday or not + +=cut + +sub is_holiday { + my($self, $date) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + $date= $dtf->format_datetime($date); + #if the date is not found + my $isHoliday = -1; + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => \['date = DATE(?)', $date] + } + ); + + if ($rs->count() != 0) { + $isHoliday = ($rs->first()->is_opened() ? 0 : 1); + } + + return $isHoliday; +} + +=head2 copy_holiday + + $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber); + +Copies a holiday's parameters from a range to a new range +C<$from_startDate> the source holiday's start date +C<$from_endDate> the source holiday's end date +C<$to_startDate> the destination holiday's start date +C<$to_endDate> the destination holiday's end date +C<$daysnumber> the number of days in the range. + +Both ranges should have the same number of days in them. + +=cut + +sub copy_holiday { + my ($self, $from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber) = @_; + my $branchcode = $self->{branchcode}; + my $copyFromType = $from_startDate && $from_endDate eq '' ? 'oneDay': 'range'; + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + + if ($copyFromType eq 'oneDay') { + my $where; + $to_startDate = $dtf->format_datetime($to_startDate); + if ($to_startDate && $to_endDate) { + $to_endDate = $dtf->format_datetime($to_endDate); + $where = { date => { -between => [$to_startDate, $to_endDate]}}; + } else { + $where = { date => $to_startDate }; + } + + $from_startDate = $dtf->format_datetime($from_startDate); + my $fromDate = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + date => $from_startDate + } + ); + my $toDates = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => $where + } + ); + + my $copyDate = $fromDate->next(); + while (my $date = $toDates->next()) { + $date->update({ + is_opened => $copyDate->is_opened(), + holiday_type => $copyDate->holiday_type(), + note => $copyDate->note(), + open_hour => $copyDate->open_hour(), + close_hour => $copyDate->close_hour() + }) + } + + } else { + my $endDate = dt_from_string($from_endDate); + $to_startDate = $dtf->format_datetime($to_startDate); + $to_endDate = $dtf->format_datetime($to_endDate); + if ($daysnumber == 7) { + for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)) { + my $formatedDate = $dtf->format_datetime($tempDate); + my $fromDate = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + date => $formatedDate, + }, + { + select => [{ DAYOFWEEK => 'date' }], + as => [qw/ weekday /], + columns =>[ qw/ holiday_type note open_hour close_hour note/] + } + ); + my $copyDate = $fromDate->next(); + my $weekday = $copyDate->get_column('weekday'); + + my $toDate = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + + }, + { + where => {date => {-between => [$to_startDate, $to_endDate]}, "DAYOFWEEK(date)" => $weekday}, + } + ); + my $copyToDate = $toDate->next(); + $copyToDate->update({ + is_opened => $copyDate->is_opened(), + holiday_type => $copyDate->holiday_type(), + note => $copyDate->note(), + open_hour => $copyDate->open_hour(), + close_hour => $copyDate->close_hour() + }); + + } + } else { + my $to_startDate = dt_from_string($to_startDate); + my $to_endDate = dt_from_string($to_endDate); + for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)) { + my $from_formatedDate = $dtf->format_datetime($tempDate); + my $fromDate = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + date => $from_formatedDate, + }, + { + order_by => { -asc => 'date' } + } + ); + my $to_formatedDate = $dtf->format_datetime($to_startDate); + my $toDate = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + date => $to_formatedDate + }, + { + order_by => { -asc => 'date' } + } + ); + my $copyDate = $fromDate->next(); + $toDate->next()->update({ + is_opened => $copyDate->is_opened(), + holiday_type => $copyDate->holiday_type(), + note => $copyDate->note(), + open_hour => $copyDate->open_hour(), + close_hour => $copyDate->close_hour() + }); + $to_startDate->add(days =>1); + } + } + + + } +} + +=head2 days_between + + $cal->days_between( $start_date, $end_date ) + +Calculates the number of days the library is opened between C<$start_date> and C<$end_date> + +=cut + +sub days_between { + my ($self, $start_date, $end_date, ) = @_; + my $branchcode = $self->{branchcode}; + + if ( $start_date->compare($end_date) > 0 ) { + # swap dates + ($start_date, $end_date) = ($end_date, $start_date); + } + + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + $start_date = $dtf->format_datetime($start_date->clone->truncate(to => 'day')); + $end_date = $dtf->format_datetime($end_date->clone->truncate(to => 'day')); + + my $days_between = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 1, + }, + { + where => \['date >= date(?) AND date < date(?)', $start_date, $end_date] + } + ); + + return DateTime::Duration->new( days => $days_between->count()); +} + +=head2 next_open_day + + $open_date = $self->next_open_day($base_date); + +Returns a string representing the next day the library is open, starting from C<$base_date> + +=cut + +sub next_open_day { + my ( $self, $date ) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + my $formatted_date = $dtf->format_datetime($date); + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 1, + }, + { + where => \['date > date(?)', $formatted_date], + order_by => { -asc => 'date' }, + rows => 1 + } + ); + + my $next = $rs->next(); + unless ( $next ) { + warn "No next opened date in calendar. Reduced to current date: $formatted_date."; + return $date; + } + return dt_from_string( $next->date(), 'iso'); +} + +=head2 prev_open_day + + $open_date = $self->prev_open_day($base_date); + +Returns a string representing the closest previous day the library was open, starting from C<$base_date> + +=cut + +sub prev_open_day { + my ( $self, $date ) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + my $formatted_date = $dtf->format_datetime($date); + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 1, + }, + { + where => \['date < date(?)', $formatted_date], + order_by => { -desc => 'date' }, + rows => 1 + } + ); + + my $prev = $rs->next(); + unless ( $prev ) { + warn "No previous opened date in calendar. Reduced to current date: $formatted_date."; + return $date; + } + return dt_from_string( $prev->date(), 'iso'); +} + +=head2 days_forward + + $fwrd_date = $calendar->days_forward($start, $count) + +Returns the date C<$count> days in the future from C<$start>, ignoring days where the library is closed. + +=cut + +sub days_forward { + my $self = shift; + my $start_dt = shift; + my $num_days = shift; + + return $start_dt unless $num_days > 0; + + my $base_dt = $start_dt->clone(); + + while ($num_days--) { + $base_dt = $self->next_open_day($base_dt); + } + + return $base_dt; +} + +=head2 hours_between + + $hours = $calendar->hours_between($start_dt, $end_dt) + +Returns the number of hours between C<$start_dt> and C<$end_dt>. This is the imprecise +version, which simply calculates the number of day times 24. To take opening hours into account +see C/ + +=cut + +sub hours_between { + my ($self, $start_dt, $end_dt) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + my $start_date = $start_dt->clone(); + my $end_date = $end_dt->clone(); + my $duration = $end_date->delta_ms($start_date); + $start_date->truncate( to => 'day' ); + $end_date->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; + $start_date = $dtf->format_datetime($start_date); + $end_date = $dtf->format_datetime($end_date); + my $hours_between = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 0 + }, + { + where => {date => {-between => [$start_date, $end_date]}}, + } + ); + + if ($skipped_days = $hours_between->count()) { + $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days)); + } + + return $duration; +} + +=head2 open_hours_between + + $hours = $calendar->open_hours_between($start_date, $end_date) + +Returns the number of hours between C<$start_date> and C<$end_date>, taking into +account the opening hours of the library. + +=cut + +sub open_hours_between { + my ($self, $start_date, $end_date) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T"); + $start_date = $dtf->format_datetime($start_date); + $end_date = $dtf->format_datetime($end_date); + + my $working_hours_between = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 1, + }, + { + select => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 3600)'], + as => [qw /hours_between/], + where => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date] + } + ); + + my $loan_day = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ], + rows => 1, + } + ); + + my $return_day = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ], + rows => 1, + } + ); + + #Capture the time portion of the date + $start_date =~ /\s(.*)/; + my $loan_date_time = $1; + $end_date =~ /\s(.*)/; + my $return_date_time = $1; + + my $not_used_hours = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 1, + }, + { + select => \[ '(time_to_sec(timediff(?, ?)) + time_to_sec(timediff(?, ?)) ) / 3600', $return_day->next()->close_hour(), $return_date_time, $loan_date_time, $loan_day->next()->open_hour()], + as => [qw /not_used_hours/], + } + ); + + return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours')); +} + +=head2 addDuration + + my $dt = $calendar->addDuration($date, $dur, $unit) + +C<$date> is a DateTime object representing the starting date of the interval. +C<$offset> is a duration to add to it (DateTime::Duration objects are supported as legacy) +C<$unit> is a string value 'days' or 'hours' toflag granularity of duration + +=cut + +sub addDuration { + my ( $self, $startdate, $add_duration, $unit ) = @_; + + # Default to days duration (legacy support I guess) + if ( ref $add_duration ne 'DateTime::Duration' ) { + $add_duration = DateTime::Duration->new( days => $add_duration ); + } + + $unit ||= 'days'; # default days ? + my $dt; + + if ( $unit eq 'hours' ) { + # Fixed for legacy support. Should be set as a branch parameter + my $return_by_hour = 10; + + $dt = $self->addHours($startdate, $add_duration, $return_by_hour); + } else { + # days + $dt = $self->addDays($startdate, $add_duration); + } + + return $dt; +} + +=head2 addHours + + $end = $calendar->addHours($start, $hours_duration, $return_by_hour) + +Add C<$hours_duration> to C<$start> date. +C<$return_by_hour> is an integer value representing the opening hour for the branch + +=cut + +sub addHours { + my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_; + my $base_date = $startdate->clone(); + + $base_date->add_duration($hours_duration); + + # If we are using the calendar behave for now as if Datedue + # was the chosen option (current intended behaviour) + + if ( $self->{days_mode} ne 'Days' && + $self->is_holiday($base_date) ) { + + if ( $hours_duration->is_negative() ) { + $base_date = $self->prev_open_day($base_date); + } else { + $base_date = $self->next_open_day($base_date); + } + + $base_date->set_hour($return_by_hour); + + } + + return $base_date; +} + +=head2 addDays + + $date = $calendar->addDays($start, $duration) + +Add C<$days_duration> to C<$start> date. If the calendar's days_mode is set +to 'Calendar', it ignores closed days. Else if the calendar is set to 'Datedue' +it calculates the date normally, and then pushes to result to the next open day. + +=cut + +sub addDays { + my ( $self, $startdate, $days_duration ) = @_; + my $base_date = $startdate->clone(); + + $self->{days_mode} ||= q{}; + + if ( $self->{days_mode} eq 'Calendar' ) { + # use the calendar to skip all days the library is closed + # when adding + my $days = abs $days_duration->in_units('days'); + + if ( $days_duration->is_negative() ) { + while ($days) { + $base_date = $self->prev_open_day($base_date); + --$days; + } + } else { + while ($days) { + $base_date = $self->next_open_day($base_date); + --$days; + } + } + + } else { # Days or Datedue + # use straight days, then use calendar to push + # the date to the next open day if Datedue + $base_date->add_duration($days_duration); + + if ( $self->{days_mode} eq 'Datedue' ) { + # Datedue, then use the calendar to push + # the date to the next open day if holiday + if (!$self->is_opened($base_date) ) { + + if ( $days_duration->is_negative() ) { + $base_date = $self->prev_open_day($base_date); + } else { + $base_date = $self->next_open_day($base_date); + } + } + } + } + + return $base_date; +} + +1; diff --git a/Koha/Hold.pm b/Koha/Hold.pm index 892fb20cd5..be16fcef01 100644 --- a/Koha/Hold.pm +++ b/Koha/Hold.pm @@ -35,7 +35,7 @@ use Koha::Biblios; use Koha::Items; use Koha::Libraries; use Koha::Old::Holds; -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::Exceptions::Hold; @@ -67,7 +67,7 @@ sub age { my $age; if ( $use_calendar ) { - my $calendar = Koha::Calendar->new( branchcode => $self->branchcode ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode }); $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today ); } else { @@ -207,7 +207,7 @@ sub set_waiting { branchcode => $self->branchcode, } ); - my $calendar = Koha::Calendar->new( branchcode => $self->branchcode, days_mode => $daysmode ); + my $calendar = Koha::DiscreteCalendar->new( branchcode => $self->branchcode, days_mode => $daysmode ); $expirationdate = $calendar->days_forward( dt_from_string(), $max_pickup_delay ); } diff --git a/circ/returns.pl b/circ/returns.pl index b58e340f85..db20dec7f7 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -50,9 +50,9 @@ use C4::Reserves; use C4::RotatingCollections; use Koha::AuthorisedValues; use Koha::BiblioFrameworks; -use Koha::Calendar; use Koha::Checkouts; use Koha::DateUtils; +use Koha::DiscreteCalendar; use Koha::Holds; use Koha::Items; use Koha::Patrons; @@ -191,6 +191,7 @@ my $dropboxmode = $query->param('dropboxmode'); my $dotransfer = $query->param('dotransfer'); my $canceltransfer = $query->param('canceltransfer'); my $dest = $query->param('dest'); +my $calendar = Koha::DiscreteCalendar->new({ branchcode => $userenv_branch }); #dropbox: get last open day (today - 1) my $dropboxdate = Koha::Checkouts::calculate_dropbox_date(); diff --git a/koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css b/koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css new file mode 100644 index 0000000000..1be90a5959 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css @@ -0,0 +1,213 @@ +#jcalendar-container .ui-datepicker { + float: left; + font-size: 185%; +} + +#holidayweeklyrepeatable, +#holidaysyearlyrepeatable, +#holidaysunique, +#holidayexceptions { + font-size: 90%; + margin-bottom: 1em; +} + +#showHoliday { + margin: .5em 0; +} + +.key { + padding: 3px; + white-space: nowrap; + line-height: 230%; +} + +.ui-datepicker { + font-size: 150%; +} + +.ui-datepicker th, +.ui-datepicker .ui-datepicker-title select { + font-size: 80%; +} + +.ui-datepicker td a { + padding: .5em; +} + +.ui-datepicker td span { + padding: .5em; + border: 1px solid #BCBCBC; +} + +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + font-size: 80%; +} + +.key { + padding: 3px; + white-space: nowrap; + line-height: 230%; +} + +.normalday { + background-color: #EDEDED; + color: #000; + border: 1px solid #BCBCBC; +} + +.ui-datepicker-unselectable { + padding: .5em; + white-space: nowrap; +} + +.ui-state-disabled { + padding: .5em; + white-space: nowrap; +} + +.exception { + background-color: #b3d4ff; + color: #000; + border: 1px solid #BCBCBC; +} + +.past-date { + background-color: #e6e6e6; + color: #555; + border: 1px solid #BCBCBC; +} + +td.past-date a.ui-state-default { + background: #e6e6e6; + color: #555; +} + +.float { + background-color: #6f3; + color: #000; + border: 1px solid #BCBCBC; +} + +.holiday { + background-color: #ffaeae; + color: #000; + border: 1px solid #BCBCBC; +} + +.repeatableweekly { + background-color: #FF9; + color: #000; + border: 1px solid #BCBCBC; +} + +.repeatableyearly { + background-color: #FC6; + color: #000; + border: 1px solid #BCBCBC; +} + +td.exception a.ui-state-default { + background: #b3d4ff none; + color: #000; + border: 1px solid #BCBCBC; +} + +td.float a.ui-state-default { + background: #6f3 none; + color: #000; + border: 1px solid #BCBCBC; +} + +td.holiday a.ui-state-default { + background: #ffaeae none; + color: #000; + border: 1px solid #BCBCBC; +} + +td.repeatableweekly a.ui-state-default { + background: #FF9 none; + color: #000; + border: 1px solid #BCBCBC; +} + +td.repeatableyearly a.ui-state-default { + background: #FC6 none; + color: #000; + border: 1px solid #BCBCBC; +} + +.information { + background-color: #DCD2F1; + width: 300px; + display: none; + border: 1px solid #000; + color: #000; + font-size: 8pt; + font-weight: 700; + background-color: #FFD700; + cursor: pointer; + padding: 2px; +} + +.panel { + z-index: 1; + display: none; + float: left; + border: 3px solid #CCC; + padding: 3px; + margin-top: .3em; + margin-left: 15px; + background-color: #FEFEFE; +} + +fieldset.brief { + border: 0; + margin: 0; +} + +h1 select { + width: 20em; +} + +fieldset.brief ol { + font-size: 100%; +} + +fieldset.brief li, +fieldset.brief li.radio { + padding: .2em 0; +} + +.CopyDatePanel { + margin-left: 15px; +} + +.help { + margin: .3em 0; + border: 1px solid #EEE; + padding: .3em .7em; + font-size: 90%; +} + +.calendar td, +.calendar th, +.calendar .button, +.calendar tbody .day { + padding: .7em; + font-size: 110%; +} + +.calendar { + width: auto; + border: 0; +} + +.copyHoliday form li { + display: table-row; +} + +.copyHoliday form li b, +.copyHoliday form li input { + display: table-cell; + margin-bottom: 2px; +} \ No newline at end of file diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc index ba3f54a281..97e43fbbcd 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc @@ -107,7 +107,7 @@
Additional tools
    [% IF ( CAN_user_tools_edit_calendar ) %] -
  • Calendar
  • +
  • Calendar
  • [% END %] [% IF ( CAN_user_tools_manage_csv_profiles ) %]
  • CSV profiles
  • diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt new file mode 100644 index 0000000000..ca4482bde1 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt @@ -0,0 +1,708 @@ +[% USE raw %] +[% USE Asset %] +[% USE Branches %] +[% SET footerjs = 1 %] +[% INCLUDE 'doc-head-open.inc' %] +Koha › Tools › [% Branches.GetName( branch ) | html %] calendar +[% INCLUDE 'doc-head-close.inc' %] +[% Asset.css("css/discretecalendar.css") | $raw %] + + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + + +
    +
    +
    +
    + [% IF no_branch_selected %] +
    + No library set! +
    + [% END %] + + [% UNLESS datesInfos %] +
    + Error! You have to run add_days_discrete_calendar.pl in order to use Discrete Calendar. +
    + [% END %] + + [% IF date_format_error %] +
    + Error! Date format error. Please try again. +
    + [% END %] + + [% IF cannot_edit_past_dates %] +
    + Error! You cannot edit the past. The date '[% error_date | html %]' was out of range for the requested action. +
    + [% END %] + +

    [% Branches.GetName( branch ) | html %] calendar

    + +
    +
    + +
    + + Copy calendar to + + + +
    + +

    Calendar information

    + +
    + + +
    +
    +
    +

    Edit date details

    + +
      +
    1. + Library: + + +
    2. +
    3. + From date: + , + + [% IF ( dateformat == "us" ) %]// + [% ELSIF ( dateformat == "metric" ) %]// + [% ELSIF ( dateformat == "dmydot" ) %].. + [% ELSE %]//[% END %] + + + + + +
    4. +
    5. + To date: + +
    6. +
    7. + + +
    8. +
    9. + + +
    10. +
    11. + + +
    12. +
    13. + + [?] +
      Remove all repeated or weekly holidays of the selected date or week day
      if working day is selected.
      +
    14. +
    15. + +
    16. +
    17. + +
    18. +
    19. + + +
    20. +
    21. + + +
    22. +
    23. + + + + +
    24. +
    + + + + + + + + + +
    + + Cancel +
    +
    +
    +
    +
    + +
    +
    +

    Hints

    +
      +
    • Search in the calendar the day you want to set as holiday.
    • +
    • Click the date to add or edit a holiday.
    • +
    • Specify how the holiday should repeat.
    • +
    • Click Save to finish.
    • +
    • PS: +
        +
      • Past dates cannot be changed
      • +
      • Weekly holidays change open/close hours for all the days affected unless inputs are empty
      • +
      +
    • +
    +

    Key

    +

    + Working day + Unique holiday + Holiday repeating weekly + Holiday repeating yearly + Floating holiday + Need validation +

    +
    + +
    + [% IF ( NEED_VALIDATION_HOLIDAYS ) %] +

    Need validation holidays

    + + + + + + + + + [% FOREACH need_validation_holiday IN NEED_VALIDATION_HOLIDAYS %] + + + + + [% END %] + +
    DateTitle
    [% need_validation_holiday.outputdate | html %][% need_validation_holiday.note | html %]
    + [% END %] + + [% IF ( WEEKLY_HOLIDAYS ) %] +

    Weekly - Repeatable holidays

    + + + + + + + + + [% FOREACH WEEK_DAYS_LOO IN WEEKLY_HOLIDAYS %] + + + + + [% END %] + +
    Day of weekTitle
    [% WEEK_DAYS_LOO.weekday | html %][% WEEK_DAYS_LOO.note | html %]
    + [% END %] + + [% IF ( REPEATABLE_HOLIDAYS ) %] +

    Yearly - Repeatable holidays

    + + + + [% IF ( dateformat == "metric" ) %] + + [% ELSE %] + + [% END %] + + + + + [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN REPEATABLE_HOLIDAYS %] + + [% IF ( dateformat == "metric" ) %] + + [% ELSE %] + + [% END %] + + + [% END %] + +
    Day/monthMonth/dayTitle
    [% DAY_MONTH_HOLIDAYS_LOO.day | html %]/[% DAY_MONTH_HOLIDAYS_LOO.month | html %][% DAY_MONTH_HOLIDAYS_LOO.month | html %]/[% DAY_MONTH_HOLIDAYS_LOO.day | html %][% DAY_MONTH_HOLIDAYS_LOO.note | html %]
    + [% END %] + + [% IF ( UNIQUE_HOLIDAYS ) %] +

    Unique holidays

    + + + + + + + + + [% FOREACH HOLIDAYS_LOO IN UNIQUE_HOLIDAYS %] + + + + + [% END %] + +
    DateTitle
    [% HOLIDAYS_LOO.outputdate | html %][% HOLIDAYS_LOO.note | html %]
    + [% END %] + + [% IF ( FLOAT_HOLIDAYS ) %] +

    Floating holidays

    + + + + + + + + + [% FOREACH float_holiday IN FLOAT_HOLIDAYS %] + + + + + [% END %] + +
    DateTitle
    [% float_holiday.outputdate | html %][% float_holiday.note | html %]
    + [% END %] +
    +
    +
    + +
    +
    + +
    + +
    +
    + +[% MACRO jsinclude BLOCK %] + [% INCLUDE 'calendar.inc' %] + [% INCLUDE 'datatables.inc' %] + [% Asset.js("lib/jquery/plugins/jquery-ui-timepicker-addon.min.js") | $raw %] + [% Asset.js("js/tools-menu.js") | $raw %] + +[% END %] + + +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/holidays.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/holidays.tt deleted file mode 100644 index e4eec724aa..0000000000 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/holidays.tt +++ /dev/null @@ -1,590 +0,0 @@ -[% USE raw %] -[% USE Asset %] -[% USE Branches %] -[% SET footerjs = 1 %] -[% INCLUDE 'doc-head-open.inc' %] -Koha › Tools › [% Branches.GetName( branch ) | html %] calendar -[% INCLUDE 'doc-head-close.inc' %] - - - -[% INCLUDE 'header.inc' %] -[% INCLUDE 'cat-search.inc' %] - - - -
    -
    -
    -
    - -

    [% Branches.GetName( branch ) | html %] calendar

    - -
    -
    - - - - - - - - -
    -
    - -
    -

    Edit this holiday

    - -
      -
    1. - Library: - -
    2. -
    3. - From date: - , - - [% IF ( dateformat == "us" ) %]//[% ELSIF ( dateformat == "metric") %]//[% ELSIF ( dateformat == "dmydot") %]..[% ELSE %]//[% END %] - - - - - - -
    4. -
    5. - To date: - -
    6. -
    7. - -
    8. - - -
    9. -
    10. - - [?] -
      You can make an exception for this holiday rule. This means that you will be able to say that for a repeatable holiday there is one day which is going to be an exception.
      -
    11. -
    12. - - - [?] -
      You can make an exception on a range of dates repeated yearly.
      -
    13. -
    14. - [?] -
      This will delete this holiday rule. If it is a repeatable holiday, this option checks for possible exceptions. If an exception exists, this option will remove the exception and set the date to a regular holiday.
    15. -
    16. . - [?] -
      This will delete the single holidays rules only. The repeatable holidays and exceptions will not be deleted.
      -
    17. -
    18. . - [?] -
      This will delete the repeated holidays rules only. The repeatable holidays will be deleted but not the exceptions.
      -
    19. -
    20. . - [?] -
      This will delete the exceptions inside a given range. Be careful about your scope range; if it is oversized you could slow down Koha.
      -
    21. -
    22. - [?] -
      This will save changes to the holiday's title and description. If the information for a repeatable holiday is modified, it affects all of the dates on which the holiday is repeated.
    23. -
    24. - - . - [?] -
      If checked, changes for this holiday will be copied to all libraries. If the holiday doesn't exists for a library, no change is made.
      -
    25. -
    -
    - - Cancel -
    -
    -
    -
    - - -
    -
    -
    -

    Add new holiday

    -
      -
    1. - Library: - - -
    2. -
    3. - From date: - , - - [% IF ( dateformat == "us" ) %]//[% ELSIF ( dateformat == "metric" ) %]//[% ELSIF ( dateformat == "dmydot" ) %]..[% ELSE %]//[% END %] - - - - - - -
    4. -
    5. - To date: - -
    6. -
    7. -
    8. - -
    9. -
    10. - . - [?] -
      Make a single holiday. For example, selecting August 1, 2012 will make it a holiday, but will not affect August 1 in other years.
      -
    11. -
    12. - . - [?] -
      Make this weekday a holiday, every week. For example, if your library is closed on Saturdays, use this option to make every Saturday a holiday.
      -
    13. -
    14. - . - [?] -
      This will take this day and month as a reference to make it a holiday. Through this option, you can repeat this rule for every year. For example, selecting August 1 will make August 1 a holiday every year.
      -
    15. -
    16. - . - [?] -
      Make a single holiday on a range. For example, selecting August 1, 2012 and August 10, 2012 will make all days between August 1 and 10 a holiday, but will not affect August 1-10 in other years.
      -
    17. -
    18. - . - [?] -
      Make a single holiday on a range repeated yearly. For example, selecting August 1, 2012 and August 10, 2012 will make all days between August 1 and 10 a holiday, and will affect August 1-10 in other years.
      -
    19. -
    20. - - . - [?] -
      If checked, this holiday will be copied to all libraries. If the holiday already exists for a library, no change is made.
      -
    -
    - - Cancel -
    -
    -
    -
    - - - - - - - - -

    Calendar information

    -
    - -
    -
    - - - - -
    -
    - -
    -
    -
    -

    Hints

    -
      -
    • Search in the calendar the day you want to set as holiday.
    • -
    • Click the date to add or edit a holiday.
    • -
    • Enter a title and description for the holiday.
    • -
    • Specify how the holiday should repeat.
    • -
    • Click Save to finish.
    • -
    -

    Key

    -

    - Working day - Unique holiday - Holiday repeating weekly - Holiday repeating yearly - Holiday exception -

    -
    -
    - - -[% IF ( EXCEPTION_HOLIDAYS_LOOP ) %] -

    Exceptions

    - - - - - - - - - [% FOREACH EXCEPTION_HOLIDAYS_LOO IN EXCEPTION_HOLIDAYS_LOOP %] - - - - - - [% END %] - -
    DateTitleDescription
    [% EXCEPTION_HOLIDAYS_LOO.DATE | html %][% EXCEPTION_HOLIDAYS_LOO.TITLE | html %][% EXCEPTION_HOLIDAYS_LOO.DESCRIPTION | html %]
    -[% END %] - -[% IF ( WEEK_DAYS_LOOP ) %] -

    Weekly - Repeatable holidays

    - - - - - - - - - - [% FOREACH WEEK_DAYS_LOO IN WEEK_DAYS_LOOP %] - - - - - - - [% END %] - -
    Day of weekTitleDescription
    [% WEEK_DAYS_LOO.KEY | html %][% WEEK_DAYS_LOO.TITLE | html %][% WEEK_DAYS_LOO.DESCRIPTION | html %]
    -[% END %] - -[% IF ( DAY_MONTH_HOLIDAYS_LOOP ) %] -

    Yearly - Repeatable holidays

    - - - - [% IF ( dateformat == "metric" ) %] - - [% ELSE %] - - [% END %] - - - - - - [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN DAY_MONTH_HOLIDAYS_LOOP %] - - - - - - [% END %] - -
    Day/monthMonth/dayTitleDescription
    [% DAY_MONTH_HOLIDAYS_LOO.DATE | html %][% DAY_MONTH_HOLIDAYS_LOO.TITLE | html %][% DAY_MONTH_HOLIDAYS_LOO.DESCRIPTION | html %]
    -[% END %] - -[% IF ( HOLIDAYS_LOOP ) %] -

    Unique holidays

    - - - - - - - - - - [% FOREACH HOLIDAYS_LOO IN HOLIDAYS_LOOP %] - - - - - - [% END %] - -
    DateTitleDescription
    [% HOLIDAYS_LOO.DATE | html %][% HOLIDAYS_LOO.TITLE | html %][% HOLIDAYS_LOO.DESCRIPTION.replace('\\\r\\\n', '
    ') | html %]
    -[% END %] -
    -
    -
    - -
    -
    - -
    - -
    -
    - -[% MACRO jsinclude BLOCK %] - [% INCLUDE 'calendar.inc' %] - [% INCLUDE 'datatables.inc' %] - [% Asset.js("js/tools-menu.js") | $raw %] - -[% END %] - -[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt index ab77dc8592..0081dd97ff 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt @@ -102,7 +102,7 @@ [% END %]
    [% IF ( CAN_user_tools_edit_calendar ) %] -
    Calendar
    +
    Calendar
    Define days when the library is closed
    [% END %] diff --git a/misc/cronjobs/add_days_discrete_calendar.pl b/misc/cronjobs/add_days_discrete_calendar.pl new file mode 100755 index 0000000000..327c35c315 --- /dev/null +++ b/misc/cronjobs/add_days_discrete_calendar.pl @@ -0,0 +1,165 @@ +#!/usr/bin/perl + +# +# This script adds one day into discrete_calendar table based on the same day from the week before +# +use Modern::Perl; +use DateTime; +use DateTime::Format::Strptime; +use Data::Dumper; +use Getopt::Long; +use C4::Context; +use Koha::Database; +use Koha::DiscreteCalendar; +use Koha::DateUtils; + +# Options +my $help = 0; +my $daysInFuture = 1; +my $branch = undef; +my $debug = 0; +GetOptions ( + 'help|?|h' => \$help, + 'n=i' => \$daysInFuture, + 'b|branch=s' => \$branch, + 'd|debug' => \$debug +); + +my $usage = << 'ENDUSAGE'; + +This script adds days into discrete_calendar table based on the same day from the week before. + +Examples : + The latest date on discrete_calendar is : 28-07-2017 + The current date : 01-08-2016 + The dates that will be added are : 29-07-2017, 30-07-2017, 31-07-2017, 01-08-2017 +Open close exemples : + Date added is : 29-07-2017 + Opening/closing hours will be base on : 22-07-2017 (- 7 days) + Library open or closed will be based on : 29-07-2017 (- 1 year) +This script has the following parameters: + -h --help: this message + -n : number of days to add in the futre, default : 1 + -b --branch: branchcode of the library to which days will be added + -d --debug: displays all added days and errors if there is any + +ENDUSAGE + +if ($help) { + print $usage; + exit; +} + +my $schema = Koha::Database->new->schema; +$schema->storage->txn_begin; +my $dbh = C4::Context->dbh; + +# Predeclaring variables that will be used several times in the code +my $query; +my $statement; + +#getting the all the branches +my @branches = (); +if ( $branch ) { + push @branches, $branch; +} else { + $query = 'SELECT branchcode FROM discrete_calendar GROUP BY branchcode'; + $statement = $dbh->prepare($query); + $statement->execute(); + for my $branchcode ( @{$statement->fetchall_arrayref} ) { + push @branches, $branchcode->[0]; + } +} + +foreach my $branchCode (@branches) { + #get the latest date in the table + $query = "SELECT MAX(date) FROM discrete_calendar WHERE branchcode = ?"; + $statement = $dbh->prepare($query); + $statement->execute($branchCode); + my $latestDate = $statement->fetchrow_array; + + if ( $latestDate ) { + my $parser = DateTime::Format::Strptime->new( + pattern => '%Y-%m-%d %H:%M:%S', + on_error => 'croak', + ); + $latestDate = $parser->parse_datetime($latestDate); + } else { + $latestDate = dt_from_string(); + } + + my $newDay = $latestDate->clone(); + $latestDate->add(days => $daysInFuture); + + for ($newDay->add(days => 1); $newDay <= $latestDate; $newDay->add(days => 1)) { + my $lastWeekDay = $newDay->clone(); + $lastWeekDay->add(days=> -8); + my $dayOfWeek = $lastWeekDay->day_of_week; + # Representation fix + # DateTime object dow (1-7) where Monday is 1 + # Arrays are 0-based where 0 = Sunday, not 7. + $dayOfWeek -= 1 unless $dayOfWeek == 7; + $dayOfWeek = 0 if $dayOfWeek == 7; + + #checking if it was open on the same day from last year + my $yearAgo = $newDay->clone(); + $yearAgo = $yearAgo->add(years => -1); + my $last_year = 'SELECT is_opened, holiday_type, note FROM discrete_calendar WHERE date=? AND branchcode=?'; + my $day_last_week = "SELECT open_hour, close_hour, holiday_type, note FROM discrete_calendar WHERE DAYOFWEEK(date)=DAYOFWEEK(?) and date < ? order by date desc limit 1"; + my $add_Day = 'INSERT INTO discrete_calendar (date, branchcode, is_opened, open_hour, close_hour) VALUES (?, ?, ?, ?, ?)'; + + #insert into discrete_calendar + $statement = $dbh->prepare($last_year); + $statement->execute($yearAgo, $branchCode); + my ($is_opened, $holiday_type, $note) = $statement->fetchrow_array; + #weekly and unique holidays are not replicated in the future + if ( $holiday_type && $holiday_type ne "R" ) { + $is_opened = 1; + if ( $holiday_type eq "W" || $holiday_type eq "E" ) { + $holiday_type=''; + $note=''; + } elsif ( $holiday_type eq "F" ) { + $holiday_type = 'N'; + } + } + $holiday_type = '' if $is_opened; + $statement = $dbh->prepare($day_last_week); + $statement->execute($newDay, $newDay); + my ( $open_hour, $close_hour, $weekly_holiday_type, $weekly_note ) = $statement->fetchrow_array; + + # weekly repeatable holidays + if ( $weekly_holiday_type && $weekly_holiday_type eq 'W' ) { + $is_opened = 0; + $holiday_type = $weekly_holiday_type unless $holiday_type; + $note = $weekly_note unless $note; + } + + my $data = { + date => output_pref( { dt => $newDay, dateformat => 'iso', timeformat => '24hr' }), + branchcode => $branchCode, + }; + + $data->{is_opened} = $is_opened if ( defined $is_opened ); + $data->{holiday_type} = $holiday_type if ( defined $holiday_type ); + $data->{note} = $note if ( defined $note ); + $data->{open_hour} = $open_hour // "09:00:00"; + $data->{close_hour} = $close_hour // "17:00:00"; + + my $calendar_date = $schema->resultset( "DiscreteCalendar" )->create( $data )->get_from_storage(); + + if ( $debug && !$@ ) { + warn "Added day " . $calendar_date->date + . " to " . $calendar_date->branchcode + . " is opened: " . $calendar_date->is_opened + . ", holiday_type: " . $calendar_date->holiday_type + . ", note: " . $calendar_date->note + . ", open_hour: " . $calendar_date->open_hour + . ", close_hour: " . $calendar_date->close_hour + . " \n"; + } elsif ( $@ ) { + warn "Failed to add day $newDay to $branchCode : $_\n"; + } + } +} +# If everything went well we commit to the database +$schema->storage->txn_commit; \ No newline at end of file diff --git a/misc/cronjobs/fines.pl b/misc/cronjobs/fines.pl index afc6b4d556..f61adc75cb 100755 --- a/misc/cronjobs/fines.pl +++ b/misc/cronjobs/fines.pl @@ -38,7 +38,7 @@ use Carp; use File::Spec; use Try::Tiny; -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::DateUtils; use Koha::Patrons; use C4::Log; @@ -203,7 +203,7 @@ EOM sub set_holiday { my ( $branch, $dt ) = @_; - my $calendar = Koha::Calendar->new( branchcode => $branch ); + my $calendar = Koha::DiscreteCalendar->new( branchcode => $branch ); return $calendar->is_holiday($dt); } diff --git a/misc/cronjobs/holds/cancel_unfilled_holds.pl b/misc/cronjobs/holds/cancel_unfilled_holds.pl index ba6a40098a..a957a9a661 100755 --- a/misc/cronjobs/holds/cancel_unfilled_holds.pl +++ b/misc/cronjobs/holds/cancel_unfilled_holds.pl @@ -32,7 +32,7 @@ use Koha::Script -cron; use C4::Reserves; use C4::Log; use Koha::Holds; -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::DateUtils; use Koha::Libraries; diff --git a/misc/cronjobs/overdue_notices.pl b/misc/cronjobs/overdue_notices.pl index bf1a370a11..3dbe65e3a5 100755 --- a/misc/cronjobs/overdue_notices.pl +++ b/misc/cronjobs/overdue_notices.pl @@ -41,7 +41,7 @@ use C4::Overdues qw(GetFine GetOverdueMessageTransportTypes parse_overdues_lette use C4::Log; use Koha::Patron::Debarments qw(AddUniqueDebarment); use Koha::DateUtils; -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::Libraries; use Koha::Acquisition::Currencies; use Koha::Patrons; @@ -460,9 +460,8 @@ elsif ( defined $text_filename ) { } foreach my $branchcode (@branches) { - my $calendar; if ( C4::Context->preference('OverdueNoticeCalendar') ) { - $calendar = Koha::Calendar->new( branchcode => $branchcode ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode }); if ( $calendar->is_holiday($date_to_run) ) { next; } @@ -575,13 +574,11 @@ END_SQL my $days_between; if ( C4::Context->preference('OverdueNoticeCalendar') ) { - $days_between = - $calendar->days_between( dt_from_string($data->{date_due}), - $date_to_run ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode }); + $days_between = $calendar->days_between( dt_from_string($data->{date_due}), $date_to_run ); } else { - $days_between = - $date_to_run->delta_days( dt_from_string($data->{date_due}) ); + $days_between = $date_to_run->delta_days( dt_from_string($data->{date_due}) ); } $days_between = $days_between->in_units('days'); if ($triggered) { @@ -658,9 +655,8 @@ END_SQL my $exceededPrintNoticesMaxLines = 0; while ( my $item_info = $sth2->fetchrow_hashref() ) { if ( C4::Context->preference('OverdueNoticeCalendar') ) { - $days_between = - $calendar->days_between( - dt_from_string( $item_info->{date_due} ), $date_to_run ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode }); + $days_between = $calendar->days_between( dt_from_string( $item_info->{date_due} ), $date_to_run ); } else { $days_between = diff --git a/misc/cronjobs/staticfines.pl b/misc/cronjobs/staticfines.pl index 1e7d287702..cb0f031200 100755 --- a/misc/cronjobs/staticfines.pl +++ b/misc/cronjobs/staticfines.pl @@ -41,7 +41,7 @@ use Koha::Script -cron; use C4::Context; use C4::Circulation; use C4::Overdues; -use C4::Calendar qw(); # don't need any exports from Calendar +use Koha::DiscreteCalendar qw(); # don't need any exports from Calendar use C4::Biblio; use C4::Debug; # supplying $debug and $cgi_debug use C4::Log; @@ -177,7 +177,7 @@ for ( my $i = 0 ; $i < scalar(@$data) ; $i++ ) { my $calendar; unless ( defined( $calendars{$branchcode} ) ) { - $calendars{$branchcode} = C4::Calendar->new( branchcode => $branchcode ); + $calendars{$branchcode} = Koha::DiscreteCalendar->new({ branchcode => $branchcode }); } $calendar = $calendars{$branchcode}; my $isHoliday = $calendar->isHoliday( $tday, $tmonth, $tyear ); diff --git a/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl b/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl index ae6a4f4e00..37aa429bb4 100755 --- a/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl +++ b/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl @@ -36,7 +36,7 @@ use C4::Context; use C4::Items; use C4::Letters; use C4::Overdues; -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::DateUtils; use Koha::Patrons; use Koha::Libraries; @@ -341,7 +341,7 @@ sub GetWaitingHolds { } ); - my $calendar = Koha::Calendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode ); + my $calendar = Koha::DiscreteCalendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode ); my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' ); my $pickup_date = $waiting_date->clone->add( days => $pickupdelay ); diff --git a/tools/copy-holidays.pl b/tools/copy-holidays.pl deleted file mode 100755 index e11353a09d..0000000000 --- a/tools/copy-holidays.pl +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/perl - -# Copyright 2012 Catalyst IT -# -# 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 3 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, see . - -use Modern::Perl; - -use CGI qw ( -utf8 ); - -use C4::Auth; -use C4::Output; - - -use C4::Calendar; - -my $input = CGI->new; -my $dbh = C4::Context->dbh(); - -checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet'); - -my $branchcode = $input->param('branchcode'); -my $from_branchcode = $input->param('from_branchcode'); - -C4::Calendar->new(branchcode => $from_branchcode)->copy_to_branch($branchcode) if $from_branchcode && $branchcode; - -print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=".($branchcode || $from_branchcode)); diff --git a/tools/discrete_calendar.pl b/tools/discrete_calendar.pl new file mode 100755 index 0000000000..1adf19b72e --- /dev/null +++ b/tools/discrete_calendar.pl @@ -0,0 +1,175 @@ +#!/usr/bin/perl + +# 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 3 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, see . + +#####Sets holiday periods for each branch. Datedues will be extended if branch is closed -TG +use Modern::Perl; + +use CGI qw ( -utf8 ); + +use C4::Auth; +use C4::Output; + +use Koha::DateUtils; +use Koha::DiscreteCalendar; + +my $input = CGI->new; + +# Get the template to use +my ($template, $loggedinuser, $cookie) = get_template_and_user( + { + template_name => "tools/discrete_calendar.tt", + type => "intranet", + query => $input, + flagsrequired => {tools => 'edit_calendar'}, + debug => 1, + } +); + +my $branch = $input->param('branch') || C4::Context->userenv->{'branch'}; +my $calendar = Koha::DiscreteCalendar->new({branchcode => $branch}); +#alert the user that they are using the default calendar because they do not have a library set +my $no_branch_selected = $calendar->{no_branch_selected}; + +my $weekday = $input->param('day_of_week'); + +my $holiday_type = $input->param('holidayType'); +my $allbranches = $input->param('allBranches'); + +my $title = $input->param('Title'); + +my $action = $input->param('action') || ''; + +# calendardate - date passed in url for human readability (syspref) +# if the url has an invalid date default to 'now.' +my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate')); } || dt_from_string; +my $calendardate = output_pref( { dt => $calendarinput_dt, dateonly => 1 } ); + +if ($action eq 'copyBranch') { + $calendar->copy_to_branch(scalar $input->param('newBranch')); +} elsif($action eq 'copyDates') { + my $from_startDate = $input->param('from_date') ||''; + my $from_endDate = $input->param('to_date') || ''; + my $to_startDate = $input->param('copyto_from') || ''; + my $to_endDate = $input->param('copyto_to') || ''; + my $local_today = $input->param('local_today'); + my $daysnumber = $input->param('daysnumber'); + + $from_startDate = eval { dt_from_string(scalar $from_startDate) } if $from_startDate ne ''; + $from_endDate = eval { dt_from_string(scalar $from_endDate) } if $from_endDate ne ''; + $to_startDate = eval { dt_from_string(scalar $to_startDate) } if $to_startDate ne ''; + $to_endDate = eval { dt_from_string(scalar $to_endDate) } if $to_endDate ne ''; + $local_today = eval { dt_from_string( $local_today, 'iso') }; + + unless ($@) { + my $diff_from_today = $local_today - $to_startDate; + if ( $diff_from_today->is_positive ) { + $template->param( + cannot_edit_past_dates => 1, + error_date => $to_startDate + ); + } + else { + $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber); + } + } else { + $template->param( date_format_error => 1 ); + } +} elsif ($action eq 'edit') { + my $openHour = $input->param('openHour'); + my $closeHour = $input->param('closeHour'); + my $startDate = $input->param('from_date'); + my $endDate = $input->param('to_date'); + my $deleteType = $input->param('deleteType') || 0; + #Get today from javascript for a precise local time + my $local_today = $input->param('local_today'); + $local_today = eval { dt_from_string( $local_today, 'iso') }; + + $startDate = eval { dt_from_string(scalar $startDate) }; + + unless ($@) { + if($endDate ne '' ) { + $endDate = eval { dt_from_string(scalar $endDate) }; + } else { + $endDate = $startDate->clone(); + } + + $calendar->edit_holiday({ + title => $title, + weekday => $weekday, + holiday_type => $holiday_type, + open_hour => $openHour, + close_hour => $closeHour, + start_date => $startDate, + end_date => $endDate, + delete_type => $deleteType, + today => $local_today + }); + } else { + $template->param( date_format_error => 1 ); + } +} + +# keydate - date passed to calendar.js. calendar.js does not process dashes within a date. + +my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } ); +$keydate =~ s/-/\//g; + +# Set all the branches. +if ( C4::Context->only_my_library ) { + $branch = C4::Context->userenv->{'branch'}; +} + +# Get all the holidays + +#discrete_calendar weekly holidays +my @week_days = $calendar->get_week_days_holidays(); + +#discrete_calendar repeatable holidays +my @repeatable_holidays = $calendar->get_repeatable_holidays(); + +#discrete_calendar unique holidays +my @unique_holidays =$calendar->get_unique_holidays(); +#discrete_calendar floating holidays +my @float_holidays =$calendar->get_float_holidays(); +#discrete_caledar need validation holidays +my @need_validation_holidays =$calendar->get_need_validation_holidays(); + +#Calendar maximum date +my $minDate = $calendar->get_min_date(); + +#Calendar minimum date +my $maxDate = $calendar->get_max_date(); + +my @datesInfos = $calendar->get_dates_info(); + +$template->param( + UNIQUE_HOLIDAYS => \@unique_holidays, + FLOAT_HOLIDAYS => \@float_holidays, + NEED_VALIDATION_HOLIDAYS => \@need_validation_holidays, + REPEATABLE_HOLIDAYS => \@repeatable_holidays, + WEEKLY_HOLIDAYS => \@week_days, + calendardate => $calendardate, + keydate => $keydate, + branch => $branch, + minDate => $minDate, + maxDate => $maxDate, + datesInfos => \@datesInfos, + no_branch_selected => $no_branch_selected, +); + +# Shows the template with the real values replaced +output_html_with_http_headers $input, $cookie, $template->output; diff --git a/tools/exceptionHolidays.pl b/tools/exceptionHolidays.pl deleted file mode 100755 index 6894074459..0000000000 --- a/tools/exceptionHolidays.pl +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/perl - -use Modern::Perl; - -use CGI qw ( -utf8 ); - -use C4::Auth; -use C4::Output; -use DateTime; - -use C4::Calendar; -use Koha::DateUtils; - -my $input = CGI->new; -my $dbh = C4::Context->dbh(); - -checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet'); - - -our $branchcode = $input->param('showBranchName'); -my $originalbranchcode = $branchcode; -our $weekday = $input->param('showWeekday'); -our $day = $input->param('showDay'); -our $month = $input->param('showMonth'); -our $year = $input->param('showYear'); -our $title = $input->param('showTitle'); -our $description = $input->param('showDescription'); -our $holidaytype = $input->param('showHolidayType'); -my $datecancelrange_dt = eval { dt_from_string( scalar $input->param('datecancelrange') ) }; -my $calendardate = sprintf("%04d-%02d-%02d", $year, $month, $day); -our $showoperation = $input->param('showOperation'); -my $allbranches = $input->param('allBranches'); - -$title || ($title = ''); -if ($description) { - $description =~ s/\r/\\r/g; - $description =~ s/\n/\\n/g; -} else { - $description = ''; -} - -# We make an array with holiday's days -our @holiday_list; -if ($datecancelrange_dt){ - my $first_dt = DateTime->new(year => $year, month => $month, day => $day); - - for (my $dt = $first_dt->clone(); - $dt <= $datecancelrange_dt; - $dt->add(days => 1) ) - { - push @holiday_list, $dt->clone(); - } -} - -if($allbranches) { - my $libraries = Koha::Libraries->search; - while ( my $library = $libraries->next ) { - edit_holiday($showoperation, $library->branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list); - } -} else { - edit_holiday($showoperation, $branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list); -} - -print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$originalbranchcode&calendardate=$calendardate"); - -sub edit_holiday { - ($showoperation, $branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list) = @_; - my $calendar = C4::Calendar->new(branchcode => $branchcode); - - if ($showoperation eq 'exception') { - $calendar->insert_exception_holiday(day => $day, - month => $month, - year => $year, - title => $title, - description => $description); - } elsif ($showoperation eq 'exceptionrange' ) { - if (@holiday_list){ - foreach my $date (@holiday_list){ - $calendar->insert_exception_holiday( - day => $date->{local_c}->{day}, - month => $date->{local_c}->{month}, - year => $date->{local_c}->{year}, - title => $title, - description => $description - ); - } - } - } elsif ($showoperation eq 'edit') { - if($holidaytype eq 'weekday') { - $calendar->ModWeekdayholiday(weekday => $weekday, - title => $title, - description => $description); - } elsif ($holidaytype eq 'daymonth') { - $calendar->ModDaymonthholiday(day => $day, - month => $month, - title => $title, - description => $description); - } elsif ($holidaytype eq 'ymd') { - $calendar->ModSingleholiday(day => $day, - month => $month, - year => $year, - title => $title, - description => $description); - } elsif ($holidaytype eq 'exception') { - $calendar->ModExceptionholiday(day => $day, - month => $month, - year => $year, - title => $title, - description => $description); - } - } elsif ($showoperation eq 'delete') { - $calendar->delete_holiday(weekday => $weekday, - day => $day, - month => $month, - year => $year); - }elsif ($showoperation eq 'deleterange') { - if (@holiday_list){ - foreach my $date (@holiday_list){ - $calendar->delete_holiday_range(weekday => $weekday, - day => $date->{local_c}->{day}, - month => $date->{local_c}->{month}, - year => $date->{local_c}->{year}); - } - } - }elsif ($showoperation eq 'deleterangerepeat') { - if (@holiday_list){ - foreach my $date (@holiday_list){ - $calendar->delete_holiday_range_repeatable(weekday => $weekday, - day => $date->{local_c}->{day}, - month => $date->{local_c}->{month}); - } - } - }elsif ($showoperation eq 'deleterangerepeatexcept') { - if (@holiday_list){ - foreach my $date (@holiday_list){ - $calendar->delete_exception_holiday_range(weekday => $weekday, - day => $date->{local_c}->{day}, - month => $date->{local_c}->{month}, - year => $date->{local_c}->{year}); - } - } - } -} diff --git a/tools/holidays.pl b/tools/holidays.pl deleted file mode 100755 index 252882dbef..0000000000 --- a/tools/holidays.pl +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/perl - -# 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 3 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, see . - -#####Sets holiday periods for each branch. Datedues will be extended if branch is closed -TG -use Modern::Perl; - -use CGI qw ( -utf8 ); - -use C4::Auth; -use C4::Output; - -use C4::Calendar; -use Koha::DateUtils; - -my $input = CGI->new; - -my $dbh = C4::Context->dbh(); -# Get the template to use -my ($template, $loggedinuser, $cookie) - = get_template_and_user({template_name => "tools/holidays.tt", - type => "intranet", - query => $input, - flagsrequired => {tools => 'edit_calendar'}, - debug => 1, - }); - -# calendardate - date passed in url for human readability (syspref) -# if the url has an invalid date default to 'now.' -my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate') ); } || dt_from_string; -my $calendardate = output_pref( { dt => $calendarinput_dt, dateonly => 1 } ); - -# keydate - date passed to calendar.js. calendar.js does not process dashes within a date. -my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } ); -$keydate =~ s/-/\//g; - -my $branch= $input->param('branch') || C4::Context->userenv->{'branch'}; - -# Get all the holidays - -my $calendar = C4::Calendar->new(branchcode => $branch); -my $week_days_holidays = $calendar->get_week_days_holidays(); -my @week_days; -foreach my $weekday (keys %$week_days_holidays) { -# warn "WEEK DAY : $weekday"; - my %week_day; - %week_day = (KEY => $weekday, - TITLE => $week_days_holidays->{$weekday}{title}, - DESCRIPTION => $week_days_holidays->{$weekday}{description}); - push @week_days, \%week_day; -} - -my $day_month_holidays = $calendar->get_day_month_holidays(); -my @day_month_holidays; -foreach my $monthDay (keys %$day_month_holidays) { - # Determine date format on month and day. - my $day_monthdate; - my $day_monthdate_sort; - if (C4::Context->preference("dateformat") eq "metric") { - $day_monthdate_sort = "$day_month_holidays->{$monthDay}{month}-$day_month_holidays->{$monthDay}{day}"; - $day_monthdate = "$day_month_holidays->{$monthDay}{day}/$day_month_holidays->{$monthDay}{month}"; - } elsif (C4::Context->preference("dateformat") eq "dmydot") { - $day_monthdate_sort = "$day_month_holidays->{$monthDay}{month}.$day_month_holidays->{$monthDay}{day}"; - $day_monthdate = "$day_month_holidays->{$monthDay}{day}.$day_month_holidays->{$monthDay}{month}"; - }elsif (C4::Context->preference("dateformat") eq "us") { - $day_monthdate = "$day_month_holidays->{$monthDay}{month}/$day_month_holidays->{$monthDay}{day}"; - $day_monthdate_sort = $day_monthdate; - } else { - $day_monthdate = "$day_month_holidays->{$monthDay}{month}-$day_month_holidays->{$monthDay}{day}"; - $day_monthdate_sort = $day_monthdate; - } - my %day_month; - %day_month = (KEY => $monthDay, - DATE_SORT => $day_monthdate_sort, - DATE => $day_monthdate, - TITLE => $day_month_holidays->{$monthDay}{title}, - DESCRIPTION => $day_month_holidays->{$monthDay}{description}); - push @day_month_holidays, \%day_month; -} - -my $exception_holidays = $calendar->get_exception_holidays(); -my @exception_holidays; -foreach my $yearMonthDay (keys %$exception_holidays) { - my $exceptiondate = eval { dt_from_string( $exception_holidays->{$yearMonthDay}{date} ) }; - my %exception_holiday; - %exception_holiday = (KEY => $yearMonthDay, - DATE_SORT => $exception_holidays->{$yearMonthDay}{date}, - DATE => output_pref( { dt => $exceptiondate, dateonly => 1 } ), - TITLE => $exception_holidays->{$yearMonthDay}{title}, - DESCRIPTION => $exception_holidays->{$yearMonthDay}{description}); - push @exception_holidays, \%exception_holiday; -} - -my $single_holidays = $calendar->get_single_holidays(); -my @holidays; -foreach my $yearMonthDay (keys %$single_holidays) { - my $holidaydate_dt = eval { dt_from_string( $single_holidays->{$yearMonthDay}{date} ) }; - my %holiday; - %holiday = (KEY => $yearMonthDay, - DATE_SORT => $single_holidays->{$yearMonthDay}{date}, - DATE => output_pref( { dt => $holidaydate_dt, dateonly => 1 } ), - TITLE => $single_holidays->{$yearMonthDay}{title}, - DESCRIPTION => $single_holidays->{$yearMonthDay}{description}); - push @holidays, \%holiday; -} - -$template->param( - WEEK_DAYS_LOOP => \@week_days, - HOLIDAYS_LOOP => \@holidays, - EXCEPTION_HOLIDAYS_LOOP => \@exception_holidays, - DAY_MONTH_HOLIDAYS_LOOP => \@day_month_holidays, - calendardate => $calendardate, - keydate => $keydate, - branch => $branch, -); - -# Shows the template with the real values replaced -output_html_with_http_headers $input, $cookie, $template->output; diff --git a/tools/newHolidays.pl b/tools/newHolidays.pl deleted file mode 100755 index c402dfdf7f..0000000000 --- a/tools/newHolidays.pl +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/perl -#FIXME: perltidy this file - -# 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 3 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 Lic# along with Koha; if not, see . -# along with Koha; if not, see . - - -use Modern::Perl; - -use CGI qw ( -utf8 ); - -use C4::Auth; -use C4::Output; - -use C4::Calendar; -use DateTime; -use Koha::DateUtils; - -my $input = CGI->new; -my $dbh = C4::Context->dbh(); - -checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet'); - -our $branchcode = $input->param('newBranchName'); -my $originalbranchcode = $branchcode; -our $weekday = $input->param('newWeekday'); -our $day = $input->param('newDay'); -our $month = $input->param('newMonth'); -our $year = $input->param('newYear'); -my $dateofrange = $input->param('dateofrange'); -our $title = $input->param('newTitle'); -our $description = $input->param('newDescription'); -our $newoperation = $input->param('newOperation'); -my $allbranches = $input->param('allBranches'); - - -my $first_dt = DateTime->new(year => $year, month => $month, day => $day); -my $end_dt = eval { dt_from_string( $dateofrange ); }; - -my $calendardate = output_pref( { dt => $first_dt, dateonly => 1, dateformat => 'iso' } ); - -$title || ($title = ''); -if ($description) { - $description =~ s/\r/\\r/g; - $description =~ s/\n/\\n/g; -} else { - $description = ''; -} - -# We make an array with holiday's days -our @holiday_list; -if ($end_dt){ - for (my $dt = $first_dt->clone(); - $dt <= $end_dt; - $dt->add(days => 1) ) - { - push @holiday_list, $dt->clone(); - } -} - -if($allbranches) { - my $libraries = Koha::Libraries->search; - while ( my $library = $libraries->next ) { - add_holiday($newoperation, $library->branchcode, $weekday, $day, $month, $year, $title, $description); - } -} else { - add_holiday($newoperation, $branchcode, $weekday, $day, $month, $year, $title, $description); -} - -print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$originalbranchcode&calendardate=$calendardate"); - -#FIXME: move add_holiday() to a better place -sub add_holiday { - ($newoperation, $branchcode, $weekday, $day, $month, $year, $title, $description) = @_; - my $calendar = C4::Calendar->new(branchcode => $branchcode); - - if ($newoperation eq 'weekday') { - unless ( $weekday && ($weekday ne '') ) { - # was dow calculated by javascript? original code implies it was supposed to be. - # if not, we need it. - $weekday = &Date::Calc::Day_of_Week($year, $month, $day) % 7 unless($weekday); - } - unless($calendar->isHoliday($day, $month, $year)) { - $calendar->insert_week_day_holiday(weekday => $weekday, - title => $title, - description => $description); - } - } elsif ($newoperation eq 'repeatable') { - unless($calendar->isHoliday($day, $month, $year)) { - $calendar->insert_day_month_holiday(day => $day, - month => $month, - title => $title, - description => $description); - } - } elsif ($newoperation eq 'holiday') { - unless($calendar->isHoliday($day, $month, $year)) { - $calendar->insert_single_holiday(day => $day, - month => $month, - year => $year, - title => $title, - description => $description); - } - - } elsif ( $newoperation eq 'holidayrange' ) { - if (@holiday_list){ - foreach my $date (@holiday_list){ - unless ( $calendar->isHoliday( $date->{local_c}->{day}, $date->{local_c}->{month}, $date->{local_c}->{year} ) ) { - $calendar->insert_single_holiday( - day => $date->{local_c}->{day}, - month => $date->{local_c}->{month}, - year => $date->{local_c}->{year}, - title => $title, - description => $description - ); - } - } - } - } elsif ( $newoperation eq 'holidayrangerepeat' ) { - if (@holiday_list){ - foreach my $date (@holiday_list){ - unless ( $calendar->isHoliday( $date->{local_c}->{day}, $date->{local_c}->{month}, $date->{local_c}->{year} ) ) { - $calendar->insert_day_month_holiday( - day => $date->{local_c}->{day}, - month => $date->{local_c}->{month}, - title => $title, - description => $description - ); - } - } - } - } -} -- 2.17.1