From 03f4f61f0bcfc5f3ee0c5ba2fe8d3b3f3af2d2e9 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 Signed-off-by: Michal Denar Bug 17015: When using book drop mode, set check in time as closing hours Signed-off-by: Michal Denar Bug 17015: Automatically update calendar when creating or deleting a branch Signed-off-by: Michal Denar Bug 17015: Rebase and tests for discrete calendar This patch is a rebase of all the previous patches in order to show the discrete calendar. TEST PLAN: 1- Apply the patch 2- Run installer/data/mysql/updatedatabase.pl 3- Run misc/cronjobs/add_days_discrete_calendar.pl 4- Check that the max date is today + 1 day 5- Run t/db_dependent/DiscreteCalendar.t (All tests should pass) Signed-off-by: Michal Denar Bug 17015: DiscreteCalendar->new() method is now in a HASH This patch adds missing curly brackets in all new() calls. All tests in t/ should pass too (The one that don't pass are not passing before the patch) TEST PLAN: 1- Apply the patch 2- Run installer/data/mysql/updatedatabase.pl 3- Run misc/cronjobs/add_days_discrete_calendar.pl 4- Check that the max date is today + 1 day 5- Attempt a checkout ~ The error "Can't use string ("branchcode") as a HASH ref while "strict refs" in use at /kohadevbox/koha/Koha/DiscreteCalendar.pm line 79" is no longer here 5- Run t/db_dependent/DiscreteCalendar.t (prove -l t/db_dependent/DiscreteCalendar.t) 6- Run all koha test in t/ (prove t). All tests should pass 7- Use Discrete Calendar in Tools->Calendar USING THE DISCRETE CALENDAR: 1- New UI a) Calendar on the left b) Holidays list on the right 2- New color system for holiday types: a) Working day : white b) Unique holiday: red c) Holiday repeating weekly: yellow d) Holiday repeating yearly: orange e) Floating holiday: green f) Need validation: blue 3- When clicking on a date in the calendar, opening, closing hours and holiday type are displayed. 4- All the fields are editable 5- New radio button to Edit selected dates or Copy to different dates 6- Option to copy the calendar to another branch Bug 17015: Fixing QA tools failures This patch fixes tests that fails with the QA tool. To test, run the QA tool All tests should say OK except 2 that fails. Bug 17015: Check out with the useDaysMode preference When checking out with useDaysMode preference set to "Use the calendar to push the due date to the next open day", an error 500 appears. This patch fixes this error. To test: 1. Go in Administration->useDaysMode 2. Set this preference t0 "Use the calendar to push the due date to the next open day" 3. Make sure you have a patron and a item (note the patron's category and the item's type) 4. Make sure you have a circulation rule with the patron's category and the item type in step 3 5. Attempt a checkout 6. An error 500 appears 7. Apply the patch 8. The error is no longer there --- C4/Calendar.pm | 736 --------- C4/Circulation.pm | 15 +- C4/HoldsQueue.pm | 11 +- C4/Overdues.pm | 7 +- C4/Reserves.pm | 5 +- Koha/Calendar.pm | 555 ------- Koha/Charges/Fees.pm | 4 +- Koha/Checkouts.pm | 4 +- Koha/CurbsidePickup.pm | 4 +- Koha/DiscreteCalendar.pm | 1372 +++++++++++++++++ Koha/Hold.pm | 6 +- admin/branches.pl | 3 + 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 | 653 -------- .../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 | 3 +- misc/cronjobs/holds/holds_reminder.pl | 4 +- misc/cronjobs/overdue_notices.pl | 18 +- misc/cronjobs/staticfines.pl | 8 +- .../thirdparty/TalkingTech_itiva_outbound.pl | 4 +- .../benchmark_checkins.pl | 0 tools/copy-holidays.pl | 40 - tools/discrete_calendar.pl | 175 +++ tools/exceptionHolidays.pl | 143 -- tools/holidays.pl | 131 -- tools/newHolidays.pl | 144 -- 31 files changed, 2695 insertions(+), 2447 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 mode change 100644 => 100755 tmp/DiscreteCalendar_performance/benchmark_checkins.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 257119c8e3..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 qw( croak ); -use Date::Calc qw( 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 7b028bed0f..81ad39bf3f 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -43,7 +43,7 @@ use Koha::AuthorisedValues; use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue; use Koha::Biblioitems; use Koha::DateUtils qw( dt_from_string ); -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::Checkouts; use Koha::Illrequests; use Koha::Items; @@ -1406,7 +1406,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, $patron->unblessed ); @@ -2631,10 +2631,10 @@ 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' - ); + }); $new_debar_dt = $calendar->addDuration( $return_date, $suspension_days ); } else { @@ -3796,7 +3796,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); @@ -3835,14 +3835,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_day( $datedue )->set(hour => 23, minute => 59); } } } - return $datedue; } diff --git a/C4/HoldsQueue.pm b/C4/HoldsQueue.pm index f2eb1ee4b8..40fc186798 100644 --- a/C4/HoldsQueue.pm +++ b/C4/HoldsQueue.pm @@ -31,6 +31,8 @@ use Koha::Libraries; use List::Util qw( shuffle ); use List::MoreUtils qw( any ); +use Koha::DiscreteCalendar; +use Data::Dumper; our (@ISA, @EXPORT_OK); BEGIN { @@ -76,6 +78,13 @@ sub TransportCostMatrix { cost => $cost, disable_transfer => $disabled }; + + if ( !my $ignore_holds_queue_skip_closed && C4::Context->preference("HoldsQueueSkipClosed") ) { + my $calendars->{$from} ||= Koha::DiscreteCalendar->new({ branchcode => $from }); + $transport_cost_matrix{$to}{$from}{disable_transfer} ||= + $calendars->{$from}->is_holiday( $today ); + } + } return \%transport_cost_matrix; @@ -796,7 +805,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 56a0010f19..ed8d8b1cd8 100644 --- a/C4/Overdues.pm +++ b/C4/Overdues.pm @@ -29,6 +29,9 @@ use Carp qw( carp ); use C4::Accounts; use C4::Context; +use C4::Log; # logaction +use Koha::DateUtils; +use Koha::DiscreteCalendar; use Koha::Account::Lines; use Koha::Account::Offsets; use Koha::DateUtils qw( output_pref ); @@ -320,7 +323,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 ); @@ -332,7 +335,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 a03072d8df..65db883190 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -35,10 +35,11 @@ use C4::Members; use Koha::Account::Lines; use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue; use Koha::Biblios; -use Koha::Calendar; use Koha::CirculationRules; use Koha::Database; use Koha::DateUtils qw( dt_from_string output_pref ); +use Koha::DiscreteCalendar; +use Koha::Hold; use Koha::Holds; use Koha::ItemTypes; use Koha::Items; @@ -1013,7 +1014,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 5ffebb47ab..0000000000 --- a/Koha/Calendar.pm +++ /dev/null @@ -1,555 +0,0 @@ -package Koha::Calendar; - -use Modern::Perl; - -use Carp qw( croak ); -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 a9bbf0fe60..ed17b200fd 100644 --- a/Koha/Charges/Fees.pm +++ b/Koha/Charges/Fees.pm @@ -21,7 +21,7 @@ use Modern::Perl; use Carp; -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/Checkouts.pm b/Koha/Checkouts.pm index 76a3b9e686..69263e7d95 100644 --- a/Koha/Checkouts.pm +++ b/Koha/Checkouts.pm @@ -55,9 +55,11 @@ sub calculate_dropbox_date { branchcode => $branchcode, } ); - my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => $daysmode }); my $today = dt_from_string; my $dropbox_date = $calendar->addDuration( $today, -1 ); + my $dateInfo = $calendar->get_date_info($dropbox_date); + $dropbox_date = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso', C4::Context->tz()); return $dropbox_date; } diff --git a/Koha/CurbsidePickup.pm b/Koha/CurbsidePickup.pm index cbbd1632d3..8896602f5e 100644 --- a/Koha/CurbsidePickup.pm +++ b/Koha/CurbsidePickup.pm @@ -26,7 +26,7 @@ use base qw(Koha::Object); use C4::Circulation qw( CanBookBeIssued AddIssue ); use C4::Members::Messaging qw( GetMessagingPreferences ); use C4::Letters qw( GetPreparedLetter EnqueueLetter ); -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::DateUtils qw( dt_from_string ); use Koha::Patron; use Koha::Library; @@ -56,7 +56,7 @@ sub new { Koha::Exceptions::CurbsidePickup::NotEnabled->throw unless $policy && $policy->enabled; - my $calendar = Koha::Calendar->new( branchcode => $params->{branchcode} ); + my $calendar = Koha::DiscreteCalendar->new( branchcode => $params->{branchcode} ); Koha::Exceptions::CurbsidePickup::LibraryIsClosed->throw if $calendar->is_holiday( $params->{scheduled_pickup_datetime} ); diff --git a/Koha/DiscreteCalendar.pm b/Koha/DiscreteCalendar.pm new file mode 100644 index 0000000000..9ee432e6ad --- /dev/null +++ b/Koha/DiscreteCalendar.pm @@ -0,0 +1,1372 @@ +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 qw ( dt_from_string output_pref ); + +# 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 + }); + + unless ($branch_rs->count) { + $copyBranch = Koha::Libraries->next->branchcode; + $branch_rs = $schema->resultset('DiscreteCalendar')->search({ + branchcode => $copyBranch + }); + } + + $schema->{AutoCommit} = 0; + $schema->storage->txn_begin; + + 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(), + }); + } + + eval { $schema->storage->txn_commit; }; + + if ($@) { + $schema->storage->rollback; + } + + $schema->{AutoCommit} = 1; +} + +=head2 delete_branch + + Koha::DiscreteCalendar->delete_branch($branchcode) + +This method will delete every discrete_calendar entry for a given branch +C<$branchcode> is the code of the branch we want to remove from the table + +=cut + +sub delete_branch { + my ( $classname, $branchcode ) = @_; + + my $schema = Koha::Database->new->schema; + + my $branch_rs = $schema->resultset('DiscreteCalendar')->search({ + branchcode => $branchcode + }); + + if ($branch_rs->count) { + $branch_rs->delete; + } +} + + +=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 database 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 database 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 => \[ 'distinct DAYOFWEEK(date), note'], + as => [qw/ weekday note /], + 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 ca261086c7..345e642cea 100644 --- a/Koha/Hold.pm +++ b/Koha/Hold.pm @@ -35,8 +35,8 @@ use Koha::Hold::CancellationRequests; use Koha::Items; use Koha::Libraries; use Koha::Old::Holds; -use Koha::Calendar; use Koha::Plugins; +use Koha::DiscreteCalendar; use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue; @@ -71,7 +71,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 { @@ -234,7 +234,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 }); $new_expiration_date = $calendar->days_forward( dt_from_string(), $max_pickup_delay ); } diff --git a/admin/branches.pl b/admin/branches.pl index 134321a2bd..201f6efe0e 100755 --- a/admin/branches.pl +++ b/admin/branches.pl @@ -153,6 +153,8 @@ if ( $op eq 'add_form' ) { } } + Koha::DiscreteCalendar->add_new_branch(undef, $branchcode); + push @messages, { type => 'message', code => 'success_on_insert' }; } ); @@ -198,6 +200,7 @@ if ( $op eq 'add_form' ) { if ( $@ or not $deleted ) { push @messages, { type => 'alert', code => 'error_on_delete' }; } else { + Koha::DiscreteCalendar->delete_branch($branchcode); push @messages, { type => 'message', code => 'success_on_delete' }; } $op = 'list'; diff --git a/circ/returns.pl b/circ/returns.pl index 4ba8812888..f58ca2b217 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -45,9 +45,9 @@ use C4::Reserves qw( ModReserve ModReserveAffect GetOtherReserves ); use C4::RotatingCollections; use Koha::AuthorisedValues; use Koha::BiblioFrameworks; -use Koha::Calendar; use Koha::Checkouts; use Koha::DateUtils qw( dt_from_string ); +use Koha::DiscreteCalendar; use Koha::Holds; use Koha::Items; use Koha::Item::Transfers; @@ -212,6 +212,7 @@ my $dotransfer = $query->param('dotransfer'); my $canceltransfer = $query->param('canceltransfer'); my $transit = $query->param('transit'); 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 0cc705c6f4..cbe15f4b54 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc @@ -99,7 +99,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..4169af3d69 --- /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' %] +[% Branches.GetName( branch ) | html %] calendar; Tools › Koha &rsaquo +[% 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 86d581fb95..0000000000 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/holidays.tt +++ /dev/null @@ -1,653 +0,0 @@ -[% USE raw %] -[% USE Asset %] -[% USE KohaDates %] -[% USE Branches %] -[% SET footerjs = 1 %] -[% INCLUDE 'doc-head-open.inc' %] -[% Branches.GetName( branch ) | html %] calendar › Tools › Koha -[% INCLUDE 'doc-head-close.inc' %] -[% Asset.css("css/calendar.css") | $raw %] - - -[% 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. -
    11. -
      - - [?] -
      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.
      -
      -
    12. -
    13. -
      - - - [?] -
      You can make an exception on a range of dates repeated yearly.
      -
      -
    14. -
    15. - - - [?] -
      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.
      -
    16. -
    17. - . - [?] -
      This will delete the single holidays rules only. The repeatable holidays and exceptions will not be deleted.
      -
    18. -
    19. - . - [?] -
      This will delete the repeated holidays rules only. The repeatable holidays will be deleted but not the exceptions.
      -
    20. -
    21. - . - [?] -
      This will delete the exceptions inside a given range. Be careful about your scope range; if it is oversized you could slow down Koha.
      -
    22. -
    23. - - [?] -
      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.
    24. -
    25. - - . - [?] -
      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.
      -
    26. -
    -
    - - 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. -
    11. - - . - [?] -
      Make a single holiday. For example, selecting August 1, 2012 will make it a holiday, but will not affect August 1 in other years.
      -
    12. -
    13. - - . - [?] -
      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.
      -
    14. -
    15. - - . - [?] -
      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.
      -
    16. -
    17. - - . - [?] -
      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.
      -
    18. -
    19. - - . - [?] -
      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.
      -
    20. -
    21. - - . - [?] -
      If checked, this holiday will be copied to all libraries. If the holiday already exists for a library, no change is made.
      -
    22. -
    -
    - - 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 ( EXCEPTION_HOLIDAYS_LOOP ) %] - - [% 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 ( WEEK_DAYS_LOOP ) %] - - [% 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 ( DAY_MONTH_HOLIDAYS_LOOP ) %] - - [% 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 # /IF ( HOLIDAYS_LOOP ) %] -
    -
    -
    -
    -
    - -
    - -
    -
    - -[% 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 2ebca53c9e..b928ff99af 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 @@ -101,7 +101,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..1c146ce44d --- /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 qw ( output_pref ); + +# 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; diff --git a/misc/cronjobs/fines.pl b/misc/cronjobs/fines.pl index e919d42a66..f0dc4eddff 100755 --- a/misc/cronjobs/fines.pl +++ b/misc/cronjobs/fines.pl @@ -38,8 +38,8 @@ use Carp qw( carp croak ); use File::Spec; use Try::Tiny qw( catch try ); -use Koha::Calendar; use Koha::DateUtils qw( dt_from_string output_pref ); +use Koha::DiscreteCalendar; use Koha::Patrons; use C4::Log qw( cronlogaction ); @@ -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 ea5d70ab48..09e7879376 100755 --- a/misc/cronjobs/holds/cancel_unfilled_holds.pl +++ b/misc/cronjobs/holds/cancel_unfilled_holds.pl @@ -25,7 +25,8 @@ use Koha::Script -cron; use C4::Reserves; use C4::Log qw( cronlogaction ); use Koha::Holds; -use Koha::Calendar; +use Koha::DiscreteCalendar; +use Koha::DateUtils; use Koha::Libraries; cronlogaction(); diff --git a/misc/cronjobs/holds/holds_reminder.pl b/misc/cronjobs/holds/holds_reminder.pl index d1f49287ca..78cb515fe2 100755 --- a/misc/cronjobs/holds/holds_reminder.pl +++ b/misc/cronjobs/holds/holds_reminder.pl @@ -26,7 +26,7 @@ use C4::Context; use C4::Letters; use C4::Log qw( cronlogaction ); use Koha::DateUtils qw( dt_from_string ); -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::Libraries; use Koha::Notice::Templates; use Koha::Patrons; @@ -246,7 +246,7 @@ foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP # If respecting calendar get the correct waiting since date my $waiting_date; if( $use_calendar ){ - my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => 'Calendar' ); + my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => 'Calendar' }); my $duration = DateTime::Duration->new( days => -$days ); $waiting_date = $calendar->addDays($date_to_run,$duration); #Add negative of days } else { diff --git a/misc/cronjobs/overdue_notices.pl b/misc/cronjobs/overdue_notices.pl index 9c13fd279a..3680200d29 100755 --- a/misc/cronjobs/overdue_notices.pl +++ b/misc/cronjobs/overdue_notices.pl @@ -33,7 +33,7 @@ use C4::Overdues qw( GetOverdueMessageTransportTypes parse_overdues_letter ); use C4::Log qw( cronlogaction ); use Koha::Patron::Debarments qw( AddUniqueDebarment ); use Koha::DateUtils qw( dt_from_string output_pref ); -use Koha::Calendar; +use Koha::DiscreteCalendar; use Koha::Libraries; use Koha::Acquisition::Currencies; use Koha::Patrons; @@ -454,9 +454,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; } @@ -568,13 +567,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 36d5fb20b2..0e50b5a22a 100755 --- a/misc/cronjobs/staticfines.pl +++ b/misc/cronjobs/staticfines.pl @@ -32,11 +32,13 @@ use Date::Calc qw( Date_to_Days ); use Koha::Script -cron; use C4::Context; use C4::Overdues qw( CalcFine checkoverdues GetFine Getoverdues ); -use C4::Calendar qw(); # don't need any exports from Calendar use C4::Log qw( cronlogaction ); use Getopt::Long qw( GetOptions ); use List::MoreUtils qw( none ); -use Koha::DateUtils qw( dt_from_string ); +use Koha::DateUtils qw( dt_from_string output_pref ); +use C4::Circulation; +use Koha::DiscreteCalendar qw(); # don't need any exports from Calendar +use C4::Biblio; use Koha::Patrons; my $help = 0; @@ -166,7 +168,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 6a1456987f..3fe64bb29c 100755 --- a/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl +++ b/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl @@ -27,8 +27,8 @@ use Koha::Script -cron; use C4::Context; use C4::Letters; use C4::Overdues; -use Koha::Calendar; use Koha::DateUtils qw( dt_from_string output_pref ); +use Koha::DiscreteCalendar; use Koha::Patrons; use Koha::Libraries; @@ -335,7 +335,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/tmp/DiscreteCalendar_performance/benchmark_checkins.pl b/tmp/DiscreteCalendar_performance/benchmark_checkins.pl old mode 100644 new mode 100755 diff --git a/tools/copy-holidays.pl b/tools/copy-holidays.pl deleted file mode 100755 index 381f9e70a5..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 qw( checkauth ); -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..f5c740e919 --- /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 qw ( get_template_and_user ); +use C4::Output qw ( output_html_with_http_headers ); + +use Koha::DateUtils qw ( dt_from_string output_pref ); +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 a96b1d0ebb..0000000000 --- a/tools/exceptionHolidays.pl +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/perl - -use Modern::Perl; - -use CGI qw ( -utf8 ); - -use C4::Auth qw( checkauth ); -use C4::Output; -use DateTime; - -use C4::Calendar; -use Koha::DateUtils qw( dt_from_string ); - -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 49bd19915f..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 qw( get_template_and_user ); -use C4::Output qw( output_html_with_http_headers ); - -use C4::Calendar; -use Koha::DateUtils qw( dt_from_string output_pref ); - -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'}, - }); - -# calendardate - date passed in url for human readability (syspref) -# if the url has an invalid date default to 'now.' -# FIXME There is something to improve in the date handling here -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 b07079d52b..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 qw( checkauth ); -use C4::Output; - -use C4::Calendar; -use DateTime; -use Koha::DateUtils qw( dt_from_string output_pref ); - -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.25.1